Skip to content

Commit

Permalink
Auto merge of #106416 - Nilstrieb:better-failure, r=compiler-errors
Browse files Browse the repository at this point in the history
Shrink `ParseResult` in the hot path.

#105570 increased the size, which caused regressions. This uses the existing generic infrastructure to differentiate between the hot path and the diagnostics path.
  • Loading branch information
bors committed Jan 5, 2023
2 parents 388538f + 5112f02 commit b85f57d
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 30 deletions.
25 changes: 23 additions & 2 deletions compiler/rustc_expand/src/mbe/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ impl BestFailure {
}

impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
type Failure = (Token, usize, &'static str);

fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
(tok, position, msg)
}

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)
Expand All @@ -122,7 +128,7 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
}
}

fn after_arm(&mut self, result: &NamedParseResult) {
fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
match result {
Success(_) => {
// Nonterminal parser recovery might turn failed matches into successful ones,
Expand All @@ -132,7 +138,7 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
"should not collect detailed info for successful macro match",
);
}
Failure(token, approx_position, msg) => {
Failure((token, approx_position, msg)) => {
debug!(?token, ?msg, "a new failure of an arm");

if self
Expand Down Expand Up @@ -175,6 +181,21 @@ impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
}
}

/// Currently used by macro_rules! compilation to extract a little information from the `Failure` case.
pub struct FailureForwarder;

impl<'matcher> Tracker<'matcher> for FailureForwarder {
type Failure = (Token, usize, &'static str);

fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
(tok, position, msg)
}

fn description() -> &'static str {
"failure-forwarder"
}
}

