]>
Commit | Line | Data |
---|---|---|
487cf647 FG |
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 | }; | |
353b0b11 | 7 | use rustc_ast::token::{self, Token, TokenKind}; |
487cf647 FG |
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; | |
353b0b11 | 15 | use std::borrow::Cow; |
487cf647 FG |
16 | |
17 | use super::macro_rules::{parser_from_cx, NoopTracker}; | |
18 | ||
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 | |
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. | |
112 | struct 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 |
121 | struct BestFailure { |
122 | token: Token, | |
123 | position_in_tokenstream: usize, | |
124 | msg: &'static str, | |
125 | remaining_matcher: MatcherLoc, | |
126 | } | |
127 | ||
128 | impl BestFailure { | |
129 | fn is_better_position(&self, position: usize) -> bool { | |
130 | position > self.position_in_tokenstream | |
131 | } | |
132 | } | |
133 | ||
487cf647 | 134 | impl<'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 | ||
196 | impl<'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. |
203 | pub struct FailureForwarder; | |
204 | ||
205 | impl<'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 |
217 | pub(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 | ||
285 | pub(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)] | |
298 | enum 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 | ||
311 | pub(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 | 323 | pub(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 | } |