Skip to content

Commit c77d3b7

Browse files
committed
internal: init: add tuple struct field and constructor syntax
Expand `init!` and `pin_init!` to initialize tuple structs using both indexed brace syntax and tuple-constructor syntax. Add tuple-specific runtime and UI coverage for the basic initializer forms, duplicate/missing fields, invalid indices, and syntax errors. Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
1 parent 259ffbf commit c77d3b7

17 files changed

Lines changed: 480 additions & 64 deletions

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11-
12-
- Support tuple structs in `#[pin_data]`, including tuple struct projections.
11+
- Support tuple structs in `#[pin_data]`, including tuple struct projections, and tuple struct
12+
initialization in `init!` and `pin_init!`.
1313
- `[pin_]init_scope` functions to run arbitrary code inside of an initializer.
1414
- `&'static mut MaybeUninit<T>` now implements `InPlaceWrite`. This enables users to use external
1515
allocation mechanisms such as `static_cell`.

internal/src/init.rs

Lines changed: 169 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
use proc_macro2::{Span, TokenStream};
44
use quote::{format_ident, quote};
55
use syn::{
6-
braced,
6+
braced, parenthesized,
77
parse::{End, Parse},
88
parse_quote,
99
punctuated::Punctuated,
1010
spanned::Spanned,
11-
token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type,
11+
token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Index, LitInt, Member, Path, Token,
12+
Type,
1213
};
1314

