1use 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 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 if generics_require_sized_self(db, trait_.into()) {
103 cb(DynCompatibilityViolation::SizedSelf)?;
104 }
105
106 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 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 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
168fn 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
177fn 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 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 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 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 let unsized_self_ty = Ty::new_param(interner, self_param_id, u32::MAX);
442 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 let unsize_predicate =
450 TraitRef::new(interner, unsize_did.into(), [self_param_ty, unsized_self_ty]);
451
452 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 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 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 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;