diff options
Diffstat (limited to 'compiler/rustc_macros')
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 + } + }, + ) +} |