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,
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, Copy, 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, Copy, 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(returns(copy))]
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
139#[salsa::tracked(returns(copy))]
140pub fn generics_require_sized_self(db: &dyn HirDatabase, def_id: GenericDefId) -> bool {
141    let krate = def_id.krate(db);
142    let interner = DbInterner::new_with(db, krate);
143    let Some(sized_def_id) = interner.lang_items().Sized else {
144        return false; /* No Sized trait, can't require it! */
145    };
146
147    // Search for a clause like `Self: Sized` amongst the trait bounds.
148    let clauses = GenericPredicates::query_own_explicit(db, def_id)
149        .iter_identity()
150        .map(Unnormalized::skip_norm_wip);
151    elaborate::elaborate(interner, clauses).any(|clause| match clause.kind().skip_binder() {
152        ClauseKind::Trait(ref trait_pred) => {
153            trait_pred.def_id().0 == sized_def_id && trait_pred.self_ty().is_param(0)
154        }
155        ClauseKind::RegionOutlives(_)
156        | ClauseKind::TypeOutlives(_)
157        | ClauseKind::Projection(_)
158        | ClauseKind::ConstArgHasType(_, _)
159        | ClauseKind::WellFormed(_)
160        | ClauseKind::ConstEvaluatable(_)
161        | ClauseKind::UnstableFeature(_)
162        | ClauseKind::HostEffect(..) => false,
163    })
164}
165
166// rustc gathers all the spans that references `Self` for error rendering,
167// but we don't have good way to render such locations.
168// So, just return single boolean value for existence of such `Self` reference
169fn predicates_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool {
170    GenericPredicates::query_explicit(db, trait_.into()).iter_identity().any(|pred| {
171        predicate_references_self(db, trait_, pred.skip_norm_wip(), AllowSelfProjections::No)
172    })
173}
174
175// Same as the above, `predicates_reference_self`
176fn bounds_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool {
177    let trait_data = trait_.trait_items(db);
178    trait_data
179        .items
180        .iter()
181        .filter_map(|(_, it)| match *it {
182            AssocItemId::TypeAliasId(id) => Some(id),
183            _ => None,
184        })
185        .filter(|&assoc_type| !generics_require_sized_self(db, assoc_type.into()))
186        .flat_map(|assoc_type| {
187            db.type_alias_bounds(assoc_type).iter_identity_copied().map(Unnormalized::skip_norm_wip)
188        })
189        .any(|clause| {
190            // Item bounds *can* have self projections, since they never get
191            // their self type erased.
192            predicate_references_self(db, trait_, clause, AllowSelfProjections::Yes)
193        })
194}
195
196#[derive(Clone, Copy)]
197enum AllowSelfProjections {
198    Yes,
199    No,
200}
201
202fn predicate_references_self<'db>(
203    db: &'db dyn HirDatabase,
204    trait_: TraitId,
205    predicate: Clause<'db>,
206    allow_self_projection: AllowSelfProjections,
207) -> bool {
208    match predicate.kind().skip_binder() {
209        ClauseKind::Trait(trait_pred) => trait_pred.trait_ref.args.iter().skip(1).any(|arg| {
210            contains_illegal_self_type_reference(db, trait_, &arg, allow_self_projection)
211        }),
212        ClauseKind::Projection(proj_pred) => {
213            proj_pred.projection_term.args.iter().skip(1).any(|arg| {
214                contains_illegal_self_type_reference(db, trait_, &arg, allow_self_projection)
215            })
216        }
217        _ => false,
218    }
219}
220
221fn contains_illegal_self_type_reference<'db, T: rustc_type_ir::TypeVisitable<DbInterner<'db>>>(
222    db: &'db dyn HirDatabase,
223    trait_: TraitId,
224    t: &T,
225    allow_self_projection: AllowSelfProjections,
226) -> bool {
227    struct IllegalSelfTypeVisitor<'db> {
228        db: &'db dyn HirDatabase,
229        trait_: TraitId,
230        super_traits: Option<SmallVec<[TraitId; 4]>>,
231        allow_self_projection: AllowSelfProjections,
232    }
233    impl<'db> rustc_type_ir::TypeVisitor<DbInterner<'db>> for IllegalSelfTypeVisitor<'db> {
234        type Result = ControlFlow<()>;
235
236        fn visit_ty(
237            &mut self,
238            ty: <DbInterner<'db> as rustc_type_ir::Interner>::Ty,
239        ) -> Self::Result {
240            let interner = DbInterner::new_no_crate(self.db);
241            match ty.kind() {
242                rustc_type_ir::TyKind::Param(param) if param.index == 0 => ControlFlow::Break(()),
243                rustc_type_ir::TyKind::Param(_) => ControlFlow::Continue(()),
244                rustc_type_ir::TyKind::Alias(
245                    proj @ AliasTy { kind: AliasTyKind::Projection { .. }, .. },
246                ) => match self.allow_self_projection {
247                    AllowSelfProjections::Yes => {
248                        let trait_ = proj.trait_def_id(interner).0;
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                    AllowSelfProjections::No => ty.super_visit_with(self),
263                },
264                _ => ty.super_visit_with(self),
265            }
266        }
267    }
268
269    let mut visitor =
270        IllegalSelfTypeVisitor { db, trait_, super_traits: None, allow_self_projection };
271    t.visit_with(&mut visitor).is_break()
272}
273
274fn dyn_compatibility_violation_for_assoc_item<'db, F>(
275    db: &'db dyn HirDatabase,
276    features: &mut Option<&'db UnstableFeatures>,
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            if features
299                .get_or_insert_with(|| UnstableFeatures::query(db, trait_.krate(db)))
300                .generic_associated_type_extended
301            {
302                ControlFlow::Continue(())
303            } else {
304                let generic_params = GenericParams::of(db, item.into());
305                if !generic_params.is_empty() {
306                    cb(DynCompatibilityViolation::GAT(it))
307                } else {
308                    ControlFlow::Continue(())
309                }
310            }
311        }
312    }
313}
314
315fn virtual_call_violations_for_method<F>(
316    db: &dyn HirDatabase,
317    trait_: TraitId,
318    func: FunctionId,
319    cb: &mut F,
320) -> ControlFlow<()>
321where
322    F: FnMut(MethodViolationCode) -> ControlFlow<()>,
323{
324    let func_data = FunctionSignature::of(db, func);
325    if !func_data.has_self_param() {
326        cb(MethodViolationCode::StaticMethod)?;
327    }
328
329    if func_data.is_async() {
330        cb(MethodViolationCode::AsyncFn)?;
331    }
332
333    let sig = db.callable_item_signature(func.into());
334    if sig.skip_binder().inputs().iter().skip(1).any(|ty| {
335        contains_illegal_self_type_reference(
336            db,
337            trait_,
338            ty.skip_binder(),
339            AllowSelfProjections::Yes,
340        )
341    }) {
342        cb(MethodViolationCode::ReferencesSelfInput)?;
343    }
344
345    if contains_illegal_self_type_reference(
346        db,
347        trait_,
348        &sig.skip_binder().output(),
349        AllowSelfProjections::Yes,
350    ) {
351        cb(MethodViolationCode::ReferencesSelfOutput)?;
352    }
353
354    if !func_data.is_async()
355        && let Some(mvc) = contains_illegal_impl_trait_in_trait(db, &sig)
356    {
357        cb(mvc)?;
358    }
359
360    let generic_params = GenericParams::of(db, func.into());
361    if generic_params.len_type_or_consts() > 0 {
362        cb(MethodViolationCode::Generic)?;
363    }
364
365    if func_data.has_self_param() && !receiver_is_dispatchable(db, trait_, func, &sig) {
366        cb(MethodViolationCode::UndispatchableReceiver)?;
367    }
368
369    let predicates = GenericPredicates::query_own_explicit(db, func.into());
370    for pred in predicates.iter_identity() {
371        let pred = pred.kind().skip_binder();
372
373        if matches!(pred, ClauseKind::TypeOutlives(_)) {
374            continue;
375        }
376
377        // Allow `impl AutoTrait` predicates
378        if let ClauseKind::Trait(TraitPredicate {
379            trait_ref: pred_trait_ref,
380            polarity: PredicatePolarity::Positive,
381        }) = pred
382            && let trait_data = TraitSignature::of(db, pred_trait_ref.def_id.0)
383            && trait_data.flags.contains(TraitFlags::AUTO)
384            && let rustc_type_ir::TyKind::Param(ParamTy { index: 0, .. }) =
385                pred_trait_ref.self_ty().kind()
386        {
387            continue;
388        }
389
390        if contains_illegal_self_type_reference(db, trait_, &pred, AllowSelfProjections::Yes) {
391            cb(MethodViolationCode::WhereClauseReferencesSelf)?;
392            break;
393        }
394    }
395
396    ControlFlow::Continue(())
397}
398
399fn receiver_is_dispatchable<'db>(
400    db: &'db dyn HirDatabase,
401    trait_: TraitId,
402    func: FunctionId,
403    sig: &EarlyBinder<'db, Binder<'db, rustc_type_ir::FnSig<DbInterner<'db>>>>,
404) -> bool {
405    let sig = sig.instantiate_identity().skip_norm_wip();
406
407    let module = trait_.module(db);
408    let interner = DbInterner::new_with(db, module.krate(db));
409    let self_param_id = TypeParamId::from_unchecked(TypeOrConstParamId {
410        parent: trait_.into(),
411        local_id: LocalTypeOrConstParamId::from_raw(la_arena::RawIdx::from_u32(0)),
412    });
413    let self_param_ty =
414        Ty::new(interner, rustc_type_ir::TyKind::Param(ParamTy { index: 0, id: self_param_id }));
415
416    // `self: Self` can't be dispatched on, but this is already considered dyn-compatible
417    // See rustc's comment on https://github.com/rust-lang/rust/blob/3f121b9461cce02a703a0e7e450568849dfaa074/compiler/rustc_trait_selection/src/traits/object_safety.rs#L433-L437
418    if sig.inputs().iter().next().is_some_and(|p| *p.skip_binder() == self_param_ty) {
419        return true;
420    }
421
422    let receiver_ty = interner.liberate_late_bound_regions(func.into(), sig.input(0));
423
424    let lang_items = interner.lang_items();
425    let traits = (lang_items.Unsize, lang_items.DispatchFromDyn);
426    let (Some(unsize_did), Some(dispatch_from_dyn_did)) = traits else {
427        return false;
428    };
429
430    let Some(meta_sized_did) = lang_items.MetaSized else {
431        return false;
432    };
433
434    // Type `U`
435    // FIXME: That seems problematic to fake a generic param like that?
436    let unsized_self_ty = Ty::new_param(interner, self_param_id, u32::MAX);
437    // `Receiver[Self => U]`
438    let unsized_receiver_ty = receiver_for_self_ty(interner, func, receiver_ty, unsized_self_ty);
439
440    let param_env = {
441        let generic_predicates = GenericPredicates::query_all(db, func.into());
442
443        // Self: Unsize<U>
444        let unsize_predicate =
445            TraitRef::new(interner, unsize_did.into(), [self_param_ty, unsized_self_ty]);
446
447        // U: Trait<Arg1, ..., ArgN>
448        let args = GenericArgs::for_item(interner, trait_.into(), |index, kind, _, _| {
449            if index == 0 { unsized_self_ty.into() } else { mk_param(interner, index, kind) }
450        });
451        let trait_predicate = TraitRef::new_from_args(interner, trait_.into(), args);
452
453        let meta_sized_predicate =
454            TraitRef::new(interner, meta_sized_did.into(), [unsized_self_ty]).upcast(interner);
455
456        ParamEnv {
457            clauses: Clauses::new_from_iter(
458                generic_predicates
459                    .iter_identity()
460                    .map(Unnormalized::skip_norm_wip)
461                    .chain([unsize_predicate.upcast(interner), trait_predicate.upcast(interner)])
462                    .chain(std::iter::once(meta_sized_predicate)),
463            ),
464        }
465    };
466
467    // Receiver: DispatchFromDyn<Receiver[Self => U]>
468    let predicate =
469        TraitRef::new(interner, dispatch_from_dyn_did.into(), [receiver_ty, unsized_receiver_ty]);
470    let obligation = Obligation::new(interner, ObligationCause::dummy(), param_env, predicate);
471
472    let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
473    // the receiver is dispatchable iff the obligation holds
474    infcx.predicate_must_hold_modulo_regions(&obligation)
475}
476
477fn receiver_for_self_ty<'db>(
478    interner: DbInterner<'db>,
479    func: FunctionId,
480    receiver_ty: Ty<'db>,
481    self_ty: Ty<'db>,
482) -> Ty<'db> {
483    let args =
484        GenericArgs::for_item(interner, SolverDefId::FunctionId(func), |index, kind, _, _| {
485            if index == 0 { self_ty.into() } else { mk_param(interner, index, kind) }
486        });
487
488    EarlyBinder::bind(receiver_ty).instantiate(interner, args).skip_norm_wip()
489}
490
491fn contains_illegal_impl_trait_in_trait<'db>(
492    db: &'db dyn HirDatabase,
493    sig: &EarlyBinder<'db, Binder<'db, rustc_type_ir::FnSig<DbInterner<'db>>>>,
494) -> Option<MethodViolationCode> {
495    struct OpaqueTypeCollector<'db>(FxHashSet<InternedOpaqueTyId<'db>>);
496
497    impl<'db> rustc_type_ir::TypeVisitor<DbInterner<'db>> for OpaqueTypeCollector<'db> {
498        type Result = ControlFlow<()>;
499
500        fn visit_ty(
501            &mut self,
502            ty: <DbInterner<'db> as rustc_type_ir::Interner>::Ty,
503        ) -> Self::Result {
504            if let rustc_type_ir::TyKind::Alias(AliasTy {
505                kind: AliasTyKind::Opaque { def_id },
506                ..
507            }) = ty.kind()
508            {
509                self.0.insert(def_id.0);
510            }
511            ty.super_visit_with(self)
512        }
513    }
514
515    let ret = sig.skip_binder().output();
516    let mut visitor = OpaqueTypeCollector(FxHashSet::default());
517    _ = ret.visit_with(&mut visitor);
518
519    // Since we haven't implemented RPITIT in proper way like rustc yet,
520    // just check whether `ret` contains RPIT for now
521    for opaque_ty in visitor.0 {
522        let impl_trait_id = opaque_ty.loc(db);
523        if matches!(impl_trait_id, ImplTraitId::ReturnTypeImplTrait(..)) {
524            return Some(MethodViolationCode::ReferencesImplTraitInTrait);
525        }
526    }
527
528    None
529}