internal: add tuple struct support in pin_data and pin_init! (V2) - #155
internal: add tuple struct support in pin_data and pin_init! (V2)#155mqqz wants to merge 4 commits into
pin_data and pin_init! (V2)#155Conversation
c9dab99 to
1f2c279
Compare
1f2c279 to
2d2042e
Compare
| fn member_ident(member: &Member) -> Ident { | ||
| match member { | ||
| Member::Named(ident) => ident.clone(), | ||
| Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"), | ||
| } | ||
| } | ||
|
|
||
| fn member_display_name(member: &Member) -> String { | ||
| match member { | ||
| Member::Named(ident) => format!("`{ident}`"), | ||
| Member::Unnamed(Index { index, .. }) => format!("index `{index}`"), | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
These should be methods of FieldInfo
| )); | ||
| field_projections.push(quote!(#binding,)); | ||
| } | ||
| field_bindings.push(quote!(ref mut #binding,)); |
There was a problem hiding this comment.
Any reason to use this instead of .0, .1, etc?
There was a problem hiding this comment.
I agree; direct .0, .1 is nicer and more reasonable. Will fix
| #ty, | ||
| >, | ||
| }); | ||
| field_values.push(quote! { |
There was a problem hiding this comment.
This is changing how tuple struct is handled to be completely different from normal structs?
There's a reason that methods are used, because they support HRTB, so it makes it possible to add self-reference support later (#156)
There was a problem hiding this comment.
I see.. I'll make tuple fields use the same methods
| this: Option<This>, | ||
| path: Path, | ||
| brace_token: token::Brace, | ||
| close_span: Span, |
There was a problem hiding this comment.
| close_span: Span, | |
| delim_close_span: Span, |
| if input.peek(Token![<-]) { | ||
| return Err(input.error( | ||
| "`<-` is not supported in tuple constructor syntax; use braces with indices, e.g. `Type { 0 <- init, 1: value }`", | ||
| )); |
There was a problem hiding this comment.
I'd move this into parse_paren_initializer and drop the type.
| field_bindings.push(quote!(ref mut #binding,)); | ||
| } | ||
| let projection_init = if let Some(last_field) = fields.last() { | ||
| if let Some(cfg) = cfg_condition(&last_field.field.attrs) { |
There was a problem hiding this comment.
You should just unconditionally apply cfgs?
| let tuple_fields: Vec<_> = tuple_fields.into_iter().collect(); | ||
| let mut fields = Punctuated::new(); | ||
|
|
||
| for tuple_field in tuple_fields |
There was a problem hiding this comment.
Cfg rejection should happen before tuple struct is accepted at all, so there is no intermediate commit that allows it but performs incorrectly.
5a4e625 to
65d2dc5
Compare
|
I pushed new changes that addressed your comments and rebased. |
|
Can you rebase on #161? |
9a5c70e to
a61d14e
Compare
| else { | ||
| unreachable!() | ||
| }; | ||
| let unpin_allow = if fields.iter().any(|f| matches!(f.member, Member::Named(_))) { |
There was a problem hiding this comment.
I'd rather unconditionally allow non_snake_case. Same elsewhere.
| // type. If a field is structurally pinned, we create a `Slot` with `Pinned` which must be | ||
| // initialized via `PinInit`; if it is not structurally pinned, then we create a `Slot` with | ||
| // `Unpinned` which allows initialization via `Init`. | ||
| // For every field, we create an initializing projection function according to its |
| let field_safety_docs = match member { | ||
| Member::Named(_) => quote! { | ||
| /// - `(*slot).#field_name` is properly aligned. | ||
| /// - `(*slot).#field_name` points to uninitialized and exclusively accessed | ||
| /// memory. | ||
| }, | ||
| Member::Unnamed(_) => quote! { | ||
| /// - The field is properly aligned. | ||
| /// - The field points to uninitialized and exclusively accessed memory. | ||
| }, | ||
| }; |
| let pin_data_new = quote! { | ||
| __ThePinData { __phantom: ::pin_init::__internal::PhantomInvariant::new() } | ||
| }; |
|
|
||
| fn member_binding(member: &Member) -> Option<Ident> { | ||
| match member { | ||
| Member::Named(ident) => Some(ident.clone()), |
| // - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned. | ||
| // - `make_field_check` prevents `#ident` from being used twice, therefore | ||
| // `(*slot).#ident` is exclusively accessed and has not been initialized. | ||
| // - `make_field_check` checks that the field is properly aligned. |
There was a problem hiding this comment.
Why updating the comment here?
| path: Path, | ||
| delim_close_span: Span, | ||
| fields: Punctuated<InitializerField, Token![,]>, | ||
| is_tuple_constructor: bool, |
There was a problem hiding this comment.
If the code wouldn't be over-complicated, I would change this the parsing to give two distinct types for tuple syntax and struct syntax.
pub(crate) struct InitStruct {
path: Path,
brace: token::Brace,
fields: Punctuated<InitializerField, Token![,]>,
rest: Option<(Token![..], Expr)>,
}
pub(crate) struct InitTuple {
path: Path,
paren: token::Paren,
fields: Punctuated<Expr, Token![,]>,
}
impl InitTuple {
fn to_init_struct(self) -> InitStruct { ... }
}
pub(crate) enum InitKind { .. }
pub(crate) struct Initializer { ... }| if let Some(attr) = field.attrs.iter().find(|attr| attr.path().is_ident("cfg")) { | ||
| return Err(dcx.error( | ||
| attr, | ||
| "`#[cfg]` on tuple constructor arguments is only supported on the last argument", |
There was a problem hiding this comment.
| "`#[cfg]` on tuple constructor arguments is only supported on the last argument", | |
| "`#[cfg]` is not supported in this position", |
this whole commit should be folded into commit 2.
|
Could you handle cfgs in a way similar to #165? Unlike |
|
I'll rethink this PR as a whole and double check (the last few fixes were rushed because they were slaving me away at work) |
|
Thanks for working on this! |
a61d14e to
ec74456
Compare
|
oh its my absolute pleasure to work on this. I took my time to fix botched rebase and clean up commits. Also, I think CI is broken now. |
Binding a value of an uninhabited type now makes everything following it
unreachable. In `stack_pin_init!` the `Infallible` annotation is such a
binding, so the `match x {}` after it is reported as unreachable and
`-Dwarnings` turns that into an error.
Annotate the `Result` instead of the error value, which requires the
initializer to be infallible just the same but has nothing following it.
`Infallible` is also printed as `!` in diagnostics now, so bless the two
tests that show it.
Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
`#[pin_data]` rejects tuple structs because it assumes every field has a name, which it uses for the projection field, the `__Unpin` field and the pin-data accessor. Identify fields by `syn::Member` instead, so that tuple fields are referred to by their index in generated field accesses. The names that generated items still need are derived from the index as `_0`, `_1`, ...; put that mapping in one place, since `[pin_]init!` has to call the accessors it names. The projection of a tuple struct is a tuple struct itself, so projected fields are accessed with the same `.0`, `.1`, ... syntax as on the input type rather than through synthesised names. Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
Extend the initializer syntax so that a field can be named by an index,
addressing tuple struct fields the same way a struct expression does:
pin_init!(Foo { 0: value, 1 <- initializer })
Tuple fields are not exposed by a `let` binding to the fields after them,
since they have no name to bind; `_0` would shadow a user variable.
`cfg` needs different treatment from named fields. Non-derive proc macros
are invoked before cfg is resolved, so the macro cannot know whether a
field survives, and dropping a tuple field renumbers every field after it.
That cannot be expressed by attaching a `cfg` attribute to the initializer
of a single field.
Resolve tuple field cfgs up front instead, by generating two cfg-gated
invocations of the macro with one field resolved in each. This is the
approach of commit 3445a65 ("internal: rework how `#[pin_data]`
handles cfg"), and it is linear because only one of the two branches is
ever expanded. Named fields do not renumber, so they keep using the
existing attribute-based handling.
Link: Rust-for-Linux#165
Suggested-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
A tuple struct whose fields are all set to a value reads better written
like a call to its constructor than with the indices spelled out:
pin_init!(Foo(value, value))
Parse the two forms into separate types and rewrite the constructor
arguments into the indexed fields they are shorthand for, so that only the
parser has to know about the second form.
The arguments have no names, so they cannot use `<-`. Parse it anyway and
reject it afterwards, which reports the position of every offending `<-`
rather than stopping at the first one.
Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
ec74456 to
3ec0b2f
Compare
Replaces #113.
This adds tuple struct support to
#[pin_data],init!, andpin_init!.The projected form of a tuple struct is also a tuple struct, so projected fields are accessed with native tuple syntax (
.0,.1, ...), which keeps the generated API closer toordinary Rust and avoids exposing synthetic field names (also makes it easier fields are
cfg'd out).#[pin_data]pin_init!(Foo { 0 <- ..., 1: ... })pin_init!(Foo(...))(but no<-e.g.Foo(a, <- b, c)is rejected)support for tuple structs when fields or constructor arguments are removed by(Error out unless#[cfg]#[cfg]is on the final tuple field)Notes
._0like last time.cfg handling does not try to evaluate user#[cfg]conditions in the proc macroinstead, the macro generates the necessary cfg-dependent layouts and lets rustc select the active branch#[cfg]is supported only on the final tuple field / constructor argument, when it does not change numbering.Tests
Added coverage for:
#[pin]interaction with!Unpinfield typesfeature-dependent cfg field layoutsComparison with the old PR
This replaces the earlier attempt in #113.
Compared with that PR, this version is intentionally narrower and easier to review:
cfghandling wasredesigned to correctly handlesimplified to accept only on last field/arg. or error outcfg-stripped tuple fields and constructor argumentsCloses: #85