summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_macros/src')
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs4
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs27
-rw-r--r--compiler/rustc_macros/src/diagnostics/fluent.rs433
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs25
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs67
-rw-r--r--compiler/rustc_macros/src/lib.rs31
-rw-r--r--compiler/rustc_macros/src/serialize.rs47
-rw-r--r--compiler/rustc_macros/src/type_foldable.rs33
-rw-r--r--compiler/rustc_macros/src/type_visitable.rs23
9 files changed, 401 insertions, 289 deletions
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<TokenStream>), 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<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,
@@ -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<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");
+ }],
+ 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<Span>` 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<Inner>`, returns `FieldInnerTy::Option(Inner)`.
+ /// - If `ty` is a `Vec<Inner>`, 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,
}
diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs
index bb3722fe1..737500cc2 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 {
@@ -124,8 +120,27 @@ decl_derive!([TyDecodable] => serialize::type_decodable_derive);
decl_derive!([TyEncodable] => serialize::type_encodable_derive);
decl_derive!([MetadataDecodable] => serialize::meta_decodable_derive);
decl_derive!([MetadataEncodable] => serialize::meta_encodable_derive);
-decl_derive!([TypeFoldable, attributes(type_foldable)] => type_foldable::type_foldable_derive);
-decl_derive!([TypeVisitable, attributes(type_visitable)] => type_visitable::type_visitable_derive);
+decl_derive!(
+ [TypeFoldable, attributes(type_foldable)] =>
+ /// Derives `TypeFoldable` for the annotated `struct` or `enum` (`union` is not supported).
+ ///
+ /// The fold will produce a value of the same struct or enum variant as the input, with
+ /// each field respectively folded using the `TypeFoldable` implementation for its type.
+ /// However, if a field of a struct or an enum variant is annotated with
+ /// `#[type_foldable(identity)]` then that field will retain its incumbent value (and its
+ /// type is not required to implement `TypeFoldable`).
+ type_foldable::type_foldable_derive
+);
+decl_derive!(
+ [TypeVisitable, attributes(type_visitable)] =>
+ /// Derives `TypeVisitable` for the annotated `struct` or `enum` (`union` is not supported).
+ ///
+ /// Each field of the struct or enum variant will be visited in definition order, using the
+ /// `TypeVisitable` implementation for its type. However, if a field of a struct or an enum
+ /// variant is annotated with `#[type_visitable(ignore)]` then that field will not be
+ /// visited (and its type is not required to implement `TypeVisitable`).
+ type_visitable::type_visitable_derive
+);
decl_derive!([Lift, attributes(lift)] => lift::lift_derive);
decl_derive!(
[Diagnostic, attributes(
diff --git a/compiler/rustc_macros/src/serialize.rs b/compiler/rustc_macros/src/serialize.rs
index 82e6972d0..8d017d149 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 23e619221..388e254cd 100644
--- a/compiler/rustc_macros/src/type_foldable.rs
+++ b/compiler/rustc_macros/src/type_foldable.rs
@@ -1,5 +1,5 @@
-use quote::quote;
-use syn::parse_quote;
+use quote::{quote, ToTokens};
+use syn::{parse_quote, Attribute, Meta, NestedMeta};
pub fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
if let syn::Data::Union(_) = s.ast().data {
@@ -16,16 +16,37 @@ pub fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::
let bindings = vi.bindings();
vi.construct(|_, index| {
let bind = &bindings[index];
- quote! {
- ::rustc_middle::ty::fold::TypeFoldable::try_fold_with(#bind, __folder)?
+
+ // 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,
+ });
+
+ if fixed {
+ bind.to_token_stream()
+ } else {
+ quote! {
+ ::rustc_middle::ty::fold::TypeFoldable::try_fold_with(#bind, __folder)?
+ }
}
})
});
s.bound_impl(
- quote!(::rustc_middle::ty::fold::TypeFoldable<'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 1f95661ce..f6f4c4779 100644
--- a/compiler/rustc_macros/src/type_visitable.rs
+++ b/compiler/rustc_macros/src/type_visitable.rs
@@ -1,11 +1,28 @@
use quote::quote;
-use syn::parse_quote;
+use syn::{parse_quote, Attribute, Meta, NestedMeta};
pub fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
if let syn::Data::Union(_) = s.ast().data {
panic!("cannot derive on union")
}
+ // 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,
+ })
+ });
+
if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") {
s.add_impl_generic(parse_quote! { 'tcx });
}
@@ -19,9 +36,9 @@ pub fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2:
s.bind_with(|_| synstructure::BindStyle::Move);
s.bound_impl(
- quote!(::rustc_middle::ty::visit::TypeVisitable<'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> {