Skip to content

Commit 51b1934

Browse files
committed
internal: add syn version of the pin_data proc macro
Implement the `pin_data` attribute macro using syn to simplify parsing by not going through an additional declarative macro. This not only simplifies the code by a lot, increasing maintainability and making it easier to implement new features. But also improves the user experience by improving the error messages one gets when giving incorrect inputs to the macro. For example, annotating a function with `pin_data` is not allowed: use pin_init::*; #[pin_data] fn foo() {} This results in the following rather unwieldy error with the declarative version: error: no rules expected keyword `fn` | 4 | fn foo() {} | ^^ no rules expected this token in macro call | note: while trying to match keyword `struct` --> src/macros.rs | | $vis:vis struct $name:ident | ^^^^^^ error: Could not locate type name. | 3 | #[pin_data] | ^^^^^^^^^^^ | = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) The syn version gives this very concise error: error: expected `struct` | 4 | fn foo() {} | ^^ The syn version is only enabled in the user-space version and disabled in the kernel until syn becomes available there. Signed-off-by: Benno Lossin <benno.lossin@proton.me>
1 parent 0cd89d6 commit 51b1934

18 files changed

Lines changed: 444 additions & 196 deletions

internal/src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,18 @@ use proc_macro::TokenStream;
2424
#[macro_use]
2525
#[cfg_attr(not(kernel), rustfmt::skip)]
2626
mod quote;
27-
#[cfg(not(kernel))]
28-
#[macro_use]
29-
extern crate quote;
30-
27+
#[cfg(kernel)]
3128
mod helpers;
29+
#[cfg(kernel)]
3230
mod pin_data;
3331
#[cfg(kernel)]
3432
mod pinned_drop;
3533
#[cfg(kernel)]
3634
mod zeroable;
3735

36+
#[cfg(not(kernel))]
37+
#[path = "syn_pin_data.rs"]
38+
mod pin_data;
3839
#[cfg(not(kernel))]
3940
#[path = "syn_pinned_drop.rs"]
4041
mod pinned_drop;

