Repository navigation
Clearer errors for keyword field names and Enum! variant fields - #277
Merged
Merged
Conversation
A keyword written as a shape's field name, as in `Struct! { type: u8 }`,
was read as a positional entry and failed in syn's type parser with a
message listing every token a type may start with. It now reports
"`type` is a keyword: write the field name as `r#type`", or that the
name cannot be a field for `self`, `Self`, `super`, and `crate`.
`Enum!` variant fields now go through `parse_variant_fields`, the loop
that reads a `Struct!` body with the form fixed by the variant's
delimiter. A literal or a keyword name inside a variant gets the same
message as in a `Struct!` body, and an entry that contradicts the
delimiter, such as `V { u8 }` or `V(a: u8)`, says which form the
brackets require. Empty `V()` and `V {}` keep their delimiter's form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR improves the error messages of the
Struct!andEnum!shape macros (#276) for threemistakes a user is likely to make. A keyword used as a field name now gets a message that says so
and suggests the raw form. A field inside an
Enum!variant now goes through exactly the checksand messages a
Struct!field does. A variant whose fields contradict its brackets says which formthe brackets require. No input that compiled before changes its meaning. Every case below was
already rejected, and only the message changes.
Background: what the shape macros do
Struct!andEnum!write the type-level shape of a struct or enum as the body of itsdeclaration. A shape is the
Fieldstype that#[derive(HasFields)]generates: a list ofFieldentries, one per field or variant, each tagged by its name. Generic code names a shape in bounds
such as
T: FromFields<Fields = …>, and the macros let that code write the shape in the form aRust programmer already reads:
Each macro reads its body much as
rustcreads a declaration, then rejects the parts a shape hasno use for (attributes, visibility,
_, duplicate names, discriminants) with its own message. ThisPR closes three gaps in that parsing.
What changes for the user
A keyword field name says what is wrong. A keyword is not an identifier, so
Struct!readtype: u8as a positional entry, andtypethen failed to parse as a type. The message listedevery token a type may start with and never mentioned the name. A keyword that has no raw form,
such as
self, failed with an unrelated "expected,".Struct! { type: u8 }expected one of: `for`, parentheses, `fn`, …`type` is a keyword: write the field name as `r#type`Struct! { self: u8 }expected `,``self` cannot be a field nameself,Self,super, andcrateget the second message, sincer#selfand the others are notvalid Rust.
An
Enum!variant's fields follow theStruct!rules, with the same messages.Enum!read avariant's fields with
syn's own struct-body parsers, which skip the literal and keyword checksthat
Struct!runs. The same mistake therefore got a different message inside a variant:Enum! { V(1) }expected one of: `for`, parentheses, `fn`, …expected a type: a type-level shape lists field types, not valuesEnum! { V { a: 1 } }expected one of: `for`, parentheses, `fn`, …expected a type: a type-level shape lists field types, not valuesEnum! { V { type: u8 } }expected identifier, found keyword `type``type` is a keyword: write the field name as `r#type`A variant whose fields contradict its brackets says which form the brackets require. Inside
Enum!, a variant's brackets keep their Rust meaning: braces hold named fields and parentheses holdbare types. This differs from the brackets around the whole
Struct!orEnum!invocation, which aprocedural macro cannot see and which therefore do not matter. A mismatch used to surface as a
missing punctuation token:
Enum! { V { u8 } }expected `:`a variant in braces holds named fields: write each entry as `name: Type`Enum! { V(a: u8) }expected `,`a variant in parentheses holds bare types: write named fields in braces, as in `Variant { name: Type }`How it works
The changes are in
functions/shape/parse_fields.rs,with a small change to
parse_variantintypes/shape/enum_type.rs.Keyword detection sits in the entry classifier.
peek_named_entrydecides whether an entry isname: Typeor a bare type before parsing it. Itspeek(Ident)test, whichsyndefines toexclude keywords, stays as it was. When that test fails, the classifier now also peeks with
Ident::peek_any, and a keyword followed by a single:(not::) is reported at the keyword'sspan. The existing checks are unchanged. A raw identifier still counts as
a name,
_still reaches its own rejection, and a path type such ascrate::FooorSelf::Associs still a positional field, because its
:begins a::.Enum!variants now share theStruct!loop. The loop that reads aStruct!body is factoredinto a private
parse_entries, behind two entry points:parse_shape_fieldsreads aStruct!body. The first entry decides the form, and a later entry ofthe other form is reported as mixed, exactly as before.
parse_variant_fieldsreads the inside of a variant's braces or parentheses. The delimiter fixesthe form, and an entry of the other form is reported against that delimiter.
Every entry in both paths goes through
peek_named_entryand thenvalidate_shape_fields, so afield rule and its message cannot differ between a struct body and a variant. An empty variant list
keeps its delimiter's form (
V()parses as empty positional fields,V {}as empty named fields),as
syn's parsers did before. All three spellings,V,V(), andV {}, still encode to the sameNilpayload.