Skip to content

Commit 257d43d

Browse files
committed
Auto merge of #50069 - alexcrichton:fix-proc-macro, r=nrc
proc_macro: Stay on the "use the cache" path more Discovered in #50061 we're falling off the "happy path" of using a stringified token stream more often than we should. This was due to the fact that a user-written token like `0xf` is equality-different from the stringified token of `15` (despite being semantically equivalent). This patch updates the call to `eq_unspanned` with an even more awful solution, `probably_equal_for_proc_macro`, which ignores the value of each token and basically only compares the structure of the token stream, assuming that the AST doesn't change just one token at a time. While this is a step towards fixing #50061 there is still one regression from #49154 which needs to be fixed.
2 parents f4a3df1 + e934873 commit 257d43d

File tree

3 files changed

+127
-9
lines changed

3 files changed

+127
-9
lines changed

src/libsyntax/parse/token.rs

+91-9
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use tokenstream::{TokenStream, TokenTree};
2626
use tokenstream;
2727

2828
use std::{cmp, fmt};
29+
use std::mem;
2930
use rustc_data_structures::sync::{Lrc, Lock};
3031

3132
#[derive(Clone, RustcEncodable, RustcDecodable, PartialEq, Eq, Hash, Debug, Copy)]
@@ -88,6 +89,12 @@ impl Lit {
8889
ByteStr(_) | ByteStrRaw(..) => "byte string"
8990
}
9091
}
92+
93+
// See comments in `interpolated_to_tokenstream` for why we care about
94+
// *probably* equal here rather than actual equality
95+
fn probably_equal_for_proc_macro(&self, other: &Lit) -> bool {
96+
mem::discriminant(self) == mem::discriminant(other)
97+
}
9198
}
9299

93100
pub(crate) fn ident_can_begin_expr(ident: ast::Ident, is_raw: bool) -> bool {
@@ -530,14 +537,6 @@ impl Token {
530537
// stream they came from. Here we attempt to extract these
531538
// lossless token streams before we fall back to the
532539
// stringification.
533-
//
534-
// During early phases of the compiler, though, the AST could
535-
// get modified directly (e.g. attributes added or removed) and
536-
// the internal cache of tokens my not be invalidated or
537-
// updated. Consequently if the "lossless" token stream
538-
// disagrees with our actuall stringification (which has
539-
// historically been much more battle-tested) then we go with
540-
// the lossy stream anyway (losing span information).
541540
let mut tokens = None;
542541

543542
match nt.0 {
@@ -569,13 +568,96 @@ impl Token {
569568
let source = pprust::token_to_string(self);
570569
parse_stream_from_source_str(FileName::MacroExpansion, source, sess, Some(span))
571570
});
571+
572+
// During early phases of the compiler the AST could get modified
573+
// directly (e.g. attributes added or removed) and the internal cache
574+
// of tokens my not be invalidated or updated. Consequently if the
575+
// "lossless" token stream disagrees with our actual stringification
576+
// (which has historically been much more battle-tested) then we go
577+
// with the lossy stream anyway (losing span information).
578+
//
579+
// Note that the comparison isn't `==` here to avoid comparing spans,
580+
// but it *also* is a "probable" equality which is a pretty weird
581+
// definition. We mostly want to catch actual changes to the AST
582+
// like a `#[cfg]` being processed or some weird `macro_rules!`
583+
// expansion.
584+
//
585+
// What we *don't* want to catch is the fact that a user-defined
586+
// literal like `0xf` is stringified as `15`, causing the cached token
587+
// stream to not be literal `==` token-wise (ignoring spans) to the
588+
// token stream we got from stringification.
589+
//
590+
// Instead the "probably equal" check here is "does each token
591+
// recursively have the same discriminant?" We basically don't look at
592+
// the token values here and assume that such fine grained modifications
593+
// of token streams doesn't happen.
572594
if let Some(tokens) = tokens {
573-
if tokens.eq_unspanned(&tokens_for_real) {
595+
if tokens.probably_equal_for_proc_macro(&tokens_for_real) {
574596
return tokens
575597
}
576598
}
577599
return tokens_for_real
578600
}
601+
602+
// See comments in `interpolated_to_tokenstream` for why we care about
603+
// *probably* equal here rather than actual equality
604+
pub fn probably_equal_for_proc_macro(&self, other: &Token) -> bool {
605+
if mem::discriminant(self) != mem::discriminant(other) {
606+
return false
607+
}
608+
match (self, other) {
609+
(&Eq, &Eq) |
610+
(&Lt, &Lt) |
611+
(&Le, &Le) |
612+
(&EqEq, &EqEq) |
613+
(&Ne, &Ne) |
614+
(&Ge, &Ge) |
615+
(&Gt, &Gt) |
616+
(&AndAnd, &AndAnd) |
617+
(&OrOr, &OrOr) |
618+
(&Not, &Not) |
619+
(&Tilde, &Tilde) |
620+
(&At, &At) |
621+
(&Dot, &Dot) |
622+
(&DotDot, &DotDot) |
623+
(&DotDotDot, &DotDotDot) |
624+
(&DotDotEq, &DotDotEq) |
625+
(&DotEq, &DotEq) |
626+
(&Comma, &Comma) |
627+
(&Semi, &Semi) |
628+
(&Colon, &Colon) |
629+
(&ModSep, &ModSep) |
630+
(&RArrow, &RArrow) |
631+
(&LArrow, &LArrow) |
632+
(&FatArrow, &FatArrow) |
633+
(&Pound, &Pound) |
634+
(&Dollar, &Dollar) |
635+
(&Question, &Question) |
636+
(&Whitespace, &Whitespace) |
637+
(&Comment, &Comment) |
638+
(&Eof, &Eof) => true,
639+
640+
(&BinOp(a), &BinOp(b)) |
641+
(&BinOpEq(a), &BinOpEq(b)) => a == b,
642+
643+
(&OpenDelim(a), &OpenDelim(b)) |
644+
(&CloseDelim(a), &CloseDelim(b)) => a == b,
645+
646+
(&DocComment(a), &DocComment(b)) |
647+
(&Shebang(a), &Shebang(b)) => a == b,
648+
649+
(&Lifetime(a), &Lifetime(b)) => a.name == b.name,
650+
(&Ident(a, b), &Ident(c, d)) => a.name == c.name && b == d,
651+
652+
(&Literal(ref a, b), &Literal(ref c, d)) => {
653+
b == d && a.probably_equal_for_proc_macro(c)
654+
}
655+
656+
(&Interpolated(_), &Interpolated(_)) => false,
657+
658+
_ => panic!("forgot to add a token?"),
659+
}
660+
}
579661
}
580662

581663
#[derive(Clone, RustcEncodable, RustcDecodable, Eq, Hash)]

src/libsyntax/tokenstream.rs

+34
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,24 @@ impl TokenTree {
124124
}
125125
}
126126

