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 crate::grammar::attributes::ATTRIBUTE_FIRST;
use super::*;
pub(crate) use self::atom::{block_expr, match_arm_list};
pub(super) use self::atom::{literal, LITERAL_FIRST};
pub(crate) use atom::{block_expr, match_arm_list};
pub(super) use atom::{literal, LITERAL_FIRST};
#[derive(PartialEq, Eq)]
pub(super) enum Semicolon {
@ -188,47 +188,56 @@ struct Restrictions {
prefer_stmt: bool,
}
enum Associativity {
Left,
Right,
}
/// Binding powers of operators for a Pratt parser.
///
/// 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]
fn current_op(p: &Parser<'_>) -> (u8, SyntaxKind) {
const NOT_AN_OP: (u8, SyntaxKind) = (0, T![@]);
fn current_op(p: &Parser<'_>) -> (u8, SyntaxKind, Associativity) {
use Associativity::*;
const NOT_AN_OP: (u8, SyntaxKind, Associativity) = (0, T![@], Left);
match p.current() {
T![|] if p.at(T![||]) => (3, T![||]),
T![|] if p.at(T![|=]) => (1, T![|=]),
T![|] => (6, T![|]),
T![>] if p.at(T![>>=]) => (1, T![>>=]),
T![>] if p.at(T![>>]) => (9, T![>>]),
T![>] if p.at(T![>=]) => (5, T![>=]),
T![>] => (5, T![>]),
T![|] if p.at(T![||]) => (3, T![||], Left),
T![|] if p.at(T![|=]) => (1, T![|=], Right),
T![|] => (6, T![|], Left),
T![>] if p.at(T![>>=]) => (1, T![>>=], Right),
T![>] if p.at(T![>>]) => (9, T![>>], Left),
T![>] if p.at(T![>=]) => (5, T![>=], Left),
T![>] => (5, T![>], Left),
T![=] if p.at(T![=>]) => NOT_AN_OP,
T![=] if p.at(T![==]) => (5, T![==]),
T![=] => (1, T![=]),
T![<] if p.at(T![<=]) => (5, T![<=]),
T![<] if p.at(T![<<=]) => (1, T![<<=]),
T![<] if p.at(T![<<]) => (9, T![<<]),
T![<] => (5, T![<]),
T![+] if p.at(T![+=]) => (1, T![+=]),
T![+] => (10, T![+]),
T![^] if p.at(T![^=]) => (1, T![^=]),
T![^] => (7, T![^]),
T![%] if p.at(T![%=]) => (1, T![%=]),
T![%] => (11, T![%]),
T![&] if p.at(T![&=]) => (1, T![&=]),
T![=] if p.at(T![==]) => (5, T![==], Left),
T![=] => (1, T![=], Right),
T![<] if p.at(T![<=]) => (5, T![<=], Left),
T![<] if p.at(T![<<=]) => (1, T![<<=], Right),
T![<] if p.at(T![<<]) => (9, T![<<], Left),
T![<] => (5, T![<], Left),
T![+] if p.at(T![+=]) => (1, T![+=], Right),
T![+] => (10, T![+], Left),
T![^] if p.at(T![^=]) => (1, T![^=], Right),
T![^] => (7, T![^], Left),
T![%] if p.at(T![%=]) => (1, T![%=], Right),
T![%] => (11, T![%], Left),
T![&] if p.at(T![&=]) => (1, T![&=], Right),
// If you update this, remember to update `expr_let()` too.
T![&] if p.at(T![&&]) => (4, T![&&]),
T![&] => (8, T![&]),
T![/] if p.at(T![/=]) => (1, T![/=]),
T![/] => (11, T![/]),
T![*] if p.at(T![*=]) => (1, T![*=]),
T![*] => (11, T![*]),
T![.] if p.at(T![..=]) => (2, T![..=]),
T![.] if p.at(T![..]) => (2, T![..]),
T![!] if p.at(T![!=]) => (5, T![!=]),
T![-] if p.at(T![-=]) => (1, T![-=]),
T![-] => (10, T![-]),
T![as] => (12, T![as]),
T![&] if p.at(T![&&]) => (4, T![&&], Left),
T![&] => (8, T![&], Left),
T![/] if p.at(T![/=]) => (1, T![/=], Right),
T![/] => (11, T![/], Left),
T![*] if p.at(T![*=]) => (1, T![*=], Right),
T![*] => (11, T![*], Left),
T![.] if p.at(T![..=]) => (2, T![..=], Left),
T![.] if p.at(T![..]) => (2, T![..], Left),
T![!] if p.at(T![!=]) => (5, T![!=], Left),
T![-] if p.at(T![-=]) => (1, T![-=], Right),
T![-] => (10, T![-], Left),
T![as] => (12, T![as], Left),
_ => NOT_AN_OP
}
@ -273,7 +282,7 @@ fn expr_bp(
loop {
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 {
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 });
}
Some((lhs, BlockLike::NotBlock))

View File

@ -183,4 +183,273 @@ SOURCE_FILE
COMMENT "//---&*1 - --2 * 9;"
WHITESPACE "\n"
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"

View File

@ -12,3 +12,16 @@ fn binding_power() {
//1 = 2 .. 3;
//---&*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 "
EXPR_STMT
BIN_EXPR
BIN_EXPR
CALL_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Some"
ARG_LIST
L_PAREN "("
RANGE_EXPR
DOT2 ".."
R_PAREN ")"
WHITESPACE " "
EQ "="
WHITESPACE " "
METHOD_CALL_EXPR
CALL_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Some"
ARG_LIST
L_PAREN "("
LITERAL
INT_NUMBER "0"
R_PAREN ")"
DOT "."
WHITESPACE "\n "
NAME_REF
IDENT "Ok"
ARG_LIST
L_PAREN "("
UNDERSCORE_EXPR
UNDERSCORE "_"
R_PAREN ")"
CALL_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Some"
ARG_LIST
L_PAREN "("
RANGE_EXPR
DOT2 ".."
R_PAREN ")"
WHITESPACE " "
EQ "="
WHITESPACE " "
CALL_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Some"
ARG_LIST
L_PAREN "("
LITERAL
INT_NUMBER "0"
R_PAREN ")"
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
BIN_EXPR
CALL_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Ok"
ARG_LIST
L_PAREN "("
UNDERSCORE_EXPR
UNDERSCORE "_"
R_PAREN ")"
WHITESPACE " "
EQ "="
WHITESPACE " "

View File

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