Skip to content

Commit 1fbac0e

Browse files
fix
1 parent ea1d299 commit 1fbac0e

File tree

2 files changed

+154
-10
lines changed

2 files changed

+154
-10
lines changed

crates/ide-completion/src/render/function.rs

Lines changed: 119 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
//! Renderer for function calls.
22
3-
use hir::{AsAssocItem, HirDisplay, db::HirDatabase};
4-
use ide_db::{SnippetCap, SymbolKind};
3+
use hir::{AsAssocItem, AssocItemContainer, HirDisplay, PathResolution, db::HirDatabase};
4+
use ide_db::{SnippetCap, SymbolKind, text_edit::TextEdit};
55
use itertools::Itertools;
6+
use std::borrow::Cow;
67
use stdx::{format_to, to_lower_snake_case};
7-
use syntax::{AstNode, SmolStr, ToSmolStr, format_smolstr};
8+
use syntax::{AstNode, SmolStr, TextRange, ToSmolStr, format_smolstr};
89

910
use crate::{
1011
CallableSnippets,
1112
context::{
12-
CompleteSemicolon, CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind,
13+
CompleteSemicolon, CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx,
14+
PathKind, Qualified,
1315
},
1416
item::{
1517
Builder, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevanceFn,
@@ -26,6 +28,17 @@ enum FuncKind<'ctx> {
2628
Method(&'ctx DotAccess<'ctx>, Option<SmolStr>),
2729
}
2830

31+
struct UfcsData {
32+
prefix: String,
33+
replacement_range: TextRange,
34+
}
35+
36+
#[derive(Clone, Copy)]
37+
pub(super) struct CallSnippetRewrite<'a> {
38+
prefix: &'a str,
39+
replace_range: TextRange,
40+
}
41+
2942
pub(crate) fn render_fn(
3043
ctx: RenderContext<'_>,
3144
path_ctx: &PathCompletionCtx<'_>,
@@ -87,6 +100,18 @@ fn render(
87100
}
88101
});
89102

