From 38fed171d9389b5b59ea8a6f81ca6fff486bdf91 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 24 Aug 2025 19:26:12 +0800 Subject: [PATCH] Fix .let completion not work for let-chain Example --- ```rust fn main() { let bar = Some(true); if true && bar.$0 } ``` **Before this PR**: Cannot complete `.let` **After this PR**: ```rust fn main() { let bar = Some(true); if true && let Some($0) = bar } ``` --- .../ide-completion/src/completions/postfix.rs | 62 ++++++++++++++++--- crates/ide-completion/src/context/analysis.rs | 33 +++------- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index d355fdbe0739..51acdb7802b3 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -11,12 +11,12 @@ use ide_db::{ text_edit::TextEdit, ty_filter::TryEnum, }; -use itertools::Either; use stdx::never; use syntax::{ SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR}, - TextRange, TextSize, + T, TextRange, TextSize, ast::{self, AstNode, AstToken}, + match_ast, }; use crate::{ @@ -113,12 +113,8 @@ pub(crate) fn complete_postfix( if let Some(parent) = dot_receiver_including_refs.syntax().parent() && let Some(second_ancestor) = parent.parent() { - let sec_ancestor_kind = second_ancestor.kind(); - if let Some(expr) = >::cast(second_ancestor) { - is_in_cond = match expr { - Either::Left(it) => it.condition().is_some_and(|cond| *cond.syntax() == parent), - Either::Right(it) => it.condition().is_some_and(|cond| *cond.syntax() == parent), - } + if let Some(parent_expr) = ast::Expr::cast(parent) { + is_in_cond = is_in_condition(&parent_expr); } match &try_enum { Some(try_enum) if is_in_cond => match try_enum { @@ -147,7 +143,7 @@ pub(crate) fn complete_postfix( .add_to(acc, ctx.db); } }, - _ if matches!(sec_ancestor_kind, STMT_LIST | EXPR_STMT) => { + _ if matches!(second_ancestor.kind(), STMT_LIST | EXPR_STMT) => { postfix_snippet("let", "let", &format!("let $0 = {receiver_text};")) .add_to(acc, ctx.db); postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};")) @@ -454,6 +450,22 @@ fn add_custom_postfix_completions( None } +pub(crate) fn is_in_condition(it: &ast::Expr) -> bool { + it.syntax() + .parent() + .and_then(|parent| { + Some(match_ast! { match parent { + ast::IfExpr(expr) => expr.condition()? == *it, + ast::WhileExpr(expr) => expr.condition()? == *it, + ast::MatchGuard(guard) => guard.condition()? == *it, + ast::BinExpr(bin_expr) => (bin_expr.op_token()?.kind() == T![&&]) + .then(|| is_in_condition(&bin_expr.into()))?, + _ => return None, + } }) + }) + .unwrap_or(false) +} + #[cfg(test)] mod tests { use expect_test::expect; @@ -648,6 +660,38 @@ fn main() { let bar = Some(true); if let Some($0) = bar } +"#, + ); + check_edit( + "let", + r#" +//- minicore: option +fn main() { + let bar = Some(true); + if true && bar.$0 +} +"#, + r#" +fn main() { + let bar = Some(true); + if true && let Some($0) = bar +} +"#, + ); + check_edit( + "let", + r#" +//- minicore: option +fn main() { + let bar = Some(true); + if true && true && bar.$0 +} +"#, + r#" +fn main() { + let bar = Some(true); + if true && true && let Some($0) = bar +} "#, ); } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 77a94403abb9..6a154c61b82f 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -20,12 +20,15 @@ use syntax::{ match_ast, }; -use crate::context::{ - AttrCtx, BreakableKind, COMPLETION_MARKER, CompletionAnalysis, DotAccess, DotAccessExprCtx, - DotAccessKind, ItemListKind, LifetimeContext, LifetimeKind, NameContext, NameKind, - NameRefContext, NameRefKind, ParamContext, ParamKind, PathCompletionCtx, PathExprCtx, PathKind, - PatternContext, PatternRefutability, Qualified, QualifierCtx, TypeAscriptionTarget, - TypeLocation, +use crate::{ + completions::postfix::is_in_condition, + context::{ + AttrCtx, BreakableKind, COMPLETION_MARKER, CompletionAnalysis, DotAccess, DotAccessExprCtx, + DotAccessKind, ItemListKind, LifetimeContext, LifetimeKind, NameContext, NameKind, + NameRefContext, NameRefKind, ParamContext, ParamKind, PathCompletionCtx, PathExprCtx, + PathKind, PatternContext, PatternRefutability, Qualified, QualifierCtx, + TypeAscriptionTarget, TypeLocation, + }, }; #[derive(Debug)] @@ -1216,24 +1219,6 @@ fn classify_name_ref<'db>( Some(res) }; - fn is_in_condition(it: &ast::Expr) -> bool { - (|| { - let parent = it.syntax().parent()?; - if let Some(expr) = ast::WhileExpr::cast(parent.clone()) { - Some(expr.condition()? == *it) - } else if let Some(expr) = ast::IfExpr::cast(parent.clone()) { - Some(expr.condition()? == *it) - } else if let Some(expr) = ast::BinExpr::cast(parent) - && expr.op_token()?.kind() == T![&&] - { - Some(is_in_condition(&expr.into())) - } else { - None - } - })() - .unwrap_or(false) - } - let make_path_kind_expr = |expr: ast::Expr| { let it = expr.syntax(); let in_block_expr = is_in_block(it);