Skip to content

Commit c4898ee

Browse files
committed
Implement thin-arrow completion in fn return position
Very cool feature that can quickly complete simple return types Example --- ```rust fn foo() u$0 ``` **Before this PR** ```text kw where ``` **After this PR** ```text bt u32 (adds ->) u32 kw where ... ``` ```rust fn foo() -> u32 ```
1 parent 437b0ce commit c4898ee

8 files changed

Lines changed: 372 additions & 26 deletions

File tree

crates/ide-completion/src/completions.rs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use crate::{
3434
CompletionContext, CompletionItem, CompletionItemKind,
3535
context::{
3636
DotAccess, ItemListKind, NameContext, NameKind, NameRefContext, NameRefKind,
37-
PathCompletionCtx, PathKind, PatternContext, TypeLocation, Visible,
37+
PathCompletionCtx, PathKind, PatternContext, TypeAscriptionTarget, TypeLocation, Visible,
3838
},
3939
item::Builder,
4040
render::{
@@ -45,7 +45,7 @@ use crate::{
4545
macro_::render_macro,
4646
pattern::{render_struct_pat, render_variant_pat},
4747
render_expr, render_field, render_path_resolution, render_pattern_resolution,
48-
render_tuple_field,
48+
render_tuple_field, render_type_keyword_snippet,
4949
type_alias::{render_type_alias, render_type_alias_with_eq},
5050
union_literal::render_union_literal,
5151
},
@@ -104,6 +104,21 @@ impl Completions {
104104
}
105105
}
106106

107+
pub(crate) fn add_nameref_keywords_with_type_like(
108+
&mut self,
109+
ctx: &CompletionContext<'_>,
110+
path_ctx: &PathCompletionCtx<'_>,
111+
) {
112+
let mut add_keyword = |kw| {
113+
render_type_keyword_snippet(ctx, path_ctx, kw, kw).add_to(self, ctx.db);
114+
};
115+
["self::", "crate::"].into_iter().for_each(&mut add_keyword);
116+
117+
if ctx.depth_from_crate_root > 0 {
118+
add_keyword("super::");
119+
}
120+
}
121+
107122
pub(crate) fn add_nameref_keywords(&mut self, ctx: &CompletionContext<'_>) {
108123
["self", "crate"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
109124

@@ -112,11 +127,19 @@ impl Completions {
112127
}
113128
}
114129

115-
pub(crate) fn add_type_keywords(&mut self, ctx: &CompletionContext<'_>) {
116-
self.add_keyword_snippet(ctx, "fn", "fn($1)");
117-
self.add_keyword_snippet(ctx, "dyn", "dyn $0");
118-
self.add_keyword_snippet(ctx, "impl", "impl $0");
119-
self.add_keyword_snippet(ctx, "for", "for<$1>");
130+
pub(crate) fn add_type_keywords(
131+
&mut self,
132+
ctx: &CompletionContext<'_>,
133+
path_ctx: &PathCompletionCtx<'_>,
134+
) {
135+
let mut add_keyword = |kw, snippet| {
136+
render_type_keyword_snippet(ctx, path_ctx, kw, snippet).add_to(self, ctx.db);
137+
};
138+
139+
add_keyword("fn", "fn($1)");
140+
add_keyword("dyn", "dyn $0");
141+
add_keyword("impl", "impl $0");
142+
add_keyword("for", "for<$1>");
120143
}
121144

122145
pub(crate) fn add_super_keyword(
@@ -747,6 +770,12 @@ pub(super) fn complete_name_ref(
747770
field::complete_field_list_tuple_variant(acc, ctx, path_ctx);
748771
}
749772
TypeLocation::TypeAscription(ascription) => {
773+
if let TypeAscriptionTarget::RetType { item: Some(item), .. } =
774+
ascription
775+
&& path_ctx.required_thin_arrow().is_some()
776+
{
777+
keyword::complete_for_and_where(acc, ctx, &item.clone().into());
778+
}
750779
r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription);
751780
}
752781
TypeLocation::GenericArg { .. }

crates/ide-completion/src/completions/type.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ pub(crate) fn complete_type_path(
206206
_ => {}
207207
};
208208

209-
acc.add_nameref_keywords_with_colon(ctx);
210-
acc.add_type_keywords(ctx);
209+
acc.add_nameref_keywords_with_type_like(ctx, path_ctx);
210+
acc.add_type_keywords(ctx, path_ctx);
211211
ctx.process_all_names(&mut |name, def, doc_aliases| {
212212
if scope_def_applicable(def) {
213213
acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases);
@@ -230,14 +230,14 @@ pub(crate) fn complete_ascribed_type(
230230
TypeAscriptionTarget::Let(pat) | TypeAscriptionTarget::FnParam(pat) => {
231231
ctx.sema.type_of_pat(pat.as_ref()?)
232232
}
233-
TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType(exp) => {
233+
TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType { body: exp, .. } => {
234234
ctx.sema.type_of_expr(exp.as_ref()?)
235235
}
236236
}?
237237
.adjusted();
238238
if !ty.is_unknown() {
239239
let ty_string = ty.display_source_code(ctx.db, ctx.module.into(), true).ok()?;
240-
acc.add(render_type_inference(ty_string, ctx));
240+
acc.add(render_type_inference(ty_string, ctx, path_ctx));
241241
}
242242
None
243243
}

crates/ide-completion/src/context.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,28 @@ impl PathCompletionCtx<'_> {
102102
}
103103
)
104104
}
105+
106+
pub(crate) fn required_thin_arrow(&self) -> Option<(&'static str, TextSize)> {
107+
let PathKind::Type {
108+
location:
109+
TypeLocation::TypeAscription(TypeAscriptionTarget::RetType {
110+
item: Some(ref fn_item),
111+
..
112+
}),
113+
} = self.kind
114+
else {
115+
return None;
116+
};
117+
if fn_item.ret_type().is_some_and(|it| it.thin_arrow_token().is_some()) {
118+
return None;
119+
}
120+
let ret_type = fn_item.ret_type().and_then(|it| it.ty());
121+
match (ret_type, fn_item.param_list()) {
122+
(Some(ty), _) => Some(("-> ", ty.syntax().text_range().start())),
123+
(None, Some(param)) => Some((" ->", param.syntax().text_range().end())),
124+
(None, None) => None,
125+
}
126+
}
105127
}
106128

107129
/// The kind of path we are completing right now.
@@ -231,7 +253,7 @@ impl TypeLocation {
231253
pub(crate) enum TypeAscriptionTarget {
232254
Let(Option<ast::Pat>),
233255
FnParam(Option<ast::Pat>),
234-
RetType(Option<ast::Expr>),
256+
RetType { body: Option<ast::Expr>, item: Option<ast::Fn> },
235257
Const(Option<ast::Expr>),
236258
}
237259

crates/ide-completion/src/context/analysis.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,15 +1278,14 @@ fn classify_name_ref<'db>(
12781278
let original = ast::Static::cast(name.syntax().parent()?)?;
12791279
TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body()))
12801280
},
1281-
ast::RetType(it) => {
1282-
it.thin_arrow_token()?;
1281+
ast::RetType(_) => {
12831282
let parent = match ast::Fn::cast(parent.parent()?) {
12841283
Some(it) => it.param_list(),
12851284
None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
12861285
};
12871286

12881287
let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?;
1289-
TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! {
1288+
let body = match_ast! {
12901289
match parent {
12911290
ast::ClosureExpr(it) => {
12921291
it.body()
@@ -1296,7 +1295,9 @@ fn classify_name_ref<'db>(
12961295
},
12971296
_ => return None,
12981297
}
1299-
}))
1298+
};
1299+
let item = ast::Fn::cast(parent);
1300+
TypeLocation::TypeAscription(TypeAscriptionTarget::RetType { body, item })
13001301
},
13011302
ast::Param(it) => {
13021303
it.colon_token()?;

crates/ide-completion/src/item.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ impl CompletionItem {
481481

482482
/// A helper to make `CompletionItem`s.
483483
#[must_use]
484-
#[derive(Clone)]
484+
#[derive(Debug, Clone)]
485485
pub(crate) struct Builder {
486486
source_range: TextRange,
487487
imports_to_add: SmallVec<[LocatedImport; 1]>,

crates/ide-completion/src/render.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,13 +220,15 @@ pub(crate) fn render_tuple_field(
220220
pub(crate) fn render_type_inference(
221221
ty_string: String,
222222
ctx: &CompletionContext<'_>,
223+
path_ctx: &PathCompletionCtx<'_>,
223224
) -> CompletionItem {
224225
let mut builder = CompletionItem::new(
225226
CompletionItemKind::InferredType,
226227
ctx.source_range(),
227-
ty_string,
228+
&ty_string,
228229
ctx.edition,
229230
);
231+
adds_ret_type_arrow(ctx, path_ctx, &mut builder, ty_string);
230232
builder.set_relevance(CompletionRelevance {
231233
type_match: Some(CompletionRelevanceTypeMatch::Exact),
232234
exact_name_match: true,
@@ -425,11 +427,10 @@ fn render_resolution_path(
425427
let config = completion.config;
426428
let requires_import = import_to_add.is_some();
427429

428-
let name = local_name.display_no_db(ctx.completion.edition).to_smolstr();
430+
let name = local_name.display(db, completion.edition).to_smolstr();
429431
let mut item = render_resolution_simple_(ctx, &local_name, import_to_add, resolution);
430-
if local_name.needs_escape(completion.edition) {
431-
item.insert_text(local_name.display_no_db(completion.edition).to_smolstr());
432-
}
432+
let mut insert_text = name.clone();
433+
433434
// Add `<>` for generic types
434435
let type_path_no_ty_args = matches!(
435436
path_ctx,
@@ -446,12 +447,14 @@ fn render_resolution_path(
446447

447448
if has_non_default_type_params {
448449
cov_mark::hit!(inserts_angle_brackets_for_generics);
450+
insert_text = format_smolstr!("{insert_text}<$0>");
449451
item.lookup_by(name.clone())
450452
.label(SmolStr::from_iter([&name, "<…>"]))
451453
.trigger_call_info()
452-
.insert_snippet(cap, format!("{}<$0>", local_name.display(db, completion.edition)));
454+
.insert_snippet(cap, ""); // set is snippet
453455
}
454456
}
457+
adds_ret_type_arrow(completion, path_ctx, &mut item, insert_text.into());
455458

456459
let mut set_item_relevance = |ty: Type<'_>| {
457460
if !ty.is_unknown() {
@@ -577,6 +580,45 @@ fn scope_def_is_deprecated(ctx: &RenderContext<'_>, resolution: ScopeDef) -> boo
577580
}
578581
}
579582

583+
pub(crate) fn render_type_keyword_snippet(
584+
ctx: &CompletionContext<'_>,
585+
path_ctx: &PathCompletionCtx<'_>,
586+
label: &str,
587+
snippet: &str,
588+
) -> Builder {
589+
let source_range = ctx.source_range();
590+
let mut item =
591+
CompletionItem::new(CompletionItemKind::Keyword, source_range, label, ctx.edition);
592+
593+
let cap = ctx.config.snippet_cap;
594+
if let Some(cap) = cap {
595+
item.insert_snippet(cap, snippet);
596+
}
597+
598+
let insert_text = if cap.is_some() { snippet } else { label }.to_owned();
599+
adds_ret_type_arrow(ctx, path_ctx, &mut item, insert_text);
600+
601+
item
602+
}
603+
604+
fn adds_ret_type_arrow(
605+
ctx: &CompletionContext<'_>,
606+
path_ctx: &PathCompletionCtx<'_>,
607+
item: &mut Builder,
608+
insert_text: String,
609+
) {
610+
if let Some((arrow, at)) = path_ctx.required_thin_arrow() {
611+
let mut edit = TextEdit::builder();
612+
613+
edit.insert(at, arrow.to_owned());
614+
edit.replace(ctx.source_range(), insert_text);
615+
616+
item.text_edit(edit.finish()).adds_text(SmolStr::new_static(arrow));
617+
} else {
618+
item.insert_text(insert_text);
619+
}
620+
}
621+
580622
// FIXME: This checks types without possible coercions which some completions might want to do
581623
fn match_types(
582624
ctx: &CompletionContext<'_>,

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,23 @@ fn completes_where() {
116116
check_with_base_items(
117117
r"fn func() $0",
118118
expect![[r#"
119-
kw where
120-
"#]],
119+
en Enum (adds ->) Enum
120+
ma makro!(…) macro_rules! makro
121+
md module (adds ->)
122+
st Record (adds ->) Record
123+
st Tuple (adds ->) Tuple
124+
st Unit (adds ->) Unit
125+
tt Trait (adds ->)
126+
un Union (adds ->) Union
127+
bt u32 (adds ->) u32
128+
kw crate:: (adds ->)
129+
kw dyn (adds ->)
130+
kw fn (adds ->)
131+
kw for (adds ->)
132+
kw impl (adds ->)
133+
kw self:: (adds ->)
134+
kw where
135+
"#]],
121136
);
122137
check_with_base_items(
123138
r"enum Enum $0",
@@ -243,6 +258,19 @@ impl Copy for S where $0
243258
);
244259
}
245260

261+
#[test]
262+
fn fn_item_where_kw() {
263+
check_edit(
264+
"where",
265+
r#"
266+
fn foo() $0
267+
"#,
268+
r#"
269+
fn foo() where $0
270+
"#,
271+
);
272+
}
273+
246274
#[test]
247275
fn test_is_not_considered_macro() {
248276
check_with_base_items(

0 commit comments

Comments
 (0)