1415
use crate::diagnostics::{DiagCtxt, ErrorGuaranteed};
@@ -17,7 +18,7 @@ pub(crate) struct Initializer {
1718
attrs: Vec<InitializerAttribute>,
1819
this: Option<This>,
1920
path: Path,
20-
brace_token: token::Brace,
21+
close_span: Span,
2122
fields: Punctuated<InitializerField, Token![,]>,
2223
rest: Option<(Token![..], Expr)>,
2324
error: Option<(Token![?], Type)>,
@@ -34,13 +35,18 @@ struct InitializerField {
3435
kind: InitializerKind,
3536
}
3637

38+
struct TupleInitializerField {
39+
attrs: Vec<Attribute>,
40+
value: Expr,
41+
}
42+
3743
enum InitializerKind {
3844
Value {
39-
ident: Ident,
45+
member: Member,
4046
value: Option<(Token![:], Expr)>,
4147
},
4248
Init {
43-
ident: Ident,
49+
member: Member,
4450
_left_arrow_token: Token![<-],
4551
value: Expr,
4652
},
@@ -52,14 +58,28 @@ enum InitializerKind {
5258
}
5359

5460
impl InitializerKind {
55-
fn ident(&self) -> Option<&Ident> {
61+
fn member(&self) -> Option<&Member> {
5662
match self {
57-
Self::Value { ident, .. } | Self::Init { ident, .. } => Some(ident),
63+
Self::Value { member, .. } | Self::Init { member, .. } => Some(member),
5864
Self::Code { .. } => None,
5965
}
6066
}
6167
}
6268

69+
fn member_ident(member: &Member) -> Ident {
70+
match member {
71+
Member::Named(ident) => ident.clone(),
72+
Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"),
73+
}
74+
}
75+
76+
fn member_binding(member: &Member) -> Option<Ident> {
77+
match member {
78+
Member::Named(ident) => Some(ident.clone()),
79+
Member::Unnamed(_) => None,
80+
}
81+
}
82+
6383
enum InitializerAttribute {
6484
DefaultError(DefaultErrorAttribute),
6585
}
@@ -73,7 +93,7 @@ pub(crate) fn expand(
7393
attrs,
7494
this,
7595
path,
76-
brace_token,
96+
close_span,
7797
fields,
7898
rest,
7999
error,
@@ -96,7 +116,7 @@ pub(crate) fn expand(
96116
} else if let Some(default_error) = default_error {
97117
syn::parse_str(default_error).unwrap()
98118
} else {
99-
dcx.error(brace_token.span.close(), "expected `? <type>` after `}`");
119+
dcx.error(close_span, "expected `? <type>` after initializer");
100120
parse_quote!(::core::convert::Infallible)
101121
}
102122
},
@@ -229,9 +249,9 @@ fn init_fields(
229249
cfgs
230250
};
231251

232-
let ident = match kind {
233-
InitializerKind::Value { ident, .. } => ident,
234-
InitializerKind::Init { ident, .. } => ident,
252+
let member = match kind {
253+
InitializerKind::Value { member, .. } => member,
254+
InitializerKind::Init { member, .. } => member,
235255
InitializerKind::Code { block, .. } => {
236256
res.extend(quote! {
237257
#(#attrs)*
@@ -241,27 +261,38 @@ fn init_fields(
241261
continue;
242262
}
243263
};
264+
let ident = member_ident(member);
244265

245266
let slot = if pinned {
246-
quote! {
247-
// SAFETY:
248-
// - `slot` is valid and properly aligned.
249-
// - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned.
250-
// - `make_field_check` prevents `#ident` from being used twice, therefore
251-
// `(*slot).#ident` is exclusively accessed and has not been initialized.
252-
(unsafe { #data.#ident(#slot) })
267+
match member {
268+
Member::Named(_) => quote! {
269+
// SAFETY:
270+
// - `slot` is valid and properly aligned.
271+
// - `make_field_check` checks that the field is properly aligned.
272+
// - `make_field_check` prevents the field from being used twice, therefore
273+
// it is exclusively accessed and has not been initialized.
274+
(unsafe { #data.#ident(#slot) })
275+
},
276+
Member::Unnamed(_) => quote! {
277+
// SAFETY:
278+
// - `slot` is valid and properly aligned.
279+
// - `make_field_check` checks that the field is properly aligned.
280+
// - `make_field_check` prevents the field from being used twice, therefore
281+
// it is exclusively accessed and has not been initialized.
282+
(unsafe { #data.#member.slot(&raw mut (*#slot).#member) })
283+
},
253284
}
254285
} else {
255286
quote! {
256287
// For `init!()` macro, everything is unpinned.
257288
// SAFETY:
258-
// - `&raw mut (*slot).#ident` is valid.
259-
// - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned.
260-
// - `make_field_check` prevents `#ident` from being used twice, therefore
261-
// `(*slot).#ident` is exclusively accessed and has not been initialized.
289+
// - The field pointer is valid.
290+
// - `make_field_check` checks that the field is properly aligned.
291+
// - `make_field_check` prevents the field from being used twice, therefore
292+
// it is exclusively accessed and has not been initialized.
262293
(unsafe {
263294
::pin_init::__internal::Slot::<::pin_init::__internal::Unpinned, _>::new(
264-
&raw mut (*#slot).#ident
295+
&raw mut (*#slot).#member
265296
)
266297
})
267298
}
@@ -271,7 +302,7 @@ fn init_fields(
271302
let guard = format_ident!("__{ident}_guard", span = Span::mixed_site());
272303

273304
let init = match kind {
274-
InitializerKind::Value { ident, value } => {
305+
InitializerKind::Value { value, .. } => {
275306
let value = value
276307
.as_ref()
277308
.map(|(_, value)| quote!(#value))
@@ -291,13 +322,18 @@ fn init_fields(
291322
}
292323
InitializerKind::Code { .. } => unreachable!(),
293324
};
325+
let binding = member_binding(member).map(|ident| {
326+
quote! {
327+
#(#cfgs)*
328+
#[allow(unused_variables)]
329+
let #ident = #guard.let_binding();
330+
}
331+
});
294332

295333
res.extend(quote! {
296334
#init
297335

298-
#(#cfgs)*
299-
#[allow(unused_variables)]
300-
let #ident = #guard.let_binding();
336+
#binding
301337
});
302338

303339
guards.push(guard);
@@ -322,9 +358,9 @@ fn make_field_check(
322358
) -> TokenStream {
323359
let field_attrs: Vec<_> = fields
324360
.iter()
325-
.filter_map(|f| f.kind.ident().map(|_| &f.attrs))
361+
.filter_map(|f| f.kind.member().map(|_| &f.attrs))
326362
.collect();
327-
let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.ident()).collect();
363+
let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.member()).collect();
328364
let zeroing_trailer = match init_kind {
329365
InitKind::Normal => None,
330366
InitKind::Zeroing => Some(quote! {
@@ -360,36 +396,73 @@ fn make_field_check(
360396
}
361397
}
362398

363-
impl Parse for Initializer {
364-
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
365-
let attrs = input.call(Attribute::parse_outer)?;
366-
let this = input.peek(Token![&]).then(|| input.parse()).transpose()?;
367-
let path = input.parse()?;
368-
let content;
369-
let brace_token = braced!(content in input);
370-
let mut fields = Punctuated::new();
371-
loop {
399+
type InitFields = Punctuated<InitializerField, Token![,]>;
400+
type InitRest = Option<(Token![..], Expr)>;
401+
402+
fn parse_brace_initializer(
403+
input: syn::parse::ParseStream<'_>,
404+
) -> syn::Result<(Span, InitFields, InitRest)> {
405+
let content;
406+
let brace_token = braced!(content in input);
407+
let mut fields = Punctuated::new();
408+
loop {
409+
let lh = content.lookahead1();
410+
if lh.peek(End) || lh.peek(Token![..]) {
411+
break;
412+
} else if lh.peek(Ident) || lh.peek(LitInt) || lh.peek(Token![_]) || lh.peek(Token![#]) {
413+
fields.push_value(content.parse()?);
372414
let lh = content.lookahead1();
373-
if lh.peek(End) || lh.peek(Token![..]) {
415+
if lh.peek(End) {
374416
break;
375-
} else if lh.peek(Ident) || lh.peek(Token![_]) || lh.peek(Token![#]) {
376-
fields.push_value(content.parse()?);
377-
let lh = content.lookahead1();
378-
if lh.peek(End) {
379-
break;
380-
} else if lh.peek(Token![,]) {
381-
fields.push_punct(content.parse()?);
382-
} else {
383-
return Err(lh.error());
384-
}
417+
} else if lh.peek(Token![,]) {
418+
fields.push_punct(content.parse()?);
385419
} else {
386420
return Err(lh.error());
387421
}
422+
} else {
423+
return Err(lh.error());
388424
}
389-
let rest = content
390-
.peek(Token![..])
391-
.then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?)))
392-
.transpose()?;
425+
}
426+
let rest = content
427+
.peek(Token![..])
428+
.then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?)))
429+
.transpose()?;
430+
Ok((brace_token.span.close(), fields, rest))
431+
}
432+
433+
fn parse_paren_initializer(input: syn::parse::ParseStream<'_>) -> syn::Result<(Span, InitFields)> {
434+
let content;
435+
let paren_token = parenthesized!(content in input);
436+
let tuple_fields = content.parse_terminated(TupleInitializerField::parse, Token![,])?;
437+
let mut fields = Punctuated::new();
438+
for (index, tuple_field) in tuple_fields.into_iter().enumerate() {
439+
fields.push(InitializerField {
440+
attrs: tuple_field.attrs,
441+
kind: InitializerKind::Value {
442+
member: Member::Unnamed(Index {
443+
index: index as u32,
444+
span: tuple_field.value.span(),
445+
}),
446+
value: Some((Token![:](tuple_field.value.span()), tuple_field.value)),
447+
},
448+
});
449+
}
450+
Ok((paren_token.span.close(), fields))
451+
}
452+
453+
impl Parse for Initializer {
454+
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
455+
let attrs = input.call(Attribute::parse_outer)?;
456+
let this = input.peek(Token![&]).then(|| input.parse()).transpose()?;
457+
let path = input.parse()?;
458+
let (close_span, fields, rest) = if input.peek(token::Brace) {
459+
parse_brace_initializer(input)?
460+
} else if input.peek(token::Paren) {
461+
let (close_span, fields) = parse_paren_initializer(input)?;
462+
(close_span, fields, None)
463+
} else {
464+
return Err(input.error("expected curly braces or parentheses"));
465+
};
393466
let error = input
394467
.peek(Token![?])
395468
.then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?)))
@@ -409,7 +482,7 @@ impl Parse for Initializer {
409482
attrs,
410483
this,
411484
path,
412-
brace_token,
485+
close_span,
413486
fields,
414487
rest,
415488
error,
@@ -452,22 +525,43 @@ impl Parse for InitializerKind {
452525
_colon_token: input.parse()?,
453526
block: input.parse()?,
454527
})
455-
} else if lh.peek(Ident) {
456-
let ident = input.parse()?;
528+
} else if lh.peek(Ident) || lh.peek(LitInt) {
529+
let member = if lh.peek(Ident) {
530+
Member::Named(input.parse()?)
531+
} else {
532+
let lit: LitInt = input.parse()?;
533+
let index: u32 = lit.base10_parse().map_err(|_| {
534+
syn::Error::new(
535+
lit.span(),
536+
"tuple field index must be a non-negative integer",
537+
)
538+
})?;
539+
Member::Unnamed(Index {
540+
index,
541+
span: lit.span(),
542+
})
543+
};
457544
let lh = input.lookahead1();
458545
if lh.peek(Token![<-]) {
459546
Ok(Self::Init {
460-
ident,
547+
member,
461548
_left_arrow_token: input.parse()?,
462549
value: input.parse()?,
463550
})
464551
} else if lh.peek(Token![:]) {
465552
Ok(Self::Value {
466-
ident,
553+
member,
467554
value: Some((input.parse()?, input.parse()?)),
468555
})
469556
} else if lh.peek(Token![,]) || lh.peek(End) {
470-
Ok(Self::Value { ident, value: None })
557+
if matches!(member, Member::Unnamed(_)) {
558+
Err(lh.error())
559+
} else {
560+
Ok(Self::Value {
561+
member,
562+
value: None,
563+
})
564+
}
471565
} else {
472566
Err(lh.error())
473567
}
@@ -476,3 +570,18 @@ impl Parse for InitializerKind {
476570
}
477571
}
478572
}
573+
574+
impl Parse for TupleInitializerField {
575+
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
576+
let attrs = input.call(Attribute::parse_outer)?;
577+
if input.peek(Token![<-]) {
578+
return Err(input.error(
579+
"`<-` is not supported in tuple constructor syntax; use braces with indices, e.g. `Type { 0 <- init, 1: value }`",
580+
));
581+
}
582+
Ok(Self {
583+
attrs,
584+
value: input.parse()?,
585+
})
586+
}
587+
}

0 commit comments

Comments
 (0)