]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_expand/src/mbe/diagnostics.rs
Update upstream source from tag 'upstream/1.70.0+dfsg1'
[rustc.git] / compiler / rustc_expand / src / mbe / diagnostics.rs
CommitLineData
487cf647
FG
1use crate::base::{DummyResult, ExtCtxt, MacResult};
2use crate::expand::{parse_ast_fragment, AstFragmentKind};
3use crate::mbe::{
4 macro_parser::{MatcherLoc, NamedParseResult, ParseResult::*, TtParser},
5 macro_rules::{try_match_macro, Tracker},
6};
353b0b11 7use rustc_ast::token::{self, Token, TokenKind};
487cf647
FG
8use rustc_ast::tokenstream::TokenStream;
9use rustc_ast_pretty::pprust;
10use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage};
11use rustc_parse::parser::{Parser, Recovery};
12use rustc_span::source_map::SourceMap;
13use rustc_span::symbol::Ident;
14use rustc_span::Span;
353b0b11 15use std::borrow::Cow;
487cf647
FG
16
17use super::macro_rules::{parser_from_cx, NoopTracker};
18
19pub(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
9c376795 45 let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure else {
487cf647
FG
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
353b0b11
FG
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
487cf647
FG
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.
105struct 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)
9c376795 109 best_failure: Option<BestFailure>,
487cf647
FG
110 root_span: Span,
111 result: Option<Box<dyn MacResult + 'cx>>,
112}
113
9c376795
FG
114struct BestFailure {
115 token: Token,
116 position_in_tokenstream: usize,
117 msg: &'static str,
118 remaining_matcher: MatcherLoc,
119}
120
121impl BestFailure {
122 fn is_better_position(&self, position: usize) -> bool {
123 position > self.position_in_tokenstream
124 }
125}
126
487cf647 127impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
9c376795
FG
128 type Failure = (Token, usize, &'static str);
129
130 fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
131 (tok, position, msg)
132 }
133
487cf647
FG
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
9c376795 142 fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
487cf647
FG
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 }
9c376795
FG
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,
487cf647 163 msg,
9c376795
FG
164 remaining_matcher: self
165 .remaining_matcher
487cf647
FG
166 .expect("must have collected matcher already")
167 .clone(),
9c376795 168 })
487cf647 169 }
9c376795 170 }
487cf647
FG
171 Error(err_sp, msg) => {
172 let span = err_sp.substitute_dummy(self.root_span);
173 self.cx.struct_span_err(span, msg).emit();
174 self.result = Some(DummyResult::any(span));
175 }
176 ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)),
177 }
178 }
179
180 fn description() -> &'static str {
181 "detailed"
182 }
183
184 fn recovery() -> Recovery {
185 Recovery::Allowed
186 }
187}
188
189impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
190 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
9c376795
FG
195/// Currently used by macro_rules! compilation to extract a little information from the `Failure` case.
196pub struct FailureForwarder;
197
198impl<'matcher> Tracker<'matcher> for FailureForwarder {
199 type Failure = (Token, usize, &'static str);
200
201 fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
202 (tok, position, msg)
203 }
204
205 fn description() -> &'static str {
206 "failure-forwarder"
207 }
208}
209
487cf647
FG
210pub(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::Str(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)
9c376795 233 e.replace_span_with(parser.token.span.shrink_to_hi(), true);
487cf647
FG
234 }
235 }
236 if e.span.is_dummy() {
237 // Get around lack of span in error (#30128)
9c376795 238 e.replace_span_with(site_span, true);
487cf647
FG
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 );
353b0b11
FG
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 }
487cf647
FG
271 }
272 },
273 _ => annotate_err_with_kind(&mut e, kind, site_span),
274 };
275 e.emit();
276}
277
278pub(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)]
291enum 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
304pub(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...".
316pub(super) fn parse_failure_msg(tok: &Token) -> String {
317 match tok.kind {
318 token::Eof => "unexpected end of macro invocation".to_string(),
319 _ => format!("no rules expected the token `{}`", pprust::token_to_string(tok),),
320 }
321}