From f2e874d404c67aafb9665d9471dbc903c54b5f23 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 18 Jul 2026 10:25:10 -0700 Subject: [PATCH] Parse field default values --- src/data.rs | 38 +++++++++++++++++++++++++++++++++++--- 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 | 11 ++++++++++- syn.json | 12 ++++++++++++ tests/debug/gen.rs | 14 ++++++++++++++ tests/repo/mod.rs | 27 +-------------------------- 12 files changed, 85 insertions(+), 30 deletions(-) diff --git a/src/data.rs b/src/data.rs index 14e63f3605..1e73f7ecb0 100644 --- a/src/data.rs +++ b/src/data.rs @@ -200,6 +200,8 @@ ast_struct! { pub colon_token: Option, pub ty: Type, + + pub default: Option<(Token![=], Expr)>, } } @@ -380,6 +382,14 @@ pub(crate) mod parsing { input.parse()? }; + let default = if input.peek(Token![=]) { + let eq_token: Token![=] = input.parse()?; + let expr: Expr = input.parse()?; + Some((eq_token, expr)) + } else { + None + }; + Ok(Field { attrs, vis, @@ -387,19 +397,37 @@ pub(crate) mod parsing { ident: Some(ident), colon_token: Some(colon_token), ty, + default, }) } /// Parses an unnamed (tuple struct) field. #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_unnamed(input: ParseStream) -> Result { + let attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let ty: Type = input.parse()?; + + if input.peek(Token![=]) { + input.parse::()?; + let start_span = input.span(); + input.parse::()?; + let end_span = input.cursor().prev_span(); + return Err(crate::error::new2( + start_span, + end_span, + "field default value is only supported in structs with named fields", + )); + } + Ok(Field { - attrs: input.call(Attribute::parse_outer)?, - vis: input.parse()?, + attrs, + vis, modifiers: FieldModifiers {}, ident: None, colon_token: None, - ty: input.parse()?, + ty, + default: None, }) } } @@ -453,6 +481,10 @@ mod printing { TokensOrDefault(&self.colon_token).to_tokens(tokens); } self.ty.to_tokens(tokens); + if let Some((eq_token, default)) = &self.default { + eq_token.to_tokens(tokens); + default.to_tokens(tokens); + } } } } diff --git a/src/gen/clone.rs b/src/gen/clone.rs index 0679a641c2..ecf57681ff 100644 --- a/src/gen/clone.rs +++ b/src/gen/clone.rs @@ -790,6 +790,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 7f4d7177b5..376a65fce4 100644 --- a/src/gen/debug.rs +++ b/src/gen/debug.rs @@ -1208,6 +1208,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 71f14634b5..2b48062394 100644 --- a/src/gen/eq.rs +++ b/src/gen/eq.rs @@ -782,6 +782,7 @@ impl PartialEq for crate::Field { self.attrs == other.attrs && self.vis == other.vis && self.modifiers == other.modifiers && 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 10fd3a058b..df3a4554fe 100644 --- a/src/gen/fold.rs +++ b/src/gen/fold.rs @@ -1949,6 +1949,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(feature = "full")] diff --git a/src/gen/hash.rs b/src/gen/hash.rs index 48a9958eb1..81279eb2d3 100644 --- a/src/gen/hash.rs +++ b/src/gen/hash.rs @@ -1027,6 +1027,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 9c859a6940..81633d16d0 100644 --- a/src/gen/visit.rs +++ b/src/gen/visit.rs @@ -1987,6 +1987,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(feature = "full")] #[cfg_attr(docsrs, doc(cfg(feature = "full")))] diff --git a/src/gen/visit_mut.rs b/src/gen/visit_mut.rs index bcc7d69a59..458cc7508d 100644 --- a/src/gen/visit_mut.rs +++ b/src/gen/visit_mut.rs @@ -1911,6 +1911,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(feature = "full")] #[cfg_attr(docsrs, doc(cfg(feature = "full")))] diff --git a/src/parse_quote.rs b/src/parse_quote.rs index 905f234a07..a6c3f97e4d 100644 --- a/src/parse_quote.rs +++ b/src/parse_quote.rs @@ -154,7 +154,7 @@ impl ParseQuote for T { use crate::punctuated::Punctuated; #[cfg(any(feature = "full", feature = "derive"))] -use crate::{attr, Attribute, Field, FieldModifiers, Ident, Type, Visibility}; +use crate::{attr, Attribute, Expr, Field, FieldModifiers, Ident, Type, Visibility}; #[cfg(feature = "full")] use crate::{Arm, Block, Pat, Safety, Stmt}; @@ -199,6 +199,14 @@ impl ParseQuote for Field { let ty: Type = input.parse()?; + let default = if is_named && input.peek(Token![=]) { + let eq_token: Token![=] = input.parse()?; + let expr: Expr = input.parse()?; + Some((eq_token, expr)) + } else { + None + }; + Ok(Field { attrs, vis, @@ -206,6 +214,7 @@ impl ParseQuote for Field { ident, colon_token, ty, + default, }) } } diff --git a/syn.json b/syn.json index 473913f6b4..e5a5cbac58 100644 --- a/syn.json +++ b/syn.json @@ -1974,6 +1974,18 @@ }, "ty": { "syn": "Type" + }, + "default": { + "option": { + "tuple": [ + { + "token": "Eq" + }, + { + "syn": "Expr" + } + ] + } } } }, diff --git a/tests/debug/gen.rs b/tests/debug/gen.rs index 46059e2514..3b56cdb83d 100644 --- a/tests/debug/gen.rs +++ b/tests/debug/gen.rs @@ -1727,6 +1727,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/repo/mod.rs b/tests/repo/mod.rs index ca9a647f4d..a0f2755213 100644 --- a/tests/repo/mod.rs +++ b/tests/repo/mod.rs @@ -64,6 +64,7 @@ static EXCLUDE_FILES: &[&str] = &[ "tests/ui/consts/trait_alias_method_call.rs", "tests/ui/generic-const-items/const-trait-impl.rs", "tests/ui/parser/impls-nested-within-fns-semantic-1.rs", + "tests/ui/structs/default-field-values/support.rs", "tests/ui/traits/const-traits/assoc-type-const-bound-usage-0.rs", "tests/ui/traits/const-traits/assoc-type-const-bound-usage-1.rs", "tests/ui/traits/const-traits/auxiliary/associated-const-stability.rs", @@ -232,32 +233,6 @@ static EXCLUDE_FILES: &[&str] = &[ "tests/ui/pattern/rfc-3637-guard-patterns/only-gather-locals-once.rs", "tests/ui/reachable/guard_read_for_never.rs", - // TODO: struct field default: `struct S { field: i32 = 1 }` - // https://github.com/dtolnay/syn/issues/1774 - "compiler/rustc_ast_lowering/src/delegation/generics.rs", - "compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs", - "compiler/rustc_errors/src/markdown/parse.rs", - "compiler/rustc_hir/src/attrs/diagnostic.rs", - "compiler/rustc_hir_analysis/src/hir_wf_check.rs", - "compiler/rustc_middle/src/hir/map.rs", - "compiler/rustc_middle/src/ty/mod.rs", - "compiler/rustc_parse/src/parser/mod.rs", - "compiler/rustc_parse/src/parser/stmt.rs", - "compiler/rustc_privacy/src/lib.rs", - "compiler/rustc_resolve/src/imports.rs", - "compiler/rustc_resolve/src/lib.rs", - "compiler/rustc_session/src/config.rs", - "compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs", - "src/tools/clippy/tests/ui/exhaustive_items.rs", - "src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/record_field_default_values.rs", - "src/tools/rustfmt/tests/source/default-field-values.rs", - "src/tools/rustfmt/tests/target/default-field-values.rs", - "tests/ui/structs/default-field-values/auxiliary/struct_field_default.rs", - "tests/ui/structs/default-field-values/const-trait-default-field-value.rs", - "tests/ui/structs/default-field-values/field-references-param.rs", - "tests/ui/structs/default-field-values/support.rs", - "tests/ui/structs/default-field-values/use-normalized-ty-for-default-struct-value.rs", - // TODO: final associated functions: `final fn` // https://github.com/dtolnay/syn/issues/1981 "library/core/src/io/size_hint.rs",