Auto merge of #14952 - lowr:fix/assignments-are-right-associative, r=HKalbasi

fix: assignment operators are right associative

Fixes #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
This commit is contained in:
bors 2023-06-03 12:01:09 +00:00
commit dd0c29c934
5 changed files with 374 additions and 75 deletions

View File

@ -4,8 +4,8 @@
use super::*; use super::*;
pub(crate) use self::atom::{block_expr, match_arm_list}; pub(crate) use atom::{block_expr, match_arm_list};
pub(super) use self::atom::{literal, LITERAL_FIRST}; pub(super) use atom::{literal, LITERAL_FIRST};
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
pub(super) enum Semicolon { pub(super) enum Semicolon {
@ -188,47 +188,56 @@ struct Restrictions {
prefer_stmt: bool, prefer_stmt: bool,
} }
enum Associativity {
Left,
Right,
}
/// Binding powers of operators for a Pratt parser. /// Binding powers of operators for a Pratt parser.
/// ///
/// See <https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html> /// See <https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html>
///
/// Note that Rust doesn't define associativity for some infix operators (e.g. `==` and `..`) and
/// requires parentheses to disambiguate. We just treat them as left associative.
#[rustfmt::skip] #[rustfmt::skip]
fn current_op(p: &Parser<'_>) -> (u8, SyntaxKind) { fn current_op(p: &Parser<'_>) -> (u8, SyntaxKind, Associativity) {
const NOT_AN_OP: (u8, SyntaxKind) = (0, T![@]); use Associativity::*;
const NOT_AN_OP: (u8, SyntaxKind, Associativity) = (0, T![@], Left);
match p.current() { match p.current() {
T![|] if p.at(T![||]) => (3, T![||]), T![|] if p.at(T![||]) => (3, T![||], Left),
T![|] if p.at(T![|=]) => (1, T![|=]), T![|] if p.at(T![|=]) => (1, T![|=], Right),
T![|] => (6, T![|]), T![|] => (6, T![|], Left),
T![>] if p.at(T![>>=]) => (1, T![>>=]), T![>] if p.at(T![>>=]) => (1, T![>>=], Right),
T![>] if p.at(T![>>]) => (9, T![>>]), T![>] if p.at(T![>>]) => (9, T![>>], Left),
T![>] if p.at(T![>=]) => (5, T![>=]), T![>] if p.at(T![>=]) => (5, T![>=], Left),
T![>] => (5, T![>]), T![>] => (5, T![>], Left),
T![=] if p.at(T![=>]) => NOT_AN_OP, T![=] if p.at(T![=>]) => NOT_AN_OP,
T![=] if p.at(T![==]) => (5, T![==]), T![=] if p.at(T![==]) => (5, T![==], Left),
T![=] => (1, T![=]), T![=] => (1, T![=], Right),
T![<] if p.at(T![<=]) => (5, T![<=]), T![<] if p.at(T![<=]) => (5, T![<=], Left),
T![<] if p.at(T![<<=]) => (1, T![<<=]), T![<] if p.at(T![<<=]) => (1, T![<<=], Right),
T![<] if p.at(T![<<]) => (9, T![<<]), T![<] if p.at(T![<<]) => (9, T![<<], Left),
T![<] => (5, T![<]), T![<] => (5, T![<], Left),
T![+] if p.at(T![+=]) => (1, T![+=]), T![+] if p.at(T![+=]) => (1, T![+=], Right),
T![+] => (10, T![+]), T![+] => (10, T![+], Left),
T![^] if p.at(T![^=]) => (1, T![^=]), T![^] if p.at(T![^=]) => (1, T![^=], Right),
T![^] => (7, T![^]), T![^] => (7, T![^], Left),
T![%] if p.at(T![%=]) => (1, T![%=]), T![%] if p.at(T![%=]) => (1, T![%=], Right),
T![%] => (11, T![%]), T![%] => (11, T![%], Left),
T![&] if p.at(T![&=]) => (1, T![&=]), T![&] if p.at(T![&=]) => (1, T![&=], Right),
// If you update this, remember to update `expr_let()` too. // If you update this, remember to update `expr_let()` too.
T![&] if p.at(T![&&]) => (4, T![&&]), T![&] if p.at(T![&&]) => (4, T![&&], Left),
T![&] => (8, T![&]), T![&] => (8, T![&], Left),
T![/] if p.at(T![/=]) => (1, T![/=]), T![/] if p.at(T![/=]) => (1, T![/=], Right),
T![/] => (11, T![/]), T![/] => (11, T![/], Left),
T![*] if p.at(T![*=]) => (1, T![*=]), T![*] if p.at(T![*=]) => (1, T![*=], Right),
T![*] => (11, T![*]), T![*] => (11, T![*], Left),
T![.] if p.at(T![..=]) => (2, T![..=]), T![.] if p.at(T![..=]) => (2, T![..=], Left),
T![.] if p.at(T![..]) => (2, T![..]), T![.] if p.at(T![..]) => (2, T![..], Left),
T![!] if p.at(T![!=]) => (5, T![!=]), T![!] if p.at(T![!=]) => (5, T![!=], Left),
T![-] if p.at(T![-=]) => (1, T![-=]), T![-] if p.at(T![-=]) => (1, T![-=], Right),
T![-] => (10, T![-]), T![-] => (10, T![-], Left),
T![as] => (12, T![as]), T![as] => (12, T![as], Left),
_ => NOT_AN_OP _ => NOT_AN_OP
} }
@ -273,7 +282,7 @@ fn expr_bp(
loop { loop {
let is_range = p.at(T![..]) || p.at(T![..=]); let is_range = p.at(T![..]) || p.at(T![..=]);
let (op_bp, op) = current_op(p); let (op_bp, op, associativity) = current_op(p);
if op_bp < bp { if op_bp < bp {
break; break;
} }
@ -306,7 +315,11 @@ fn expr_bp(
} }
} }
expr_bp(p, None, Restrictions { prefer_stmt: false, ..r }, op_bp + 1); let op_bp = match associativity {
Associativity::Left => op_bp + 1,
Associativity::Right => op_bp,
};
expr_bp(p, None, Restrictions { prefer_stmt: false, ..r }, op_bp);
lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR }); lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR });
} }
Some((lhs, BlockLike::NotBlock)) Some((lhs, BlockLike::NotBlock))

