summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_macros
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /compiler/rustc_macros
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_macros')
-rw-r--r--compiler/rustc_macros/Cargo.toml17
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs225
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs630
-rw-r--r--compiler/rustc_macros/src/diagnostics/error.rs141
-rw-r--r--compiler/rustc_macros/src/diagnostics/fluent.rs277
-rw-r--r--compiler/rustc_macros/src/diagnostics/mod.rs159
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs491
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs356
-rw-r--r--compiler/rustc_macros/src/hash_stable.rs131
-rw-r--r--compiler/rustc_macros/src/lib.rs180
-rw-r--r--compiler/rustc_macros/src/lift.rs52
-rw-r--r--compiler/rustc_macros/src/newtype.rs333
-rw-r--r--compiler/rustc_macros/src/query.rs566
-rw-r--r--compiler/rustc_macros/src/serialize.rs224
-rw-r--r--compiler/rustc_macros/src/symbols.rs236
-rw-r--r--compiler/rustc_macros/src/symbols/tests.rs102
-rw-r--r--compiler/rustc_macros/src/type_foldable.rs36
-rw-r--r--compiler/rustc_macros/src/type_visitable.rs33
18 files changed, 4189 insertions, 0 deletions
diff --git a/compiler/rustc_macros/Cargo.toml b/compiler/rustc_macros/Cargo.toml
new file mode 100644
index 000000000..25b3aadc1
--- /dev/null
+++ b/compiler/rustc_macros/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "rustc_macros"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+annotate-snippets = "0.8.0"
+fluent-bundle = "0.15.2"
+fluent-syntax = "0.11"
+synstructure = "0.12.1"
+syn = { version = "1", features = ["full"] }
+proc-macro2 = "1"
+quote = "1"
+unic-langid = { version = "0.9.0", features = ["macros"] }
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
new file mode 100644
index 000000000..6b5b8b593
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
@@ -0,0 +1,225 @@
+#![deny(unused_must_use)]
+
+use crate::diagnostics::diagnostic_builder::{DiagnosticDeriveBuilder, DiagnosticDeriveKind};
+use crate::diagnostics::error::{span_err, DiagnosticDeriveError};
+use crate::diagnostics::utils::{build_field_mapping, SetOnce};
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::spanned::Spanned;
+use synstructure::Structure;
+
+/// The central struct for constructing the `into_diagnostic` method from an annotated struct.
+pub(crate) struct SessionDiagnosticDerive<'a> {
+ structure: Structure<'a>,
+ sess: syn::Ident,
+ builder: DiagnosticDeriveBuilder,
+}
+
+impl<'a> SessionDiagnosticDerive<'a> {
+ pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self {
+ Self {
+ builder: DiagnosticDeriveBuilder {
+ diag,
+ fields: build_field_mapping(&structure),
+ kind: None,
+ code: None,
+ slug: None,
+ },
+ sess,
+ structure,
+ }
+ }
+
+ pub(crate) fn into_tokens(self) -> TokenStream {
+ let SessionDiagnosticDerive { mut structure, sess, mut builder } = self;
+
+ let ast = structure.ast();
+ let (implementation, param_ty) = {
+ if let syn::Data::Struct(..) = ast.data {
+ let preamble = builder.preamble(&structure);
+ let (attrs, args) = builder.body(&mut structure);
+
+ let span = ast.span().unwrap();
+ let diag = &builder.diag;
+ let init = match (builder.kind.value(), builder.slug.value()) {
+ (None, _) => {
+ span_err(span, "diagnostic kind not specified")
+ .help("use the `#[error(...)]` attribute to create an error")
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(kind), None) => {
+ span_err(span, "diagnostic slug not specified")
+ .help(&format!(
+ "specify the slug as the first argument to the attribute, such as \
+ `#[{}(typeck::example_error)]`",
+ kind.descr()
+ ))
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(DiagnosticDeriveKind::Lint), _) => {
+ span_err(span, "only `#[error(..)]` and `#[warning(..)]` are supported")
+ .help("use the `#[error(...)]` attribute to create a error")
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(DiagnosticDeriveKind::Error), Some(slug)) => {
+ quote! {
+ let mut #diag = #sess.struct_err(rustc_errors::fluent::#slug);
+ }
+ }
+ (Some(DiagnosticDeriveKind::Warn), Some(slug)) => {
+ quote! {
+ let mut #diag = #sess.struct_warn(rustc_errors::fluent::#slug);
+ }
+ }
+ };
+
+ let implementation = quote! {
+ #init
+ #preamble
+ match self {
+ #attrs
+ }
+ match self {
+ #args
+ }
+ #diag
+ };
+ let param_ty = match builder.kind {
+ Some((DiagnosticDeriveKind::Error, _)) => {
+ quote! { rustc_errors::ErrorGuaranteed }
+ }
+ Some((DiagnosticDeriveKind::Lint | DiagnosticDeriveKind::Warn, _)) => {
+ quote! { () }
+ }
+ _ => unreachable!(),
+ };
+
+ (implementation, param_ty)
+ } else {
+ span_err(
+ ast.span().unwrap(),
+ "`#[derive(SessionDiagnostic)]` can only be used on structs",
+ )
+ .emit();
+
+ let implementation = DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ let param_ty = quote! { rustc_errors::ErrorGuaranteed };
+ (implementation, param_ty)
+ }
+ };
+
+ structure.gen_impl(quote! {
+ gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty>
+ for @Self
+ {
+ fn into_diagnostic(
+ self,
+ #sess: &'__session_diagnostic_sess rustc_session::parse::ParseSess
+ ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> {
+ use rustc_errors::IntoDiagnosticArg;
+ #implementation
+ }
+ }
+ })
+ }
+}
+
+/// The central struct for constructing the `decorate_lint` method from an annotated struct.
+pub(crate) struct LintDiagnosticDerive<'a> {
+ structure: Structure<'a>,
+ builder: DiagnosticDeriveBuilder,
+}
+
+impl<'a> LintDiagnosticDerive<'a> {
+ pub(crate) fn new(diag: syn::Ident, structure: Structure<'a>) -> Self {
+ Self {
+ builder: DiagnosticDeriveBuilder {
+ diag,
+ fields: build_field_mapping(&structure),
+ kind: None,
+ code: None,
+ slug: None,
+ },
+ structure,
+ }
+ }
+
+ pub(crate) fn into_tokens(self) -> TokenStream {
+ let LintDiagnosticDerive { mut structure, mut builder } = self;
+
+ let ast = structure.ast();
+ let implementation = {
+ if let syn::Data::Struct(..) = ast.data {
+ let preamble = builder.preamble(&structure);
+ let (attrs, args) = builder.body(&mut structure);
+
+ let diag = &builder.diag;
+ let span = ast.span().unwrap();
+ let init = match (builder.kind.value(), builder.slug.value()) {
+ (None, _) => {
+ span_err(span, "diagnostic kind not specified")
+ .help("use the `#[error(...)]` attribute to create an error")
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(kind), None) => {
+ span_err(span, "diagnostic slug not specified")
+ .help(&format!(
+ "specify the slug as the first argument to the attribute, such as \
+ `#[{}(typeck::example_error)]`",
+ kind.descr()
+ ))
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(DiagnosticDeriveKind::Error | DiagnosticDeriveKind::Warn), _) => {
+ span_err(span, "only `#[lint(..)]` is supported")
+ .help("use the `#[lint(...)]` attribute to create a lint")
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(DiagnosticDeriveKind::Lint), Some(slug)) => {
+ quote! {
+ let mut #diag = #diag.build(rustc_errors::fluent::#slug);
+ }
+ }
+ };
+
+ let implementation = quote! {
+ #init
+ #preamble
+ match self {
+ #attrs
+ }
+ match self {
+ #args
+ }
+ #diag.emit();
+ };
+
+ implementation
+ } else {
+ span_err(
+ ast.span().unwrap(),
+ "`#[derive(LintDiagnostic)]` can only be used on structs",
+ )
+ .emit();
+
+ DiagnosticDeriveError::ErrorHandled.to_compile_error()
+ }
+ };
+
+ let diag = &builder.diag;
+ structure.gen_impl(quote! {
+ gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
+ fn decorate_lint(self, #diag: rustc_errors::LintDiagnosticBuilder<'__a, ()>) {
+ use rustc_errors::IntoDiagnosticArg;
+ #implementation
+ }
+ }
+ })
+ }
+}
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
new file mode 100644
index 000000000..6c9561925
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -0,0 +1,630 @@
+#![deny(unused_must_use)]
+
+use crate::diagnostics::error::{
+ invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
+ DiagnosticDeriveError,
+};
+use crate::diagnostics::utils::{
+ report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path,
+ Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
+};
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::{format_ident, quote};
+use std::collections::HashMap;
+use std::str::FromStr;
+use syn::{
+ parse_quote, spanned::Spanned, Attribute, Field, Meta, MetaList, MetaNameValue, NestedMeta,
+ Path, Type,
+};
+use synstructure::{BindingInfo, Structure};
+
+/// What kind of diagnostic is being derived - an error, a warning or a lint?
+#[derive(Copy, Clone)]
+pub(crate) enum DiagnosticDeriveKind {
+ /// `#[error(..)]`
+ Error,
+ /// `#[warn(..)]`
+ Warn,
+ /// `#[lint(..)]`
+ Lint,
+}
+
+impl DiagnosticDeriveKind {
+ /// Returns human-readable string corresponding to the kind.
+ pub fn descr(&self) -> &'static str {
+ match self {
+ DiagnosticDeriveKind::Error => "error",
+ DiagnosticDeriveKind::Warn => "warning",
+ DiagnosticDeriveKind::Lint => "lint",
+ }
+ }
+}
+
+/// Tracks persistent information required for building up individual calls to diagnostic methods
+/// for generated diagnostic derives - both `SessionDiagnostic` for errors/warnings and
+/// `LintDiagnostic` for lints.
+pub(crate) struct DiagnosticDeriveBuilder {
+ /// The identifier to use for the generated `DiagnosticBuilder` instance.
+ pub diag: syn::Ident,
+
+ /// Store a map of field name to its corresponding field. This is built on construction of the
+ /// derive builder.
+ pub fields: HashMap<String, TokenStream>,
+
+ /// Kind of diagnostic requested via the struct attribute.
+ pub kind: Option<(DiagnosticDeriveKind, proc_macro::Span)>,
+ /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
+ /// has the actual diagnostic message.
+ pub slug: Option<(Path, proc_macro::Span)>,
+ /// Error codes are a optional part of the struct attribute - this is only set to detect
+ /// multiple specifications.
+ pub code: Option<(String, proc_macro::Span)>,
+}
+
+impl HasFieldMap for DiagnosticDeriveBuilder {
+ fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
+ self.fields.get(field)
+ }
+}
+
+impl DiagnosticDeriveBuilder {
+ pub fn preamble<'s>(&mut self, structure: &Structure<'s>) -> TokenStream {
+ let ast = structure.ast();
+ let attrs = &ast.attrs;
+ let preamble = attrs.iter().map(|attr| {
+ self.generate_structure_code_for_attr(attr).unwrap_or_else(|v| v.to_compile_error())
+ });
+
+ quote! {
+ #(#preamble)*;
+ }
+ }
+
+ pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, TokenStream) {
+ // Keep track of which fields need to be handled with a by-move binding.
+ let mut needs_moved = std::collections::HashSet::new();
+
+ // Generates calls to `span_label` and similar functions based on the attributes
+ // on fields. Code for suggestions uses formatting machinery and the value of
+ // other fields - because any given field can be referenced multiple times, it
+ // should be accessed through a borrow. When passing fields to `add_subdiagnostic`
+ // or `set_arg` (which happens below) for Fluent, we want to move the data, so that
+ // has to happen in a separate pass over the fields.
+ let attrs = structure
+ .clone()
+ .filter(|field_binding| {
+ let ast = &field_binding.ast();
+ !self.needs_move(ast) || {
+ needs_moved.insert(field_binding.binding.clone());
+ false
+ }
+ })
+ .each(|field_binding| self.generate_field_attrs_code(field_binding));
+
+ structure.bind_with(|_| synstructure::BindStyle::Move);
+ // When a field has attributes like `#[label]` or `#[note]` then it doesn't
+ // need to be passed as an argument to the diagnostic. But when a field has no
+ // attributes or a `#[subdiagnostic]` attribute then it must be passed as an
+ // argument to the diagnostic so that it can be referred to by Fluent messages.
+ let args = structure
+ .filter(|field_binding| needs_moved.contains(&field_binding.binding))
+ .each(|field_binding| self.generate_field_attrs_code(field_binding));
+
+ (attrs, args)
+ }
+
+ /// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
+ /// call (like `span_label`).
+ fn should_generate_set_arg(&self, field: &Field) -> bool {
+ field.attrs.is_empty()
+ }
+
+ /// Returns `true` if `field` needs to have code generated in the by-move branch of the
+ /// generated derive rather than the by-ref branch.
+ fn needs_move(&self, field: &Field) -> bool {
+ let generates_set_arg = self.should_generate_set_arg(field);
+ let is_multispan = type_matches_path(&field.ty, &["rustc_errors", "MultiSpan"]);
+ // FIXME(davidtwco): better support for one field needing to be in the by-move and
+ // by-ref branches.
+ let is_subdiagnostic = field
+ .attrs
+ .iter()
+ .map(|attr| attr.path.segments.last().unwrap().ident.to_string())
+ .any(|attr| attr == "subdiagnostic");
+
+ // `set_arg` calls take their argument by-move..
+ generates_set_arg
+ // If this is a `MultiSpan` field then it needs to be moved to be used by any
+ // attribute..
+ || is_multispan
+ // If this a `#[subdiagnostic]` then it needs to be moved as the other diagnostic is
+ // unlikely to be `Copy`..
+ || is_subdiagnostic
+ }
+
+ /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
+ /// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
+ /// diagnostic builder calls for setting error code and creating note/help messages.
+ fn generate_structure_code_for_attr(
+ &mut self,
+ attr: &Attribute,
+ ) -> Result<TokenStream, DiagnosticDeriveError> {
+ let diag = &self.diag;
+ let span = attr.span().unwrap();
+
+ let name = attr.path.segments.last().unwrap().ident.to_string();
+ let name = name.as_str();
+ let meta = attr.parse_meta()?;
+
+ let is_help_note_or_warn = matches!(name, "help" | "note" | "warn_");
+
+ let nested = match meta {
+ // Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` for most cases or
+ // `#[help(..)]`/`#[note(..)]` when the user is specifying a alternative slug.
+ Meta::List(MetaList { ref nested, .. }) => nested,
+ // Subdiagnostics without spans can be applied to the type too, and these are just
+ // paths: `#[help]` and `#[note]`
+ Meta::Path(_) if is_help_note_or_warn => {
+ let fn_name = if name == "warn_" {
+ Ident::new("warn", attr.span())
+ } else {
+ Ident::new(name, attr.span())
+ };
+ return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::_subdiag::#fn_name); });
+ }
+ _ => throw_invalid_attr!(attr, &meta),
+ };
+
+ // Check the kind before doing any further processing so that there aren't misleading
+ // "no kind specified" errors if there are failures later.
+ match name {
+ "error" => self.kind.set_once((DiagnosticDeriveKind::Error, span)),
+ "warning" => self.kind.set_once((DiagnosticDeriveKind::Warn, span)),
+ "lint" => self.kind.set_once((DiagnosticDeriveKind::Lint, span)),
+ "help" | "note" | "warn_" => (),
+ _ => throw_invalid_attr!(attr, &meta, |diag| {
+ diag.help(
+ "only `error`, `warning`, `help`, `note` and `warn_` are valid attributes",
+ )
+ }),
+ }
+
+ // First nested element should always be the path, e.g. `#[error(typeck::invalid)]` or
+ // `#[help(typeck::another_help)]`.
+ let mut nested_iter = nested.into_iter();
+ if let Some(nested_attr) = nested_iter.next() {
+ // Report an error if there are any other list items after the path.
+ if is_help_note_or_warn && nested_iter.next().is_some() {
+ throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ diag.help(
+ "`help`, `note` and `warn_` struct attributes can only have one argument",
+ )
+ });
+ }
+
+ match nested_attr {
+ NestedMeta::Meta(Meta::Path(path)) if is_help_note_or_warn => {
+ let fn_name = proc_macro2::Ident::new(name, attr.span());
+ return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); });
+ }
+ NestedMeta::Meta(Meta::Path(path)) => {
+ self.slug.set_once((path.clone(), span));
+ }
+ NestedMeta::Meta(meta @ Meta::NameValue(_))
+ if !is_help_note_or_warn
+ && meta.path().segments.last().unwrap().ident == "code" =>
+ {
+ // don't error for valid follow-up attributes
+ }
+ nested_attr => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ diag.help("first argument of the attribute should be the diagnostic slug")
+ }),
+ };
+ }
+
+ // Remaining attributes are optional, only `code = ".."` at the moment.
+ let mut tokens = Vec::new();
+ for nested_attr in nested_iter {
+ let meta = match nested_attr {
+ syn::NestedMeta::Meta(meta) => meta,
+ _ => throw_invalid_nested_attr!(attr, &nested_attr),
+ };
+
+ let path = meta.path();
+ 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.
+ if let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) = &meta {
+ let span = s.span().unwrap();
+ match nested_name.as_str() {
+ "code" => {
+ self.code.set_once((s.value(), span));
+ let code = &self.code.as_ref().map(|(v, _)| v);
+ tokens.push(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(),
+ }
+ } else {
+ invalid_nested_attr(attr, &nested_attr).emit()
+ }
+ }
+
+ Ok(tokens.drain(..).collect())
+ }
+
+ fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
+ let field = binding_info.ast();
+ let field_binding = &binding_info.binding;
+
+ if self.should_generate_set_arg(&field) {
+ let diag = &self.diag;
+ let ident = field.ident.as_ref().unwrap();
+ return quote! {
+ #diag.set_arg(
+ stringify!(#ident),
+ #field_binding
+ );
+ };
+ }
+
+ let needs_move = self.needs_move(&field);
+ let inner_ty = FieldInnerTy::from_type(&field.ty);
+
+ field
+ .attrs
+ .iter()
+ .map(move |attr| {
+ 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)
+ } else if needs_move {
+ (quote! { #field_binding }, true)
+ } else {
+ (quote! { *#field_binding }, true)
+ };
+
+ 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(),
+ },
+ binding,
+ )
+ .unwrap_or_else(|v| v.to_compile_error());
+
+ if needs_destructure {
+ inner_ty.with(field_binding, generated_code)
+ } else {
+ generated_code
+ }
+ })
+ .collect()
+ }
+
+ fn generate_inner_field_code(
+ &mut self,
+ attr: &Attribute,
+ info: FieldInfo<'_>,
+ binding: TokenStream,
+ ) -> Result<TokenStream, DiagnosticDeriveError> {
+ let meta = attr.parse_meta()?;
+ match meta {
+ Meta::Path(_) => self.generate_inner_field_code_path(attr, info, binding),
+ Meta::List(MetaList { .. }) => self.generate_inner_field_code_list(attr, info, binding),
+ _ => throw_invalid_attr!(attr, &meta),
+ }
+ }
+
+ fn generate_inner_field_code_path(
+ &mut self,
+ attr: &Attribute,
+ info: FieldInfo<'_>,
+ binding: TokenStream,
+ ) -> Result<TokenStream, DiagnosticDeriveError> {
+ assert!(matches!(attr.parse_meta()?, Meta::Path(_)));
+ let diag = &self.diag;
+
+ let meta = attr.parse_meta()?;
+
+ let ident = &attr.path.segments.last().unwrap().ident;
+ let name = ident.to_string();
+ let name = name.as_str();
+ match name {
+ "skip_arg" => {
+ // Don't need to do anything - by virtue of the attribute existing, the
+ // `set_arg` call will not be generated.
+ Ok(quote! {})
+ }
+ "primary_span" => {
+ report_error_if_not_applied_to_span(attr, &info)?;
+ Ok(quote! {
+ #diag.set_span(#binding);
+ })
+ }
+ "label" => {
+ report_error_if_not_applied_to_span(attr, &info)?;
+ Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label }))
+ }
+ "note" | "help" | "warn_" => {
+ let warn_ident = Ident::new("warn", Span::call_site());
+ let (ident, path) = match name {
+ "note" => (ident, parse_quote! { _subdiag::note }),
+ "help" => (ident, parse_quote! { _subdiag::help }),
+ "warn_" => (&warn_ident, parse_quote! { _subdiag::warn }),
+ _ => unreachable!(),
+ };
+ if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
+ Ok(self.add_spanned_subdiagnostic(binding, ident, path))
+ } else if type_is_unit(&info.ty) {
+ Ok(self.add_subdiagnostic(ident, path))
+ } else {
+ report_type_error(attr, "`Span` or `()`")?
+ }
+ }
+ "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }),
+ _ => throw_invalid_attr!(attr, &meta, |diag| {
+ diag.help(
+ "only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` \
+ are valid field attributes",
+ )
+ }),
+ }
+ }
+
+ fn generate_inner_field_code_list(
+ &mut self,
+ attr: &Attribute,
+ info: FieldInfo<'_>,
+ binding: TokenStream,
+ ) -> Result<TokenStream, DiagnosticDeriveError> {
+ let meta = attr.parse_meta()?;
+ let Meta::List(MetaList { ref path, ref nested, .. }) = meta else { unreachable!() };
+
+ let ident = &attr.path.segments.last().unwrap().ident;
+ let name = path.segments.last().unwrap().ident.to_string();
+ let name = name.as_ref();
+ match name {
+ "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => {
+ return self.generate_inner_field_code_suggestion(attr, info);
+ }
+ "label" | "help" | "note" | "warn_" => (),
+ _ => throw_invalid_attr!(attr, &meta, |diag| {
+ diag.help(
+ "only `label`, `help`, `note`, `warn` or `suggestion{,_short,_hidden,_verbose}` are \
+ valid field attributes",
+ )
+ }),
+ }
+
+ // For `#[label(..)]`, `#[note(..)]` and `#[help(..)]`, the first nested element must be a
+ // path, e.g. `#[label(typeck::label)]`.
+ let mut nested_iter = nested.into_iter();
+ let msg = match nested_iter.next() {
+ Some(NestedMeta::Meta(Meta::Path(path))) => path.clone(),
+ Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr),
+ None => throw_invalid_attr!(attr, &meta),
+ };
+
+ // None of these attributes should have anything following the slug.
+ if nested_iter.next().is_some() {
+ throw_invalid_attr!(attr, &meta);
+ }
+
+ match name {
+ "label" => {
+ report_error_if_not_applied_to_span(attr, &info)?;
+ Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
+ }
+ "note" | "help" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => {
+ Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
+ }
+ "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)),
+ // `warn_` must be special-cased because the attribute `warn` already has meaning and
+ // so isn't used, despite the diagnostic API being named `warn`.
+ "warn_" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => Ok(self
+ .add_spanned_subdiagnostic(binding, &Ident::new("warn", Span::call_site()), msg)),
+ "warn_" if type_is_unit(&info.ty) => {
+ Ok(self.add_subdiagnostic(&Ident::new("warn", Span::call_site()), msg))
+ }
+ "note" | "help" | "warn_" => report_type_error(attr, "`Span` or `()`")?,
+ _ => unreachable!(),
+ }
+ }
+
+ fn generate_inner_field_code_suggestion(
+ &mut self,
+ attr: &Attribute,
+ info: FieldInfo<'_>,
+ ) -> Result<TokenStream, DiagnosticDeriveError> {
+ let diag = &self.diag;
+
+ let mut meta = attr.parse_meta()?;
+ let Meta::List(MetaList { ref path, ref mut nested, .. }) = meta else { unreachable!() };
+
+ let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
+
+ let mut msg = None;
+ let mut code = None;
+
+ let mut nested_iter = nested.into_iter().peekable();
+ if let Some(nested_attr) = nested_iter.peek() {
+ if let NestedMeta::Meta(Meta::Path(path)) = nested_attr {
+ msg = Some(path.clone());
+ }
+ };
+ // Move the iterator forward if a path was found (don't otherwise so that
+ // code/applicability can be found or an error emitted).
+ if msg.is_some() {
+ let _ = nested_iter.next();
+ }
+
+ for nested_attr in nested_iter {
+ let meta = match nested_attr {
+ syn::NestedMeta::Meta(ref meta) => meta,
+ syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr),
+ };
+
+ let nested_name = meta.path().segments.last().unwrap().ident.to_string();
+ let nested_name = nested_name.as_str();
+ match meta {
+ Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
+ let span = meta.span().unwrap();
+ match nested_name {
+ "code" => {
+ let formatted_str = self.build_format(&s.value(), s.span());
+ code = Some(formatted_str);
+ }
+ "applicability" => {
+ applicability = match applicability {
+ Some(v) => {
+ span_err(
+ span,
+ "applicability cannot be set in both the field and \
+ attribute",
+ )
+ .emit();
+ Some(v)
+ }
+ None => match Applicability::from_str(&s.value()) {
+ Ok(v) => Some(quote! { #v }),
+ Err(()) => {
+ span_err(span, "invalid applicability").emit();
+ None
+ }
+ },
+ }
+ }
+ _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ diag.help(
+ "only `message`, `code` and `applicability` are valid field \
+ attributes",
+ )
+ }),
+ }
+ }
+ _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ if matches!(meta, Meta::Path(_)) {
+ diag.help("a diagnostic slug must be the first argument to the attribute")
+ } else {
+ diag
+ }
+ }),
+ }
+ }
+
+ let applicability =
+ applicability.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
+
+ let name = path.segments.last().unwrap().ident.to_string();
+ let method = format_ident!("span_{}", name);
+
+ let msg = msg.unwrap_or_else(|| parse_quote! { _subdiag::suggestion });
+ let msg = quote! { rustc_errors::fluent::#msg };
+ let code = code.unwrap_or_else(|| quote! { String::new() });
+
+ Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
+ }
+
+ /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
+ /// and `fluent_attr_identifier`.
+ fn add_spanned_subdiagnostic(
+ &self,
+ field_binding: TokenStream,
+ kind: &Ident,
+ fluent_attr_identifier: Path,
+ ) -> TokenStream {
+ let diag = &self.diag;
+ let fn_name = format_ident!("span_{}", kind);
+ quote! {
+ #diag.#fn_name(
+ #field_binding,
+ rustc_errors::fluent::#fluent_attr_identifier
+ );
+ }
+ }
+
+ /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
+ /// and `fluent_attr_identifier`.
+ fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream {
+ let diag = &self.diag;
+ quote! {
+ #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier);
+ }
+ }
+
+ fn span_and_applicability_of_ty(
+ &self,
+ info: FieldInfo<'_>,
+ ) -> Result<(TokenStream, Option<TokenStream>), DiagnosticDeriveError> {
+ match &info.ty {
+ // 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;
+ Ok((quote!(*#binding), None))
+ }
+ // If `ty` is `(Span, Applicability)` then return tokens accessing those.
+ Type::Tuple(tup) => {
+ let mut span_idx = None;
+ let mut applicability_idx = None;
+
+ for (idx, elem) in tup.elems.iter().enumerate() {
+ if type_matches_path(elem, &["rustc_span", "Span"]) {
+ if span_idx.is_none() {
+ span_idx = Some(syn::Index::from(idx));
+ } else {
+ throw_span_err!(
+ info.span.unwrap(),
+ "type of field annotated with `#[suggestion(...)]` contains more \
+ than one `Span`"
+ );
+ }
+ } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
+ if applicability_idx.is_none() {
+ applicability_idx = Some(syn::Index::from(idx));
+ } else {
+ throw_span_err!(
+ info.span.unwrap(),
+ "type of field annotated with `#[suggestion(...)]` contains more \
+ than one Applicability"
+ );
+ }
+ }
+ }
+
+ if let Some(span_idx) = span_idx {
+ let binding = &info.binding.binding;
+ let span = quote!(#binding.#span_idx);
+ let applicability = applicability_idx
+ .map(|applicability_idx| quote!(#binding.#applicability_idx))
+ .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
+
+ return Ok((span, Some(applicability)));
+ }
+
+ throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
+ diag.help(
+ "`#[suggestion(...)]` on a tuple field must be applied to fields of type \
+ `(Span, Applicability)`",
+ )
+ });
+ }
+ // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
+ _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
+ diag.help(
+ "`#[suggestion(...)]` should be applied to fields of type `Span` or \
+ `(Span, Applicability)`",
+ )
+ }),
+ }
+ }
+}
diff --git a/compiler/rustc_macros/src/diagnostics/error.rs b/compiler/rustc_macros/src/diagnostics/error.rs
new file mode 100644
index 000000000..0b1ededa7
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/error.rs
@@ -0,0 +1,141 @@
+use proc_macro::{Diagnostic, Level, MultiSpan};
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{spanned::Spanned, Attribute, Error as SynError, Meta, NestedMeta};
+
+#[derive(Debug)]
+pub(crate) enum DiagnosticDeriveError {
+ SynError(SynError),
+ ErrorHandled,
+}
+
+impl DiagnosticDeriveError {
+ pub(crate) fn to_compile_error(self) -> TokenStream {
+ match self {
+ DiagnosticDeriveError::SynError(e) => e.to_compile_error(),
+ DiagnosticDeriveError::ErrorHandled => {
+ // Return ! to avoid having to create a blank DiagnosticBuilder to return when an
+ // error has already been emitted to the compiler.
+ quote! {
+ { unreachable!(); }
+ }
+ }
+ }
+ }
+}
+
+impl From<SynError> for DiagnosticDeriveError {
+ fn from(e: SynError) -> Self {
+ DiagnosticDeriveError::SynError(e)
+ }
+}
+
+/// Helper function for use with `throw_*` macros - constraints `$f` to an `impl FnOnce`.
+pub(crate) fn _throw_err(
+ diag: Diagnostic,
+ f: impl FnOnce(Diagnostic) -> Diagnostic,
+) -> DiagnosticDeriveError {
+ f(diag).emit();
+ DiagnosticDeriveError::ErrorHandled
+}
+
+/// Helper function for printing `syn::Path` - doesn't handle arguments in paths and these are
+/// unlikely to come up much in use of the macro.
+fn path_to_string(path: &syn::Path) -> String {
+ let mut out = String::new();
+ for (i, segment) in path.segments.iter().enumerate() {
+ if i > 0 || path.leading_colon.is_some() {
+ out.push_str("::");
+ }
+ out.push_str(&segment.ident.to_string());
+ }
+ out
+}
+
+/// Returns an error diagnostic on span `span` with msg `msg`.
+pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic {
+ Diagnostic::spanned(span, Level::Error, msg)
+}
+
+/// Emit a diagnostic on span `$span` with msg `$msg` (optionally performing additional decoration
+/// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
+///
+/// For methods that return a `Result<_, DiagnosticDeriveError>`:
+macro_rules! throw_span_err {
+ ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }};
+ ($span:expr, $msg:expr, $f:expr) => {{
+ let diag = span_err($span, $msg);
+ return Err(crate::diagnostics::error::_throw_err(diag, $f));
+ }};
+}
+
+pub(crate) use throw_span_err;
+
+/// Returns an error diagnostic for an invalid attribute.
+pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic {
+ let span = attr.span().unwrap();
+ let path = path_to_string(&attr.path);
+ match meta {
+ Meta::Path(_) => span_err(span, &format!("`#[{}]` is not a valid attribute", path)),
+ Meta::NameValue(_) => {
+ span_err(span, &format!("`#[{} = ...]` is not a valid attribute", path))
+ }
+ Meta::List(_) => span_err(span, &format!("`#[{}(...)]` is not a valid attribute", path)),
+ }
+}
+
+/// Emit a error diagnostic for an invalid 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_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);
+ 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!("`#[{}(\"...\")]` is not a valid attribute", name));
+ }
+ };
+
+ let span = meta.span().unwrap();
+ let path = path_to_string(meta.path());
+ match meta {
+ Meta::NameValue(..) => {
+ span_err(span, &format!("`#[{}({} = ...)]` is not a valid attribute", name, path))
+ }
+ Meta::Path(..) => {
+ span_err(span, &format!("`#[{}({})]` is not a valid attribute", name, path))
+ }
+ Meta::List(..) => {
+ span_err(span, &format!("`#[{}({}(...))]` is not a valid attribute", name, path))
+ }
+ }
+}
+
+/// Emit a 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
new file mode 100644
index 000000000..562d5e9f4
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/fluent.rs
@@ -0,0 +1,277 @@
+use annotate_snippets::{
+ display_list::DisplayList,
+ snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
+};
+use fluent_bundle::{FluentBundle, FluentError, FluentResource};
+use fluent_syntax::{
+ ast::{Attribute, Entry, Identifier, Message},
+ 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 {
+ ident: Ident,
+ #[allow(dead_code)]
+ fat_arrow_token: token::FatArrow,
+ resource: LitStr,
+}
+
+impl Parse for Resource {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ Ok(Resource {
+ ident: input.parse()?,
+ fat_arrow_token: input.parse()?,
+ resource: 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();
+
+ let mut includes = TokenStream::new();
+ let mut generated = TokenStream::new();
+ for res in resources.0 {
+ let ident_span = res.ident.span().unwrap();
+ let path_span = res.resource.span().unwrap();
+
+ // 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 relative_ftl_path = res.resource.value();
+ let absolute_ftl_path =
+ invocation_relative_path_to_absolute(ident_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!("{}\n", dl);
+ }
+ continue;
+ }
+ };
+
+ let mut constants = TokenStream::new();
+ for entry in resource.entries() {
+ let span = res.ident.span();
+ if let Entry::Message(Message { id: Identifier { name }, attributes, .. }) = entry {
+ let _ = previous_defns.entry(name.to_string()).or_insert(ident_span);
+
+ // `typeck-foo-bar` => `foo_bar` (in `typeck.ftl`)
+ // `const-eval-baz` => `baz` (in `const_eval.ftl`)
+ let snake_name = Ident::new(
+ // FIXME: should probably trim prefix, not replace all occurrences
+ &name
+ .replace(&format!("{}-", res.ident).replace('_', "-"), "")
+ .replace('-', "_"),
+ 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;
+ }
+
+ 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(
+ ident_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), });
+
+ let ident = res.ident;
+ generated.extend(quote! {
+ pub mod #ident {
+ #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
new file mode 100644
index 000000000..399790026
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/mod.rs
@@ -0,0 +1,159 @@
+mod diagnostic;
+mod diagnostic_builder;
+mod error;
+mod fluent;
+mod subdiagnostic;
+mod utils;
+
+use diagnostic::{LintDiagnosticDerive, SessionDiagnosticDerive};
+pub(crate) use fluent::fluent_messages;
+use proc_macro2::TokenStream;
+use quote::format_ident;
+use subdiagnostic::SessionSubdiagnosticDerive;
+use synstructure::Structure;
+
+/// Implements `#[derive(SessionDiagnostic)]`, which allows for errors to be specified as a struct,
+/// independent from the actual diagnostics emitting code.
+///
+/// ```ignore (rust)
+/// # extern crate rustc_errors;
+/// # use rustc_errors::Applicability;
+/// # extern crate rustc_span;
+/// # use rustc_span::{symbol::Ident, Span};
+/// # extern crate rust_middle;
+/// # use rustc_middle::ty::Ty;
+/// #[derive(SessionDiagnostic)]
+/// #[error(borrowck::move_out_of_borrow, code = "E0505")]
+/// pub struct MoveOutOfBorrowError<'tcx> {
+/// pub name: Ident,
+/// pub ty: Ty<'tcx>,
+/// #[primary_span]
+/// #[label]
+/// pub span: Span,
+/// #[label(borrowck::first_borrow_label)]
+/// pub first_borrow_span: Span,
+/// #[suggestion(code = "{name}.clone()")]
+/// pub clone_sugg: Option<(Span, Applicability)>
+/// }
+/// ```
+///
+/// ```fluent
+/// move-out-of-borrow = cannot move out of {$name} because it is borrowed
+/// .label = cannot move out of borrow
+/// .first-borrow-label = `{$ty}` first borrowed here
+/// .suggestion = consider cloning here
+/// ```
+///
+/// Then, later, to emit the error:
+///
+/// ```ignore (rust)
+/// sess.emit_err(MoveOutOfBorrowError {
+/// expected,
+/// actual,
+/// span,
+/// first_borrow_span,
+/// clone_sugg: Some(suggestion, Applicability::MachineApplicable),
+/// });
+/// ```
+///
+/// See rustc dev guide for more examples on using the `#[derive(SessionDiagnostic)]`:
+/// <https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html>
+pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
+ SessionDiagnosticDerive::new(format_ident!("diag"), format_ident!("sess"), s).into_tokens()
+}
+
+/// Implements `#[derive(LintDiagnostic)]`, which allows for lints to be specified as a struct,
+/// independent from the actual lint emitting code.
+///
+/// ```ignore (rust)
+/// #[derive(LintDiagnostic)]
+/// #[lint(lint::atomic_ordering_invalid_fail_success)]
+/// pub struct AtomicOrderingInvalidLint {
+/// method: Symbol,
+/// success_ordering: Symbol,
+/// fail_ordering: Symbol,
+/// #[label(lint::fail_label)]
+/// fail_order_arg_span: Span,
+/// #[label(lint::success_label)]
+/// #[suggestion(
+/// code = "std::sync::atomic::Ordering::{success_suggestion}",
+/// applicability = "maybe-incorrect"
+/// )]
+/// success_order_arg_span: Span,
+/// }
+/// ```
+///
+/// ```fluent
+/// lint-atomic-ordering-invalid-fail-success = `{$method}`'s success ordering must be at least as strong as its failure ordering
+/// .fail-label = `{$fail_ordering}` failure ordering
+/// .success-label = `{$success_ordering}` success ordering
+/// .suggestion = consider using `{$success_suggestion}` success ordering instead
+/// ```
+///
+/// Then, later, to emit the error:
+///
+/// ```ignore (rust)
+/// cx.struct_span_lint(INVALID_ATOMIC_ORDERING, fail_order_arg_span, AtomicOrderingInvalidLint {
+/// method,
+/// success_ordering,
+/// fail_ordering,
+/// fail_order_arg_span,
+/// success_order_arg_span,
+/// });
+/// ```
+///
+/// See rustc dev guide for more examples on using the `#[derive(LintDiagnostic)]`:
+/// <https://rustc-dev-guide.rust-lang.org/diagnostics/sessiondiagnostic.html>
+pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream {
+ LintDiagnosticDerive::new(format_ident!("diag"), s).into_tokens()
+}
+
+/// Implements `#[derive(SessionSubdiagnostic)]`, which allows for labels, notes, helps and
+/// suggestions to be specified as a structs or enums, independent from the actual diagnostics
+/// emitting code or diagnostic derives.
+///
+/// ```ignore (rust)
+/// #[derive(SessionSubdiagnostic)]
+/// pub enum ExpectedIdentifierLabel<'tcx> {
+/// #[label(parser::expected_identifier)]
+/// WithoutFound {
+/// #[primary_span]
+/// span: Span,
+/// }
+/// #[label(parser::expected_identifier_found)]
+/// WithFound {
+/// #[primary_span]
+/// span: Span,
+/// found: String,
+/// }
+/// }
+///
+/// #[derive(SessionSubdiagnostic)]
+/// #[suggestion_verbose(parser::raw_identifier)]
+/// pub struct RawIdentifierSuggestion<'tcx> {
+/// #[primary_span]
+/// span: Span,
+/// #[applicability]
+/// applicability: Applicability,
+/// ident: Ident,
+/// }
+/// ```
+///
+/// ```fluent
+/// parser-expected-identifier = expected identifier
+///
+/// parser-expected-identifier-found = expected identifier, found {$found}
+///
+/// parser-raw-identifier = escape `{$ident}` to use it as an identifier
+/// ```
+///
+/// Then, later, to add the subdiagnostic:
+///
+/// ```ignore (rust)
+/// diag.subdiagnostic(ExpectedIdentifierLabel::WithoutFound { span });
+///
+/// diag.subdiagnostic(RawIdentifierSuggestion { span, applicability, ident });
+/// ```
+pub fn session_subdiagnostic_derive(s: Structure<'_>) -> TokenStream {
+ SessionSubdiagnosticDerive::new(s).into_tokens()
+}
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
new file mode 100644
index 000000000..edf4dbed9
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -0,0 +1,491 @@
+#![deny(unused_must_use)]
+
+use crate::diagnostics::error::{
+ span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
+};
+use crate::diagnostics::utils::{
+ report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
+ Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
+};
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+use std::collections::HashMap;
+use std::fmt;
+use std::str::FromStr;
+use syn::{parse_quote, spanned::Spanned, Meta, MetaList, MetaNameValue, NestedMeta, Path};
+use synstructure::{BindingInfo, Structure, VariantInfo};
+
+/// Which kind of suggestion is being created?
+#[derive(Clone, Copy)]
+enum SubdiagnosticSuggestionKind {
+ /// `#[suggestion]`
+ Normal,
+ /// `#[suggestion_short]`
+ Short,
+ /// `#[suggestion_hidden]`
+ Hidden,
+ /// `#[suggestion_verbose]`
+ Verbose,
+}
+
+/// Which kind of subdiagnostic is being created from a variant?
+#[derive(Clone, Copy)]
+enum SubdiagnosticKind {
+ /// `#[label(...)]`
+ Label,
+ /// `#[note(...)]`
+ Note,
+ /// `#[help(...)]`
+ Help,
+ /// `#[warn_(...)]`
+ Warn,
+ /// `#[suggestion{,_short,_hidden,_verbose}]`
+ Suggestion(SubdiagnosticSuggestionKind),
+}
+
+impl FromStr for SubdiagnosticKind {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "label" => Ok(SubdiagnosticKind::Label),
+ "note" => Ok(SubdiagnosticKind::Note),
+ "help" => Ok(SubdiagnosticKind::Help),
+ "warn_" => Ok(SubdiagnosticKind::Warn),
+ "suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)),
+ "suggestion_short" => {
+ Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short))
+ }
+ "suggestion_hidden" => {
+ Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden))
+ }
+ "suggestion_verbose" => {
+ Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose))
+ }
+ _ => Err(()),
+ }
+ }
+}
+
+impl quote::IdentFragment for SubdiagnosticKind {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ SubdiagnosticKind::Label => write!(f, "label"),
+ SubdiagnosticKind::Note => write!(f, "note"),
+ SubdiagnosticKind::Help => write!(f, "help"),
+ SubdiagnosticKind::Warn => write!(f, "warn"),
+ SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal) => {
+ write!(f, "suggestion")
+ }
+ SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short) => {
+ write!(f, "suggestion_short")
+ }
+ SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden) => {
+ write!(f, "suggestion_hidden")
+ }
+ SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose) => {
+ write!(f, "suggestion_verbose")
+ }
+ }
+ }
+
+ fn span(&self) -> Option<proc_macro2::Span> {
+ None
+ }
+}
+
+/// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
+pub(crate) struct SessionSubdiagnosticDerive<'a> {
+ structure: Structure<'a>,
+ diag: syn::Ident,
+}
+
+impl<'a> SessionSubdiagnosticDerive<'a> {
+ pub(crate) fn new(structure: Structure<'a>) -> Self {
+ let diag = format_ident!("diag");
+ Self { structure, diag }
+ }
+
+ pub(crate) fn into_tokens(self) -> TokenStream {
+ let SessionSubdiagnosticDerive { mut structure, diag } = self;
+ let implementation = {
+ let ast = structure.ast();
+ let span = ast.span().unwrap();
+ match ast.data {
+ syn::Data::Struct(..) | syn::Data::Enum(..) => (),
+ syn::Data::Union(..) => {
+ span_err(
+ span,
+ "`#[derive(SessionSubdiagnostic)]` can only be used on structs and enums",
+ );
+ }
+ }
+
+ if matches!(ast.data, syn::Data::Enum(..)) {
+ for attr in &ast.attrs {
+ span_err(
+ attr.span().unwrap(),
+ "unsupported type attribute for subdiagnostic enum",
+ )
+ .emit();
+ }
+ }
+
+ structure.bind_with(|_| synstructure::BindStyle::Move);
+ let variants_ = structure.each_variant(|variant| {
+ // Build the mapping of field names to fields. This allows attributes to peek
+ // values from other fields.
+ let mut fields_map = HashMap::new();
+ for binding in variant.bindings() {
+ let field = binding.ast();
+ if let Some(ident) = &field.ident {
+ fields_map.insert(ident.to_string(), quote! { #binding });
+ }
+ }
+
+ let mut builder = SessionSubdiagnosticDeriveBuilder {
+ diag: &diag,
+ variant,
+ span,
+ fields: fields_map,
+ kind: None,
+ slug: None,
+ code: None,
+ span_field: None,
+ applicability: None,
+ };
+ builder.into_tokens().unwrap_or_else(|v| v.to_compile_error())
+ });
+
+ quote! {
+ match self {
+ #variants_
+ }
+ }
+ };
+
+ let ret = structure.gen_impl(quote! {
+ gen impl rustc_errors::AddSubdiagnostic for @Self {
+ fn add_to_diagnostic(self, #diag: &mut rustc_errors::Diagnostic) {
+ use rustc_errors::{Applicability, IntoDiagnosticArg};
+ #implementation
+ }
+ }
+ });
+ ret
+ }
+}
+
+/// Tracks persistent information required for building up the call to add to the diagnostic
+/// for the final generated method. This is a separate struct to `SessionSubdiagnosticDerive`
+/// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
+/// double mut borrow later on.
+struct SessionSubdiagnosticDeriveBuilder<'a> {
+ /// The identifier to use for the generated `DiagnosticBuilder` instance.
+ diag: &'a syn::Ident,
+
+ /// Info for the current variant (or the type if not an enum).
+ variant: &'a VariantInfo<'a>,
+ /// Span for the entire type.
+ span: proc_macro::Span,
+
+ /// Store a map of field name to its corresponding field. This is built on construction of the
+ /// derive builder.
+ fields: HashMap<String, TokenStream>,
+
+ /// Subdiagnostic kind of the type/variant.
+ kind: Option<(SubdiagnosticKind, proc_macro::Span)>,
+
+ /// Slug of the subdiagnostic - corresponds to the Fluent identifier for the message - from the
+ /// `#[kind(slug)]` attribute on the type or variant.
+ slug: Option<(Path, proc_macro::Span)>,
+ /// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]`
+ /// attribute on the type or variant.
+ code: Option<(TokenStream, proc_macro::Span)>,
+
+ /// Identifier for the binding to the `#[primary_span]` field.
+ span_field: Option<(proc_macro2::Ident, proc_macro::Span)>,
+ /// If a suggestion, the identifier for the binding to the `#[applicability]` field or a
+ /// `rustc_errors::Applicability::*` variant directly.
+ applicability: Option<(TokenStream, proc_macro::Span)>,
+}
+
+impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
+ fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
+ self.fields.get(field)
+ }
+}
+
+impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
+ fn identify_kind(&mut self) -> Result<(), DiagnosticDeriveError> {
+ for attr in self.variant.ast().attrs {
+ let span = attr.span().unwrap();
+
+ let name = attr.path.segments.last().unwrap().ident.to_string();
+ let name = name.as_str();
+
+ let meta = attr.parse_meta()?;
+ let kind = match meta {
+ Meta::List(MetaList { ref nested, .. }) => {
+ let mut nested_iter = nested.into_iter();
+ if let Some(nested_attr) = nested_iter.next() {
+ match nested_attr {
+ NestedMeta::Meta(Meta::Path(path)) => {
+ self.slug.set_once((path.clone(), span));
+ }
+ NestedMeta::Meta(meta @ Meta::NameValue(_))
+ if matches!(
+ meta.path().segments.last().unwrap().ident.to_string().as_str(),
+ "code" | "applicability"
+ ) =>
+ {
+ // don't error for valid follow-up attributes
+ }
+ nested_attr => {
+ throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ diag.help(
+ "first argument of the attribute should be the diagnostic \
+ slug",
+ )
+ })
+ }
+ };
+ }
+
+ for nested_attr in nested_iter {
+ let meta = match nested_attr {
+ NestedMeta::Meta(ref meta) => meta,
+ _ => 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 meta {
+ Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
+ match nested_name {
+ "code" => {
+ let formatted_str = self.build_format(&s.value(), s.span());
+ self.code.set_once((formatted_str, span));
+ }
+ "applicability" => {
+ let value = match Applicability::from_str(&s.value()) {
+ Ok(v) => v,
+ Err(()) => {
+ span_err(span, "invalid applicability").emit();
+ Applicability::Unspecified
+ }
+ };
+ self.applicability.set_once((quote! { #value }, span));
+ }
+ _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ diag.help(
+ "only `code` and `applicability` are valid nested \
+ attributes",
+ )
+ }),
+ }
+ }
+ _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ if matches!(meta, Meta::Path(_)) {
+ diag.help(
+ "a diagnostic slug must be the first argument to the \
+ attribute",
+ )
+ } else {
+ diag
+ }
+ }),
+ }
+ }
+
+ let Ok(kind) = SubdiagnosticKind::from_str(name) else {
+ throw_invalid_attr!(attr, &meta)
+ };
+
+ kind
+ }
+ _ => throw_invalid_attr!(attr, &meta),
+ };
+
+ if matches!(
+ kind,
+ SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note
+ ) && self.code.is_some()
+ {
+ throw_span_err!(
+ span,
+ &format!("`code` is not a valid nested attribute of a `{}` attribute", name)
+ );
+ }
+
+ if matches!(
+ kind,
+ SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note
+ ) && self.applicability.is_some()
+ {
+ throw_span_err!(
+ span,
+ &format!(
+ "`applicability` is not a valid nested attribute of a `{}` attribute",
+ name
+ )
+ );
+ }
+
+ if self.slug.is_none() {
+ throw_span_err!(
+ span,
+ &format!(
+ "diagnostic slug must be first argument of a `#[{}(...)]` attribute",
+ name
+ )
+ );
+ }
+
+ self.kind.set_once((kind, span));
+ }
+
+ Ok(())
+ }
+
+ fn generate_field_code(
+ &mut self,
+ binding: &BindingInfo<'_>,
+ is_suggestion: bool,
+ ) -> Result<TokenStream, DiagnosticDeriveError> {
+ let ast = binding.ast();
+
+ let inner_ty = FieldInnerTy::from_type(&ast.ty);
+ let info = FieldInfo {
+ binding: binding,
+ ty: inner_ty.inner_type().unwrap_or(&ast.ty),
+ span: &ast.span(),
+ };
+
+ for attr in &ast.attrs {
+ let name = attr.path.segments.last().unwrap().ident.to_string();
+ let name = name.as_str();
+ let span = attr.span().unwrap();
+
+ let meta = attr.parse_meta()?;
+ match meta {
+ Meta::Path(_) => match name {
+ "primary_span" => {
+ report_error_if_not_applied_to_span(attr, &info)?;
+ self.span_field.set_once((binding.binding.clone(), span));
+ return Ok(quote! {});
+ }
+ "applicability" if is_suggestion => {
+ report_error_if_not_applied_to_applicability(attr, &info)?;
+ let binding = binding.binding.clone();
+ self.applicability.set_once((quote! { #binding }, span));
+ return Ok(quote! {});
+ }
+ "applicability" => {
+ span_err(span, "`#[applicability]` is only valid on suggestions").emit();
+ return Ok(quote! {});
+ }
+ "skip_arg" => {
+ return Ok(quote! {});
+ }
+ _ => throw_invalid_attr!(attr, &meta, |diag| {
+ diag.help(
+ "only `primary_span`, `applicability` and `skip_arg` are valid field \
+ attributes",
+ )
+ }),
+ },
+ _ => throw_invalid_attr!(attr, &meta),
+ }
+ }
+
+ let ident = ast.ident.as_ref().unwrap();
+
+ let diag = &self.diag;
+ let generated = quote! {
+ #diag.set_arg(
+ stringify!(#ident),
+ #binding
+ );
+ };
+
+ Ok(inner_ty.with(binding, generated))
+ }
+
+ fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
+ self.identify_kind()?;
+ let Some(kind) = self.kind.map(|(kind, _)| kind) else {
+ throw_span_err!(
+ self.variant.ast().ident.span().unwrap(),
+ "subdiagnostic kind not specified"
+ );
+ };
+
+ let is_suggestion = matches!(kind, SubdiagnosticKind::Suggestion(_));
+
+ let mut args = TokenStream::new();
+ for binding in self.variant.bindings() {
+ let arg = self
+ .generate_field_code(binding, is_suggestion)
+ .unwrap_or_else(|v| v.to_compile_error());
+ args.extend(arg);
+ }
+
+ // Missing slug errors will already have been reported.
+ let slug = self
+ .slug
+ .as_ref()
+ .map(|(slug, _)| slug.clone())
+ .unwrap_or_else(|| parse_quote! { you::need::to::specify::a::slug });
+ let code = match self.code.as_ref() {
+ Some((code, _)) => Some(quote! { #code }),
+ None if is_suggestion => {
+ span_err(self.span, "suggestion without `code = \"...\"`").emit();
+ Some(quote! { /* macro error */ "..." })
+ }
+ None => None,
+ };
+
+ let span_field = self.span_field.as_ref().map(|(span, _)| span);
+ let applicability = match self.applicability.clone() {
+ Some((applicability, _)) => Some(applicability),
+ None if is_suggestion => {
+ span_err(self.span, "suggestion without `applicability`").emit();
+ Some(quote! { rustc_errors::Applicability::Unspecified })
+ }
+ None => None,
+ };
+
+ let diag = &self.diag;
+ let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
+ let message = quote! { rustc_errors::fluent::#slug };
+ let call = if matches!(kind, SubdiagnosticKind::Suggestion(..)) {
+ if let Some(span) = span_field {
+ quote! { #diag.#name(#span, #message, #code, #applicability); }
+ } else {
+ span_err(self.span, "suggestion without `#[primary_span]` field").emit();
+ quote! { unreachable!(); }
+ }
+ } else if matches!(kind, SubdiagnosticKind::Label) {
+ if let Some(span) = span_field {
+ quote! { #diag.#name(#span, #message); }
+ } else {
+ span_err(self.span, "label without `#[primary_span]` field").emit();
+ quote! { unreachable!(); }
+ }
+ } else {
+ if let Some(span) = span_field {
+ quote! { #diag.#name(#span, #message); }
+ } else {
+ quote! { #diag.#name(#message); }
+ }
+ };
+
+ Ok(quote! {
+ #call
+ #args
+ })
+ }
+}
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
new file mode 100644
index 000000000..002abb152
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -0,0 +1,356 @@
+use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError};
+use proc_macro::Span;
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote, ToTokens};
+use std::collections::{BTreeSet, HashMap};
+use std::str::FromStr;
+use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
+use synstructure::{BindingInfo, Structure};
+
+/// Checks whether the type name of `ty` matches `name`.
+///
+/// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
+/// `a::b::c::Foo`. This reasonably allows qualified names to be used in the macro.
+pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool {
+ if let Type::Path(ty) = ty {
+ ty.path
+ .segments
+ .iter()
+ .map(|s| s.ident.to_string())
+ .rev()
+ .zip(name.iter().rev())
+ .all(|(x, y)| &x.as_str() == y)
+ } else {
+ false
+ }
+}
+
+/// Checks whether the type `ty` is `()`.
+pub(crate) fn type_is_unit(ty: &Type) -> bool {
+ if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false }
+}
+
+/// 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()?;
+
+ throw_span_err!(
+ attr.span().unwrap(),
+ &format!(
+ "the `#[{}{}]` attribute can only be applied to fields of type {}",
+ name,
+ match meta {
+ Meta::Path(_) => "",
+ Meta::NameValue(_) => " = ...",
+ Meta::List(_) => "(...)",
+ },
+ ty_name
+ )
+ );
+}
+
+/// Reports an error if the field's type does not match `path`.
+fn report_error_if_not_applied_to_ty(
+ attr: &Attribute,
+ info: &FieldInfo<'_>,
+ path: &[&str],
+ ty_name: &str,
+) -> Result<(), DiagnosticDeriveError> {
+ if !type_matches_path(&info.ty, path) {
+ report_type_error(attr, ty_name)?;
+ }
+
+ Ok(())
+}
+
+/// Reports an error if the field's type is not `Applicability`.
+pub(crate) fn report_error_if_not_applied_to_applicability(
+ attr: &Attribute,
+ info: &FieldInfo<'_>,
+) -> Result<(), DiagnosticDeriveError> {
+ report_error_if_not_applied_to_ty(
+ attr,
+ info,
+ &["rustc_errors", "Applicability"],
+ "`Applicability`",
+ )
+}
+
+/// Reports an error if the field's type is not `Span`.
+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"])
+ {
+ report_type_error(attr, "`Span` or `MultiSpan`")?;
+ }
+
+ Ok(())
+}
+
+/// Inner type of a field and type of wrapper.
+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,
+}
+
+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`.
+ 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;
+ };
+
+ 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);
+ }
+ }
+ }
+ }
+
+ unreachable!();
+ }
+
+ /// Returns `Option` containing inner type if there is one.
+ pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
+ match self {
+ FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) => Some(inner),
+ FieldInnerTy::None => None,
+ }
+ }
+
+ /// Surrounds `inner` with destructured wrapper type, exposing inner type as `binding`.
+ pub(crate) fn with(&self, binding: impl ToTokens, inner: impl ToTokens) -> TokenStream {
+ match self {
+ FieldInnerTy::Option(..) => quote! {
+ if let Some(#binding) = #binding {
+ #inner
+ }
+ },
+ FieldInnerTy::Vec(..) => quote! {
+ for #binding in #binding {
+ #inner
+ }
+ },
+ FieldInnerTy::None => quote! { #inner },
+ }
+ }
+}
+
+/// Field information passed to the builder. Deliberately omits attrs to discourage the
+/// `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) span: &'a proc_macro2::Span,
+}
+
+/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
+/// for error reporting if they are set more than once.
+pub(crate) trait SetOnce<T> {
+ fn set_once(&mut self, _: (T, Span));
+
+ fn value(self) -> Option<T>;
+}
+
+impl<T> SetOnce<T> for Option<(T, Span)> {
+ fn set_once(&mut self, (value, span): (T, Span)) {
+ match self {
+ None => {
+ *self = Some((value, span));
+ }
+ Some((_, prev_span)) => {
+ span_err(span, "specified multiple times")
+ .span_note(*prev_span, "previously specified here")
+ .emit();
+ }
+ }
+ }
+
+ fn value(self) -> Option<T> {
+ self.map(|(v, _)| v)
+ }
+}
+
+pub(crate) trait HasFieldMap {
+ /// Returns the binding for the field with the given name, if it exists on the type.
+ fn get_field_binding(&self, field: &String) -> Option<&TokenStream>;
+
+ /// In the strings in the attributes supplied to this macro, we want callers to be able to
+ /// reference fields in the format string. For example:
+ ///
+ /// ```ignore (not-usage-example)
+ /// /// Suggest `==` when users wrote `===`.
+ /// #[suggestion(slug = "parser-not-javascript-eq", code = "{lhs} == {rhs}")]
+ /// struct NotJavaScriptEq {
+ /// #[primary_span]
+ /// span: Span,
+ /// lhs: Ident,
+ /// rhs: Ident,
+ /// }
+ /// ```
+ ///
+ /// We want to automatically pick up that `{lhs}` refers `self.lhs` and `{rhs}` refers to
+ /// `self.rhs`, then generate this call to `format!`:
+ ///
+ /// ```ignore (not-usage-example)
+ /// format!("{lhs} == {rhs}", lhs = self.lhs, rhs = self.rhs)
+ /// ```
+ ///
+ /// This function builds the entire call to `format!`.
+ fn build_format(&self, input: &str, span: proc_macro2::Span) -> TokenStream {
+ // This set is used later to generate the final format string. To keep builds reproducible,
+ // the iteration order needs to be deterministic, hence why we use a `BTreeSet` here
+ // instead of a `HashSet`.
+ let mut referenced_fields: BTreeSet<String> = BTreeSet::new();
+
+ // At this point, we can start parsing the format string.
+ let mut it = input.chars().peekable();
+
+ // Once the start of a format string has been found, process the format string and spit out
+ // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so
+ // the next call to `it.next()` retrieves the next character.
+ while let Some(c) = it.next() {
+ if c == '{' && *it.peek().unwrap_or(&'\0') != '{' {
+ let mut eat_argument = || -> Option<String> {
+ let mut result = String::new();
+ // Format specifiers look like:
+ //
+ // format := '{' [ argument ] [ ':' format_spec ] '}' .
+ //
+ // Therefore, we only need to eat until ':' or '}' to find the argument.
+ while let Some(c) = it.next() {
+ result.push(c);
+ let next = *it.peek().unwrap_or(&'\0');
+ if next == '}' {
+ break;
+ } else if next == ':' {
+ // Eat the ':' character.
+ assert_eq!(it.next().unwrap(), ':');
+ break;
+ }
+ }
+ // Eat until (and including) the matching '}'
+ while it.next()? != '}' {
+ continue;
+ }
+ Some(result)
+ };
+
+ if let Some(referenced_field) = eat_argument() {
+ referenced_fields.insert(referenced_field);
+ }
+ }
+ }
+
+ // At this point, `referenced_fields` contains a set of the unique fields that were
+ // referenced in the format string. Generate the corresponding "x = self.x" format
+ // string parameters:
+ let args = referenced_fields.into_iter().map(|field: String| {
+ let field_ident = format_ident!("{}", field);
+ let value = match self.get_field_binding(&field) {
+ Some(value) => value.clone(),
+ // This field doesn't exist. Emit a diagnostic.
+ None => {
+ span_err(
+ span.unwrap(),
+ &format!("`{}` doesn't refer to a field on this type", field),
+ )
+ .emit();
+ quote! {
+ "{#field}"
+ }
+ }
+ };
+ quote! {
+ #field_ident = #value
+ }
+ });
+ quote! {
+ format!(#input #(,#args)*)
+ }
+ }
+}
+
+/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
+/// the user's selection of applicability if specified in an attribute.
+pub(crate) enum Applicability {
+ MachineApplicable,
+ MaybeIncorrect,
+ HasPlaceholders,
+ Unspecified,
+}
+
+impl FromStr for Applicability {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "machine-applicable" => Ok(Applicability::MachineApplicable),
+ "maybe-incorrect" => Ok(Applicability::MaybeIncorrect),
+ "has-placeholders" => Ok(Applicability::HasPlaceholders),
+ "unspecified" => Ok(Applicability::Unspecified),
+ _ => Err(()),
+ }
+ }
+}
+
+impl quote::ToTokens for Applicability {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ tokens.extend(match self {
+ Applicability::MachineApplicable => {
+ quote! { rustc_errors::Applicability::MachineApplicable }
+ }
+ Applicability::MaybeIncorrect => {
+ quote! { rustc_errors::Applicability::MaybeIncorrect }
+ }
+ Applicability::HasPlaceholders => {
+ quote! { rustc_errors::Applicability::HasPlaceholders }
+ }
+ Applicability::Unspecified => {
+ quote! { rustc_errors::Applicability::Unspecified }
+ }
+ });
+ }
+}
+
+/// Build the mapping of field names to fields. This allows attributes to peek values from
+/// other fields.
+pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap<String, TokenStream> {
+ let mut fields_map = HashMap::new();
+
+ let ast = structure.ast();
+ if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
+ for field in fields.iter() {
+ if let Some(ident) = &field.ident {
+ fields_map.insert(ident.to_string(), quote! { &self.#ident });
+ }
+ }
+ }
+
+ fields_map
+}
diff --git a/compiler/rustc_macros/src/hash_stable.rs b/compiler/rustc_macros/src/hash_stable.rs
new file mode 100644
index 000000000..63bdcea87
--- /dev/null
+++ b/compiler/rustc_macros/src/hash_stable.rs
@@ -0,0 +1,131 @@
+use proc_macro2::{self, Ident};
+use quote::quote;
+use syn::{self, parse_quote, Meta, NestedMeta};
+
+struct Attributes {
+ ignore: bool,
+ project: Option<Ident>,
+}
+
+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 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 !any_attr {
+ panic!("error parsing stable_hasher");
+ }
+ }
+ }
+ attrs
+}
+
+pub fn hash_stable_generic_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+ let generic: syn::GenericParam = parse_quote!(__CTX);
+ s.add_bounds(synstructure::AddBounds::Generics);
+ s.add_impl_generic(generic);
+ s.add_where_predicate(parse_quote! { __CTX: crate::HashStableContext });
+ let body = s.each(|bi| {
+ let attrs = parse_attributes(bi.ast());
+ if attrs.ignore {
+ quote! {}
+ } else if let Some(project) = attrs.project {
+ quote! {
+ (&#bi.#project).hash_stable(__hcx, __hasher);
+ }
+ } else {
+ quote! {
+ #bi.hash_stable(__hcx, __hasher);
+ }
+ }
+ });
+
+ let discriminant = match s.ast().data {
+ syn::Data::Enum(_) => quote! {
+ ::std::mem::discriminant(self).hash_stable(__hcx, __hasher);
+ },
+ syn::Data::Struct(_) => quote! {},
+ syn::Data::Union(_) => panic!("cannot derive on union"),
+ };
+
+ s.bound_impl(
+ quote!(::rustc_data_structures::stable_hasher::HashStable<__CTX>),
+ quote! {
+ #[inline]
+ fn hash_stable(
+ &self,
+ __hcx: &mut __CTX,
+ __hasher: &mut ::rustc_data_structures::stable_hasher::StableHasher) {
+ #discriminant
+ match *self { #body }
+ }
+ },
+ )
+}
+
+pub fn hash_stable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+ let generic: syn::GenericParam = parse_quote!('__ctx);
+ s.add_bounds(synstructure::AddBounds::Generics);
+ s.add_impl_generic(generic);
+ let body = s.each(|bi| {
+ let attrs = parse_attributes(bi.ast());
+ if attrs.ignore {
+ quote! {}
+ } else if let Some(project) = attrs.project {
+ quote! {
+ (&#bi.#project).hash_stable(__hcx, __hasher);
+ }
+ } else {
+ quote! {
+ #bi.hash_stable(__hcx, __hasher);
+ }
+ }
+ });
+
+ let discriminant = match s.ast().data {
+ syn::Data::Enum(_) => quote! {
+ ::std::mem::discriminant(self).hash_stable(__hcx, __hasher);
+ },
+ syn::Data::Struct(_) => quote! {},
+ syn::Data::Union(_) => panic!("cannot derive on union"),
+ };
+
+ s.bound_impl(
+ quote!(
+ ::rustc_data_structures::stable_hasher::HashStable<
+ ::rustc_query_system::ich::StableHashingContext<'__ctx>,
+ >
+ ),
+ quote! {
+ #[inline]
+ fn hash_stable(
+ &self,
+ __hcx: &mut ::rustc_query_system::ich::StableHashingContext<'__ctx>,
+ __hasher: &mut ::rustc_data_structures::stable_hasher::StableHasher) {
+ #discriminant
+ match *self { #body }
+ }
+ },
+ )
+}
diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs
new file mode 100644
index 000000000..ab509b26f
--- /dev/null
+++ b/compiler/rustc_macros/src/lib.rs
@@ -0,0 +1,180 @@
+#![feature(allow_internal_unstable)]
+#![feature(let_else)]
+#![feature(never_type)]
+#![feature(proc_macro_diagnostic)]
+#![feature(proc_macro_span)]
+#![allow(rustc::default_hash_types)]
+#![recursion_limit = "128"]
+
+use synstructure::decl_derive;
+
+use proc_macro::TokenStream;
+
+mod diagnostics;
+mod hash_stable;
+mod lift;
+mod newtype;
+mod query;
+mod serialize;
+mod symbols;
+mod type_foldable;
+mod type_visitable;
+
+#[proc_macro]
+pub fn rustc_queries(input: TokenStream) -> TokenStream {
+ query::rustc_queries(input)
+}
+
+#[proc_macro]
+pub fn symbols(input: TokenStream) -> TokenStream {
+ symbols::symbols(input.into()).into()
+}
+
+/// Creates a struct type `S` that can be used as an index with
+/// `IndexVec` and so on.
+///
+/// There are two ways of interacting with these indices:
+///
+/// - The `From` impls are the preferred way. So you can do
+/// `S::from(v)` with a `usize` or `u32`. And you can convert back
+/// to an integer with `u32::from(s)`.
+///
+/// - Alternatively, you can use the methods `S::new(v)` and `s.index()`
+/// to create/return a value.
+///
+/// Internally, the index uses a u32, so the index must not exceed
+/// `u32::MAX`. You can also customize things like the `Debug` impl,
+/// what traits are derived, and so forth via the macro.
+#[proc_macro]
+#[allow_internal_unstable(step_trait, rustc_attrs, trusted_step)]
+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)] =>
+ hash_stable::hash_stable_generic_derive
+);
+
+decl_derive!([Decodable] => serialize::decodable_derive);
+decl_derive!([Encodable] => serialize::encodable_derive);
+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!([Lift, attributes(lift)] => lift::lift_derive);
+decl_derive!(
+ [SessionDiagnostic, attributes(
+ // struct attributes
+ warning,
+ error,
+ lint,
+ help,
+ note,
+ warn_,
+ // field attributes
+ skip_arg,
+ primary_span,
+ label,
+ subdiagnostic,
+ suggestion,
+ suggestion_short,
+ suggestion_hidden,
+ suggestion_verbose)] => diagnostics::session_diagnostic_derive
+);
+decl_derive!(
+ [LintDiagnostic, attributes(
+ // struct attributes
+ warning,
+ error,
+ lint,
+ help,
+ note,
+ warn_,
+ // field attributes
+ skip_arg,
+ primary_span,
+ label,
+ subdiagnostic,
+ suggestion,
+ suggestion_short,
+ suggestion_hidden,
+ suggestion_verbose)] => diagnostics::lint_diagnostic_derive
+);
+decl_derive!(
+ [SessionSubdiagnostic, attributes(
+ // struct/variant attributes
+ label,
+ help,
+ note,
+ warn_,
+ suggestion,
+ suggestion_short,
+ suggestion_hidden,
+ suggestion_verbose,
+ // field attributes
+ skip_arg,
+ primary_span,
+ applicability)] => diagnostics::session_subdiagnostic_derive
+);
diff --git a/compiler/rustc_macros/src/lift.rs b/compiler/rustc_macros/src/lift.rs
new file mode 100644
index 000000000..ad7ac7404
--- /dev/null
+++ b/compiler/rustc_macros/src/lift.rs
@@ -0,0 +1,52 @@
+use quote::quote;
+use syn::{self, parse_quote};
+
+pub fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+ s.add_bounds(synstructure::AddBounds::Generics);
+ s.bind_with(|_| synstructure::BindStyle::Move);
+
+ let tcx: syn::Lifetime = parse_quote!('tcx);
+ let newtcx: syn::GenericParam = parse_quote!('__lifted);
+
+ let lifted = {
+ let ast = s.ast();
+ let ident = &ast.ident;
+
+ // Replace `'tcx` lifetime by the `'__lifted` lifetime
+ let (_, generics, _) = ast.generics.split_for_impl();
+ let mut generics: syn::AngleBracketedGenericArguments = syn::parse_quote! { #generics };
+ for arg in generics.args.iter_mut() {
+ match arg {
+ syn::GenericArgument::Lifetime(l) if *l == tcx => {
+ *arg = parse_quote!('__lifted);
+ }
+ syn::GenericArgument::Type(t) => {
+ *arg = syn::parse_quote! { #t::Lifted };
+ }
+ _ => {}
+ }
+ }
+
+ quote! { #ident #generics }
+ };
+
+ let body = s.each_variant(|vi| {
+ let bindings = &vi.bindings();
+ vi.construct(|_, index| {
+ let bi = &bindings[index];
+ quote! { __tcx.lift(#bi)? }
+ })
+ });
+
+ s.add_impl_generic(newtcx);
+ s.bound_impl(
+ quote!(::rustc_middle::ty::Lift<'__lifted>),
+ quote! {
+ type Lifted = #lifted;
+
+ fn lift_to_tcx(self, __tcx: ::rustc_middle::ty::TyCtxt<'__lifted>) -> Option<#lifted> {
+ Some(match self { #body })
+ }
+ },
+ )
+}
diff --git a/compiler/rustc_macros/src/newtype.rs b/compiler/rustc_macros/src/newtype.rs
new file mode 100644
index 000000000..0a77b734c
--- /dev/null
+++ b/compiler/rustc_macros/src/newtype.rs
@@ -0,0 +1,333 @@
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use syn::parse::*;
+use syn::punctuated::Punctuated;
+use syn::*;
+
+mod kw {
+ syn::custom_keyword!(derive);
+ syn::custom_keyword!(DEBUG_FORMAT);
+ syn::custom_keyword!(MAX);
+ syn::custom_keyword!(ENCODABLE);
+ syn::custom_keyword!(custom);
+ syn::custom_keyword!(ORD_IMPL);
+}
+
+#[derive(Debug)]
+enum DebugFormat {
+ // The user will provide a custom `Debug` impl, so we shouldn't generate
+ // one
+ Custom,
+ // Use the specified format string in the generated `Debug` impl
+ // By default, this is "{}"
+ Format(String),
+}
+
+// We parse the input and emit the output in a single step.
+// This field stores the final macro output
+struct Newtype(TokenStream);
+
+impl Parse for Newtype {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let attrs = input.call(Attribute::parse_outer)?;
+ let vis: Visibility = input.parse()?;
+ input.parse::<Token![struct]>()?;
+ let name: Ident = input.parse()?;
+
+ let body;
+ braced!(body in input);
+
+ // Any additional `#[derive]` macro paths to apply
+ let mut derive_paths: Vec<Path> = Vec::new();
+ let mut debug_format: Option<DebugFormat> = None;
+ let mut max = None;
+ let mut consts = Vec::new();
+ let mut encodable = true;
+ let mut ord = true;
+
+ // Parse an optional trailing comma
+ let try_comma = || -> Result<()> {
+ if body.lookahead1().peek(Token![,]) {
+ body.parse::<Token![,]>()?;
+ }
+ Ok(())
+ };
+
+ if body.lookahead1().peek(Token![..]) {
+ body.parse::<Token![..]>()?;
+ } else {
+ loop {
+ if body.lookahead1().peek(kw::derive) {
+ body.parse::<kw::derive>()?;
+ let derives;
+ bracketed!(derives in body);
+ let derives: Punctuated<Path, Token![,]> =
+ derives.parse_terminated(Path::parse)?;
+ try_comma()?;
+ derive_paths.extend(derives);
+ continue;
+ }
+ if body.lookahead1().peek(kw::DEBUG_FORMAT) {
+ body.parse::<kw::DEBUG_FORMAT>()?;
+ body.parse::<Token![=]>()?;
+ let new_debug_format = if body.lookahead1().peek(kw::custom) {
+ body.parse::<kw::custom>()?;
+ DebugFormat::Custom
+ } else {
+ let format_str: LitStr = body.parse()?;
+ DebugFormat::Format(format_str.value())
+ };
+ try_comma()?;
+ if let Some(old) = debug_format.replace(new_debug_format) {
+ panic!("Specified multiple debug format options: {:?}", old);
+ }
+ continue;
+ }
+ if body.lookahead1().peek(kw::MAX) {
+ body.parse::<kw::MAX>()?;
+ body.parse::<Token![=]>()?;
+ let val: Lit = body.parse()?;
+ try_comma()?;
+ if let Some(old) = max.replace(val) {
+ panic!("Specified multiple MAX: {:?}", old);
+ }
+ continue;
+ }
+ if body.lookahead1().peek(kw::ENCODABLE) {
+ body.parse::<kw::ENCODABLE>()?;
+ body.parse::<Token![=]>()?;
+ body.parse::<kw::custom>()?;
+ try_comma()?;
+ encodable = false;
+ continue;
+ }
+ if body.lookahead1().peek(kw::ORD_IMPL) {
+ body.parse::<kw::ORD_IMPL>()?;
+ body.parse::<Token![=]>()?;
+ body.parse::<kw::custom>()?;
+ ord = false;
+ continue;
+ }
+
+ // We've parsed everything that the user provided, so we're done
+ if body.is_empty() {
+ break;
+ }
+
+ // Otherwise, we are parsing a user-defined constant
+ let const_attrs = body.call(Attribute::parse_outer)?;
+ body.parse::<Token![const]>()?;
+ let const_name: Ident = body.parse()?;
+ body.parse::<Token![=]>()?;
+ let const_val: Expr = body.parse()?;
+ try_comma()?;
+ consts.push(quote! { #(#const_attrs)* #vis const #const_name: #name = #name::from_u32(#const_val); });
+ }
+ }
+
+ let debug_format = debug_format.unwrap_or(DebugFormat::Format("{}".to_string()));
+ // shave off 256 indices at the end to allow space for packing these indices into enums
+ let max = max.unwrap_or_else(|| Lit::Int(LitInt::new("0xFFFF_FF00", Span::call_site())));
+
+ let encodable_impls = if encodable {
+ quote! {
+ impl<D: ::rustc_serialize::Decoder> ::rustc_serialize::Decodable<D> for #name {
+ fn decode(d: &mut D) -> Self {
+ Self::from_u32(d.read_u32())
+ }
+ }
+ impl<E: ::rustc_serialize::Encoder> ::rustc_serialize::Encodable<E> for #name {
+ fn encode(&self, e: &mut E) {
+ e.emit_u32(self.private);
+ }
+ }
+ }
+ } else {
+ quote! {}
+ };
+
+ if ord {
+ derive_paths.push(parse_quote!(Ord));
+ derive_paths.push(parse_quote!(PartialOrd));
+ }
+
+ let step = if ord {
+ quote! {
+ impl ::std::iter::Step for #name {
+ #[inline]
+ fn steps_between(start: &Self, end: &Self) -> Option<usize> {
+ <usize as ::std::iter::Step>::steps_between(
+ &Self::index(*start),
+ &Self::index(*end),
+ )
+ }
+
+ #[inline]
+ fn forward_checked(start: Self, u: usize) -> Option<Self> {
+ Self::index(start).checked_add(u).map(Self::from_usize)
+ }
+
+ #[inline]
+ fn backward_checked(start: Self, u: usize) -> Option<Self> {
+ Self::index(start).checked_sub(u).map(Self::from_usize)
+ }
+ }
+
+ // Safety: The implementation of `Step` upholds all invariants.
+ unsafe impl ::std::iter::TrustedStep for #name {}
+ }
+ } else {
+ quote! {}
+ };
+
+ let debug_impl = match debug_format {
+ DebugFormat::Custom => quote! {},
+ DebugFormat::Format(format) => {
+ quote! {
+ impl ::std::fmt::Debug for #name {
+ fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ write!(fmt, #format, self.as_u32())
+ }
+ }
+ }
+ }
+ };
+
+ Ok(Self(quote! {
+ #(#attrs)*
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, #(#derive_paths),*)]
+ #[rustc_layout_scalar_valid_range_end(#max)]
+ #[rustc_pass_by_value]
+ #vis struct #name {
+ private: u32,
+ }
+
+ #(#consts)*
+
+ impl #name {
+ /// Maximum value the index can take, as a `u32`.
+ #vis const MAX_AS_U32: u32 = #max;
+
+ /// Maximum value the index can take.
+ #vis const MAX: Self = Self::from_u32(#max);
+
+ /// Creates a new index from a given `usize`.
+ ///
+ /// # Panics
+ ///
+ /// Will panic if `value` exceeds `MAX`.
+ #[inline]
+ #vis const fn from_usize(value: usize) -> Self {
+ assert!(value <= (#max as usize));
+ // SAFETY: We just checked that `value <= max`.
+ unsafe {
+ Self::from_u32_unchecked(value as u32)
+ }
+ }
+
+ /// Creates a new index from a given `u32`.
+ ///
+ /// # Panics
+ ///
+ /// Will panic if `value` exceeds `MAX`.
+ #[inline]
+ #vis const fn from_u32(value: u32) -> Self {
+ assert!(value <= #max);
+ // SAFETY: We just checked that `value <= max`.
+ unsafe {
+ Self::from_u32_unchecked(value)
+ }
+ }
+
+ /// Creates a new index from a given `u32`.
+ ///
+ /// # Safety
+ ///
+ /// The provided value must be less than or equal to the maximum value for the newtype.
+ /// Providing a value outside this range is undefined due to layout restrictions.
+ ///
+ /// Prefer using `from_u32`.
+ #[inline]
+ #vis const unsafe fn from_u32_unchecked(value: u32) -> Self {
+ Self { private: value }
+ }
+
+ /// Extracts the value of this index as a `usize`.
+ #[inline]
+ #vis const fn index(self) -> usize {
+ self.as_usize()
+ }
+
+ /// Extracts the value of this index as a `u32`.
+ #[inline]
+ #vis const fn as_u32(self) -> u32 {
+ self.private
+ }
+
+ /// Extracts the value of this index as a `usize`.
+ #[inline]
+ #vis const fn as_usize(self) -> usize {
+ self.as_u32() as usize
+ }
+ }
+
+ impl std::ops::Add<usize> for #name {
+ type Output = Self;
+
+ fn add(self, other: usize) -> Self {
+ Self::from_usize(self.index() + other)
+ }
+ }
+
+ impl rustc_index::vec::Idx for #name {
+ #[inline]
+ fn new(value: usize) -> Self {
+ Self::from_usize(value)
+ }
+
+ #[inline]
+ fn index(self) -> usize {
+ self.as_usize()
+ }
+ }
+
+ #step
+
+ impl From<#name> for u32 {
+ #[inline]
+ fn from(v: #name) -> u32 {
+ v.as_u32()
+ }
+ }
+
+ impl From<#name> for usize {
+ #[inline]
+ fn from(v: #name) -> usize {
+ v.as_usize()
+ }
+ }
+
+ impl From<usize> for #name {
+ #[inline]
+ fn from(value: usize) -> Self {
+ Self::from_usize(value)
+ }
+ }
+
+ impl From<u32> for #name {
+ #[inline]
+ fn from(value: u32) -> Self {
+ Self::from_u32(value)
+ }
+ }
+
+ #encodable_impls
+ #debug_impl
+ }))
+ }
+}
+
+pub fn newtype(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let input = parse_macro_input!(input as Newtype);
+ input.0.into()
+}
diff --git a/compiler/rustc_macros/src/query.rs b/compiler/rustc_macros/src/query.rs
new file mode 100644
index 000000000..a69126533
--- /dev/null
+++ b/compiler/rustc_macros/src/query.rs
@@ -0,0 +1,566 @@
+use proc_macro::TokenStream;
+use proc_macro2::{Delimiter, TokenTree};
+use quote::{quote, quote_spanned};
+use syn::parse::{Parse, ParseStream, Result};
+use syn::punctuated::Punctuated;
+use syn::spanned::Spanned;
+use syn::{
+ braced, parenthesized, parse_macro_input, parse_quote, AttrStyle, Attribute, Block, Error,
+ Expr, Ident, ReturnType, Token, Type,
+};
+
+mod kw {
+ syn::custom_keyword!(query);
+}
+
+/// Ident or a wildcard `_`.
+struct IdentOrWild(Ident);
+
+impl Parse for IdentOrWild {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ Ok(if input.peek(Token![_]) {
+ let underscore = input.parse::<Token![_]>()?;
+ IdentOrWild(Ident::new("_", underscore.span()))
+ } else {
+ IdentOrWild(input.parse()?)
+ })
+ }
+}
+
+/// A modifier for a query
+enum QueryModifier {
+ /// The description of the query.
+ Desc(Option<Ident>, Punctuated<Expr, Token![,]>),
+
+ /// Use this type for the in-memory cache.
+ Storage(Type),
+
+ /// Cache the query to disk if the `Expr` returns true.
+ Cache(Option<IdentOrWild>, Block),
+
+ /// Custom code to load the query from disk.
+ LoadCached(Ident, Ident, Block),
+
+ /// A cycle error for this query aborting the compilation with a fatal error.
+ FatalCycle(Ident),
+
+ /// A cycle error results in a delay_bug call
+ CycleDelayBug(Ident),
+
+ /// Don't hash the result, instead just mark a query red if it runs
+ NoHash(Ident),
+
+ /// Generate a dep node based on the dependencies of the query
+ Anon(Ident),
+
+ /// Always evaluate the query, ignoring its dependencies
+ EvalAlways(Ident),
+
+ /// Use a separate query provider for local and extern crates
+ SeparateProvideExtern(Ident),
+
+ /// Always remap the ParamEnv's constness before hashing and passing to the query provider
+ RemapEnvConstness(Ident),
+}
+
+impl Parse for QueryModifier {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let modifier: Ident = input.parse()?;
+ if modifier == "desc" {
+ // Parse a description modifier like:
+ // `desc { |tcx| "foo {}", tcx.item_path(key) }`
+ let attr_content;
+ braced!(attr_content in input);
+ let tcx = if attr_content.peek(Token![|]) {
+ attr_content.parse::<Token![|]>()?;
+ let tcx = attr_content.parse()?;
+ attr_content.parse::<Token![|]>()?;
+ Some(tcx)
+ } else {
+ None
+ };
+ let desc = attr_content.parse_terminated(Expr::parse)?;
+ Ok(QueryModifier::Desc(tcx, desc))
+ } else if modifier == "cache_on_disk_if" {
+ // Parse a cache modifier like:
+ // `cache(tcx, value) { |tcx| key.is_local() }`
+ let has_args = if let TokenTree::Group(group) = input.fork().parse()? {
+ group.delimiter() == Delimiter::Parenthesis
+ } else {
+ false
+ };
+ let args = if has_args {
+ let args;
+ parenthesized!(args in input);
+ let tcx = args.parse()?;
+ Some(tcx)
+ } else {
+ None
+ };
+ let block = input.parse()?;
+ Ok(QueryModifier::Cache(args, block))
+ } else if modifier == "load_cached" {
+ // Parse a load_cached modifier like:
+ // `load_cached(tcx, id) { tcx.on_disk_cache.try_load_query_result(tcx, id) }`
+ let args;
+ parenthesized!(args in input);
+ let tcx = args.parse()?;
+ args.parse::<Token![,]>()?;
+ let id = args.parse()?;
+ let block = input.parse()?;
+ Ok(QueryModifier::LoadCached(tcx, id, block))
+ } else if modifier == "storage" {
+ let args;
+ parenthesized!(args in input);
+ let ty = args.parse()?;
+ Ok(QueryModifier::Storage(ty))
+ } else if modifier == "fatal_cycle" {
+ Ok(QueryModifier::FatalCycle(modifier))
+ } else if modifier == "cycle_delay_bug" {
+ Ok(QueryModifier::CycleDelayBug(modifier))
+ } else if modifier == "no_hash" {
+ Ok(QueryModifier::NoHash(modifier))
+ } else if modifier == "anon" {
+ Ok(QueryModifier::Anon(modifier))
+ } else if modifier == "eval_always" {
+ Ok(QueryModifier::EvalAlways(modifier))
+ } else if modifier == "separate_provide_extern" {
+ Ok(QueryModifier::SeparateProvideExtern(modifier))
+ } else if modifier == "remap_env_constness" {
+ Ok(QueryModifier::RemapEnvConstness(modifier))
+ } else {
+ Err(Error::new(modifier.span(), "unknown query modifier"))
+ }
+ }
+}
+
+/// 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") {
+ Err(Error::new(attr.span(), "attributes not supported on queries"))
+ } else if attr.style != AttrStyle::Outer {
+ Err(Error::new(
+ attr.span(),
+ "attributes must be outer attributes (`///`), not inner attributes",
+ ))
+ } else {
+ Ok(attr)
+ }
+ };
+ attrs.into_iter().map(inner).collect()
+}
+
+/// A compiler query. `query ... { ... }`
+struct Query {
+ doc_comments: Vec<Attribute>,
+ modifiers: List<QueryModifier>,
+ name: Ident,
+ key: IdentOrWild,
+ arg: Type,
+ result: ReturnType,
+}
+
+impl Parse for Query {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?;
+
+ // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>`
+ input.parse::<kw::query>()?;
+ let name: Ident = input.parse()?;
+ let arg_content;
+ parenthesized!(arg_content in input);
+ let key = arg_content.parse()?;
+ arg_content.parse::<Token![:]>()?;
+ let arg = arg_content.parse()?;
+ let result = input.parse()?;
+
+ // Parse the query modifiers
+ let content;
+ braced!(content in input);
+ let modifiers = content.parse()?;
+
+ Ok(Query { doc_comments, modifiers, name, key, arg, result })
+ }
+}
+
+/// A type used to greedily parse another type until the input is empty.
+struct List<T>(Vec<T>);
+
+impl<T: Parse> Parse for List<T> {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let mut list = Vec::new();
+ while !input.is_empty() {
+ list.push(input.parse()?);
+ }
+ Ok(List(list))
+ }
+}
+
+struct QueryModifiers {
+ /// The description of the query.
+ desc: (Option<Ident>, Punctuated<Expr, Token![,]>),
+
+ /// Use this type for the in-memory cache.
+ storage: Option<Type>,
+
+ /// Cache the query to disk if the `Block` returns true.
+ cache: Option<(Option<IdentOrWild>, Block)>,
+
+ /// Custom code to load the query from disk.
+ load_cached: Option<(Ident, Ident, Block)>,
+
+ /// A cycle error for this query aborting the compilation with a fatal error.
+ fatal_cycle: Option<Ident>,
+
+ /// A cycle error results in a delay_bug call
+ cycle_delay_bug: Option<Ident>,
+
+ /// Don't hash the result, instead just mark a query red if it runs
+ no_hash: Option<Ident>,
+
+ /// Generate a dep node based on the dependencies of the query
+ anon: Option<Ident>,
+
+ // Always evaluate the query, ignoring its dependencies
+ eval_always: Option<Ident>,
+
+ /// 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>,
+}
+
+/// Process query modifiers into a struct, erroring on duplicates
+fn process_modifiers(query: &mut Query) -> QueryModifiers {
+ let mut load_cached = None;
+ let mut storage = None;
+ let mut cache = None;
+ let mut desc = None;
+ let mut fatal_cycle = None;
+ let mut cycle_delay_bug = None;
+ let mut no_hash = None;
+ let mut anon = None;
+ let mut eval_always = None;
+ let mut separate_provide_extern = None;
+ let mut remap_env_constness = None;
+ for modifier in query.modifiers.0.drain(..) {
+ match modifier {
+ QueryModifier::LoadCached(tcx, id, block) => {
+ if load_cached.is_some() {
+ panic!("duplicate modifier `load_cached` for query `{}`", query.name);
+ }
+ load_cached = Some((tcx, id, block));
+ }
+ QueryModifier::Storage(ty) => {
+ if storage.is_some() {
+ panic!("duplicate modifier `storage` for query `{}`", query.name);
+ }
+ storage = Some(ty);
+ }
+ QueryModifier::Cache(args, expr) => {
+ if cache.is_some() {
+ panic!("duplicate modifier `cache` for query `{}`", query.name);
+ }
+ cache = Some((args, expr));
+ }
+ QueryModifier::Desc(tcx, list) => {
+ if desc.is_some() {
+ panic!("duplicate modifier `desc` for query `{}`", query.name);
+ }
+ // If there are no doc-comments, give at least some idea of what
+ // it does by showing the query description.
+ if query.doc_comments.is_empty() {
+ use ::syn::*;
+ let mut list = list.iter();
+ let format_str: String = match list.next() {
+ Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => {
+ lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency
+ }
+ _ => panic!("Expected a string literal"),
+ };
+ let mut fmt_fragments = format_str.split("{}");
+ let mut doc_string = fmt_fragments.next().unwrap().to_string();
+ list.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each(
+ |(tts, next_fmt_fragment)| {
+ use ::core::fmt::Write;
+ write!(
+ &mut doc_string,
+ " `{}` {}",
+ tts.to_string().replace(" . ", "."),
+ next_fmt_fragment,
+ )
+ .unwrap();
+ },
+ );
+ let doc_string = format!(
+ "[query description - consider adding a doc-comment!] {}",
+ doc_string
+ );
+ let comment = parse_quote! {
+ #[doc = #doc_string]
+ };
+ query.doc_comments.push(comment);
+ }
+ desc = Some((tcx, list));
+ }
+ QueryModifier::FatalCycle(ident) => {
+ if fatal_cycle.is_some() {
+ panic!("duplicate modifier `fatal_cycle` for query `{}`", query.name);
+ }
+ fatal_cycle = Some(ident);
+ }
+ QueryModifier::CycleDelayBug(ident) => {
+ if cycle_delay_bug.is_some() {
+ panic!("duplicate modifier `cycle_delay_bug` for query `{}`", query.name);
+ }
+ cycle_delay_bug = Some(ident);
+ }
+ QueryModifier::NoHash(ident) => {
+ if no_hash.is_some() {
+ panic!("duplicate modifier `no_hash` for query `{}`", query.name);
+ }
+ no_hash = Some(ident);
+ }
+ QueryModifier::Anon(ident) => {
+ if anon.is_some() {
+ panic!("duplicate modifier `anon` for query `{}`", query.name);
+ }
+ anon = Some(ident);
+ }
+ QueryModifier::EvalAlways(ident) => {
+ if eval_always.is_some() {
+ panic!("duplicate modifier `eval_always` for query `{}`", query.name);
+ }
+ eval_always = Some(ident);
+ }
+ QueryModifier::SeparateProvideExtern(ident) => {
+ if separate_provide_extern.is_some() {
+ panic!(
+ "duplicate modifier `separate_provide_extern` for query `{}`",
+ query.name
+ );
+ }
+ separate_provide_extern = Some(ident);
+ }
+ QueryModifier::RemapEnvConstness(ident) => {
+ if remap_env_constness.is_some() {
+ panic!("duplicate modifier `remap_env_constness` for query `{}`", query.name);
+ }
+ remap_env_constness = Some(ident)
+ }
+ }
+ }
+ let desc = desc.unwrap_or_else(|| {
+ panic!("no description provided for query `{}`", query.name);
+ });
+ QueryModifiers {
+ load_cached,
+ storage,
+ cache,
+ desc,
+ fatal_cycle,
+ cycle_delay_bug,
+ no_hash,
+ anon,
+ eval_always,
+ separate_provide_extern,
+ remap_env_constness,
+ }
+}
+
+/// Add the impl of QueryDescription for the query to `impls` if one is requested
+fn add_query_description_impl(
+ query: &Query,
+ modifiers: QueryModifiers,
+ impls: &mut proc_macro2::TokenStream,
+) {
+ let name = &query.name;
+ let key = &query.key.0;
+
+ // Find out if we should cache the query on disk
+ let cache = if let Some((args, expr)) = modifiers.cache.as_ref() {
+ let try_load_from_disk = if let Some((tcx, id, block)) = modifiers.load_cached.as_ref() {
+ // Use custom code to load the query from disk
+ quote! {
+ const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>>
+ = Some(|#tcx, #id| { #block });
+ }
+ } else {
+ // Use the default code to load the query from disk
+ quote! {
+ const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>>
+ = Some(|tcx, id| tcx.on_disk_cache().as_ref()?.try_load_query_result(*tcx, id));
+ }
+ };
+
+ let tcx = args
+ .as_ref()
+ .map(|t| {
+ let t = &t.0;
+ quote! { #t }
+ })
+ .unwrap_or_else(|| quote! { _ });
+ // expr is a `Block`, meaning that `{ #expr }` gets expanded
+ // to `{ { stmts... } }`, which triggers the `unused_braces` lint.
+ quote! {
+ #[allow(unused_variables, unused_braces)]
+ #[inline]
+ fn cache_on_disk(#tcx: TyCtxt<'tcx>, #key: &Self::Key) -> bool {
+ #expr
+ }
+
+ #try_load_from_disk
+ }
+ } else {
+ if modifiers.load_cached.is_some() {
+ panic!("load_cached modifier on query `{}` without a cache modifier", name);
+ }
+ quote! {
+ #[inline]
+ fn cache_on_disk(_: TyCtxt<'tcx>, _: &Self::Key) -> bool {
+ false
+ }
+
+ const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>> = None;
+ }
+ };
+
+ let (tcx, desc) = modifiers.desc;
+ let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t });
+
+ let desc = quote! {
+ #[allow(unused_variables)]
+ fn describe(tcx: QueryCtxt<$tcx>, key: Self::Key) -> String {
+ let (#tcx, #key) = (*tcx, key);
+ ::rustc_middle::ty::print::with_no_trimmed_paths!(
+ format!(#desc)
+ )
+ }
+ };
+
+ impls.extend(quote! {
+ (#name<$tcx:tt>) => {
+ #desc
+ #cache
+ };
+ });
+}
+
+pub fn rustc_queries(input: TokenStream) -> TokenStream {
+ let queries = parse_macro_input!(input as List<Query>);
+
+ let mut query_stream = quote! {};
+ let mut query_description_stream = quote! {};
+ let mut dep_node_def_stream = quote! {};
+ let mut cached_queries = quote! {};
+
+ for mut query in queries.0 {
+ let modifiers = process_modifiers(&mut query);
+ let name = &query.name;
+ let arg = &query.arg;
+ let result_full = &query.result;
+ let result = match query.result {
+ ReturnType::Default => quote! { -> () },
+ _ => quote! { #result_full },
+ };
+
+ if modifiers.cache.is_some() {
+ cached_queries.extend(quote! {
+ #name,
+ });
+ }
+
+ let mut attributes = Vec::new();
+
+ // Pass on the fatal_cycle modifier
+ if let Some(fatal_cycle) = &modifiers.fatal_cycle {
+ attributes.push(quote! { (#fatal_cycle) });
+ };
+ // Pass on the storage modifier
+ if let Some(ref ty) = modifiers.storage {
+ let span = ty.span();
+ attributes.push(quote_spanned! {span=> (storage #ty) });
+ };
+ // Pass on the cycle_delay_bug modifier
+ if let Some(cycle_delay_bug) = &modifiers.cycle_delay_bug {
+ attributes.push(quote! { (#cycle_delay_bug) });
+ };
+ // Pass on the no_hash modifier
+ if let Some(no_hash) = &modifiers.no_hash {
+ attributes.push(quote! { (#no_hash) });
+ };
+ // Pass on the anon modifier
+ if let Some(anon) = &modifiers.anon {
+ attributes.push(quote! { (#anon) });
+ };
+ // Pass on the eval_always modifier
+ if let Some(eval_always) = &modifiers.eval_always {
+ attributes.push(quote! { (#eval_always) });
+ };
+ // Pass on the separate_provide_extern modifier
+ if let Some(separate_provide_extern) = &modifiers.separate_provide_extern {
+ attributes.push(quote! { (#separate_provide_extern) });
+ }
+ // Pass on the remap_env_constness modifier
+ if let Some(remap_env_constness) = &modifiers.remap_env_constness {
+ attributes.push(quote! { (#remap_env_constness) });
+ }
+
+ // This uses the span of the query definition for the commas,
+ // which can be important if we later encounter any ambiguity
+ // errors with any of the numerous macro_rules! macros that
+ // we use. Using the call-site span would result in a span pointing
+ // at the entire `rustc_queries!` invocation, which wouldn't
+ // be very useful.
+ let span = name.span();
+ let attribute_stream = quote_spanned! {span=> #(#attributes),*};
+ let doc_comments = query.doc_comments.iter();
+ // Add the query to the group
+ query_stream.extend(quote! {
+ #(#doc_comments)*
+ [#attribute_stream] fn #name(#arg) #result,
+ });
+
+ // Create a dep node for the query
+ dep_node_def_stream.extend(quote! {
+ [#attribute_stream] #name(#arg),
+ });
+
+ add_query_description_impl(&query, modifiers, &mut query_description_stream);
+ }
+
+ TokenStream::from(quote! {
+ #[macro_export]
+ macro_rules! rustc_query_append {
+ ([$($macro:tt)*][$($other:tt)*]) => {
+ $($macro)* {
+ $($other)*
+
+ #query_stream
+
+ }
+ }
+ }
+ macro_rules! rustc_dep_node_append {
+ ([$($macro:tt)*][$($other:tt)*]) => {
+ $($macro)*(
+ $($other)*
+
+ #dep_node_def_stream
+ );
+ }
+ }
+ #[macro_export]
+ macro_rules! rustc_cached_queries {
+ ($($macro:tt)*) => {
+ $($macro)*(#cached_queries);
+ }
+ }
+ #[macro_export]
+ macro_rules! rustc_query_description {
+ #query_description_stream
+ }
+ })
+}
diff --git a/compiler/rustc_macros/src/serialize.rs b/compiler/rustc_macros/src/serialize.rs
new file mode 100644
index 000000000..82e6972d0
--- /dev/null
+++ b/compiler/rustc_macros/src/serialize.rs
@@ -0,0 +1,224 @@
+use proc_macro2::TokenStream;
+use quote::{quote, quote_spanned};
+use syn::parse_quote;
+use syn::spanned::Spanned;
+
+pub fn type_decodable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+ let decoder_ty = quote! { __D };
+ if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") {
+ s.add_impl_generic(parse_quote! { 'tcx });
+ }
+ s.add_impl_generic(parse_quote! {#decoder_ty: ::rustc_type_ir::codec::TyDecoder<I = ::rustc_middle::ty::TyCtxt<'tcx>>});
+ s.add_bounds(synstructure::AddBounds::Generics);
+
+ decodable_body(s, decoder_ty)
+}
+
+pub fn meta_decodable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+ if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") {
+ s.add_impl_generic(parse_quote! { 'tcx });
+ }
+ s.add_impl_generic(parse_quote! { '__a });
+ let decoder_ty = quote! { DecodeContext<'__a, 'tcx> };
+ s.add_bounds(synstructure::AddBounds::Generics);
+
+ decodable_body(s, decoder_ty)
+}
+
+pub fn decodable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+ let decoder_ty = quote! { __D };
+ s.add_impl_generic(parse_quote! {#decoder_ty: ::rustc_serialize::Decoder});
+ s.add_bounds(synstructure::AddBounds::Generics);
+
+ decodable_body(s, decoder_ty)
+}
+
+fn decodable_body(
+ s: synstructure::Structure<'_>,
+ decoder_ty: TokenStream,
+) -> proc_macro2::TokenStream {
+ if let syn::Data::Union(_) = s.ast().data {
+ panic!("cannot derive on union")
+ }
+ let ty_name = s.ast().ident.to_string();
+ let decode_body = match s.variants() {
+ [vi] => vi.construct(|field, _index| decode_field(field)),
+ variants => {
+ let match_inner: TokenStream = variants
+ .iter()
+ .enumerate()
+ .map(|(idx, vi)| {
+ let construct = vi.construct(|field, _index| decode_field(field));
+ quote! { #idx => { #construct } }
+ })
+ .collect();
+ let message = format!(
+ "invalid enum variant tag while decoding `{}`, expected 0..{}",
+ ty_name,
+ variants.len()
+ );
+ quote! {
+ match ::rustc_serialize::Decoder::read_usize(__decoder) {
+ #match_inner
+ _ => panic!(#message),
+ }
+ }
+ }
+ };
+
+ s.bound_impl(
+ quote!(::rustc_serialize::Decodable<#decoder_ty>),
+ quote! {
+ fn decode(__decoder: &mut #decoder_ty) -> Self {
+ #decode_body
+ }
+ },
+ )
+}
+
+fn decode_field(field: &syn::Field) -> proc_macro2::TokenStream {
+ let field_span = field.ident.as_ref().map_or(field.ty.span(), |ident| ident.span());
+
+ let decode_inner_method = if let syn::Type::Reference(_) = field.ty {
+ quote! { ::rustc_middle::ty::codec::RefDecodable::decode }
+ } else {
+ quote! { ::rustc_serialize::Decodable::decode }
+ };
+ let __decoder = quote! { __decoder };
+ // Use the span of the field for the method call, so
+ // that backtraces will point to the field.
+ quote_spanned! {field_span=> #decode_inner_method(#__decoder) }
+}
+
+pub fn type_encodable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+ if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") {
+ s.add_impl_generic(parse_quote! {'tcx});
+ }
+ let encoder_ty = quote! { __E };
+ s.add_impl_generic(parse_quote! {#encoder_ty: ::rustc_type_ir::codec::TyEncoder<I = ::rustc_middle::ty::TyCtxt<'tcx>>});
+ s.add_bounds(synstructure::AddBounds::Generics);
+
+ encodable_body(s, encoder_ty, false)
+}
+
+pub fn meta_encodable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+ if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") {
+ s.add_impl_generic(parse_quote! {'tcx});
+ }
+ s.add_impl_generic(parse_quote! { '__a });
+ let encoder_ty = quote! { EncodeContext<'__a, 'tcx> };
+ s.add_bounds(synstructure::AddBounds::Generics);
+
+ encodable_body(s, encoder_ty, true)
+}
+
+pub fn encodable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+ let encoder_ty = quote! { __E };
+ s.add_impl_generic(parse_quote! { #encoder_ty: ::rustc_serialize::Encoder});
+ s.add_bounds(synstructure::AddBounds::Generics);
+
+ encodable_body(s, encoder_ty, false)
+}
+
+fn encodable_body(
+ mut s: synstructure::Structure<'_>,
+ encoder_ty: TokenStream,
+ allow_unreachable_code: bool,
+) -> proc_macro2::TokenStream {
+ if let syn::Data::Union(_) = s.ast().data {
+ panic!("cannot derive on union")
+ }
+
+ s.bind_with(|binding| {
+ // Handle the lack of a blanket reference impl.
+ if let syn::Type::Reference(_) = binding.ast().ty {
+ synstructure::BindStyle::Move
+ } else {
+ synstructure::BindStyle::Ref
+ }
+ });
+
+ let encode_body = match s.variants() {
+ [_] => {
+ let encode_inner = s.each_variant(|vi| {
+ vi.bindings()
+ .iter()
+ .map(|binding| {
+ let bind_ident = &binding.binding;
+ let result = quote! {
+ ::rustc_serialize::Encodable::<#encoder_ty>::encode(
+ #bind_ident,
+ __encoder,
+ );
+ };
+ result
+ })
+ .collect::<TokenStream>()
+ });
+ quote! {
+ match *self { #encode_inner }
+ }
+ }
+ _ => {
+ let mut variant_idx = 0usize;
+ let encode_inner = s.each_variant(|vi| {
+ let encode_fields: TokenStream = vi
+ .bindings()
+ .iter()
+ .map(|binding| {
+ let bind_ident = &binding.binding;
+ let result = quote! {
+ ::rustc_serialize::Encodable::<#encoder_ty>::encode(
+ #bind_ident,
+ __encoder,
+ );
+ };
+ 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
+ });
+ quote! {
+ match *self {
+ #encode_inner
+ }
+ }
+ }
+ };
+
+ let lints = if allow_unreachable_code {
+ quote! { #![allow(unreachable_code)] }
+ } else {
+ quote! {}
+ };
+
+ s.bound_impl(
+ quote!(::rustc_serialize::Encodable<#encoder_ty>),
+ quote! {
+ fn encode(
+ &self,
+ __encoder: &mut #encoder_ty,
+ ) {
+ #lints
+ #encode_body
+ }
+ },
+ )
+}
diff --git a/compiler/rustc_macros/src/symbols.rs b/compiler/rustc_macros/src/symbols.rs
new file mode 100644
index 000000000..1b245f2a7
--- /dev/null
+++ b/compiler/rustc_macros/src/symbols.rs
@@ -0,0 +1,236 @@
+//! Proc macro which builds the Symbol table
+//!
+//! # Debugging
+//!
+//! Since this proc-macro does some non-trivial work, debugging it is important.
+//! This proc-macro can be invoked as an ordinary unit test, like so:
+//!
+//! ```bash
+//! cd compiler/rustc_macros
+//! cargo test symbols::test_symbols -- --nocapture
+//! ```
+//!
+//! This unit test finds the `symbols!` invocation in `compiler/rustc_span/src/symbol.rs`
+//! and runs it. It verifies that the output token stream can be parsed as valid module
+//! items and that no errors were produced.
+//!
+//! You can also view the generated code by using `cargo expand`:
+//!
+//! ```bash
+//! cargo install cargo-expand # this is necessary only once
+//! cd compiler/rustc_span
+//! cargo expand > /tmp/rustc_span.rs # it's a big file
+//! ```
+
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use std::collections::HashMap;
+use syn::parse::{Parse, ParseStream, Result};
+use syn::{braced, punctuated::Punctuated, Ident, LitStr, Token};
+
+#[cfg(test)]
+mod tests;
+
+mod kw {
+ syn::custom_keyword!(Keywords);
+ syn::custom_keyword!(Symbols);
+}
+
+struct Keyword {
+ name: Ident,
+ value: LitStr,
+}
+
+impl Parse for Keyword {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let name = input.parse()?;
+ input.parse::<Token![:]>()?;
+ let value = input.parse()?;
+
+ Ok(Keyword { name, value })
+ }
+}
+
+struct Symbol {
+ name: Ident,
+ value: Option<LitStr>,
+}
+
+impl Parse for Symbol {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let name = input.parse()?;
+ let value = match input.parse::<Token![:]>() {
+ Ok(_) => Some(input.parse()?),
+ Err(_) => None,
+ };
+
+ Ok(Symbol { name, value })
+ }
+}
+
+struct Input {
+ keywords: Punctuated<Keyword, Token![,]>,
+ symbols: Punctuated<Symbol, Token![,]>,
+}
+
+impl Parse for Input {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ input.parse::<kw::Keywords>()?;
+ let content;
+ braced!(content in input);
+ let keywords = Punctuated::parse_terminated(&content)?;
+
+ input.parse::<kw::Symbols>()?;
+ let content;
+ braced!(content in input);
+ let symbols = Punctuated::parse_terminated(&content)?;
+
+ Ok(Input { keywords, symbols })
+ }
+}
+
+#[derive(Default)]
+struct Errors {
+ list: Vec<syn::Error>,
+}
+
+impl Errors {
+ fn error(&mut self, span: Span, message: String) {
+ self.list.push(syn::Error::new(span, message));
+ }
+}
+
+pub fn symbols(input: TokenStream) -> TokenStream {
+ let (mut output, errors) = symbols_with_errors(input);
+
+ // If we generated any errors, then report them as compiler_error!() macro calls.
+ // This lets the errors point back to the most relevant span. It also allows us
+ // to report as many errors as we can during a single run.
+ output.extend(errors.into_iter().map(|e| e.to_compile_error()));
+
+ output
+}
+
+fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
+ let mut errors = Errors::default();
+
+ let input: Input = match syn::parse2(input) {
+ Ok(input) => input,
+ Err(e) => {
+ // This allows us to display errors at the proper span, while minimizing
+ // unrelated errors caused by bailing out (and not generating code).
+ errors.list.push(e);
+ Input { keywords: Default::default(), symbols: Default::default() }
+ }
+ };
+
+ let mut keyword_stream = quote! {};
+ let mut symbols_stream = quote! {};
+ let mut prefill_stream = quote! {};
+ let mut counter = 0u32;
+ let mut keys =
+ HashMap::<String, Span>::with_capacity(input.keywords.len() + input.symbols.len() + 10);
+ let mut prev_key: Option<(Span, String)> = None;
+
+ let mut check_dup = |span: Span, str: &str, errors: &mut Errors| {
+ if let Some(prev_span) = keys.get(str) {
+ errors.error(span, format!("Symbol `{}` is duplicated", str));
+ errors.error(*prev_span, "location of previous definition".to_string());
+ } else {
+ keys.insert(str.to_string(), span);
+ }
+ };
+
+ let mut check_order = |span: Span, str: &str, errors: &mut Errors| {
+ if let Some((prev_span, ref prev_str)) = prev_key {
+ if str < prev_str {
+ errors.error(span, format!("Symbol `{}` must precede `{}`", str, prev_str));
+ errors.error(prev_span, format!("location of previous symbol `{}`", prev_str));
+ }
+ }
+ prev_key = Some((span, str.to_string()));
+ };
+
+ // Generate the listed keywords.
+ for keyword in input.keywords.iter() {
+ let name = &keyword.name;
+ let value = &keyword.value;
+ let value_string = value.value();
+ check_dup(keyword.name.span(), &value_string, &mut errors);
+ prefill_stream.extend(quote! {
+ #value,
+ });
+ keyword_stream.extend(quote! {
+ pub const #name: Symbol = Symbol::new(#counter);
+ });
+ counter += 1;
+ }
+
+ // Generate the listed symbols.
+ for symbol in input.symbols.iter() {
+ let name = &symbol.name;
+ let value = match &symbol.value {
+ Some(value) => value.value(),
+ None => name.to_string(),
+ };
+ check_dup(symbol.name.span(), &value, &mut errors);
+ check_order(symbol.name.span(), &name.to_string(), &mut errors);
+
+ prefill_stream.extend(quote! {
+ #value,
+ });
+ symbols_stream.extend(quote! {
+ pub const #name: Symbol = Symbol::new(#counter);
+ });
+ counter += 1;
+ }
+
+ // Generate symbols for the strings "0", "1", ..., "9".
+ let digits_base = counter;
+ counter += 10;
+ for n in 0..10 {
+ let n = n.to_string();
+ check_dup(Span::call_site(), &n, &mut errors);
+ prefill_stream.extend(quote! {
+ #n,
+ });
+ }
+ let _ = counter; // for future use
+
+ let output = quote! {
+ const SYMBOL_DIGITS_BASE: u32 = #digits_base;
+
+ #[doc(hidden)]
+ #[allow(non_upper_case_globals)]
+ mod kw_generated {
+ use super::Symbol;
+ #keyword_stream
+ }
+
+ #[allow(non_upper_case_globals)]
+ #[doc(hidden)]
+ pub mod sym_generated {
+ use super::Symbol;
+ #symbols_stream
+ }
+
+ impl Interner {
+ pub(crate) fn fresh() -> Self {
+ Interner::prefill(&[
+ #prefill_stream
+ ])
+ }
+ }
+ };
+
+ (output, errors.list)
+
+ // To see the generated code, use the "cargo expand" command.
+ // Do this once to install:
+ // cargo install cargo-expand
+ //
+ // Then, cd to rustc_span and run:
+ // cargo expand > /tmp/rustc_span_expanded.rs
+ //
+ // and read that file.
+}
diff --git a/compiler/rustc_macros/src/symbols/tests.rs b/compiler/rustc_macros/src/symbols/tests.rs
new file mode 100644
index 000000000..842d2a977
--- /dev/null
+++ b/compiler/rustc_macros/src/symbols/tests.rs
@@ -0,0 +1,102 @@
+use super::*;
+
+// This test is mainly here for interactive development. Use this test while
+// you're working on the proc-macro defined in this file.
+#[test]
+fn test_symbols() {
+ // We textually include the symbol.rs file, which contains the list of all
+ // symbols, keywords, and common words. Then we search for the
+ // `symbols! { ... }` call.
+
+ static SYMBOL_RS_FILE: &str = include_str!("../../../rustc_span/src/symbol.rs");
+
+ let file = syn::parse_file(SYMBOL_RS_FILE).unwrap();
+ let symbols_path: syn::Path = syn::parse_quote!(symbols);
+
+ let m: &syn::ItemMacro = file
+ .items
+ .iter()
+ .filter_map(|i| {
+ if let syn::Item::Macro(m) = i {
+ if m.mac.path == symbols_path { Some(m) } else { None }
+ } else {
+ None
+ }
+ })
+ .next()
+ .expect("did not find `symbols!` macro invocation.");
+
+ let body_tokens = m.mac.tokens.clone();
+
+ test_symbols_macro(body_tokens, &[]);
+}
+
+fn test_symbols_macro(input: TokenStream, expected_errors: &[&str]) {
+ let (output, found_errors) = symbols_with_errors(input);
+
+ // It should always parse.
+ let _parsed_file = syn::parse2::<syn::File>(output).unwrap();
+
+ assert_eq!(
+ found_errors.len(),
+ expected_errors.len(),
+ "Macro generated a different number of errors than expected"
+ );
+
+ for (found_error, &expected_error) in found_errors.iter().zip(expected_errors) {
+ let found_error_str = format!("{}", found_error);
+ assert_eq!(found_error_str, expected_error);
+ }
+}
+
+#[test]
+fn check_dup_keywords() {
+ let input = quote! {
+ Keywords {
+ Crate: "crate",
+ Crate: "crate",
+ }
+ Symbols {}
+ };
+ test_symbols_macro(input, &["Symbol `crate` is duplicated", "location of previous definition"]);
+}
+
+#[test]
+fn check_dup_symbol() {
+ let input = quote! {
+ Keywords {}
+ Symbols {
+ splat,
+ splat,
+ }
+ };
+ test_symbols_macro(input, &["Symbol `splat` is duplicated", "location of previous definition"]);
+}
+
+#[test]
+fn check_dup_symbol_and_keyword() {
+ let input = quote! {
+ Keywords {
+ Splat: "splat",
+ }
+ Symbols {
+ splat,
+ }
+ };
+ test_symbols_macro(input, &["Symbol `splat` is duplicated", "location of previous definition"]);
+}
+
+#[test]
+fn check_symbol_order() {
+ let input = quote! {
+ Keywords {}
+ Symbols {
+ zebra,
+ aardvark,
+ }
+ };
+ test_symbols_macro(
+ input,
+ &["Symbol `aardvark` must precede `zebra`", "location of previous symbol `zebra`"],
+ );
+}
diff --git a/compiler/rustc_macros/src/type_foldable.rs b/compiler/rustc_macros/src/type_foldable.rs
new file mode 100644
index 000000000..23e619221
--- /dev/null
+++ b/compiler/rustc_macros/src/type_foldable.rs
@@ -0,0 +1,36 @@
+use quote::quote;
+use syn::parse_quote;
+
+pub fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+ if let syn::Data::Union(_) = s.ast().data {
+ panic!("cannot derive on union")
+ }
+
+ if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") {
+ s.add_impl_generic(parse_quote! { 'tcx });
+ }
+
+ s.add_bounds(synstructure::AddBounds::Generics);
+ s.bind_with(|_| synstructure::BindStyle::Move);
+ let body_fold = s.each_variant(|vi| {
+ let bindings = vi.bindings();
+ vi.construct(|_, index| {
+ let bind = &bindings[index];
+ quote! {
+ ::rustc_middle::ty::fold::TypeFoldable::try_fold_with(#bind, __folder)?
+ }
+ })
+ });
+
+ s.bound_impl(
+ quote!(::rustc_middle::ty::fold::TypeFoldable<'tcx>),
+ quote! {
+ fn try_fold_with<__F: ::rustc_middle::ty::fold::FallibleTypeFolder<'tcx>>(
+ self,
+ __folder: &mut __F
+ ) -> Result<Self, __F::Error> {
+ Ok(match self { #body_fold })
+ }
+ },
+ )
+}
diff --git a/compiler/rustc_macros/src/type_visitable.rs b/compiler/rustc_macros/src/type_visitable.rs
new file mode 100644
index 000000000..14e6aa6e0
--- /dev/null
+++ b/compiler/rustc_macros/src/type_visitable.rs
@@ -0,0 +1,33 @@
+use quote::quote;
+use syn::parse_quote;
+
+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")
+ }
+
+ if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") {
+ s.add_impl_generic(parse_quote! { 'tcx });
+ }
+
+ s.add_bounds(synstructure::AddBounds::Generics);
+ let body_visit = s.each(|bind| {
+ quote! {
+ ::rustc_middle::ty::visit::TypeVisitable::visit_with(#bind, __visitor)?;
+ }
+ });
+ s.bind_with(|_| synstructure::BindStyle::Move);
+
+ s.bound_impl(
+ quote!(::rustc_middle::ty::visit::TypeVisitable<'tcx>),
+ quote! {
+ fn visit_with<__V: ::rustc_middle::ty::visit::TypeVisitor<'tcx>>(
+ &self,
+ __visitor: &mut __V
+ ) -> ::std::ops::ControlFlow<__V::BreakTy> {
+ match *self { #body_visit }
+ ::std::ops::ControlFlow::CONTINUE
+ }
+ },
+ )
+}