Skip to content

Commit 1da9156

Browse files
committed
Auto merge of #12982 - jridgewell:into_future, r=Veykril
Implement IntoFuture type inference One of my projects is using [IntoFuture](https://doc.rust-lang.org/std/future/trait.IntoFuture.html) to make our async code a little less verbose. However, rust-analyzer can't infer the output type of an await expression if the value uses `IntoFuture` to convert into another type. So we're getting `{unknown}` types everywhere since switching. `foo.await` itself [desugars](https://github.com/rust-lang/rust/blob/e4417cf020fbcd6182c11637bc6b8694434bd81a/compiler/rustc_ast_lowering/src/expr.rs#L644-L658) into a `match into_future(foo) {}`, with every `Future` impl getting a [default](https://github.com/rust-lang/rust/blob/e4417cf020fbcd6182c11637bc6b8694434bd81a/library/core/src/future/into_future.rs#L131-L139) `IntoFuture` implementation. I'm not sure if we want to disable the old `future_trait` paths, since this only recently [stabilize](rust-lang/rust#98718).
2 parents ae57b69 + cebf957 commit 1da9156

File tree

11 files changed

+178
-35
lines changed

11 files changed

+178
-35
lines changed

crates/hir-expand/src/mod_path.rs

+1
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ macro_rules! __known_path {
257257
(core::ops::RangeToInclusive) => {};
258258
(core::ops::RangeInclusive) => {};
259259
(core::future::Future) => {};
260+
(core::future::IntoFuture) => {};
260261
(core::ops::Try) => {};
261262
($path:path) => {
262263
compile_error!("Please register your known path in the path module")

crates/hir-expand/src/name.rs

+2
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ pub mod known {
266266
Try,
267267
Ok,
268268
Future,
269+
IntoFuture,
269270
Result,
270271
Option,
271272
Output,
@@ -399,6 +400,7 @@ pub mod known {
399400
future_trait,
400401
index,
401402
index_mut,
403+
into_future,
402404
mul_assign,
403405
mul,
404406
neg,

crates/hir-ty/src/infer.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,10 @@ impl<'a> InferenceContext<'a> {
875875
}
876876

877877
fn resolve_future_future_output(&self) -> Option<TypeAliasId> {
878-
let trait_ = self.resolve_lang_item(name![future_trait])?.as_trait()?;
878+
let trait_ = self
879+
.resolver
880+
.resolve_known_trait(self.db.upcast(), &path![core::future::IntoFuture])
881+
.or_else(|| self.resolve_lang_item(name![future_trait])?.as_trait())?;
879882
self.db.trait_data(trait_).associated_type_by_name(&name![Output])
880883
}
881884

crates/hir-ty/src/tests/traits.rs

+25
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,31 @@ fn not_send() -> Box<dyn Future<Output = ()> + 'static> {
137137
);
138138
}
139139

140+
#[test]
141+
fn into_future_trait() {
142+
check_types(
143+
r#"
144+
//- minicore: future
145+
struct Futurable;
146+
impl core::future::IntoFuture for Futurable {
147+
type Output = u64;
148+
type IntoFuture = IntFuture;
149+
}
150+
151+
struct IntFuture;
152+
impl core::future::Future for IntFuture {
153+
type Output = u64;
154+
}
155+
156+
fn test() {
157+
let r = Futurable;
158+
let v = r.await;
159+
v;
160+
} //^ u64
161+
"#,
162+
);
163+
}
164+
140165
#[test]
141166
fn infer_try() {
142167
check_types(

crates/hir/src/lib.rs

+19-7
Original file line numberDiff line numberDiff line change
@@ -2778,20 +2778,32 @@ impl Type {
27782778
self.ty.is_unknown()
27792779
}
27802780

2781-
/// Checks that particular type `ty` implements `std::future::Future`.
2781+
/// Checks that particular type `ty` implements `std::future::IntoFuture` or
2782+
/// `std::future::Future`.
27822783
/// This function is used in `.await` syntax completion.
2783-
pub fn impls_future(&self, db: &dyn HirDatabase) -> bool {
2784-
let std_future_trait = db
2785-
.lang_item(self.env.krate, SmolStr::new_inline("future_trait"))
2786-
.and_then(|it| it.as_trait());
2787-
let std_future_trait = match std_future_trait {
2784+
pub fn impls_into_future(&self, db: &dyn HirDatabase) -> bool {
2785+
let trait_ = db
2786+
.lang_item(self.env.krate, SmolStr::new_inline("into_future"))
2787+
.and_then(|it| {
2788+
let into_future_fn = it.as_function()?;
2789+
let assoc_item = as_assoc_item(db, AssocItem::Function, into_future_fn)?;
2790+
let into_future_trait = assoc_item.containing_trait_or_trait_impl(db)?;
2791+
Some(into_future_trait.id)
2792+
})
2793+
.or_else(|| {
2794+
let future_trait =
2795+
db.lang_item(self.env.krate, SmolStr::new_inline("future_trait"))?;
2796+
future_trait.as_trait()
2797+
});
2798+
2799+
let trait_ = match trait_ {
27882800
Some(it) => it,
27892801
None => return false,
27902802
};
27912803

27922804
let canonical_ty =
27932805
Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) };
2794-
method_resolution::implements_trait(&canonical_ty, db, self.env.clone(), std_future_trait)
2806+
method_resolution::implements_trait(&canonical_ty, db, self.env.clone(), trait_)
27952807
}
27962808

27972809
/// Checks that particular type `ty` implements `std::ops::FnOnce`.

crates/hir/src/source_analyzer.rs

+27-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use hir_def::{
2727
use hir_expand::{
2828
builtin_fn_macro::BuiltinFnLikeExpander,
2929
hygiene::Hygiene,
30+
mod_path::path,
3031
name,
3132
name::{AsName, Name},
3233
HirFileId, InFile,
@@ -269,14 +270,35 @@ impl SourceAnalyzer {
269270
db: &dyn HirDatabase,
270271
await_expr: &ast::AwaitExpr,
271272
) -> Option<FunctionId> {
272-
let ty = self.ty_of_expr(db, &await_expr.expr()?.into())?;
273+
let mut ty = self.ty_of_expr(db, &await_expr.expr()?.into())?.clone();
274+
275+
let into_future_trait = self
276+
.resolver
277+
.resolve_known_trait(db.upcast(), &path![core::future::IntoFuture])
278+
.map(Trait::from);
279+
280+
if let Some(into_future_trait) = into_future_trait {
281+
let type_ = Type::new_with_resolver(db, &self.resolver, ty.clone());
282+
if type_.impls_trait(db, into_future_trait, &[]) {
283+
let items = into_future_trait.items(db);
284+
let into_future_type = items.into_iter().find_map(|item| match item {
285+
AssocItem::TypeAlias(alias)
286+
if alias.name(db) == hir_expand::name![IntoFuture] =>
287+
{
288+
Some(alias)
289+
}
290+
_ => None,
291+
})?;
292+
let future_trait = type_.normalize_trait_assoc_type(db, &[], into_future_type)?;
293+
ty = future_trait.ty;
294+
}
295+
}
273296

274-
let op_fn = db
297+
let poll_fn = db
275298
.lang_item(self.resolver.krate(), hir_expand::name![poll].to_smol_str())?
276299
.as_function()?;
277-
let substs = hir_ty::TyBuilder::subst_for_def(db, op_fn).push(ty.clone()).build();
278-
279-
Some(self.resolve_impl_method_or_trait_def(db, op_fn, &substs))
300+
let substs = hir_ty::TyBuilder::subst_for_def(db, poll_fn).push(ty.clone()).build();
301+
Some(self.resolve_impl_method_or_trait_def(db, poll_fn, &substs))
280302
}
281303

282304
pub(crate) fn resolve_prefix_expr(

crates/ide-assists/src/utils/suggest_name.rs

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const USELESS_METHODS: &[&str] = &[
5555
"iter",
5656
"into_iter",
5757
"iter_mut",
58+
"into_future",
5859
];
5960

6061
pub(crate) fn for_generic_parameter(ty: &ast::ImplTraitType) -> SmolStr {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub(crate) fn complete_dot(
1919
};
2020

2121
// Suggest .await syntax for types that implement Future trait
22-
if receiver_ty.impls_future(ctx.db) {
22+
if receiver_ty.impls_into_future(ctx.db) {
2323
let mut item =
2424
CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), "await");
2525
item.detail("expr.await");

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

+49-21
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,17 @@ impl Future for A {}
7575
fn foo(a: A) { a.$0 }
7676
"#,
7777
expect![[r#"
78-
kw await expr.await
79-
sn box Box::new(expr)
80-
sn call function(expr)
81-
sn dbg dbg!(expr)
82-
sn dbgr dbg!(&expr)
83-
sn let let
84-
sn letm let mut
85-
sn match match expr {}
86-
sn ref &expr
87-
sn refm &mut expr
78+
kw await expr.await
79+
me into_future() (as IntoFuture) fn(self) -> <Self as IntoFuture>::IntoFuture
80+
sn box Box::new(expr)
81+
sn call function(expr)
82+
sn dbg dbg!(expr)
83+
sn dbgr dbg!(&expr)
84+
sn let let
85+
sn letm let mut
86+
sn match match expr {}
87+
sn ref &expr
88+
sn refm &mut expr
8889
"#]],
8990
);
9091

@@ -98,18 +99,45 @@ fn foo() {
9899
}
99100
"#,
100101
expect![[r#"
101-
kw await expr.await
102-
sn box Box::new(expr)
103-
sn call function(expr)
104-
sn dbg dbg!(expr)
105-
sn dbgr dbg!(&expr)
106-
sn let let
107-
sn letm let mut
108-
sn match match expr {}
109-
sn ref &expr
110-
sn refm &mut expr
102+
kw await expr.await
103+
me into_future() (use core::future::IntoFuture) fn(self) -> <Self as IntoFuture>::IntoFuture
104+
sn box Box::new(expr)
105+
sn call function(expr)
106+
sn dbg dbg!(expr)
107+
sn dbgr dbg!(&expr)
108+
sn let let
109+
sn letm let mut
110+
sn match match expr {}
111+
sn ref &expr
112+
sn refm &mut expr
111113
"#]],
112-
)
114+
);
115+
}
116+
117+
#[test]
118+
fn test_completion_await_impls_into_future() {
119+
check(
120+
r#"
121+
//- minicore: future
122+
use core::future::*;
123+
struct A {}
124+
impl IntoFuture for A {}
125+
fn foo(a: A) { a.$0 }
126+
"#,
127+
expect![[r#"
128+
kw await expr.await
129+
me into_future() (as IntoFuture) fn(self) -> <Self as IntoFuture>::IntoFuture
130+
sn box Box::new(expr)
131+
sn call function(expr)
132+
sn dbg dbg!(expr)
133+
sn dbgr dbg!(&expr)
134+
sn let let
135+
sn letm let mut
136+
sn match match expr {}
137+
sn ref &expr
138+
sn refm &mut expr
139+
"#]],
140+
);
113141
}
114142

115143
#[test]

crates/ide/src/goto_definition.rs

+34
Original file line numberDiff line numberDiff line change
@@ -1664,6 +1664,40 @@ fn f() {
16641664
);
16651665
}
16661666

1667+
#[test]
1668+
fn goto_await_into_future_poll() {
1669+
check(
1670+
r#"
1671+
//- minicore: future
1672+
1673+
struct Futurable;
1674+
1675+
impl core::future::IntoFuture for Futurable {
1676+
type IntoFuture = MyFut;
1677+
}
1678+
1679+
struct MyFut;
1680+
1681+
impl core::future::Future for MyFut {
1682+
type Output = ();
1683+
1684+
fn poll(
1685+
//^^^^
1686+
self: std::pin::Pin<&mut Self>,
1687+
cx: &mut std::task::Context<'_>
1688+
) -> std::task::Poll<Self::Output>
1689+
{
1690+
()
1691+
}
1692+
}
1693+
1694+
fn f() {
1695+
Futurable.await$0;
1696+
}
1697+
"#,
1698+
);
1699+
}
1700+
16671701
#[test]
16681702
fn goto_try_op() {
16691703
check(

crates/test-utils/src/minicore.rs

+15
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,21 @@ pub mod future {
471471
#[lang = "poll"]
472472
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
473473
}
474+
475+
pub trait IntoFuture {
476+
type Output;
477+
type IntoFuture: Future<Output = Self::Output>;
478+
#[lang = "into_future"]
479+
fn into_future(self) -> Self::IntoFuture;
480+
}
481+
482+
impl<F: Future> IntoFuture for F {
483+
type Output = F::Output;
484+
type IntoFuture = F;
485+
fn into_future(self) -> F {
486+
self
487+
}
488+
}
474489
}
475490
pub mod task {
476491
pub enum Poll<T> {

0 commit comments

Comments
 (0)