From 7b24caa9112672bfac4c4d6a73a94c7741d1fe4e Mon Sep 17 00:00:00 2001 From: muzarski Date: Wed, 24 Apr 2024 23:31:05 +0200 Subject: [PATCH 1/6] macros/parser: adjust `parse_named_fields` The docstring associated to this function was not easy to understand - it got adjusted. Extracted error message creation to closure, to not repeat same multiple lines of code. --- scylla-macros/src/parser.rs | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/scylla-macros/src/parser.rs b/scylla-macros/src/parser.rs index 6c33fba0c3..883bf2fbae 100644 --- a/scylla-macros/src/parser.rs +++ b/scylla-macros/src/parser.rs @@ -1,37 +1,25 @@ use syn::{Data, DeriveInput, ExprLit, Fields, FieldsNamed, Lit}; use syn::{Expr, Meta}; -/// Parses the tokens_input to a DeriveInput and returns the struct name from which it derives and -/// the named fields +/// Parses a struct DeriveInput and returns named fields of this struct. pub(crate) fn parse_named_fields<'a>( input: &'a DeriveInput, current_derive: &str, ) -> Result<&'a FieldsNamed, syn::Error> { + let create_err_msg = || { + format!( + "derive({}) works only for structs with named fields", + current_derive + ) + }; + match &input.data { Data::Struct(data) => match &data.fields { Fields::Named(named_fields) => Ok(named_fields), - _ => Err(syn::Error::new_spanned( - data.struct_token, - format!( - "derive({}) works only for structs with named fields", - current_derive - ), - )), + _ => Err(syn::Error::new_spanned(data.struct_token, create_err_msg())), }, - Data::Enum(e) => Err(syn::Error::new_spanned( - e.enum_token, - format!( - "derive({}) works only for structs with named fields", - current_derive - ), - )), - Data::Union(u) => Err(syn::Error::new_spanned( - u.union_token, - format!( - "derive({}) works only for structs with named fields", - current_derive - ), - )), + Data::Enum(e) => Err(syn::Error::new_spanned(e.enum_token, create_err_msg())), + Data::Union(u) => Err(syn::Error::new_spanned(u.union_token, create_err_msg())), } } From b77bf057c2b430016f9e14715c6c5450aa6785c4 Mon Sep 17 00:00:00 2001 From: muzarski Date: Fri, 19 Apr 2024 00:41:34 +0200 Subject: [PATCH 2/6] macros/parser: extract struct fields Add a parser util function that extracts the fields from a struct. --- scylla-macros/src/parser.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/scylla-macros/src/parser.rs b/scylla-macros/src/parser.rs index 883bf2fbae..ec72a81b1c 100644 --- a/scylla-macros/src/parser.rs +++ b/scylla-macros/src/parser.rs @@ -1,4 +1,4 @@ -use syn::{Data, DeriveInput, ExprLit, Fields, FieldsNamed, Lit}; +use syn::{Data, DeriveInput, ExprLit, Fields, FieldsNamed, FieldsUnnamed, Lit}; use syn::{Expr, Meta}; /// Parses a struct DeriveInput and returns named fields of this struct. @@ -23,6 +23,34 @@ pub(crate) fn parse_named_fields<'a>( } } +pub(crate) enum StructFields<'a> { + Named(&'a FieldsNamed), + Unnamed(&'a FieldsUnnamed), +} + +/// Parses a struct DeriveInput and returns fields (both named and unnamed) of this struct. +pub(crate) fn parse_struct_fields<'a>( + input: &'a DeriveInput, + current_derive: &str, +) -> Result, syn::Error> { + let create_err_msg = || { + format!( + "derive({}) works only for structs with fields", + current_derive + ) + }; + + match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(named_fields) => Ok(StructFields::Named(named_fields)), + Fields::Unnamed(unnamed_fields) => Ok(StructFields::Unnamed(unnamed_fields)), + Fields::Unit => Err(syn::Error::new_spanned(data.struct_token, create_err_msg())), + }, + Data::Enum(e) => Err(syn::Error::new_spanned(e.enum_token, create_err_msg())), + Data::Union(u) => Err(syn::Error::new_spanned(u.union_token, create_err_msg())), + } +} + pub(crate) fn get_path(input: &DeriveInput) -> Result { let mut this_path: Option = None; for attr in input.attrs.iter() { From d34d9f1ad6eb8793fe185d570cb6362d5af9218b Mon Sep 17 00:00:00 2001 From: muzarski Date: Wed, 24 Apr 2024 23:38:06 +0200 Subject: [PATCH 3/6] from_row: adjust code for named fields This commit prepares for code generation for unnamed fields. The behaviour for named fields becomes unchanged, however code generation logic was slightly adjusted so the very similar approach can be applied to unnamed fields. --- scylla-macros/src/from_row.rs | 56 ++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/scylla-macros/src/from_row.rs b/scylla-macros/src/from_row.rs index 454f9f234d..ce215908d6 100644 --- a/scylla-macros/src/from_row.rs +++ b/scylla-macros/src/from_row.rs @@ -6,33 +6,43 @@ use syn::{spanned::Spanned, DeriveInput}; pub(crate) fn from_row_derive(tokens_input: TokenStream) -> Result { let item = syn::parse::(tokens_input)?; let path = crate::parser::get_path(&item)?; - let struct_fields = crate::parser::parse_named_fields(&item, "FromRow")?; + let struct_fields = crate::parser::parse_struct_fields(&item, "FromRow")?; let struct_name = &item.ident; let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl(); - // Generates tokens for field_name: field_type::from_cql(vals_iter.next().ok_or(...)?), ... - let set_fields_code = struct_fields.named.iter().map(|field| { - let field_name = &field.ident; - let field_type = &field.ty; - - quote_spanned! {field.span() => - #field_name: { - let (col_ix, col_value) = vals_iter - .next() - .unwrap(); // vals_iter size is checked before this code is reached, so - // it is safe to unwrap - - <#field_type as FromCqlVal<::std::option::Option>>::from_cql(col_value) - .map_err(|e| FromRowError::BadCqlVal { - err: e, - column: col_ix, - })? - }, + // Generates a token that sets the values of struct fields: field_type::from_cql(...) + let (fill_struct_code, fields_count) = match struct_fields { + crate::parser::StructFields::Named(fields) => { + let set_fields_code = fields.named.iter().map(|field| { + let field_name = &field.ident; + let field_type = &field.ty; + + quote_spanned! {field.span() => + #field_name: { + let (col_ix, col_value) = vals_iter + .next() + .unwrap(); // vals_iter size is checked before this code is reached, so + // it is safe to unwrap + + <#field_type as FromCqlVal<::std::option::Option>>::from_cql(col_value) + .map_err(|e| FromRowError::BadCqlVal { + err: e, + column: col_ix, + })? + }, + } + }); + + // This generates: { field1: {field1_code}, field2: {field2_code}, ...} + let fill_struct_code = quote! { + {#(#set_fields_code)*} + }; + (fill_struct_code, fields.named.len()) } - }); + crate::parser::StructFields::Unnamed(_fields) => todo!(), + }; - let fields_count = struct_fields.named.len(); let generated = quote! { impl #impl_generics #path::FromRow for #struct_name #ty_generics #where_clause { fn from_row(row: #path::Row) @@ -49,9 +59,7 @@ pub(crate) fn from_row_derive(tokens_input: TokenStream) -> Result Date: Fri, 19 Apr 2024 02:01:23 +0200 Subject: [PATCH 4/6] from_row: code generation for unnamed fields This commit adjusts the `from_row_derive` macro function that implements `FromRow` for the structs, so that it works for structs with unnamed fields as well. --- scylla-macros/src/from_row.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/scylla-macros/src/from_row.rs b/scylla-macros/src/from_row.rs index ce215908d6..42e717991e 100644 --- a/scylla-macros/src/from_row.rs +++ b/scylla-macros/src/from_row.rs @@ -40,7 +40,32 @@ pub(crate) fn from_row_derive(tokens_input: TokenStream) -> Result todo!(), + crate::parser::StructFields::Unnamed(fields) => { + let set_fields_code = fields.unnamed.iter().map(|field| { + let field_type = &field.ty; + + quote_spanned! {field.span() => + { + let (col_ix, col_value) = vals_iter + .next() + .unwrap(); // vals_iter size is checked before this code is reached, so + // it is safe to unwrap + + <#field_type as FromCqlVal<::std::option::Option>>::from_cql(col_value) + .map_err(|e| FromRowError::BadCqlVal { + err: e, + column: col_ix, + })? + }, + } + }); + + // This generates: ( {}, {}, ... ) + let fill_struct_code = quote! { + (#(#set_fields_code)*) + }; + (fill_struct_code, fields.unnamed.len()) + } }; let generated = quote! { From 4de9669a427ff92b65c675dbd3d352dfa9747a4a Mon Sep 17 00:00:00 2001 From: muzarski Date: Fri, 19 Apr 2024 02:12:25 +0200 Subject: [PATCH 5/6] from_row: test case for unnamed fields --- scylla-cql/src/frame/response/cql_to_rust.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scylla-cql/src/frame/response/cql_to_rust.rs b/scylla-cql/src/frame/response/cql_to_rust.rs index f40f2346fb..c55dd3ee85 100644 --- a/scylla-cql/src/frame/response/cql_to_rust.rs +++ b/scylla-cql/src/frame/response/cql_to_rust.rs @@ -1000,4 +1000,24 @@ mod tests { }) ); } + + #[test] + fn unnamed_struct_from_row() { + #[derive(FromRow)] + struct MyRow(i32, Option, Option>); + + let row = Row { + columns: vec![ + Some(CqlValue::Int(16)), + None, + Some(CqlValue::Set(vec![CqlValue::Int(1), CqlValue::Int(2)])), + ], + }; + + let my_row: MyRow = MyRow::from_row(row).unwrap(); + + assert_eq!(my_row.0, 16); + assert_eq!(my_row.1, None); + assert_eq!(my_row.2, Some(vec![1, 2])); + } } From 12a0436e9d79be40c1682e974484f120d528f373 Mon Sep 17 00:00:00 2001 From: muzarski Date: Fri, 19 Apr 2024 02:19:25 +0200 Subject: [PATCH 6/6] from_row: mention unnamed fields in docstrings --- scylla/src/macros.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scylla/src/macros.rs b/scylla/src/macros.rs index 67a7461bf7..5c01bff053 100644 --- a/scylla/src/macros.rs +++ b/scylla/src/macros.rs @@ -1,6 +1,8 @@ -/// #[derive(FromRow)] derives FromRow for struct +/// Derive macro for the [`FromRow`](crate::frame::response::cql_to_rust::FromRow) trait +/// which deserializes a row to given Rust structure. /// -/// Works only on simple structs without generics etc +/// It is supported for structs with either named or unnamed fields. +/// It works only for simple structs without generics etc. /// /// --- ///