pub(super) fn emit_frag_parse_err(
mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>,
parser: &Parser<'_>,
Expand Down
26 changes: 13 additions & 13 deletions compiler/rustc_expand/src/mbe/macro_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,13 +305,13 @@ enum EofMatcherPositions {
}

/// Represents the possible results of an attempted parse.
pub(crate) enum ParseResult<T> {
pub(crate) enum ParseResult<T, F> {
/// Parsed successfully.
Success(T),
/// Arm failed to match. If the second parameter is `token::Eof`, it indicates an unexpected
/// end of macro invocation. Otherwise, it indicates that no rules expected the given token.
/// The usize is the approximate position of the token in the input token stream.
Failure(Token, usize, &'static str),
Failure(F),
/// Fatal error (malformed macro?). Abort compilation.
Error(rustc_span::Span, String),
ErrorReported(ErrorGuaranteed),
Expand All @@ -320,7 +320,7 @@ pub(crate) enum ParseResult<T> {
/// 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<NamedMatches>;
pub(crate) type NamedParseResult<F> = ParseResult<NamedMatches, F>;

/// Contains a mapping of `MacroRulesNormalizedIdent`s to `NamedMatch`es.
/// This represents the mapping of metavars to the token trees they bind to.
Expand Down Expand Up @@ -458,7 +458,7 @@ impl TtParser {
token: &Token,
approx_position: usize,
track: &mut T,
) -> Option<NamedParseResult> {
) -> Option<NamedParseResult<T::Failure>> {
// Matcher positions that would be valid if the macro invocation was over now. Only
// modified if `token == Eof`.
let mut eof_mps = EofMatcherPositions::None;
Expand Down Expand Up @@ -595,14 +595,14 @@ impl TtParser {
EofMatcherPositions::Multiple => {
Error(token.span, "ambiguity: multiple successful parses".to_string())
}
EofMatcherPositions::None => Failure(
EofMatcherPositions::None => Failure(T::build_failure(
Token::new(
token::Eof,
if token.span.is_dummy() { token.span } else { token.span.shrink_to_hi() },
),
approx_position,
"missing tokens in macro arguments",
),
)),
})
} else {
None
Expand All @@ -615,7 +615,7 @@ impl TtParser {
parser: &mut Cow<'_, Parser<'_>>,
matcher: &'matcher [MatcherLoc],
track: &mut T,
) -> NamedParseResult {
) -> NamedParseResult<T::Failure> {
// 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`.
// `parse_tt_inner` then processes all of these possible matcher positions and produces
Expand Down Expand Up @@ -648,11 +648,11 @@ impl TtParser {
(0, 0) => {
// There are no possible next positions AND we aren't waiting for the black-box
// parser: syntax error.
return Failure(
return Failure(T::build_failure(
parser.token.clone(),
parser.approx_token_stream_pos(),
"no rules expected this token in macro call",
);
));
}

(_, 0) => {
Expand Down Expand Up @@ -711,11 +711,11 @@ impl TtParser {
}
}

fn ambiguity_error(
fn ambiguity_error<F>(
&self,
matcher: &[MatcherLoc],
token_span: rustc_span::Span,
) -> NamedParseResult {
) -> NamedParseResult<F> {
let nts = self
.bb_mps
.iter()
Expand All @@ -741,11 +741,11 @@ impl TtParser {
)
}

fn nameize<I: Iterator<Item = NamedMatch>>(
fn nameize<I: Iterator<Item = NamedMatch>, F>(
&self,
matcher: &[MatcherLoc],
mut res: I,
) -> NamedParseResult {
) -> NamedParseResult<F> {
// Make that each metavar has _exactly one_ binding. If so, insert the binding into the
// `NamedParseResult`. Otherwise, it's an error.
let mut ret_val = FxHashMap::default();
Expand Down
60 changes: 45 additions & 15 deletions compiler/rustc_expand/src/mbe/macro_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,31 +141,40 @@ fn trace_macros_note(cx_expansions: &mut FxIndexMap<Span, Vec<String>>, sp: Span
}

pub(super) trait Tracker<'matcher> {
/// The contents of `ParseResult::Failure`.
type Failure;

/// Arm failed to match. If the token is `token::Eof`, it indicates an unexpected
/// end of macro invocation. Otherwise, it indicates that no rules expected the given token.
/// The usize is the approximate position of the token in the input token stream.
fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure;

/// This is called before trying to match next MatcherLoc on the current token.
fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc);
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);
fn after_arm(&mut self, _result: &NamedParseResult<Self::Failure>) {}

/// For tracing.
fn description() -> &'static str;

fn recovery() -> Recovery;
fn recovery() -> Recovery {
Recovery::Forbidden
}
}

/// 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) {}
type Failure = ();

fn build_failure(_tok: Token, _position: usize, _msg: &'static str) -> Self::Failure {}

fn description() -> &'static str {
"none"
}
fn recovery() -> Recovery {
Recovery::Forbidden
}
}

/// Expands the rules based macro defined by `lhses` and `rhses` for a given
Expand Down Expand Up @@ -326,8 +335,8 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(

return Ok((i, named_matches));
}
Failure(_, reached_position, _) => {
trace!(%reached_position, "Failed to match arm, trying the next one");
Failure(_) => {
trace!("Failed to match arm, trying the next one");
// Try the next arm.
}
Error(_, _) => {
Expand Down Expand Up @@ -381,11 +390,13 @@ pub fn compile_declarative_macro(
let rhs_nm = Ident::new(sym::rhs, def.span);
let tt_spec = Some(NonterminalKind::TT);

// Parse the macro_rules! invocation
let (macro_rules, body) = match &def.kind {
ast::ItemKind::MacroDef(def) => (def.macro_rules, def.body.tokens.clone()),
let macro_def = match &def.kind {
ast::ItemKind::MacroDef(def) => def,
_ => unreachable!(),
};
let macro_rules = macro_def.macro_rules;

// Parse the macro_rules! invocation

// The pattern that macro_rules matches.
// The grammar for macro_rules! is:
Expand Down Expand Up @@ -426,13 +437,32 @@ pub fn compile_declarative_macro(
// Convert it into `MatcherLoc` form.
let argument_gram = mbe::macro_parser::compute_locs(&argument_gram);

let parser = Parser::new(&sess.parse_sess, body, true, rustc_parse::MACRO_ARGUMENTS);
let create_parser = || {
let body = macro_def.body.tokens.clone();
Parser::new(&sess.parse_sess, body, true, rustc_parse::MACRO_ARGUMENTS)
};

let parser = create_parser();
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::Owned(parser), &argument_gram, &mut NoopTracker) {
Success(m) => m,
Failure(token, _, msg) => {
Failure(()) => {
// The fast `NoopTracker` doesn't have any info on failure, so we need to retry it with another one
// that gives us the information we need.
// For this we need to reclone the macro body as the previous parser consumed it.
let retry_parser = create_parser();

let parse_result = tt_parser.parse_tt(
&mut Cow::Owned(retry_parser),
&argument_gram,
&mut diagnostics::FailureForwarder,
);
let Failure((token, _, msg)) = parse_result else {
unreachable!("matcher returned something other than Failure after retry");
};

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);
Expand Down

0 comments on commit b85f57d

Please sign in to comment.