diff options
Diffstat (limited to 'compiler/rustc_macros/src')
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/diagnostic.rs | 4 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs | 191 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/error.rs | 55 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/fluent.rs | 338 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/mod.rs | 4 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/subdiagnostic.rs | 116 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/utils.rs | 241 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/hash_stable.rs | 45 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/lib.rs | 58 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/newtype.rs | 12 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/query.rs | 16 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/serialize.rs | 47 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/type_foldable.rs | 33 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/type_visitable.rs | 36 |
14 files changed, 386 insertions, 810 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs index 9ff94486404..12a954258d1 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 12bcd939bd6..cd6e3687460 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -1,19 +1,17 @@ #![deny(unused_must_use)] use crate::diagnostics::error::{ - invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, - DiagnosticDeriveError, + span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError, }; use crate::diagnostics::utils::{ build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error, - should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy, FieldMap, - HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind, + should_generate_set_arg, type_is_bool, type_is_unit, type_matches_path, FieldInfo, + FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind, }; use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote}; -use syn::{ - parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type, -}; +use quote::{format_ident, quote, quote_spanned}; +use syn::Token; +use syn::{parse_quote, spanned::Spanned, Attribute, Meta, Path, Type}; use synstructure::{BindingInfo, Structure, VariantInfo}; /// What kind of diagnostic is being derived - a fatal/error/warning or a lint? @@ -77,7 +75,7 @@ impl DiagnosticDeriveBuilder { match ast.data { syn::Data::Struct(..) | syn::Data::Enum(..) => (), syn::Data::Union(..) => { - span_err(span, "diagnostic derives can only be used on structs and enums"); + span_err(span, "diagnostic derives can only be used on structs and enums").emit(); } } @@ -121,7 +119,7 @@ impl DiagnosticDeriveBuilder { impl<'a> DiagnosticDeriveVariantBuilder<'a> { /// Generates calls to `code` and similar functions based on the attributes on the type or /// variant. - pub fn preamble<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream { + pub fn preamble(&mut self, variant: &VariantInfo<'_>) -> TokenStream { let ast = variant.ast(); let attrs = &ast.attrs; let preamble = attrs.iter().map(|attr| { @@ -135,7 +133,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { /// Generates calls to `span_label` and similar functions based on the attributes on fields or /// calls to `set_arg` when no attributes are present. - pub fn body<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream { + pub fn body(&mut self, variant: &VariantInfo<'_>) -> TokenStream { let mut body = quote! {}; // Generate `set_arg` calls first.. for binding in variant.bindings().iter().filter(|bi| should_generate_set_arg(bi.ast())) { @@ -160,8 +158,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { }; if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag { - let meta = attr.parse_meta()?; - throw_invalid_attr!(attr, &meta, |diag| diag + throw_invalid_attr!(attr, |diag| diag .help("consider creating a `Subdiagnostic` instead")); } @@ -191,71 +188,44 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { return Ok(quote! {}); } - let name = attr.path.segments.last().unwrap().ident.to_string(); + let name = attr.path().segments.last().unwrap().ident.to_string(); let name = name.as_str(); - let meta = attr.parse_meta()?; - if name == "diag" { - let Meta::List(MetaList { ref nested, .. }) = meta else { - throw_invalid_attr!( - attr, - &meta - ); - }; + let mut first = true; - let mut nested_iter = nested.into_iter().peekable(); + if name == "diag" { + let mut tokens = TokenStream::new(); + attr.parse_nested_meta(|nested| { + let path = &nested.path; - match nested_iter.peek() { - Some(NestedMeta::Meta(Meta::Path(slug))) => { - self.slug.set_once(slug.clone(), slug.span().unwrap()); - nested_iter.next(); + if first && (nested.input.is_empty() || nested.input.peek(Token![,])) { + self.slug.set_once(path.clone(), path.span().unwrap()); + first = false; + return Ok(()) } - Some(NestedMeta::Meta(Meta::NameValue { .. })) => {} - Some(nested_attr) => throw_invalid_nested_attr!(attr, nested_attr, |diag| diag - .help("a diagnostic slug is required as the first argument")), - None => throw_invalid_attr!(attr, &meta, |diag| diag - .help("a diagnostic slug is required as the first argument")), - }; - // Remaining attributes are optional, only `code = ".."` at the moment. - let mut tokens = TokenStream::new(); - for nested_attr in nested_iter { - let (value, path) = match nested_attr { - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - lit: syn::Lit::Str(value), - path, - .. - })) => (value, path), - NestedMeta::Meta(Meta::Path(_)) => { - invalid_nested_attr(attr, nested_attr) - .help("diagnostic slug must be the first argument") - .emit(); - continue; - } - _ => { - invalid_nested_attr(attr, nested_attr).emit(); - continue; - } + first = false; + + let Ok(nested) = nested.value() else { + span_err(nested.input.span().unwrap(), "diagnostic slug must be the first argument").emit(); + return Ok(()) }; - let nested_name = path.segments.last().unwrap().ident.to_string(); - // Struct attributes are only allowed to be applied once, and the diagnostic - // changes will be set in the initialisation code. - let span = value.span().unwrap(); - match nested_name.as_str() { - "code" => { - self.code.set_once((), span); - - let code = value.value(); - tokens.extend(quote! { - #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); - }); - } - _ => invalid_nested_attr(attr, nested_attr) - .help("only `code` is a valid nested attributes following the slug") - .emit(), + if path.is_ident("code") { + self.code.set_once((), path.span().unwrap()); + + let code = nested.parse::<syn::LitStr>()?; + tokens.extend(quote! { + #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); + }); + } else { + span_err(path.span().unwrap(), "unknown argument").note("only the `code` parameter is valid after the slug").emit(); + + // consume the buffer so we don't have syntax errors from syn + let _ = nested.parse::<TokenStream>(); } - } + Ok(()) + })?; return Ok(tokens); } @@ -270,7 +240,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { Ok(self.add_subdiagnostic(&fn_ident, slug)) } SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => { - throw_invalid_attr!(attr, &meta, |diag| diag + throw_invalid_attr!(attr, |diag| diag .help("`#[label]` and `#[suggestion]` can only be applied to fields")); } SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), @@ -281,7 +251,8 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { let diag = &self.parent.diag; let field = binding_info.ast(); - let field_binding = &binding_info.binding; + let mut field_binding = binding_info.binding.clone(); + field_binding.set_span(field.ty.span()); let ident = field.ident.as_ref().unwrap(); let ident = format_ident!("{}", ident); // strip `r#` prefix, if present @@ -309,14 +280,14 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { return quote! {}; } - let name = attr.path.segments.last().unwrap().ident.to_string(); + let name = attr.path().segments.last().unwrap().ident.to_string(); let needs_clone = name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_)); let (binding, needs_destructure) = if needs_clone { // `primary_span` can accept a `Vec<Span>` so don't destructure that. - (quote! { #field_binding.clone() }, false) + (quote_spanned! {inner_ty.span()=> #field_binding.clone() }, false) } else { - (quote! { #field_binding }, true) + (quote_spanned! {inner_ty.span()=> #field_binding }, true) }; let generated_code = self @@ -343,11 +314,10 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { binding: TokenStream, ) -> Result<TokenStream, DiagnosticDeriveError> { let diag = &self.parent.diag; - let meta = attr.parse_meta()?; - let ident = &attr.path.segments.last().unwrap().ident; + let ident = &attr.path().segments.last().unwrap().ident; let name = ident.to_string(); - match (&meta, name.as_str()) { + match (&attr.meta, name.as_str()) { // Don't need to do anything - by virtue of the attribute existing, the // `set_arg` call will not be generated. (Meta::Path(_), "skip_arg") => return Ok(quote! {}), @@ -361,7 +331,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { }); } DiagnosticDeriveKind::LintDiagnostic => { - throw_invalid_attr!(attr, &meta, |diag| { + throw_invalid_attr!(attr, |diag| { diag.help("the `primary_span` field attribute is not valid for lint diagnostics") }) } @@ -378,26 +348,34 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { return Ok(quote! { #diag.subdiagnostic(#binding); }); } } - (Meta::List(MetaList { ref nested, .. }), "subdiagnostic") => { - if nested.len() == 1 - && let Some(NestedMeta::Meta(Meta::Path(path))) = nested.first() - && path.is_ident("eager") { - let handler = match &self.parent.kind { - DiagnosticDeriveKind::Diagnostic { handler } => handler, - DiagnosticDeriveKind::LintDiagnostic => { - throw_invalid_attr!(attr, &meta, |diag| { - diag.help("eager subdiagnostics are not supported on lints") - }) - } - }; - return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); }); - } else { - throw_invalid_attr!(attr, &meta, |diag| { - diag.help( - "`eager` is the only supported nested attribute for `subdiagnostic`", - ) - }) + (Meta::List(meta_list), "subdiagnostic") => { + let err = || { + span_err( + meta_list.span().unwrap(), + "`eager` is the only supported nested attribute for `subdiagnostic`", + ) + .emit(); + }; + + let Ok(p): Result<Path, _> = meta_list.parse_args() else { + err(); + return Ok(quote! {}); + }; + + if !p.is_ident("eager") { + err(); + return Ok(quote! {}); } + + let handler = match &self.parent.kind { + DiagnosticDeriveKind::Diagnostic { handler } => handler, + DiagnosticDeriveKind::LintDiagnostic => { + throw_invalid_attr!(attr, |diag| { + diag.help("eager subdiagnostics are not supported on lints") + }) + } + }; + return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); }); } _ => (), } @@ -414,12 +392,17 @@ 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.inner_type(), &["rustc_span", "Span"]) { + let inner = info.ty.inner_type(); + if type_matches_path(inner, &["rustc_span", "Span"]) + || type_matches_path(inner, &["rustc_span", "MultiSpan"]) + { Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) - } else if type_is_unit(info.ty.inner_type()) { + } else if type_is_unit(inner) + || (matches!(info.ty, FieldInnerTy::Plain(_)) && type_is_bool(inner)) + { Ok(self.add_subdiagnostic(&fn_ident, slug)) } else { - report_type_error(attr, "`Span` or `()`")? + report_type_error(attr, "`Span`, `MultiSpan`, `bool` or `()`")? } } SubdiagnosticKind::Suggestion { @@ -429,7 +412,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { code_init, } => { if let FieldInnerTy::Vec(_) = info.ty { - throw_invalid_attr!(attr, &meta, |diag| { + throw_invalid_attr!(attr, |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(...)]`") @@ -452,7 +435,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 @@ -476,7 +459,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { quote! { #diag.#fn_name( #field_binding, - rustc_errors::fluent::#fluent_attr_identifier + crate::fluent_generated::#fluent_attr_identifier ); } } @@ -486,7 +469,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); } } diff --git a/compiler/rustc_macros/src/diagnostics/error.rs b/compiler/rustc_macros/src/diagnostics/error.rs index 2d62d593163..b37dc826d28 100644 --- a/compiler/rustc_macros/src/diagnostics/error.rs +++ b/compiler/rustc_macros/src/diagnostics/error.rs @@ -1,7 +1,7 @@ use proc_macro::{Diagnostic, Level, MultiSpan}; use proc_macro2::TokenStream; use quote::quote; -use syn::{spanned::Spanned, Attribute, Error as SynError, Meta, NestedMeta}; +use syn::{spanned::Spanned, Attribute, Error as SynError, Meta}; #[derive(Debug)] pub(crate) enum DiagnosticDeriveError { @@ -53,6 +53,7 @@ fn path_to_string(path: &syn::Path) -> String { } /// Returns an error diagnostic on span `span` with msg `msg`. +#[must_use] pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic { Diagnostic::spanned(span, Level::Error, msg) } @@ -72,10 +73,10 @@ macro_rules! throw_span_err { pub(crate) use throw_span_err; /// Returns an error diagnostic for an invalid attribute. -pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic { +pub(crate) fn invalid_attr(attr: &Attribute) -> Diagnostic { let span = attr.span().unwrap(); - let path = path_to_string(&attr.path); - match meta { + let path = path_to_string(attr.path()); + match attr.meta { Meta::Path(_) => span_err(span, &format!("`#[{path}]` is not a valid attribute")), Meta::NameValue(_) => { span_err(span, &format!("`#[{path} = ...]` is not a valid attribute")) @@ -89,51 +90,11 @@ pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic { /// /// For methods that return a `Result<_, DiagnosticDeriveError>`: macro_rules! throw_invalid_attr { - ($attr:expr, $meta:expr) => {{ throw_invalid_attr!($attr, $meta, |diag| diag) }}; - ($attr:expr, $meta:expr, $f:expr) => {{ - let diag = crate::diagnostics::error::invalid_attr($attr, $meta); + ($attr:expr) => {{ throw_invalid_attr!($attr, |diag| diag) }}; + ($attr:expr, $f:expr) => {{ + let diag = crate::diagnostics::error::invalid_attr($attr); return Err(crate::diagnostics::error::_throw_err(diag, $f)); }}; } pub(crate) use throw_invalid_attr; - -/// Returns an error diagnostic for an invalid nested attribute. -pub(crate) fn invalid_nested_attr(attr: &Attribute, nested: &NestedMeta) -> Diagnostic { - let name = attr.path.segments.last().unwrap().ident.to_string(); - let name = name.as_str(); - - let span = nested.span().unwrap(); - let meta = match nested { - syn::NestedMeta::Meta(meta) => meta, - syn::NestedMeta::Lit(_) => { - return span_err(span, &format!("`#[{name}(\"...\")]` is not a valid attribute")); - } - }; - - let span = meta.span().unwrap(); - let path = path_to_string(meta.path()); - match meta { - Meta::NameValue(..) => { - span_err(span, &format!("`#[{name}({path} = ...)]` is not a valid attribute")) - } - Meta::Path(..) => span_err(span, &format!("`#[{name}({path})]` is not a valid attribute")), - Meta::List(..) => { - span_err(span, &format!("`#[{name}({path}(...))]` is not a valid attribute")) - } - } -} - -/// Emit an error diagnostic for an invalid nested attribute (optionally performing additional -/// decoration using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`. -/// -/// For methods that return a `Result<_, DiagnosticDeriveError>`: -macro_rules! throw_invalid_nested_attr { - ($attr:expr, $nested_attr:expr) => {{ throw_invalid_nested_attr!($attr, $nested_attr, |diag| diag) }}; - ($attr:expr, $nested_attr:expr, $f:expr) => {{ - let diag = crate::diagnostics::error::invalid_nested_attr($attr, $nested_attr); - return Err(crate::diagnostics::error::_throw_err(diag, $f)); - }}; -} - -pub(crate) use throw_invalid_nested_attr; diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs deleted file mode 100644 index 08098c9bb2a..00000000000 --- a/compiler/rustc_macros/src/diagnostics/fluent.rs +++ /dev/null @@ -1,338 +0,0 @@ -use annotate_snippets::{ - display_list::DisplayList, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; -use fluent_bundle::{FluentBundle, FluentError, FluentResource}; -use fluent_syntax::{ - ast::{ - Attribute, Entry, Expression, Identifier, InlineExpression, Message, Pattern, - PatternElement, - }, - parser::ParserError, -}; -use proc_macro::{Diagnostic, Level, Span}; -use proc_macro2::TokenStream; -use quote::quote; -use std::{ - collections::{HashMap, HashSet}, - fs::File, - io::Read, - path::{Path, PathBuf}, -}; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, - punctuated::Punctuated, - token, Ident, LitStr, Result, -}; -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<Self> { - Ok(Resource { - krate: input.parse()?, - fat_arrow_token: input.parse()?, - resource_path: input.parse()?, - }) - } -} - -struct Resources(Punctuated<Resource, token::Comma>); - -impl Parse for Resources { - fn parse(input: ParseStream<'_>) -> Result<Self> { - 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, -/// then it is appended to the directory containing the source file with this macro invocation. -fn invocation_relative_path_to_absolute(span: Span, path: &str) -> PathBuf { - let path = Path::new(path); - if path.is_absolute() { - path.to_path_buf() - } else { - // `/a/b/c/foo/bar.rs` contains the current macro invocation - let mut source_file_path = span.source_file().path(); - // `/a/b/c/foo/` - source_file_path.pop(); - // `/a/b/c/foo/../locales/en-US/example.ftl` - source_file_path.push(path); - source_file_path - } -} - -/// 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); - - // 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(); - - for res in resources.0 { - let krate_span = res.krate.span().unwrap(); - let path_span = res.resource_path.span().unwrap(); - - 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") - .note(e.to_string()) - .emit(); - continue; - } - 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 line_starts: Vec<usize> = 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, - 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), - }], - }], - opt: Default::default(), - }; - let dl = DisplayList::from(snippet); - eprintln!("{dl}\n"); - } - continue; - } - }; - - let mut constants = TokenStream::new(); - let mut messagerefs = Vec::new(); - for entry in resource.entries() { - let span = res.krate.span(); - if let Entry::Message(Message { id: Identifier { name }, attributes, value, .. }) = - entry - { - let _ = previous_defns.entry(name.to_string()).or_insert(path_span); - - if name.contains('-') { - Diagnostic::spanned( - path_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 - { - messagerefs.push((id.name, *name)); - } - } - } - - // 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) { - Diagnostic::spanned( - path_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, span); - - constants.extend(quote! { - pub const #snake_name: crate::DiagnosticMessage = - crate::DiagnosticMessage::FluentIdentifier( - std::borrow::Cow::Borrowed(#name), - None - ); - }); - - 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) - ); - }); - } - } - } - - for (mref, name) in messagerefs.into_iter() { - if !previous_defns.contains_key(mref) { - Diagnostic::spanned( - path_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( - 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!(), - } - } - } - - includes.extend(quote! { include_str!(#relative_ftl_path), }); - - generated.extend(constants); - } - - quote! { - #[allow(non_upper_case_globals)] - #[doc(hidden)] - pub mod fluent_generated { - pub static DEFAULT_LOCALE_RESOURCES: &'static [&'static str] = &[ - #includes - ]; - - #generated - - 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() -} diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs index 78df0cd1d34..a536eb3b04e 100644 --- a/compiler/rustc_macros/src/diagnostics/mod.rs +++ b/compiler/rustc_macros/src/diagnostics/mod.rs @@ -1,12 +1,10 @@ mod diagnostic; mod diagnostic_builder; mod error; -mod fluent; mod subdiagnostic; mod utils; use diagnostic::{DiagnosticDerive, LintDiagnosticDerive}; -pub(crate) use fluent::fluent_messages; use proc_macro2::TokenStream; use quote::format_ident; use subdiagnostic::SubdiagnosticDeriveBuilder; @@ -142,7 +140,7 @@ pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream { /// ```fluent /// parser_expected_identifier = expected identifier /// -/// parser_expected_identifier-found = expected identifier, found {$found} +/// parser_expected_identifier_found = expected identifier, found {$found} /// /// parser_raw_identifier = escape `{$ident}` to use it as an identifier /// ``` diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index 906e4c0b0e1..374ba1a45c0 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -1,21 +1,19 @@ #![deny(unused_must_use)] use crate::diagnostics::error::{ - invalid_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, - DiagnosticDeriveError, + invalid_attr, span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError, }; use crate::diagnostics::utils::{ - build_field_mapping, is_doc_comment, new_code_ident, - report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span, FieldInfo, - FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind, + build_field_mapping, build_suggestion_code, is_doc_comment, new_code_ident, + report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span, + should_generate_set_arg, AllowMultipleAlternatives, FieldInfo, FieldInnerTy, FieldMap, + HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind, }; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{spanned::Spanned, Attribute, Meta, MetaList, NestedMeta, Path}; +use syn::{spanned::Spanned, Attribute, Meta, MetaList, Path}; use synstructure::{BindingInfo, Structure, VariantInfo}; -use super::utils::{build_suggestion_code, AllowMultipleAlternatives}; - /// The central struct for constructing the `add_to_diagnostic` method from an annotated struct. pub(crate) struct SubdiagnosticDeriveBuilder { diag: syn::Ident, @@ -39,7 +37,8 @@ impl SubdiagnosticDeriveBuilder { span_err( span, "`#[derive(Subdiagnostic)]` can only be used on structs and enums", - ); + ) + .emit(); } } @@ -192,7 +191,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { }; let Some(slug) = slug else { - let name = attr.path.segments.last().unwrap().ident.to_string(); + let name = attr.path().segments.last().unwrap().ident.to_string(); let name = name.as_str(); throw_span_err!( @@ -210,19 +209,20 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { } /// Generates the code for a field with no attributes. - fn generate_field_set_arg(&mut self, binding: &BindingInfo<'_>) -> TokenStream { - let ast = binding.ast(); - assert_eq!(ast.attrs.len(), 0, "field with attribute used as diagnostic arg"); - + fn generate_field_set_arg(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { let diag = &self.parent.diag; - let ident = ast.ident.as_ref().unwrap(); - // strip `r#` prefix, if present - let ident = format_ident!("{}", ident); + + let field = binding_info.ast(); + let mut field_binding = binding_info.binding.clone(); + field_binding.set_span(field.ty.span()); + + let ident = field.ident.as_ref().unwrap(); + let ident = format_ident!("{}", ident); // strip `r#` prefix, if present quote! { #diag.set_arg( stringify!(#ident), - #binding + #field_binding ); } } @@ -265,17 +265,18 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { info: FieldInfo<'_>, clone_suggestion_code: bool, ) -> Result<TokenStream, DiagnosticDeriveError> { - let meta = attr.parse_meta()?; - match meta { - Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path), - Meta::List(list @ MetaList { .. }) => self.generate_field_code_inner_list( + match &attr.meta { + Meta::Path(path) => { + self.generate_field_code_inner_path(kind_stats, attr, info, path.clone()) + } + Meta::List(list) => self.generate_field_code_inner_list( kind_stats, attr, info, list, clone_suggestion_code, ), - _ => throw_invalid_attr!(attr, &meta), + _ => throw_invalid_attr!(attr), } } @@ -296,7 +297,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { "skip_arg" => Ok(quote! {}), "primary_span" => { if kind_stats.has_multipart_suggestion { - invalid_attr(attr, &Meta::Path(path)) + invalid_attr(attr) .help( "multipart suggestions use one or more `#[suggestion_part]`s rather \ than one `#[primary_span]`", @@ -309,7 +310,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { // FIXME(#100717): support `Option<Span>` on `primary_span` like in the // diagnostic derive if !matches!(info.ty, FieldInnerTy::Plain(_)) { - throw_invalid_attr!(attr, &Meta::Path(path), |diag| { + throw_invalid_attr!(attr, |diag| { let diag = diag.note("there must be exactly one primary span"); if kind_stats.has_normal_suggestion { @@ -335,7 +336,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`") .emit(); } else { - invalid_attr(attr, &Meta::Path(path)) + invalid_attr(attr) .help( "`#[suggestion_part(...)]` is only valid in multipart suggestions, \ use `#[primary_span]` instead", @@ -375,7 +376,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { span_attrs.push("primary_span") } - invalid_attr(attr, &Meta::Path(path)) + invalid_attr(attr) .help(format!( "only `{}`, `applicability` and `skip_arg` are valid field attributes", span_attrs.join(", ") @@ -394,18 +395,19 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { kind_stats: KindsStatistics, attr: &Attribute, info: FieldInfo<'_>, - list: MetaList, + list: &MetaList, clone_suggestion_code: bool, ) -> Result<TokenStream, DiagnosticDeriveError> { let span = attr.span().unwrap(); - let ident = &list.path.segments.last().unwrap().ident; + let mut ident = list.path.segments.last().unwrap().ident.clone(); + ident.set_span(info.ty.span()); let name = ident.to_string(); let name = name.as_str(); match name { "suggestion_part" => { if !kind_stats.has_multipart_suggestion { - throw_invalid_attr!(attr, &Meta::List(list), |diag| { + throw_invalid_attr!(attr, |diag| { diag.help( "`#[suggestion_part(...)]` is only valid in multipart suggestions", ) @@ -417,31 +419,27 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { report_error_if_not_applied_to_span(attr, &info)?; let mut code = None; - for nested_attr in list.nested.iter() { - let NestedMeta::Meta(ref meta) = nested_attr else { - throw_invalid_nested_attr!(attr, nested_attr); - }; - - let span = meta.span().unwrap(); - let nested_name = meta.path().segments.last().unwrap().ident.to_string(); - let nested_name = nested_name.as_str(); - - match nested_name { - "code" => { - let code_field = new_code_ident(); - let formatting_init = build_suggestion_code( - &code_field, - meta, - self, - AllowMultipleAlternatives::No, - ); - code.set_once((code_field, formatting_init), span); - } - _ => throw_invalid_nested_attr!(attr, nested_attr, |diag| { - diag.help("`code` is the only valid nested attribute") - }), + + list.parse_nested_meta(|nested| { + if nested.path.is_ident("code") { + let code_field = new_code_ident(); + let span = nested.path.span().unwrap(); + let formatting_init = build_suggestion_code( + &code_field, + nested, + self, + AllowMultipleAlternatives::No, + ); + code.set_once((code_field, formatting_init), span); + } else { + span_err( + nested.path.span().unwrap(), + "`code` is the only valid nested attribute", + ) + .emit(); } - } + Ok(()) + })?; let Some((code_field, formatting_init)) = code.value() else { span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`") @@ -458,7 +456,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { }; Ok(quote! { suggestions.push((#binding, #code_field)); }) } - _ => throw_invalid_attr!(attr, &Meta::List(list), |diag| { + _ => throw_invalid_attr!(attr, |diag| { let mut span_attrs = vec![]; if kind_stats.has_multipart_suggestion { span_attrs.push("suggestion_part"); @@ -501,7 +499,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { .variant .bindings() .iter() - .filter(|binding| !binding.ast().attrs.is_empty()) + .filter(|binding| !should_generate_set_arg(binding.ast())) .map(|binding| self.generate_field_attr_code(binding, kind_stats)) .collect(); @@ -512,7 +510,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 { @@ -581,7 +581,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { .variant .bindings() .iter() - .filter(|binding| binding.ast().attrs.is_empty()) + .filter(|binding| should_generate_set_arg(binding.ast())) .map(|binding| self.generate_field_set_arg(binding)) .collect(); diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index 27b8f676f3f..e2434981f8d 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -1,5 +1,5 @@ use crate::diagnostics::error::{ - span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError, + span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError, }; use proc_macro::Span; use proc_macro2::{Ident, TokenStream}; @@ -8,11 +8,13 @@ use std::cell::RefCell; use std::collections::{BTreeSet, HashMap}; use std::fmt; use std::str::FromStr; +use syn::meta::ParseNestedMeta; +use syn::punctuated::Punctuated; +use syn::{parenthesized, LitStr, Path, Token}; use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple}; -use syn::{MetaList, MetaNameValue, NestedMeta, Path}; use synstructure::{BindingInfo, VariantInfo}; -use super::error::{invalid_attr, invalid_nested_attr}; +use super::error::invalid_attr; thread_local! { pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0); @@ -50,13 +52,18 @@ pub(crate) fn type_is_unit(ty: &Type) -> bool { if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false } } +/// Checks whether the type `ty` is `bool`. +pub(crate) fn type_is_bool(ty: &Type) -> bool { + type_matches_path(ty, &["bool"]) +} + /// Reports a type error for field with `attr`. pub(crate) fn report_type_error( attr: &Attribute, ty_name: &str, ) -> Result<!, DiagnosticDeriveError> { - let name = attr.path.segments.last().unwrap().ident.to_string(); - let meta = attr.parse_meta()?; + let name = attr.path().segments.last().unwrap().ident.to_string(); + let meta = &attr.meta; throw_span_err!( attr.span().unwrap(), @@ -192,9 +199,20 @@ impl<'ty> FieldInnerTy<'ty> { #inner } }, + FieldInnerTy::Plain(t) if type_is_bool(t) => quote! { + if #binding { + #inner + } + }, FieldInnerTy::Plain(..) => quote! { #inner }, } } + + pub fn span(&self) -> proc_macro2::Span { + match self { + FieldInnerTy::Option(ty) | FieldInnerTy::Vec(ty) | FieldInnerTy::Plain(ty) => ty.span(), + } + } } /// Field information passed to the builder. Deliberately omits attrs to discourage the @@ -408,59 +426,62 @@ pub(super) enum AllowMultipleAlternatives { Yes, } +fn parse_suggestion_values( + nested: ParseNestedMeta<'_>, + allow_multiple: AllowMultipleAlternatives, +) -> syn::Result<Vec<LitStr>> { + let values = if let Ok(val) = nested.value() { + vec![val.parse()?] + } else { + let content; + parenthesized!(content in nested.input); + + if let AllowMultipleAlternatives::No = allow_multiple { + span_err( + nested.input.span().unwrap(), + "expected exactly one string literal for `code = ...`", + ) + .emit(); + vec![] + } else { + let literals = Punctuated::<LitStr, Token![,]>::parse_terminated(&content); + + match literals { + Ok(p) if p.is_empty() => { + span_err( + content.span().unwrap(), + "expected at least one string literal for `code(...)`", + ) + .emit(); + vec![] + } + Ok(p) => p.into_iter().collect(), + Err(_) => { + span_err( + content.span().unwrap(), + "`code(...)` must contain only string literals", + ) + .emit(); + vec![] + } + } + } + }; + + Ok(values) +} + /// Constructs the `format!()` invocation(s) necessary for a `#[suggestion*(code = "foo")]` or /// `#[suggestion*(code("foo", "bar"))]` attribute field pub(super) fn build_suggestion_code( code_field: &Ident, - meta: &Meta, + nested: ParseNestedMeta<'_>, fields: &impl HasFieldMap, allow_multiple: AllowMultipleAlternatives, ) -> TokenStream { - let values = match meta { - // `code = "foo"` - Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => vec![s], - // `code("foo", "bar")` - Meta::List(MetaList { nested, .. }) => { - if let AllowMultipleAlternatives::No = allow_multiple { - span_err( - meta.span().unwrap(), - "expected exactly one string literal for `code = ...`", - ) - .emit(); - vec![] - } else if nested.is_empty() { - span_err( - meta.span().unwrap(), - "expected at least one string literal for `code(...)`", - ) - .emit(); - vec![] - } else { - nested - .into_iter() - .filter_map(|item| { - if let NestedMeta::Lit(syn::Lit::Str(s)) = item { - Some(s) - } else { - span_err( - item.span().unwrap(), - "`code(...)` must contain only string literals", - ) - .emit(); - None - } - }) - .collect() - } - } - _ => { - span_err( - meta.span().unwrap(), - r#"`code = "..."`/`code(...)` must contain only string literals"#, - ) - .emit(); - vec![] - } + let values = match parse_suggestion_values(nested, allow_multiple) { + Ok(x) => x, + Err(e) => return e.into_compile_error(), }; if let AllowMultipleAlternatives::Yes = allow_multiple { @@ -591,11 +612,9 @@ impl SubdiagnosticKind { let span = attr.span().unwrap(); - let name = attr.path.segments.last().unwrap().ident.to_string(); + let name = attr.path().segments.last().unwrap().ident.to_string(); let name = name.as_str(); - let meta = attr.parse_meta()?; - let mut kind = match name { "label" => SubdiagnosticKind::Label, "note" => SubdiagnosticKind::Note, @@ -608,7 +627,7 @@ impl SubdiagnosticKind { name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix) { if suggestion_kind != SuggestionKind::Normal { - invalid_attr(attr, &meta) + invalid_attr(attr) .help(format!( r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"# )) @@ -625,7 +644,7 @@ impl SubdiagnosticKind { name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix) { if suggestion_kind != SuggestionKind::Normal { - invalid_attr(attr, &meta) + invalid_attr(attr) .help(format!( r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"# )) @@ -637,16 +656,16 @@ impl SubdiagnosticKind { applicability: None, } } else { - throw_invalid_attr!(attr, &meta); + throw_invalid_attr!(attr); } } }; - let nested = match meta { - Meta::List(MetaList { ref nested, .. }) => { + let list = match &attr.meta { + Meta::List(list) => { // An attribute with properties, such as `#[suggestion(code = "...")]` or // `#[error(some::slug)]` - nested + list } Meta::Path(_) => { // An attribute without a slug or other properties, such as `#[note]` - return @@ -668,69 +687,68 @@ impl SubdiagnosticKind { } } _ => { - throw_invalid_attr!(attr, &meta) + throw_invalid_attr!(attr) } }; let mut code = None; let mut suggestion_kind = None; - let mut nested_iter = nested.into_iter().peekable(); - - // Peek at the first nested attribute: if it's a slug path, consume it. - let slug = if let Some(NestedMeta::Meta(Meta::Path(path))) = nested_iter.peek() { - let path = path.clone(); - // Advance the iterator. - nested_iter.next(); - Some(path) - } else { - None - }; + let mut first = true; + let mut slug = None; - for nested_attr in nested_iter { - let meta = match nested_attr { - NestedMeta::Meta(ref meta) => meta, - NestedMeta::Lit(_) => { - invalid_nested_attr(attr, nested_attr).emit(); - continue; + list.parse_nested_meta(|nested| { + if nested.input.is_empty() || nested.input.peek(Token![,]) { + if first { + slug = Some(nested.path); + } else { + span_err(nested.input.span().unwrap(), "a diagnostic slug must be the first argument to the attribute").emit(); } - }; - let span = meta.span().unwrap(); - let nested_name = meta.path().segments.last().unwrap().ident.to_string(); + first = false; + return Ok(()); + } + + first = false; + + let nested_name = nested.path.segments.last().unwrap().ident.to_string(); let nested_name = nested_name.as_str(); - let string_value = match meta { - Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => Some(value), + let path_span = nested.path.span().unwrap(); + let val_span = nested.input.span().unwrap(); - Meta::Path(_) => throw_invalid_nested_attr!(attr, nested_attr, |diag| { - diag.help("a diagnostic slug must be the first argument to the attribute") - }), - _ => None, - }; + macro_rules! get_string { + () => {{ + let Ok(value) = nested.value().and_then(|x| x.parse::<LitStr>()) else { + span_err(val_span, "expected `= \"xxx\"`").emit(); + return Ok(()); + }; + value + }}; + } + + let mut has_errors = false; + let input = nested.input; match (nested_name, &mut kind) { ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => { let code_init = build_suggestion_code( code_field, - meta, + nested, fields, AllowMultipleAlternatives::Yes, ); - code.set_once(code_init, span); + code.set_once(code_init, path_span); } ( "applicability", SubdiagnosticKind::Suggestion { ref mut applicability, .. } | SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. }, ) => { - let Some(value) = string_value else { - invalid_nested_attr(attr, nested_attr).emit(); - continue; - }; - + let value = get_string!(); let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| { - span_err(span, "invalid applicability").emit(); + span_err(value.span().unwrap(), "invalid applicability").emit(); + has_errors = true; Applicability::Unspecified }); applicability.set_once(value, span); @@ -740,15 +758,13 @@ impl SubdiagnosticKind { SubdiagnosticKind::Suggestion { .. } | SubdiagnosticKind::MultipartSuggestion { .. }, ) => { - let Some(value) = string_value else { - invalid_nested_attr(attr, nested_attr).emit(); - continue; - }; + let value = get_string!(); let value = value.value().parse().unwrap_or_else(|()| { span_err(value.span().unwrap(), "invalid suggestion style") .help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`") .emit(); + has_errors = true; SuggestionKind::Normal }); @@ -757,22 +773,32 @@ impl SubdiagnosticKind { // Invalid nested attribute (_, SubdiagnosticKind::Suggestion { .. }) => { - invalid_nested_attr(attr, nested_attr) + span_err(path_span, "invalid nested attribute") .help( "only `style`, `code` and `applicability` are valid nested attributes", ) .emit(); + has_errors = true; } (_, SubdiagnosticKind::MultipartSuggestion { .. }) => { - invalid_nested_attr(attr, nested_attr) + span_err(path_span, "invalid nested attribute") .help("only `style` and `applicability` are valid nested attributes") - .emit() + .emit(); + has_errors = true; } _ => { - invalid_nested_attr(attr, nested_attr).emit(); + span_err(path_span, "invalid nested attribute").emit(); + has_errors = true; } } - } + + if has_errors { + // Consume the rest of the input to avoid spamming errors + let _ = input.parse::<TokenStream>(); + } + + Ok(()) + })?; match kind { SubdiagnosticKind::Suggestion { @@ -831,9 +857,10 @@ impl quote::IdentFragment for SubdiagnosticKind { /// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic /// call (like `span_label`). pub(super) fn should_generate_set_arg(field: &Field) -> bool { - field.attrs.is_empty() + // Perhaps this should be an exhaustive list... + field.attrs.iter().all(|attr| is_doc_comment(attr)) } pub(super) fn is_doc_comment(attr: &Attribute) -> bool { - attr.path.segments.last().unwrap().ident == "doc" + attr.path().segments.last().unwrap().ident == "doc" } diff --git a/compiler/rustc_macros/src/hash_stable.rs b/compiler/rustc_macros/src/hash_stable.rs index 63bdcea87f8..75a2f7009c2 100644 --- a/compiler/rustc_macros/src/hash_stable.rs +++ b/compiler/rustc_macros/src/hash_stable.rs @@ -1,6 +1,6 @@ use proc_macro2::{self, Ident}; use quote::quote; -use syn::{self, parse_quote, Meta, NestedMeta}; +use syn::{self, parse_quote}; struct Attributes { ignore: bool, @@ -10,32 +10,29 @@ struct Attributes { fn parse_attributes(field: &syn::Field) -> Attributes { let mut attrs = Attributes { ignore: false, project: None }; for attr in &field.attrs { - if let Ok(meta) = attr.parse_meta() { - if !meta.path().is_ident("stable_hasher") { - continue; + let meta = &attr.meta; + if !meta.path().is_ident("stable_hasher") { + continue; + } + let mut any_attr = false; + let _ = attr.parse_nested_meta(|nested| { + if nested.path.is_ident("ignore") { + attrs.ignore = true; + any_attr = true; } - let mut any_attr = false; - if let Meta::List(list) = meta { - for nested in list.nested.iter() { - if let NestedMeta::Meta(meta) = nested { - if meta.path().is_ident("ignore") { - attrs.ignore = true; - any_attr = true; - } - if meta.path().is_ident("project") { - if let Meta::List(list) = meta { - if let Some(NestedMeta::Meta(meta)) = list.nested.iter().next() { - attrs.project = meta.path().get_ident().cloned(); - any_attr = true; - } - } - } + if nested.path.is_ident("project") { + let _ = nested.parse_nested_meta(|meta| { + if attrs.project.is_none() { + attrs.project = meta.path.get_ident().cloned(); } - } - } - if !any_attr { - panic!("error parsing stable_hasher"); + any_attr = true; + Ok(()) + }); } + Ok(()) + }); + if !any_attr { + panic!("error parsing stable_hasher"); } } attrs diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index d2cb6ee9f71..904f8eb5731 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -54,64 +54,6 @@ pub fn newtype_index(input: TokenStream) -> TokenStream { newtype::newtype(input) } -/// Implements the `fluent_messages` macro, which performs compile-time validation of the -/// compiler's Fluent resources (i.e. that the resources parse and don't multiply define the same -/// messages) and generates constants that make using those messages in diagnostics more ergonomic. -/// -/// For example, given the following invocation of the macro.. -/// -/// ```ignore (rust) -/// fluent_messages! { -/// typeck => "./typeck.ftl", -/// } -/// ``` -/// ..where `typeck.ftl` has the following contents.. -/// -/// ```fluent -/// typeck_field_multiply_specified_in_initializer = -/// field `{$ident}` specified more than once -/// .label = used more than once -/// .label_previous_use = first use of `{$ident}` -/// ``` -/// ...then the macro parse the Fluent resource, emitting a diagnostic if it fails to do so, and -/// will generate the following code: -/// -/// ```ignore (rust) -/// pub static DEFAULT_LOCALE_RESOURCES: &'static [&'static str] = &[ -/// include_str!("./typeck.ftl"), -/// ]; -/// -/// mod fluent_generated { -/// mod typeck { -/// pub const field_multiply_specified_in_initializer: DiagnosticMessage = -/// DiagnosticMessage::fluent("typeck_field_multiply_specified_in_initializer"); -/// pub const field_multiply_specified_in_initializer_label_previous_use: DiagnosticMessage = -/// DiagnosticMessage::fluent_attr( -/// "typeck_field_multiply_specified_in_initializer", -/// "previous_use_label" -/// ); -/// } -/// } -/// ``` -/// When emitting a diagnostic, the generated constants can be used as follows: -/// -/// ```ignore (rust) -/// let mut err = sess.struct_span_err( -/// span, -/// fluent::typeck::field_multiply_specified_in_initializer -/// ); -/// err.span_default_label(span); -/// err.span_label( -/// previous_use_span, -/// fluent::typeck::field_multiply_specified_in_initializer_label_previous_use -/// ); -/// err.emit(); -/// ``` -#[proc_macro] -pub fn fluent_messages(input: TokenStream) -> TokenStream { - diagnostics::fluent_messages(input) -} - decl_derive!([HashStable, attributes(stable_hasher)] => hash_stable::hash_stable_derive); decl_derive!( [HashStable_Generic, attributes(stable_hasher)] => diff --git a/compiler/rustc_macros/src/newtype.rs b/compiler/rustc_macros/src/newtype.rs index 89ea89cf502..415a89b0f92 100644 --- a/compiler/rustc_macros/src/newtype.rs +++ b/compiler/rustc_macros/src/newtype.rs @@ -25,7 +25,7 @@ impl Parse for Newtype { let mut encodable = true; let mut ord = true; - attrs.retain(|attr| match attr.path.get_ident() { + attrs.retain(|attr| match attr.path().get_ident() { Some(ident) => match &*ident.to_string() { "custom_encodable" => { encodable = false; @@ -36,22 +36,22 @@ impl Parse for Newtype { false } "max" => { - let Ok(Meta::NameValue(literal) )= attr.parse_meta() else { + let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta else { panic!("#[max = NUMBER] attribute requires max value"); }; - if let Some(old) = max.replace(literal.lit) { + if let Some(old) = max.replace(lit.lit.clone()) { panic!("Specified multiple max: {old:?}"); } false } "debug_format" => { - let Ok(Meta::NameValue(literal) )= attr.parse_meta() else { + let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta else { panic!("#[debug_format = FMT] attribute requires a format"); }; - if let Some(old) = debug_format.replace(literal.lit) { + if let Some(old) = debug_format.replace(lit.lit.clone()) { panic!("Specified multiple debug format options: {old:?}"); } @@ -254,7 +254,7 @@ impl Parse for Newtype { } } - impl rustc_index::vec::Idx for #name { + impl rustc_index::Idx for #name { #[inline] fn new(value: usize) -> Self { Self::from_usize(value) diff --git a/compiler/rustc_macros/src/query.rs b/compiler/rustc_macros/src/query.rs index 08e42a8a08f..a8b25ff66d7 100644 --- a/compiler/rustc_macros/src/query.rs +++ b/compiler/rustc_macros/src/query.rs @@ -15,7 +15,7 @@ mod kw { /// Ensures only doc comment attributes are used fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> { let inner = |attr: Attribute| { - if !attr.path.is_ident("doc") { + if !attr.path().is_ident("doc") { Err(Error::new(attr.span(), "attributes not supported on queries")) } else if attr.style != AttrStyle::Outer { Err(Error::new( @@ -48,7 +48,7 @@ impl Parse for Query { let name: Ident = input.parse()?; let arg_content; parenthesized!(arg_content in input); - let key = arg_content.parse()?; + let key = Pat::parse_single(&arg_content)?; arg_content.parse::<Token![:]>()?; let arg = arg_content.parse()?; let result = input.parse()?; @@ -112,9 +112,6 @@ struct QueryModifiers { /// Use a separate query provider for local and extern crates separate_provide_extern: Option<Ident>, - /// Always remap the ParamEnv's constness before hashing. - remap_env_constness: Option<Ident>, - /// Generate a `feed` method to set the query's value from another query. feedable: Option<Ident>, } @@ -130,7 +127,6 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> { let mut eval_always = None; let mut depth_limit = None; let mut separate_provide_extern = None; - let mut remap_env_constness = None; let mut feedable = None; while !input.is_empty() { @@ -158,7 +154,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> { } else { None }; - let list = attr_content.parse_terminated(Expr::parse)?; + let list = attr_content.parse_terminated(Expr::parse, Token![,])?; try_insert!(desc = (tcx, list)); } else if modifier == "cache_on_disk_if" { // Parse a cache modifier like: @@ -166,7 +162,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> { let args = if input.peek(token::Paren) { let args; parenthesized!(args in input); - let tcx = args.parse()?; + let tcx = Pat::parse_single(&args)?; Some(tcx) } else { None @@ -189,8 +185,6 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> { try_insert!(depth_limit = modifier); } else if modifier == "separate_provide_extern" { try_insert!(separate_provide_extern = modifier); - } else if modifier == "remap_env_constness" { - try_insert!(remap_env_constness = modifier); } else if modifier == "feedable" { try_insert!(feedable = modifier); } else { @@ -211,7 +205,6 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> { eval_always, depth_limit, separate_provide_extern, - remap_env_constness, feedable, }) } @@ -332,7 +325,6 @@ pub fn rustc_queries(input: TokenStream) -> TokenStream { eval_always, depth_limit, separate_provide_extern, - remap_env_constness, ); if modifiers.cache.is_some() { diff --git a/compiler/rustc_macros/src/serialize.rs b/compiler/rustc_macros/src/serialize.rs index 82e6972d027..8d017d149f6 100644 --- a/compiler/rustc_macros/src/serialize.rs +++ b/compiler/rustc_macros/src/serialize.rs @@ -42,6 +42,12 @@ fn decodable_body( } let ty_name = s.ast().ident.to_string(); let decode_body = match s.variants() { + [] => { + let message = format!("`{}` has no variants to decode", ty_name); + quote! { + panic!(#message) + } + } [vi] => vi.construct(|field, _index| decode_field(field)), variants => { let match_inner: TokenStream = variants @@ -139,6 +145,11 @@ fn encodable_body( }); let encode_body = match s.variants() { + [] => { + quote! { + match *self {} + } + } [_] => { let encode_inner = s.each_variant(|vi| { vi.bindings() @@ -160,6 +171,23 @@ fn encodable_body( } } _ => { + let disc = { + let mut variant_idx = 0usize; + let encode_inner = s.each_variant(|_| { + let result = quote! { + #variant_idx + }; + variant_idx += 1; + result + }); + quote! { + let disc = match *self { + #encode_inner + }; + ::rustc_serialize::Encoder::emit_usize(__encoder, disc); + } + }; + let mut variant_idx = 0usize; let encode_inner = s.each_variant(|vi| { let encode_fields: TokenStream = vi @@ -176,26 +204,11 @@ fn encodable_body( result }) .collect(); - - let result = if !vi.bindings().is_empty() { - quote! { - ::rustc_serialize::Encoder::emit_enum_variant( - __encoder, - #variant_idx, - |__encoder| { #encode_fields } - ) - } - } else { - quote! { - ::rustc_serialize::Encoder::emit_fieldless_enum_variant::<#variant_idx>( - __encoder, - ) - } - }; variant_idx += 1; - result + encode_fields }); quote! { + #disc match *self { #encode_inner } diff --git a/compiler/rustc_macros/src/type_foldable.rs b/compiler/rustc_macros/src/type_foldable.rs index 51729a377d9..5ee4d879313 100644 --- a/compiler/rustc_macros/src/type_foldable.rs +++ b/compiler/rustc_macros/src/type_foldable.rs @@ -1,5 +1,5 @@ use quote::{quote, ToTokens}; -use syn::{parse_quote, Attribute, Meta, NestedMeta}; +use syn::parse_quote; pub fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { if let syn::Data::Union(_) = s.ast().data { @@ -17,36 +17,35 @@ pub fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2:: vi.construct(|_, index| { let bind = &bindings[index]; + let mut fixed = false; + // retain value of fields with #[type_foldable(identity)] - let fixed = bind - .ast() - .attrs - .iter() - .map(Attribute::parse_meta) - .filter_map(Result::ok) - .flat_map(|attr| match attr { - Meta::List(list) if list.path.is_ident("type_foldable") => list.nested, - _ => Default::default(), - }) - .any(|nested| match nested { - NestedMeta::Meta(Meta::Path(path)) => path.is_ident("identity"), - _ => false, + bind.ast().attrs.iter().for_each(|x| { + if !x.path().is_ident("type_foldable") { + return; + } + let _ = x.parse_nested_meta(|nested| { + if nested.path.is_ident("identity") { + fixed = true; + } + Ok(()) }); + }); if fixed { bind.to_token_stream() } else { quote! { - ::rustc_middle::ty::fold::ir::TypeFoldable::try_fold_with(#bind, __folder)? + ::rustc_middle::ty::fold::TypeFoldable::try_fold_with(#bind, __folder)? } } }) }); s.bound_impl( - quote!(::rustc_middle::ty::fold::ir::TypeFoldable<::rustc_middle::ty::TyCtxt<'tcx>>), + quote!(::rustc_middle::ty::fold::TypeFoldable<::rustc_middle::ty::TyCtxt<'tcx>>), quote! { - fn try_fold_with<__F: ::rustc_middle::ty::fold::FallibleTypeFolder<'tcx>>( + fn try_fold_with<__F: ::rustc_middle::ty::fold::FallibleTypeFolder<::rustc_middle::ty::TyCtxt<'tcx>>>( self, __folder: &mut __F ) -> Result<Self, __F::Error> { diff --git a/compiler/rustc_macros/src/type_visitable.rs b/compiler/rustc_macros/src/type_visitable.rs index 0a16a371fdc..dcd505a105e 100644 --- a/compiler/rustc_macros/src/type_visitable.rs +++ b/compiler/rustc_macros/src/type_visitable.rs @@ -1,5 +1,5 @@ use quote::quote; -use syn::{parse_quote, Attribute, Meta, NestedMeta}; +use syn::parse_quote; pub fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { if let syn::Data::Union(_) = s.ast().data { @@ -8,19 +8,21 @@ pub fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2: // ignore fields with #[type_visitable(ignore)] s.filter(|bi| { - !bi.ast() - .attrs - .iter() - .map(Attribute::parse_meta) - .filter_map(Result::ok) - .flat_map(|attr| match attr { - Meta::List(list) if list.path.is_ident("type_visitable") => list.nested, - _ => Default::default(), - }) - .any(|nested| match nested { - NestedMeta::Meta(Meta::Path(path)) => path.is_ident("ignore"), - _ => false, - }) + let mut ignored = false; + + bi.ast().attrs.iter().for_each(|attr| { + if !attr.path().is_ident("type_visitable") { + return; + } + let _ = attr.parse_nested_meta(|nested| { + if nested.path.is_ident("ignore") { + ignored = true; + } + Ok(()) + }); + }); + + !ignored }); if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") { @@ -30,15 +32,15 @@ pub fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2: s.add_bounds(synstructure::AddBounds::Generics); let body_visit = s.each(|bind| { quote! { - ::rustc_middle::ty::visit::ir::TypeVisitable::visit_with(#bind, __visitor)?; + ::rustc_middle::ty::visit::TypeVisitable::visit_with(#bind, __visitor)?; } }); s.bind_with(|_| synstructure::BindStyle::Move); s.bound_impl( - quote!(::rustc_middle::ty::visit::ir::TypeVisitable<::rustc_middle::ty::TyCtxt<'tcx>>), + quote!(::rustc_middle::ty::visit::TypeVisitable<::rustc_middle::ty::TyCtxt<'tcx>>), quote! { - fn visit_with<__V: ::rustc_middle::ty::visit::TypeVisitor<'tcx>>( + fn visit_with<__V: ::rustc_middle::ty::visit::TypeVisitor<::rustc_middle::ty::TyCtxt<'tcx>>>( &self, __visitor: &mut __V ) -> ::std::ops::ControlFlow<__V::BreakTy> { |
