summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaurice Lam <yukl@google.com>2023-02-10 18:31:00 +0000
committerMaurice Lam <yukl@google.com>2023-02-10 18:31:00 +0000
commit1fbdf0606023c8471a93c2cb444467534d189192 (patch)
tree800b5f2dae5d2893308a5d8af4eca58ddc4bc272
parenta26c1b916da3abe386063b3597e9bb01d570d01e (diff)
downloadouroboros_macro-1fbdf0606023c8471a93c2cb444467534d189192.tar.gz
Import ouroboros_macro crate
Bug: 267375624 Test: None, no build files added yet Change-Id: I5c75467da301dda127e5aaf136272722adff5930
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Cargo.toml43
-rw-r--r--Cargo.toml.orig22
l---------LICENSE1
-rw-r--r--LICENSE_APACHE202
-rw-r--r--LICENSE_MIT22
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS1
-rw-r--r--src/covariance_detection.rs142
-rw-r--r--src/generate/constructor.rs202
-rw-r--r--src/generate/derives.rs98
-rw-r--r--src/generate/into_heads.rs81
-rw-r--r--src/generate/mod.rs9
-rw-r--r--src/generate/struc.rs50
-rw-r--r--src/generate/summon_checker.rs62
-rw-r--r--src/generate/try_constructor.rs301
-rw-r--r--src/generate/type_asserts.rs41
-rw-r--r--src/generate/with_all.rs134
-rw-r--r--src/generate/with_each.rs132
-rw-r--r--src/info_structures.rs304
-rw-r--r--src/lib.rs165
-rw-r--r--src/parse.rs271
-rw-r--r--src/utils.rs140
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 = []
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..6d46dcc
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -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(&ltname, 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 = &lt.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 = &lt.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()
+}