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 391e2379dcd5..284876ffc885 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 @@ -883,9 +883,10 @@ fn classify_name_ref( }, ast::MethodCallExpr(method) => { let receiver = find_opt_node_in_file(original_file, method.receiver()); + let has_parens = has_parens(&method); let kind = NameRefKind::DotAccess(DotAccess { receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), - kind: DotAccessKind::Method { has_parens: method.arg_list().is_some_and(|it| it.l_paren_token().is_some()) }, + kind: DotAccessKind::Method { has_parens }, receiver, ctx: DotAccessExprCtx { in_block_expr: is_in_block(method.syntax()), in_breakable: is_in_breakable(method.syntax()) } }); @@ -1372,7 +1373,7 @@ fn classify_name_ref( } } - path_ctx.has_call_parens = it.syntax().parent().is_some_and(|it| ast::CallExpr::can_cast(it.kind())); + path_ctx.has_call_parens = it.syntax().parent().is_some_and(|it| ast::CallExpr::cast(it).is_some_and(|it| has_parens(&it))); make_path_kind_expr(it.into()) }, @@ -1401,7 +1402,7 @@ fn classify_name_ref( match parent { ast::PathType(it) => make_path_kind_type(it.into()), ast::PathExpr(it) => { - path_ctx.has_call_parens = it.syntax().parent().is_some_and(|it| ast::CallExpr::can_cast(it.kind())); + path_ctx.has_call_parens = it.syntax().parent().is_some_and(|it| ast::CallExpr::cast(it).is_some_and(|it| has_parens(&it))); make_path_kind_expr(it.into()) }, @@ -1559,6 +1560,30 @@ fn classify_name_ref( Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx)) } +/// When writing in the middle of some code the following situation commonly occurs (`|` denotes the cursor): +/// ```ignore +/// value.method| +/// (1, 2, 3) +/// ``` +/// Here, we want to complete the method parentheses & arguments (if the corresponding settings are on), +/// but the thing is parsed as a method call with parentheses. Therefore we use heuristics: if the parentheses +/// are on the next line, consider them non-existent. +fn has_parens(node: &dyn HasArgList) -> bool { + let Some(arg_list) = node.arg_list() else { return false }; + if arg_list.l_paren_token().is_none() { + return false; + } + let prev_siblings = iter::successors(arg_list.syntax().prev_sibling_or_token(), |it| { + it.prev_sibling_or_token() + }); + prev_siblings + .take_while(|syntax| syntax.kind().is_trivia()) + .filter_map(|syntax| { + syntax.into_token().filter(|token| token.kind() == SyntaxKind::WHITESPACE) + }) + .all(|whitespace| !whitespace.text().contains('\n')) +} + fn pattern_context_for( sema: &Semantics<'_, RootDatabase>, original_file: &SyntaxNode, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs index d5137949d42f..3750ad72c8bd 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs @@ -2126,3 +2126,51 @@ fn main() { "#]], ); } + +#[test] +fn call_parens_with_newline() { + check_edit( + "foo", + r#" +fn foo(v: i32) {} + +fn bar() { + foo$0 + () +} + "#, + r#" +fn foo(v: i32) {} + +fn bar() { + foo(${1:v});$0 + () +} + "#, + ); + check_edit( + "foo", + r#" +struct Foo; +impl Foo { + fn foo(&self, v: i32) {} +} + +fn bar() { + Foo.foo$0 + () +} + "#, + r#" +struct Foo; +impl Foo { + fn foo(&self, v: i32) {} +} + +fn bar() { + Foo.foo(${1:v});$0 + () +} + "#, + ); +}