From e9b24a487bfb287346425e309b8f18dfb2653fde Mon Sep 17 00:00:00 2001 From: tangtingting Date: Thu, 26 Jun 2025 13:36:00 +0800 Subject: [PATCH] New upstream version 0.18.5 --- .cargo_vcs_info.json | 2 +- Cargo.toml | 27 ++++-- Cargo.toml.orig | 12 +-- LICENSE_APACHE | 2 +- LICENSE_MIT | 2 +- src/covariance_detection.rs | 16 +--- src/generate/constructor.rs | 23 +++-- src/generate/derives.rs | 7 +- src/generate/drop.rs | 22 +++++ src/generate/into_heads.rs | 14 ++- src/generate/mod.rs | 4 +- src/generate/struc.rs | 32 ++++++- src/generate/summon_checker.rs | 4 +- src/generate/try_constructor.rs | 25 +++-- src/generate/{with_all.rs => with.rs} | 51 ++-------- src/generate/with_each.rs | 30 ++++-- src/generate/with_mut.rs | 128 ++++++++++++++++++++++++++ src/info_structures.rs | 30 ++++-- src/lib.rs | 41 ++++++--- src/parse.rs | 79 ++++++++-------- src/utils.rs | 26 ++++-- 21 files changed, 395 insertions(+), 182 deletions(-) create mode 100644 src/generate/drop.rs rename src/generate/{with_all.rs => with.rs} (63%) create mode 100644 src/generate/with_mut.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 72507c4..a78795e 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "8940b2551e7e2269f595c669fe532c3ad3790194" + "sha1": "a8cd334fbb25e6559ed5343e7826d283d0ac001c" }, "path_in_vcs": "ouroboros_macro" } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6005099..cc945d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,31 +12,38 @@ [package] edition = "2018" name = "ouroboros_macro" -version = "0.15.6" -authors = ["Joshua Maros "] +version = "0.18.5" +authors = ["Josh "] +build = false +autobins = false +autoexamples = false +autotests = false +autobenches = false description = "Proc macro for ouroboros crate." documentation = "https://docs.rs/ouroboros_macro" +readme = false license = "MIT OR Apache-2.0" -repository = "https://github.com/joshua-maros/ouroboros" +repository = "https://github.com/someguynamedjosh/ouroboros" [lib] +name = "ouroboros_macro" +path = "src/lib.rs" proc-macro = true -[dependencies.Inflector] -version = "0.11" -default-features = false - -[dependencies.proc-macro-error] -version = "1.0.4" +[dependencies.heck] +version = "0.4.1" [dependencies.proc-macro2] version = "1.0" +[dependencies.proc-macro2-diagnostics] +version = "0.10" + [dependencies.quote] version = "1.0" [dependencies.syn] -version = "1.0" +version = "2.0" features = ["full"] [features] diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 11acd77..5deeb16 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,22 +1,22 @@ [package] name = "ouroboros_macro" -version = "0.15.6" -authors = ["Joshua Maros "] +version = "0.18.5" +authors = ["Josh "] edition = "2018" license = "MIT OR Apache-2.0" description = "Proc macro for ouroboros crate." documentation = "https://docs.rs/ouroboros_macro" -repository = "https://github.com/joshua-maros/ouroboros" +repository = "https://github.com/someguynamedjosh/ouroboros" [lib] proc-macro = true [dependencies] -Inflector = { version = "0.11", default-features = false } +heck = "0.4.1" proc-macro2 = "1.0" -proc-macro-error = "1.0.4" +proc-macro2-diagnostics = "0.10" quote = "1.0" -syn = { version = "1.0", features = ["full"] } +syn = { version = "2.0", features = ["full"] } [features] std = [] diff --git a/LICENSE_APACHE b/LICENSE_APACHE index 574cb18..eb8c962 100644 --- a/LICENSE_APACHE +++ b/LICENSE_APACHE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2021 Joshua Maros + Copyright 2021 Josh Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/LICENSE_MIT b/LICENSE_MIT index 9ed5b2a..5b972fd 100644 --- a/LICENSE_MIT +++ b/LICENSE_MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Joshua Maros +Copyright (c) 2021 Josh Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/covariance_detection.rs b/src/covariance_detection.rs index 2f50983..1dbd293 100644 --- a/src/covariance_detection.rs +++ b/src/covariance_detection.rs @@ -13,11 +13,7 @@ pub fn apparent_std_container_type(raw_type: &Type) -> Option<(&'static str, &Ty } else { return None; }; - let segment = if let Some(segment) = tpath.path.segments.last() { - segment - } else { - return None; - }; + let segment = tpath.path.segments.last()?; let args = if let PathArguments::AngleBracketed(args) = &segment.arguments { args } else { @@ -49,7 +45,7 @@ pub fn type_is_covariant_over_this_lifetime(ty: &syn::Type) -> Option { return Some(true); } match ty { - Array(arr) => type_is_covariant_over_this_lifetime(&*arr.elem), + Array(arr) => type_is_covariant_over_this_lifetime(&arr.elem), BareFn(f) => { debug_assert!(uses_this_lifetime(f.to_token_stream())); None @@ -80,13 +76,11 @@ pub fn type_is_covariant_over_this_lifetime(ty: &syn::Type) -> Option { if !type_is_covariant_over_this_lifetime(ty)? { return Some(false); } - } else { - if uses_this_lifetime(ty.to_token_stream()) { - return None; - } + } else if uses_this_lifetime(ty.to_token_stream()) { + return None; } } else if let syn::GenericArgument::Lifetime(lt) = arg { - if lt.ident.to_string() == "this" && !all_parameters_are_covariant { + if lt.ident == "this" && !all_parameters_are_covariant { return None; } } diff --git a/src/generate/constructor.rs b/src/generate/constructor.rs index 6ab47bc..994412d 100644 --- a/src/generate/constructor.rs +++ b/src/generate/constructor.rs @@ -45,7 +45,7 @@ pub fn create_builder_and_constructor( .to_owned(); let build_fn_documentation = format!( concat!( - "Calls [`{0}::new()`]({0}::new) using the provided values. This is preferrable over ", + "Calls [`{0}::new()`]({0}::new) using the provided values. This is preferable over ", "calling `new()` directly for the reasons listed above. " ), info.ident.to_string() @@ -67,7 +67,7 @@ pub fn create_builder_and_constructor( for field in &info.fields { let field_name = &field.name; - let arg_type = field.make_constructor_arg_type(&info, builder_type)?; + let arg_type = field.make_constructor_arg_type(info, builder_type)?; if let ArgType::Plain(plain_type) = arg_type { // No fancy builder function, we can just move the value directly into the struct. params.push(quote! { #field_name: #plain_type }); @@ -75,17 +75,17 @@ pub fn create_builder_and_constructor( builder_struct_field_names.push(quote! { #field_name }); doc_table += &format!( "| `{}` | Directly pass in the value this field should contain |\n", - field_name.to_string() + field_name ); } else if let ArgType::TraitBound(bound_type) = arg_type { // Trait bounds are much trickier. We need a special syntax to accept them in the - // contructor, and generic parameters need to be added to the builder struct to make + // constructor, and generic parameters need to be added to the builder struct to make // it work. let builder_name = field.builder_name(); params.push(quote! { #builder_name : impl #bound_type }); doc_table += &format!( "| `{}` | Use a function or closure: `(", - builder_name.to_string() + builder_name ); let mut builder_args = Vec::new(); for (index, borrow) in field.borrows.iter().enumerate() { @@ -93,14 +93,14 @@ pub fn create_builder_and_constructor( builder_args.push(format_ident!("{}_illegal_static_reference", borrowed_name)); doc_table += &format!( "{}: &{}_", - borrowed_name.to_string(), + borrowed_name, if borrow.mutable { "mut " } else { "" }, ); if index < field.borrows.len() - 1 { doc_table += ", "; } } - doc_table += &format!(") -> {}: _` | \n", field_name.to_string()); + doc_table += &format!(") -> {}: _` | \n", field_name); if builder_type.is_async() { code.push(quote! { let #field_name = #builder_name (#(#builder_args),*).await; }); } else { @@ -154,12 +154,17 @@ pub fn create_builder_and_constructor( BuilderType::Sync => quote! { fn new }, }; let field_names: Vec<_> = info.fields.iter().map(|field| field.name.clone()).collect(); + let internal_ident = &info.internal_ident; let constructor_def = quote! { #documentation #vis #constructor_fn(#(#params),*) -> #struct_name <#(#generic_args),*> { #(#code)* - Self { - #(#field_names),* + unsafe { + Self { + actual_data: ::core::mem::MaybeUninit::new(#internal_ident { + #(#field_names),* + }) + } } } }; diff --git a/src/generate/derives.rs b/src/generate/derives.rs index 0233a3f..55b229d 100644 --- a/src/generate/derives.rs +++ b/src/generate/derives.rs @@ -5,10 +5,11 @@ use syn::{Error, GenericParam, TypeParamBound}; fn add_trait_bound(param: &GenericParam, bound: &TypeParamBound) -> GenericParam { let mut new = param.clone(); - match &mut new { - GenericParam::Type(t) => t.bounds.push(bound.clone()), - _ => (), + + if let GenericParam::Type(t) = &mut new { + t.bounds.push(bound.clone()) } + new } diff --git a/src/generate/drop.rs b/src/generate/drop.rs new file mode 100644 index 0000000..703f6e2 --- /dev/null +++ b/src/generate/drop.rs @@ -0,0 +1,22 @@ +use crate::info_structures::StructInfo; +use proc_macro2::TokenStream; +use quote::quote; +use syn::Error; + +pub fn create_drop_impl(info: &StructInfo) -> Result { + let ident = &info.ident; + let generics = &info.generics; + let generic_args = info.generic_arguments(); + + let mut where_clause = quote! {}; + if let Some(clause) = &generics.where_clause { + where_clause = quote! { #clause }; + } + Ok(quote! { + impl #generics ::core::ops::Drop for #ident<#(#generic_args,)*> #where_clause { + fn drop(&mut self) { + unsafe { self.actual_data.assume_init_drop() }; + } + } + }) +} diff --git a/src/generate/into_heads.rs b/src/generate/into_heads.rs index 5784e46..47ad372 100644 --- a/src/generate/into_heads.rs +++ b/src/generate/into_heads.rs @@ -13,12 +13,16 @@ pub fn make_into_heads(info: &StructInfo, options: Options) -> (TokenStream, Tok let mut code = Vec::new(); let mut field_initializers = Vec::new(); let mut head_fields = Vec::new(); + let internal_struct = &info.internal_ident; // Drop everything in the reverse order of what it was declared in. Fields that come later // are only dependent on fields that came before them. for field in info.fields.iter().rev() { let field_name = &field.name; - if !field.self_referencing { - code.push(quote! { let #field_name = self.#field_name; }); + if field.self_referencing { + // Heads are fields that do not borrow anything. + code.push(quote! { ::core::mem::drop(this.#field_name); }); + } else { + code.push(quote! { let #field_name = this.#field_name; }); if field.is_borrowed() { field_initializers .push(quote! { #field_name: ::ouroboros::macro_help::unbox(#field_name) }); @@ -27,9 +31,6 @@ pub fn make_into_heads(info: &StructInfo, options: Options) -> (TokenStream, Tok } let field_type = &field.typ; head_fields.push(quote! { #visibility #field_name: #field_type }); - } else { - // Heads are fields that do not borrow anything. - code.push(quote! { ::core::mem::drop(self.#field_name); }); } } for (ty, ident) in info.generic_consumers() { @@ -71,6 +72,9 @@ pub fn make_into_heads(info: &StructInfo, options: Options) -> (TokenStream, Tok #[allow(clippy::drop_copy)] #[allow(clippy::drop_non_drop)] #visibility fn into_heads(self) -> Heads<#(#generic_args),*> { + let this_ptr = &self as *const _; + let this: #internal_struct<#(#generic_args),*> = unsafe { ::core::mem::transmute_copy(&*this_ptr) }; + ::core::mem::forget(self); #(#code)* Heads { #(#field_initializers),* diff --git a/src/generate/mod.rs b/src/generate/mod.rs index 0b229d1..5658036 100644 --- a/src/generate/mod.rs +++ b/src/generate/mod.rs @@ -1,9 +1,11 @@ pub mod constructor; pub mod derives; +pub mod drop; pub mod into_heads; pub mod struc; pub mod summon_checker; pub mod try_constructor; pub mod type_asserts; -pub mod with_all; +pub mod with; pub mod with_each; +pub mod with_mut; diff --git a/src/generate/struc.rs b/src/generate/struc.rs index 9b2ff51..b13f6d7 100644 --- a/src/generate/struc.rs +++ b/src/generate/struc.rs @@ -6,12 +6,34 @@ use proc_macro2::TokenStream; use quote::quote; use syn::Error; -/// Creates the struct that will actually store the data. This involves properly organizing the -/// fields, collecting metadata about them, reversing the order everything is stored in, and -/// converting any uses of 'this to 'static. +/// Creates the struct that will actually store the data. pub fn create_actual_struct_def(info: &StructInfo) -> Result { - let vis = utils::submodule_contents_visiblity(&info.vis); + let visibility = utils::submodule_contents_visibility(&info.vis); + let mut fields = Vec::new(); + for (ty, ident) in info.generic_consumers() { + fields.push(quote! { #ident: ::core::marker::PhantomData<#ty> }); + } + let generic_params = info.generic_params(); + let generic_args = info.generic_arguments(); + let generic_where = &info.generics.where_clause; let ident = &info.ident; + let internal_ident = &info.internal_ident; + Ok(quote! { + #[repr(transparent)] + #visibility struct #ident <#generic_params> #generic_where { + actual_data: ::core::mem::MaybeUninit<#internal_ident<#(#generic_args),*>>, + } + }) +} + +/// Creates a struct with fields like the original struct. Instances of the +/// "actual" struct are reinterpreted as instances of the "internal" struct +/// whenever data needs to be accessed. (This gets around the problem that +/// references passed to functions must be valid through the entire function, +/// but references *created* inside a function can be considered invalid +/// whenever, even during the duration of the function.) +pub fn create_internal_struct_def(info: &StructInfo) -> Result { + let ident = &info.internal_ident; let generics = &info.generics; let field_defs: Vec<_> = info @@ -37,7 +59,7 @@ pub fn create_actual_struct_def(info: &StructInfo) -> Result where_clause = quote! { #clause }; } let def = quote! { - #vis struct #ident #generics #where_clause { + struct #ident #generics #where_clause { #(#field_defs),* } }; diff --git a/src/generate/summon_checker.rs b/src/generate/summon_checker.rs index 743c199..d96386d 100644 --- a/src/generate/summon_checker.rs +++ b/src/generate/summon_checker.rs @@ -12,13 +12,13 @@ pub fn generate_checker_summoner(info: &StructInfo) -> Result Result<{}: _, Error_>` | \n", field_name.to_string()); + doc_table += &format!(") -> Result<{}: _, Error_>` | \n", field_name); let builder_value = if builder_type.is_async() { quote! { #builder_name (#(#builder_args),*).await } } else { @@ -223,6 +223,7 @@ pub fn create_try_builder_and_constructor( quote! { #struct_name::#or_recover_ident(#(#builder_struct_field_names),*).map_err(|(error, _heads)| error) } }; let field_names: Vec<_> = info.fields.iter().map(|field| field.name.clone()).collect(); + let internal_ident = &info.internal_ident; let constructor_def = quote! { #documentation #visibility #constructor_fn(#(#params),*) -> ::core::result::Result<#struct_name <#(#generic_args),*>, Error_> { @@ -231,7 +232,13 @@ pub fn create_try_builder_and_constructor( #or_recover_documentation #visibility #or_recover_constructor_fn(#(#params),*) -> ::core::result::Result<#struct_name <#(#generic_args),*>, (Error_, Heads<#(#generic_args),*>)> { #(#or_recover_code)* - ::core::result::Result::Ok(Self { #(#field_names),* }) + ::core::result::Result::Ok(unsafe { + Self { + actual_data: ::core::mem::MaybeUninit::new(#internal_ident { + #(#field_names),* + }) + } + }) } }; builder_struct_generic_producers.push(quote! { Error_ }); diff --git a/src/generate/with_all.rs b/src/generate/with.rs similarity index 63% rename from src/generate/with_all.rs rename to src/generate/with.rs index e6a2665..3e21dc0 100644 --- a/src/generate/with_all.rs +++ b/src/generate/with.rs @@ -14,27 +14,21 @@ pub fn make_with_all_function( }; let mut fields = Vec::new(); let mut field_assignments = Vec::new(); - let mut mut_fields = Vec::new(); - let mut mut_field_assignments = Vec::new(); // I don't think the reverse is necessary but it does make the expanded code more uniform. for field in info.fields.iter().rev() { let field_name = &field.name; let field_type = &field.typ; if field.field_type == FieldType::Tail { fields.push(quote! { #visibility #field_name: &'outer_borrow #field_type }); - field_assignments.push(quote! { #field_name: &self.#field_name }); - mut_fields.push(quote! { #visibility #field_name: &'outer_borrow mut #field_type }); - mut_field_assignments.push(quote! { #field_name: &mut self.#field_name }); + field_assignments.push(quote! { #field_name: &this.#field_name }); } else if field.field_type == FieldType::Borrowed { let ass = quote! { #field_name: unsafe { ::ouroboros::macro_help::change_lifetime( - &*self.#field_name + &*this.#field_name ) } }; fields.push(quote! { #visibility #field_name: &'this #field_type }); field_assignments.push(ass.clone()); - mut_fields.push(quote! { #visibility #field_name: &'this #field_type }); - mut_field_assignments.push(ass); } else if field.field_type == FieldType::BorrowedMut { // Add nothing because we cannot borrow something that has already been mutably // borrowed. @@ -43,9 +37,7 @@ pub fn make_with_all_function( for (ty, ident) in info.generic_consumers() { fields.push(quote! { #ident: ::core::marker::PhantomData<#ty> }); - mut_fields.push(quote! { #ident: ::core::marker::PhantomData<#ty> }); field_assignments.push(quote! { #ident: ::core::marker::PhantomData }); - mut_field_assignments.push(quote! { #ident: ::core::marker::PhantomData }); } let new_generic_params = info.borrowed_generic_params(); let new_generic_args = info.borrowed_generic_arguments(); @@ -58,42 +50,31 @@ pub fn make_with_all_function( ), info.ident.to_string() ); - let mut_struct_documentation = format!( - concat!( - "A struct for holding mutable references to all ", - "[tail fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) in an instance of ", - "[`{0}`]({0})." - ), - info.ident.to_string() - ); let ltname = format!("'{}", info.fake_lifetime()); let lifetime = Lifetime::new(<name, Span::call_site()); let generic_where = if let Some(clause) = &info.generics.where_clause { let mut clause = clause.clone(); let extra: WhereClause = syn::parse_quote! { where #lifetime: 'this }; + clause + .predicates + .push(extra.predicates.first().unwrap().clone()); + let extra: WhereClause = syn::parse_quote! { where 'this: 'outer_borrow }; clause .predicates .push(extra.predicates.first().unwrap().clone()); clause } else { - syn::parse_quote! { where #lifetime: 'this } + syn::parse_quote! { where #lifetime: 'this, 'this: 'outer_borrow } }; let struct_defs = quote! { #[doc=#struct_documentation] #visibility struct BorrowedFields #new_generic_params #generic_where { #(#fields),* } - #[doc=#mut_struct_documentation] - #visibility struct BorrowedMutFields #new_generic_params #generic_where { #(#mut_fields),* } }; let borrowed_fields_type = quote! { BorrowedFields<#(#new_generic_args),*> }; - let borrowed_mut_fields_type = quote! { BorrowedMutFields<#(#new_generic_args),*> }; let documentation = concat!( "This method provides immutable references to all ", "[tail and immutably borrowed fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions).", ); - let mut_documentation = concat!( - "This method provides mutable references to all ", - "[tail fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions).", - ); let documentation = if !options.do_no_doc { quote! { #[doc=#documentation] @@ -101,13 +82,6 @@ pub fn make_with_all_function( } else { quote! { #[doc(hidden)] } }; - let mut_documentation = if !options.do_no_doc { - quote! { - #[doc=#mut_documentation] - } - } else { - quote! { #[doc(hidden)] } - }; let fn_defs = quote! { #documentation #[inline(always)] @@ -115,20 +89,11 @@ pub fn make_with_all_function( &'outer_borrow self, user: impl for<'this> ::core::ops::FnOnce(#borrowed_fields_type) -> ReturnType ) -> ReturnType { + let this = unsafe { self.actual_data.assume_init_ref() }; user(BorrowedFields { #(#field_assignments),* }) } - #mut_documentation - #[inline(always)] - #visibility fn with_mut <'outer_borrow, ReturnType>( - &'outer_borrow mut self, - user: impl for<'this> ::core::ops::FnOnce(#borrowed_mut_fields_type) -> ReturnType - ) -> ReturnType { - user(BorrowedMutFields { - #(#mut_field_assignments),* - }) - } }; Ok((struct_defs, fn_defs)) } diff --git a/src/generate/with_each.rs b/src/generate/with_each.rs index 8985857..38a663a 100644 --- a/src/generate/with_each.rs +++ b/src/generate/with_each.rs @@ -1,10 +1,17 @@ use crate::info_structures::{FieldType, Options, StructInfo}; use proc_macro2::TokenStream; +use proc_macro2_diagnostics::Diagnostic; use quote::{format_ident, quote}; use syn::Error; -pub fn make_with_functions(info: &StructInfo, options: Options) -> Result, Error> { +pub enum ProcessingError { + Syntax(Error), + Covariance(Vec), +} + +pub fn make_with_functions(info: &StructInfo, options: Options) -> (Vec, Vec) { let mut users = Vec::new(); + let mut errors = Vec::new(); for field in &info.fields { let visibility = &field.vis; let field_name = &field.name; @@ -34,7 +41,8 @@ pub fn make_with_functions(info: &StructInfo, options: Options) -> Result ::core::ops::FnOnce(&'outer_borrow #field_type) -> ReturnType, ) -> ReturnType { - user(&self. #field_name) + let field = &unsafe { self.actual_data.assume_init_ref() }.#field_name; + user(field) } }); if field.covariant == Some(true) { @@ -45,11 +53,11 @@ pub fn make_with_functions(info: &StructInfo, options: Options) -> Result( &'this self, ) -> &'this #field_type { - &self.#field_name + &unsafe { self.actual_data.assume_init_ref() }.#field_name } }); } else if field.covariant.is_none() { - field.covariance_error(); + errors.push(field.covariance_error()); } // If it is not borrowed at all it's safe to allow mutably borrowing it. let user_name = format_ident!("with_{}_mut", &field.name); @@ -76,7 +84,8 @@ pub fn make_with_functions(info: &StructInfo, options: Options) -> Result ::core::ops::FnOnce(&'outer_borrow mut #field_type) -> ReturnType, ) -> ReturnType { - user(&mut self. #field_name) + let field = &mut unsafe { self.actual_data.assume_init_mut() }.#field_name; + user(field) } }); } else if field.field_type == FieldType::Borrowed { @@ -102,7 +111,8 @@ pub fn make_with_functions(info: &StructInfo, options: Options) -> Result ::core::ops::FnOnce(&'outer_borrow #field_type) -> ReturnType, ) -> ReturnType { - user(&*self.#field_name) + let field = &unsafe { self.actual_data.assume_init_ref() }.#field_name; + user(field) } }); if field.self_referencing { @@ -110,7 +120,7 @@ pub fn make_with_functions(info: &StructInfo, options: Options) -> Result Result( &'this self, ) -> &'this #field_type { - &*self.#field_name + &unsafe { self.actual_data.assume_init_ref() }.#field_name } }); } else if field.field_type == FieldType::BorrowedMut { - // Do not generate anything becaue if it is borrowed mutably once, we should not be able + // Do not generate anything because if it is borrowed mutably once, we should not be able // to get any other kinds of references to it. } } - Ok(users) + (users, errors) } diff --git a/src/generate/with_mut.rs b/src/generate/with_mut.rs new file mode 100644 index 0000000..7b2c582 --- /dev/null +++ b/src/generate/with_mut.rs @@ -0,0 +1,128 @@ +use crate::{ + info_structures::{FieldType, Options, StructInfo}, + utils::{replace_this_with_lifetime, uses_this_lifetime}, +}; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use syn::{Error, Lifetime, WhereClause}; + +pub fn make_with_all_mut_function( + info: &StructInfo, + options: Options, +) -> Result<(TokenStream, TokenStream), Error> { + let visibility = if options.do_pub_extras { + info.vis.clone() + } else { + syn::parse_quote! { pub(super) } + }; + let mut mut_fields = Vec::new(); + let mut mut_field_assignments = Vec::new(); + let mut lifetime_idents = Vec::new(); + // I don't think the reverse is necessary but it does make the expanded code more uniform. + for (index, field) in info.fields.iter().rev().enumerate() { + let field_name = &field.name; + let original_field_type = &field.typ; + let lifetime = format_ident!("this{}", index); + let field_type = replace_this_with_lifetime(quote! { #original_field_type }, lifetime.clone()); + if field.field_type == FieldType::Tail { + mut_fields.push(quote! { #visibility #field_name: &'outer_borrow mut #field_type }); + mut_field_assignments.push(quote! { #field_name: &mut this.#field_name }); + if uses_this_lifetime(quote! { #original_field_type }) { + lifetime_idents.push(lifetime.clone()); + } + } else if field.field_type == FieldType::Borrowed { + let ass = quote! { #field_name: unsafe { + ::ouroboros::macro_help::change_lifetime( + &*this.#field_name + ) + } }; + let lt = Lifetime::new(&format!("'{}", lifetime), Span::call_site()); + mut_fields.push(quote! { #visibility #field_name: &#lt #field_type }); + mut_field_assignments.push(ass); + lifetime_idents.push(lifetime.clone()); + } else if field.field_type == FieldType::BorrowedMut { + // Add nothing because we cannot borrow something that has already been mutably + // borrowed. + } + } + + for (ty, ident) in info.generic_consumers() { + mut_fields.push(quote! { #ident: ::core::marker::PhantomData<#ty> }); + mut_field_assignments.push(quote! { #ident: ::core::marker::PhantomData }); + } + + let mut new_generic_params = info.generic_params().clone(); + for lt in &lifetime_idents { + let lt = Lifetime::new(&format!("'{}", lt), Span::call_site()); + new_generic_params.insert(0, syn::parse_quote! { #lt }); + } + new_generic_params.insert(0, syn::parse_quote! { 'outer_borrow }); + let mut new_generic_args = info.generic_arguments(); + let mut lifetimes = Vec::new(); + for lt in &lifetime_idents { + let lt = Lifetime::new(&format!("'{}", lt), Span::call_site()); + lifetimes.push(lt.clone()); + new_generic_args.insert(0, quote! { #lt }); + } + new_generic_args.insert(0, quote! { 'outer_borrow }); + + let mut_struct_documentation = format!( + concat!( + "A struct for holding mutable references to all ", + "[tail fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) in an instance of ", + "[`{0}`]({0})." + ), + info.ident.to_string() + ); + let fake_lifetime = Lifetime::new(&format!("'{}", info.fake_lifetime()), Span::call_site()); + let mut generic_where = if let Some(clause) = &info.generics.where_clause { + clause.clone() + } else { + syn::parse_quote! { where } + }; + for lt in &lifetime_idents { + let lt = Lifetime::new(&format!("'{}", lt), Span::call_site()); + let extra: WhereClause = syn::parse_quote! { where #fake_lifetime: #lt }; + generic_where + .predicates + .extend(extra.predicates.into_iter()); + } + for idents in lifetime_idents.windows(2) { + let lt = Lifetime::new(&format!("'{}", idents[1]), Span::call_site()); + let outlives = Lifetime::new(&format!("'{}", idents[0]), Span::call_site()); + let extra: WhereClause = syn::parse_quote! { where #lt: #outlives }; + generic_where + .predicates + .extend(extra.predicates.into_iter()); + } + let struct_defs = quote! { + #[doc=#mut_struct_documentation] + #visibility struct BorrowedMutFields <#new_generic_params> #generic_where { #(#mut_fields),* } + }; + let borrowed_mut_fields_type = quote! { BorrowedMutFields<#(#new_generic_args),*> }; + let mut_documentation = concat!( + "This method provides mutable references to all ", + "[tail fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions).", + ); + let mut_documentation = if !options.do_no_doc { + quote! { + #[doc=#mut_documentation] + } + } else { + quote! { #[doc(hidden)] } + }; + let fn_defs = quote! { + #mut_documentation + #[inline(always)] + #visibility fn with_mut <'outer_borrow, ReturnType>( + &'outer_borrow mut self, + user: impl for<#(#lifetimes),*> ::core::ops::FnOnce(#borrowed_mut_fields_type) -> ReturnType + ) -> ReturnType { + let this = unsafe { self.actual_data.assume_init_mut() }; + user(BorrowedMutFields { + #(#mut_field_assignments),* + }) + } + }; + Ok((struct_defs, fn_defs)) +} diff --git a/src/info_structures.rs b/src/info_structures.rs index 22e4cde..c78d73d 100644 --- a/src/info_structures.rs +++ b/src/info_structures.rs @@ -1,9 +1,10 @@ use crate::utils::{make_generic_arguments, make_generic_consumers, replace_this_with_lifetime}; use proc_macro2::{Ident, TokenStream}; +use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt}; use quote::{format_ident, quote, ToTokens}; use syn::{ punctuated::Punctuated, token::Comma, Attribute, ConstParam, Error, GenericParam, Generics, - LifetimeDef, Type, TypeParam, Visibility, + LifetimeParam, Type, TypeParam, Visibility, spanned::Spanned, }; #[derive(Clone, Copy)] @@ -12,6 +13,16 @@ pub struct Options { pub do_pub_extras: bool, } +impl Options { + // pub fn documentation_to_tokens(&self, documentation: &str) -> TokenStream { + // if self.do_no_doc { + // quote! { #[doc(hidden)] } + // } else { + // quote! { #[doc=#documentation] } + // } + // } +} + #[derive(Clone, Copy, PartialEq)] pub enum FieldType { /// Not borrowed by other parts of the struct. @@ -50,10 +61,7 @@ pub enum BuilderType { impl BuilderType { pub fn is_async(&self) -> bool { - match self { - BuilderType::Sync => false, - _ => true, - } + !matches!(self, BuilderType::Sync) } } @@ -61,6 +69,7 @@ impl BuilderType { pub struct StructInfo { pub derives: Vec, pub ident: Ident, + pub internal_ident: Ident, pub generics: Generics, pub vis: Visibility, pub fields: Vec, @@ -72,7 +81,7 @@ impl StructInfo { // The lifetime to use in place of 'this for internal implementations, // should never be exposed to the user. pub fn fake_lifetime(&self) -> Ident { - return self.first_lifetime.clone(); + self.first_lifetime.clone() } pub fn generic_params(&self) -> &Punctuated { @@ -98,13 +107,13 @@ impl StructInfo { Type(TypeParam { ident, .. }) | Const(ConstParam { ident, .. }) => { ident.to_token_stream() } - Lifetime(LifetimeDef { lifetime, .. }) => lifetime.to_token_stream(), + Lifetime(LifetimeParam { lifetime, .. }) => lifetime.to_token_stream(), }); quote! { <'_, '_, #(#params,)*> } } pub fn generic_arguments(&self) -> Vec { - make_generic_arguments(&self.generics) + make_generic_arguments(self.generics.params.iter().collect()) } /// Same as generic_arguments but with 'outer_borrow and 'this prepended. @@ -202,7 +211,8 @@ impl StructFieldInfo { /// Generates an error requesting that the user explicitly specify whether or not the /// field's type is covariant. - pub fn covariance_error(&self) { + #[must_use] + pub fn covariance_error(&self) -> Diagnostic { let error = concat!( "Ouroboros cannot automatically determine if this type is covariant.\n\n", "As an example, a Box<&'this ()> is covariant because it can be used as a\n", @@ -213,7 +223,7 @@ impl StructFieldInfo { "guarantee means the value is not covariant.\n\n", "To resolve this error, add #[covariant] or #[not_covariant] to the field.\n", ); - proc_macro_error::emit_error!(self.typ, error); + self.typ.span().error(error) } pub fn make_constructor_arg_type_impl( diff --git a/src/lib.rs b/src/lib.rs index cd12419..36482e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,20 +9,22 @@ mod utils; use crate::{ generate::{ constructor::create_builder_and_constructor, derives::create_derives, - into_heads::make_into_heads, struc::create_actual_struct_def, + into_heads::make_into_heads, struc::create_internal_struct_def, summon_checker::generate_checker_summoner, try_constructor::create_try_builder_and_constructor, type_asserts::make_type_asserts, - with_all::make_with_all_function, with_each::make_with_functions, + with::make_with_all_function, with_each::make_with_functions, }, info_structures::Options, parse::parse_struct, }; -use inflector::Inflector; +use generate::{ + drop::create_drop_impl, struc::create_actual_struct_def, with_mut::make_with_all_mut_function, +}; +use heck::ToSnakeCase; use info_structures::BuilderType; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenTree; -use proc_macro_error::proc_macro_error; use quote::{format_ident, quote}; use syn::{Error, ItemStruct}; @@ -37,6 +39,8 @@ fn self_referencing_impl( let info = parse_struct(original_struct_def)?; let actual_struct_def = create_actual_struct_def(&info)?; + let internal_struct_def = create_internal_struct_def(&info)?; + let drop_impl = create_drop_impl(&info)?; let borrowchk_summoner = generate_checker_summoner(&info)?; @@ -50,11 +54,20 @@ fn self_referencing_impl( create_try_builder_and_constructor(&info, options, BuilderType::Sync)?; let (async_try_builder_struct_name, async_try_builder_def, async_try_constructor_def) = create_try_builder_and_constructor(&info, options, BuilderType::Async)?; - let (async_send_try_builder_struct_name, async_send_try_builder_def, async_send_try_constructor_def) = - create_try_builder_and_constructor(&info, options, BuilderType::AsyncSend)?; + let ( + async_send_try_builder_struct_name, + async_send_try_builder_def, + async_send_try_constructor_def, + ) = create_try_builder_and_constructor(&info, options, BuilderType::AsyncSend)?; - let with_defs = make_with_functions(&info, options)?; - let (with_all_struct_defs, with_all_fn_defs) = make_with_all_function(&info, options)?; + let (with_defs, with_errors) = make_with_functions(&info, options); + let with_errors = with_errors + .into_iter() + .map(|err| err.emit_as_item_tokens()) + .collect::>(); + let (with_all_struct_def, with_all_fn_def) = make_with_all_function(&info, options)?; + let (with_all_mut_struct_def, with_all_mut_fn_def) = + make_with_all_mut_function(&info, options)?; let (heads_struct_def, into_heads_fn) = make_into_heads(&info, options); let impls = create_derives(&info)?; @@ -78,6 +91,8 @@ fn self_referencing_impl( use super::*; #[doc="The self-referencing struct."] #actual_struct_def + #internal_struct_def + #drop_impl #borrowchk_summoner #builder_def #async_builder_def @@ -85,7 +100,9 @@ fn self_referencing_impl( #try_builder_def #async_try_builder_def #async_send_try_builder_def - #with_all_struct_defs + #with_all_struct_def + #with_all_mut_struct_def + #(#with_errors)* #heads_struct_def #impls impl <#generic_params> #struct_name <#(#generic_args),*> #generic_where { @@ -96,7 +113,8 @@ fn self_referencing_impl( #async_try_constructor_def #async_send_try_constructor_def #(#with_defs)* - #with_all_fn_defs + #with_all_fn_def + #with_all_mut_fn_def #into_heads_fn } #type_asserts_def @@ -111,7 +129,6 @@ fn self_referencing_impl( })) } -#[proc_macro_error] #[proc_macro_attribute] pub fn self_referencing(attr: TokenStream, item: TokenStream) -> TokenStream { let mut options = Options { @@ -131,7 +148,7 @@ pub fn self_referencing(attr: TokenStream, item: TokenStream) -> TokenStream { "pub_extras" => options.do_pub_extras = true, _ => { return Error::new_spanned( - &ident, + ident, "Unknown identifier, expected 'no_doc' or 'pub_extras'.", ) .to_compile_error() diff --git a/src/parse.rs b/src/parse.rs index 546aa7c..e68969b 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,11 +1,13 @@ -use proc_macro2::{Delimiter, Span, TokenTree}; +use proc_macro2::{Span, TokenTree}; use quote::format_ident; -use syn::{spanned::Spanned, Attribute, Error, Fields, GenericParam, ItemStruct}; +use syn::{ + spanned::Spanned, Attribute, Error, Fields, GenericParam, ItemStruct, MacroDelimiter, Meta, +}; use crate::{ covariance_detection::type_is_covariant_over_this_lifetime, info_structures::{BorrowRequest, Derive, FieldType, StructFieldInfo, StructInfo}, - utils::submodule_contents_visiblity, + utils::submodule_contents_visibility, }; fn handle_borrows_attr( @@ -15,12 +17,14 @@ fn handle_borrows_attr( ) -> Result<(), Error> { let mut borrow_mut = false; let mut waiting_for_comma = false; - let tokens = attr.tokens.clone(); - let possible_error = Error::new_spanned(&tokens, "Invalid syntax for borrows() macro."); - let tokens = if let Some(TokenTree::Group(group)) = tokens.into_iter().next() { - group.stream() - } else { - return Err(possible_error); + let tokens = match &attr.meta { + Meta::List(ml) => ml.tokens.clone(), + _ => { + return Err(Error::new_spanned( + &attr.meta, + "Invalid syntax for borrows() macro.", + )) + } }; for token in tokens { if let TokenTree::Ident(ident) = token { @@ -113,21 +117,26 @@ fn parse_derive_token(token: &TokenTree) -> Result, Error> { } fn parse_derive_attribute(attr: &Attribute) -> Result, Error> { - let body = &attr.tokens; - if let Some(TokenTree::Group(body)) = body.clone().into_iter().next() { - if body.delimiter() != Delimiter::Parenthesis { - panic!("TODO: nice error, bad define syntax") - } - let mut derives = Vec::new(); - for token in body.stream().into_iter() { - if let Some(derive) = parse_derive_token(&token)? { - derives.push(derive); - } + let body = match &attr.meta { + Meta::List(ml) => ml, + _ => unreachable!(), + }; + if !matches!(body.delimiter, MacroDelimiter::Paren(_)) { + return Err(Error::new( + attr.span(), + format!( + "malformed derive input, derive attributes are of the form `#[derive({})]`", + body.tokens + ), + )); + } + let mut derives = Vec::new(); + for token in body.tokens.clone().into_iter() { + if let Some(derive) = parse_derive_token(&token)? { + derives.push(derive); } - Ok(derives) - } else { - Err(Error::new(attr.span(), "bad syntax")) } + Ok(derives) } pub fn parse_struct(def: &ItemStruct) -> Result { @@ -144,7 +153,7 @@ pub fn parse_struct(def: &ItemStruct) -> Result { let mut covariant = type_is_covariant_over_this_lifetime(&field.ty); let mut remove_attrs = Vec::new(); for (index, attr) in field.attrs.iter().enumerate() { - let path = &attr.path; + let path = &attr.path(); if path.leading_colon.is_some() { continue; } @@ -176,7 +185,7 @@ pub fn parse_struct(def: &ItemStruct) -> Result { } // We should not be able to access the field outside of the hidden module where // everything is generated. - let with_vis = submodule_contents_visiblity(&field.vis.clone()); + let with_vis = submodule_contents_visibility(&field.vis.clone()); fields.push(StructFieldInfo { name: field.ident.clone().expect("Named field has no name."), typ: field.ty.clone(), @@ -217,7 +226,7 @@ pub fn parse_struct(def: &ItemStruct) -> Result { if !has_non_tail { return Err(Error::new( Span::call_site(), - &format!( + format!( concat!( "Self-referencing struct cannot be made entirely of tail fields, try adding ", "#[borrows({0})] to a field defined after {0}." @@ -234,19 +243,16 @@ pub fn parse_struct(def: &ItemStruct) -> Result { let mut attributes = Vec::new(); let mut derives = Vec::new(); for attr in &def.attrs { - let p = &attr.path.segments; - if p.len() == 0 { - return Err(Error::new(p.span(), &format!("Unsupported attribute"))); + let p = &attr.path().segments; + if p.is_empty() { + return Err(Error::new(p.span(), "Unsupported attribute".to_string())); } let name = p[0].ident.to_string(); - let good = match &name[..] { - "clippy" | "allow" | "deny" | "doc" => true, - _ => false, - }; + let good = matches!(&name[..], "clippy" | "allow" | "deny" | "doc"); if good { attributes.push(attr.clone()) } else if name == "derive" { - if derives.len() > 0 { + if !derives.is_empty() { return Err(Error::new( attr.span(), "Multiple derive attributes not allowed", @@ -255,17 +261,18 @@ pub fn parse_struct(def: &ItemStruct) -> Result { derives = parse_derive_attribute(attr)?; } } else { - return Err(Error::new(p.span(), &format!("Unsupported attribute"))); + return Err(Error::new(p.span(), "Unsupported attribute".to_string())); } } - return Ok(StructInfo { + Ok(StructInfo { derives, ident: def.ident.clone(), + internal_ident: format_ident!("{}Internal", def.ident), generics: def.generics.clone(), fields, vis, first_lifetime, attributes, - }); + }) } diff --git a/src/utils.rs b/src/utils.rs index 5bbae6c..f22bf07 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -use inflector::Inflector; +use heck::ToSnakeCase; use proc_macro2::{Group, Ident, TokenStream, TokenTree}; use quote::{format_ident, quote}; use syn::{GenericParam, Generics, Visibility}; @@ -28,14 +28,24 @@ pub fn make_generic_consumers(generics: &Generics) -> impl Iterator unimplemented!(), + // rustc don't require constants to consume, so we just skip it. + GenericParam::Const(ct) => { + let ident = ct.ident; + ( + quote! { () }, + format_ident!( + "_comsume_template_const_parameter_{}", + ident.to_string().to_snake_case() + ), + ) + }, }) } // Takes the generics parameters from the original struct and turns them into arguments. -pub fn make_generic_arguments(generics: &Generics) -> Vec { +pub fn make_generic_arguments(generics: Vec<&GenericParam>) -> Vec { let mut arguments = Vec::new(); - for generic in generics.params.clone() { + for generic in generics { match generic { GenericParam::Type(typ) => { let ident = &typ.ident; @@ -45,7 +55,10 @@ pub fn make_generic_arguments(generics: &Generics) -> Vec { let lifetime = <.lifetime; arguments.push(quote! { #lifetime }); } - GenericParam::Const(_) => unimplemented!("Const generics are not supported yet."), + GenericParam::Const(ct) => { + let ident = &ct.ident; + arguments.push(quote! { #ident }); + }, } } arguments @@ -90,7 +103,7 @@ pub fn replace_this_with_lifetime(input: TokenStream, lifetime: Ident) -> TokenS .collect() } -pub fn submodule_contents_visiblity(original_visibility: &Visibility) -> Visibility { +pub fn submodule_contents_visibility(original_visibility: &Visibility) -> Visibility { match original_visibility { // inherited: allow parent of inner submodule to see Visibility::Inherited => syn::parse_quote! { pub(super) }, @@ -107,7 +120,6 @@ pub fn submodule_contents_visiblity(original_visibility: &Visibility) -> Visibil new_visibility.in_token = Some( restricted .in_token - .clone() .unwrap_or_else(|| syn::parse_quote! { in }), ); new_visibility.path.segments = std::iter::once(syn::parse_quote! { super }) -- Gitee