//! Compute the dyn-compatibility of a trait use std::ops::ControlFlow; use hir_def::{ AssocItemId, ConstId, CrateRootModuleId, FunctionId, GenericDefId, HasModule, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, hir::generics::LocalTypeOrConstParamId, lang_item::LangItem, signatures::TraitFlags, }; use intern::Symbol; use rustc_hash::FxHashSet; use rustc_type_ir::{ AliasTyKind, ClauseKind, PredicatePolarity, TypeSuperVisitable as _, TypeVisitable as _, Upcast, elaborate, inherent::{IntoKind, SliceLike}, }; use smallvec::SmallVec; use crate::{ ImplTraitId, db::{HirDatabase, InternedOpaqueTyId}, lower_nextsolver::associated_ty_item_bounds, next_solver::{ Clause, Clauses, DbInterner, GenericArgs, ParamEnv, SolverDefId, TraitPredicate, TraitRef, TypingMode, infer::DbInternerInferExt, mk_param, }, traits::next_trait_solve_in_ctxt, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum DynCompatibilityViolation { SizedSelf, SelfReferential, Method(FunctionId, MethodViolationCode), AssocConst(ConstId), GAT(TypeAliasId), // This doesn't exist in rustc, but added for better visualization HasNonCompatibleSuperTrait(TraitId), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum MethodViolationCode { StaticMethod, ReferencesSelfInput, ReferencesSelfOutput, ReferencesImplTraitInTrait, AsyncFn, WhereClauseReferencesSelf, Generic, UndispatchableReceiver, } pub fn dyn_compatibility( db: &dyn HirDatabase, trait_: TraitId, ) -> Option { let interner = DbInterner::new_with(db, Some(trait_.krate(db)), None); for super_trait in elaborate::supertrait_def_ids(interner, trait_.into()) { if let Some(v) = db.dyn_compatibility_of_trait(super_trait.0) { return if super_trait.0 == trait_ { Some(v) } else { Some(DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait.0)) }; } } None } pub fn dyn_compatibility_with_callback( db: &dyn HirDatabase, trait_: TraitId, cb: &mut F, ) -> ControlFlow<()> where F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>, { let interner = DbInterner::new_with(db, Some(trait_.krate(db)), None); for super_trait in elaborate::supertrait_def_ids(interner, trait_.into()).skip(1) { if db.dyn_compatibility_of_trait(super_trait.0).is_some() { cb(DynCompatibilityViolation::HasNonCompatibleSuperTrait(trait_))?; } } dyn_compatibility_of_trait_with_callback(db, trait_, cb) } pub fn dyn_compatibility_of_trait_with_callback( db: &dyn HirDatabase, trait_: TraitId, cb: &mut F, ) -> ControlFlow<()> where F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>, { // Check whether this has a `Sized` bound if generics_require_sized_self(db, trait_.into()) { cb(DynCompatibilityViolation::SizedSelf)?; } // Check if there exist bounds that referencing self if predicates_reference_self(db, trait_) { cb(DynCompatibilityViolation::SelfReferential)?; } if bounds_reference_self(db, trait_) { cb(DynCompatibilityViolation::SelfReferential)?; } // rustc checks for non-lifetime binders here, but we don't support HRTB yet let trait_data = trait_.trait_items(db); for (_, assoc_item) in &trait_data.items { dyn_compatibility_violation_for_assoc_item(db, trait_, *assoc_item, cb)?; } ControlFlow::Continue(()) } pub fn dyn_compatibility_of_trait_query( db: &dyn HirDatabase, trait_: TraitId, ) -> Option { let mut res = None; _ = dyn_compatibility_of_trait_with_callback(db, trait_, &mut |osv| { res = Some(osv); ControlFlow::Break(()) }); res } pub fn generics_require_sized_self(db: &dyn HirDatabase, def: GenericDefId) -> bool { let krate = def.module(db).krate(); let Some(sized) = LangItem::Sized.resolve_trait(db, krate) else { return false; }; let interner = DbInterner::new_with(db, Some(krate), None); let predicates = db.generic_predicates_ns(def); // FIXME: We should use `explicit_predicates_of` here, which hasn't been implemented to // rust-analyzer yet // https://github.com/rust-lang/rust/blob/ddaf12390d3ffb7d5ba74491a48f3cd528e5d777/compiler/rustc_hir_analysis/src/collect/predicates_of.rs#L490 elaborate::elaborate(interner, predicates.iter().copied()).any(|pred| { match pred.kind().skip_binder() { ClauseKind::Trait(trait_pred) => { if sized == trait_pred.def_id().0 && let rustc_type_ir::TyKind::Param(param_ty) = trait_pred.trait_ref.self_ty().kind() && param_ty.index == 0 { true } else { false } } _ => false, } }) } // rustc gathers all the spans that references `Self` for error rendering, // but we don't have good way to render such locations. // So, just return single boolean value for existence of such `Self` reference fn predicates_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool { db.generic_predicates_ns(trait_.into()) .iter() .any(|pred| predicate_references_self(db, trait_, pred, AllowSelfProjection::No)) } // Same as the above, `predicates_reference_self` fn bounds_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool { let trait_data = trait_.trait_items(db); trait_data .items .iter() .filter_map(|(_, it)| match *it { AssocItemId::TypeAliasId(id) => Some(associated_ty_item_bounds(db, id)), _ => None, }) .any(|bounds| { bounds.skip_binder().iter().any(|pred| match pred.skip_binder() { rustc_type_ir::ExistentialPredicate::Trait(it) => it.args.iter().any(|arg| { contains_illegal_self_type_reference(db, trait_, &arg, AllowSelfProjection::Yes) }), rustc_type_ir::ExistentialPredicate::Projection(it) => it.args.iter().any(|arg| { contains_illegal_self_type_reference(db, trait_, &arg, AllowSelfProjection::Yes) }), rustc_type_ir::ExistentialPredicate::AutoTrait(_) => false, }) }) } #[derive(Clone, Copy)] enum AllowSelfProjection { Yes, No, } fn predicate_references_self<'db>( db: &'db dyn HirDatabase, trait_: TraitId, predicate: &Clause<'db>, allow_self_projection: AllowSelfProjection, ) -> bool { match predicate.kind().skip_binder() { ClauseKind::Trait(trait_pred) => trait_pred.trait_ref.args.iter().skip(1).any(|arg| { contains_illegal_self_type_reference(db, trait_, &arg, allow_self_projection) }), ClauseKind::Projection(proj_pred) => { proj_pred.projection_term.args.iter().skip(1).any(|arg| { contains_illegal_self_type_reference(db, trait_, &arg, allow_self_projection) }) } _ => false, } } fn contains_illegal_self_type_reference<'db, T: rustc_type_ir::TypeVisitable>>( db: &'db dyn HirDatabase, trait_: TraitId, t: &T, allow_self_projection: AllowSelfProjection, ) -> bool { struct IllegalSelfTypeVisitor<'db> { db: &'db dyn HirDatabase, trait_: TraitId, super_traits: Option>, allow_self_projection: AllowSelfProjection, } impl<'db> rustc_type_ir::TypeVisitor> for IllegalSelfTypeVisitor<'db> { type Result = ControlFlow<()>; fn visit_ty( &mut self, ty: as rustc_type_ir::Interner>::Ty, ) -> Self::Result { let interner = DbInterner::new_with(self.db, None, None); match ty.kind() { rustc_type_ir::TyKind::Param(param) if param.index == 0 => ControlFlow::Break(()), rustc_type_ir::TyKind::Param(_) => ControlFlow::Continue(()), rustc_type_ir::TyKind::Alias(AliasTyKind::Projection, proj) => match self .allow_self_projection { AllowSelfProjection::Yes => { let trait_ = proj.trait_def_id(DbInterner::new_with(self.db, None, None)); let trait_ = match trait_ { SolverDefId::TraitId(id) => id, _ => unreachable!(), }; if self.super_traits.is_none() { self.super_traits = Some( elaborate::supertrait_def_ids(interner, self.trait_.into()) .map(|super_trait| super_trait.0) .collect(), ) } if self.super_traits.as_ref().is_some_and(|s| s.contains(&trait_)) { ControlFlow::Continue(()) } else { ty.super_visit_with(self) } } AllowSelfProjection::No => ty.super_visit_with(self), }, _ => ty.super_visit_with(self), } } } let mut visitor = IllegalSelfTypeVisitor { db, trait_, super_traits: None, allow_self_projection }; t.visit_with(&mut visitor).is_break() } fn dyn_compatibility_violation_for_assoc_item( db: &dyn HirDatabase, trait_: TraitId, item: AssocItemId, cb: &mut F, ) -> ControlFlow<()> where F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>, { // Any item that has a `Self : Sized` requisite is otherwise // exempt from the regulations. if generics_require_sized_self(db, item.into()) { return ControlFlow::Continue(()); } match item { AssocItemId::ConstId(it) => cb(DynCompatibilityViolation::AssocConst(it)), AssocItemId::FunctionId(it) => { virtual_call_violations_for_method(db, trait_, it, &mut |mvc| { cb(DynCompatibilityViolation::Method(it, mvc)) }) } AssocItemId::TypeAliasId(it) => { let def_map = CrateRootModuleId::from(trait_.krate(db)).def_map(db); if def_map.is_unstable_feature_enabled(&intern::sym::generic_associated_type_extended) { ControlFlow::Continue(()) } else { let generic_params = db.generic_params(item.into()); if !generic_params.is_empty() { cb(DynCompatibilityViolation::GAT(it)) } else { ControlFlow::Continue(()) } } } } } fn virtual_call_violations_for_method( db: &dyn HirDatabase, trait_: TraitId, func: FunctionId, cb: &mut F, ) -> ControlFlow<()> where F: FnMut(MethodViolationCode) -> ControlFlow<()>, { let func_data = db.function_signature(func); if !func_data.has_self_param() { cb(MethodViolationCode::StaticMethod)?; } if func_data.is_async() { cb(MethodViolationCode::AsyncFn)?; } let sig = db.callable_item_signature_ns(func.into()); if sig .skip_binder() .inputs() .iter() .skip(1) .any(|ty| contains_illegal_self_type_reference(db, trait_, &ty, AllowSelfProjection::Yes)) { cb(MethodViolationCode::ReferencesSelfInput)?; } if contains_illegal_self_type_reference( db, trait_, &sig.skip_binder().output(), AllowSelfProjection::Yes, ) { cb(MethodViolationCode::ReferencesSelfOutput)?; } if !func_data.is_async() && let Some(mvc) = contains_illegal_impl_trait_in_trait(db, &sig) { cb(mvc)?; } let generic_params = db.generic_params(func.into()); if generic_params.len_type_or_consts() > 0 { cb(MethodViolationCode::Generic)?; } if func_data.has_self_param() && !receiver_is_dispatchable(db, trait_, func, &sig) { cb(MethodViolationCode::UndispatchableReceiver)?; } let predicates = &*db.generic_predicates_without_parent_ns(func.into()); for pred in predicates { let pred = pred.kind().skip_binder(); if matches!(pred, ClauseKind::TypeOutlives(_)) { continue; } // Allow `impl AutoTrait` predicates if let ClauseKind::Trait(TraitPredicate { trait_ref: pred_trait_ref, polarity: PredicatePolarity::Positive, }) = pred && let trait_data = db.trait_signature(pred_trait_ref.def_id.0) && trait_data.flags.contains(TraitFlags::AUTO) && let rustc_type_ir::TyKind::Param(crate::next_solver::ParamTy { index: 0, .. }) = pred_trait_ref.self_ty().kind() { continue; } if contains_illegal_self_type_reference(db, trait_, &pred, AllowSelfProjection::Yes) { cb(MethodViolationCode::WhereClauseReferencesSelf)?; break; } } ControlFlow::Continue(()) } fn receiver_is_dispatchable<'db>( db: &dyn HirDatabase, trait_: TraitId, func: FunctionId, sig: &crate::next_solver::EarlyBinder< 'db, crate::next_solver::Binder<'db, rustc_type_ir::FnSig>>, >, ) -> bool { let sig = sig.instantiate_identity(); let interner: DbInterner<'_> = DbInterner::new_with(db, Some(trait_.krate(db)), None); let self_param_id = TypeParamId::from_unchecked(TypeOrConstParamId { parent: trait_.into(), local_id: LocalTypeOrConstParamId::from_raw(la_arena::RawIdx::from_u32(0)), }); let self_param_ty = crate::next_solver::Ty::new( interner, rustc_type_ir::TyKind::Param(crate::next_solver::ParamTy { index: 0, id: self_param_id }), ); // `self: Self` can't be dispatched on, but this is already considered dyn-compatible // See rustc's comment on https://github.com/rust-lang/rust/blob/3f121b9461cce02a703a0e7e450568849dfaa074/compiler/rustc_trait_selection/src/traits/object_safety.rs#L433-L437 if sig.inputs().iter().next().is_some_and(|p| p.skip_binder() == self_param_ty) { return true; } let Some(&receiver_ty) = sig.inputs().skip_binder().as_slice().first() else { return false; }; let krate = func.module(db).krate(); let traits = ( LangItem::Unsize.resolve_trait(db, krate), LangItem::DispatchFromDyn.resolve_trait(db, krate), ); let (Some(unsize_did), Some(dispatch_from_dyn_did)) = traits else { return false; }; let meta_sized_did = LangItem::MetaSized.resolve_trait(db, krate); let Some(meta_sized_did) = meta_sized_did else { return false; }; // Type `U` // FIXME: That seems problematic to fake a generic param like that? let unsized_self_ty = crate::next_solver::Ty::new_param(interner, self_param_id, u32::MAX, Symbol::empty()); // `Receiver[Self => U]` let unsized_receiver_ty = receiver_for_self_ty(interner, func, receiver_ty, unsized_self_ty); let param_env = { let generic_predicates = &*db.generic_predicates_ns(func.into()); // Self: Unsize let unsize_predicate = TraitRef::new(interner, unsize_did.into(), [self_param_ty, unsized_self_ty]); // U: Trait let args = GenericArgs::for_item(interner, trait_.into(), |name, index, kind, _| { if index == 0 { unsized_self_ty.into() } else { mk_param(interner, index, name, kind) } }); let trait_predicate = TraitRef::new_from_args(interner, trait_.into(), args); let meta_sized_predicate = TraitRef::new(interner, meta_sized_did.into(), [unsized_self_ty]); ParamEnv { clauses: Clauses::new_from_iter( interner, generic_predicates.iter().copied().chain([ unsize_predicate.upcast(interner), trait_predicate.upcast(interner), meta_sized_predicate.upcast(interner), ]), ), } }; // Receiver: DispatchFromDyn U]> let predicate = TraitRef::new(interner, dispatch_from_dyn_did.into(), [receiver_ty, unsized_receiver_ty]); let goal = crate::next_solver::Goal::new(interner, param_env, predicate); let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); // the receiver is dispatchable iff the obligation holds let res = next_trait_solve_in_ctxt(&infcx, goal); res.map_or(false, |res| matches!(res.1, rustc_type_ir::solve::Certainty::Yes)) } fn receiver_for_self_ty<'db>( interner: DbInterner<'db>, func: FunctionId, receiver_ty: crate::next_solver::Ty<'db>, self_ty: crate::next_solver::Ty<'db>, ) -> crate::next_solver::Ty<'db> { let args = crate::next_solver::GenericArgs::for_item( interner, SolverDefId::FunctionId(func), |name, index, kind, _| { if index == 0 { self_ty.into() } else { mk_param(interner, index, name, kind) } }, ); crate::next_solver::EarlyBinder::bind(receiver_ty).instantiate(interner, args) } fn contains_illegal_impl_trait_in_trait<'db>( db: &'db dyn HirDatabase, sig: &crate::next_solver::EarlyBinder< 'db, crate::next_solver::Binder<'db, rustc_type_ir::FnSig>>, >, ) -> Option { struct OpaqueTypeCollector(FxHashSet); impl<'db> rustc_type_ir::TypeVisitor> for OpaqueTypeCollector { type Result = ControlFlow<()>; fn visit_ty( &mut self, ty: as rustc_type_ir::Interner>::Ty, ) -> Self::Result { if let rustc_type_ir::TyKind::Alias(AliasTyKind::Opaque, op) = ty.kind() { let id = match op.def_id { SolverDefId::InternedOpaqueTyId(id) => id, _ => unreachable!(), }; self.0.insert(id); } ty.super_visit_with(self) } } let ret = sig.skip_binder().output(); let mut visitor = OpaqueTypeCollector(FxHashSet::default()); _ = ret.visit_with(&mut visitor); // Since we haven't implemented RPITIT in proper way like rustc yet, // just check whether `ret` contains RPIT for now for opaque_ty in visitor.0 { let impl_trait_id = db.lookup_intern_impl_trait_id(opaque_ty); if matches!(impl_trait_id, ImplTraitId::ReturnTypeImplTrait(..)) { return Some(MethodViolationCode::ReferencesImplTraitInTrait); } } None } #[cfg(test)] mod tests;