From 4f9fe856a25ab29345b90e7725509e9ee38a37be Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:19:41 +0200 Subject: Adding upstream version 1.69.0+dfsg1. Signed-off-by: Daniel Baumann --- .../rustc_macros/src/diagnostics/diagnostic.rs | 4 +- .../src/diagnostics/diagnostic_builder.rs | 27 +- compiler/rustc_macros/src/diagnostics/fluent.rs | 433 +++++++++++---------- .../rustc_macros/src/diagnostics/subdiagnostic.rs | 25 +- compiler/rustc_macros/src/diagnostics/utils.rs | 67 ++-- 5 files changed, 301 insertions(+), 255 deletions(-) (limited to 'compiler/rustc_macros/src/diagnostics') diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs index 9ff944864..12a954258 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -57,7 +57,7 @@ impl<'a> DiagnosticDerive<'a> { } Some(slug) => { quote! { - let mut #diag = #handler.struct_diagnostic(rustc_errors::fluent::#slug); + let mut #diag = #handler.struct_diagnostic(crate::fluent_generated::#slug); } } }; @@ -149,7 +149,7 @@ impl<'a> LintDiagnosticDerive<'a> { } Some(slug) => { quote! { - rustc_errors::fluent::#slug.into() + crate::fluent_generated::#slug.into() } } } diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs index e405462bb..46068f8c8 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -322,11 +322,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { let generated_code = self .generate_inner_field_code( attr, - FieldInfo { - binding: binding_info, - ty: inner_ty.inner_type().unwrap_or(&field.ty), - span: &field.span(), - }, + FieldInfo { binding: binding_info, ty: inner_ty, span: &field.span() }, binding, ) .unwrap_or_else(|v| v.to_compile_error()); @@ -418,9 +414,9 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) } SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => { - if type_matches_path(info.ty, &["rustc_span", "Span"]) { + if type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"]) { Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) - } else if type_is_unit(info.ty) { + } else if type_is_unit(info.ty.inner_type()) { Ok(self.add_subdiagnostic(&fn_ident, slug)) } else { report_type_error(attr, "`Span` or `()`")? @@ -432,6 +428,15 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { code_field, code_init, } => { + if let FieldInnerTy::Vec(_) = info.ty { + throw_invalid_attr!(attr, &meta, |diag| { + diag + .note("`#[suggestion(...)]` applied to `Vec` field is ambiguous") + .help("to show a suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]`") + .help("to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]`") + }); + } + let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?; if let Some((static_applicability, span)) = static_applicability { @@ -447,7 +452,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { Ok(quote! { #diag.span_suggestions_with_style( #span_field, - rustc_errors::fluent::#slug, + crate::fluent_generated::#slug, #code_field, #applicability, #style @@ -471,7 +476,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { quote! { #diag.#fn_name( #field_binding, - rustc_errors::fluent::#fluent_attr_identifier + crate::fluent_generated::#fluent_attr_identifier ); } } @@ -481,7 +486,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream { let diag = &self.parent.diag; quote! { - #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier); + #diag.#kind(crate::fluent_generated::#fluent_attr_identifier); } } @@ -489,7 +494,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { &self, info: FieldInfo<'_>, ) -> Result<(TokenStream, SpannedOption), DiagnosticDeriveError> { - match &info.ty { + match &info.ty.inner_type() { // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`. ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => { let binding = &info.binding.binding; diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs index 32338f9df..38c0f4895 100644 --- a/compiler/rustc_macros/src/diagnostics/fluent.rs +++ b/compiler/rustc_macros/src/diagnostics/fluent.rs @@ -4,7 +4,10 @@ use annotate_snippets::{ }; use fluent_bundle::{FluentBundle, FluentError, FluentResource}; use fluent_syntax::{ - ast::{Attribute, Entry, Identifier, Message}, + ast::{ + Attribute, Entry, Expression, Identifier, InlineExpression, Message, Pattern, + PatternElement, + }, parser::ParserError, }; use proc_macro::{Diagnostic, Level, Span}; @@ -16,52 +19,9 @@ use std::{ io::Read, path::{Path, PathBuf}, }; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, - punctuated::Punctuated, - token, Ident, LitStr, Result, -}; +use syn::{parse_macro_input, Ident, LitStr}; use unic_langid::langid; -struct Resource { - krate: Ident, - #[allow(dead_code)] - fat_arrow_token: token::FatArrow, - resource_path: LitStr, -} - -impl Parse for Resource { - fn parse(input: ParseStream<'_>) -> Result { - Ok(Resource { - krate: input.parse()?, - fat_arrow_token: input.parse()?, - resource_path: input.parse()?, - }) - } -} - -struct Resources(Punctuated); - -impl Parse for Resources { - fn parse(input: ParseStream<'_>) -> Result { - let mut resources = Punctuated::new(); - loop { - if input.is_empty() || input.peek(token::Brace) { - break; - } - let value = input.parse()?; - resources.push_value(value); - if !input.peek(token::Comma) { - break; - } - let punct = input.parse()?; - resources.push_punct(punct); - } - Ok(Resources(resources)) - } -} - /// Helper function for returning an absolute path for macro-invocation relative file paths. /// /// If the input is already absolute, then the input is returned. If the input is not absolute, @@ -81,224 +41,285 @@ fn invocation_relative_path_to_absolute(span: Span, path: &str) -> PathBuf { } } +/// Tokens to be returned when the macro cannot proceed. +fn failed(crate_name: &Ident) -> proc_macro::TokenStream { + quote! { + pub static DEFAULT_LOCALE_RESOURCE: &'static str = ""; + + #[allow(non_upper_case_globals)] + #[doc(hidden)] + pub(crate) mod fluent_generated { + pub mod #crate_name { + } + + pub mod _subdiag { + pub const help: crate::SubdiagnosticMessage = + crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("help")); + pub const note: crate::SubdiagnosticMessage = + crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("note")); + pub const warn: crate::SubdiagnosticMessage = + crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("warn")); + pub const label: crate::SubdiagnosticMessage = + crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("label")); + pub const suggestion: crate::SubdiagnosticMessage = + crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("suggestion")); + } + } + } + .into() +} + /// See [rustc_macros::fluent_messages]. pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let resources = parse_macro_input!(input as Resources); + let crate_name = std::env::var("CARGO_PKG_NAME") + // If `CARGO_PKG_NAME` is missing, then we're probably running in a test, so use + // `no_crate`. + .unwrap_or_else(|_| "no_crate".to_string()) + .replace("rustc_", ""); // Cannot iterate over individual messages in a bundle, so do that using the // `FluentResource` instead. Construct a bundle anyway to find out if there are conflicting // messages in the resources. let mut bundle = FluentBundle::new(vec![langid!("en-US")]); - // Map of Fluent identifiers to the `Span` of the resource that defined them, used for better - // diagnostics. - let mut previous_defns = HashMap::new(); - // Set of Fluent attribute names already output, to avoid duplicate type errors - any given // constant created for a given attribute is the same. let mut previous_attrs = HashSet::new(); - let mut includes = TokenStream::new(); - let mut generated = TokenStream::new(); + let resource_str = parse_macro_input!(input as LitStr); + let resource_span = resource_str.span().unwrap(); + let relative_ftl_path = resource_str.value(); + let absolute_ftl_path = invocation_relative_path_to_absolute(resource_span, &relative_ftl_path); - for res in resources.0 { - let krate_span = res.krate.span().unwrap(); - let path_span = res.resource_path.span().unwrap(); + let crate_name = Ident::new(&crate_name, resource_str.span()); - let relative_ftl_path = res.resource_path.value(); - let absolute_ftl_path = - invocation_relative_path_to_absolute(krate_span, &relative_ftl_path); - // As this macro also outputs an `include_str!` for this file, the macro will always be - // re-executed when the file changes. - let mut resource_file = match File::open(absolute_ftl_path) { - Ok(resource_file) => resource_file, - Err(e) => { - Diagnostic::spanned(path_span, Level::Error, "could not open Fluent resource") - .note(e.to_string()) - .emit(); - continue; - } - }; - let mut resource_contents = String::new(); - if let Err(e) = resource_file.read_to_string(&mut resource_contents) { - Diagnostic::spanned(path_span, Level::Error, "could not read Fluent resource") + // As this macro also outputs an `include_str!` for this file, the macro will always be + // re-executed when the file changes. + let mut resource_file = match File::open(absolute_ftl_path) { + Ok(resource_file) => resource_file, + Err(e) => { + Diagnostic::spanned(resource_span, Level::Error, "could not open Fluent resource") .note(e.to_string()) .emit(); - continue; + return failed(&crate_name); } - let resource = match FluentResource::try_new(resource_contents) { - Ok(resource) => resource, - Err((this, errs)) => { - Diagnostic::spanned(path_span, Level::Error, "could not parse Fluent resource") - .help("see additional errors emitted") - .emit(); - for ParserError { pos, slice: _, kind } in errs { - let mut err = kind.to_string(); - // Entirely unnecessary string modification so that the error message starts - // with a lowercase as rustc errors do. - err.replace_range( - 0..1, - &err.chars().next().unwrap().to_lowercase().to_string(), - ); + }; + let mut resource_contents = String::new(); + if let Err(e) = resource_file.read_to_string(&mut resource_contents) { + Diagnostic::spanned(resource_span, Level::Error, "could not read Fluent resource") + .note(e.to_string()) + .emit(); + return failed(&crate_name); + } + + let resource = match FluentResource::try_new(resource_contents) { + Ok(resource) => resource, + Err((this, errs)) => { + Diagnostic::spanned(resource_span, Level::Error, "could not parse Fluent resource") + .help("see additional errors emitted") + .emit(); + for ParserError { pos, slice: _, kind } in errs { + let mut err = kind.to_string(); + // Entirely unnecessary string modification so that the error message starts + // with a lowercase as rustc errors do. + err.replace_range(0..1, &err.chars().next().unwrap().to_lowercase().to_string()); - let line_starts: Vec = std::iter::once(0) - .chain( - this.source() - .char_indices() - .filter_map(|(i, c)| Some(i + 1).filter(|_| c == '\n')), - ) - .collect(); - let line_start = line_starts - .iter() - .enumerate() - .map(|(line, idx)| (line + 1, idx)) - .filter(|(_, idx)| **idx <= pos.start) - .last() - .unwrap() - .0; + let line_starts: Vec = std::iter::once(0) + .chain( + this.source() + .char_indices() + .filter_map(|(i, c)| Some(i + 1).filter(|_| c == '\n')), + ) + .collect(); + let line_start = line_starts + .iter() + .enumerate() + .map(|(line, idx)| (line + 1, idx)) + .filter(|(_, idx)| **idx <= pos.start) + .last() + .unwrap() + .0; - let snippet = Snippet { - title: Some(Annotation { - label: Some(&err), - id: None, + let snippet = Snippet { + title: Some(Annotation { + label: Some(&err), + id: None, + annotation_type: AnnotationType::Error, + }), + footer: vec![], + slices: vec![Slice { + source: this.source(), + line_start, + origin: Some(&relative_ftl_path), + fold: true, + annotations: vec![SourceAnnotation { + label: "", annotation_type: AnnotationType::Error, - }), - footer: vec![], - slices: vec![Slice { - source: this.source(), - line_start, - origin: Some(&relative_ftl_path), - fold: true, - annotations: vec![SourceAnnotation { - label: "", - annotation_type: AnnotationType::Error, - range: (pos.start, pos.end - 1), - }], + range: (pos.start, pos.end - 1), }], - opt: Default::default(), - }; - let dl = DisplayList::from(snippet); - eprintln!("{dl}\n"); + }], + opt: Default::default(), + }; + let dl = DisplayList::from(snippet); + eprintln!("{dl}\n"); + } + + return failed(&crate_name); + } + }; + + let mut constants = TokenStream::new(); + let mut previous_defns = HashMap::new(); + let mut message_refs = Vec::new(); + for entry in resource.entries() { + if let Entry::Message(Message { id: Identifier { name }, attributes, value, .. }) = entry { + let _ = previous_defns.entry(name.to_string()).or_insert(resource_span); + if name.contains('-') { + Diagnostic::spanned( + resource_span, + Level::Error, + format!("name `{name}` contains a '-' character"), + ) + .help("replace any '-'s with '_'s") + .emit(); + } + + if let Some(Pattern { elements }) = value { + for elt in elements { + if let PatternElement::Placeable { + expression: + Expression::Inline(InlineExpression::MessageReference { id, .. }), + } = elt + { + message_refs.push((id.name, *name)); + } } + } + + // `typeck_foo_bar` => `foo_bar` (in `typeck.ftl`) + // `const_eval_baz` => `baz` (in `const_eval.ftl`) + // `const-eval-hyphen-having` => `hyphen_having` (in `const_eval.ftl`) + // The last case we error about above, but we want to fall back gracefully + // so that only the error is being emitted and not also one about the macro + // failing. + let crate_prefix = format!("{crate_name}_"); + + let snake_name = name.replace('-', "_"); + if !snake_name.starts_with(&crate_prefix) { + Diagnostic::spanned( + resource_span, + Level::Error, + format!("name `{name}` does not start with the crate name"), + ) + .help(format!( + "prepend `{crate_prefix}` to the slug name: `{crate_prefix}{snake_name}`" + )) + .emit(); + }; + let snake_name = Ident::new(&snake_name, resource_str.span()); + + if !previous_attrs.insert(snake_name.clone()) { continue; } - }; - let mut constants = TokenStream::new(); - for entry in resource.entries() { - let span = res.krate.span(); - if let Entry::Message(Message { id: Identifier { name }, attributes, .. }) = entry { - let _ = previous_defns.entry(name.to_string()).or_insert(path_span); + let msg = format!("Constant referring to Fluent message `{name}` from `{crate_name}`"); + constants.extend(quote! { + #[doc = #msg] + pub const #snake_name: crate::DiagnosticMessage = + crate::DiagnosticMessage::FluentIdentifier( + std::borrow::Cow::Borrowed(#name), + None + ); + }); - if name.contains('-') { - Diagnostic::spanned( - path_span, - Level::Error, - format!("name `{name}` contains a '-' character"), - ) - .help("replace any '-'s with '_'s") - .emit(); + for Attribute { id: Identifier { name: attr_name }, .. } in attributes { + let snake_name = Ident::new( + &format!("{}{}", &crate_prefix, &attr_name.replace('-', "_")), + resource_str.span(), + ); + if !previous_attrs.insert(snake_name.clone()) { + continue; } - // Require that the message name starts with the crate name - // `hir_typeck_foo_bar` (in `hir_typeck.ftl`) - // `const_eval_baz` (in `const_eval.ftl`) - // `const-eval-hyphen-having` => `hyphen_having` (in `const_eval.ftl`) - // The last case we error about above, but we want to fall back gracefully - // so that only the error is being emitted and not also one about the macro - // failing. - let crate_prefix = format!("{}_", res.krate); - - let snake_name = name.replace('-', "_"); - if !snake_name.starts_with(&crate_prefix) { + if attr_name.contains('-') { Diagnostic::spanned( - path_span, + resource_span, Level::Error, - format!("name `{name}` does not start with the crate name"), + format!("attribute `{attr_name}` contains a '-' character"), ) - .help(format!( - "prepend `{crate_prefix}` to the slug name: `{crate_prefix}{snake_name}`" - )) + .help("replace any '-'s with '_'s") .emit(); - }; - - let snake_name = Ident::new(&snake_name, span); + } + let msg = format!( + "Constant referring to Fluent message `{name}.{attr_name}` from `{crate_name}`" + ); constants.extend(quote! { - pub const #snake_name: crate::DiagnosticMessage = - crate::DiagnosticMessage::FluentIdentifier( - std::borrow::Cow::Borrowed(#name), - None + #[doc = #msg] + pub const #snake_name: crate::SubdiagnosticMessage = + crate::SubdiagnosticMessage::FluentAttr( + std::borrow::Cow::Borrowed(#attr_name) ); }); - - for Attribute { id: Identifier { name: attr_name }, .. } in attributes { - let snake_name = Ident::new(&attr_name.replace('-', "_"), span); - if !previous_attrs.insert(snake_name.clone()) { - continue; - } - - if attr_name.contains('-') { - Diagnostic::spanned( - path_span, - Level::Error, - format!("attribute `{attr_name}` contains a '-' character"), - ) - .help("replace any '-'s with '_'s") - .emit(); - } - - constants.extend(quote! { - pub const #snake_name: crate::SubdiagnosticMessage = - crate::SubdiagnosticMessage::FluentAttr( - std::borrow::Cow::Borrowed(#attr_name) - ); - }); - } } } + } - if let Err(errs) = bundle.add_resource(resource) { - for e in errs { - match e { - FluentError::Overriding { kind, id } => { - Diagnostic::spanned( - path_span, - Level::Error, - format!("overrides existing {kind}: `{id}`"), - ) - .span_help(previous_defns[&id], "previously defined in this resource") - .emit(); - } - FluentError::ResolverError(_) | FluentError::ParserError(_) => unreachable!(), + for (mref, name) in message_refs.into_iter() { + if !previous_defns.contains_key(mref) { + Diagnostic::spanned( + resource_span, + Level::Error, + format!("referenced message `{mref}` does not exist (in message `{name}`)"), + ) + .help(&format!("you may have meant to use a variable reference (`{{${mref}}}`)")) + .emit(); + } + } + + if let Err(errs) = bundle.add_resource(resource) { + for e in errs { + match e { + FluentError::Overriding { kind, id } => { + Diagnostic::spanned( + resource_span, + Level::Error, + format!("overrides existing {kind}: `{id}`"), + ) + .emit(); } + FluentError::ResolverError(_) | FluentError::ParserError(_) => unreachable!(), } } - - includes.extend(quote! { include_str!(#relative_ftl_path), }); - - generated.extend(constants); } quote! { + /// Raw content of Fluent resource for this crate, generated by `fluent_messages` macro, + /// imported by `rustc_driver` to include all crates' resources in one bundle. + pub static DEFAULT_LOCALE_RESOURCE: &'static str = include_str!(#relative_ftl_path); + #[allow(non_upper_case_globals)] #[doc(hidden)] - pub mod fluent_generated { - pub static DEFAULT_LOCALE_RESOURCES: &'static [&'static str] = &[ - #includes - ]; - - #generated + /// Auto-generated constants for type-checked references to Fluent messages. + pub(crate) mod fluent_generated { + #constants + /// Constants expected to exist by the diagnostic derive macros to use as default Fluent + /// identifiers for different subdiagnostic kinds. pub mod _subdiag { + /// Default for `#[help]` pub const help: crate::SubdiagnosticMessage = crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("help")); + /// Default for `#[note]` pub const note: crate::SubdiagnosticMessage = crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("note")); + /// Default for `#[warn]` pub const warn: crate::SubdiagnosticMessage = crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("warn")); + /// Default for `#[label]` pub const label: crate::SubdiagnosticMessage = crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("label")); + /// Default for `#[suggestion]` pub const suggestion: crate::SubdiagnosticMessage = crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("suggestion")); } diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index baffd3cec..90660fc1f 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -247,11 +247,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { return quote! {}; } - let info = FieldInfo { - binding, - ty: inner_ty.inner_type().unwrap_or(&ast.ty), - span: &ast.span(), - }; + let info = FieldInfo { binding, ty: inner_ty, span: &ast.span() }; let generated = self .generate_field_code_inner(kind_stats, attr, info, inner_ty.will_iterate()) @@ -312,6 +308,21 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { let binding = info.binding.binding.clone(); // FIXME(#100717): support `Option` on `primary_span` like in the // diagnostic derive + if !matches!(info.ty, FieldInnerTy::Plain(_)) { + throw_invalid_attr!(attr, &Meta::Path(path), |diag| { + let diag = diag.note("there must be exactly one primary span"); + + if kind_stats.has_normal_suggestion { + diag.help( + "to create a suggestion with multiple spans, \ + use `#[multipart_suggestion]` instead", + ) + } else { + diag + } + }); + } + self.span_field.set_once(binding, span); } @@ -501,7 +512,9 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { let mut calls = TokenStream::new(); for (kind, slug) in kind_slugs { let message = format_ident!("__message"); - calls.extend(quote! { let #message = #f(#diag, rustc_errors::fluent::#slug.into()); }); + calls.extend( + quote! { let #message = #f(#diag, crate::fluent_generated::#slug.into()); }, + ); let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind); let call = match kind { diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index 6f52a3de1..27b8f676f 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -80,7 +80,7 @@ fn report_error_if_not_applied_to_ty( path: &[&str], ty_name: &str, ) -> Result<(), DiagnosticDeriveError> { - if !type_matches_path(info.ty, path) { + if !type_matches_path(info.ty.inner_type(), path) { report_type_error(attr, ty_name)?; } @@ -105,8 +105,8 @@ pub(crate) fn report_error_if_not_applied_to_span( attr: &Attribute, info: &FieldInfo<'_>, ) -> Result<(), DiagnosticDeriveError> { - if !type_matches_path(info.ty, &["rustc_span", "Span"]) - && !type_matches_path(info.ty, &["rustc_errors", "MultiSpan"]) + if !type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"]) + && !type_matches_path(info.ty.inner_type(), &["rustc_errors", "MultiSpan"]) { report_type_error(attr, "`Span` or `MultiSpan`")?; } @@ -115,44 +115,50 @@ pub(crate) fn report_error_if_not_applied_to_span( } /// Inner type of a field and type of wrapper. +#[derive(Copy, Clone)] pub(crate) enum FieldInnerTy<'ty> { /// Field is wrapped in a `Option<$inner>`. Option(&'ty Type), /// Field is wrapped in a `Vec<$inner>`. Vec(&'ty Type), /// Field isn't wrapped in an outer type. - None, + Plain(&'ty Type), } impl<'ty> FieldInnerTy<'ty> { /// Returns inner type for a field, if there is one. /// - /// - If `ty` is an `Option`, returns `FieldInnerTy::Option { inner: (inner type) }`. - /// - If `ty` is a `Vec`, returns `FieldInnerTy::Vec { inner: (inner type) }`. - /// - Otherwise returns `None`. + /// - If `ty` is an `Option`, returns `FieldInnerTy::Option(Inner)`. + /// - If `ty` is a `Vec`, returns `FieldInnerTy::Vec(Inner)`. + /// - Otherwise returns `FieldInnerTy::Plain(ty)`. pub(crate) fn from_type(ty: &'ty Type) -> Self { - let variant: &dyn Fn(&'ty Type) -> FieldInnerTy<'ty> = - if type_matches_path(ty, &["std", "option", "Option"]) { - &FieldInnerTy::Option - } else if type_matches_path(ty, &["std", "vec", "Vec"]) { - &FieldInnerTy::Vec - } else { - return FieldInnerTy::None; + fn single_generic_type(ty: &Type) -> &Type { + let Type::Path(ty_path) = ty else { + panic!("expected path type"); }; - if let Type::Path(ty_path) = ty { let path = &ty_path.path; let ty = path.segments.iter().last().unwrap(); - if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments { - if bracketed.args.len() == 1 { - if let syn::GenericArgument::Type(ty) = &bracketed.args[0] { - return variant(ty); - } - } - } + let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments else { + panic!("expected bracketed generic arguments"); + }; + + assert_eq!(bracketed.args.len(), 1); + + let syn::GenericArgument::Type(ty) = &bracketed.args[0] else { + panic!("expected generic parameter to be a type generic"); + }; + + ty } - unreachable!(); + if type_matches_path(ty, &["std", "option", "Option"]) { + FieldInnerTy::Option(single_generic_type(ty)) + } else if type_matches_path(ty, &["std", "vec", "Vec"]) { + FieldInnerTy::Vec(single_generic_type(ty)) + } else { + FieldInnerTy::Plain(ty) + } } /// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e. @@ -160,15 +166,16 @@ impl<'ty> FieldInnerTy<'ty> { pub(crate) fn will_iterate(&self) -> bool { match self { FieldInnerTy::Vec(..) => true, - FieldInnerTy::Option(..) | FieldInnerTy::None => false, + FieldInnerTy::Option(..) | FieldInnerTy::Plain(_) => false, } } - /// Returns `Option` containing inner type if there is one. - pub(crate) fn inner_type(&self) -> Option<&'ty Type> { + /// Returns the inner type. + pub(crate) fn inner_type(&self) -> &'ty Type { match self { - FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) => Some(inner), - FieldInnerTy::None => None, + FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) | FieldInnerTy::Plain(inner) => { + inner + } } } @@ -185,7 +192,7 @@ impl<'ty> FieldInnerTy<'ty> { #inner } }, - FieldInnerTy::None => quote! { #inner }, + FieldInnerTy::Plain(..) => quote! { #inner }, } } } @@ -194,7 +201,7 @@ impl<'ty> FieldInnerTy<'ty> { /// `generate_*` methods from walking the attributes themselves. pub(crate) struct FieldInfo<'a> { pub(crate) binding: &'a BindingInfo<'a>, - pub(crate) ty: &'a Type, + pub(crate) ty: FieldInnerTy<'a>, pub(crate) span: &'a proc_macro2::Span, } -- cgit v1.2.3