Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ pub(crate) mod parsing {

let colon_token: Token![:] = input.parse()?;

let type_start = input.fork();
let ty: Type = if unnamed_field
&& (input.peek(Token![struct])
|| input.peek(Token![union]) && input.peek2(token::Brace))
Expand All @@ -346,6 +347,19 @@ pub(crate) mod parsing {
input.parse()?
};

let ty = if input.peek(Token![=]) {
// We don't support parsing default_field_values, a (currently unstable) feature (see https://github.com/rust-lang/rfcs/pull/3681)
// because `Field` is not `non_exhaustive`, and so can't have new fields added to it.
// We know that this isn't the correct semantics; the default field value is not part of the type.
// However, for parsing purposes, it is in type position.
// We use a similar solution for negative inherent impls, e.g. `impl !SomeType {}`
input.parse::<Token![=]>()?;
input.parse::<Expr>()?;
Type::Verbatim(verbatim::between(&type_start, input))
} else {
ty
};

Ok(Field {
attrs,
vis,
Expand Down
2 changes: 2 additions & 0 deletions src/parse_quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ impl ParseQuote for Field {

let ty: Type = input.parse()?;

// TODO: Once `Field` supports default_field_values, parse them here

Ok(Field {
attrs,
vis,
Expand Down
61 changes: 61 additions & 0 deletions tests/test_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,67 @@ 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::Verbatim(`i32 = const { 42 }`),
},
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::Verbatim(`i32 = 42`),
Copy link

@Veetaha Veetaha Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a bearable hack. Although I do forsee that existing proc macros will still break if they encounter the default field value syntax, this makes it easier to hackily get the default expression from the type for macros that want to start supporting this syntax (and I would be interested in that too with bon).

An alternative way to approach this could be to add a new syn::Item variant with versioning applied e.g. syn::Item::StructV2(StructV2) where StructV2 is the same as the current Struct but with the additional default field. Although, this would also necessitate the FieldV2 struct and bloating the crate with lots of similar/but-slightly-different types. This idea seems crazy, but IDK, just wanted to state it here, and maybe someone could be inspired with it

},
Token![,],
],
},
},
],
}
"#);
}

#[test]
fn test_impl_type_parameter_defaults() {
#[cfg(any())]
Expand Down