diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/thiserror-impl | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/thiserror-impl')
-rw-r--r-- | third_party/rust/thiserror-impl/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/Cargo.toml | 35 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/LICENSE-APACHE | 201 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/LICENSE-MIT | 23 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/src/ast.rs | 165 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/src/attr.rs | 213 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/src/expand.rs | 546 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/src/fmt.rs | 170 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/src/generics.rs | 83 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/src/lib.rs | 35 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/src/prop.rs | 135 | ||||
-rw-r--r-- | third_party/rust/thiserror-impl/src/valid.rs | 237 |
12 files changed, 1844 insertions, 0 deletions
diff --git a/third_party/rust/thiserror-impl/.cargo-checksum.json b/third_party/rust/thiserror-impl/.cargo-checksum.json new file mode 100644 index 0000000000..4005c4c074 --- /dev/null +++ b/third_party/rust/thiserror-impl/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"5431537713726199373bc09d8044cde83911dcf02c38a03534f1ea937be2e333","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","src/ast.rs":"c7601e8394f5ee304365c520181f0dbeaa807ddaa71ce4a8618ea1a70f81b3b2","src/attr.rs":"d1fe75dde04cb8df6da4e1aaec5b38c2b0f634fa3432877993b3fec4cc08b6b5","src/expand.rs":"8e2bc7ca24e8f8d9b179b792e772d3699154b750a08dcb942be298b7cc032563","src/fmt.rs":"d63d39120c18712596f9f2a1715821148c2becd4d8bad5bc1b307210a84dbe98","src/generics.rs":"2076cde22271be355a8131a77add4b93f83ab0af4317cd2df5471fffa4f95c66","src/lib.rs":"7d023310cd3db670554ce108a6afd94e1ae3c55c83661d4b9fcebdf1865b9e4b","src/prop.rs":"6709932aee8f9d217f40cd644629c0ecb2f46d333ae8a1398e8d745534f4e028","src/valid.rs":"ac95253944fd360d3578d0643a7baabb2cfa6bf9fbced7a6ce1f7b0529a3bb98"},"package":"1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"}
\ No newline at end of file diff --git a/third_party/rust/thiserror-impl/Cargo.toml b/third_party/rust/thiserror-impl/Cargo.toml new file mode 100644 index 0000000000..ca2a0b4f4d --- /dev/null +++ b/third_party/rust/thiserror-impl/Cargo.toml @@ -0,0 +1,35 @@ +# 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" +rust-version = "1.31" +name = "thiserror-impl" +version = "1.0.38" +authors = ["David Tolnay <dtolnay@gmail.com>"] +description = "Implementation detail of the `thiserror` crate" +license = "MIT OR Apache-2.0" +repository = "https://github.com/dtolnay/thiserror" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +proc-macro = true + +[dependencies.proc-macro2] +version = "1.0" + +[dependencies.quote] +version = "1.0" + +[dependencies.syn] +version = "1.0.45" diff --git a/third_party/rust/thiserror-impl/LICENSE-APACHE b/third_party/rust/thiserror-impl/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/third_party/rust/thiserror-impl/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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 [yyyy] [name of copyright owner] + +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/third_party/rust/thiserror-impl/LICENSE-MIT b/third_party/rust/thiserror-impl/LICENSE-MIT new file mode 100644 index 0000000000..31aa79387f --- /dev/null +++ b/third_party/rust/thiserror-impl/LICENSE-MIT @@ -0,0 +1,23 @@ +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/third_party/rust/thiserror-impl/src/ast.rs b/third_party/rust/thiserror-impl/src/ast.rs new file mode 100644 index 0000000000..2aa7246c82 --- /dev/null +++ b/third_party/rust/thiserror-impl/src/ast.rs @@ -0,0 +1,165 @@ +use crate::attr::{self, Attrs}; +use crate::generics::ParamsInScope; +use proc_macro2::Span; +use syn::{ + Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Generics, Ident, Index, Member, Result, + Type, +}; + +pub enum Input<'a> { + Struct(Struct<'a>), + Enum(Enum<'a>), +} + +pub struct Struct<'a> { + pub original: &'a DeriveInput, + pub attrs: Attrs<'a>, + pub ident: Ident, + pub generics: &'a Generics, + pub fields: Vec<Field<'a>>, +} + +pub struct Enum<'a> { + pub original: &'a DeriveInput, + pub attrs: Attrs<'a>, + pub ident: Ident, + pub generics: &'a Generics, + pub variants: Vec<Variant<'a>>, +} + +pub struct Variant<'a> { + pub original: &'a syn::Variant, + pub attrs: Attrs<'a>, + pub ident: Ident, + pub fields: Vec<Field<'a>>, +} + +pub struct Field<'a> { + pub original: &'a syn::Field, + pub attrs: Attrs<'a>, + pub member: Member, + pub ty: &'a Type, + pub contains_generic: bool, +} + +impl<'a> Input<'a> { + pub fn from_syn(node: &'a DeriveInput) -> Result<Self> { + match &node.data { + Data::Struct(data) => Struct::from_syn(node, data).map(Input::Struct), + Data::Enum(data) => Enum::from_syn(node, data).map(Input::Enum), + Data::Union(_) => Err(Error::new_spanned( + node, + "union as errors are not supported", + )), + } + } +} + +impl<'a> Struct<'a> { + fn from_syn(node: &'a DeriveInput, data: &'a DataStruct) -> Result<Self> { + let mut attrs = attr::get(&node.attrs)?; + let scope = ParamsInScope::new(&node.generics); + let span = attrs.span().unwrap_or_else(Span::call_site); + let fields = Field::multiple_from_syn(&data.fields, &scope, span)?; + if let Some(display) = &mut attrs.display { + display.expand_shorthand(&fields); + } + Ok(Struct { + original: node, + attrs, + ident: node.ident.clone(), + generics: &node.generics, + fields, + }) + } +} + +impl<'a> Enum<'a> { + fn from_syn(node: &'a DeriveInput, data: &'a DataEnum) -> Result<Self> { + let attrs = attr::get(&node.attrs)?; + let scope = ParamsInScope::new(&node.generics); + let span = attrs.span().unwrap_or_else(Span::call_site); + let variants = data + .variants + .iter() + .map(|node| { + let mut variant = Variant::from_syn(node, &scope, span)?; + if let display @ None = &mut variant.attrs.display { + *display = attrs.display.clone(); + } + if let Some(display) = &mut variant.attrs.display { + display.expand_shorthand(&variant.fields); + } else if variant.attrs.transparent.is_none() { + variant.attrs.transparent = attrs.transparent; + } + Ok(variant) + }) + .collect::<Result<_>>()?; + Ok(Enum { + original: node, + attrs, + ident: node.ident.clone(), + generics: &node.generics, + variants, + }) + } +} + +impl<'a> Variant<'a> { + fn from_syn(node: &'a syn::Variant, scope: &ParamsInScope<'a>, span: Span) -> Result<Self> { + let attrs = attr::get(&node.attrs)?; + let span = attrs.span().unwrap_or(span); + Ok(Variant { + original: node, + attrs, + ident: node.ident.clone(), + fields: Field::multiple_from_syn(&node.fields, scope, span)?, + }) + } +} + +impl<'a> Field<'a> { + fn multiple_from_syn( + fields: &'a Fields, + scope: &ParamsInScope<'a>, + span: Span, + ) -> Result<Vec<Self>> { + fields + .iter() + .enumerate() + .map(|(i, field)| Field::from_syn(i, field, scope, span)) + .collect() + } + + fn from_syn( + i: usize, + node: &'a syn::Field, + scope: &ParamsInScope<'a>, + span: Span, + ) -> Result<Self> { + Ok(Field { + original: node, + attrs: attr::get(&node.attrs)?, + member: node.ident.clone().map(Member::Named).unwrap_or_else(|| { + Member::Unnamed(Index { + index: i as u32, + span, + }) + }), + ty: &node.ty, + contains_generic: scope.intersects(&node.ty), + }) + } +} + +impl Attrs<'_> { + pub fn span(&self) -> Option<Span> { + if let Some(display) = &self.display { + Some(display.fmt.span()) + } else if let Some(transparent) = &self.transparent { + Some(transparent.span) + } else { + None + } + } +} diff --git a/third_party/rust/thiserror-impl/src/attr.rs b/third_party/rust/thiserror-impl/src/attr.rs new file mode 100644 index 0000000000..9963fd6dbf --- /dev/null +++ b/third_party/rust/thiserror-impl/src/attr.rs @@ -0,0 +1,213 @@ +use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree}; +use quote::{format_ident, quote, ToTokens}; +use std::collections::BTreeSet as Set; +use std::iter::FromIterator; +use syn::parse::{Nothing, ParseStream}; +use syn::{ + braced, bracketed, parenthesized, token, Attribute, Error, Ident, Index, LitInt, LitStr, + Result, Token, +}; + +pub struct Attrs<'a> { + pub display: Option<Display<'a>>, + pub source: Option<&'a Attribute>, + pub backtrace: Option<&'a Attribute>, + pub from: Option<&'a Attribute>, + pub transparent: Option<Transparent<'a>>, +} + +#[derive(Clone)] +pub struct Display<'a> { + pub original: &'a Attribute, + pub fmt: LitStr, + pub args: TokenStream, + pub has_bonus_display: bool, + pub implied_bounds: Set<(usize, Trait)>, +} + +#[derive(Copy, Clone)] +pub struct Transparent<'a> { + pub original: &'a Attribute, + pub span: Span, +} + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] +pub enum Trait { + Debug, + Display, + Octal, + LowerHex, + UpperHex, + Pointer, + Binary, + LowerExp, + UpperExp, +} + +pub fn get(input: &[Attribute]) -> Result<Attrs> { + let mut attrs = Attrs { + display: None, + source: None, + backtrace: None, + from: None, + transparent: None, + }; + + for attr in input { + if attr.path.is_ident("error") { + parse_error_attribute(&mut attrs, attr)?; + } else if attr.path.is_ident("source") { + require_empty_attribute(attr)?; + if attrs.source.is_some() { + return Err(Error::new_spanned(attr, "duplicate #[source] attribute")); + } + attrs.source = Some(attr); + } else if attr.path.is_ident("backtrace") { + require_empty_attribute(attr)?; + if attrs.backtrace.is_some() { + return Err(Error::new_spanned(attr, "duplicate #[backtrace] attribute")); + } + attrs.backtrace = Some(attr); + } else if attr.path.is_ident("from") { + if !attr.tokens.is_empty() { + // Assume this is meant for derive_more crate or something. + continue; + } + if attrs.from.is_some() { + return Err(Error::new_spanned(attr, "duplicate #[from] attribute")); + } + attrs.from = Some(attr); + } + } + + Ok(attrs) +} + +fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Result<()> { + syn::custom_keyword!(transparent); + + attr.parse_args_with(|input: ParseStream| { + if let Some(kw) = input.parse::<Option<transparent>>()? { + if attrs.transparent.is_some() { + return Err(Error::new_spanned( + attr, + "duplicate #[error(transparent)] attribute", + )); + } + attrs.transparent = Some(Transparent { + original: attr, + span: kw.span, + }); + return Ok(()); + } + + let display = Display { + original: attr, + fmt: input.parse()?, + args: parse_token_expr(input, false)?, + has_bonus_display: false, + implied_bounds: Set::new(), + }; + if attrs.display.is_some() { + return Err(Error::new_spanned( + attr, + "only one #[error(...)] attribute is allowed", + )); + } + attrs.display = Some(display); + Ok(()) + }) +} + +fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result<TokenStream> { + let mut tokens = Vec::new(); + while !input.is_empty() { + if begin_expr && input.peek(Token![.]) { + if input.peek2(Ident) { + input.parse::<Token![.]>()?; + begin_expr = false; + continue; + } + if input.peek2(LitInt) { + input.parse::<Token![.]>()?; + let int: Index = input.parse()?; + let ident = format_ident!("_{}", int.index, span = int.span); + tokens.push(TokenTree::Ident(ident)); + begin_expr = false; + continue; + } + } + + begin_expr = input.peek(Token![break]) + || input.peek(Token![continue]) + || input.peek(Token![if]) + || input.peek(Token![in]) + || input.peek(Token![match]) + || input.peek(Token![mut]) + || input.peek(Token![return]) + || input.peek(Token![while]) + || input.peek(Token![+]) + || input.peek(Token![&]) + || input.peek(Token![!]) + || input.peek(Token![^]) + || input.peek(Token![,]) + || input.peek(Token![/]) + || input.peek(Token![=]) + || input.peek(Token![>]) + || input.peek(Token![<]) + || input.peek(Token![|]) + || input.peek(Token![%]) + || input.peek(Token![;]) + || input.peek(Token![*]) + || input.peek(Token![-]); + + let token: TokenTree = if input.peek(token::Paren) { + let content; + let delimiter = parenthesized!(content in input); + let nested = parse_token_expr(&content, true)?; + let mut group = Group::new(Delimiter::Parenthesis, nested); + group.set_span(delimiter.span); + TokenTree::Group(group) + } else if input.peek(token::Brace) { + let content; + let delimiter = braced!(content in input); + let nested = parse_token_expr(&content, true)?; + let mut group = Group::new(Delimiter::Brace, nested); + group.set_span(delimiter.span); + TokenTree::Group(group) + } else if input.peek(token::Bracket) { + let content; + let delimiter = bracketed!(content in input); + let nested = parse_token_expr(&content, true)?; + let mut group = Group::new(Delimiter::Bracket, nested); + group.set_span(delimiter.span); + TokenTree::Group(group) + } else { + input.parse()? + }; + tokens.push(token); + } + Ok(TokenStream::from_iter(tokens)) +} + +fn require_empty_attribute(attr: &Attribute) -> Result<()> { + syn::parse2::<Nothing>(attr.tokens.clone())?; + Ok(()) +} + +impl ToTokens for Display<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let fmt = &self.fmt; + let args = &self.args; + tokens.extend(quote! { + write!(__formatter, #fmt #args) + }); + } +} + +impl ToTokens for Trait { + fn to_tokens(&self, tokens: &mut TokenStream) { + let trait_name = format_ident!("{}", format!("{:?}", self)); + tokens.extend(quote!(std::fmt::#trait_name)); + } +} diff --git a/third_party/rust/thiserror-impl/src/expand.rs b/third_party/rust/thiserror-impl/src/expand.rs new file mode 100644 index 0000000000..43522096a1 --- /dev/null +++ b/third_party/rust/thiserror-impl/src/expand.rs @@ -0,0 +1,546 @@ +use crate::ast::{Enum, Field, Input, Struct}; +use crate::attr::Trait; +use crate::generics::InferredBounds; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, quote_spanned, ToTokens}; +use std::collections::BTreeSet as Set; +use syn::spanned::Spanned; +use syn::{ + Data, DeriveInput, GenericArgument, Member, PathArguments, Result, Token, Type, Visibility, +}; + +pub fn derive(node: &DeriveInput) -> Result<TokenStream> { + let input = Input::from_syn(node)?; + input.validate()?; + Ok(match input { + Input::Struct(input) => impl_struct(input), + Input::Enum(input) => impl_enum(input), + }) +} + +fn impl_struct(input: Struct) -> TokenStream { + let ty = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let mut error_inferred_bounds = InferredBounds::new(); + + let source_body = if input.attrs.transparent.is_some() { + let only_field = &input.fields[0]; + if only_field.contains_generic { + error_inferred_bounds.insert(only_field.ty, quote!(std::error::Error)); + } + let member = &only_field.member; + Some(quote! { + std::error::Error::source(self.#member.as_dyn_error()) + }) + } else if let Some(source_field) = input.source_field() { + let source = &source_field.member; + if source_field.contains_generic { + let ty = unoptional_type(source_field.ty); + error_inferred_bounds.insert(ty, quote!(std::error::Error + 'static)); + } + let asref = if type_is_option(source_field.ty) { + Some(quote_spanned!(source.span()=> .as_ref()?)) + } else { + None + }; + let dyn_error = quote_spanned!(source.span()=> self.#source #asref.as_dyn_error()); + Some(quote! { + std::option::Option::Some(#dyn_error) + }) + } else { + None + }; + let source_method = source_body.map(|body| { + quote! { + fn source(&self) -> std::option::Option<&(dyn std::error::Error + 'static)> { + use thiserror::__private::AsDynError; + #body + } + } + }); + + let provide_method = input.backtrace_field().map(|backtrace_field| { + let demand = quote!(demand); + let backtrace = &backtrace_field.member; + let body = if let Some(source_field) = input.source_field() { + let source = &source_field.member; + let source_provide = if type_is_option(source_field.ty) { + quote_spanned! {source.span()=> + if let std::option::Option::Some(source) = &self.#source { + source.thiserror_provide(#demand); + } + } + } else { + quote_spanned! {source.span()=> + self.#source.thiserror_provide(#demand); + } + }; + let self_provide = if source == backtrace { + None + } else if type_is_option(backtrace_field.ty) { + Some(quote! { + if let std::option::Option::Some(backtrace) = &self.#backtrace { + #demand.provide_ref::<std::backtrace::Backtrace>(backtrace); + } + }) + } else { + Some(quote! { + #demand.provide_ref::<std::backtrace::Backtrace>(&self.#backtrace); + }) + }; + quote! { + use thiserror::__private::ThiserrorProvide; + #source_provide + #self_provide + } + } else if type_is_option(backtrace_field.ty) { + quote! { + if let std::option::Option::Some(backtrace) = &self.#backtrace { + #demand.provide_ref::<std::backtrace::Backtrace>(backtrace); + } + } + } else { + quote! { + #demand.provide_ref::<std::backtrace::Backtrace>(&self.#backtrace); + } + }; + quote! { + fn provide<'_demand>(&'_demand self, #demand: &mut std::any::Demand<'_demand>) { + #body + } + } + }); + + let mut display_implied_bounds = Set::new(); + let display_body = if input.attrs.transparent.is_some() { + let only_field = &input.fields[0].member; + display_implied_bounds.insert((0, Trait::Display)); + Some(quote! { + std::fmt::Display::fmt(&self.#only_field, __formatter) + }) + } else if let Some(display) = &input.attrs.display { + display_implied_bounds = display.implied_bounds.clone(); + let use_as_display = if display.has_bonus_display { + Some(quote! { + #[allow(unused_imports)] + use thiserror::__private::{DisplayAsDisplay, PathAsDisplay}; + }) + } else { + None + }; + let pat = fields_pat(&input.fields); + Some(quote! { + #use_as_display + #[allow(unused_variables, deprecated)] + let Self #pat = self; + #display + }) + } else { + None + }; + let display_impl = display_body.map(|body| { + let mut display_inferred_bounds = InferredBounds::new(); + for (field, bound) in display_implied_bounds { + let field = &input.fields[field]; + if field.contains_generic { + display_inferred_bounds.insert(field.ty, bound); + } + } + let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics); + quote! { + #[allow(unused_qualifications)] + impl #impl_generics std::fmt::Display for #ty #ty_generics #display_where_clause { + #[allow(clippy::used_underscore_binding)] + fn fmt(&self, __formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + #body + } + } + } + }); + + let from_impl = input.from_field().map(|from_field| { + let backtrace_field = input.distinct_backtrace_field(); + let from = unoptional_type(from_field.ty); + let body = from_initializer(from_field, backtrace_field); + quote! { + #[allow(unused_qualifications)] + impl #impl_generics std::convert::From<#from> for #ty #ty_generics #where_clause { + #[allow(deprecated)] + fn from(source: #from) -> Self { + #ty #body + } + } + } + }); + + let error_trait = spanned_error_trait(input.original); + if input.generics.type_params().next().is_some() { + let self_token = <Token![Self]>::default(); + error_inferred_bounds.insert(self_token, Trait::Debug); + error_inferred_bounds.insert(self_token, Trait::Display); + } + let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics); + + quote! { + #[allow(unused_qualifications)] + impl #impl_generics #error_trait for #ty #ty_generics #error_where_clause { + #source_method + #provide_method + } + #display_impl + #from_impl + } +} + +fn impl_enum(input: Enum) -> TokenStream { + let ty = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let mut error_inferred_bounds = InferredBounds::new(); + + let source_method = if input.has_source() { + let arms = input.variants.iter().map(|variant| { + let ident = &variant.ident; + if variant.attrs.transparent.is_some() { + let only_field = &variant.fields[0]; + if only_field.contains_generic { + error_inferred_bounds.insert(only_field.ty, quote!(std::error::Error)); + } + let member = &only_field.member; + let source = quote!(std::error::Error::source(transparent.as_dyn_error())); + quote! { + #ty::#ident {#member: transparent} => #source, + } + } else if let Some(source_field) = variant.source_field() { + let source = &source_field.member; + if source_field.contains_generic { + let ty = unoptional_type(source_field.ty); + error_inferred_bounds.insert(ty, quote!(std::error::Error + 'static)); + } + let asref = if type_is_option(source_field.ty) { + Some(quote_spanned!(source.span()=> .as_ref()?)) + } else { + None + }; + let varsource = quote!(source); + let dyn_error = quote_spanned!(source.span()=> #varsource #asref.as_dyn_error()); + quote! { + #ty::#ident {#source: #varsource, ..} => std::option::Option::Some(#dyn_error), + } + } else { + quote! { + #ty::#ident {..} => std::option::Option::None, + } + } + }); + Some(quote! { + fn source(&self) -> std::option::Option<&(dyn std::error::Error + 'static)> { + use thiserror::__private::AsDynError; + #[allow(deprecated)] + match self { + #(#arms)* + } + } + }) + } else { + None + }; + + let provide_method = if input.has_backtrace() { + let demand = quote!(demand); + let arms = input.variants.iter().map(|variant| { + let ident = &variant.ident; + match (variant.backtrace_field(), variant.source_field()) { + (Some(backtrace_field), Some(source_field)) + if backtrace_field.attrs.backtrace.is_none() => + { + let backtrace = &backtrace_field.member; + let source = &source_field.member; + let varsource = quote!(source); + let source_provide = if type_is_option(source_field.ty) { + quote_spanned! {source.span()=> + if let std::option::Option::Some(source) = #varsource { + source.thiserror_provide(#demand); + } + } + } else { + quote_spanned! {source.span()=> + #varsource.thiserror_provide(#demand); + } + }; + let self_provide = if type_is_option(backtrace_field.ty) { + quote! { + if let std::option::Option::Some(backtrace) = backtrace { + #demand.provide_ref::<std::backtrace::Backtrace>(backtrace); + } + } + } else { + quote! { + #demand.provide_ref::<std::backtrace::Backtrace>(backtrace); + } + }; + quote! { + #ty::#ident { + #backtrace: backtrace, + #source: #varsource, + .. + } => { + use thiserror::__private::ThiserrorProvide; + #source_provide + #self_provide + } + } + } + (Some(backtrace_field), Some(source_field)) + if backtrace_field.member == source_field.member => + { + let backtrace = &backtrace_field.member; + let varsource = quote!(source); + let source_provide = if type_is_option(source_field.ty) { + quote_spanned! {backtrace.span()=> + if let std::option::Option::Some(source) = #varsource { + source.thiserror_provide(#demand); + } + } + } else { + quote_spanned! {backtrace.span()=> + #varsource.thiserror_provide(#demand); + } + }; + quote! { + #ty::#ident {#backtrace: #varsource, ..} => { + use thiserror::__private::ThiserrorProvide; + #source_provide + } + } + } + (Some(backtrace_field), _) => { + let backtrace = &backtrace_field.member; + let body = if type_is_option(backtrace_field.ty) { + quote! { + if let std::option::Option::Some(backtrace) = backtrace { + #demand.provide_ref::<std::backtrace::Backtrace>(backtrace); + } + } + } else { + quote! { + #demand.provide_ref::<std::backtrace::Backtrace>(backtrace); + } + }; + quote! { + #ty::#ident {#backtrace: backtrace, ..} => { + #body + } + } + } + (None, _) => quote! { + #ty::#ident {..} => {} + }, + } + }); + Some(quote! { + fn provide<'_demand>(&'_demand self, #demand: &mut std::any::Demand<'_demand>) { + #[allow(deprecated)] + match self { + #(#arms)* + } + } + }) + } else { + None + }; + + let display_impl = if input.has_display() { + let mut display_inferred_bounds = InferredBounds::new(); + let use_as_display = if input.variants.iter().any(|v| { + v.attrs + .display + .as_ref() + .map_or(false, |display| display.has_bonus_display) + }) { + Some(quote! { + #[allow(unused_imports)] + use thiserror::__private::{DisplayAsDisplay, PathAsDisplay}; + }) + } else { + None + }; + let void_deref = if input.variants.is_empty() { + Some(quote!(*)) + } else { + None + }; + let arms = input.variants.iter().map(|variant| { + let mut display_implied_bounds = Set::new(); + let display = match &variant.attrs.display { + Some(display) => { + display_implied_bounds = display.implied_bounds.clone(); + display.to_token_stream() + } + None => { + let only_field = match &variant.fields[0].member { + Member::Named(ident) => ident.clone(), + Member::Unnamed(index) => format_ident!("_{}", index), + }; + display_implied_bounds.insert((0, Trait::Display)); + quote!(std::fmt::Display::fmt(#only_field, __formatter)) + } + }; + for (field, bound) in display_implied_bounds { + let field = &variant.fields[field]; + if field.contains_generic { + display_inferred_bounds.insert(field.ty, bound); + } + } + let ident = &variant.ident; + let pat = fields_pat(&variant.fields); + quote! { + #ty::#ident #pat => #display + } + }); + let arms = arms.collect::<Vec<_>>(); + let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics); + Some(quote! { + #[allow(unused_qualifications)] + impl #impl_generics std::fmt::Display for #ty #ty_generics #display_where_clause { + fn fmt(&self, __formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + #use_as_display + #[allow(unused_variables, deprecated, clippy::used_underscore_binding)] + match #void_deref self { + #(#arms,)* + } + } + } + }) + } else { + None + }; + + let from_impls = input.variants.iter().filter_map(|variant| { + let from_field = variant.from_field()?; + let backtrace_field = variant.distinct_backtrace_field(); + let variant = &variant.ident; + let from = unoptional_type(from_field.ty); + let body = from_initializer(from_field, backtrace_field); + Some(quote! { + #[allow(unused_qualifications)] + impl #impl_generics std::convert::From<#from> for #ty #ty_generics #where_clause { + #[allow(deprecated)] + fn from(source: #from) -> Self { + #ty::#variant #body + } + } + }) + }); + + let error_trait = spanned_error_trait(input.original); + if input.generics.type_params().next().is_some() { + let self_token = <Token![Self]>::default(); + error_inferred_bounds.insert(self_token, Trait::Debug); + error_inferred_bounds.insert(self_token, Trait::Display); + } + let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics); + + quote! { + #[allow(unused_qualifications)] + impl #impl_generics #error_trait for #ty #ty_generics #error_where_clause { + #source_method + #provide_method + } + #display_impl + #(#from_impls)* + } +} + +fn fields_pat(fields: &[Field]) -> TokenStream { + let mut members = fields.iter().map(|field| &field.member).peekable(); + match members.peek() { + Some(Member::Named(_)) => quote!({ #(#members),* }), + Some(Member::Unnamed(_)) => { + let vars = members.map(|member| match member { + Member::Unnamed(member) => format_ident!("_{}", member), + Member::Named(_) => unreachable!(), + }); + quote!((#(#vars),*)) + } + None => quote!({}), + } +} + +fn from_initializer(from_field: &Field, backtrace_field: Option<&Field>) -> TokenStream { + let from_member = &from_field.member; + let some_source = if type_is_option(from_field.ty) { + quote!(std::option::Option::Some(source)) + } else { + quote!(source) + }; + let backtrace = backtrace_field.map(|backtrace_field| { + let backtrace_member = &backtrace_field.member; + if type_is_option(backtrace_field.ty) { + quote! { + #backtrace_member: std::option::Option::Some(std::backtrace::Backtrace::capture()), + } + } else { + quote! { + #backtrace_member: std::convert::From::from(std::backtrace::Backtrace::capture()), + } + } + }); + quote!({ + #from_member: #some_source, + #backtrace + }) +} + +fn type_is_option(ty: &Type) -> bool { + type_parameter_of_option(ty).is_some() +} + +fn unoptional_type(ty: &Type) -> TokenStream { + let unoptional = type_parameter_of_option(ty).unwrap_or(ty); + quote!(#unoptional) +} + +fn type_parameter_of_option(ty: &Type) -> Option<&Type> { + let path = match ty { + Type::Path(ty) => &ty.path, + _ => return None, + }; + + let last = path.segments.last().unwrap(); + if last.ident != "Option" { + return None; + } + + let bracketed = match &last.arguments { + PathArguments::AngleBracketed(bracketed) => bracketed, + _ => return None, + }; + + if bracketed.args.len() != 1 { + return None; + } + + match &bracketed.args[0] { + GenericArgument::Type(arg) => Some(arg), + _ => None, + } +} + +fn spanned_error_trait(input: &DeriveInput) -> TokenStream { + let vis_span = match &input.vis { + Visibility::Public(vis) => Some(vis.pub_token.span()), + Visibility::Crate(vis) => Some(vis.crate_token.span()), + Visibility::Restricted(vis) => Some(vis.pub_token.span()), + Visibility::Inherited => None, + }; + let data_span = match &input.data { + Data::Struct(data) => data.struct_token.span(), + Data::Enum(data) => data.enum_token.span(), + Data::Union(data) => data.union_token.span(), + }; + let first_span = vis_span.unwrap_or(data_span); + let last_span = input.ident.span(); + let path = quote_spanned!(first_span=> std::error::); + let error = quote_spanned!(last_span=> Error); + quote!(#path #error) +} diff --git a/third_party/rust/thiserror-impl/src/fmt.rs b/third_party/rust/thiserror-impl/src/fmt.rs new file mode 100644 index 0000000000..807dfb9677 --- /dev/null +++ b/third_party/rust/thiserror-impl/src/fmt.rs @@ -0,0 +1,170 @@ +use crate::ast::Field; +use crate::attr::{Display, Trait}; +use proc_macro2::TokenTree; +use quote::{format_ident, quote_spanned}; +use std::collections::{BTreeSet as Set, HashMap as Map}; +use syn::ext::IdentExt; +use syn::parse::{ParseStream, Parser}; +use syn::{Ident, Index, LitStr, Member, Result, Token}; + +impl Display<'_> { + // Transform `"error {var}"` to `"error {}", var`. + pub fn expand_shorthand(&mut self, fields: &[Field]) { + let raw_args = self.args.clone(); + let mut named_args = explicit_named_args.parse2(raw_args).unwrap(); + let mut member_index = Map::new(); + for (i, field) in fields.iter().enumerate() { + member_index.insert(&field.member, i); + } + + let span = self.fmt.span(); + let fmt = self.fmt.value(); + let mut read = fmt.as_str(); + let mut out = String::new(); + let mut args = self.args.clone(); + let mut has_bonus_display = false; + let mut implied_bounds = Set::new(); + + let mut has_trailing_comma = false; + if let Some(TokenTree::Punct(punct)) = args.clone().into_iter().last() { + if punct.as_char() == ',' { + has_trailing_comma = true; + } + } + + while let Some(brace) = read.find('{') { + out += &read[..brace + 1]; + read = &read[brace + 1..]; + if read.starts_with('{') { + out.push('{'); + read = &read[1..]; + continue; + } + let next = match read.chars().next() { + Some(next) => next, + None => return, + }; + let member = match next { + '0'..='9' => { + let int = take_int(&mut read); + let member = match int.parse::<u32>() { + Ok(index) => Member::Unnamed(Index { index, span }), + Err(_) => return, + }; + if !member_index.contains_key(&member) { + out += ∫ + continue; + } + member + } + 'a'..='z' | 'A'..='Z' | '_' => { + let mut ident = take_ident(&mut read); + ident.set_span(span); + Member::Named(ident) + } + _ => continue, + }; + if let Some(&field) = member_index.get(&member) { + let end_spec = match read.find('}') { + Some(end_spec) => end_spec, + None => return, + }; + let bound = match read[..end_spec].chars().next_back() { + Some('?') => Trait::Debug, + Some('o') => Trait::Octal, + Some('x') => Trait::LowerHex, + Some('X') => Trait::UpperHex, + Some('p') => Trait::Pointer, + Some('b') => Trait::Binary, + Some('e') => Trait::LowerExp, + Some('E') => Trait::UpperExp, + Some(_) | None => Trait::Display, + }; + implied_bounds.insert((field, bound)); + } + let local = match &member { + Member::Unnamed(index) => format_ident!("_{}", index), + Member::Named(ident) => ident.clone(), + }; + let mut formatvar = local.clone(); + if formatvar.to_string().starts_with("r#") { + formatvar = format_ident!("r_{}", formatvar); + } + if formatvar.to_string().starts_with('_') { + // Work around leading underscore being rejected by 1.40 and + // older compilers. https://github.com/rust-lang/rust/pull/66847 + formatvar = format_ident!("field_{}", formatvar); + } + out += &formatvar.to_string(); + if !named_args.insert(formatvar.clone()) { + // Already specified in the format argument list. + continue; + } + if !has_trailing_comma { + args.extend(quote_spanned!(span=> ,)); + } + args.extend(quote_spanned!(span=> #formatvar = #local)); + if read.starts_with('}') && member_index.contains_key(&member) { + has_bonus_display = true; + args.extend(quote_spanned!(span=> .as_display())); + } + has_trailing_comma = false; + } + + out += read; + self.fmt = LitStr::new(&out, self.fmt.span()); + self.args = args; + self.has_bonus_display = has_bonus_display; + self.implied_bounds = implied_bounds; + } +} + +fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> { + let mut named_args = Set::new(); + + while !input.is_empty() { + if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) { + input.parse::<Token![,]>()?; + let ident = input.call(Ident::parse_any)?; + input.parse::<Token![=]>()?; + named_args.insert(ident); + } else { + input.parse::<TokenTree>()?; + } + } + + Ok(named_args) +} + +fn take_int(read: &mut &str) -> String { + let mut int = String::new(); + for (i, ch) in read.char_indices() { + match ch { + '0'..='9' => int.push(ch), + _ => { + *read = &read[i..]; + break; + } + } + } + int +} + +fn take_ident(read: &mut &str) -> Ident { + let mut ident = String::new(); + let raw = read.starts_with("r#"); + if raw { + ident.push_str("r#"); + *read = &read[2..]; + } + for (i, ch) in read.char_indices() { + match ch { + 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch), + _ => { + *read = &read[i..]; + break; + } + } + } + Ident::parse_any.parse_str(&ident).unwrap() +} diff --git a/third_party/rust/thiserror-impl/src/generics.rs b/third_party/rust/thiserror-impl/src/generics.rs new file mode 100644 index 0000000000..95592a7370 --- /dev/null +++ b/third_party/rust/thiserror-impl/src/generics.rs @@ -0,0 +1,83 @@ +use proc_macro2::TokenStream; +use quote::ToTokens; +use std::collections::btree_map::Entry; +use std::collections::{BTreeMap as Map, BTreeSet as Set}; +use syn::punctuated::Punctuated; +use syn::{parse_quote, GenericArgument, Generics, Ident, PathArguments, Token, Type, WhereClause}; + +pub struct ParamsInScope<'a> { + names: Set<&'a Ident>, +} + +impl<'a> ParamsInScope<'a> { + pub fn new(generics: &'a Generics) -> Self { + ParamsInScope { + names: generics.type_params().map(|param| ¶m.ident).collect(), + } + } + + pub fn intersects(&self, ty: &Type) -> bool { + let mut found = false; + crawl(self, ty, &mut found); + found + } +} + +fn crawl(in_scope: &ParamsInScope, ty: &Type, found: &mut bool) { + if let Type::Path(ty) = ty { + if ty.qself.is_none() { + if let Some(ident) = ty.path.get_ident() { + if in_scope.names.contains(ident) { + *found = true; + } + } + } + for segment in &ty.path.segments { + if let PathArguments::AngleBracketed(arguments) = &segment.arguments { + for arg in &arguments.args { + if let GenericArgument::Type(ty) = arg { + crawl(in_scope, ty, found); + } + } + } + } + } +} + +pub struct InferredBounds { + bounds: Map<String, (Set<String>, Punctuated<TokenStream, Token![+]>)>, + order: Vec<TokenStream>, +} + +impl InferredBounds { + pub fn new() -> Self { + InferredBounds { + bounds: Map::new(), + order: Vec::new(), + } + } + + #[allow(clippy::type_repetition_in_bounds, clippy::trait_duplication_in_bounds)] // clippy bug: https://github.com/rust-lang/rust-clippy/issues/8771 + pub fn insert(&mut self, ty: impl ToTokens, bound: impl ToTokens) { + let ty = ty.to_token_stream(); + let bound = bound.to_token_stream(); + let entry = self.bounds.entry(ty.to_string()); + if let Entry::Vacant(_) = entry { + self.order.push(ty); + } + let (set, tokens) = entry.or_default(); + if set.insert(bound.to_string()) { + tokens.push(bound); + } + } + + pub fn augment_where_clause(&self, generics: &Generics) -> WhereClause { + let mut generics = generics.clone(); + let where_clause = generics.make_where_clause(); + for ty in &self.order { + let (_set, bounds) = &self.bounds[&ty.to_string()]; + where_clause.predicates.push(parse_quote!(#ty: #bounds)); + } + generics.where_clause.unwrap() + } +} diff --git a/third_party/rust/thiserror-impl/src/lib.rs b/third_party/rust/thiserror-impl/src/lib.rs new file mode 100644 index 0000000000..f0fc969170 --- /dev/null +++ b/third_party/rust/thiserror-impl/src/lib.rs @@ -0,0 +1,35 @@ +#![allow( + clippy::blocks_in_if_conditions, + clippy::cast_lossless, + clippy::cast_possible_truncation, + clippy::manual_find, + clippy::manual_map, + clippy::map_unwrap_or, + clippy::needless_pass_by_value, + clippy::option_if_let_else, + clippy::range_plus_one, + clippy::single_match_else, + clippy::too_many_lines, + clippy::wrong_self_convention +)] + +extern crate proc_macro; + +mod ast; +mod attr; +mod expand; +mod fmt; +mod generics; +mod prop; +mod valid; + +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(Error, attributes(backtrace, error, from, source))] +pub fn derive_error(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + expand::derive(&input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/third_party/rust/thiserror-impl/src/prop.rs b/third_party/rust/thiserror-impl/src/prop.rs new file mode 100644 index 0000000000..6d8a924c11 --- /dev/null +++ b/third_party/rust/thiserror-impl/src/prop.rs @@ -0,0 +1,135 @@ +use crate::ast::{Enum, Field, Struct, Variant}; +use syn::{Member, Type}; + +impl Struct<'_> { + pub(crate) fn from_field(&self) -> Option<&Field> { + from_field(&self.fields) + } + + pub(crate) fn source_field(&self) -> Option<&Field> { + source_field(&self.fields) + } + + pub(crate) fn backtrace_field(&self) -> Option<&Field> { + backtrace_field(&self.fields) + } + + pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> { + let backtrace_field = self.backtrace_field()?; + distinct_backtrace_field(backtrace_field, self.from_field()) + } +} + +impl Enum<'_> { + pub(crate) fn has_source(&self) -> bool { + self.variants + .iter() + .any(|variant| variant.source_field().is_some() || variant.attrs.transparent.is_some()) + } + + pub(crate) fn has_backtrace(&self) -> bool { + self.variants + .iter() + .any(|variant| variant.backtrace_field().is_some()) + } + + pub(crate) fn has_display(&self) -> bool { + self.attrs.display.is_some() + || self.attrs.transparent.is_some() + || self + .variants + .iter() + .any(|variant| variant.attrs.display.is_some()) + || self + .variants + .iter() + .all(|variant| variant.attrs.transparent.is_some()) + } +} + +impl Variant<'_> { + pub(crate) fn from_field(&self) -> Option<&Field> { + from_field(&self.fields) + } + + pub(crate) fn source_field(&self) -> Option<&Field> { + source_field(&self.fields) + } + + pub(crate) fn backtrace_field(&self) -> Option<&Field> { + backtrace_field(&self.fields) + } + + pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> { + let backtrace_field = self.backtrace_field()?; + distinct_backtrace_field(backtrace_field, self.from_field()) + } +} + +impl Field<'_> { + pub(crate) fn is_backtrace(&self) -> bool { + type_is_backtrace(self.ty) + } +} + +fn from_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { + for field in fields { + if field.attrs.from.is_some() { + return Some(field); + } + } + None +} + +fn source_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { + for field in fields { + if field.attrs.from.is_some() || field.attrs.source.is_some() { + return Some(field); + } + } + for field in fields { + match &field.member { + Member::Named(ident) if ident == "source" => return Some(field), + _ => {} + } + } + None +} + +fn backtrace_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { + for field in fields { + if field.attrs.backtrace.is_some() { + return Some(field); + } + } + for field in fields { + if field.is_backtrace() { + return Some(field); + } + } + None +} + +// The #[backtrace] field, if it is not the same as the #[from] field. +fn distinct_backtrace_field<'a, 'b>( + backtrace_field: &'a Field<'b>, + from_field: Option<&Field>, +) -> Option<&'a Field<'b>> { + if from_field.map_or(false, |from_field| { + from_field.member == backtrace_field.member + }) { + None + } else { + Some(backtrace_field) + } +} + +fn type_is_backtrace(ty: &Type) -> bool { + let path = match ty { + Type::Path(ty) => &ty.path, + _ => return false, + }; + + let last = path.segments.last().unwrap(); + last.ident == "Backtrace" && last.arguments.is_empty() +} diff --git a/third_party/rust/thiserror-impl/src/valid.rs b/third_party/rust/thiserror-impl/src/valid.rs new file mode 100644 index 0000000000..cf5b8592c2 --- /dev/null +++ b/third_party/rust/thiserror-impl/src/valid.rs @@ -0,0 +1,237 @@ +use crate::ast::{Enum, Field, Input, Struct, Variant}; +use crate::attr::Attrs; +use quote::ToTokens; +use std::collections::BTreeSet as Set; +use syn::{Error, GenericArgument, Member, PathArguments, Result, Type}; + +impl Input<'_> { + pub(crate) fn validate(&self) -> Result<()> { + match self { + Input::Struct(input) => input.validate(), + Input::Enum(input) => input.validate(), + } + } +} + +impl Struct<'_> { + fn validate(&self) -> Result<()> { + check_non_field_attrs(&self.attrs)?; + if let Some(transparent) = self.attrs.transparent { + if self.fields.len() != 1 { + return Err(Error::new_spanned( + transparent.original, + "#[error(transparent)] requires exactly one field", + )); + } + if let Some(source) = self.fields.iter().find_map(|f| f.attrs.source) { + return Err(Error::new_spanned( + source, + "transparent error struct can't contain #[source]", + )); + } + } + check_field_attrs(&self.fields)?; + for field in &self.fields { + field.validate()?; + } + Ok(()) + } +} + +impl Enum<'_> { + fn validate(&self) -> Result<()> { + check_non_field_attrs(&self.attrs)?; + let has_display = self.has_display(); + for variant in &self.variants { + variant.validate()?; + if has_display && variant.attrs.display.is_none() && variant.attrs.transparent.is_none() + { + return Err(Error::new_spanned( + variant.original, + "missing #[error(\"...\")] display attribute", + )); + } + } + let mut from_types = Set::new(); + for variant in &self.variants { + if let Some(from_field) = variant.from_field() { + let repr = from_field.ty.to_token_stream().to_string(); + if !from_types.insert(repr) { + return Err(Error::new_spanned( + from_field.original, + "cannot derive From because another variant has the same source type", + )); + } + } + } + Ok(()) + } +} + +impl Variant<'_> { + fn validate(&self) -> Result<()> { + check_non_field_attrs(&self.attrs)?; + if self.attrs.transparent.is_some() { + if self.fields.len() != 1 { + return Err(Error::new_spanned( + self.original, + "#[error(transparent)] requires exactly one field", + )); + } + if let Some(source) = self.fields.iter().find_map(|f| f.attrs.source) { + return Err(Error::new_spanned( + source, + "transparent variant can't contain #[source]", + )); + } + } + check_field_attrs(&self.fields)?; + for field in &self.fields { + field.validate()?; + } + Ok(()) + } +} + +impl Field<'_> { + fn validate(&self) -> Result<()> { + if let Some(display) = &self.attrs.display { + return Err(Error::new_spanned( + display.original, + "not expected here; the #[error(...)] attribute belongs on top of a struct or an enum variant", + )); + } + Ok(()) + } +} + +fn check_non_field_attrs(attrs: &Attrs) -> Result<()> { + if let Some(from) = &attrs.from { + return Err(Error::new_spanned( + from, + "not expected here; the #[from] attribute belongs on a specific field", + )); + } + if let Some(source) = &attrs.source { + return Err(Error::new_spanned( + source, + "not expected here; the #[source] attribute belongs on a specific field", + )); + } + if let Some(backtrace) = &attrs.backtrace { + return Err(Error::new_spanned( + backtrace, + "not expected here; the #[backtrace] attribute belongs on a specific field", + )); + } + if let Some(display) = &attrs.display { + if attrs.transparent.is_some() { + return Err(Error::new_spanned( + display.original, + "cannot have both #[error(transparent)] and a display attribute", + )); + } + } + Ok(()) +} + +fn check_field_attrs(fields: &[Field]) -> Result<()> { + let mut from_field = None; + let mut source_field = None; + let mut backtrace_field = None; + let mut has_backtrace = false; + for field in fields { + if let Some(from) = field.attrs.from { + if from_field.is_some() { + return Err(Error::new_spanned(from, "duplicate #[from] attribute")); + } + from_field = Some(field); + } + if let Some(source) = field.attrs.source { + if source_field.is_some() { + return Err(Error::new_spanned(source, "duplicate #[source] attribute")); + } + source_field = Some(field); + } + if let Some(backtrace) = field.attrs.backtrace { + if backtrace_field.is_some() { + return Err(Error::new_spanned( + backtrace, + "duplicate #[backtrace] attribute", + )); + } + backtrace_field = Some(field); + has_backtrace = true; + } + if let Some(transparent) = field.attrs.transparent { + return Err(Error::new_spanned( + transparent.original, + "#[error(transparent)] needs to go outside the enum or struct, not on an individual field", + )); + } + has_backtrace |= field.is_backtrace(); + } + if let (Some(from_field), Some(source_field)) = (from_field, source_field) { + if !same_member(from_field, source_field) { + return Err(Error::new_spanned( + from_field.attrs.from, + "#[from] is only supported on the source field, not any other field", + )); + } + } + if let Some(from_field) = from_field { + let max_expected_fields = match backtrace_field { + Some(backtrace_field) => 1 + !same_member(from_field, backtrace_field) as usize, + None => 1 + has_backtrace as usize, + }; + if fields.len() > max_expected_fields { + return Err(Error::new_spanned( + from_field.attrs.from, + "deriving From requires no fields other than source and backtrace", + )); + } + } + if let Some(source_field) = source_field.or(from_field) { + if contains_non_static_lifetime(source_field.ty) { + return Err(Error::new_spanned( + &source_field.original.ty, + "non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static", + )); + } + } + Ok(()) +} + +fn same_member(one: &Field, two: &Field) -> bool { + match (&one.member, &two.member) { + (Member::Named(one), Member::Named(two)) => one == two, + (Member::Unnamed(one), Member::Unnamed(two)) => one.index == two.index, + _ => unreachable!(), + } +} + +fn contains_non_static_lifetime(ty: &Type) -> bool { + match ty { + Type::Path(ty) => { + let bracketed = match &ty.path.segments.last().unwrap().arguments { + PathArguments::AngleBracketed(bracketed) => bracketed, + _ => return false, + }; + for arg in &bracketed.args { + match arg { + GenericArgument::Type(ty) if contains_non_static_lifetime(ty) => return true, + GenericArgument::Lifetime(lifetime) if lifetime.ident != "static" => { + return true + } + _ => {} + } + } + false + } + Type::Reference(ty) => ty + .lifetime + .as_ref() + .map_or(false, |lifetime| lifetime.ident != "static"), + _ => false, // maybe implement later if there are common other cases + } +} |