summaryrefslogtreecommitdiff
path: root/vendor/thiserror-impl/src/fmt.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/thiserror-impl/src/fmt.rs')
-rw-r--r--vendor/thiserror-impl/src/fmt.rs322
1 files changed, 322 insertions, 0 deletions
diff --git a/vendor/thiserror-impl/src/fmt.rs b/vendor/thiserror-impl/src/fmt.rs
new file mode 100644
index 00000000..1da28c19
--- /dev/null
+++ b/vendor/thiserror-impl/src/fmt.rs
@@ -0,0 +1,322 @@
+use crate::ast::{ContainerKind, Field};
+use crate::attr::{Display, Trait};
+use crate::scan_expr::scan_expr;
+use crate::unraw::{IdentUnraw, MemberUnraw};
+use proc_macro2::{Delimiter, TokenStream, TokenTree};
+use quote::{format_ident, quote, quote_spanned, ToTokens as _};
+use std::collections::{BTreeSet, HashMap};
+use std::iter;
+use syn::ext::IdentExt;
+use syn::parse::discouraged::Speculative;
+use syn::parse::{Error, ParseStream, Parser, Result};
+use syn::{Expr, Ident, Index, LitStr, Token};
+
+impl Display<'_> {
+ pub fn expand_shorthand(&mut self, fields: &[Field], container: ContainerKind) -> Result<()> {
+ let raw_args = self.args.clone();
+ let FmtArguments {
+ named: user_named_args,
+ first_unnamed,
+ } = explicit_named_args.parse2(raw_args).unwrap();
+
+ let mut member_index = HashMap::new();
+ let mut extra_positional_arguments_allowed = true;
+ for (i, field) in fields.iter().enumerate() {
+ member_index.insert(&field.member, i);
+ extra_positional_arguments_allowed &= matches!(&field.member, MemberUnraw::Named(_));
+ }
+
+ let span = self.fmt.span();
+ let fmt = self.fmt.value();
+ let mut read = fmt.as_str();
+ let mut out = String::new();
+ let mut has_bonus_display = false;
+ let mut infinite_recursive = false;
+ let mut implied_bounds = BTreeSet::new();
+ let mut bindings = Vec::new();
+ let mut macro_named_args = BTreeSet::new();
+
+ self.requires_fmt_machinery = self.requires_fmt_machinery || fmt.contains('}');
+
+ while let Some(brace) = read.find('{') {
+ self.requires_fmt_machinery = true;
+ 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 Ok(()),
+ };
+ let member = match next {
+ '0'..='9' => {
+ let int = take_int(&mut read);
+ if !extra_positional_arguments_allowed {
+ if let Some(first_unnamed) = &first_unnamed {
+ let msg = format!("ambiguous reference to positional arguments by number in a {container}; change this to a named argument");
+ return Err(Error::new_spanned(first_unnamed, msg));
+ }
+ }
+ match int.parse::<u32>() {
+ Ok(index) => MemberUnraw::Unnamed(Index { index, span }),
+ Err(_) => return Ok(()),
+ }
+ }
+ 'a'..='z' | 'A'..='Z' | '_' => {
+ if read.starts_with("r#") {
+ continue;
+ }
+ let repr = take_ident(&mut read);
+ if repr == "_" {
+ // Invalid. Let rustc produce the diagnostic.
+ out += repr;
+ continue;
+ }
+ let ident = IdentUnraw::new(Ident::new(repr, span));
+ if user_named_args.contains(&ident) {
+ // Refers to a named argument written by the user, not to field.
+ out += repr;
+ continue;
+ }
+ MemberUnraw::Named(ident)
+ }
+ _ => continue,
+ };
+ let end_spec = match read.find('}') {
+ Some(end_spec) => end_spec,
+ None => return Ok(()),
+ };
+ let mut bonus_display = false;
+ 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(_) => Trait::Display,
+ None => {
+ bonus_display = true;
+ has_bonus_display = true;
+ Trait::Display
+ }
+ };
+ infinite_recursive |= member == *"self" && bound == Trait::Display;
+ let field = match member_index.get(&member) {
+ Some(&field) => field,
+ None => {
+ out += &member.to_string();
+ continue;
+ }
+ };
+ implied_bounds.insert((field, bound));
+ let formatvar_prefix = if bonus_display {
+ "__display"
+ } else if bound == Trait::Pointer {
+ "__pointer"
+ } else {
+ "__field"
+ };
+ let mut formatvar = IdentUnraw::new(match &member {
+ MemberUnraw::Unnamed(index) => format_ident!("{}{}", formatvar_prefix, index),
+ MemberUnraw::Named(ident) => {
+ format_ident!("{}_{}", formatvar_prefix, ident.to_string())
+ }
+ });
+ while user_named_args.contains(&formatvar) {
+ formatvar = IdentUnraw::new(format_ident!("_{}", formatvar.to_string()));
+ }
+ formatvar.set_span(span);
+ out += &formatvar.to_string();
+ if !macro_named_args.insert(formatvar.clone()) {
+ // Already added to bindings by a previous use.
+ continue;
+ }
+ let mut binding_value = match &member {
+ MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
+ MemberUnraw::Named(ident) => ident.to_local(),
+ };
+ binding_value.set_span(span.resolved_at(fields[field].member.span()));
+ let wrapped_binding_value = if bonus_display {
+ quote_spanned!(span=> #binding_value.as_display())
+ } else if bound == Trait::Pointer {
+ quote!(::thiserror::__private::Var(#binding_value))
+ } else {
+ binding_value.into_token_stream()
+ };
+ bindings.push((formatvar.to_local(), wrapped_binding_value));
+ }
+
+ out += read;
+ self.fmt = LitStr::new(&out, self.fmt.span());
+ self.has_bonus_display = has_bonus_display;
+ self.infinite_recursive = infinite_recursive;
+ self.implied_bounds = implied_bounds;
+ self.bindings = bindings;
+ Ok(())
+ }
+}
+
+struct FmtArguments {
+ named: BTreeSet<IdentUnraw>,
+ first_unnamed: Option<TokenStream>,
+}
+
+#[allow(clippy::unnecessary_wraps)]
+fn explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
+ let ahead = input.fork();
+ if let Ok(set) = try_explicit_named_args(&ahead) {
+ input.advance_to(&ahead);
+ return Ok(set);
+ }
+
+ let ahead = input.fork();
+ if let Ok(set) = fallback_explicit_named_args(&ahead) {
+ input.advance_to(&ahead);
+ return Ok(set);
+ }
+
+ input.parse::<TokenStream>().unwrap();
+ Ok(FmtArguments {
+ named: BTreeSet::new(),
+ first_unnamed: None,
+ })
+}
+
+fn try_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
+ let mut syn_full = None;
+ let mut args = FmtArguments {
+ named: BTreeSet::new(),
+ first_unnamed: None,
+ };
+
+ while !input.is_empty() {
+ input.parse::<Token![,]>()?;
+ if input.is_empty() {
+ break;
+ }
+
+ let mut begin_unnamed = None;
+ if input.peek(Ident::peek_any) && input.peek2(Token![=]) && !input.peek2(Token![==]) {
+ let ident: IdentUnraw = input.parse()?;
+ input.parse::<Token![=]>()?;
+ args.named.insert(ident);
+ } else {
+ begin_unnamed = Some(input.fork());
+ }
+
+ let ahead = input.fork();
+ if *syn_full.get_or_insert_with(is_syn_full) && ahead.parse::<Expr>().is_ok() {
+ input.advance_to(&ahead);
+ } else {
+ scan_expr(input)?;
+ }
+
+ if let Some(begin_unnamed) = begin_unnamed {
+ if args.first_unnamed.is_none() {
+ args.first_unnamed = Some(between(&begin_unnamed, input));
+ }
+ }
+ }
+
+ Ok(args)
+}
+
+fn fallback_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
+ let mut args = FmtArguments {
+ named: BTreeSet::new(),
+ first_unnamed: None,
+ };
+
+ while !input.is_empty() {
+ if input.peek(Token![,])
+ && input.peek2(Ident::peek_any)
+ && input.peek3(Token![=])
+ && !input.peek3(Token![==])
+ {
+ input.parse::<Token![,]>()?;
+ let ident: IdentUnraw = input.parse()?;
+ input.parse::<Token![=]>()?;
+ args.named.insert(ident);
+ } else {
+ input.parse::<TokenTree>()?;
+ }
+ }
+
+ Ok(args)
+}
+
+fn is_syn_full() -> bool {
+ // Expr::Block contains syn::Block which contains Vec<syn::Stmt>. In the
+ // current version of Syn, syn::Stmt is exhaustive and could only plausibly
+ // represent `trait Trait {}` in Stmt::Item which contains syn::Item. Most
+ // of the point of syn's non-"full" mode is to avoid compiling Item and the
+ // entire expansive syntax tree it comprises. So the following expression
+ // being parsed to Expr::Block is a reliable indication that "full" is
+ // enabled.
+ let test = quote!({
+ trait Trait {}
+ });
+ match syn::parse2(test) {
+ Ok(Expr::Verbatim(_)) | Err(_) => false,
+ Ok(Expr::Block(_)) => true,
+ Ok(_) => unreachable!(),
+ }
+}
+
+fn take_int<'a>(read: &mut &'a str) -> &'a str {
+ let mut int_len = 0;
+ for ch in read.chars() {
+ match ch {
+ '0'..='9' => int_len += 1,
+ _ => break,
+ }
+ }
+ let (int, rest) = read.split_at(int_len);
+ *read = rest;
+ int
+}
+
+fn take_ident<'a>(read: &mut &'a str) -> &'a str {
+ let mut ident_len = 0;
+ for ch in read.chars() {
+ match ch {
+ 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident_len += 1,
+ _ => break,
+ }
+ }
+ let (ident, rest) = read.split_at(ident_len);
+ *read = rest;
+ ident
+}
+
+fn between<'a>(begin: ParseStream<'a>, end: ParseStream<'a>) -> TokenStream {
+ let end = end.cursor();
+ let mut cursor = begin.cursor();
+ let mut tokens = TokenStream::new();
+
+ while cursor < end {
+ let (tt, next) = cursor.token_tree().unwrap();
+
+ if end < next {
+ if let Some((inside, _span, _after)) = cursor.group(Delimiter::None) {
+ cursor = inside;
+ continue;
+ }
+ if tokens.is_empty() {
+ tokens.extend(iter::once(tt));
+ }
+ break;
+ }
+
+ tokens.extend(iter::once(tt));
+ cursor = next;
+ }
+
+ tokens
+}