diff options
-rw-r--r-- | .cargo_vcs_info.json | 6 | ||||
-rw-r--r-- | Cargo.toml | 43 | ||||
-rw-r--r-- | Cargo.toml.orig | 22 | ||||
l--------- | LICENSE | 1 | ||||
-rw-r--r-- | LICENSE_APACHE | 202 | ||||
-rw-r--r-- | LICENSE_MIT | 22 | ||||
-rw-r--r-- | METADATA | 19 | ||||
-rw-r--r-- | MODULE_LICENSE_APACHE2 | 0 | ||||
-rw-r--r-- | OWNERS | 1 | ||||
-rw-r--r-- | src/covariance_detection.rs | 142 | ||||
-rw-r--r-- | src/generate/constructor.rs | 202 | ||||
-rw-r--r-- | src/generate/derives.rs | 98 | ||||
-rw-r--r-- | src/generate/into_heads.rs | 81 | ||||
-rw-r--r-- | src/generate/mod.rs | 9 | ||||
-rw-r--r-- | src/generate/struc.rs | 50 | ||||
-rw-r--r-- | src/generate/summon_checker.rs | 62 | ||||
-rw-r--r-- | src/generate/try_constructor.rs | 301 | ||||
-rw-r--r-- | src/generate/type_asserts.rs | 41 | ||||
-rw-r--r-- | src/generate/with_all.rs | 134 | ||||
-rw-r--r-- | src/generate/with_each.rs | 132 | ||||
-rw-r--r-- | src/info_structures.rs | 304 | ||||
-rw-r--r-- | src/lib.rs | 165 | ||||
-rw-r--r-- | src/parse.rs | 271 | ||||
-rw-r--r-- | src/utils.rs | 140 |
24 files changed, 2448 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..e454f04 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "79bac55f29edbb44f7205246ab5c3f706cbc4647" + }, + "path_in_vcs": "ouroboros_macro" +}
\ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..52734ba --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,43 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "ouroboros_macro" +version = "0.15.5" +authors = ["Joshua Maros <joshua-maros@github.com>"] +description = "Proc macro for ouroboros crate." +documentation = "https://docs.rs/ouroboros_macro" +license = "MIT OR Apache-2.0" +repository = "https://github.com/joshua-maros/ouroboros" + +[lib] +proc-macro = true + +[dependencies.Inflector] +version = "0.11" +default-features = false + +[dependencies.proc-macro-error] +version = "1.0.4" + +[dependencies.proc-macro2] +version = "1.0" + +[dependencies.quote] +version = "1.0" + +[dependencies.syn] +version = "1.0" +features = ["full"] + +[features] +std = [] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..2db8277 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,22 @@ +[package] +name = "ouroboros_macro" +version = "0.15.5" +authors = ["Joshua Maros <joshua-maros@github.com>"] +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" + +[lib] +proc-macro = true + +[dependencies] +Inflector = { version = "0.11", default-features = false } +proc-macro2 = "1.0" +proc-macro-error = "1.0.4" +quote = "1.0" +syn = { version = "1.0", features = ["full"] } + +[features] +std = [] @@ -0,0 +1 @@ +LICENSE_MIT
\ No newline at end of file diff --git a/LICENSE_APACHE b/LICENSE_APACHE new file mode 100644 index 0000000..574cb18 --- /dev/null +++ b/LICENSE_APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 Joshua Maros + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/LICENSE_MIT b/LICENSE_MIT new file mode 100644 index 0000000..9ed5b2a --- /dev/null +++ b/LICENSE_MIT @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Joshua Maros + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..bdc87ab --- /dev/null +++ b/METADATA @@ -0,0 +1,19 @@ +name: "ouroboros_macro" +description: "Proc macro for ouroboros crate." +third_party { + url { + type: HOMEPAGE + value: "https://crates.io/crates/ouroboros_macro" + } + url { + type: ARCHIVE + value: "https://static.crates.io/crates/ouroboros_macro/ouroboros_macro-0.15.5.crate" + } + version: "0.15.5" + license_type: NOTICE + last_upgrade_date { + year: 2023 + month: 2 + day: 10 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE2 @@ -0,0 +1 @@ +include platform/prebuilts/rust:master:/OWNERS diff --git a/src/covariance_detection.rs b/src/covariance_detection.rs new file mode 100644 index 0000000..2f50983 --- /dev/null +++ b/src/covariance_detection.rs @@ -0,0 +1,142 @@ +use quote::ToTokens; +use syn::{GenericArgument, PathArguments, Type}; + +use crate::utils::uses_this_lifetime; + +const STD_CONTAINER_TYPES: &[&str] = &["Box", "Arc", "Rc"]; + +/// Returns Some((type_name, element_type)) if the provided type appears to be Box, Arc, or Rc from +/// the standard library. Returns None if not. +pub fn apparent_std_container_type(raw_type: &Type) -> Option<(&'static str, &Type)> { + let tpath = if let Type::Path(x) = raw_type { + x + } else { + return None; + }; + let segment = if let Some(segment) = tpath.path.segments.last() { + segment + } else { + return None; + }; + let args = if let PathArguments::AngleBracketed(args) = &segment.arguments { + args + } else { + return None; + }; + if args.args.len() != 1 { + return None; + } + let arg = args.args.first().unwrap(); + let eltype = if let GenericArgument::Type(x) = arg { + x + } else { + return None; + }; + for type_name in STD_CONTAINER_TYPES { + if segment.ident == type_name { + return Some((type_name, eltype)); + } + } + None +} + +/// Returns Some(true or false) if the type is known to be covariant / not covariant. +pub fn type_is_covariant_over_this_lifetime(ty: &syn::Type) -> Option<bool> { + use syn::Type::*; + // If the type never uses the 'this lifetime, we don't have to + // worry about it not being covariant. + if !uses_this_lifetime(ty.to_token_stream()) { + return Some(true); + } + match ty { + Array(arr) => type_is_covariant_over_this_lifetime(&*arr.elem), + BareFn(f) => { + debug_assert!(uses_this_lifetime(f.to_token_stream())); + None + } + Group(ty) => type_is_covariant_over_this_lifetime(&ty.elem), + ImplTrait(..) => None, // Unusable in struct definition. + Infer(..) => None, // Unusable in struct definition. + Macro(..) => None, // We don't know what the macro will resolve to. + Never(..) => None, + Paren(ty) => type_is_covariant_over_this_lifetime(&ty.elem), + Path(path) => { + if let Some(qself) = &path.qself { + if !type_is_covariant_over_this_lifetime(&qself.ty)? { + return Some(false); + } + } + let mut all_parameters_are_covariant = false; + // If the type is Box, Arc, or Rc, we can assume it to be covariant. + if apparent_std_container_type(ty).is_some() { + all_parameters_are_covariant = true; + } + for segment in path.path.segments.iter() { + let args = &segment.arguments; + if let syn::PathArguments::AngleBracketed(args) = &args { + for arg in args.args.iter() { + if let syn::GenericArgument::Type(ty) = arg { + if all_parameters_are_covariant { + if !type_is_covariant_over_this_lifetime(ty)? { + return Some(false); + } + } 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 { + return None; + } + } + } + } else if let syn::PathArguments::Parenthesized(args) = &args { + for arg in args.inputs.iter() { + if uses_this_lifetime(arg.to_token_stream()) { + return None; + } + } + if let syn::ReturnType::Type(_, ty) = &args.output { + if uses_this_lifetime(ty.to_token_stream()) { + return None; + } + } + } + } + Some(true) + } + Ptr(ptr) => { + if ptr.mutability.is_some() { + Some(false) + } else { + type_is_covariant_over_this_lifetime(&ptr.elem) + } + } + // Ignore the actual lifetime of the reference because Rust can automatically convert those. + Reference(rf) => { + if rf.mutability.is_some() { + Some(!uses_this_lifetime(rf.elem.to_token_stream())) + } else { + type_is_covariant_over_this_lifetime(&rf.elem) + } + } + Slice(sl) => type_is_covariant_over_this_lifetime(&sl.elem), + TraitObject(..) => None, + Tuple(tup) => { + let mut result = Some(true); + for ty in tup.elems.iter() { + match type_is_covariant_over_this_lifetime(ty) { + Some(true) => (), + Some(false) => return Some(false), + None => result = None, + } + } + result + } + // As of writing this, syn parses all the types we could need. However, + // just to be safe, return that we don't know if it's covariant. + Verbatim(..) => None, + _ => None, + } +} diff --git a/src/generate/constructor.rs b/src/generate/constructor.rs new file mode 100644 index 0000000..6ab47bc --- /dev/null +++ b/src/generate/constructor.rs @@ -0,0 +1,202 @@ +use crate::{ + info_structures::{ArgType, BuilderType, FieldType, Options, StructInfo}, + utils::to_class_case, +}; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; +use syn::Error; + +pub fn create_builder_and_constructor( + info: &StructInfo, + options: Options, + builder_type: BuilderType, +) -> Result<(Ident, TokenStream, TokenStream), Error> { + let struct_name = info.ident.clone(); + let generic_args = info.generic_arguments(); + + let vis = if options.do_pub_extras { + info.vis.clone() + } else { + syn::parse_quote! { pub(super) } + }; + let builder_struct_name = match builder_type { + BuilderType::AsyncSend => format_ident!("{}AsyncSendBuilder", info.ident), + BuilderType::Async => format_ident!("{}AsyncBuilder", info.ident), + BuilderType::Sync => format_ident!("{}Builder", info.ident), + }; + let documentation = format!( + concat!( + "Constructs a new instance of this self-referential struct. (See also ", + "[`{0}::build()`]({0}::build)). Each argument is a field of ", + "the new struct. Fields that refer to other fields inside the struct are initialized ", + "using functions instead of directly passing their value. The arguments are as ", + "follows:\n\n| Argument | Suggested Use |\n| --- | --- |\n", + ), + builder_struct_name.to_string() + ); + let builder_documentation = concat!( + "A more verbose but stable way to construct self-referencing structs. It is ", + "comparable to using `StructName { field1: value1, field2: value2 }` rather than ", + "`StructName::new(value1, value2)`. This has the dual benefit of making your code ", + "both easier to refactor and more readable. Call [`build()`](Self::build) to ", + "construct the actual struct. The fields of this struct should be used as follows:\n\n", + "| Field | Suggested Use |\n| --- | --- |\n", + ) + .to_owned(); + let build_fn_documentation = format!( + concat!( + "Calls [`{0}::new()`]({0}::new) using the provided values. This is preferrable over ", + "calling `new()` directly for the reasons listed above. " + ), + info.ident.to_string() + ); + let mut doc_table = "".to_owned(); + let mut code: Vec<TokenStream> = Vec::new(); + let mut params: Vec<TokenStream> = Vec::new(); + let mut builder_struct_generic_producers: Vec<_> = info + .generic_params() + .iter() + .map(|param| quote! { #param }) + .collect(); + let mut builder_struct_generic_consumers = info.generic_arguments(); + let mut builder_struct_fields = Vec::new(); + let mut builder_struct_field_names = Vec::new(); + + // code.push(quote! { let mut result = ::core::mem::MaybeUninit::<Self>::uninit(); }); + + for field in &info.fields { + let field_name = &field.name; + + 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 }); + builder_struct_fields.push(quote! { #field_name: #plain_type }); + 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() + ); + } 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 }); + doc_table += &format!( + "| `{}` | Use a function or closure: `(", + builder_name.to_string() + ); + let mut builder_args = Vec::new(); + for (index, borrow) in field.borrows.iter().enumerate() { + let borrowed_name = &info.fields[borrow.index].name; + builder_args.push(format_ident!("{}_illegal_static_reference", borrowed_name)); + doc_table += &format!( + "{}: &{}_", + borrowed_name.to_string(), + if borrow.mutable { "mut " } else { "" }, + ); + if index < field.borrows.len() - 1 { + doc_table += ", "; + } + } + doc_table += &format!(") -> {}: _` | \n", field_name.to_string()); + if builder_type.is_async() { + code.push(quote! { let #field_name = #builder_name (#(#builder_args),*).await; }); + } else { + code.push(quote! { let #field_name = #builder_name (#(#builder_args),*); }); + } + let generic_type_name = + format_ident!("{}Builder_", to_class_case(field_name.to_string().as_str())); + + builder_struct_generic_producers.push(quote! { #generic_type_name: #bound_type }); + builder_struct_generic_consumers.push(quote! { #generic_type_name }); + builder_struct_fields.push(quote! { #builder_name: #generic_type_name }); + builder_struct_field_names.push(quote! { #builder_name }); + } + if field.is_borrowed() { + let boxed = field.boxed(); + if field.field_type == FieldType::BorrowedMut { + code.push(quote! { let mut #field_name = #boxed; }); + } else { + code.push(quote! { let #field_name = #boxed; }); + } + }; + + if field.field_type == FieldType::Borrowed { + code.push(field.make_illegal_static_reference()); + } else if field.field_type == FieldType::BorrowedMut { + code.push(field.make_illegal_static_mut_reference()); + } + } + + let documentation = if !options.do_no_doc { + let documentation = documentation + &doc_table; + quote! { + #[doc=#documentation] + } + } else { + quote! { #[doc(hidden)] } + }; + + let builder_documentation = if !options.do_no_doc { + let builder_documentation = builder_documentation + &doc_table; + quote! { + #[doc=#builder_documentation] + } + } else { + quote! { #[doc(hidden)] } + }; + + let constructor_fn = match builder_type { + BuilderType::AsyncSend => quote! { async fn new_async_send }, + BuilderType::Async => quote! { async fn new_async }, + BuilderType::Sync => quote! { fn new }, + }; + let field_names: Vec<_> = info.fields.iter().map(|field| field.name.clone()).collect(); + let constructor_def = quote! { + #documentation + #vis #constructor_fn(#(#params),*) -> #struct_name <#(#generic_args),*> { + #(#code)* + Self { + #(#field_names),* + } + } + }; + let generic_where = &info.generics.where_clause; + let builder_fn = if builder_type.is_async() { + quote! { async fn build } + } else { + quote! { fn build } + }; + let builder_code = match builder_type { + BuilderType::AsyncSend => quote! { + #struct_name::new_async_send( + #(self.#builder_struct_field_names),* + ).await + }, + BuilderType::Async => quote! { + #struct_name::new_async( + #(self.#builder_struct_field_names),* + ).await + }, + BuilderType::Sync => quote! { + #struct_name::new( + #(self.#builder_struct_field_names),* + ) + }, + }; + let builder_def = quote! { + #builder_documentation + #vis struct #builder_struct_name <#(#builder_struct_generic_producers),*> #generic_where { + #(#vis #builder_struct_fields),* + } + impl<#(#builder_struct_generic_producers),*> #builder_struct_name <#(#builder_struct_generic_consumers),*> #generic_where { + #[doc=#build_fn_documentation] + #vis #builder_fn(self) -> #struct_name <#(#generic_args),*> { + #builder_code + } + } + }; + Ok((builder_struct_name, builder_def, constructor_def)) +} diff --git a/src/generate/derives.rs b/src/generate/derives.rs new file mode 100644 index 0000000..0233a3f --- /dev/null +++ b/src/generate/derives.rs @@ -0,0 +1,98 @@ +use crate::info_structures::{Derive, StructInfo}; +use proc_macro2::TokenStream; +use quote::quote; +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()), + _ => (), + } + new +} + +fn impl_trait(info: &StructInfo, trait_name: TypeParamBound, body: TokenStream) -> TokenStream { + let generic_params = info.generic_params(); + let generic_params = generic_params + .into_iter() + .map(|i| add_trait_bound(i, &trait_name)) + .collect::<Vec<_>>(); + let generic_args = info.generic_arguments(); + let generic_where = &info.generics.where_clause; + let struct_name = &info.ident; + quote! { + impl <#(#generic_params),*> #trait_name for #struct_name <#(#generic_args),*> #generic_where { + #body + } + } +} + +fn impl_debug(info: &StructInfo) -> Result<TokenStream, Error> { + let fields = info + .fields + .iter() + .filter(|field| !field.is_mutably_borrowed()) + .map(|field| { + let name = &field.name; + quote! { + field(stringify!(#name), &safe_self.#name) + } + }) + .collect::<Vec<_>>(); + let trait_name = syn::parse_quote! { ::core::fmt::Debug }; + let struct_name = &info.ident; + let body = quote! { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + self.with(|safe_self| { + f.debug_struct(stringify!(#struct_name)) + #(.#fields)* + .finish() + }) + } + }; + Ok(impl_trait(info, trait_name, body)) +} + +fn impl_partial_eq(info: &StructInfo) -> Result<TokenStream, Error> { + let fields = info + .fields + .iter() + .filter(|field| !field.is_mutably_borrowed()) + .map(|field| { + let name = &field.name; + quote! { + &*safe_self.#name == &*safe_other.#name + } + }) + .collect::<Vec<_>>(); + let trait_name = syn::parse_quote! { ::core::cmp::PartialEq }; + let body = quote! { + fn eq(&self, other: &Self) -> bool { + self.with(|safe_self| { + other.with(|safe_other| { + #(#fields)&&* + }) + }) + } + }; + Ok(impl_trait(info, trait_name, body)) +} + +fn impl_eq(info: &StructInfo) -> Result<TokenStream, Error> { + let trait_name = syn::parse_quote! { ::core::cmp::Eq }; + let body = quote! {}; + Ok(impl_trait(info, trait_name, body)) +} + +pub fn create_derives(info: &StructInfo) -> Result<TokenStream, Error> { + let mut impls = Vec::new(); + for derive in &info.derives { + match derive { + Derive::Debug => impls.push(impl_debug(info)?), + Derive::PartialEq => impls.push(impl_partial_eq(info)?), + Derive::Eq => impls.push(impl_eq(info)?), + } + } + Ok(quote! { #(#impls)* }) +} diff --git a/src/generate/into_heads.rs b/src/generate/into_heads.rs new file mode 100644 index 0000000..5784e46 --- /dev/null +++ b/src/generate/into_heads.rs @@ -0,0 +1,81 @@ +use proc_macro2::TokenStream; +use quote::quote; + +use crate::info_structures::{Options, StructInfo}; + +/// Returns the Heads struct and a function to convert the original struct into a Heads instance. +pub fn make_into_heads(info: &StructInfo, options: Options) -> (TokenStream, TokenStream) { + let visibility = if options.do_pub_extras { + info.vis.clone() + } else { + syn::parse_quote! { pub(super) } + }; + let mut code = Vec::new(); + let mut field_initializers = Vec::new(); + let mut head_fields = Vec::new(); + // 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.is_borrowed() { + field_initializers + .push(quote! { #field_name: ::ouroboros::macro_help::unbox(#field_name) }); + } else { + field_initializers.push(quote! { #field_name }); + } + 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() { + head_fields.push(quote! { #ident: ::core::marker::PhantomData<#ty> }); + field_initializers.push(quote! { #ident: ::core::marker::PhantomData }); + } + let documentation = format!( + concat!( + "A struct which contains only the ", + "[head fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) of [`{0}`]({0})." + ), + info.ident.to_string() + ); + let generic_params = info.generic_params(); + let generic_where = &info.generics.where_clause; + let heads_struct_def = quote! { + #[doc=#documentation] + #visibility struct Heads <#generic_params> #generic_where { + #(#head_fields),* + } + }; + let documentation = concat!( + "This function drops all internally referencing fields and returns only the ", + "[head fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) of this struct." + ).to_owned(); + + let documentation = if !options.do_no_doc { + quote! { + #[doc=#documentation] + } + } else { + quote! { #[doc(hidden)] } + }; + + let generic_args = info.generic_arguments(); + let into_heads_fn = quote! { + #documentation + #[allow(clippy::drop_ref)] + #[allow(clippy::drop_copy)] + #[allow(clippy::drop_non_drop)] + #visibility fn into_heads(self) -> Heads<#(#generic_args),*> { + #(#code)* + Heads { + #(#field_initializers),* + } + } + }; + (heads_struct_def, into_heads_fn) +} diff --git a/src/generate/mod.rs b/src/generate/mod.rs new file mode 100644 index 0000000..0b229d1 --- /dev/null +++ b/src/generate/mod.rs @@ -0,0 +1,9 @@ +pub mod constructor; +pub mod derives; +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_each; diff --git a/src/generate/struc.rs b/src/generate/struc.rs new file mode 100644 index 0000000..9b2ff51 --- /dev/null +++ b/src/generate/struc.rs @@ -0,0 +1,50 @@ +use crate::{ + info_structures::StructInfo, + utils::{self, replace_this_with_lifetime}, +}; +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. +pub fn create_actual_struct_def(info: &StructInfo) -> Result<TokenStream, Error> { + let vis = utils::submodule_contents_visiblity(&info.vis); + let ident = &info.ident; + let generics = &info.generics; + + let field_defs: Vec<_> = info + .fields + .iter() + // Reverse the order of all fields. We ensure that items in the struct are only dependent + // on references to items above them. Rust drops items in a struct in forward declaration order. + // This would cause parents being dropped before children, necessitating the reversal. + .rev() + .map(|field| { + let name = &field.name; + let ty = field.stored_type(); + quote! { + #[doc(hidden)] + #name: #ty + } + }) + .collect(); + + // Create the new struct definition. + let mut where_clause = quote! {}; + if let Some(clause) = &generics.where_clause { + where_clause = quote! { #clause }; + } + let def = quote! { + #vis struct #ident #generics #where_clause { + #(#field_defs),* + } + }; + + // Finally, replace the fake 'this lifetime with the one we found. + let fake_lifetime = info.fake_lifetime(); + let def = replace_this_with_lifetime(quote! { #def }, fake_lifetime.clone()); + + Ok(def) +} diff --git a/src/generate/summon_checker.rs b/src/generate/summon_checker.rs new file mode 100644 index 0000000..743c199 --- /dev/null +++ b/src/generate/summon_checker.rs @@ -0,0 +1,62 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::Error; + +use crate::info_structures::{ArgType, BuilderType, StructInfo}; + +pub fn generate_checker_summoner(info: &StructInfo) -> Result<TokenStream, Error> { + let mut code: Vec<TokenStream> = Vec::new(); + let mut params: Vec<TokenStream> = Vec::new(); + let mut value_consumers: Vec<TokenStream> = Vec::new(); + let mut template_consumers: Vec<TokenStream> = Vec::new(); + for field in &info.fields { + let field_name = &field.name; + + let arg_type = field.make_constructor_arg_type(&info, BuilderType::Sync)?; + 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; + if borrow.mutable { + builder_args.push(quote! { &mut #borrowed_name }); + } else { + builder_args.push(quote! { &#borrowed_name }); + } + } + code.push(quote! { let #field_name = #builder_name (#(#builder_args),*); }); + } + if field.is_mutably_borrowed() { + code.push(quote! { let mut #field_name = #field_name; }); + } else { + code.push(quote! { let #field_name = #field_name; }); + 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(); + let where_clause = &info.generics.where_clause; + let borrowed_generic_params_inferred = info.borrowed_generic_params_inferred(); + Ok(quote! { + fn check_if_okay_according_to_checkers<#generic_params>( + #(#params,)* + ) + #where_clause + { + #(#code;)* + BorrowedFields::#borrowed_generic_params_inferred { + #(#value_consumers,)* + #(#template_consumers,)* + }; + } + }) +} diff --git a/src/generate/try_constructor.rs b/src/generate/try_constructor.rs new file mode 100644 index 0000000..4078c56 --- /dev/null +++ b/src/generate/try_constructor.rs @@ -0,0 +1,301 @@ +use crate::{ + info_structures::{ArgType, BuilderType, FieldType, Options, StructInfo}, + utils::to_class_case, +}; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; +use syn::Error; + +pub fn create_try_builder_and_constructor( + info: &StructInfo, + options: Options, + builder_type: BuilderType, +) -> Result<(Ident, TokenStream, TokenStream), Error> { + let struct_name = info.ident.clone(); + let generic_args = info.generic_arguments(); + + let visibility = if options.do_pub_extras { + info.vis.clone() + } else { + syn::parse_quote! { pub(super) } + }; + let mut head_recover_code = Vec::new(); + for field in &info.fields { + if !field.self_referencing { + let field_name = &field.name; + head_recover_code.push(quote! { #field_name }); + } + } + for (_ty, ident) in info.generic_consumers() { + head_recover_code.push(quote! { #ident: ::core::marker::PhantomData }); + } + let mut current_head_index = 0; + + let builder_struct_name = match builder_type { + BuilderType::AsyncSend => format_ident!("{}AsyncSendTryBuilder", info.ident), + BuilderType::Async => format_ident!("{}AsyncTryBuilder", info.ident), + BuilderType::Sync => format_ident!("{}TryBuilder", info.ident), + }; + let documentation = format!( + concat!( + "(See also [`{0}::try_build()`]({0}::try_build).) Like [`new`](Self::new), but ", + "builders for [self-referencing fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) ", + "can return results. If any of them fail, `Err` is returned. If all of them ", + "succeed, `Ok` is returned. The arguments are as follows:\n\n", + "| Argument | Suggested Use |\n| --- | --- |\n", + ), + builder_struct_name.to_string() + ); + let or_recover_documentation = format!( + concat!( + "(See also [`{0}::try_build_or_recover()`]({0}::try_build_or_recover).) Like ", + "[`try_new`](Self::try_new), but all ", + "[head fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) ", + "are returned in the case of an error. The arguments are as follows:\n\n", + "| Argument | Suggested Use |\n| --- | --- |\n", + ), + builder_struct_name.to_string() + ); + let builder_documentation = concat!( + "A more verbose but stable way to construct self-referencing structs. It is ", + "comparable to using `StructName { field1: value1, field2: value2 }` rather than ", + "`StructName::new(value1, value2)`. This has the dual benefit of makin your code ", + "both easier to refactor and more readable. Call [`try_build()`](Self::try_build) or ", + "[`try_build_or_recover()`](Self::try_build_or_recover) to ", + "construct the actual struct. The fields of this struct should be used as follows:\n\n", + "| Field | Suggested Use |\n| --- | --- |\n", + ) + .to_owned(); + let build_fn_documentation = format!( + concat!( + "Calls [`{0}::try_new()`]({0}::try_new) using the provided values. This is ", + "preferrable over calling `try_new()` directly for the reasons listed above. " + ), + info.ident.to_string() + ); + let build_or_recover_fn_documentation = format!( + concat!( + "Calls [`{0}::try_new_or_recover()`]({0}::try_new_or_recover) using the provided ", + "values. This is preferrable over calling `try_new_or_recover()` directly for the ", + "reasons listed above. " + ), + info.ident.to_string() + ); + let mut doc_table = "".to_owned(); + let mut or_recover_code: Vec<TokenStream> = Vec::new(); + let mut params: Vec<TokenStream> = Vec::new(); + let mut builder_struct_generic_producers: Vec<_> = info + .generic_params() + .iter() + .map(|param| quote! { #param }) + .collect(); + let mut builder_struct_generic_consumers = info.generic_arguments(); + let mut builder_struct_fields = Vec::new(); + let mut builder_struct_field_names = Vec::new(); + + for field in &info.fields { + let field_name = &field.name; + + let arg_type = field.make_try_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 }); + builder_struct_fields.push(quote! { #field_name: #plain_type }); + 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() + ); + if !field.self_referencing { + if field.is_borrowed() { + head_recover_code[current_head_index] = quote! { + #field_name: ::ouroboros::macro_help::unbox(#field_name) + }; + } else { + head_recover_code[current_head_index] = quote! { #field_name }; + } + current_head_index += 1; + } + } 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 }); + // 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() + ); + let mut builder_args = Vec::new(); + for (index, borrow) in field.borrows.iter().enumerate() { + let borrowed_name = &info.fields[borrow.index].name; + builder_args.push(format_ident!("{}_illegal_static_reference", borrowed_name)); + doc_table += &format!( + "{}: &{}_", + borrowed_name.to_string(), + if borrow.mutable { "mut " } else { "" }, + ); + if index < field.borrows.len() - 1 { + doc_table += ", "; + } + } + doc_table += &format!(") -> Result<{}: _, Error_>` | \n", field_name.to_string()); + let builder_value = if builder_type.is_async() { + quote! { #builder_name (#(#builder_args),*).await } + } else { + quote! { #builder_name (#(#builder_args),*) } + }; + or_recover_code.push(quote! { + let #field_name = match #builder_value { + ::core::result::Result::Ok(value) => value, + ::core::result::Result::Err(err) + => return ::core::result::Result::Err((err, Heads { #(#head_recover_code),* })), + }; + }); + let generic_type_name = + format_ident!("{}Builder_", to_class_case(field_name.to_string().as_str())); + + builder_struct_generic_producers.push(quote! { #generic_type_name: #bound_type }); + builder_struct_generic_consumers.push(quote! { #generic_type_name }); + builder_struct_fields.push(quote! { #builder_name: #generic_type_name }); + builder_struct_field_names.push(quote! { #builder_name }); + } + if field.is_borrowed() { + let boxed = field.boxed(); + if field.field_type == FieldType::BorrowedMut { + or_recover_code.push(quote! { let mut #field_name = #boxed; }); + } else { + or_recover_code.push(quote! { let #field_name = #boxed; }); + } + } + + if field.field_type == FieldType::Borrowed { + or_recover_code.push(field.make_illegal_static_reference()); + } else if field.field_type == FieldType::BorrowedMut { + or_recover_code.push(field.make_illegal_static_mut_reference()); + } + } + let documentation = if !options.do_no_doc { + let documentation = documentation + &doc_table; + quote! { + #[doc=#documentation] + } + } else { + quote! { #[doc(hidden)] } + }; + let or_recover_documentation = if !options.do_no_doc { + let or_recover_documentation = or_recover_documentation + &doc_table; + quote! { + #[doc=#or_recover_documentation] + } + } else { + quote! { #[doc(hidden)] } + }; + let builder_documentation = if !options.do_no_doc { + let builder_documentation = builder_documentation + &doc_table; + quote! { + #[doc=#builder_documentation] + } + } else { + quote! { #[doc(hidden)] } + }; + let or_recover_ident = match builder_type { + BuilderType::AsyncSend => quote! { try_new_or_recover_async_send }, + BuilderType::Async => quote! { try_new_or_recover_async }, + BuilderType::Sync => quote! { try_new_or_recover }, + }; + let or_recover_constructor_fn = if builder_type.is_async() { + quote! { async fn #or_recover_ident } + } else { + quote! { fn #or_recover_ident } + }; + let constructor_fn = match builder_type { + BuilderType::AsyncSend => quote! { async fn try_new_async_send }, + BuilderType::Async => quote! { async fn try_new_async }, + BuilderType::Sync => quote! { fn try_new }, + }; + let constructor_code = if builder_type.is_async() { + quote! { #struct_name::#or_recover_ident(#(#builder_struct_field_names),*).await.map_err(|(error, _heads)| error) } + } else { + 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 constructor_def = quote! { + #documentation + #visibility #constructor_fn<Error_>(#(#params),*) -> ::core::result::Result<#struct_name <#(#generic_args),*>, Error_> { + #constructor_code + } + #or_recover_documentation + #visibility #or_recover_constructor_fn<Error_>(#(#params),*) -> ::core::result::Result<#struct_name <#(#generic_args),*>, (Error_, Heads<#(#generic_args),*>)> { + #(#or_recover_code)* + ::core::result::Result::Ok(Self { #(#field_names),* }) + } + }; + builder_struct_generic_producers.push(quote! { Error_ }); + builder_struct_generic_consumers.push(quote! { Error_ }); + let generic_where = &info.generics.where_clause; + let builder_fn = if builder_type.is_async() { + quote! { async fn try_build } + } else { + quote! { fn try_build } + }; + let or_recover_builder_fn = if builder_type.is_async() { + quote! { async fn try_build_or_recover } + } else { + quote! { fn try_build_or_recover } + }; + let builder_code = match builder_type { + BuilderType::AsyncSend => quote! { + #struct_name::try_new_async_send( + #(self.#builder_struct_field_names),* + ).await + }, + BuilderType::Async => quote! { + #struct_name::try_new_async( + #(self.#builder_struct_field_names),* + ).await + }, + BuilderType::Sync => quote! { + #struct_name::try_new( + #(self.#builder_struct_field_names),* + ) + }, + }; + let or_recover_builder_code = match builder_type { + BuilderType::AsyncSend => quote! { + #struct_name::try_new_or_recover_async_send( + #(self.#builder_struct_field_names),* + ).await + }, + BuilderType::Async => quote! { + #struct_name::try_new_or_recover_async( + #(self.#builder_struct_field_names),* + ).await + }, + BuilderType::Sync => quote! { + #struct_name::try_new_or_recover( + #(self.#builder_struct_field_names),* + ) + }, + }; + let builder_def = quote! { + #builder_documentation + #visibility struct #builder_struct_name <#(#builder_struct_generic_producers),*> #generic_where { + #(#visibility #builder_struct_fields),* + } + impl<#(#builder_struct_generic_producers),*> #builder_struct_name <#(#builder_struct_generic_consumers),*> #generic_where { + #[doc=#build_fn_documentation] + #visibility #builder_fn(self) -> ::core::result::Result<#struct_name <#(#generic_args),*>, Error_> { + #builder_code + } + #[doc=#build_or_recover_fn_documentation] + #visibility #or_recover_builder_fn(self) -> ::core::result::Result<#struct_name <#(#generic_args),*>, (Error_, Heads<#(#generic_args),*>)> { + #or_recover_builder_code + } + } + }; + Ok((builder_struct_name, builder_def, constructor_def)) +} diff --git a/src/generate/type_asserts.rs b/src/generate/type_asserts.rs new file mode 100644 index 0000000..3c8c7d1 --- /dev/null +++ b/src/generate/type_asserts.rs @@ -0,0 +1,41 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::GenericParam; + +use crate::{ + covariance_detection::apparent_std_container_type, info_structures::StructInfo, + utils::replace_this_with_lifetime, +}; + +pub fn make_type_asserts(info: &StructInfo) -> TokenStream { + let mut checks = Vec::new(); + let fake_lifetime = if let Some(GenericParam::Lifetime(param)) = info.generic_params().first() { + param.lifetime.ident.clone() + } else { + format_ident!("static") + }; + for field in &info.fields { + let field_type = &field.typ; + if let Some((std_type, _eltype)) = apparent_std_container_type(field_type) { + let checker_name = match std_type { + "Box" => "is_std_box_type", + "Arc" => "is_std_arc_type", + "Rc" => "is_std_rc_type", + _ => unreachable!(), + }; + let checker_name = format_ident!("{}", checker_name); + let static_field_type = + replace_this_with_lifetime(quote! { #field_type }, fake_lifetime.clone()); + checks.push(quote! { + ::ouroboros::macro_help::CheckIfTypeIsStd::<#static_field_type>::#checker_name(); + }); + } + } + let generic_params = info.generic_params(); + let generic_where = &info.generics.where_clause; + quote! { + fn type_asserts <#generic_params>() #generic_where { + #(#checks)* + } + } +} diff --git a/src/generate/with_all.rs b/src/generate/with_all.rs new file mode 100644 index 0000000..e6a2665 --- /dev/null +++ b/src/generate/with_all.rs @@ -0,0 +1,134 @@ +use crate::info_structures::{FieldType, Options, StructInfo}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{Error, Lifetime, WhereClause}; + +pub fn make_with_all_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 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 }); + } else if field.field_type == FieldType::Borrowed { + let ass = quote! { #field_name: unsafe { + ::ouroboros::macro_help::change_lifetime( + &*self.#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. + } + } + + 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!( + "A struct for holding immutable references to all ", + "[tail and immutably borrowed fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) in an instance of ", + "[`{0}`]({0})." + ), + 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()); + clause + } else { + syn::parse_quote! { where #lifetime: 'this } + }; + 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] + } + } 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)] + #visibility fn with <'outer_borrow, ReturnType>( + &'outer_borrow self, + user: impl for<'this> ::core::ops::FnOnce(#borrowed_fields_type) -> ReturnType + ) -> ReturnType { + 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 new file mode 100644 index 0000000..8985857 --- /dev/null +++ b/src/generate/with_each.rs @@ -0,0 +1,132 @@ +use crate::info_structures::{FieldType, Options, StructInfo}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::Error; + +pub fn make_with_functions(info: &StructInfo, options: Options) -> Result<Vec<TokenStream>, Error> { + let mut users = Vec::new(); + for field in &info.fields { + let visibility = &field.vis; + let field_name = &field.name; + let field_type = &field.typ; + // If the field is not a tail, we need to serve up the same kind of reference that other + // fields in the struct may have borrowed to ensure safety. + if field.field_type == FieldType::Tail { + let user_name = format_ident!("with_{}", &field.name); + let documentation = format!( + concat!( + "Provides an immutable reference to `{0}`. This method was generated because ", + "`{0}` is a [tail field](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions)." + ), + field.name.to_string() + ); + let documentation = if !options.do_no_doc { + quote! { + #[doc=#documentation] + } + } else { + quote! { #[doc(hidden)] } + }; + users.push(quote! { + #documentation + #[inline(always)] + #visibility fn #user_name <'outer_borrow, ReturnType>( + &'outer_borrow self, + user: impl for<'this> ::core::ops::FnOnce(&'outer_borrow #field_type) -> ReturnType, + ) -> ReturnType { + user(&self. #field_name) + } + }); + if field.covariant == Some(true) { + let borrower_name = format_ident!("borrow_{}", &field.name); + users.push(quote! { + #documentation + #[inline(always)] + #visibility fn #borrower_name<'this>( + &'this self, + ) -> &'this #field_type { + &self.#field_name + } + }); + } else if field.covariant.is_none() { + 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); + let documentation = format!( + concat!( + "Provides a mutable reference to `{0}`. This method was generated because ", + "`{0}` is a [tail field](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions). ", + "No `borrow_{0}_mut` function was generated because Rust's borrow checker is ", + "currently unable to guarantee that such a method would be used safely." + ), + field.name.to_string() + ); + let documentation = if !options.do_no_doc { + quote! { + #[doc=#documentation] + } + } else { + quote! { #[doc(hidden)] } + }; + users.push(quote! { + #documentation + #[inline(always)] + #visibility fn #user_name <'outer_borrow, ReturnType>( + &'outer_borrow mut self, + user: impl for<'this> ::core::ops::FnOnce(&'outer_borrow mut #field_type) -> ReturnType, + ) -> ReturnType { + user(&mut self. #field_name) + } + }); + } else if field.field_type == FieldType::Borrowed { + let user_name = format_ident!("with_{}", &field.name); + let documentation = format!( + concat!( + "Provides limited immutable access to `{0}`. This method was generated ", + "because the contents of `{0}` are immutably borrowed by other fields." + ), + field.name.to_string() + ); + let documentation = if !options.do_no_doc { + quote! { + #[doc=#documentation] + } + } else { + quote! { #[doc(hidden)] } + }; + users.push(quote! { + #documentation + #[inline(always)] + #visibility fn #user_name <'outer_borrow, ReturnType>( + &'outer_borrow self, + user: impl for<'this> ::core::ops::FnOnce(&'outer_borrow #field_type) -> ReturnType, + ) -> ReturnType { + user(&*self.#field_name) + } + }); + if field.self_referencing { + if field.covariant == Some(false) { + // Skip the other functions, they will cause compiler errors. + continue; + } else if field.covariant.is_none() { + field.covariance_error(); + } + } + let borrower_name = format_ident!("borrow_{}", &field.name); + users.push(quote! { + #documentation + #[inline(always)] + #visibility fn #borrower_name<'this>( + &'this self, + ) -> &'this #field_type { + &*self.#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 + // to get any other kinds of references to it. + } + } + Ok(users) +} diff --git a/src/info_structures.rs b/src/info_structures.rs new file mode 100644 index 0000000..05dc734 --- /dev/null +++ b/src/info_structures.rs @@ -0,0 +1,304 @@ +use crate::utils::{make_generic_arguments, make_generic_consumers, replace_this_with_lifetime}; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use syn::{ + punctuated::Punctuated, token::Comma, Attribute, ConstParam, Error, GenericParam, Generics, + LifetimeDef, Type, TypeParam, Visibility, +}; + +#[derive(Clone, Copy)] +pub struct Options { + pub do_no_doc: bool, + pub do_pub_extras: bool, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum FieldType { + /// Not borrowed by other parts of the struct. + Tail, + /// Immutably borrowed by at least one other field. + Borrowed, + /// Mutably borrowed by one other field. + BorrowedMut, +} + +impl FieldType { + pub fn is_tail(self) -> bool { + self == Self::Tail + } +} + +#[derive(Clone)] +pub struct BorrowRequest { + pub index: usize, + pub mutable: bool, +} + +#[derive(Clone)] +pub enum Derive { + Debug, + PartialEq, + Eq, +} + +#[derive(Copy, Clone)] +pub enum BuilderType { + Sync, + Async, + AsyncSend, +} + +impl BuilderType { + pub fn is_async(&self) -> bool { + match self { + BuilderType::Sync => false, + _ => true, + } + } +} + +#[derive(Clone)] +pub struct StructInfo { + pub derives: Vec<Derive>, + pub ident: Ident, + pub generics: Generics, + pub vis: Visibility, + pub fields: Vec<StructFieldInfo>, + pub first_lifetime: Ident, + pub attributes: Vec<Attribute>, +} + +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(); + } + + pub fn generic_params(&self) -> &Punctuated<GenericParam, Comma> { + &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> } + } + } + + /// Same as generic_params but without bounds and with '_ prepended twice. + pub fn borrowed_generic_params_inferred(&self) -> TokenStream { + use GenericParam::*; + let params = self.generic_params().iter().map(|p| match p { + Type(TypeParam { ident, .. }) | Const(ConstParam { ident, .. }) => { + ident.to_token_stream() + } + Lifetime(LifetimeDef { lifetime, .. }) => lifetime.to_token_stream(), + }); + quote! { <'_, '_, #(#params,)*> } + } + + pub fn generic_arguments(&self) -> Vec<TokenStream> { + make_generic_arguments(&self.generics) + } + + /// Same as generic_arguments but with 'outer_borrow and 'this prepended. + pub fn borrowed_generic_arguments(&self) -> Vec<TokenStream> { + 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<Item = (TokenStream, Ident)> { + make_generic_consumers(&self.generics) + } +} + +#[derive(Clone)] +pub struct StructFieldInfo { + pub name: Ident, + pub typ: Type, + pub field_type: FieldType, + pub vis: Visibility, + pub borrows: Vec<BorrowRequest>, + /// If this is true and borrows is empty, the struct will borrow from self in the future but + /// does not require a builder to be initialized. It should not be able to be removed from the + /// struct with into_heads. + pub self_referencing: bool, + /// If it is None, the user has not specified whether or not the field is covariant. If this is + /// Some(false), we should avoid making borrow_* or borrow_*_mut functions as they will not + /// be able to compile. + pub covariant: Option<bool>, +} + +#[derive(Clone)] +pub enum ArgType { + /// Used when the initial value of a field can be passed directly into the constructor. + Plain(TokenStream), + /// Used when a field requires self references and thus requires something that implements + /// a builder function trait instead of a simple plain type. + TraitBound(TokenStream), +} + +impl StructFieldInfo { + pub fn builder_name(&self) -> Ident { + format_ident!("{}_builder", self.name) + } + + pub fn illegal_ref_name(&self) -> Ident { + format_ident!("{}_illegal_static_reference", self.name) + } + + pub fn is_borrowed(&self) -> bool { + self.field_type != FieldType::Tail + } + + pub fn is_mutably_borrowed(&self) -> bool { + self.field_type == FieldType::BorrowedMut + } + + pub fn boxed(&self) -> TokenStream { + let name = &self.name; + quote! { ::ouroboros::macro_help::aliasable_boxed(#name) } + } + + pub fn stored_type(&self) -> TokenStream { + let t = &self.typ; + if self.is_borrowed() { + quote! { ::ouroboros::macro_help::AliasableBox<#t> } + } else { + quote! { #t } + } + } + + /// Returns code which takes a variable with the same name and type as this field and turns it + /// into a static reference to its dereffed contents. + pub fn make_illegal_static_reference(&self) -> TokenStream { + let field_name = &self.name; + let ref_name = self.illegal_ref_name(); + quote! { + let #ref_name = unsafe { + ::ouroboros::macro_help::change_lifetime(&*#field_name) + }; + } + } + + /// Like make_illegal_static_reference, but provides a mutable reference instead. + pub fn make_illegal_static_mut_reference(&self) -> TokenStream { + let field_name = &self.name; + let ref_name = self.illegal_ref_name(); + quote! { + let #ref_name = unsafe { + ::ouroboros::macro_help::change_lifetime_mut(&mut *#field_name) + }; + } + } + + /// Generates an error requesting that the user explicitly specify whether or not the + /// field's type is covariant. + pub fn covariance_error(&self) { + let error = concat!( + "Ouroboros cannot automatically determine if this type is covariant.\n\n", + "If it is covariant, it should be legal to convert any instance of that type to an ", + "instance of that type where all usages of 'this are replaced with a smaller ", + "lifetime. For example, Box<&'this i32> is covariant because it is legal to use it as ", + "a Box<&'a i32> where 'this: 'a. In contrast, Fn(&'this i32) cannot be used as ", + "Fn(&'a i32).\n\n", + "To resolve this error, add #[covariant] or #[not_covariant] to the field.\n", + ); + proc_macro_error::emit_error!(self.typ, error); + } + + pub fn make_constructor_arg_type_impl( + &self, + info: &StructInfo, + make_builder_return_type: impl FnOnce() -> TokenStream, + ) -> Result<ArgType, Error> { + let field_type = &self.typ; + let fake_lifetime = info.fake_lifetime(); + if self.borrows.is_empty() { + // Even if self_referencing is true, as long as borrows is empty, we don't need to use a + // builder to construct it. + let field_type = + replace_this_with_lifetime(field_type.into_token_stream(), fake_lifetime.clone()); + Ok(ArgType::Plain(quote! { #field_type })) + } else { + let mut field_builder_params = Vec::new(); + for borrow in &self.borrows { + if borrow.mutable { + let field = &info.fields[borrow.index]; + let field_type = &field.typ; + field_builder_params.push(quote! { + &'this mut #field_type + }); + } else { + let field = &info.fields[borrow.index]; + let field_type = &field.typ; + field_builder_params.push(quote! { + &'this #field_type + }); + } + } + let return_type = make_builder_return_type(); + let bound = quote! { for<'this> ::core::ops::FnOnce(#(#field_builder_params),*) -> #return_type }; + Ok(ArgType::TraitBound(bound)) + } + } + + /// Returns a trait bound if `for_field` refers to any other fields, and a plain type if not. This + /// is the type used in the constructor to initialize the value of `for_field`. + pub fn make_constructor_arg_type( + &self, + info: &StructInfo, + builder_type: BuilderType, + ) -> Result<ArgType, Error> { + let field_type = &self.typ; + let return_ty_constructor = || match builder_type { + BuilderType::AsyncSend => { + quote! { + ::core::pin::Pin<::ouroboros::macro_help::alloc::boxed::Box< + dyn ::core::future::Future<Output=#field_type> + ::core::marker::Send + 'this>> + } + } + BuilderType::Async => { + quote! { ::core::pin::Pin<::ouroboros::macro_help::alloc::boxed::Box< + dyn ::core::future::Future<Output=#field_type> + 'this>> } + } + BuilderType::Sync => quote! { #field_type }, + }; + self.make_constructor_arg_type_impl(info, return_ty_constructor) + } + + /// Like make_constructor_arg_type, but used for the try_new constructor. + pub fn make_try_constructor_arg_type( + &self, + info: &StructInfo, + builder_type: BuilderType, + ) -> Result<ArgType, Error> { + let field_type = &self.typ; + let return_ty_constructor = || match builder_type { + BuilderType::AsyncSend => { + quote! { + ::core::pin::Pin<::ouroboros::macro_help::alloc::boxed::Box< + dyn ::core::future::Future<Output=::core::result::Result<#field_type, Error_>> + + ::core::marker::Send + 'this>> + } + } + BuilderType::Async => { + quote! { + ::core::pin::Pin<::ouroboros::macro_help::alloc::boxed::Box< + dyn ::core::future::Future<Output=::core::result::Result<#field_type, Error_>> + + 'this>> + } + } + BuilderType::Sync => quote! { ::core::result::Result<#field_type, Error_> }, + }; + self.make_constructor_arg_type_impl(info, return_ty_constructor) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cd12419 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,165 @@ +extern crate proc_macro; + +mod covariance_detection; +mod generate; +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, + 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, + }, + info_structures::Options, + parse::parse_struct, +}; +use inflector::Inflector; +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}; + +fn self_referencing_impl( + original_struct_def: &ItemStruct, + options: Options, +) -> Result<TokenStream, Error> { + let struct_name = &original_struct_def.ident; + let mod_name = format_ident!("ouroboros_impl_{}", struct_name.to_string().to_snake_case()); + let visibility = &original_struct_def.vis; + + let info = parse_struct(original_struct_def)?; + + let actual_struct_def = create_actual_struct_def(&info)?; + + let borrowchk_summoner = generate_checker_summoner(&info)?; + + let (builder_struct_name, builder_def, constructor_def) = + create_builder_and_constructor(&info, options, BuilderType::Sync)?; + let (async_builder_struct_name, async_builder_def, async_constructor_def) = + create_builder_and_constructor(&info, options, BuilderType::Async)?; + let (async_send_builder_struct_name, async_send_builder_def, async_send_constructor_def) = + create_builder_and_constructor(&info, options, BuilderType::AsyncSend)?; + let (try_builder_struct_name, try_builder_def, try_constructor_def) = + 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 with_defs = make_with_functions(&info, options)?; + let (with_all_struct_defs, with_all_fn_defs) = make_with_all_function(&info, options)?; + let (heads_struct_def, into_heads_fn) = make_into_heads(&info, options); + + let impls = create_derives(&info)?; + + // These check that types like Box, Arc, and Rc refer to those types in the std lib and have not + // been overridden. + let type_asserts_def = make_type_asserts(&info); + + let extra_visibility = if options.do_pub_extras { + visibility.clone() + } else { + syn::Visibility::Inherited + }; + + let generic_params = info.generic_params(); + let generic_args = info.generic_arguments(); + let generic_where = &info.generics.where_clause; + Ok(TokenStream::from(quote! { + #[doc="Encapsulates implementation details for a self-referencing struct. This module is only visible when using --document-private-items."] + mod #mod_name { + use super::*; + #[doc="The self-referencing struct."] + #actual_struct_def + #borrowchk_summoner + #builder_def + #async_builder_def + #async_send_builder_def + #try_builder_def + #async_try_builder_def + #async_send_try_builder_def + #with_all_struct_defs + #heads_struct_def + #impls + impl <#generic_params> #struct_name <#(#generic_args),*> #generic_where { + #constructor_def + #async_constructor_def + #async_send_constructor_def + #try_constructor_def + #async_try_constructor_def + #async_send_try_constructor_def + #(#with_defs)* + #with_all_fn_defs + #into_heads_fn + } + #type_asserts_def + } + #visibility use #mod_name :: #struct_name; + #extra_visibility use #mod_name :: #builder_struct_name; + #extra_visibility use #mod_name :: #async_builder_struct_name; + #extra_visibility use #mod_name :: #async_send_builder_struct_name; + #extra_visibility use #mod_name :: #try_builder_struct_name; + #extra_visibility use #mod_name :: #async_try_builder_struct_name; + #extra_visibility use #mod_name :: #async_send_try_builder_struct_name; + })) +} + +#[proc_macro_error] +#[proc_macro_attribute] +pub fn self_referencing(attr: TokenStream, item: TokenStream) -> TokenStream { + let mut options = Options { + do_no_doc: false, + do_pub_extras: false, + }; + let mut expecting_comma = false; + for token in <TokenStream as std::convert::Into<TokenStream2>>::into(attr).into_iter() { + if let TokenTree::Ident(ident) = &token { + if expecting_comma { + return Error::new(token.span(), "Unexpected identifier, expected comma.") + .to_compile_error() + .into(); + } + match &ident.to_string()[..] { + "no_doc" => options.do_no_doc = true, + "pub_extras" => options.do_pub_extras = true, + _ => { + return Error::new_spanned( + &ident, + "Unknown identifier, expected 'no_doc' or 'pub_extras'.", + ) + .to_compile_error() + .into() + } + } + expecting_comma = true; + } else if let TokenTree::Punct(punct) = &token { + if !expecting_comma { + return Error::new(token.span(), "Unexpected punctuation, expected identifier.") + .to_compile_error() + .into(); + } + if punct.as_char() != ',' { + return Error::new(token.span(), "Unknown punctuation, expected comma.") + .to_compile_error() + .into(); + } + expecting_comma = false; + } else { + return Error::new(token.span(), "Unknown syntax, expected identifier.") + .to_compile_error() + .into(); + } + } + let original_struct_def: ItemStruct = syn::parse_macro_input!(item); + match self_referencing_impl(&original_struct_def, options) { + Ok(content) => content, + Err(err) => err.to_compile_error().into(), + } +} diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..546aa7c --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,271 @@ +use proc_macro2::{Delimiter, Span, TokenTree}; +use quote::format_ident; +use syn::{spanned::Spanned, Attribute, Error, Fields, GenericParam, ItemStruct}; + +use crate::{ + covariance_detection::type_is_covariant_over_this_lifetime, + info_structures::{BorrowRequest, Derive, FieldType, StructFieldInfo, StructInfo}, + utils::submodule_contents_visiblity, +}; + +fn handle_borrows_attr( + field_info: &mut [StructFieldInfo], + attr: &Attribute, + borrows: &mut Vec<BorrowRequest>, +) -> 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); + }; + for token in tokens { + if let TokenTree::Ident(ident) = token { + if waiting_for_comma { + return Err(Error::new_spanned(&ident, "Expected comma.")); + } + let istr = ident.to_string(); + if istr == "mut" { + if borrow_mut { + return Err(Error::new_spanned(&ident, "Unexpected double 'mut'")); + } + borrow_mut = true; + } else { + let index = field_info.iter().position(|item| item.name == istr); + let index = if let Some(v) = index { + v + } else { + return Err(Error::new_spanned( + &ident, + concat!( + "Unknown identifier, make sure that it is spelled ", + "correctly and defined above the location it is borrowed." + ), + )); + }; + if borrow_mut { + if field_info[index].field_type == FieldType::Borrowed { + return Err(Error::new_spanned( + &ident, + "Cannot borrow mutably, this field was previously borrowed immutably.", + )); + } + if field_info[index].field_type == FieldType::BorrowedMut { + return Err(Error::new_spanned(&ident, "Cannot borrow mutably twice.")); + } + field_info[index].field_type = FieldType::BorrowedMut; + } else { + if field_info[index].field_type == FieldType::BorrowedMut { + return Err(Error::new_spanned( + &ident, + "Cannot borrow as immutable as it was previously borrowed mutably.", + )); + } + field_info[index].field_type = FieldType::Borrowed; + } + borrows.push(BorrowRequest { + index, + mutable: borrow_mut, + }); + waiting_for_comma = true; + borrow_mut = false; + } + } else if let TokenTree::Punct(punct) = token { + if punct.as_char() == ',' { + if waiting_for_comma { + waiting_for_comma = false; + } else { + return Err(Error::new_spanned(&punct, "Unexpected extra comma.")); + } + } else { + return Err(Error::new_spanned( + &punct, + "Unexpected punctuation, expected comma or identifier.", + )); + } + } else { + return Err(Error::new_spanned( + &token, + "Unexpected token, expected comma or identifier.", + )); + } + } + Ok(()) +} + +fn parse_derive_token(token: &TokenTree) -> Result<Option<Derive>, Error> { + match token { + TokenTree::Ident(ident) => match &ident.to_string()[..] { + "Debug" => Ok(Some(Derive::Debug)), + "PartialEq" => Ok(Some(Derive::PartialEq)), + "Eq" => Ok(Some(Derive::Eq)), + _ => Err(Error::new( + ident.span(), + format!("{} cannot be derived for self-referencing structs", ident), + )), + }, + TokenTree::Punct(..) => Ok(None), + _ => Err(Error::new(token.span(), "bad syntax")), + } +} + +fn parse_derive_attribute(attr: &Attribute) -> Result<Vec<Derive>, 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); + } + } + Ok(derives) + } else { + Err(Error::new(attr.span(), "bad syntax")) + } +} + +pub fn parse_struct(def: &ItemStruct) -> Result<StructInfo, Error> { + let vis = def.vis.clone(); + let generics = def.generics.clone(); + let mut actual_struct_def = def.clone(); + actual_struct_def.vis = vis.clone(); + let mut fields = Vec::new(); + match &mut actual_struct_def.fields { + Fields::Named(def_fields) => { + for field in &mut def_fields.named { + let mut borrows = Vec::new(); + let mut self_referencing = false; + 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; + if path.leading_colon.is_some() { + continue; + } + if path.segments.len() != 1 { + continue; + } + if path.segments.first().unwrap().ident == "borrows" { + if self_referencing { + panic!("TODO: Nice error, used #[borrows()] twice."); + } + self_referencing = true; + handle_borrows_attr(&mut fields[..], attr, &mut borrows)?; + remove_attrs.push(index); + } + if path.segments.first().unwrap().ident == "covariant" { + if covariant.is_some() { + panic!("TODO: Nice error, covariance specified twice."); + } + covariant = Some(true); + remove_attrs.push(index); + } + if path.segments.first().unwrap().ident == "not_covariant" { + if covariant.is_some() { + panic!("TODO: Nice error, covariance specified twice."); + } + covariant = Some(false); + remove_attrs.push(index); + } + } + // 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()); + fields.push(StructFieldInfo { + name: field.ident.clone().expect("Named field has no name."), + typ: field.ty.clone(), + field_type: FieldType::Tail, + vis: with_vis, + borrows, + self_referencing, + covariant, + }); + } + } + Fields::Unnamed(_fields) => { + return Err(Error::new( + Span::call_site(), + "Tuple structs are not supported yet.", + )) + } + Fields::Unit => { + return Err(Error::new( + Span::call_site(), + "Unit structs cannot be self-referential.", + )) + } + } + if fields.len() < 2 { + return Err(Error::new( + Span::call_site(), + "Self-referencing structs must have at least 2 fields.", + )); + } + let mut has_non_tail = false; + for field in &fields { + if !field.field_type.is_tail() { + has_non_tail = true; + break; + } + } + if !has_non_tail { + return Err(Error::new( + Span::call_site(), + &format!( + concat!( + "Self-referencing struct cannot be made entirely of tail fields, try adding ", + "#[borrows({0})] to a field defined after {0}." + ), + fields[0].name + ), + )); + } + let first_lifetime = if let Some(GenericParam::Lifetime(param)) = generics.params.first() { + param.lifetime.ident.clone() + } else { + format_ident!("static") + }; + 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 name = p[0].ident.to_string(); + let good = match &name[..] { + "clippy" | "allow" | "deny" | "doc" => true, + _ => false, + }; + if good { + attributes.push(attr.clone()) + } else if name == "derive" { + if derives.len() > 0 { + return Err(Error::new( + attr.span(), + "Multiple derive attributes not allowed", + )); + } else { + derives = parse_derive_attribute(attr)?; + } + } else { + return Err(Error::new(p.span(), &format!("Unsupported attribute"))); + } + } + + return Ok(StructInfo { + derives, + ident: def.ident.clone(), + generics: def.generics.clone(), + fields, + vis, + first_lifetime, + attributes, + }); +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..5bbae6c --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,140 @@ +use inflector::Inflector; +use proc_macro2::{Group, Ident, TokenStream, TokenTree}; +use quote::{format_ident, quote}; +use syn::{GenericParam, Generics, Visibility}; + +/// Makes phantom data definitions so that we don't get unused template parameter errors. +pub fn make_generic_consumers(generics: &Generics) -> impl Iterator<Item = (TokenStream, Ident)> { + generics + .params + .clone() + .into_iter() + .map(|param| match param { + GenericParam::Type(ty) => { + let ident = &ty.ident; + ( + quote! { #ident }, + format_ident!( + "_consume_template_type_{}", + ident.to_string().to_snake_case() + ), + ) + } + GenericParam::Lifetime(lt) => { + let lifetime = <.lifetime; + let ident = &lifetime.ident; + ( + quote! { &#lifetime () }, + format_ident!("_consume_template_lifetime_{}", ident), + ) + } + GenericParam::Const(..) => unimplemented!(), + }) +} + +// Takes the generics parameters from the original struct and turns them into arguments. +pub fn make_generic_arguments(generics: &Generics) -> Vec<TokenStream> { + let mut arguments = Vec::new(); + for generic in generics.params.clone() { + match generic { + GenericParam::Type(typ) => { + let ident = &typ.ident; + arguments.push(quote! { #ident }); + } + GenericParam::Lifetime(lt) => { + let lifetime = <.lifetime; + arguments.push(quote! { #lifetime }); + } + GenericParam::Const(_) => unimplemented!("Const generics are not supported yet."), + } + } + arguments +} + +pub fn uses_this_lifetime(input: TokenStream) -> bool { + for token in input.into_iter() { + match token { + TokenTree::Ident(ident) => { + if ident == "this" { + return true; + } + } + TokenTree::Group(group) => { + if uses_this_lifetime(group.stream()) { + return true; + } + } + _ => (), + } + } + false +} + +pub fn replace_this_with_lifetime(input: TokenStream, lifetime: Ident) -> TokenStream { + input + .into_iter() + .map(|token| match &token { + TokenTree::Ident(ident) => { + if ident == "this" { + TokenTree::Ident(lifetime.clone()) + } else { + token + } + } + TokenTree::Group(group) => TokenTree::Group(Group::new( + group.delimiter(), + replace_this_with_lifetime(group.stream(), lifetime.clone()), + )), + _ => token, + }) + .collect() +} + +pub fn submodule_contents_visiblity(original_visibility: &Visibility) -> Visibility { + match original_visibility { + // inherited: allow parent of inner submodule to see + Visibility::Inherited => syn::parse_quote! { pub(super) }, + // restricted: add an extra super if needed + Visibility::Restricted(ref restricted) => { + let is_first_component_super = restricted + .path + .segments + .first() + .map(|segm| segm.ident == "super") + .unwrap_or(false); + if restricted.path.leading_colon.is_none() && is_first_component_super { + let mut new_visibility = restricted.clone(); + 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 }) + .chain(restricted.path.segments.iter().cloned()) + .collect(); + Visibility::Restricted(new_visibility) + } else { + original_visibility.clone() + } + } + // others are absolute, can use them as-is + _ => original_visibility.clone(), + } +} + +/// Functionality inspired by `Inflector`, reimplemented here to avoid the +/// `regex` dependency. +pub fn to_class_case(s: &str) -> String { + s.split('_') + .flat_map(|word| { + let mut chars = word.chars(); + let first = chars.next(); + // Unicode allows for a single character to become multiple characters when converting between cases. + first + .into_iter() + .flat_map(|c| c.to_uppercase()) + .chain(chars.flat_map(|c| c.to_lowercase())) + }) + .collect() +} |