blob: 2795a774ca09fc4201bc8ef7e9473b3d92194ae5 [file] [log] [blame]
Devin Jeanpierref643afa2022-02-23 20:50:48 +00001// Part of the Crubit project, under the Apache License v2.0 with LLVM
2// Exceptions. See /LICENSE for license information.
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5use proc_macro::TokenStream;
6use proc_macro2::{Ident, Span};
7use quote::{quote, quote_spanned, ToTokens as _};
Devin Jeanpierreab912362022-06-09 16:49:52 -07008use std::borrow::Cow;
9use std::collections::HashSet;
Devin Jeanpierrebe1efd22022-05-10 16:52:07 -070010use syn::parse::Parse;
Devin Jeanpierref643afa2022-02-23 20:50:48 +000011use syn::spanned::Spanned as _;
Devin Jeanpierrebe1efd22022-05-10 16:52:07 -070012use syn::Token;
Devin Jeanpierref643afa2022-02-23 20:50:48 +000013
14// TODO(jeanpierreda): derive constructors and assignment for copy and move.
15
Devin Jeanpierree9866732022-07-25 05:52:09 -070016const FIELD_FOR_MUST_USE_CTOR: &'static str = "__must_use_ctor_to_initialize";
17
Devin Jeanpierref643afa2022-02-23 20:50:48 +000018#[proc_macro_derive(CtorFrom_Default)]
19pub fn derive_default(item: TokenStream) -> TokenStream {
20 let input = syn::parse_macro_input!(item as syn::DeriveInput);
21
22 let struct_name = input.ident;
Devin Jeanpierre1b8f4a12022-03-22 21:29:42 +000023 let struct_ctor_name =
24 Ident::new(&format!("_ctor_derive_{}_CtorType_Default", struct_name), Span::call_site());
Devin Jeanpierre31a2f972022-05-09 06:11:05 -070025 let fields: proc_macro2::TokenStream = match &input.data {
Devin Jeanpierref643afa2022-02-23 20:50:48 +000026 syn::Data::Struct(data) => {
27 if let syn::Fields::Unit = data.fields {
Devin Jeanpierre31a2f972022-05-09 06:11:05 -070028 quote! {}
29 } else {
Devin Jeanpierree9866732022-07-25 05:52:09 -070030 let filled_fields = data.fields.iter().enumerate().filter_map(|(i, field)| {
Devin Jeanpierref643afa2022-02-23 20:50:48 +000031 let field_i = syn::Index::from(i);
Devin Jeanpierree9866732022-07-25 05:52:09 -070032 let field_name;
33 // This logic is here in case you derive default on the output of
34 // `#[recursively_pinned]`, but it's obviously not very flexible. For example,
35 // maybe we want to compute a non-colliding field name, and maybe there are
36 // other ordering problems.
37 match &field.ident {
38 Some(name) if name == FIELD_FOR_MUST_USE_CTOR => return None,
39 Some(name) => field_name = quote! {#name},
40 None => field_name = quote! {#field_i},
Devin Jeanpierref643afa2022-02-23 20:50:48 +000041 };
42
43 let field_type = &field.ty;
Devin Jeanpierree9866732022-07-25 05:52:09 -070044 Some(quote_spanned! {field.span() =>
Devin Jeanpierref643afa2022-02-23 20:50:48 +000045 #field_name: <#field_type as ::ctor::CtorNew<()>>::ctor_new(())
Devin Jeanpierree9866732022-07-25 05:52:09 -070046 })
Devin Jeanpierre31a2f972022-05-09 06:11:05 -070047 });
48 quote! {{ #(#filled_fields),* }}
49 }
Devin Jeanpierref643afa2022-02-23 20:50:48 +000050 }
Devin Jeanpierre83f26942022-05-09 09:21:33 -070051 syn::Data::Enum(e) => {
52 return syn::Error::new(e.enum_token.span, "Enums are not supported")
53 .into_compile_error()
54 .into();
55 }
56 syn::Data::Union(u) => {
57 return syn::Error::new(u.union_token.span, "Unions are not supported")
58 .into_compile_error()
59 .into();
60 }
Devin Jeanpierref643afa2022-02-23 20:50:48 +000061 };
62
63 let expanded = quote! {
64 struct #struct_ctor_name();
65
66 impl ::ctor::Ctor for #struct_ctor_name {
67 type Output = #struct_name;
68 unsafe fn ctor(self, dest: ::std::pin::Pin<&mut ::std::mem::MaybeUninit<Self::Output>>) {
69 ::ctor::ctor!(
Devin Jeanpierre31a2f972022-05-09 06:11:05 -070070 #struct_name #fields
Devin Jeanpierref643afa2022-02-23 20:50:48 +000071 ).ctor(dest)
72 }
73 }
74
75 impl !::std::marker::Unpin for #struct_ctor_name {}
76
77 impl ::ctor::CtorNew<()> for #struct_name {
78 type CtorType = #struct_ctor_name;
79
80 fn ctor_new(_args: ()) -> #struct_ctor_name { #struct_ctor_name() }
81 }
82 };
83 TokenStream::from(expanded)
84}
85
Devin Jeanpierre57aac932022-05-17 17:18:44 -070086/// `project_pin_type!(foo::T)` is the name of the type returned by
87/// `foo::T::project_pin()`.
Devin Jeanpierref643afa2022-02-23 20:50:48 +000088///
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -070089/// If `foo::T` is not `#[recursively_pinned]`, then this returns the name it
90/// would have used, but is essentially useless.
91#[proc_macro]
Devin Jeanpierre57aac932022-05-17 17:18:44 -070092pub fn project_pin_type(name: TokenStream) -> TokenStream {
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -070093 let mut name = syn::parse_macro_input!(name as syn::Path);
Devin Jeanpierre83f26942022-05-09 09:21:33 -070094 match name.segments.last_mut() {
95 None => {
96 return syn::Error::new(name.span(), "Path must have at least one element")
97 .into_compile_error()
98 .into();
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -070099 }
Devin Jeanpierre83f26942022-05-09 09:21:33 -0700100 Some(last) => {
101 if let syn::PathArguments::Parenthesized(p) = &last.arguments {
102 return syn::Error::new(
103 p.span(),
104 "Parenthesized paths (e.g. fn, Fn) do not have projected equivalents.",
105 )
106 .into_compile_error()
107 .into();
108 }
Devin Jeanpierre57aac932022-05-17 17:18:44 -0700109 last.ident = project_pin_ident(&last.ident);
Devin Jeanpierre83f26942022-05-09 09:21:33 -0700110 }
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700111 }
112 TokenStream::from(quote! { #name })
113}
114
Devin Jeanpierre57aac932022-05-17 17:18:44 -0700115fn project_pin_ident(ident: &Ident) -> Ident {
116 Ident::new(&format!("__CrubitProjectPin{}", ident), Span::call_site())
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700117}
118
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700119/// Defines the `project_pin` function, and its return value.
120///
121/// If the input is a union, this returns nothing, and pin-projection is not
122/// implemented.
Devin Jeanpierreab912362022-06-09 16:49:52 -0700123fn project_pin_impl(input: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
124 let is_fieldless = match &input.data {
Devin Jeanpierre239f7612022-05-09 06:09:08 -0700125 syn::Data::Struct(data) => data.fields.is_empty(),
126 syn::Data::Enum(e) => e.variants.iter().all(|variant| variant.fields.is_empty()),
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700127 syn::Data::Union(_) => {
128 return Ok(quote! {});
Devin Jeanpierre83f26942022-05-09 09:21:33 -0700129 }
Devin Jeanpierre239f7612022-05-09 06:09:08 -0700130 };
131
Devin Jeanpierreab912362022-06-09 16:49:52 -0700132 let mut projected = input.clone();
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700133 // TODO(jeanpierreda): check attributes for repr(packed)
134 projected.attrs.clear();
135 projected.ident = project_pin_ident(&projected.ident);
136
Devin Jeanpierreab912362022-06-09 16:49:52 -0700137 let lifetime = if is_fieldless {
138 quote! {}
Devin Jeanpierre239f7612022-05-09 06:09:08 -0700139 } else {
Devin Jeanpierreab912362022-06-09 16:49:52 -0700140 add_lifetime(&mut projected.generics, "'proj")
141 };
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700142
143 let project_field = |field: &mut syn::Field| {
144 field.attrs.clear();
145 let field_ty = &field.ty;
146 let pin_ty = syn::parse_quote!(::std::pin::Pin<& #lifetime mut #field_ty>);
147 field.ty = syn::Type::Path(pin_ty);
148 };
149 // returns the braced parts of a projection pattern and return value.
Devin Jeanpierree9866732022-07-25 05:52:09 -0700150 // e.g. {foo, bar, ..}, {foo: Pin::new_unchecked(foo), bar:
151 // Pin::new_unchecked(bar)}
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700152 let pat_project = |fields: &mut syn::Fields| {
153 let mut pat = quote! {};
154 let mut project = quote! {};
155 for (i, field) in fields.iter_mut().enumerate() {
156 // TODO(jeanpierreda): check attributes for e.g. #[unpin]
157 field.attrs.clear();
158 let lhs;
159 let rhs;
160 if let Some(ident) = &field.ident {
161 lhs = quote! {#ident};
162 rhs = ident.clone();
163 pat.extend(quote! {#lhs,});
164 } else {
165 lhs = proc_macro2::Literal::usize_unsuffixed(i).into_token_stream();
166 rhs = Ident::new(&format!("item_{i}"), Span::call_site());
167 pat.extend(quote! {#lhs: #rhs,});
168 }
169 project.extend(quote! {#lhs: ::std::pin::Pin::new_unchecked(#rhs),});
170 }
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700171 // Also ignore the __must_use_ctor_to_initialize field, if present.
172 pat.extend(quote! {..});
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700173 (quote! {{#pat}}, quote! {{#project}})
174 };
175 let project_body;
Devin Jeanpierreab912362022-06-09 16:49:52 -0700176 let input_ident = &input.ident;
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700177 let projected_ident = &projected.ident;
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700178 match &mut projected.data {
179 syn::Data::Struct(data) => {
180 for field in &mut data.fields {
181 project_field(field);
182 }
183 let (pat, project) = pat_project(&mut data.fields);
184 project_body = quote! {
Devin Jeanpierreab912362022-06-09 16:49:52 -0700185 let #input_ident #pat = from;
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700186 #projected_ident #project
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700187 };
188 }
189 syn::Data::Enum(e) => {
190 let mut match_body = quote! {};
191 for variant in &mut e.variants {
192 for field in &mut variant.fields {
193 project_field(field);
194 }
195 let (pat, project) = pat_project(&mut variant.fields);
196 let variant_ident = &variant.ident;
197 match_body.extend(quote! {
Devin Jeanpierreab912362022-06-09 16:49:52 -0700198 #input_ident::#variant_ident #pat => #projected_ident::#variant_ident #project,
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700199 });
200 }
201 project_body = quote! {
202 match from {
203 #match_body
204 }
205 };
206 }
Devin Jeanpierreab912362022-06-09 16:49:52 -0700207 syn::Data::Union(_) => {
208 unreachable!("project_pin_impl should early return when it finds a union")
209 }
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700210 }
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700211
Devin Jeanpierreab912362022-06-09 16:49:52 -0700212 let (input_impl_generics, input_ty_generics, input_where_clause) =
213 input.generics.split_for_impl();
214 let (_, projected_generics, _) = projected.generics.split_for_impl();
215
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700216 Ok(quote! {
217 #projected
218
Devin Jeanpierreab912362022-06-09 16:49:52 -0700219 impl #input_impl_generics #input_ident #input_ty_generics #input_where_clause {
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700220 #[must_use]
Devin Jeanpierreab912362022-06-09 16:49:52 -0700221 pub fn project_pin<#lifetime>(self: ::std::pin::Pin<& #lifetime mut Self>) -> #projected_ident #projected_generics {
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700222 unsafe {
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700223 let from = ::std::pin::Pin::into_inner_unchecked(self);
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700224 #project_body
225 }
226 }
227 }
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700228 })
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700229}
230
Devin Jeanpierreab912362022-06-09 16:49:52 -0700231/// Adds a new lifetime to `generics`, returning the quoted lifetime name.
232fn add_lifetime(generics: &mut syn::Generics, prefix: &str) -> proc_macro2::TokenStream {
233 let taken_lifetimes: HashSet<&syn::Lifetime> =
234 generics.lifetimes().map(|def| &def.lifetime).collect();
235 let mut name = Cow::Borrowed(prefix);
236 let mut i = 1;
237 let lifetime = loop {
238 let lifetime = syn::Lifetime::new(&name, Span::call_site());
239 if !taken_lifetimes.contains(&lifetime) {
240 break lifetime;
241 }
242
243 i += 1;
244 name = Cow::Owned(format!("{prefix}_{i}"));
245 };
246 let quoted_lifetime = quote! {#lifetime};
247 generics.params.push(syn::GenericParam::Lifetime(syn::LifetimeDef::new(lifetime)));
248 quoted_lifetime
249}
250
Devin Jeanpierrebe1efd22022-05-10 16:52:07 -0700251#[derive(Default)]
252struct RecursivelyPinnedArgs {
253 is_pinned_drop: bool,
254}
255
256impl Parse for RecursivelyPinnedArgs {
257 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
258 let args = <syn::punctuated::Punctuated<Ident, Token![,]>>::parse_terminated(input)?;
259 if args.len() > 1 {
260 return Err(syn::Error::new(
261 input.span(), // not args.span(), as that is only for the first argument.
262 &format!("expected at most 1 argument, got: {}", args.len()),
263 ));
264 }
265 let is_pinned_drop = if let Some(arg) = args.first() {
266 if arg != "PinnedDrop" {
267 return Err(syn::Error::new(
268 arg.span(),
269 "unexpected argument (wasn't `PinnedDrop`)",
270 ));
271 }
272 true
273 } else {
274 false
275 };
276 Ok(RecursivelyPinnedArgs { is_pinned_drop })
277 }
278}
279
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700280/// Prevents this type from being directly created outside of this crate in safe
281/// code.
282///
283/// For enums and unit structs, this uses the `#[non_exhaustive]` attribute.
284/// This leads to unfortunate error messages, but there is no other way to
285/// prevent creation of an enum or a unit struct at this time.
286///
287/// For tuple structs, we also use `#[non_exhaustive]`, as it's no worse than
288/// the alternative. Both adding a private field and adding `#[non_exhaustive]`
289/// lead to indirect error messages, but `#[non_exhaustive]` is the more likely
290/// of the two to ever get custom error message support.
291///
292/// Finally, for structs with named fields, we actually *cannot* use
293/// `#[non_exhaustive]`, because it would make the struct not FFI-safe, and
294/// structs with named fields are specifically supported for C++ interop.
295/// Instead, we use a private field with a name that indicates the error.
296/// (`__must_use_ctor_to_initialize`).
297///
298/// Unions are not yet implemented properly.
299///
300/// ---
301///
302/// Note that the use of `#[non_exhaustive]` also has other effects. At the
303/// least: tuple variants and tuple structs marked with `#[non_exhaustive]`
304/// cannot be pattern matched using the "normal" syntax. Instead, one must use
305/// curly braces. (Broken: `T(x, ..)`; woken: `T{0: x, ..}`).
306///
307/// (This does not seem very intentional, and with all luck will be fixed before
308/// too long.)
309fn forbid_initialization(s: &mut syn::DeriveInput) {
310 let non_exhaustive_attr = syn::parse_quote!(#[non_exhaustive]);
311 match &mut s.data {
312 // TODO(b/232969667): prevent creation of unions from safe code.
313 // (E.g. hide inside a struct.)
314 syn::Data::Union(_) => return,
315 syn::Data::Struct(data) => {
316 match &mut data.fields {
317 syn::Fields::Unit | syn::Fields::Unnamed(_) => {
318 s.attrs.insert(0, non_exhaustive_attr);
319 }
320 syn::Fields::Named(fields) => {
321 fields.named.push(syn::Field {
322 attrs: vec![],
323 vis: syn::Visibility::Inherited,
324 // TODO(jeanpierreda): better hygiene: work even if a field has the same name.
Devin Jeanpierree9866732022-07-25 05:52:09 -0700325 ident: Some(Ident::new(FIELD_FOR_MUST_USE_CTOR, Span::call_site())),
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700326 colon_token: Some(<syn::Token![:]>::default()),
327 ty: syn::parse_quote!([u8; 0]),
328 });
329 }
330 }
331 }
332 syn::Data::Enum(e) => {
333 // Enums can't have private fields. Instead, we need to add #[non_exhaustive] to
334 // every variant -- this makes it impossible to construct the
335 // variants.
336 for variant in &mut e.variants {
337 variant.attrs.insert(0, non_exhaustive_attr.clone());
338 }
339 }
340 }
341}
342
Devin Jeanpierrebe1efd22022-05-10 16:52:07 -0700343/// `#[recursively_pinned]` pins every field, similar to `#[pin_project]`, and
344/// marks the struct `!Unpin`.
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000345///
346/// Example:
347///
348/// ```
349/// #[recursively_pinned]
350/// struct S {
351/// field: i32,
352/// }
353/// ```
354///
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700355/// This is analogous to using pin_project, pinning every field, as so:
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000356///
357/// ```
Devin Jeanpierre1c28ff22022-05-09 06:04:57 -0700358/// #[pin_project(!Unpin)]
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000359/// struct S {
360/// #[pin]
361/// field: i32,
362/// }
363/// ```
Devin Jeanpierrebe1efd22022-05-10 16:52:07 -0700364///
365/// ## Arguments
366///
367/// ### `PinnedDrop`
368///
369/// To define a destructor for a recursively-pinned struct, pass `PinnedDrop`
370/// and implement the `PinnedDrop` trait.
371///
372/// `#[recursively_pinned]` prohibits implementing `Drop`, as that would make it
373/// easy to violate the `Pin` guarantee. Instead, to define a destructor, one
374/// must define a `PinnedDrop` impl, as so:
375///
376/// ```
377/// #[recursively_pinned(PinnedDrop)]
378/// struct S {
379/// field: i32,
380/// }
381///
382/// impl PinnedDrop for S {
383/// unsafe fn pinned_drop(self: Pin<&mut Self>) {
384/// println!("I am being destroyed!");
385/// }
386/// }
387/// ```
388///
389/// (This is analogous to `#[pin_project(PinnedDrop)]`.)
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700390///
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700391/// ## Direct initialization
392///
393/// Use the `ctor!` macro to instantiate recursively pinned types. For example:
394///
395/// ```
396/// // equivalent to `let x = Point {x: 3, y: 4}`, but uses pinned construction.
397/// emplace! {
398/// let x = ctor!(Point {x: 3, y: 4});
399/// }
400/// ```
401///
402/// Recursively pinned types cannot be created directly in safe code, as they
403/// are pinned from the very moment of their creation.
404///
405/// This is prevented either using `#[non_exhaustive]` or using a private field,
406/// depending on the type in question. For example, enums use
407/// `#[non_exhaustive]`, and structs with named fields use a private field named
408/// `__must_use_ctor_to_initialize`. This can lead to confusing error messages,
409/// so watch out!
410///
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700411/// ## Supported types
412///
413/// Structs, enums, and unions are all supported. However, unions do not receive
414/// a `pin_project` method, as there is no way to implement pin projection for
415/// unions. (One cannot know which field is active.)
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000416#[proc_macro_attribute]
417pub fn recursively_pinned(args: TokenStream, item: TokenStream) -> TokenStream {
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700418 match recursively_pinned_impl(args.into(), item.into()) {
419 Ok(t) => t.into(),
420 Err(e) => e.into_compile_error().into(),
421 }
422}
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000423
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700424/// A separate function for calling from tests.
425///
426/// See e.g. https://users.rust-lang.org/t/procedural-macro-api-is-used-outside-of-a-procedural-macro/30841
427fn recursively_pinned_impl(
428 args: proc_macro2::TokenStream,
429 item: proc_macro2::TokenStream,
430) -> syn::Result<proc_macro2::TokenStream> {
431 let args = syn::parse2::<RecursivelyPinnedArgs>(args)?;
432 let mut input = syn::parse2::<syn::DeriveInput>(item)?;
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000433
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700434 let project_pin_impl = project_pin_impl(&input)?;
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000435 let name = input.ident.clone();
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000436
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700437 // Create two copies of input: one (public) has a private field that can't be
438 // instantiated. The other (only visible via
439 // RecursivelyPinned::CtorInitializedFields) doesn't have this field.
440 // This causes `ctor!(Foo {})` to work, but `Foo{}` to complain of a missing
441 // field.
442 let mut ctor_initialized_input = input.clone();
Devin Jeanpierree9866732022-07-25 05:52:09 -0700443 // Removing repr(C) triggers dead-code detection.
444 ctor_initialized_input.attrs = vec![syn::parse_quote!(#[allow(dead_code)])];
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700445 // TODO(jeanpierreda): This should really check for name collisions with any types
446 // used in the fields. Collisions with other names don't matter, because the
447 // type is locally defined within a narrow scope.
448 ctor_initialized_input.ident = syn::Ident::new(&format!("__CrubitCtor{name}"), name.span());
449 let ctor_initialized_name = &ctor_initialized_input.ident;
450 forbid_initialization(&mut input);
451
Devin Jeanpierreab912362022-06-09 16:49:52 -0700452 let (input_impl_generics, input_ty_generics, input_where_clause) =
453 input.generics.split_for_impl();
454
Devin Jeanpierrebe1efd22022-05-10 16:52:07 -0700455 let drop_impl = if args.is_pinned_drop {
456 quote! {
Devin Jeanpierreab912362022-06-09 16:49:52 -0700457 impl #input_impl_generics Drop for #name #input_ty_generics #input_where_clause {
Devin Jeanpierrebe1efd22022-05-10 16:52:07 -0700458 fn drop(&mut self) {
459 unsafe {::ctor::PinnedDrop::pinned_drop(::std::pin::Pin::new_unchecked(self))}
460 }
461 }
462 }
463 } else {
464 quote! {
Devin Jeanpierreab912362022-06-09 16:49:52 -0700465 impl #input_impl_generics ::ctor::macro_internal::DoNotImplDrop for #name #input_ty_generics #input_where_clause {}
Devin Jeanpierrebe1efd22022-05-10 16:52:07 -0700466 /// A no-op PinnedDrop that will cause an error if the user also defines PinnedDrop,
467 /// due to forgetting to pass `PinnedDrop` to #[recursively_pinned(PinnedDrop)]`.
Devin Jeanpierreab912362022-06-09 16:49:52 -0700468 impl #input_impl_generics ::ctor::PinnedDrop for #name #input_ty_generics #input_where_clause {
Devin Jeanpierrebe1efd22022-05-10 16:52:07 -0700469 unsafe fn pinned_drop(self: ::std::pin::Pin<&mut Self>) {}
470 }
471 }
472 };
473
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700474 Ok(quote! {
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000475 #input
Devin Jeanpierre190b90a2022-05-24 06:00:34 -0700476 #project_pin_impl
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000477
Devin Jeanpierrebe1efd22022-05-10 16:52:07 -0700478 #drop_impl
Devin Jeanpierreab912362022-06-09 16:49:52 -0700479 impl #input_impl_generics !Unpin for #name #input_ty_generics #input_where_clause {}
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000480
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700481 // Introduce a new scope to limit the blast radius of the CtorInitializedFields type.
482 // This lets us use relatively readable names: while the impl is visible outside the scope,
483 // type is otherwise not visible.
484 const _ : () = {
485 #ctor_initialized_input
486
487 unsafe impl #input_impl_generics ::ctor::RecursivelyPinned for #name #input_ty_generics #input_where_clause {
488 type CtorInitializedFields = #ctor_initialized_name #input_ty_generics;
489 }
490 };
491 })
492}
493
494#[cfg(test)]
495mod test {
496 use super::*;
497 use token_stream_matchers::assert_rs_matches;
498
499 /// Essentially a change detector, but handy for debugging.
500 ///
501 /// At time of writing, we can't write negative compilation tests, so
502 /// asserting on the output is as close as we can get. Once negative
503 /// compilation tests are added, it would be better to test various
504 /// safety features that way.
505 #[test]
506 fn test_recursively_pinned_struct() {
507 let definition =
508 recursively_pinned_impl(quote! {}, quote! {#[repr(C)] struct S {x: i32}}).unwrap();
509
510 // The struct can't be directly created, but can be created via
511 // CtorInitializedFields:
512 assert_rs_matches!(
513 definition,
514 quote! {
515 #[repr(C)]
516 struct S {
517 x: i32,
518 __must_use_ctor_to_initialize: [u8; 0]
519 }
520 }
521 );
522 assert_rs_matches!(
523 definition,
524 quote! {
525 const _: () = {
Devin Jeanpierree9866732022-07-25 05:52:09 -0700526 #[allow(dead_code)]
527 struct __CrubitCtorS {x: i32}
528 unsafe impl ::ctor::RecursivelyPinned for S {
529 type CtorInitializedFields = __CrubitCtorS;
530 }
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700531 };
532 }
533 );
534
535 // The type is non-Unpin:
536 assert_rs_matches!(
537 definition,
538 quote! {
539 impl !Unpin for S {}
540 }
541 );
542
543 // The remaining features of the generated output are better tested via
544 // real tests that exercise the code.
545 }
546
547 /// The enum version of `test_recursively_pinned_struct`.
548 #[test]
549 fn test_recursively_pinned_enum() {
550 let definition = recursively_pinned_impl(
551 quote! {},
552 quote! {
553 #[repr(C)]
554 enum E {
555 A,
556 B(i32),
557 }
558 },
559 )
560 .unwrap();
561
562 // The enum variants can't be directly created, but can be created via
563 // CtorInitializedFields:
564 assert_rs_matches!(
565 definition,
566 quote! {
567 #[repr(C)]
568 enum E {
569 #[non_exhaustive]
570 A,
571 #[non_exhaustive]
572 B(i32),
573 }
574 }
575 );
576 assert_rs_matches!(
577 definition,
578 quote! {
579 const _: () = {
Devin Jeanpierree9866732022-07-25 05:52:09 -0700580 #[allow(dead_code)]
Devin Jeanpierred6f3e2a2022-07-20 18:45:42 -0700581 enum __CrubitCtorE {
582 A,
583 B(i32),
584 }
585 unsafe impl ::ctor::RecursivelyPinned for E {
586 type CtorInitializedFields = __CrubitCtorE;
587 }
588 };
589 }
590 );
591
592 // The type is non-Unpin:
593 assert_rs_matches!(
594 definition,
595 quote! {
596 impl !Unpin for E {}
597 }
598 );
599
600 // The remaining features of the generated output are better tested via
601 // real tests that exercise the code.
602 }
Devin Jeanpierref643afa2022-02-23 20:50:48 +0000603}