From 1b3cf4aac9184f76803088dd1cbb6273b83b23c6 Mon Sep 17 00:00:00 2001 From: Dylan Burati Date: Thu, 22 Jun 2023 20:21:34 -0400 Subject: [PATCH 1/9] Replace map_from_arrays in NifTaggedEnum encoder gen --- rustler_codegen/src/tagged_enum.rs | 12 +++++---- rustler_tests/lib/rustler_test.ex | 1 + rustler_tests/native/rustler_test/src/lib.rs | 1 + .../native/rustler_test/src/test_codegen.rs | 12 +++++++++ rustler_tests/test/codegen_test.exs | 25 +++++++++++++++++++ 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/rustler_codegen/src/tagged_enum.rs b/rustler_codegen/src/tagged_enum.rs index ecdadd45..33d106bd 100644 --- a/rustler_codegen/src/tagged_enum.rs +++ b/rustler_codegen/src/tagged_enum.rs @@ -312,7 +312,7 @@ fn gen_named_encoder( } }) .collect::>(); - let (keys, values): (Vec<_>, Vec<_>) = fields + let field_defs: Vec<_> = fields .named .iter() .map(|field| { @@ -321,15 +321,17 @@ fn gen_named_encoder( .as_ref() .expect("Named fields must have an ident."); let atom_fun = Context::field_to_atom_fun(field); - (atom_fun, field_ident) + quote_spanned! { field.span() => + map = map.map_put(#atom_fun(), &#field_ident).unwrap(); + } }) - .unzip(); + .collect(); quote! { #enum_name :: #variant_ident{ #(#field_decls)* } => { - let map = ::rustler::Term::map_from_arrays(env, &[#(#keys()),*], &[#(#values),*]) - .expect("Failed to create map"); + let mut map = ::rustler::types::map::map_new(env); + #(#field_defs)* ::rustler::types::tuple::make_tuple(env, &[::rustler::Encoder::encode(&#atom_fn(), env), map]) } } diff --git a/rustler_tests/lib/rustler_test.ex b/rustler_tests/lib/rustler_test.ex index 0d8bb277..d75ebaa6 100644 --- a/rustler_tests/lib/rustler_test.ex +++ b/rustler_tests/lib/rustler_test.ex @@ -95,6 +95,7 @@ defmodule RustlerTest do def tagged_enum_1_echo(_), do: err() def tagged_enum_2_echo(_), do: err() def tagged_enum_3_echo(_), do: err() + def tagged_enum_4_echo(_), do: err() def untagged_enum_echo(_), do: err() def untagged_enum_with_truthy(_), do: err() def untagged_enum_for_issue_370(_), do: err() diff --git a/rustler_tests/native/rustler_test/src/lib.rs b/rustler_tests/native/rustler_test/src/lib.rs index 03a744d3..bd22ac1f 100644 --- a/rustler_tests/native/rustler_test/src/lib.rs +++ b/rustler_tests/native/rustler_test/src/lib.rs @@ -70,6 +70,7 @@ rustler::init!( test_codegen::tagged_enum_1_echo, test_codegen::tagged_enum_2_echo, test_codegen::tagged_enum_3_echo, + test_codegen::tagged_enum_4_echo, test_codegen::untagged_enum_echo, test_codegen::untagged_enum_with_truthy, test_codegen::untagged_enum_for_issue_370, diff --git a/rustler_tests/native/rustler_test/src/test_codegen.rs b/rustler_tests/native/rustler_test/src/test_codegen.rs index 00f97c9a..393ab4ed 100644 --- a/rustler_tests/native/rustler_test/src/test_codegen.rs +++ b/rustler_tests/native/rustler_test/src/test_codegen.rs @@ -125,6 +125,18 @@ pub fn tagged_enum_3_echo(tagged_enum: TaggedEnum3) -> TaggedEnum3 { tagged_enum } +#[derive(NifTaggedEnum)] +pub enum TaggedEnum4 { + Unit, + Unnamed(u64, bool), + Named { size: u64, filename: String }, +} + +#[rustler::nif] +pub fn tagged_enum_4_echo(tagged_enum: TaggedEnum4) -> TaggedEnum4 { + tagged_enum +} + #[derive(NifUntaggedEnum)] pub enum UntaggedEnum { Foo(u32), diff --git a/rustler_tests/test/codegen_test.exs b/rustler_tests/test/codegen_test.exs index b3d75f2e..7b95d468 100644 --- a/rustler_tests/test/codegen_test.exs +++ b/rustler_tests/test/codegen_test.exs @@ -279,6 +279,31 @@ defmodule RustlerTest.CodegenTest do end) end + test "tagged enum transcoder 4" do + assert :unit == RustlerTest.tagged_enum_4_echo(:unit) + + assert {:unnamed, 10_000_000_000, true} == + RustlerTest.tagged_enum_4_echo({:unnamed, 10_000_000_000, true}) + + assert {:named, %{filename: "\u2200", size: 123}} == + RustlerTest.tagged_enum_4_echo({:named, %{filename: "\u2200", size: 123}}) + + assert %ErlangError{original: :invalid_variant} == + assert_raise(ErlangError, fn -> + RustlerTest.tagged_enum_4_echo({}) + end) + + assert %ErlangError{original: :invalid_variant} == + assert_raise(ErlangError, fn -> + RustlerTest.tagged_enum_4_echo(%{}) + end) + + assert %ErlangError{original: :invalid_variant} == + assert_raise(ErlangError, fn -> + RustlerTest.tagged_enum_4_echo({nil}) + end) + end + test "untagged enum transcoder" do assert 123 == RustlerTest.untagged_enum_echo(123) assert "Hello" == RustlerTest.untagged_enum_echo("Hello") From 53ffc6dca6cf3c0951d3aa701bafb4feb377a0e2 Mon Sep 17 00:00:00 2001 From: Dylan Burati Date: Fri, 23 Jun 2023 01:54:10 -0400 Subject: [PATCH 2/9] update testcases, rerun ci --- rustler_tests/test/codegen_test.exs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rustler_tests/test/codegen_test.exs b/rustler_tests/test/codegen_test.exs index 7b95d468..fea953e9 100644 --- a/rustler_tests/test/codegen_test.exs +++ b/rustler_tests/test/codegen_test.exs @@ -290,17 +290,22 @@ defmodule RustlerTest.CodegenTest do assert %ErlangError{original: :invalid_variant} == assert_raise(ErlangError, fn -> - RustlerTest.tagged_enum_4_echo({}) + RustlerTest.tagged_enum_4_echo(:unnamed) end) assert %ErlangError{original: :invalid_variant} == assert_raise(ErlangError, fn -> - RustlerTest.tagged_enum_4_echo(%{}) + RustlerTest.tagged_enum_4_echo({:unit, 2, false}) end) assert %ErlangError{original: :invalid_variant} == assert_raise(ErlangError, fn -> - RustlerTest.tagged_enum_4_echo({nil}) + RustlerTest.tagged_enum_4_echo({:named, "@", 45}) + end) + + assert %ErlangError{original: :invalid_variant} == + assert_raise(ErlangError, fn -> + RustlerTest.tagged_enum_4_echo(nil) end) end From 844ae7a8b66c0c981ad99cf4e7f3966f464d63dc Mon Sep 17 00:00:00 2001 From: Dylan Burati Date: Fri, 23 Jun 2023 13:02:02 -0400 Subject: [PATCH 3/9] Revert codegen change --- rustler_codegen/src/tagged_enum.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/rustler_codegen/src/tagged_enum.rs b/rustler_codegen/src/tagged_enum.rs index 33d106bd..ecdadd45 100644 --- a/rustler_codegen/src/tagged_enum.rs +++ b/rustler_codegen/src/tagged_enum.rs @@ -312,7 +312,7 @@ fn gen_named_encoder( } }) .collect::>(); - let field_defs: Vec<_> = fields + let (keys, values): (Vec<_>, Vec<_>) = fields .named .iter() .map(|field| { @@ -321,17 +321,15 @@ fn gen_named_encoder( .as_ref() .expect("Named fields must have an ident."); let atom_fun = Context::field_to_atom_fun(field); - quote_spanned! { field.span() => - map = map.map_put(#atom_fun(), &#field_ident).unwrap(); - } + (atom_fun, field_ident) }) - .collect(); + .unzip(); quote! { #enum_name :: #variant_ident{ #(#field_decls)* } => { - let mut map = ::rustler::types::map::map_new(env); - #(#field_defs)* + let map = ::rustler::Term::map_from_arrays(env, &[#(#keys()),*], &[#(#values),*]) + .expect("Failed to create map"); ::rustler::types::tuple::make_tuple(env, &[::rustler::Encoder::encode(&#atom_fn(), env), map]) } } From 26c172dccc36049c4ca5d404dcde22a4da042df1 Mon Sep 17 00:00:00 2001 From: Dylan Burati Date: Fri, 23 Jun 2023 15:30:47 -0400 Subject: [PATCH 4/9] Create Term::map_from_iterables --- rustler/src/types/map.rs | 70 ++++++++++++++++++- rustler_codegen/src/tagged_enum.rs | 44 +++++++++--- .../native/rustler_test/src/test_codegen.rs | 11 +++ rustler_tests/test/codegen_test.exs | 5 ++ 4 files changed, 120 insertions(+), 10 deletions(-) diff --git a/rustler/src/types/map.rs b/rustler/src/types/map.rs index e3ea5764..7d9f9fcd 100644 --- a/rustler/src/types/map.rs +++ b/rustler/src/types/map.rs @@ -27,7 +27,7 @@ impl<'a> Term<'a> { /// ```elixir /// keys = ["foo", "bar"] /// values = [1, 2] - /// List.zip(keys, values) |> Map.new() + /// Enum.zip(keys, values) |> Map.new() /// ``` pub fn map_from_arrays( env: Env<'a>, @@ -47,6 +47,36 @@ impl<'a> Term<'a> { } } + /// Construct a new map from two iterables + /// + /// It is identical to map_from_arrays, but accepts tuples + /// instead of arrays to allow differing types within the + /// keys and values. + /// + /// ### Elixir equivalent + /// ```elixir + /// keys = ["foo", "bar"] + /// values = [1, true] + /// Enum.zip(values) |> Map.new() + /// ``` + pub fn map_from_iterables( + env: Env<'a>, + keys: impl EncodeIterator<'a>, + values: impl EncodeIterator<'a>, + ) -> NifResult> { + let keys: Vec<_> = keys.iter_terms(env).map(|t| t.as_c_arg()).collect(); + let values: Vec<_> = values.iter_terms(env).map(|t| t.as_c_arg()).collect(); + + if keys.len() == values.len() { + unsafe { + map::make_map_from_arrays(env.as_c_arg(), &keys, &values) + .map_or_else(|| Err(Error::BadArg), |map| Ok(Term::new(env, map))) + } + } else { + Err(Error::BadArg) + } + } + /// Construct a new map from pairs of terms /// /// It is similar to `map_from_arrays` but @@ -237,3 +267,41 @@ where Ok(first..=last) } } + +/// A trait for types that can be converted to an `Iterator` +/// using an `Env`. +/// +/// Used by [Term::map_from_iterables] +pub trait EncodeIterator<'a> { + type IntoIter: Iterator>; + + fn iter_terms(&self, env: Env<'a>) -> Self::IntoIter; +} + +macro_rules! impl_encoder_args { + ( $($index:tt : $tyvar:ident),* ) => ( + impl<'a, $( $tyvar: Encoder ),*> + EncodeIterator<'a> for ( $( $tyvar ),* ) { + + type IntoIter = std::vec::IntoIter>; + + fn iter_terms(&self, env: Env<'a>) -> Self::IntoIter { + vec![ $( Encoder::encode(&self.$index, env) ),* ].into_iter() + } + } + ); +} + +impl <'a, A:Encoder> EncodeIterator<'a> for (A,) { + type IntoIter = std::iter::Once>; + + fn iter_terms(&self,env:Env<'a>) -> Self::IntoIter { + std::iter::once(Encoder::encode(&self.0, env)) + } +} +impl_encoder_args!(0: A, 1: B); +impl_encoder_args!(0: A, 1: B, 2: C); +impl_encoder_args!(0: A, 1: B, 2: C, 3: D); +impl_encoder_args!(0: A, 1: B, 2: C, 3: D, 4: E); +impl_encoder_args!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F); +impl_encoder_args!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G); diff --git a/rustler_codegen/src/tagged_enum.rs b/rustler_codegen/src/tagged_enum.rs index ecdadd45..2a6d8fe3 100644 --- a/rustler_codegen/src/tagged_enum.rs +++ b/rustler_codegen/src/tagged_enum.rs @@ -312,7 +312,7 @@ fn gen_named_encoder( } }) .collect::>(); - let (keys, values): (Vec<_>, Vec<_>) = fields + let (pairs, field_defs): (Vec<_>, Vec<_>) = fields .named .iter() .map(|field| { @@ -321,16 +321,42 @@ fn gen_named_encoder( .as_ref() .expect("Named fields must have an ident."); let atom_fun = Context::field_to_atom_fun(field); - (atom_fun, field_ident) + let field_put = quote_spanned! { field.span() => + map = map.map_put(#atom_fun(), &#field_ident).unwrap(); + }; + ((atom_fun, field_ident), field_put) }) .unzip(); - quote! { - #enum_name :: #variant_ident{ - #(#field_decls)* - } => { - let map = ::rustler::Term::map_from_arrays(env, &[#(#keys()),*], &[#(#values),*]) - .expect("Failed to create map"); - ::rustler::types::tuple::make_tuple(env, &[::rustler::Encoder::encode(&#atom_fn(), env), map]) + let (keys, values): (Vec<_>, Vec<_>) = pairs.into_iter().unzip(); + if keys.len() < 8 { + let key_tuple = match &keys[..] { + [] => panic!("{}", "Named fields can not be empty"), + [fn0] => quote! { (#fn0(),) }, + _ => quote! { (#(#keys()),*) } + }; + let value_tuple = match &values[..] { + [] => panic!("{}", "Named fields can not be empty"), + [v0] => quote! { (#v0,) }, + _ => quote! { (#(#values),*) } + }; + quote! { + #enum_name :: #variant_ident{ + #(#field_decls)* + } => { + let map = ::rustler::Term::map_from_iterables(env, #key_tuple, #value_tuple) + .expect("Failed to create map"); + ::rustler::types::tuple::make_tuple(env, &[::rustler::Encoder::encode(&#atom_fn(), env), map]) + } + } + } else { + quote! { + #enum_name :: #variant_ident{ + #(#field_decls)* + } => { + let mut map = ::rustler::types::map::map_new(env); + #(#field_defs)* + ::rustler::types::tuple::make_tuple(env, &[::rustler::Encoder::encode(&#atom_fn(), env), map]) + } } } } diff --git a/rustler_tests/native/rustler_test/src/test_codegen.rs b/rustler_tests/native/rustler_test/src/test_codegen.rs index 393ab4ed..f7a14e8a 100644 --- a/rustler_tests/native/rustler_test/src/test_codegen.rs +++ b/rustler_tests/native/rustler_test/src/test_codegen.rs @@ -130,6 +130,17 @@ pub enum TaggedEnum4 { Unit, Unnamed(u64, bool), Named { size: u64, filename: String }, + Long { + f0: bool, + f1: u8, + f2: u8, + f3: u8, + f4: u8, + f5: Option, + f6: Option, + f7: Option, + f8: Option, + } } #[rustler::nif] diff --git a/rustler_tests/test/codegen_test.exs b/rustler_tests/test/codegen_test.exs index fea953e9..5b98b2c5 100644 --- a/rustler_tests/test/codegen_test.exs +++ b/rustler_tests/test/codegen_test.exs @@ -288,6 +288,11 @@ defmodule RustlerTest.CodegenTest do assert {:named, %{filename: "\u2200", size: 123}} == RustlerTest.tagged_enum_4_echo({:named, %{filename: "\u2200", size: 123}}) + long_map = %{f0: true, f1: 8, f2: 5, f3: 12, f4: 12, f5: 15, + f6: nil, f7: nil, f8: nil} + assert {:long, long_map} == + RustlerTest.tagged_enum_4_echo({:long, long_map}) + assert %ErlangError{original: :invalid_variant} == assert_raise(ErlangError, fn -> RustlerTest.tagged_enum_4_echo(:unnamed) From d197c5ada157bc91d0c4bfef53e42eaaef2ccf16 Mon Sep 17 00:00:00 2001 From: Dylan Burati Date: Fri, 23 Jun 2023 16:07:13 -0400 Subject: [PATCH 5/9] Use plain arrays --- rustler/src/types/map.rs | 42 ++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/rustler/src/types/map.rs b/rustler/src/types/map.rs index 7d9f9fcd..5d2c0559 100644 --- a/rustler/src/types/map.rs +++ b/rustler/src/types/map.rs @@ -61,11 +61,11 @@ impl<'a> Term<'a> { /// ``` pub fn map_from_iterables( env: Env<'a>, - keys: impl EncodeIterator<'a>, - values: impl EncodeIterator<'a>, + keys: impl EncoderIterable<'a>, + values: impl EncoderIterable<'a>, ) -> NifResult> { - let keys: Vec<_> = keys.iter_terms(env).map(|t| t.as_c_arg()).collect(); - let values: Vec<_> = values.iter_terms(env).map(|t| t.as_c_arg()).collect(); + let keys: Vec<_> = keys.terms(env).into_iter().map(|t| t.as_c_arg()).collect(); + let values: Vec<_> = values.terms(env).into_iter().map(|t| t.as_c_arg()).collect(); if keys.len() == values.len() { unsafe { @@ -268,35 +268,43 @@ where } } -/// A trait for types that can be converted to an `Iterator` +/// A trait for types that can be converted to an iterable of terms /// using an `Env`. /// -/// Used by [Term::map_from_iterables] -pub trait EncodeIterator<'a> { - type IntoIter: Iterator>; +/// Used by Term::map_from_iterables +pub trait EncoderIterable<'a> { + type IntoIter: IntoIterator>; - fn iter_terms(&self, env: Env<'a>) -> Self::IntoIter; + fn terms(&self, env: Env<'a>) -> Self::IntoIter; +} + +/// Helper macro that returns the number of comma-separated expressions passed to it. +/// For example, `count!(a + b, c)` evaluates to `2`. +macro_rules! count { + ( ) => ( 0 ); + ( $blah:expr ) => ( 1 ); + ( $blah:expr, $( $others:expr ),* ) => ( 1 + count!( $( $others ),* ) ) } macro_rules! impl_encoder_args { ( $($index:tt : $tyvar:ident),* ) => ( impl<'a, $( $tyvar: Encoder ),*> - EncodeIterator<'a> for ( $( $tyvar ),* ) { + EncoderIterable<'a> for ( $( $tyvar ),* ) { - type IntoIter = std::vec::IntoIter>; + type IntoIter = [Term<'a>; count!( $( $tyvar ),* )]; - fn iter_terms(&self, env: Env<'a>) -> Self::IntoIter { - vec![ $( Encoder::encode(&self.$index, env) ),* ].into_iter() + fn terms(&self, env: Env<'a>) -> Self::IntoIter { + [ $( Encoder::encode(&self.$index, env) ),* ] } } ); } -impl <'a, A:Encoder> EncodeIterator<'a> for (A,) { - type IntoIter = std::iter::Once>; +impl <'a, A:Encoder> EncoderIterable<'a> for (A,) { + type IntoIter = [Term<'a>; 1]; - fn iter_terms(&self,env:Env<'a>) -> Self::IntoIter { - std::iter::once(Encoder::encode(&self.0, env)) + fn terms(&self,env:Env<'a>) -> Self::IntoIter { + [Encoder::encode(&self.0, env)] } } impl_encoder_args!(0: A, 1: B); From b31e9dc7ad3f380ba9b13440d8eae37873658b20 Mon Sep 17 00:00:00 2001 From: Dylan Burati Date: Fri, 23 Jun 2023 18:07:01 -0400 Subject: [PATCH 6/9] Formatting --- rustler/src/types/map.rs | 10 +++++++--- rustler_codegen/src/tagged_enum.rs | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/rustler/src/types/map.rs b/rustler/src/types/map.rs index 5d2c0559..4982dd8b 100644 --- a/rustler/src/types/map.rs +++ b/rustler/src/types/map.rs @@ -65,7 +65,11 @@ impl<'a> Term<'a> { values: impl EncoderIterable<'a>, ) -> NifResult> { let keys: Vec<_> = keys.terms(env).into_iter().map(|t| t.as_c_arg()).collect(); - let values: Vec<_> = values.terms(env).into_iter().map(|t| t.as_c_arg()).collect(); + let values: Vec<_> = values + .terms(env) + .into_iter() + .map(|t| t.as_c_arg()) + .collect(); if keys.len() == values.len() { unsafe { @@ -300,10 +304,10 @@ macro_rules! impl_encoder_args { ); } -impl <'a, A:Encoder> EncoderIterable<'a> for (A,) { +impl<'a, A: Encoder> EncoderIterable<'a> for (A,) { type IntoIter = [Term<'a>; 1]; - fn terms(&self,env:Env<'a>) -> Self::IntoIter { + fn terms(&self, env: Env<'a>) -> Self::IntoIter { [Encoder::encode(&self.0, env)] } } diff --git a/rustler_codegen/src/tagged_enum.rs b/rustler_codegen/src/tagged_enum.rs index 2a6d8fe3..46af14aa 100644 --- a/rustler_codegen/src/tagged_enum.rs +++ b/rustler_codegen/src/tagged_enum.rs @@ -332,12 +332,12 @@ fn gen_named_encoder( let key_tuple = match &keys[..] { [] => panic!("{}", "Named fields can not be empty"), [fn0] => quote! { (#fn0(),) }, - _ => quote! { (#(#keys()),*) } + _ => quote! { (#(#keys()),*) }, }; let value_tuple = match &values[..] { [] => panic!("{}", "Named fields can not be empty"), [v0] => quote! { (#v0,) }, - _ => quote! { (#(#values),*) } + _ => quote! { (#(#values),*) }, }; quote! { #enum_name :: #variant_ident{ From 5458035b0af03a1e14d44427704ae89d81b4b4ab Mon Sep 17 00:00:00 2001 From: Dylan Burati Date: Mon, 26 Jun 2023 18:16:42 -0400 Subject: [PATCH 7/9] Use map_from_term_arrays in Nif{Map,Struct,Exception,TaggedEnum} encoders - Removed map_from_iterables and supporting trait, from earlier in the PR - Updated tests for Map, Struct, and Exception to have heterogenous types --- rustler/src/types/map.rs | 79 +++---------------- rustler_codegen/src/ex_struct.rs | 33 ++++---- rustler_codegen/src/map.rs | 17 ++-- rustler_codegen/src/tagged_enum.rs | 47 +++-------- .../native/rustler_test/src/test_codegen.rs | 3 + rustler_tests/test/codegen_test.exs | 35 +++++--- 6 files changed, 73 insertions(+), 141 deletions(-) diff --git a/rustler/src/types/map.rs b/rustler/src/types/map.rs index 4982dd8b..ecffe981 100644 --- a/rustler/src/types/map.rs +++ b/rustler/src/types/map.rs @@ -47,31 +47,20 @@ impl<'a> Term<'a> { } } - /// Construct a new map from two iterables - /// - /// It is identical to map_from_arrays, but accepts tuples - /// instead of arrays to allow differing types within the - /// keys and values. - /// - /// ### Elixir equivalent - /// ```elixir - /// keys = ["foo", "bar"] - /// values = [1, true] - /// Enum.zip(values) |> Map.new() - /// ``` - pub fn map_from_iterables( + /// Construct a new map from two vectors of terms. + /// + /// It is identical to map_from_arrays, but requires the keys and values to + /// be encoded already - this is useful for constructing maps whose values + /// or keys are different Rust types, with the same performance as map_from_arrays. + pub fn map_from_term_arrays( env: Env<'a>, - keys: impl EncoderIterable<'a>, - values: impl EncoderIterable<'a>, + keys: &[Term<'a>], + values: &[Term<'a>], ) -> NifResult> { - let keys: Vec<_> = keys.terms(env).into_iter().map(|t| t.as_c_arg()).collect(); - let values: Vec<_> = values - .terms(env) - .into_iter() - .map(|t| t.as_c_arg()) - .collect(); - if keys.len() == values.len() { + let keys: Vec<_> = keys.iter().map(|k| k.as_c_arg()).collect(); + let values: Vec<_> = values.iter().map(|v| v.as_c_arg()).collect(); + unsafe { map::make_map_from_arrays(env.as_c_arg(), &keys, &values) .map_or_else(|| Err(Error::BadArg), |map| Ok(Term::new(env, map))) @@ -271,49 +260,3 @@ where Ok(first..=last) } } - -/// A trait for types that can be converted to an iterable of terms -/// using an `Env`. -/// -/// Used by Term::map_from_iterables -pub trait EncoderIterable<'a> { - type IntoIter: IntoIterator>; - - fn terms(&self, env: Env<'a>) -> Self::IntoIter; -} - -/// Helper macro that returns the number of comma-separated expressions passed to it. -/// For example, `count!(a + b, c)` evaluates to `2`. -macro_rules! count { - ( ) => ( 0 ); - ( $blah:expr ) => ( 1 ); - ( $blah:expr, $( $others:expr ),* ) => ( 1 + count!( $( $others ),* ) ) -} - -macro_rules! impl_encoder_args { - ( $($index:tt : $tyvar:ident),* ) => ( - impl<'a, $( $tyvar: Encoder ),*> - EncoderIterable<'a> for ( $( $tyvar ),* ) { - - type IntoIter = [Term<'a>; count!( $( $tyvar ),* )]; - - fn terms(&self, env: Env<'a>) -> Self::IntoIter { - [ $( Encoder::encode(&self.$index, env) ),* ] - } - } - ); -} - -impl<'a, A: Encoder> EncoderIterable<'a> for (A,) { - type IntoIter = [Term<'a>; 1]; - - fn terms(&self, env: Env<'a>) -> Self::IntoIter { - [Encoder::encode(&self.0, env)] - } -} -impl_encoder_args!(0: A, 1: B); -impl_encoder_args!(0: A, 1: B, 2: C); -impl_encoder_args!(0: A, 1: B, 2: C, 3: D); -impl_encoder_args!(0: A, 1: B, 2: C, 3: D, 4: E); -impl_encoder_args!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F); -impl_encoder_args!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G); diff --git a/rustler_codegen/src/ex_struct.rs b/rustler_codegen/src/ex_struct.rs index e489ca7f..e051c494 100644 --- a/rustler_codegen/src/ex_struct.rs +++ b/rustler_codegen/src/ex_struct.rs @@ -136,34 +136,31 @@ fn gen_encoder( atoms_module_name: &Ident, add_exception: bool, ) -> TokenStream { - let field_defs: Vec = fields + let mut keys = vec![quote! { ::rustler::Encoder::encode(&atom_struct(), env) }]; + let mut values = vec![quote! { ::rustler::Encoder::encode(&atom_module(), env) }]; + if add_exception { + keys.push(quote! { ::rustler::Encoder::encode(&atom_exception(), env) }); + values.push(quote! { ::rustler::Encoder::encode(&true, env) }); + } + let (mut data_keys, mut data_values): (Vec<_>, Vec<_>) = fields .iter() .map(|field| { let field_ident = field.ident.as_ref().unwrap(); let atom_fun = Context::field_to_atom_fun(field); - quote_spanned! { field.span() => - map = map.map_put(#atom_fun(), &self.#field_ident).unwrap(); - } + ( + quote! { ::rustler::Encoder::encode(&#atom_fun(), env) }, + quote! { ::rustler::Encoder::encode(&self.#field_ident, env) }, + ) }) - .collect(); - - let exception_field = if add_exception { - quote! { - map = map.map_put(atom_exception(), true).unwrap(); - } - } else { - quote! {} - }; + .unzip(); + keys.append(&mut data_keys); + values.append(&mut data_values); super::encode_decode_templates::encoder( ctx, quote! { use #atoms_module_name::*; - let mut map = ::rustler::types::map::map_new(env); - map = map.map_put(atom_struct(), atom_module()).unwrap(); - #exception_field - #(#field_defs)* - map + ::rustler::Term::map_from_term_arrays(env, &[#(#keys),*], &[#(#values),*]).unwrap() }, ) } diff --git a/rustler_codegen/src/map.rs b/rustler_codegen/src/map.rs index 8584484e..9065d132 100644 --- a/rustler_codegen/src/map.rs +++ b/rustler_codegen/src/map.rs @@ -107,26 +107,23 @@ fn gen_decoder(ctx: &Context, fields: &[&Field], atoms_module_name: &Ident) -> T } fn gen_encoder(ctx: &Context, fields: &[&Field], atoms_module_name: &Ident) -> TokenStream { - let field_defs: Vec = fields + let (keys, values): (Vec<_>, Vec<_>) = fields .iter() .map(|field| { let field_ident = field.ident.as_ref().unwrap(); let atom_fun = Context::field_to_atom_fun(field); - - quote_spanned! { field.span() => - map = map.map_put(#atom_fun(), &self.#field_ident).unwrap(); - } + ( + quote! { ::rustler::Encoder::encode(&#atom_fun(), env) }, + quote! { ::rustler::Encoder::encode(&self.#field_ident, env) }, + ) }) - .collect(); + .unzip(); super::encode_decode_templates::encoder( ctx, quote! { use #atoms_module_name::*; - - let mut map = ::rustler::types::map::map_new(env); - #(#field_defs)* - map + ::rustler::Term::map_from_term_arrays(env, &[#(#keys),*], &[#(#values),*]).unwrap() }, ) } diff --git a/rustler_codegen/src/tagged_enum.rs b/rustler_codegen/src/tagged_enum.rs index 46af14aa..fe78d331 100644 --- a/rustler_codegen/src/tagged_enum.rs +++ b/rustler_codegen/src/tagged_enum.rs @@ -312,7 +312,7 @@ fn gen_named_encoder( } }) .collect::>(); - let (pairs, field_defs): (Vec<_>, Vec<_>) = fields + let (keys, values): (Vec<_>, Vec<_>) = fields .named .iter() .map(|field| { @@ -321,42 +321,19 @@ fn gen_named_encoder( .as_ref() .expect("Named fields must have an ident."); let atom_fun = Context::field_to_atom_fun(field); - let field_put = quote_spanned! { field.span() => - map = map.map_put(#atom_fun(), &#field_ident).unwrap(); - }; - ((atom_fun, field_ident), field_put) + ( + quote! { ::rustler::Encoder::encode(&#atom_fun(), env) }, + quote! { ::rustler::Encoder::encode(&#field_ident, env) }, + ) }) .unzip(); - let (keys, values): (Vec<_>, Vec<_>) = pairs.into_iter().unzip(); - if keys.len() < 8 { - let key_tuple = match &keys[..] { - [] => panic!("{}", "Named fields can not be empty"), - [fn0] => quote! { (#fn0(),) }, - _ => quote! { (#(#keys()),*) }, - }; - let value_tuple = match &values[..] { - [] => panic!("{}", "Named fields can not be empty"), - [v0] => quote! { (#v0,) }, - _ => quote! { (#(#values),*) }, - }; - quote! { - #enum_name :: #variant_ident{ - #(#field_decls)* - } => { - let map = ::rustler::Term::map_from_iterables(env, #key_tuple, #value_tuple) - .expect("Failed to create map"); - ::rustler::types::tuple::make_tuple(env, &[::rustler::Encoder::encode(&#atom_fn(), env), map]) - } - } - } else { - quote! { - #enum_name :: #variant_ident{ - #(#field_decls)* - } => { - let mut map = ::rustler::types::map::map_new(env); - #(#field_defs)* - ::rustler::types::tuple::make_tuple(env, &[::rustler::Encoder::encode(&#atom_fn(), env), map]) - } + quote! { + #enum_name :: #variant_ident{ + #(#field_decls)* + } => { + let map = ::rustler::Term::map_from_term_arrays(env, &[#(#keys),*], &[#(#values),*]) + .expect("Failed to create map"); + ::rustler::types::tuple::make_tuple(env, &[::rustler::Encoder::encode(&#atom_fn(), env), map]) } } } diff --git a/rustler_tests/native/rustler_test/src/test_codegen.rs b/rustler_tests/native/rustler_test/src/test_codegen.rs index f7a14e8a..3ebde417 100644 --- a/rustler_tests/native/rustler_test/src/test_codegen.rs +++ b/rustler_tests/native/rustler_test/src/test_codegen.rs @@ -44,6 +44,7 @@ pub fn record_echo(record: AddRecord) -> AddRecord { pub struct AddMap { lhs: i32, rhs: i32, + loc: (u32, u32), } #[rustler::nif] @@ -57,12 +58,14 @@ pub fn map_echo(map: AddMap) -> AddMap { pub struct AddStruct { lhs: i32, rhs: i32, + loc: (u32, u32), } #[derive(Debug, NifException)] #[module = "AddException"] pub struct AddException { message: String, + loc: (u32, u32), } #[rustler::nif] diff --git a/rustler_tests/test/codegen_test.exs b/rustler_tests/test/codegen_test.exs index 5b98b2c5..85b449e3 100644 --- a/rustler_tests/test/codegen_test.exs +++ b/rustler_tests/test/codegen_test.exs @@ -1,9 +1,9 @@ defmodule AddStruct do - defstruct lhs: 0, rhs: 0 + defstruct lhs: 0, rhs: 0, loc: {1, 1} end defmodule AddException do - defexception message: "" + defexception message: "", loc: {1, 1} end defmodule AddRecord do @@ -40,14 +40,13 @@ defmodule RustlerTest.CodegenTest do end describe "map" do - test "transcoder" do - value = %{lhs: 1, rhs: 2} + test "transcoder 1" do + value = %{lhs: 1, rhs: 2, loc: {52, 15}} assert value == RustlerTest.map_echo(value) end test "with invalid map" do - value = %{lhs: "invalid", rhs: 2} - + value = %{lhs: "invalid", rhs: 2, loc: {57, 15}} assert_raise ErlangError, "Erlang error: \"Could not decode field :lhs on %{}\"", fn -> assert value == RustlerTest.map_echo(value) end @@ -56,7 +55,7 @@ defmodule RustlerTest.CodegenTest do describe "struct" do test "transcoder" do - value = %AddStruct{lhs: 45, rhs: 123} + value = %AddStruct{lhs: 45, rhs: 123, loc: {66, 15}} assert value == RustlerTest.struct_echo(value) assert %ErlangError{original: :invalid_struct} == @@ -66,19 +65,27 @@ defmodule RustlerTest.CodegenTest do end test "with invalid struct" do - value = %AddStruct{lhs: "lhs", rhs: 123} + value = %AddStruct{lhs: "lhs", rhs: 123, loc: {76, 15}} assert_raise ErlangError, "Erlang error: \"Could not decode field :lhs on %AddStruct{}\"", fn -> RustlerTest.struct_echo(value) end + + value = %AddStruct{lhs: 45, rhs: 123, loc: {-76, -15}} + + assert_raise ErlangError, + "Erlang error: \"Could not decode field :loc on %AddStruct{}\"", + fn -> + RustlerTest.struct_echo(value) + end end end describe "exception" do test "transcoder" do - value = %AddException{message: "testing"} + value = %AddException{message: "testing", loc: {96, 15}} assert value == RustlerTest.exception_echo(value) assert %ErlangError{original: :invalid_struct} == @@ -88,13 +95,21 @@ defmodule RustlerTest.CodegenTest do end test "with invalid struct" do - value = %AddException{message: 'this is a charlist'} + value = %AddException{message: 'this is a charlist', loc: {106, 15}} assert_raise ErlangError, "Erlang error: \"Could not decode field :message on %AddException{}\"", fn -> RustlerTest.exception_echo(value) end + + value = %AddException{message: "testing", loc: %{line: 114, col: 15}} + + assert_raise ErlangError, + "Erlang error: \"Could not decode field :loc on %AddException{}\"", + fn -> + RustlerTest.exception_echo(value) + end end end From aeeaed2a73687fe9ea77f440880fae37fa4aee95 Mon Sep 17 00:00:00 2001 From: Dylan Burati Date: Mon, 26 Jun 2023 18:19:38 -0400 Subject: [PATCH 8/9] Formatting --- rustler/src/types/map.rs | 2 +- rustler_tests/native/rustler_test/src/test_codegen.rs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rustler/src/types/map.rs b/rustler/src/types/map.rs index ecffe981..77ddf27b 100644 --- a/rustler/src/types/map.rs +++ b/rustler/src/types/map.rs @@ -48,7 +48,7 @@ impl<'a> Term<'a> { } /// Construct a new map from two vectors of terms. - /// + /// /// It is identical to map_from_arrays, but requires the keys and values to /// be encoded already - this is useful for constructing maps whose values /// or keys are different Rust types, with the same performance as map_from_arrays. diff --git a/rustler_tests/native/rustler_test/src/test_codegen.rs b/rustler_tests/native/rustler_test/src/test_codegen.rs index 3ebde417..d7671b68 100644 --- a/rustler_tests/native/rustler_test/src/test_codegen.rs +++ b/rustler_tests/native/rustler_test/src/test_codegen.rs @@ -132,7 +132,10 @@ pub fn tagged_enum_3_echo(tagged_enum: TaggedEnum3) -> TaggedEnum3 { pub enum TaggedEnum4 { Unit, Unnamed(u64, bool), - Named { size: u64, filename: String }, + Named { + size: u64, + filename: String, + }, Long { f0: bool, f1: u8, @@ -143,7 +146,7 @@ pub enum TaggedEnum4 { f6: Option, f7: Option, f8: Option, - } + }, } #[rustler::nif] From 73e87a04ca97b237219bd71e29dd1b3163b80758 Mon Sep 17 00:00:00 2001 From: Dylan Burati Date: Tue, 27 Jun 2023 18:25:55 -0400 Subject: [PATCH 9/9] Formatting elixir --- rustler_tests/test/codegen_test.exs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/rustler_tests/test/codegen_test.exs b/rustler_tests/test/codegen_test.exs index 85b449e3..722891d8 100644 --- a/rustler_tests/test/codegen_test.exs +++ b/rustler_tests/test/codegen_test.exs @@ -47,6 +47,7 @@ defmodule RustlerTest.CodegenTest do test "with invalid map" do value = %{lhs: "invalid", rhs: 2, loc: {57, 15}} + assert_raise ErlangError, "Erlang error: \"Could not decode field :lhs on %{}\"", fn -> assert value == RustlerTest.map_echo(value) end @@ -76,10 +77,10 @@ defmodule RustlerTest.CodegenTest do value = %AddStruct{lhs: 45, rhs: 123, loc: {-76, -15}} assert_raise ErlangError, - "Erlang error: \"Could not decode field :loc on %AddStruct{}\"", - fn -> - RustlerTest.struct_echo(value) - end + "Erlang error: \"Could not decode field :loc on %AddStruct{}\"", + fn -> + RustlerTest.struct_echo(value) + end end end @@ -106,10 +107,10 @@ defmodule RustlerTest.CodegenTest do value = %AddException{message: "testing", loc: %{line: 114, col: 15}} assert_raise ErlangError, - "Erlang error: \"Could not decode field :loc on %AddException{}\"", - fn -> - RustlerTest.exception_echo(value) - end + "Erlang error: \"Could not decode field :loc on %AddException{}\"", + fn -> + RustlerTest.exception_echo(value) + end end end @@ -303,10 +304,10 @@ defmodule RustlerTest.CodegenTest do assert {:named, %{filename: "\u2200", size: 123}} == RustlerTest.tagged_enum_4_echo({:named, %{filename: "\u2200", size: 123}}) - long_map = %{f0: true, f1: 8, f2: 5, f3: 12, f4: 12, f5: 15, - f6: nil, f7: nil, f8: nil} + long_map = %{f0: true, f1: 8, f2: 5, f3: 12, f4: 12, f5: 15, f6: nil, f7: nil, f8: nil} + assert {:long, long_map} == - RustlerTest.tagged_enum_4_echo({:long, long_map}) + RustlerTest.tagged_enum_4_echo({:long, long_map}) assert %ErlangError{original: :invalid_variant} == assert_raise(ErlangError, fn ->