diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d9cf32d..9adbee33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Tuple structs are now supported. For `[pin_]init!` , if pinning (`<-` syntax) is required, only the struct syntax can be used, e.g. `init!(Foo { 0: value, 1 <- initializer })`. +- Safely creating references from sibling fields is now supported. - `[pin_]init_scope` functions to run arbitrary code inside of an initializer. - `&'static mut MaybeUninit` now implements `InPlaceWrite`. This enables users to use external allocation mechanisms such as `static_cell`. diff --git a/examples/selfref.rs b/examples/selfref.rs new file mode 100644 index 00000000..5212b925 --- /dev/null +++ b/examples/selfref.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![allow(clippy::disallowed_names)] + +use pin_init::*; + +#[pin_data] +struct SelfRef { + #[uses('_: invariant)] + not_cov: Box bool + 'str>, + + part: &'str str, + str: String, + + mut_part: &'mut_str mut str, + #[borrowed(mut)] + mut_str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + mut_str: "hello world".to_owned(), + mut_part: &mut mut_str[..5], + not_cov: Box::new(move |s| s == str), + })); + + // Access via projection. + println!("{}", foo.as_mut().project().part); + + // Access via accessor. + println!("{}", foo.part()); + + // Access via `with_project`, gives mutable reference. + foo.as_mut().with_project(|proj| { + *proj.part = &proj.str[5..]; + }); + + println!("{}", foo.part()); + + // Access fields that mutable borrow others are similar to those of shared borrow. + println!("{}", foo.as_mut().project().mut_part); + println!("{}", foo.mut_part()); + foo.as_mut().with_project(|proj| { + proj.mut_part.make_ascii_uppercase(); + }); + + // Access non-covariant type using `with_` accessor. + foo.with_not_cov(|not_cov| { + not_cov(""); + }); + + // Access non-covariant type using `with_project`. + foo.as_mut().with_project(|proj| (proj.not_cov)(proj.str)); +} + +fn main() { + use_self_ref(); +} diff --git a/internal/Cargo.toml b/internal/Cargo.toml index e2714fb3..81e668e9 100644 --- a/internal/Cargo.toml +++ b/internal/Cargo.toml @@ -16,7 +16,7 @@ proc-macro = true [dependencies] quote = "1.0.40" proc-macro2 = "1.0.101" -syn = { version = "2.0.106", features = ["full", "parsing", "visit-mut"] } +syn = { version = "2.0.106", features = ["full", "parsing", "visit", "visit-mut"] } [build-dependencies] rustc_version = "0.4" diff --git a/internal/src/init.rs b/internal/src/init.rs index 80e4dc06..ded2b528 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -288,8 +288,10 @@ fn expand( }, }; // `mixed_site` ensures that the data is not accessible to the user-controlled code. - let init_fields = init_fields(&fields, pinned); + let init_fields = make_field_init(&fields, pinned, false); + let drop_check = make_field_init(&fields, pinned, true); let field_check = make_field_check(&fields, init_kind, &path); + Ok(quote_spanned! { Span::mixed_site() => { // Get the data about fields from the supplied type. let data = { @@ -303,17 +305,29 @@ fn expand( // Ensure that `data` really is of type `data` and help with type inference: let init = data.__make_closure::<_, #error>( - move |slot| { + move |slot, data_lt| { #zeroable_check #this - #init_fields + // Generate init twice, which is mostly identical except for lifetimes. + if true { + // In this path, we use the field lifetime from HRTB to prevent environment + // lifetime from entering the fields, and to ensure that the dependency of + // fields is consistent with the field drop of the struct. + #init_fields + } else { + // In this path, we use local lifetime, to make sure that if initialization + // fails, the destructor execution will not cause lifetime issues. This is + // separate as implied bounds between field lifetimes can be inconsistent with + // that of the drop. + #drop_check + } #field_check // SAFETY: we are the `init!` macro that is allowed to call this. Ok(unsafe { ::pin_init::__internal::InitOk::new() }) } ); let init = move |slot| -> ::core::result::Result<(), #error> { - init(slot).map(|__InitOk| ()) + init(slot, data.__with_lt()).map(|__InitOk| ()) }; // SAFETY: TODO unsafe { ::pin_init::#init_from_closure::<_, #error>(init) } @@ -360,7 +374,11 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, dcx: &mut DiagCtxt) -> InitKi } /// Generate the code that initializes the fields of the struct using the initializers in `field`. -fn init_fields(fields: &Punctuated, pinned: bool) -> TokenStream { +fn make_field_init( + fields: &Punctuated, + pinned: bool, + dropck: bool, +) -> TokenStream { let mut forget_guards = vec![]; let mut res = TokenStream::new(); for InitializerField { attrs, kind } in fields { @@ -388,13 +406,18 @@ fn init_fields(fields: &Punctuated, pinned: bool) - let span = Span::mixed_site().located_at(ident.span()); let slot = if pinned { + let data = if !dropck { + quote_spanned!(span => data_lt) + } else { + quote_spanned!(span => data.__with_lt()) + }; quote_spanned! { span => // SAFETY: // - `slot` is valid and properly aligned. // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned. // - `make_field_check` prevents `#member` from being used twice, therefore // `(*slot).#member` is exclusively accessed and has not been initialized. - (unsafe { data.#ident(slot) }) + (unsafe { #data.#ident(slot) }) } } else { quote_spanned! { span => @@ -455,6 +478,11 @@ fn init_fields(fields: &Punctuated, pinned: bool) - // A tuple field has no name that could be bound here (the `_0` identifiers are considered // implementation detail and not user-facing). + let let_binding_method = if !dropck { + format_ident!("let_binding", span = span) + } else { + format_ident!("let_binding_in_dropck", span = span) + }; let binding = match member { Member::Named(ident) => quote_spanned! { span => #(#cfgs)* @@ -462,7 +490,7 @@ fn init_fields(fields: &Punctuated, pinned: bool) - // struct field. #[allow(unused_variables, non_snake_case)] // Include `mut` so that `Pin<&mut T>` bindings can be reborrowed via `.as_mut()`. - let mut #ident = #guard.let_binding(); + let mut #ident = #guard.#let_binding_method(); }, Member::Unnamed(_) => quote!(), }; diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 03e893cf..dbf9084b 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -1,14 +1,19 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; +use std::collections::{BTreeMap, BTreeSet}; + +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ - parse::{End, Nothing, Parse}, + parse::{End, Nothing, Parse, ParseStream}, parse_quote, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, + visit::Visit, visit_mut::VisitMut, - Field, Fields, Generics, Index, Item, ItemStruct, Member, PathSegment, Type, TypePath, + Attribute, Field, Fields, GenericArgument, GenericParam, Generics, Ident, Index, Item, + ItemStruct, Lifetime, LifetimeParam, Member, Meta, PathArguments, PathSegment, Token, Type, + TypeParamBound, TypePath, WhereClause, WherePredicate, }; use crate::{ @@ -17,6 +22,9 @@ use crate::{ }; pub(crate) mod kw { + syn::custom_keyword!(covariant); + syn::custom_keyword!(invariant); + syn::custom_keyword!(contravariant); syn::custom_keyword!(PinnedDrop); } @@ -48,17 +56,204 @@ impl ToTokens for Args { } } +/// Description of how a field is borrowed. +#[derive(Clone, Copy, Default, PartialEq, Eq)] +enum BorrowedKind { + /// `#[borrowed]`, or implicitly inferreed. + #[default] + Shared, + // `#[borrowed(mut)]`. + Mutable, +} + +impl BorrowedKind { + fn parse(dcx: &mut DiagCtxt, attrs: &mut Vec) -> Option { + let attr = attrs.extract_single_attr(dcx, "borrowed")?; + + Some(if let Meta::Path(_) = attr.meta { + BorrowedKind::Shared + } else { + match attr.parse_args_with(|input: ParseStream<'_>| { + let _: Token![mut] = input.parse()?; + Ok(BorrowedKind::Mutable) + }) { + Ok(v) => v, + Err(err) => { + // Swallow the error and recover by inferring shared. + dcx.error(attr.path(), err); + BorrowedKind::Shared + } + } + }) + } +} + +/// Information about a borrowed field. +struct BorrowedInfo { + kind: BorrowedKind, + /// Field lifetime for this field. + lifetime: Lifetime, + // Variance of the field lifetime, as captured by other fields. + lt_variance: Variance, +} + +#[derive(Clone, Copy, Default, PartialEq, Eq)] +enum Variance { + /// `covariant` annotation, or implicitly inferred. + #[default] + Covariant, + // `invariant` annotation. + Invariant, +} + +impl Parse for Variance { + fn parse(input: ParseStream<'_>) -> syn::Result { + let lh = input.lookahead1(); + Ok(if lh.peek(kw::covariant) { + let _: kw::covariant = input.parse()?; + Variance::Covariant + } else if lh.peek(kw::invariant) { + let _: kw::invariant = input.parse()?; + Variance::Invariant + } else if lh.peek(kw::contravariant) { + // Field lifetimes are inherently covariant, so combining with contravariance, + // we would constrain it to be invariant. + let token: kw::contravariant = input.parse()?; + DiagCtxt::current(|dcx| { + dcx.error( + token, + "field lifetimes cannot be `contravariant`; use `invariant` instead", + ) + }); + Variance::Invariant + } else { + Err(lh.error())? + }) + } +} + +/// Information about field lifetimes captured in a type. +struct Capture { + variance: Variance, + /// Lifetime to be captured. + lifetime: Lifetime, +} + +impl std::borrow::Borrow for Capture { + fn borrow(&self) -> &Lifetime { + &self.lifetime + } +} + +impl PartialEq for Capture { + fn eq(&self, other: &Self) -> bool { + self.lifetime == other.lifetime + } +} + +impl Eq for Capture {} + +impl PartialOrd for Capture { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Capture { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.lifetime.cmp(&other.lifetime) + } +} + +impl Parse for Capture { + fn parse(input: ParseStream<'_>) -> syn::Result { + let lifetime = input.parse()?; + let variance = if input.peek(Token![:]) { + let _: Token![:] = input.parse()?; + input.parse()? + } else { + // If variance is not explicitly specified, infer covariance by default. + Variance::Covariant + }; + Ok(Capture { variance, lifetime }) + } +} + +impl Capture { + fn parse_list( + dcx: &mut DiagCtxt, + attrs: &mut Vec, + bound_lifetimes: &BTreeSet<&Lifetime>, + exist_lifetimes: &BTreeSet, + field_idx_map: &BTreeMap, + ) -> Option<(BTreeSet, Variance)> { + let attr = attrs.extract_single_attr(dcx, "uses")?; + let punctuated: Punctuated = attr + .parse_args_with(Punctuated::parse_terminated) + .map_err(ErrorGuaranteed::from) + .ok()?; + + // Check for misuses inside attribute. + let mut set = BTreeSet::new(); + for borrow in punctuated { + let lt = &borrow.lifetime; + if set.contains(&borrow) { + dcx.error(lt, format!("lifetime `{lt}` is mentioned more than once")); + continue; + } + + if bound_lifetimes.contains(lt) { + dcx.error( + lt, + format!("`{lt}` is a struct generics and cannot be used in `#[uses]`"), + ); + continue; + } + + if lt.ident != "_" + && !exist_lifetimes.contains(lt) + && !field_idx_map.contains_key(<.ident) + { + dcx.error(lt, format!("`{lt}` is not a field name")); + continue; + } + + set.insert(borrow); + } + + let wildcard = Lifetime::new("'_", Span::mixed_site()); + let wildcard_variance = set.take(&wildcard).map(|b| b.variance).unwrap_or_default(); + Some((set, wildcard_variance)) + } +} + +#[allow(unused)] struct FieldInfo { field: Field, member: Member, pinned: bool, + borrowed: Option, + captures: BTreeSet, + generic_lt_captures: BTreeSet, + generic_ty_captures: BTreeSet, } struct StructInfo { args: Args, struct_: ItemStruct, fields: Vec, + field_idx_map: BTreeMap, is_tuple_struct: bool, + self_referential: bool, + /// Existential lifetimes defined. + exist_lts: BTreeSet, + /// Field lifetime genercis with outlive chain. + field_lts_outlive_chain: Generics, + /// Field lifetime genercis with outlive chain, with one chain for covariant fields and one + /// chain for invariant fields. + field_lts_split_variance_outlive_chain: Generics, + /// Existential lifetime with their outlives bounds. + exist_lts_generics: Generics, } pub(crate) fn expand_with_cfg( @@ -147,7 +342,40 @@ fn expand( replacer.visit_fields_mut(&mut struct_.fields); let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_)); - let fields: Vec = struct_ + + let exist_lts = if let Some(whr) = &mut struct_.generics.where_clause { + ExistLt::parse(dcx, whr) + } else { + BTreeSet::new() + }; + + // Collect all bound lifetimes from generics. + let bound_lifetimes: BTreeSet<&Lifetime> = + struct_.generics.lifetimes().map(|x| &x.lifetime).collect(); + // Collect all type parameters from generics. + let type_params: BTreeSet<&Ident> = struct_.generics.type_params().map(|x| &x.ident).collect(); + // Collect all fields. + let field_idx_map: BTreeMap = struct_ + .fields + .iter() + .enumerate() + .filter_map(|(index, field)| Some((field.ident.clone()?, index))) + .collect(); + + for l in &exist_lts { + let lt = &l.lifetime; + if bound_lifetimes.contains(<) { + dcx.error( + lt, + format!("existential `{lt}` conflicts with struct generics"), + ); + } + } + + // Keep track on fields being implicitly borrowed by being mentioned. + let mut implicitly_borrowed = BTreeSet::new(); + + let mut fields: Vec = struct_ .fields .into_iter() .enumerate() @@ -166,20 +394,362 @@ fn expand( }), }; + // Parse `#[uses]` attribute. + let (mut captures, wildcard_variance) = Capture::parse_list( + dcx, + &mut field.attrs, + &bound_lifetimes, + &exist_lts, + &field_idx_map, + ) + .unwrap_or_default(); + + let mut generic_lt_captures = BTreeSet::new(); + let mut generic_ty_captures = BTreeSet::new(); + + // Infer lifetime based on the field referenced. + // Bound lifetimes from struct generics take priority. + // + // For example, + // ``` + // struct Foo<'a> { + // bar: &'a (), + // a: u32, + // } + // ``` + // would not be inferred as self-referential because `'a` is already bound by the + // struct generics. + Lifetime::visitor(|lt| { + if bound_lifetimes.contains(lt) { + generic_lt_captures.insert(lt.clone()); + return; + } + + if captures.contains(lt) { + return; + } + + if !exist_lts.contains(lt) && !field_idx_map.contains_key(<.ident) { + dcx.error( + lt, + format!("`{lt}` is neither a lifetime in generics nor a field name"), + ); + return; + } + + captures.insert(Capture { + variance: wildcard_variance, + lifetime: lt.clone(), + }); + }) + .visit_type(&field.ty); + + for capture in captures.iter(){ + if !exist_lts.contains(&capture.lifetime){ + implicitly_borrowed.insert(capture.lifetime.ident.clone()); + } + } + + GenericParam::maybe_type_params_visitor(|ident| { + if type_params.contains(ident) { + generic_ty_captures.insert(ident.clone()); + } + }) + .visit_type(&field.ty); + + let borrowed = BorrowedKind::parse(dcx, &mut field.attrs).and_then(|kind| { + let lifetime = Lifetime::from_ident(&member.as_ident()); + + if bound_lifetimes.contains(&lifetime) { + dcx.error( + &lifetime, + format!("`{lifetime}` appear in generics and would conflict with field lifetime"), + ); + return None; + } + + Some(BorrowedInfo { kind, lifetime, lt_variance: Variance::default() }) + }); + FieldInfo { field, member, pinned, + borrowed, + captures, + generic_lt_captures, + generic_ty_captures, } }) .collect(); + for field_name in implicitly_borrowed.into_iter() { + let field = &mut fields[field_idx_map[&field_name]]; + + // If field is not explicit marked as borrowed, infer a shared borrow. + if field.borrowed.is_none() { + field.borrowed = Some(BorrowedInfo { + kind: BorrowedKind::Shared, + // Obtaining from `field` instead of `field_name` for the correct span. + lifetime: Lifetime::from_ident(&field.member.as_ident()), + lt_variance: Variance::Covariant, + }); + } + } + + // Check that field lifetimes do not appear in the bounds. + Lifetime::visitor(|lt| { + if bound_lifetimes.contains(<) { + return; + } + + if field_idx_map.contains_key(<.ident) { + // Forbid the use of field lifetimes within bounds. + dcx.error(lt, "field lifetimes cannot be used in bounds"); + } + + // Otherwise this is completely unbound. Let Rust compiler produce that error instead. + }) + .visit_generics(&struct_.generics); + + // Obtain an closure of invariant fields. + // + // If a field lifetime is used invariantly, we also need to make sure that + // for each generic parameter `T: 'field` bound, `T` is also captured + // invariantly (same is true for lifetime parameters). For example, say we + // have a struct `SelfRef<'a>` and a field `f: &'a ()`. For wellformedness, + // we would have `'a: 'f`. If we have a covariant `'a`, we can observe a + // shortened `SelfRef<'short_a>` where `'f` outlives `'short_a`, conflicting + // with the wellformedness bound `'short_a: 'f`. This will enable the + // `&'short_a ()` to `&'f ()` coercion, which effectively shortens `'f` too, + // so we shortened the field lifetime despite it being invariant. + let mut invariant_field_idx = BTreeSet::new(); + let mut worklist = Vec::new(); + for field in fields.iter() { + for borrow in field.captures.iter() { + if exist_lts.contains(&borrow.lifetime) { + continue; + } + if let Variance::Invariant = borrow.variance { + let Some(borrow_idx) = fields + .iter() + .position(|f| f.member.as_ident() == borrow.lifetime.ident) + else { + continue; + }; + if invariant_field_idx.insert(borrow_idx) { + worklist.push(&fields[borrow_idx]); + } + } + } + } + while let Some(field) = worklist.pop() { + for borrow in field.captures.iter() { + if exist_lts.contains(&borrow.lifetime) { + continue; + } + let Some(borrow_idx) = fields + .iter() + .position(|f| f.member.as_ident() == borrow.lifetime.ident) + else { + continue; + }; + let borrow = &fields[borrow_idx]; + if invariant_field_idx.insert(borrow_idx) { + worklist.push(borrow); + } + } + } + for idx in invariant_field_idx { + fields[idx].borrowed.as_mut().unwrap().lt_variance = Variance::Invariant; + } + + // Create a lifetime parameter for each field. + let borrowed_fields: Vec<_> = fields.iter().filter_map(|f| f.borrowed.as_ref()).collect(); + let borrowed_covariant_fields: Vec<_> = borrowed_fields + .iter() + .filter(|f| f.lt_variance == Variance::Covariant) + .collect(); + let borrowed_invariant_fields: Vec<_> = borrowed_fields + .iter() + .filter(|f| f.lt_variance == Variance::Invariant) + .collect(); + + let mut field_lts_outlive_chain = Generics { + lt_token: None, + params: borrowed_fields + .iter() + .zip(std::iter::once(None).chain(borrowed_fields.iter().map(Some))) + .map(|(borrowed, prev)| { + GenericParam::Lifetime(LifetimeParam { + attrs: Vec::new(), + lifetime: borrowed.lifetime.clone(), + colon_token: None, + bounds: prev + .iter() + .map(|borrowed| borrowed.lifetime.clone()) + .collect(), + }) + }) + .collect(), + gt_token: None, + where_clause: None, + }; + + if let Some(last_borrowed_field) = borrowed_fields.last() { + let field_lt = &last_borrowed_field.lifetime; + for param in struct_.generics.params.iter() { + match param { + GenericParam::Lifetime(param) => { + let lt = ¶m.lifetime; + field_lts_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#lt: #field_lt)); + } + GenericParam::Type(param) => { + let ty = ¶m.ident; + field_lts_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#ty: #field_lt)); + } + GenericParam::Const(_) => (), + } + } + } + + let exist_lt_generics = Generics { + lt_token: Some(Default::default()), + params: exist_lts + .iter() + .map(|l| { + GenericParam::Lifetime(LifetimeParam { + attrs: Vec::new(), + lifetime: l.lifetime.clone(), + colon_token: Default::default(), + bounds: l.bounds.iter().cloned().collect(), + }) + }) + .collect(), + gt_token: Some(Default::default()), + where_clause: None, + }; + + let mut field_lts_split_variance_outlive_chain = Generics { + lt_token: Some(Default::default()), + params: borrowed_covariant_fields + .iter() + .zip(std::iter::once(None).chain(borrowed_covariant_fields.iter().map(Some))) + .map(|(borrowed, prev)| { + GenericParam::Lifetime(LifetimeParam { + attrs: Vec::new(), + lifetime: borrowed.lifetime.clone(), + colon_token: None, + bounds: prev + .iter() + .map(|borrowed| borrowed.lifetime.clone()) + .collect(), + }) + }) + .chain( + borrowed_invariant_fields + .iter() + .zip(std::iter::once(None).chain(borrowed_invariant_fields.iter().map(Some))) + .map(|(borrowed, prev)| { + GenericParam::Lifetime(LifetimeParam { + attrs: Vec::new(), + lifetime: borrowed.lifetime.clone(), + colon_token: None, + bounds: prev + .iter() + .map(|borrowed| borrowed.lifetime.clone()) + .collect(), + }) + }), + ) + .collect(), + gt_token: Some(Default::default()), + where_clause: None, + }; + + // For `field_lts_split_variance_outlive_chain`, we may need to insert some additional bounds + // for types to be WF. + for field in fields.iter() { + let Some(borrowed) = &field.borrowed else { + continue; + }; + let field_lt = &borrowed.lifetime; + + // For each borrowed field that references a generic, we also need to insert their outlive + // bounds so they can refer to generics. + for lt in field.generic_lt_captures.iter() { + field_lts_split_variance_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#lt: #field_lt)); + } + + for ty in field.generic_ty_captures.iter() { + field_lts_split_variance_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#ty: #field_lt)); + } + + for borrow in field.captures.iter().rev() { + let lt = &borrow.lifetime; + if exist_lts.contains(lt) { + field_lts_split_variance_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#lt: #field_lt)); + } + } + + // If a field is invariant, then the invariance closure rule will make all borrowed fields + // to be invariant, so they're already captured in `field_lts_split_variance_outlive_chain`. + if borrowed.lt_variance != Variance::Covariant { + continue; + } + + for capture in field.captures.iter() { + let Some(&idx) = field_idx_map.get(&capture.lifetime.ident) else { + continue; + }; + + let prev_borrowed = fields[idx].borrowed.as_ref().unwrap(); + + // If borrowed field is covariant, it's already captured in + // `field_lts_split_variance_outlive_chain`. + if prev_borrowed.lt_variance == Variance::Invariant { + // Covariant field borrowing an invariant field. This is not captured in the chain + // so we need to add additional bound. This bound is okay, as the invariant lifetime + // is the longer living one, so arbitrary shortening of the covariant one does not + // violate their relation. + let param = field_lts_split_variance_outlive_chain + .lifetimes_mut() + .find(|l| l.lifetime == prev_borrowed.lifetime) + .unwrap(); + param.bounds.push(borrowed.lifetime.clone()); + } + } + } + struct_.fields = Fields::Unit; let info = StructInfo { + self_referential: fields + .iter() + .any(|f| !f.captures.is_empty() || f.borrowed.is_some()), args, struct_, fields, + field_idx_map, is_tuple_struct, + exist_lts, + field_lts_outlive_chain, + field_lts_split_variance_outlive_chain, + exist_lts_generics: exist_lt_generics, }; for field in &info.fields { @@ -198,6 +768,8 @@ fn expand( let struct_def = generate_struct_def(&info); let unpin_impl = generate_unpin_impl(&info); let drop_impl = generate_drop_impl(&info); + let drop_order_check = generate_drop_order_check(dcx, &info); + let variance_check = generate_variance_check(&info); let projections = generate_projections(&info); let the_pin_data = generate_the_pin_data(&info); @@ -206,6 +778,8 @@ fn expand( // We put the rest into this const item, because it then will not be accessible to anything // outside. const _: () = { + #drop_order_check + #variance_check #projections #the_pin_data #unpin_impl @@ -238,6 +812,129 @@ fn is_phantom_pinned(ty: &Type) -> bool { } } +struct ExistLt { + lifetime: Lifetime, + bounds: Vec, +} + +impl std::borrow::Borrow for ExistLt { + fn borrow(&self) -> &Lifetime { + &self.lifetime + } +} + +impl PartialEq for ExistLt { + fn eq(&self, other: &Self) -> bool { + self.lifetime == other.lifetime + } +} + +impl Eq for ExistLt {} + +impl PartialOrd for ExistLt { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ExistLt { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.lifetime.cmp(&other.lifetime) + } +} + +impl ExistLt { + fn parse(dcx: &mut DiagCtxt, whr: &mut WhereClause) -> BTreeSet { + fn parse_one( + dcx: &mut DiagCtxt, + pred: &WherePredicate, + result: &mut BTreeSet, + ) -> bool { + let WherePredicate::Type(pred) = &pred else { + return false; + }; + let Type::Path(path) = &pred.bounded_ty else { + return false; + }; + + if path.qself.is_some() + || path.path.leading_colon.is_some() + || path.path.segments.len() != 1 + { + return false; + } + let path_seg = &path.path.segments[0]; + + if path_seg.ident != "exists" { + return false; + }; + + let PathArguments::AngleBracketed(p) = &path_seg.arguments else { + dcx.error( + path_seg, + "existential clauses require a single lifetime, e.g. `exists<'a>`", + ); + return true; + }; + + if p.args.len() != 1 { + dcx.error( + path_seg, + "existential clauses require a single lifetime, e.g. `exists<'a>`", + ); + return true; + } + + let GenericArgument::Lifetime(lt) = &p.args[0] else { + dcx.error( + path_seg, + "existential clauses require a single lifetime, e.g. `exists<'a>`", + ); + return true; + }; + + if result.contains(lt) { + dcx.error( + lt, + format!("an existential clause for `{lt}` already exists"), + ); + return true; + } + + let mut bounds = Vec::new(); + for bound in pred.bounds.iter() { + let TypeParamBound::Lifetime(lt) = bound else { + dcx.error(bound, "only lifetime can appear in existential clauses"); + return true; + }; + bounds.push(lt.clone()); + } + if bounds.is_empty() { + dcx.error( + pred.colon_token, + "existential clauses require at least one outlive bounds", + ); + return true; + } + + result.insert(ExistLt { + lifetime: lt.clone(), + bounds: bounds.clone(), + }); + + true + } + + let mut result = BTreeSet::new(); + whr.predicates = std::mem::take(&mut whr.predicates) + .into_pairs() + .filter(|p| !parse_one(dcx, p.value(), &mut result)) + .collect(); + + result + } +} + fn generate_struct_def(info: &StructInfo) -> TokenStream { let ItemStruct { attrs, @@ -259,6 +956,56 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream { ty, } = &field.field; + let mut ty = ty.to_token_stream(); + + // Replace lifetime for self-referential fields. For mutable fields, this uses `Erase` to + // block direct access. + if !field.captures.is_empty() + || matches!( + field.borrowed, + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + .. + }) + ) + { + // Build a chain `for<'a> fn(&'a ()) -> ... -> (Ty,)`. Such type will have a `EraseLt` + // implementation and thus may be used inside `Erase`. + ty = quote!((#ty,)); + + let mut exist_lt_phantoms = Vec::new(); + for borrow in field.captures.iter().rev() { + let lt = &borrow.lifetime; + ty = quote!(for<#lt> fn(&#lt()) -> #ty); + + if let Some(exist_lt) = info.exist_lts.get(lt) { + for bound in &exist_lt.bounds { + exist_lt_phantoms.push(quote!(::pin_init::__internal::ExistLt<#bound>)); + } + } + } + + ty = quote!(::pin_init::__internal::Erase<#ty>); + + if !exist_lt_phantoms.is_empty() { + ty = quote!(::pin_init::__internal::WithPhantom<#ty, (#(#exist_lt_phantoms,)*)>); + } + }; + + if let Some(borrowed) = &field.borrowed { + let mut invariance_capture = Vec::new(); + if borrowed.lt_variance == Variance::Invariant { + for lt in &field.generic_lt_captures { + invariance_capture.push(quote!(&#lt ())); + } + + for ty in &field.generic_ty_captures { + invariance_capture.push(quote!(::core::marker::PhantomData<#ty>)); + } + } + ty = quote!(::pin_init::__internal::Borrowed<#ty, (#(#invariance_capture,)*)>); + } + quote! { #(#attrs)* #vis #ident #colon_token #ty } @@ -294,6 +1041,20 @@ fn generate_unpin_impl(info: &StructInfo) -> TokenStream { .map(|x| &x.predicates) .unwrap_or(const { &Punctuated::new() }); + if info.self_referential { + // Self-referential structs must always be pinned. + return quote! { + #[doc(hidden)] + impl #impl_generics ::core::marker::Unpin for #ident #ty_generics + where + // the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds` + // feature . + for<'__dummy> ::core::marker::PhantomPinned: ::core::marker::Unpin, + #predicates + {} + }; + } + let pinned_fields = info.fields.iter().filter(|f| f.pinned).map(|f| { let ident = f.member.as_ident(); let ty = &f.field.ty; @@ -301,6 +1062,7 @@ fn generate_unpin_impl(info: &StructInfo) -> TokenStream { #ident: #ty ) }); + quote! { // This struct will be used for the unpin analysis. It is needed, because only structurally // pinned fields are relevant whether the struct should implement `Unpin`. @@ -375,6 +1137,199 @@ fn generate_drop_impl(info: &StructInfo) -> TokenStream { } } +fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStream { + let ItemStruct { + ident: struct_name, + generics, + .. + } = &info.struct_; + + // If the struct is not self-referential then we can just skip. + if !info.self_referential { + return quote!(); + } + + // Make sure fields are dropped earlier than the fields that they borrow. + for (i, field) in info.fields.iter().enumerate() { + let ident = field.member.as_ident(); + for capture in &field.captures { + let borrowed_field = &capture.lifetime.ident; + + if let Some(&borrowed_idx) = info.field_idx_map.get(borrowed_field) { + if i == borrowed_idx { + // We need a strict outlive relationship, in case the lifetime is needed by the + // field's drop glue. + dcx.error( + borrowed_field, + format!("field `{ident}` cannot borrow from itself"), + ); + } else if i > borrowed_idx { + dcx.error( + borrowed_field, + format!("field `{ident}` borrows `{borrowed_field}`, but drops later"), + ); + } + } + } + } + + // The check above is necessary, but not sufficient. + // + // Consider this case: + // ``` + // struct Foo { + // x: &'b &'a (), + // a: String, + // y: PrintOnDrop<&'b str>, + // b: String, + // } + // ``` + // we need to ensure that `b` will strictly outlive `a`. + // + // Rust needs to ensure that types are well-formed; in the above example, `&'b &'a ()` is + // well-formed only if `a` outlive `b`. To avoid requiring everyone from having to express this + // bound explicitly when declaring a struct, the `'b: 'a` bound is inferred by the Rust + // compiler. However this causes an issue, where now `&'a str` can be coerced to `&'b str` + // because compiler thinks that it shorten the lifetime. We'll be able to put a reference to `a` + // into `y`; but `a` drops first, so when `y` drops, it accesses `a` and causes a + // use-after-free! + // + // Therefore, we must ensure the types contained within the struct has their implied bound being + // consistent with the actual lifetime relationship. We create a `__drop_order_check` function, + // with known lifetime bounds as bounds on the function, and asks Rust to *prove* that the types + // are wellformed, given the bounds that we understand. + + let generics_with_field_lt = CombinedGenerics(vec![ + &info.exist_lts_generics, + &info.field_lts_split_variance_outlive_chain, + generics, + ]); + + let (_, ty_generics, _) = generics.split_for_impl(); + let (impl_generics_with_field_lt, _, whr_with_field_lt) = + generics_with_field_lt.split_for_impl(); + + // Prove the wellformedness of struct fields with regarding to the bounds of + // `__drop_order_check`. + // + // Consider this case: + // ``` + // struct Foo { + // x: &'b &'a (), + // a: String, + // y: PrintOnDrop<&'b str>, + // b: String, + // } + // ``` + // we need to ensure that `b` will strictly outlive `a`. + // + // Rust needs to ensure that types are well-formed; in the above example, `&'b &'a ()` is + // well-formed only if `a` outlive `b`. To avoid requiring everyone from having to express this + // bound explicitly when declaring a struct, the `'b: 'a` bound is inferred by the Rust + // compiler. However this causes an issue, where now `&'a str` can be coerced to `&'b str` + // because compiler thinks that it shorten the lifetime. We'll be able to put a reference to `a` + // into `y`; but `a` drops first, so when `y` drops, it accesses `a` and causes a + // use-after-free! + // + // Rust needs to *prove* the wellformedness of the type below, taking into account only the + // explicitly defined bounds plus the bounds implied by the lifetime-erased struct (but not + // the full implied bound between the field lifetimes). + let wf_proofs = info.fields.iter().rev().map(|f| { + let ty = &f.field.ty; + let span = ty.span().resolved_at(Span::mixed_site()); + let ident = f.member.as_ident(); + let lt = f.borrowed.as_ref().map(|b| &b.lifetime); + quote_spanned!(span => + let #ident: &#lt mut #ty = loop {}; + ) + }); + + let struct_span = struct_name.span().resolved_at(Span::mixed_site()); + quote_spanned! {struct_span => + #[allow(non_snake_case, unused)] + fn __drop_order_check #impl_generics_with_field_lt ( + // This must be present so the function can *assume* the implied bounds on the erased + // struct. For example, if the struct has `&'a T`, Rust will infer `T: 'a`; we still + // want to assume these bounds as they are not relevant to the field lifetimes. + _: &#struct_name #ty_generics, + ) #whr_with_field_lt { + #(#wf_proofs)* + } + } +} + +/// Produce variance checks, so we can ensure that the variance of lifetimes captured by field types +/// actually match our expectation. +fn generate_variance_check(info: &StructInfo) -> TokenStream { + if !info.self_referential { + return quote!(); + } + + let mut checks = Vec::new(); + + for f in info.fields.iter() { + let covariant_captures: Vec<_> = f + .captures + .iter() + .filter(|b| b.variance == Variance::Covariant) + .map(|b| &b.lifetime) + .collect(); + if covariant_captures.is_empty() { + continue; + } + + let ident = f.member.as_ident(); + // Use the span of type for better error message. + let span = f.field.ty.span().resolved_at(Span::mixed_site()); + + let other_field_lifetimes = Generics { + lt_token: None, + params: f + .captures + .iter() + .filter(|b| b.variance != Variance::Covariant) + .map(|b| GenericParam::Lifetime(LifetimeParam::new(b.lifetime.clone()))) + .collect(), + gt_token: None, + where_clause: None, + }; + + let long = Lifetime::new("'__long", span); + let long_ty = f + .field + .ty + .replace_lifetimes(&covariant_captures, &vec![&long; covariant_captures.len()]); + + let short = Lifetime::new("'__short", span); + let short_ty = f + .field + .ty + .replace_lifetimes(&covariant_captures, &vec![&short; covariant_captures.len()]); + + let check_name = format_ident!("__{ident}_covariance", span = span); + + // Add `<'__long: '__short, 'short>` as additional generics. + let covariance_check_generics = parse_quote!(<#long: #short, #short>); + let combined_generics = CombinedGenerics(vec![ + &covariance_check_generics, + &other_field_lifetimes, + &info.struct_.generics, + ]); + let (combined_impl_generics, _, whr) = combined_generics.split_for_impl(); + + checks.push(quote_spanned!(span => + // Emit a check to ensure the type is *really* covariant for soundness. + fn #check_name #combined_impl_generics (long: #long_ty) -> #short_ty #whr { + long + } + )); + } + + quote!( + #(#checks)* + ) +} + fn generate_projections(info: &StructInfo) -> TokenStream { let ItemStruct { vis, @@ -382,59 +1337,206 @@ fn generate_projections(info: &StructInfo) -> TokenStream { generics, .. } = &info.struct_; - let this_lt_generics: Generics = parse_quote!(<'__this>); - let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]); + let this_lt = Lifetime::new("'__this", Span::mixed_site()); + let this_lt_generics: Generics = parse_quote!(<#this_lt>); + // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be + // used with `for`. + let field_lts = CombinedGenerics(vec![ + &info.field_lts_outlive_chain, + &info.exist_lts_generics, + ]); + let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]); + let generics_with_this_field_lt = CombinedGenerics(vec![ + &this_lt_generics, + &info.exist_lts_generics, + &info.field_lts_outlive_chain, + generics, + ]); + let generics_with_this_field_ref_lt = CombinedGenerics(vec![ + &this_lt_generics, + &info.exist_lts_generics, + &info.field_lts_split_variance_outlive_chain, + generics, + ]); + let (_, field_lt_ty_generics, _) = field_lts.split_for_impl(); let (impl_generics, ty_generics, whr) = generics.split_for_impl(); let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl(); + let (_, ty_generics_with_this_field_lt, _) = generics_with_this_field_lt.split_for_impl(); + let (_, ty_generics_with_this_field_ref_lt, _) = + generics_with_this_field_ref_lt.split_for_impl(); let this = format_ident!("this"); let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = info .fields .iter() - .map(|field| { - let Field { vis, ty, .. } = &field.field; - let member = &field.member; + .map(|f| { + let vis = &f.field.vis; + let ident = f.member.as_ident(); + let member = &f.member; // The projection of a tuple struct is a tuple struct itself, so its fields are // positional and must not be named. - let name = (!info.is_tuple_struct).then(|| { - let ident = field.member.as_ident(); - quote!(#ident:) - }); + let name = (!info.is_tuple_struct).then(|| quote!(#ident:)); + + // if `f.ty` contains field lifetimes, which we need to replace them with shorter + // `'__this` lifetime as field lifetimes are not available in this context. + let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect(); + let ty = f + .field + .ty + .replace_lifetimes(&all_lifetimes, &vec![&this_lt; all_lifetimes.len()]); + + // Fields sharedly borrowed by other fields can only be shared accessed. Fields that + // references other field and are covariant can also only be given shared reference + // as mutable reference is invariant. + let mut_token: Option = if f.borrowed.is_none() && f.captures.is_empty() { + Some(Default::default()) + } else { + None + }; + + let mut accessor = quote!(&#mut_token #this.#member); + if !f.captures.is_empty() || f.borrowed.is_some() { + accessor = quote!( + // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`. + // Field lifetimes in `f.ty` can be shortened to `#ty` due to covariance. + unsafe { ::core::mem::transmute::<_, &#mut_token #ty>(#accessor) } + ) + } - if field.pinned { + if !f.captures.iter().all(|b| b.variance == Variance::Covariant) + || matches!( + f.borrowed, + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + .. + }) + ) + { + // If the type is not covariant, it must omitted, as projection shortens the + // lifetime to `'__this`. + // Mutable borrow must be omitted for aliasing reason. + ( + quote!( + #vis #name ::pin_init::__internal::NotVisible<&'__this #mut_token #ty>, + ), + quote!( + #name ::pin_init::__internal::NotVisible::new(), + ), + ) + } else if f.pinned { ( quote!( - #vis #name ::core::pin::Pin<&'__this mut #ty>, + #vis #name ::core::pin::Pin<&'__this #mut_token #ty>, ), quote!( // SAFETY: this field is structurally pinned. - #name unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) }, + #name unsafe { ::core::pin::Pin::new_unchecked(#accessor) }, ), ) } else { ( quote!( - #vis #name &'__this mut #ty, + #vis #name &'__this #mut_token #ty, ), quote!( - #name &mut #this.#member, + #name #accessor, ), ) } }) .collect(); - let structurally_pinned_fields_docs = info + + let (fields_decl_lt, fields_proj_lt): (Vec<_>, Vec<_>) = info + .fields + .iter() + .map(|f| { + let vis = &f.field.vis; + let ident = f.member.as_ident(); + let member = &f.member; + let name = (!info.is_tuple_struct).then(|| quote!(#ident:)); + + let ty = &f.field.ty; + + // Fields shared-referenced by other fields can only be shared accessed. + let mut_token: Option = if f.borrowed.is_none() { + Some(Default::default()) + } else { + None + }; + + let mut accessor = quote!(&#mut_token #this.#member); + if !f.captures.is_empty() || f.borrowed.is_some() { + accessor = quote!( + // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`. + // We cannot include explicit type name here as the field lifetimes are nameable + // in this context, so `for<'field_name> ..` would fail. + unsafe { ::core::mem::transmute(#accessor) } + ) + } + + // In `with_project`, borrowed fields have their field lifetime available, so use it + // instead of `'__this`. + let lt = if f.borrowed.is_some() { + Lifetime::from_ident(&ident) + } else { + this_lt.clone() + }; + + if matches!( + f.borrowed, + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + .. + }) + ) { + // If the type is not covariant, it must omitted, as projection shortens the + // lifetime to `'__this`. + // Mutable borrow must be omitted for aliasing reason. + ( + quote!( + #vis #name ::pin_init::__internal::NotVisible<&#lt #mut_token #ty>, + ), + quote!( + #name ::pin_init::__internal::NotVisible::new(), + ), + ) + } else if f.pinned { + ( + quote!( + #vis #name ::core::pin::Pin<&#lt #mut_token #ty>, + ), + quote!( + // SAFETY: this field is structurally pinned. + #name unsafe { ::core::pin::Pin::new_unchecked(#accessor) }, + ), + ) + } else { + ( + quote!( + #vis #name &#lt #mut_token #ty, + ), + quote!( + #name #accessor, + ), + ) + } + }) + .collect(); + + let structurally_pinned_fields_docs: Vec<_> = info .fields .iter() .filter(|f| f.pinned) - .map(|f| format!(" - {}", f.member.display_name())); - let not_structurally_pinned_fields_docs = info + .map(|f| format!(" - {}", f.member.display_name())) + .collect(); + let not_structurally_pinned_fields_docs: Vec<_> = info .fields .iter() .filter(|f| !f.pinned) - .map(|f| format!(" - {}", f.member.display_name())); + .map(|f| format!(" - {}", f.member.display_name())) + .collect(); let docs = format!(" Pin-projections of [`{ident}`]"); let (projection_def, projection_init) = if info.is_tuple_struct { ( @@ -469,6 +1571,216 @@ fn generate_projections(info: &StructInfo) -> TokenStream { }, ) }; + + let projection_lt = format_ident!("__ProjectionLt"); + let (projection_lt_def, projection_lt_init) = if info.is_tuple_struct { + ( + quote! { + #vis struct #projection_lt #generics_with_this_field_lt ( + #(#fields_decl_lt)* + ::core::marker::PhantomData<&'__this mut #ident #ty_generics>, + ) #whr; + }, + quote! { + #projection_lt( + #(#fields_proj_lt)* + ::core::marker::PhantomData, + ) + }, + ) + } else { + ( + quote! { + #vis struct #projection_lt #generics_with_this_field_lt + #whr + { + #(#fields_decl_lt)* + ___pin_phantom_data: + ::core::marker::PhantomData<&'__this mut #ident #ty_generics>, + } + }, + quote! { + #projection_lt { + #(#fields_proj_lt)* + ___pin_phantom_data: ::core::marker::PhantomData, + } + }, + ) + }; + + // For fields that references other fields, field access syntax stops working as they're wrapped + // behind `Erase` because their actual lifetime is not on the struct. + // + // Generate an accessor method for them. + let mut accessors = Vec::new(); + for f in info.fields.iter() { + let ident = f.member.as_ident(); + let member = &f.member; + + if f.captures.is_empty() { + // They can be accessed normally, no accessor to be generated. + continue; + } + + if matches!( + f.borrowed, + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + .. + }) + ) { + // Mutably borrowed fields cannot be accessed directly under any circumstance. + continue; + } + + if f.captures.iter().all(|b| b.variance == Variance::Covariant) { + let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`."); + let vis = &f.field.vis; + + // Use the span of type for better error message. + let span = f.field.ty.span().resolved_at(Span::mixed_site()); + + let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect(); + let ty = f + .field + .ty + .replace_lifetimes(&all_lifetimes, &vec![&this_lt; all_lifetimes.len()]); + + accessors.push(quote_spanned!(span => + #[doc = #f_doc] + #[inline] + #[allow(clippy::mut_from_ref)] // false positive when `&&mut` is returned. + #vis fn #ident<#this_lt>(&#this_lt self) -> &#this_lt #ty { + // SAFETY: we have `Erased<..>` which we know is layout compatible with `f.ty`. + // Field lifetimes in `f.ty` can be shortened to `#ty` due to covariance. + unsafe { ::core::mem::transmute(&self.#member) } + } + )) + } else { + let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`."); + let vis = &f.field.vis; + let with_ident = format_ident!("with_{ident}"); + + let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect(); + let ty = &f.field.ty; + + accessors.push(quote!( + #[doc = #f_doc] + #[inline] + #vis fn #with_ident<'__this, R>( + &'__this self, + f: impl for<#(#all_lifetimes,)*> ::core::ops::FnOnce(&'__this #ty) -> R, + ) -> R { + // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`. + f(unsafe { ::core::mem::transmute(&self.#member) }) + } + )) + } + } + + let (fields_ref_decl_lt, fields_ref_proj_lt): (Vec<_>, Vec<_>) = info + .fields + .iter() + .map(|f| { + let vis = &f.field.vis; + let ident = f.member.as_ident(); + let member = &f.member; + let name = (!info.is_tuple_struct).then(|| quote!(#ident:)); + + let ty = &f.field.ty; + + let mut accessor = quote!(&#this.#member); + if !f.captures.is_empty() || f.borrowed.is_some() { + accessor = quote!( + // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`. + // We cannot include explicit type name here as the field lifetimes are nameable + // in this context. + unsafe { ::core::mem::transmute(#accessor) } + ) + } + + // In `with_project`, borrowed fields have their field lifetime available, so use it + // instead of `'__this`. + let lt = if f.borrowed.is_some() { + Lifetime::from_ident(&ident) + } else { + this_lt.clone() + }; + + if matches!( + f.borrowed, + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + .. + }) + ) { + // Mutable borrow must be omitted for aliasing reason. + ( + quote!( + #vis #name ::pin_init::__internal::NotVisible<&#lt #ty>, + ), + quote!( + #name ::pin_init::__internal::NotVisible::new(), + ), + ) + } else if f.pinned { + ( + quote!( + #vis #name ::core::pin::Pin<&#lt #ty>, + ), + quote!( + // SAFETY: this field is structurally pinned. + #name unsafe { ::core::pin::Pin::new_unchecked(#accessor) }, + ), + ) + } else { + ( + quote!( + #vis #name &#lt #ty, + ), + quote!( + #name #accessor, + ), + ) + } + }) + .collect(); + + let projection_ref_lt = format_ident!("__ProjectionRef"); + let (projection_ref_lt_def, projection_ref_lt_init) = if info.is_tuple_struct { + ( + quote!( + #vis struct #projection_ref_lt #generics_with_this_field_ref_lt( + #(#fields_ref_decl_lt)* + ::core::marker::PhantomData<&'__this #ident #ty_generics>, + ) #whr; + ), + quote!( + #projection_ref_lt( + #(#fields_ref_proj_lt)* + ::core::marker::PhantomData, + ) + ), + ) + } else { + ( + quote! { + #vis struct #projection_ref_lt #generics_with_this_field_ref_lt + #whr + { + #(#fields_ref_decl_lt)* + ___pin_phantom_data: ::core::marker::PhantomData<&'__this #ident #ty_generics>, + } + }, + quote! { + #projection_ref_lt { + #(#fields_ref_proj_lt)* + ___pin_phantom_data: ::core::marker::PhantomData, + } + }, + ) + }; + quote! { #[doc = #docs] // Allow `non_snake_case` since the same warning will be emitted on @@ -477,6 +1789,19 @@ fn generate_projections(info: &StructInfo) -> TokenStream { #[doc(hidden)] #projection_def + #[doc = #docs] + // Allow `non_snake_case` since the same warning will be emitted on + // the struct definition. + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + #projection_lt_def + + // Allow `non_snake_case` since the same warning will be emitted on + // the struct definition. + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + #projection_ref_lt_def + impl #impl_generics #ident #ty_generics #whr { @@ -495,6 +1820,45 @@ fn generate_projections(info: &StructInfo) -> TokenStream { let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; #projection_init } + + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + #(#[doc = #structurally_pinned_fields_docs])* + /// + /// These fields are **not** structurally pinned: + #(#[doc = #not_structurally_pinned_fields_docs])* + #[inline] + #vis fn with_project<'__this, R>( + self: ::core::pin::Pin<&'__this mut Self>, + f: impl for #field_lt_ty_generics ::core::ops::FnOnce( + #projection_lt #ty_generics_with_this_field_lt + ) -> R, + ) -> R { + // SAFETY: we only give access to `&mut` for fields not structurally pinned. + let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(#projection_lt_init) + } + + /// Pin-projects all fields of `Self` from a shared reference with proper lifetime. + /// + /// These fields are structurally pinned: + #(#[doc = #structurally_pinned_fields_docs])* + /// + /// These fields are **not** structurally pinned: + #(#[doc = #not_structurally_pinned_fields_docs])* + #[inline] + #vis fn with_project_ref<'__this, R>( + self: ::core::pin::Pin<&'__this Self>, + f: impl for #field_lt_ty_generics ::core::ops::FnOnce( + #projection_ref_lt #ty_generics_with_this_field_ref_lt + ) -> R, + ) -> R { + let #this = ::core::pin::Pin::get_ref(self); + f(#projection_ref_lt_init) + } + + #(#accessors)* } } } @@ -506,12 +1870,45 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { generics, .. } = &info.struct_; + + // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be + // used with `for`. + let field_lts = CombinedGenerics(vec![&info.field_lts_outlive_chain]); + let field_exist_lts = CombinedGenerics(vec![ + &info.field_lts_outlive_chain, + &info.exist_lts_generics, + ]); + let generics_with_field_lt = CombinedGenerics(vec![ + &info.field_lts_outlive_chain, + &info.exist_lts_generics, + generics, + ]); + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + let (_, field_lt_ty_generics, _) = field_lts.split_for_impl(); + let (_, field_exist_lt_ty_generics, _) = field_exist_lts.split_for_impl(); + let (impl_generics_with_lt, ty_generics_with_field_lt, whr_with_field_lt) = + generics_with_field_lt.split_for_impl(); + + // Wrap each field in a `PhantomInvariant`. For borrowed fields, additionally + // use `&#lt mut #ty` so the `lt` becomes associated with `#ty` which deduces + // implied bounds. + let phantom_fields = info.fields.iter().map(|f| { + let ty = &f.field.ty; + let ident = f.member.as_ident(); + + if let Some(borrowed) = &f.borrowed { + let lt = &borrowed.lifetime; + quote!( + #ident: ::pin_init::__internal::PhantomInvariant<&#lt mut #ty>, + ) + } else { + quote!( + #ident: ::pin_init::__internal::PhantomInvariant<#ty>, + ) + } + }); - // For every field, we create an initializing projection function according to its projection - // type. If a field is structurally pinned, we create a `Slot` with `Pinned` which must be - // initialized via `PinInit`; if it is not structurally pinned, then we create a `Slot` with - // `Unpinned` which allows initialization via `Init`. let field_accessors = info .fields .iter() @@ -524,6 +1921,51 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { } else { quote!(Unpinned) }; + + let (slot_ty, slot_arg) = match &f.borrowed { + None => (quote!(Slot), quote!()), + Some(BorrowedInfo { + kind: BorrowedKind::Shared, + lifetime, + .. + }) => ( + // For borrowed fields, create a `SelfRefSlot`, which after initialization + // turns into a `SelfRefDropGuard` instead of `DropGuard`. + // + // They're mostly the same, except that `SelfRefDropGuard` returns `&'field T` + // instead of `&'guard T` for let bindings; this allows it to be used to be + // used to initialize other fields. + // + // The soundness of doing so relies on fact that `__make_init` requires a + // higher-ranked trait bound on the closure. Within the closure (which is the + // caller of the generated slot projection functions here), it can make no + // assumptions on the lifetime except for those implied by the struct's bounds, + // and we have validated them in `generate_drop_check`. + quote!(SelfRefSlot), + quote!(#lifetime, ::pin_init::__internal::Shared, ), + ), + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + lifetime, + .. + }) => ( + // For borrowed fields, create a `SelfRefSlot`, which after initialization + // turns into a `SelfRefDropGuard` instead of `DropGuard`. + // + // They're mostly the same, except that `SelfRefDropGuard` returns `&'field T` + // instead of `&'guard T` for let bindings; this allows it to be used to be + // used to initialize other fields. + // + // The soundness of doing so relies on fact that `__make_init` requires a + // higher-ranked trait bound on the closure. Within the closure (which is the + // caller of the generated slot projection functions here), it can make no + // assumptions on the lifetime except for those implied by the struct's bounds, + // and we have validated them in `generate_drop_check`. + quote!(SelfRefSlot), + quote!(#lifetime, ::pin_init::__internal::Mutable, ), + ), + }; + quote! { /// # Safety /// @@ -538,19 +1980,55 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { #vis unsafe fn #field_name( self, slot: *mut #struct_name #ty_generics, - ) -> ::pin_init::__internal::Slot<::pin_init::__internal::#pin_marker, #ty> { + ) -> ::pin_init::__internal::#slot_ty< + #slot_arg ::pin_init::__internal::#pin_marker, #ty + > { + // CAST: `as _` is needed to convert types wrapped inside `SelfRef`. // SAFETY: // - If `#pin_marker` is `Pinned`, the corresponding field is structurally // pinned. // - Other safety requirements follows the safety requirement. - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#member) } + // - If `#slot_ty` is `SelfRefSlot`, the lifetime `#lt` represents that of the + // field. + unsafe { ::pin_init::__internal::#slot_ty::new(&raw mut (*slot).#member as _) } } } }) .collect::(); + + let exist_lt_generics_params = info.exist_lts_generics.params.iter(); quote! { - // We declare this struct which will host all of the projection function for our type. It - // will be invariant over all generic parameters which are inherited from the struct. + // We declare this struct which will host all of the projection function for our type. + #[doc(hidden)] + #[allow(non_snake_case)] + #vis struct __PinDataLt #generics_with_field_lt + #whr_with_field_lt + { + #(#phantom_fields)* + __pin_phantom: ::core::marker::PhantomData<#struct_name #ty_generics>, + } + + impl #impl_generics_with_lt ::core::clone::Clone for __PinDataLt #ty_generics_with_field_lt + #whr_with_field_lt + { + fn clone(&self) -> Self { *self } + } + + impl #impl_generics_with_lt ::core::marker::Copy for __PinDataLt #ty_generics_with_field_lt + #whr_with_field_lt + {} + + #[allow(dead_code)] // Some functions might never be used and private. + #[expect(clippy::missing_safety_doc)] + impl #impl_generics_with_lt __PinDataLt #ty_generics_with_field_lt + #whr_with_field_lt + { + #field_accessors + } + + // Declare a type that serves as the entry point of interaction with the `pin_init!` macro. + // We use this type instead of defining methods directly on user's type to avoid possibility + // of name conflicts. #[doc(hidden)] #vis struct __ThePinData #generics #whr @@ -569,21 +2047,29 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { #whr {} - #[allow(dead_code)] // Some functions might never be used and private. impl #impl_generics __ThePinData #ty_generics #whr { /// Type inference helper function. #[inline(always)] - #vis fn __make_closure<__F, __E>(self, f: __F) -> __F + #vis fn __make_closure<#(#exist_lt_generics_params,)* __F, __E>(self, f: __F) -> __F where - __F: FnOnce(*mut #struct_name #ty_generics) -> - ::core::result::Result<::pin_init::__internal::InitOk, __E>, + __F: for #field_lt_ty_generics ::core::ops::FnOnce( + *mut #struct_name #ty_generics, + __PinDataLt #ty_generics_with_field_lt + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, { f } - #field_accessors + #[inline(always)] + #vis fn __with_lt #field_exist_lt_ty_generics(self) + -> __PinDataLt #ty_generics_with_field_lt + { + // Generate a zeroed to avoid naming all fields. + // SAFETY: `__PinDataLt` only contains phantom fields. + unsafe { ::core::mem::zeroed() } + } } // SAFETY: We have added the correct projection functions above to `__ThePinData` and diff --git a/internal/src/util.rs b/internal/src/util.rs index 3ce498ce..4d3331b8 100644 --- a/internal/src/util.rs +++ b/internal/src/util.rs @@ -1,8 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT +use std::collections::{BTreeMap, BTreeSet}; + use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, ToTokens}; -use syn::{Attribute, GenericParam, Generics, Index, Member, Token}; +use syn::{ + parse_quote, visit::Visit, visit_mut::VisitMut, Attribute, BoundLifetimes, GenericParam, + Generics, Index, Lifetime, Member, Token, Type, TypePath, +}; use crate::DiagCtxt; @@ -81,6 +86,7 @@ impl MemberExt for Member { pub(crate) struct CombinedGenerics<'a>(pub(crate) Vec<&'a Generics>); pub(crate) struct CombinedImplGenerics<'a>(&'a CombinedGenerics<'a>); pub(crate) struct CombinedTypeGenerics<'a>(&'a CombinedGenerics<'a>); +pub(crate) struct CombinedWhereClauses<'a>(&'a CombinedGenerics<'a>); impl CombinedGenerics<'_> { pub(crate) fn split_for_impl( @@ -88,10 +94,13 @@ impl CombinedGenerics<'_> { ) -> ( CombinedImplGenerics<'_>, CombinedTypeGenerics<'_>, - // A stub type so `split_for_impl` signature matches that of `syn`'s. - impl Sized, + CombinedWhereClauses<'_>, ) { - (CombinedImplGenerics(self), CombinedTypeGenerics(self), ()) + ( + CombinedImplGenerics(self), + CombinedTypeGenerics(self), + CombinedWhereClauses(self), + ) } } @@ -237,3 +246,255 @@ impl ToTokens for CombinedTypeGenerics<'_> { .to_tokens(tokens); } } + +impl ToTokens for CombinedWhereClauses<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0 + .0 + .iter() + .filter_map(|x| Some(x.where_clause.as_ref()?.where_token)) + .next_back() + .unwrap_or_default() + .to_tokens(tokens); + + let comma: Token![,] = Default::default(); + + for generics in self.0 .0.iter() { + let Some(where_clause) = &generics.where_clause else { + continue; + }; + + where_clause.predicates.to_tokens(tokens); + if !where_clause.predicates.empty_or_trailing() { + comma.to_tokens(tokens); + } + } + } +} + +pub(crate) trait LifetimeExt { + /// Get a visitor that call the provided function for all unbound lifetimes. + fn visitor<'a>(f: impl FnMut(&'a Lifetime)) -> impl Visit<'a>; + + /// Obtain a lifetime from a identifier. + /// + /// The created lifetime has the same span. + fn from_ident(ident: &Ident) -> Self; +} + +impl LifetimeExt for Lifetime { + fn visitor<'a>(f: impl FnMut(&'a Lifetime)) -> impl Visit<'a> { + LifetimeVisitor { + bound: BTreeSet::new(), + visit: f, + } + } + + fn from_ident(ident: &Ident) -> Self { + Lifetime { + apostrophe: ident.span(), + ident: ident.clone(), + } + } +} + +struct LifetimeVisitor<'a, F> { + bound: BTreeSet<&'a Lifetime>, + visit: F, +} + +impl<'a, F> LifetimeVisitor<'a, F> { + fn with_bound_lifetimes( + &mut self, + bound: Option<&'a BoundLifetimes>, + f: impl FnOnce(&mut Self), + ) { + // In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`, + // the lifetimes in the binder are bound and should not be visited. + + let mut to_remove = Vec::new(); + if let Some(bound) = bound { + for lt in &bound.lifetimes { + let GenericParam::Lifetime(lt) = lt else { + continue; + }; + if !self.bound.contains(&<.lifetime) { + self.bound.insert(<.lifetime); + to_remove.push(<.lifetime); + } + } + } + + f(self); + + for lt in to_remove { + self.bound.remove(lt); + } + } +} + +impl<'a, F: FnMut(&'a Lifetime)> Visit<'a> for LifetimeVisitor<'a, F> { + fn visit_lifetime(&mut self, lt: &'a Lifetime) { + if lt.ident == "static" { + return; + } + + if !self.bound.contains(lt) { + (self.visit)(lt); + } + } + + fn visit_trait_bound(&mut self, bound: &'a syn::TraitBound) { + self.with_bound_lifetimes(bound.lifetimes.as_ref(), |this| { + this.visit_path(&bound.path) + }); + } + + fn visit_type_bare_fn(&mut self, bare_fn: &'a syn::TypeBareFn) { + self.with_bound_lifetimes(bare_fn.lifetimes.as_ref(), |this| { + for input in bare_fn.inputs.iter() { + this.visit_bare_fn_arg(input); + } + this.visit_return_type(&bare_fn.output); + }); + } +} + +pub(crate) trait GenericParamExt { + fn maybe_type_params_visitor<'a>(f: impl FnMut(&'a Ident)) -> impl Visit<'a>; +} + +impl GenericParamExt for GenericParam { + fn maybe_type_params_visitor<'a>(f: impl FnMut(&'a Ident)) -> impl Visit<'a> { + struct TypeParamVisitor(F); + + impl<'a, F> Visit<'a> for TypeParamVisitor + where + F: FnMut(&'a Ident), + { + fn visit_type_path(&mut self, ty: &'a TypePath) { + if ty.qself.is_none() + && ty.path.leading_colon.is_none() + && ty.path.segments[0].arguments.is_none() + { + (self.0)(&ty.path.segments[0].ident); + } + syn::visit::visit_type_path(self, ty); + } + } + + TypeParamVisitor(f) + } +} + +pub(crate) trait TypeExt { + /// Check if the type includes macro invocations. + /// + /// Proc-macros cannot expand macros and peek into them, so if macro is involved sometimes + /// special handling is required. + fn has_macro(&self) -> bool; + + fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type; +} + +impl TypeExt for Type { + fn has_macro(&self) -> bool { + struct HasMacro(bool); + + impl<'ast> Visit<'ast> for HasMacro { + fn visit_macro(&mut self, _: &'ast syn::Macro) { + self.0 = true; + } + } + + let mut visitor = HasMacro(false); + visitor.visit_type(self); + visitor.0 + } + + fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type { + if needle.is_empty() { + return self.clone(); + } + + // If the type has macro, we cannot peek into it. Use some different approach to replace + // the type using GAT. + if self.has_macro() { + return parse_quote!( + < + for<#(#needle,)*> fn(#(&#needle (),)*) -> #self + as + ::pin_init::__internal::FnOutput<(#(&#replacement (),)*)> + >::Output + ); + } + + struct LifetimeReplacer<'a> { + to_replace: BTreeMap<&'a Lifetime, &'a Lifetime>, + } + + impl<'a> LifetimeReplacer<'a> { + fn with_bound_lifetimes( + &mut self, + bound: Option<&BoundLifetimes>, + f: impl FnOnce(&mut Self), + ) { + // In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`, + // temporarily remove them from to_replace if they're. + + let mut removed = Vec::new(); + if let Some(bound) = bound { + for lt in &bound.lifetimes { + let GenericParam::Lifetime(lt) = lt else { + continue; + }; + if let Some(entry) = self.to_replace.remove_entry(<.lifetime) { + removed.push(entry); + } + } + } + + f(self); + + for (key, val) in removed { + self.to_replace.insert(key, val); + } + } + } + + impl VisitMut for LifetimeReplacer<'_> { + fn visit_lifetime_mut(&mut self, lt: &mut syn::Lifetime) { + if let Some(&replacement) = self.to_replace.get(lt) { + *lt = replacement.clone(); + } + } + + fn visit_trait_bound_mut(&mut self, bound: &mut syn::TraitBound) { + self.with_bound_lifetimes(bound.lifetimes.as_ref(), |this| { + this.visit_path_mut(&mut bound.path) + }); + } + + fn visit_type_bare_fn_mut(&mut self, bare_fn: &mut syn::TypeBareFn) { + self.with_bound_lifetimes(bare_fn.lifetimes.as_ref(), |this| { + for input in bare_fn.inputs.iter_mut() { + this.visit_bare_fn_arg_mut(input); + } + + this.visit_return_type_mut(&mut bare_fn.output); + }); + } + } + + let mut ret = self.clone(); + LifetimeReplacer { + to_replace: needle + .iter() + .copied() + .zip(replacement.iter().copied()) + .collect(), + } + .visit_type_mut(&mut ret); + ret + } +} diff --git a/src/__internal.rs b/src/__internal.rs index cba53b8c..04abb759 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -5,6 +5,11 @@ //! These items must not be used outside of this crate and the pin-init-internal crate located at //! `../internal`. +#![expect(clippy::new_without_default, reason = "private API")] + +use core::marker::PhantomPinned; +use core::ops::Deref; + use super::*; /// Zero-sized type used to mark a type as invariant. @@ -106,10 +111,15 @@ impl InitData { #[inline(always)] pub fn __make_closure(self, f: F) -> F where - F: FnOnce(*mut T) -> Result, + F: FnOnce(*mut T, Self) -> Result, { f } + + #[inline(always)] + pub fn __with_lt(self) -> Self { + self + } } /// Stack initializer helper type. Use [`stack_pin_init`] instead of this primitive. @@ -319,6 +329,12 @@ impl DropGuard { // SAFETY: Per type invariant. unsafe { &mut *self.ptr } } + + /// Create a let binding for accessor use in dropck. + #[inline] + pub fn let_binding_in_dropck(&mut self) -> &mut T { + self.let_binding() + } } impl DropGuard { @@ -329,6 +345,12 @@ impl DropGuard { // pinned per type invariant. unsafe { Pin::new_unchecked(&mut *self.ptr) } } + + /// Create a let binding for accessor use in dropck. + #[inline] + pub fn let_binding_in_dropck(&mut self) -> Pin<&mut T> { + self.let_binding() + } } impl Drop for DropGuard { @@ -339,6 +361,192 @@ impl Drop for DropGuard { } } +pub struct Shared; +pub struct Mutable; + +/// Represent an uninitialized field in a pinned struct that will be referenced by other fields. +/// +/// # Invariants +/// +/// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed memory +/// and will live longer than `'a`. +/// - If `P` is `Pinned`, then `ptr` is structurally pinned. +pub struct SelfRefSlot<'a, M, P, T: ?Sized> { + ptr: *mut T, + _phantom: PhantomData<(M, P, &'a mut T)>, +} + +impl<'a, M, P, T: ?Sized> SelfRefSlot<'a, M, P, T> { + /// # Safety + /// + /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed + /// memory and will live longer than `'a`. + /// - If `P` is `Pinned`, then `ptr` is structurally pinned. + #[inline] + pub unsafe fn new(ptr: *mut T) -> Self { + // INVARIANT: Per safety requirement. + Self { + ptr, + _phantom: PhantomData, + } + } + + /// Initialize the field by value. + #[inline] + pub fn write(self, value: T) -> SelfRefDropGuard<'a, M, P, T> + where + T: Sized, + { + // SAFETY: `self.ptr` is a valid and aligned pointer for write. + unsafe { self.ptr.write(value) } + // SAFETY: + // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant. + // - `*self.ptr` is initialized above and the ownership is transferred to the guard. + // - If `P` is `Pinned`, `self.ptr` is pinned. + unsafe { SelfRefDropGuard::new(self.ptr) } + } +} + +impl<'a, M, T: ?Sized> SelfRefSlot<'a, M, Unpinned, T> { + /// Initialize the field. + #[inline] + pub fn init(self, init: impl Init) -> Result, E> { + // SAFETY: + // - `self.ptr` is valid and properly aligned. + // - when `Err` is returned, we also propagate the error without touching `ptr`; + // also `self` is consumed so it cannot be touched further. + unsafe { init.__init(self.ptr)? }; + + // SAFETY: + // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant. + // - `*self.ptr` is initialized above and the ownership is transferred to the guard. + Ok(unsafe { SelfRefDropGuard::new(self.ptr) }) + } +} + +impl<'a, M, T: ?Sized> SelfRefSlot<'a, M, Pinned, T> { + /// Initialize the field. + #[inline] + pub fn init( + self, + init: impl PinInit, + ) -> Result, E> { + // SAFETY: + // - `ptr` is valid + // - when `Err` is returned, we also propagate the error without touching `ptr`; + // also `self` is consumed so it cannot be touched further. + // - the drop guard will not hand out `&mut` (only `Pin<&mut T>`). + unsafe { init.__init(self.ptr)? }; + + // SAFETY: + // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant. + // - `*self.ptr` is initialized above and the ownership is transferred to the guard. + Ok(unsafe { SelfRefDropGuard::new(self.ptr) }) + } +} +/// When a value of this type is dropped, it drops a `T`. +/// +/// Can be forgotten to prevent the drop. +/// +/// # Invariants +/// +/// - `ptr` is valid, properly aligned and live longer than `'a`. +/// - `*ptr` is initialized and owned by this guard. +/// - if `P` is `Pinned`, `ptr` is pinned. +pub struct SelfRefDropGuard<'a, M, P, T: ?Sized> { + ptr: *mut T, + phantom: PhantomData<(M, P, &'a mut T)>, +} + +impl<'a, M, P, T: ?Sized> SelfRefDropGuard<'a, M, P, T> { + /// Creates a drop guard and transfer the ownership of the pointer content. + /// + /// The ownership is only relinquished if the guard is forgotten via [`core::mem::forget`]. + /// + /// # Safety + /// + /// - `ptr` is valid, properly aligned and live longer than `'a`. + /// - `*ptr` is initialized, and the ownership is transferred to this guard. + /// - if `P` is `Pinned`, `ptr` is pinned. + #[inline] + pub unsafe fn new(ptr: *mut T) -> Self { + // INVARIANT: By safety requirement. + Self { + ptr, + phantom: PhantomData, + } + } +} + +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Shared, Unpinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> &'a T { + // SAFETY: Per type invariant. + unsafe { &*self.ptr } + } + + /// Create a let binding for accessor use in dropck. + #[inline] + pub fn let_binding_in_dropck(&mut self) -> &T { + self.let_binding() + } +} + +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Shared, Pinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> Pin<&'a T> { + // SAFETY: `self.ptr` is valid, properly aligned, live longer than `'a`, initialized, + // exclusively accessible and pinned per type invariant. + unsafe { Pin::new_unchecked(&*self.ptr) } + } + + /// Create a let binding for accessor use in dropck. + #[inline] + pub fn let_binding_in_dropck(&mut self) -> Pin<&T> { + self.let_binding() + } +} + +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Mutable, Unpinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> &'a mut T { + // SAFETY: Per type invariant. + unsafe { &mut *self.ptr } + } + + /// Create a let binding for accessor use in dropck. + #[inline] + pub fn let_binding_in_dropck(&mut self) -> &mut T { + self.let_binding() + } +} + +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Mutable, Pinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> Pin<&'a mut T> { + // SAFETY: `self.ptr` is valid, properly aligned, live longer than `'a`, initialized, + // exclusively accessible and pinned per type invariant. + unsafe { Pin::new_unchecked(&mut *self.ptr) } + } + + #[inline] + pub fn let_binding_in_dropck(&mut self) -> Pin<&mut T> { + self.let_binding() + } +} + +impl Drop for SelfRefDropGuard<'_, M, P, T> { + #[inline] + fn drop(&mut self) { + // SAFETY: `self.ptr` is valid, properly aligned and `*self.ptr` is owned by this guard. + unsafe { ptr::drop_in_place(self.ptr) } + } +} + /// Token used by `PinnedDrop` to prevent calling the function without creating this unsafely /// created struct. This is needed, because the `drop` function is safe, but should not be called /// manually. @@ -385,3 +593,150 @@ unsafe impl PinInit for AlwaysFail { Err(()) } } +/// Polyfill of `FnOnce` trait to be able to reference output via associated type. +pub trait FnOutput { + type Output; +} + +macro_rules! impl_fn_output { + () => {}; + ($ret:ident, $($arg:ident,)*) => { + impl FnOutput<($($arg,)*)> for This + where + This: FnOnce($($arg,)*) -> $ret, + { + type Output = $ret; + } + impl_fn_output!($($arg,)*); + }; +} + +impl_fn_output!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U,); + +/// Lifetime erasure facility. +/// +/// Say we have `exists<'a, 'b> Foo<'a, 'b>` and we want to store it. There's no concrete +/// lifetimes we can use, so we want to erase the lifetime. +/// +/// Such erasure can be encoded as +/// `Erased fn(&'a ()) -> for<'b> fn(&'b()) -> (Foo<'a, 'b>,)`. +/// +/// This can be considered the stable version of Rust's `unsafe_binder` feature, without the +/// no-drop-glue requirement. +#[repr(transparent)] +#[allow(private_bounds)] +pub struct Erase(F::Erased); + +/// Helper trait to resolve the erased lifetime. +trait EraseLt { + type Erased; +} + +impl EraseLt for (T,) { + type Erased = T; +} + +impl EraseLt for T +where + T: for<'a> FnOutput<(&'a (),), Output: EraseLt>, +{ + type Erased = <>::Output as EraseLt>::Erased; +} + +// The default `Send` and `Sync` are not sufficient, because one can use lifetime specialization to +// implement `Send` or `Sync` for a concrete instance of lifetime. Use HRTB to ensure that the type +// will only implement `Send` or `Sync` if it's implemented for *all* erased lifetimes. + +// SAFETY: Trivial, no lifetime to erase. +unsafe impl Send for Erase<(T,)> {} + +// SAFETY: If we erased a lifetime, then the type needs to be `Send` for across *all* that +// lifetimes. +unsafe impl Send for Erase +where + F: for<'a> FnOutput<(&'a (),), Output: EraseLt>, + for<'a> Erase<>::Output>: Send, +{ +} + +// SAFETY: Trivial, no lifetime to erase. +unsafe impl Sync for Erase<(T,)> {} + +// SAFETY: If we erased a lifetime, then the type needs to be `Sync` for across *all* that +// lifetimes. +unsafe impl Sync for Erase +where + F: for<'a> FnOutput<(&'a (),), Output: EraseLt>, + for<'a> Erase<>::Output>: Sync, +{ +} + +/// Wrapper for borrowed fields. +/// +/// This should be switched to `UnsafePinned` when it is stable. +/// NOTE: This type needs to be covariant; Rust's 1.89+'s `UnsafePinned` is invariant. +#[repr(transparent)] +pub struct Borrowed(PhantomInvariant

