Skip to content

Commit d6477b9

Browse files
authored
Merge pull request rust-lang#18927 from ChayimFriedman2/skip-iter-await
feat: Add smart completions that skip `await` or `iter()` and `into_iter()`
2 parents 1c73899 + 553d525 commit d6477b9

File tree

7 files changed

+230
-72
lines changed

7 files changed

+230
-72
lines changed

src/tools/rust-analyzer/crates/hir/src/lib.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ use hir_def::{
5858
CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FunctionId,
5959
GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstId, ItemContainerId,
6060
LifetimeParamId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId,
61-
SyntheticSyntax, TraitAliasId, TraitId, TupleId, TypeAliasId, TypeOrConstParamId, TypeParamId,
62-
UnionId,
61+
SyntheticSyntax, TraitAliasId, TupleId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId,
6362
};
6463
use hir_expand::{
6564
attrs::collect_attrs, proc_macro::ProcMacroKind, AstId, MacroCallKind, RenderedExpandError,
@@ -128,7 +127,7 @@ pub use {
128127
ImportPathConfig,
129128
// FIXME: This is here since some queries take it as input that are used
130129
// outside of hir.
131-
ModuleDefId,
130+
{ModuleDefId, TraitId},
132131
},
133132
hir_expand::{
134133
attrs::{Attr, AttrId},
@@ -4749,6 +4748,14 @@ impl Type {
47494748
Some((self.derived(ty.clone()), m))
47504749
}
47514750

4751+
pub fn add_reference(&self, mutability: Mutability) -> Type {
4752+
let ty_mutability = match mutability {
4753+
Mutability::Shared => hir_ty::Mutability::Not,
4754+
Mutability::Mut => hir_ty::Mutability::Mut,
4755+
};
4756+
self.derived(TyKind::Ref(ty_mutability, error_lifetime(), self.ty.clone()).intern(Interner))
4757+
}
4758+
47524759
pub fn is_slice(&self) -> bool {
47534760
matches!(self.ty.kind(Interner), TyKind::Slice(..))
47544761
}
@@ -4804,9 +4811,9 @@ impl Type {
48044811
}
48054812

48064813
/// Checks that particular type `ty` implements `std::future::IntoFuture` or
4807-
/// `std::future::Future`.
4814+
/// `std::future::Future` and returns the `Output` associated type.
48084815
/// This function is used in `.await` syntax completion.
4809-
pub fn impls_into_future(&self, db: &dyn HirDatabase) -> bool {
4816+
pub fn into_future_output(&self, db: &dyn HirDatabase) -> Option<Type> {
48104817
let trait_ = db
48114818
.lang_item(self.env.krate, LangItem::IntoFutureIntoFuture)
48124819
.and_then(|it| {
@@ -4818,16 +4825,18 @@ impl Type {
48184825
.or_else(|| {
48194826
let future_trait = db.lang_item(self.env.krate, LangItem::Future)?;
48204827
future_trait.as_trait()
4821-
});
4822-
4823-
let trait_ = match trait_ {
4824-
Some(it) => it,
4825-
None => return false,
4826-
};
4828+
})?;
48274829

48284830
let canonical_ty =
48294831
Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) };
4830-
method_resolution::implements_trait(&canonical_ty, db, &self.env, trait_)
4832+
if !method_resolution::implements_trait_unique(&canonical_ty, db, &self.env, trait_) {
4833+
return None;
4834+
}
4835+
4836+
let output_assoc_type = db
4837+
.trait_data(trait_)
4838+
.associated_type_by_name(&Name::new_symbol_root(sym::Output.clone()))?;
4839+
self.normalize_trait_assoc_type(db, &[], output_assoc_type.into())
48314840
}
48324841

48334842
/// This does **not** resolve `IntoFuture`, only `Future`.
@@ -4846,6 +4855,26 @@ impl Type {
48464855
self.normalize_trait_assoc_type(db, &[], iterator_item.into())
48474856
}
48484857

4858+
pub fn into_iterator_iter(self, db: &dyn HirDatabase) -> Option<Type> {
4859+
let trait_ = db.lang_item(self.env.krate, LangItem::IntoIterIntoIter).and_then(|it| {
4860+
let into_iter_fn = it.as_function()?;
4861+
let assoc_item = as_assoc_item(db, AssocItem::Function, into_iter_fn)?;
4862+
let into_iter_trait = assoc_item.container_or_implemented_trait(db)?;
4863+
Some(into_iter_trait.id)
4864+
})?;
4865+
4866+
let canonical_ty =
4867+
Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) };
4868+
if !method_resolution::implements_trait_unique(&canonical_ty, db, &self.env, trait_) {
4869+
return None;
4870+
}
4871+
4872+
let into_iter_assoc_type = db
4873+
.trait_data(trait_)
4874+
.associated_type_by_name(&Name::new_symbol_root(sym::IntoIter.clone()))?;
4875+
self.normalize_trait_assoc_type(db, &[], into_iter_assoc_type.into())
4876+
}
4877+
48494878
/// Checks that particular type `ty` implements `std::ops::FnOnce`.
48504879
///
48514880
/// This function can be used to check if a particular type is callable, since FnOnce is a

