Skip to content

Commit c9dab99

Browse files
committed
internal: init: support cfg-stripped tuple fields and arguments
Handle tuple structs whose fields or constructor arguments are removed by `#[cfg]`. For tuple projections and tuple-constructor lowering, generate the needed cfg-specific layouts instead of trying to evaluate user cfgs inside the proc macro, and let rustc select the active branch. Add regression tests covering cfg-stripped tuple fields, cfg-stripped tuple constructor arguments, and feature-dependent field layouts. Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
1 parent 5c9bd8d commit c9dab99

6 files changed

Lines changed: 262 additions & 45 deletions

File tree

internal/src/init.rs

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ fn member_binding(member: &Member) -> Option<Ident> {
8080
}
8181
}
8282

83+
fn cfg_condition(attrs: &[Attribute]) -> Option<TokenStream> {
84+
let cfgs: Vec<_> = attrs
85+
.iter()
86+
.filter(|attr| attr.path().is_ident("cfg"))
87+
.filter_map(|attr| attr.parse_args::<syn::Meta>().ok())
88+
.collect();
89+
(!cfgs.is_empty()).then(|| quote!(all(#(#cfgs),*)))
90+
}
91+
8392
enum InitializerAttribute {
8493
DefaultError(DefaultErrorAttribute),
8594
}
@@ -434,18 +443,69 @@ fn parse_paren_initializer(input: syn::parse::ParseStream<'_>) -> syn::Result<(S
434443
let content;
435444
let paren_token = parenthesized!(content in input);
436445
let tuple_fields = content.parse_terminated(TupleInitializerField::parse, Token![,])?;
446+
let tuple_fields: Vec<_> = tuple_fields.into_iter().collect();
447+
let cfg_conditions: Vec<_> = tuple_fields
448+
.iter()
449+
.map(|field| cfg_condition(&field.attrs))
450+
.collect();
451+
let conditional_fields: Vec<_> = cfg_conditions
452+
.iter()
453+
.enumerate()
454+
.filter_map(|(index, cfg)| cfg.as_ref().map(|cfg| (index, cfg)))
455+
.collect();
456+
437457
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-
});
458+
459+
// Tuple constructor arguments are reindexed by rustc after cfg stripping. Since the proc macro
460+
// cannot evaluate user cfgs, generate one initializer sequence for every cfg combination.
461+
for combination in 0..(1usize << conditional_fields.len()) {
462+
let cfgs: Vec<_> = conditional_fields
463+
.iter()
464+
.enumerate()
465+
.map(|(bit, (_, cfg))| {
466+
if combination & (1 << bit) == 0 {
467+
quote!(not(#cfg))
468+
} else {
469+
quote!(#cfg)
470+
}
471+
})
472+
.collect();
473+
let combination_attr: Option<Attribute> =
474+
(!cfgs.is_empty()).then(|| parse_quote!(#[cfg(all(#(#cfgs),*))]));
475+
let mut active_index = 0u32;
476+
477+
for (index, tuple_field) in tuple_fields.iter().enumerate() {
478+
let included = match cfg_conditions[index] {
479+
None => true,
480+
Some(_) => {
481+
let bit = conditional_fields
482+
.iter()
483+
.position(|(field_index, _)| *field_index == index)
484+
.expect("cfg-bearing field must be present");
485+
combination & (1 << bit) != 0
486+
}
487+
};
488+
if !included {
489+
continue;
490+
}
491+
492+
let mut attrs = tuple_field.attrs.clone();
493+
attrs.extend(combination_attr.clone());
494+
fields.push(InitializerField {
495+
attrs,
496+
kind: InitializerKind::Value {
497+
member: Member::Unnamed(Index {
498+
index: active_index,
499+
span: tuple_field.value.span(),
500+
}),
501+
value: Some((
502+
Token![:](tuple_field.value.span()),
503+
tuple_field.value.clone(),
504+
)),
505+
},
506+
});
507+
active_index += 1;
508+
}
449509
}
450510
Ok((paren_token.span.close(), fields))
451511
}

internal/src/pin_data.rs

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use syn::{
77
parse_quote, parse_quote_spanned,
88
spanned::Spanned,
99
visit_mut::VisitMut,
10-
Field, Fields, Generics, Ident, Index, Item, Member, PathSegment, Type, TypePath,
11-
Visibility, WhereClause,
10+
Attribute, Field, Fields, Generics, Ident, Index, Item, Member, Meta, PathSegment, Type,
11+
TypePath, Visibility, WhereClause,
1212
};
1313

1414
use crate::diagnostics::{DiagCtxt, ErrorGuaranteed};
@@ -56,6 +56,15 @@ fn member_display_name(member: &Member) -> String {
5656
}
5757
}
5858

59+
fn cfg_condition(attrs: &[Attribute]) -> Option<TokenStream> {
60+
let cfgs: Vec<_> = attrs
61+
.iter()
62+
.filter(|attr| attr.path().is_ident("cfg"))
63+
.filter_map(|attr| attr.parse_args::<Meta>().ok())
64+
.collect();
65+
(!cfgs.is_empty()).then(|| quote!(all(#(#cfgs),*)))
66+
}
67+
5968
pub(crate) fn pin_data(
6069
args: Args,
6170
input: Item,
@@ -309,6 +318,12 @@ fn generate_projections(
309318
.map(|f| format!(" - {}", member_display_name(&f.member)));
310319
let docs = format!(" Pin-projections of [`{ident}`]");
311320

321+
let needs_unreachable_allow = is_tuple_struct
322+
&& fields
323+
.iter()
324+
.any(|f| cfg_condition(&f.field.attrs).is_some());
325+
let unreachable_allow = needs_unreachable_allow.then(|| quote!(#[allow(unreachable_code)]));
326+
312327
let (projection_def, projection_init) = if !is_tuple_struct {
313328
let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields
314329
.iter()
@@ -368,9 +383,12 @@ fn generate_projections(
368383
let mut fields_decl = Vec::new();
369384
let mut field_bindings = Vec::new();
370385
let mut field_projections = Vec::new();
386+
let mut cfg_conditions = Vec::new();
387+
371388
for (index, field) in fields.iter().enumerate() {
372389
let Field { vis, ty, attrs, .. } = &field.field;
373390
let binding = format_ident!("__field_{index}");
391+
let cfg_condition = cfg_condition(attrs);
374392

375393
if field.pinned {
376394
fields_decl.push(quote!(
@@ -389,8 +407,79 @@ fn generate_projections(
389407
field_projections.push(quote!(#binding,));
390408
}
391409
field_bindings.push(quote!(ref mut #binding,));
410+
cfg_conditions.push(cfg_condition);
392411
}
393412

413+
let conditional_fields: Vec<_> = cfg_conditions
414+
.iter()
415+
.enumerate()
416+
.filter_map(|(index, cfg)| cfg.as_ref().map(|cfg| (index, cfg)))
417+
.collect();
418+
let combinations = 1usize << conditional_fields.len();
419+
let mut projection_bodies = Vec::new();
420+
421+
// We cannot evaluate user cfgs inside the proc macro. Instead, generate one arm per
422+
// cfg combination and let rustc keep the active tuple pattern.
423+
for combination in 0..combinations {
424+
let cfgs: Vec<_> = conditional_fields
425+
.iter()
426+
.enumerate()
427+
.map(|(bit, (_, cfg))| {
428+
if combination & (1 << bit) == 0 {
429+
quote!(not(#cfg))
430+
} else {
431+
quote!(#cfg)
432+
}
433+
})
434+
.collect();
435+
let cfg_attr = (!cfgs.is_empty()).then(|| quote!(#[cfg(all(#(#cfgs),*))]));
436+
437+
let mut patterns = Vec::new();
438+
let mut projections = Vec::new();
439+
for (index, binding) in field_bindings.iter().enumerate() {
440+
let included = match cfg_conditions[index] {
441+
None => true,
442+
Some(_) => {
443+
let bit = conditional_fields
444+
.iter()
445+
.position(|(field_index, _)| *field_index == index)
446+
.expect("cfg-bearing field must be present");
447+
combination & (1 << bit) != 0
448+
}
449+
};
450+
if included {
451+
patterns.push(binding);
452+
projections.push(&field_projections[index]);
453+
}
454+
}
455+
456+
projection_bodies.push(quote! {
457+
#cfg_attr
458+
{
459+
let #ident(#(#patterns)*) = *#this;
460+
return #projection(
461+
#(#projections)*
462+
::core::marker::PhantomData,
463+
);
464+
}
465+
});
466+
}
467+
let projection_init = if conditional_fields.is_empty() {
468+
quote! {{
469+
let #ident(#(#field_bindings)*) = *#this;
470+
#projection(
471+
#(#field_projections)*
472+
::core::marker::PhantomData,
473+
)
474+
}}
475+
} else {
476+
quote! {{
477+
#(#projection_bodies)*
478+
#[allow(unreachable_code)]
479+
::core::unreachable!()
480+
}}
481+
};
482+
394483
(
395484
quote! {
396485
#[doc = #docs]
@@ -401,13 +490,7 @@ fn generate_projections(
401490
::core::marker::PhantomData<&'__pin mut ()>,
402491
) #whr;
403492
},
404-
quote! {{
405-
let #ident(#(#field_bindings)*) = *#this;
406-
#projection(
407-
#(#field_projections)*
408-
::core::marker::PhantomData,
409-
)
410-
}},
493+
projection_init,
411494
)
412495
};
413496

@@ -425,6 +508,7 @@ fn generate_projections(
425508
/// These fields are **not** structurally pinned:
426509
#(#[doc = #not_structurally_pinned_fields_docs])*
427510
#[inline]
511+
#unreachable_allow
428512
#vis fn project<'__pin>(
429513
self: ::core::pin::Pin<&'__pin mut Self>,
430514
) -> #projection #ty_generics_with_pin_lt {
@@ -465,9 +549,8 @@ fn generate_the_pin_data(
465549
/// # Safety
466550
///
467551
/// - `slot` is valid and properly aligned.
468-
/// - `(*slot).#field_name` is properly aligned.
469-
/// - `(*slot).#field_name` points to uninitialized and exclusively accessed
470-
/// memory.
552+
/// - The field is properly aligned.
553+
/// - The field points to uninitialized and exclusively accessed memory.
471554
#(#attrs)*
472555
#[inline(always)]
473556
#vis unsafe fn #field_name(

tests/cfgs.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use pin_init::{pin_data, pin_init, PinInit};
1+
use core::{marker::PhantomPinned, pin::Pin};
2+
3+
use pin_init::{pin_data, pin_init, stack_pin_init, PinInit};
24

35
#[pin_data]
46
pub struct Struct {
@@ -21,9 +23,88 @@ impl Struct {
2123

2224
struct Field {}
2325

26+
fn assert_pinned<T>(_: Pin<&mut T>) {}
27+
2428
#[pin_data]
2529
pub struct Struct2 {
2630
// Test for cases where the type is not even defined when cfg is not satisfied.
2731
#[cfg(any())]
2832
non_exist: NonExistentType,
2933
}
34+
35+
#[pin_data]
36+
pub struct TupleStruct(#[cfg(any())] HiddenField, Field, #[pin] PhantomPinned);
37+
38+
impl TupleStruct {
39+
pub fn new() -> impl PinInit<Self> {
40+
pin_init!(Self(Field {}, PhantomPinned))
41+
}
42+
}
43+
44+
#[allow(dead_code)]
45+
struct HiddenField;
46+
47+
#[test]
48+
fn tuple_struct_reindexes_cfgd_out_fields() {
49+
stack_pin_init!(let value = TupleStruct::new());
50+
let projected = value.as_mut().project();
51+
let _ = projected.0;
52+
assert_pinned(projected.1);
53+
}
54+
55+
#[pin_data]
56+
pub struct ConstructorCfgTuple(#[cfg(any())] HiddenField, Field, #[pin] PhantomPinned);
57+
58+
impl ConstructorCfgTuple {
59+
pub fn new() -> impl PinInit<Self> {
60+
pin_init!(Self(
61+
#[cfg(any())]
62+
HiddenField,
63+
Field {},
64+
PhantomPinned
65+
))
66+
}
67+
}
68+
69+
#[test]
70+
fn tuple_constructor_reindexes_cfgd_out_arguments() {
71+
stack_pin_init!(let value = ConstructorCfgTuple::new());
72+
let projected = value.as_mut().project();
73+
let _ = projected.0;
74+
assert_pinned(projected.1);
75+
}
76+
77+
#[pin_data]
78+
pub struct FeatureTupleStruct(
79+
#[cfg(not(feature = "std"))] HiddenField,
80+
Field,
81+
#[pin] PhantomPinned,
82+
);
83+
84+
impl FeatureTupleStruct {
85+
pub fn new() -> impl PinInit<Self> {
86+
pin_init!(Self(
87+
#[cfg(not(feature = "std"))]
88+
HiddenField,
89+
Field {},
90+
PhantomPinned
91+
))
92+
}
93+
}
94+
95+
#[test]
96+
fn tuple_struct_reindexes_feature_cfgd_out_fields() {
97+
stack_pin_init!(let value = FeatureTupleStruct::new());
98+
let projected = value.as_mut().project();
99+
#[cfg(feature = "std")]
100+
{
101+
let _ = projected.0;
102+
assert_pinned(projected.1);
103+
}
104+
#[cfg(not(feature = "std"))]
105+
{
106+
let _ = projected.0;
107+
let _ = projected.1;
108+
assert_pinned(projected.2);
109+
}
110+
}

tests/ui/expand/many_generics.expanded.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,8 @@ const _: () = {
9595
/// # Safety
9696
///
9797
/// - `slot` is valid and properly aligned.
98-
/// - `(*slot).#field_name` is properly aligned.
99-
/// - `(*slot).#field_name` points to uninitialized and exclusively accessed
100-
/// memory.
98+
/// - The field is properly aligned.
99+
/// - The field points to uninitialized and exclusively accessed memory.
101100
#[inline(always)]
102101
unsafe fn array(
103102
self,
@@ -111,9 +110,8 @@ const _: () = {
111110
/// # Safety
112111
///
113112
/// - `slot` is valid and properly aligned.
114-
/// - `(*slot).#field_name` is properly aligned.
115-
/// - `(*slot).#field_name` points to uninitialized and exclusively accessed
116-
/// memory.
113+
/// - The field is properly aligned.
114+
/// - The field points to uninitialized and exclusively accessed memory.
117115
#[inline(always)]
118116
unsafe fn r(
119117
self,
@@ -127,9 +125,8 @@ const _: () = {
127125
/// # Safety
128126
///
129127
/// - `slot` is valid and properly aligned.
130-
/// - `(*slot).#field_name` is properly aligned.
131-
/// - `(*slot).#field_name` points to uninitialized and exclusively accessed
132-
/// memory.
128+
/// - The field is properly aligned.
129+
/// - The field points to uninitialized and exclusively accessed memory.
133130
#[inline(always)]
134131
unsafe fn _pin(
135132
self,

0 commit comments

Comments
 (0)