diff --git a/filecaster-derive/src/from_file/ast.rs b/filecaster-derive/src/from_file/ast.rs index c9f7a89..73b7f92 100644 --- a/filecaster-derive/src/from_file/ast.rs +++ b/filecaster-derive/src/from_file/ast.rs @@ -15,7 +15,7 @@ pub struct Field { pub attrs: Vec, pub vis: TokenStream, pub name: Ident, - pub ty: Ident, + pub ty: TokenStream, } #[derive(Debug)] @@ -53,7 +53,7 @@ impl From for Field { .collect(), vis: value.vis.to_token_stream(), name: value.name, - ty: value.ty, + ty: value.ty.to_token_stream(), } } } diff --git a/filecaster-derive/src/from_file/codegen.rs b/filecaster-derive/src/from_file/codegen.rs index 51b8baf..df195a2 100644 --- a/filecaster-derive/src/from_file/codegen.rs +++ b/filecaster-derive/src/from_file/codegen.rs @@ -6,29 +6,29 @@ pub fn generate_impl(info: &Struct) -> Result { let name = &info.name; let vis = &info.vis; let generics = &info.generics; - let file_ident = format_ident!("{name}File"); + let file_ident = format_ident!("{}File", name.to_string()); let mut file_fields = Vec::new(); let mut assignments = Vec::new(); for field in &info.fields { - let name = &field.name; - let ty = &field.ty; - let vis = &field.vis; + let fname = &field.name; + let fty = &field.ty; + let fvis = &field.vis; let default_override = parse_from_file_default_attr(&field.attrs)?; - let shadow_ty = quote! { <#ty as filecaster::FromFile>::Shadow }; - file_fields.push(quote! { #vis #name: Option<#shadow_ty> }); + let shadow_ty = quote! { <#fty as ::filecaster::FromFile>::Shadow }; + file_fields.push(quote! { #fvis #fname: Option<#shadow_ty> }); if let Some(expr) = default_override { assignments.push(quote! { - #name: file.#name - .map(|inner| <#ty as filecaster::FromFile>::from_file(Some(inner))) - .unwrap_or(#expr.into()) + #fname: file.#fname + .map(|inner| <#fty as ::filecaster::FromFile>::from_file(Some(inner))) + .unwrap_or_else(|| (#expr).into()) }); } else { assignments.push(quote! { - #name: <#ty as filecaster::FromFile>::from_file(file.#name) + #fname: <#fty as ::filecaster::FromFile>::from_file(file.#fname) }); } } @@ -41,7 +41,7 @@ pub fn generate_impl(info: &Struct) -> Result { #(#file_fields),* } - impl #generics filecaster::FromFile for #name #generics { + impl #generics ::filecaster::FromFile for #name #generics { type Shadow = #file_ident #generics; fn from_file(file: Option) -> Self { @@ -54,32 +54,24 @@ pub fn generate_impl(info: &Struct) -> Result { impl #generics From> for #name #generics { fn from(value: Option<#file_ident #generics>) -> Self { - ::from_file(value) + ::from_file(value) } } impl #generics From<#file_ident #generics> for #name #generics { fn from(value: #file_ident #generics) -> Self { - ::from_file(Some(value)) + ::from_file(Some(value)) } } }) } fn build_derive_clause() -> TokenStream { - let mut traits = vec![quote! { Debug }, quote! { Clone }, quote! { Default }]; - #[cfg(feature = "serde")] - { - traits.push(quote! { serde::Deserialize }); - traits.push(quote! { serde::Serialize }); + quote! { + #[derive(Debug, Clone, Default)] + #[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))] + #[cfg_attr(feature = "merge", derive(::merge::Merge))] } - - #[cfg(feature = "merge")] - { - traits.push(quote! { merge::Merge }); - } - - quote! { #[derive( #(#traits),* )] } } #[cfg(test)] diff --git a/filecaster-derive/src/from_file/grammar.rs b/filecaster-derive/src/from_file/grammar.rs index 86e3f03..3c27555 100644 --- a/filecaster-derive/src/from_file/grammar.rs +++ b/filecaster-derive/src/from_file/grammar.rs @@ -12,7 +12,6 @@ pub struct Foo { } */ unsynn! { - pub struct Attribute { pub path: Ident, // attr pub tokens: ParenthesisGroupContaining // "value" diff --git a/filecaster-derive/src/from_file/parser.rs b/filecaster-derive/src/from_file/parser.rs index 1e3b565..feeb7bc 100644 --- a/filecaster-derive/src/from_file/parser.rs +++ b/filecaster-derive/src/from_file/parser.rs @@ -4,7 +4,7 @@ use unsynn::*; pub fn parse_from_file_default_attr(attrs: &[Attribute]) -> Result> { for attr in attrs { - if attr.path == "from_file" { + if attr.path.tokens_to_string().trim() == "from_file" { let tokens = attr.tokens.clone(); let iter = tokens.clone().into_token_iter(); @@ -21,16 +21,22 @@ pub fn parse_from_file_default_attr(attrs: &[Attribute]) -> Result Option { let mut iter = token.into_token_iter().peekable(); - - while let Some(TokenTree::Ident(id)) = iter.next() { - if id != "default" { - continue; - } - match iter.next() { - Some(TokenTree::Punct(eq)) if eq.as_char() == '=' => { - return Some(collect_until_commas(&mut iter)); + while let Some(tt) = iter.next() { + match &tt { + TokenTree::Ident(id) if id == "default" => { + // accept optional whitespace/punct and then '=' + // next non-whitespace token should be '=' + if let Some(next) = iter.peek() + && let TokenTree::Punct(p) = next + && p.as_char() == '=' + { + iter.next(); + return Some(collect_until_commas(&mut iter)); + } + // if we see "default" without '=', treat as parse failure + return None; } - _ => return None, + _ => continue, } } @@ -48,7 +54,8 @@ where iter.next(); break; } - expr.extend(once(iter.next().unwrap())); + // peek returned Some, so unwrap is safe + expr.extend(once(iter.next().expect("this should be impossible to see"))); } expr }