Skip to content

Commit ea468f4

Browse files
committed
Allow skipping extra paren insertion during AST pretty-printing
Fixes rust-lang#74616 Makes progress towards rust-lang#43081 Unblocks PR rust-lang#76130 When pretty-printing an AST node, we may insert additional parenthesis to ensure that precedence is properly preserved in code we output. However, the proc macro implementation relies on comparing a pretty-printed AST node to the captured `TokenStream`. Inserting extra parenthesis changes the structure of the reparsed `TokenStream`, making the comparison fail. This PR refactors the AST pretty-printing code to allow skipping the insertion of additional parenthesis. Several freestanding methods are moved to trait methods on `PrintState`, which keep track of an internal `insert_extra_parens` flag. This flag is normally `true`, but we expose a public method which allows pretty-printing a nonterminal with `insert_extra_parens = false`. To avoid changing the public interface of `rustc_ast_pretty`, the freestanding `_to_string` methods are changed to delegate to a newly-crated `State`. The main pretty-printing code is moved to a new `state` module to ensure that it does not accidentally call any of these public helper functions (instead, the internal functions with the same name should be used).
1 parent a20ae89 commit ea468f4

File tree

6 files changed

+219
-12
lines changed

6 files changed

+219
-12
lines changed

compiler/rustc_ast_pretty/src/pprust/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ use rustc_ast as ast;
88
use rustc_ast::token::{Nonterminal, Token, TokenKind};
99
use rustc_ast::tokenstream::{TokenStream, TokenTree};
1010

11+
pub fn nonterminal_to_string_no_extra_parens(nt: &Nonterminal) -> String {
12+
let state = State::without_insert_extra_parens();
13+
state.nonterminal_to_string(nt)
14+
}
15+
1116
pub fn nonterminal_to_string(nt: &Nonterminal) -> String {
1217
State::new().nonterminal_to_string(nt)
1318
}

compiler/rustc_ast_pretty/src/pprust/state.rs

+21-5
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ use rustc_span::{BytePos, FileName, Span};
2020

2121
use std::borrow::Cow;
2222

23-
#[cfg(test)]
24-
mod tests;
25-
2623
pub enum MacHeader<'a> {
2724
Path(&'a ast::Path),
2825
Keyword(&'static str),
@@ -91,6 +88,13 @@ pub struct State<'a> {
9188
comments: Option<Comments<'a>>,
9289
ann: &'a (dyn PpAnn + 'a),
9390
is_expanded: bool,
91+
// If `true`, additional parenthesis (separate from `ExprKind::Paren`)
92+
// are inserted to ensure that proper precedence is preserved
93+
// in the pretty-printed output.
94+
//
95+
// This is usually `true`, except when performing the pretty-print/reparse
96+
// check in `nt_to_tokenstream`
97+
insert_extra_parens: bool,
9498
}
9599

96100
crate const INDENT_UNIT: usize = 4;
@@ -112,6 +116,7 @@ pub fn print_crate<'a>(
112116
comments: Some(Comments::new(sm, filename, input)),
113117
ann,
114118
is_expanded,
119+
insert_extra_parens: true,
115120
};
116121

117122
if is_expanded && has_injected_crate {
@@ -225,7 +230,7 @@ pub fn literal_to_string(lit: token::Lit) -> String {
225230
}
226231

227232
fn visibility_qualified(vis: &ast::Visibility, s: &str) -> String {
228-
format!("{}{}", State::new().to_string(|s| s.print_visibility(vis)), s)
233+
format!("{}{}", State::new().to_string(|s| s.print_visibility(vis)), s)
229234
}
230235

231236
impl std::ops::Deref for State<'_> {
@@ -242,6 +247,7 @@ impl std::ops::DerefMut for State<'_> {
242247
}
243248

