use std::ops::ControlFlow; use hir_def::db::DefDatabase; use rustc_hash::{FxHashMap, FxHashSet}; use syntax::ToSmolStr; use test_fixture::WithFixture; use crate::{dyn_compatibility::dyn_compatibility_with_callback, test_db::TestDB}; use super::{ DynCompatibilityViolation, MethodViolationCode::{self, *}, }; use DynCompatibilityViolationKind::*; #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum DynCompatibilityViolationKind { SizedSelf, SelfReferential, Method(MethodViolationCode), AssocConst, GAT, HasNonCompatibleSuperTrait, } fn check_dyn_compatibility<'a>( #[rust_analyzer::rust_fixture] ra_fixture: &str, expected: impl IntoIterator)>, ) { let mut expected: FxHashMap<_, _> = expected.into_iter().map(|(id, osvs)| (id, FxHashSet::from_iter(osvs))).collect(); let (db, file_ids) = TestDB::with_many_files(ra_fixture); for (trait_id, name) in file_ids.into_iter().flat_map(|file_id| { let module_id = db.module_for_file(file_id.file_id(&db)); let def_map = module_id.def_map(&db); let scope = &def_map[module_id].scope; scope .declarations() .filter_map(|def| { if let hir_def::ModuleDefId::TraitId(trait_id) = def { let name = db .trait_signature(trait_id) .name .display_no_db(file_id.edition(&db)) .to_smolstr(); Some((trait_id, name)) } else { None } }) .collect::>() }) { let Some(expected) = expected.remove(name.as_str()) else { continue; }; let mut osvs = FxHashSet::default(); let db = &db; crate::attach_db(db, || { _ = dyn_compatibility_with_callback(db, trait_id, &mut |osv| { osvs.insert(match osv { DynCompatibilityViolation::SizedSelf => SizedSelf, DynCompatibilityViolation::SelfReferential => SelfReferential, DynCompatibilityViolation::Method(_, mvc) => Method(mvc), DynCompatibilityViolation::AssocConst(_) => AssocConst, DynCompatibilityViolation::GAT(_) => GAT, DynCompatibilityViolation::HasNonCompatibleSuperTrait(_) => { HasNonCompatibleSuperTrait } }); ControlFlow::Continue(()) }); }); assert_eq!(osvs, expected, "dyn-compatibility violations for `{name}` do not match;"); } let remains: Vec<_> = expected.keys().collect(); assert!(remains.is_empty(), "Following traits do not exist in the test fixture; {remains:?}"); } #[test] fn item_bounds_can_reference_self() { check_dyn_compatibility( r#" //- minicore: eq pub trait Foo { type X: PartialEq; type Y: PartialEq; type Z: PartialEq; } "#, [("Foo", vec![])], ); } #[test] fn associated_consts() { check_dyn_compatibility( r#" trait Bar { const X: usize; } "#, [("Bar", vec![AssocConst])], ); } #[test] fn bounds_reference_self() { check_dyn_compatibility( r#" //- minicore: eq trait X { type U: PartialEq; } "#, [("X", vec![SelfReferential])], ); } #[test] fn by_value_self() { check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar { fn bar(self); } trait Baz { fn baz(self: Self); } trait Quux { // Legal because of the where clause: fn baz(self: Self) where Self : Sized; } "#, [("Bar", vec![]), ("Baz", vec![]), ("Quux", vec![])], ); } #[test] fn generic_methods() { check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar { fn bar(&self, t: T); } trait Quux { fn bar(&self, t: T) where Self : Sized; } trait Qax { fn bar<'a>(&self, t: &'a ()); } "#, [("Bar", vec![Method(Generic)]), ("Quux", vec![]), ("Qax", vec![])], ); } #[test] fn mentions_self() { check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar { fn bar(&self, x: &Self); } trait Baz { fn baz(&self) -> Self; } trait Quux { fn quux(&self, s: &Self) -> Self where Self : Sized; } "#, [ ("Bar", vec![Method(ReferencesSelfInput)]), ("Baz", vec![Method(ReferencesSelfOutput)]), ("Quux", vec![]), ], ); } #[test] fn no_static() { check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Foo { fn foo() {} } "#, [("Foo", vec![Method(StaticMethod)])], ); } #[test] fn sized_self() { check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar: Sized { fn bar(&self, t: T); } "#, [("Bar", vec![SizedSelf])], ); check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar where Self : Sized { fn bar(&self, t: T); } "#, [("Bar", vec![SizedSelf])], ); } #[test] fn supertrait_gat() { check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait GatTrait { type Gat; } trait SuperTrait: GatTrait {} "#, [("GatTrait", vec![GAT]), ("SuperTrait", vec![HasNonCompatibleSuperTrait])], ); } #[test] fn supertrait_mentions_self() { check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Bar { fn bar(&self, x: &T); } trait Baz : Bar { } "#, // FIXME: We should also report `SizedSelf` here [("Bar", vec![]), ("Baz", vec![SelfReferential])], ); } #[test] fn rustc_issue_19538() { check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Foo { fn foo(&self, val: T); } trait Bar: Foo {} "#, [("Foo", vec![Method(Generic)]), ("Bar", vec![HasNonCompatibleSuperTrait])], ); } #[test] fn rustc_issue_22040() { check_dyn_compatibility( r#" //- minicore: fmt, eq, dispatch_from_dyn use core::fmt::Debug; trait Expr: Debug + PartialEq { fn print_element_count(&self); } "#, [("Expr", vec![SelfReferential])], ); } #[test] fn rustc_issue_102762() { check_dyn_compatibility( r#" //- minicore: future, send, sync, dispatch_from_dyn, deref use core::pin::Pin; struct Box {} impl core::ops::Deref for Box { type Target = T; fn deref(&self) -> &Self::Target { loop {} } } impl, U: ?Sized> DispatchFromDyn> for Box {} struct Vec {} pub trait Fetcher: Send + Sync { fn get<'a>(self: &'a Box) -> Pin> + 'a>> where Self: Sync, { loop {} } } "#, [("Fetcher", vec![Method(UndispatchableReceiver)])], ); } #[test] fn rustc_issue_102933() { check_dyn_compatibility( r#" //- minicore: future, dispatch_from_dyn, deref use core::future::Future; struct Box {} impl core::ops::Deref for Box { type Target = T; fn deref(&self) -> &Self::Target { loop {} } } impl, U: ?Sized> DispatchFromDyn> for Box {} pub trait Service { type Response; type Future: Future; } pub trait A1: Service {} pub trait A2: Service>> + A1 { fn foo(&self) {} } pub trait B1: Service>> {} pub trait B2: Service + B1 { fn foo(&self) {} } "#, [("A2", vec![]), ("B2", vec![])], ); } #[test] fn rustc_issue_106247() { check_dyn_compatibility( r#" //- minicore: sync, dispatch_from_dyn pub trait Trait { fn method(&self) where Self: Sync; } "#, [("Trait", vec![])], ); } #[test] fn std_error_is_dyn_compatible() { check_dyn_compatibility( r#" //- minicore: fmt, dispatch_from_dyn trait Erased<'a>: 'a {} pub struct Request<'a>(dyn Erased<'a> + 'a); pub trait Error: core::fmt::Debug + core::fmt::Display { fn provide<'a>(&'a self, request: &mut Request<'a>); } "#, [("Error", vec![])], ); } #[test] fn lifetime_gat_is_dyn_incompatible() { check_dyn_compatibility( r#" //- minicore: dispatch_from_dyn trait Foo { type Bar<'a>; } "#, [("Foo", vec![DynCompatibilityViolationKind::GAT])], ); }