Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trait environment #1515

Merged
merged 4 commits into from Jul 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 3 additions & 10 deletions crates/ra_hir/src/db.rs
Expand Up @@ -213,18 +213,11 @@ pub trait HirDatabase: DefDatabase + AstDatabase {
#[salsa::invoke(crate::ty::traits::chalk::impl_datum_query)]
fn impl_datum(&self, krate: Crate, impl_id: chalk_ir::ImplId) -> Arc<chalk_rust_ir::ImplDatum>;

#[salsa::invoke(crate::ty::traits::implements_query)]
fn implements(
#[salsa::invoke(crate::ty::traits::solve_query)]
fn solve(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Micro nit about naming: perhaps this should be something like chalk_solve or trait_solve?

Solve is fine within chalk itself, but, in larger context, it is a bit too ambiguous.

&self,
krate: Crate,
goal: crate::ty::Canonical<crate::ty::TraitRef>,
) -> Option<crate::ty::traits::Solution>;

#[salsa::invoke(crate::ty::traits::normalize_query)]
fn normalize(
&self,
krate: Crate,
goal: crate::ty::Canonical<crate::ty::ProjectionPredicate>,
goal: crate::ty::Canonical<crate::ty::InEnvironment<crate::ty::Obligation>>,
) -> Option<crate::ty::traits::Solution>;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/ra_hir/src/ty.rs
Expand Up @@ -26,7 +26,7 @@ pub(crate) use lower::{
callable_item_sig, generic_defaults_query, generic_predicates_query, type_for_def,
type_for_field, TypableDef,
};
pub(crate) use traits::ProjectionPredicate;
pub(crate) use traits::{Environment, InEnvironment, Obligation, ProjectionPredicate};

/// A type constructor or type name: this might be something like the primitive
/// type `bool`, a struct like `Vec`, or things like function pointers or
Expand Down
10 changes: 8 additions & 2 deletions crates/ra_hir/src/ty/autoderef.rs
Expand Up @@ -52,6 +52,8 @@ fn deref_by_trait(

// FIXME make the Canonical handling nicer

let env = super::lower::trait_env(db, resolver);

let projection = super::traits::ProjectionPredicate {
ty: Ty::Bound(0),
projection_ty: super::ProjectionTy {
Expand All @@ -60,9 +62,13 @@ fn deref_by_trait(
},
};

let canonical = super::Canonical { num_vars: 1 + ty.num_vars, value: projection };
let obligation = super::Obligation::Projection(projection);

let in_env = super::traits::InEnvironment { value: obligation, environment: env };

let canonical = super::Canonical { num_vars: 1 + ty.num_vars, value: in_env };

let solution = db.normalize(krate, canonical)?;
let solution = db.solve(krate, canonical)?;

match &solution {
Solution::Unique(vars) => {
Expand Down
69 changes: 23 additions & 46 deletions crates/ra_hir/src/ty/infer.rs
Expand Up @@ -27,9 +27,10 @@ use ra_prof::profile;
use test_utils::tested_by;

use super::{
autoderef, method_resolution, op, primitive,
autoderef, lower, method_resolution, op, primitive,
traits::{Guidance, Obligation, ProjectionPredicate, Solution},
ApplicationTy, CallableDef, ProjectionTy, Substs, TraitRef, Ty, TypableDef, TypeCtor,
ApplicationTy, CallableDef, Environment, InEnvironment, ProjectionTy, Substs, TraitRef, Ty,
TypableDef, TypeCtor,
};
use crate::{
adt::VariantDef,
Expand Down Expand Up @@ -165,6 +166,7 @@ struct InferenceContext<'a, D: HirDatabase> {
body: Arc<Body>,
resolver: Resolver,
var_unification_table: InPlaceUnificationTable<TypeVarId>,
trait_env: Arc<Environment>,
obligations: Vec<Obligation>,
method_resolutions: FxHashMap<ExprId, Function>,
field_resolutions: FxHashMap<ExprId, StructField>,
Expand All @@ -188,6 +190,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
var_unification_table: InPlaceUnificationTable::new(),
obligations: Vec::default(),
return_ty: Ty::Unknown, // set in collect_fn_signature
trait_env: lower::trait_env(db, &resolver),
db,
body,
resolver,
Expand Down Expand Up @@ -328,51 +331,25 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
fn resolve_obligations_as_possible(&mut self) {
let obligations = mem::replace(&mut self.obligations, Vec::new());
for obligation in obligations {
match &obligation {
Obligation::Trait(tr) => {
let canonicalized = self.canonicalizer().canonicalize_trait_ref(tr.clone());
let solution = self
.db
.implements(self.resolver.krate().unwrap(), canonicalized.value.clone());
match solution {
Some(Solution::Unique(substs)) => {
canonicalized.apply_solution(self, substs.0);
}
Some(Solution::Ambig(Guidance::Definite(substs))) => {
canonicalized.apply_solution(self, substs.0);
self.obligations.push(obligation);
}
Some(_) => {
// FIXME use this when trying to resolve everything at the end
self.obligations.push(obligation);
}
None => {
// FIXME obligation cannot be fulfilled => diagnostic
}
};
let in_env = InEnvironment::new(self.trait_env.clone(), obligation.clone());
let canonicalized = self.canonicalizer().canonicalize_obligation(in_env);
let solution =
self.db.solve(self.resolver.krate().unwrap(), canonicalized.value.clone());

match solution {
Some(Solution::Unique(substs)) => {
canonicalized.apply_solution(self, substs.0);
}
Obligation::Projection(pr) => {
let canonicalized = self.canonicalizer().canonicalize_projection(pr.clone());
let solution = self
.db
.normalize(self.resolver.krate().unwrap(), canonicalized.value.clone());

match solution {
Some(Solution::Unique(substs)) => {
canonicalized.apply_solution(self, substs.0);
}
Some(Solution::Ambig(Guidance::Definite(substs))) => {
canonicalized.apply_solution(self, substs.0);
self.obligations.push(obligation);
}
Some(_) => {
// FIXME use this when trying to resolve everything at the end
self.obligations.push(obligation);
}
None => {
// FIXME obligation cannot be fulfilled => diagnostic
}
};
Some(Solution::Ambig(Guidance::Definite(substs))) => {
canonicalized.apply_solution(self, substs.0);
self.obligations.push(obligation);
}
Some(_) => {
// FIXME use this when trying to resolve everything at the end
self.obligations.push(obligation);
}
None => {
// FIXME obligation cannot be fulfilled => diagnostic
}
};
}
Expand Down
34 changes: 21 additions & 13 deletions crates/ra_hir/src/ty/infer/unify.rs
@@ -1,8 +1,10 @@
//! Unification and canonicalization logic.

use super::InferenceContext;
use super::{InferenceContext, Obligation};
use crate::db::HirDatabase;
use crate::ty::{Canonical, InferTy, ProjectionPredicate, ProjectionTy, TraitRef, Ty};
use crate::ty::{
Canonical, InEnvironment, InferTy, ProjectionPredicate, ProjectionTy, TraitRef, Ty,
};

impl<'a, D: HirDatabase> InferenceContext<'a, D> {
pub(super) fn canonicalizer<'b>(&'b mut self) -> Canonicalizer<'a, 'b, D>
Expand Down Expand Up @@ -105,22 +107,28 @@ where
ProjectionPredicate { ty, projection_ty }
}

pub fn canonicalize_ty(mut self, ty: Ty) -> Canonicalized<Ty> {
let result = self.do_canonicalize_ty(ty);
self.into_canonicalized(result)
}
// FIXME: add some point, we need to introduce a `Fold` trait that abstracts
// over all the things that can be canonicalized (like Chalk and rustc have)

pub fn canonicalize_trait_ref(mut self, trait_ref: TraitRef) -> Canonicalized<TraitRef> {
let result = self.do_canonicalize_trait_ref(trait_ref);
pub(crate) fn canonicalize_ty(mut self, ty: Ty) -> Canonicalized<Ty> {
let result = self.do_canonicalize_ty(ty);
self.into_canonicalized(result)
}

pub fn canonicalize_projection(
pub(crate) fn canonicalize_obligation(
mut self,
projection: ProjectionPredicate,
) -> Canonicalized<ProjectionPredicate> {
let result = self.do_canonicalize_projection_predicate(projection);
self.into_canonicalized(result)
obligation: InEnvironment<Obligation>,
) -> Canonicalized<InEnvironment<Obligation>> {
let result = match obligation.value {
Obligation::Trait(tr) => Obligation::Trait(self.do_canonicalize_trait_ref(tr)),
Obligation::Projection(pr) => {
Obligation::Projection(self.do_canonicalize_projection_predicate(pr))
}
};
self.into_canonicalized(InEnvironment {
value: result,
environment: obligation.environment,
})
}
}

Expand Down
12 changes: 12 additions & 0 deletions crates/ra_hir/src/ty/lower.rs
Expand Up @@ -317,6 +317,18 @@ pub(crate) fn type_for_field(db: &impl HirDatabase, field: StructField) -> Ty {
Ty::from_hir(db, &resolver, type_ref)
}

pub(crate) fn trait_env(db: &impl HirDatabase, resolver: &Resolver) -> Arc<super::Environment> {
let predicates = resolver
.where_predicates_in_scope()
.map(|pred| {
TraitRef::for_where_predicate(db, &resolver, pred)
.map_or(GenericPredicate::Error, GenericPredicate::Implemented)
})
.collect::<Vec<_>>();

Arc::new(super::Environment { predicates })
}

/// Resolve the where clause(s) of an item with generics.
pub(crate) fn generic_predicates_query(
db: &impl HirDatabase,
Expand Down
21 changes: 12 additions & 9 deletions crates/ra_hir/src/ty/method_resolution.rs
Expand Up @@ -7,7 +7,7 @@ use std::sync::Arc;
use arrayvec::ArrayVec;
use rustc_hash::FxHashMap;

use super::{autoderef, Canonical, TraitRef};
use super::{autoderef, lower, Canonical, Environment, InEnvironment, TraitRef};
use crate::{
generics::HasGenericParams,
impl_block::{ImplBlock, ImplId, ImplItem},
Expand Down Expand Up @@ -198,6 +198,8 @@ fn iterate_trait_method_candidates<T>(
mut callback: impl FnMut(&Ty, Function) -> Option<T>,
) -> Option<T> {
let krate = resolver.krate()?;
// FIXME: maybe put the trait_env behind a query (need to figure out good input parameters for that)
let env = lower::trait_env(db, resolver);
'traits: for t in resolver.traits_in_scope(db) {
let data = t.trait_data(db);
// we'll be lazy about checking whether the type implements the
Expand All @@ -209,8 +211,8 @@ fn iterate_trait_method_candidates<T>(
let data = m.data(db);
if name.map_or(true, |name| data.name() == name) && data.has_self_param() {
if !known_implemented {
let trait_ref = canonical_trait_ref(db, t, ty.clone());
if db.implements(krate, trait_ref).is_none() {
let goal = generic_implements_goal(db, env.clone(), t, ty.clone());
if db.solve(krate, goal).is_none() {
continue 'traits;
}
}
Expand Down Expand Up @@ -277,11 +279,12 @@ impl Ty {

/// This creates Substs for a trait with the given Self type and type variables
/// for all other parameters, to query Chalk with it.
fn canonical_trait_ref(
fn generic_implements_goal(
db: &impl HirDatabase,
env: Arc<Environment>,
trait_: Trait,
self_ty: Canonical<Ty>,
) -> Canonical<TraitRef> {
) -> Canonical<InEnvironment<super::Obligation>> {
let mut substs = Vec::new();
let generics = trait_.generic_params(db);
let num_vars = self_ty.num_vars;
Expand All @@ -294,8 +297,8 @@ fn canonical_trait_ref(
.enumerate()
.map(|(i, _p)| Ty::Bound((i + num_vars) as u32)),
);
Canonical {
num_vars: substs.len() - 1 + self_ty.num_vars,
value: TraitRef { trait_, substs: substs.into() },
}
let num_vars = substs.len() - 1 + self_ty.num_vars;
let trait_ref = TraitRef { trait_, substs: substs.into() };
let obligation = super::Obligation::Trait(trait_ref);
Canonical { num_vars, value: InEnvironment::new(env, obligation) }
}
79 changes: 79 additions & 0 deletions crates/ra_hir/src/ty/tests.rs
Expand Up @@ -2950,6 +2950,85 @@ fn test(o: O<S>) {
assert_eq!(t, "&str");
}

#[test]
fn generic_param_env_1() {
let t = type_at(
r#"
//- /main.rs
trait Clone {}
trait Trait { fn foo(self) -> u128; }
struct S;
impl Clone for S {}
impl<T> Trait for T where T: Clone {}
fn test<T: Clone>(t: T) { t.foo()<|>; }
"#,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

This test doesn't work in IntelliJ Rust 😜

);
assert_eq!(t, "u128");
}

#[test]
fn generic_param_env_1_not_met() {
let t = type_at(
r#"
//- /main.rs
trait Clone {}
trait Trait { fn foo(self) -> u128; }
struct S;
impl Clone for S {}
impl<T> Trait for T where T: Clone {}
fn test<T>(t: T) { t.foo()<|>; }
"#,
);
assert_eq!(t, "{unknown}");
}

#[test]
fn generic_param_env_2() {
let t = type_at(
r#"
//- /main.rs
trait Trait { fn foo(self) -> u128; }
struct S;
impl Trait for S {}
fn test<T: Trait>(t: T) { t.foo()<|>; }
"#,
);
assert_eq!(t, "u128");
}

#[test]
fn generic_param_env_2_not_met() {
let t = type_at(
r#"
//- /main.rs
trait Trait { fn foo(self) -> u128; }
struct S;
impl Trait for S {}
fn test<T>(t: T) { t.foo()<|>; }
"#,
);
assert_eq!(t, "{unknown}");
}

#[test]
fn generic_param_env_deref() {
let t = type_at(
r#"
//- /main.rs
#[lang = "deref"]
trait Deref {
type Target;
}
trait Trait {}
impl<T> Deref for T where T: Trait {
type Target = i128;
}
fn test<T: Trait>(t: T) { (*t)<|>; }
"#,
);
assert_eq!(t, "i128");
}

fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String {
let file = db.parse(pos.file_id).ok().unwrap();
let expr = algo::find_node_at_offset::<ast::Expr>(file.syntax(), pos.offset).unwrap();
Expand Down