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,
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 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 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(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; };
146
147 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
166fn 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
175fn 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 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 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 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 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 let unsized_self_ty = Ty::new_param(interner, self_param_id, u32::MAX);
437 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 let unsize_predicate =
445 TraitRef::new(interner, unsize_did.into(), [self_param_ty, unsized_self_ty]);
446
447 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 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 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 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}