diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index b33a547dee97..3725ddcd4b1d 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -1875,11 +1875,37 @@ fn is_in_breakable(node: &SyntaxNode) -> BreakableKind { } fn is_in_block(node: &SyntaxNode) -> bool { + if has_in_newline_expr_first(node) { + return true; + }; node.parent() .map(|node| ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind())) .unwrap_or(false) } +/// Similar to `has_parens`, heuristic sensing incomplete statement before ambigiguous `Expr` +/// +/// Heuristic: +/// +/// If the `PathExpr` is left part of the `Expr` and there is a newline after the `PathExpr`, +/// it is considered that the `PathExpr` is not part of the `Expr`. +fn has_in_newline_expr_first(node: &SyntaxNode) -> bool { + if node.kind() == SyntaxKind::PATH_EXPR + && let Some(NodeOrToken::Token(next)) = node.next_sibling_or_token() + && next.kind() == SyntaxKind::WHITESPACE + && next.text().contains('\n') + && let Some(stmt_like) = node + .ancestors() + .take_while(|it| it.text_range().start() == node.text_range().start()) + .filter_map(Either::::cast) + .last() + { + stmt_like.syntax().parent().and_then(ast::StmtList::cast).is_some() + } else { + false + } +} + fn next_non_trivia_token(e: impl Into) -> Option { let mut token = match e.into() { SyntaxElement::Node(n) => n.last_token()?, diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index 79137104b568..d393ff8b54d4 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -2782,6 +2782,173 @@ fn let_in_let_chain() { check_edit("let", r#"fn f() { if true && $0 {} }"#, r#"fn f() { if true && let $1 = $0 {} }"#); } +#[test] +fn let_in_previous_line_of_ambiguous_expr() { + check_edit( + "let", + r#" + fn f() { + $0 + (1, 2).foo(); + }"#, + r#" + fn f() { + let $1 = $0; + (1, 2).foo(); + }"#, + ); + + check_edit( + "let", + r#" + fn f() { + $0 + (1, 2) + }"#, + r#" + fn f() { + let $1 = $0; + (1, 2) + }"#, + ); + + check_edit( + "let", + r#" + fn f() -> i32 { + $0 + -2 + }"#, + r#" + fn f() -> i32 { + let $1 = $0; + -2 + }"#, + ); + + check_edit( + "let", + r#" + fn f() -> [i32; 2] { + $0 + [1, 2] + }"#, + r#" + fn f() -> [i32; 2] { + let $1 = $0; + [1, 2] + }"#, + ); + + check_edit( + "let", + r#" + fn f() -> [u8; 2] { + $0 + *b"01" + }"#, + r#" + fn f() -> [u8; 2] { + let $1 = $0; + *b"01" + }"#, + ); + + check( + r#" + fn foo() { + $0 + *b"01" + }"#, + expect![[r#" + fn foo() fn() + bt u32 u32 + kw async + kw const + kw crate:: + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw impl for + kw let + kw letm + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + + check( + r#" + fn foo() { + match $0 {} + }"#, + expect![[r#" + fn foo() fn() + bt u32 u32 + kw const + kw crate:: + kw false + kw for + kw if + kw if let + kw loop + kw match + kw return + kw self:: + kw true + kw unsafe + kw while + kw while let + "#]], + ); + + check( + r#" + fn foo() { + $0 *b"01" + }"#, + expect![[r#" + fn foo() fn() + bt u32 u32 + kw const + kw crate:: + kw false + kw for + kw if + kw if let + kw loop + kw match + kw return + kw self:: + kw true + kw unsafe + kw while + kw while let + "#]], + ); +} + #[test] fn private_inherent_and_public_trait() { check(