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