]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_expand/src/mbe/diagnostics.rs
Rollup merge of #105362 - WaffleLapkin:🙅, r=oli-obk
[rust.git] / compiler / rustc_expand / src / mbe / diagnostics.rs
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 }