244249
pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::DerefMut {
250+
fn insert_extra_parens(&self) -> bool;
245251
fn comments(&mut self) -> &mut Option<Comments<'a>>;
246252
fn print_ident(&mut self, ident: Ident);
247253
fn print_generic_args(&mut self, args: &ast::GenericArgs, colons_before_params: bool);
@@ -827,12 +833,16 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
827833

828834
fn to_string(&self, f: impl FnOnce(&mut State<'_>)) -> String {
829835
let mut printer = State::new();
836+
printer.insert_extra_parens = self.insert_extra_parens();
830837
f(&mut printer);
831838
printer.s.eof()
832839
}
833840
}
834841

835842
impl<'a> PrintState<'a> for State<'a> {
843+
fn insert_extra_parens(&self) -> bool {
844+
self.insert_extra_parens
845+
}
836846
fn comments(&mut self) -> &mut Option<Comments<'a>> {
837847
&mut self.comments
838848
}
@@ -874,9 +884,14 @@ impl<'a> State<'a> {
874884
comments: None,
875885
ann: &NoAnn,
876886
is_expanded: false,
887+
insert_extra_parens: true,
877888
}
878889
}
879890

891+
pub(super) fn without_insert_extra_parens() -> State<'a> {
892+
State { insert_extra_parens: false, ..State::new() }
893+
}
894+
880895
// Synthesizes a comment that was not textually present in the original source
881896
// file.
882897
pub fn synth_comment(&mut self, text: String) {
@@ -1679,7 +1694,8 @@ impl<'a> State<'a> {
16791694
}
16801695

16811696
/// Prints `expr` or `(expr)` when `needs_par` holds.
1682-
fn print_expr_cond_paren(&mut self, expr: &ast::Expr, needs_par: bool) {
1697+
fn print_expr_cond_paren(&mut self, expr: &ast::Expr, mut needs_par: bool) {
1698+
needs_par &= self.insert_extra_parens;
16831699
if needs_par {
16841700
self.popen();
16851701
}

compiler/rustc_hir_pretty/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ impl std::ops::DerefMut for State<'_> {
141141
}
142142

143143
impl<'a> PrintState<'a> for State<'a> {
144+
fn insert_extra_parens(&self) -> bool {
145+
true
146+
}
144147
fn comments(&mut self) -> &mut Option<Comments<'a>> {
145148
&mut self.comments
146149
}

compiler/rustc_parse/src/lib.rs

+30-7
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,11 @@ pub fn nt_to_tokenstream(nt: &Nonterminal, sess: &ParseSess, span: Span) -> Toke
297297
};
298298

299299
// FIXME(#43081): Avoid this pretty-print + reparse hack
300-
let source = pprust::nonterminal_to_string(nt);
300+
// Pretty-print the AST struct without inserting any parenthesis
301+
// beyond those explicitly written by the user (e.g. `ExpnKind::Paren`).
302+
// The resulting stream may have incorrect precedence, but it's only
303+
// ever used for a comparison against the capture tokenstream.
304+
let source = pprust::nonterminal_to_string_no_extra_parens(nt);
301305
let filename = FileName::macro_expansion_source_code(&source);
302306
let reparsed_tokens = parse_stream_from_source_str(filename, source, sess, Some(span));
303307

@@ -325,9 +329,28 @@ pub fn nt_to_tokenstream(nt: &Nonterminal, sess: &ParseSess, span: Span) -> Toke
325329
// modifications, including adding/removing typically non-semantic
326330
// tokens such as extra braces and commas, don't happen.
327331
if let Some(tokens) = tokens {
332+
// If the streams match, then the AST hasn't been modified. Return the captured
333+
// `TokenStream`.
328334
if tokenstream_probably_equal_for_proc_macro(&tokens, &reparsed_tokens, sess) {
329335
return tokens;
330336
}
337+
338+
// The check failed. This time, we pretty-print the AST struct with parenthesis
339+
// inserted to preserve precedence. This may cause `None`-delimiters in the captured
340+
// token stream to match up with inserted parenthesis in the reparsed stream.
341+
let source_with_parens = pprust::nonterminal_to_string(nt);
342+
let filename_with_parens = FileName::macro_expansion_source_code(&source_with_parens);
343+
let tokens_with_parens = parse_stream_from_source_str(
344+
filename_with_parens,
345+
source_with_parens,
346+
sess,
347+
Some(span),
348+
);
349+
350+
if tokenstream_probably_equal_for_proc_macro(&tokens, &tokens_with_parens, sess) {
351+
return tokens;
352+
}
353+
331354
info!(
332355
"cached tokens found, but they're not \"probably equal\", \
333356
going with stringified version"
@@ -489,12 +512,12 @@ pub fn tokentree_probably_equal_for_proc_macro(
489512
(TokenTree::Token(token), TokenTree::Token(reparsed_token)) => {
490513
token_probably_equal_for_proc_macro(token, reparsed_token)
491514
}
492-
(
493-
TokenTree::Delimited(_, delim, tokens),
494-
TokenTree::Delimited(_, reparsed_delim, reparsed_tokens),
495-
) => {
496-
delim == reparsed_delim
497-
&& tokenstream_probably_equal_for_proc_macro(tokens, reparsed_tokens, sess)
515+
(TokenTree::Delimited(_, delim, tts), TokenTree::Delimited(_, delim2, tts2)) => {
516+
// `NoDelim` delimiters can appear in the captured tokenstream, but not
517+
// in the reparsed tokenstream. Allow them to match with anything, so
518+
// that we check if the two streams are structurally equivalent.
519+
(delim == delim2 || *delim == DelimToken::NoDelim || *delim2 == DelimToken::NoDelim)
520+
&& tokenstream_probably_equal_for_proc_macro(&tts, &tts2, sess)
498521
}
499522
_ => false,
500523
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Regression test for issue #75734
2+
// Ensures that we don't lose tokens when pretty-printing would
3+
// normally insert extra parentheses.
4+
5+
// check-pass
6+
// aux-build:test-macros.rs
7+
// compile-flags: -Z span-debug
8+
9+
#![no_std] // Don't load unnecessary hygiene information from std
10+
extern crate std;
11+
12+
#[macro_use]
13+
extern crate test_macros;
14+
15+
macro_rules! mul_2 {
16+
($val:expr) => {
17+
print_bang!($val * 2);
18+
};
19+
}
20+
21+
22+
#[print_attr]
23+
fn main() {
24+
&|_: u8| {};
25+
mul_2!(1 + 1);
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
PRINT-ATTR INPUT (DISPLAY): fn main() { & | _ : u8 | { } ; mul_2 ! (1 + 1) ; }
2+
PRINT-ATTR INPUT (DEBUG): TokenStream [
3+
Ident {
4+
ident: "fn",
5+
span: $DIR/issue-75734-pp-paren.rs:23:1: 23:3 (#0),
6+
},
7+
Ident {
8+
ident: "main",
9+
span: $DIR/issue-75734-pp-paren.rs:23:4: 23:8 (#0),
10+
},
11+
Group {
12+
delimiter: Parenthesis,
13+
stream: TokenStream [],
14+
span: $DIR/issue-75734-pp-paren.rs:23:8: 23:10 (#0),
15+
},
16+
Group {
17+
delimiter: Brace,
18+
stream: TokenStream [
19+
Punct {
20+
ch: '&',
21+
spacing: Joint,
22+
span: $DIR/issue-75734-pp-paren.rs:24:5: 24:6 (#0),
23+
},
24+
Punct {
25+
ch: '|',
26+
spacing: Alone,
27+
span: $DIR/issue-75734-pp-paren.rs:24:6: 24:7 (#0),
28+
},
29+
Ident {
30+
ident: "_",
31+
span: $DIR/issue-75734-pp-paren.rs:24:7: 24:8 (#0),
32+
},
33+
Punct {
34+
ch: ':',
35+
spacing: Alone,
36+
span: $DIR/issue-75734-pp-paren.rs:24:8: 24:9 (#0),
37+
},
38+
Ident {
39+
ident: "u8",
40+
span: $DIR/issue-75734-pp-paren.rs:24:10: 24:12 (#0),
41+
},
42+
Punct {
43+
ch: '|',
44+
spacing: Alone,
45+
span: $DIR/issue-75734-pp-paren.rs:24:12: 24:13 (#0),
46+
},
47+
Group {
48+
delimiter: Brace,
49+
stream: TokenStream [],
50+
span: $DIR/issue-75734-pp-paren.rs:24:14: 24:16 (#0),
51+
},
52+
Punct {
53+
ch: ';',
54+
spacing: Alone,
55+
span: $DIR/issue-75734-pp-paren.rs:24:16: 24:17 (#0),
56+
},
57+
Ident {
58+
ident: "mul_2",
59+
span: $DIR/issue-75734-pp-paren.rs:25:5: 25:10 (#0),
60+
},
61+
Punct {
62+
ch: '!',
63+
spacing: Alone,
64+
span: $DIR/issue-75734-pp-paren.rs:25:10: 25:11 (#0),
65+
},
66+
Group {
67+
delimiter: Parenthesis,
68+
stream: TokenStream [
69+
Literal {
70+
kind: Integer,
71+
symbol: "1",
72+
suffix: None,
73+
span: $DIR/issue-75734-pp-paren.rs:25:12: 25:13 (#0),
74+
},
75+
Punct {
76+
ch: '+',
77+
spacing: Alone,
78+
span: $DIR/issue-75734-pp-paren.rs:25:14: 25:15 (#0),
79+
},
80+
Literal {
81+
kind: Integer,
82+
symbol: "1",
83+
suffix: None,
84+
span: $DIR/issue-75734-pp-paren.rs:25:16: 25:17 (#0),
85+
},
86+
],
87+
span: $DIR/issue-75734-pp-paren.rs:25:11: 25:18 (#0),
88+
},
89+
Punct {
90+
ch: ';',
91+
spacing: Alone,
92+
span: $DIR/issue-75734-pp-paren.rs:25:18: 25:19 (#0),
93+
},
94+
],
95+
span: $DIR/issue-75734-pp-paren.rs:23:11: 26:2 (#0),
96+
},
97+
]
98+
PRINT-BANG INPUT (DISPLAY): 1 + 1 * 2
99+
PRINT-BANG INPUT (DEBUG): TokenStream [
100+
Group {
101+
delimiter: None,
102+
stream: TokenStream [
103+
Literal {
104+
kind: Integer,
105+
symbol: "1",
106+
suffix: None,
107+
span: $DIR/issue-75734-pp-paren.rs:25:12: 25:13 (#0),
108+
},
109+
Punct {
110+
ch: '+',
111+
spacing: Alone,
112+
span: $DIR/issue-75734-pp-paren.rs:25:14: 25:15 (#0),
113+
},
114+
Literal {
115+
kind: Integer,
116+
symbol: "1",
117+
suffix: None,
118+
span: $DIR/issue-75734-pp-paren.rs:25:16: 25:17 (#0),
119+
},
120+
],
121+
span: $DIR/issue-75734-pp-paren.rs:17:21: 17:25 (#7),
122+
},
123+
Punct {
124+
ch: '*',
125+
spacing: Alone,
126+
span: $DIR/issue-75734-pp-paren.rs:17:26: 17:27 (#7),
127+
},
128+
Literal {
129+
kind: Integer,
130+
symbol: "2",
131+
suffix: None,
132+
span: $DIR/issue-75734-pp-paren.rs:17:28: 17:29 (#7),
133+
},
134+
]

0 commit comments

Comments
 (0)