View File

@ -183,4 +183,273 @@ SOURCE_FILE
COMMENT "//---&*1 - --2 * 9;" COMMENT "//---&*1 - --2 * 9;"
WHITESPACE "\n" WHITESPACE "\n"
R_CURLY "}" R_CURLY "}"
WHITESPACE "\n\n"
FN
FN_KW "fn"
WHITESPACE " "
NAME
IDENT "right_associative"
PARAM_LIST
L_PAREN "("
R_PAREN ")"
WHITESPACE " "
BLOCK_EXPR
STMT_LIST
L_CURLY "{"
WHITESPACE "\n "
EXPR_STMT
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "a"
WHITESPACE " "
EQ "="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "b"
WHITESPACE " "
EQ "="
WHITESPACE " "
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "c"
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "a"
WHITESPACE " "
EQ "="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "b"
WHITESPACE " "
PLUSEQ "+="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "c"
WHITESPACE " "
MINUSEQ "-="
WHITESPACE " "
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "d"
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "a"
WHITESPACE " "
EQ "="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "b"
WHITESPACE " "
STAREQ "*="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "c"
WHITESPACE " "
SLASHEQ "/="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "d"
WHITESPACE " "
PERCENTEQ "%="
WHITESPACE " "
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "e"
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "a"
WHITESPACE " "
EQ "="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "b"
WHITESPACE " "
AMPEQ "&="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "c"
WHITESPACE " "
PIPEEQ "|="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "d"
WHITESPACE " "
CARETEQ "^="
WHITESPACE " "
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "e"
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "a"
WHITESPACE " "
EQ "="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "b"
WHITESPACE " "
SHLEQ "<<="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "c"
WHITESPACE " "
SHREQ ">>="
WHITESPACE " "
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "d"
SEMICOLON ";"
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n\n"
FN
FN_KW "fn"
WHITESPACE " "
NAME
IDENT "mixed_associativity"
PARAM_LIST
L_PAREN "("
R_PAREN ")"
WHITESPACE " "
BLOCK_EXPR
STMT_LIST
L_CURLY "{"
WHITESPACE "\n "
COMMENT "// (a + b) = (c += ((d * e) = f))"
WHITESPACE "\n "
EXPR_STMT
BIN_EXPR
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "a"
WHITESPACE " "
PLUS "+"
WHITESPACE " "
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "b"
WHITESPACE " "
EQ "="
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "c"
WHITESPACE " "
PLUSEQ "+="
WHITESPACE " "
BIN_EXPR
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "d"
WHITESPACE " "
STAR "*"
WHITESPACE " "
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "e"
WHITESPACE " "
EQ "="
WHITESPACE " "
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "f"
SEMICOLON ";"
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n" WHITESPACE "\n"

