]>
Commit | Line | Data |
---|---|---|
9fa01778 XL |
1 | //! The main parser interface. |
2 | ||
60c5eb7d | 3 | #![feature(crate_visibility_modifier)] |
ba9703b0 | 4 | #![feature(bindings_after_at)] |
1b1a35ee | 5 | #![feature(iter_order_by)] |
ba9703b0 | 6 | #![feature(or_patterns)] |
60c5eb7d | 7 | |
3dfed10e | 8 | use rustc_ast as ast; |
fc512014 | 9 | use rustc_ast::attr::HasAttrs; |
29967ef6 XL |
10 | use rustc_ast::token::{self, DelimToken, Nonterminal, Token, TokenKind}; |
11 | use rustc_ast::tokenstream::{self, LazyTokenStream, TokenStream, TokenTree}; | |
74b04a01 | 12 | use rustc_ast_pretty::pprust; |
fc512014 | 13 | use rustc_data_structures::fx::FxHashSet; |
e74abb32 | 14 | use rustc_data_structures::sync::Lrc; |
dfeec247 | 15 | use rustc_errors::{Diagnostic, FatalError, Level, PResult}; |
74b04a01 | 16 | use rustc_session::parse::ParseSess; |
3dfed10e | 17 | use rustc_span::{symbol::kw, FileName, SourceFile, Span, DUMMY_SP}; |
9346a6ac | 18 | |
3dfed10e | 19 | use smallvec::SmallVec; |
fc512014 | 20 | use std::cell::RefCell; |
3dfed10e | 21 | use std::mem; |
ba9703b0 | 22 | use std::path::Path; |
1a4d82fc | 23 | use std::str; |
223e47cc | 24 | |
3dfed10e | 25 | use tracing::{debug, info}; |
e74abb32 | 26 | |
29967ef6 | 27 | pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments"); |
9346a6ac | 28 | |
1a4d82fc | 29 | #[macro_use] |
223e47cc | 30 | pub mod parser; |
dfeec247 | 31 | use parser::{emit_unclosed_delims, make_unclosed_delims_error, Parser}; |
1a4d82fc | 32 | pub mod lexer; |
60c5eb7d | 33 | pub mod validate_attr; |
476ff2be | 34 | |
e1599b0c | 35 | // A bunch of utility functions of the form `parse_<thing>_from_<source>` |
223e47cc LB |
36 | // where <thing> includes crate, expr, item, stmt, tts, and one that |
37 | // uses a HOF to parse anything, and <source> includes file and | |
e1599b0c | 38 | // `source_str`. |
223e47cc | 39 | |
e74abb32 XL |
40 | /// A variant of 'panictry!' that works on a Vec<Diagnostic> instead of a single DiagnosticBuilder. |
41 | macro_rules! panictry_buffer { | |
dfeec247 | 42 | ($handler:expr, $e:expr) => {{ |
60c5eb7d | 43 | use rustc_errors::FatalError; |
dfeec247 | 44 | use std::result::Result::{Err, Ok}; |
e74abb32 XL |
45 | match $e { |
46 | Ok(e) => e, | |
47 | Err(errs) => { | |
48 | for e in errs { | |
49 | $handler.emit_diagnostic(&e); | |
50 | } | |
51 | FatalError.raise() | |
52 | } | |
53 | } | |
dfeec247 | 54 | }}; |
e74abb32 XL |
55 | } |
56 | ||
c30ab7b3 | 57 | pub fn parse_crate_from_file<'a>(input: &Path, sess: &'a ParseSess) -> PResult<'a, ast::Crate> { |
ba9703b0 | 58 | let mut parser = new_parser_from_file(sess, input, None); |
54a0048b | 59 | parser.parse_crate_mod() |
223e47cc LB |
60 | } |
61 | ||
dfeec247 XL |
62 | pub fn parse_crate_attrs_from_file<'a>( |
63 | input: &Path, | |
64 | sess: &'a ParseSess, | |
65 | ) -> PResult<'a, Vec<ast::Attribute>> { | |
ba9703b0 | 66 | let mut parser = new_parser_from_file(sess, input, None); |
54a0048b | 67 | parser.parse_inner_attributes() |
1a4d82fc JJ |
68 | } |
69 | ||
dfeec247 XL |
70 | pub fn parse_crate_from_source_str( |
71 | name: FileName, | |
72 | source: String, | |
73 | sess: &ParseSess, | |
74 | ) -> PResult<'_, ast::Crate> { | |
c30ab7b3 | 75 | new_parser_from_source_str(sess, name, source).parse_crate_mod() |
223e47cc LB |
76 | } |
77 | ||
dfeec247 XL |
78 | pub fn parse_crate_attrs_from_source_str( |
79 | name: FileName, | |
80 | source: String, | |
81 | sess: &ParseSess, | |
82 | ) -> PResult<'_, Vec<ast::Attribute>> { | |
c30ab7b3 | 83 | new_parser_from_source_str(sess, name, source).parse_inner_attributes() |
1a4d82fc JJ |
84 | } |
85 | ||
9fa01778 XL |
86 | pub fn parse_stream_from_source_str( |
87 | name: FileName, | |
88 | source: String, | |
89 | sess: &ParseSess, | |
90 | override_span: Option<Span>, | |
91 | ) -> TokenStream { | |
dfeec247 XL |
92 | let (stream, mut errors) = |
93 | source_file_to_stream(sess, sess.source_map().new_source_file(name, source), override_span); | |
e74abb32 | 94 | emit_unclosed_delims(&mut errors, &sess); |
9fa01778 | 95 | stream |
223e47cc LB |
96 | } |
97 | ||
9fa01778 XL |
98 | /// Creates a new parser from a source string. |
99 | pub fn new_parser_from_source_str(sess: &ParseSess, name: FileName, source: String) -> Parser<'_> { | |
a1dfa0c6 XL |
100 | panictry_buffer!(&sess.span_diagnostic, maybe_new_parser_from_source_str(sess, name, source)) |
101 | } | |
102 | ||
9fa01778 | 103 | /// Creates a new parser from a source string. Returns any buffered errors from lexing the initial |
a1dfa0c6 | 104 | /// token stream. |
dfeec247 XL |
105 | pub fn maybe_new_parser_from_source_str( |
106 | sess: &ParseSess, | |
107 | name: FileName, | |
108 | source: String, | |
109 | ) -> Result<Parser<'_>, Vec<Diagnostic>> { | |
ba9703b0 | 110 | maybe_source_file_to_parser(sess, sess.source_map().new_source_file(name, source)) |
223e47cc LB |
111 | } |
112 | ||
e1599b0c | 113 | /// Creates a new parser, handling errors as appropriate if the file doesn't exist. |
1b1a35ee | 114 | /// If a span is given, that is used on an error as the source of the problem. |
ba9703b0 XL |
115 | pub fn new_parser_from_file<'a>(sess: &'a ParseSess, path: &Path, sp: Option<Span>) -> Parser<'a> { |
116 | source_file_to_parser(sess, file_to_source_file(sess, path, sp)) | |
223e47cc LB |
117 | } |
118 | ||
e1599b0c | 119 | /// Given a `source_file` and config, returns a parser. |
9fa01778 | 120 | fn source_file_to_parser(sess: &ParseSess, source_file: Lrc<SourceFile>) -> Parser<'_> { |
dfeec247 | 121 | panictry_buffer!(&sess.span_diagnostic, maybe_source_file_to_parser(sess, source_file)) |
a1dfa0c6 XL |
122 | } |
123 | ||
e1599b0c | 124 | /// Given a `source_file` and config, return a parser. Returns any buffered errors from lexing the |
a1dfa0c6 | 125 | /// initial token stream. |
9fa01778 XL |
126 | fn maybe_source_file_to_parser( |
127 | sess: &ParseSess, | |
128 | source_file: Lrc<SourceFile>, | |
129 | ) -> Result<Parser<'_>, Vec<Diagnostic>> { | |
b7449926 | 130 | let end_pos = source_file.end_pos; |
9fa01778 | 131 | let (stream, unclosed_delims) = maybe_file_to_stream(sess, source_file, None)?; |
dc9dc135 | 132 | let mut parser = stream_to_parser(sess, stream, None); |
9fa01778 | 133 | parser.unclosed_delims = unclosed_delims; |
74b04a01 | 134 | if parser.token == token::Eof { |
dc9dc135 | 135 | parser.token.span = Span::new(end_pos, end_pos, parser.token.span.ctxt()); |
c1a9b12d SL |
136 | } |
137 | ||
a1dfa0c6 | 138 | Ok(parser) |
970d7e83 LB |
139 | } |
140 | ||
e1599b0c | 141 | // Base abstractions |
970d7e83 | 142 | |
0731742a XL |
143 | /// Given a session and a path and an optional span (for error reporting), |
144 | /// add the path to the session's source_map and return the new source_file or | |
145 | /// error when a file can't be read. | |
dfeec247 XL |
146 | fn try_file_to_source_file( |
147 | sess: &ParseSess, | |
148 | path: &Path, | |
149 | spanopt: Option<Span>, | |
150 | ) -> Result<Lrc<SourceFile>, Diagnostic> { | |
151 | sess.source_map().load_file(path).map_err(|e| { | |
0731742a XL |
152 | let msg = format!("couldn't read {}: {}", path.display(), e); |
153 | let mut diag = Diagnostic::new(Level::Fatal, &msg); | |
154 | if let Some(sp) = spanopt { | |
155 | diag.set_span(sp); | |
156 | } | |
157 | diag | |
158 | }) | |
159 | } | |
160 | ||
970d7e83 | 161 | /// Given a session and a path and an optional span (for error reporting), |
e1599b0c | 162 | /// adds the path to the session's `source_map` and returns the new `source_file`. |
dfeec247 | 163 | fn file_to_source_file(sess: &ParseSess, path: &Path, spanopt: Option<Span>) -> Lrc<SourceFile> { |
0731742a | 164 | match try_file_to_source_file(sess, path, spanopt) { |
b7449926 | 165 | Ok(source_file) => source_file, |
0731742a | 166 | Err(d) => { |
e1599b0c | 167 | sess.span_diagnostic.emit_diagnostic(&d); |
0731742a | 168 | FatalError.raise(); |
223e47cc LB |
169 | } |
170 | } | |
171 | } | |
172 | ||
e1599b0c | 173 | /// Given a `source_file`, produces a sequence of token trees. |
9fa01778 XL |
174 | pub fn source_file_to_stream( |
175 | sess: &ParseSess, | |
176 | source_file: Lrc<SourceFile>, | |
177 | override_span: Option<Span>, | |
178 | ) -> (TokenStream, Vec<lexer::UnmatchedBrace>) { | |
a1dfa0c6 XL |
179 | panictry_buffer!(&sess.span_diagnostic, maybe_file_to_stream(sess, source_file, override_span)) |
180 | } | |
181 | ||
9fa01778 | 182 | /// Given a source file, produces a sequence of token trees. Returns any buffered errors from |
48663c56 | 183 | /// parsing the token stream. |
9fa01778 XL |
184 | pub fn maybe_file_to_stream( |
185 | sess: &ParseSess, | |
186 | source_file: Lrc<SourceFile>, | |
187 | override_span: Option<Span>, | |
188 | ) -> Result<(TokenStream, Vec<lexer::UnmatchedBrace>), Vec<Diagnostic>> { | |
1b1a35ee XL |
189 | let src = source_file.src.as_ref().unwrap_or_else(|| { |
190 | sess.span_diagnostic | |
191 | .bug(&format!("cannot lex `source_file` without source: {}", source_file.name)); | |
192 | }); | |
193 | ||
194 | let (token_trees, unmatched_braces) = | |
195 | lexer::parse_token_trees(sess, src.as_str(), source_file.start_pos, override_span); | |
a1dfa0c6 | 196 | |
48663c56 XL |
197 | match token_trees { |
198 | Ok(stream) => Ok((stream, unmatched_braces)), | |
a1dfa0c6 XL |
199 | Err(err) => { |
200 | let mut buffer = Vec::with_capacity(1); | |
201 | err.buffer(&mut buffer); | |
9fa01778 | 202 | // Not using `emit_unclosed_delims` to use `db.buffer` |
48663c56 | 203 | for unmatched in unmatched_braces { |
e74abb32 XL |
204 | if let Some(err) = make_unclosed_delims_error(unmatched, &sess) { |
205 | err.buffer(&mut buffer); | |
9fa01778 | 206 | } |
9fa01778 | 207 | } |
a1dfa0c6 XL |
208 | Err(buffer) |
209 | } | |
210 | } | |
970d7e83 LB |
211 | } |
212 | ||
e1599b0c | 213 | /// Given a stream and the `ParseSess`, produces a parser. |
dc9dc135 XL |
214 | pub fn stream_to_parser<'a>( |
215 | sess: &'a ParseSess, | |
216 | stream: TokenStream, | |
217 | subparser_name: Option<&'static str>, | |
218 | ) -> Parser<'a> { | |
ba9703b0 | 219 | Parser::new(sess, stream, false, subparser_name) |
1a4d82fc JJ |
220 | } |
221 | ||
e74abb32 | 222 | /// Runs the given subparser `f` on the tokens of the given `attr`'s item. |
60c5eb7d | 223 | pub fn parse_in<'a, T>( |
e74abb32 | 224 | sess: &'a ParseSess, |
60c5eb7d XL |
225 | tts: TokenStream, |
226 | name: &'static str, | |
e74abb32 XL |
227 | mut f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>, |
228 | ) -> PResult<'a, T> { | |
ba9703b0 | 229 | let mut parser = Parser::new(sess, tts, false, Some(name)); |
e74abb32 XL |
230 | let result = f(&mut parser)?; |
231 | if parser.token != token::Eof { | |
232 | parser.unexpected()?; | |
233 | } | |
234 | Ok(result) | |
8faf50e0 XL |
235 | } |
236 | ||
e74abb32 XL |
237 | // NOTE(Centril): The following probably shouldn't be here but it acknowledges the |
238 | // fact that architecturally, we are using parsing (read on below to understand why). | |
239 | ||
240 | pub fn nt_to_tokenstream(nt: &Nonterminal, sess: &ParseSess, span: Span) -> TokenStream { | |
241 | // A `Nonterminal` is often a parsed AST item. At this point we now | |
242 | // need to convert the parsed AST to an actual token stream, e.g. | |
243 | // un-parse it basically. | |
244 | // | |
245 | // Unfortunately there's not really a great way to do that in a | |
246 | // guaranteed lossless fashion right now. The fallback here is to just | |
247 | // stringify the AST node and reparse it, but this loses all span | |
248 | // information. | |
249 | // | |
250 | // As a result, some AST nodes are annotated with the token stream they | |
251 | // came from. Here we attempt to extract these lossless token streams | |
252 | // before we fall back to the stringification. | |
29967ef6 XL |
253 | |
254 | let convert_tokens = | |
fc512014 | 255 | |tokens: Option<&LazyTokenStream>| tokens.as_ref().map(|t| t.create_token_stream()); |
29967ef6 | 256 | |
e74abb32 | 257 | let tokens = match *nt { |
29967ef6 | 258 | Nonterminal::NtItem(ref item) => prepend_attrs(&item.attrs, item.tokens.as_ref()), |
fc512014 XL |
259 | Nonterminal::NtBlock(ref block) => convert_tokens(block.tokens.as_ref()), |
260 | Nonterminal::NtStmt(ref stmt) => prepend_attrs(stmt.attrs(), stmt.tokens()), | |
261 | Nonterminal::NtPat(ref pat) => convert_tokens(pat.tokens.as_ref()), | |
262 | Nonterminal::NtTy(ref ty) => convert_tokens(ty.tokens.as_ref()), | |
e74abb32 XL |
263 | Nonterminal::NtIdent(ident, is_raw) => { |
264 | Some(tokenstream::TokenTree::token(token::Ident(ident.name, is_raw), ident.span).into()) | |
265 | } | |
266 | Nonterminal::NtLifetime(ident) => { | |
267 | Some(tokenstream::TokenTree::token(token::Lifetime(ident.name), ident.span).into()) | |
8faf50e0 | 268 | } |
fc512014 XL |
269 | Nonterminal::NtMeta(ref attr) => convert_tokens(attr.tokens.as_ref()), |
270 | Nonterminal::NtPath(ref path) => convert_tokens(path.tokens.as_ref()), | |
271 | Nonterminal::NtVis(ref vis) => convert_tokens(vis.tokens.as_ref()), | |
dfeec247 | 272 | Nonterminal::NtTT(ref tt) => Some(tt.clone().into()), |
1b1a35ee | 273 | Nonterminal::NtExpr(ref expr) | Nonterminal::NtLiteral(ref expr) => { |
f9f354fc XL |
274 | if expr.tokens.is_none() { |
275 | debug!("missing tokens for expr {:?}", expr); | |
276 | } | |
29967ef6 | 277 | prepend_attrs(&expr.attrs, expr.tokens.as_ref()) |
f9f354fc | 278 | } |
e74abb32 XL |
279 | }; |
280 | ||
fc512014 XL |
281 | // Caches the stringification of 'good' `TokenStreams` which passed |
282 | // `tokenstream_probably_equal_for_proc_macro`. This allows us to avoid | |
283 | // repeatedly stringifying and comparing the same `TokenStream` for deeply | |
284 | // nested nonterminals. | |
285 | // | |
286 | // We cache by the strinification instead of the `TokenStream` to avoid | |
287 | // needing to implement `Hash` for `TokenStream`. Note that it's possible to | |
288 | // have two distinct `TokenStream`s that stringify to the same result | |
289 | // (e.g. if they differ only in hygiene information). However, any | |
290 | // information lost during the stringification process is also intentionally | |
291 | // ignored by `tokenstream_probably_equal_for_proc_macro`, so it's fine | |
292 | // that a single cache entry may 'map' to multiple distinct `TokenStream`s. | |
293 | // | |
294 | // This is a temporary hack to prevent compilation blowup on certain inputs. | |
295 | // The entire pretty-print/retokenize process will be removed soon. | |
296 | thread_local! { | |
297 | static GOOD_TOKEN_CACHE: RefCell<FxHashSet<String>> = Default::default(); | |
298 | } | |
299 | ||
e74abb32 | 300 | // FIXME(#43081): Avoid this pretty-print + reparse hack |
29967ef6 XL |
301 | // Pretty-print the AST struct without inserting any parenthesis |
302 | // beyond those explicitly written by the user (e.g. `ExpnKind::Paren`). | |
303 | // The resulting stream may have incorrect precedence, but it's only | |
304 | // ever used for a comparison against the capture tokenstream. | |
305 | let source = pprust::nonterminal_to_string_no_extra_parens(nt); | |
e74abb32 | 306 | let filename = FileName::macro_expansion_source_code(&source); |
fc512014 | 307 | let reparsed_tokens = parse_stream_from_source_str(filename, source.clone(), sess, Some(span)); |
e74abb32 XL |
308 | |
309 | // During early phases of the compiler the AST could get modified | |
310 | // directly (e.g., attributes added or removed) and the internal cache | |
311 | // of tokens my not be invalidated or updated. Consequently if the | |
312 | // "lossless" token stream disagrees with our actual stringification | |
313 | // (which has historically been much more battle-tested) then we go | |
314 | // with the lossy stream anyway (losing span information). | |
315 | // | |
316 | // Note that the comparison isn't `==` here to avoid comparing spans, | |
317 | // but it *also* is a "probable" equality which is a pretty weird | |
318 | // definition. We mostly want to catch actual changes to the AST | |
319 | // like a `#[cfg]` being processed or some weird `macro_rules!` | |
320 | // expansion. | |
321 | // | |
322 | // What we *don't* want to catch is the fact that a user-defined | |
323 | // literal like `0xf` is stringified as `15`, causing the cached token | |
324 | // stream to not be literal `==` token-wise (ignoring spans) to the | |
325 | // token stream we got from stringification. | |
326 | // | |
327 | // Instead the "probably equal" check here is "does each token | |
328 | // recursively have the same discriminant?" We basically don't look at | |
329 | // the token values here and assume that such fine grained token stream | |
330 | // modifications, including adding/removing typically non-semantic | |
331 | // tokens such as extra braces and commas, don't happen. | |
332 | if let Some(tokens) = tokens { | |
fc512014 XL |
333 | if GOOD_TOKEN_CACHE.with(|cache| cache.borrow().contains(&source)) { |
334 | return tokens; | |
335 | } | |
336 | ||
29967ef6 XL |
337 | // Compare with a non-relaxed delim match to start. |
338 | if tokenstream_probably_equal_for_proc_macro(&tokens, &reparsed_tokens, sess, false) { | |
fc512014 | 339 | GOOD_TOKEN_CACHE.with(|cache| cache.borrow_mut().insert(source.clone())); |
dfeec247 | 340 | return tokens; |
e74abb32 | 341 | } |
29967ef6 XL |
342 | |
343 | // The check failed. This time, we pretty-print the AST struct with parenthesis | |
344 | // inserted to preserve precedence. This may cause `None`-delimiters in the captured | |
345 | // token stream to match up with inserted parenthesis in the reparsed stream. | |
346 | let source_with_parens = pprust::nonterminal_to_string(nt); | |
347 | let filename_with_parens = FileName::macro_expansion_source_code(&source_with_parens); | |
fc512014 XL |
348 | |
349 | if GOOD_TOKEN_CACHE.with(|cache| cache.borrow().contains(&source_with_parens)) { | |
350 | return tokens; | |
351 | } | |
352 | ||
29967ef6 XL |
353 | let reparsed_tokens_with_parens = parse_stream_from_source_str( |
354 | filename_with_parens, | |
355 | source_with_parens, | |
356 | sess, | |
357 | Some(span), | |
358 | ); | |
359 | ||
360 | // Compare with a relaxed delim match - we want inserted parenthesis in the | |
361 | // reparsed stream to match `None`-delimiters in the original stream. | |
362 | if tokenstream_probably_equal_for_proc_macro( | |
363 | &tokens, | |
364 | &reparsed_tokens_with_parens, | |
365 | sess, | |
366 | true, | |
367 | ) { | |
fc512014 | 368 | GOOD_TOKEN_CACHE.with(|cache| cache.borrow_mut().insert(source.clone())); |
29967ef6 XL |
369 | return tokens; |
370 | } | |
371 | ||
dfeec247 XL |
372 | info!( |
373 | "cached tokens found, but they're not \"probably equal\", \ | |
374 | going with stringified version" | |
375 | ); | |
29967ef6 XL |
376 | info!("cached tokens: {}", pprust::tts_to_string(&tokens)); |
377 | info!("reparsed tokens: {}", pprust::tts_to_string(&reparsed_tokens_with_parens)); | |
378 | ||
379 | info!("cached tokens debug: {:?}", tokens); | |
380 | info!("reparsed tokens debug: {:?}", reparsed_tokens_with_parens); | |
8faf50e0 | 381 | } |
1b1a35ee | 382 | reparsed_tokens |
e74abb32 | 383 | } |
8faf50e0 | 384 | |
3dfed10e XL |
385 | // See comments in `Nonterminal::to_tokenstream` for why we care about |
386 | // *probably* equal here rather than actual equality | |
387 | // | |
388 | // This is otherwise the same as `eq_unspanned`, only recursing with a | |
389 | // different method. | |
390 | pub fn tokenstream_probably_equal_for_proc_macro( | |
1b1a35ee XL |
391 | tokens: &TokenStream, |
392 | reparsed_tokens: &TokenStream, | |
3dfed10e | 393 | sess: &ParseSess, |
29967ef6 | 394 | relaxed_delim_match: bool, |
3dfed10e XL |
395 | ) -> bool { |
396 | // When checking for `probably_eq`, we ignore certain tokens that aren't | |
397 | // preserved in the AST. Because they are not preserved, the pretty | |
398 | // printer arbitrarily adds or removes them when printing as token | |
399 | // streams, making a comparison between a token stream generated from an | |
400 | // AST and a token stream which was parsed into an AST more reliable. | |
401 | fn semantic_tree(tree: &TokenTree) -> bool { | |
402 | if let TokenTree::Token(token) = tree { | |
403 | if let | |
404 | // The pretty printer tends to add trailing commas to | |
405 | // everything, and in particular, after struct fields. | |
406 | | token::Comma | |
3dfed10e XL |
407 | // The pretty printer collapses many semicolons into one. |
408 | | token::Semi | |
1b1a35ee XL |
409 | // We don't preserve leading `|` tokens in patterns, so |
410 | // we ignore them entirely | |
411 | | token::BinOp(token::BinOpToken::Or) | |
412 | // We don't preserve trailing '+' tokens in trait bounds, | |
413 | // so we ignore them entirely | |
414 | | token::BinOp(token::BinOpToken::Plus) | |
3dfed10e XL |
415 | // The pretty printer can turn `$crate` into `::crate_name` |
416 | | token::ModSep = token.kind { | |
417 | return false; | |
418 | } | |
419 | } | |
420 | true | |
421 | } | |
422 | ||
423 | // When comparing two `TokenStream`s, we ignore the `IsJoint` information. | |
424 | // | |
425 | // However, `rustc_parse::lexer::tokentrees::TokenStreamBuilder` will | |
426 | // use `Token.glue` on adjacent tokens with the proper `IsJoint`. | |
427 | // Since we are ignoreing `IsJoint`, a 'glued' token (e.g. `BinOp(Shr)`) | |
428 | // and its 'split'/'unglued' compoenents (e.g. `Gt, Gt`) are equivalent | |
429 | // when determining if two `TokenStream`s are 'probably equal'. | |
430 | // | |
431 | // Therefore, we use `break_two_token_op` to convert all tokens | |
432 | // to the 'unglued' form (if it exists). This ensures that two | |
433 | // `TokenStream`s which differ only in how their tokens are glued | |
434 | // will be considered 'probably equal', which allows us to keep spans. | |
435 | // | |
436 | // This is important when the original `TokenStream` contained | |
437 | // extra spaces (e.g. `f :: < Vec < _ > > ( ) ;'). These extra spaces | |
438 | // will be omitted when we pretty-print, which can cause the original | |
439 | // and reparsed `TokenStream`s to differ in the assignment of `IsJoint`, | |
440 | // leading to some tokens being 'glued' together in one stream but not | |
441 | // the other. See #68489 for more details. | |
442 | fn break_tokens(tree: TokenTree) -> impl Iterator<Item = TokenTree> { | |
443 | // In almost all cases, we should have either zero or one levels | |
444 | // of 'unglueing'. However, in some unusual cases, we may need | |
445 | // to iterate breaking tokens mutliple times. For example: | |
446 | // '[BinOpEq(Shr)] => [Gt, Ge] -> [Gt, Gt, Eq]' | |
447 | let mut token_trees: SmallVec<[_; 2]>; | |
fc512014 | 448 | if let TokenTree::Token(token) = tree { |
3dfed10e | 449 | let mut out = SmallVec::<[_; 2]>::new(); |
fc512014 | 450 | out.push(token); |
3dfed10e XL |
451 | // Iterate to fixpoint: |
452 | // * We start off with 'out' containing our initial token, and `temp` empty | |
453 | // * If we are able to break any tokens in `out`, then `out` will have | |
454 | // at least one more element than 'temp', so we will try to break tokens | |
455 | // again. | |
456 | // * If we cannot break any tokens in 'out', we are done | |
457 | loop { | |
458 | let mut temp = SmallVec::<[_; 2]>::new(); | |
459 | let mut changed = false; | |
460 | ||
461 | for token in out.into_iter() { | |
462 | if let Some((first, second)) = token.kind.break_two_token_op() { | |
463 | temp.push(Token::new(first, DUMMY_SP)); | |
464 | temp.push(Token::new(second, DUMMY_SP)); | |
465 | changed = true; | |
466 | } else { | |
467 | temp.push(token); | |
468 | } | |
469 | } | |
470 | out = temp; | |
471 | if !changed { | |
472 | break; | |
473 | } | |
474 | } | |
475 | token_trees = out.into_iter().map(TokenTree::Token).collect(); | |
476 | } else { | |
477 | token_trees = SmallVec::new(); | |
478 | token_trees.push(tree); | |
479 | } | |
480 | token_trees.into_iter() | |
481 | } | |
482 | ||
1b1a35ee XL |
483 | fn expand_token(tree: TokenTree, sess: &ParseSess) -> impl Iterator<Item = TokenTree> { |
484 | // When checking tokenstreams for 'probable equality', we are comparing | |
485 | // a captured (from parsing) `TokenStream` to a reparsed tokenstream. | |
486 | // The reparsed Tokenstream will never have `None`-delimited groups, | |
487 | // since they are only ever inserted as a result of macro expansion. | |
488 | // Therefore, inserting a `None`-delimtied group here (when we | |
489 | // convert a nested `Nonterminal` to a tokenstream) would cause | |
490 | // a mismatch with the reparsed tokenstream. | |
491 | // | |
492 | // Note that we currently do not handle the case where the | |
493 | // reparsed stream has a `Parenthesis`-delimited group | |
494 | // inserted. This will cause a spurious mismatch: | |
495 | // issue #75734 tracks resolving this. | |
496 | ||
497 | let expanded: SmallVec<[_; 1]> = | |
498 | if let TokenTree::Token(Token { kind: TokenKind::Interpolated(nt), span }) = &tree { | |
499 | nt_to_tokenstream(nt, sess, *span) | |
500 | .into_trees() | |
501 | .flat_map(|t| expand_token(t, sess)) | |
502 | .collect() | |
503 | } else { | |
504 | // Filter before and after breaking tokens, | |
505 | // since we may want to ignore both glued and unglued tokens. | |
506 | std::iter::once(tree) | |
507 | .filter(semantic_tree) | |
508 | .flat_map(break_tokens) | |
509 | .filter(semantic_tree) | |
510 | .collect() | |
511 | }; | |
512 | expanded.into_iter() | |
513 | } | |
3dfed10e XL |
514 | |
515 | // Break tokens after we expand any nonterminals, so that we break tokens | |
516 | // that are produced as a result of nonterminal expansion. | |
1b1a35ee XL |
517 | let tokens = tokens.trees().flat_map(|t| expand_token(t, sess)); |
518 | let reparsed_tokens = reparsed_tokens.trees().flat_map(|t| expand_token(t, sess)); | |
519 | ||
29967ef6 XL |
520 | tokens.eq_by(reparsed_tokens, |t, rt| { |
521 | tokentree_probably_equal_for_proc_macro(&t, &rt, sess, relaxed_delim_match) | |
522 | }) | |
3dfed10e XL |
523 | } |
524 | ||
525 | // See comments in `Nonterminal::to_tokenstream` for why we care about | |
526 | // *probably* equal here rather than actual equality | |
527 | // | |
528 | // This is otherwise the same as `eq_unspanned`, only recursing with a | |
529 | // different method. | |
530 | pub fn tokentree_probably_equal_for_proc_macro( | |
1b1a35ee XL |
531 | token: &TokenTree, |
532 | reparsed_token: &TokenTree, | |
3dfed10e | 533 | sess: &ParseSess, |
29967ef6 | 534 | relaxed_delim_match: bool, |
3dfed10e | 535 | ) -> bool { |
1b1a35ee XL |
536 | match (token, reparsed_token) { |
537 | (TokenTree::Token(token), TokenTree::Token(reparsed_token)) => { | |
538 | token_probably_equal_for_proc_macro(token, reparsed_token) | |
3dfed10e | 539 | } |
1b1a35ee XL |
540 | ( |
541 | TokenTree::Delimited(_, delim, tokens), | |
542 | TokenTree::Delimited(_, reparsed_delim, reparsed_tokens), | |
29967ef6 XL |
543 | ) if delim == reparsed_delim => tokenstream_probably_equal_for_proc_macro( |
544 | tokens, | |
545 | reparsed_tokens, | |
546 | sess, | |
547 | relaxed_delim_match, | |
548 | ), | |
549 | (TokenTree::Delimited(_, DelimToken::NoDelim, tokens), reparsed_token) => { | |
550 | if relaxed_delim_match { | |
551 | if let TokenTree::Delimited(_, DelimToken::Paren, reparsed_tokens) = reparsed_token | |
552 | { | |
553 | if tokenstream_probably_equal_for_proc_macro( | |
554 | tokens, | |
555 | reparsed_tokens, | |
556 | sess, | |
557 | relaxed_delim_match, | |
558 | ) { | |
559 | return true; | |
560 | } | |
561 | } | |
562 | } | |
563 | tokens.len() == 1 | |
564 | && tokentree_probably_equal_for_proc_macro( | |
565 | &tokens.trees().next().unwrap(), | |
566 | reparsed_token, | |
567 | sess, | |
568 | relaxed_delim_match, | |
569 | ) | |
3dfed10e XL |
570 | } |
571 | _ => false, | |
572 | } | |
573 | } | |
574 | ||
575 | // See comments in `Nonterminal::to_tokenstream` for why we care about | |
576 | // *probably* equal here rather than actual equality | |
577 | fn token_probably_equal_for_proc_macro(first: &Token, other: &Token) -> bool { | |
578 | if mem::discriminant(&first.kind) != mem::discriminant(&other.kind) { | |
579 | return false; | |
580 | } | |
581 | use rustc_ast::token::TokenKind::*; | |
582 | match (&first.kind, &other.kind) { | |
583 | (&Eq, &Eq) | |
584 | | (&Lt, &Lt) | |
585 | | (&Le, &Le) | |
586 | | (&EqEq, &EqEq) | |
587 | | (&Ne, &Ne) | |
588 | | (&Ge, &Ge) | |
589 | | (&Gt, &Gt) | |
590 | | (&AndAnd, &AndAnd) | |
591 | | (&OrOr, &OrOr) | |
592 | | (&Not, &Not) | |
593 | | (&Tilde, &Tilde) | |
594 | | (&At, &At) | |
595 | | (&Dot, &Dot) | |
596 | | (&DotDot, &DotDot) | |
597 | | (&DotDotDot, &DotDotDot) | |
598 | | (&DotDotEq, &DotDotEq) | |
599 | | (&Comma, &Comma) | |
600 | | (&Semi, &Semi) | |
601 | | (&Colon, &Colon) | |
602 | | (&ModSep, &ModSep) | |
603 | | (&RArrow, &RArrow) | |
604 | | (&LArrow, &LArrow) | |
605 | | (&FatArrow, &FatArrow) | |
606 | | (&Pound, &Pound) | |
607 | | (&Dollar, &Dollar) | |
608 | | (&Question, &Question) | |
3dfed10e XL |
609 | | (&Eof, &Eof) => true, |
610 | ||
611 | (&BinOp(a), &BinOp(b)) | (&BinOpEq(a), &BinOpEq(b)) => a == b, | |
612 | ||
613 | (&OpenDelim(a), &OpenDelim(b)) | (&CloseDelim(a), &CloseDelim(b)) => a == b, | |
614 | ||
615 | (&DocComment(a1, a2, a3), &DocComment(b1, b2, b3)) => a1 == b1 && a2 == b2 && a3 == b3, | |
616 | ||
3dfed10e XL |
617 | (&Literal(a), &Literal(b)) => a == b, |
618 | ||
619 | (&Lifetime(a), &Lifetime(b)) => a == b, | |
620 | (&Ident(a, b), &Ident(c, d)) => { | |
621 | b == d && (a == c || a == kw::DollarCrate || c == kw::DollarCrate) | |
622 | } | |
623 | ||
624 | (&Interpolated(..), &Interpolated(..)) => panic!("Unexpanded Interpolated!"), | |
625 | ||
626 | _ => panic!("forgot to add a token?"), | |
627 | } | |
628 | } | |
629 | ||
e74abb32 | 630 | fn prepend_attrs( |
e74abb32 | 631 | attrs: &[ast::Attribute], |
29967ef6 | 632 | tokens: Option<&tokenstream::LazyTokenStream>, |
e74abb32 | 633 | ) -> Option<tokenstream::TokenStream> { |
29967ef6 | 634 | let tokens = tokens?.create_token_stream(); |
74b04a01 | 635 | if attrs.is_empty() { |
29967ef6 | 636 | return Some(tokens); |
e74abb32 XL |
637 | } |
638 | let mut builder = tokenstream::TokenStreamBuilder::new(); | |
639 | for attr in attrs { | |
29967ef6 XL |
640 | // FIXME: Correctly handle tokens for inner attributes. |
641 | // For now, we fall back to reparsing the original AST node | |
642 | if attr.style == ast::AttrStyle::Inner { | |
643 | return None; | |
e74abb32 | 644 | } |
29967ef6 | 645 | builder.push(attr.tokens()); |
8faf50e0 | 646 | } |
29967ef6 | 647 | builder.push(tokens); |
e74abb32 | 648 | Some(builder.build()) |
8faf50e0 | 649 | } |