From 3f94a90c7bbc1b3116a7960ae9f25ebe35d68ad0 Mon Sep 17 00:00:00 2001 From: adamrk Date: Sun, 7 Jun 2020 21:57:29 +0200 Subject: Infer FnSig from Fn traits --- crates/ra_hir_ty/src/infer/coerce.rs | 6 +- crates/ra_hir_ty/src/infer/expr.rs | 102 ++++++++++++++++++++++++++++----- crates/ra_hir_ty/src/traits.rs | 2 +- crates/ra_hir_ty/src/traits/builtin.rs | 6 +- crates/ra_ide/src/hover.rs | 99 ++++++++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 18 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs index 32c7c57cd..4c5f171de 100644 --- a/crates/ra_hir_ty/src/infer/coerce.rs +++ b/crates/ra_hir_ty/src/infer/coerce.rs @@ -38,8 +38,8 @@ impl<'a> InferenceContext<'a> { // Special case: two function types. Try to coerce both to // pointers to have a chance at getting a match. See // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916 - let sig1 = ty1.callable_sig(self.db).expect("FnDef without callable sig"); - let sig2 = ty2.callable_sig(self.db).expect("FnDef without callable sig"); + let sig1 = self.callable_sig(ty1).expect("FnDef without callable sig"); + let sig2 = self.callable_sig(ty2).expect("FnDef without callable sig"); let ptr_ty1 = Ty::fn_ptr(sig1); let ptr_ty2 = Ty::fn_ptr(sig2); self.coerce_merge_branch(&ptr_ty1, &ptr_ty2) @@ -93,7 +93,7 @@ impl<'a> InferenceContext<'a> { // `{function_type}` -> `fn()` (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnPtr { .. })) => { - match from_ty.callable_sig(self.db) { + match self.callable_sig(&from_ty) { None => return false, Some(sig) => { from_ty = Ty::fn_ptr(sig); diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index 9fd310f69..bba6daeb9 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs @@ -15,15 +15,15 @@ use ra_syntax::ast::RangeOp; use crate::{ autoderef, method_resolution, op, - traits::InEnvironment, + traits::{builtin::get_fn_trait, FnTrait, InEnvironment, SolutionVariables}, utils::{generics, variant_data, Generics}, - ApplicationTy, Binders, CallableDef, InferTy, IntTy, Mutability, Obligation, Rawness, Substs, - TraitRef, Ty, TypeCtor, + ApplicationTy, Binders, CallableDef, FnSig, InferTy, IntTy, Mutability, Obligation, Rawness, + Substs, TraitRef, Ty, TypeCtor, }; use super::{ find_breakable, BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, - InferenceDiagnostic, TypeMismatch, + InferenceDiagnostic, Solution, TypeMismatch, }; impl<'a> InferenceContext<'a> { @@ -63,6 +63,75 @@ impl<'a> InferenceContext<'a> { self.resolve_ty_as_possible(ty) } + fn callable_sig_from_fn_trait(&mut self, ty: &Ty) -> Option { + if let Some(krate) = self.resolver.krate() { + let fn_traits: Vec = [FnTrait::FnOnce, FnTrait::FnMut, FnTrait::Fn] + .iter() + .filter_map(|f| get_fn_trait(self.db, krate, *f)) + .collect(); + for fn_trait in fn_traits { + let fn_trait_data = self.db.trait_data(fn_trait); + let generic_params = generics(self.db.upcast(), fn_trait.into()); + if generic_params.len() != 2 { + continue; + } + + let arg_ty = self.table.new_type_var(); + let substs = Substs::build_for_generics(&generic_params) + .push(ty.clone()) + .push(arg_ty.clone()) + .build(); + + let trait_ref = TraitRef { trait_: fn_trait, substs: substs.clone() }; + let trait_env = Arc::clone(&self.trait_env); + let implements_fn_goal = + self.canonicalizer().canonicalize_obligation(InEnvironment { + value: Obligation::Trait(trait_ref), + environment: trait_env, + }); + if let Some(Solution::Unique(SolutionVariables(solution))) = + self.db.trait_solve(krate, implements_fn_goal.value.clone()) + { + match solution.value.as_slice() { + [Ty::Apply(ApplicationTy { + ctor: TypeCtor::Tuple { cardinality: _ }, + parameters, + })] => { + let output_assoc_type = match fn_trait_data + .associated_types() + .collect::>() + .as_slice() + { + [output] => *output, + _ => { + continue; + } + }; + let output_proj_ty = crate::ProjectionTy { + associated_ty: output_assoc_type, + parameters: substs, + }; + let return_ty = self.normalize_projection_ty(output_proj_ty); + return Some(FnSig::from_params_and_return( + parameters.into_iter().map(|ty| ty.clone()).collect(), + return_ty, + )); + } + _ => (), + } + } + } + }; + None + } + + pub fn callable_sig(&mut self, ty: &Ty) -> Option { + match ty.callable_sig(self.db) { + result @ Some(_) => result, + None => self.callable_sig_from_fn_trait(ty), + } + } + fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { let body = Arc::clone(&self.body); // avoid borrow checker problem let ty = match &body[tgt_expr] { @@ -198,14 +267,21 @@ impl<'a> InferenceContext<'a> { } Expr::Call { callee, args } => { let callee_ty = self.infer_expr(*callee, &Expectation::none()); - let (param_tys, ret_ty) = match callee_ty.callable_sig(self.db) { - Some(sig) => (sig.params().to_vec(), sig.ret().clone()), - None => { - // Not callable - // FIXME: report an error - (Vec::new(), Ty::Unknown) - } - }; + let canonicalized = self.canonicalizer().canonicalize_ty(callee_ty.clone()); + let mut derefs = autoderef( + self.db, + self.resolver.krate(), + InEnvironment { + value: canonicalized.value.clone(), + environment: self.trait_env.clone(), + }, + ); + let (param_tys, ret_ty): (Vec, Ty) = derefs + .find_map(|callee_deref_ty| { + self.callable_sig(&canonicalized.decanonicalize_ty(callee_deref_ty.value)) + .map(|sig| (sig.params().to_vec(), sig.ret().clone())) + }) + .unwrap_or((Vec::new(), Ty::Unknown)); self.register_obligations_for_call(&callee_ty); self.check_call_arguments(args, ¶m_tys); self.normalize_associated_types_in(ret_ty) @@ -692,7 +768,7 @@ impl<'a> InferenceContext<'a> { let method_ty = method_ty.subst(&substs); let method_ty = self.insert_type_vars(method_ty); self.register_obligations_for_call(&method_ty); - let (expected_receiver_ty, param_tys, ret_ty) = match method_ty.callable_sig(self.db) { + let (expected_receiver_ty, param_tys, ret_ty) = match self.callable_sig(&method_ty) { Some(sig) => { if !sig.params().is_empty() { (sig.params()[0].clone(), sig.params()[1..].to_vec(), sig.ret().clone()) diff --git a/crates/ra_hir_ty/src/traits.rs b/crates/ra_hir_ty/src/traits.rs index 6bc6d474c..9fef9240d 100644 --- a/crates/ra_hir_ty/src/traits.rs +++ b/crates/ra_hir_ty/src/traits.rs @@ -14,7 +14,7 @@ use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, use self::chalk::{from_chalk, Interner, ToChalk}; pub(crate) mod chalk; -mod builtin; +pub(crate) mod builtin; // This controls the maximum size of types Chalk considers. If we set this too // high, we can run into slow edge cases; if we set it too low, Chalk won't diff --git a/crates/ra_hir_ty/src/traits/builtin.rs b/crates/ra_hir_ty/src/traits/builtin.rs index 88a422d2c..b05e679ad 100644 --- a/crates/ra_hir_ty/src/traits/builtin.rs +++ b/crates/ra_hir_ty/src/traits/builtin.rs @@ -360,7 +360,11 @@ fn super_trait_object_unsize_impl_datum( BuiltinImplData { num_vars, trait_ref, where_clauses: Vec::new(), assoc_ty_values: Vec::new() } } -fn get_fn_trait(db: &dyn HirDatabase, krate: CrateId, fn_trait: super::FnTrait) -> Option { +pub fn get_fn_trait( + db: &dyn HirDatabase, + krate: CrateId, + fn_trait: super::FnTrait, +) -> Option { let target = db.lang_item(krate, fn_trait.lang_item_name().into())?; match target { LangItemTarget::TraitId(t) => Some(t), diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index d870e4cbc..9a88b4977 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -2410,4 +2410,103 @@ fn func(foo: i32) { if true { <|>foo; }; } ] "###); } + + #[test] + fn infer_closure_arg() { + check_hover_result( + r#" + //- /lib.rs + + enum Option { + None, + Some(T) + } + + fn foo() { + let s<|> = Option::None; + let f = |x: Option| {}; + (&f)(s) + } + "#, + &["Option"], + ); + } + + #[test] + fn infer_fn_trait_arg() { + check_hover_result( + r#" + //- /lib.rs deps:std + + #[lang = "fn"] + pub trait Fn { + type Output; + + extern "rust-call" fn call(&self, args: Args) -> Self::Output; + } + + enum Option { + None, + Some(T) + } + + fn foo(f: F) -> T + where + F: Fn(Option) -> T, + { + let s<|> = None; + f(s) + } + "#, + &["Option"], + ); + } + + #[test] + fn infer_box_fn_arg() { + check_hover_result( + r#" + //- /lib.rs deps:std + + #[lang = "fn_once"] + pub trait FnOnce { + type Output; + + extern "rust-call" fn call_once(self, args: Args) -> Self::Output; + } + + #[lang = "deref"] + pub trait Deref { + type Target: ?Sized; + + fn deref(&self) -> &Self::Target; + } + + #[lang = "owned_box"] + pub struct Box { + inner: *mut T, + } + + impl Deref for Box { + type Target = T; + + fn deref(&self) -> &T { + &self.inner + } + } + + enum Option { + None, + Some(T) + } + + fn foo() { + let s<|> = Option::None; + let f: Box)> = box (|ps| {}); + f(&s) + } + "#, + &["Option"], + ); + } } -- cgit v1.2.3