From 8303beb59040184bb84ff826678a8869ef501157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 6 Mar 2025 19:14:02 +0000 Subject: [PATCH] Support parsing `#![feature(default_field_values)]` - RFC: https://github.com/rust-lang/rfcs/pull/3681 - Tracking issue: https://github.com/rust-lang/rust/issues/132162 - Feature gate: `#![feature(default_field_values)]` ```rust struct Pet { name: Option, age: i128 = 42, // ^^^^ } ``` Fix #1774. --- src/data.rs | 12 ++++++ src/gen/clone.rs | 1 + src/gen/debug.rs | 1 + src/gen/eq.rs | 1 + src/gen/fold.rs | 1 + src/gen/hash.rs | 1 + src/gen/visit.rs | 4 ++ src/gen/visit_mut.rs | 4 ++ src/parse_quote.rs | 9 ++++- syn.json | 12 ++++++ tests/debug/gen.rs | 14 +++++++ tests/test_item.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 151 insertions(+), 1 deletion(-) diff --git a/src/data.rs b/src/data.rs index 96db2a0b7c..7b25a90766 100644 --- a/src/data.rs +++ b/src/data.rs @@ -196,6 +196,11 @@ ast_struct! { pub colon_token: Option, pub ty: Type, + + /// Default value: `field_name: i32 = 1` + /// + /// `#![feature(default_field_values)]` + pub default: Option<(Token![=], Expr)>, } } @@ -345,6 +350,11 @@ pub(crate) mod parsing { } else { input.parse()? }; + let mut default: Option<(Token![=], Expr)> = None; + if input.peek(Token![=]) { + let eq_token: Token![=] = input.parse()?; + default = Some((eq_token, input.parse()?)); + } Ok(Field { attrs, @@ -353,6 +363,7 @@ pub(crate) mod parsing { ident: Some(ident), colon_token: Some(colon_token), ty, + default, }) } @@ -366,6 +377,7 @@ pub(crate) mod parsing { ident: None, colon_token: None, ty: input.parse()?, + default: None, }) } } diff --git a/src/gen/clone.rs b/src/gen/clone.rs index be2b698422..ae1a854ebb 100644 --- a/src/gen/clone.rs +++ b/src/gen/clone.rs @@ -789,6 +789,7 @@ impl Clone for crate::Field { ident: self.ident.clone(), colon_token: self.colon_token.clone(), ty: self.ty.clone(), + default: self.default.clone(), } } } diff --git a/src/gen/debug.rs b/src/gen/debug.rs index aa42e32c60..924c27eab3 100644 --- a/src/gen/debug.rs +++ b/src/gen/debug.rs @@ -1205,6 +1205,7 @@ impl Debug for crate::Field { formatter.field("ident", &self.ident); formatter.field("colon_token", &self.colon_token); formatter.field("ty", &self.ty); + formatter.field("default", &self.default); formatter.finish() } } diff --git a/src/gen/eq.rs b/src/gen/eq.rs index 128e8991ee..b9fdc788cb 100644 --- a/src/gen/eq.rs +++ b/src/gen/eq.rs @@ -768,6 +768,7 @@ impl PartialEq for crate::Field { self.attrs == other.attrs && self.vis == other.vis && self.mutability == other.mutability && self.ident == other.ident && self.colon_token == other.colon_token && self.ty == other.ty + && self.default == other.default } } #[cfg(any(feature = "derive", feature = "full"))] diff --git a/src/gen/fold.rs b/src/gen/fold.rs index 6ad62945e9..e12949b422 100644 --- a/src/gen/fold.rs +++ b/src/gen/fold.rs @@ -1960,6 +1960,7 @@ where ident: (node.ident).map(|it| f.fold_ident(it)), colon_token: node.colon_token, ty: f.fold_type(node.ty), + default: (node.default).map(|it| ((it).0, f.fold_expr((it).1))), } } #[cfg(any(feature = "derive", feature = "full"))] diff --git a/src/gen/hash.rs b/src/gen/hash.rs index 04f23453a1..837bf99ab1 100644 --- a/src/gen/hash.rs +++ b/src/gen/hash.rs @@ -1022,6 +1022,7 @@ impl Hash for crate::Field { self.ident.hash(state); self.colon_token.hash(state); self.ty.hash(state); + self.default.hash(state); } } #[cfg(any(feature = "derive", feature = "full"))] diff --git a/src/gen/visit.rs b/src/gen/visit.rs index b82f2750c5..07fa904c40 100644 --- a/src/gen/visit.rs +++ b/src/gen/visit.rs @@ -2011,6 +2011,10 @@ where } skip!(node.colon_token); v.visit_type(&node.ty); + if let Some(it) = &node.default { + skip!((it).0); + v.visit_expr(&(it).1); + } } #[cfg(any(feature = "derive", feature = "full"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "derive", feature = "full"))))] diff --git a/src/gen/visit_mut.rs b/src/gen/visit_mut.rs index 72570094df..8b8da31a04 100644 --- a/src/gen/visit_mut.rs +++ b/src/gen/visit_mut.rs @@ -1929,6 +1929,10 @@ where } skip!(node.colon_token); v.visit_type_mut(&mut node.ty); + if let Some(it) = &mut node.default { + skip!((it).0); + v.visit_expr_mut(&mut (it).1); + } } #[cfg(any(feature = "derive", feature = "full"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "derive", feature = "full"))))] diff --git a/src/parse_quote.rs b/src/parse_quote.rs index 2db20597c4..5eb854fa47 100644 --- a/src/parse_quote.rs +++ b/src/parse_quote.rs @@ -149,7 +149,7 @@ impl ParseQuote for T { use crate::punctuated::Punctuated; #[cfg(any(feature = "full", feature = "derive"))] -use crate::{attr, Attribute, Field, FieldMutability, Ident, Type, Visibility}; +use crate::{attr, Attribute, Expr, Field, FieldMutability, Ident, Type, Visibility}; #[cfg(feature = "full")] use crate::{Arm, Block, Pat, Stmt}; @@ -194,6 +194,12 @@ impl ParseQuote for Field { let ty: Type = input.parse()?; + let mut default: Option<(Token![=], Expr)> = None; + if is_named && input.peek(Token![=]) { + let eq_token: Token![=] = input.parse()?; + default = Some((eq_token, input.parse()?)); + } + Ok(Field { attrs, vis, @@ -201,6 +207,7 @@ impl ParseQuote for Field { ident, colon_token, ty, + default, }) } } diff --git a/syn.json b/syn.json index 8bff6dc543..e1a82471ab 100644 --- a/syn.json +++ b/syn.json @@ -2010,6 +2010,18 @@ }, "ty": { "syn": "Type" + }, + "default": { + "option": { + "tuple": [ + { + "token": "Eq" + }, + { + "syn": "Expr" + } + ] + } } } }, diff --git a/tests/debug/gen.rs b/tests/debug/gen.rs index d766a8e300..876541d27b 100644 --- a/tests/debug/gen.rs +++ b/tests/debug/gen.rs @@ -1777,6 +1777,20 @@ impl Debug for Lite { formatter.field("colon_token", &Present); } formatter.field("ty", Lite(&self.value.ty)); + if let Some(val) = &self.value.default { + #[derive(RefCast)] + #[repr(transparent)] + struct Print((syn::token::Eq, syn::Expr)); + impl Debug for Print { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Some(")?; + Debug::fmt(Lite(&self.0.1), formatter)?; + formatter.write_str(")")?; + Ok(()) + } + } + formatter.field("default", Print::ref_cast(val)); + } formatter.finish() } } diff --git a/tests/test_item.rs b/tests/test_item.rs index 0a12b7aadc..fe6e01511d 100644 --- a/tests/test_item.rs +++ b/tests/test_item.rs @@ -274,6 +274,98 @@ fn test_impl_visibility() { snapshot!(tokens as Item, @"Item::Verbatim(`pub default unsafe impl union { }`)"); } +#[test] +fn test_struct_default_field_values() { + let tokens = quote! { + struct Foo { + field: i32 = const { 42 }, + } + }; + snapshot!(tokens as Item, @r#" + Item::Struct { + vis: Visibility::Inherited, + ident: "Foo", + generics: Generics, + fields: Fields::Named { + named: [ + Field { + vis: Visibility::Inherited, + ident: Some("field"), + colon_token: Some, + ty: Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "i32", + }, + ], + }, + }, + default: Some(Expr::Const { + block: Block { + stmts: [ + Stmt::Expr( + Expr::Lit { + lit: 42, + }, + None, + ), + ], + }, + }), + }, + Token![,], + ], + }, + } + "#); +} + +#[test] +fn test_enum_default_field_values() { + let tokens = quote! { + enum Foo { + Bar { + field: i32 = 42, + } + } + }; + snapshot!(tokens as Item, @r#" + Item::Enum { + vis: Visibility::Inherited, + ident: "Foo", + generics: Generics, + variants: [ + Variant { + ident: "Bar", + fields: Fields::Named { + named: [ + Field { + vis: Visibility::Inherited, + ident: Some("field"), + colon_token: Some, + ty: Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "i32", + }, + ], + }, + }, + default: Some(Expr::Lit { + lit: 42, + }), + }, + Token![,], + ], + }, + }, + ], + } + "#); +} + #[test] fn test_impl_type_parameter_defaults() { #[cfg(any())]