src/tools/rust-analyzer/crates/ide-completion/src/completions.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ impl Completions {
329329
ctx: &CompletionContext<'_>,
330330
dot_access: &DotAccess,
331331
func: hir::Function,
332-
receiver: Option<hir::Name>,
332+
receiver: Option<SmolStr>,
333333
local_name: Option<hir::Name>,
334334
) {
335335
if !ctx.check_stability(Some(&func.attrs(ctx.db))) {
@@ -475,7 +475,7 @@ impl Completions {
475475
&mut self,
476476
ctx: &CompletionContext<'_>,
477477
dot_access: &DotAccess,
478-
receiver: Option<hir::Name>,
478+
receiver: Option<SmolStr>,
479479
field: hir::Field,
480480
ty: &hir::Type,
481481
) {
@@ -533,7 +533,7 @@ impl Completions {
533533
pub(crate) fn add_tuple_field(
534534
&mut self,
535535
ctx: &CompletionContext<'_>,
536-
receiver: Option<hir::Name>,
536+
receiver: Option<SmolStr>,
537537
field: usize,
538538
ty: &hir::Type,
539539
) {

src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs

Lines changed: 146 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::ops::ControlFlow;
44

5-
use hir::{sym, HasContainer, ItemContainer, MethodCandidateCallback, Name};
5+
use hir::{HasContainer, ItemContainer, MethodCandidateCallback, Name};
66
use ide_db::FxHashSet;
77
use syntax::SmolStr;
88

@@ -25,8 +25,13 @@ pub(crate) fn complete_dot(
2525
_ => return,
2626
};
2727

28+
let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. });
29+
let is_method_access_with_parens =
30+
matches!(dot_access.kind, DotAccessKind::Method { has_parens: true });
31+
let traits_in_scope = ctx.traits_in_scope();
32+
2833
// Suggest .await syntax for types that implement Future trait
29-
if receiver_ty.impls_into_future(ctx.db) {
34+
if let Some(future_output) = receiver_ty.into_future_output(ctx.db) {
3035
let mut item = CompletionItem::new(
3136
CompletionItemKind::Keyword,
3237
ctx.source_range(),
@@ -35,11 +40,37 @@ pub(crate) fn complete_dot(
3540
);
3641
item.detail("expr.await");
3742
item.add_to(acc, ctx.db);
38-
}
3943

40-
let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. });
41-
let is_method_access_with_parens =
42-
matches!(dot_access.kind, DotAccessKind::Method { has_parens: true });
44+
// Completions that skip `.await`, e.g. `.await.foo()`.
45+
let dot_access_kind = match &dot_access.kind {
46+
DotAccessKind::Field { receiver_is_ambiguous_float_literal: _ } => {
47+
DotAccessKind::Field { receiver_is_ambiguous_float_literal: false }
48+
}
49+
it @ DotAccessKind::Method { .. } => *it,
50+
};
51+
let dot_access = DotAccess {
52+
receiver: dot_access.receiver.clone(),
53+
receiver_ty: Some(hir::TypeInfo { original: future_output.clone(), adjusted: None }),
54+
kind: dot_access_kind,
55+
ctx: dot_access.ctx,
56+
};
57+
complete_fields(
58+
acc,
59+
ctx,
60+
&future_output,
61+
|acc, field, ty| {
62+
acc.add_field(ctx, &dot_access, Some(SmolStr::new_static("await")), field, &ty)
63+
},
64+
|acc, field, ty| {
65+
acc.add_tuple_field(ctx, Some(SmolStr::new_static("await")), field, &ty)
66+
},
67+
is_field_access,
68+
is_method_access_with_parens,
69+
);
70+
complete_methods(ctx, &future_output, &traits_in_scope, |func| {
71+
acc.add_method(ctx, &dot_access, func, Some(SmolStr::new_static("await")), None)
72+
});
73+
}
4374

4475
complete_fields(
4576
acc,
@@ -50,8 +81,41 @@ pub(crate) fn complete_dot(
5081
is_field_access,
5182
is_method_access_with_parens,
5283
);
84+
complete_methods(ctx, receiver_ty, &traits_in_scope, |func| {
85+
acc.add_method(ctx, dot_access, func, None, None)
86+
});
5387

54-
complete_methods(ctx, receiver_ty, |func| acc.add_method(ctx, dot_access, func, None, None));
88+
// Checking for the existence of `iter()` is complicated in our setup, because we need to substitute
89+
// its return type, so we instead check for `<&Self as IntoIterator>::IntoIter`.
90+
let iter = receiver_ty
91+
.strip_references()
92+
.add_reference(hir::Mutability::Shared)
93+
.into_iterator_iter(ctx.db)
94+
.map(|ty| (ty, SmolStr::new_static("iter()")))
95+
.or_else(|| {
96+
receiver_ty
97+
.clone()
98+
.into_iterator_iter(ctx.db)
99+
.map(|ty| (ty, SmolStr::new_static("into_iter()")))
100+
});
101+
if let Some((iter, iter_sym)) = iter {
102+
// Skip iterators, e.g. complete `.iter().filter_map()`.
103+
let dot_access_kind = match &dot_access.kind {
104+
DotAccessKind::Field { receiver_is_ambiguous_float_literal: _ } => {
105+
DotAccessKind::Field { receiver_is_ambiguous_float_literal: false }
106+
}
107+
it @ DotAccessKind::Method { .. } => *it,
108+
};
109+
let dot_access = DotAccess {
110+
receiver: dot_access.receiver.clone(),
111+
receiver_ty: Some(hir::TypeInfo { original: iter.clone(), adjusted: None }),
112+
kind: dot_access_kind,
113+
ctx: dot_access.ctx,
114+
};
115+
complete_methods(ctx, &iter, &traits_in_scope, |func| {
116+
acc.add_method(ctx, &dot_access, func, Some(iter_sym.clone()), None)
117+
});
118+
}
55119
}
56120

57121
pub(crate) fn complete_undotted_self(
@@ -94,18 +158,16 @@ pub(crate) fn complete_undotted_self(
94158
in_breakable: expr_ctx.in_breakable,
95159
},
96160
},
97-
Some(Name::new_symbol_root(sym::self_.clone())),
161+
Some(SmolStr::new_static("self")),
98162
field,
99163
&ty,
100164
)
101165
},
102-
|acc, field, ty| {
103-
acc.add_tuple_field(ctx, Some(Name::new_symbol_root(sym::self_.clone())), field, &ty)
104-
},
166+
|acc, field, ty| acc.add_tuple_field(ctx, Some(SmolStr::new_static("self")), field, &ty),
105167
true,
106168
false,
107169
);
108-
complete_methods(ctx, &ty, |func| {
170+
complete_methods(ctx, &ty, &ctx.traits_in_scope(), |func| {
109171
acc.add_method(
110172
ctx,
111173
&DotAccess {
@@ -118,7 +180,7 @@ pub(crate) fn complete_undotted_self(
118180
},
119181
},
120182
func,
121-
Some(Name::new_symbol_root(sym::self_.clone())),
183+
Some(SmolStr::new_static("self")),
122184
None,
123185
)
124186
});
@@ -160,6 +222,7 @@ fn complete_fields(
160222
fn complete_methods(
161223
ctx: &CompletionContext<'_>,
162224
receiver: &hir::Type,
225+
traits_in_scope: &FxHashSet<hir::TraitId>,
163226
f: impl FnMut(hir::Function),
164227
) {
165228
struct Callback<'a, F> {
@@ -205,7 +268,7 @@ fn complete_methods(
205268
receiver.iterate_method_candidates_split_inherent(
206269
ctx.db,
207270
&ctx.scope,
208-
&ctx.traits_in_scope(),
271+
traits_in_scope,
209272
Some(ctx.module),
210273
None,
211274
Callback { ctx, f, seen_methods: FxHashSet::default() },
@@ -1306,4 +1369,73 @@ fn baz() {
13061369
"#]],
13071370
);
13081371
}
1372+
1373+
#[test]
1374+
fn skip_iter() {
1375+
check_no_kw(
1376+
r#"
1377+
//- minicore: iterator
1378+
fn foo() {
1379+
[].$0
1380+
}
1381+
"#,
1382+
expect![[r#"
1383+
me clone() (as Clone) fn(&self) -> Self
1384+
me into_iter() (as IntoIterator) fn(self) -> <Self as IntoIterator>::IntoIter
1385+
"#]],
1386+
);
1387+
check_no_kw(
1388+
r#"
1389+
//- minicore: iterator
1390+
struct MyIntoIter;
1391+
impl IntoIterator for MyIntoIter {
1392+
type Item = ();
1393+
type IntoIter = MyIterator;
1394+
fn into_iter(self) -> Self::IntoIter {
1395+
MyIterator
1396+
}
1397+
}
1398+
1399+
struct MyIterator;
1400+
impl Iterator for MyIterator {
1401+
type Item = ();
1402+
fn next(&mut self) -> Self::Item {}
1403+
}
1404+
1405+
fn foo() {
1406+
MyIntoIter.$0
1407+
}
1408+
"#,
1409+
expect![[r#"
1410+
me into_iter() (as IntoIterator) fn(self) -> <Self as IntoIterator>::IntoIter
1411+
me into_iter().by_ref() (as Iterator) fn(&mut self) -> &mut Self
1412+
me into_iter().into_iter() (as IntoIterator) fn(self) -> <Self as IntoIterator>::IntoIter
1413+
me into_iter().next() (as Iterator) fn(&mut self) -> Option<<Self as Iterator>::Item>
1414+
me into_iter().nth(…) (as Iterator) fn(&mut self, usize) -> Option<<Self as Iterator>::Item>
1415+
"#]],
1416+
);
1417+
}
1418+
1419+
#[test]
1420+
fn skip_await() {
1421+
check_no_kw(
1422+
r#"
1423+
//- minicore: future
1424+
struct Foo;
1425+
impl Foo {
1426+
fn foo(self) {}
1427+
}
1428+
1429+
async fn foo() -> Foo { Foo }
1430+
1431+
async fn bar() {
1432+
foo().$0
1433+
}
1434+
"#,
1435+
expect![[r#"
1436+
me await.foo() fn(self)
1437+
me into_future() (use core::future::IntoFuture) fn(self) -> <Self as IntoFuture>::IntoFuture
1438+
"#]],
1439+
);
1440+
}
13091441
}

src/tools/rust-analyzer/crates/ide-completion/src/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ pub(crate) struct DotAccess {
390390
pub(crate) ctx: DotAccessExprCtx,
391391
}
392392

393-
#[derive(Debug)]
393+
#[derive(Debug, Clone, Copy)]
394394
pub(crate) enum DotAccessKind {
395395
Field {
396396
/// True if the receiver is an integer and there is no ident in the original file after it yet
@@ -402,7 +402,7 @@ pub(crate) enum DotAccessKind {
402402
},
403403
}
404404

405-
#[derive(Debug, PartialEq, Eq)]
405+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
406406
pub(crate) struct DotAccessExprCtx {
407407
pub(crate) in_block_expr: bool,
408408
pub(crate) in_breakable: BreakableKind,

0 commit comments

Comments
 (0)