1use 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 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 if generics_require_sized_self(db, trait_.into()) {
98 cb(DynCompatibilityViolation::SizedSelf)?;
99 }
100
101 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 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 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
161fn 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
170fn 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 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 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 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 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_copied()
466 .chain([unsize_predicate.upcast(interner), trait_predicate.upcast(interner)])
467 .chain(meta_sized_predicate),
468 ),
469 }
470 };
471
472 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 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 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;