View File

@ -12,3 +12,16 @@ fn binding_power() {
//1 = 2 .. 3; //1 = 2 .. 3;
//---&*1 - --2 * 9; //---&*1 - --2 * 9;
} }
fn right_associative() {
a = b = c;
a = b += c -= d;
a = b *= c /= d %= e;
a = b &= c |= d ^= e;
a = b <<= c >>= d;
}
fn mixed_associativity() {
// (a + b) = (c += ((d * e) = f))
a + b = c += d * e = f;
}

View File

@ -168,42 +168,46 @@ SOURCE_FILE
WHITESPACE "\n " WHITESPACE "\n "
EXPR_STMT EXPR_STMT
BIN_EXPR BIN_EXPR
BIN_EXPR CALL_EXPR
CALL_EXPR PATH_EXPR
PATH_EXPR PATH
PATH PATH_SEGMENT
PATH_SEGMENT NAME_REF
NAME_REF IDENT "Some"
IDENT "Some" ARG_LIST
ARG_LIST L_PAREN "("
L_PAREN "(" RANGE_EXPR
RANGE_EXPR DOT2 ".."
DOT2 ".." R_PAREN ")"
R_PAREN ")" WHITESPACE " "
WHITESPACE " " EQ "="
EQ "=" WHITESPACE " "
WHITESPACE " " CALL_EXPR
METHOD_CALL_EXPR PATH_EXPR
CALL_EXPR PATH
PATH_EXPR PATH_SEGMENT
PATH NAME_REF
PATH_SEGMENT IDENT "Some"
NAME_REF ARG_LIST
IDENT "Some" L_PAREN "("
ARG_LIST LITERAL
L_PAREN "(" INT_NUMBER "0"
LITERAL R_PAREN ")"
INT_NUMBER "0" SEMICOLON ";"
R_PAREN ")" WHITESPACE "\n "
DOT "." EXPR_STMT
WHITESPACE "\n " BIN_EXPR
NAME_REF CALL_EXPR
IDENT "Ok" PATH_EXPR
ARG_LIST PATH
L_PAREN "(" PATH_SEGMENT
UNDERSCORE_EXPR NAME_REF
UNDERSCORE "_" IDENT "Ok"
R_PAREN ")" ARG_LIST
L_PAREN "("
UNDERSCORE_EXPR
UNDERSCORE "_"
R_PAREN ")"
WHITESPACE " " WHITESPACE " "
EQ "=" EQ "="
WHITESPACE " " WHITESPACE " "

View File

@ -4,7 +4,7 @@ fn foo() {
(_) = ..; (_) = ..;
struct S { a: i32 } struct S { a: i32 }
S { .. } = S { ..S::default() }; S { .. } = S { ..S::default() };
Some(..) = Some(0). Some(..) = Some(0);
Ok(_) = 0; Ok(_) = 0;
let (a, b); let (a, b);
[a, .., b] = [1, .., 2]; [a, .., b] = [1, .., 2];