diff options
| author | David Wood <david.wood@huawei.com> | 2022-10-13 10:13:02 +0100 | 
|---|---|---|
| committer | David Wood <david.wood@huawei.com> | 2023-02-22 09:15:53 +0000 | 
| commit | d1fcf611175e695c35c6cc0537d710277c1a5c6f (patch) | |
| tree | 7f3dfc8cdfa379edef39ff5f8f54fdbf88668fc5 /compiler/rustc_macros | |
| parent | f9216b75646cde0c4c69ae00232778a47fc893d3 (diff) | |
| download | rust-d1fcf611175e695c35c6cc0537d710277c1a5c6f.tar.gz rust-d1fcf611175e695c35c6cc0537d710277c1a5c6f.zip  | |
errors: generate typed identifiers in each crate
Instead of loading the Fluent resources for every crate in `rustc_error_messages`, each crate generates typed identifiers for its own diagnostics and creates a static which are pulled together in the `rustc_driver` crate and provided to the diagnostic emitter. Signed-off-by: David Wood <david.wood@huawei.com>
Diffstat (limited to 'compiler/rustc_macros')
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/diagnostic.rs | 4 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/fluent.rs | 449 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/subdiagnostic.rs | 4 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/lib.rs | 8 | 
5 files changed, 230 insertions, 241 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..46068f8c868 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -452,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 @@ -476,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 ); } } @@ -486,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); } } diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs index 08098c9bb2a..38c0f4895db 100644 --- a/compiler/rustc_macros/src/diagnostics/fluent.rs +++ b/compiler/rustc_macros/src/diagnostics/fluent.rs @@ -19,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<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, @@ -84,251 +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<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 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, + 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"); - } - continue; + }], + opt: Default::default(), + }; + let dl = DisplayList::from(snippet); + eprintln!("{dl}\n"); } - }; - 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); + return failed(&crate_name); + } + }; - if name.contains('-') { - Diagnostic::spanned( - path_span, - Level::Error, - format!("name `{name}` contains a '-' character"), - ) - .help("replace any '-'s with '_'s") - .emit(); - } + 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 - { - messagerefs.push((id.name, *name)); - } + 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)); } } + } - // 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); + // `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) { + 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 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 + ); + }); + + 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; + } + + 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) - ); - }); - } } } + } - 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(); - } + 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( - 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!(), + 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 906e4c0b0e1..90660fc1f93 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -512,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/lib.rs b/compiler/rustc_macros/src/lib.rs index d2cb6ee9f71..737500cc257 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -61,9 +61,7 @@ pub fn newtype_index(input: TokenStream) -> TokenStream { /// For example, given the following invocation of the macro.. /// /// ```ignore (rust) -/// fluent_messages! { -/// typeck => "./typeck.ftl", -/// } +/// fluent_messages! { "./typeck.ftl" } /// ``` /// ..where `typeck.ftl` has the following contents.. /// @@ -77,9 +75,7 @@ pub fn newtype_index(input: TokenStream) -> TokenStream { /// will generate the following code: /// /// ```ignore (rust) -/// pub static DEFAULT_LOCALE_RESOURCES: &'static [&'static str] = &[ -/// include_str!("./typeck.ftl"), -/// ]; +/// pub static DEFAULT_LOCALE_RESOURCE: &'static [&'static str] = include_str!("./typeck.ftl"); /// /// mod fluent_generated { /// mod typeck {  | 