, PhantomPinned, T); + +// Lifetimes not needed by drop glue are considered by Rust's drop check to be considered +// `#[may_dangle]`. In case for a self-referential struct, we may have fields which need lifetime of +// borrowed fields in their drop glue, so compiler's automatic check is insufficient. +// +// Code like this: +// ``` +// #[pin_data] +// struct SelfRef<'a> { +// borrow: PrintOnDrop<&'owner str>, +// owner: &'a str, +// } +// ``` +// may access `owner` during the drop, however Rust will determine that since `'a` only is used in +// `owner`, the `'a` lifetime may dangle during drop. +// +// This is undesirable for pin-init self references, because `&'a str` could be coerecd to +// `&'owner str` and this could further coerce if there're implied outlives, e.g. +// `&'earlier_field &'owner ()` would allow `&'owner str` to further coerce to `&'earlier_field`. +// +// Thus, if any self-referential field require field lifetime access in `Drop` impl, we would need +// to ensure that the all generic parameters visible by self-referential fields would strictly +// outlive the struct. And this can be done by a simple `Drop` impl that does nothing. Without a +// dropck eye patch, presence of `Drop` impl, albeit empty, tells the drop check that the strict +// outlive relation is needed. +impl Drop for Borrowed { + #[inline(always)] + fn drop(&mut self) {} +} + +impl Deref for Borrowed { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &T { + &self.2 + } +} + +/// An alias of `PhantomData` but with a name to aid user in case of misuse. +pub struct NotVisible(PhantomData); + +impl NotVisible { + #[inline(always)] + pub fn new() -> Self { + Self(PhantomData) + } +} + +/// Marker type to capture outlive bounds coming from existential lifetimes. +pub struct ExistLt<'a>(PhantomData<&'a ()>); + +// Dummy `Drop`, same reason as `Borrowed`. +impl Drop for ExistLt<'_> { + #[inline(always)] + fn drop(&mut self) {} +} + +/// Type that allows additional `PhantomData` to be attached. +/// +/// This allows changing variance of types without introducing additional fields. +#[repr(transparent)] +pub struct WithPhantom(PhantomData

