From 251ef93ac3bbb138a2eedf6090f2f56f1a15d898 Mon Sep 17 00:00:00 2001 From: oxalica Date: Thu, 10 Sep 2020 20:01:23 +0800 Subject: [PATCH] Implement async blocks --- crates/hir/src/code_model.rs | 2 + crates/hir_def/src/body/lower.rs | 5 +- crates/hir_def/src/expr.rs | 5 +- crates/hir_ty/src/display.rs | 28 +++-- crates/hir_ty/src/infer/expr.rs | 11 +- crates/hir_ty/src/lib.rs | 39 ++++++- crates/hir_ty/src/tests/simple.rs | 45 +++++--- crates/hir_ty/src/tests/traits.rs | 40 +++++++ crates/hir_ty/src/traits/chalk.rs | 103 ++++++++++++++---- crates/hir_ty/src/traits/chalk/tls.rs | 3 + crates/ide/src/completion/complete_keyword.rs | 22 ++++ 11 files changed, 248 insertions(+), 55 deletions(-) diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index c2fc819e764..613b35afd86 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs @@ -1283,6 +1283,8 @@ impl Type { /// Checks that particular type `ty` implements `std::future::Future`. /// This function is used in `.await` syntax completion. pub fn impls_future(&self, db: &dyn HirDatabase) -> bool { + // No special case for the type of async block, since Chalk can figure it out. + let krate = self.krate; let std_future_trait = diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs index 30ac12a12e4..c5ebc2aa063 100644 --- a/crates/hir_def/src/body/lower.rs +++ b/crates/hir_def/src/body/lower.rs @@ -239,7 +239,10 @@ impl ExprCollector<'_> { None => self.missing_expr(), }, // FIXME: we need to record these effects somewhere... - ast::Effect::Async(_) => self.collect_block_opt(e.block_expr()), + ast::Effect::Async(_) => { + let body = self.collect_block_opt(e.block_expr()); + self.alloc_expr(Expr::Async { body }, syntax_ptr) + } }, ast::Expr::BlockExpr(e) => self.collect_block(e), ast::Expr::LoopExpr(e) => { diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs index c94b3a36f57..e862c208004 100644 --- a/crates/hir_def/src/expr.rs +++ b/crates/hir_def/src/expr.rs @@ -111,6 +111,9 @@ pub enum Expr { TryBlock { body: ExprId, }, + Async { + body: ExprId, + }, Cast { expr: ExprId, type_ref: TypeRef, @@ -250,7 +253,7 @@ impl Expr { f(*expr); } } - Expr::TryBlock { body } | Expr::Unsafe { body } => f(*body), + Expr::TryBlock { body } | Expr::Unsafe { body } | Expr::Async { body } => f(*body), Expr::Loop { body, .. } => f(*body), Expr::While { condition, body, .. } => { f(*condition); diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs index 64b68014d8a..efb48c7ee4b 100644 --- a/crates/hir_ty/src/display.rs +++ b/crates/hir_ty/src/display.rs @@ -381,19 +381,24 @@ impl HirDisplay for ApplicationTy { } } TypeCtor::OpaqueType(opaque_ty_id) => { - let bounds = match opaque_ty_id { + match opaque_ty_id { OpaqueTyId::ReturnTypeImplTrait(func, idx) => { let datas = f.db.return_type_impl_traits(func).expect("impl trait id without data"); let data = (*datas) .as_ref() .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); - data.subst(&self.parameters) + let bounds = data.subst(&self.parameters); + write!(f, "impl ")?; + write_bounds_like_dyn_trait(&bounds.value, f)?; + // FIXME: it would maybe be good to distinguish this from the alias type (when debug printing), and to show the substitution } - }; - write!(f, "impl ")?; - write_bounds_like_dyn_trait(&bounds.value, f)?; - // FIXME: it would maybe be good to distinguish this from the alias type (when debug printing), and to show the substitution + OpaqueTyId::AsyncBlockTypeImplTrait(..) => { + write!(f, "impl Future")?; + } + } } TypeCtor::Closure { .. } => { let sig = self.parameters[0].callable_sig(f.db); @@ -474,18 +479,21 @@ impl HirDisplay for Ty { write_bounds_like_dyn_trait(predicates, f)?; } Ty::Opaque(opaque_ty) => { - let bounds = match opaque_ty.opaque_ty_id { + match opaque_ty.opaque_ty_id { OpaqueTyId::ReturnTypeImplTrait(func, idx) => { let datas = f.db.return_type_impl_traits(func).expect("impl trait id without data"); let data = (*datas) .as_ref() .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); - data.subst(&opaque_ty.parameters) + let bounds = data.subst(&opaque_ty.parameters); + write!(f, "impl ")?; + write_bounds_like_dyn_trait(&bounds.value, f)?; + } + OpaqueTyId::AsyncBlockTypeImplTrait(..) => { + write!(f, "{{async block}}")?; } }; - write!(f, "impl ")?; - write_bounds_like_dyn_trait(&bounds.value, f)?; } Ty::Unknown => write!(f, "{{unknown}}")?, Ty::Infer(..) => write!(f, "_")?, diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs index a2f849d0213..0a141b9cb94 100644 --- a/crates/hir_ty/src/infer/expr.rs +++ b/crates/hir_ty/src/infer/expr.rs @@ -17,8 +17,8 @@ use crate::{ autoderef, method_resolution, op, traits::{FnTrait, InEnvironment}, utils::{generics, variant_data, Generics}, - ApplicationTy, Binders, CallableDefId, InferTy, IntTy, Mutability, Obligation, Rawness, Substs, - TraitRef, Ty, TypeCtor, + ApplicationTy, Binders, CallableDefId, InferTy, IntTy, Mutability, Obligation, OpaqueTyId, + Rawness, Substs, TraitRef, Ty, TypeCtor, }; use super::{ @@ -146,6 +146,13 @@ impl<'a> InferenceContext<'a> { // FIXME should be std::result::Result<{inner}, _> Ty::Unknown } + Expr::Async { body } => { + // Use the first type parameter as the output type of future. + // existenail type AsyncBlockImplTrait: Future + let inner_ty = self.infer_expr(*body, &Expectation::none()); + let opaque_ty_id = OpaqueTyId::AsyncBlockTypeImplTrait(self.owner, *body); + Ty::apply_one(TypeCtor::OpaqueType(opaque_ty_id), inner_ty) + } Expr::Loop { body, label } => { self.breakables.push(BreakableContext { may_break: false, diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs index 1e748476ac1..4a7d3a0d9d5 100644 --- a/crates/hir_ty/src/lib.rs +++ b/crates/hir_ty/src/lib.rs @@ -33,6 +33,7 @@ use hir_def::{ AdtId, AssocContainerId, DefWithBodyId, GenericDefId, HasModule, Lookup, TraitId, TypeAliasId, TypeParamId, }; +use hir_expand::name::name; use itertools::Itertools; use crate::{ @@ -129,8 +130,9 @@ pub enum TypeCtor { /// This represents a placeholder for an opaque type in situations where we /// don't know the hidden type (i.e. currently almost always). This is - /// analogous to the `AssociatedType` type constructor. As with that one, - /// these are only produced by Chalk. + /// analogous to the `AssociatedType` type constructor. + /// It is also used as the type of async block, with one type parameter + /// representing the Future::Output type. OpaqueType(OpaqueTyId), /// The type of a specific closure. @@ -173,6 +175,8 @@ impl TypeCtor { let generic_params = generics(db.upcast(), func.into()); generic_params.len() } + // 1 param representing Future::Output type. + OpaqueTyId::AsyncBlockTypeImplTrait(..) => 1, } } TypeCtor::FnPtr { num_args, is_varargs: _ } => num_args as usize + 1, @@ -205,6 +209,7 @@ impl TypeCtor { OpaqueTyId::ReturnTypeImplTrait(func, _) => { Some(func.lookup(db.upcast()).module(db.upcast()).krate) } + OpaqueTyId::AsyncBlockTypeImplTrait(def, _) => Some(def.module(db.upcast()).krate), }, } } @@ -843,6 +848,33 @@ impl Ty { pub fn impl_trait_bounds(&self, db: &dyn HirDatabase) -> Option> { match self { + Ty::Apply(ApplicationTy { ctor: TypeCtor::OpaqueType(opaque_ty_id), parameters }) => { + match opaque_ty_id { + OpaqueTyId::AsyncBlockTypeImplTrait(def, _expr) => { + let krate = def.module(db.upcast()).krate; + if let Some(future_output) = db + .lang_item(krate, "future_trait".into()) + .and_then(|item| item.as_trait()) + .and_then(|trait_| { + db.trait_data(trait_).associated_type_by_name(&name![Output]) + }) + { + let proj = GenericPredicate::Projection(ProjectionPredicate { + projection_ty: ProjectionTy { + associated_ty: future_output, + // Self type. + parameters: Substs::single(self.clone()), + }, + ty: parameters[0].clone(), + }); + Some(vec![proj]) + } else { + None + } + } + OpaqueTyId::ReturnTypeImplTrait(..) => None, + } + } Ty::Opaque(opaque_ty) => { let predicates = match opaque_ty.opaque_ty_id { OpaqueTyId::ReturnTypeImplTrait(func, idx) => { @@ -853,6 +885,8 @@ impl Ty { data.subst(&opaque_ty.parameters) }) } + // It always has an parameter for Future::Output type. + OpaqueTyId::AsyncBlockTypeImplTrait(..) => unreachable!(), }; predicates.map(|it| it.value) @@ -1065,6 +1099,7 @@ impl TypeWalk for Vec { #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum OpaqueTyId { ReturnTypeImplTrait(hir_def::FunctionId, u16), + AsyncBlockTypeImplTrait(hir_def::DefWithBodyId, ExprId), } #[derive(Clone, PartialEq, Eq, Debug, Hash)] diff --git a/crates/hir_ty/src/tests/simple.rs b/crates/hir_ty/src/tests/simple.rs index 48db23a3416..5b07948f3da 100644 --- a/crates/hir_ty/src/tests/simple.rs +++ b/crates/hir_ty/src/tests/simple.rs @@ -1889,31 +1889,40 @@ fn fn_pointer_return() { fn effects_smoke_test() { check_infer( r#" - fn main() { + async fn main() { let x = unsafe { 92 }; let y = async { async { () }.await }; let z = try { () }; let t = 'a: { 92 }; } + + #[prelude_import] use future::*; + + mod future { + #[lang = "future_trait"] + pub trait Future { type Output; } + } "#, expect![[r#" - 10..130 '{ ...2 }; }': () - 20..21 'x': i32 - 24..37 'unsafe { 92 }': i32 - 31..37 '{ 92 }': i32 - 33..35 '92': i32 - 47..48 'y': {unknown} - 57..79 '{ asyn...wait }': {unknown} - 59..77 'async ....await': {unknown} - 65..71 '{ () }': () - 67..69 '()': () - 89..90 'z': {unknown} - 93..103 'try { () }': {unknown} - 97..103 '{ () }': () - 99..101 '()': () - 113..114 't': i32 - 121..127 '{ 92 }': i32 - 123..125 '92': i32 + 16..136 '{ ...2 }; }': () + 26..27 'x': i32 + 30..43 'unsafe { 92 }': i32 + 37..43 '{ 92 }': i32 + 39..41 '92': i32 + 53..54 'y': impl Future + 57..85 'async ...wait }': impl Future + 63..85 '{ asyn...wait }': () + 65..77 'async { () }': impl Future + 65..83 'async ....await': () + 71..77 '{ () }': () + 73..75 '()': () + 95..96 'z': {unknown} + 99..109 'try { () }': {unknown} + 103..109 '{ () }': () + 105..107 '()': () + 119..120 't': i32 + 127..133 '{ 92 }': i32 + 129..131 '92': i32 "#]], ) } diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs index 1f1056962b7..41d0975197b 100644 --- a/crates/hir_ty/src/tests/traits.rs +++ b/crates/hir_ty/src/tests/traits.rs @@ -85,6 +85,46 @@ mod future { ); } +#[test] +fn infer_async_block() { + check_types( + r#" +//- /main.rs crate:main deps:core +async fn test() { + let a = async { 42 }; + a; +// ^ impl Future + let x = a.await; + x; +// ^ i32 + let b = async {}.await; + b; +// ^ () + let c = async { + let y = Option::None; + y + // ^ Option + }; + let _: Option = c.await; + c; +// ^ impl Future> +} + +enum Option { None, Some(T) } + +//- /core.rs crate:core +#[prelude_import] use future::*; +mod future { + #[lang = "future_trait"] + trait Future { + type Output; + } +} + +"#, + ); +} + #[test] fn infer_try() { check_types( diff --git a/crates/hir_ty/src/traits/chalk.rs b/crates/hir_ty/src/traits/chalk.rs index 17c83b6a46e..25e8ac1868a 100644 --- a/crates/hir_ty/src/traits/chalk.rs +++ b/crates/hir_ty/src/traits/chalk.rs @@ -11,6 +11,7 @@ use hir_def::{ lang_item::{lang_attr, LangItemTarget}, AssocContainerId, AssocItemId, HasModule, Lookup, TypeAliasId, }; +use hir_expand::name::name; use super::ChalkContext; use crate::{ @@ -18,7 +19,8 @@ use crate::{ display::HirDisplay, method_resolution::{TyFingerprint, ALL_FLOAT_FPS, ALL_INT_FPS}, utils::generics, - CallableDefId, DebruijnIndex, FnSig, GenericPredicate, Substs, Ty, TypeCtor, + BoundVar, CallableDefId, DebruijnIndex, FnSig, GenericPredicate, ProjectionPredicate, + ProjectionTy, Substs, TraitRef, Ty, TypeCtor, }; use mapping::{ convert_where_clauses, generic_predicate_to_inline_bound, make_binders, TypeAliasAsValue, @@ -166,27 +168,86 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { fn opaque_ty_data(&self, id: chalk_ir::OpaqueTyId) -> Arc { let interned_id = crate::db::InternedOpaqueTyId::from(id); let full_id = self.db.lookup_intern_impl_trait_id(interned_id); - let (func, idx) = match full_id { - crate::OpaqueTyId::ReturnTypeImplTrait(func, idx) => (func, idx), + let bound = match full_id { + crate::OpaqueTyId::ReturnTypeImplTrait(func, idx) => { + let datas = self + .db + .return_type_impl_traits(func) + .expect("impl trait id without impl traits"); + let data = &datas.value.impl_traits[idx as usize]; + let bound = OpaqueTyDatumBound { + bounds: make_binders( + data.bounds + .value + .iter() + .cloned() + .filter(|b| !b.is_error()) + .map(|b| b.to_chalk(self.db)) + .collect(), + 1, + ), + where_clauses: make_binders(vec![], 0), + }; + let num_vars = datas.num_binders; + make_binders(bound, num_vars) + } + crate::OpaqueTyId::AsyncBlockTypeImplTrait(..) => { + if let Some((future_trait, future_output)) = self + .db + .lang_item(self.krate, "future_trait".into()) + .and_then(|item| item.as_trait()) + .and_then(|trait_| { + let alias = + self.db.trait_data(trait_).associated_type_by_name(&name![Output])?; + Some((trait_, alias)) + }) + { + // AsyncBlock: Future + // This is required by `fn impls_future` to check if we need to provide `.await` completion. + let impl_bound = GenericPredicate::Implemented(TraitRef { + trait_: future_trait, + // Self type as the first parameter. + substs: Substs::single(Ty::Bound(BoundVar { + debruijn: DebruijnIndex::INNERMOST, + index: 0, + })), + }); + // AsyncBlock: Future; + // debruijn: ^1 ^0 + let proj_bound = GenericPredicate::Projection(ProjectionPredicate { + // The parameter of the opaque type. + ty: Ty::Bound(BoundVar { debruijn: DebruijnIndex::ONE, index: 0 }), + projection_ty: ProjectionTy { + associated_ty: future_output, + // Self type as the first parameter. + parameters: Substs::single(Ty::Bound(BoundVar::new( + DebruijnIndex::INNERMOST, + 0, + ))), + }, + }); + let bound = OpaqueTyDatumBound { + bounds: make_binders( + vec![impl_bound.to_chalk(self.db), proj_bound.to_chalk(self.db)], + 1, + ), + where_clauses: make_binders(vec![], 0), + }; + // The opaque type has 1 parameter. + make_binders(bound, 1) + } else { + // If failed to find `Future::Output`, return empty bounds as fallback. + let bound = OpaqueTyDatumBound { + bounds: make_binders(vec![], 0), + where_clauses: make_binders(vec![], 0), + }; + // The opaque type has 1 parameter. + make_binders(bound, 1) + } + } }; - let datas = - self.db.return_type_impl_traits(func).expect("impl trait id without impl traits"); - let data = &datas.value.impl_traits[idx as usize]; - let bound = OpaqueTyDatumBound { - bounds: make_binders( - data.bounds - .value - .iter() - .cloned() - .filter(|b| !b.is_error()) - .map(|b| b.to_chalk(self.db)) - .collect(), - 1, - ), - where_clauses: make_binders(vec![], 0), - }; - let num_vars = datas.num_binders; - Arc::new(OpaqueTyDatum { opaque_ty_id: id, bound: make_binders(bound, num_vars) }) + + Arc::new(OpaqueTyDatum { opaque_ty_id: id, bound }) } fn hidden_opaque_type(&self, _id: chalk_ir::OpaqueTyId) -> chalk_ir::Ty { diff --git a/crates/hir_ty/src/traits/chalk/tls.rs b/crates/hir_ty/src/traits/chalk/tls.rs index db915625c2b..cb6b0fe81fd 100644 --- a/crates/hir_ty/src/traits/chalk/tls.rs +++ b/crates/hir_ty/src/traits/chalk/tls.rs @@ -73,6 +73,9 @@ impl DebugContext<'_> { crate::OpaqueTyId::ReturnTypeImplTrait(func, idx) => { write!(f, "{{impl trait {} of {:?}}}", idx, func)?; } + crate::OpaqueTyId::AsyncBlockTypeImplTrait(def, idx) => { + write!(f, "{{impl trait of async block {} of {:?}}}", idx.into_raw(), def)?; + } }, TypeCtor::Closure { def, expr } => { write!(f, "{{closure {:?} in ", expr.into_raw())?; diff --git a/crates/ide/src/completion/complete_keyword.rs b/crates/ide/src/completion/complete_keyword.rs index 53ba76e0e9f..5645b41fa48 100644 --- a/crates/ide/src/completion/complete_keyword.rs +++ b/crates/ide/src/completion/complete_keyword.rs @@ -506,6 +506,28 @@ pub mod future { #[lang = "future_trait"] pub trait Future {} } +"#, + expect![[r#" + kw await expr.await + "#]], + ); + + check( + r#" +//- /main.rs +use std::future::*; +fn foo() { + let a = async {}; + a.<|> +} + +//- /std/lib.rs +pub mod future { + #[lang = "future_trait"] + pub trait Future { + type Output; + } +} "#, expect![[r#" kw await expr.await