127+
// See comments in `interpolated_to_tokenstream` for why we care about
128+
// *probably* equal here rather than actual equality
129+
//
130+
// This is otherwise the same as `eq_unspanned`, only recursing with a
131+
// different method.
132+
pub fn probably_equal_for_proc_macro(&self, other: &TokenTree) -> bool {
133+
match (self, other) {
134+
(&TokenTree::Token(_, ref tk), &TokenTree::Token(_, ref tk2)) => {
135+
tk.probably_equal_for_proc_macro(tk2)
136+
}
137+
(&TokenTree::Delimited(_, ref dl), &TokenTree::Delimited(_, ref dl2)) => {
138+
dl.delim == dl2.delim &&
139+
dl.stream().probably_equal_for_proc_macro(&dl2.stream())
140+
}
141+
(_, _) => false,
142+
}
143+
}
144+
127145
/// Retrieve the TokenTree's span.
128146
pub fn span(&self) -> Span {
129147
match *self {
@@ -250,6 +268,22 @@ impl TokenStream {
250268
t1.next().is_none() && t2.next().is_none()
251269
}
252270

271+
// See comments in `interpolated_to_tokenstream` for why we care about
272+
// *probably* equal here rather than actual equality
273+
//
274+
// This is otherwise the same as `eq_unspanned`, only recursing with a
275+
// different method.
276+
pub fn probably_equal_for_proc_macro(&self, other: &TokenStream) -> bool {
277+
let mut t1 = self.trees();
278+
let mut t2 = other.trees();
279+
for (t1, t2) in t1.by_ref().zip(t2.by_ref()) {
280+
if !t1.probably_equal_for_proc_macro(&t2) {
281+
return false;
282+
}
283+
}
284+
t1.next().is_none() && t2.next().is_none()
285+
}
286+
253287
/// Precondition: `self` consists of a single token tree.
254288
/// Returns true if the token tree is a joint operation w.r.t. `proc_macro::TokenNode`.
255289
pub fn as_tree(self) -> (TokenTree, bool /* joint? */) {

src/test/compile-fail-fulldeps/proc-macro/attribute-with-error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ use attribute_with_error::foo;
2121
fn test1() {
2222
let a: i32 = "foo";
2323
//~^ ERROR: mismatched types
24+
let b: i32 = "f'oo";
25+
//~^ ERROR: mismatched types
2426
}
2527

2628
fn test2() {

0 commit comments

Comments
 (0)