]>
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 | |
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 | ||
49aad941 | 51 | let mut err = cx.struct_span_err(span, parse_failure_msg(&token)); |
487cf647 FG |
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. | |
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) | |
9c376795 | 109 | best_failure: Option<BestFailure>, |
487cf647 FG |
110 | root_span: Span, |
111 | result: Option<Box<dyn MacResult + 'cx>>, | |
112 | } | |
113 | ||
9c376795 FG |
114 | struct BestFailure { |
115 | token: Token, | |
116 | position_in_tokenstream: usize, | |
117 | msg: &'static str, | |
118 | remaining_matcher: MatcherLoc, | |
119 | } | |
120 | ||
121 | impl BestFailure { | |
122 | fn is_better_position(&self, position: usize) -> bool { | |
123 | position > self.position_in_tokenstream | |
124 | } | |
125 | } | |
126 | ||
487cf647 | 127 | impl<'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); | |
49aad941 | 173 | self.cx.struct_span_err(span, msg.as_str()).emit(); |
487cf647 FG |
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 | ||
189 | impl<'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. |
196 | pub struct FailureForwarder; | |
197 | ||
198 | impl<'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 |
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::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 | ||
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 | ||
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...". | |
316 | pub(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 | } |