Skip to content

Commit 2d2042e

Browse files
committed
internal: support cfg on final tuple fields and arguments
Support tuple structs whose final field or final constructor argument is removed by #[cfg]. This preserves tuple indices without needing to evaluate user cfgs in the proc macro. Reject #[cfg] on earlier tuple fields and tuple constructor arguments, because those cases would require reindexing the remaining fields after cfg stripping. That is possible to generate, but the extra complexity is not justified for the tuple struct support added here. Add regression tests for the supported final-field case and UI diagnostics for the unsupported index-shifting cases. Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
1 parent 02d105f commit 2d2042e

7 files changed

Lines changed: 183 additions & 10 deletions

File tree

internal/src/init.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,25 @@ fn parse_paren_initializer(input: syn::parse::ParseStream<'_>) -> syn::Result<(S
434434
let content;
435435
let paren_token = parenthesized!(content in input);
436436
let tuple_fields = content.parse_terminated(TupleInitializerField::parse, Token![,])?;
437+
let tuple_fields: Vec<_> = tuple_fields.into_iter().collect();
437438
let mut fields = Punctuated::new();
439+
440+
for tuple_field in tuple_fields
441+
.iter()
442+
.take(tuple_fields.len().saturating_sub(1))
443+
{
444+
if let Some(attr) = tuple_field
445+
.attrs
446+
.iter()
447+
.find(|attr| attr.path().is_ident("cfg"))
448+
{
449+
return Err(syn::Error::new_spanned(
450+
attr,
451+
"`#[cfg]` on tuple constructor arguments is only supported on the last argument",
452+
));
453+
}
454+
}
455+
438456
for (index, tuple_field) in tuple_fields.into_iter().enumerate() {
439457
fields.push(InitializerField {
440458
attrs: tuple_field.attrs,

internal/src/pin_data.rs

Lines changed: 65 additions & 9 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, Visibility,
11-
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,19 @@ fn member_display_name(member: &Member) -> String {
5656
}
5757
}
5858

59+
fn has_cfg_attr(attrs: &[Attribute]) -> bool {
60+
attrs.iter().any(|attr| attr.path().is_ident("cfg"))
61+
}
62+
63+
fn cfg_condition(attrs: &[Attribute]) -> Option<TokenStream> {
64+
let cfgs: Vec<_> = attrs
65+
.iter()
66+
.filter(|attr| attr.path().is_ident("cfg"))
67+
.filter_map(|attr| attr.parse_args::<Meta>().ok())
68+
.collect();
69+
(!cfgs.is_empty()).then(|| quote!(all(#(#cfgs),*)))
70+
}
71+
5972
pub(crate) fn pin_data(
6073
args: Args,
6174
input: Item,
@@ -119,6 +132,17 @@ pub(crate) fn pin_data(
119132
})
120133
.collect();
121134

135+
if is_tuple_struct {
136+
for field in fields.iter().take(fields.len().saturating_sub(1)) {
137+
if has_cfg_attr(&field.field.attrs) {
138+
return Err(dcx.error(
139+
field.field,
140+
"`#[cfg]` on tuple struct fields is only supported on the last field",
141+
));
142+
}
143+
}
144+
}
145+
122146
for field in &fields {
123147
if !field.pinned && is_phantom_pinned(&field.field.ty) {
124148
dcx.warn(
@@ -368,6 +392,7 @@ fn generate_projections(
368392
let mut fields_decl = Vec::new();
369393
let mut field_bindings = Vec::new();
370394
let mut field_projections = Vec::new();
395+
371396
for (index, field) in fields.iter().enumerate() {
372397
let Field { vis, ty, attrs, .. } = &field.field;
373398
let binding = format_ident!("__field_{index}");
@@ -390,6 +415,43 @@ fn generate_projections(
390415
}
391416
field_bindings.push(quote!(ref mut #binding,));
392417
}
418+
let projection_init = if let Some(last_field) = fields.last() {
419+
if let Some(cfg) = cfg_condition(&last_field.field.attrs) {
420+
let field_bindings_without_last = &field_bindings[..field_bindings.len() - 1];
421+
let field_projections_without_last =
422+
&field_projections[..field_projections.len() - 1];
423+
quote! {{
424+
#[cfg(#cfg)]
425+
{
426+
let #ident(#(#field_bindings)*) = *#this;
427+
#projection(
428+
#(#field_projections)*
429+
::core::marker::PhantomData,
430+
)
431+
}
432+
#[cfg(not(#cfg))]
433+
{
434+
let #ident(#(#field_bindings_without_last)*) = *#this;
435+
#projection(
436+
#(#field_projections_without_last)*
437+
::core::marker::PhantomData,
438+
)
439+
}
440+
}}
441+
} else {
442+
quote! {{
443+
let #ident(#(#field_bindings)*) = *#this;
444+
#projection(
445+
#(#field_projections)*
446+
::core::marker::PhantomData,
447+
)
448+
}}
449+
}
450+
} else {
451+
quote! {
452+
#projection(::core::marker::PhantomData)
453+
}
454+
};
393455

394456
(
395457
quote! {
@@ -401,13 +463,7 @@ fn generate_projections(
401463
::core::marker::PhantomData<&'__pin mut ()>,
402464
) #whr;
403465
},
404-
quote! {{
405-
let #ident(#(#field_bindings)*) = *#this;
406-
#projection(
407-
#(#field_projections)*
408-
::core::marker::PhantomData,
409-
)
410-
}},
466+
projection_init,
411467
)
412468
};
413469

tests/cfgs.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use pin_init::{pin_data, pin_init, PinInit};
1+
use pin_init::{pin_data, pin_init, stack_pin_init, PinInit};
22

33
#[pin_data]
44
pub struct Struct {
@@ -21,9 +21,80 @@ impl Struct {
2121

2222
struct Field {}
2323

24+
#[cfg(not(feature = "std"))]
25+
fn assert_pinned<T>(_: core::pin::Pin<&mut T>) {}
26+
2427
#[pin_data]
2528
pub struct Struct2 {
2629
// Test for cases where the type is not even defined when cfg is not satisfied.
2730
#[cfg(any())]
2831
non_exist: NonExistentType,
2932
}
33+
34+
#[pin_data]
35+
pub struct TupleStruct(Field, #[cfg(any())] HiddenField);
36+
37+
impl TupleStruct {
38+
pub fn new() -> impl PinInit<Self> {
39+
pin_init!(Self(Field {}))
40+
}
41+
}
42+
43+
#[allow(dead_code)]
44+
struct HiddenField;
45+
46+
#[test]
47+
fn tuple_struct_allows_cfgd_out_last_field() {
48+
stack_pin_init!(let value = TupleStruct::new());
49+
let projected = value.as_mut().project();
50+
let _ = projected.0;
51+
}
52+
53+
#[pin_data]
54+
pub struct ConstructorCfgTuple(Field, #[cfg(any())] HiddenField);
55+
56+
impl ConstructorCfgTuple {
57+
pub fn new() -> impl PinInit<Self> {
58+
pin_init!(Self(
59+
Field {},
60+
#[cfg(any())]
61+
HiddenField
62+
))
63+
}
64+
}
65+
66+
#[test]
67+
fn tuple_constructor_allows_cfgd_out_last_argument() {
68+
stack_pin_init!(let value = ConstructorCfgTuple::new());
69+
let projected = value.as_mut().project();
70+
let _ = projected.0;
71+
}
72+
73+
#[pin_data]
74+
pub struct FeatureTupleStruct(
75+
Field,
76+
#[cfg(not(feature = "std"))]
77+
#[pin]
78+
core::marker::PhantomPinned,
79+
);
80+
81+
impl FeatureTupleStruct {
82+
pub fn new() -> impl PinInit<Self> {
83+
pin_init!(Self(
84+
Field {},
85+
#[cfg(not(feature = "std"))]
86+
core::marker::PhantomPinned
87+
))
88+
}
89+
}
90+
91+
#[test]
92+
fn tuple_struct_allows_feature_cfgd_out_last_field() {
93+
stack_pin_init!(let value = FeatureTupleStruct::new());
94+
let projected = value.as_mut().project();
95+
let _ = projected.0;
96+
#[cfg(not(feature = "std"))]
97+
{
98+
assert_pinned(projected.1);
99+
}
100+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use pin_init::*;
2+
3+
#[pin_data]
4+
struct Tuple(Option<i32>, i32);
5+
6+
fn main() {
7+
let _ = pin_init!(Tuple(
8+
#[cfg(any())]
9+
None,
10+
1
11+
));
12+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: `#[cfg]` on tuple constructor arguments is only supported on the last argument
2+
--> tests/ui/compile-fail/init/tuple_constructor_cfg_non_last.rs:8:9
3+
|
4+
8 | #[cfg(any())]
5+
| ^^^^^^^^^^^^^
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use pin_init::*;
2+
3+
#[pin_data]
4+
struct Tuple(#[cfg(any())] HiddenField, i32);
5+
6+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: `#[cfg]` on tuple struct fields is only supported on the last field
2+
--> tests/ui/compile-fail/pin_data/tuple_struct_cfg_non_last.rs:4:14
3+
|
4+
4 | struct Tuple(#[cfg(any())] HiddenField, i32);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)