Skip to content

Commit 51ecee3

Browse files
committed
internal: pin_data: refactor for tuple struct support
Introduce `FieldInfo` struct to encapsulate field and other relevant data (e.g. pinned and member name) to abstract over named/unnamed fields. Also, generate projections and pin-data accessors for unnamed members. Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
1 parent 4f2b40f commit 51ecee3

File tree

2 files changed

+116
-67
lines changed

2 files changed

+116
-67
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- `[pin_data]` now supports tuple structs
1213
- `[pin_]init_scope` functions to run arbitrary code inside of an initializer.
1314
- `&'static mut MaybeUninit<T>` now implements `InPlaceWrite`. This enables users to use external
1415
allocation mechanisms such as `static_cell`.

internal/src/pin_data.rs

Lines changed: 115 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use syn::{
77
parse_quote, parse_quote_spanned,
88
spanned::Spanned,
99
visit_mut::VisitMut,
10-
Field, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause,
10+
Field, Generics, Ident, Item, Member, PathSegment, Type, TypePath, Visibility, WhereClause,
1111
};
1212

1313
use crate::diagnostics::{DiagCtxt, ErrorGuaranteed};
@@ -35,6 +35,52 @@ impl Parse for Args {
3535
}
3636
}
3737

38+
struct FieldInfo<'a> {
39+
pinned: bool,
40+
field: &'a Field,
41+
member: Member,
42+
proj_ident: Ident,
43+
display_name: String,
44+
}
45+
46+
impl<'a> FieldInfo<'a> {
47+
fn new(field: &'a mut Field, idx: Option<usize>) -> Self {
48+
// Invariant: either it's a named field OR it's a tuple field with an index.
49+
debug_assert_eq!(field.ident.is_some(), idx.is_none());
50+
51+
// Strip #[pin] and remember whether it was present.
52+
let mut pinned = false;
53+
field.attrs.retain(|a| {
54+
let is_pin = a.path().is_ident("pin");
55+
pinned |= is_pin;
56+
!is_pin
57+
});
58+
59+
let (member, proj_ident, display_name) = if let Some(ident) = field.ident.as_ref() {
60+
(
61+
Member::Named(ident.clone()),
62+
ident.clone(),
63+
format!("`{ident}`"),
64+
)
65+
} else {
66+
let i = idx.expect("tuple struct fields must have an index"); // Invariant
67+
(
68+
Member::Unnamed(i.into()),
69+
format_ident!("_{i}"),
70+
format!("index `{i}`"),
71+
)
72+
};
73+
74+
Self {
75+
pinned,
76+
field,
77+
member,
78+
proj_ident,
79+
display_name,
80+
}
81+
}
82+
}
83+
3884
pub(crate) fn pin_data(
3985
args: Args,
4086
input: Item,
@@ -73,27 +119,25 @@ pub(crate) fn pin_data(
73119
replacer.visit_generics_mut(&mut struct_.generics);
74120
replacer.visit_fields_mut(&mut struct_.fields);
75121

76-
let fields: Vec<(bool, &Field)> = struct_
122+
let fields: Vec<FieldInfo> = struct_
77123
.fields
78124
.iter_mut()
79-
.map(|field| {
80-
let len = field.attrs.len();
81-
field.attrs.retain(|a| !a.path().is_ident("pin"));
82-
(len != field.attrs.len(), &*field)
83-
})
125+
.enumerate()
126+
.map(|(i, field)| FieldInfo::new(field, field.ident.is_none().then_some(i)))
84127
.collect();
85128

86-
for (pinned, field) in &fields {
87-
if !pinned && is_phantom_pinned(&field.ty) {
88-
dcx.error(
89-
field,
90-
format!(
91-
"The field `{}` of type `PhantomPinned` only has an effect \
92-
if it has the `#[pin]` attribute",
93-
field.ident.as_ref().unwrap(),
94-
),
95-
);
96-
}
129+
for field in fields
130+
.iter()
131+
.filter(|f| !f.pinned && is_phantom_pinned(&f.field.ty))
132+
{
133+
dcx.error(
134+
field.field,
135+
format!(
136+
"The field {} of type `PhantomPinned` only has an effect \
137+
if it has the `#[pin]` attribute",
138+
field.display_name
139+
),
140+
);
97141
}
98142

99143
let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields);
@@ -140,11 +184,7 @@ fn is_phantom_pinned(ty: &Type) -> bool {
140184
}
141185
}
142186

143-
fn generate_unpin_impl(
144-
ident: &Ident,
145-
generics: &Generics,
146-
fields: &[(bool, &Field)],
147-
) -> TokenStream {
187+
fn generate_unpin_impl(ident: &Ident, generics: &Generics, fields: &[FieldInfo]) -> TokenStream {
148188
let (_, ty_generics, _) = generics.split_for_impl();
149189
let mut generics_with_pin_lt = generics.clone();
150190
generics_with_pin_lt.params.insert(0, parse_quote!('__pin));
@@ -160,7 +200,17 @@ fn generate_unpin_impl(
160200
else {
161201
unreachable!()
162202
};
163-
let pinned_fields = fields.iter().filter_map(|(b, f)| b.then_some(f));
203+
let pinned_fields = fields.iter().filter(|f| f.pinned).map(
204+
|FieldInfo {
205+
field, proj_ident, ..
206+
}| {
207+
let Field { attrs, vis, ty, .. } = field;
208+
quote!(
209+
#(#attrs)*
210+
#vis #proj_ident: #ty
211+
)
212+
},
213+
);
164214
quote! {
165215
// This struct will be used for the unpin analysis. It is needed, because only structurally
166216
// pinned fields are relevant whether the struct should implement `Unpin`.
@@ -238,7 +288,7 @@ fn generate_projections(
238288
vis: &Visibility,
239289
ident: &Ident,
240290
generics: &Generics,
241-
fields: &[(bool, &Field)],
291+
fields: &[FieldInfo],
242292
) -> TokenStream {
243293
let (impl_generics, ty_generics, _) = generics.split_for_impl();
244294
let mut generics_with_pin_lt = generics.clone();
@@ -248,57 +298,55 @@ fn generate_projections(
248298
let this = format_ident!("this");
249299

250300
let (fields_decl, fields_proj) = collect_tuple(fields.iter().map(
251-
|(
252-
pinned,
253-
Field {
254-
vis,
255-
ident,
256-
ty,
257-
attrs,
258-
..
259-
},
260-
)| {
261-
let mut attrs = attrs.clone();
301+
|FieldInfo {
302+
pinned,
303+
field,
304+
member,
305+
proj_ident,
306+
..
307+
}| {
308+
let Field { vis, ty, .. } = field;
309+
let mut attrs = field.attrs.clone();
262310
attrs.retain(|a| !a.path().is_ident("pin"));
263311
let mut no_doc_attrs = attrs.clone();
264312
no_doc_attrs.retain(|a| !a.path().is_ident("doc"));
265-
let ident = ident
266-
.as_ref()
267-
.expect("only structs with named fields are supported");
313+
268314
if *pinned {
269315
(
270316
quote!(
271317
#(#attrs)*
272-
#vis #ident: ::core::pin::Pin<&'__pin mut #ty>,
318+
#vis #proj_ident: ::core::pin::Pin<&'__pin mut #ty>,
273319
),
274320
quote!(
275321
#(#no_doc_attrs)*
276322
// SAFETY: this field is structurally pinned.
277-
#ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) },
323+
#proj_ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) },
278324
),
279325
)
280326
} else {
281327
(
282328
quote!(
283329
#(#attrs)*
284-
#vis #ident: &'__pin mut #ty,
330+
#vis #proj_ident: &'__pin mut #ty,
285331
),
286332
quote!(
287333
#(#no_doc_attrs)*
288-
#ident: &mut #this.#ident,
334+
#proj_ident: &mut #this.#member,
289335
),
290336
)
291337
}
292338
},
293339
));
294340
let structurally_pinned_fields_docs = fields
295341
.iter()
296-
.filter_map(|(pinned, field)| pinned.then_some(field))
297-
.map(|Field { ident, .. }| format!(" - `{}`", ident.as_ref().unwrap()));
342+
.filter(|f| f.pinned)
343+
.map(|f| format!(" - {}", f.display_name));
344+
298345
let not_structurally_pinned_fields_docs = fields
299346
.iter()
300-
.filter_map(|(pinned, field)| (!pinned).then_some(field))
301-
.map(|Field { ident, .. }| format!(" - `{}`", ident.as_ref().unwrap()));
347+
.filter(|f| !f.pinned)
348+
.map(|f| format!(" - {}", f.display_name));
349+
302350
let docs = format!(" Pin-projections of [`{ident}`]");
303351
quote! {
304352
#[doc = #docs]
@@ -338,7 +386,7 @@ fn generate_the_pin_data(
338386
vis: &Visibility,
339387
ident: &Ident,
340388
generics: &Generics,
341-
fields: &[(bool, &Field)],
389+
fields: &[FieldInfo],
342390
) -> TokenStream {
343391
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
344392

@@ -347,24 +395,24 @@ fn generate_the_pin_data(
347395
// not structurally pinned, then it can be initialized via `Init`.
348396
//
349397
// The functions are `unsafe` to prevent accidentally calling them.
350-
fn handle_field(
351-
Field {
352-
vis,
353-
ident,
354-
ty,
355-
attrs,
356-
..
357-
}: &Field,
358-
struct_ident: &Ident,
359-
pinned: bool,
360-
) -> TokenStream {
398+
fn handle_field(field: &FieldInfo, struct_ident: &Ident) -> TokenStream {
399+
let FieldInfo {
400+
pinned,
401+
field,
402+
member,
403+
proj_ident,
404+
display_name,
405+
} = field;
406+
let Field { attrs, vis, ty, .. } = field;
407+
361408
let mut attrs = attrs.clone();
362409
attrs.retain(|a| !a.path().is_ident("pin"));
363-
let ident = ident
364-
.as_ref()
365-
.expect("only structs with named fields are supported");
366-
let project_ident = format_ident!("__project_{ident}");
367-
let (init_ty, init_fn, project_ty, project_body, pin_safety) = if pinned {
410+
411+
let project_ident = match member {
412+
Member::Named(ident) => format_ident!("__project_{ident}"),
413+
Member::Unnamed(index) => format_ident!("__project_{}", index.index),
414+
};
415+
let (init_ty, init_fn, project_ty, project_body, pin_safety) = if *pinned {
368416
(
369417
quote!(PinInit),
370418
quote!(__pinned_init),
@@ -385,7 +433,7 @@ fn generate_the_pin_data(
385433
)
386434
};
387435
let slot_safety = format!(
388-
" `slot` points at the field `{ident}` inside of `{struct_ident}`, which is pinned.",
436+
" `slot` points at the field {display_name} inside of `{struct_ident}`, which is pinned.",
389437
);
390438
quote! {
391439
/// # Safety
@@ -395,7 +443,7 @@ fn generate_the_pin_data(
395443
/// to deallocate.
396444
#pin_safety
397445
#(#attrs)*
398-
#vis unsafe fn #ident<E>(
446+
#vis unsafe fn #proj_ident<E>(
399447
self,
400448
slot: *mut #ty,
401449
init: impl ::pin_init::#init_ty<#ty, E>,
@@ -420,7 +468,7 @@ fn generate_the_pin_data(
420468

421469
let field_accessors = fields
422470
.iter()
423-
.map(|(pinned, field)| handle_field(field, ident, *pinned))
471+
.map(|f| handle_field(f, ident))
424472
.collect::<TokenStream>();
425473
quote! {
426474
// We declare this struct which will host all of the projection function for our type. It

0 commit comments

Comments
 (0)