|
| 1 | +use std::borrow::Cow; |
| 2 | + |
| 3 | +use crate::base::{DummyResult, ExtCtxt, MacResult}; |
| 4 | +use crate::expand::{parse_ast_fragment, AstFragmentKind}; |
| 5 | +use crate::mbe::{ |
| 6 | + macro_parser::{MatcherLoc, NamedParseResult, ParseResult::*, TtParser}, |
| 7 | + macro_rules::{try_match_macro, Tracker}, |
| 8 | +}; |
| 9 | +use rustc_ast::token::{self, Token}; |
| 10 | +use rustc_ast::tokenstream::TokenStream; |
| 11 | +use rustc_ast_pretty::pprust; |
| 12 | +use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage}; |
| 13 | +use rustc_parse::parser::{Parser, Recovery}; |
| 14 | +use rustc_span::source_map::SourceMap; |
| 15 | +use rustc_span::symbol::Ident; |
| 16 | +use rustc_span::Span; |
| 17 | + |
| 18 | +use super::macro_rules::{parser_from_cx, NoopTracker}; |
| 19 | + |
| 20 | +pub(super) fn failed_to_match_macro<'cx>( |
| 21 | + cx: &'cx mut ExtCtxt<'_>, |
| 22 | + sp: Span, |
| 23 | + def_span: Span, |
| 24 | + name: Ident, |
| 25 | + arg: TokenStream, |
| 26 | + lhses: &[Vec<MatcherLoc>], |
| 27 | +) -> Box<dyn MacResult + 'cx> { |
| 28 | + let sess = &cx.sess.parse_sess; |
| 29 | + |
| 30 | + // An error occurred, try the expansion again, tracking the expansion closely for better diagnostics. |
| 31 | + let mut tracker = CollectTrackerAndEmitter::new(cx, sp); |
| 32 | + |
| 33 | + let try_success_result = try_match_macro(sess, name, &arg, lhses, &mut tracker); |
| 34 | + |
| 35 | + if try_success_result.is_ok() { |
| 36 | + // Nonterminal parser recovery might turn failed matches into successful ones, |
| 37 | + // but for that it must have emitted an error already |
| 38 | + tracker.cx.sess.delay_span_bug(sp, "Macro matching returned a success on the second try"); |
| 39 | + } |
| 40 | + |
| 41 | + if let Some(result) = tracker.result { |
| 42 | + // An irrecoverable error occurred and has been emitted. |
| 43 | + return result; |
| 44 | + } |
| 45 | + |
| 46 | + let Some((token, label, remaining_matcher)) = tracker.best_failure else { |
| 47 | + return DummyResult::any(sp); |
| 48 | + }; |
| 49 | + |
| 50 | + let span = token.span.substitute_dummy(sp); |
| 51 | + |
| 52 | + let mut err = cx.struct_span_err(span, &parse_failure_msg(&token)); |
| 53 | + err.span_label(span, label); |
| 54 | + if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) { |
| 55 | + err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro"); |
| 56 | + } |
| 57 | + |
| 58 | + annotate_doc_comment(&mut err, sess.source_map(), span); |
| 59 | + |
| 60 | + if let Some(span) = remaining_matcher.span() { |
| 61 | + err.span_note(span, format!("while trying to match {remaining_matcher}")); |
| 62 | + } else { |
| 63 | + err.note(format!("while trying to match {remaining_matcher}")); |
| 64 | + } |
| 65 | + |
| 66 | + // Check whether there's a missing comma in this macro call, like `println!("{}" a);` |
| 67 | + if let Some((arg, comma_span)) = arg.add_comma() { |
| 68 | + for lhs in lhses { |
| 69 | + let parser = parser_from_cx(sess, arg.clone(), Recovery::Allowed); |
| 70 | + let mut tt_parser = TtParser::new(name); |
| 71 | + |
| 72 | + if let Success(_) = |
| 73 | + tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker) |
| 74 | + { |
| 75 | + if comma_span.is_dummy() { |
| 76 | + err.note("you might be missing a comma"); |
| 77 | + } else { |
| 78 | + err.span_suggestion_short( |
| 79 | + comma_span, |
| 80 | + "missing comma here", |
| 81 | + ", ", |
| 82 | + Applicability::MachineApplicable, |
| 83 | + ); |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + err.emit(); |
| 89 | + cx.trace_macros_diag(); |
| 90 | + DummyResult::any(sp) |
| 91 | +} |
| 92 | + |
| 93 | +/// The tracker used for the slow error path that collects useful info for diagnostics. |
| 94 | +struct CollectTrackerAndEmitter<'a, 'cx, 'matcher> { |
| 95 | + cx: &'a mut ExtCtxt<'cx>, |
| 96 | + remaining_matcher: Option<&'matcher MatcherLoc>, |
| 97 | + /// Which arm's failure should we report? (the one furthest along) |
| 98 | + best_failure: Option<(Token, &'static str, MatcherLoc)>, |
| 99 | + root_span: Span, |
| 100 | + result: Option<Box<dyn MacResult + 'cx>>, |
| 101 | +} |
| 102 | + |
| 103 | +impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> { |
| 104 | + fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) { |
| 105 | + if self.remaining_matcher.is_none() |
| 106 | + || (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof) |
| 107 | + { |
| 108 | + self.remaining_matcher = Some(matcher); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + fn after_arm(&mut self, result: &NamedParseResult) { |
| 113 | + match result { |
| 114 | + Success(_) => { |
| 115 | + // Nonterminal parser recovery might turn failed matches into successful ones, |
| 116 | + // but for that it must have emitted an error already |
| 117 | + self.cx.sess.delay_span_bug( |
| 118 | + self.root_span, |
| 119 | + "should not collect detailed info for successful macro match", |
| 120 | + ); |
| 121 | + } |
| 122 | + Failure(token, msg) => match self.best_failure { |
| 123 | + Some((ref best_token, _, _)) if best_token.span.lo() >= token.span.lo() => {} |
| 124 | + _ => { |
| 125 | + self.best_failure = Some(( |
| 126 | + token.clone(), |
| 127 | + msg, |
| 128 | + self.remaining_matcher |
| 129 | + .expect("must have collected matcher already") |
| 130 | + .clone(), |
| 131 | + )) |
| 132 | + } |
| 133 | + }, |
| 134 | + Error(err_sp, msg) => { |
| 135 | + let span = err_sp.substitute_dummy(self.root_span); |
| 136 | + self.cx.struct_span_err(span, msg).emit(); |
| 137 | + self.result = Some(DummyResult::any(span)); |
| 138 | + } |
| 139 | + ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)), |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + fn description() -> &'static str { |
| 144 | + "detailed" |
| 145 | + } |
| 146 | + |
| 147 | + fn recovery() -> Recovery { |
| 148 | + Recovery::Allowed |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> { |
| 153 | + fn new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self { |
| 154 | + Self { cx, remaining_matcher: None, best_failure: None, root_span, result: None } |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +pub(super) fn emit_frag_parse_err( |
| 159 | + mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>, |
| 160 | + parser: &Parser<'_>, |
| 161 | + orig_parser: &mut Parser<'_>, |
| 162 | + site_span: Span, |
| 163 | + arm_span: Span, |
| 164 | + kind: AstFragmentKind, |
| 165 | +) { |
| 166 | + // FIXME(davidtwco): avoid depending on the error message text |
| 167 | + if parser.token == token::Eof |
| 168 | + && let DiagnosticMessage::Str(message) = &e.message[0].0 |
| 169 | + && message.ends_with(", found `<eof>`") |
| 170 | + { |
| 171 | + let msg = &e.message[0]; |
| 172 | + e.message[0] = ( |
| 173 | + DiagnosticMessage::Str(format!( |
| 174 | + "macro expansion ends with an incomplete expression: {}", |
| 175 | + message.replace(", found `<eof>`", ""), |
| 176 | + )), |
| 177 | + msg.1, |
| 178 | + ); |
| 179 | + if !e.span.is_dummy() { |
| 180 | + // early end of macro arm (#52866) |
| 181 | + e.replace_span_with(parser.token.span.shrink_to_hi()); |
| 182 | + } |
| 183 | + } |
| 184 | + if e.span.is_dummy() { |
| 185 | + // Get around lack of span in error (#30128) |
| 186 | + e.replace_span_with(site_span); |
| 187 | + if !parser.sess.source_map().is_imported(arm_span) { |
| 188 | + e.span_label(arm_span, "in this macro arm"); |
| 189 | + } |
| 190 | + } else if parser.sess.source_map().is_imported(parser.token.span) { |
| 191 | + e.span_label(site_span, "in this macro invocation"); |
| 192 | + } |
| 193 | + match kind { |
| 194 | + // Try a statement if an expression is wanted but failed and suggest adding `;` to call. |
| 195 | + AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) { |
| 196 | + Err(err) => err.cancel(), |
| 197 | + Ok(_) => { |
| 198 | + e.note( |
| 199 | + "the macro call doesn't expand to an expression, but it can expand to a statement", |
| 200 | + ); |
| 201 | + e.span_suggestion_verbose( |
| 202 | + site_span.shrink_to_hi(), |
| 203 | + "add `;` to interpret the expansion as a statement", |
| 204 | + ";", |
| 205 | + Applicability::MaybeIncorrect, |
| 206 | + ); |
| 207 | + } |
| 208 | + }, |
| 209 | + _ => annotate_err_with_kind(&mut e, kind, site_span), |
| 210 | + }; |
| 211 | + e.emit(); |
| 212 | +} |
| 213 | + |
| 214 | +pub(crate) fn annotate_err_with_kind(err: &mut Diagnostic, kind: AstFragmentKind, span: Span) { |
| 215 | + match kind { |
| 216 | + AstFragmentKind::Ty => { |
| 217 | + err.span_label(span, "this macro call doesn't expand to a type"); |
| 218 | + } |
| 219 | + AstFragmentKind::Pat => { |
| 220 | + err.span_label(span, "this macro call doesn't expand to a pattern"); |
| 221 | + } |
| 222 | + _ => {} |
| 223 | + }; |
| 224 | +} |
| 225 | + |
| 226 | +#[derive(Subdiagnostic)] |
| 227 | +enum ExplainDocComment { |
| 228 | + #[label(expand_explain_doc_comment_inner)] |
| 229 | + Inner { |
| 230 | + #[primary_span] |
| 231 | + span: Span, |
| 232 | + }, |
| 233 | + #[label(expand_explain_doc_comment_outer)] |
| 234 | + Outer { |
| 235 | + #[primary_span] |
| 236 | + span: Span, |
| 237 | + }, |
| 238 | +} |
| 239 | + |
| 240 | +pub(super) fn annotate_doc_comment(err: &mut Diagnostic, sm: &SourceMap, span: Span) { |
| 241 | + if let Ok(src) = sm.span_to_snippet(span) { |
| 242 | + if src.starts_with("///") || src.starts_with("/**") { |
| 243 | + err.subdiagnostic(ExplainDocComment::Outer { span }); |
| 244 | + } else if src.starts_with("//!") || src.starts_with("/*!") { |
| 245 | + err.subdiagnostic(ExplainDocComment::Inner { span }); |
| 246 | + } |
| 247 | + } |
| 248 | +} |
| 249 | + |
| 250 | +/// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For |
| 251 | +/// other tokens, this is "unexpected token...". |
| 252 | +pub(super) fn parse_failure_msg(tok: &Token) -> String { |
| 253 | + match tok.kind { |
| 254 | + token::Eof => "unexpected end of macro invocation".to_string(), |
| 255 | + _ => format!("no rules expected the token `{}`", pprust::token_to_string(tok),), |
| 256 | + } |
| 257 | +} |
0 commit comments