Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 1 | // 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 | |
| 5 | use proc_macro::TokenStream; |
| 6 | use proc_macro2::{Ident, Span}; |
| 7 | use quote::{quote, quote_spanned, ToTokens as _}; |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 8 | use std::borrow::Cow; |
| 9 | use std::collections::HashSet; |
Devin Jeanpierre | be1efd2 | 2022-05-10 16:52:07 -0700 | [diff] [blame] | 10 | use syn::parse::Parse; |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 11 | use syn::spanned::Spanned as _; |
Devin Jeanpierre | be1efd2 | 2022-05-10 16:52:07 -0700 | [diff] [blame] | 12 | use syn::Token; |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 13 | |
| 14 | // TODO(jeanpierreda): derive constructors and assignment for copy and move. |
| 15 | |
Devin Jeanpierre | e986673 | 2022-07-25 05:52:09 -0700 | [diff] [blame] | 16 | const FIELD_FOR_MUST_USE_CTOR: &'static str = "__must_use_ctor_to_initialize"; |
| 17 | |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 18 | #[proc_macro_derive(CtorFrom_Default)] |
| 19 | pub 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 Jeanpierre | 1b8f4a1 | 2022-03-22 21:29:42 +0000 | [diff] [blame] | 23 | let struct_ctor_name = |
| 24 | Ident::new(&format!("_ctor_derive_{}_CtorType_Default", struct_name), Span::call_site()); |
Devin Jeanpierre | 31a2f97 | 2022-05-09 06:11:05 -0700 | [diff] [blame] | 25 | let fields: proc_macro2::TokenStream = match &input.data { |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 26 | syn::Data::Struct(data) => { |
| 27 | if let syn::Fields::Unit = data.fields { |
Devin Jeanpierre | 31a2f97 | 2022-05-09 06:11:05 -0700 | [diff] [blame] | 28 | quote! {} |
| 29 | } else { |
Devin Jeanpierre | e986673 | 2022-07-25 05:52:09 -0700 | [diff] [blame] | 30 | let filled_fields = data.fields.iter().enumerate().filter_map(|(i, field)| { |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 31 | let field_i = syn::Index::from(i); |
Devin Jeanpierre | e986673 | 2022-07-25 05:52:09 -0700 | [diff] [blame] | 32 | 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 Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 41 | }; |
| 42 | |
| 43 | let field_type = &field.ty; |
Devin Jeanpierre | e986673 | 2022-07-25 05:52:09 -0700 | [diff] [blame] | 44 | Some(quote_spanned! {field.span() => |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 45 | #field_name: <#field_type as ::ctor::CtorNew<()>>::ctor_new(()) |
Devin Jeanpierre | e986673 | 2022-07-25 05:52:09 -0700 | [diff] [blame] | 46 | }) |
Devin Jeanpierre | 31a2f97 | 2022-05-09 06:11:05 -0700 | [diff] [blame] | 47 | }); |
| 48 | quote! {{ #(#filled_fields),* }} |
| 49 | } |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 50 | } |
Devin Jeanpierre | 83f2694 | 2022-05-09 09:21:33 -0700 | [diff] [blame] | 51 | 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 Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 61 | }; |
| 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 Jeanpierre | 31a2f97 | 2022-05-09 06:11:05 -0700 | [diff] [blame] | 70 | #struct_name #fields |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 71 | ).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 Jeanpierre | 57aac93 | 2022-05-17 17:18:44 -0700 | [diff] [blame] | 86 | /// `project_pin_type!(foo::T)` is the name of the type returned by |
| 87 | /// `foo::T::project_pin()`. |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 88 | /// |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 89 | /// 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 Jeanpierre | 57aac93 | 2022-05-17 17:18:44 -0700 | [diff] [blame] | 92 | pub fn project_pin_type(name: TokenStream) -> TokenStream { |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 93 | let mut name = syn::parse_macro_input!(name as syn::Path); |
Devin Jeanpierre | 83f2694 | 2022-05-09 09:21:33 -0700 | [diff] [blame] | 94 | 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 Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 99 | } |
Devin Jeanpierre | 83f2694 | 2022-05-09 09:21:33 -0700 | [diff] [blame] | 100 | 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 Jeanpierre | 57aac93 | 2022-05-17 17:18:44 -0700 | [diff] [blame] | 109 | last.ident = project_pin_ident(&last.ident); |
Devin Jeanpierre | 83f2694 | 2022-05-09 09:21:33 -0700 | [diff] [blame] | 110 | } |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 111 | } |
| 112 | TokenStream::from(quote! { #name }) |
| 113 | } |
| 114 | |
Devin Jeanpierre | 57aac93 | 2022-05-17 17:18:44 -0700 | [diff] [blame] | 115 | fn project_pin_ident(ident: &Ident) -> Ident { |
| 116 | Ident::new(&format!("__CrubitProjectPin{}", ident), Span::call_site()) |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 117 | } |
| 118 | |
Devin Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 119 | /// 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 Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 123 | fn project_pin_impl(input: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> { |
| 124 | let is_fieldless = match &input.data { |
Devin Jeanpierre | 239f761 | 2022-05-09 06:09:08 -0700 | [diff] [blame] | 125 | syn::Data::Struct(data) => data.fields.is_empty(), |
| 126 | syn::Data::Enum(e) => e.variants.iter().all(|variant| variant.fields.is_empty()), |
Devin Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 127 | syn::Data::Union(_) => { |
| 128 | return Ok(quote! {}); |
Devin Jeanpierre | 83f2694 | 2022-05-09 09:21:33 -0700 | [diff] [blame] | 129 | } |
Devin Jeanpierre | 239f761 | 2022-05-09 06:09:08 -0700 | [diff] [blame] | 130 | }; |
| 131 | |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 132 | let mut projected = input.clone(); |
Devin Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 133 | // TODO(jeanpierreda): check attributes for repr(packed) |
| 134 | projected.attrs.clear(); |
| 135 | projected.ident = project_pin_ident(&projected.ident); |
| 136 | |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 137 | let lifetime = if is_fieldless { |
| 138 | quote! {} |
Devin Jeanpierre | 239f761 | 2022-05-09 06:09:08 -0700 | [diff] [blame] | 139 | } else { |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 140 | add_lifetime(&mut projected.generics, "'proj") |
| 141 | }; |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 142 | |
| 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 Jeanpierre | e986673 | 2022-07-25 05:52:09 -0700 | [diff] [blame] | 150 | // e.g. {foo, bar, ..}, {foo: Pin::new_unchecked(foo), bar: |
| 151 | // Pin::new_unchecked(bar)} |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 152 | 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 Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 171 | // Also ignore the __must_use_ctor_to_initialize field, if present. |
| 172 | pat.extend(quote! {..}); |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 173 | (quote! {{#pat}}, quote! {{#project}}) |
| 174 | }; |
| 175 | let project_body; |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 176 | let input_ident = &input.ident; |
Devin Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 177 | let projected_ident = &projected.ident; |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 178 | 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 Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 185 | let #input_ident #pat = from; |
Devin Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 186 | #projected_ident #project |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 187 | }; |
| 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 Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 198 | #input_ident::#variant_ident #pat => #projected_ident::#variant_ident #project, |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 199 | }); |
| 200 | } |
| 201 | project_body = quote! { |
| 202 | match from { |
| 203 | #match_body |
| 204 | } |
| 205 | }; |
| 206 | } |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 207 | syn::Data::Union(_) => { |
| 208 | unreachable!("project_pin_impl should early return when it finds a union") |
| 209 | } |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 210 | } |
Devin Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 211 | |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 212 | 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 Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 216 | Ok(quote! { |
| 217 | #projected |
| 218 | |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 219 | impl #input_impl_generics #input_ident #input_ty_generics #input_where_clause { |
Devin Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 220 | #[must_use] |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 221 | pub fn project_pin<#lifetime>(self: ::std::pin::Pin<& #lifetime mut Self>) -> #projected_ident #projected_generics { |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 222 | unsafe { |
Devin Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 223 | let from = ::std::pin::Pin::into_inner_unchecked(self); |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 224 | #project_body |
| 225 | } |
| 226 | } |
| 227 | } |
Devin Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 228 | }) |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 229 | } |
| 230 | |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 231 | /// Adds a new lifetime to `generics`, returning the quoted lifetime name. |
| 232 | fn 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 Jeanpierre | be1efd2 | 2022-05-10 16:52:07 -0700 | [diff] [blame] | 251 | #[derive(Default)] |
| 252 | struct RecursivelyPinnedArgs { |
| 253 | is_pinned_drop: bool, |
| 254 | } |
| 255 | |
| 256 | impl 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 Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 280 | /// 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.) |
| 309 | fn 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 Jeanpierre | e986673 | 2022-07-25 05:52:09 -0700 | [diff] [blame] | 325 | ident: Some(Ident::new(FIELD_FOR_MUST_USE_CTOR, Span::call_site())), |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 326 | 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 Jeanpierre | be1efd2 | 2022-05-10 16:52:07 -0700 | [diff] [blame] | 343 | /// `#[recursively_pinned]` pins every field, similar to `#[pin_project]`, and |
| 344 | /// marks the struct `!Unpin`. |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 345 | /// |
| 346 | /// Example: |
| 347 | /// |
| 348 | /// ``` |
| 349 | /// #[recursively_pinned] |
| 350 | /// struct S { |
| 351 | /// field: i32, |
| 352 | /// } |
| 353 | /// ``` |
| 354 | /// |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 355 | /// This is analogous to using pin_project, pinning every field, as so: |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 356 | /// |
| 357 | /// ``` |
Devin Jeanpierre | 1c28ff2 | 2022-05-09 06:04:57 -0700 | [diff] [blame] | 358 | /// #[pin_project(!Unpin)] |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 359 | /// struct S { |
| 360 | /// #[pin] |
| 361 | /// field: i32, |
| 362 | /// } |
| 363 | /// ``` |
Devin Jeanpierre | be1efd2 | 2022-05-10 16:52:07 -0700 | [diff] [blame] | 364 | /// |
| 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 Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 390 | /// |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 391 | /// ## 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 Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 411 | /// ## 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 Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 416 | #[proc_macro_attribute] |
| 417 | pub fn recursively_pinned(args: TokenStream, item: TokenStream) -> TokenStream { |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 418 | match recursively_pinned_impl(args.into(), item.into()) { |
| 419 | Ok(t) => t.into(), |
| 420 | Err(e) => e.into_compile_error().into(), |
| 421 | } |
| 422 | } |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 423 | |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 424 | /// 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 |
| 427 | fn 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 Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 433 | |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 434 | let project_pin_impl = project_pin_impl(&input)?; |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 435 | let name = input.ident.clone(); |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 436 | |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 437 | // 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 Jeanpierre | e986673 | 2022-07-25 05:52:09 -0700 | [diff] [blame] | 443 | // Removing repr(C) triggers dead-code detection. |
| 444 | ctor_initialized_input.attrs = vec![syn::parse_quote!(#[allow(dead_code)])]; |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 445 | // 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 Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 452 | let (input_impl_generics, input_ty_generics, input_where_clause) = |
| 453 | input.generics.split_for_impl(); |
| 454 | |
Devin Jeanpierre | be1efd2 | 2022-05-10 16:52:07 -0700 | [diff] [blame] | 455 | let drop_impl = if args.is_pinned_drop { |
| 456 | quote! { |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 457 | impl #input_impl_generics Drop for #name #input_ty_generics #input_where_clause { |
Devin Jeanpierre | be1efd2 | 2022-05-10 16:52:07 -0700 | [diff] [blame] | 458 | fn drop(&mut self) { |
| 459 | unsafe {::ctor::PinnedDrop::pinned_drop(::std::pin::Pin::new_unchecked(self))} |
| 460 | } |
| 461 | } |
| 462 | } |
| 463 | } else { |
| 464 | quote! { |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 465 | impl #input_impl_generics ::ctor::macro_internal::DoNotImplDrop for #name #input_ty_generics #input_where_clause {} |
Devin Jeanpierre | be1efd2 | 2022-05-10 16:52:07 -0700 | [diff] [blame] | 466 | /// 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 Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 468 | impl #input_impl_generics ::ctor::PinnedDrop for #name #input_ty_generics #input_where_clause { |
Devin Jeanpierre | be1efd2 | 2022-05-10 16:52:07 -0700 | [diff] [blame] | 469 | unsafe fn pinned_drop(self: ::std::pin::Pin<&mut Self>) {} |
| 470 | } |
| 471 | } |
| 472 | }; |
| 473 | |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 474 | Ok(quote! { |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 475 | #input |
Devin Jeanpierre | 190b90a | 2022-05-24 06:00:34 -0700 | [diff] [blame] | 476 | #project_pin_impl |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 477 | |
Devin Jeanpierre | be1efd2 | 2022-05-10 16:52:07 -0700 | [diff] [blame] | 478 | #drop_impl |
Devin Jeanpierre | ab91236 | 2022-06-09 16:49:52 -0700 | [diff] [blame] | 479 | impl #input_impl_generics !Unpin for #name #input_ty_generics #input_where_clause {} |
Devin Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 480 | |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 481 | // 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)] |
| 495 | mod 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 Jeanpierre | e986673 | 2022-07-25 05:52:09 -0700 | [diff] [blame] | 526 | #[allow(dead_code)] |
| 527 | struct __CrubitCtorS {x: i32} |
| 528 | unsafe impl ::ctor::RecursivelyPinned for S { |
| 529 | type CtorInitializedFields = __CrubitCtorS; |
| 530 | } |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 531 | }; |
| 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 Jeanpierre | e986673 | 2022-07-25 05:52:09 -0700 | [diff] [blame] | 580 | #[allow(dead_code)] |
Devin Jeanpierre | d6f3e2a | 2022-07-20 18:45:42 -0700 | [diff] [blame] | 581 | 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 Jeanpierre | f643afa | 2022-02-23 20:50:48 +0000 | [diff] [blame] | 603 | } |