• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::base::{DummyResult, ExtCtxt, MacResult};
2 use crate::expand::{parse_ast_fragment, AstFragmentKind};
3 use crate::mbe::{
4     macro_parser::{MatcherLoc, NamedParseResult, ParseResult::*, TtParser},
5     macro_rules::{try_match_macro, Tracker},
6 };
7 use rustc_ast::token::{self, Token, TokenKind};
8 use rustc_ast::tokenstream::TokenStream;
9 use rustc_ast_pretty::pprust;
10 use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage};
11 use rustc_parse::parser::{Parser, Recovery};
12 use rustc_span::source_map::SourceMap;
13 use rustc_span::symbol::Ident;
14 use rustc_span::Span;
15 use std::borrow::Cow;
16 
17 use super::macro_rules::{parser_from_cx, NoopTracker};
18 
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>19 pub(super) fn failed_to_match_macro<'cx>(
20     cx: &'cx mut ExtCtxt<'_>,
21     sp: Span,
22     def_span: Span,
23     name: Ident,
24     arg: TokenStream,
25     lhses: &[Vec<MatcherLoc>],
26 ) -> Box<dyn MacResult + 'cx> {
27     let sess = &cx.sess.parse_sess;
28 
29     // An error occurred, try the expansion again, tracking the expansion closely for better diagnostics.
30     let mut tracker = CollectTrackerAndEmitter::new(cx, sp);
31 
32     let try_success_result = try_match_macro(sess, name, &arg, lhses, &mut tracker);
33 
34     if try_success_result.is_ok() {
35         // Nonterminal parser recovery might turn failed matches into successful ones,
36         // but for that it must have emitted an error already
37         tracker.cx.sess.delay_span_bug(sp, "Macro matching returned a success on the second try");
38     }
39 
40     if let Some(result) = tracker.result {
41         // An irrecoverable error occurred and has been emitted.
42         return result;
43     }
44 
45     let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure else {
46         return DummyResult::any(sp);
47     };
48 
49     let span = token.span.substitute_dummy(sp);
50 
51     let mut err = cx.struct_span_err(span, parse_failure_msg(&token));
52     err.span_label(span, label);
53     if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) {
54         err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro");
55     }
56 
57     annotate_doc_comment(&mut err, sess.source_map(), span);
58 
59     if let Some(span) = remaining_matcher.span() {
60         err.span_note(span, format!("while trying to match {remaining_matcher}"));
61     } else {
62         err.note(format!("while trying to match {remaining_matcher}"));
63     }
64 
65     if let MatcherLoc::Token { token: expected_token } = &remaining_matcher
66         && (matches!(expected_token.kind, TokenKind::Interpolated(_))
67             || matches!(token.kind, TokenKind::Interpolated(_)))
68     {
69         err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens");
70         err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information");
71 
72         if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) {
73             err.help("try using `:tt` instead in the macro definition");
74         }
75     }
76 
77     // Check whether there's a missing comma in this macro call, like `println!("{}" a);`
78     if let Some((arg, comma_span)) = arg.add_comma() {
79         for lhs in lhses {
80             let parser = parser_from_cx(sess, arg.clone(), Recovery::Allowed);
81             let mut tt_parser = TtParser::new(name);
82 
83             if let Success(_) =
84                 tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
85             {
86                 if comma_span.is_dummy() {
87                     err.note("you might be missing a comma");
88                 } else {
89                     err.span_suggestion_short(
90                         comma_span,
91                         "missing comma here",
92                         ", ",
93                         Applicability::MachineApplicable,
94                     );
95                 }
96             }
97         }
98     }
99     err.emit();
100     cx.trace_macros_diag();
101     DummyResult::any(sp)
102 }
103 
104 /// The tracker used for the slow error path that collects useful info for diagnostics.
105 struct CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
106     cx: &'a mut ExtCtxt<'cx>,
107     remaining_matcher: Option<&'matcher MatcherLoc>,
108     /// Which arm's failure should we report? (the one furthest along)
109     best_failure: Option<BestFailure>,
110     root_span: Span,
111     result: Option<Box<dyn MacResult + 'cx>>,
112 }
113 
114 struct BestFailure {
115     token: Token,
116     position_in_tokenstream: usize,
117     msg: &'static str,
118     remaining_matcher: MatcherLoc,
119 }
120 
121 impl BestFailure {
is_better_position(&self, position: usize) -> bool122     fn is_better_position(&self, position: usize) -> bool {
123         position > self.position_in_tokenstream
124     }
125 }
126 
127 impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
128     type Failure = (Token, usize, &'static str);
129 
build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure130     fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
131         (tok, position, msg)
132     }
133 
before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc)134     fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
135         if self.remaining_matcher.is_none()
136             || (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
137         {
138             self.remaining_matcher = Some(matcher);
139         }
140     }
141 
after_arm(&mut self, result: &NamedParseResult<Self::Failure>)142     fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
143         match result {
144             Success(_) => {
145                 // Nonterminal parser recovery might turn failed matches into successful ones,
146                 // but for that it must have emitted an error already
147                 self.cx.sess.delay_span_bug(
148                     self.root_span,
149                     "should not collect detailed info for successful macro match",
150                 );
151             }
152             Failure((token, approx_position, msg)) => {
153                 debug!(?token, ?msg, "a new failure of an arm");
154 
155                 if self
156                     .best_failure
157                     .as_ref()
158                     .map_or(true, |failure| failure.is_better_position(*approx_position))
159                 {
160                     self.best_failure = Some(BestFailure {
161                         token: token.clone(),
162                         position_in_tokenstream: *approx_position,
163                         msg,
164                         remaining_matcher: self
165                             .remaining_matcher
166                             .expect("must have collected matcher already")
167                             .clone(),
168                     })
169                 }
170             }
171             Error(err_sp, msg) => {
172                 let span = err_sp.substitute_dummy(self.root_span);
173                 self.cx.struct_span_err(span, msg.clone()).emit();
174                 self.result = Some(DummyResult::any(span));
175             }
176             ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)),
177         }
178     }
179 
description() -> &'static str180     fn description() -> &'static str {
181         "detailed"
182     }
183 
recovery() -> Recovery184     fn recovery() -> Recovery {
185         Recovery::Allowed
186     }
187 }
188 
189 impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self190     fn new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self {
191         Self { cx, remaining_matcher: None, best_failure: None, root_span, result: None }
192     }
193 }
194 
195 /// Currently used by macro_rules! compilation to extract a little information from the `Failure` case.
196 pub struct FailureForwarder;
197 
198 impl<'matcher> Tracker<'matcher> for FailureForwarder {
199     type Failure = (Token, usize, &'static str);
200 
build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure201     fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
202         (tok, position, msg)
203     }
204 
description() -> &'static str205     fn description() -> &'static str {
206         "failure-forwarder"
207     }
208 }
209 
emit_frag_parse_err( mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>, parser: &Parser<'_>, orig_parser: &mut Parser<'_>, site_span: Span, arm_span: Span, kind: AstFragmentKind, )210 pub(super) fn emit_frag_parse_err(
211     mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>,
212     parser: &Parser<'_>,
213     orig_parser: &mut Parser<'_>,
214     site_span: Span,
215     arm_span: Span,
216     kind: AstFragmentKind,
217 ) {
218     // FIXME(davidtwco): avoid depending on the error message text
219     if parser.token == token::Eof
220         && let DiagnosticMessage::Str(message) = &e.message[0].0
221         && message.ends_with(", found `<eof>`")
222     {
223         let msg = &e.message[0];
224         e.message[0] = (
225             DiagnosticMessage::from(format!(
226                 "macro expansion ends with an incomplete expression: {}",
227                 message.replace(", found `<eof>`", ""),
228             )),
229             msg.1,
230         );
231         if !e.span.is_dummy() {
232             // early end of macro arm (#52866)
233             e.replace_span_with(parser.token.span.shrink_to_hi(), true);
234         }
235     }
236     if e.span.is_dummy() {
237         // Get around lack of span in error (#30128)
238         e.replace_span_with(site_span, true);
239         if !parser.sess.source_map().is_imported(arm_span) {
240             e.span_label(arm_span, "in this macro arm");
241         }
242     } else if parser.sess.source_map().is_imported(parser.token.span) {
243         e.span_label(site_span, "in this macro invocation");
244     }
245     match kind {
246         // Try a statement if an expression is wanted but failed and suggest adding `;` to call.
247         AstFragmentKind::Expr => match parse_ast_fragment(orig_parser, AstFragmentKind::Stmts) {
248             Err(err) => err.cancel(),
249             Ok(_) => {
250                 e.note(
251                     "the macro call doesn't expand to an expression, but it can expand to a statement",
252                 );
253 
254                 if parser.token == token::Semi {
255                     if let Ok(snippet) = parser.sess.source_map().span_to_snippet(site_span) {
256                         e.span_suggestion_verbose(
257                             site_span,
258                             "surround the macro invocation with `{}` to interpret the expansion as a statement",
259                             format!("{{ {}; }}", snippet),
260                             Applicability::MaybeIncorrect,
261                         );
262                     }
263                 } else {
264                     e.span_suggestion_verbose(
265                         site_span.shrink_to_hi(),
266                         "add `;` to interpret the expansion as a statement",
267                         ";",
268                         Applicability::MaybeIncorrect,
269                     );
270                 }
271             }
272         },
273         _ => annotate_err_with_kind(&mut e, kind, site_span),
274     };
275     e.emit();
276 }
277 
annotate_err_with_kind(err: &mut Diagnostic, kind: AstFragmentKind, span: Span)278 pub(crate) fn annotate_err_with_kind(err: &mut Diagnostic, kind: AstFragmentKind, span: Span) {
279     match kind {
280         AstFragmentKind::Ty => {
281             err.span_label(span, "this macro call doesn't expand to a type");
282         }
283         AstFragmentKind::Pat => {
284             err.span_label(span, "this macro call doesn't expand to a pattern");
285         }
286         _ => {}
287     };
288 }
289 
290 #[derive(Subdiagnostic)]
291 enum ExplainDocComment {
292     #[label(expand_explain_doc_comment_inner)]
293     Inner {
294         #[primary_span]
295         span: Span,
296     },
297     #[label(expand_explain_doc_comment_outer)]
298     Outer {
299         #[primary_span]
300         span: Span,
301     },
302 }
303 
annotate_doc_comment(err: &mut Diagnostic, sm: &SourceMap, span: Span)304 pub(super) fn annotate_doc_comment(err: &mut Diagnostic, sm: &SourceMap, span: Span) {
305     if let Ok(src) = sm.span_to_snippet(span) {
306         if src.starts_with("///") || src.starts_with("/**") {
307             err.subdiagnostic(ExplainDocComment::Outer { span });
308         } else if src.starts_with("//!") || src.starts_with("/*!") {
309             err.subdiagnostic(ExplainDocComment::Inner { span });
310         }
311     }
312 }
313 
314 /// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For
315 /// other tokens, this is "unexpected token...".
parse_failure_msg(tok: &Token) -> Cow<'static, str>316 pub(super) fn parse_failure_msg(tok: &Token) -> Cow<'static, str> {
317     match tok.kind {
318         token::Eof => Cow::from("unexpected end of macro invocation"),
319         _ => Cow::from(format!("no rules expected the token `{}`", pprust::token_to_string(tok))),
320     }
321 }
322