summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_expand
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_expand')
-rw-r--r--compiler/rustc_expand/Cargo.toml15
-rw-r--r--compiler/rustc_expand/src/base.rs36
-rw-r--r--compiler/rustc_expand/src/build.rs50
-rw-r--r--compiler/rustc_expand/src/config.rs2
-rw-r--r--compiler/rustc_expand/src/expand.rs42
-rw-r--r--compiler/rustc_expand/src/mbe.rs3
-rw-r--r--compiler/rustc_expand/src/mbe/diagnostics.rs257
-rw-r--r--compiler/rustc_expand/src/mbe/macro_parser.rs99
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs407
-rw-r--r--compiler/rustc_expand/src/parse/tests.rs2
-rw-r--r--compiler/rustc_expand/src/placeholders.rs14
-rw-r--r--compiler/rustc_expand/src/proc_macro.rs1
-rw-r--r--compiler/rustc_expand/src/proc_macro_server.rs21
-rw-r--r--compiler/rustc_expand/src/tests.rs109
14 files changed, 676 insertions, 382 deletions
diff --git a/compiler/rustc_expand/Cargo.toml b/compiler/rustc_expand/Cargo.toml
index 4ee7b6c42..192f54171 100644
--- a/compiler/rustc_expand/Cargo.toml
+++ b/compiler/rustc_expand/Cargo.toml
@@ -8,20 +8,21 @@ build = false
doctest = false
[dependencies]
-rustc_serialize = { path = "../rustc_serialize" }
-tracing = "0.1"
-rustc_span = { path = "../rustc_span" }
-rustc_ast_pretty = { path = "../rustc_ast_pretty" }
+crossbeam-channel = "0.5.0"
rustc_ast_passes = { path = "../rustc_ast_passes" }
+rustc_ast = { path = "../rustc_ast" }
+rustc_ast_pretty = { path = "../rustc_ast_pretty" }
rustc_attr = { path = "../rustc_attr" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }
rustc_feature = { path = "../rustc_feature" }
+rustc_lexer = { path = "../rustc_lexer" }
rustc_lint_defs = { path = "../rustc_lint_defs" }
rustc_macros = { path = "../rustc_macros" }
-rustc_lexer = { path = "../rustc_lexer" }
rustc_parse = { path = "../rustc_parse" }
+rustc_serialize = { path = "../rustc_serialize" }
rustc_session = { path = "../rustc_session" }
+rustc_span = { path = "../rustc_span" }
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
-rustc_ast = { path = "../rustc_ast" }
-crossbeam-channel = "0.5.0"
+thin-vec = "0.2.8"
+tracing = "0.1"
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index c8de60ccb..9d6a4f9a1 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -16,6 +16,7 @@ use rustc_errors::{
use rustc_lint_defs::builtin::PROC_MACRO_BACK_COMPAT;
use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics};
use rustc_parse::{self, parser, MACRO_ARGUMENTS};
+use rustc_session::errors::report_lit_error;
use rustc_session::{parse::ParseSess, Limit, Session};
use rustc_span::def_id::{CrateNum, DefId, LocalDefId};
use rustc_span::edition::Edition;
@@ -242,14 +243,15 @@ pub enum ExpandResult<T, U> {
Retry(U),
}
-// `meta_item` is the attribute, and `item` is the item being modified.
pub trait MultiItemModifier {
+ /// `meta_item` is the attribute, and `item` is the item being modified.
fn expand(
&self,
ecx: &mut ExtCtxt<'_>,
span: Span,
meta_item: &ast::MetaItem,
item: Annotatable,
+ is_derive_const: bool,
) -> ExpandResult<Vec<Annotatable>, Annotatable>;
}
@@ -263,6 +265,7 @@ where
span: Span,
meta_item: &ast::MetaItem,
item: Annotatable,
+ _is_derive_const: bool,
) -> ExpandResult<Vec<Annotatable>, Annotatable> {
ExpandResult::Ready(self(ecx, span, meta_item, item))
}
@@ -505,7 +508,7 @@ impl MacResult for MacEager {
return Some(p);
}
if let Some(e) = self.expr {
- if let ast::ExprKind::Lit(_) = e.kind {
+ if matches!(e.kind, ast::ExprKind::Lit(_) | ast::ExprKind::IncludedBytes(_)) {
return Some(P(ast::Pat {
id: ast::DUMMY_NODE_ID,
span: e.span,
@@ -674,8 +677,13 @@ pub enum SyntaxExtensionKind {
/// A token-based derive macro.
Derive(
- /// An expander with signature TokenStream -> TokenStream (not yet).
+ /// An expander with signature TokenStream -> TokenStream.
/// The produced TokenSteam is appended to the input TokenSteam.
+ ///
+ /// FIXME: The text above describes how this should work. Currently it
+ /// is handled identically to `LegacyDerive`. It should be migrated to
+ /// a token-based representation like `Bang` and `Attr`, instead of
+ /// using `MultiItemModifier`.
Box<dyn MultiItemModifier + sync::Sync + sync::Send>,
),
@@ -873,7 +881,7 @@ impl SyntaxExtension {
/// Error type that denotes indeterminacy.
pub struct Indeterminate;
-pub type DeriveResolutions = Vec<(ast::Path, Annotatable, Option<Lrc<SyntaxExtension>>)>;
+pub type DeriveResolutions = Vec<(ast::Path, Annotatable, Option<Lrc<SyntaxExtension>>, bool)>;
pub trait ResolverExpand {
fn next_node_id(&mut self) -> NodeId;
@@ -952,7 +960,7 @@ pub trait LintStoreExpand {
node_id: NodeId,
attrs: &[Attribute],
items: &[P<Item>],
- name: &str,
+ name: Symbol,
);
}
@@ -1224,10 +1232,10 @@ pub fn expr_to_spanned_string<'a>(
let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
Err(match expr.kind {
- ast::ExprKind::Lit(ref l) => match l.kind {
- ast::LitKind::Str(s, style) => return Ok((s, style, expr.span)),
- ast::LitKind::ByteStr(_) => {
- let mut err = cx.struct_span_err(l.span, err_msg);
+ ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
+ Ok(ast::LitKind::Str(s, style)) => return Ok((s, style, expr.span)),
+ Ok(ast::LitKind::ByteStr(_)) => {
+ let mut err = cx.struct_span_err(expr.span, err_msg);
let span = expr.span.shrink_to_lo();
err.span_suggestion(
span.with_hi(span.lo() + BytePos(1)),
@@ -1237,8 +1245,12 @@ pub fn expr_to_spanned_string<'a>(
);
Some((err, true))
}
- ast::LitKind::Err => None,
- _ => Some((cx.struct_span_err(l.span, err_msg), false)),
+ Ok(ast::LitKind::Err) => None,
+ Err(err) => {
+ report_lit_error(&cx.sess.parse_sess, err, token_lit, expr.span);
+ None
+ }
+ _ => Some((cx.struct_span_err(expr.span, err_msg), false)),
},
ast::ExprKind::Err => None,
_ => Some((cx.struct_span_err(expr.span, err_msg), false)),
@@ -1433,7 +1445,7 @@ fn pretty_printing_compatibility_hack(item: &Item, sess: &ParseSess) -> bool {
let crate_matches = if c.starts_with("allsorts-rental") {
true
} else {
- let mut version = c.trim_start_matches("rental-").split(".");
+ let mut version = c.trim_start_matches("rental-").split('.');
version.next() == Some("0")
&& version.next() == Some("5")
&& version
diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs
index 0952e65cf..4812bdd9d 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -1,13 +1,12 @@
use crate::base::ExtCtxt;
-
use rustc_ast::attr;
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, AttrVec, BlockCheckMode, Expr, LocalKind, PatKind, UnOp};
use rustc_data_structures::sync::Lrc;
use rustc_span::source_map::Spanned;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
-
use rustc_span::Span;
+use thin_vec::ThinVec;
impl<'a> ExtCtxt<'a> {
pub fn path(&self, span: Span, strs: Vec<Ident>) -> ast::Path {
@@ -28,7 +27,7 @@ impl<'a> ExtCtxt<'a> {
) -> ast::Path {
assert!(!idents.is_empty());
let add_root = global && !idents[0].is_path_segment_keyword();
- let mut segments = Vec::with_capacity(idents.len() + add_root as usize);
+ let mut segments = ThinVec::with_capacity(idents.len() + add_root as usize);
if add_root {
segments.push(ast::PathSegment::path_root(span));
}
@@ -194,7 +193,7 @@ impl<'a> ExtCtxt<'a> {
self.stmt_local(local, sp)
}
- // Generates `let _: Type;`, which is usually used for type assertions.
+ /// Generates `let _: Type;`, which is usually used for type assertions.
pub fn stmt_let_type_only(&self, span: Span, ty: P<ast::Ty>) -> ast::Stmt {
let local = P(ast::Local {
pat: self.pat_wild(span),
@@ -334,8 +333,8 @@ impl<'a> ExtCtxt<'a> {
}
fn expr_lit(&self, span: Span, lit_kind: ast::LitKind) -> P<ast::Expr> {
- let lit = ast::Lit::from_lit_kind(lit_kind, span);
- self.expr(span, ast::ExprKind::Lit(lit))
+ let token_lit = lit_kind.to_token_lit();
+ self.expr(span, ast::ExprKind::Lit(token_lit))
}
pub fn expr_usize(&self, span: Span, i: usize) -> P<ast::Expr> {
@@ -532,15 +531,18 @@ impl<'a> ExtCtxt<'a> {
// here, but that's not entirely clear.
self.expr(
span,
- ast::ExprKind::Closure(
- ast::ClosureBinder::NotPresent,
- ast::CaptureBy::Ref,
- ast::Async::No,
- ast::Movability::Movable,
+ ast::ExprKind::Closure(Box::new(ast::Closure {
+ binder: ast::ClosureBinder::NotPresent,
+ capture_clause: ast::CaptureBy::Ref,
+ asyncness: ast::Async::No,
+ movability: ast::Movability::Movable,
fn_decl,
body,
- span,
- ),
+ fn_decl_span: span,
+ // FIXME(SarthakSingh31): This points to the start of the declaration block and
+ // not the span of the argument block.
+ fn_arg_span: span,
+ })),
)
}
@@ -580,8 +582,6 @@ impl<'a> ExtCtxt<'a> {
attrs: ast::AttrVec,
kind: ast::ItemKind,
) -> P<ast::Item> {
- // FIXME: Would be nice if our generated code didn't violate
- // Rust coding conventions
P(ast::Item {
ident: name,
attrs,
@@ -619,11 +619,23 @@ impl<'a> ExtCtxt<'a> {
self.item(span, name, AttrVec::new(), ast::ItemKind::Const(def, ty, Some(expr)))
}
- pub fn attribute(&self, mi: ast::MetaItem) -> ast::Attribute {
- attr::mk_attr_outer(&self.sess.parse_sess.attr_id_generator, mi)
+ // Builds `#[name]`.
+ pub fn attr_word(&self, name: Symbol, span: Span) -> ast::Attribute {
+ let g = &self.sess.parse_sess.attr_id_generator;
+ attr::mk_attr_word(g, ast::AttrStyle::Outer, name, span)
+ }
+
+ // Builds `#[name = val]`.
+ //
+ // Note: `span` is used for both the identifer and the value.
+ pub fn attr_name_value_str(&self, name: Symbol, val: Symbol, span: Span) -> ast::Attribute {
+ let g = &self.sess.parse_sess.attr_id_generator;
+ attr::mk_attr_name_value_str(g, ast::AttrStyle::Outer, name, val, span)
}
- pub fn meta_word(&self, sp: Span, w: Symbol) -> ast::MetaItem {
- attr::mk_word_item(Ident::new(w, sp))
+ // Builds `#[outer(inner)]`.
+ pub fn attr_nested_word(&self, outer: Symbol, inner: Symbol, span: Span) -> ast::Attribute {
+ let g = &self.sess.parse_sess.attr_id_generator;
+ attr::mk_attr_nested_word(g, ast::AttrStyle::Outer, outer, inner, span)
}
}
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index 1d2b1298a..2510795c2 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -200,7 +200,7 @@ fn get_features(
features
}
-// `cfg_attr`-process the crate's attributes and compute the crate's features.
+/// `cfg_attr`-process the crate's attributes and compute the crate's features.
pub fn features(
sess: &Session,
mut krate: ast::Crate,
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 57713fb3c..1014ec220 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -1,7 +1,7 @@
use crate::base::*;
use crate::config::StripUnconfigured;
use crate::hygiene::SyntaxContext;
-use crate::mbe::macro_rules::annotate_err_with_kind;
+use crate::mbe::diagnostics::annotate_err_with_kind;
use crate::module::{mod_dir_path, parse_external_mod, DirOwnership, ParsedExternalMod};
use crate::placeholders::{placeholder, PlaceholderExpander};
@@ -11,9 +11,9 @@ use rustc_ast::ptr::P;
use rustc_ast::token::{self, Delimiter};
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::visit::{self, AssocCtxt, Visitor};
-use rustc_ast::{AssocItemKind, AstNodeWrapper, AttrStyle, AttrVec, ExprKind, ForeignItemKind};
-use rustc_ast::{HasAttrs, HasNodeId};
-use rustc_ast::{Inline, ItemKind, MacArgs, MacStmtStyle, MetaItemKind, ModKind};
+use rustc_ast::{AssocItemKind, AstNodeWrapper, AttrArgs, AttrStyle, AttrVec, ExprKind};
+use rustc_ast::{ForeignItemKind, HasAttrs, HasNodeId};
+use rustc_ast::{Inline, ItemKind, MacStmtStyle, MetaItemKind, ModKind};
use rustc_ast::{NestedMetaItem, NodeId, PatKind, StmtKind, TyKind};
use rustc_ast_pretty::pprust;
use rustc_data_structures::map_in_place::MapInPlace;
@@ -337,6 +337,7 @@ pub enum InvocationKind {
},
Derive {
path: ast::Path,
+ is_const: bool,
item: Annotatable,
},
}
@@ -400,7 +401,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
krate
}
- // Recursively expand all macro invocations in this AST fragment.
+ /// Recursively expand all macro invocations in this AST fragment.
pub fn fully_expand_fragment(&mut self, input_fragment: AstFragment) -> AstFragment {
let orig_expansion_data = self.cx.current_expansion.clone();
let orig_force_mode = self.cx.force_mode;
@@ -478,13 +479,13 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
derive_invocations.reserve(derives.len());
derives
.into_iter()
- .map(|(path, item, _exts)| {
+ .map(|(path, item, _exts, is_const)| {
// FIXME: Consider using the derive resolutions (`_exts`)
// instead of enqueuing the derives to be resolved again later.
let expn_id = LocalExpnId::fresh_empty();
derive_invocations.push((
Invocation {
- kind: InvocationKind::Derive { path, item },
+ kind: InvocationKind::Derive { path, item, is_const },
fragment_kind,
expansion_data: ExpansionData {
id: expn_id,
@@ -653,7 +654,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
ExpandResult::Ready(match invoc.kind {
InvocationKind::Bang { mac, .. } => match ext {
SyntaxExtensionKind::Bang(expander) => {
- let Ok(tok_result) = expander.expand(self.cx, span, mac.args.inner_tokens()) else {
+ let Ok(tok_result) = expander.expand(self.cx, span, mac.args.tokens.clone()) else {
return ExpandResult::Ready(fragment_kind.dummy(span));
};
self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span)
@@ -661,7 +662,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
SyntaxExtensionKind::LegacyBang(expander) => {
let prev = self.cx.current_expansion.prior_type_ascription;
self.cx.current_expansion.prior_type_ascription = mac.prior_type_ascription;
- let tok_result = expander.expand(self.cx, span, mac.args.inner_tokens());
+ let tok_result = expander.expand(self.cx, span, mac.args.tokens.clone());
let result = if let Some(result) = fragment_kind.make_from(tok_result) {
result
} else {
@@ -705,7 +706,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
_ => item.to_tokens(),
};
let attr_item = attr.unwrap_normal_item();
- if let MacArgs::Eq(..) = attr_item.args {
+ if let AttrArgs::Eq(..) = attr_item.args {
self.cx.span_err(span, "key-value macro attributes are not supported");
}
let inner_tokens = attr_item.args.inner_tokens();
@@ -717,7 +718,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
SyntaxExtensionKind::LegacyAttr(expander) => {
match validate_attr::parse_meta(&self.cx.sess.parse_sess, &attr) {
Ok(meta) => {
- let items = match expander.expand(self.cx, span, &meta, item) {
+ let items = match expander.expand(self.cx, span, &meta, item, false) {
ExpandResult::Ready(items) => items,
ExpandResult::Retry(item) => {
// Reassemble the original invocation for retrying.
@@ -749,19 +750,19 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
}
_ => unreachable!(),
},
- InvocationKind::Derive { path, item } => match ext {
+ InvocationKind::Derive { path, item, is_const } => match ext {
SyntaxExtensionKind::Derive(expander)
| SyntaxExtensionKind::LegacyDerive(expander) => {
if let SyntaxExtensionKind::Derive(..) = ext {
self.gate_proc_macro_input(&item);
}
let meta = ast::MetaItem { kind: MetaItemKind::Word, span, path };
- let items = match expander.expand(self.cx, span, &meta, item) {
+ let items = match expander.expand(self.cx, span, &meta, item, is_const) {
ExpandResult::Ready(items) => items,
ExpandResult::Retry(item) => {
// Reassemble the original invocation for retrying.
return ExpandResult::Retry(Invocation {
- kind: InvocationKind::Derive { path: meta.path, item },
+ kind: InvocationKind::Derive { path: meta.path, item, is_const },
..invoc
});
}
@@ -1121,7 +1122,7 @@ impl InvocationCollectorNode for P<ast::Item> {
ecx.current_expansion.lint_node_id,
&attrs,
&items,
- ident.name.as_str(),
+ ident.name,
);
}
@@ -1643,7 +1644,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
let mut span: Option<Span> = None;
while let Some(attr) = attrs.next() {
rustc_ast_passes::feature_gate::check_attribute(attr, self.cx.sess, features);
- validate_attr::check_meta(&self.cx.sess.parse_sess, attr);
+ validate_attr::check_attr(&self.cx.sess.parse_sess, attr);
let current_span = if let Some(sp) = span { sp.to(attr.span) } else { attr.span };
span = Some(current_span);
@@ -1930,9 +1931,12 @@ pub struct ExpansionConfig<'feat> {
pub features: Option<&'feat Features>,
pub recursion_limit: Limit,
pub trace_mac: bool,
- pub should_test: bool, // If false, strip `#[test]` nodes
- pub span_debug: bool, // If true, use verbose debugging for `proc_macro::Span`
- pub proc_macro_backtrace: bool, // If true, show backtraces for proc-macro panics
+ /// If false, strip `#[test]` nodes
+ pub should_test: bool,
+ /// If true, use verbose debugging for `proc_macro::Span`
+ pub span_debug: bool,
+ /// If true, show backtraces for proc-macro panics
+ pub proc_macro_backtrace: bool,
}
impl<'feat> ExpansionConfig<'feat> {
diff --git a/compiler/rustc_expand/src/mbe.rs b/compiler/rustc_expand/src/mbe.rs
index f42576b16..a43b2a001 100644
--- a/compiler/rustc_expand/src/mbe.rs
+++ b/compiler/rustc_expand/src/mbe.rs
@@ -3,6 +3,7 @@
//! why we call this module `mbe`. For external documentation, prefer the
//! official terminology: "declarative macros".
+pub(crate) mod diagnostics;
pub(crate) mod macro_check;
pub(crate) mod macro_parser;
pub(crate) mod macro_rules;
@@ -52,7 +53,7 @@ impl KleeneToken {
/// A Kleene-style [repetition operator](https://en.wikipedia.org/wiki/Kleene_star)
/// for token sequences.
#[derive(Clone, PartialEq, Encodable, Decodable, Debug, Copy)]
-enum KleeneOp {
+pub(crate) enum KleeneOp {
/// Kleene star (`*`) for zero or more repetitions
ZeroOrMore,
/// Kleene plus (`+`) for one or more repetitions
diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs
new file mode 100644
index 000000000..197f05691
--- /dev/null
+++ b/compiler/rustc_expand/src/mbe/diagnostics.rs
@@ -0,0 +1,257 @@
+use std::borrow::Cow;
+
+use crate::base::{DummyResult, ExtCtxt, MacResult};
+use crate::expand::{parse_ast_fragment, AstFragmentKind};
+use crate::mbe::{
+ macro_parser::{MatcherLoc, NamedParseResult, ParseResult::*, TtParser},
+ macro_rules::{try_match_macro, Tracker},
+};
+use rustc_ast::token::{self, Token};
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast_pretty::pprust;
+use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage};
+use rustc_parse::parser::{Parser, Recovery};
+use rustc_span::source_map::SourceMap;
+use rustc_span::symbol::Ident;
+use rustc_span::Span;
+
+use super::macro_rules::{parser_from_cx, NoopTracker};
+
+pub(super) fn failed_to_match_macro<'cx>(
+ cx: &'cx mut ExtCtxt<'_>,
+ sp: Span,
+ def_span: Span,
+ name: Ident,
+ arg: TokenStream,
+ lhses: &[Vec<MatcherLoc>],
+) -> Box<dyn MacResult + 'cx> {
+ let sess = &cx.sess.parse_sess;
+
+ // An error occurred, try the expansion again, tracking the expansion closely for better diagnostics.
+ let mut tracker = CollectTrackerAndEmitter::new(cx, sp);
+
+ let try_success_result = try_match_macro(sess, name, &arg, lhses, &mut tracker);
+
+ if try_success_result.is_ok() {
+ // Nonterminal parser recovery might turn failed matches into successful ones,
+ // but for that it must have emitted an error already
+ tracker.cx.sess.delay_span_bug(sp, "Macro matching returned a success on the second try");
+ }
+
+ if let Some(result) = tracker.result {
+ // An irrecoverable error occurred and has been emitted.
+ return result;
+ }
+
+ let Some((token, label, remaining_matcher)) = tracker.best_failure else {
+ return DummyResult::any(sp);
+ };
+
+ let span = token.span.substitute_dummy(sp);
+
+ let mut err = cx.struct_span_err(span, &parse_failure_msg(&token));
+ err.span_label(span, label);
+ if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) {
+ err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro");
+ }
+
+ annotate_doc_comment(&mut err, sess.source_map(), span);
+
+ if let Some(span) = remaining_matcher.span() {
+ err.span_note(span, format!("while trying to match {remaining_matcher}"));
+ } else {
+ err.note(format!("while trying to match {remaining_matcher}"));
+ }
+
+ // Check whether there's a missing comma in this macro call, like `println!("{}" a);`
+ if let Some((arg, comma_span)) = arg.add_comma() {
+ for lhs in lhses {
+ let parser = parser_from_cx(sess, arg.clone(), Recovery::Allowed);
+ let mut tt_parser = TtParser::new(name);
+
+ if let Success(_) =
+ tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
+ {
+ if comma_span.is_dummy() {
+ err.note("you might be missing a comma");
+ } else {
+ err.span_suggestion_short(
+ comma_span,
+ "missing comma here",
+ ", ",
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+ err.emit();
+ cx.trace_macros_diag();
+ DummyResult::any(sp)
+}
+
+/// The tracker used for the slow error path that collects useful info for diagnostics.
+struct CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
+ cx: &'a mut ExtCtxt<'cx>,
+ remaining_matcher: Option<&'matcher MatcherLoc>,
+ /// Which arm's failure should we report? (the one furthest along)
+ best_failure: Option<(Token, &'static str, MatcherLoc)>,
+ root_span: Span,
+ result: Option<Box<dyn MacResult + 'cx>>,
+}
+
+impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
+ fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
+ if self.remaining_matcher.is_none()
+ || (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
+ {
+ self.remaining_matcher = Some(matcher);
+ }
+ }
+
+ fn after_arm(&mut self, result: &NamedParseResult) {
+ match result {
+ Success(_) => {
+ // Nonterminal parser recovery might turn failed matches into successful ones,
+ // but for that it must have emitted an error already
+ self.cx.sess.delay_span_bug(
+ self.root_span,
+ "should not collect detailed info for successful macro match",
+ );
+ }
+ Failure(token, msg) => match self.best_failure {
+ Some((ref best_token, _, _)) if best_token.span.lo() >= token.span.lo() => {}
+ _ => {
+ self.best_failure = Some((
+ token.clone(),
+ msg,
+ self.remaining_matcher
+ .expect("must have collected matcher already")
+ .clone(),
+ ))
+ }
+ },
+ Error(err_sp, msg) => {
+ let span = err_sp.substitute_dummy(self.root_span);
+ self.cx.struct_span_err(span, msg).emit();
+ self.result = Some(DummyResult::any(span));
+ }
+ ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)),
+ }
+ }
+
+ fn description() -> &'static str {
+ "detailed"
+ }
+
+ fn recovery() -> Recovery {
+ Recovery::Allowed
+ }
+}
+
+impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
+ fn new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self {
+ Self { cx, remaining_matcher: None, best_failure: None, root_span, result: None }
+ }
+}
+
+pub(super) fn emit_frag_parse_err(
+ mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>,
+ parser: &Parser<'_>,
+ orig_parser: &mut Parser<'_>,
+ site_span: Span,
+ arm_span: Span,
+ kind: AstFragmentKind,
+) {
+ // FIXME(davidtwco): avoid depending on the error message text
+ if parser.token == token::Eof
+ && let DiagnosticMessage::Str(message) = &e.message[0].0
+ && message.ends_with(", found `<eof>`")
+ {
+ let msg = &e.message[0];
+ e.message[0] = (
+ DiagnosticMessage::Str(format!(
+ "macro expansion ends with an incomplete expression: {}",
+ message.replace(", found `<eof>`", ""),
+ )),
+ msg.1,
+ );
+ if !e.span.is_dummy() {
+ // early end of macro arm (#52866)
+ e.replace_span_with(parser.token.span.shrink_to_hi());
+ }
+ }
+ if e.span.is_dummy() {
+ // Get around lack of span in error (#30128)
+ e.replace_span_with(site_span);
+ if !parser.sess.source_map().is_imported(arm_span) {
+ e.span_label(arm_span, "in this macro arm");
+ }
+ } else if parser.sess.source_map().is_imported(parser.token.span) {
+ e.span_label(site_span, "in this macro invocation");
+ }
+ match kind {
+ // Try a statement if an expression is wanted but failed and suggest adding `;` to call.
+ AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {
+ Err(err) => err.cancel(),
+ Ok(_) => {
+ e.note(
+ "the macro call doesn't expand to an expression, but it can expand to a statement",
+ );
+ e.span_suggestion_verbose(
+ site_span.shrink_to_hi(),
+ "add `;` to interpret the expansion as a statement",
+ ";",
+ Applicability::MaybeIncorrect,
+ );
+ }
+ },
+ _ => annotate_err_with_kind(&mut e, kind, site_span),
+ };
+ e.emit();
+}
+
+pub(crate) fn annotate_err_with_kind(err: &mut Diagnostic, kind: AstFragmentKind, span: Span) {
+ match kind {
+ AstFragmentKind::Ty => {
+ err.span_label(span, "this macro call doesn't expand to a type");
+ }
+ AstFragmentKind::Pat => {
+ err.span_label(span, "this macro call doesn't expand to a pattern");
+ }
+ _ => {}
+ };
+}
+
+#[derive(Subdiagnostic)]
+enum ExplainDocComment {
+ #[label(expand_explain_doc_comment_inner)]
+ Inner {
+ #[primary_span]
+ span: Span,
+ },
+ #[label(expand_explain_doc_comment_outer)]
+ Outer {
+ #[primary_span]
+ span: Span,
+ },
+}
+
+pub(super) fn annotate_doc_comment(err: &mut Diagnostic, sm: &SourceMap, span: Span) {
+ if let Ok(src) = sm.span_to_snippet(span) {
+ if src.starts_with("///") || src.starts_with("/**") {
+ err.subdiagnostic(ExplainDocComment::Outer { span });
+ } else if src.starts_with("//!") || src.starts_with("/*!") {
+ err.subdiagnostic(ExplainDocComment::Inner { span });
+ }
+ }
+}
+
+/// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For
+/// other tokens, this is "unexpected token...".
+pub(super) fn parse_failure_msg(tok: &Token) -> String {
+ match tok.kind {
+ token::Eof => "unexpected end of macro invocation".to_string(),
+ _ => format!("no rules expected the token `{}`", pprust::token_to_string(tok),),
+ }
+}
diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs
index c8bdc3931..d161868ed 100644
--- a/compiler/rustc_expand/src/mbe/macro_parser.rs
+++ b/compiler/rustc_expand/src/mbe/macro_parser.rs
@@ -73,19 +73,21 @@
pub(crate) use NamedMatch::*;
pub(crate) use ParseResult::*;
-use crate::mbe::{KleeneOp, TokenTree};
+use crate::mbe::{macro_rules::Tracker, KleeneOp, TokenTree};
use rustc_ast::token::{self, DocComment, Nonterminal, NonterminalKind, Token};
+use rustc_ast_pretty::pprust;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::sync::Lrc;
+use rustc_errors::ErrorGuaranteed;
use rustc_lint_defs::pluralize;
use rustc_parse::parser::{NtOrTt, Parser};
+use rustc_span::symbol::Ident;
use rustc_span::symbol::MacroRulesNormalizedIdent;
use rustc_span::Span;
-
-use rustc_data_structures::fx::FxHashMap;
-use rustc_data_structures::sync::Lrc;
-use rustc_span::symbol::Ident;
use std::borrow::Cow;
use std::collections::hash_map::Entry::{Occupied, Vacant};
+use std::fmt::Display;
/// A unit within a matcher that a `MatcherPos` can refer to. Similar to (and derived from)
/// `mbe::TokenTree`, but designed specifically for fast and easy traversal during matching.
@@ -96,7 +98,8 @@ use std::collections::hash_map::Entry::{Occupied, Vacant};
///
/// This means a matcher can be represented by `&[MatcherLoc]`, and traversal mostly involves
/// simply incrementing the current matcher position index by one.
-pub(super) enum MatcherLoc {
+#[derive(Debug, PartialEq, Clone)]
+pub(crate) enum MatcherLoc {
Token {
token: Token,
},
@@ -128,6 +131,46 @@ pub(super) enum MatcherLoc {
Eof,
}
+impl MatcherLoc {
+ pub(super) fn span(&self) -> Option<Span> {
+ match self {
+ MatcherLoc::Token { token } => Some(token.span),
+ MatcherLoc::Delimited => None,
+ MatcherLoc::Sequence { .. } => None,
+ MatcherLoc::SequenceKleeneOpNoSep { .. } => None,
+ MatcherLoc::SequenceSep { .. } => None,
+ MatcherLoc::SequenceKleeneOpAfterSep { .. } => None,
+ MatcherLoc::MetaVarDecl { span, .. } => Some(*span),
+ MatcherLoc::Eof => None,
+ }
+ }
+}
+
+impl Display for MatcherLoc {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ MatcherLoc::Token { token } | MatcherLoc::SequenceSep { separator: token } => {
+ write!(f, "`{}`", pprust::token_to_string(token))
+ }
+ MatcherLoc::MetaVarDecl { bind, kind, .. } => {
+ write!(f, "meta-variable `${bind}")?;
+ if let Some(kind) = kind {
+ write!(f, ":{}", kind)?;
+ }
+ write!(f, "`")?;
+ Ok(())
+ }
+ MatcherLoc::Eof => f.write_str("end of macro"),
+
+ // These are not printed in the diagnostic
+ MatcherLoc::Delimited => f.write_str("delimiter"),
+ MatcherLoc::Sequence { .. } => f.write_str("sequence start"),
+ MatcherLoc::SequenceKleeneOpNoSep { .. } => f.write_str("sequence end"),
+ MatcherLoc::SequenceKleeneOpAfterSep { .. } => f.write_str("sequence end"),
+ }
+ }
+}
+
pub(super) fn compute_locs(matcher: &[TokenTree]) -> Vec<MatcherLoc> {
fn inner(
tts: &[TokenTree],
@@ -270,13 +313,17 @@ pub(crate) enum ParseResult<T> {
Failure(Token, &'static str),
/// Fatal error (malformed macro?). Abort compilation.
Error(rustc_span::Span, String),
- ErrorReported,
+ ErrorReported(ErrorGuaranteed),
}
/// A `ParseResult` where the `Success` variant contains a mapping of
/// `MacroRulesNormalizedIdent`s to `NamedMatch`es. This represents the mapping
/// of metavars to the token trees they bind to.
-pub(crate) type NamedParseResult = ParseResult<FxHashMap<MacroRulesNormalizedIdent, NamedMatch>>;
+pub(crate) type NamedParseResult = ParseResult<NamedMatches>;
+
+/// Contains a mapping of `MacroRulesNormalizedIdent`s to `NamedMatch`es.
+/// This represents the mapping of metavars to the token trees they bind to.
+pub(crate) type NamedMatches = FxHashMap<MacroRulesNormalizedIdent, NamedMatch>;
/// Count how many metavars declarations are in `matcher`.
pub(super) fn count_metavar_decls(matcher: &[TokenTree]) -> usize {
@@ -393,6 +440,10 @@ impl TtParser {
}
}
+ pub(super) fn has_no_remaining_items_for_step(&self) -> bool {
+ self.cur_mps.is_empty()
+ }
+
/// Process the matcher positions of `cur_mps` until it is empty. In the process, this will
/// produce more mps in `next_mps` and `bb_mps`.
///
@@ -400,17 +451,21 @@ impl TtParser {
///
/// `Some(result)` if everything is finished, `None` otherwise. Note that matches are kept
/// track of through the mps generated.
- fn parse_tt_inner(
+ fn parse_tt_inner<'matcher, T: Tracker<'matcher>>(
&mut self,
- matcher: &[MatcherLoc],
+ matcher: &'matcher [MatcherLoc],
token: &Token,
+ track: &mut T,
) -> Option<NamedParseResult> {
// Matcher positions that would be valid if the macro invocation was over now. Only
// modified if `token == Eof`.
let mut eof_mps = EofMatcherPositions::None;
while let Some(mut mp) = self.cur_mps.pop() {
- match &matcher[mp.idx] {
+ let matcher_loc = &matcher[mp.idx];
+ track.before_match_loc(self, matcher_loc);
+
+ match matcher_loc {
MatcherLoc::Token { token: t } => {
// If it's a doc comment, we just ignore it and move on to the next tt in the
// matcher. This is a bug, but #95267 showed that existing programs rely on
@@ -450,7 +505,7 @@ impl TtParser {
// Try zero matches of this sequence, by skipping over it.
self.cur_mps.push(MatcherPos {
idx: idx_first_after,
- matches: mp.matches.clone(), // a cheap clone
+ matches: Lrc::clone(&mp.matches),
});
}
@@ -463,8 +518,8 @@ impl TtParser {
// sequence. If that's not possible, `ending_mp` will fail quietly when it is
// processed next time around the loop.
let ending_mp = MatcherPos {
- idx: mp.idx + 1, // +1 skips the Kleene op
- matches: mp.matches.clone(), // a cheap clone
+ idx: mp.idx + 1, // +1 skips the Kleene op
+ matches: Lrc::clone(&mp.matches),
};
self.cur_mps.push(ending_mp);
@@ -479,8 +534,8 @@ impl TtParser {
// separator yet. Try ending the sequence. If that's not possible, `ending_mp`
// will fail quietly when it is processed next time around the loop.
let ending_mp = MatcherPos {
- idx: mp.idx + 2, // +2 skips the separator and the Kleene op
- matches: mp.matches.clone(), // a cheap clone
+ idx: mp.idx + 2, // +2 skips the separator and the Kleene op
+ matches: Lrc::clone(&mp.matches),
};
self.cur_mps.push(ending_mp);
@@ -552,10 +607,11 @@ impl TtParser {
}
/// Match the token stream from `parser` against `matcher`.
- pub(super) fn parse_tt(
+ pub(super) fn parse_tt<'matcher, T: Tracker<'matcher>>(
&mut self,
parser: &mut Cow<'_, Parser<'_>>,
- matcher: &[MatcherLoc],
+ matcher: &'matcher [MatcherLoc],
+ track: &mut T,
) -> NamedParseResult {
// A queue of possible matcher positions. We initialize it with the matcher position in
// which the "dot" is before the first token of the first token tree in `matcher`.
@@ -571,7 +627,8 @@ impl TtParser {
// Process `cur_mps` until either we have finished the input or we need to get some
// parsing from the black-box parser done.
- if let Some(res) = self.parse_tt_inner(matcher, &parser.token) {
+ let res = self.parse_tt_inner(matcher, &parser.token, track);
+ if let Some(res) = res {
return res;
}
@@ -612,14 +669,14 @@ impl TtParser {
// edition-specific matching behavior for non-terminals.
let nt = match parser.to_mut().parse_nonterminal(kind) {
Err(mut err) => {
- err.span_label(
+ let guarantee = err.span_label(
span,
format!(
"while parsing argument for this `{kind}` macro fragment"
),
)
.emit();
- return ErrorReported;
+ return ErrorReported(guarantee);
}
Ok(nt) => nt,
};
diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs
index f6fe38174..2dbb90e21 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -2,6 +2,7 @@ use crate::base::{DummyResult, ExtCtxt, MacResult, TTMacroExpander};
use crate::base::{SyntaxExtension, SyntaxExtensionKind};
use crate::expand::{ensure_complete_parse, parse_ast_fragment, AstFragment, AstFragmentKind};
use crate::mbe;
+use crate::mbe::diagnostics::{annotate_doc_comment, parse_failure_msg};
use crate::mbe::macro_check;
use crate::mbe::macro_parser::{Error, ErrorReported, Failure, Success, TtParser};
use crate::mbe::macro_parser::{MatchedSeq, MatchedTokenTree, MatcherLoc};
@@ -14,18 +15,17 @@ use rustc_ast::{NodeId, DUMMY_NODE_ID};
use rustc_ast_pretty::pprust;
use rustc_attr::{self as attr, TransparencyError};
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
-use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage};
+use rustc_errors::{Applicability, ErrorGuaranteed};
use rustc_feature::Features;
use rustc_lint_defs::builtin::{
RUST_2021_INCOMPATIBLE_OR_PATTERNS, SEMICOLON_IN_EXPRESSIONS_FROM_MACROS,
};
use rustc_lint_defs::BuiltinLintDiagnostics;
-use rustc_parse::parser::Parser;
+use rustc_parse::parser::{Parser, Recovery};
use rustc_session::parse::ParseSess;
use rustc_session::Session;
use rustc_span::edition::Edition;
use rustc_span::hygiene::Transparency;
-use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{kw, sym, Ident, MacroRulesNormalizedIdent};
use rustc_span::Span;
@@ -33,6 +33,9 @@ use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::{mem, slice};
+use super::diagnostics;
+use super::macro_parser::{NamedMatches, NamedParseResult};
+
pub(crate) struct ParserAnyMacro<'a> {
parser: Parser<'a>,
@@ -47,74 +50,6 @@ pub(crate) struct ParserAnyMacro<'a> {
is_local: bool,
}
-pub(crate) fn annotate_err_with_kind(err: &mut Diagnostic, kind: AstFragmentKind, span: Span) {
- match kind {
- AstFragmentKind::Ty => {
- err.span_label(span, "this macro call doesn't expand to a type");
- }
- AstFragmentKind::Pat => {
- err.span_label(span, "this macro call doesn't expand to a pattern");
- }
- _ => {}
- };
-}
-
-fn emit_frag_parse_err(
- mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>,
- parser: &Parser<'_>,
- orig_parser: &mut Parser<'_>,
- site_span: Span,
- arm_span: Span,
- kind: AstFragmentKind,
-) {
- // FIXME(davidtwco): avoid depending on the error message text
- if parser.token == token::Eof
- && let DiagnosticMessage::Str(message) = &e.message[0].0
- && message.ends_with(", found `<eof>`")
- {
- let msg = &e.message[0];
- e.message[0] = (
- DiagnosticMessage::Str(format!(
- "macro expansion ends with an incomplete expression: {}",
- message.replace(", found `<eof>`", ""),
- )),
- msg.1,
- );
- if !e.span.is_dummy() {
- // early end of macro arm (#52866)
- e.replace_span_with(parser.token.span.shrink_to_hi());
- }
- }
- if e.span.is_dummy() {
- // Get around lack of span in error (#30128)
- e.replace_span_with(site_span);
- if !parser.sess.source_map().is_imported(arm_span) {
- e.span_label(arm_span, "in this macro arm");
- }
- } else if parser.sess.source_map().is_imported(parser.token.span) {
- e.span_label(site_span, "in this macro invocation");
- }
- match kind {
- // Try a statement if an expression is wanted but failed and suggest adding `;` to call.
- AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {
- Err(err) => err.cancel(),
- Ok(_) => {
- e.note(
- "the macro call doesn't expand to an expression, but it can expand to a statement",
- );
- e.span_suggestion_verbose(
- site_span.shrink_to_hi(),
- "add `;` to interpret the expansion as a statement",
- ";",
- Applicability::MaybeIncorrect,
- );
- }
- },
- _ => annotate_err_with_kind(&mut e, kind, site_span),
- };
- e.emit();
-}
-
impl<'a> ParserAnyMacro<'a> {
pub(crate) fn make(mut self: Box<ParserAnyMacro<'a>>, kind: AstFragmentKind) -> AstFragment {
let ParserAnyMacro {
@@ -130,7 +65,7 @@ impl<'a> ParserAnyMacro<'a> {
let fragment = match parse_ast_fragment(parser, kind) {
Ok(f) => f,
Err(err) => {
- emit_frag_parse_err(err, parser, snapshot, site_span, arm_span, kind);
+ diagnostics::emit_frag_parse_err(err, parser, snapshot, site_span, arm_span, kind);
return kind.dummy(site_span);
}
};
@@ -205,8 +140,37 @@ fn trace_macros_note(cx_expansions: &mut FxIndexMap<Span, Vec<String>>, sp: Span
cx_expansions.entry(sp).or_default().push(message);
}
+pub(super) trait Tracker<'matcher> {
+ /// This is called before trying to match next MatcherLoc on the current token.
+ fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc);
+
+ /// This is called after an arm has been parsed, either successfully or unsuccessfully. When this is called,
+ /// `before_match_loc` was called at least once (with a `MatcherLoc::Eof`).
+ fn after_arm(&mut self, result: &NamedParseResult);
+
+ /// For tracing.
+ fn description() -> &'static str;
+
+ fn recovery() -> Recovery;
+}
+
+/// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to monomorphization.
+pub(super) struct NoopTracker;
+
+impl<'matcher> Tracker<'matcher> for NoopTracker {
+ fn before_match_loc(&mut self, _: &TtParser, _: &'matcher MatcherLoc) {}
+ fn after_arm(&mut self, _: &NamedParseResult) {}
+ fn description() -> &'static str {
+ "none"
+ }
+ fn recovery() -> Recovery {
+ Recovery::Forbidden
+ }
+}
+
/// Expands the rules based macro defined by `lhses` and `rhses` for a given
/// input `arg`.
+#[instrument(skip(cx, transparency, arg, lhses, rhses))]
fn expand_macro<'cx>(
cx: &'cx mut ExtCtxt<'_>,
sp: Span,
@@ -228,9 +192,96 @@ fn expand_macro<'cx>(
trace_macros_note(&mut cx.expansions, sp, msg);
}
- // Which arm's failure should we report? (the one furthest along)
- let mut best_failure: Option<(Token, &str)> = None;
+ // Track nothing for the best performance.
+ let try_success_result = try_match_macro(sess, name, &arg, lhses, &mut NoopTracker);
+
+ match try_success_result {
+ Ok((i, named_matches)) => {
+ let (rhs, rhs_span): (&mbe::Delimited, DelimSpan) = match &rhses[i] {
+ mbe::TokenTree::Delimited(span, delimited) => (&delimited, *span),
+ _ => cx.span_bug(sp, "malformed macro rhs"),
+ };
+ let arm_span = rhses[i].span();
+
+ let rhs_spans = rhs.tts.iter().map(|t| t.span()).collect::<Vec<_>>();
+ // rhs has holes ( `$id` and `$(...)` that need filled)
+ let mut tts = match transcribe(cx, &named_matches, &rhs, rhs_span, transparency) {
+ Ok(tts) => tts,
+ Err(mut err) => {
+ err.emit();
+ return DummyResult::any(arm_span);
+ }
+ };
+
+ // Replace all the tokens for the corresponding positions in the macro, to maintain
+ // proper positions in error reporting, while maintaining the macro_backtrace.
+ if rhs_spans.len() == tts.len() {
+ tts = tts.map_enumerated(|i, tt| {
+ let mut tt = tt.clone();
+ let mut sp = rhs_spans[i];
+ sp = sp.with_ctxt(tt.span().ctxt());
+ tt.set_span(sp);
+ tt
+ });
+ }
+
+ if cx.trace_macros() {
+ let msg = format!("to `{}`", pprust::tts_to_string(&tts));
+ trace_macros_note(&mut cx.expansions, sp, msg);
+ }
+
+ let mut p = Parser::new(sess, tts, false, None);
+ p.last_type_ascription = cx.current_expansion.prior_type_ascription;
+
+ if is_local {
+ cx.resolver.record_macro_rule_usage(node_id, i);
+ }
+
+ // Let the context choose how to interpret the result.
+ // Weird, but useful for X-macros.
+ return Box::new(ParserAnyMacro {
+ parser: p,
+
+ // Pass along the original expansion site and the name of the macro
+ // so we can print a useful error message if the parse of the expanded
+ // macro leaves unparsed tokens.
+ site_span: sp,
+ macro_ident: name,
+ lint_node_id: cx.current_expansion.lint_node_id,
+ is_trailing_mac: cx.current_expansion.is_trailing_mac,
+ arm_span,
+ is_local,
+ });
+ }
+ Err(CanRetry::No(_)) => {
+ debug!("Will not retry matching as an error was emitted already");
+ return DummyResult::any(sp);
+ }
+ Err(CanRetry::Yes) => {
+ // Retry and emit a better error below.
+ }
+ }
+
+ diagnostics::failed_to_match_macro(cx, sp, def_span, name, arg, lhses)
+}
+
+pub(super) enum CanRetry {
+ Yes,
+ /// We are not allowed to retry macro expansion as a fatal error has been emitted already.
+ No(ErrorGuaranteed),
+}
+/// Try expanding the macro. Returns the index of the successful arm and its named_matches if it was successful,
+/// and nothing if it failed. On failure, it's the callers job to use `track` accordingly to record all errors
+/// correctly.
+#[instrument(level = "debug", skip(sess, arg, lhses, track), fields(tracking = %T::description()))]
+pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
+ sess: &ParseSess,
+ name: Ident,
+ arg: &TokenStream,
+ lhses: &'matcher [Vec<MatcherLoc>],
+ track: &mut T,
+) -> Result<(usize, NamedMatches), CanRetry> {
// We create a base parser that can be used for the "black box" parts.
// Every iteration needs a fresh copy of that parser. However, the parser
// is not mutated on many of the iterations, particularly when dealing with
@@ -250,127 +301,53 @@ fn expand_macro<'cx>(
// hacky, but speeds up the `html5ever` benchmark significantly. (Issue
// 68836 suggests a more comprehensive but more complex change to deal with
// this situation.)
- // FIXME(Nilstrieb): Stop recovery from happening on this parser and retry later with recovery if the macro failed to match.
- let parser = parser_from_cx(sess, arg.clone());
-
+ let parser = parser_from_cx(sess, arg.clone(), T::recovery());
// Try each arm's matchers.
let mut tt_parser = TtParser::new(name);
for (i, lhs) in lhses.iter().enumerate() {
+ let _tracing_span = trace_span!("Matching arm", %i);
+
// Take a snapshot of the state of pre-expansion gating at this point.
// This is used so that if a matcher is not `Success(..)`ful,
// then the spans which became gated when parsing the unsuccessful matcher
// are not recorded. On the first `Success(..)`ful matcher, the spans are merged.
let mut gated_spans_snapshot = mem::take(&mut *sess.gated_spans.spans.borrow_mut());
- match tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs) {
+ let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track);
+
+ track.after_arm(&result);
+
+ match result {
Success(named_matches) => {
+ debug!("Parsed arm successfully");
// The matcher was `Success(..)`ful.
// Merge the gated spans from parsing the matcher with the pre-existing ones.
sess.gated_spans.merge(gated_spans_snapshot);
- let (rhs, rhs_span): (&mbe::Delimited, DelimSpan) = match &rhses[i] {
- mbe::TokenTree::Delimited(span, delimited) => (&delimited, *span),
- _ => cx.span_bug(sp, "malformed macro rhs"),
- };
- let arm_span = rhses[i].span();
-
- let rhs_spans = rhs.tts.iter().map(|t| t.span()).collect::<Vec<_>>();
- // rhs has holes ( `$id` and `$(...)` that need filled)
- let mut tts = match transcribe(cx, &named_matches, &rhs, rhs_span, transparency) {
- Ok(tts) => tts,
- Err(mut err) => {
- err.emit();
- return DummyResult::any(arm_span);
- }
- };
-
- // Replace all the tokens for the corresponding positions in the macro, to maintain
- // proper positions in error reporting, while maintaining the macro_backtrace.
- if rhs_spans.len() == tts.len() {
- tts = tts.map_enumerated(|i, tt| {
- let mut tt = tt.clone();
- let mut sp = rhs_spans[i];
- sp = sp.with_ctxt(tt.span().ctxt());
- tt.set_span(sp);
- tt
- });
- }
-
- if cx.trace_macros() {
- let msg = format!("to `{}`", pprust::tts_to_string(&tts));
- trace_macros_note(&mut cx.expansions, sp, msg);
- }
-
- let mut p = Parser::new(sess, tts, false, None);
- p.last_type_ascription = cx.current_expansion.prior_type_ascription;
-
- if is_local {
- cx.resolver.record_macro_rule_usage(node_id, i);
- }
-
- // Let the context choose how to interpret the result.
- // Weird, but useful for X-macros.
- return Box::new(ParserAnyMacro {
- parser: p,
-
- // Pass along the original expansion site and the name of the macro
- // so we can print a useful error message if the parse of the expanded
- // macro leaves unparsed tokens.
- site_span: sp,
- macro_ident: name,
- lint_node_id: cx.current_expansion.lint_node_id,
- is_trailing_mac: cx.current_expansion.is_trailing_mac,
- arm_span,
- is_local,
- });
+ return Ok((i, named_matches));
}
- Failure(token, msg) => match best_failure {
- Some((ref best_token, _)) if best_token.span.lo() >= token.span.lo() => {}
- _ => best_failure = Some((token, msg)),
- },
- Error(err_sp, ref msg) => {
- let span = err_sp.substitute_dummy(sp);
- cx.struct_span_err(span, &msg).emit();
- return DummyResult::any(span);
+ Failure(_, _) => {
+ trace!("Failed to match arm, trying the next one");
+ // Try the next arm.
+ }
+ Error(_, _) => {
+ debug!("Fatal error occurred during matching");
+ // We haven't emitted an error yet, so we can retry.
+ return Err(CanRetry::Yes);
+ }
+ ErrorReported(guarantee) => {
+ debug!("Fatal error occurred and was reported during matching");
+ // An error has been reported already, we cannot retry as that would cause duplicate errors.
+ return Err(CanRetry::No(guarantee));
}
- ErrorReported => return DummyResult::any(sp),
}
// The matcher was not `Success(..)`ful.
// Restore to the state before snapshotting and maybe try again.
mem::swap(&mut gated_spans_snapshot, &mut sess.gated_spans.spans.borrow_mut());
}
- drop(parser);
-
- let (token, label) = best_failure.expect("ran no matchers");
- let span = token.span.substitute_dummy(sp);
- let mut err = cx.struct_span_err(span, &parse_failure_msg(&token));
- err.span_label(span, label);
- if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) {
- err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro");
- }
- annotate_doc_comment(&mut err, sess.source_map(), span);
- // Check whether there's a missing comma in this macro call, like `println!("{}" a);`
- if let Some((arg, comma_span)) = arg.add_comma() {
- for lhs in lhses {
- let parser = parser_from_cx(sess, arg.clone());
- if let Success(_) = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs) {
- if comma_span.is_dummy() {
- err.note("you might be missing a comma");
- } else {
- err.span_suggestion_short(
- comma_span,
- "missing comma here",
- ", ",
- Applicability::MachineApplicable,
- );
- }
- }
- }
- }
- err.emit();
- cx.trace_macros_diag();
- DummyResult::any(sp)
+
+ Err(CanRetry::Yes)
}
// Note that macro-by-example's input is also matched against a token tree:
@@ -406,7 +383,7 @@ pub fn compile_declarative_macro(
// Parse the macro_rules! invocation
let (macro_rules, body) = match &def.kind {
- ast::ItemKind::MacroDef(def) => (def.macro_rules, def.body.inner_tokens()),
+ ast::ItemKind::MacroDef(def) => (def.macro_rules, def.body.tokens.clone()),
_ => unreachable!(),
};
@@ -452,28 +429,29 @@ pub fn compile_declarative_macro(
let parser = Parser::new(&sess.parse_sess, body, true, rustc_parse::MACRO_ARGUMENTS);
let mut tt_parser =
TtParser::new(Ident::with_dummy_span(if macro_rules { kw::MacroRules } else { kw::Macro }));
- let argument_map = match tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &argument_gram) {
- Success(m) => m,
- Failure(token, msg) => {
- let s = parse_failure_msg(&token);
- let sp = token.span.substitute_dummy(def.span);
- let mut err = sess.parse_sess.span_diagnostic.struct_span_err(sp, &s);
- err.span_label(sp, msg);
- annotate_doc_comment(&mut err, sess.source_map(), sp);
- err.emit();
- return dummy_syn_ext();
- }
- Error(sp, msg) => {
- sess.parse_sess
- .span_diagnostic
- .struct_span_err(sp.substitute_dummy(def.span), &msg)
- .emit();
- return dummy_syn_ext();
- }
- ErrorReported => {
- return dummy_syn_ext();
- }
- };
+ let argument_map =
+ match tt_parser.parse_tt(&mut Cow::Owned(parser), &argument_gram, &mut NoopTracker) {
+ Success(m) => m,
+ Failure(token, msg) => {
+ let s = parse_failure_msg(&token);
+ let sp = token.span.substitute_dummy(def.span);
+ let mut err = sess.parse_sess.span_diagnostic.struct_span_err(sp, &s);
+ err.span_label(sp, msg);
+ annotate_doc_comment(&mut err, sess.source_map(), sp);
+ err.emit();
+ return dummy_syn_ext();
+ }
+ Error(sp, msg) => {
+ sess.parse_sess
+ .span_diagnostic
+ .struct_span_err(sp.substitute_dummy(def.span), &msg)
+ .emit();
+ return dummy_syn_ext();
+ }
+ ErrorReported(_) => {
+ return dummy_syn_ext();
+ }
+ };
let mut valid = true;
@@ -597,30 +575,6 @@ pub fn compile_declarative_macro(
(mk_syn_ext(expander), rule_spans)
}
-#[derive(Subdiagnostic)]
-enum ExplainDocComment {
- #[label(expand_explain_doc_comment_inner)]
- Inner {
- #[primary_span]
- span: Span,
- },
- #[label(expand_explain_doc_comment_outer)]
- Outer {
- #[primary_span]
- span: Span,
- },
-}
-
-fn annotate_doc_comment(err: &mut Diagnostic, sm: &SourceMap, span: Span) {
- if let Ok(src) = sm.span_to_snippet(span) {
- if src.starts_with("///") || src.starts_with("/**") {
- err.subdiagnostic(ExplainDocComment::Outer { span });
- } else if src.starts_with("//!") || src.starts_with("/*!") {
- err.subdiagnostic(ExplainDocComment::Inner { span });
- }
- }
-}
-
fn check_lhs_nt_follows(sess: &ParseSess, def: &ast::Item, lhs: &mbe::TokenTree) -> bool {
// lhs is going to be like TokenTree::Delimited(...), where the
// entire lhs is those tts. Or, it can be a "bare sequence", not wrapped in parens.
@@ -1405,15 +1359,6 @@ fn quoted_tt_to_string(tt: &mbe::TokenTree) -> String {
}
}
-fn parser_from_cx(sess: &ParseSess, tts: TokenStream) -> Parser<'_> {
- Parser::new(sess, tts, true, rustc_parse::MACRO_ARGUMENTS)
-}
-
-/// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For
-/// other tokens, this is "unexpected token...".
-fn parse_failure_msg(tok: &Token) -> String {
- match tok.kind {
- token::Eof => "unexpected end of macro invocation".to_string(),
- _ => format!("no rules expected the token `{}`", pprust::token_to_string(tok),),
- }
+pub(super) fn parser_from_cx(sess: &ParseSess, tts: TokenStream, recovery: Recovery) -> Parser<'_> {
+ Parser::new(sess, tts, true, rustc_parse::MACRO_ARGUMENTS).recovery(recovery)
}
diff --git a/compiler/rustc_expand/src/parse/tests.rs b/compiler/rustc_expand/src/parse/tests.rs
index a3c631d33..e49f112bf 100644
--- a/compiler/rustc_expand/src/parse/tests.rs
+++ b/compiler/rustc_expand/src/parse/tests.rs
@@ -291,7 +291,7 @@ fn ttdelim_span() {
.unwrap();
let tts: Vec<_> = match expr.kind {
- ast::ExprKind::MacCall(ref mac) => mac.args.inner_tokens().into_trees().collect(),
+ ast::ExprKind::MacCall(ref mac) => mac.args.tokens.clone().into_trees().collect(),
_ => panic!("not a macro"),
};
diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs
index faaf3b3fe..03bb5c1df 100644
--- a/compiler/rustc_expand/src/placeholders.rs
+++ b/compiler/rustc_expand/src/placeholders.rs
@@ -1,14 +1,12 @@
use crate::expand::{AstFragment, AstFragmentKind};
-
use rustc_ast as ast;
use rustc_ast::mut_visit::*;
use rustc_ast::ptr::P;
+use rustc_data_structures::fx::FxHashMap;
use rustc_span::source_map::DUMMY_SP;
use rustc_span::symbol::Ident;
-
use smallvec::{smallvec, SmallVec};
-
-use rustc_data_structures::fx::FxHashMap;
+use thin_vec::ThinVec;
pub fn placeholder(
kind: AstFragmentKind,
@@ -17,8 +15,12 @@ pub fn placeholder(
) -> AstFragment {
fn mac_placeholder() -> P<ast::MacCall> {
P(ast::MacCall {
- path: ast::Path { span: DUMMY_SP, segments: Vec::new(), tokens: None },
- args: P(ast::MacArgs::Empty),
+ path: ast::Path { span: DUMMY_SP, segments: ThinVec::new(), tokens: None },
+ args: P(ast::DelimArgs {
+ dspan: ast::tokenstream::DelimSpan::dummy(),
+ delim: ast::MacDelimiter::Parenthesis,
+ tokens: ast::tokenstream::TokenStream::new(Vec::new()),
+ }),
prior_type_ascription: None,
})
}
diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs
index 1a2ab9d19..e9a691920 100644
--- a/compiler/rustc_expand/src/proc_macro.rs
+++ b/compiler/rustc_expand/src/proc_macro.rs
@@ -112,6 +112,7 @@ impl MultiItemModifier for DeriveProcMacro {
span: Span,
_meta_item: &ast::MetaItem,
item: Annotatable,
+ _is_derive_const: bool,
) -> ExpandResult<Vec<Annotatable>, Annotatable> {
// We need special handling for statement items
// (e.g. `fn foo() { #[derive(Debug)] struct Bar; }`)
diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs
index cc2858d3f..761657961 100644
--- a/compiler/rustc_expand/src/proc_macro_server.rs
+++ b/compiler/rustc_expand/src/proc_macro_server.rs
@@ -516,26 +516,27 @@ impl server::TokenStream for Rustc<'_, '_> {
// We don't use `TokenStream::from_ast` as the tokenstream currently cannot
// be recovered in the general case.
match &expr.kind {
- ast::ExprKind::Lit(l) if l.token_lit.kind == token::Bool => {
+ ast::ExprKind::Lit(token_lit) if token_lit.kind == token::Bool => {
Ok(tokenstream::TokenStream::token_alone(
- token::Ident(l.token_lit.symbol, false),
- l.span,
+ token::Ident(token_lit.symbol, false),
+ expr.span,
))
}
- ast::ExprKind::Lit(l) => {
- Ok(tokenstream::TokenStream::token_alone(token::Literal(l.token_lit), l.span))
+ ast::ExprKind::Lit(token_lit) => {
+ Ok(tokenstream::TokenStream::token_alone(token::Literal(*token_lit), expr.span))
+ }
+ ast::ExprKind::IncludedBytes(bytes) => {
+ let lit = ast::LitKind::ByteStr(bytes.clone()).to_token_lit();
+ Ok(tokenstream::TokenStream::token_alone(token::TokenKind::Literal(lit), expr.span))
}
ast::ExprKind::Unary(ast::UnOp::Neg, e) => match &e.kind {
- ast::ExprKind::Lit(l) => match l.token_lit {
+ ast::ExprKind::Lit(token_lit) => match token_lit {
token::Lit { kind: token::Integer | token::Float, .. } => {
Ok(Self::TokenStream::from_iter([
// FIXME: The span of the `-` token is lost when
// parsing, so we cannot faithfully recover it here.
tokenstream::TokenTree::token_alone(token::BinOp(token::Minus), e.span),
- tokenstream::TokenTree::token_alone(
- token::Literal(l.token_lit),
- l.span,
- ),
+ tokenstream::TokenTree::token_alone(token::Literal(*token_lit), e.span),
]))
}
_ => Err(()),
diff --git a/compiler/rustc_expand/src/tests.rs b/compiler/rustc_expand/src/tests.rs
index e44f06081..539b04535 100644
--- a/compiler/rustc_expand/src/tests.rs
+++ b/compiler/rustc_expand/src/tests.rs
@@ -151,6 +151,7 @@ fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &
false,
None,
false,
+ false,
);
let handler = Handler::with_emitter(true, None, Box::new(emitter));
handler.span_err(msp, "foo");
@@ -271,13 +272,13 @@ error: foo
--> test.rs:3:3
|
3 | X0 Y0
- | ____^__-
- | | ___|
+ | ___^__-
+ | |___|
| ||
4 | || X1 Y1
5 | || X2 Y2
| ||____^__- `Y` is a good letter too
- | |____|
+ | |_____|
| `X` is a good letter
"#,
@@ -310,12 +311,12 @@ error: foo
--> test.rs:3:3
|
3 | X0 Y0
- | ____^__-
- | | ___|
+ | ___^__-
+ | |___|
| ||
4 | || Y1 X1
| ||____-__^ `X` is a good letter
- | |_____|
+ | |____|
| `Y` is a good letter too
"#,
@@ -350,13 +351,13 @@ error: foo
--> test.rs:3:6
|
3 | X0 Y0 Z0
- | ______^
-4 | | X1 Y1 Z1
- | |_________-
+ | _______^
+4 | | X1 Y1 Z1
+ | | _________-
5 | || X2 Y2 Z2
| ||____^ `X` is a good letter
-6 | | X3 Y3 Z3
- | |_____- `Y` is a good letter too
+6 | | X3 Y3 Z3
+ | |____- `Y` is a good letter too
"#,
);
@@ -394,15 +395,15 @@ error: foo
--> test.rs:3:3
|
3 | X0 Y0 Z0
- | _____^__-__-
- | | ____|__|
- | || ___|
+ | ___^__-__-
+ | |___|__|
+ | ||___|
| |||
4 | ||| X1 Y1 Z1
5 | ||| X2 Y2 Z2
| |||____^__-__- `Z` label
- | ||____|__|
- | |____| `Y` is a good letter too
+ | ||_____|__|
+ | |______| `Y` is a good letter too
| `X` is a good letter
"#,
@@ -486,17 +487,17 @@ error: foo
--> test.rs:3:6
|
3 | X0 Y0 Z0
- | ______^
-4 | | X1 Y1 Z1
- | |____^_-
+ | _______^
+4 | | X1 Y1 Z1
+ | | ____^_-
| ||____|
- | | `X` is a good letter
-5 | | X2 Y2 Z2
- | |____-______- `Y` is a good letter too
- | ____|
- | |
-6 | | X3 Y3 Z3
- | |________- `Z`
+ | | `X` is a good letter
+5 | | X2 Y2 Z2
+ | |___-______- `Y` is a good letter too
+ | ___|
+ | |
+6 | | X3 Y3 Z3
+ | |_______- `Z`
"#,
);
@@ -569,14 +570,14 @@ error: foo
--> test.rs:3:6
|
3 | X0 Y0 Z0
- | ______^
-4 | | X1 Y1 Z1
- | |____^____-
+ | _______^
+4 | | X1 Y1 Z1
+ | | ____^____-
| ||____|
- | | `X` is a good letter
-5 | | X2 Y2 Z2
-6 | | X3 Y3 Z3
- | |___________- `Y` is a good letter too
+ | | `X` is a good letter
+5 | | X2 Y2 Z2
+6 | | X3 Y3 Z3
+ | |__________- `Y` is a good letter too
"#,
);
@@ -940,18 +941,18 @@ error: foo
--> test.rs:3:6
|
3 | X0 Y0 Z0
- | ______^
-4 | | X1 Y1 Z1
- | |____^____-
+ | _______^
+4 | | X1 Y1 Z1
+ | | ____^____-
| ||____|
- | | `X` is a good letter
-5 | | 1
-6 | | 2
-7 | | 3
-... |
-15 | | X2 Y2 Z2
-16 | | X3 Y3 Z3
- | |___________- `Y` is a good letter too
+ | | `X` is a good letter
+5 | | 1
+6 | | 2
+7 | | 3
+... |
+15 | | X2 Y2 Z2
+16 | | X3 Y3 Z3
+ | |__________- `Y` is a good letter too
"#,
);
@@ -995,21 +996,21 @@ error: foo
--> test.rs:3:6
|
3 | X0 Y0 Z0
- | ______^
-4 | | 1
-5 | | 2
-6 | | 3
-7 | | X1 Y1 Z1
- | |_________-
+ | _______^
+4 | | 1
+5 | | 2
+6 | | 3
+7 | | X1 Y1 Z1
+ | | _________-
8 | || 4
9 | || 5
10 | || 6
11 | || X2 Y2 Z2
| ||__________- `Z` is a good letter too
-... |
-15 | | 10
-16 | | X3 Y3 Z3
- | |_______^ `Y` is a good letter
+... |
+15 | | 10
+16 | | X3 Y3 Z3
+ | |________^ `Y` is a good letter
"#,
);