Skip to content

Commit 8079c35

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 <[email protected]>
1 parent 135c024 commit 8079c35

16 files changed

+416
-69
lines changed

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: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
// SPDX-License-Identifier: Apache-2.0 OR MIT
2+
3+
use proc_macro2::TokenStream;
4+
use quote::quote;
5+
use syn::{
6+
parse_macro_input, parse_quote,
7+
visit_mut::{visit_path_segment_mut, VisitMut},
8+
Attribute, 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+
quote! {
121+
#(#attrs)*
122+
#vis unsafe fn #ident<E>(
123+
self,
124+
slot: *mut #ty,
125+
init: impl ::pin_init::Init<#ty, E>,
126+
) -> ::core::result::Result<(), E> {
127+
unsafe { ::pin_init::Init::__init(init, slot) }
128+
}
129+
}
130+
},
131+
)
132+
.collect::<TokenStream>();
133+
134+
// For every field, we create a projection function according to its projection type. If a
135+
// field is structurally pinned, then it must be initialized via `PinInit`, if it is not
136+
// structurally pinned, then it must be initialized via `Init`.
137+
let pinned_field_accessors = fields
138+
.iter_mut()
139+
.filter(|f| is_pinned(f))
140+
.map(
141+
|Field {
142+
vis,
143+
ident,
144+
ty,
145+
attrs,
146+
..
147+
}| {
148+
attrs.retain(|a| !a.path().is_ident("pin"));
149+
quote! {
150+
#(#attrs)*
151+
#vis unsafe fn #ident<E>(
152+
self,
153+
slot: *mut #ty,
154+
init: impl ::pin_init::PinInit<#ty, E>,
155+
) -> ::core::result::Result<(), E> {
156+
unsafe { ::pin_init::PinInit::__pinned_init(init, slot) }
157+
}
158+
}
159+
},
160+
)
161+
.collect::<TokenStream>();
162+
quote! {
163+
// We declare this struct which will host all of the projection function for our type. It
164+
// will be invariant over all generic parameters which are inherited from the struct.
165+
#vis struct __ThePinData #generics
166+
#whr
167+
{
168+
__phantom: ::core::marker::PhantomData<
169+
fn(#ident #ty_generics) -> #ident #ty_generics
170+
>,
171+
}
172+
173+
impl #impl_generics ::core::clone::Clone for __ThePinData #ty_generics
174+
#whr
175+
{
176+
fn clone(&self) -> Self { *self }
177+
}
178+
179+
impl #impl_generics ::core::marker::Copy for __ThePinData #ty_generics
180+
#whr
181+
{}
182+
183+
#[allow(dead_code)] // Some functions might never be used and private.
184+
#[expect(clippy::missing_safety_doc)]
185+
impl #impl_generics __ThePinData #ty_generics
186+
#whr
187+
{
188+
#pinned_field_accessors
189+
#not_pinned_field_accessors
190+
}
191+
192+
// SAFETY: We have added the correct projection functions above to `__ThePinData` and
193+
// we also use the least restrictive generics possible.
194+
unsafe impl #impl_generics
195+
::pin_init::__internal::HasPinData for #ident #ty_generics
196+
#whr
197+
{
198+
type PinData = __ThePinData #ty_generics;
199+
200+
unsafe fn __pin_data() -> Self::PinData {
201+
__ThePinData { __phantom: ::core::marker::PhantomData }
202+
}
203+
}
204+
205+
unsafe impl #impl_generics
206+
::pin_init::__internal::PinData for __ThePinData #ty_generics
207+
#whr
208+
{
209+
type Datee = #ident #ty_generics;
210+
}
211+
}
212+
}
213+
214+
fn unpin_impl(
215+
ItemStruct {
216+
ident,
217+
generics,
218+
fields,
219+
..
220+
}: &ItemStruct,
221+
) -> TokenStream {
222+
let generics_with_pinlt = {
223+
let mut g = generics.clone();
224+
g.params.insert(0, parse_quote!('__pin));
225+
let _ = g.make_where_clause();
226+
g
227+
};
228+
let (
229+
impl_generics_with_pinlt,
230+
ty_generics_with_pinlt,
231+
Some(WhereClause {
232+
where_token,
233+
predicates,
234+
}),
235+
) = generics_with_pinlt.split_for_impl()
236+
else {
237+
unreachable!()
238+
};
239+
let (_, ty_generics, _) = generics.split_for_impl();
240+
let mut pinned_fields = fields
241+
.iter()
242+
.filter(|f| is_pinned(f))
243+
.cloned()
244+
.collect::<Vec<_>>();
245+
for field in &mut pinned_fields {
246+
field.attrs.retain(|a| !a.path().is_ident("pin"));
247+
}
248+
quote! {
249+
// This struct will be used for the unpin analysis. It is needed, because only structurally
250+
// pinned fields are relevant whether the struct should implement `Unpin`.
251+
#[allow(dead_code)] // The fields below are never used.
252+
struct __Unpin #generics_with_pinlt
253+
#where_token
254+
#predicates
255+
{
256+
__phantom_pin: ::core::marker::PhantomData<fn(&'__pin ()) -> &'__pin ()>,
257+
__phantom: ::core::marker::PhantomData<
258+
fn(#ident #ty_generics) -> #ident #ty_generics
259+
>,
260+
#(#pinned_fields),*
261+
}
262+
263+
#[doc(hidden)]
264+
impl #impl_generics_with_pinlt ::core::marker::Unpin for #ident #ty_generics
265+
#where_token
266+
__Unpin #ty_generics_with_pinlt: ::core::marker::Unpin,
267+
#predicates
268+
{}
269+
}
270+
}
271+
272+
fn drop_impl(
273+
ItemStruct {
274+
ident, generics, ..
275+
}: &ItemStruct,
276+
args: TokenStream,
277+
) -> Result<TokenStream> {
278+
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
279+
let has_pinned_drop = match syn::parse2::<Option<syn::Ident>>(args.clone()) {
280+
Ok(None) => false,
281+
Ok(Some(ident)) if ident == "PinnedDrop" => true,
282+
_ => {
283+
return Err(Error::new_spanned(
284+
args,
285+
"Expected nothing or `PinnedDrop` as arguments to `#[pin_data]`.",
286+
))
287+
}
288+
};
289+
// We need to disallow normal `Drop` implementation, the exact behavior depends on whether
290+
// `PinnedDrop` was specified in `args`.
291+
Ok(if has_pinned_drop {
292+
// When `PinnedDrop` was specified we just implement `Drop` and delegate.
293+
quote! {
294+
impl #impl_generics ::core::ops::Drop for #ident #ty_generics
295+
#whr
296+
{
297+
fn drop(&mut self) {
298+
// SAFETY: Since this is a destructor, `self` will not move after this function
299+
// terminates, since it is inaccessible.
300+
let pinned = unsafe { ::core::pin::Pin::new_unchecked(self) };
301+
// SAFETY: Since this is a drop function, we can create this token to call the
302+
// pinned destructor of this type.
303+
let token = unsafe { ::pin_init::__internal::OnlyCallFromDrop::new() };
304+
::pin_init::PinnedDrop::drop(pinned, token);
305+
}
306+
}
307+
}
308+
} else {
309+
// When no `PinnedDrop` was specified, then we have to prevent implementing drop.
310+
quote! {
311+
// We prevent this by creating a trait that will be implemented for all types implementing
312+
// `Drop`. Additionally we will implement this trait for the struct leading to a conflict,
313+
// if it also implements `Drop`
314+
trait MustNotImplDrop {}
315+
#[expect(drop_bounds)]
316+
impl<T: ::core::ops::Drop> MustNotImplDrop for T {}
317+
impl #impl_generics MustNotImplDrop for #ident #ty_generics
318+
#whr
319+
{}
320+
// We also take care to prevent users from writing a useless `PinnedDrop` implementation.
321+
// They might implement `PinnedDrop` correctly for the struct, but forget to give
322+
// `PinnedDrop` as the parameter to `#[pin_data]`.
323+
#[expect(non_camel_case_types)]
324+
trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {}
325+
impl<T: ::pin_init::PinnedDrop> UselessPinnedDropImpl_you_need_to_specify_PinnedDrop
326+
for T {}
327+
impl #impl_generics
328+
UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for #ident #ty_generics
329+
#whr
330+
{}
331+
}
332+
})
333+
}

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() {}

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@ error: expected `,`, or `}`, found `b`
44
5 | a: Box<Foo>
55
| ^ help: try adding a comma: `,`
66

7-
error: recursion limit reached while expanding `$crate::__pin_data!`
8-
--> tests/ui/compile-fail/pin_data/missing_comma.rs:3:1
7+
error: expected `,`
8+
--> tests/ui/compile-fail/pin_data/missing_comma.rs:6:5
99
|
10-
3 | #[pin_data]
11-
| ^^^^^^^^^^^
12-
|
13-
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`$CRATE`)
14-
= note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info)
10+
6 | b: Box<Foo>
11+
| ^

tests/ui/compile-fail/pin_data/missing_pin.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ note: required by a bound in `__ThePinData::a`
1818
5 | struct Foo {
1919
6 | a: usize,
2020
| - required by a bound in this associated function
21-
= note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info)
21+
= note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)