Skip to content

Commit dd0c29c

Browse files
committed
Auto merge of rust-lang#14952 - lowr:fix/assignments-are-right-associative, r=HKalbasi
fix: assignment operators are right associative Fixes rust-lang#14944 Assignment operators, be they simple or complex, are right associative in Rust ([reference]). We need to consider that fact when computing [binding power][bp] of infix operators. The changes in `0072_destructuring_assignment.{rs,rast}` are unexpected, but I'm pretty sure it's a typo and fixed the `.rs` file accordingly. [reference]: https://doc.rust-lang.org/reference/expressions.html#expression-precedence [bp]: https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
2 parents 7f3bfc6 + f9c1a7d commit dd0c29c

File tree

5 files changed

+374
-75
lines changed

5 files changed

+374
-75
lines changed

crates/parser/src/grammar/expressions.rs

+51-38
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use crate::grammar::attributes::ATTRIBUTE_FIRST;
44

55
use super::*;
66

7-
pub(crate) use self::atom::{block_expr, match_arm_list};
8-
pub(super) use self::atom::{literal, LITERAL_FIRST};
7+
pub(crate) use atom::{block_expr, match_arm_list};
8+
pub(super) use atom::{literal, LITERAL_FIRST};
99

1010
#[derive(PartialEq, Eq)]
1111
pub(super) enum Semicolon {
@@ -188,47 +188,56 @@ struct Restrictions {
188188
prefer_stmt: bool,
189189
}
190190

191+
enum Associativity {
192+
Left,
193+
Right,
194+
}
195+
191196
/// Binding powers of operators for a Pratt parser.
192197
///
193198
/// See <https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html>
199+
///
200+
/// Note that Rust doesn't define associativity for some infix operators (e.g. `==` and `..`) and
201+
/// requires parentheses to disambiguate. We just treat them as left associative.
194202
#[rustfmt::skip]
195-
fn current_op(p: &Parser<'_>) -> (u8, SyntaxKind) {
196-
const NOT_AN_OP: (u8, SyntaxKind) = (0, T![@]);
203+
fn current_op(p: &Parser<'_>) -> (u8, SyntaxKind, Associativity) {
204+
use Associativity::*;
205+
const NOT_AN_OP: (u8, SyntaxKind, Associativity) = (0, T![@], Left);
197206
match p.current() {
198-
T![|] if p.at(T![||]) => (3, T![||]),
199-
T![|] if p.at(T![|=]) => (1, T![|=]),
200-
T![|] => (6, T![|]),
201-
T![>] if p.at(T![>>=]) => (1, T![>>=]),
202-
T![>] if p.at(T![>>]) => (9, T![>>]),
203-
T![>] if p.at(T![>=]) => (5, T![>=]),
204-
T![>] => (5, T![>]),
207+
T![|] if p.at(T![||]) => (3, T![||], Left),
208+
T![|] if p.at(T![|=]) => (1, T![|=], Right),
209+
T![|] => (6, T![|], Left),
210+
T![>] if p.at(T![>>=]) => (1, T![>>=], Right),
211+
T![>] if p.at(T![>>]) => (9, T![>>], Left),
212+
T![>] if p.at(T![>=]) => (5, T![>=], Left),
213+
T![>] => (5, T![>], Left),
205214
T![=] if p.at(T![=>]) => NOT_AN_OP,
206-
T![=] if p.at(T![==]) => (5, T![==]),
207-
T![=] => (1, T![=]),
208-
T![<] if p.at(T![<=]) => (5, T![<=]),
209-
T![<] if p.at(T![<<=]) => (1, T![<<=]),
210-
T![<] if p.at(T![<<]) => (9, T![<<]),
211-
T![<] => (5, T![<]),
212-
T![+] if p.at(T![+=]) => (1, T![+=]),
213-
T![+] => (10, T![+]),
214-
T![^] if p.at(T![^=]) => (1, T![^=]),
215-
T![^] => (7, T![^]),
216-
T![%] if p.at(T![%=]) => (1, T![%=]),
217-
T![%] => (11, T![%]),
218-
T![&] if p.at(T![&=]) => (1, T![&=]),
215+
T![=] if p.at(T![==]) => (5, T![==], Left),
216+
T![=] => (1, T![=], Right),
217+
T![<] if p.at(T![<=]) => (5, T![<=], Left),
218+
T![<] if p.at(T![<<=]) => (1, T![<<=], Right),
219+
T![<] if p.at(T![<<]) => (9, T![<<], Left),
220+
T![<] => (5, T![<], Left),
221+
T![+] if p.at(T![+=]) => (1, T![+=], Right),
222+
T![+] => (10, T![+], Left),
223+
T![^] if p.at(T![^=]) => (1, T![^=], Right),
224+
T![^] => (7, T![^], Left),
225+
T![%] if p.at(T![%=]) => (1, T![%=], Right),
226+
T![%] => (11, T![%], Left),
227+
T![&] if p.at(T![&=]) => (1, T![&=], Right),
219228
// If you update this, remember to update `expr_let()` too.
220-
T![&] if p.at(T![&&]) => (4, T![&&]),
221-
T![&] => (8, T![&]),
222-
T![/] if p.at(T![/=]) => (1, T![/=]),
223-
T![/] => (11, T![/]),
224-
T![*] if p.at(T![*=]) => (1, T![*=]),
225-
T![*] => (11, T![*]),
226-
T![.] if p.at(T![..=]) => (2, T![..=]),
227-
T![.] if p.at(T![..]) => (2, T![..]),
228-
T![!] if p.at(T![!=]) => (5, T![!=]),
229-
T![-] if p.at(T![-=]) => (1, T![-=]),
230-
T![-] => (10, T![-]),
231-
T![as] => (12, T![as]),
229+
T![&] if p.at(T![&&]) => (4, T![&&], Left),
230+
T![&] => (8, T![&], Left),
231+
T![/] if p.at(T![/=]) => (1, T![/=], Right),
232+
T![/] => (11, T![/], Left),
233+
T![*] if p.at(T![*=]) => (1, T![*=], Right),
234+
T![*] => (11, T![*], Left),
235+
T![.] if p.at(T![..=]) => (2, T![..=], Left),
236+
T![.] if p.at(T![..]) => (2, T![..], Left),
237+
T![!] if p.at(T![!=]) => (5, T![!=], Left),
238+
T![-] if p.at(T![-=]) => (1, T![-=], Right),
239+
T![-] => (10, T![-], Left),
240+
T![as] => (12, T![as], Left),
232241

233242
_ => NOT_AN_OP
234243
}
@@ -273,7 +282,7 @@ fn expr_bp(
273282

274283
loop {
275284
let is_range = p.at(T![..]) || p.at(T![..=]);
276-
let (op_bp, op) = current_op(p);
285+
let (op_bp, op, associativity) = current_op(p);
277286
if op_bp < bp {
278287
break;
279288
}
@@ -306,7 +315,11 @@ fn expr_bp(
306315
}
307316
}
308317

309-
expr_bp(p, None, Restrictions { prefer_stmt: false, ..r }, op_bp + 1);
318+
let op_bp = match associativity {
319+
Associativity::Left => op_bp + 1,
320+
Associativity::Right => op_bp,
321+
};
322+
expr_bp(p, None, Restrictions { prefer_stmt: false, ..r }, op_bp);
310323
lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR });
311324
}
312325
Some((lhs, BlockLike::NotBlock))

crates/parser/test_data/parser/ok/0028_operator_binding_power.rast

+269
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,273 @@ SOURCE_FILE
183183
COMMENT "//---&*1 - --2 * 9;"
184184
WHITESPACE "\n"
185185
R_CURLY "}"
186+
WHITESPACE "\n\n"
187+
FN
188+
FN_KW "fn"
189+
WHITESPACE " "
190+
NAME
191+
IDENT "right_associative"
192+
PARAM_LIST
193+
L_PAREN "("
194+
R_PAREN ")"
195+
WHITESPACE " "
196+
BLOCK_EXPR
197+
STMT_LIST
198+
L_CURLY "{"
199+
WHITESPACE "\n "
200+
EXPR_STMT
201+
BIN_EXPR
202+
PATH_EXPR
203+
PATH
204+
PATH_SEGMENT
205+
NAME_REF
206+
IDENT "a"
207+
WHITESPACE " "
208+
EQ "="
209+
WHITESPACE " "
210+
BIN_EXPR
211+
PATH_EXPR
212+
PATH
213+
PATH_SEGMENT
214+
NAME_REF
215+
IDENT "b"
216+
WHITESPACE " "
217+
EQ "="
218+
WHITESPACE " "
219+
PATH_EXPR
220+
PATH
221+
PATH_SEGMENT
222+
NAME_REF
223+
IDENT "c"
224+
SEMICOLON ";"
225+
WHITESPACE "\n "
226+
EXPR_STMT
227+
BIN_EXPR
228+
PATH_EXPR
229+
PATH
230+
PATH_SEGMENT
231+
NAME_REF
232+
IDENT "a"
233+
WHITESPACE " "
234+
EQ "="
235+
WHITESPACE " "
236+
BIN_EXPR
237+
PATH_EXPR
238+
PATH
239+
PATH_SEGMENT
240+
NAME_REF
241+
IDENT "b"
242+
WHITESPACE " "
243+
PLUSEQ "+="
244+
WHITESPACE " "
245+
BIN_EXPR
246+
PATH_EXPR
247+
PATH
248+
PATH_SEGMENT
249+
NAME_REF
250+
IDENT "c"
251+
WHITESPACE " "
252+
MINUSEQ "-="
253+
WHITESPACE " "
254+
PATH_EXPR
255+
PATH
256+
PATH_SEGMENT
257+
NAME_REF
258+
IDENT "d"
259+
SEMICOLON ";"
260+
WHITESPACE "\n "
261+
EXPR_STMT
262+
BIN_EXPR
263+
PATH_EXPR
264+
PATH
265+
PATH_SEGMENT
266+
NAME_REF
267+
IDENT "a"
268+
WHITESPACE " "
269+
EQ "="
270+
WHITESPACE " "
271+
BIN_EXPR
272+
PATH_EXPR
273+
PATH
274+
PATH_SEGMENT
275+
NAME_REF
276+
IDENT "b"
277+
WHITESPACE " "
278+
STAREQ "*="
279+
WHITESPACE " "
280+
BIN_EXPR
281+
PATH_EXPR
282+
PATH
283+
PATH_SEGMENT
284+
NAME_REF
285+
IDENT "c"
286+
WHITESPACE " "
287+
SLASHEQ "/="
288+
WHITESPACE " "
289+
BIN_EXPR
290+
PATH_EXPR
291+
PATH
292+
PATH_SEGMENT
293+
NAME_REF
294+
IDENT "d"
295+
WHITESPACE " "
296+
PERCENTEQ "%="
297+
WHITESPACE " "
298+
PATH_EXPR
299+
PATH
300+
PATH_SEGMENT
301+
NAME_REF
302+
IDENT "e"
303+
SEMICOLON ";"
304+
WHITESPACE "\n "
305+
EXPR_STMT
306+
BIN_EXPR
307+
PATH_EXPR
308+
PATH
309+
PATH_SEGMENT
310+
NAME_REF
311+
IDENT "a"
312+
WHITESPACE " "
313+
EQ "="
314+
WHITESPACE " "
315+
BIN_EXPR
316+
PATH_EXPR
317+
PATH
318+
PATH_SEGMENT
319+
NAME_REF
320+
IDENT "b"
321+
WHITESPACE " "
322+
AMPEQ "&="
323+
WHITESPACE " "
324+
BIN_EXPR
325+
PATH_EXPR
326+
PATH
327+
PATH_SEGMENT
328+
NAME_REF
329+
IDENT "c"
330+
WHITESPACE " "
331+
PIPEEQ "|="
332+
WHITESPACE " "
333+
BIN_EXPR
334+
PATH_EXPR
335+
PATH
336+
PATH_SEGMENT
337+
NAME_REF
338+
IDENT "d"
339+
WHITESPACE " "
340+
CARETEQ "^="
341+
WHITESPACE " "
342+
PATH_EXPR
343+
PATH
344+
PATH_SEGMENT
345+
NAME_REF
346+
IDENT "e"
347+
SEMICOLON ";"
348+
WHITESPACE "\n "
349+
EXPR_STMT
350+
BIN_EXPR
351+
PATH_EXPR
352+
PATH
353+
PATH_SEGMENT
354+
NAME_REF
355+
IDENT "a"
356+
WHITESPACE " "
357+
EQ "="
358+
WHITESPACE " "
359+
BIN_EXPR
360+
PATH_EXPR
361+
PATH
362+
PATH_SEGMENT
363+
NAME_REF
364+
IDENT "b"
365+
WHITESPACE " "
366+
SHLEQ "<<="
367+
WHITESPACE " "
368+
BIN_EXPR
369+
PATH_EXPR
370+
PATH
371+
PATH_SEGMENT
372+
NAME_REF
373+
IDENT "c"
374+
WHITESPACE " "
375+
SHREQ ">>="
376+
WHITESPACE " "
377+
PATH_EXPR
378+
PATH
379+
PATH_SEGMENT
380+
NAME_REF
381+
IDENT "d"
382+
SEMICOLON ";"
383+
WHITESPACE "\n"
384+
R_CURLY "}"
385+
WHITESPACE "\n\n"
386+
FN
387+
FN_KW "fn"
388+
WHITESPACE " "
389+
NAME
390+
IDENT "mixed_associativity"
391+
PARAM_LIST
392+
L_PAREN "("
393+
R_PAREN ")"
394+
WHITESPACE " "
395+
BLOCK_EXPR
396+
STMT_LIST
397+
L_CURLY "{"
398+
WHITESPACE "\n "
399+
COMMENT "// (a + b) = (c += ((d * e) = f))"
400+
WHITESPACE "\n "
401+
EXPR_STMT
402+
BIN_EXPR
403+
BIN_EXPR
404+
PATH_EXPR
405+
PATH
406+
PATH_SEGMENT
407+
NAME_REF
408+
IDENT "a"
409+
WHITESPACE " "
410+
PLUS "+"
411+
WHITESPACE " "
412+
PATH_EXPR
413+
PATH
414+
PATH_SEGMENT
415+
NAME_REF
416+
IDENT "b"
417+
WHITESPACE " "
418+
EQ "="
419+
WHITESPACE " "
420+
BIN_EXPR
421+
PATH_EXPR
422+
PATH
423+
PATH_SEGMENT
424+
NAME_REF
425+
IDENT "c"
426+
WHITESPACE " "
427+
PLUSEQ "+="
428+
WHITESPACE " "
429+
BIN_EXPR
430+
BIN_EXPR
431+
PATH_EXPR
432+
PATH
433+
PATH_SEGMENT
434+
NAME_REF
435+
IDENT "d"
436+
WHITESPACE " "
437+
STAR "*"
438+
WHITESPACE " "
439+
PATH_EXPR
440+
PATH
441+
PATH_SEGMENT
442+
NAME_REF
443+
IDENT "e"
444+
WHITESPACE " "
445+
EQ "="
446+
WHITESPACE " "
447+
PATH_EXPR
448+
PATH
449+
PATH_SEGMENT
450+
NAME_REF
451+
IDENT "f"
452+
SEMICOLON ";"
453+
WHITESPACE "\n"
454+
R_CURLY "}"
186455
WHITESPACE "\n"

0 commit comments

Comments
 (0)