diff --git a/Cargo.lock b/Cargo.lock index 51b6e109d79a..735285a6c17f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -712,6 +712,7 @@ dependencies = [ "syntax", "test-fixture", "test-utils", + "thin-vec", "tracing", "tracing-subscriber", "tracing-tree", diff --git a/Cargo.toml b/Cargo.toml index c4c2fdf34bae..37a4b1517338 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -160,6 +160,7 @@ tracing-subscriber = { version = "0.3.19", default-features = false, features = triomphe = { version = "0.1.14", default-features = false, features = ["std"] } url = "2.5.4" xshell = "0.2.7" +thin-vec = "0.2.14" # We need to freeze the version of the crate, as the raw-api feature is considered unstable dashmap = { version = "=6.1.0", features = ["raw-api", "inline"] } diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs index a67fbf75c02f..4c86db45ad38 100644 --- a/crates/base-db/src/lib.rs +++ b/crates/base-db/src/lib.rs @@ -28,6 +28,7 @@ use syntax::{Parse, SyntaxError, ast}; use triomphe::Arc; pub use vfs::{AnchoredPath, AnchoredPathBuf, FileId, VfsPath, file_set::FileSet}; +/// Prefer to use `impl_intern_key_ref!()`, which will not clone the value. #[macro_export] macro_rules! impl_intern_key { ($id:ident, $loc:ident) => { @@ -47,6 +48,26 @@ macro_rules! impl_intern_key { }; } +#[macro_export] +macro_rules! impl_intern_key_ref { + ($id:ident, $loc:ident) => { + #[salsa_macros::interned(no_lifetime)] + pub struct $id { + #[return_ref] + pub loc: $loc, + } + + // If we derive this salsa prints the values recursively, and this causes us to blow. + impl ::std::fmt::Debug for $id { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + f.debug_tuple(stringify!($id)) + .field(&format_args!("{:04x}", self.0.as_u32())) + .finish() + } + } + }; +} + pub const DEFAULT_FILE_TEXT_LRU_CAP: u16 = 16; pub const DEFAULT_PARSE_LRU_CAP: u16 = 128; pub const DEFAULT_BORROWCK_LRU_CAP: u16 = 2024; diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml index c1c89e8d1cc3..dd79b9d80fdd 100644 --- a/crates/hir-def/Cargo.toml +++ b/crates/hir-def/Cargo.toml @@ -44,7 +44,7 @@ mbe.workspace = true cfg.workspace = true tt.workspace = true span.workspace = true -thin-vec = "0.2.14" +thin-vec.workspace = true [dev-dependencies] expect-test.workspace = true diff --git a/crates/hir-def/src/nameres/assoc.rs b/crates/hir-def/src/nameres/assoc.rs index d45709b8b903..dafe093eb9d8 100644 --- a/crates/hir-def/src/nameres/assoc.rs +++ b/crates/hir-def/src/nameres/assoc.rs @@ -112,6 +112,13 @@ impl ImplItems { pub fn attribute_calls(&self) -> impl Iterator, MacroCallId)> + '_ { self.macro_calls.iter().flat_map(|it| it.iter()).copied() } + + pub fn method_by_name(&self, name: &Name) -> Option { + self.items.iter().find_map(|(item_name, item)| match item { + AssocItemId::FunctionId(t) if item_name == name => Some(*t), + _ => None, + }) + } } struct AssocItemCollector<'a> { diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index efa544cf3965..9a8fd75915e0 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -35,6 +35,7 @@ rustc_apfloat = "0.2.2" query-group.workspace = true salsa.workspace = true salsa-macros.workspace = true +thin-vec.workspace = true ra-ap-rustc_abi.workspace = true ra-ap-rustc_index.workspace = true diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs index cd799c03ddf7..fad8508da108 100644 --- a/crates/hir-ty/src/chalk_db.rs +++ b/crates/hir-ty/src/chalk_db.rs @@ -8,8 +8,12 @@ use intern::sym; use span::Edition; use tracing::debug; -use chalk_ir::{CanonicalVarKinds, cast::Caster, fold::shift::Shift}; -use chalk_solve::rust_ir::{self, OpaqueTyDatumBound, WellKnownTrait}; +use chalk_ir::{ + Binders, CanonicalVarKinds, + cast::{Cast, Caster}, + fold::shift::Shift, +}; +use chalk_solve::rust_ir::{self, AssociatedTyDatumBound, OpaqueTyDatumBound, WellKnownTrait}; use base_db::Crate; use hir_def::{ @@ -23,18 +27,24 @@ use hir_def::{ use crate::{ AliasEq, AliasTy, BoundVar, DebruijnIndex, Interner, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, - WhereClause, + VariableKinds, WhereClause, db::{HirDatabase, InternedCoroutine}, from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, generics::generics, - lower::LifetimeElisionKind, + lower::{LifetimeElisionKind, trait_fn_signature}, make_binders, make_single_type_binders, - mapping::{ToChalk, TypeAliasAsValue, from_chalk}, + mapping::{ + AnyImplAssocType, AnyTraitAssocType, ToChalk, from_assoc_type_value_id, from_chalk, + to_assoc_type_id_rpitit, to_assoc_type_value_id, to_assoc_type_value_id_rpitit, + }, method_resolution::{ALL_FLOAT_FPS, ALL_INT_FPS, TraitImpls, TyFingerprint}, + rpitit::{ + RpititImplAssocTy, RpititImplAssocTyId, impl_method_rpitit_values, recovery_rpitit_value, + }, to_assoc_type_id, to_chalk_trait_id, traits::ChalkContext, utils::ClosureSubst, - wrap_empty_binders, + variable_kinds_from_generics, wrap_empty_binders, }; pub(crate) type AssociatedTyDatum = chalk_solve::rust_ir::AssociatedTyDatum; @@ -54,23 +64,48 @@ pub(crate) type Variances = chalk_ir::Variances; impl chalk_solve::RustIrDatabase for ChalkContext<'_> { fn associated_ty_data(&self, id: AssocTypeId) -> Arc { - self.db.associated_ty_data(from_assoc_type_id(id)) + match from_assoc_type_id(self.db, id) { + AnyTraitAssocType::Normal(id) => self.db.associated_ty_data(id), + AnyTraitAssocType::Rpitit(assoc_type_id) => { + let assoc_type = assoc_type_id.loc(self.db); + Arc::new(AssociatedTyDatum { + id, + trait_id: to_chalk_trait_id(assoc_type.trait_id), + name: sym::synthesized_rpitit_assoc, + binders: assoc_type + .bounds + .clone() + .map(|bounds| AssociatedTyDatumBound { bounds, where_clauses: Vec::new() }), + }) + } + } } fn associated_ty_from_impl( &self, impl_id: chalk_ir::ImplId, assoc_type_id: chalk_ir::AssocTypeId, ) -> Option> { - let alias_id = from_assoc_type_id(assoc_type_id); - let trait_sig = self.db.type_alias_signature(alias_id); - self.db.impl_items(hir_def::ImplId::from_chalk(self.db, impl_id)).items.iter().find_map( - |(name, item)| match item { - AssocItemId::TypeAliasId(alias) if &trait_sig.name == name => { - Some(TypeAliasAsValue(*alias).to_chalk(self.db)) - } - _ => None, - }, - ) + match from_assoc_type_id(self.db, assoc_type_id) { + AnyTraitAssocType::Normal(alias_id) => { + let trait_sig = self.db.type_alias_signature(alias_id); + self.db + .impl_items(hir_def::ImplId::from_chalk(self.db, impl_id)) + .items + .iter() + .find_map(|(name, item)| match item { + AssocItemId::TypeAliasId(alias) if &trait_sig.name == name => { + Some(to_assoc_type_value_id(*alias)) + } + _ => None, + }) + } + AnyTraitAssocType::Rpitit(trait_assoc) => { + Some(to_assoc_type_value_id_rpitit(RpititImplAssocTyId::new( + self.db, + RpititImplAssocTy { impl_id: from_chalk(self.db, impl_id), trait_assoc }, + ))) + } + } } fn trait_datum(&self, trait_id: TraitId) -> Arc { self.db.trait_datum(self.krate, trait_id) @@ -456,8 +491,13 @@ impl chalk_solve::RustIrDatabase for ChalkContext<'_> { Arc::new(rust_ir::AdtSizeAlign::from_one_zst(false)) } fn assoc_type_name(&self, assoc_ty_id: chalk_ir::AssocTypeId) -> String { - let id = self.db.associated_ty_data(from_assoc_type_id(assoc_ty_id)).name; - self.db.type_alias_signature(id).name.display(self.db, self.edition()).to_string() + let name = match from_assoc_type_id(self.db, assoc_ty_id) { + AnyTraitAssocType::Normal(id) => self.db.type_alias_signature(id).name.clone(), + AnyTraitAssocType::Rpitit(id) => { + self.db.function_signature(id.loc(self.db).synthesized_from_method).name.clone() + } + }; + name.display(self.db, self.edition()).to_string() } fn opaque_type_name(&self, opaque_ty_id: chalk_ir::OpaqueTyId) -> String { format!("Opaque_{:?}", opaque_ty_id.0) @@ -673,7 +713,7 @@ pub(crate) fn associated_ty_data_query( let datum = AssociatedTyDatum { trait_id: to_chalk_trait_id(trait_), id: to_assoc_type_id(type_alias), - name: type_alias, + name: type_alias_data.name.symbol().clone(), binders: make_binders(db, &generic_params, bound_data), }; Arc::new(datum) @@ -700,8 +740,20 @@ pub(crate) fn trait_datum_query( fundamental: trait_data.flags.contains(TraitFlags::FUNDAMENTAL), }; let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars); + let trait_items = db.trait_items(trait_); + + let rpitits = trait_items + .items + .iter() + .filter_map(|&(_, item)| match item { + AssocItemId::FunctionId(it) => Some(it), + _ => None, + }) + .flat_map(|method| &trait_fn_signature(db, method).1) + .map(|assoc_id| to_assoc_type_id_rpitit(*assoc_id)); let associated_ty_ids = - db.trait_items(trait_).associated_types().map(to_assoc_type_id).collect(); + trait_items.associated_types().map(to_assoc_type_id).chain(rpitits).collect(); + let trait_datum_bound = rust_ir::TraitDatumBound { where_clauses }; let well_known = db.lang_attr(trait_.into()).and_then(well_known_trait_from_lang_item); let trait_datum = TraitDatum { @@ -848,12 +900,11 @@ pub(crate) fn impl_datum_query( } fn impl_def_datum(db: &dyn HirDatabase, krate: Crate, impl_id: hir_def::ImplId) -> Arc { - let trait_ref = db + let trait_ref_binders = db .impl_trait(impl_id) // ImplIds for impls where the trait ref can't be resolved should never reach Chalk - .expect("invalid impl passed to Chalk") - .into_value_and_skipped_binders() - .0; + .expect("invalid impl passed to Chalk"); + let trait_ref = trait_ref_binders.skip_binders().clone(); let impl_data = db.impl_signature(impl_id); let generic_params = generics(db, impl_id.into()); @@ -870,8 +921,9 @@ fn impl_def_datum(db: &dyn HirDatabase, krate: Crate, impl_id: hir_def::ImplId) let impl_datum_bound = rust_ir::ImplDatumBound { trait_ref, where_clauses }; let trait_data = db.trait_items(trait_); - let associated_ty_value_ids = db - .impl_items(impl_id) + let impl_items = db.impl_items(impl_id); + let trait_datum = db.trait_datum(krate, to_chalk_trait_id(trait_)); + let associated_ty_value_ids = impl_items .items .iter() .filter_map(|(_, item)| match item { @@ -883,7 +935,15 @@ fn impl_def_datum(db: &dyn HirDatabase, krate: Crate, impl_id: hir_def::ImplId) let name = &db.type_alias_signature(type_alias).name; trait_data.associated_type_by_name(name).is_some() }) - .map(|type_alias| TypeAliasAsValue(type_alias).to_chalk(db)) + .map(to_assoc_type_value_id) + .chain(trait_datum.associated_ty_ids.iter().filter_map(|&trait_assoc| { + match from_assoc_type_id(db, trait_assoc) { + AnyTraitAssocType::Rpitit(trait_assoc) => Some(to_assoc_type_value_id_rpitit( + RpititImplAssocTyId::new(db, RpititImplAssocTy { impl_id, trait_assoc }), + )), + AnyTraitAssocType::Normal(_) => None, + } + })) .collect(); debug!("impl_datum: {:?}", impl_datum_bound); let impl_datum = ImplDatum { @@ -895,13 +955,110 @@ fn impl_def_datum(db: &dyn HirDatabase, krate: Crate, impl_id: hir_def::ImplId) Arc::new(impl_datum) } +pub(crate) fn inline_bound_to_generic_predicate( + bound: &Binders>, + self_ty: Ty, +) -> QuantifiedWhereClause { + let (bound, binders) = bound.as_ref().into_value_and_skipped_binders(); + match bound { + rust_ir::InlineBound::TraitBound(trait_bound) => { + let trait_ref = TraitRef { + trait_id: trait_bound.trait_id, + substitution: Substitution::from_iter( + Interner, + iter::once(self_ty.cast(Interner)) + .chain(trait_bound.args_no_self.iter().cloned()), + ), + }; + chalk_ir::Binders::new(binders, WhereClause::Implemented(trait_ref)) + } + rust_ir::InlineBound::AliasEqBound(alias_eq) => { + let substitution = Substitution::from_iter( + Interner, + iter::once(self_ty.cast(Interner)).chain( + alias_eq + .trait_bound + .args_no_self + .iter() + .cloned() + .chain(alias_eq.parameters.iter().cloned()), + ), + ); + let alias = AliasEq { + ty: alias_eq.value.clone(), + alias: AliasTy::Projection(ProjectionTy { + associated_ty_id: alias_eq.associated_ty_id, + substitution, + }), + }; + chalk_ir::Binders::new(binders, WhereClause::AliasEq(alias)) + } + } +} + pub(crate) fn associated_ty_value_query( db: &dyn HirDatabase, krate: Crate, id: AssociatedTyValueId, ) -> Arc { - let type_alias: TypeAliasAsValue = from_chalk(db, id); - type_alias_associated_ty_value(db, krate, type_alias.0) + match from_assoc_type_value_id(db, id) { + AnyImplAssocType::Normal(type_alias) => { + type_alias_associated_ty_value(db, krate, type_alias) + } + AnyImplAssocType::Rpitit(assoc_type_id) => rpitit_associated_ty_value(db, assoc_type_id), + } +} + +/// We need cycle recovery because RPITITs can cause cycles. +pub(crate) fn associated_ty_value_cycle( + db: &dyn HirDatabase, + krate: Crate, + id: AssociatedTyValueId, +) -> Arc { + match from_assoc_type_value_id(db, id) { + AnyImplAssocType::Normal(type_alias) => { + type_alias_associated_ty_value(db, krate, type_alias) + } + AnyImplAssocType::Rpitit(assoc_type_id) => recovery_rpitit_value(db, assoc_type_id), + } +} + +fn rpitit_associated_ty_value( + db: &dyn HirDatabase, + assoc_type_id: RpititImplAssocTyId, +) -> Arc { + let assoc_type = assoc_type_id.loc(db); + let trait_assoc = assoc_type.trait_assoc.loc(db); + let all_method_assocs = + impl_method_rpitit_values(db, assoc_type.impl_id, trait_assoc.synthesized_from_method); + let trait_assoc_id = to_assoc_type_id_rpitit(assoc_type.trait_assoc); + all_method_assocs + .iter() + .find(|method_assoc| method_assoc.associated_ty_id == trait_assoc_id) + .cloned() + .unwrap_or_else(|| { + let impl_id = hir_def::ImplId::to_chalk(assoc_type.impl_id, db); + let trait_method_generics = generics(db, trait_assoc.synthesized_from_method.into()); + let impl_generics = generics(db, assoc_type.impl_id.into()); + // In this situation, we don't know even that the trait and impl generics match, therefore + // the only binders we can give to comply with the trait's binders are the trait's binders. + // However, for impl associated types chalk wants only their own generics, excluding + // those of the impl (unlike in traits), therefore we filter them here. + // Completely unlike the docs, Chalk requires both the impl generics and the associated type + // generics in the binder. + let value = Binders::new( + VariableKinds::from_iter( + Interner, + trait_assoc.bounds.binders.as_slice(Interner) + [..trait_method_generics.len_self()] + .iter() + .cloned() + .chain(variable_kinds_from_generics(db, impl_generics.iter_id())), + ), + rust_ir::AssociatedTyValueBound { ty: TyKind::Error.intern(Interner) }, + ); + Arc::new(AssociatedTyValue { associated_ty_id: trait_assoc_id, impl_id, value }) + }) } fn type_alias_associated_ty_value( @@ -1037,7 +1194,16 @@ pub(super) fn generic_predicate_to_inline_bound( Some(chalk_ir::Binders::new(binders, rust_ir::InlineBound::TraitBound(trait_bound))) } WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(projection_ty), ty }) => { - let generics = generics(db, from_assoc_type_id(projection_ty.associated_ty_id).into()); + let generic_def = match from_assoc_type_id(db, projection_ty.associated_ty_id) { + AnyTraitAssocType::Normal(type_alias) => type_alias.into(), + AnyTraitAssocType::Rpitit(_) => { + unreachable!( + "there is no way to refer to a RPITIT synthesized \ + associated type on associated type's self bounds (`type Assoc: Bound`)" + ) + } + }; + let generics = generics(db, generic_def); let parent_len = generics.parent_generics().map_or(0, |g| g.len_self()); let (trait_args, assoc_args) = projection_ty.substitution.as_slice(Interner).split_at(parent_len); diff --git a/crates/hir-ty/src/chalk_ext.rs b/crates/hir-ty/src/chalk_ext.rs index aabc4c4234db..7a82ab622405 100644 --- a/crates/hir-ty/src/chalk_ext.rs +++ b/crates/hir-ty/src/chalk_ext.rs @@ -4,7 +4,7 @@ use chalk_ir::{ FloatTy, IntTy, Mutability, Scalar, TyVariableKind, TypeOutlives, UintTy, cast::Cast, }; use hir_def::{ - DefWithBodyId, FunctionId, GenericDefId, HasModule, ItemContainerId, Lookup, TraitId, + DefWithBodyId, FunctionId, HasModule, ItemContainerId, Lookup, TraitId, builtin_type::{BuiltinFloat, BuiltinInt, BuiltinType, BuiltinUint}, hir::generics::{TypeOrConstParamData, TypeParamProvenance}, lang_item::LangItem, @@ -12,11 +12,11 @@ use hir_def::{ }; use crate::{ - AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, - ClosureId, DynTy, FnPointer, ImplTraitId, InEnvironment, Interner, Lifetime, ProjectionTy, - QuantifiedWhereClause, Substitution, TraitRef, Ty, TyBuilder, TyKind, TypeFlags, WhereClause, - db::HirDatabase, from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, - from_placeholder_idx, generics::generics, to_chalk_trait_id, utils::ClosureSubst, + AdtId, AliasEq, AliasTy, AssocTypeId, Binders, CallableDefId, CallableSig, Canonical, + CanonicalVarKinds, ClosureId, DynTy, FnPointer, ImplTraitId, InEnvironment, Interner, Lifetime, + ProjectionTy, QuantifiedWhereClause, Substitution, TraitRef, Ty, TyBuilder, TyKind, TypeFlags, + WhereClause, db::HirDatabase, from_assoc_type_id, from_chalk_trait_id, from_placeholder_idx, + generics::generics, mapping::AnyTraitAssocType, to_chalk_trait_id, utils::ClosureSubst, }; pub trait TyExt { @@ -39,7 +39,6 @@ pub trait TyExt { fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)>; fn as_raw_ptr(&self) -> Option<(&Ty, Mutability)>; fn as_reference_or_ptr(&self) -> Option<(&Ty, Rawness, Mutability)>; - fn as_generic_def(&self, db: &dyn HirDatabase) -> Option; fn callable_def(&self, db: &dyn HirDatabase) -> Option; fn callable_sig(&self, db: &dyn HirDatabase) -> Option; @@ -187,19 +186,6 @@ impl TyExt for Ty { } } - fn as_generic_def(&self, db: &dyn HirDatabase) -> Option { - match *self.kind(Interner) { - TyKind::Adt(AdtId(adt), ..) => Some(adt.into()), - TyKind::FnDef(callable, ..) => Some(GenericDefId::from_callable( - db, - db.lookup_intern_callable_def(callable.into()), - )), - TyKind::AssociatedType(type_alias, ..) => Some(from_assoc_type_id(type_alias).into()), - TyKind::Foreign(type_alias, ..) => Some(from_foreign_def_id(type_alias).into()), - _ => None, - } - } - fn callable_def(&self, db: &dyn HirDatabase) -> Option { match self.kind(Interner) { &TyKind::FnDef(def, ..) => Some(db.lookup_intern_callable_def(def.into())), @@ -346,15 +332,9 @@ impl TyExt for Ty { fn associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option { match self.kind(Interner) { - TyKind::AssociatedType(id, ..) => match from_assoc_type_id(*id).lookup(db).container { - ItemContainerId::TraitId(trait_id) => Some(trait_id), - _ => None, - }, + TyKind::AssociatedType(id, ..) => Some(assoc_type_parent_trait(db, *id)), TyKind::Alias(AliasTy::Projection(projection_ty)) => { - match from_assoc_type_id(projection_ty.associated_ty_id).lookup(db).container { - ItemContainerId::TraitId(trait_id) => Some(trait_id), - _ => None, - } + Some(assoc_type_parent_trait(db, projection_ty.associated_ty_id)) } _ => None, } @@ -405,6 +385,16 @@ impl TyExt for Ty { } } +fn assoc_type_parent_trait(db: &dyn HirDatabase, id: AssocTypeId) -> TraitId { + match from_assoc_type_id(db, id) { + AnyTraitAssocType::Normal(type_alias) => match type_alias.lookup(db).container { + ItemContainerId::TraitId(trait_id) => trait_id, + _ => panic!("`AssocTypeId` without parent trait"), + }, + AnyTraitAssocType::Rpitit(assoc_type) => assoc_type.loc(db).trait_id, + } +} + pub trait ProjectionTyExt { fn trait_ref(&self, db: &dyn HirDatabase) -> TraitRef; fn trait_(&self, db: &dyn HirDatabase) -> TraitId; @@ -414,7 +404,15 @@ pub trait ProjectionTyExt { impl ProjectionTyExt for ProjectionTy { fn trait_ref(&self, db: &dyn HirDatabase) -> TraitRef { // FIXME: something like `Split` trait from chalk-solve might be nice. - let generics = generics(db, from_assoc_type_id(self.associated_ty_id).into()); + let generic_def = match from_assoc_type_id(db, self.associated_ty_id) { + AnyTraitAssocType::Normal(type_alias) => type_alias.into(), + // FIXME: This isn't entirely correct, the generics of the RPITIT assoc type may differ from its method + // wrt. lifetimes, but we don't handle that currently. See https://rustc-dev-guide.rust-lang.org/return-position-impl-trait-in-trait.html. + AnyTraitAssocType::Rpitit(assoc_type) => { + assoc_type.loc(db).synthesized_from_method.into() + } + }; + let generics = generics(db, generic_def); let parent_len = generics.parent_generics().map_or(0, |g| g.len_self()); let substitution = Substitution::from_iter(Interner, self.substitution.iter(Interner).take(parent_len)); @@ -422,10 +420,7 @@ impl ProjectionTyExt for ProjectionTy { } fn trait_(&self, db: &dyn HirDatabase) -> TraitId { - match from_assoc_type_id(self.associated_ty_id).lookup(db).container { - ItemContainerId::TraitId(it) => it, - _ => panic!("projection ty without parent trait"), - } + assoc_type_parent_trait(db, self.associated_ty_id) } fn self_type_parameter(&self, db: &dyn HirDatabase) -> Ty { diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index 980ee264b027..b9f732cabfa5 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -187,7 +187,6 @@ pub trait HirDatabase: DefDatabase + std::fmt::Debug { fn generic_predicates_without_parent(&self, def: GenericDefId) -> GenericPredicates; #[salsa::invoke(crate::lower::trait_environment_for_body_query)] - #[salsa::transparent] fn trait_environment_for_body(&self, def: DefWithBodyId) -> Arc; #[salsa::invoke(crate::lower::trait_environment_query)] @@ -290,6 +289,7 @@ pub trait HirDatabase: DefDatabase + std::fmt::Debug { fn variances_of(&self, def: GenericDefId) -> Option>; #[salsa::invoke(chalk_db::associated_ty_value_query)] + #[salsa::cycle(cycle_result = chalk_db::associated_ty_value_cycle)] fn associated_ty_value( &self, krate: Crate, diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index f0989d9de91f..73b0e3fed2c2 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -48,6 +48,7 @@ use crate::{ LifetimeData, LifetimeOutlives, MemoryMap, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyExt, WhereClause, + chalk_db::inline_bound_to_generic_predicate, consteval::try_const_usize, db::{HirDatabase, InternedClosure}, from_assoc_type_id, from_foreign_def_id, from_placeholder_idx, @@ -55,9 +56,9 @@ use crate::{ infer::normalize, layout::Layout, lt_from_placeholder_idx, - mapping::from_chalk, + mapping::{AnyTraitAssocType, from_chalk}, mir::pad16, - primitive, to_assoc_type_id, + primitive, utils::{self, ClosureSubst, detect_variant_from_bytes}, }; @@ -653,20 +654,54 @@ impl HirDisplay for ProjectionTy { } } - write!(f, "<")?; - self_ty.hir_fmt(f)?; - write!(f, " as ")?; - trait_ref.hir_fmt(f)?; - write!( - f, - ">::{}", - f.db.type_alias_signature(from_assoc_type_id(self.associated_ty_id)) - .name - .display(f.db, f.edition()) - )?; - let proj_params = - &self.substitution.as_slice(Interner)[trait_ref.substitution.len(Interner)..]; - hir_fmt_generics(f, proj_params, None, None) + match from_assoc_type_id(f.db, self.associated_ty_id) { + AnyTraitAssocType::Normal(type_alias) => { + write!(f, "<")?; + self_ty.hir_fmt(f)?; + write!(f, " as ")?; + trait_ref.hir_fmt(f)?; + write!( + f, + ">::{}", + f.db.type_alias_signature(type_alias).name.display(f.db, f.edition()) + )?; + let proj_params = + &self.substitution.as_slice(Interner)[trait_ref.substitution.len(Interner)..]; + hir_fmt_generics(f, proj_params, None, None) + } + AnyTraitAssocType::Rpitit(assoc_type) => { + // Format RPITIT as `impl Trait`. + // FIXME: In some cases, it makes more sense to show this as RTN (`Trait::method(..)`). + // However not *all* associated types are the same as the corresponding RTN (the `impl Trait` + // can be nested). Figuring out when we should display RTN will be tricky. + let assoc_type = assoc_type.loc(f.db); + f.format_bounds_with(self.clone(), |f| { + write_bounds_like_dyn_trait_with_prefix( + f, + "impl", + Either::Left( + &TyKind::Alias(AliasTy::Projection(self.clone())).intern(Interner), + ), + &assoc_type + .bounds + .clone() + .substitute(Interner, &self.substitution) + .iter() + .map(|bound| { + // We ignore `Self` anyway when formatting, so it's fine put an error type in it. + inline_bound_to_generic_predicate( + bound, + TyKind::Error.intern(Interner), + ) + }) + .collect::>(), + SizedByDefault::Sized { + anchor: assoc_type.trait_id.lookup(f.db).container.krate(), + }, + ) + }) + } + } } } @@ -1310,35 +1345,54 @@ impl HirDisplay for Ty { } f.end_location_link(); - let generic_def = self.as_generic_def(db); - - hir_fmt_generics(f, parameters.as_slice(Interner), generic_def, None)?; + hir_fmt_generics(f, parameters.as_slice(Interner), Some((*def_id).into()), None)?; } TyKind::AssociatedType(assoc_type_id, parameters) => { - let type_alias = from_assoc_type_id(*assoc_type_id); - let trait_ = match type_alias.lookup(db).container { - ItemContainerId::TraitId(it) => it, - _ => panic!("not an associated type"), - }; - let trait_data = db.trait_signature(trait_); - let type_alias_data = db.type_alias_signature(type_alias); - // Use placeholder associated types when the target is test (https://rust-lang.github.io/chalk/book/clauses/type_equality.html#placeholder-associated-types) if f.display_kind.is_test() { - f.start_location_link(trait_.into()); - write!(f, "{}", trait_data.name.display(f.db, f.edition()))?; - f.end_location_link(); - write!(f, "::")?; + match from_assoc_type_id(f.db, *assoc_type_id) { + AnyTraitAssocType::Normal(type_alias) => { + let trait_ = match type_alias.lookup(db).container { + ItemContainerId::TraitId(it) => it, + _ => panic!("not an associated type"), + }; + let trait_data = db.trait_signature(trait_); + let type_alias_data = db.type_alias_signature(type_alias); + + f.start_location_link(trait_.into()); + write!(f, "{}", trait_data.name.display(f.db, f.edition()))?; + f.end_location_link(); + write!(f, "::")?; - f.start_location_link(type_alias.into()); - write!(f, "{}", type_alias_data.name.display(f.db, f.edition()))?; - f.end_location_link(); - // Note that the generic args for the associated type come before those for the - // trait (including the self type). - hir_fmt_generics(f, parameters.as_slice(Interner), None, None) + f.start_location_link(type_alias.into()); + write!(f, "{}", type_alias_data.name.display(f.db, f.edition()))?; + f.end_location_link(); + // Note that the generic args for the associated type come before those for the + // trait (including the self type). + hir_fmt_generics(f, parameters.as_slice(Interner), None, None) + } + AnyTraitAssocType::Rpitit(assoc_type) => { + // In tests show the associated type as is. + let assoc_type = assoc_type.loc(f.db); + + let trait_data = f.db.trait_signature(assoc_type.trait_id); + let method_data = + f.db.function_signature(assoc_type.synthesized_from_method); + + f.start_location_link(assoc_type.trait_id.into()); + write!(f, "{}", trait_data.name.display(f.db, f.edition()))?; + f.end_location_link(); + write!(f, "::")?; + + f.start_location_link(assoc_type.synthesized_from_method.into()); + write!(f, "__{}_rpitit", method_data.name.display(f.db, f.edition()))?; + f.end_location_link(); + hir_fmt_generics(f, parameters.as_slice(Interner), None, None) + } + } } else { let projection_ty = ProjectionTy { - associated_ty_id: to_assoc_type_id(type_alias), + associated_ty_id: *assoc_type_id, substitution: parameters.clone(), }; @@ -1806,7 +1860,7 @@ pub fn write_bounds_like_dyn_trait_with_prefix( } } -fn write_bounds_like_dyn_trait( +pub(crate) fn write_bounds_like_dyn_trait( f: &mut HirFormatter<'_>, this: Either<&Ty, &Lifetime>, predicates: &[QuantifiedWhereClause], @@ -1918,7 +1972,14 @@ fn write_bounds_like_dyn_trait( angle_open = true; } if let AliasTy::Projection(proj) = alias { - let assoc_ty_id = from_assoc_type_id(proj.associated_ty_id); + let assoc_ty_id = match from_assoc_type_id(f.db, proj.associated_ty_id) { + AnyTraitAssocType::Normal(it) => it, + AnyTraitAssocType::Rpitit(_) => { + unreachable!( + "Rust does not currently have a way to specify alias equation on RPITIT" + ) + } + }; let type_alias = f.db.type_alias_signature(assoc_ty_id); f.start_location_link(assoc_ty_id.into()); write!(f, "{}", type_alias.name.display(f.db, f.edition()))?; @@ -1998,7 +2059,14 @@ impl HirDisplay for WhereClause { write!(f, " as ")?; trait_ref.hir_fmt(f)?; write!(f, ">::",)?; - let type_alias = from_assoc_type_id(projection_ty.associated_ty_id); + let type_alias = match from_assoc_type_id(f.db, projection_ty.associated_ty_id) { + AnyTraitAssocType::Normal(it) => it, + AnyTraitAssocType::Rpitit(_) => { + unreachable!( + "Rust does not currently have a way to specify alias equation on RPITIT" + ) + } + }; f.start_location_link(type_alias.into()); write!( f, diff --git a/crates/hir-ty/src/dyn_compatibility.rs b/crates/hir-ty/src/dyn_compatibility.rs index ed8d8dc26240..4e03d186da2e 100644 --- a/crates/hir-ty/src/dyn_compatibility.rs +++ b/crates/hir-ty/src/dyn_compatibility.rs @@ -19,7 +19,7 @@ use crate::{ AliasEq, AliasTy, Binders, BoundVar, CallableSig, GoalData, ImplTraitId, Interner, OpaqueTyId, ProjectionTyExt, Solution, Substitution, TraitRef, Ty, TyKind, WhereClause, all_super_traits, db::HirDatabase, - from_assoc_type_id, from_chalk_trait_id, + from_chalk_trait_id, generics::{generics, trait_self_param_idx}, to_chalk_trait_id, utils::elaborate_clause_supertraits, @@ -171,18 +171,17 @@ fn bounds_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool { .filter_map(|(_, it)| match *it { AssocItemId::TypeAliasId(id) => { let assoc_ty_data = db.associated_ty_data(id); - Some(assoc_ty_data) + Some((id, assoc_ty_data)) } _ => None, }) - .any(|assoc_ty_data| { + .any(|(assoc_ty_id, assoc_ty_data)| { assoc_ty_data.binders.skip_binders().bounds.iter().any(|bound| { - let def = from_assoc_type_id(assoc_ty_data.id).into(); match bound.skip_binders() { InlineBound::TraitBound(it) => it.args_no_self.iter().any(|arg| { contains_illegal_self_type_reference( db, - def, + assoc_ty_id.into(), trait_, arg, DebruijnIndex::ONE, @@ -192,7 +191,7 @@ fn bounds_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool { InlineBound::AliasEqBound(it) => it.parameters.iter().any(|arg| { contains_illegal_self_type_reference( db, - def, + assoc_ty_id.into(), trait_, arg, DebruijnIndex::ONE, diff --git a/crates/hir-ty/src/generics.rs b/crates/hir-ty/src/generics.rs index bb4aaf788958..b5c11dddf3d4 100644 --- a/crates/hir-ty/src/generics.rs +++ b/crates/hir-ty/src/generics.rs @@ -51,6 +51,10 @@ where } impl Generics { + pub(crate) fn self_params(&self) -> &GenericParams { + &self.params + } + pub(crate) fn def(&self) -> GenericDefId { self.def } @@ -84,6 +88,14 @@ impl Generics { chain!(trait_self_param, toc) } + pub(crate) fn iter_self_type_or_consts_id( + &self, + ) -> impl DoubleEndedIterator + '_ { + self.params + .iter_type_or_consts() + .map(|(local_id, data)| (TypeOrConstParamId { parent: self.def, local_id }, data)) + } + /// Iterate over the parent params followed by self params. pub(crate) fn iter( &self, @@ -121,9 +133,11 @@ impl Generics { /// Returns total number of generic parameters in scope, including those from parent. pub(crate) fn len(&self) -> usize { - let parent = self.parent_generics().map_or(0, Generics::len); - let child = self.params.len(); - parent + child + self.len_parent() + self.len_self() + } + + pub(crate) fn len_parent(&self) -> usize { + self.parent_generics().map_or(0, Generics::len_self) } /// Returns numbers of generic parameters excluding those from parent. diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 800897c6fc3a..e013e5039597 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -29,9 +29,9 @@ use stdx::{format_to, never}; use syntax::utils::is_raw_identifier; use crate::{ - Adjust, Adjustment, AliasEq, AliasTy, Binders, BindingMode, ChalkTraitId, ClosureId, DynTy, - DynTyExt, FnAbi, FnPointer, FnSig, GenericArg, Interner, OpaqueTy, ProjectionTy, - ProjectionTyExt, Substitution, Ty, TyBuilder, TyExt, WhereClause, + Adjust, Adjustment, AliasEq, AliasTy, AnyTraitAssocType, Binders, BindingMode, ChalkTraitId, + ClosureId, DynTy, DynTyExt, FnAbi, FnPointer, FnSig, GenericArg, Interner, OpaqueTy, + ProjectionTy, ProjectionTyExt, Substitution, Ty, TyBuilder, TyExt, WhereClause, db::{HirDatabase, InternedClosure, InternedCoroutine}, error_lifetime, from_assoc_type_id, from_chalk_trait_id, from_placeholder_idx, generics::Generics, @@ -344,8 +344,11 @@ impl InferenceContext<'_> { if let WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(projection), ty }) = bound.skip_binders() { - let assoc_data = - self.db.associated_ty_data(from_assoc_type_id(projection.associated_ty_id)); + let assoc_ty_id = match from_assoc_type_id(self.db, projection.associated_ty_id) { + AnyTraitAssocType::Normal(it) => it, + AnyTraitAssocType::Rpitit(_) => continue, + }; + let assoc_data = self.db.associated_ty_data(assoc_ty_id); if !fn_traits.contains(&assoc_data.trait_id) { return None; } @@ -383,8 +386,12 @@ impl InferenceContext<'_> { projection_ty: &ProjectionTy, projected_ty: &Ty, ) -> Option> { - let container = - from_assoc_type_id(projection_ty.associated_ty_id).lookup(self.db).container; + let container = match from_assoc_type_id(self.db, projection_ty.associated_ty_id) { + AnyTraitAssocType::Normal(id) => id.lookup(self.db).container, + AnyTraitAssocType::Rpitit(id) => { + hir_def::ItemContainerId::TraitId(id.loc(self.db).trait_id) + } + }; let trait_ = match container { hir_def::ItemContainerId::TraitId(trait_) => trait_, _ => return None, diff --git a/crates/hir-ty/src/interner.rs b/crates/hir-ty/src/interner.rs index fecb3f4242a9..02f350849aab 100644 --- a/crates/hir-ty/src/interner.rs +++ b/crates/hir-ty/src/interner.rs @@ -9,8 +9,7 @@ use crate::{ TyData, TyKind, VariableKind, VariableKinds, chalk_db, tls, }; use chalk_ir::{ProgramClauseImplication, SeparatorTraitRef, Variance}; -use hir_def::TypeAliasId; -use intern::{Interned, impl_internable}; +use intern::{Interned, Symbol, impl_internable}; use smallvec::SmallVec; use std::fmt; use triomphe::Arc; @@ -69,7 +68,7 @@ impl chalk_ir::interner::Interner for Interner { type InternedVariances = SmallVec<[Variance; 16]>; type DefId = salsa::Id; type InternedAdtId = hir_def::AdtId; - type Identifier = TypeAliasId; + type Identifier = Symbol; type FnAbi = FnAbi; fn debug_adt_id( diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 128569d55dc9..29811958a539 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -30,6 +30,7 @@ mod inhabitedness; mod interner; mod lower; mod mapping; +mod rpitit; mod target_feature; mod tls; mod utils; @@ -62,7 +63,10 @@ use chalk_ir::{ interner::HasInterner, }; use either::Either; -use hir_def::{CallableDefId, GeneralConstId, TypeOrConstParamId, hir::ExprId, type_ref::Rawness}; +use hir_def::{ + CallableDefId, GeneralConstId, GenericParamId, TypeOrConstParamId, hir::ExprId, + type_ref::Rawness, +}; use hir_expand::name::Name; use indexmap::{IndexMap, map::Entry}; use intern::{Symbol, sym}; @@ -98,8 +102,9 @@ pub use lower::{ ValueTyDefId, associated_type_shorthand_candidates, diagnostics::*, }; pub use mapping::{ - from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, - lt_from_placeholder_idx, lt_to_placeholder_idx, to_assoc_type_id, to_chalk_trait_id, + AnyImplAssocType, AnyTraitAssocType, from_assoc_type_id, from_assoc_type_value_id, + from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, lt_from_placeholder_idx, + lt_to_placeholder_idx, to_assoc_type_id, to_assoc_type_value_id, to_chalk_trait_id, to_foreign_def_id, to_placeholder_idx, }; pub use method_resolution::check_orphan_rules; @@ -342,29 +347,25 @@ pub(crate) fn make_single_type_binders>( ) } +pub(crate) fn variable_kinds_from_generics( + db: &dyn HirDatabase, + generics: impl Iterator, +) -> impl Iterator { + generics.map(|x| match x { + GenericParamId::ConstParamId(id) => VariableKind::Const(db.const_param_ty(id)), + GenericParamId::TypeParamId(_) => VariableKind::Ty(chalk_ir::TyVariableKind::General), + GenericParamId::LifetimeParamId(_) => VariableKind::Lifetime, + }) +} + pub(crate) fn make_binders>( db: &dyn HirDatabase, generics: &Generics, value: T, ) -> Binders { - Binders::new(variable_kinds_from_iter(db, generics.iter_id()), value) -} - -pub(crate) fn variable_kinds_from_iter( - db: &dyn HirDatabase, - iter: impl Iterator, -) -> VariableKinds { - VariableKinds::from_iter( - Interner, - iter.map(|x| match x { - hir_def::GenericParamId::ConstParamId(id) => { - chalk_ir::VariableKind::Const(db.const_param_ty(id)) - } - hir_def::GenericParamId::TypeParamId(_) => { - chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General) - } - hir_def::GenericParamId::LifetimeParamId(_) => chalk_ir::VariableKind::Lifetime, - }), + Binders::new( + VariableKinds::from_iter(Interner, variable_kinds_from_generics(db, generics.iter_id())), + value, ) } diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index ea8e7cc2be90..b8e0b6d07ce2 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -22,16 +22,17 @@ use chalk_ir::{ interner::HasInterner, }; +use chalk_solve::rust_ir; use either::Either; use hir_def::{ AdtId, AssocItemId, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, ItemContainerId, LocalFieldId, - Lookup, StaticId, StructId, TypeAliasId, TypeOrConstParamId, UnionId, VariantId, + Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, UnionId, VariantId, builtin_type::BuiltinType, expr_store::{ExpressionStore, path::Path}, hir::generics::{GenericParamDataRef, TypeOrConstParamData, WherePredicate}, item_tree::FieldsShape, - lang_item::LangItem, + lang_item::{LangItem, lang_item}, resolver::{HasResolver, LifetimeNs, Resolver, TypeNs}, signatures::{FunctionSignature, TraitFlags, TypeAliasFlags}, type_ref::{ @@ -43,14 +44,16 @@ use hir_expand::name::Name; use la_arena::{Arena, ArenaMap}; use rustc_hash::FxHashSet; use stdx::{impl_from, never}; +use thin_vec::ThinVec; use triomphe::{Arc, ThinArc}; use crate::{ AliasTy, Binders, BoundVar, CallableSig, Const, DebruijnIndex, DynTy, FnAbi, FnPointer, FnSig, FnSubst, ImplTrait, ImplTraitId, ImplTraits, Interner, Lifetime, LifetimeData, - LifetimeOutlives, PolyFnSig, ProgramClause, QuantifiedWhereClause, QuantifiedWhereClauses, - Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder, TyKind, WhereClause, - all_super_traits, + LifetimeOutlives, PlaceholderIndex, PolyFnSig, ProgramClause, ProjectionTy, + QuantifiedWhereClause, QuantifiedWhereClauses, Substitution, TraitEnvironment, TraitRef, + TraitRefExt, Ty, TyBuilder, TyKind, VariableKinds, WhereClause, all_super_traits, + chalk_db::generic_predicate_to_inline_bound, consteval::{intern_const_ref, path_to_const, unknown_const, unknown_const_as_generic}, db::HirDatabase, error_lifetime, @@ -60,10 +63,11 @@ use crate::{ path::{PathDiagnosticCallback, PathLoweringContext}, }, make_binders, - mapping::{ToChalk, from_chalk_trait_id, lt_to_placeholder_idx}, + mapping::{ToChalk, from_chalk_trait_id, lt_to_placeholder_idx, to_assoc_type_id_rpitit}, + rpitit::{RpititTraitAssocTy, RpititTraitAssocTyId, add_method_body_rpitit_clauses}, static_lifetime, to_chalk_trait_id, to_placeholder_idx, utils::all_super_trait_refs, - variable_kinds_from_iter, + variable_kinds_from_generics, }; #[derive(Debug, Default)] @@ -74,10 +78,12 @@ struct ImplTraitLoweringState { mode: ImplTraitLoweringMode, // This is structured as a struct with fields and not as an enum because it helps with the borrow checker. opaque_type_data: Arena, + /// The associated types that were synthesized for `impl Trait`s if `mode` is [`ImplTraitLoweringMode::AssocType`]. + synthesized_assoc_types: Vec, } impl ImplTraitLoweringState { fn new(mode: ImplTraitLoweringMode) -> ImplTraitLoweringState { - Self { mode, opaque_type_data: Arena::new() } + Self { mode, opaque_type_data: Arena::new(), synthesized_assoc_types: Vec::new() } } } @@ -255,6 +261,12 @@ pub enum ImplTraitLoweringMode { /// i.e. for arguments of the function we're currently checking, and return /// types of functions we're calling. Opaque, + /// `impl Trait` gets lowered into a synthesized associated type, represented as + /// [`RpititTraitAssocTy`]. This is used when lowering RPITIT (Return Position Impl + /// Trait In Traits) in traits (not impls; inside an impl, RPITIT gets lowered into + /// an opaque then the return type is unified with that of the trait method to tell + /// the value of the associated types). + AssocType, /// `impl Trait` is disallowed and will be an error. #[default] Disallowed, @@ -438,14 +450,14 @@ impl<'a> TyLoweringContext<'a> { |a| ImplTraitId::TypeAliasImplTrait(a, idx), ); let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into(); - let generics = generics(self.db, origin.either(|f| f.into(), |a| a.into())); - let parameters = generics.bound_vars_subst(self.db, self.in_binders); + let parameters = self.subst_for_generics(); TyKind::OpaqueType(opaque_ty_id, parameters).intern(Interner) } ImplTraitLoweringMode::Disallowed => { // FIXME: report error TyKind::Error.intern(Interner) } + ImplTraitLoweringMode::AssocType => self.lower_rpitit_in_trait(bounds), } } TypeRef::Error => TyKind::Error.intern(Interner), @@ -453,6 +465,141 @@ impl<'a> TyLoweringContext<'a> { (ty, res) } + /// Lowers a Return Position Impl Trait In Traits in the trait (not the impl). + /// + /// RPITITs create a synthesized associated type for each `impl Trait`. For example, + /// for the following trait: + /// ```ignore + /// trait Trait<'a, T, const N: usize> { + /// fn foo<'b, U>(&self) -> impl Future; + /// } + /// ``` + /// We desugar it to the following: + /// ```ignore + /// trait Trait<'a, T, const N: usize> { + /// type FooRpitit1<'b, U>: Display; + /// type FooRpitit2<'b, U>: Future>; + /// fn foo<'b, U>(&self) -> Self::FooRpitit2<'b, U>; + /// } + /// ``` + /// Actually, lifetime parameters are lowered somewhat differently in rustc, but I didn't duplicate that here + /// (because we don't handle lifetimes generally yet). + /// + /// The way we implement this is that when we lower a trait method and encounter an `impl Trait`, + /// we intern a [`RpititTraitAssocTyId`] containing the bounds, and we collect all such instances + /// within a method. When asking for the trait datum, we walk its method and collect all of their + /// RPITITs. + /// + /// Then, we need to infer the value for these associated types for an impl. We do that in `impl_rpitit_values()`, + /// but the outline of the process is as follows: we walk the methods, and for each method we take its return + /// type in the impl, and equate with the its return type in the trait with all RPITITs swapped with inference vars. + /// Then those inference vars are the values for the associated types. + /// + /// For example, consider: + /// ```ignore + /// trait Trait { + /// fn foo(&self) -> impl Debug; + /// } + /// + /// impl Trait for Foo { + /// fn foo(&self) -> Option; + /// } + /// ``` + /// The equation will tell us that the hidden associated type has value `Option` (note: this + /// `impl Debug` is **not** a RPITIT, it's a normal function RPIT!). + fn lower_rpitit_in_trait(&mut self, bounds: &[TypeBound]) -> Ty { + let method_generics = self.generics(); + let Some(GenericDefId::FunctionId(method_id)) = self.resolver.generic_def() else { + panic!("`ImplTraitLoweringMode::AssocType` used outside a method"); + }; + let ItemContainerId::TraitId(trait_id) = method_id.loc(self.db).container else { + panic!("`ImplTraitLoweringMode::AssocType` used outside a trait method"); + }; + + let assoc_type_binders = VariableKinds::from_iter( + Interner, + variable_kinds_from_generics(self.db, method_generics.iter_id()), + ); + + let returned_subst = self.subst_for_generics(); + + // This is a placeholder (pun intended): we insert it and then remove it. + // Ideally it'd be a projection `Self::SynthesizedAssoc`, but we have no way to refer + // to the associated type here because it was not created yet! + let self_ty = TyKind::Placeholder(PlaceholderIndex { + ui: chalk_ir::UniverseIndex::ROOT, + idx: usize::MAX, + }) + .intern(Interner); + let mut assoc_type_bounds = Vec::new(); + let db = self.db; + // FIXME: `DebruijnIndex::INNERMOST` does not seem correct here, we need level 1 binder + // (level 0 is the bound itself binders). But `lower_type_bound()` shifts the bound in. + // I guess what we actually need is for `ParamLoweringMode::Variable` to contain the debruijn + // index we want to lower generic parameters to, then another field for binders of HRTB. + // But since we don't handle HRTB at all currently this should be fine for now. + self.with_debruijn(DebruijnIndex::INNERMOST, |this| { + let old_param_lowering_mode = + mem::replace(&mut this.type_param_mode, ParamLoweringMode::Variable); + for bound in bounds { + for bound in this.lower_type_bound(bound, self_ty.clone(), false) { + let bound = generic_predicate_to_inline_bound(db, &bound, &self_ty); + if let Some(bound) = bound { + assoc_type_bounds.push(bound); + }; + } + } + + if !this.unsized_types.contains(&self_ty) { + let sized_trait = lang_item(db, this.resolver.krate(), LangItem::Sized) + .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id)); + let sized_bound = sized_trait.map(|sized_trait| { + let trait_bound = rust_ir::TraitBound { + trait_id: sized_trait, + args_no_self: Default::default(), + }; + let inline_bound = rust_ir::InlineBound::TraitBound(trait_bound); + chalk_ir::Binders::empty(Interner, inline_bound) + }); + if let Some(sized_bound) = sized_bound { + assoc_type_bounds.push(sized_bound); + } + } else { + // Because we used a placeholder, we must remove it before we proceed, otherwise it can affect other RPITITs. + this.unsized_types.remove(&self_ty); + } + + this.type_param_mode = old_param_lowering_mode; + }); + assoc_type_bounds.shrink_to_fit(); + + let assoc_type = RpititTraitAssocTyId::new( + self.db, + RpititTraitAssocTy { + trait_id, + synthesized_from_method: method_id, + bounds: Binders::new(assoc_type_binders, assoc_type_bounds), + }, + ); + self.impl_trait_mode.synthesized_assoc_types.push(assoc_type); + + // Now, in the place of the RPITIT, we insert a projection into this synthesized associated type. + TyKind::Alias(AliasTy::Projection(ProjectionTy { + associated_ty_id: to_assoc_type_id_rpitit(assoc_type), + substitution: returned_subst, + })) + .intern(Interner) + } + + /// Returns a `Substitution` for the current owner, with the expected param lowering mode. + fn subst_for_generics(&mut self) -> Substitution { + let generics = self.generics(); + match self.type_param_mode { + ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db), + ParamLoweringMode::Variable => generics.bound_vars_subst(self.db, self.in_binders), + } + } + /// This is only for `generic_predicates_for_param`, where we can't just /// lower the self types of the predicates since that could lead to cycles. /// So we just check here if the `type_ref` resolves to a generic param, and which. @@ -776,12 +923,28 @@ impl<'a> TyLoweringContext<'a> { /// Build the signature of a callable item (function, struct or enum variant). pub(crate) fn callable_item_signature_query(db: &dyn HirDatabase, def: CallableDefId) -> PolyFnSig { match def { - CallableDefId::FunctionId(f) => fn_sig_for_fn(db, f), + CallableDefId::FunctionId(f) => { + let container = f.loc(db).container; + match container { + ItemContainerId::TraitId(_) => trait_fn_signature(db, f).0.clone(), + _ => fn_sig_for_fn(db, f, ImplTraitLoweringMode::Opaque).0, + } + } CallableDefId::StructId(s) => fn_sig_for_struct_constructor(db, s), CallableDefId::EnumVariantId(e) => fn_sig_for_enum_variant_constructor(db, e), } } +#[salsa_macros::tracked(return_ref)] +pub(crate) fn trait_fn_signature( + db: &dyn HirDatabase, + def: FunctionId, +) -> (PolyFnSig, ThinVec) { + let (sig, rpitit_assoc_types) = fn_sig_for_fn(db, def, ImplTraitLoweringMode::AssocType); + let rpitit_assoc_types = ThinVec::from_iter(rpitit_assoc_types); + (sig, rpitit_assoc_types) +} + pub fn associated_type_shorthand_candidates( db: &dyn HirDatabase, def: GenericDefId, @@ -1017,7 +1180,16 @@ pub(crate) fn trait_environment_for_body_query( let krate = def.module(db).krate(); return TraitEnvironment::empty(krate); }; - db.trait_environment(def) + + let generics = generics(db, def); + let (resolver, traits_in_scope, mut clauses) = trait_environment_shared(db, def, &generics); + + if let GenericDefId::FunctionId(function) = def { + add_method_body_rpitit_clauses(db, &generics, &mut clauses, function); + } + + let env = chalk_ir::Environment::new(Interner).add_clauses(Interner, clauses); + TraitEnvironment::new(resolver, None, traits_in_scope.into_boxed_slice(), env) } pub(crate) fn trait_environment_query( @@ -1025,6 +1197,16 @@ pub(crate) fn trait_environment_query( def: GenericDefId, ) -> Arc { let generics = generics(db, def); + let (resolver, traits_in_scope, clauses) = trait_environment_shared(db, def, &generics); + let env = chalk_ir::Environment::new(Interner).add_clauses(Interner, clauses); + TraitEnvironment::new(resolver, None, traits_in_scope.into_boxed_slice(), env) +} + +fn trait_environment_shared( + db: &dyn HirDatabase, + def: GenericDefId, + generics: &Generics, +) -> (Crate, Vec<(Ty, TraitId)>, Vec) { let resolver = def.resolver(db); let mut ctx = TyLoweringContext::new( db, @@ -1037,7 +1219,7 @@ pub(crate) fn trait_environment_query( let mut traits_in_scope = Vec::new(); let mut clauses = Vec::new(); for maybe_parent_generics in - std::iter::successors(Some(&generics), |generics| generics.parent_generics()) + std::iter::successors(Some(generics), |generics| generics.parent_generics()) { ctx.store = maybe_parent_generics.store(); for pred in maybe_parent_generics.where_predicates() { @@ -1077,9 +1259,7 @@ pub(crate) fn trait_environment_query( }; } - let env = chalk_ir::Environment::new(Interner).add_clauses(Interner, clauses); - - TraitEnvironment::new(resolver.krate(), None, traits_in_scope.into_boxed_slice(), env) + (resolver.krate(), traits_in_scope, clauses) } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1277,7 +1457,10 @@ pub(crate) fn generic_defaults_with_diagnostics_query( p: GenericParamDataRef<'_>, generic_params: &Generics, ) -> (Binders, bool) { - let binders = variable_kinds_from_iter(ctx.db, generic_params.iter_id().take(idx)); + let binders = VariableKinds::from_iter( + Interner, + variable_kinds_from_generics(ctx.db, generic_params.iter_id().take(idx)), + ); match p { GenericParamDataRef::TypeParamData(p) => { let ty = p.default.as_ref().map_or_else( @@ -1322,7 +1505,11 @@ pub(crate) fn generic_defaults_with_diagnostics_cycle_result( (GenericDefaults(None), None) } -fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { +fn fn_sig_for_fn( + db: &dyn HirDatabase, + def: FunctionId, + return_type_impl_trait_mode: ImplTraitLoweringMode, +) -> (PolyFnSig, Vec) { let data = db.function_signature(def); let resolver = def.resolver(db); let mut ctx_params = TyLoweringContext::new( @@ -1335,6 +1522,7 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { .with_type_param_mode(ParamLoweringMode::Variable); let params = data.params.iter().map(|&tr| ctx_params.lower_ty(tr)); + let mut rpitit_assoc_types = Vec::new(); let ret = match data.ret_type { Some(ret_type) => { let mut ctx_ret = TyLoweringContext::new( @@ -1344,9 +1532,11 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { def.into(), LifetimeElisionKind::for_fn_ret(), ) - .with_impl_trait_mode(ImplTraitLoweringMode::Opaque) + .with_impl_trait_mode(return_type_impl_trait_mode) .with_type_param_mode(ParamLoweringMode::Variable); - ctx_ret.lower_ty(ret_type) + let ret_type = ctx_ret.lower_ty(ret_type); + rpitit_assoc_types = ctx_ret.impl_trait_mode.synthesized_assoc_types; + ret_type } None => TyKind::Tuple(0, Substitution::empty(Interner)).intern(Interner), }; @@ -1358,7 +1548,8 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { if data.is_unsafe() { Safety::Unsafe } else { Safety::Safe }, data.abi.as_ref().map_or(FnAbi::Rust, FnAbi::from_symbol), ); - make_binders(db, &generics, sig) + let sig = make_binders(db, &generics, sig); + (sig, rpitit_assoc_types) } /// Build the declared type of a function. This should not need to look at the diff --git a/crates/hir-ty/src/lower/path.rs b/crates/hir-ty/src/lower/path.rs index 726eaf8b0a1d..69344c80fbfe 100644 --- a/crates/hir-ty/src/lower/path.rs +++ b/crates/hir-ty/src/lower/path.rs @@ -911,7 +911,9 @@ impl<'a, 'b> PathLoweringContext<'a, 'b> { (TypeRef::ImplTrait(_), ImplTraitLoweringMode::Disallowed) => (), ( _, - ImplTraitLoweringMode::Disallowed | ImplTraitLoweringMode::Opaque, + ImplTraitLoweringMode::Disallowed + | ImplTraitLoweringMode::Opaque + | ImplTraitLoweringMode::AssocType, ) => { let ty = this.ctx.lower_ty(type_ref); let alias_eq = AliasEq { diff --git a/crates/hir-ty/src/mapping.rs b/crates/hir-ty/src/mapping.rs index 2abc1ac62a99..93db0c04ec7e 100644 --- a/crates/hir-ty/src/mapping.rs +++ b/crates/hir-ty/src/mapping.rs @@ -13,7 +13,9 @@ use salsa::{ use crate::{ AssocTypeId, CallableDefId, ChalkTraitId, FnDefId, ForeignDefId, Interner, OpaqueTyId, - PlaceholderIndex, chalk_db, db::HirDatabase, + PlaceholderIndex, chalk_db, + db::HirDatabase, + rpitit::{RpititImplAssocTyId, RpititTraitAssocTyId}, }; pub(crate) trait ToChalk { @@ -22,6 +24,18 @@ pub(crate) trait ToChalk { fn from_chalk(db: &dyn HirDatabase, chalk: Self::Chalk) -> Self; } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa_macros::Supertype)] +pub enum AnyTraitAssocType { + Normal(TypeAliasId), + Rpitit(RpititTraitAssocTyId), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa_macros::Supertype)] +pub enum AnyImplAssocType { + Normal(TypeAliasId), + Rpitit(RpititImplAssocTyId), +} + pub(crate) fn from_chalk(db: &dyn HirDatabase, chalk: ChalkT) -> T where T: ToChalk, @@ -53,23 +67,6 @@ impl ToChalk for CallableDefId { } } -pub(crate) struct TypeAliasAsValue(pub(crate) TypeAliasId); - -impl ToChalk for TypeAliasAsValue { - type Chalk = chalk_db::AssociatedTyValueId; - - fn to_chalk(self, _db: &dyn HirDatabase) -> chalk_db::AssociatedTyValueId { - rust_ir::AssociatedTyValueId(self.0.as_id()) - } - - fn from_chalk( - _db: &dyn HirDatabase, - assoc_ty_value_id: chalk_db::AssociatedTyValueId, - ) -> TypeAliasAsValue { - TypeAliasAsValue(TypeAliasId::from_id(assoc_ty_value_id.0)) - } -} - impl From for crate::db::InternedCallableDefId { fn from(fn_def_id: FnDefId) -> Self { Self::from_id(fn_def_id.0) @@ -130,8 +127,29 @@ pub fn to_assoc_type_id(id: TypeAliasId) -> AssocTypeId { chalk_ir::AssocTypeId(id.as_id()) } -pub fn from_assoc_type_id(id: AssocTypeId) -> TypeAliasId { - FromId::from_id(id.0) +pub(crate) fn to_assoc_type_id_rpitit(id: RpititTraitAssocTyId) -> AssocTypeId { + chalk_ir::AssocTypeId(id.as_id()) +} + +pub fn from_assoc_type_id(db: &dyn HirDatabase, id: AssocTypeId) -> AnyTraitAssocType { + salsa::plumbing::FromIdWithDb::from_id(id.0, db) +} + +pub fn to_assoc_type_value_id(id: TypeAliasId) -> chalk_db::AssociatedTyValueId { + rust_ir::AssociatedTyValueId(id.as_id()) +} + +pub(crate) fn to_assoc_type_value_id_rpitit( + id: RpititImplAssocTyId, +) -> chalk_db::AssociatedTyValueId { + rust_ir::AssociatedTyValueId(id.as_id()) +} + +pub fn from_assoc_type_value_id( + db: &dyn HirDatabase, + id: chalk_db::AssociatedTyValueId, +) -> AnyImplAssocType { + salsa::plumbing::FromIdWithDb::from_id(id.0, db) } pub fn from_placeholder_idx(db: &dyn HirDatabase, idx: PlaceholderIndex) -> TypeOrConstParamId { diff --git a/crates/hir-ty/src/rpitit.rs b/crates/hir-ty/src/rpitit.rs new file mode 100644 index 000000000000..a2c5c2ecec4b --- /dev/null +++ b/crates/hir-ty/src/rpitit.rs @@ -0,0 +1,654 @@ +//! This module contains the implementation of Return Position Impl Trait In Traits. + +use std::{iter, sync::Arc}; + +use base_db::impl_intern_key_ref; +use chalk_ir::{ + BoundVar, DebruijnIndex, + cast::Cast, + fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}, +}; +use chalk_solve::rust_ir::AssociatedTyValueBound; +use hir_def::{ + ConstParamId, FunctionId, GenericDefId, GenericParamId, ImplId, ItemContainerId, TraitId, + hir::generics::{GenericParams, TypeOrConstParamData}, + resolver::HasResolver, +}; +use rustc_hash::FxHashMap; +use thin_vec::ThinVec; + +use crate::{ + AliasEq, AliasTy, AnyTraitAssocType, Binders, Const, ConstData, ConstValue, DomainGoal, Goal, + GoalData, ImplTraitLoweringMode, InferenceTable, Interner, Lifetime, LifetimeData, + LifetimeElisionKind, ParamLoweringMode, PlaceholderIndex, ProgramClause, ProjectionTy, + Substitution, TraitRef, Ty, TyKind, TyLoweringContext, VariableKinds, WhereClause, + chalk_db::{AssociatedTyValue, inline_bound_to_generic_predicate}, + db::HirDatabase, + error_lifetime, from_assoc_type_id, from_chalk_trait_id, from_placeholder_idx, + generics::{Generics, generics}, + lt_from_placeholder_idx, + mapping::{ToChalk, to_assoc_type_id_rpitit}, + to_placeholder_idx, variable_kinds_from_generics, +}; + +/// An associated type synthesized from a Return Position Impl Trait In Trait +/// of the trait (not the impls). +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RpititTraitAssocTy { + pub trait_id: TraitId, + /// The method that contains this RPITIT. + pub synthesized_from_method: FunctionId, + /// The bounds of this associated type (coming from the `impl Bounds`). + /// + /// The generics are the generics of the method (with some modifications that we + /// don't currently implement, see https://rustc-dev-guide.rust-lang.org/return-position-impl-trait-in-trait.html). + pub bounds: Binders>>, +} + +impl_intern_key_ref!(RpititTraitAssocTyId, RpititTraitAssocTy); + +/// An associated type synthesized from a Return Position Impl Trait In Trait +/// of the impl (not the trait). +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RpititImplAssocTy { + pub impl_id: ImplId, + /// The definition of this associated type in the trait. + pub trait_assoc: RpititTraitAssocTyId, +} + +impl_intern_key_ref!(RpititImplAssocTyId, RpititImplAssocTy); + +fn impl_method_rpitit_values_cycle( + _db: &dyn HirDatabase, + _impl_id: ImplId, + _trait_method_id: FunctionId, +) -> ThinVec> { + ThinVec::new() +} + +// We return a list and not a hasmap because the number of RPITITs in a function should be small. +#[salsa_macros::tracked(return_ref, cycle_result = impl_method_rpitit_values_cycle)] +pub(crate) fn impl_method_rpitit_values( + db: &dyn HirDatabase, + impl_id: ImplId, + trait_method_id: FunctionId, +) -> ThinVec> { + let impl_items = db.impl_items(impl_id); + let trait_method_generics = generics(db, trait_method_id.into()); + let trait_method = db.function_signature(trait_method_id); + let impl_trait_ref = db.impl_trait(impl_id).expect("invalid impl passed to Chalk"); + let impl_method = impl_items.method_by_name(&trait_method.name); + let impl_method = match impl_method { + Some(impl_method) => impl_method, + None => { + // Method not in the impl, so it is defaulted. + return defaulted_impl_method_rpitit_values( + db, + impl_id, + trait_method_id, + impl_trait_ref, + &trait_method_generics, + ); + } + }; + + let impl_method_generics = generics(db, impl_method.into()); + + // First, just so we won't ICE, check that the impl method generics match the trait method generics. + if !check_method_generics_are_structurally_compatible( + trait_method_generics.self_params(), + impl_method_generics.self_params(), + ) { + return ThinVec::new(); + } + + // The inference algorithm works as follows: in the trait method, we replace each RPITIT with an infer var, + // then we equate the return type of the trait method with the return type of the impl method. The values + // of the inference vars now represent the value of the RPITIT assoc types. + let mut table = InferenceTable::new(db, db.trait_environment(impl_method.into())); + let impl_method_placeholder_subst = impl_method_generics.placeholder_subst(db); + + let impl_method_ret = db + .callable_item_signature(impl_method.into()) + .substitute(Interner, &impl_method_placeholder_subst) + .ret() + .clone(); + let impl_method_ret = table.normalize_associated_types_in(impl_method_ret); + + // Create mapping from trait to impl (i.e. impl trait header + impl method identity args). + let trait_ref_placeholder_subst = + &impl_method_placeholder_subst.as_slice(Interner)[..impl_method_generics.len_parent()]; + // We want to substitute the TraitRef with placeholders, but placeholders from the method, not the impl. + let impl_trait_ref = impl_trait_ref.substitute(Interner, trait_ref_placeholder_subst); + let trait_to_impl_args = rebase_impl_params_onto_trait( + db, + &impl_trait_ref.substitution, + &impl_method_generics, + &trait_method_generics, + ); + let trait_method_ret = db + .callable_item_signature(trait_method_id.into()) + .substitute(Interner, &trait_to_impl_args) + .ret() + .clone(); + let mut rpitit_to_infer_var_folder = RpititToInferVarFolder { + db, + table: &mut table, + trait_method_id, + trait_rpitit_to_infer_var: FxHashMap::default(), + }; + let trait_method_ret = + trait_method_ret.fold_with(&mut rpitit_to_infer_var_folder, DebruijnIndex::INNERMOST); + let trait_rpitit_to_infer_var = rpitit_to_infer_var_folder.trait_rpitit_to_infer_var; + let trait_method_ret = table.normalize_associated_types_in(trait_method_ret); + + table.resolve_obligations_as_possible(); + // Even if unification fails, we want to continue. We will fill the RPITITs with error types. + table.unify(&trait_method_ret, &impl_method_ret); + table.resolve_obligations_as_possible(); + + trait_rpitit_to_infer_var + .into_iter() + .map(|(trait_assoc_id, infer_var)| { + let impl_rpitit = table.resolve_completely(infer_var); + // FIXME: We may be able to get rid of `PlaceholderToBoundVarFolder` and some other stuff if we lower + // the return type with `ParamLoweringMode::Variable`, but for some reason the unification does not work then. + let impl_rpitit = impl_rpitit.fold_with( + &mut PlaceholderToBoundVarFolder { + db, + method: impl_method.into(), + method_generics: impl_method_generics.self_params(), + parent: impl_id.into(), + parent_generics: impl_method_generics + .parent_generics() + .expect("parent should be an impl") + .self_params(), + }, + DebruijnIndex::INNERMOST, + ); + let trait_assoc = trait_assoc_id.loc(db); + // Completely unlike the docs, Chalk requires both the impl generics and the associated type + // generics in the binder. + let impl_rpitit_binders = VariableKinds::from_iter( + Interner, + variable_kinds_from_generics(db, impl_method_generics.iter_parent_id()).chain( + trait_assoc.bounds.binders.as_slice(Interner) + [trait_method_generics.len_parent()..] + .iter() + .cloned(), + ), + ); + let impl_rpitit = + Binders::new(impl_rpitit_binders, AssociatedTyValueBound { ty: impl_rpitit }); + Arc::new(AssociatedTyValue { + associated_ty_id: to_assoc_type_id_rpitit(trait_assoc_id), + impl_id: ImplId::to_chalk(impl_id, db), + value: impl_rpitit, + }) + }) + .collect() +} + +fn defaulted_impl_method_rpitit_values( + db: &dyn HirDatabase, + impl_id: ImplId, + trait_method_id: FunctionId, + impl_trait_ref: Binders, + trait_method_generics: &Generics, +) -> ThinVec> { + let defaulted_rpitit_values = defaulted_trait_method_rpitit_values(db, trait_method_id); + let impl_generics = generics(db, impl_id.into()); + // The associated type generics as the same as the trait method's, but we take the impl as + // the parent instead of the trait. + // The impl generics need to be shifted to account for the associated type generics. + let trait_method_subst = trait_method_generics.bound_vars_subst(db, DebruijnIndex::INNERMOST); + let impl_subst = Substitution::from_iter( + Interner, + impl_generics.iter_id().enumerate().map(|(idx, id)| match id { + GenericParamId::ConstParamId(id) => { + BoundVar::new(DebruijnIndex::INNERMOST, idx + trait_method_generics.len_self()) + .to_const(Interner, db.const_param_ty(id)) + .cast(Interner) + } + GenericParamId::TypeParamId(_) => { + BoundVar::new(DebruijnIndex::INNERMOST, idx + trait_method_generics.len_self()) + .to_ty(Interner) + .cast(Interner) + } + GenericParamId::LifetimeParamId(_) => { + BoundVar::new(DebruijnIndex::INNERMOST, idx + trait_method_generics.len_self()) + .to_lifetime(Interner) + .cast(Interner) + } + }), + ); + let impl_trait_ref = impl_trait_ref.substitute(Interner, &impl_subst); + let impl_rpitit_subst = + Substitution::from_iter( + Interner, + impl_trait_ref.substitution.as_slice(Interner).iter().chain( + &trait_method_subst.as_slice(Interner)[trait_method_generics.len_parent()..], + ), + ); + let binders = VariableKinds::from_iter( + Interner, + variable_kinds_from_generics( + db, + impl_generics.iter_id().chain(trait_method_generics.iter_self_id()), + ), + ); + defaulted_rpitit_values + .iter() + .map(|(trait_assoc, trait_rpitit)| { + let impl_rpitit = trait_rpitit.clone().substitute(Interner, &impl_rpitit_subst); + Arc::new(AssociatedTyValue { + associated_ty_id: to_assoc_type_id_rpitit(*trait_assoc), + impl_id: ImplId::to_chalk(impl_id, db), + value: Binders::new(binders.clone(), AssociatedTyValueBound { ty: impl_rpitit }), + }) + }) + .collect() +} + +fn defaulted_trait_method_rpitit_values_cycle( + _db: &dyn HirDatabase, + _method_id: FunctionId, +) -> ThinVec<(RpititTraitAssocTyId, Binders)> { + ThinVec::new() +} + +/// This is called only for defaulted trait methods, as there the value of the RPITIT associated +/// items on an impl (if the method body is left defaulted) is the same as with the trait method. +// This returns an `ThinVec` and not `Box<[]>` because this is called from inference, +// and most methods don't have RPITITs. +#[salsa_macros::tracked(return_ref, cycle_result = defaulted_trait_method_rpitit_values_cycle)] +pub(crate) fn defaulted_trait_method_rpitit_values( + db: &dyn HirDatabase, + method_id: FunctionId, +) -> ThinVec<(RpititTraitAssocTyId, Binders)> { + let method_generics = generics(db, method_id.into()); + let mut table = InferenceTable::new(db, db.trait_environment(method_id.into())); + + let data = db.function_signature(method_id); + let resolver = method_id.resolver(db); + let mut ctx_ret = TyLoweringContext::new( + db, + &resolver, + &data.store, + method_id.into(), + LifetimeElisionKind::Infer, + ) + .with_impl_trait_mode(ImplTraitLoweringMode::Opaque) + .with_type_param_mode(ParamLoweringMode::Placeholder); + // This is the return type of the method, with RPITIT lowered as opaques. In other words, like if it was written + // in an impl. + let method_opaques_ret = match data.ret_type { + Some(ret_type) => ctx_ret.lower_ty(ret_type), + None => TyKind::Tuple(0, Substitution::empty(Interner)).intern(Interner), + }; + let method_opaques_ret = table.normalize_associated_types_in(method_opaques_ret); + + // This is the return type of the method, with RPITITs lowered as associated types. In other words, like in its + // signature. + let method_assocs_ret = db + .callable_item_signature(method_id.into()) + .substitute(Interner, &method_generics.placeholder_subst(db)) + .ret() + .clone(); + let mut rpitit_to_infer_var_folder = RpititToInferVarFolder { + db, + table: &mut table, + trait_method_id: method_id, + trait_rpitit_to_infer_var: FxHashMap::default(), + }; + let method_assocs_ret = + method_assocs_ret.fold_with(&mut rpitit_to_infer_var_folder, DebruijnIndex::INNERMOST); + let trait_rpitit_to_infer_var = rpitit_to_infer_var_folder.trait_rpitit_to_infer_var; + let method_assocs_ret = table.normalize_associated_types_in(method_assocs_ret); + + table.resolve_obligations_as_possible(); + // Even if unification fails, we want to continue. We will fill the RPITITs with error types. + table.unify(&method_assocs_ret, &method_opaques_ret); + table.resolve_obligations_as_possible(); + + ThinVec::from_iter(trait_rpitit_to_infer_var.into_iter().map(|(trait_assoc_id, infer_var)| { + let trait_assoc = trait_assoc_id.loc(db); + let rpitit = table.resolve_completely(infer_var); + let rpitit = rpitit.fold_with( + &mut PlaceholderToBoundVarFolder { + db, + method: method_id.into(), + method_generics: method_generics.self_params(), + parent: trait_assoc.trait_id.into(), + parent_generics: method_generics + .parent_generics() + .expect("method should be inside trait") + .self_params(), + }, + DebruijnIndex::INNERMOST, + ); + let impl_rpitit = trait_assoc.bounds.as_ref().map(|_| rpitit); + (trait_assoc_id, impl_rpitit) + })) +} + +fn check_method_generics_are_structurally_compatible( + trait_method_generics: &GenericParams, + impl_method_generics: &GenericParams, +) -> bool { + if trait_method_generics.len_type_or_consts() != impl_method_generics.len_type_or_consts() { + return false; + } + + for ((_, trait_arg), (_, impl_arg)) in iter::zip( + trait_method_generics.iter_type_or_consts(), + impl_method_generics.iter_type_or_consts(), + ) { + match (trait_arg, impl_arg) { + (TypeOrConstParamData::TypeParamData(_), TypeOrConstParamData::TypeParamData(_)) + | (TypeOrConstParamData::ConstParamData(_), TypeOrConstParamData::ConstParamData(_)) => { + } + _ => return false, + } + } + + true +} + +#[derive(chalk_derive::FallibleTypeFolder)] +#[has_interner(Interner)] +struct RpititToInferVarFolder<'a, 'b> { + db: &'a dyn HirDatabase, + table: &'a mut InferenceTable<'b>, + trait_rpitit_to_infer_var: FxHashMap, + trait_method_id: FunctionId, +} +impl TypeFolder for RpititToInferVarFolder<'_, '_> { + fn as_dyn(&mut self) -> &mut dyn TypeFolder { + self + } + + fn interner(&self) -> Interner { + Interner + } + + fn fold_ty(&mut self, ty: Ty, outer_binder: DebruijnIndex) -> Ty { + let result = match ty.kind(Interner) { + TyKind::Alias(AliasTy::Projection(ProjectionTy { associated_ty_id, substitution })) + | TyKind::AssociatedType(associated_ty_id, substitution) => { + if let AnyTraitAssocType::Rpitit(assoc_id) = + from_assoc_type_id(self.db, *associated_ty_id) + { + let assoc = assoc_id.loc(self.db); + if assoc.synthesized_from_method == self.trait_method_id { + if let Some(ty) = self.trait_rpitit_to_infer_var.get(&assoc_id) { + return ty.clone(); + } + + // Replace with new infer var. + // This needs to come before we fold the bounds, because they also contain this associated type. + let var = self.table.new_type_var(); + self.trait_rpitit_to_infer_var.insert(assoc_id, var.clone()); + + // Recurse into bounds, so that nested RPITITs will be handled correctly. + for bound in assoc.bounds.clone().substitute(Interner, substitution) { + let bound = inline_bound_to_generic_predicate(&bound, var.clone()); + // This is an unrelated binder, therefore `DebruijnIndex::INNERMOST`. + let bound = bound.fold_with(self, DebruijnIndex::INNERMOST); + let bound = self.table.normalize_associated_types_in(bound); + self.table.register_obligation(Goal::new( + Interner, + GoalData::Quantified( + chalk_ir::QuantifierKind::ForAll, + bound.map(|bound| { + Goal::new( + Interner, + GoalData::DomainGoal(DomainGoal::Holds(bound)), + ) + }), + ), + )); + } + + return var; + } + } + ty.clone() + } + _ => ty.clone(), + }; + result.super_fold_with(self, outer_binder) + } +} + +#[derive(chalk_derive::FallibleTypeFolder)] +#[has_interner(Interner)] +struct PlaceholderToBoundVarFolder<'a> { + db: &'a dyn HirDatabase, + method: GenericDefId, + method_generics: &'a GenericParams, + parent: GenericDefId, + parent_generics: &'a GenericParams, +} +impl TypeFolder for PlaceholderToBoundVarFolder<'_> { + fn as_dyn(&mut self) -> &mut dyn TypeFolder { + self + } + + fn interner(&self) -> Interner { + Interner + } + + fn fold_free_placeholder_ty( + &mut self, + universe: PlaceholderIndex, + _outer_binder: DebruijnIndex, + ) -> Ty { + let placeholder = from_placeholder_idx(self.db, universe); + if placeholder.parent == self.method { + BoundVar::new( + DebruijnIndex::INNERMOST, + placeholder.local_id.into_raw().into_u32() as usize + + self.method_generics.len_lifetimes() + + self.parent_generics.len(), + ) + .to_ty(Interner) + } else if placeholder.parent == self.parent { + let local_id = placeholder.local_id.into_raw().into_u32(); + let index = if matches!(self.parent, GenericDefId::TraitId(_)) && local_id == 0 { + // `Self` parameter. + 0 + } else { + local_id as usize + self.parent_generics.len_lifetimes() + }; + BoundVar::new(DebruijnIndex::INNERMOST, index).to_ty(Interner) + } else { + TyKind::Placeholder(universe).intern(Interner) + } + } + + fn fold_free_placeholder_const( + &mut self, + ty: Ty, + universe: PlaceholderIndex, + _outer_binder: DebruijnIndex, + ) -> Const { + let placeholder = from_placeholder_idx(self.db, universe); + if placeholder.parent == self.method { + BoundVar::new( + DebruijnIndex::INNERMOST, + placeholder.local_id.into_raw().into_u32() as usize + + self.method_generics.len_lifetimes() + + self.parent_generics.len(), + ) + .to_const(Interner, ty) + } else if placeholder.parent == self.parent { + BoundVar::new( + DebruijnIndex::INNERMOST, + placeholder.local_id.into_raw().into_u32() as usize + + self.parent_generics.len_lifetimes(), + ) + .to_const(Interner, ty) + } else { + Const::new(Interner, ConstData { ty, value: ConstValue::Placeholder(universe) }) + } + } + + fn fold_free_placeholder_lifetime( + &mut self, + universe: PlaceholderIndex, + _outer_binder: DebruijnIndex, + ) -> Lifetime { + let placeholder = lt_from_placeholder_idx(self.db, universe); + if placeholder.parent == self.method { + BoundVar::new( + DebruijnIndex::INNERMOST, + placeholder.local_id.into_raw().into_u32() as usize + self.parent_generics.len(), + ) + .to_lifetime(Interner) + } else if placeholder.parent == self.parent { + let local_id = placeholder.local_id.into_raw().into_u32() as usize; + let index = if matches!(self.parent, GenericDefId::TraitId(_)) { + // Account for `Self` parameter that comes before lifetimes. + local_id + 1 + } else { + local_id + }; + BoundVar::new(DebruijnIndex::INNERMOST, index).to_lifetime(Interner) + } else { + Lifetime::new(Interner, LifetimeData::Placeholder(universe)) + } + } +} + +/// When inferring a method body of a trait or impl, and that method has RPITITs, we need to add +/// `RpititGeneratedAssoc = Type` clauses. +pub(crate) fn add_method_body_rpitit_clauses( + db: &dyn HirDatabase, + impl_method_generics: &Generics, + clauses: &mut Vec, + impl_method: FunctionId, +) { + match impl_method.loc(db).container { + ItemContainerId::ImplId(impl_id) => { + (|| { + let method_data = db.function_signature(impl_method); + let trait_ref = db.impl_trait(impl_id)?; + let trait_items = + db.trait_items(from_chalk_trait_id(trait_ref.skip_binders().trait_id)); + let trait_method = trait_items.method_by_name(&method_data.name)?; + + let rpitits = impl_method_rpitit_values(db, impl_id, trait_method); + let mut substitution = None; + clauses.extend(rpitits.iter().map(|rpitit| { + let (impl_subst, trait_subst) = substitution.get_or_insert_with(|| { + let impl_method_subst = impl_method_generics.placeholder_subst(db); + let trait_method_generics = + crate::generics::generics(db, trait_method.into()); + let trait_method_subst = trait_method_generics.placeholder_subst(db); + let impl_subst = Substitution::from_iter( + Interner, + impl_method_subst.as_slice(Interner) + [..impl_method_generics.len_parent()] + .iter() + .chain( + &trait_method_subst.as_slice(Interner) + [trait_method_generics.len_parent()..], + ), + ); + + let trait_ref_subst = + trait_ref.clone().substitute(Interner, &impl_method_subst); + let trait_subst = rebase_impl_params_onto_trait( + db, + &trait_ref_subst.substitution, + impl_method_generics, + &trait_method_generics, + ); + + (impl_subst, trait_subst) + }); + WhereClause::AliasEq(AliasEq { + alias: AliasTy::Projection(ProjectionTy { + associated_ty_id: rpitit.associated_ty_id, + substitution: trait_subst.clone(), + }), + ty: rpitit.value.clone().substitute(Interner, &*impl_subst).ty, + }) + .cast(Interner) + })); + + Some(()) + })(); + } + ItemContainerId::TraitId(_) => { + let rpitits = defaulted_trait_method_rpitit_values(db, impl_method); + let mut substitution = None; + clauses.extend(rpitits.iter().map(|(trait_rpitit, rpitit_value)| { + let substitution = + substitution.get_or_insert_with(|| impl_method_generics.placeholder_subst(db)); + WhereClause::AliasEq(AliasEq { + alias: AliasTy::Projection(ProjectionTy { + associated_ty_id: to_assoc_type_id_rpitit(*trait_rpitit), + substitution: substitution.clone(), + }), + ty: rpitit_value.clone().substitute(Interner, &*substitution), + }) + .cast(Interner) + })); + } + _ => {} + } +} + +/// Returns a `Substitution` that works like the trait method, but with the impl method params. +fn rebase_impl_params_onto_trait( + db: &dyn HirDatabase, + trait_ref_subst: &Substitution, + impl_method_generics: &Generics, + trait_method_generics: &Generics, +) -> Substitution { + // Lifetime parameters may change between trait and impl, and we don't check from that in `impl_method_rpitit_values()` + // (because it's valid). So fill them with errors. + // FIXME: This isn't really correct, we should still fill the lifetimes. rustc does some kind of mapping, I think there + // are also restrictions on what exactly lifetimes can change between trait and impl. + let trait_method_subst = std::iter::repeat_n( + error_lifetime().cast(Interner), + trait_method_generics.len_lifetimes_self(), + ) + .chain(impl_method_generics.iter_self_type_or_consts_id().map(|(param_id, param_data)| { + let placeholder = to_placeholder_idx(db, param_id); + match param_data { + TypeOrConstParamData::TypeParamData(_) => placeholder.to_ty(Interner).cast(Interner), + TypeOrConstParamData::ConstParamData(_) => placeholder + .to_const(Interner, db.const_param_ty(ConstParamId::from_unchecked(param_id))) + .cast(Interner), + } + })); + Substitution::from_iter( + Interner, + trait_ref_subst.iter(Interner).cloned().chain(trait_method_subst), + ) +} + +pub(crate) fn recovery_rpitit_value( + db: &dyn HirDatabase, + impl_assoc: RpititImplAssocTyId, +) -> Arc { + let impl_assoc = impl_assoc.loc(db); + let trait_assoc = impl_assoc.trait_assoc.loc(db); + let impl_generics = generics(db, impl_assoc.impl_id.into()); + let trait_method_generics = generics(db, trait_assoc.synthesized_from_method.into()); + let binders = VariableKinds::from_iter( + Interner, + variable_kinds_from_generics( + db, + impl_generics.iter_id().chain(trait_method_generics.iter_self_id()), + ), + ); + Arc::new(AssociatedTyValue { + associated_ty_id: to_assoc_type_id_rpitit(impl_assoc.trait_assoc), + impl_id: impl_assoc.impl_id.to_chalk(db), + value: Binders::new(binders, AssociatedTyValueBound { ty: TyKind::Error.intern(Interner) }), + }) +} diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 2b527a4ae12e..65b3a9484bb1 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -1,5 +1,25 @@ +use base_db::RootQueryDb; use cov_mark::check; -use expect_test::expect; +use either::Either; +use expect_test::{Expect, expect}; +use hir_def::db::DefDatabase; +use hir_def::nameres::DefMap; +use hir_def::{ImplId, ModuleDefId}; +use itertools::Itertools; +use test_fixture::WithFixture; + +use crate::chalk_db::inline_bound_to_generic_predicate; +use crate::db::HirDatabase; +use crate::display::{ + DisplayTarget, HirDisplay, HirDisplayError, HirFormatter, SizedByDefault, + write_bounds_like_dyn_trait, +}; +use crate::mapping::ToChalk; +use crate::test_db::TestDB; +use crate::{ + AnyImplAssocType, AnyTraitAssocType, Binders, Interner, TyKind, from_assoc_type_id, + from_assoc_type_value_id, to_chalk_trait_id, +}; use super::{check, check_infer, check_infer_with_mismatches, check_no_mismatches, check_types}; @@ -4903,3 +4923,310 @@ fn main() { "#]], ); } + +#[test] +fn infer_rpitit() { + check_infer( + r#" +trait Trait<'a, T, const N: usize> { + fn foo<'b, U, const M: usize>(&self) -> (impl Trait<'b, U, M>,); +} + +fn bar<'a, 'b, T, const N: usize, U, const M: usize, Ty: Trait<'a, T, N>>(v: &Ty) { + let _ = v.foo::<'b, U, M>(); +} + "#, + // The `{unknown}` is not related to RPITIT, see https://github.com/rust-lang/rust-analyzer/issues/19392. + // The ordering (lifetimes before `Self`) is not representative of what happens; + // it's just that our display infra shows lifetimes first. + expect![[r#" + 72..76 'self': &'? Self + 183..184 'v': &'? Ty + 191..227 '{ ...>(); }': () + 201..202 '_': (Trait::__foo_rpitit<'?, {unknown}, _, 'b, U, M, Ty>,) + 205..206 'v': &'? Ty + 205..224 'v.foo:..., M>()': (Trait::__foo_rpitit<'?, {unknown}, _, 'b, U, M, Ty>,) + "#]], + ); +} + +#[track_caller] +fn check_rpitit(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { + let (db, _) = TestDB::with_many_files(ra_fixture); + let test_crate = *db.all_crates().last().unwrap(); + let def_map = hir_def::nameres::crate_def_map(&db, test_crate); + + let trait_ = def_map[DefMap::ROOT] + .scope + .declarations() + .filter_map(|decl| match decl { + ModuleDefId::TraitId(it) => Some(it), + _ => None, + }) + .at_most_one() + .unwrap_or_else(|_| panic!("at most one trait is supported for `check_rpitit()`")); + let trait_rpitits = trait_.into_iter().flat_map(|trait_| { + let trait_datum = db.trait_datum(test_crate, to_chalk_trait_id(trait_)); + trait_datum + .associated_ty_ids + .iter() + .copied() + .filter_map(|assoc_id| { + let assoc = match from_assoc_type_id(&db, assoc_id) { + AnyTraitAssocType::Rpitit(assoc_id) => Some(assoc_id.loc(&db)), + AnyTraitAssocType::Normal(_) => None, + }?; + let method_name = + db.function_signature(assoc.synthesized_from_method).name.symbol().clone(); + let bounds = AssocTypeBounds(&assoc.bounds); + let bounds = bounds.display_test(&db, DisplayTarget::from_crate(&db, test_crate)); + let method_name = method_name; + let description = format!("type __{method_name}_rpitit: {bounds};\n"); + Some(description) + }) + .collect::>() + }); + + let impl_ = def_map[DefMap::ROOT] + .scope + .impls() + .at_most_one() + .unwrap_or_else(|_| panic!("at most one impl is supported for `check_rpitit()`")); + let impl_rpitits = impl_.into_iter().flat_map(|impl_| { + let trait_datum = db.impl_datum(test_crate, ImplId::to_chalk(impl_, &db)); + trait_datum + .associated_ty_value_ids + .iter() + .copied() + .filter_map(|assoc_id| { + let trait_assoc = match from_assoc_type_value_id(&db, assoc_id) { + AnyImplAssocType::Rpitit(assoc) => assoc.loc(&db).trait_assoc, + AnyImplAssocType::Normal(_) => return None, + }; + let assoc_datum = db.associated_ty_value(test_crate, assoc_id); + let ty = assoc_datum + .value + .skip_binders() + .ty + .display_test(&db, DisplayTarget::from_crate(&db, test_crate)); + let method_name = db + .function_signature(trait_assoc.loc(&db).synthesized_from_method) + .name + .symbol() + .clone(); + let description = format!("type __{method_name}_rpitit = {ty};\n"); + Some(description) + }) + .collect::>() + }); + + let all_rpitits = + trait_rpitits.chain(std::iter::once("\n".to_owned())).chain(impl_rpitits).join(""); + expect.assert_eq(&all_rpitits); + + struct AssocTypeBounds<'a>( + &'a Binders>>>, + ); + impl HirDisplay for AssocTypeBounds<'_> { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + let bounds = self + .0 + .skip_binders() + .iter() + .map(|bound| { + inline_bound_to_generic_predicate(bound, TyKind::Error.intern(Interner)) + }) + .collect::>(); + write_bounds_like_dyn_trait( + f, + Either::Left(&TyKind::Error.intern(Interner)), + &bounds, + SizedByDefault::Sized { anchor: f.krate() }, + ) + } + } +} + +#[test] +fn check_rpitit_trait_bounds_and_impl_value() { + check_rpitit( + r#" +//- minicore: sized +//- /helpers.rs crate:helpers +pub struct Foo(*mut T); +pub trait T1 {} +pub trait T2 {} +pub trait T3<'a, 'b, const N: usize> {} +pub trait T4 { + type Assoc; +} + +//- /lib.rs crate:lib deps:helpers +use helpers::*; +trait Trait { + fn foo<'a, B>() -> (impl T1, Foo + T2 + ?Sized>); + fn bar<'a>(&'a self) -> impl T2 + T3<'a, 'a, 123> + Trait; + fn baz() -> impl T4; +} +impl Trait for Foo { + fn foo<'a, B>() -> (impl T1, Foo) {} + fn bar<'a>(&'a self) -> impl T2 {} + fn baz() -> impl T4 {} +} + "#, + expect![[r#" + type __foo_rpitit: T1; + type __foo_rpitit: T2 + T2 + ?Sized; + type __bar_rpitit: T2 + T3 + Trait; + type __baz_rpitit: T1; + type __baz_rpitit: T4; + + type __foo_rpitit = impl T1; + type __foo_rpitit = ?0.2; + type __bar_rpitit = impl T2; + type __baz_rpitit = (); + type __baz_rpitit = impl T4; + "#]], + ); +} + +#[test] +fn rpitit_referring_self_assoc_type_in_impl_does_not_cycle() { + check_rpitit( + r#" +//- minicore: sized +trait Trait { + type Assoc; + fn foo() -> impl Sized; +} +impl Trait for () { + type Assoc = (); + fn foo() -> Self::Assoc; +} + "#, + expect![[r#" + type __foo_rpitit: Sized; + + type __foo_rpitit = (); + "#]], + ); +} + +#[test] +fn defaulted_method_with_rpitit() { + check_rpitit( + r#" +//- minicore: sized +//- /helpers.rs crate:helpers +pub trait Bar<'a, B: ?Sized, C: ?Sized, D: ?Sized> {} + +//- /lib.rs crate:library deps:helpers +use helpers::*; +trait Trait { + fn foo<'a, B>() -> impl Bar<'a, B, Self, T>; +} +struct Foo(T); +impl Trait<(Foo<()>, U)> for Foo {} + "#, + // The debruijn index in the value is 2, but should be 0 to refer to the associated + // type generics. It is 2 because opaques are wrapped in two binders, and so the 0 + // is shifted in twice. Since users are not expected to see debruijn indices anyway, + // this does not matter. + expect![[r#" + type __foo_rpitit: Bar; + + type __foo_rpitit = impl Bar, (Foo<()>, ?2.3)>; + "#]], + ); +} + +#[test] +fn rpitit_cycle() { + // This shouldn't cause a cycle, but it does due to Chalk shortcomings. It should be + // fixed when we switch to the new trait solver. However, I believe it will still possible + // to cause cycles with malformed code, so we still need the cycle handlers. + check_rpitit( + r#" +//- minicore: future, send, sized +use core::future::Future; + +trait DesugaredAsyncTrait { + fn foo(&self) -> impl Future + Send; +} + +impl DesugaredAsyncTrait for () {} + "#, + expect![[r#" + type __foo_rpitit: Future + Send; + + type __foo_rpitit = {unknown}; + "#]], + ); +} + +#[test] +fn rpitit_method_body() { + // This test checks that when inferring a defaulted/impl method with RPITITs, we tell Chalk the generated + // RPITITs assoc types have values. + // The expectation should not be (the assoc type) `__foo_rpitit` but (the opaque) `impl Trait`. + check_infer( + r#" +//- minicore: sized +trait Trait { + fn foo(&self) -> impl Trait { + let _ = self.foo(); + loop {} + } +} + "#, + expect![[r#" + 26..30 'self': &'? Self + 46..97 '{ ... }': ! + 60..61 '_': impl Trait + 64..68 'self': &'? Self + 64..74 'self.foo()': impl Trait + 84..91 'loop {}': ! + 89..91 '{}': () + "#]], + ); +} + +#[test] +fn rpitit_cycle_with_impl_generics() { + check_infer( + r#" +//- minicore: sized, iterator +pub trait HasAttrs: Sized { + fn foo(self) -> impl Iterator { + loop {} + } +} +enum Either { + Left(L), + Right(R), +} +impl HasAttrs for Either +where + L: HasAttrs, + R: HasAttrs, +{ +} +struct AnyHasAttrs; +impl HasAttrs for AnyHasAttrs {} + +fn foo(v: AnyHasAttrs) { + v.foo(); +} + "#, + expect![[r#" + 39..43 'self': Self + 73..96 '{ ... }': ! + 83..90 'loop {}': ! + 88..90 '{}': () + 290..291 'v': AnyHasAttrs + 306..322 '{ ...o(); }': () + 312..313 'v': AnyHasAttrs + 312..319 'v.foo()': {unknown} + "#]], + ); +} diff --git a/crates/hir-ty/src/tls.rs b/crates/hir-ty/src/tls.rs index f5911e2161d0..e2785542f9a7 100644 --- a/crates/hir-ty/src/tls.rs +++ b/crates/hir-ty/src/tls.rs @@ -4,11 +4,12 @@ use std::fmt::{self, Display}; use itertools::Itertools; use span::Edition; +use crate::mapping::AnyTraitAssocType; use crate::{ CallableDefId, Interner, ProjectionTyExt, chalk_db, db::HirDatabase, from_assoc_type_id, from_chalk_trait_id, mapping::from_chalk, }; -use hir_def::{AdtId, ItemContainerId, Lookup, TypeAliasId}; +use hir_def::{AdtId, ItemContainerId, Lookup}; pub(crate) use unsafe_tls::{set_current_program, with_current_program}; @@ -45,20 +46,35 @@ impl DebugContext<'_> { id: chalk_db::AssocTypeId, fmt: &mut fmt::Formatter<'_>, ) -> Result<(), fmt::Error> { - let type_alias: TypeAliasId = from_assoc_type_id(id); - let type_alias_data = self.0.type_alias_signature(type_alias); - let trait_ = match type_alias.lookup(self.0).container { - ItemContainerId::TraitId(t) => t, - _ => panic!("associated type not in trait"), - }; - let trait_data = self.0.trait_signature(trait_); - write!( - fmt, - "{}::{}", - trait_data.name.display(self.0, Edition::LATEST), - type_alias_data.name.display(self.0, Edition::LATEST) - )?; - Ok(()) + match from_assoc_type_id(self.0, id) { + AnyTraitAssocType::Normal(type_alias) => { + let type_alias_data = self.0.type_alias_signature(type_alias); + let trait_ = match type_alias.lookup(self.0).container { + ItemContainerId::TraitId(t) => t, + _ => panic!("associated type not in trait"), + }; + let trait_data = self.0.trait_signature(trait_); + write!( + fmt, + "{}::{}", + trait_data.name.display(self.0, Edition::LATEST), + type_alias_data.name.display(self.0, Edition::LATEST) + )?; + Ok(()) + } + AnyTraitAssocType::Rpitit(assoc_type) => { + let assoc_type = assoc_type.loc(self.0); + let method_data = self.0.function_signature(assoc_type.synthesized_from_method); + let trait_data = self.0.trait_signature(assoc_type.trait_id); + write!( + fmt, + "{}::__{}_rpitit", + trait_data.name.display(self.0, Edition::LATEST), + method_data.name.display(self.0, Edition::LATEST) + )?; + Ok(()) + } + } } pub(crate) fn debug_projection_ty( @@ -66,12 +82,26 @@ impl DebugContext<'_> { projection_ty: &chalk_ir::ProjectionTy, fmt: &mut fmt::Formatter<'_>, ) -> Result<(), fmt::Error> { - let type_alias = from_assoc_type_id(projection_ty.associated_ty_id); - let type_alias_data = self.0.type_alias_signature(type_alias); - let trait_ = match type_alias.lookup(self.0).container { - ItemContainerId::TraitId(t) => t, - _ => panic!("associated type not in trait"), - }; + let (trait_, assoc_type_name) = + match from_assoc_type_id(self.0, projection_ty.associated_ty_id) { + AnyTraitAssocType::Normal(type_alias) => { + let type_alias_data = self.0.type_alias_signature(type_alias); + let trait_ = match type_alias.lookup(self.0).container { + ItemContainerId::TraitId(it) => it, + _ => panic!("associated type not in trait"), + }; + let type_alias_name = + type_alias_data.name.display(self.0, Edition::LATEST).to_string(); + (trait_, type_alias_name) + } + AnyTraitAssocType::Rpitit(assoc_type) => { + let assoc_type = assoc_type.loc(self.0); + let method_data = self.0.function_signature(assoc_type.synthesized_from_method); + let placeholder_assoc_type_name = + format!("__{}_rpitit", method_data.name.display(self.0, Edition::LATEST)); + (assoc_type.trait_id, placeholder_assoc_type_name) + } + }; let trait_name = &self.0.trait_signature(trait_).name; let trait_ref = projection_ty.trait_ref(self.0); let trait_params = trait_ref.substitution.as_slice(Interner); @@ -84,7 +114,7 @@ impl DebugContext<'_> { trait_params[1..].iter().format_with(", ", |x, f| f(&format_args!("{x:?}"))), )?; } - write!(fmt, ">::{}", type_alias_data.name.display(self.0, Edition::LATEST))?; + write!(fmt, ">::{}", assoc_type_name)?; let proj_params = &projection_ty.substitution.as_slice(Interner)[trait_params.len()..]; if !proj_params.is_empty() { diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 3a91050d15fa..9440f7a3d2ce 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2381,30 +2381,24 @@ impl Function { } } - pub fn returns_impl_future(self, db: &dyn HirDatabase) -> bool { - if self.is_async(db) { - return true; - } + /// Returns `Future::Output`. + pub fn returns_impl_future(self, db: &dyn HirDatabase) -> Option { + let future_trait_id = LangItem::Future.resolve_trait(db, self.ty(db).env.krate)?; - let Some(impl_traits) = self.ret_type(db).as_impl_traits(db) else { return false }; - let Some(future_trait_id) = LangItem::Future.resolve_trait(db, self.ty(db).env.krate) - else { - return false; - }; - let Some(sized_trait_id) = LangItem::Sized.resolve_trait(db, self.ty(db).env.krate) else { - return false; - }; + let ret_type = self.ret_type(db); + let canonical_ty = + Canonical { value: ret_type.ty.clone(), binders: CanonicalVarKinds::empty(Interner) }; + if !method_resolution::implements_trait_unique( + &canonical_ty, + db, + &ret_type.env, + future_trait_id, + ) { + return None; + } - let mut has_impl_future = false; - impl_traits - .filter(|t| { - let fut = t.id == future_trait_id; - has_impl_future |= fut; - !fut && t.id != sized_trait_id - }) - // all traits but the future trait must be auto traits - .all(|t| t.is_auto(db)) - && has_impl_future + let future_output = LangItem::FutureOutput.resolve_type_alias(db, self.ty(db).env.krate)?; + ret_type.normalize_trait_assoc_type(db, &[], future_output.into()) } /// Does this function have `#[test]` attribute? diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index ea21546f9d76..57c546261a8d 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -36,8 +36,8 @@ use hir_expand::{ name::{AsName, Name}, }; use hir_ty::{ - Adjustment, AliasTy, InferenceResult, Interner, LifetimeElisionKind, ProjectionTy, - Substitution, TraitEnvironment, Ty, TyExt, TyKind, TyLoweringContext, + Adjustment, AliasTy, AnyTraitAssocType, InferenceResult, Interner, LifetimeElisionKind, + ProjectionTy, Substitution, TraitEnvironment, Ty, TyExt, TyKind, TyLoweringContext, diagnostics::{ InsideUnsafeBlock, record_literal_missing_fields, record_pattern_missing_fields, unsafe_operations, @@ -1182,11 +1182,14 @@ impl<'db> SourceAnalyzer<'db> { PathResolution::Def(ModuleDef::Adt(adt_id.0.into())), ), TyKind::AssociatedType(assoc_id, subst) => { - let assoc_id = from_assoc_type_id(*assoc_id); - ( - GenericSubstitution::new(assoc_id.into(), subst.clone(), env), - PathResolution::Def(ModuleDef::TypeAlias(assoc_id.into())), - ) + match from_assoc_type_id(db, *assoc_id) { + AnyTraitAssocType::Normal(assoc_id) => ( + GenericSubstitution::new(assoc_id.into(), subst.clone(), env), + PathResolution::Def(ModuleDef::TypeAlias(assoc_id.into())), + ), + // Showing substitution for RPITIT assoc types which are rendered as `impl Trait` will be very confusing. + AnyTraitAssocType::Rpitit(_) => return None, + } } TyKind::FnDef(fn_id, subst) => { let fn_id = hir_ty::db::InternedCallableDefId::from(*fn_id); diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs index 58aead73fd6f..c0b5a09d6bc2 100644 --- a/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -31,6 +31,7 @@ //! } //! ``` +use hir::HirDisplay; use hir::{MacroCallId, Name, db::ExpandDatabase}; use ide_db::text_edit::TextEdit; use ide_db::{ @@ -39,7 +40,7 @@ use ide_db::{ }; use syntax::{ AstNode, SmolStr, SyntaxElement, SyntaxKind, T, TextRange, ToSmolStr, - ast::{self, HasGenericArgs, HasTypeBounds, edit_in_place::AttrsOwnerEdit, make}, + ast::{self, HasTypeBounds, edit_in_place::AttrsOwnerEdit, make}, format_smolstr, ted, }; @@ -185,12 +186,12 @@ fn add_function_impl( let fn_name = &func.name(ctx.db); let sugar: &[_] = if func.is_async(ctx.db) { &[AsyncSugaring::Async, AsyncSugaring::Desugar] - } else if func.returns_impl_future(ctx.db) { - &[AsyncSugaring::Plain, AsyncSugaring::Resugar] + } else if let Some(future_output) = func.returns_impl_future(ctx.db) { + &[AsyncSugaring::Plain, AsyncSugaring::Resugar { future_output }] } else { &[AsyncSugaring::Plain] }; - for &sugaring in sugar { + for sugaring in sugar { add_function_impl_(acc, ctx, replacement_range, func, impl_def, fn_name, sugaring); } } @@ -202,9 +203,9 @@ fn add_function_impl_( func: hir::Function, impl_def: hir::Impl, fn_name: &Name, - async_sugaring: AsyncSugaring, + async_sugaring: &AsyncSugaring, ) { - let async_ = if let AsyncSugaring::Async | AsyncSugaring::Resugar = async_sugaring { + let async_ = if let AsyncSugaring::Async | AsyncSugaring::Resugar { .. } = async_sugaring { "async " } else { "" @@ -248,10 +249,10 @@ fn add_function_impl_( } } -#[derive(Copy, Clone)] +#[derive(Clone)] enum AsyncSugaring { Desugar, - Resugar, + Resugar { future_output: hir::Type }, Async, Plain, } @@ -285,7 +286,7 @@ fn get_transformed_fn( ctx: &CompletionContext<'_>, fn_: ast::Fn, impl_def: hir::Impl, - async_: AsyncSugaring, + async_: &AsyncSugaring, ) -> Option { let trait_ = impl_def.trait_(ctx.db)?; let source_scope = &ctx.sema.scope(fn_.syntax())?; @@ -323,31 +324,16 @@ fn get_transformed_fn( } fn_.async_token().unwrap().detach(); } - AsyncSugaring::Resugar => { - let ty = fn_.ret_type()?.ty()?; - match &ty { - // best effort guessing here - ast::Type::ImplTraitType(t) => { - let output = t.type_bound_list()?.bounds().find_map(|b| match b.ty()? { - ast::Type::PathType(p) => { - let p = p.path()?.segment()?; - if p.name_ref()?.text() != "Future" { - return None; - } - match p.generic_arg_list()?.generic_args().next()? { - ast::GenericArg::AssocTypeArg(a) - if a.name_ref()?.text() == "Output" => - { - a.ty() - } - _ => None, - } - } - _ => None, - })?; - ted::replace(ty.syntax(), output.syntax()); - } - _ => (), + AsyncSugaring::Resugar { future_output } => { + let ast_ret = fn_.ret_type()?; + if future_output.is_unit() { + ted::remove(ast_ret.syntax()); + } else { + let ret = future_output + .display_source_code(ctx.db, ctx.module.into(), true) + .unwrap_or_else(|_| "_".to_owned()); + let ast_ret_ty = ast_ret.ty()?; + ted::replace(ast_ret_ty.syntax(), make::ty(&ret).syntax().clone_for_update()); } ted::prepend_child(fn_.syntax(), make::token(T![async])); } diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index abde48d15127..1e38e6069b88 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -108,6 +108,7 @@ define_symbols! { vectorcall_dash_unwind = "vectorcall-unwind", win64_dash_unwind = "win64-unwind", x86_dash_interrupt = "x86-interrupt", + synthesized_rpitit_assoc = "$synthesized_RPITIT_assoc$", @PLAIN: __ra_fixup,