From c8b2ec8c20be44ae19d15e90ff812745f029899e Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Sun, 12 Apr 2020 12:28:24 +0200 Subject: [PATCH] Add support for bounds on associated types in trait definitions E.g. ``` trait Trait { type Item: SomeOtherTrait; } ``` Note that these don't simply desugar to where clauses; as I understand it, where clauses have to be proved by the *user* of the trait, but these bounds are proved by the *implementor*. (Also, where clauses on associated types are unstable.) --- crates/ra_hir_def/src/data.rs | 18 +++++-- crates/ra_hir_ty/src/tests/traits.rs | 27 +++++++++++ crates/ra_hir_ty/src/traits.rs | 7 ++- crates/ra_hir_ty/src/traits/chalk.rs | 72 ++++++++++++++++++++++++++-- 4 files changed, 113 insertions(+), 11 deletions(-) diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs index 56a20c5bd18..5dfde75d93e 100644 --- a/crates/ra_hir_def/src/data.rs +++ b/crates/ra_hir_def/src/data.rs @@ -9,7 +9,8 @@ use hir_expand::{ }; use ra_prof::profile; use ra_syntax::ast::{ - self, AstNode, ImplItem, ModuleItemOwner, NameOwner, TypeAscriptionOwner, VisibilityOwner, + self, AstNode, ImplItem, ModuleItemOwner, NameOwner, TypeAscriptionOwner, TypeBoundsOwner, + VisibilityOwner, }; use crate::{ @@ -106,6 +107,7 @@ pub struct TypeAliasData { pub name: Name, pub type_ref: Option, pub visibility: RawVisibility, + pub bounds: Vec, } impl TypeAliasData { @@ -118,9 +120,17 @@ impl TypeAliasData { let name = node.value.name().map_or_else(Name::missing, |n| n.as_name()); let type_ref = node.value.type_ref().map(TypeRef::from_ast); let vis_default = RawVisibility::default_for_container(loc.container); - let visibility = - RawVisibility::from_ast_with_default(db, vis_default, node.map(|n| n.visibility())); - Arc::new(TypeAliasData { name, type_ref, visibility }) + let visibility = RawVisibility::from_ast_with_default( + db, + vis_default, + node.as_ref().map(|n| n.visibility()), + ); + let bounds = if let Some(bound_list) = node.value.type_bound_list() { + bound_list.bounds().map(TypeBound::from_ast).collect() + } else { + Vec::new() + }; + Arc::new(TypeAliasData { name, type_ref, visibility, bounds }) } } diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs index 22ae6ca9092..af8c63d64f4 100644 --- a/crates/ra_hir_ty/src/tests/traits.rs +++ b/crates/ra_hir_ty/src/tests/traits.rs @@ -2022,6 +2022,33 @@ fn main() { ); } +#[test] +fn associated_type_bound() { + let t = type_at( + r#" +//- /main.rs +pub trait Trait { + type Item: OtherTrait; +} +pub trait OtherTrait { + fn foo(&self) -> T; +} + +// this is just a workaround for chalk#234 +pub struct S; +impl Trait for S { + type Item = ::Item; +} + +fn test() { + let y: as Trait>::Item = no_matter; + y.foo()<|>; +} +"#, + ); + assert_eq!(t, "u32"); +} + #[test] fn dyn_trait_through_chalk() { let t = type_at( diff --git a/crates/ra_hir_ty/src/traits.rs b/crates/ra_hir_ty/src/traits.rs index 43d8d1e802e..44fbdb19701 100644 --- a/crates/ra_hir_ty/src/traits.rs +++ b/crates/ra_hir_ty/src/traits.rs @@ -194,13 +194,16 @@ fn solve( } remaining > 0 }; - let mut solve = || solver.solve_limited(&context, goal, should_continue); + let mut solve = || { + let solution = solver.solve_limited(&context, goal, should_continue); + log::debug!("solve({:?}) => {:?}", goal, solution); + solution + }; // don't set the TLS for Chalk unless Chalk debugging is active, to make // extra sure we only use it for debugging let solution = if is_chalk_debug() { chalk::tls::set_current_program(db, solve) } else { solve() }; - log::debug!("solve({:?}) => {:?}", goal, solution); solution } diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs index e05fea8430f..12ffa69a006 100644 --- a/crates/ra_hir_ty/src/traits/chalk.rs +++ b/crates/ra_hir_ty/src/traits/chalk.rs @@ -626,6 +626,55 @@ fn convert_where_clauses( result } +fn generic_predicate_to_inline_bound( + db: &dyn HirDatabase, + pred: &GenericPredicate, + self_ty: &Ty, +) -> Option> { + // An InlineBound is like a GenericPredicate, except the self type is left out. + // We don't have a special type for this, but Chalk does. + match pred { + GenericPredicate::Implemented(trait_ref) => { + if &trait_ref.substs[0] != self_ty { + // we can only convert predicates back to type bounds if they + // have the expected self type + return None; + } + let args_no_self = trait_ref.substs[1..] + .iter() + .map(|ty| ty.clone().to_chalk(db).cast(&Interner)) + .collect(); + let trait_bound = + chalk_rust_ir::TraitBound { trait_id: trait_ref.trait_.to_chalk(db), args_no_self }; + Some(chalk_rust_ir::InlineBound::TraitBound(trait_bound)) + } + GenericPredicate::Projection(proj) => { + if &proj.projection_ty.parameters[0] != self_ty { + return None; + } + let trait_ = match proj.projection_ty.associated_ty.lookup(db.upcast()).container { + AssocContainerId::TraitId(t) => t, + _ => panic!("associated type not in trait"), + }; + let args_no_self = proj.projection_ty.parameters[1..] + .iter() + .map(|ty| ty.clone().to_chalk(db).cast(&Interner)) + .collect(); + let alias_eq_bound = chalk_rust_ir::AliasEqBound { + value: proj.ty.clone().to_chalk(db), + trait_bound: chalk_rust_ir::TraitBound { + trait_id: trait_.to_chalk(db), + args_no_self, + }, + associated_ty_id: proj.projection_ty.associated_ty.to_chalk(db), + parameters: Vec::new(), // FIXME we don't support generic associated types yet + }; + Some(chalk_rust_ir::InlineBound::AliasEqBound(alias_eq_bound)) + } + GenericPredicate::Error => None, + } +} + impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { fn associated_ty_data(&self, id: AssocTypeId) -> Arc { self.db.associated_ty_data(id) @@ -708,12 +757,25 @@ pub(crate) fn associated_ty_data_query( AssocContainerId::TraitId(t) => t, _ => panic!("associated type not in trait"), }; + + // Lower bounds -- we could/should maybe move this to a separate query in `lower` + let type_alias_data = db.type_alias_data(type_alias); let generic_params = generics(db.upcast(), type_alias.into()); - let bound_data = chalk_rust_ir::AssociatedTyDatumBound { - // FIXME add bounds and where clauses - bounds: vec![], - where_clauses: vec![], - }; + let bound_vars = Substs::bound_vars(&generic_params); + let resolver = hir_def::resolver::HasResolver::resolver(type_alias, db.upcast()); + let ctx = crate::TyLoweringContext::new(db, &resolver) + .with_type_param_mode(crate::lower::TypeParamLoweringMode::Variable); + let self_ty = Ty::Bound(crate::BoundVar::new(crate::DebruijnIndex::INNERMOST, 0)); + let bounds = type_alias_data + .bounds + .iter() + .flat_map(|bound| GenericPredicate::from_type_bound(&ctx, bound, self_ty.clone())) + .filter_map(|pred| generic_predicate_to_inline_bound(db, &pred, &self_ty)) + .map(|bound| make_binders(bound.shifted_in(&Interner), 0)) + .collect(); + + let where_clauses = convert_where_clauses(db, type_alias.into(), &bound_vars); + let bound_data = chalk_rust_ir::AssociatedTyDatumBound { bounds, where_clauses }; let datum = AssociatedTyDatum { trait_id: trait_.to_chalk(db), id,