, T); diff --git a/src/lib.rs b/src/lib.rs index d1ed0561..9ffa7643 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -923,6 +923,7 @@ macro_rules! assert_pinned { let data = <$ty as $crate::__internal::HasInitData>::__init_data(); let data = $crate::__internal::HasPinData::__pin_data(data); _ = data + .__with_lt() .$field(ptr) .init($crate::__internal::AlwaysFail::<$field_ty>::new()); }; diff --git a/tests/existential.rs b/tests/existential.rs new file mode 100644 index 00000000..bf709ad3 --- /dev/null +++ b/tests/existential.rs @@ -0,0 +1,35 @@ +#![allow(dead_code)] + +use std::marker::PhantomData; +use std::sync::Mutex; + +use pin_init::*; + +// An example kernel use case, where `'a` is the lifetime of device, and `T` being some other owned +// data. +struct CoherentBox<'a, T>(PhantomData<(&'a (), T)>); + +#[pin_data] +struct Foo<'a> +where + exists<'x>: 'a, +{ + // We have a mutex to synchronize the access to the data. + // But we never want to put in a `CoherentBox` from another device, + // so ideally we want this data structure to be covariant over `'a`. + #[uses('x: invariant)] + m: Mutex>, + p: PhantomData<&'a ()>, +} + +fn shorten<'long: 'short, 'short>(f: Foo<'long>) -> Foo<'short> { + f +} + +fn use_foo(f: &Foo) { + f.with_m(|m| { + let mut g = m.lock().unwrap(); + /* do something with `g` */ + let _ = &mut *g; + }) +} diff --git a/tests/selfref_shorten.rs b/tests/selfref_shorten.rs new file mode 100644 index 00000000..fbe83a10 --- /dev/null +++ b/tests/selfref_shorten.rs @@ -0,0 +1,148 @@ +use std::marker::PhantomData; +use std::sync::Mutex; + +use pin_init::*; + +#[pin_data] +struct SelfRef { + #[uses('bar)] + part: &'foo str, + foo: String, + bar: String, +} + +#[test] +fn self_ref() { + stack_pin_init!(let _foo = pin_init!(SelfRef { + foo: "hello world".to_owned(), + bar: "hello world".to_owned(), + part: &foo[..5], + })); + + stack_pin_init!(let _bar = pin_init!(SelfRef { + foo: "hello world".to_owned(), + bar: "hello world".to_owned(), + // In this case, we borrow from a field that lives longers. + // We're allowed to coerce it into a shorter-living lifetime. + part: &bar[..5], + })); +} + +// This struct is covariant over `'a`, despite there are some invariant fields. +#[pin_data] +struct SelfRefCov<'a> { + cov_part: PhantomData<&'cov_owner ()>, + cov_owner: &'a String, + #[uses('owner: invariant)] + part: Mutex<&'owner str>, + owner: String, +} + +// Swapping the order of unrelated groups should not affect the variance. +#[pin_data] +struct SelfRefCovSwapped<'a> { + #[uses('owner: invariant)] + part: Mutex<&'owner str>, + owner: String, + cov_part: PhantomData<&'cov_owner ()>, + cov_owner: &'a String, +} + +fn shorten_cov<'long, 'short>(foo: &'short SelfRefCov<'long>) -> &'short SelfRefCov<'short> +where + 'long: 'short, +{ + foo +} + +fn shorten_cov_swapped<'long, 'short>( + foo: &'short SelfRefCovSwapped<'long>, +) -> &'short SelfRefCovSwapped<'short> +where + 'long: 'short, +{ + foo +} + +fn init_cov(s: &String) -> impl PinInit> + '_ { + pin_init!(SelfRefCov { + cov_part: PhantomData, + cov_owner: s, + owner: "early".to_owned(), + part: Mutex::new("static"), + }) +} + +fn init_cov_swapped(s: &String) -> impl PinInit> + '_ { + pin_init!(SelfRefCovSwapped { + owner: "early".to_owned(), + part: Mutex::new("static"), + cov_part: PhantomData, + cov_owner: s, + }) +} + +#[test] +fn shorten_with_inv_field() { + let s = "hello".to_owned(); + stack_pin_init!(let first = init_cov(&s)); + let _first: &SelfRefCov<'_> = shorten_cov(&first); + stack_pin_init!(let second = init_cov_swapped(&s)); + let _second: &SelfRefCovSwapped<'_> = shorten_cov_swapped(&second); +} + +// Shortening is allowed, even for invariant field. +// +// This capability is only allowed in `with_project` though (`with_project_ref` rejects this). +#[pin_data] +struct SelfRefInv { + #[uses('early: invariant, 'later)] + part: Mutex<&'early str>, + early: String, + later: String, +} + +#[test] +fn shorten_inv_with_project() { + stack_pin_init!(let foo = pin_init!(SelfRefInv { + early: "early".to_owned(), + later: "later".to_owned(), + part: Mutex::new("static"), + })); + foo.as_mut().with_project(|proj| { + *proj.part.get_mut().unwrap() = proj.later.as_str(); + }); + assert_eq!( + foo.as_ref() + .with_project_ref(|proj| *proj.part.lock().unwrap()), + "later" + ); +} + +// Mutation in initializer is okay, as long as the order is correct. +// See tests/ui/compile-fail/selfref_mutate_in_init.rs for a wrong example. +#[pin_data] +struct MutateInInit { + #[uses('early: invariant, 'later)] + part: Mutex<&'early str>, + early: String, + later: String, +} + +#[test] +fn mutate_in_init() { + stack_try_pin_init!(let foo = pin_init!(MutateInInit { + early: "early".to_owned(), + later: "later".to_owned(), + part: Mutex::new(""), + _: { + *part.get_mut().unwrap() = later; + }, + }? ())); + let foo = foo.unwrap(); + assert_eq!( + foo.as_ref() + .with_project_ref(|proj| *proj.part.lock().unwrap()), + "later" + ); +} diff --git a/tests/ui/compile-fail/init/selfref_field_selfref.rs b/tests/ui/compile-fail/init/selfref_field_selfref.rs new file mode 100644 index 00000000..e7155e25 --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_field_selfref.rs @@ -0,0 +1,30 @@ +use pin_init::*; +use std::fmt::Display; +use std::sync::Mutex; + +struct PrintOnDrop(T); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct SelfRef { + // Given the drop glue, this will essentially imply that `'r` strictly outlive `'r` which is + // obviously nonsense. + #[uses('_: invariant)] + r: (String, Mutex>), +} + +fn main() { + let mut data = Box::pin_init(pin_init!(SelfRef { + r: ("hello".to_owned(), Mutex::new(PrintOnDrop("str"))), + })) + .unwrap(); + + data.as_mut().with_project(|data| { + *data.r.1.lock().unwrap() = PrintOnDrop(&data.r.0); + }) +} diff --git a/tests/ui/compile-fail/init/selfref_field_selfref.stderr b/tests/ui/compile-fail/init/selfref_field_selfref.stderr new file mode 100644 index 00000000..53f9ff94 --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_field_selfref.stderr @@ -0,0 +1,5 @@ +error: field `r` cannot borrow from itself + --> tests/ui/compile-fail/init/selfref_field_selfref.rs:18:36 + | +18 | r: (String, Mutex>), + | ^^ diff --git a/tests/ui/compile-fail/init/selfref_may_dangle.rs b/tests/ui/compile-fail/init/selfref_may_dangle.rs new file mode 100644 index 00000000..0223b69f --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_may_dangle.rs @@ -0,0 +1,48 @@ +use pin_init::*; +use std::convert::Infallible; +use std::fmt::Display; + +struct PrintOnDrop(T); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct SelfRef<'a> { + part: PrintOnDrop<&'outer String>, + outer: &'a String, +} + +// Similar one to the above, but make use of `exists`. +#[pin_data] +struct ExistsOwner<'a> +where + exists<'x>: 'a, +{ + part: PrintOnDrop<&'outer String>, + outer: &'x String, +} + +fn new<'a>(str: &'a String) -> impl PinInit, Infallible> { + pin_init!(SelfRef { + outer: str, + part: PrintOnDrop(*outer), + }) +} + +fn new_exists<'a>(s: &'a String) -> impl PinInit, Infallible> { + pin_init!(ExistsOwner { + outer: s, + part: PrintOnDrop(*outer), + }) +} + +fn main() { + let str = "hello world".to_owned(); + let _selfref = Box::pin_init(new(&str)).unwrap(); + let _selfref_exists = Box::pin_init(new_exists(&str)).unwrap(); + drop(str); +} diff --git a/tests/ui/compile-fail/init/selfref_may_dangle.stderr b/tests/ui/compile-fail/init/selfref_may_dangle.stderr new file mode 100644 index 00000000..de18691c --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_may_dangle.stderr @@ -0,0 +1,17 @@ +error[E0505]: cannot move out of `str` because it is borrowed + --> tests/ui/compile-fail/init/selfref_may_dangle.rs:47:10 + | +44 | let str = "hello world".to_owned(); + | --- binding `str` declared here +45 | let _selfref = Box::pin_init(new(&str)).unwrap(); + | ---- borrow of `str` occurs here +46 | let _selfref_exists = Box::pin_init(new_exists(&str)).unwrap(); +47 | drop(str); + | ^^^ move out of `str` occurs here +48 | } + | - borrow might be used here, when `_selfref` is dropped and runs the destructor for type `Pin>>` + | +help: consider cloning the value if the performance cost is acceptable + | +45 | let _selfref = Box::pin_init(new(&str.clone())).unwrap(); + | ++++++++ diff --git a/tests/ui/compile-fail/init/selfref_mutate_in_init.rs b/tests/ui/compile-fail/init/selfref_mutate_in_init.rs new file mode 100644 index 00000000..391faf55 --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_mutate_in_init.rs @@ -0,0 +1,60 @@ +#![allow(warnings)] +use pin_init::*; +use std::fmt::Display; +use std::marker::PhantomData; +use std::sync::Mutex; + +struct PrintOnDrop(T); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct Direct { + #[uses('early: invariant)] + part: Mutex>, + early: String, +} + +#[pin_data] +struct Indirect { + #[uses('early: invariant, 'later)] + part: Mutex>, + early: String, + later: String, +} + +fn broken_direct() -> impl PinInit { + pin_init!(Direct { + part: Mutex::new(PrintOnDrop("")), + early: "hello world".to_owned(), + _: { + *part.lock().unwrap() = PrintOnDrop(early); + if true { + return Err(()); + } + }, + }? ()) +} + +fn broken_indirect() -> impl PinInit { + pin_init!(Indirect { + part: Mutex::new(PrintOnDrop("")), + later: "later".to_owned(), + early: "early".to_owned(), + _: { + *part.lock().unwrap() = PrintOnDrop(early); + if true { + return Err(()); + } + }, + }? ()) +} + +fn main() { + stack_try_pin_init!(let _s = broken_direct()); + stack_try_pin_init!(let _s = broken_indirect()); +} diff --git a/tests/ui/compile-fail/init/selfref_mutate_in_init.stderr b/tests/ui/compile-fail/init/selfref_mutate_in_init.stderr new file mode 100644 index 00000000..65a75b60 --- /dev/null +++ b/tests/ui/compile-fail/init/selfref_mutate_in_init.stderr @@ -0,0 +1,26 @@ +error[E0505]: cannot move out of value because it is borrowed + --> tests/ui/compile-fail/init/selfref_mutate_in_init.rs:33:9 + | +32 | part: Mutex::new(PrintOnDrop("")), + | ---- borrow later used here +33 | early: "hello world".to_owned(), + | ^^^^^ + | | + | move out of value occurs here + | borrow of value occurs here + | + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0505]: cannot move out of value because it is borrowed + --> tests/ui/compile-fail/init/selfref_mutate_in_init.rs:47:9 + | +45 | part: Mutex::new(PrintOnDrop("")), + | ---- borrow later used here +46 | later: "later".to_owned(), +47 | early: "early".to_owned(), + | ^^^^^ + | | + | move out of value occurs here + | borrow of value occurs here + | + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/init/tuple_invalid_field.stderr b/tests/ui/compile-fail/init/tuple_invalid_field.stderr index 9ed771c6..a70f0db2 100644 --- a/tests/ui/compile-fail/init/tuple_invalid_field.stderr +++ b/tests/ui/compile-fail/init/tuple_invalid_field.stderr @@ -1,4 +1,4 @@ -error[E0599]: no method named `_2` found for struct `__ThePinData` in the current scope +error[E0599]: no method named `_2` found for struct `__PinDataLt` in the current scope --> tests/ui/compile-fail/init/tuple_invalid_field.rs:7:43 | 3 | #[pin_data] diff --git a/tests/ui/compile-fail/pin_data/selfref_always_pin.rs b/tests/ui/compile-fail/pin_data/selfref_always_pin.rs new file mode 100644 index 00000000..b109dfee --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_always_pin.rs @@ -0,0 +1,32 @@ +// Ensure that types that self-references are always pinned. + +use pin_init::*; + +#[pin_data] +struct Foo { + #[borrowed] + f: u32, +} + +#[pin_data] +struct Bar { + b: &'f u32, + f: u32, +} + +#[pin_data] +struct Baz { + #[borrowed] + f: u32, +} + +// Manual implementation must fail. +impl Unpin for Baz {} + +fn assert_unpin() {} + +fn main() { + // All of the below checks must fail. + assert_unpin::(); + assert_unpin::(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_always_pin.stderr b/tests/ui/compile-fail/pin_data/selfref_always_pin.stderr new file mode 100644 index 00000000..a64c174c --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_always_pin.stderr @@ -0,0 +1,55 @@ +error[E0119]: conflicting implementations of trait `Unpin` for type `Baz` + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:17:1 + | +17 | #[pin_data] + | ^^^^^^^^^^^ conflicting implementation for `Baz` +... +24 | impl Unpin for Baz {} + | ------------------ first implementation here + | + = note: upstream crates may add a new impl of trait `std::marker::Unpin` for type `std::marker::PhantomPinned` in future versions + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `PhantomPinned` cannot be unpinned + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:30:20 + | +30 | assert_unpin::(); + | ^^^ the trait `Unpin` is not implemented for `PhantomPinned` + | + = note: consider using the `pin!` macro + consider using `Box::pin` if you need to access the pinned value outside of the current scope +note: required for `Foo` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:5:1 + | + 5 | #[pin_data] + | ^^^^^^^^^^^ unsatisfied trait bound introduced here + 6 | struct Foo { + | ^^^ +note: required by a bound in `assert_unpin` + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:26:20 + | +26 | fn assert_unpin() {} + | ^^^^^ required by this bound in `assert_unpin` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `PhantomPinned` cannot be unpinned + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:31:20 + | +31 | assert_unpin::(); + | ^^^ the trait `Unpin` is not implemented for `PhantomPinned` + | + = note: consider using the `pin!` macro + consider using `Box::pin` if you need to access the pinned value outside of the current scope +note: required for `Bar` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:11:1 + | +11 | #[pin_data] + | ^^^^^^^^^^^ unsatisfied trait bound introduced here +12 | struct Bar { + | ^^^ +note: required by a bound in `assert_unpin` + --> tests/ui/compile-fail/pin_data/selfref_always_pin.rs:26:20 + | +26 | fn assert_unpin() {} + | ^^^^^ required by this bound in `assert_unpin` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_covariant_check.rs b/tests/ui/compile-fail/pin_data/selfref_covariant_check.rs new file mode 100644 index 00000000..d471822e --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_covariant_check.rs @@ -0,0 +1,9 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + not_cov: Box bool + 'str>, + str: String, +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr b/tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr new file mode 100644 index 00000000..1bceaa19 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr @@ -0,0 +1,15 @@ +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_covariant_check.rs:5:14 + | +3 | #[pin_data] + | ----------- in this attribute macro expansion +4 | struct SelfRef { +5 | not_cov: Box bool + 'str>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | lifetime `'__short` defined here + | lifetime `'__long` defined here + | function was supposed to return data with lifetime `'__long` but it is returning data with lifetime `'__short` + | + = help: consider adding the following bound: `'__short: '__long` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_dropck.rs b/tests/ui/compile-fail/pin_data/selfref_dropck.rs new file mode 100644 index 00000000..6c7b9d47 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_dropck.rs @@ -0,0 +1,34 @@ +use pin_init::*; + +#[pin_data] +struct WrongDropOrder { + b: u32, + ptr: &'b u32, +} + +struct PrintOnDrop<'a>(&'a str); + +impl<'a> Drop for PrintOnDrop<'a> { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct UnsoundImplied { + // Provides an implied bound that `a` outlives `b`! + ptr: &'b &'a (), + a: String, + cannot_refer_a: PrintOnDrop<'b>, + b: String, +} + +fn main() { + let _foo = Box::pin_init(pin_init!(UnsoundImplied { + ptr: &&(), + a: "hello".to_owned(), + cannot_refer_a: PrintOnDrop(a), + b: "world".to_owned(), + })) + .unwrap(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_dropck.stderr b/tests/ui/compile-fail/pin_data/selfref_dropck.stderr new file mode 100644 index 00000000..ef87016f --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_dropck.stderr @@ -0,0 +1,22 @@ +error: field `ptr` borrows `b`, but drops later + --> tests/ui/compile-fail/pin_data/selfref_dropck.rs:6:11 + | +6 | ptr: &'b u32, + | ^^ + +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_dropck.rs:20:10 + | +17 | #[pin_data] + | ----------- in this attribute macro expansion +... +20 | ptr: &'b &'a (), + | ^^^^^^^^^^ requires that `'a` must outlive `'b` +21 | a: String, + | - lifetime `'a` defined here +22 | cannot_refer_a: PrintOnDrop<'b>, +23 | b: String, + | - lifetime `'b` defined here + | + = help: consider adding the following bound: `'a: 'b` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs new file mode 100644 index 00000000..4ae5c8dc --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs @@ -0,0 +1,47 @@ +use pin_init::*; + +#[pin_data] +struct InvalidAttr<'a> { + #[uses('non_exist: covariant)] // Borrows non-existent fields + explicit: u32, + + implicit: &'non_exist u32, + + #[uses('b: covariant, 'b: invariant)] + duplicate: u32, + + #[borrowed] + valid_explicit: u32, + + #[borrowed(mut)] + valid_explicit_mut: u32, + + #[borrowed = "foobar"] + #[borrowed(foobar)] + invalid: u32, + + bound: &'a u32, + okay: &'b u32, + b: u32, +} + +#[pin_data] +struct Conflict<'a> { + b: &'a u32, + #[borrowed] + a: u32, +} + +#[pin_data] +struct InvalidTuple(#[uses(1)] u32, u32); + +#[pin_data] +struct InvalidBounds<'a: 'y> +where + 'a: 'x, +{ + y: &'x (), + x: &'a (), +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr new file mode 100644 index 00000000..97be8022 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr @@ -0,0 +1,101 @@ +error: `'non_exist` is not a field name + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:5:12 + | +5 | #[uses('non_exist: covariant)] // Borrows non-existent fields + | ^^^^^^^^^^ + +error: `'non_exist` is neither a lifetime in generics nor a field name + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:8:16 + | +8 | implicit: &'non_exist u32, + | ^^^^^^^^^^ + +error: lifetime `'b` is mentioned more than once + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:10:27 + | +10 | #[uses('b: covariant, 'b: invariant)] + | ^^ + +error: `#[borrowed]` attribute specified more than once + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:20:5 + | +20 | #[borrowed(foobar)] + | ^^^^^^^^^^^^^^^^^^^ + +error: expected parentheses: #[borrowed(...)] + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:19:7 + | +19 | #[borrowed = "foobar"] + | ^^^^^^^^ + +error: `'a` appear in generics and would conflict with field lifetime + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:32:5 + | +32 | a: u32, + | ^ + +error: expected lifetime + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:36:28 + | +36 | struct InvalidTuple(#[uses(1)] u32, u32); + | ^ + +error: field lifetimes cannot be used in bounds + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:39:26 + | +39 | struct InvalidBounds<'a: 'y> + | ^^ + +error: field lifetimes cannot be used in bounds + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:41:9 + | +41 | 'a: 'x, + | ^^ + +error[E0261]: use of undeclared lifetime name `'non_exist` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:8:16 + | +8 | implicit: &'non_exist u32, + | ^^^^^^^^^^ undeclared lifetime + | +help: consider introducing lifetime `'non_exist` here + | +4 | struct InvalidAttr<'non_exist, 'a> { + | +++++++++++ + +error[E0261]: use of undeclared lifetime name `'non_exist` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:8:16 + | +8 | implicit: &'non_exist u32, + | ^^^^^^^^^^ undeclared lifetime + | +help: consider introducing lifetime `'non_exist` here + | +8 | implicit<'non_exist>: &'non_exist u32, + | ++++++++++++ +help: consider introducing lifetime `'non_exist` here + | +4 | struct InvalidAttr<'non_exist, 'a> { + | +++++++++++ + +error[E0261]: use of undeclared lifetime name `'y` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:39:26 + | +39 | struct InvalidBounds<'a: 'y> + | ^^ undeclared lifetime + | +help: consider introducing lifetime `'y` here + | +39 | struct InvalidBounds<'y, 'a: 'y> + | +++ + +error[E0261]: use of undeclared lifetime name `'x` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:41:9 + | +41 | 'a: 'x, + | ^^ undeclared lifetime + | +help: consider introducing lifetime `'x` here + | +39 | struct InvalidBounds<'x, 'a: 'y> + | +++ diff --git a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.rs b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.rs new file mode 100644 index 00000000..05e030ad --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.rs @@ -0,0 +1,29 @@ +#![allow(warnings)] + +use pin_init::*; +use std::{convert::Infallible, pin::Pin, sync::Mutex}; + +#[pin_data] +struct SelfRef<'a> { + #[uses('_: invariant)] + part: Mutex<&'outer str>, + + // In this case, the type of this field is covariant over `'a`. However, the field lifetime + // `'outer` is invariant. Conceptually, a field's type must outlive the field lifetime, so if we + // allow `'a` to be covariant, we can have a shortened `SelfRef<'short_a>` where `'outer` + // outlives `'short_a`. That will be unsound. + // + // Therefore, we need to ensure that all invariant field lifetimes will cause the field types + // themselves to also be invariant. + outer: &'a String, +} + +// Must fail. +fn shorten<'long: 'short, 'short>( + x: &'long SelfRef<'long>, + short: &'short String, +) -> &'short SelfRef<'short> { + x +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.stderr b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.stderr new file mode 100644 index 00000000..416b7585 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.stderr @@ -0,0 +1,15 @@ +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_invariant_field_lt.rs:26:5 + | +22 | fn shorten<'long: 'short, 'short>( + | ----- ------ lifetime `'short` defined here + | | + | lifetime `'long` defined here +... +26 | x + | ^ function was supposed to return data with lifetime `'long` but it is returning data with lifetime `'short` + | + = help: consider adding the following bound: `'short: 'long` + = note: requirement occurs because of the type `SelfRef<'_>`, which makes the generic argument `'_` invariant + = note: the struct `SelfRef<'a>` is invariant over the parameter `'a` + = help: see for more information about variance diff --git a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs new file mode 100644 index 00000000..0b7d6fd6 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs @@ -0,0 +1,68 @@ +use std::fmt::Display; +use std::marker::PhantomData; +use std::sync::Mutex; + +use pin_init::*; + +struct PrintOnDrop(T); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct Foo<'a> { + // In this case, we will establish that `'a: 'early`, but `'early` is invariant, so we need to + // either make `'a` invariant (which is difficult because we cannot tell in proc macro), or reject + // such implied bounds. + marker: PhantomData<&'early &'later ()>, + #[uses('early: invariant)] + slot: Mutex>, + early: String, + later: &'a str, +} + +fn shorten<'long, 'short>(foo: &'short Foo<'long>) -> &'short Foo<'short> +where + 'long: 'short, +{ + foo +} + +// Rejected by split variance chain. Could technically be allowed. +// `&'early &'later ()` is well-formed only when `'later` outlives the invariant `'early`. +#[pin_data] +struct SplitVarianceChain { + link: &'early &'later (), + #[uses('early: invariant)] + slot: Mutex<&'early str>, + early: String, + later: String, +} + +// Rejected by split variance chain. Must not be allowed. +// `&'early &'a ()` is well-formed only when `'a` outlives the invariant `'early`. +#[pin_data] +struct SplitVarianceChainWithGeneric<'a> { + link: &'early &'a (), + #[uses('early: invariant)] + slot: Mutex<&'early str>, + early: String, +} + +fn main() { + stack_pin_init!(let foo = pin_init!(Foo { + early: "early".to_owned(), + later: "static", + slot: Mutex::new(PrintOnDrop("initial")), + marker: PhantomData, + })); + { + // let short = String::from("short"); + // shorten(&foo).with_project_ref(|proj| { + // *proj.slot.lock().unwrap() = PrintOnDrop(&short); + // }); + } +} diff --git a/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr new file mode 100644 index 00000000..1794be4b --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.stderr @@ -0,0 +1,66 @@ +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs:20:13 + | +15 | #[pin_data] + | ----------- in this attribute macro expansion +... +20 | marker: PhantomData<&'early &'later ()>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'later` must outlive `'early` +... +23 | early: String, + | ----- lifetime `'early` defined here +24 | later: &'a str, + | ----- lifetime `'later` defined here + | + = help: consider adding the following bound: `'later: 'early` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs:20:13 + | +15 | #[pin_data] + | ----------- in this attribute macro expansion +16 | struct Foo<'a> { + | -- lifetime `'a` defined here +... +20 | marker: PhantomData<&'early &'later ()>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'a` must outlive `'early` +... +23 | early: String, + | ----- lifetime `'early` defined here + | + = help: consider adding the following bound: `'a: 'early` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs:38:11 + | +36 | #[pin_data] + | ----------- in this attribute macro expansion +37 | struct SplitVarianceChain { +38 | link: &'early &'later (), + | ^^^^^^^^^^^^^^^^^^ requires that `'later` must outlive `'early` +... +41 | early: String, + | ----- lifetime `'early` defined here +42 | later: String, + | ----- lifetime `'later` defined here + | + = help: consider adding the following bound: `'later: 'early` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_invariant_field_lt_implied.rs:49:11 + | +47 | #[pin_data] + | ----------- in this attribute macro expansion +48 | struct SplitVarianceChainWithGeneric<'a> { + | -- lifetime `'a` defined here +49 | link: &'early &'a (), + | ^^^^^^^^^^^^^^ requires that `'a` must outlive `'early` +... +52 | early: String, + | ----- lifetime `'early` defined here + | + = help: consider adding the following bound: `'a: 'early` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs new file mode 100644 index 00000000..442cdf43 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs @@ -0,0 +1,38 @@ +// Ensure that types that have impl that specialize on a single lifetime can be used to exploit +// pin-init. + +use std::marker::PhantomData; + +use pin_init::*; + +struct LtSpec<'a>(PhantomData<*const &'a u32>); +struct LtSpec2<'a, 'b>(PhantomData<*const &'a &'b u32>); + +unsafe impl Send for LtSpec<'static> {} +unsafe impl Sync for LtSpec<'static> {} +unsafe impl<'a> Send for LtSpec2<'a, 'a> {} +unsafe impl<'a> Sync for LtSpec2<'a, 'a> {} + +#[pin_data] +struct Foo { + lt_spec: LtSpec<'a>, + a: u32, +} + +#[pin_data] +struct Bar { + lt_spec2: LtSpec2<'a, 'b>, + a: u32, + b: u32, +} + +fn assert_send() {} +fn assert_sync() {} + +fn main() { + // All of the below checks must fail. + assert_send::(); + assert_sync::(); + assert_send::(); + assert_sync::(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr new file mode 100644 index 00000000..886bd8d3 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr @@ -0,0 +1,51 @@ +error: higher-ranked lifetime error + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:34:5 + | +34 | assert_send::(); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: could not prove `Foo: Send` + +error: higher-ranked lifetime error + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:35:5 + | +35 | assert_sync::(); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: could not prove `Foo: Sync` + +error: lifetime bound not satisfied + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:36:5 + | +36 | assert_send::(); + | ^^^^^^^^^^^^^^^^^^^^ + | +note: the lifetime `'a` defined here... + --> src/__internal.rs + | + | for<'a> Erase<>::Output>: Send, + | ^^ +note: ...must outlive the lifetime `'a` defined here + --> src/__internal.rs + | + | for<'a> Erase<>::Output>: Send, + | ^^ + = note: this is a known limitation that will be removed in the future (see issue #100013 for more information) + +error: lifetime bound not satisfied + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:37:5 + | +37 | assert_sync::(); + | ^^^^^^^^^^^^^^^^^^^^ + | +note: the lifetime `'a` defined here... + --> src/__internal.rs + | + | for<'a> Erase<>::Output>: Sync, + | ^^ +note: ...must outlive the lifetime `'a` defined here + --> src/__internal.rs + | + | for<'a> Erase<>::Output>: Sync, + | ^^ + = note: this is a known limitation that will be removed in the future (see issue #100013 for more information) diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs new file mode 100644 index 00000000..4f788fff --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs @@ -0,0 +1,28 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + str: String, + mut_part: &'mut_str mut str, + #[borrowed(mut)] + mut_str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + mut_str: "hello world".to_owned(), + mut_part: &mut mut_str[..5], + })); + + // Should fail due to not accessible. + foo.as_mut().with_project(|proj| { + let _: &_ = proj.mut_str; + }) +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr new file mode 100644 index 00000000..4738074a --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr @@ -0,0 +1,14 @@ +error[E0308]: mismatched types + --> tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs:22:21 + | +22 | let _: &_ = proj.mut_str; + | -- ^^^^^^^^^^^^ expected `&_`, found `NotVisible<&String>` + | | + | expected due to this + | + = note: expected reference `&_` + found struct `pin_init::__internal::NotVisible<&String>` +help: consider borrowing here + | +22 | let _: &_ = &proj.mut_str; + | + diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs new file mode 100644 index 00000000..d2cfa04a --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs @@ -0,0 +1,21 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + mut_part: &'str mut str, + #[borrowed(mut)] + str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + mut_part: &mut str[..5], + })); +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr new file mode 100644 index 00000000..9d3748f9 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr @@ -0,0 +1,24 @@ +error[E0502]: cannot borrow value as mutable because it is also borrowed as immutable + --> tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs:15:24 + | +14 | part: &str[..5], + | ---- --- immutable borrow occurs here + | | + | immutable borrow later used here +15 | mut_part: &mut str[..5], + | ^^^ mutable borrow occurs here + +error[E0502]: cannot borrow value as mutable because it is also borrowed as immutable + --> tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs:15:24 + | +12 | stack_pin_init!(let foo = pin_init!(SelfRef { + | _______________________________- +13 | | str: "hello world".to_owned(), +14 | | part: &str[..5], + | | ---- --- immutable borrow occurs here + | | | + | | argument requires that immutable borrow lasts for `'1` +15 | | mut_part: &mut str[..5], + | | ^^^ mutable borrow occurs here +16 | | })); + | |______- has type `__PinDataLt<'1>` diff --git a/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs new file mode 100644 index 00000000..d2b87d6e --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs @@ -0,0 +1,16 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'foo str, + foo: String, +} + +fn self_ref(outer: &str) { + stack_pin_init!(let foo = pin_init!(SelfRef { + foo: "hello world".to_owned(), + part: &outer[..5], + })); +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr new file mode 100644 index 00000000..f3cf6f9f --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr @@ -0,0 +1,15 @@ +error[E0521]: borrowed data escapes outside of function + --> tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs:12:9 + | + 9 | fn self_ref(outer: &str) { + | ----- - let's call the lifetime of this reference `'1` + | | + | `outer` is only valid in the function body +... +12 | part: &outer[..5], + | ^^^^ + | | + | `outer` escapes the function body here + | argument requires that `'1` must outlive `'static` + | + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_project_mut.rs b/tests/ui/compile-fail/pin_data/selfref_project_mut.rs new file mode 100644 index 00000000..1df5df28 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_project_mut.rs @@ -0,0 +1,29 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + str: String, + mut_part: &'mut_str mut str, + #[borrowed(mut)] + mut_str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + mut_str: "hello world".to_owned(), + mut_part: &mut mut_str[..5], + })); + + // Should fail due to reference not being mutable. + *foo.as_mut().project().part = "foo"; + + // Should fail due to reference not being mutable. + foo.as_mut().project().mut_part.make_ascii_uppercase(); +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr b/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr new file mode 100644 index 00000000..57d88d84 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr @@ -0,0 +1,11 @@ +error[E0594]: cannot assign to data in a `&` reference + --> tests/ui/compile-fail/pin_data/selfref_project_mut.rs:21:5 + | +21 | *foo.as_mut().project().part = "foo"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign + +error[E0596]: cannot borrow data in a `&` reference as mutable + --> tests/ui/compile-fail/pin_data/selfref_project_mut.rs:24:5 + | +24 | foo.as_mut().project().mut_part.make_ascii_uppercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable diff --git a/tests/ui/compile-fail/pin_data/selfref_with_project.rs b/tests/ui/compile-fail/pin_data/selfref_with_project.rs new file mode 100644 index 00000000..f77690f4 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_with_project.rs @@ -0,0 +1,28 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + str: String, + mut_part: &'mut_str mut str, + #[borrowed(mut)] + mut_str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + mut_str: "hello world".to_owned(), + mut_part: &mut mut_str[..5], + })); + + let local = "hello world".to_owned(); + foo.as_mut().with_project(|proj| { + *proj.part = &local; + }); +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_with_project.stderr b/tests/ui/compile-fail/pin_data/selfref_with_project.stderr new file mode 100644 index 00000000..ee1ed52b --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_with_project.stderr @@ -0,0 +1,15 @@ +error[E0597]: `local` does not live long enough + --> tests/ui/compile-fail/pin_data/selfref_with_project.rs:22:23 + | +20 | let local = "hello world".to_owned(); + | ----- binding `local` declared here +21 | foo.as_mut().with_project(|proj| { + | ------ value captured here +22 | *proj.part = &local; + | --------------^^^^^ + | | | + | | borrowed value does not live long enough + | assignment requires that `local` is borrowed for `'static` +23 | }); +24 | } + | - `local` dropped here while still borrowed diff --git a/tests/ui/compile-fail/pin_data/selfref_wrong_owner.rs b/tests/ui/compile-fail/pin_data/selfref_wrong_owner.rs new file mode 100644 index 00000000..6ed3ddc1 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_wrong_owner.rs @@ -0,0 +1,27 @@ +use pin_init::*; +use std::fmt::Display; + +struct PrintOnDrop(T); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct MyStruct { + borrow: &'owner String, + owner: String, + print_on_drop: PrintOnDrop<&'later_owner String>, + later_owner: String, +} + +fn main() { + stack_pin_init!(let x = pin_init!(MyStruct { + owner: "hello world".to_owned(), + borrow: owner, + later_owner: "hello world".to_owned(), + print_on_drop: PrintOnDrop(owner), + })); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_wrong_owner.stderr b/tests/ui/compile-fail/pin_data/selfref_wrong_owner.stderr new file mode 100644 index 00000000..71185427 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_wrong_owner.stderr @@ -0,0 +1,20 @@ +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_wrong_owner.rs:25:9 + | +21 | stack_pin_init!(let x = pin_init!(MyStruct { + | _____________________________- +22 | | owner: "hello world".to_owned(), +23 | | borrow: owner, +24 | | later_owner: "hello world".to_owned(), +25 | | print_on_drop: PrintOnDrop(owner), + | | ^^^^^^^^^^^^^ argument requires that `'1` must outlive `'2` +26 | | })); + | | - + | | | + | |______has type `__PinDataLt<'1, '_>` + | has type `__PinDataLt<'_, '2>` + | + = note: requirement occurs because of the type `__PinDataLt<'_, '_>`, which makes the generic argument `'_` invariant + = note: the struct `__PinDataLt<'owner, 'later_owner>` is invariant over the parameter `'owner` + = help: see for more information about variance + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/twice.stderr b/tests/ui/compile-fail/pin_data/twice.stderr index 562177ea..9f538a56 100644 --- a/tests/ui/compile-fail/pin_data/twice.stderr +++ b/tests/ui/compile-fail/pin_data/twice.stderr @@ -27,3 +27,23 @@ error[E0592]: duplicate definitions with name `project` | ^^^^^^^^^^^ duplicate definitions for `project` | = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0592]: duplicate definitions with name `with_project` + --> tests/ui/compile-fail/pin_data/twice.rs:4:1 + | +3 | #[pin_data] + | ----------- other definition for `with_project` +4 | #[pin_data] + | ^^^^^^^^^^^ duplicate definitions for `with_project` + | + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0592]: duplicate definitions with name `with_project_ref` + --> tests/ui/compile-fail/pin_data/twice.rs:4:1 + | +3 | #[pin_data] + | ----------- other definition for `with_project_ref` +4 | #[pin_data] + | ^^^^^^^^^^^ duplicate definitions for `with_project_ref` + | + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index 5c0ef341..e7fb2634 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -31,6 +31,43 @@ const _: () = { _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, __this: ::core::marker::PhantomData<&'__this mut Foo<'a, 'b, T, SIZE>>, } + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt< + '__this, + 'a, + 'b: 'a, + T: Bar<'b> + ?Sized + 'a, + const SIZE: usize = 0, + > + where + T: Bar<'a, 1>, + { + array: &'__this mut [u8; 1024 * 1024], + r: &'__this mut &'b mut [&'a mut T; SIZE], + _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData< + &'__this mut Foo<'a, 'b, T, SIZE>, + >, + } + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionRef< + '__this, + 'a, + 'b: 'a, + T: Bar<'b> + ?Sized + 'a, + const SIZE: usize = 0, + > + where + T: Bar<'a, 1>, + { + array: &'__this [u8; 1024 * 1024], + r: &'__this &'b mut [&'a mut T; SIZE], + _pin: ::core::pin::Pin<&'__this PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__this Foo<'a, 'b, T, SIZE>>, + } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> Foo<'a, 'b, T, SIZE> where T: Bar<'a, 1>, @@ -55,49 +92,85 @@ const _: () = { __this: ::core::marker::PhantomData, } } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + /// - `r` + #[inline] + fn with_project<'__this, R>( + self: ::core::pin::Pin<&'__this mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__this, 'a, 'b, T, SIZE>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(__ProjectionLt { + array: &mut this.array, + r: &mut this.r, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } + /// Pin-projects all fields of `Self` from a shared reference with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + /// - `r` + #[inline] + fn with_project_ref<'__this, R>( + self: ::core::pin::Pin<&'__this Self>, + f: impl ::core::ops::FnOnce(__ProjectionRef<'__this, 'a, 'b, T, SIZE>) -> R, + ) -> R { + let this = ::core::pin::Pin::get_ref(self); + f(__ProjectionRef { + array: &this.array, + r: &this.r, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] - struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> + #[allow(non_snake_case)] + struct __PinDataLt<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> where T: Bar<'a, 1>, { - __phantom: ::pin_init::__internal::PhantomInvariant>, + array: ::pin_init::__internal::PhantomInvariant<[u8; 1024 * 1024]>, + r: ::pin_init::__internal::PhantomInvariant<&'b mut [&'a mut T; SIZE]>, + _pin: ::pin_init::__internal::PhantomInvariant, + __pin_phantom: ::core::marker::PhantomData>, } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::clone::Clone - for __ThePinData<'a, 'b, T, SIZE> + for __PinDataLt<'a, 'b, T, SIZE> where T: Bar<'a, 1>, { - #[inline] fn clone(&self) -> Self { *self } } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::marker::Copy - for __ThePinData<'a, 'b, T, SIZE> + for __PinDataLt<'a, 'b, T, SIZE> where T: Bar<'a, 1>, {} #[allow(dead_code)] + #[expect(clippy::missing_safety_doc)] impl< 'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize, - > __ThePinData<'a, 'b, T, SIZE> + > __PinDataLt<'a, 'b, T, SIZE> where T: Bar<'a, 1>, { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo<'a, 'b, T, SIZE>, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } /// # Safety /// /// - `slot` is valid and properly aligned. @@ -113,7 +186,7 @@ const _: () = { ::pin_init::__internal::Unpinned, [u8; 1024 * 1024], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array as _) } } /// # Safety /// @@ -130,7 +203,7 @@ const _: () = { ::pin_init::__internal::Unpinned, &'b mut [&'a mut T; SIZE], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).r) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).r as _) } } /// # Safety /// @@ -147,7 +220,54 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin as _) } + } + } + #[doc(hidden)] + struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> + where + T: Bar<'a, 1>, + { + __phantom: ::pin_init::__internal::PhantomInvariant>, + } + impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::clone::Clone + for __ThePinData<'a, 'b, T, SIZE> + where + T: Bar<'a, 1>, + { + #[inline] + fn clone(&self) -> Self { + *self + } + } + impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::marker::Copy + for __ThePinData<'a, 'b, T, SIZE> + where + T: Bar<'a, 1>, + {} + impl< + 'a, + 'b: 'a, + T: Bar<'b> + ?Sized + 'a, + const SIZE: usize, + > __ThePinData<'a, 'b, T, SIZE> + where + T: Bar<'a, 1>, + { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo<'a, 'b, T, SIZE>, + __PinDataLt<'a, 'b, T, SIZE>, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt<'a, 'b, T, SIZE> { + unsafe { ::core::mem::zeroed() } } } unsafe impl< diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index 3c8f88e3..b821bad7 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -13,6 +13,21 @@ const _: () = { _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, __this: ::core::marker::PhantomData<&'__this mut Foo>, } + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt<'__this> { + array: &'__this mut [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__this mut Foo>, + } + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionRef<'__this> { + array: &'__this [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__this PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__this Foo>, + } impl Foo { /// Pin-projects all fields of `Self`. /// @@ -32,30 +47,61 @@ const _: () = { __this: ::core::marker::PhantomData, } } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn with_project<'__this, R>( + self: ::core::pin::Pin<&'__this mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__this>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(__ProjectionLt { + array: &mut this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } + /// Pin-projects all fields of `Self` from a shared reference with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn with_project_ref<'__this, R>( + self: ::core::pin::Pin<&'__this Self>, + f: impl ::core::ops::FnOnce(__ProjectionRef<'__this>) -> R, + ) -> R { + let this = ::core::pin::Pin::get_ref(self); + f(__ProjectionRef { + array: &this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] - struct __ThePinData { - __phantom: ::pin_init::__internal::PhantomInvariant, + #[allow(non_snake_case)] + struct __PinDataLt { + array: ::pin_init::__internal::PhantomInvariant<[u8; 1024 * 1024]>, + _pin: ::pin_init::__internal::PhantomInvariant, + __pin_phantom: ::core::marker::PhantomData, } - impl ::core::clone::Clone for __ThePinData { - #[inline] + impl ::core::clone::Clone for __PinDataLt { fn clone(&self) -> Self { *self } } - impl ::core::marker::Copy for __ThePinData {} + impl ::core::marker::Copy for __PinDataLt {} #[allow(dead_code)] - impl __ThePinData { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } + #[expect(clippy::missing_safety_doc)] + impl __PinDataLt { /// # Safety /// /// - `slot` is valid and properly aligned. @@ -71,7 +117,7 @@ const _: () = { ::pin_init::__internal::Unpinned, [u8; 1024 * 1024], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array as _) } } /// # Safety /// @@ -88,7 +134,35 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin as _) } + } + } + #[doc(hidden)] + struct __ThePinData { + __phantom: ::pin_init::__internal::PhantomInvariant, + } + impl ::core::clone::Clone for __ThePinData { + #[inline] + fn clone(&self) -> Self { + *self + } + } + impl ::core::marker::Copy for __ThePinData {} + impl __ThePinData { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo, + __PinDataLt, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt { + unsafe { ::core::mem::zeroed() } } } unsafe impl ::pin_init::__internal::HasPinData for Foo { diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 7dfe06f4..c90ad117 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -13,6 +13,21 @@ const _: () = { _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, __this: ::core::marker::PhantomData<&'__this mut Foo>, } + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt<'__this> { + array: &'__this mut [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__this mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__this mut Foo>, + } + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionRef<'__this> { + array: &'__this [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__this PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__this Foo>, + } impl Foo { /// Pin-projects all fields of `Self`. /// @@ -32,30 +47,61 @@ const _: () = { __this: ::core::marker::PhantomData, } } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn with_project<'__this, R>( + self: ::core::pin::Pin<&'__this mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__this>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(__ProjectionLt { + array: &mut this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } + /// Pin-projects all fields of `Self` from a shared reference with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn with_project_ref<'__this, R>( + self: ::core::pin::Pin<&'__this Self>, + f: impl ::core::ops::FnOnce(__ProjectionRef<'__this>) -> R, + ) -> R { + let this = ::core::pin::Pin::get_ref(self); + f(__ProjectionRef { + array: &this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] - struct __ThePinData { - __phantom: ::pin_init::__internal::PhantomInvariant, + #[allow(non_snake_case)] + struct __PinDataLt { + array: ::pin_init::__internal::PhantomInvariant<[u8; 1024 * 1024]>, + _pin: ::pin_init::__internal::PhantomInvariant, + __pin_phantom: ::core::marker::PhantomData, } - impl ::core::clone::Clone for __ThePinData { - #[inline] + impl ::core::clone::Clone for __PinDataLt { fn clone(&self) -> Self { *self } } - impl ::core::marker::Copy for __ThePinData {} + impl ::core::marker::Copy for __PinDataLt {} #[allow(dead_code)] - impl __ThePinData { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } + #[expect(clippy::missing_safety_doc)] + impl __PinDataLt { /// # Safety /// /// - `slot` is valid and properly aligned. @@ -71,7 +117,7 @@ const _: () = { ::pin_init::__internal::Unpinned, [u8; 1024 * 1024], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array as _) } } /// # Safety /// @@ -88,7 +134,35 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin as _) } + } + } + #[doc(hidden)] + struct __ThePinData { + __phantom: ::pin_init::__internal::PhantomInvariant, + } + impl ::core::clone::Clone for __ThePinData { + #[inline] + fn clone(&self) -> Self { + *self + } + } + impl ::core::marker::Copy for __ThePinData {} + impl __ThePinData { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo, + __PinDataLt, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt { + unsafe { ::core::mem::zeroed() } } } unsafe impl ::pin_init::__internal::HasPinData for Foo { diff --git a/tests/ui/expand/simple-init.expanded.rs b/tests/ui/expand/simple-init.expanded.rs index fa621a24..c90efb08 100644 --- a/tests/ui/expand/simple-init.expanded.rs +++ b/tests/ui/expand/simple-init.expanded.rs @@ -10,7 +10,8 @@ fn main() { .__make_closure::< _, ::core::convert::Infallible, - >(move |slot| { + >(move |slot, data_lt| { + if true {} else {} #[allow(unreachable_code)] let _ = || unsafe { ::core::ptr::write(slot, Foo {}) }; Ok(unsafe { ::pin_init::__internal::InitOk::new() }) @@ -18,7 +19,7 @@ fn main() { let init = move | slot, | -> ::core::result::Result<(), ::core::convert::Infallible> { - init(slot).map(|__InitOk| ()) + init(slot, data.__with_lt()).map(|__InitOk| ()) }; unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } }; diff --git a/tests/ui/expand/tuple_struct.expanded.rs b/tests/ui/expand/tuple_struct.expanded.rs index 6c913f13..4ae147e6 100644 --- a/tests/ui/expand/tuple_struct.expanded.rs +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -11,6 +11,23 @@ const _: () = { &'__this mut usize, ::core::marker::PhantomData<&'__this mut Foo<'a, T, N>>, ); + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt<'__this, 'a, T: Copy, const N: usize>( + &'__this mut &'a mut [T; N], + ::core::pin::Pin<&'__this mut PhantomPinned>, + &'__this mut usize, + ::core::marker::PhantomData<&'__this mut Foo<'a, T, N>>, + ); + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionRef<'__this, 'a, T: Copy, const N: usize>( + &'__this &'a mut [T; N], + ::core::pin::Pin<&'__this PhantomPinned>, + &'__this usize, + ::core::marker::PhantomData<&'__this Foo<'a, T, N>>, + ); impl<'a, T: Copy, const N: usize> Foo<'a, T, N> { /// Pin-projects all fields of `Self`. /// @@ -32,30 +49,70 @@ const _: () = { ::core::marker::PhantomData, ) } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - index `1` + /// + /// These fields are **not** structurally pinned: + /// - index `0` + /// - index `2` + #[inline] + fn with_project<'__this, R>( + self: ::core::pin::Pin<&'__this mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__this, 'a, T, N>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f( + __ProjectionLt( + &mut this.0, + unsafe { ::core::pin::Pin::new_unchecked(&mut this.1) }, + &mut this.2, + ::core::marker::PhantomData, + ), + ) + } + /// Pin-projects all fields of `Self` from a shared reference with proper lifetime. + /// + /// These fields are structurally pinned: + /// - index `1` + /// + /// These fields are **not** structurally pinned: + /// - index `0` + /// - index `2` + #[inline] + fn with_project_ref<'__this, R>( + self: ::core::pin::Pin<&'__this Self>, + f: impl ::core::ops::FnOnce(__ProjectionRef<'__this, 'a, T, N>) -> R, + ) -> R { + let this = ::core::pin::Pin::get_ref(self); + f( + __ProjectionRef( + &this.0, + unsafe { ::core::pin::Pin::new_unchecked(&this.1) }, + &this.2, + ::core::marker::PhantomData, + ), + ) + } } #[doc(hidden)] - struct __ThePinData<'a, T: Copy, const N: usize> { - __phantom: ::pin_init::__internal::PhantomInvariant>, + #[allow(non_snake_case)] + struct __PinDataLt<'a, T: Copy, const N: usize> { + _0: ::pin_init::__internal::PhantomInvariant<&'a mut [T; N]>, + _1: ::pin_init::__internal::PhantomInvariant, + _2: ::pin_init::__internal::PhantomInvariant, + __pin_phantom: ::core::marker::PhantomData>, } - impl<'a, T: Copy, const N: usize> ::core::clone::Clone for __ThePinData<'a, T, N> { - #[inline] + impl<'a, T: Copy, const N: usize> ::core::clone::Clone for __PinDataLt<'a, T, N> { fn clone(&self) -> Self { *self } } - impl<'a, T: Copy, const N: usize> ::core::marker::Copy for __ThePinData<'a, T, N> {} + impl<'a, T: Copy, const N: usize> ::core::marker::Copy for __PinDataLt<'a, T, N> {} #[allow(dead_code)] - impl<'a, T: Copy, const N: usize> __ThePinData<'a, T, N> { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo<'a, T, N>, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } + #[expect(clippy::missing_safety_doc)] + impl<'a, T: Copy, const N: usize> __PinDataLt<'a, T, N> { /// # Safety /// /// - `slot` is valid and properly aligned. @@ -71,7 +128,7 @@ const _: () = { ::pin_init::__internal::Unpinned, &'a mut [T; N], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).0) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).0 as _) } } /// # Safety /// @@ -88,7 +145,7 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).1) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).1 as _) } } /// # Safety /// @@ -102,7 +159,35 @@ const _: () = { self, slot: *mut Foo<'a, T, N>, ) -> ::pin_init::__internal::Slot<::pin_init::__internal::Unpinned, usize> { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).2) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).2 as _) } + } + } + #[doc(hidden)] + struct __ThePinData<'a, T: Copy, const N: usize> { + __phantom: ::pin_init::__internal::PhantomInvariant>, + } + impl<'a, T: Copy, const N: usize> ::core::clone::Clone for __ThePinData<'a, T, N> { + #[inline] + fn clone(&self) -> Self { + *self + } + } + impl<'a, T: Copy, const N: usize> ::core::marker::Copy for __ThePinData<'a, T, N> {} + impl<'a, T: Copy, const N: usize> __ThePinData<'a, T, N> { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo<'a, T, N>, + __PinDataLt<'a, T, N>, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt<'a, T, N> { + unsafe { ::core::mem::zeroed() } } } unsafe impl<'a, T: Copy, const N: usize> ::pin_init::__internal::HasPinData @@ -149,31 +234,58 @@ fn main() { .__make_closure::< _, ::core::convert::Infallible, - >(move |slot| { - let mut ___0_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).0) - }) - .write(&mut first); - let mut ___1_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).1) - }) - .write(PhantomPinned); - let mut ___2_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).2) - }) - .init(10)?; - ::core::mem::forget(___2_guard); - ::core::mem::forget(___1_guard); - ::core::mem::forget(___0_guard); + >(move |slot, data_lt| { + if true { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut first); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .init(10)?; + ::core::mem::forget(___2_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___0_guard); + } else { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut first); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .init(10)?; + ::core::mem::forget(___2_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___0_guard); + } #[allow(unreachable_code)] let _ = || unsafe { let _ = &(*slot).0; @@ -193,7 +305,7 @@ fn main() { let init = move | slot, | -> ::core::result::Result<(), ::core::convert::Infallible> { - init(slot).map(|__InitOk| ()) + init(slot, data.__with_lt()).map(|__InitOk| ()) }; unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } }; @@ -207,31 +319,58 @@ fn main() { .__make_closure::< _, ::core::convert::Infallible, - >(move |slot| { - let mut ___0_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).0) - }) - .write(&mut second); - let mut ___1_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).1) - }) - .write(PhantomPinned); - let mut ___2_guard = (unsafe { - ::pin_init::__internal::Slot::< - ::pin_init::__internal::Unpinned, - _, - >::new(&raw mut (*slot).2) - }) - .write(20); - ::core::mem::forget(___2_guard); - ::core::mem::forget(___1_guard); - ::core::mem::forget(___0_guard); + >(move |slot, data_lt| { + if true { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut second); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .write(20); + ::core::mem::forget(___2_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___0_guard); + } else { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut second); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .write(20); + ::core::mem::forget(___2_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___0_guard); + } #[allow(unreachable_code)] let _ = || unsafe { let _ = &(*slot).0; @@ -251,7 +390,7 @@ fn main() { let init = move | slot, | -> ::core::result::Result<(), ::core::convert::Infallible> { - init(slot).map(|__InitOk| ()) + init(slot, data.__with_lt()).map(|__InitOk| ()) }; unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } };