rustc_ast_pretty/pprust/state/fixup.rs
1use rustc_ast::Expr;
2use rustc_ast::util::{classify, parser};
3
4// The default amount of fixing is minimal fixing, so all fixups are set to `false` by `Default`.
5// Fixups should be turned on in a targeted fashion where needed.
6#[derive(Copy, Clone, Debug, Default)]
7pub(crate) struct FixupContext {
8 /// Print expression such that it can be parsed back as a statement
9 /// consisting of the original expression.
10 ///
11 /// The effect of this is for binary operators in statement position to set
12 /// `leftmost_subexpression_in_stmt` when printing their left-hand operand.
13 ///
14 /// ```ignore (illustrative)
15 /// (match x {}) - 1; // match needs parens when LHS of binary operator
16 ///
17 /// match x {}; // not when its own statement
18 /// ```
19 stmt: bool,
20
21 /// This is the difference between:
22 ///
23 /// ```ignore (illustrative)
24 /// (match x {}) - 1; // subexpression needs parens
25 ///
26 /// let _ = match x {} - 1; // no parens
27 /// ```
28 ///
29 /// There are 3 distinguishable contexts in which `print_expr` might be
30 /// called with the expression `$match` as its argument, where `$match`
31 /// represents an expression of kind `ExprKind::Match`:
32 ///
33 /// - stmt=false leftmost_subexpression_in_stmt=false
34 ///
35 /// Example: `let _ = $match - 1;`
36 ///
37 /// No parentheses required.
38 ///
39 /// - stmt=false leftmost_subexpression_in_stmt=true
40 ///
41 /// Example: `$match - 1;`
42 ///
43 /// Must parenthesize `($match)`, otherwise parsing back the output as a
44 /// statement would terminate the statement after the closing brace of
45 /// the match, parsing `-1;` as a separate statement.
46 ///
47 /// - stmt=true leftmost_subexpression_in_stmt=false
48 ///
49 /// Example: `$match;`
50 ///
51 /// No parentheses required.
52 leftmost_subexpression_in_stmt: bool,
53
54 /// Print expression such that it can be parsed as a match arm.
55 ///
56 /// This is almost equivalent to `stmt`, but the grammar diverges a tiny bit
57 /// between statements and match arms when it comes to braced macro calls.
58 /// Macro calls with brace delimiter terminate a statement without a
59 /// semicolon, but do not terminate a match-arm without comma.
60 ///
61 /// ```ignore (illustrative)
62 /// m! {} - 1; // two statements: a macro call followed by -1 literal
63 ///
64 /// match () {
65 /// _ => m! {} - 1, // binary subtraction operator
66 /// }
67 /// ```
68 match_arm: bool,
69
70 /// This is almost equivalent to `leftmost_subexpression_in_stmt`, other
71 /// than for braced macro calls.
72 ///
73 /// If we have `m! {} - 1` as an expression, the leftmost subexpression
74 /// `m! {}` will need to be parenthesized in the statement case but not the
75 /// match-arm case.
76 ///
77 /// ```ignore (illustrative)
78 /// (m! {}) - 1; // subexpression needs parens
79 ///
80 /// match () {
81 /// _ => m! {} - 1, // no parens
82 /// }
83 /// ```
84 leftmost_subexpression_in_match_arm: bool,
85
86 /// This is the difference between:
87 ///
88 /// ```ignore (illustrative)
89 /// if let _ = (Struct {}) {} // needs parens
90 ///
91 /// match () {
92 /// () if let _ = Struct {} => {} // no parens
93 /// }
94 /// ```
95 parenthesize_exterior_struct_lit: bool,
96}
97
98impl FixupContext {
99 /// Create the initial fixup for printing an expression in statement
100 /// position.
101 pub(crate) fn new_stmt() -> Self {
102 FixupContext { stmt: true, ..FixupContext::default() }
103 }
104
105 /// Create the initial fixup for printing an expression as the right-hand
106 /// side of a match arm.
107 pub(crate) fn new_match_arm() -> Self {
108 FixupContext { match_arm: true, ..FixupContext::default() }
109 }
110
111 /// Create the initial fixup for printing an expression as the "condition"
112 /// of an `if` or `while`. There are a few other positions which are
113 /// grammatically equivalent and also use this, such as the iterator
114 /// expression in `for` and the scrutinee in `match`.
115 pub(crate) fn new_cond() -> Self {
116 FixupContext { parenthesize_exterior_struct_lit: true, ..FixupContext::default() }
117 }
118
119 /// Transform this fixup into the one that should apply when printing the
120 /// leftmost subexpression of the current expression.
121 ///
122 /// The leftmost subexpression is any subexpression that has the same first
123 /// token as the current expression, but has a different last token.
124 ///
125 /// For example in `$a + $b` and `$a.method()`, the subexpression `$a` is a
126 /// leftmost subexpression.
127 ///
128 /// Not every expression has a leftmost subexpression. For example neither
129 /// `-$a` nor `[$a]` have one.
130 pub(crate) fn leftmost_subexpression(self) -> Self {
131 FixupContext {
132 stmt: false,
133 leftmost_subexpression_in_stmt: self.stmt || self.leftmost_subexpression_in_stmt,
134 match_arm: false,
135 leftmost_subexpression_in_match_arm: self.match_arm
136 || self.leftmost_subexpression_in_match_arm,
137 ..self
138 }
139 }
140
141 /// Transform this fixup into the one that should apply when printing a
142 /// leftmost subexpression followed by a `.` or `?` token, which confer
143 /// different statement boundary rules compared to other leftmost
144 /// subexpressions.
145 pub(crate) fn leftmost_subexpression_with_dot(self) -> Self {
146 FixupContext {
147 stmt: self.stmt || self.leftmost_subexpression_in_stmt,
148 leftmost_subexpression_in_stmt: false,
149 match_arm: self.match_arm || self.leftmost_subexpression_in_match_arm,
150 leftmost_subexpression_in_match_arm: false,
151 ..self
152 }
153 }
154
155 /// Transform this fixup into the one that should apply when printing any
156 /// subexpression that is neither a leftmost subexpression nor surrounded in
157 /// delimiters.
158 ///
159 /// This is for any subexpression that has a different first token than the
160 /// current expression, and is not surrounded by a paren/bracket/brace. For
161 /// example the `$b` in `$a + $b` and `-$b`, but not the one in `[$b]` or
162 /// `$a.f($b)`.
163 pub(crate) fn subsequent_subexpression(self) -> Self {
164 FixupContext {
165 stmt: false,
166 leftmost_subexpression_in_stmt: false,
167 match_arm: false,
168 leftmost_subexpression_in_match_arm: false,
169 ..self
170 }
171 }
172
173 /// Determine whether parentheses are needed around the given expression to
174 /// head off an unintended statement boundary.
175 ///
176 /// The documentation on `FixupContext::leftmost_subexpression_in_stmt` has
177 /// examples.
178 pub(crate) fn would_cause_statement_boundary(self, expr: &Expr) -> bool {
179 (self.leftmost_subexpression_in_stmt && !classify::expr_requires_semi_to_be_stmt(expr))
180 || (self.leftmost_subexpression_in_match_arm && classify::expr_is_complete(expr))
181 }
182
183 /// Determine whether parentheses are needed around the given `let`
184 /// scrutinee.
185 ///
186 /// In `if let _ = $e {}`, some examples of `$e` that would need parentheses
187 /// are:
188 ///
189 /// - `Struct {}.f()`, because otherwise the `{` would be misinterpreted
190 /// as the opening of the if's then-block.
191 ///
192 /// - `true && false`, because otherwise this would be misinterpreted as a
193 /// "let chain".
194 pub(crate) fn needs_par_as_let_scrutinee(self, expr: &Expr) -> bool {
195 self.parenthesize_exterior_struct_lit && parser::contains_exterior_struct_lit(expr)
196 || parser::needs_par_as_let_scrutinee(expr.precedence())
197 }
198}