Skip to content

Commit c797110

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 u32 kw where ... ``` ```rust fn foo() -> u32 ```
1 parent 53cfd4a commit c797110

File tree

8 files changed

+287
-23
lines changed

8 files changed

+287
-23
lines changed

crates/ide-completion/src/completions.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use crate::{
3333
CompletionContext, CompletionItem, CompletionItemKind,
3434
context::{
3535
DotAccess, ItemListKind, NameContext, NameKind, NameRefContext, NameRefKind,
36-
PathCompletionCtx, PathKind, PatternContext, TypeLocation, Visible,
36+
PathCompletionCtx, PathKind, PatternContext, TypeAscriptionTarget, TypeLocation, Visible,
3737
},
3838
item::Builder,
3939
render::{
@@ -111,11 +111,22 @@ impl Completions {
111111
}
112112
}
113113

114-
pub(crate) fn add_type_keywords(&mut self, ctx: &CompletionContext<'_>) {
115-
self.add_keyword_snippet(ctx, "fn", "fn($1)");
116-
self.add_keyword_snippet(ctx, "dyn", "dyn $0");
117-
self.add_keyword_snippet(ctx, "impl", "impl $0");
118-
self.add_keyword_snippet(ctx, "for", "for<$1>");
114+
pub(crate) fn add_type_keywords(
115+
&mut self,
116+
ctx: &CompletionContext<'_>,
117+
required_thin_arrow: bool,
118+
) {
119+
let mut add_keyword = |kw, snippet| {
120+
if required_thin_arrow {
121+
self.add_keyword_snippet(ctx, kw, snippet);
122+
} else {
123+
self.add_keyword_snippet(ctx, kw, &snippet[3..]);
124+
}
125+
};
126+
add_keyword("fn", "-> fn($1)");
127+
add_keyword("dyn", "-> dyn $0");
128+
add_keyword("impl", "-> impl $0");
129+
add_keyword("for", "-> for<$1>");
119130
}
120131

121132
pub(crate) fn add_super_keyword(
@@ -746,6 +757,12 @@ pub(super) fn complete_name_ref(
746757
field::complete_field_list_tuple_variant(acc, ctx, path_ctx);
747758
}
748759
TypeLocation::TypeAscription(ascription) => {
760+
if let TypeAscriptionTarget::RetType { item: Some(item), .. } =
761+
ascription
762+
&& path_ctx.required_thin_arrow()
763+
{
764+
keyword::complete_for_and_where(acc, ctx, &item.clone().into());
765+
}
749766
r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription);
750767
}
751768
TypeLocation::GenericArg { .. }

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,9 @@ pub(crate) fn complete_type_path(
204204
_ => {}
205205
};
206206

207+
// FIXME: "crate::" -> "-> crate::" when required_thin_arrow
207208
acc.add_nameref_keywords_with_colon(ctx);
208-
acc.add_type_keywords(ctx);
209+
acc.add_type_keywords(ctx, path_ctx.required_thin_arrow());
209210
ctx.process_all_names(&mut |name, def, doc_aliases| {
210211
if scope_def_applicable(def) {
211212
acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases);
@@ -228,14 +229,14 @@ pub(crate) fn complete_ascribed_type(
228229
TypeAscriptionTarget::Let(pat) | TypeAscriptionTarget::FnParam(pat) => {
229230
ctx.sema.type_of_pat(pat.as_ref()?)
230231
}
231-
TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType(exp) => {
232+
TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType { body: exp, .. } => {
232233
ctx.sema.type_of_expr(exp.as_ref()?)
233234
}
234235
}?
235236
.adjusted();
236237
if !ty.is_unknown() {
237238
let ty_string = ty.display_source_code(ctx.db, ctx.module.into(), true).ok()?;
238-
acc.add(render_type_inference(ty_string, ctx));
239+
acc.add(render_type_inference(ty_string, ctx, path_ctx));
239240
}
240241
None
241242
}

crates/ide-completion/src/context.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,30 @@ impl PathCompletionCtx<'_> {
101101
}
102102
)
103103
}
104+
105+
pub(crate) fn required_thin_arrow(&self) -> bool {
106+
let PathKind::Type {
107+
location:
108+
TypeLocation::TypeAscription(TypeAscriptionTarget::RetType {
109+
item: Some(ref fn_item),
110+
..
111+
}),
112+
} = self.kind
113+
else {
114+
return false;
115+
};
116+
let Some(ret_type) = fn_item.ret_type() else { return true };
117+
if ret_type.thin_arrow_token().is_some() {
118+
return false;
119+
}
120+
match ret_type.ty() {
121+
Some(ast::Type::PathType(path_ty)) => {
122+
path_ty.path().is_some_and(|path| path.as_single_name_ref().is_some())
123+
}
124+
Some(_) => false,
125+
None => true,
126+
}
127+
}
104128
}
105129

106130
/// The kind of path we are completing right now.
@@ -230,7 +254,7 @@ impl TypeLocation {
230254
pub(crate) enum TypeAscriptionTarget {
231255
Let(Option<ast::Pat>),
232256
FnParam(Option<ast::Pat>),
233-
RetType(Option<ast::Expr>),
257+
RetType { body: Option<ast::Expr>, item: Option<ast::Fn> },
234258
Const(Option<ast::Expr>),
235259
}
236260

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,15 +1200,14 @@ fn classify_name_ref<'db>(
12001200
let original = ast::Const::cast(name.syntax().parent()?)?;
12011201
TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body()))
12021202
},
1203-
ast::RetType(it) => {
1204-
it.thin_arrow_token()?;
1203+
ast::RetType(_) => {
12051204
let parent = match ast::Fn::cast(parent.parent()?) {
12061205
Some(it) => it.param_list(),
12071206
None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
12081207
};
12091208

12101209
let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?;
1211-
TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! {
1210+
let body = match_ast! {
12121211
match parent {
12131212
ast::ClosureExpr(it) => {
12141213
it.body()
@@ -1218,7 +1217,9 @@ fn classify_name_ref<'db>(
12181217
},
12191218
_ => return None,
12201219
}
1221-
}))
1220+
};
1221+
let item = ast::Fn::cast(parent);
1222+
TypeLocation::TypeAscription(TypeAscriptionTarget::RetType { body, item })
12221223
},
12231224
ast::Param(it) => {
12241225
it.colon_token()?;

crates/ide-completion/src/item.rs

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

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

crates/ide-completion/src/render.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,17 @@ pub(crate) fn render_tuple_field(
221221
pub(crate) fn render_type_inference(
222222
ty_string: String,
223223
ctx: &CompletionContext<'_>,
224+
path_ctx: &PathCompletionCtx<'_>,
224225
) -> CompletionItem {
225226
let mut builder = CompletionItem::new(
226227
CompletionItemKind::InferredType,
227228
ctx.source_range(),
228-
ty_string,
229+
&ty_string,
229230
ctx.edition,
230231
);
232+
if path_ctx.required_thin_arrow() {
233+
builder.insert_text(format!("-> {ty_string}"));
234+
}
231235
builder.set_relevance(CompletionRelevance {
232236
type_match: Some(CompletionRelevanceTypeMatch::Exact),
233237
exact_name_match: true,
@@ -423,11 +427,13 @@ fn render_resolution_path(
423427
let db = completion.db;
424428
let config = completion.config;
425429
let requires_import = import_to_add.is_some();
430+
let arrow = if path_ctx.required_thin_arrow() { "-> " } else { "" };
426431

427-
let name = local_name.display_no_db(ctx.completion.edition).to_smolstr();
432+
let name = local_name.display(db, completion.edition).to_smolstr();
433+
let prefix = format_args!("{arrow}{name}");
428434
let mut item = render_resolution_simple_(ctx, &local_name, import_to_add, resolution);
429-
if local_name.needs_escape(completion.edition) {
430-
item.insert_text(local_name.display_no_db(completion.edition).to_smolstr());
435+
if local_name.needs_escape(completion.edition) || !arrow.is_empty() {
436+
item.insert_text(prefix.to_smolstr());
431437
}
432438
// Add `<>` for generic types
433439
let type_path_no_ty_args = matches!(
@@ -448,7 +454,7 @@ fn render_resolution_path(
448454
item.lookup_by(name.clone())
449455
.label(SmolStr::from_iter([&name, "<…>"]))
450456
.trigger_call_info()
451-
.insert_snippet(cap, format!("{}<$0>", local_name.display(db, completion.edition)));
457+
.insert_snippet(cap, format!("{prefix}<$0>"));
452458
}
453459
}
454460

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 Enum
120+
ma makro!(…) macro_rules! makro
121+
md module
122+
st Record Record
123+
st Tuple Tuple
124+
st Unit Unit
125+
tt Trait
126+
un Union Union
127+
bt u32 u32
128+
kw crate::
129+
kw dyn
130+
kw fn
131+
kw for
132+
kw impl
133+
kw self::
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)