From 53a44555354d4f445c9789fc5721dff99ef99ee1 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 14 Mar 2025 11:58:08 +0200 Subject: [PATCH 01/11] Introduce a new kind of associated types, RPITIT associated types They don't exist in hir-def, only hir-ty. The idea is that the trait/impl datum query will compute and intern them, then other queries can treat them as (partially) normal associated types. (Lowering RPITIT to synthesized associated types, like rustc does, is required to properly support Return Type Notation). --- crates/base-db/src/lib.rs | 21 +++ crates/hir-ty/src/chalk_db.rs | 95 ++++++++++--- crates/hir-ty/src/chalk_ext.rs | 61 ++++---- crates/hir-ty/src/db.rs | 34 ++++- crates/hir-ty/src/display.rs | 184 +++++++++++++++++++------ crates/hir-ty/src/dyn_compatibility.rs | 11 +- crates/hir-ty/src/infer/closure.rs | 21 ++- crates/hir-ty/src/interner.rs | 5 +- crates/hir-ty/src/lib.rs | 5 +- crates/hir-ty/src/mapping.rs | 57 +++++--- crates/hir-ty/src/tls.rs | 76 +++++++--- crates/hir/src/source_analyzer.rs | 17 ++- crates/intern/src/symbol/symbols.rs | 1 + 13 files changed, 424 insertions(+), 164 deletions(-) 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-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs index cd799c03ddf7..612ec746e4d3 100644 --- a/crates/hir-ty/src/chalk_db.rs +++ b/crates/hir-ty/src/chalk_db.rs @@ -9,7 +9,7 @@ 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_solve::rust_ir::{self, AssociatedTyDatumBound, OpaqueTyDatumBound, WellKnownTrait}; use base_db::Crate; use hir_def::{ @@ -24,12 +24,15 @@ use crate::{ AliasEq, AliasTy, BoundVar, DebruijnIndex, Interner, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, WhereClause, - db::{HirDatabase, InternedCoroutine}, + db::{HirDatabase, InternedCoroutine, RpititImplAssocTy, RpititImplAssocTyId}, from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, generics::generics, lower::LifetimeElisionKind, 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}, to_assoc_type_id, to_chalk_trait_id, traits::ChalkContext, @@ -54,23 +57,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 +484,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 +706,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) @@ -883,7 +916,7 @@ 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(|type_alias| to_assoc_type_value_id(type_alias)) .collect(); debug!("impl_datum: {:?}", impl_datum_bound); let impl_datum = ImplDatum { @@ -900,8 +933,19 @@ pub(crate) fn associated_ty_value_query( 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) => { + let assoc_type = assoc_type_id.loc(db); + Arc::new(AssociatedTyValue { + impl_id: assoc_type.impl_id.to_chalk(db), + associated_ty_id: to_assoc_type_id_rpitit(assoc_type.trait_assoc), + value: assoc_type.value.clone(), + }) + } + } } fn type_alias_associated_ty_value( @@ -1037,7 +1081,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..e63d7d78482b 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -3,7 +3,7 @@ use std::sync; -use base_db::{Crate, impl_intern_key}; +use base_db::{Crate, impl_intern_key, impl_intern_key_ref}; use hir_def::{ AdtId, BlockId, CallableDefId, ConstParamId, DefWithBodyId, EnumVariantId, FunctionId, GeneralConstId, GenericDefId, ImplId, LifetimeParamId, LocalFieldId, StaticId, TraitId, @@ -349,3 +349,35 @@ impl_intern_key!(InternedCoroutineId, InternedCoroutine); // This exists just for Chalk, because Chalk just has a single `FnDefId` where // we have different IDs for struct and enum variant constructors. impl_intern_key!(InternedCallableDefId, CallableDefId); + +/// 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, + /// 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 value: Binders>, +} + +impl_intern_key_ref!(RpititImplAssocTyId, RpititImplAssocTy); diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index f0989d9de91f..de8189fca353 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -4,11 +4,12 @@ use std::{ fmt::{self, Debug}, - mem, + iter, mem, }; use base_db::Crate; -use chalk_ir::{BoundVar, Safety, TyKind}; +use chalk_ir::{BoundVar, Safety, TyKind, cast::Cast}; +use chalk_solve::rust_ir; use either::Either; use hir_def::{ GenericDefId, HasModule, ImportPathConfig, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, @@ -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,86 @@ 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 + .skip_binders() + .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) + }) + .collect::>(), + SizedByDefault::Sized { + anchor: assoc_type.trait_id.lookup(f.db).container.krate(), + }, + ) + }) + } + } + } +} + +/// Fills `Self` with an error type. +pub(crate) fn inline_bound_to_generic_predicate( + bound: &chalk_ir::Binders>, +) -> 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(TyKind::Error.intern(Interner).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(TyKind::Error.intern(Interner).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)) + } } } @@ -1310,35 +1377,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(), }; @@ -1918,7 +2004,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 +2091,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/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..092034c1182c 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -98,8 +98,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; diff --git a/crates/hir-ty/src/mapping.rs b/crates/hir-ty/src/mapping.rs index 2abc1ac62a99..a8b4c51f2a95 100644 --- a/crates/hir-ty/src/mapping.rs +++ b/crates/hir-ty/src/mapping.rs @@ -13,7 +13,8 @@ use salsa::{ use crate::{ AssocTypeId, CallableDefId, ChalkTraitId, FnDefId, ForeignDefId, Interner, OpaqueTyId, - PlaceholderIndex, chalk_db, db::HirDatabase, + PlaceholderIndex, chalk_db, + db::{HirDatabase, RpititImplAssocTyId, RpititTraitAssocTyId}, }; pub(crate) trait ToChalk { @@ -22,6 +23,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 +66,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 +126,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/tls.rs b/crates/hir-ty/src/tls.rs index f5911e2161d0..b4952bfe46c0 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,28 @@ 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).to_string() + ); + (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 +116,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/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index ea21546f9d76..6fc652e53d91 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, AnyTraitAssocType, InferenceResult, Interner, 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/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, From cb468ae5bfa16494526b2b1bac42447df2b83a5e Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 18 Mar 2025 20:24:22 +0200 Subject: [PATCH 02/11] Lower Return Position Impl Trait In Traits correctly Instead of lowering them as opaque type, in the trait they should get lowered into a synthesized associated type. Opaques "mostly worked" here, but they break when trying to implement Return Type Notation, which is essentially a way to refer in code to this synthesized associated type. So we need to do the correct thing. --- Cargo.lock | 1 + Cargo.toml | 1 + crates/hir-def/Cargo.toml | 2 +- crates/hir-ty/Cargo.toml | 1 + crates/hir-ty/src/chalk_db.rs | 419 ++++++++++++++++++++++++++++-- crates/hir-ty/src/display.rs | 49 +--- crates/hir-ty/src/generics.rs | 4 + crates/hir-ty/src/lower.rs | 203 ++++++++++++++- crates/hir-ty/src/lower/path.rs | 4 +- crates/hir-ty/src/tests/traits.rs | 185 ++++++++++++- 10 files changed, 795 insertions(+), 74 deletions(-) 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/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-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 612ec746e4d3..34cb77e33e60 100644 --- a/crates/hir-ty/src/chalk_db.rs +++ b/crates/hir-ty/src/chalk_db.rs @@ -5,30 +5,44 @@ use std::{iter, ops::ControlFlow, sync::Arc}; use hir_expand::name::Name; use intern::sym; +use rustc_hash::FxHashMap; use span::Edition; use tracing::debug; -use chalk_ir::{CanonicalVarKinds, cast::Caster, fold::shift::Shift}; +use chalk_ir::{ + Binders, CanonicalVarKinds, + cast::{Cast, Caster}, + fold::{TypeFoldable, TypeFolder, TypeSuperFoldable, shift::Shift}, +}; use chalk_solve::rust_ir::{self, AssociatedTyDatumBound, OpaqueTyDatumBound, WellKnownTrait}; use base_db::Crate; use hir_def::{ - AssocItemId, BlockId, CallableDefId, GenericDefId, HasModule, ItemContainerId, Lookup, - TypeAliasId, VariantId, - hir::Movability, + AssocItemId, BlockId, CallableDefId, FunctionId, GenericDefId, HasModule, ItemContainerId, + Lookup, TypeAliasId, VariantId, + hir::{ + Movability, + generics::{GenericParams, TypeOrConstParamData}, + }, lang_item::LangItem, + nameres::assoc::ImplItems, signatures::{ImplFlags, StructFlags, TraitFlags}, }; use crate::{ - AliasEq, AliasTy, BoundVar, DebruijnIndex, Interner, ProjectionTy, ProjectionTyExt, - QuantifiedWhereClause, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, - WhereClause, - db::{HirDatabase, InternedCoroutine, RpititImplAssocTy, RpititImplAssocTyId}, - from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, + AliasEq, AliasTy, BoundVar, Const, ConstData, ConstValue, DebruijnIndex, DomainGoal, Goal, + GoalData, InferenceTable, Interner, Lifetime, LifetimeData, PlaceholderIndex, ProjectionTy, + ProjectionTyExt, QuantifiedWhereClause, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder, + TyExt, TyKind, VariableKinds, WhereClause, + db::{ + HirDatabase, InternedCoroutine, RpititImplAssocTy, RpititImplAssocTyId, + RpititTraitAssocTyId, + }, + from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, generics::generics, lower::LifetimeElisionKind, - make_binders, make_single_type_binders, + lower::trait_fn_signature, + lt_from_placeholder_idx, make_binders, make_single_type_binders, 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, @@ -733,8 +747,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 { @@ -881,12 +907,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()); @@ -903,8 +928,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 { @@ -916,7 +942,8 @@ 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| to_assoc_type_value_id(type_alias)) + .map(to_assoc_type_value_id) + .chain(impl_rpitit_values(db, impl_id, &trait_ref_binders, &impl_items, &trait_datum)) .collect(); debug!("impl_datum: {:?}", impl_datum_bound); let impl_datum = ImplDatum { @@ -928,6 +955,362 @@ fn impl_def_datum(db: &dyn HirDatabase, krate: Crate, impl_id: hir_def::ImplId) Arc::new(impl_datum) } +fn impl_rpitit_values( + db: &dyn HirDatabase, + impl_id: hir_def::ImplId, + impl_trait: &Binders, + impl_items: &ImplItems, + trait_datum: &TraitDatum, +) -> impl Iterator { + let mut trait_rpitit_to_impl_rpitit = FxHashMap::default(); + + return trait_datum + .associated_ty_ids + .iter() + .filter_map(|&trait_assoc| match from_assoc_type_id(db, trait_assoc) { + AnyTraitAssocType::Rpitit(it) => Some(it), + AnyTraitAssocType::Normal(_) => None, + }) + .map(move |trait_assoc_id| { + if let Some(&impl_assoc_id) = trait_rpitit_to_impl_rpitit.get(&trait_assoc_id) { + return impl_assoc_id; + } + + let trait_assoc = trait_assoc_id.loc(db); + let trait_method_id = trait_assoc.synthesized_from_method; + let trait_method_generics = generics(db, trait_method_id.into()); + + let impl_assoc_id = (|| { + let trait_method = db.function_signature(trait_method_id); + let impl_method = impl_items.items.iter().find_map(|(name, id)| { + if *name == trait_method.name { + match *id { + AssocItemId::FunctionId(it) => Some(it), + _ => None, + } + } else { + None + } + })?; + + 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 None; + } + + // 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_self()..]; + // We want to substitute the TraitRef with placeholders, but placeholders from the method, not the impl. + let impl_trait_ref = + impl_trait.clone().substitute(Interner, trait_ref_placeholder_subst); + let trait_to_impl_args = Substitution::from_iter( + Interner, + impl_method_placeholder_subst.as_slice(Interner) + [..impl_method_generics.len_self()] + .iter() + .chain(impl_trait_ref.substitution.as_slice(Interner)), + ); + 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(); + for (trait_rpitit, infer_var) in trait_rpitit_to_infer_var { + let impl_rpitit = table.resolve_completely(infer_var); + let impl_rpitit = impl_rpitit.fold_with( + &mut PlaceholderToBoundVarFolder { + db, + method: impl_method.into(), + method_generics: impl_method_generics.self_params(), + }, + DebruijnIndex::INNERMOST, + ); + let impl_rpitit_binders = VariableKinds::from_iter( + Interner, + &trait_assoc.bounds.binders.as_slice(Interner) + [..trait_method_generics.len()], + ); + let impl_rpitit = Binders::new( + impl_rpitit_binders, + rust_ir::AssociatedTyValueBound { ty: impl_rpitit }, + ); + let impl_rpitit = RpititImplAssocTyId::new( + db, + RpititImplAssocTy { + impl_id, + trait_assoc: trait_rpitit, + value: impl_rpitit, + }, + ); + trait_rpitit_to_impl_rpitit.insert(trait_rpitit, impl_rpitit); + } + + trait_rpitit_to_impl_rpitit.get(&trait_assoc_id).copied() + })(); + + impl_assoc_id.unwrap_or_else(|| { + RpititImplAssocTyId::new( + db, + RpititImplAssocTy { + impl_id, + trait_assoc: trait_assoc_id, + // 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. + value: Binders::new( + VariableKinds::from_iter( + Interner, + &trait_assoc.bounds.binders.as_slice(Interner) + [..trait_method_generics.len_self()], + ), + rust_ir::AssociatedTyValueBound { ty: TyKind::Error.intern(Interner) }, + ), + }, + ) + }) + }) + .map(to_assoc_type_value_id_rpitit); + + #[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()); + let bound = bound.fold_with(self, outer_binder); + 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, + } + 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(), + ) + .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(), + ) + .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, + ) + .to_lifetime(Interner) + } else { + Lifetime::new(Interner, LifetimeData::Placeholder(universe)) + } + } + } +} + +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)) + } + } +} + +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 +} + pub(crate) fn associated_ty_value_query( db: &dyn HirDatabase, krate: Crate, diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index de8189fca353..d7cdecf6b2ca 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -4,12 +4,11 @@ use std::{ fmt::{self, Debug}, - iter, mem, + mem, }; use base_db::Crate; -use chalk_ir::{BoundVar, Safety, TyKind, cast::Cast}; -use chalk_solve::rust_ir; +use chalk_ir::{BoundVar, Safety, TyKind}; use either::Either; use hir_def::{ GenericDefId, HasModule, ImportPathConfig, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, @@ -49,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, @@ -688,7 +688,10 @@ impl HirDisplay for ProjectionTy { .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) + inline_bound_to_generic_predicate( + bound, + TyKind::Error.intern(Interner), + ) }) .collect::>(), SizedByDefault::Sized { @@ -701,42 +704,6 @@ impl HirDisplay for ProjectionTy { } } -/// Fills `Self` with an error type. -pub(crate) fn inline_bound_to_generic_predicate( - bound: &chalk_ir::Binders>, -) -> 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(TyKind::Error.intern(Interner).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(TyKind::Error.intern(Interner).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)) - } - } -} - impl HirDisplay for OpaqueTy { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { if f.should_truncate() { @@ -1892,7 +1859,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], diff --git a/crates/hir-ty/src/generics.rs b/crates/hir-ty/src/generics.rs index bb4aaf788958..7003118dce2b 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 } diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index ea8e7cc2be90..1d2cd7992a39 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -22,6 +22,7 @@ use chalk_ir::{ interner::HasInterner, }; +use chalk_solve::rust_ir; use either::Either; use hir_def::{ AdtId, AssocItemId, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, @@ -31,7 +32,7 @@ use hir_def::{ 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,16 +44,18 @@ 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, VariableKind, 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, + db::{HirDatabase, RpititTraitAssocTy, RpititTraitAssocTyId}, error_lifetime, generics::{Generics, generics, trait_self_param_idx}, lower::{ @@ -60,7 +63,7 @@ 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}, static_lifetime, to_chalk_trait_id, to_placeholder_idx, utils::all_super_trait_refs, variable_kinds_from_iter, @@ -74,10 +77,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 +260,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, @@ -446,6 +457,7 @@ impl<'a> TyLoweringContext<'a> { // FIXME: report error TyKind::Error.intern(Interner) } + ImplTraitLoweringMode::AssocType => self.lower_rpitit_in_trait(bounds), } } TypeRef::Error => TyKind::Error.intern(Interner), @@ -453,6 +465,149 @@ 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, + method_generics.iter_id().map(|param_id| match param_id { + GenericParamId::TypeParamId(_) => { + VariableKind::Ty(chalk_ir::TyVariableKind::General) + } + GenericParamId::ConstParamId(param_id) => { + VariableKind::Const(self.db.const_param_ty(param_id)) + } + GenericParamId::LifetimeParamId(_) => VariableKind::Lifetime, + }), + ); + + 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 +931,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, @@ -1322,7 +1493,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 +1510,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 +1520,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 +1536,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/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 2b527a4ae12e..b4288cd1d248 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,166 @@ 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. + expect![[r#" + 72..76 'self': &'? Self + 183..184 'v': &'? Ty + 191..227 '{ ...>(); }': () + 201..202 '_': (Trait::__foo_rpitit<'b, U, M, Ty, '?, {unknown}, _>,) + 205..206 'v': &'? Ty + 205..224 'v.foo:..., M>()': (Trait::__foo_rpitit<'b, U, M, Ty, '?, {unknown}, _>,) + "#]], + ); +} + +#[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 assoc = match from_assoc_type_value_id(&db, assoc_id) { + AnyImplAssocType::Rpitit(assoc_id) => Some(assoc_id.loc(&db)), + AnyImplAssocType::Normal(_) => None, + }?; + let ty = assoc + .value + .skip_binders() + .ty + .display_test(&db, DisplayTarget::from_crate(&db, test_crate)); + let method_name = db + .function_signature(assoc.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.1; + type __bar_rpitit = impl T2; + type __baz_rpitit = (); + type __baz_rpitit = impl T4; + "#]], + ); +} From a609ba6dbaddf27755f48ffc82e95519ba3b4468 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 18 Mar 2025 20:49:52 +0200 Subject: [PATCH 03/11] Fix resugaring of `fn() -> impl Future` to `async fn` in completions It broke due to the RPITIT changes. And also make it more robust, by relying on semantic instead of textual match. --- crates/hir/src/lib.rs | 38 ++++++------- .../src/completions/item_list/trait_impl.rs | 54 +++++++------------ 2 files changed, 36 insertions(+), 56 deletions(-) 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/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])); } From 0e480cc53434a53efa6d2a44d7f67daf09f5f12f Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 19 Mar 2025 23:36:25 +0200 Subject: [PATCH 04/11] Compute values of RPITITs in impls on a separate query The previous setup led to a cycle in scenario like the following: ```rust trait Trait { type Assoc; fn foo() -> impl Sized; } impl Trait for () { type Assoc = (); fn foo() -> Self::Assoc; } ``` Because we asked Chalk to normalize the return type of the method, and for that it asked us the datum of the impl, which again causes us to evaluate the RPITITs. Instead, we only put the associated type ID in `impl_datum()`, and we compute it on `associated_ty_value()`. This still causes a cycle because Chalk needlessly evaluates all associated types for an impl, but at least it'll work for the new trait solver, and compiler-errors will try to fix that for Chalk too. --- crates/hir-ty/src/chalk_db.rs | 293 +++++++++++++++--------------- crates/hir-ty/src/db.rs | 5 - crates/hir-ty/src/tests/traits.rs | 35 +++- crates/hir/src/source_analyzer.rs | 4 +- 4 files changed, 175 insertions(+), 162 deletions(-) diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs index 34cb77e33e60..d2971155fdd9 100644 --- a/crates/hir-ty/src/chalk_db.rs +++ b/crates/hir-ty/src/chalk_db.rs @@ -25,7 +25,6 @@ use hir_def::{ generics::{GenericParams, TypeOrConstParamData}, }, lang_item::LangItem, - nameres::assoc::ImplItems, signatures::{ImplFlags, StructFlags, TraitFlags}, }; @@ -943,7 +942,14 @@ fn impl_def_datum(db: &dyn HirDatabase, krate: Crate, impl_id: hir_def::ImplId) trait_data.associated_type_by_name(name).is_some() }) .map(to_assoc_type_value_id) - .chain(impl_rpitit_values(db, impl_id, &trait_ref_binders, &impl_items, &trait_datum)) + .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 { @@ -955,157 +961,119 @@ fn impl_def_datum(db: &dyn HirDatabase, krate: Crate, impl_id: hir_def::ImplId) Arc::new(impl_datum) } -fn impl_rpitit_values( +// We return a list and not a hasmap because the number of RPITITs in a function should be small. +#[salsa_macros::tracked(return_ref)] +fn impl_method_rpitit_values( db: &dyn HirDatabase, impl_id: hir_def::ImplId, - impl_trait: &Binders, - impl_items: &ImplItems, - trait_datum: &TraitDatum, -) -> impl Iterator { - let mut trait_rpitit_to_impl_rpitit = FxHashMap::default(); - - return trait_datum - .associated_ty_ids - .iter() - .filter_map(|&trait_assoc| match from_assoc_type_id(db, trait_assoc) { - AnyTraitAssocType::Rpitit(it) => Some(it), - AnyTraitAssocType::Normal(_) => None, - }) - .map(move |trait_assoc_id| { - if let Some(&impl_assoc_id) = trait_rpitit_to_impl_rpitit.get(&trait_assoc_id) { - return impl_assoc_id; + trait_method_id: FunctionId, +) -> Box<[Arc]> { + let impl_items = db.impl_items(impl_id); + let trait_method_generics = generics(db, trait_method_id.into()); + let impl_datum = + db.impl_datum(impl_id.loc(db).container.krate(), hir_def::ImplId::to_chalk(impl_id, db)); + let trait_method = db.function_signature(trait_method_id); + let Some(impl_method) = impl_items.items.iter().find_map(|(name, id)| { + if *name == trait_method.name { + match *id { + AssocItemId::FunctionId(it) => Some(it), + _ => None, } + } else { + None + } + }) else { + // FIXME: Handle defaulted methods. + return Box::default(); + }; - let trait_assoc = trait_assoc_id.loc(db); - let trait_method_id = trait_assoc.synthesized_from_method; - let trait_method_generics = generics(db, trait_method_id.into()); - - let impl_assoc_id = (|| { - let trait_method = db.function_signature(trait_method_id); - let impl_method = impl_items.items.iter().find_map(|(name, id)| { - if *name == trait_method.name { - match *id { - AssocItemId::FunctionId(it) => Some(it), - _ => None, - } - } else { - None - } - })?; - - 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 None; - } + let impl_method_generics = generics(db, impl_method.into()); - // 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_self()..]; - // We want to substitute the TraitRef with placeholders, but placeholders from the method, not the impl. - let impl_trait_ref = - impl_trait.clone().substitute(Interner, trait_ref_placeholder_subst); - let trait_to_impl_args = Substitution::from_iter( - Interner, - impl_method_placeholder_subst.as_slice(Interner) - [..impl_method_generics.len_self()] - .iter() - .chain(impl_trait_ref.substitution.as_slice(Interner)), - ); - 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(); - for (trait_rpitit, infer_var) in trait_rpitit_to_infer_var { - let impl_rpitit = table.resolve_completely(infer_var); - let impl_rpitit = impl_rpitit.fold_with( - &mut PlaceholderToBoundVarFolder { - db, - method: impl_method.into(), - method_generics: impl_method_generics.self_params(), - }, - DebruijnIndex::INNERMOST, - ); - let impl_rpitit_binders = VariableKinds::from_iter( - Interner, - &trait_assoc.bounds.binders.as_slice(Interner) - [..trait_method_generics.len()], - ); - let impl_rpitit = Binders::new( - impl_rpitit_binders, - rust_ir::AssociatedTyValueBound { ty: impl_rpitit }, - ); - let impl_rpitit = RpititImplAssocTyId::new( - db, - RpititImplAssocTy { - impl_id, - trait_assoc: trait_rpitit, - value: impl_rpitit, - }, - ); - trait_rpitit_to_impl_rpitit.insert(trait_rpitit, impl_rpitit); - } - - trait_rpitit_to_impl_rpitit.get(&trait_assoc_id).copied() - })(); + // 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 Box::default(); + } - impl_assoc_id.unwrap_or_else(|| { - RpititImplAssocTyId::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_self()..]; + // We want to substitute the TraitRef with placeholders, but placeholders from the method, not the impl. + let impl_trait_ref = impl_datum + .binders + .as_ref() + .map(|it| it.trait_ref.clone()) + .substitute(Interner, trait_ref_placeholder_subst); + let trait_to_impl_args = Substitution::from_iter( + Interner, + impl_method_placeholder_subst.as_slice(Interner)[..impl_method_generics.len_self()] + .iter() + .chain(impl_trait_ref.substitution.as_slice(Interner)), + ); + 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(); + + return trait_rpitit_to_infer_var + .into_iter() + .map(|(trait_assoc_id, infer_var)| { + let impl_rpitit = table.resolve_completely(infer_var); + let impl_rpitit = impl_rpitit.fold_with( + &mut PlaceholderToBoundVarFolder { db, - RpititImplAssocTy { - impl_id, - trait_assoc: trait_assoc_id, - // 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. - value: Binders::new( - VariableKinds::from_iter( - Interner, - &trait_assoc.bounds.binders.as_slice(Interner) - [..trait_method_generics.len_self()], - ), - rust_ir::AssociatedTyValueBound { ty: TyKind::Error.intern(Interner) }, - ), - }, - ) + method: impl_method.into(), + method_generics: impl_method_generics.self_params(), + }, + DebruijnIndex::INNERMOST, + ); + let trait_assoc = trait_assoc_id.loc(db); + let impl_rpitit_binders = VariableKinds::from_iter( + Interner, + &trait_assoc.bounds.binders.as_slice(Interner)[..trait_method_generics.len()], + ); + let impl_rpitit = Binders::new( + impl_rpitit_binders, + rust_ir::AssociatedTyValueBound { ty: impl_rpitit }, + ); + Arc::new(AssociatedTyValue { + associated_ty_id: to_assoc_type_id_rpitit(trait_assoc_id), + impl_id: hir_def::ImplId::to_chalk(impl_id, db), + value: impl_rpitit, }) }) - .map(to_assoc_type_value_id_rpitit); + .collect(); #[derive(chalk_derive::FallibleTypeFolder)] #[has_interner(Interner)] @@ -1322,11 +1290,38 @@ pub(crate) fn associated_ty_value_query( } AnyImplAssocType::Rpitit(assoc_type_id) => { let assoc_type = assoc_type_id.loc(db); - Arc::new(AssociatedTyValue { - impl_id: assoc_type.impl_id.to_chalk(db), - associated_ty_id: to_assoc_type_id_rpitit(assoc_type.trait_assoc), - value: assoc_type.value.clone(), - }) + 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()); + Arc::new(AssociatedTyValue { + associated_ty_id: trait_assoc_id, + impl_id, + // 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. + value: Binders::new( + VariableKinds::from_iter( + Interner, + &trait_assoc.bounds.binders.as_slice(Interner) + [..trait_method_generics.len_self()], + ), + rust_ir::AssociatedTyValueBound { ty: TyKind::Error.intern(Interner) }, + ), + }) + }) } } } diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index e63d7d78482b..e38ea27f98fc 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -373,11 +373,6 @@ pub struct RpititImplAssocTy { pub impl_id: ImplId, /// The definition of this associated type in the trait. pub trait_assoc: RpititTraitAssocTyId, - /// 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 value: Binders>, } impl_intern_key_ref!(RpititImplAssocTyId, RpititImplAssocTy); diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index b4288cd1d248..fee84197b004 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -4997,17 +4997,18 @@ fn check_rpitit(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) .iter() .copied() .filter_map(|assoc_id| { - let assoc = match from_assoc_type_value_id(&db, assoc_id) { - AnyImplAssocType::Rpitit(assoc_id) => Some(assoc_id.loc(&db)), - AnyImplAssocType::Normal(_) => None, - }?; - let ty = assoc + 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(assoc.trait_assoc.loc(&db).synthesized_from_method) + .function_signature(trait_assoc.loc(&db).synthesized_from_method) .name .symbol() .clone(); @@ -5086,3 +5087,25 @@ impl Trait for Foo { "#]], ); } + +#[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 = (); + "#]], + ); +} diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 6fc652e53d91..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, AnyTraitAssocType, InferenceResult, Interner, 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, From fdc54132e6791d45053f095ff92255e9facdf66d Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 20 Mar 2025 01:16:55 +0200 Subject: [PATCH 05/11] Comply with Chalk expectations Chalk apparently requires associated type values to contain both the impl and the associated ty binders, completely unlike the docs (which say they should only contain those of the associated type). So fix that. --- crates/hir-ty/src/chalk_db.rs | 83 +++++++++++++++++++---------------- crates/hir-ty/src/lib.rs | 37 ++++++++-------- crates/hir-ty/src/lower.rs | 7 ++- 3 files changed, 69 insertions(+), 58 deletions(-) diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs index d2971155fdd9..31e690bd6eaf 100644 --- a/crates/hir-ty/src/chalk_db.rs +++ b/crates/hir-ty/src/chalk_db.rs @@ -50,7 +50,7 @@ use crate::{ 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; @@ -1059,9 +1059,14 @@ fn impl_method_rpitit_values( 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, - &trait_assoc.bounds.binders.as_slice(Interner)[..trait_method_generics.len()], + trait_assoc.bounds.binders.as_slice(Interner)[..trait_method_generics.len()] + .iter() + .cloned() + .chain(variable_kinds_from_generics(db, impl_method_generics.iter_parent_id())), ); let impl_rpitit = Binders::new( impl_rpitit_binders, @@ -1288,44 +1293,48 @@ pub(crate) fn associated_ty_value_query( AnyImplAssocType::Normal(type_alias) => { type_alias_associated_ty_value(db, krate, type_alias) } - AnyImplAssocType::Rpitit(assoc_type_id) => { - 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()); - Arc::new(AssociatedTyValue { - associated_ty_id: trait_assoc_id, - impl_id, - // 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. - value: Binders::new( - VariableKinds::from_iter( - Interner, - &trait_assoc.bounds.binders.as_slice(Interner) - [..trait_method_generics.len_self()], - ), - rust_ir::AssociatedTyValueBound { ty: TyKind::Error.intern(Interner) }, - ), - }) - }) - } + AnyImplAssocType::Rpitit(assoc_type_id) => rpitit_associated_ty_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( db: &dyn HirDatabase, _krate: Crate, diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 092034c1182c..989caf99f46f 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -62,7 +62,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}; @@ -343,29 +346,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 1d2cd7992a39..1f712092e601 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -66,7 +66,7 @@ use crate::{ mapping::{ToChalk, from_chalk_trait_id, lt_to_placeholder_idx, to_assoc_type_id_rpitit}, 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)] @@ -1448,7 +1448,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( From b7692b93039cb9c2e0206db2adbf7195e1d5763e Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 20 Mar 2025 01:56:14 +0200 Subject: [PATCH 06/11] Move RPITIT stuff to a new module --- crates/hir-ty/src/chalk_db.rs | 311 ++----------------------------- crates/hir-ty/src/db.rs | 29 +-- crates/hir-ty/src/generics.rs | 8 +- crates/hir-ty/src/lib.rs | 1 + crates/hir-ty/src/lower.rs | 3 +- crates/hir-ty/src/mapping.rs | 3 +- crates/hir-ty/src/rpitit.rs | 335 ++++++++++++++++++++++++++++++++++ 7 files changed, 357 insertions(+), 333 deletions(-) create mode 100644 crates/hir-ty/src/rpitit.rs diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs index 31e690bd6eaf..541e59957e21 100644 --- a/crates/hir-ty/src/chalk_db.rs +++ b/crates/hir-ty/src/chalk_db.rs @@ -5,48 +5,41 @@ use std::{iter, ops::ControlFlow, sync::Arc}; use hir_expand::name::Name; use intern::sym; -use rustc_hash::FxHashMap; use span::Edition; use tracing::debug; use chalk_ir::{ Binders, CanonicalVarKinds, cast::{Cast, Caster}, - fold::{TypeFoldable, TypeFolder, TypeSuperFoldable, shift::Shift}, + fold::shift::Shift, }; use chalk_solve::rust_ir::{self, AssociatedTyDatumBound, OpaqueTyDatumBound, WellKnownTrait}; use base_db::Crate; use hir_def::{ - AssocItemId, BlockId, CallableDefId, FunctionId, GenericDefId, HasModule, ItemContainerId, - Lookup, TypeAliasId, VariantId, - hir::{ - Movability, - generics::{GenericParams, TypeOrConstParamData}, - }, + AssocItemId, BlockId, CallableDefId, GenericDefId, HasModule, ItemContainerId, Lookup, + TypeAliasId, VariantId, + hir::Movability, lang_item::LangItem, signatures::{ImplFlags, StructFlags, TraitFlags}, }; use crate::{ - AliasEq, AliasTy, BoundVar, Const, ConstData, ConstValue, DebruijnIndex, DomainGoal, Goal, - GoalData, InferenceTable, Interner, Lifetime, LifetimeData, PlaceholderIndex, ProjectionTy, - ProjectionTyExt, QuantifiedWhereClause, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder, - TyExt, TyKind, VariableKinds, WhereClause, - db::{ - HirDatabase, InternedCoroutine, RpititImplAssocTy, RpititImplAssocTyId, - RpititTraitAssocTyId, - }, - from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, + AliasEq, AliasTy, BoundVar, DebruijnIndex, Interner, ProjectionTy, ProjectionTyExt, + QuantifiedWhereClause, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, + VariableKinds, WhereClause, + db::{HirDatabase, InternedCoroutine}, + from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, generics::generics, lower::LifetimeElisionKind, lower::trait_fn_signature, - lt_from_placeholder_idx, make_binders, make_single_type_binders, + make_binders, make_single_type_binders, 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}, to_assoc_type_id, to_chalk_trait_id, traits::ChalkContext, utils::ClosureSubst, @@ -961,265 +954,6 @@ fn impl_def_datum(db: &dyn HirDatabase, krate: Crate, impl_id: hir_def::ImplId) Arc::new(impl_datum) } -// We return a list and not a hasmap because the number of RPITITs in a function should be small. -#[salsa_macros::tracked(return_ref)] -fn impl_method_rpitit_values( - db: &dyn HirDatabase, - impl_id: hir_def::ImplId, - trait_method_id: FunctionId, -) -> Box<[Arc]> { - let impl_items = db.impl_items(impl_id); - let trait_method_generics = generics(db, trait_method_id.into()); - let impl_datum = - db.impl_datum(impl_id.loc(db).container.krate(), hir_def::ImplId::to_chalk(impl_id, db)); - let trait_method = db.function_signature(trait_method_id); - let Some(impl_method) = impl_items.items.iter().find_map(|(name, id)| { - if *name == trait_method.name { - match *id { - AssocItemId::FunctionId(it) => Some(it), - _ => None, - } - } else { - None - } - }) else { - // FIXME: Handle defaulted methods. - return Box::default(); - }; - - 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 Box::default(); - } - - // 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_self()..]; - // We want to substitute the TraitRef with placeholders, but placeholders from the method, not the impl. - let impl_trait_ref = impl_datum - .binders - .as_ref() - .map(|it| it.trait_ref.clone()) - .substitute(Interner, trait_ref_placeholder_subst); - let trait_to_impl_args = Substitution::from_iter( - Interner, - impl_method_placeholder_subst.as_slice(Interner)[..impl_method_generics.len_self()] - .iter() - .chain(impl_trait_ref.substitution.as_slice(Interner)), - ); - 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(); - - return trait_rpitit_to_infer_var - .into_iter() - .map(|(trait_assoc_id, infer_var)| { - let impl_rpitit = table.resolve_completely(infer_var); - let impl_rpitit = impl_rpitit.fold_with( - &mut PlaceholderToBoundVarFolder { - db, - method: impl_method.into(), - method_generics: impl_method_generics.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, - trait_assoc.bounds.binders.as_slice(Interner)[..trait_method_generics.len()] - .iter() - .cloned() - .chain(variable_kinds_from_generics(db, impl_method_generics.iter_parent_id())), - ); - let impl_rpitit = Binders::new( - impl_rpitit_binders, - rust_ir::AssociatedTyValueBound { ty: impl_rpitit }, - ); - Arc::new(AssociatedTyValue { - associated_ty_id: to_assoc_type_id_rpitit(trait_assoc_id), - impl_id: hir_def::ImplId::to_chalk(impl_id, db), - value: impl_rpitit, - }) - }) - .collect(); - - #[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()); - let bound = bound.fold_with(self, outer_binder); - 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, - } - 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(), - ) - .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(), - ) - .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, - ) - .to_lifetime(Interner) - } else { - Lifetime::new(Interner, LifetimeData::Placeholder(universe)) - } - } - } -} - pub(crate) fn inline_bound_to_generic_predicate( bound: &Binders>, self_ty: Ty, @@ -1261,29 +995,6 @@ pub(crate) fn inline_bound_to_generic_predicate( } } -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 -} - pub(crate) fn associated_ty_value_query( db: &dyn HirDatabase, krate: Crate, diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index e38ea27f98fc..980ee264b027 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -3,7 +3,7 @@ use std::sync; -use base_db::{Crate, impl_intern_key, impl_intern_key_ref}; +use base_db::{Crate, impl_intern_key}; use hir_def::{ AdtId, BlockId, CallableDefId, ConstParamId, DefWithBodyId, EnumVariantId, FunctionId, GeneralConstId, GenericDefId, ImplId, LifetimeParamId, LocalFieldId, StaticId, TraitId, @@ -349,30 +349,3 @@ impl_intern_key!(InternedCoroutineId, InternedCoroutine); // This exists just for Chalk, because Chalk just has a single `FnDefId` where // we have different IDs for struct and enum variant constructors. impl_intern_key!(InternedCallableDefId, CallableDefId); - -/// 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); diff --git a/crates/hir-ty/src/generics.rs b/crates/hir-ty/src/generics.rs index 7003118dce2b..0b72cca986b0 100644 --- a/crates/hir-ty/src/generics.rs +++ b/crates/hir-ty/src/generics.rs @@ -125,9 +125,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/lib.rs b/crates/hir-ty/src/lib.rs index 989caf99f46f..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; diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index 1f712092e601..c3fd9983d884 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -55,7 +55,7 @@ use crate::{ TraitRefExt, Ty, TyBuilder, TyKind, VariableKind, 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, RpititTraitAssocTy, RpititTraitAssocTyId}, + db::HirDatabase, error_lifetime, generics::{Generics, generics, trait_self_param_idx}, lower::{ @@ -64,6 +64,7 @@ use crate::{ }, make_binders, mapping::{ToChalk, from_chalk_trait_id, lt_to_placeholder_idx, to_assoc_type_id_rpitit}, + rpitit::{RpititTraitAssocTy, RpititTraitAssocTyId}, static_lifetime, to_chalk_trait_id, to_placeholder_idx, utils::all_super_trait_refs, variable_kinds_from_generics, diff --git a/crates/hir-ty/src/mapping.rs b/crates/hir-ty/src/mapping.rs index a8b4c51f2a95..93db0c04ec7e 100644 --- a/crates/hir-ty/src/mapping.rs +++ b/crates/hir-ty/src/mapping.rs @@ -14,7 +14,8 @@ use salsa::{ use crate::{ AssocTypeId, CallableDefId, ChalkTraitId, FnDefId, ForeignDefId, Interner, OpaqueTyId, PlaceholderIndex, chalk_db, - db::{HirDatabase, RpititImplAssocTyId, RpititTraitAssocTyId}, + db::HirDatabase, + rpitit::{RpititImplAssocTyId, RpititTraitAssocTyId}, }; pub(crate) trait ToChalk { diff --git a/crates/hir-ty/src/rpitit.rs b/crates/hir-ty/src/rpitit.rs new file mode 100644 index 000000000000..ca1a218bca77 --- /dev/null +++ b/crates/hir-ty/src/rpitit.rs @@ -0,0 +1,335 @@ +//! 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, + fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}, +}; +use chalk_solve::rust_ir::AssociatedTyValueBound; +use hir_def::{ + AssocItemId, FunctionId, GenericDefId, ImplId, TraitId, + hir::generics::{GenericParams, TypeOrConstParamData}, +}; +use rustc_hash::FxHashMap; + +use crate::{ + AliasTy, AnyTraitAssocType, Binders, Const, ConstData, ConstValue, DomainGoal, Goal, GoalData, + InferenceTable, Interner, Lifetime, LifetimeData, PlaceholderIndex, ProjectionTy, Substitution, + Ty, TyKind, VariableKinds, + chalk_db::{AssociatedTyValue, inline_bound_to_generic_predicate}, + db::HirDatabase, + from_assoc_type_id, from_placeholder_idx, + generics::generics, + lt_from_placeholder_idx, + mapping::{ToChalk, to_assoc_type_id_rpitit}, + 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); + +// We return a list and not a hasmap because the number of RPITITs in a function should be small. +#[salsa_macros::tracked(return_ref)] +pub(crate) fn impl_method_rpitit_values( + db: &dyn HirDatabase, + impl_id: hir_def::ImplId, + trait_method_id: FunctionId, +) -> Box<[Arc]> { + let impl_items = db.impl_items(impl_id); + let trait_method_generics = generics(db, trait_method_id.into()); + let impl_datum = + db.impl_datum(impl_id.loc(db).container.krate(), hir_def::ImplId::to_chalk(impl_id, db)); + let trait_method = db.function_signature(trait_method_id); + let Some(impl_method) = impl_items.items.iter().find_map(|(name, id)| { + if *name == trait_method.name { + match *id { + AssocItemId::FunctionId(it) => Some(it), + _ => None, + } + } else { + None + } + }) else { + // FIXME: Handle defaulted methods. + return Box::default(); + }; + + 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 Box::default(); + } + + // 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_datum + .binders + .as_ref() + .map(|it| it.trait_ref.clone()) + .substitute(Interner, trait_ref_placeholder_subst); + let trait_to_impl_args = Substitution::from_iter( + Interner, + impl_trait_ref.substitution.as_slice(Interner).iter().chain( + &impl_method_placeholder_subst.as_slice(Interner)[impl_method_generics.len_parent()..], + ), + ); + 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(); + + return trait_rpitit_to_infer_var + .into_iter() + .map(|(trait_assoc_id, infer_var)| { + let impl_rpitit = table.resolve_completely(infer_var); + let impl_rpitit = impl_rpitit.fold_with( + &mut PlaceholderToBoundVarFolder { + db, + method: impl_method.into(), + method_generics: impl_method_generics.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, + trait_assoc.bounds.binders.as_slice(Interner)[..trait_method_generics.len()] + .iter() + .cloned() + .chain(variable_kinds_from_generics(db, impl_method_generics.iter_parent_id())), + ); + 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(); + + #[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()); + let bound = bound.fold_with(self, outer_binder); + 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, + } + 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(), + ) + .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(), + ) + .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, + ) + .to_lifetime(Interner) + } else { + Lifetime::new(Interner, LifetimeData::Placeholder(universe)) + } + } + } +} + +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 +} From 29ea2f032416ccf0f51dc4ed89f211d0cee04d2b Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 15 May 2025 04:54:55 +0300 Subject: [PATCH 07/11] Handle RPITITs in defaulted trait methods Here, we lower the method's return type twice: once with RPITITs as assoc types, and once with the RPITITs as opaques, as if it was repeated on the impl. --- crates/hir-ty/src/display.rs | 3 +- crates/hir-ty/src/lower.rs | 15 +- crates/hir-ty/src/rpitit.rs | 476 ++++++++++++++++++++---------- crates/hir-ty/src/tests/traits.rs | 59 +++- crates/hir-ty/src/tls.rs | 6 +- 5 files changed, 385 insertions(+), 174 deletions(-) diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index d7cdecf6b2ca..73b0e3fed2c2 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -684,7 +684,8 @@ impl HirDisplay for ProjectionTy { ), &assoc_type .bounds - .skip_binders() + .clone() + .substitute(Interner, &self.substitution) .iter() .map(|bound| { // We ignore `Self` anyway when formatting, so it's fine put an error type in it. diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index c3fd9983d884..c0815a9e8a4c 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -52,7 +52,7 @@ use crate::{ FnSubst, ImplTrait, ImplTraitId, ImplTraits, Interner, Lifetime, LifetimeData, LifetimeOutlives, PlaceholderIndex, PolyFnSig, ProgramClause, ProjectionTy, QuantifiedWhereClause, QuantifiedWhereClauses, Substitution, TraitEnvironment, TraitRef, - TraitRefExt, Ty, TyBuilder, TyKind, VariableKind, VariableKinds, WhereClause, all_super_traits, + 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, @@ -450,8 +450,7 @@ 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 => { @@ -519,15 +518,7 @@ impl<'a> TyLoweringContext<'a> { let assoc_type_binders = VariableKinds::from_iter( Interner, - method_generics.iter_id().map(|param_id| match param_id { - GenericParamId::TypeParamId(_) => { - VariableKind::Ty(chalk_ir::TyVariableKind::General) - } - GenericParamId::ConstParamId(param_id) => { - VariableKind::Const(self.db.const_param_ty(param_id)) - } - GenericParamId::LifetimeParamId(_) => VariableKind::Lifetime, - }), + variable_kinds_from_generics(self.db, method_generics.iter_id()), ); let returned_subst = self.subst_for_generics(); diff --git a/crates/hir-ty/src/rpitit.rs b/crates/hir-ty/src/rpitit.rs index ca1a218bca77..ff84622789b2 100644 --- a/crates/hir-ty/src/rpitit.rs +++ b/crates/hir-ty/src/rpitit.rs @@ -5,23 +5,27 @@ 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::{ - AssocItemId, FunctionId, GenericDefId, ImplId, TraitId, + AssocItemId, FunctionId, GenericDefId, GenericParamId, ImplId, TraitId, hir::generics::{GenericParams, TypeOrConstParamData}, + resolver::HasResolver, }; use rustc_hash::FxHashMap; +use thin_vec::ThinVec; use crate::{ AliasTy, AnyTraitAssocType, Binders, Const, ConstData, ConstValue, DomainGoal, Goal, GoalData, - InferenceTable, Interner, Lifetime, LifetimeData, PlaceholderIndex, ProjectionTy, Substitution, - Ty, TyKind, VariableKinds, + ImplTraitLoweringMode, InferenceTable, Interner, Lifetime, LifetimeData, LifetimeElisionKind, + ParamLoweringMode, PlaceholderIndex, ProjectionTy, Substitution, TraitRef, Ty, TyKind, + TyLoweringContext, VariableKinds, chalk_db::{AssociatedTyValue, inline_bound_to_generic_predicate}, db::HirDatabase, from_assoc_type_id, from_placeholder_idx, - generics::generics, + generics::{Generics, generics}, lt_from_placeholder_idx, mapping::{ToChalk, to_assoc_type_id_rpitit}, variable_kinds_from_generics, @@ -58,15 +62,14 @@ impl_intern_key_ref!(RpititImplAssocTyId, RpititImplAssocTy); #[salsa_macros::tracked(return_ref)] pub(crate) fn impl_method_rpitit_values( db: &dyn HirDatabase, - impl_id: hir_def::ImplId, + impl_id: ImplId, trait_method_id: FunctionId, ) -> Box<[Arc]> { let impl_items = db.impl_items(impl_id); let trait_method_generics = generics(db, trait_method_id.into()); - let impl_datum = - db.impl_datum(impl_id.loc(db).container.krate(), hir_def::ImplId::to_chalk(impl_id, db)); let trait_method = db.function_signature(trait_method_id); - let Some(impl_method) = impl_items.items.iter().find_map(|(name, id)| { + let impl_trait_ref = db.impl_trait(impl_id).expect("invalid impl passed to Chalk"); + let impl_method = impl_items.items.iter().find_map(|(name, id)| { if *name == trait_method.name { match *id { AssocItemId::FunctionId(it) => Some(it), @@ -75,9 +78,19 @@ pub(crate) fn impl_method_rpitit_values( } else { None } - }) else { - // FIXME: Handle defaulted methods. - return Box::default(); + }); + 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()); @@ -107,11 +120,7 @@ pub(crate) fn impl_method_rpitit_values( 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_datum - .binders - .as_ref() - .map(|it| it.trait_ref.clone()) - .substitute(Interner, trait_ref_placeholder_subst); + let impl_trait_ref = impl_trait_ref.substitute(Interner, trait_ref_placeholder_subst); let trait_to_impl_args = Substitution::from_iter( Interner, impl_trait_ref.substitution.as_slice(Interner).iter().chain( @@ -139,15 +148,22 @@ pub(crate) fn impl_method_rpitit_values( table.unify(&trait_method_ret, &impl_method_ret); table.resolve_obligations_as_possible(); - return trait_rpitit_to_infer_var + 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, ); @@ -169,146 +185,143 @@ pub(crate) fn impl_method_rpitit_values( value: impl_rpitit, }) }) - .collect(); - - #[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 - } + .collect() +} - 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()); - let bound = bound.fold_with(self, outer_binder); - 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) - } - } +fn defaulted_impl_method_rpitit_values( + db: &dyn HirDatabase, + impl_id: ImplId, + trait_method_id: FunctionId, + impl_trait_ref: Binders, + trait_method_generics: &Generics, +) -> Box<[Arc]> { + 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, + trait_method_generics.iter_self_id().chain(impl_generics.iter_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() +} - #[derive(chalk_derive::FallibleTypeFolder)] - #[has_interner(Interner)] - struct PlaceholderToBoundVarFolder<'a> { - db: &'a dyn HirDatabase, - method: GenericDefId, - method_generics: &'a GenericParams, - } - impl TypeFolder for PlaceholderToBoundVarFolder<'_> { - fn as_dyn(&mut self) -> &mut dyn TypeFolder { - self - } +/// 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)] +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())); - fn interner(&self) -> Interner { - Interner - } + 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); - 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(), - ) - .to_ty(Interner) - } else { - TyKind::Placeholder(universe).intern(Interner) - } - } + // 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); - 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(), - ) - .to_const(Interner, ty) - } else { - Const::new(Interner, ConstData { ty, value: ConstValue::Placeholder(universe) }) - } - } + 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(); - 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, - ) - .to_lifetime(Interner) - } else { - Lifetime::new(Interner, LifetimeData::Placeholder(universe)) - } - } - } + 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( @@ -333,3 +346,164 @@ fn check_method_generics_are_structurally_compatible( 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 { + BoundVar::new( + DebruijnIndex::INNERMOST, + placeholder.local_id.into_raw().into_u32() as usize + + self.parent_generics.len_lifetimes(), + ) + .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 { + BoundVar::new( + DebruijnIndex::INNERMOST, + placeholder.local_id.into_raw().into_u32() as usize, + ) + .to_lifetime(Interner) + } else { + Lifetime::new(Interner, LifetimeData::Placeholder(universe)) + } + } +} diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index fee84197b004..37a75595452d 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -4937,13 +4937,15 @@ fn bar<'a, 'b, T, const N: usize, U, const M: usize, Ty: Trait<'a, T, N>>(v: &Ty } "#, // 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<'b, U, M, Ty, '?, {unknown}, _>,) + 201..202 '_': (Trait::__foo_rpitit<'?, {unknown}, _, 'b, U, M, Ty>,) 205..206 'v': &'? Ty - 205..224 'v.foo:..., M>()': (Trait::__foo_rpitit<'b, U, M, Ty, '?, {unknown}, _>,) + 205..224 'v.foo:..., M>()': (Trait::__foo_rpitit<'?, {unknown}, _, 'b, U, M, Ty>,) "#]], ); } @@ -5074,14 +5076,14 @@ impl Trait for Foo { "#, expect![[r#" type __foo_rpitit: T1; - type __foo_rpitit: T2 + T2 + ?Sized; - type __bar_rpitit: T2 + T3 + Trait; + 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.1; - type __bar_rpitit = impl T2; + type __foo_rpitit = ?0.2; + type __bar_rpitit = impl T2; type __baz_rpitit = (); type __baz_rpitit = impl T4; "#]], @@ -5109,3 +5111,48 @@ impl Trait for () { "#]], ); } + +#[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 check_foo() { + check_rpitit( + r#" +//- minicore: future, send, sized +use core::future::Future; + +trait DesugaredAsyncTrait { + fn foo(&self) -> impl Future + Send; +} + +impl DesugaredAsyncTrait for () {} + "#, + expect![[r#""#]], + ); +} diff --git a/crates/hir-ty/src/tls.rs b/crates/hir-ty/src/tls.rs index b4952bfe46c0..e2785542f9a7 100644 --- a/crates/hir-ty/src/tls.rs +++ b/crates/hir-ty/src/tls.rs @@ -97,10 +97,8 @@ impl DebugContext<'_> { 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).to_string() - ); + let placeholder_assoc_type_name = + format!("__{}_rpitit", method_data.name.display(self.0, Edition::LATEST)); (assoc_type.trait_id, placeholder_assoc_type_name) } }; From a8d89ae70b2c3ff7b5951516c036add0ed4477c5 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 15 May 2025 15:18:23 +0300 Subject: [PATCH 08/11] Handle cycles in RPITIT computation Cycles can occur in malformed code (this is only a conjecture) or from Chalk bugs (for this I have a test). --- crates/hir-ty/src/chalk_db.rs | 29 ++++++++++++++++++++++++++++- crates/hir-ty/src/db.rs | 1 + crates/hir-ty/src/rpitit.rs | 19 +++++++++++++++++-- crates/hir-ty/src/tests/traits.rs | 11 +++++++++-- 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs index 541e59957e21..48073a7d8606 100644 --- a/crates/hir-ty/src/chalk_db.rs +++ b/crates/hir-ty/src/chalk_db.rs @@ -13,7 +13,9 @@ use chalk_ir::{ cast::{Cast, Caster}, fold::shift::Shift, }; -use chalk_solve::rust_ir::{self, AssociatedTyDatumBound, OpaqueTyDatumBound, WellKnownTrait}; +use chalk_solve::rust_ir::{ + self, AssociatedTyDatumBound, AssociatedTyValueBound, OpaqueTyDatumBound, WellKnownTrait, +}; use base_db::Crate; use hir_def::{ @@ -1008,6 +1010,31 @@ pub(crate) fn associated_ty_value_query( } } +/// 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) => { + let assoc = assoc_type_id.loc(db); + let trait_assoc = assoc.trait_assoc.loc(db); + Arc::new(AssociatedTyValue { + associated_ty_id: to_assoc_type_id_rpitit(assoc.trait_assoc), + impl_id: assoc.impl_id.to_chalk(db), + value: trait_assoc + .bounds + .as_ref() + .map(|_| AssociatedTyValueBound { ty: TyKind::Error.intern(Interner) }), + }) + } + } +} + fn rpitit_associated_ty_value( db: &dyn HirDatabase, assoc_type_id: RpititImplAssocTyId, diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index 980ee264b027..519f040e6b02 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -290,6 +290,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/rpitit.rs b/crates/hir-ty/src/rpitit.rs index ff84622789b2..16d76925f278 100644 --- a/crates/hir-ty/src/rpitit.rs +++ b/crates/hir-ty/src/rpitit.rs @@ -58,8 +58,16 @@ pub struct RpititImplAssocTy { 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)] +#[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, @@ -249,11 +257,18 @@ fn defaulted_impl_method_rpitit_values( .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)] +#[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, diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 37a75595452d..fa906fcee4c2 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -5141,7 +5141,10 @@ impl Trait<(Foo<()>, U)> for Foo {} } #[test] -fn check_foo() { +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 @@ -5153,6 +5156,10 @@ trait DesugaredAsyncTrait { impl DesugaredAsyncTrait for () {} "#, - expect![[r#""#]], + expect![[r#" + type __foo_rpitit: Future + Send; + + type __foo_rpitit = {unknown}; + "#]], ); } From fbdcafab48aec72968e965af7a2fdbc1c604fdf7 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 15 May 2025 20:18:23 +0300 Subject: [PATCH 09/11] Handle RPITIT when inferring the body of the method Inside the method, we should insert an environment clause `RpititAssoc = Ty` so we know the value of the associated type. This matters mostly for defaulted trait methods, because for them without this calling the same method from the same trait will return the RPITIT and not the opaque type, but it also matters for impl methods, and will matter even more once we start reporting trait errors, as then RTN bounds could cause errors on the methods without this. Unfortunately that means we have to separate `trait_environment()` from `trait_environment_for_body()` (i.e. make the latter no longer a transparent wrapper around the former but a real query), because the RPITIT infra needs to call `trait_environment()` but `trait_environment_for_body()` needs to call it for the `AliasEq` clauses. This means another query, more memory usage etc.. But looks like it's unavoidable. --- crates/hir-ty/src/db.rs | 1 - crates/hir-ty/src/generics.rs | 8 ++ crates/hir-ty/src/lower.rs | 31 ++++-- crates/hir-ty/src/rpitit.rs | 168 +++++++++++++++++++++++++----- crates/hir-ty/src/tests/traits.rs | 27 +++++ 5 files changed, 202 insertions(+), 33 deletions(-) diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index 519f040e6b02..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)] diff --git a/crates/hir-ty/src/generics.rs b/crates/hir-ty/src/generics.rs index 0b72cca986b0..b5c11dddf3d4 100644 --- a/crates/hir-ty/src/generics.rs +++ b/crates/hir-ty/src/generics.rs @@ -88,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, diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index c0815a9e8a4c..b8e0b6d07ce2 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -27,7 +27,7 @@ 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}, @@ -64,7 +64,7 @@ use crate::{ }, make_binders, mapping::{ToChalk, from_chalk_trait_id, lt_to_placeholder_idx, to_assoc_type_id_rpitit}, - rpitit::{RpititTraitAssocTy, RpititTraitAssocTyId}, + 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_generics, @@ -1180,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( @@ -1188,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, @@ -1200,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() { @@ -1240,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)] diff --git a/crates/hir-ty/src/rpitit.rs b/crates/hir-ty/src/rpitit.rs index 16d76925f278..ed731aea8ae5 100644 --- a/crates/hir-ty/src/rpitit.rs +++ b/crates/hir-ty/src/rpitit.rs @@ -10,7 +10,8 @@ use chalk_ir::{ }; use chalk_solve::rust_ir::AssociatedTyValueBound; use hir_def::{ - AssocItemId, FunctionId, GenericDefId, GenericParamId, ImplId, TraitId, + AssocItemId, ConstParamId, FunctionId, GenericDefId, GenericParamId, ImplId, ItemContainerId, + TraitId, hir::generics::{GenericParams, TypeOrConstParamData}, resolver::HasResolver, }; @@ -18,17 +19,17 @@ use rustc_hash::FxHashMap; use thin_vec::ThinVec; use crate::{ - AliasTy, AnyTraitAssocType, Binders, Const, ConstData, ConstValue, DomainGoal, Goal, GoalData, - ImplTraitLoweringMode, InferenceTable, Interner, Lifetime, LifetimeData, LifetimeElisionKind, - ParamLoweringMode, PlaceholderIndex, ProjectionTy, Substitution, TraitRef, Ty, TyKind, - TyLoweringContext, VariableKinds, + 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, - from_assoc_type_id, from_placeholder_idx, + 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}, - variable_kinds_from_generics, + to_placeholder_idx, variable_kinds_from_generics, }; /// An associated type synthesized from a Return Position Impl Trait In Trait @@ -72,7 +73,7 @@ pub(crate) fn impl_method_rpitit_values( db: &dyn HirDatabase, impl_id: ImplId, trait_method_id: FunctionId, -) -> Box<[Arc]> { +) -> 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); @@ -108,7 +109,7 @@ pub(crate) fn impl_method_rpitit_values( trait_method_generics.self_params(), impl_method_generics.self_params(), ) { - return Box::default(); + return ThinVec::new(); } // The inference algorithm works as follows: in the trait method, we replace each RPITIT with an infer var, @@ -180,10 +181,12 @@ pub(crate) fn impl_method_rpitit_values( // generics in the binder. let impl_rpitit_binders = VariableKinds::from_iter( Interner, - trait_assoc.bounds.binders.as_slice(Interner)[..trait_method_generics.len()] - .iter() - .cloned() - .chain(variable_kinds_from_generics(db, impl_method_generics.iter_parent_id())), + 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 }); @@ -202,7 +205,7 @@ fn defaulted_impl_method_rpitit_values( trait_method_id: FunctionId, impl_trait_ref: Binders, trait_method_generics: &Generics, -) -> Box<[Arc]> { +) -> 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 @@ -461,12 +464,14 @@ impl TypeFolder for PlaceholderToBoundVarFolder<'_> { ) .to_ty(Interner) } 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_ty(Interner) + 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) } @@ -512,13 +517,126 @@ impl TypeFolder for PlaceholderToBoundVarFolder<'_> { ) .to_lifetime(Interner) } else if placeholder.parent == self.parent { - BoundVar::new( - DebruijnIndex::INNERMOST, - placeholder.local_id.into_raw().into_u32() as usize, - ) - .to_lifetime(Interner) + 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); + // 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), + } + }, + ), + ); + let trait_subst = Substitution::from_iter( + Interner, + trait_ref_subst + .substitution + .iter(Interner) + .cloned() + .chain(trait_method_subst), + ); + + (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) + })); + } + _ => {} + } +} diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index fa906fcee4c2..6fc4e62b2e6a 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -5163,3 +5163,30 @@ impl DesugaredAsyncTrait for () {} "#]], ); } + +#[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 '{}': () + "#]], + ); +} From 2b201a1d3746e9410e1df362c08dae7469d9892a Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 16 May 2025 01:41:37 +0300 Subject: [PATCH 10/11] Fix a bug The binders of the recovery value weren't correct. --- crates/hir-def/src/nameres/assoc.rs | 7 +++++ crates/hir-ty/src/chalk_db.rs | 24 +++++------------ crates/hir-ty/src/rpitit.rs | 38 +++++++++++++++++---------- crates/hir-ty/src/tests/traits.rs | 40 +++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 31 deletions(-) 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/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs index 48073a7d8606..fad8508da108 100644 --- a/crates/hir-ty/src/chalk_db.rs +++ b/crates/hir-ty/src/chalk_db.rs @@ -13,9 +13,7 @@ use chalk_ir::{ cast::{Cast, Caster}, fold::shift::Shift, }; -use chalk_solve::rust_ir::{ - self, AssociatedTyDatumBound, AssociatedTyValueBound, OpaqueTyDatumBound, WellKnownTrait, -}; +use chalk_solve::rust_ir::{self, AssociatedTyDatumBound, OpaqueTyDatumBound, WellKnownTrait}; use base_db::Crate; use hir_def::{ @@ -33,15 +31,16 @@ use crate::{ db::{HirDatabase, InternedCoroutine}, from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, generics::generics, - lower::LifetimeElisionKind, - lower::trait_fn_signature, + lower::{LifetimeElisionKind, trait_fn_signature}, make_binders, make_single_type_binders, 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}, + rpitit::{ + RpititImplAssocTy, RpititImplAssocTyId, impl_method_rpitit_values, recovery_rpitit_value, + }, to_assoc_type_id, to_chalk_trait_id, traits::ChalkContext, utils::ClosureSubst, @@ -1020,18 +1019,7 @@ pub(crate) fn associated_ty_value_cycle( AnyImplAssocType::Normal(type_alias) => { type_alias_associated_ty_value(db, krate, type_alias) } - AnyImplAssocType::Rpitit(assoc_type_id) => { - let assoc = assoc_type_id.loc(db); - let trait_assoc = assoc.trait_assoc.loc(db); - Arc::new(AssociatedTyValue { - associated_ty_id: to_assoc_type_id_rpitit(assoc.trait_assoc), - impl_id: assoc.impl_id.to_chalk(db), - value: trait_assoc - .bounds - .as_ref() - .map(|_| AssociatedTyValueBound { ty: TyKind::Error.intern(Interner) }), - }) - } + AnyImplAssocType::Rpitit(assoc_type_id) => recovery_rpitit_value(db, assoc_type_id), } } diff --git a/crates/hir-ty/src/rpitit.rs b/crates/hir-ty/src/rpitit.rs index ed731aea8ae5..f04d3a175153 100644 --- a/crates/hir-ty/src/rpitit.rs +++ b/crates/hir-ty/src/rpitit.rs @@ -10,8 +10,7 @@ use chalk_ir::{ }; use chalk_solve::rust_ir::AssociatedTyValueBound; use hir_def::{ - AssocItemId, ConstParamId, FunctionId, GenericDefId, GenericParamId, ImplId, ItemContainerId, - TraitId, + ConstParamId, FunctionId, GenericDefId, GenericParamId, ImplId, ItemContainerId, TraitId, hir::generics::{GenericParams, TypeOrConstParamData}, resolver::HasResolver, }; @@ -78,16 +77,7 @@ pub(crate) fn impl_method_rpitit_values( 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.items.iter().find_map(|(name, id)| { - if *name == trait_method.name { - match *id { - AssocItemId::FunctionId(it) => Some(it), - _ => None, - } - } else { - None - } - }); + let impl_method = impl_items.method_by_name(&trait_method.name); let impl_method = match impl_method { Some(impl_method) => impl_method, None => { @@ -244,7 +234,7 @@ fn defaulted_impl_method_rpitit_values( Interner, variable_kinds_from_generics( db, - trait_method_generics.iter_self_id().chain(impl_generics.iter_id()), + impl_generics.iter_id().chain(trait_method_generics.iter_self_id()), ), ); defaulted_rpitit_values @@ -640,3 +630,25 @@ pub(crate) fn add_method_body_rpitit_clauses( _ => {} } } + +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 6fc4e62b2e6a..65b3a9484bb1 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -5190,3 +5190,43 @@ trait Trait { "#]], ); } + +#[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} + "#]], + ); +} From 7544614a15509c05b6b86f6fe5b8de94e8ec1ec0 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 16 May 2025 02:24:41 +0300 Subject: [PATCH 11/11] Fix another bug Having to do with how we rebase the impl params onto the trait params. --- crates/hir-ty/src/rpitit.rs | 80 ++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/crates/hir-ty/src/rpitit.rs b/crates/hir-ty/src/rpitit.rs index f04d3a175153..a2c5c2ecec4b 100644 --- a/crates/hir-ty/src/rpitit.rs +++ b/crates/hir-ty/src/rpitit.rs @@ -120,11 +120,11 @@ pub(crate) fn impl_method_rpitit_values( &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 = Substitution::from_iter( - Interner, - impl_trait_ref.substitution.as_slice(Interner).iter().chain( - &impl_method_placeholder_subst.as_slice(Interner)[impl_method_generics.len_parent()..], - ), + 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()) @@ -559,41 +559,11 @@ pub(crate) fn add_method_body_rpitit_clauses( let trait_ref_subst = trait_ref.clone().substitute(Interner, &impl_method_subst); - // 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), - } - }, - ), - ); - let trait_subst = Substitution::from_iter( - Interner, - trait_ref_subst - .substitution - .iter(Interner) - .cloned() - .chain(trait_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) @@ -631,6 +601,36 @@ pub(crate) fn add_method_body_rpitit_clauses( } } +/// 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,