103+
let trait_container = assoc_item.and_then(|assoc_item| match assoc_item.container(db) {
104+
AssocItemContainer::Trait(trait_) => Some(trait_),
105+
_ => None,
106+
});
107+
108+
let ufcs_data = match (&func_kind, trait_container) {
109+
(FuncKind::Function(path_ctx), Some(trait_)) => {
110+
trait_method_ufcs_data(ctx.completion, path_ctx, trait_)
111+
}
112+
_ => None,
113+
};
114+
90115
let (has_dot_receiver, has_call_parens, cap) = match func_kind {
91116
FuncKind::Function(&PathCompletionCtx {
92117
kind: PathKind::Expr { .. },
@@ -151,7 +176,16 @@ fn render(
151176
.detail(detail)
152177
.lookup_by(name.as_str().to_smolstr());
153178

179+
if let Some(data) = &ufcs_data {
180+
let insert_text = format!("{}{}", data.prefix, escaped_call);
181+
item.text_edit(TextEdit::replace(data.replacement_range, insert_text));
182+
}
183+
154184
if let Some((cap, (self_param, params))) = complete_call_parens {
185+
let rewrite = ufcs_data.as_ref().map(|data| CallSnippetRewrite {
186+
replace_range: data.replacement_range,
187+
prefix: &data.prefix,
188+
});
155189
add_call_parens(
156190
&mut item,
157191
completion,
@@ -161,6 +195,7 @@ fn render(
161195
self_param,
162196
params,
163197
&ret_type,
198+
rewrite,
164199
);
165200
}
166201

@@ -217,11 +252,19 @@ pub(super) fn add_call_parens<'b>(
217252
self_param: Option<hir::SelfParam>,
218253
params: Vec<hir::Param<'_>>,
219254
ret_type: &hir::Type<'_>,
255+
mut rewrite: Option<CallSnippetRewrite<'_>>,
220256
) -> &'b mut Builder {
221257
cov_mark::hit!(inserts_parens_for_function_calls);
222258

259+
let call_head = if let Some(rewrite_data) = rewrite.as_ref() {
260+
Cow::Owned(format!("{}{}", rewrite_data.prefix, escaped_name))
261+
} else {
262+
Cow::Borrowed(escaped_name.as_str())
263+
};
264+
let call_head = call_head.as_ref();
265+
223266
let (mut snippet, label_suffix) = if self_param.is_none() && params.is_empty() {
224-
(format!("{escaped_name}()$0"), "()")
267+
(format!("{call_head}()$0"), "()")
225268
} else {
226269
builder.trigger_call_info();
227270
let snippet = if let Some(CallableSnippets::FillArguments) = ctx.config.callable {
@@ -247,20 +290,19 @@ pub(super) fn add_call_parens<'b>(
247290
match self_param {
248291
Some(self_param) => {
249292
format!(
250-
"{}(${{1:{}}}{}{})$0",
251-
escaped_name,
293+
"{call_head}(${{1:{}}}{}{})$0",
252294
self_param.display(ctx.db, ctx.display_target),
253295
if params.is_empty() { "" } else { ", " },
254296
function_params_snippet
255297
)
256298
}
257299
None => {
258-
format!("{escaped_name}({function_params_snippet})$0")
300+
format!("{call_head}({function_params_snippet})$0")
259301
}
260302
}
261303
} else {
262304
cov_mark::hit!(suppress_arg_snippets);
263-
format!("{escaped_name}($0)")
305+
format!("{call_head}($0)")
264306
};
265307

266308
(snippet, "(…)")
@@ -283,7 +325,12 @@ pub(super) fn add_call_parens<'b>(
283325
}
284326
}
285327
}
286-
builder.label(SmolStr::from_iter([&name, label_suffix])).insert_snippet(cap, snippet)
328+
let builder = builder.label(SmolStr::from_iter([&name, label_suffix]));
329+
if let Some(rewrite_data) = rewrite.take() {
330+
builder.snippet_edit(cap, TextEdit::replace(rewrite_data.replace_range, snippet))
331+
} else {
332+
builder.insert_snippet(cap, snippet)
333+
}
287334
}
288335

289336
fn ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type<'_>) -> &'static str {
@@ -393,6 +440,68 @@ fn params<'db>(
393440
Some((self_param, func.params_without_self(ctx.db)))
394441
}
395442

443+
fn trait_method_ufcs_data(
444+
completion: &CompletionContext<'_>,
445+
path_ctx: &PathCompletionCtx<'_>,
446+
trait_: hir::Trait,
447+
) -> Option<UfcsData> {
448+
let needs_ufcs = match &path_ctx.qualified {
449+
Qualified::With { resolution: Some(resolution), .. } => resolution_targets_type(resolution),
450+
Qualified::TypeAnchor { ty, trait_: None } => ty.is_some(),
451+
_ => false,
452+
};
453+
454+
if !needs_ufcs {
455+
return None;
456+
}
457+
458+
let (qualifier_text, replacement_range) = qualifier_text_and_range(completion, path_ctx)?;
459+
let trait_path = trait_path_string(completion, trait_);
460+
let prefix = format!("<{qualifier_text} as {trait_path}>::");
461+
Some(UfcsData { prefix, replacement_range })
462+
}
463+
464+
fn qualifier_text_and_range(
465+
completion: &CompletionContext<'_>,
466+
path_ctx: &PathCompletionCtx<'_>,
467+
) -> Option<(String, TextRange)> {
468+
let qualifier = path_ctx
469+
.original_path
470+
.as_ref()
471+
.and_then(|path| path.qualifier())
472+
.or_else(|| path_ctx.path.qualifier())?;
473+
let qualifier_syntax = qualifier.syntax().clone();
474+
let text = qualifier_syntax.text().to_string();
475+
if text.is_empty() {
476+
return None;
477+
}
478+
let start = qualifier_syntax.text_range().start();
479+
let end = completion.position.offset;
480+
if start >= end {
481+
return None;
482+
}
483+
Some((text, TextRange::new(start, end)))
484+
}
485+
486+
fn trait_path_string(ctx: &CompletionContext<'_>, trait_: hir::Trait) -> String {
487+
let cfg = ctx.config.find_path_config(ctx.is_nightly);
488+
ctx.module
489+
.find_path(ctx.db, hir::ModuleDef::Trait(trait_), cfg)
490+
.map(|path| path.display(ctx.db, ctx.edition).to_string())
491+
.unwrap_or_else(|| trait_.name(ctx.db).display_no_db(ctx.edition).to_string())
492+
}
493+
494+
fn resolution_targets_type(resolution: &PathResolution) -> bool {
495+
match resolution {
496+
PathResolution::Def(def) => matches!(
497+
def,
498+
hir::ModuleDef::Adt(_) | hir::ModuleDef::TypeAlias(_) | hir::ModuleDef::BuiltinType(_)
499+
),
500+
PathResolution::TypeParam(_) | PathResolution::SelfType(_) => true,
501+
_ => false,
502+
}
503+
}
504+
396505
#[cfg(test)]
397506
mod tests {
398507
use crate::{

crates/ide-completion/src/tests/expression.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2911,6 +2911,41 @@ fn foo<T: ExcludedTrait>() {
29112911
);
29122912
}
29132913

2914+
#[test]
2915+
fn trait_method_completion_uses_ufcs_syntax() {
2916+
check_edit(
2917+
"baby_name",
2918+
r#"
2919+
trait Animal {
2920+
fn baby_name() -> String;
2921+
}
2922+
2923+
struct Dog;
2924+
impl Animal for Dog {
2925+
fn baby_name() -> String { String::from("Puppy") }
2926+
}
2927+
2928+
fn make() {
2929+
Dog::$0
2930+
}
2931+
"#,
2932+
r#"
2933+
trait Animal {
2934+
fn baby_name() -> String;
2935+
}
2936+
2937+
struct Dog;
2938+
impl Animal for Dog {
2939+
fn baby_name() -> String { String::from("Puppy") }
2940+
}
2941+
2942+
fn make() {
2943+
<Dog as Animal>::baby_name()$0
2944+
}
2945+
"#,
2946+
);
2947+
}
2948+
29142949
#[test]
29152950
fn hide_ragennew_synthetic_identifiers() {
29162951
check(

0 commit comments

Comments
 (0)