From 9c4db7bca0c90c60278c1af43983ff737424f64e Mon Sep 17 00:00:00 2001 From: joshua-maros <60271685+joshua-maros@users.noreply.github.com> Date: Sun, 10 Oct 2021 10:42:14 -0700 Subject: [PATCH] Add borrowchk summoner --- README.md | 16 +++--- examples/Cargo.toml | 4 +- ouroboros/Cargo.toml | 4 +- ouroboros_macro/Cargo.toml | 2 +- ouroboros_macro/src/generate/constructor.rs | 3 - ouroboros_macro/src/generate/mod.rs | 1 + .../src/generate/summon_borrowchk.rs | 55 +++++++++++++++++++ ouroboros_macro/src/generate/with_all.rs | 28 +++------- ouroboros_macro/src/info_structures.rs | 20 +++++++ ouroboros_macro/src/lib.rs | 15 ++++- 10 files changed, 112 insertions(+), 36 deletions(-) create mode 100644 ouroboros_macro/src/generate/summon_borrowchk.rs diff --git a/README.md b/README.md index 221fc50..dcf0a98 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,15 @@ Easy self-referential struct generation for Rust. Dual licensed under MIT / Apache 2.0. -Note: Version `0.11.0` and later place restrictions on derive macros, earlier -versions allowed using them in ways which could lead to undefined behavior if -not used properly. - -Note: Version `0.10.0` and later automatically box every field. This is done -to prevent undefined behavior, but has the side effect of making the library -easier to work with. +Version notes: +- Version `0.13.0` and later contain checks for additional situations which + cause undefined behavior if not caught. +- Version `0.11.0` and later place restrictions on derive macros, earlier + versions allowed using them in ways which could lead to undefined behavior if + not used properly. +- Version `0.10.0` and later automatically box every field. This is done + to prevent undefined behavior, but has the side effect of making the library + easier to work with. Tests are located in the examples/ folder because they need to be in a crate outside of `ouroboros` for the `self_referencing` macro to work properly. diff --git a/examples/Cargo.toml b/examples/Cargo.toml index adc963b..f2ded2e 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ouroboros_examples" -version = "0.12.0" +version = "0.13.0" authors = ["Joshua Maros "] edition = "2018" license = "MIT OR Apache-2.0" @@ -19,7 +19,7 @@ default = [] miri = [] [dependencies] -ouroboros = { version = "0.12.0", path = "../ouroboros" } +ouroboros = { version = "0.13.0", path = "../ouroboros" } stable_deref_trait = "1.2" [dev-dependencies] diff --git a/ouroboros/Cargo.toml b/ouroboros/Cargo.toml index 10adac6..34dba11 100644 --- a/ouroboros/Cargo.toml +++ b/ouroboros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ouroboros" -version = "0.12.0" +version = "0.13.0" authors = ["Joshua Maros "] edition = "2018" license = "MIT OR Apache-2.0" @@ -11,5 +11,5 @@ repository = "https://github.com/joshua-maros/ouroboros" [dependencies] aliasable = "0.1.3" -ouroboros_macro = { version = "0.12.0", path = "../ouroboros_macro" } +ouroboros_macro = { version = "0.13.0", path = "../ouroboros_macro" } stable_deref_trait = "1.2" diff --git a/ouroboros_macro/Cargo.toml b/ouroboros_macro/Cargo.toml index 2447e25..e09dbbe 100644 --- a/ouroboros_macro/Cargo.toml +++ b/ouroboros_macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ouroboros_macro" -version = "0.12.0" +version = "0.13.0" authors = ["Joshua Maros "] edition = "2018" license = "MIT OR Apache-2.0" diff --git a/ouroboros_macro/src/generate/constructor.rs b/ouroboros_macro/src/generate/constructor.rs index 4e190d7..94b3094 100644 --- a/ouroboros_macro/src/generate/constructor.rs +++ b/ouroboros_macro/src/generate/constructor.rs @@ -83,9 +83,6 @@ pub fn create_builder_and_constructor( // it work. let builder_name = field.builder_name(); params.push(quote! { #builder_name : impl #bound_type }); - // Ok so hear me out basically without this thing here my IDE thinks the rest of the - // code is a string and it all turns green. - {} doc_table += &format!( "| `{}` | Use a function or closure: `(", builder_name.to_string() diff --git a/ouroboros_macro/src/generate/mod.rs b/ouroboros_macro/src/generate/mod.rs index c997118..641a053 100644 --- a/ouroboros_macro/src/generate/mod.rs +++ b/ouroboros_macro/src/generate/mod.rs @@ -2,6 +2,7 @@ pub mod constructor; pub mod derives; pub mod into_heads; pub mod struc; +pub mod summon_borrowchk; pub mod try_constructor; pub mod type_asserts; pub mod with_all; diff --git a/ouroboros_macro/src/generate/summon_borrowchk.rs b/ouroboros_macro/src/generate/summon_borrowchk.rs new file mode 100644 index 0000000..21b47a5 --- /dev/null +++ b/ouroboros_macro/src/generate/summon_borrowchk.rs @@ -0,0 +1,55 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::Error; + +use crate::info_structures::{ArgType, StructInfo}; + +pub fn generate_borrowchk_summoner(info: &StructInfo) -> Result { + let mut code: Vec = Vec::new(); + let mut params: Vec = Vec::new(); + let mut value_consumers: Vec = Vec::new(); + let mut template_consumers: Vec = Vec::new(); + for field in &info.fields { + let field_name = &field.name; + + let arg_type = field.make_constructor_arg_type(&info, false)?; + 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 }); + } 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 + // it work. + let builder_name = field.builder_name(); + params.push(quote! { #builder_name : impl #bound_type }); + let mut builder_args = Vec::new(); + for (_, borrow) in field.borrows.iter().enumerate() { + let borrowed_name = &info.fields[borrow.index].name; + builder_args.push(quote! { &#borrowed_name }); + } + code.push(quote! { let #field_name = #builder_name (#(#builder_args),*); }); + } + if field.is_borrowed() { + let boxed = quote! { ::ouroboros::macro_help::aliasable_boxed(#field_name) }; + code.push(quote! { let #field_name = #boxed; }); + }; + if !field.is_mutably_borrowed() { + value_consumers.push(quote! { #field_name: &#field_name }); + } + } + for (_ty, ident) in info.generic_consumers() { + template_consumers.push(quote! { #ident: ::core::marker::PhantomData }); + } + let generic_params = info.generic_params(); + Ok(quote! { + fn check_if_okay_according_to_borrow_checker<#generic_params>( + #(#params,)* + ) { + #(#code;)* + BorrowedFields { + #(#value_consumers,)* + #(#template_consumers,)* + }; + } + }) +} diff --git a/ouroboros_macro/src/generate/with_all.rs b/ouroboros_macro/src/generate/with_all.rs index e278aa4..07e0a84 100644 --- a/ouroboros_macro/src/generate/with_all.rs +++ b/ouroboros_macro/src/generate/with_all.rs @@ -41,26 +41,14 @@ pub fn make_with_all_function( } } - let new_generic_params = if info.generic_params().is_empty() { - quote! { <'outer_borrow, 'this> } - } else { - 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 mut new_generic_params = info.generic_params().clone(); - new_generic_params.insert(0, syn::parse_quote! { 'this }); - new_generic_params.insert(0, syn::parse_quote! { 'outer_borrow }); - quote! { <#new_generic_params> } - }; - let new_generic_args = { - let mut args = info.generic_arguments(); - args.insert(0, quote! { 'this }); - args.insert(0, quote! { 'outer_borrow }); - args - }; + 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(); let struct_documentation = format!( concat!( diff --git a/ouroboros_macro/src/info_structures.rs b/ouroboros_macro/src/info_structures.rs index 7559dba..4bd8ab8 100644 --- a/ouroboros_macro/src/info_structures.rs +++ b/ouroboros_macro/src/info_structures.rs @@ -63,10 +63,30 @@ impl StructInfo { &self.generics.params } + /// Same as generic_params but with 'this and 'outer_borrow prepended. + pub fn borrowed_generic_params(&self) -> TokenStream { + if self.generic_params().is_empty() { + quote! { <'outer_borrow, 'this> } + } else { + let mut new_generic_params = self.generic_params().clone(); + new_generic_params.insert(0, syn::parse_quote! { 'this }); + new_generic_params.insert(0, syn::parse_quote! { 'outer_borrow }); + quote! { <#new_generic_params> } + } + } + pub fn generic_arguments(&self) -> Vec { make_generic_arguments(&self.generics) } + /// Same as generic_arguments but with 'outer_borrow and 'this prepended. + pub fn borrowed_generic_arguments(&self) -> Vec { + let mut args = self.generic_arguments(); + args.insert(0, quote! { 'this }); + args.insert(0, quote! { 'outer_borrow }); + args + } + pub fn generic_consumers(&self) -> impl Iterator { make_generic_consumers(&self.generics) } diff --git a/ouroboros_macro/src/lib.rs b/ouroboros_macro/src/lib.rs index b043f3e..811caf0 100644 --- a/ouroboros_macro/src/lib.rs +++ b/ouroboros_macro/src/lib.rs @@ -6,7 +6,17 @@ mod info_structures; mod parse; mod utils; -use crate::{generate::{constructor::create_builder_and_constructor, derives::create_derives, into_heads::make_into_heads, struc::create_actual_struct_def, try_constructor::create_try_builder_and_constructor, type_asserts::make_type_asserts, with_all::make_with_all_function, with_each::make_with_functions}, info_structures::Options, parse::parse_struct}; +use crate::{ + generate::{ + constructor::create_builder_and_constructor, derives::create_derives, + into_heads::make_into_heads, struc::create_actual_struct_def, + summon_borrowchk::generate_borrowchk_summoner, + try_constructor::create_try_builder_and_constructor, type_asserts::make_type_asserts, + with_all::make_with_all_function, with_each::make_with_functions, + }, + info_structures::Options, + parse::parse_struct, +}; use inflector::Inflector; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; @@ -27,6 +37,8 @@ fn self_referencing_impl( let actual_struct_def = create_actual_struct_def(&info)?; + let borrowchk_summoner = generate_borrowchk_summoner(&info)?; + let (builder_struct_name, builder_def, constructor_def) = create_builder_and_constructor(&info, options, false)?; let (async_builder_struct_name, async_builder_def, async_constructor_def) = @@ -60,6 +72,7 @@ fn self_referencing_impl( mod #mod_name { use super::*; #actual_struct_def + #borrowchk_summoner #builder_def #async_builder_def #try_builder_def