Skip to main content

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