From de8633f15fbfb2255d93c51e630a1e1854b1b746 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 23 Jan 2022 04:21:09 +0200 Subject: [PATCH] Parse `let` expressions in order to support `let` chains We still need to reject freestanding `let` expressions: see https://github.com/rust-analyzer/rust-analyzer/issues/11320#issuecomment-1018212465. --- crates/parser/src/grammar/expressions.rs | 10 +++ crates/parser/src/grammar/expressions/atom.rs | 33 ++++----- crates/parser/src/syntax_kind/generated.rs | 2 +- crates/syntax/src/ast/generated/nodes.rs | 73 ++++++++++--------- crates/syntax/src/ast/make.rs | 12 +-- crates/syntax/src/ast/node_ext.rs | 34 ++++++++- crates/syntax/src/tests/ast_src.rs | 2 +- 7 files changed, 99 insertions(+), 67 deletions(-) diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs index 9dbba89c568..a40db15049d 100644 --- a/crates/parser/src/grammar/expressions.rs +++ b/crates/parser/src/grammar/expressions.rs @@ -29,6 +29,15 @@ fn expr_no_struct(p: &mut Parser) { expr_bp(p, None, r, 1); } +/// Parses the expression in `let pattern = expression`. +/// It needs to be parsed with lower precedence than `&&`, so that +/// `if let true = true && false` is parsed as `if (let true = true) && (true)` +/// and not `if let true = (true && true)`. +fn expr_let(p: &mut Parser) { + let r = Restrictions { forbid_structs: true, prefer_stmt: false }; + expr_bp(p, None, r, 5); +} + pub(super) fn stmt(p: &mut Parser, semicolon: Semicolon) { if p.eat(T![;]) { return; @@ -185,6 +194,7 @@ fn current_op(p: &Parser) -> (u8, SyntaxKind) { T![%] if p.at(T![%=]) => (1, T![%=]), T![%] => (11, T![%]), T![&] if p.at(T![&=]) => (1, T![&=]), + // 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![/=]), diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs index 4b7a1b31fbd..e2c1b1fec57 100644 --- a/crates/parser/src/grammar/expressions/atom.rs +++ b/crates/parser/src/grammar/expressions/atom.rs @@ -79,6 +79,7 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMar closure_expr(p) } T![if] => if_expr(p), + T![let] => let_expr(p), T![loop] => loop_expr(p, None), T![box] => box_expr(p, None), @@ -286,7 +287,7 @@ fn if_expr(p: &mut Parser) -> CompletedMarker { assert!(p.at(T![if])); let m = p.start(); p.bump(T![if]); - condition(p); + expr_no_struct(p); block_expr(p); if p.at(T![else]) { p.bump(T![else]); @@ -335,7 +336,7 @@ fn while_expr(p: &mut Parser, m: Option) -> CompletedMarker { assert!(p.at(T![while])); let m = m.unwrap_or_else(|| p.start()); p.bump(T![while]); - condition(p); + expr_no_struct(p); block_expr(p); m.complete(p, WHILE_EXPR) } @@ -355,22 +356,18 @@ fn for_expr(p: &mut Parser, m: Option) -> CompletedMarker { m.complete(p, FOR_EXPR) } -// test cond -// fn foo() { if let Some(_) = None {} } -// fn bar() { -// if let Some(_) | Some(_) = None {} -// if let | Some(_) = None {} -// while let Some(_) | Some(_) = None {} -// while let | Some(_) = None {} +// test let_expr +// fn foo() { +// if let Some(_) = None && true {} +// while 1 == 5 && (let None = None) {} // } -fn condition(p: &mut Parser) { +fn let_expr(p: &mut Parser) -> CompletedMarker { let m = p.start(); - if p.eat(T![let]) { - patterns::pattern_top(p); - p.expect(T![=]); - } - expr_no_struct(p); - m.complete(p, CONDITION); + p.bump(T![let]); + patterns::pattern_top(p); + p.expect(T![=]); + expr_let(p); + m.complete(p, LET_EXPR) } // test match_expr @@ -482,10 +479,6 @@ fn match_guard(p: &mut Parser) -> CompletedMarker { assert!(p.at(T![if])); let m = p.start(); p.bump(T![if]); - if p.eat(T![let]) { - patterns::pattern_top(p); - p.expect(T![=]); - } expr(p); m.complete(p, MATCH_GUARD) } diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index 601a5792afd..d04b5dbf008 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -178,7 +178,6 @@ pub enum SyntaxKind { CLOSURE_EXPR, IF_EXPR, WHILE_EXPR, - CONDITION, LOOP_EXPR, FOR_EXPR, CONTINUE_EXPR, @@ -188,6 +187,7 @@ pub enum SyntaxKind { STMT_LIST, RETURN_EXPR, YIELD_EXPR, + LET_EXPR, MATCH_EXPR, MATCH_ARM_LIST, MATCH_ARM, diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index 09c5af210f5..6c4729ef365 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -884,7 +884,7 @@ pub struct IfExpr { impl ast::HasAttrs for IfExpr {} impl IfExpr { pub fn if_token(&self) -> Option { support::token(&self.syntax, T![if]) } - pub fn condition(&self) -> Option { support::child(&self.syntax) } + pub fn condition(&self) -> Option { support::child(&self.syntax) } pub fn else_token(&self) -> Option { support::token(&self.syntax, T![else]) } } @@ -1038,7 +1038,7 @@ impl ast::HasAttrs for WhileExpr {} impl ast::HasLoopBody for WhileExpr {} impl WhileExpr { pub fn while_token(&self) -> Option { support::token(&self.syntax, T![while]) } - pub fn condition(&self) -> Option { support::child(&self.syntax) } + pub fn condition(&self) -> Option { support::child(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1051,6 +1051,18 @@ impl YieldExpr { pub fn expr(&self) -> Option { support::child(&self.syntax) } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LetExpr { + pub(crate) syntax: SyntaxNode, +} +impl ast::HasAttrs for LetExpr {} +impl LetExpr { + pub fn let_token(&self) -> Option { support::token(&self.syntax, T![let]) } + pub fn pat(&self) -> Option { support::child(&self.syntax) } + pub fn eq_token(&self) -> Option { support::token(&self.syntax, T![=]) } + pub fn expr(&self) -> Option { support::child(&self.syntax) } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StmtList { pub(crate) syntax: SyntaxNode, @@ -1106,17 +1118,6 @@ impl ArgList { pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Condition { - pub(crate) syntax: SyntaxNode, -} -impl Condition { - pub fn let_token(&self) -> Option { support::token(&self.syntax, T![let]) } - pub fn pat(&self) -> Option { support::child(&self.syntax) } - pub fn eq_token(&self) -> Option { support::token(&self.syntax, T![=]) } - pub fn expr(&self) -> Option { support::child(&self.syntax) } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MatchArmList { pub(crate) syntax: SyntaxNode, @@ -1147,10 +1148,7 @@ pub struct MatchGuard { } impl MatchGuard { pub fn if_token(&self) -> Option { support::token(&self.syntax, T![if]) } - pub fn let_token(&self) -> Option { support::token(&self.syntax, T![let]) } - pub fn pat(&self) -> Option { support::child(&self.syntax) } - pub fn eq_token(&self) -> Option { support::token(&self.syntax, T![=]) } - pub fn expr(&self) -> Option { support::child(&self.syntax) } + pub fn condition(&self) -> Option { support::child(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1524,6 +1522,7 @@ pub enum Expr { TupleExpr(TupleExpr), WhileExpr(WhileExpr), YieldExpr(YieldExpr), + LetExpr(LetExpr), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -2664,6 +2663,17 @@ impl AstNode for YieldExpr { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for LetExpr { + fn can_cast(kind: SyntaxKind) -> bool { kind == LET_EXPR } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for StmtList { fn can_cast(kind: SyntaxKind) -> bool { kind == STMT_LIST } fn cast(syntax: SyntaxNode) -> Option { @@ -2719,17 +2729,6 @@ impl AstNode for ArgList { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } -impl AstNode for Condition { - fn can_cast(kind: SyntaxKind) -> bool { kind == CONDITION } - fn cast(syntax: SyntaxNode) -> Option { - if Self::can_cast(syntax.kind()) { - Some(Self { syntax }) - } else { - None - } - } - fn syntax(&self) -> &SyntaxNode { &self.syntax } -} impl AstNode for MatchArmList { fn can_cast(kind: SyntaxKind) -> bool { kind == MATCH_ARM_LIST } fn cast(syntax: SyntaxNode) -> Option { @@ -3336,6 +3335,9 @@ impl From for Expr { impl From for Expr { fn from(node: YieldExpr) -> Expr { Expr::YieldExpr(node) } } +impl From for Expr { + fn from(node: LetExpr) -> Expr { Expr::LetExpr(node) } +} impl AstNode for Expr { fn can_cast(kind: SyntaxKind) -> bool { match kind { @@ -3344,7 +3346,7 @@ impl AstNode for Expr { | INDEX_EXPR | LITERAL | LOOP_EXPR | MACRO_CALL | MACRO_STMTS | MATCH_EXPR | METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR | PREFIX_EXPR | RANGE_EXPR | RECORD_EXPR | REF_EXPR | RETURN_EXPR | TRY_EXPR | TUPLE_EXPR | WHILE_EXPR - | YIELD_EXPR => true, + | YIELD_EXPR | LET_EXPR => true, _ => false, } } @@ -3381,6 +3383,7 @@ impl AstNode for Expr { TUPLE_EXPR => Expr::TupleExpr(TupleExpr { syntax }), WHILE_EXPR => Expr::WhileExpr(WhileExpr { syntax }), YIELD_EXPR => Expr::YieldExpr(YieldExpr { syntax }), + LET_EXPR => Expr::LetExpr(LetExpr { syntax }), _ => return None, }; Some(res) @@ -3418,6 +3421,7 @@ impl AstNode for Expr { Expr::TupleExpr(it) => &it.syntax, Expr::WhileExpr(it) => &it.syntax, Expr::YieldExpr(it) => &it.syntax, + Expr::LetExpr(it) => &it.syntax, } } } @@ -3883,6 +3887,7 @@ impl AstNode for AnyHasAttrs { | TUPLE_EXPR | WHILE_EXPR | YIELD_EXPR + | LET_EXPR | STMT_LIST | RECORD_EXPR_FIELD_LIST | RECORD_EXPR_FIELD @@ -4537,6 +4542,11 @@ impl std::fmt::Display for YieldExpr { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for LetExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for StmtList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -4562,11 +4572,6 @@ impl std::fmt::Display for ArgList { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for Condition { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(self.syntax(), f) - } -} impl std::fmt::Display for MatchArmList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 8a1bcebbf67..1a754ef4609 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -397,7 +397,7 @@ pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Ex expr_from_text(&format!("match {} {}", expr, match_arm_list)) } pub fn expr_if( - condition: ast::Condition, + condition: ast::Expr, then_branch: ast::BlockExpr, else_branch: Option, ) -> ast::Expr { @@ -456,14 +456,8 @@ pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr { fn expr_from_text(text: &str) -> ast::Expr { ast_from_text(&format!("const C: () = {};", text)) } - -pub fn condition(expr: ast::Expr, pattern: Option) -> ast::Condition { - match pattern { - None => ast_from_text(&format!("const _: () = while {} {{}};", expr)), - Some(pattern) => { - ast_from_text(&format!("const _: () = while let {} = {} {{}};", pattern, expr)) - } - } +pub fn expr_let(pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr { + ast_from_text(&format!("const _: () = while let {} = {} {{}};", pattern, expr)) } pub fn arg_list(args: impl IntoIterator) -> ast::ArgList { diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 067e13ee14d..2915e7aab1a 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -528,9 +528,39 @@ impl ast::Item { } } -impl ast::Condition { +impl ast::Expr { + /// Returns the `let` only if there is exactly one (that is, `let pat = expr` + /// or `((let pat = expr))`, but not `let pat = expr && expr` or `non_let_expr`). + pub fn single_let(&self) -> Option { + return get_pat(self.clone()); + + fn get_pat(expr: ast::Expr) -> Option { + match expr { + ast::Expr::ParenExpr(expr) => expr.expr().and_then(get_pat), + ast::Expr::LetExpr(expr) => Some(expr), + _ => None, + } + } + } + pub fn is_pattern_cond(&self) -> bool { - self.let_token().is_some() + return contains_let(self.clone()); + + fn contains_let(expr: ast::Expr) -> bool { + match expr { + ast::Expr::BinExpr(expr) + if expr.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And)) => + { + expr.lhs() + .map(contains_let) + .or_else(|| expr.rhs().map(contains_let)) + .unwrap_or(false) + } + ast::Expr::ParenExpr(expr) => expr.expr().map_or(false, contains_let), + ast::Expr::LetExpr(_) => true, + _ => false, + } + } } } diff --git a/crates/syntax/src/tests/ast_src.rs b/crates/syntax/src/tests/ast_src.rs index c0f1d5ef565..aeff851ce4d 100644 --- a/crates/syntax/src/tests/ast_src.rs +++ b/crates/syntax/src/tests/ast_src.rs @@ -133,7 +133,6 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { "CLOSURE_EXPR", "IF_EXPR", "WHILE_EXPR", - "CONDITION", "LOOP_EXPR", "FOR_EXPR", "CONTINUE_EXPR", @@ -143,6 +142,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { "STMT_LIST", "RETURN_EXPR", "YIELD_EXPR", + "LET_EXPR", "MATCH_EXPR", "MATCH_ARM_LIST", "MATCH_ARM",