Repository: cloudflare/foundations (language: rust)

Issue:
Rust 1.79 clippy failing due to possible code simplification in darling's proc macro usage
foundations' CI is failing the clippy check with:
```
error: if let can be simplified with `.unwrap_or_default()`
  --> foundations-macros/src/info_metric/mod.rs:18:15
   |
18 |     #[darling(default)]
   |               ^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default
   = note: `-D clippy::manual-unwrap-or-default` implied by `-D warnings`
   = help: to override `-D warnings` add `#[allow(clippy::manual_unwrap_or_default)]`
```

This has been reported in darling's repo https://github.com/TedDriggs/darling/issues/293 but is still not fixed there. So right now foundations development is stuck.



Discussion / hints:
Temporary mitigation until this is fixed properly: https://github.com/cloudflare/foundations/pull/51
This has been fixed in darling 0.20.10
@TedDriggs thank you for the heads up
Ah, we can't easily update - we're still on `syn` 1.x
Ok, wasn't that hard after all: https://github.com/cloudflare/foundations/pull/56

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 6e83abb..b5daea6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,7 +32,7 @@ cc = "1.0"
 cf-rustracing = "1.0"
 cf-rustracing-jaeger = "1.1"
 clap = "4.4"
-darling = "0.14.4"
+darling = "0.20"
 erased-serde = "0.3.28"
 futures-util = "0.3.28"
 governor = "0.6"
@@ -53,7 +53,7 @@ regex = "1.8"
 reqwest = { version = "0.12", default-features = false }
 routerify = "3"
 socket2 = { version = "0.5.3", features = [ "all" ] }
-syn = "1"
+syn = "2"
 serde = "1"
 serde_path_to_error = "0.1.15"
 serde_yaml = "0.8.26"
diff --git a/foundations-macros/src/common.rs b/foundations-macros/src/common.rs
index 2fb16a6..67ea47a 100644
--- a/foundations-macros/src/common.rs
+++ b/foundations-macros/src/common.rs
@@ -1,7 +1,9 @@
+use darling::ast::NestedMeta;
+use quote::ToTokens as _;
 use syn::parse::{Parse, ParseStream, Parser};
 use syn::punctuated::Punctuated;
 use syn::spanned::Spanned;
-use syn::{Attribute, NestedMeta, Token};
+use syn::{Attribute, Ident, Token};
 
 pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
 
@@ -34,11 +36,12 @@ where
     T: Parse,
 {
     let parser = |input: ParseStream| {
+        let _name = input.parse::<Ident>()?;
         let _equal_token = input.parse::<Token![=]>()?;
         input.parse::<T>()
     };
 
-    parser.parse2(attr.tokens)
+    parser.parse2(attr.meta.to_token_stream())
 }
 
 #[cfg(test)]
diff --git a/foundations-macros/src/info_metric/mod.rs b/foundations-macros/src/info_metric/mod.rs
index da3fbec..4eaee47 100644
--- a/foundations-macros/src/info_metric/mod.rs
+++ b/foundations-macros/src/info_metric/mod.rs
@@ -1,6 +1,3 @@
-// See: https://github.com/cloudflare/foundations/issues/50
-#![allow(clippy::manual_unwrap_or_default)]
-
 use crate::common::Result;
 use darling::FromMeta;
 use proc_macro::TokenStream;
@@ -8,8 +5,7 @@ use proc_macro2::Span;
 use quote::{quote, ToTokens};
 use syn::punctuated::Punctuated;
 use syn::{
-    parse_macro_input, parse_quote, Attribute, AttributeArgs, Ident, LitStr, Path, Token, Type,
-    Visibility,
+    parse_macro_input, parse_quote, Attribute, Ident, LitStr, Path, Token, Type, Visibility,
 };
 
 mod parsing;
@@ -66,11 +62,8 @@ struct FieldAttrs {
 }
 
 pub(crate) fn expand(args: TokenStream, item: TokenStream) -> TokenStream {
+    let args = parse_macro_input!(args as MacroArgs);
     let mod_ = parse_macro_input!(item as Struct);
-    let args = match MacroArgs::from_list(&parse_macro_input!(args as AttributeArgs)) {
-        Ok(args) => args,
-        Err(e) => return e.write_errors().into(),
-    };
 
     expand_from_parsed(args, mod_)
         .unwrap_or_else(|e| e.to_compile_error())
diff --git a/foundations-macros/src/info_metric/parsing.rs b/foundations-macros/src/info_metric/parsing.rs
index bf26c5f..3d62e6d 100644
--- a/foundations-macros/src/info_metric/parsing.rs
+++ b/foundations-macros/src/info_metric/parsing.rs
@@ -19,6 +19,7 @@ impl Parse for MacroArgs {
         }
 
         let meta_list = parse_meta_list(&input)?;
+
         Ok(Self::from_list(&meta_list)?)
     }
 }
@@ -30,9 +31,11 @@ impl Parse for Struct {
             let mut doc = "".to_owned();
 
             for attr in attrs {
-                if attr.path.is_ident("cfg") {
+                let path = attr.path();
+
+                if path.is_ident("cfg") {
                     cfg.push(attr);
-                } else if attr.path.is_ident("doc") {
+                } else if path.is_ident("doc") {
                     doc.push_str(&parse_attr_value::<LitStr>(attr)?.value());
                 } else {
                     return error(&attr, STRUCT_ATTR_ERROR);
@@ -76,13 +79,15 @@ impl Parse for Field {
             let mut attrs = FieldAttrs::default();
 
             for attr in raw_attrs {
-                if attr.path.is_ident("serde") {
+                let path = attr.path();
+
+                if path.is_ident("serde") {
                     if attrs.serde.is_some() {
                         return error(&attr, DUPLICATE_SERDE_ATTR_ERROR);
                     }
 
                     attrs.serde = Some(attr);
-                } else if attr.path.is_ident("serde_as") {
+                } else if path.is_ident("serde_as") {
                     if attrs.serde_as.is_some() {
                         return error(&attr, DUPLICATE_SERDE_AS_ATTR_ERROR);
                     }
diff --git a/foundations-macros/src/metrics/mod.rs b/foundations-macros/src/metrics/mod.rs
index eb404af..92c7645 100644
--- a/foundations-macros/src/metrics/mod.rs
+++ b/foundations-macros/src/metrics/mod.rs
@@ -4,8 +4,8 @@ use proc_macro2::Span;
 use quote::{quote, ToTokens};
 use syn::punctuated::Punctuated;
 use syn::{
-    parse_macro_input, parse_quote, Attribute, AttributeArgs, ExprStruct, Ident, LitStr, Path,
-    Token, Type, Visibility,
+    parse_macro_input, parse_quote, Attribute, ExprStruct, Ident, LitStr, Path, Token, Type,
+    Visibility,
 };
 
 mod parsing;
@@ -77,11 +77,8 @@ enum ArgMode {
 }
 
 pub(crate) fn expand(args: TokenStream, item: TokenStream) -> TokenStream {
+    let args = parse_macro_input!(args as MacroArgs);
     let mod_ = parse_macro_input!(item as Mod);
-    let args = match MacroArgs::from_list(&parse_macro_input!(args as AttributeArgs)) {
-        Ok(args) => args,
-        Err(e) => return e.write_errors().into(),
-    };
 
     expand_from_parsed(args, mod_).into()
 }
diff --git a/foundations-macros/src/metrics/parsing.rs b/foundations-macros/src/metrics/parsing.rs
index b01928f..60b8961 100644
--- a/foundations-macros/src/metrics/parsing.rs
+++ b/foundations-macros/src/metrics/parsing.rs
@@ -1,6 +1,7 @@
 use super::{ArgAttrs, ArgMode, FnArg, FnAttrs, ItemFn, MacroArgs, Mod};
 use crate::common::{error, parse_attr_value, parse_meta_list, Result};
 use darling::FromMeta;
+use quote::ToTokens as _;
 use syn::parse::{Parse, ParseStream};
 use syn::punctuated::Punctuated;
 use syn::{
@@ -28,6 +29,7 @@ impl Parse for MacroArgs {
         }
 
         let meta_list = parse_meta_list(&input)?;
+
         Ok(Self::from_list(&meta_list)?)
     }
 }
@@ -65,25 +67,31 @@ impl Parse for ItemFn {
             let mut optional = None;
 
             for attr in attrs {
-                if attr.path.is_ident("cfg") {
+                let path = attr.path();
+
+                if path.is_ident("cfg") {
                     cfg.push(attr);
-                } else if attr.path.is_ident("doc") {
+                } else if path.is_ident("doc") {
                     doc.push_str(&parse_attr_value::<LitStr>(attr)?.value());
-                } else if attr.path.is_ident("ctor") {
+                } else if path.is_ident("ctor") {
                     if ctor.is_some() {
                         return error(&attr, DUPLICATE_CTOR_ATTR_ERROR);
                     }
 
                     ctor = Some(parse_attr_value(attr)?);
-                } else if attr.path.is_ident("optional") {
+                } else if path.is_ident("optional") {
                     if optional.is_some() {
                         return error(&attr, DUPLICATE_OPTIONAL_ATTR_ERROR);
                     }
 
-                    if attr.tokens.is_empty() {
+                    if attr.to_token_stream().is_empty() {
                         optional = Some(true);
                     } else {
-                        optional = Some(parse_attr_value::<LitBool>(attr)?.value);
+                        optional = Some(
+                            parse_attr_value::<LitBool>(attr)
+                                .map(|l| l.value)
+                                .unwrap_or(true),
+                        );
                     }
                 } else {
                     return error(&attr, FN_ATTR_ERROR);
@@ -138,13 +146,15 @@ impl Parse for FnArg {
             let mut attrs = ArgAttrs::default();
 
             for attr in raw_attrs {
-                if attr.path.is_ident("serde") {
+                let path = attr.path();
+
+                if path.is_ident("serde") {
                     if attrs.serde.is_some() {
                         return error(&attr, DUPLICATE_SERDE_ATTR_ERROR);
                     }
 
                     attrs.serde = Some(attr);
-                } else if attr.path.is_ident("serde_as") {
+                } else if path.is_ident("serde_as") {
                     if attrs.serde_as.is_some() {
                         return error(&attr, DUPLICATE_SERDE_AS_ATTR_ERROR);
                     }
diff --git a/foundations-macros/src/settings.rs b/foundations-macros/src/settings.rs
index b054ec8..b7e311d 100644
--- a/foundations-macros/src/settings.rs
+++ b/foundations-macros/src/settings.rs
@@ -1,12 +1,13 @@
 use crate::common::{error, parse_meta_list, Result};
+use darling::ast::NestedMeta;
 use darling::FromMeta;
 use proc_macro::TokenStream;
 use quote::{quote, quote_spanned, TokenStreamExt};
 use syn::parse::{Parse, ParseStream};
 use syn::spanned::Spanned;
 use syn::{
-    parse_macro_input, parse_quote, Attribute, Field, Fields, Ident, Item, ItemEnum, ItemStruct,
-    Lit, LitStr, Meta, MetaNameValue, NestedMeta, Path,
+    parse_macro_input, parse_quote, Attribute, Expr, ExprLit, Field, Fields, Ident, Item, ItemEnum,
+    ItemStruct, Lit, LitStr, Meta, MetaNameValue, Path,
 };
 
 const ERR_NOT_STRUCT_OR_ENUM: &str = "Settings should be either structure or enum.";
@@ -232,7 +233,7 @@ fn impl_settings_trait_for_field(
     let cfg_attrs = field
         .attrs
         .iter()
-        .filter(|a| a.path.is_ident("cfg"))
+        .filter(|a| a.path().is_ident("cfg"))
         .collect::<Vec<_>>();
 
     impl_for_field.append_all(quote_spanned! { span=>
@@ -265,16 +266,20 @@ fn extract_doc_comments(attrs: &[Attribute]) -> Vec<LitStr> {
     let mut comments = vec![];
 
     for attr in attrs {
-        if !attr.path.is_ident("doc") {
+        if !attr.path().is_ident("doc") {
             continue;
         }
 
-        if let Ok(Meta::NameValue(MetaNameValue {
-            lit: Lit::Str(lit_str),
+        if let Meta::NameValue(MetaNameValue {
+            value:
+                Expr::Lit(ExprLit {
+                    lit: Lit::Str(lit_str),
+                    ..
+                }),
             ..
-        })) = attr.parse_meta()
+        }) = &attr.meta
         {
-            comments.push(lit_str);
+            comments.push(lit_str.clone());
         }
     }
 
@@ -292,7 +297,11 @@ fn impl_serde_aware_default(item: &ItemStruct) -> proc_macro2::TokenStream {
             .expect("should not generate field docs for tuple struct");
 
         let span = field.ty.span();
-        let cfg_attrs = field.attrs.iter().filter(|attr| attr.path.is_ident("cfg"));
+
+        let cfg_attrs = field
+            .attrs
+            .iter()
+            .filter(|attr| attr.path().is_ident("cfg"));
 
         let function_path = get_field_default_fn(field)
             .unwrap_or_else(|| quote_spanned! { span=> Default::default });
@@ -311,15 +320,19 @@ fn impl_serde_aware_default(item: &ItemStruct) -> proc_macro2::TokenStream {
 
 fn get_field_default_fn(field: &Field) -> Option<proc_macro2::TokenStream> {
     for attr in &field.attrs {
-        if !attr.path.is_ident("serde") {
+        if !attr.path().is_ident("serde") {
             continue;
         }
 
-        let Ok(Meta::List(list)) = attr.parse_meta() else {
+        let Meta::List(list) = &attr.meta else {
+            continue;
+        };
+
+        let Ok(nested_meta_list) = NestedMeta::parse_meta_list(list.tokens.clone()) else {
             continue;
         };
 
-        for meta in list.nested {
+        for meta in nested_meta_list {
             let NestedMeta::Meta(Meta::NameValue(mnv)) = meta else {
                 continue;
             };
@@ -328,7 +341,12 @@ fn get_field_default_fn(field: &Field) -> Option<proc_macro2::TokenStream> {
                 continue;
             }
 
-            let Lit::Str(val) = mnv.lit else { continue };
+            let Expr::Lit(ExprLit {
+                lit: Lit::Str(val), ..
+            }) = mnv.value
+            else {
+                continue;
+            };
 
             match val.parse() {
                 Ok(tokens) => return Some(tokens),
diff --git a/foundations-macros/src/span_fn.rs b/foundations-macros/src/span_fn.rs
index 5f8649e..8e5dd5a 100644
--- a/foundations-macros/src/span_fn.rs
+++ b/foundations-macros/src/span_fn.rs
@@ -132,7 +132,7 @@ fn expand_from_parsed(args: Args, item_fn: ItemFn) -> TokenStream2 {
 
 fn try_async_trait_fn_rewrite(args: &Args, body: &Block) -> Option<TokenStream2> {
     let (last_expr_fn_call, last_expr_fn_call_args) = match body.stmts.last()? {
-        Stmt::Expr(Expr::Call(ExprCall { func, args, .. })) => (func, args),
+        Stmt::Expr(Expr::Call(ExprCall { func, args, .. }), ..) => (func, args),
         _ => return None,
     };
 


Test patch: