hir_ty/
dyn_compatibility.rs

1//! Compute the dyn-compatibility of a trait
2
3use std::ops::ControlFlow;
4
5use hir_def::{
6    AssocItemId, ConstId, FunctionId, GenericDefId, HasModule, TraitId, TypeAliasId,
7    TypeOrConstParamId, TypeParamId, hir::generics::LocalTypeOrConstParamId,
8    nameres::crate_def_map, signatures::TraitFlags,
9};
10use rustc_hash::FxHashSet;
11use rustc_type_ir::{
12    AliasTyKind, ClauseKind, PredicatePolarity, TypeSuperVisitable as _, TypeVisitable as _,
13    Upcast, elaborate,
14    inherent::{IntoKind, SliceLike},
15};
16use smallvec::SmallVec;
17
18use crate::{
19    ImplTraitId,
20    db::{HirDatabase, InternedOpaqueTyId},
21    lower::{GenericPredicates, associated_ty_item_bounds},
22    next_solver::{
23        Binder, Clause, Clauses, DbInterner, EarlyBinder, GenericArgs, Goal, ParamEnv, ParamTy,
24        SolverDefId, TraitPredicate, TraitRef, Ty, TypingMode, infer::DbInternerInferExt, mk_param,
25    },
26    traits::next_trait_solve_in_ctxt,
27};
28
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub enum DynCompatibilityViolation {
31    SizedSelf,
32    SelfReferential,
33    Method(FunctionId, MethodViolationCode),
34    AssocConst(ConstId),
35    GAT(TypeAliasId),
36    // This doesn't exist in rustc, but added for better visualization
37    HasNonCompatibleSuperTrait(TraitId),
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Hash)]
41pub enum MethodViolationCode {
42    StaticMethod,
43    ReferencesSelfInput,
44    ReferencesSelfOutput,
45    ReferencesImplTraitInTrait,
46    AsyncFn,
47    WhereClauseReferencesSelf,
48    Generic,
49    UndispatchableReceiver,
50}
51
52pub fn dyn_compatibility(
53    db: &dyn HirDatabase,
54    trait_: TraitId,
55) -> Option<DynCompatibilityViolation> {
56    let interner = DbInterner::new_no_crate(db);
57    for super_trait in elaborate::supertrait_def_ids(interner, trait_.into()) {
58        if let Some(v) = db.dyn_compatibility_of_trait(super_trait.0) {
59            return if super_trait.0 == trait_ {
60                Some(v)
61            } else {
62                Some(DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait.0))
63            };
64        }
65    }
66
67    None
68}
69
70pub fn dyn_compatibility_with_callback<F>(
71    db: &dyn HirDatabase,
72    trait_: TraitId,
73    cb: &mut F,
74) -> ControlFlow<()>
75where
76    F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>,
77{
78    let interner = DbInterner::new_no_crate(db);
79    for super_trait in elaborate::supertrait_def_ids(interner, trait_.into()).skip(1) {
80        if db.dyn_compatibility_of_trait(super_trait.0).is_some() {
81            cb(DynCompatibilityViolation::HasNonCompatibleSuperTrait(trait_))?;
82        }
83    }
84
85    dyn_compatibility_of_trait_with_callback(db, trait_, cb)
86}
87
88pub fn dyn_compatibility_of_trait_with_callback<F>(
89    db: &dyn HirDatabase,
90    trait_: TraitId,
91    cb: &mut F,
92) -> ControlFlow<()>
93where
94    F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>,
95{
96    // Check whether this has a `Sized` bound
97    if generics_require_sized_self(db, trait_.into()) {
98        cb(DynCompatibilityViolation::SizedSelf)?;
99    }
100
101    // Check if there exist bounds that referencing self
102    if predicates_reference_self(db, trait_) {
103        cb(DynCompatibilityViolation::SelfReferential)?;
104    }
105    if bounds_reference_self(db, trait_) {
106        cb(DynCompatibilityViolation::SelfReferential)?;
107    }
108
109    // rustc checks for non-lifetime binders here, but we don't support HRTB yet
110
111    let trait_data = trait_.trait_items(db);
112    for (_, assoc_item) in &trait_data.items {
113        dyn_compatibility_violation_for_assoc_item(db, trait_, *assoc_item, cb)?;
114    }
115
116    ControlFlow::Continue(())
117}
118
119pub fn dyn_compatibility_of_trait_query(
120    db: &dyn HirDatabase,
121    trait_: TraitId,
122) -> Option<DynCompatibilityViolation> {
123    let mut res = None;
124    _ = dyn_compatibility_of_trait_with_callback(db, trait_, &mut |osv| {
125        res = Some(osv);
126        ControlFlow::Break(())
127    });
128
129    res
130}
131
132pub fn generics_require_sized_self(db: &dyn HirDatabase, def: GenericDefId) -> bool {
133    let krate = def.module(db).krate(db);
134    let interner = DbInterner::new_with(db, krate);
135    let Some(sized) = interner.lang_items().Sized else {
136        return false;
137    };
138
139    let predicates = GenericPredicates::query_explicit(db, def);
140    // FIXME: We should use `explicit_predicates_of` here, which hasn't been implemented to
141    // rust-analyzer yet
142    // https://github.com/rust-lang/rust/blob/ddaf12390d3ffb7d5ba74491a48f3cd528e5d777/compiler/rustc_hir_analysis/src/collect/predicates_of.rs#L490
143    elaborate::elaborate(interner, predicates.iter_identity_copied()).any(|pred| {
144        match pred.kind().skip_binder() {
145            ClauseKind::Trait(trait_pred) => {
146                if sized == trait_pred.def_id().0
147                    && let rustc_type_ir::TyKind::Param(param_ty) =
148                        trait_pred.trait_ref.self_ty().kind()
149                    && param_ty.index == 0
150                {
151                    true
152                } else {
153                    false
154                }
155            }
156            _ => false,
157        }
158    })
159}
160
161// rustc gathers all the spans that references `Self` for error rendering,
162// but we don't have good way to render such locations.
163// So, just return single boolean value for existence of such `Self` reference
164fn predicates_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool {
165    GenericPredicates::query_explicit(db, trait_.into())
166        .iter_identity_copied()
167        .any(|pred| predicate_references_self(db, trait_, pred, AllowSelfProjection::No))
168}
169
170// Same as the above, `predicates_reference_self`
171fn bounds_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool {
172    let trait_data = trait_.trait_items(db);
173    trait_data
174        .items
175        .iter()
176        .filter_map(|(_, it)| match *it {
177            AssocItemId::TypeAliasId(id) => Some(associated_ty_item_bounds(db, id)),
178            _ => None,
179        })
180        .any(|bounds| {
181            bounds.skip_binder().iter().any(|pred| match pred.skip_binder() {
182                rustc_type_ir::ExistentialPredicate::Trait(it) => it.args.iter().any(|arg| {
183                    contains_illegal_self_type_reference(db, trait_, &arg, AllowSelfProjection::Yes)
184                }),
185                rustc_type_ir::ExistentialPredicate::Projection(it) => it.args.iter().any(|arg| {
186                    contains_illegal_self_type_reference(db, trait_, &arg, AllowSelfProjection::Yes)
187                }),
188                rustc_type_ir::ExistentialPredicate::AutoTrait(_) => false,
189            })
190        })
191}
192
193#[derive(Clone, Copy)]
194enum AllowSelfProjection {
195    Yes,
196    No,
197}
198
199fn predicate_references_self<'db>(
200    db: &'db dyn HirDatabase,
201    trait_: TraitId,
202    predicate: Clause<'db>,
203    allow_self_projection: AllowSelfProjection,
204) -> bool {
205    match predicate.kind().skip_binder() {
206        ClauseKind::Trait(trait_pred) => trait_pred.trait_ref.args.iter().skip(1).any(|arg| {
207            contains_illegal_self_type_reference(db, trait_, &arg, allow_self_projection)
208        }),
209        ClauseKind::Projection(proj_pred) => {
210            proj_pred.projection_term.args.iter().skip(1).any(|arg| {
211                contains_illegal_self_type_reference(db, trait_, &arg, allow_self_projection)
212            })
213        }
214        _ => false,
215    }
216}
217
218fn contains_illegal_self_type_reference<'db, T: rustc_type_ir::TypeVisitable<DbInterner<'db>>>(
219    db: &'db dyn HirDatabase,
220    trait_: TraitId,
221    t: &T,
222    allow_self_projection: AllowSelfProjection,
223) -> bool {
224    struct IllegalSelfTypeVisitor<'db> {
225        db: &'db dyn HirDatabase,
226        trait_: TraitId,
227        super_traits: Option<SmallVec<[TraitId; 4]>>,
228        allow_self_projection: AllowSelfProjection,
229    }
230    impl<'db> rustc_type_ir::TypeVisitor<DbInterner<'db>> for IllegalSelfTypeVisitor<'db> {
231        type Result = ControlFlow<()>;
232
233        fn visit_ty(
234            &mut self,
235            ty: <DbInterner<'db> as rustc_type_ir::Interner>::Ty,
236        ) -> Self::Result {
237            let interner = DbInterner::new_no_crate(self.db);
238            match ty.kind() {
239                rustc_type_ir::TyKind::Param(param) if param.index == 0 => ControlFlow::Break(()),
240                rustc_type_ir::TyKind::Param(_) => ControlFlow::Continue(()),
241                rustc_type_ir::TyKind::Alias(AliasTyKind::Projection, proj) => {
242                    match self.allow_self_projection {
243                        AllowSelfProjection::Yes => {
244                            let trait_ = proj.trait_def_id(interner);
245                            let trait_ = match trait_ {
246                                SolverDefId::TraitId(id) => id,
247                                _ => unreachable!(),
248                            };
249                            if self.super_traits.is_none() {
250                                self.super_traits = Some(
251                                    elaborate::supertrait_def_ids(interner, self.trait_.into())
252                                        .map(|super_trait| super_trait.0)
253                                        .collect(),
254                                )
255                            }
256                            if self.super_traits.as_ref().is_some_and(|s| s.contains(&trait_)) {
257                                ControlFlow::Continue(())
258                            } else {
259                                ty.super_visit_with(self)
260                            }
261                        }
262                        AllowSelfProjection::No => ty.super_visit_with(self),
263                    }
264                }
265                _ => ty.super_visit_with(self),
266            }
267        }
268    }
269
270    let mut visitor =
271        IllegalSelfTypeVisitor { db, trait_, super_traits: None, allow_self_projection };
272    t.visit_with(&mut visitor).is_break()
273}
274
275fn dyn_compatibility_violation_for_assoc_item<F>(
276    db: &dyn HirDatabase,
277    trait_: TraitId,
278    item: AssocItemId,
279    cb: &mut F,
280) -> ControlFlow<()>
281where
282    F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>,
283{
284    // Any item that has a `Self : Sized` requisite is otherwise
285    // exempt from the regulations.
286    if generics_require_sized_self(db, item.into()) {
287        return ControlFlow::Continue(());
288    }
289
290    match item {
291        AssocItemId::ConstId(it) => cb(DynCompatibilityViolation::AssocConst(it)),
292        AssocItemId::FunctionId(it) => {
293            virtual_call_violations_for_method(db, trait_, it, &mut |mvc| {
294                cb(DynCompatibilityViolation::Method(it, mvc))
295            })
296        }
297        AssocItemId::TypeAliasId(it) => {
298            let def_map = crate_def_map(db, trait_.krate(db));
299            if def_map.is_unstable_feature_enabled(&intern::sym::generic_associated_type_extended) {
300                ControlFlow::Continue(())
301            } else {
302                let generic_params = db.generic_params(item.into());
303                if !generic_params.is_empty() {
304                    cb(DynCompatibilityViolation::GAT(it))
305                } else {
306                    ControlFlow::Continue(())
307                }
308            }
309        }
310    }
311}
312
313fn virtual_call_violations_for_method<F>(
314    db: &dyn HirDatabase,
315    trait_: TraitId,
316    func: FunctionId,
317    cb: &mut F,
318) -> ControlFlow<()>
319where
320    F: FnMut(MethodViolationCode) -> ControlFlow<()>,
321{
322    let func_data = db.function_signature(func);
323    if !func_data.has_self_param() {
324        cb(MethodViolationCode::StaticMethod)?;
325    }
326
327    if func_data.is_async() {
328        cb(MethodViolationCode::AsyncFn)?;
329    }
330
331    let sig = db.callable_item_signature(func.into());
332    if sig
333        .skip_binder()
334        .inputs()
335        .iter()
336        .skip(1)
337        .any(|ty| contains_illegal_self_type_reference(db, trait_, &ty, AllowSelfProjection::Yes))
338    {
339        cb(MethodViolationCode::ReferencesSelfInput)?;
340    }
341
342    if contains_illegal_self_type_reference(
343        db,
344        trait_,
345        &sig.skip_binder().output(),
346        AllowSelfProjection::Yes,
347    ) {
348        cb(MethodViolationCode::ReferencesSelfOutput)?;
349    }
350
351    if !func_data.is_async()
352        && let Some(mvc) = contains_illegal_impl_trait_in_trait(db, &sig)
353    {
354        cb(mvc)?;
355    }
356
357    let generic_params = db.generic_params(func.into());
358    if generic_params.len_type_or_consts() > 0 {
359        cb(MethodViolationCode::Generic)?;
360    }
361
362    if func_data.has_self_param() && !receiver_is_dispatchable(db, trait_, func, &sig) {
363        cb(MethodViolationCode::UndispatchableReceiver)?;
364    }
365
366    let predicates = GenericPredicates::query_own(db, func.into());
367    for pred in predicates.iter_identity_copied() {
368        let pred = pred.kind().skip_binder();
369
370        if matches!(pred, ClauseKind::TypeOutlives(_)) {
371            continue;
372        }
373
374        // Allow `impl AutoTrait` predicates
375        if let ClauseKind::Trait(TraitPredicate {
376            trait_ref: pred_trait_ref,
377            polarity: PredicatePolarity::Positive,
378        }) = pred
379            && let trait_data = db.trait_signature(pred_trait_ref.def_id.0)
380            && trait_data.flags.contains(TraitFlags::AUTO)
381            && let rustc_type_ir::TyKind::Param(ParamTy { index: 0, .. }) =
382                pred_trait_ref.self_ty().kind()
383        {
384            continue;
385        }
386
387        if contains_illegal_self_type_reference(db, trait_, &pred, AllowSelfProjection::Yes) {
388            cb(MethodViolationCode::WhereClauseReferencesSelf)?;
389            break;
390        }
391    }
392
393    ControlFlow::Continue(())
394}
395
396fn receiver_is_dispatchable<'db>(
397    db: &dyn HirDatabase,
398    trait_: TraitId,
399    func: FunctionId,
400    sig: &EarlyBinder<'db, Binder<'db, rustc_type_ir::FnSig<DbInterner<'db>>>>,
401) -> bool {
402    let sig = sig.instantiate_identity();
403
404    let module = trait_.module(db);
405    let interner = DbInterner::new_with(db, module.krate(db));
406    let self_param_id = TypeParamId::from_unchecked(TypeOrConstParamId {
407        parent: trait_.into(),
408        local_id: LocalTypeOrConstParamId::from_raw(la_arena::RawIdx::from_u32(0)),
409    });
410    let self_param_ty =
411        Ty::new(interner, rustc_type_ir::TyKind::Param(ParamTy { index: 0, id: self_param_id }));
412
413    // `self: Self` can't be dispatched on, but this is already considered dyn-compatible
414    // See rustc's comment on https://github.com/rust-lang/rust/blob/3f121b9461cce02a703a0e7e450568849dfaa074/compiler/rustc_trait_selection/src/traits/object_safety.rs#L433-L437
415    if sig.inputs().iter().next().is_some_and(|p| p.skip_binder() == self_param_ty) {
416        return true;
417    }
418
419    let Some(&receiver_ty) = sig.inputs().skip_binder().as_slice().first() else {
420        return false;
421    };
422
423    let lang_items = interner.lang_items();
424    let traits = (lang_items.Unsize, lang_items.DispatchFromDyn);
425    let (Some(unsize_did), Some(dispatch_from_dyn_did)) = traits else {
426        return false;
427    };
428
429    let meta_sized_did = lang_items.MetaSized;
430
431    // TODO: This is for supporting dyn compatibility for toolchains doesn't contain `MetaSized`
432    // trait. Uncomment and short circuit here once `MINIMUM_SUPPORTED_TOOLCHAIN_VERSION`
433    // become > 1.88.0
434    //
435    // let Some(meta_sized_did) = meta_sized_did else {
436    //     return false;
437    // };
438
439    // Type `U`
440    // FIXME: That seems problematic to fake a generic param like that?
441    let unsized_self_ty = Ty::new_param(interner, self_param_id, u32::MAX);
442    // `Receiver[Self => U]`
443    let unsized_receiver_ty = receiver_for_self_ty(interner, func, receiver_ty, unsized_self_ty);
444
445    let param_env = {
446        let generic_predicates = GenericPredicates::query_all(db, func.into());
447
448        // Self: Unsize<U>
449        let unsize_predicate =
450            TraitRef::new(interner, unsize_did.into(), [self_param_ty, unsized_self_ty]);
451
452        // U: Trait<Arg1, ..., ArgN>
453        let args = GenericArgs::for_item(interner, trait_.into(), |index, kind, _| {
454            if index == 0 { unsized_self_ty.into() } else { mk_param(interner, index, kind) }
455        });
456        let trait_predicate = TraitRef::new_from_args(interner, trait_.into(), args);
457
458        let meta_sized_predicate = meta_sized_did
459            .map(|did| TraitRef::new(interner, did.into(), [unsized_self_ty]).upcast(interner));
460
461        ParamEnv {
462            clauses: Clauses::new_from_iter(
463                interner,
464                generic_predicates
465                    .iter_identity_copied()
466                    .chain([unsize_predicate.upcast(interner), trait_predicate.upcast(interner)])
467                    .chain(meta_sized_predicate),
468            ),
469        }
470    };
471
472    // Receiver: DispatchFromDyn<Receiver[Self => U]>
473    let predicate =
474        TraitRef::new(interner, dispatch_from_dyn_did.into(), [receiver_ty, unsized_receiver_ty]);
475    let goal = Goal::new(interner, param_env, predicate);
476
477    let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
478    // the receiver is dispatchable iff the obligation holds
479    let res = next_trait_solve_in_ctxt(&infcx, goal);
480    res.map_or(false, |res| matches!(res.1, rustc_type_ir::solve::Certainty::Yes))
481}
482
483fn receiver_for_self_ty<'db>(
484    interner: DbInterner<'db>,
485    func: FunctionId,
486    receiver_ty: Ty<'db>,
487    self_ty: Ty<'db>,
488) -> Ty<'db> {
489    let args = GenericArgs::for_item(interner, SolverDefId::FunctionId(func), |index, kind, _| {
490        if index == 0 { self_ty.into() } else { mk_param(interner, index, kind) }
491    });
492
493    EarlyBinder::bind(receiver_ty).instantiate(interner, args)
494}
495
496fn contains_illegal_impl_trait_in_trait<'db>(
497    db: &'db dyn HirDatabase,
498    sig: &EarlyBinder<'db, Binder<'db, rustc_type_ir::FnSig<DbInterner<'db>>>>,
499) -> Option<MethodViolationCode> {
500    struct OpaqueTypeCollector(FxHashSet<InternedOpaqueTyId>);
501
502    impl<'db> rustc_type_ir::TypeVisitor<DbInterner<'db>> for OpaqueTypeCollector {
503        type Result = ControlFlow<()>;
504
505        fn visit_ty(
506            &mut self,
507            ty: <DbInterner<'db> as rustc_type_ir::Interner>::Ty,
508        ) -> Self::Result {
509            if let rustc_type_ir::TyKind::Alias(AliasTyKind::Opaque, op) = ty.kind() {
510                let id = match op.def_id {
511                    SolverDefId::InternedOpaqueTyId(id) => id,
512                    _ => unreachable!(),
513                };
514                self.0.insert(id);
515            }
516            ty.super_visit_with(self)
517        }
518    }
519
520    let ret = sig.skip_binder().output();
521    let mut visitor = OpaqueTypeCollector(FxHashSet::default());
522    _ = ret.visit_with(&mut visitor);
523
524    // Since we haven't implemented RPITIT in proper way like rustc yet,
525    // just check whether `ret` contains RPIT for now
526    for opaque_ty in visitor.0 {
527        let impl_trait_id = db.lookup_intern_impl_trait_id(opaque_ty);
528        if matches!(impl_trait_id, ImplTraitId::ReturnTypeImplTrait(..)) {
529            return Some(MethodViolationCode::ReferencesImplTraitInTrait);
530        }
531    }
532
533    None
534}
535
536#[cfg(test)]
537mod tests;