From 8944338010db65e35273a7f2cf137558ad0f4480 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 17 Dec 2025 17:34:43 +0800 Subject: [PATCH 1/2] Fix match arm nested body invalid expected type Example --- ```rust struct Foo; enum E { X } fn foo() -> Foo { match E::X { Foo::X => { $0 } } } ``` **Before this PR** ```text ty: E, name: ? ``` **After this PR** ```text ty: Foo, name: ? ``` --- .../ide-completion/src/context/analysis.rs | 17 +++++++++++++++++ .../crates/ide-completion/src/context/tests.rs | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index b65c68a2401e..e48c433fa4e9 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -734,6 +734,23 @@ fn expected_type_and_name<'db>( }.map(TypeInfo::original); (ty, None) }, + ast::MatchArm(it) => { + let on_arrow = previous_non_trivia_token(token.clone()).is_some_and(|it| T![=>] == it.kind()); + let in_body = it.expr().is_some_and(|it| it.syntax().text_range().contains_range(token.text_range())); + let match_expr = it.syntax().ancestors().nth(2).and_then(ast::MatchExpr::cast); + + let ty = if on_arrow || in_body { + // match foo { ..., pat => $0 } + cov_mark::hit!(expected_type_match_arm_body_without_leading_char); + cov_mark::hit!(expected_type_match_arm_body_with_leading_char); + match_expr.and_then(|it| sema.type_of_expr(&it.into())) + } else { + // match foo { $0 } + cov_mark::hit!(expected_type_match_arm_without_leading_char); + match_expr.and_then(|it| it.expr()).and_then(|e| sema.type_of_expr(&e)) + }.map(TypeInfo::original); + (ty, None) + }, ast::IfExpr(it) => { let ty = if let Some(body) = it.then_branch() && token.text_range().end() > body.syntax().text_range().start() diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs index b929d36ce687..09a9b6f112f0 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs @@ -256,6 +256,22 @@ fn foo() -> Foo { ); } +#[test] +fn expected_type_match_arm_block_body_without_leading_char() { + cov_mark::check!(expected_type_match_arm_body_without_leading_char); + cov_mark::check!(expected_type_match_arm_body_with_leading_char); + check_expected_type_and_name( + r#" +struct Foo; +enum E { X } +fn foo() -> Foo { + match E::X { Foo::X => { $0 } } +} +"#, + expect![[r#"ty: Foo, name: ?"#]], + ); +} + #[test] fn expected_type_match_body_arm_with_leading_char() { cov_mark::check!(expected_type_match_arm_body_with_leading_char); From a67bc72e8f725464029751f275462f75ce4efe7f Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 21 Dec 2025 12:54:47 +0800 Subject: [PATCH 2/2] Add parent_match method to node_ext --- .../crates/ide-completion/src/context/analysis.rs | 6 +++--- .../rust-analyzer/crates/syntax/src/ast/node_ext.rs | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index e48c433fa4e9..f92ce5373b46 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -737,17 +737,17 @@ fn expected_type_and_name<'db>( ast::MatchArm(it) => { let on_arrow = previous_non_trivia_token(token.clone()).is_some_and(|it| T![=>] == it.kind()); let in_body = it.expr().is_some_and(|it| it.syntax().text_range().contains_range(token.text_range())); - let match_expr = it.syntax().ancestors().nth(2).and_then(ast::MatchExpr::cast); + let match_expr = it.parent_match(); let ty = if on_arrow || in_body { // match foo { ..., pat => $0 } cov_mark::hit!(expected_type_match_arm_body_without_leading_char); cov_mark::hit!(expected_type_match_arm_body_with_leading_char); - match_expr.and_then(|it| sema.type_of_expr(&it.into())) + sema.type_of_expr(&match_expr.into()) } else { // match foo { $0 } cov_mark::hit!(expected_type_match_arm_without_leading_char); - match_expr.and_then(|it| it.expr()).and_then(|e| sema.type_of_expr(&e)) + match_expr.expr().and_then(|e| sema.type_of_expr(&e)) }.map(TypeInfo::original); (ty, None) }, diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs index b872221bf711..800dd5f4ac32 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs @@ -1096,6 +1096,16 @@ impl ast::MatchGuard { } } +impl ast::MatchArm { + pub fn parent_match(&self) -> ast::MatchExpr { + self.syntax() + .parent() + .and_then(|it| it.parent()) + .and_then(ast::MatchExpr::cast) + .expect("MatchArms are always nested in MatchExprs") + } +} + impl From for ast::AnyHasAttrs { fn from(node: ast::Item) -> Self { Self::new(node)