macros: add support for exhaustive attribute - #5390
Conversation
exhaustive attrexhaustive attribute
8c61548 to
5a738cb
Compare
5a738cb to
f11b352
Compare
|
Could you base this on top of #5372? It looks like there may be some overlap. Feel free to leave a review there as well, if you have any suggestions. |
f11b352 to
f38fc87
Compare
This comment has been minimized.
This comment has been minimized.
|
@rustbot author |
|
Reminder, once the PR becomes ready for a review, use |
72651eb to
f596ba7
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
f596ba7 to
8a61e3e
Compare
This comment has been minimized.
This comment has been minimized.
|
@rustbot ready |
e2d5adb to
a58d229
Compare
a58d229 to
d492384
Compare
| /// Emit a struct with the given derive attributes plus a generated `Default` impl. | ||
| /// Emit a struct with the given derive attributes plus a generated `Default` | ||
| /// impl. Ensure that the record has an additional private field added to | ||
| /// replicate `#[non_exhaustive]`, unless it is annotated with `#[exhaustive]`. | ||
| /// | ||
| /// Fields default to `Default::default()`. A field whose default can't be derived must carry | ||
| /// `#[custom_default(EXPR)]` as its *first* attribute, and `EXPR` is used instead. | ||
| /// Fields default to `Default::default()`. A field whose default can't be | ||
| /// derived must carry `#[custom_default(EXPR)]` as its *first* attribute, and | ||
| /// `EXPR` is used instead. | ||
| /// | ||
| /// This works by scanning each field for `#[custom_default]` attributes. If one exists, the | ||
| /// attribute's contents are added to `processed_field_defaults` and will be used in the expansion | ||
| /// for `Default`. If it does not exist, `Default::default()` is used instead. In either case, the | ||
| /// field is added to `processed_fields` with `#[custom_default]` stripped if necessary, and | ||
| /// This works by scanning each field for `#[custom_default]` attributes. If one | ||
| /// exists, the attribute's contents are added to `processed_field_defaults` and | ||
| /// will be used in the expansion for `Default`. If it does not exist, | ||
| /// `Default::default()` is used instead. In either case, the field is added to | ||
| /// `processed_fields` with `#[custom_default]` stripped if necessary, and | ||
| /// `struct_with_default` is invoked again with the remaining fields. | ||
| /// | ||
| /// Attributes are split into `cfg_attrs` and `other_attrs` before the fields are scanned. Both | ||
| /// go on the struct, but only the `cfg`s are repeated on the `Default` impl. A `cfg` decides | ||
| /// whether the type exists at all, so without it a configured-out struct leaves an impl behind | ||
| /// referring to a type that isn't there. | ||
| /// Attributes are split into `cfg_attrs` and `other_attrs` before the fields | ||
| /// are scanned. Both go on the struct, but only the `cfg`s are repeated on the | ||
| /// `Default` impl. A `cfg` decides whether the type exists at all, so without | ||
| /// it a configured-out struct leaves an impl behind referring to a type that | ||
| /// isn't there. | ||
| /// | ||
| /// Out of `other_attrs`, we scan for `#[exhaustive]`. If found, we remove it | ||
| /// but take into account that the record should be expanded _without_ an | ||
| /// additional private field. The scan for both `cfg` attributes and the | ||
| /// (non-existent) `exhaustive` attribute is done in one linear pass. |
There was a problem hiding this comment.
Looks like some autoformatting got you, seems like this was rewrapped from 100 to 80.
There was a problem hiding this comment.
Nay, this one was fully intentional. I decided to go for this one
because I saw some other areas of libc that don't fully comply with the
80 columns for comments, and 100 columns for doc comments style guide.
There was a problem hiding this comment.
What style guide is that? If it suggests doc comments are 100 then this moves away from that.
In any case, I'd prefer to leave as-is.
| pub struct timespec { | ||
| pub tv_sec: time_t, | ||
| pub tv_nsec: c_long, | ||
| } |
There was a problem hiding this comment.
Could you mark this one #[exhaustive]? timespec shouldn't ever get new fields.
There was a problem hiding this comment.
Same thing for the structs in this file that don't have private fields. We can probably remove them at some point, feel free to add a FIXME, but we should keep the breakage smaller.
Do this in the first commit (adding non-exhaustive/exhaustive) to avoid changing this in one patch then restoring behavior after.
| cfg_attrs: { } | ||
| other_attrs: { } | ||
| remaining_attrs: { $($attrs)* $(#$attr)* } | ||
| found_exhaustive: { false } |
There was a problem hiding this comment.
Nit: either call this found_exhaustive_attr, or needs_non_exhaustive_field and flip the logic
| ( | ||
| found_exhaustive: { false }, | ||
| expansion: { decl }, | ||
| body: { $(#[$attr:meta])* $vis:vis $name:ident { $($field:tt)* } } | ||
| ) => { | ||
| $(#[$attr])* | ||
| $vis struct $name { $($field)* __non_exhaustive: () } | ||
| }; | ||
| ( | ||
| found_exhaustive: { false }, | ||
| expansion: { default_impl }, | ||
| body: { $($field_default:tt)* } | ||
| ) => { | ||
| Self { $($field_default)* __non_exhaustive: () } | ||
| }; | ||
|
|
||
| ( | ||
| found_exhaustive: { true }, | ||
| expansion: { decl }, | ||
| body: { $(#[$attr:meta])* $vis:vis $name:ident { $($field:tt)* } } | ||
| ) => { | ||
| $(#[$attr])* | ||
| $vis struct $name { $($field)* } | ||
| }; | ||
| ( | ||
| found_exhaustive: { true }, | ||
| expansion: { default_impl }, | ||
| body: { $($field_default:tt)* } | ||
| ) => { | ||
| Self { $($field_default)* } | ||
| }; |
There was a problem hiding this comment.
Nit: match simple statements without the braces so it doesn't look like something is getting grouped
found_exhaustive: true,
expansion: default_impl,| finalize_exhaustiveness! { | ||
| found_exhaustive: { $found_exhaustive }, | ||
| expansion: { decl }, | ||
| body: { | ||
| $($other_attrs)* | ||
| $($cfg_attrs)* | ||
| $vis $name { $($processed_fields)* } | ||
| } | ||
| } | ||
|
|
||
| $($cfg_attrs)* | ||
| // The impl names the type and its fields, which warns if either is deprecated. | ||
| // The impl names the type and its fields, which warns if either is | ||
| // deprecated. | ||
| #[allow(deprecated)] | ||
| impl ::core::default::Default for $name { | ||
| // Field attributes (`#[cfg]`, doc comments) get forwarded to the initializer too. | ||
| // Docs are harmless there but trip the lint, so silence it. | ||
| // Field attributes (`#[cfg]`, doc comments) get forwarded to the | ||
| // initializer too. Docs are harmless there but trip the lint, so | ||
| // silence it. | ||
| #[allow(unused_doc_comments)] | ||
| fn default() -> Self { | ||
| Self { $($processed_field_defaults)* } | ||
| finalize_exhaustiveness! { | ||
| found_exhaustive: { $found_exhaustive }, | ||
| expansion: { default_impl }, | ||
| body: { $($processed_field_defaults)* } | ||
| } |
There was a problem hiding this comment.
Rather than use the same finalize_exhaustiveness macro for the expansion and default impl, split into to macros like emit_struct_definition and emit_struct_default_body to make things a bit easier to follow.
d492384 to
ccf62b2
Compare
- Modify `struct_with_default` to automatically add a private field to all records such that they are always built field-by-field in downstream crates. - Add support for `exhaustive` custom attribute to opt out of having the record have a `__non_exhaustive` field added to it. - Annotate records without private fields under `src/new/helenos/time.rs` and `src/new/linux_uapi/linux/can.rs` with `exhaustive` attribute.
Add tests to ensure two things about the private field added to enforce non-exhaustiveness in records declared within `s_with_default` and `s_no_extra_traits_with_default`. - Ensure the field is added only if the `exhaustive` attribute is not found while munching the annotated attributes of the record item. - Ensure the `exhaustive` attribute also works when it appears between other sets of attributes.
- Rename `s_with_default`, `s_no_extra_traits_with_default` and `struct_with_default` to `s2`, `s_no_extra_traits2`, and `custom_struct`. These macros now both add a `Default` impl and a private field to enforce non-exhaustiveness. - Rewrap comments and doc comments to follow 80 and 100 columns guides.
ccf62b2 to
dc74109
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
Description
Attempts to take the first steps in closing #4080. This should
allow us to add a private field for each of the records declared within
s_with_defaultors_no_extra_traits_with_default.This should in turn enforce those records to be initialized field-by-field in
downstream crates, while avoiding the lint that currently pops up when using a
structmarkednon_exhaustivein FFI contexts.Checklist
libc-test/semverhave been updated*LASTor*MAXhave the standarddoc comment
cargo test -p libc-test --target mytarget); especiallyrelevant for platforms that may not be checked in CI
@rustbot label +stable-nominated