internal/src/syn_pin_data.rs

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
// SPDX-License-Identifier: Apache-2.0 OR MIT
2+
3+
use proc_macro2::TokenStream;
4+
use quote::{format_ident, quote};
5+
use syn::{
6+
parse_macro_input, parse_quote,
7+
visit_mut::{visit_path_segment_mut, VisitMut},
8+
Error, Field, ItemStruct, PathSegment, Result, Type, TypePath, WhereClause,
9+
};
10+
11+
pub(crate) fn pin_data(
12+
inner: proc_macro::TokenStream,
13+
item: proc_macro::TokenStream,
14+
) -> proc_macro::TokenStream {
15+
do_impl(inner.into(), parse_macro_input!(item as ItemStruct))
16+
.unwrap_or_else(|e| e.into_compile_error())
17+
.into()
18+
}
19+
20+
fn do_impl(args: TokenStream, mut struct_: ItemStruct) -> Result<TokenStream> {
21+
// The generics might contain the `Self` type. Since this macro will define a new type with the
22+
// same generics and bounds, this poses a problem: `Self` will refer to the new type as opposed
23+
// to this struct definition. Therefore we have to replace `Self` with the concrete name.
24+
let mut replacer = {
25+
let name = &struct_.ident;
26+
let (_, ty_generics, _) = struct_.generics.split_for_impl();
27+
SelfReplacer(parse_quote!(#name #ty_generics))
28+
};
29+
replacer.visit_generics_mut(&mut struct_.generics);
30+
31+
let mut errors = TokenStream::new();
32+
for field in &struct_.fields {
33+
if !is_pinned(field) && is_phantom_pinned(&field.ty) {
34+
let message = format!("The field `{}` of type `PhantomPinned` only has an effect, if it has the `#[pin]` attribute", field.ident.as_ref().unwrap() );
35+
errors.extend(quote!(::core::compile_error!(#message);));
36+
}
37+
}
38+
39+
let unpin_impl = unpin_impl(&struct_);
40+
let drop_impl = drop_impl(&struct_, args)?;
41+
let the_pin_data = generate_the_pin_data(&mut struct_);
42+
43+
Ok(quote! {
44+
#struct_
45+
#errors
46+
const _: () = {
47+
#the_pin_data
48+
#unpin_impl
49+
#drop_impl
50+
};
51+
})
52+
}
53+
54+
struct SelfReplacer(PathSegment);
55+
56+
impl VisitMut for SelfReplacer {
57+
fn visit_path_segment_mut(&mut self, seg: &mut PathSegment) {
58+
if seg.ident == "Self" {
59+
*seg = self.0.clone();
60+
} else {
61+
visit_path_segment_mut(self, seg);
62+
}
63+
}
64+
65+
fn visit_item_mut(&mut self, _: &mut syn::Item) {
66+
// Do not descend into items, since items reset/change what `Self` refers to.
67+
}
68+
}
69+
70+
fn is_pinned(field: &Field) -> bool {
71+
field.attrs.iter().any(|a| a.path().is_ident("pin"))
72+
}
73+
74+
fn is_phantom_pinned(ty: &Type) -> bool {
75+
match ty {
76+
Type::Path(TypePath { qself: None, path }) => {
77+
// Cannot possibly refer to `PhantomPinned`.
78+
if path.segments.len() > 3 {
79+
return false;
80+
}
81+
// If there is a `::`, then the path needs to be `::core::marker::PhantomPinned` or
82+
// `::std::marker::PhantomPinned`.
83+
if path.leading_colon.is_some() && path.segments.len() != 3 {
84+
return false;
85+
}
86+
let expected: Vec<&[&str]> = vec![&["PhantomPinned"], &["marker"], &["core", "std"]];
87+
for (actual, expected) in path.segments.iter().rev().zip(expected) {
88+
if !actual.arguments.is_empty() || expected.iter().all(|e| actual.ident != e) {
89+
return false;
90+
}
91+
}
92+
true
93+
}
94+
_ => false,
95+
}
96+
}
97+
98+
fn generate_the_pin_data(
99+
ItemStruct {
100+
vis,
101+
ident,
102+
generics,
103+
fields,
104+
..
105+
}: &mut ItemStruct,
106+
) -> TokenStream {
107+
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
108+
109+
let not_pinned_field_accessors = fields
110+
.iter()
111+
.filter(|f| !is_pinned(f))
112+
.map(
113+
|Field {
114+
vis,
115+
ident,
116+
ty,
117+
attrs,
118+
..
119+
}| {
120+
let ident = ident
121+
.as_ref()
122+
.expect("only structs with named fields are supported");
123+
let project_ident = format_ident!("__project_{ident}");
124+
quote! {
125+
#(#attrs)*
126+
#vis unsafe fn #ident<E>(
127+
self,
128+
slot: *mut #ty,
129+
init: impl ::pin_init::Init<#ty, E>,
130+
) -> ::core::result::Result<(), E> {
131+
unsafe { ::pin_init::Init::__init(init, slot) }
132+
}
133+
134+
#(#attrs)*
135+
#vis unsafe fn #project_ident<'__slot>(
136+
self,
137+
slot: &'__slot mut #ty,
138+
) -> &'__slot mut #ty {
139+
slot
140+
}
141+
}
142+
},
143+
)
144+
.collect::<TokenStream>();
145+
146+
// For every field, we create a projection function according to its projection type. If a
147+
// field is structurally pinned, then it must be initialized via `PinInit`, if it is not
148+
// structurally pinned, then it must be initialized via `Init`.
149+
let pinned_field_accessors = fields
150+
.iter_mut()
151+
.filter(|f| is_pinned(f))
152+
.map(
153+
|Field {
154+
vis,
155+
ident,
156+
ty,
157+
attrs,
158+
..
159+
}| {
160+
attrs.retain(|a| !a.path().is_ident("pin"));
161+
let ident = ident
162+
.as_ref()
163+
.expect("only structs with named fields are supported");
164+
let project_ident = format_ident!("__project_{ident}");
165+
quote! {
166+
#(#attrs)*
167+
#vis unsafe fn #ident<E>(
168+
self,
169+
slot: *mut #ty,
170+
init: impl ::pin_init::PinInit<#ty, E>,
171+
) -> ::core::result::Result<(), E> {
172+
unsafe { ::pin_init::PinInit::__pinned_init(init, slot) }
173+
}
174+
175+
#(#attrs)*
176+
#vis unsafe fn #project_ident<'__slot>(
177+
self,
178+
slot: &'__slot mut #ty,
179+
) -> ::core::pin::Pin<&'__slot mut #ty> {
180+
unsafe { ::core::pin::Pin::new_unchecked(slot) }
181+
}
182+
}
183+
},
184+
)
185+
.collect::<TokenStream>();
186+
quote! {
187+
// We declare this struct which will host all of the projection function for our type. It
188+
// will be invariant over all generic parameters which are inherited from the struct.
189+
#vis struct __ThePinData #generics
190+
#whr
191+
{
192+
__phantom: ::core::marker::PhantomData<
193+
fn(#ident #ty_generics) -> #ident #ty_generics
194+
>,
195+
}
196+
197+
impl #impl_generics ::core::clone::Clone for __ThePinData #ty_generics
198+
#whr
199+
{
200+
fn clone(&self) -> Self { *self }
201+
}
202+
203+
impl #impl_generics ::core::marker::Copy for __ThePinData #ty_generics
204+
#whr
205+
{}
206+
207+
#[allow(dead_code)] // Some functions might never be used and private.
208+
#[expect(clippy::missing_safety_doc)]
209+
impl #impl_generics __ThePinData #ty_generics
210+
#whr
211+
{
212+
#pinned_field_accessors
213+
#not_pinned_field_accessors
214+
}
215+
216+
// SAFETY: We have added the correct projection functions above to `__ThePinData` and
217+
// we also use the least restrictive generics possible.
218+
unsafe impl #impl_generics
219+
::pin_init::__internal::HasPinData for #ident #ty_generics
220+
#whr
221+
{
222+
type PinData = __ThePinData #ty_generics;
223+
224+
unsafe fn __pin_data() -> Self::PinData {
225+
__ThePinData { __phantom: ::core::marker::PhantomData }
226+
}
227+
}
228+
229+
unsafe impl #impl_generics
230+
::pin_init::__internal::PinData for __ThePinData #ty_generics
231+
#whr
232+
{
233+
type Datee = #ident #ty_generics;
234+
}
235+
}
236+
}
237+
238+
fn unpin_impl(
239+
ItemStruct {
240+
ident,
241+
generics,
242+
fields,
243+
..
244+
}: &ItemStruct,
245+
) -> TokenStream {
246+
let generics_with_pinlt = {
247+
let mut g = generics.clone();
248+
g.params.insert(0, parse_quote!('__pin));
249+
let _ = g.make_where_clause();
250+
g
251+
};
252+
let (
253+
impl_generics_with_pinlt,
254+
ty_generics_with_pinlt,
255+
Some(WhereClause {
256+
where_token,
257+
predicates,
258+
}),
259+
) = generics_with_pinlt.split_for_impl()
260+
else {
261+
unreachable!()
262+
};
263+
let (_, ty_generics, _) = generics.split_for_impl();
264+
let mut pinned_fields = fields
265+
.iter()
266+
.filter(|f| is_pinned(f))
267+
.cloned()
268+
.collect::<Vec<_>>();
269+
for field in &mut pinned_fields {
270+
field.attrs.retain(|a| !a.path().is_ident("pin"));
271+
}
272+
quote! {
273+
// This struct will be used for the unpin analysis. It is needed, because only structurally
274+
// pinned fields are relevant whether the struct should implement `Unpin`.
275+
#[allow(dead_code)] // The fields below are never used.
276+
struct __Unpin #generics_with_pinlt
277+
#where_token
278+
#predicates
279+
{
280+
__phantom_pin: ::core::marker::PhantomData<fn(&'__pin ()) -> &'__pin ()>,
281+
__phantom: ::core::marker::PhantomData<
282+
fn(#ident #ty_generics) -> #ident #ty_generics
283+
>,
284+
#(#pinned_fields),*
285+
}
286+
287+
#[doc(hidden)]
288+
impl #impl_generics_with_pinlt ::core::marker::Unpin for #ident #ty_generics
289+
#where_token
290+
__Unpin #ty_generics_with_pinlt: ::core::marker::Unpin,
291+
#predicates
292+
{}
293+
}
294+
}
295+
296+
fn drop_impl(
297+
ItemStruct {
298+
ident, generics, ..
299+
}: &ItemStruct,
300+
args: TokenStream,
301+
) -> Result<TokenStream> {
302+
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
303+
let has_pinned_drop = match syn::parse2::<Option<syn::Ident>>(args.clone()) {
304+
Ok(None) => false,
305+
Ok(Some(ident)) if ident == "PinnedDrop" => true,
306+
_ => {
307+
return Err(Error::new_spanned(
308+
args,
309+
"Expected nothing or `PinnedDrop` as arguments to `#[pin_data]`.",
310+
))
311+
}
312+
};
313+
// We need to disallow normal `Drop` implementation, the exact behavior depends on whether
314+
// `PinnedDrop` was specified in `args`.
315+
Ok(if has_pinned_drop {
316+
// When `PinnedDrop` was specified we just implement `Drop` and delegate.
317+
quote! {
318+
impl #impl_generics ::core::ops::Drop for #ident #ty_generics
319+
#whr
320+
{
321+
fn drop(&mut self) {
322+
// SAFETY: Since this is a destructor, `self` will not move after this function
323+
// terminates, since it is inaccessible.
324+
let pinned = unsafe { ::core::pin::Pin::new_unchecked(self) };
325+
// SAFETY: Since this is a drop function, we can create this token to call the
326+
// pinned destructor of this type.
327+
let token = unsafe { ::pin_init::__internal::OnlyCallFromDrop::new() };
328+
::pin_init::PinnedDrop::drop(pinned, token);
329+
}
330+
}
331+
}
332+
} else {
333+
// When no `PinnedDrop` was specified, then we have to prevent implementing drop.
334+
quote! {
335+
// We prevent this by creating a trait that will be implemented for all types implementing
336+
// `Drop`. Additionally we will implement this trait for the struct leading to a conflict,
337+
// if it also implements `Drop`
338+
trait MustNotImplDrop {}
339+
#[expect(drop_bounds)]
340+
impl<T: ::core::ops::Drop> MustNotImplDrop for T {}
341+
impl #impl_generics MustNotImplDrop for #ident #ty_generics
342+
#whr
343+
{}
344+
// We also take care to prevent users from writing a useless `PinnedDrop` implementation.
345+
// They might implement `PinnedDrop` correctly for the struct, but forget to give
346+
// `PinnedDrop` as the parameter to `#[pin_data]`.
347+
#[expect(non_camel_case_types)]
348+
trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {}
349+
impl<T: ::pin_init::PinnedDrop> UselessPinnedDropImpl_you_need_to_specify_PinnedDrop
350+
for T {}
351+
impl #impl_generics
352+
UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for #ident #ty_generics
353+
#whr
354+
{}
355+
}
356+
})
357+
}

tests/many_generics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct Foo<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0>
1212
where
1313
T: Bar<'a, 1>,
1414
{
15-
array: [u8; 1024 * 1024],
15+
_array: [u8; 1024 * 1024],
1616
r: &'b mut [&'a mut T; SIZE],
1717
#[pin]
1818
_pin: PhantomPinned,

tests/ui/compile-fail/pin_data/missing_comma.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ struct Foo {
55
a: Box<Foo>
66
b: Box<Foo>
77
}
8+
9+
fn main() {}

0 commit comments

Comments
 (0)