|
| 1 | +use rustc_hash::FxHashMap; |
| 2 | + |
1 | 3 | use crate::{ |
2 | 4 | Db, FxIndexSet, |
3 | 5 | types::{ |
@@ -295,3 +297,129 @@ pub(super) fn any_over_type<'db>( |
295 | 297 | visitor.visit_type(db, ty); |
296 | 298 | visitor.found_matching_type.get() |
297 | 299 | } |
| 300 | + |
| 301 | +/// Returns the maximum number of layers of generic specializations for a given type. |
| 302 | +/// |
| 303 | +/// For example, `int` has a depth of `0`, `list[int]` has a depth of `1`, and `list[set[int]]` |
| 304 | +/// has a depth of `2`. A set-theoretic type like `list[int] | list[list[int]]` has a maximum |
| 305 | +/// depth of `2`. |
| 306 | +pub(super) fn specialization_depth(db: &dyn Db, ty: Type<'_>) -> usize { |
| 307 | + struct SpecializationDepthVisitor<'db> { |
| 308 | + seen_types: RefCell<FxHashMap<NonAtomicType<'db>, usize>>, |
| 309 | + max_depth: Cell<usize>, |
| 310 | + } |
| 311 | + |
| 312 | + impl<'db> TypeVisitor<'db> for SpecializationDepthVisitor<'db> { |
| 313 | + fn should_visit_lazy_type_attributes(&self) -> bool { |
| 314 | + false |
| 315 | + } |
| 316 | + |
| 317 | + fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) { |
| 318 | + match TypeKind::from(ty) { |
| 319 | + TypeKind::Atomic => { |
| 320 | + if ty.is_divergent() { |
| 321 | + self.max_depth.set(usize::MAX); |
| 322 | + } |
| 323 | + } |
| 324 | + TypeKind::NonAtomic(non_atomic_type) => { |
| 325 | + if let Some(cached_depth) = self.seen_types.borrow().get(&non_atomic_type) { |
| 326 | + self.max_depth.update(|current| current.max(*cached_depth)); |
| 327 | + return; |
| 328 | + } |
| 329 | + |
| 330 | + let self_depth: usize = |
| 331 | + matches!(non_atomic_type, NonAtomicType::GenericAlias(_)).into(); |
| 332 | + |
| 333 | + let previous_max_depth = self.max_depth.get(); |
| 334 | + |
| 335 | + self.max_depth.set(0); |
| 336 | + walk_non_atomic_type(db, non_atomic_type, self); |
| 337 | + |
| 338 | + self.max_depth.update(|child_max_depth| { |
| 339 | + previous_max_depth.max(child_max_depth.saturating_add(self_depth)) |
| 340 | + }); |
| 341 | + |
| 342 | + self.seen_types |
| 343 | + .borrow_mut() |
| 344 | + .insert(non_atomic_type, self.max_depth.get()); |
| 345 | + } |
| 346 | + } |
| 347 | + } |
| 348 | + } |
| 349 | + |
| 350 | + let visitor = SpecializationDepthVisitor { |
| 351 | + seen_types: RefCell::new(FxHashMap::default()), |
| 352 | + max_depth: Cell::new(0), |
| 353 | + }; |
| 354 | + visitor.visit_type(db, ty); |
| 355 | + visitor.max_depth.get() |
| 356 | +} |
| 357 | + |
| 358 | +#[cfg(test)] |
| 359 | +mod tests { |
| 360 | + use super::*; |
| 361 | + use crate::{db::tests::setup_db, types::KnownClass}; |
| 362 | + |
| 363 | + #[test] |
| 364 | + fn test_generics_layering_depth() { |
| 365 | + let db = setup_db(); |
| 366 | + |
| 367 | + let list_of_int = |
| 368 | + KnownClass::List.to_specialized_instance(&db, [KnownClass::Int.to_instance(&db)]); |
| 369 | + assert_eq!(specialization_depth(&db, list_of_int), 1); |
| 370 | + |
| 371 | + let list_of_list_of_int = KnownClass::List.to_specialized_instance(&db, [list_of_int]); |
| 372 | + assert_eq!(specialization_depth(&db, list_of_list_of_int), 2); |
| 373 | + |
| 374 | + let list_of_list_of_list_of_int = |
| 375 | + KnownClass::List.to_specialized_instance(&db, [list_of_list_of_int]); |
| 376 | + assert_eq!(specialization_depth(&db, list_of_list_of_list_of_int), 3); |
| 377 | + |
| 378 | + let set_of_dict_of_str_and_list_of_int = KnownClass::Set.to_specialized_instance( |
| 379 | + &db, |
| 380 | + [KnownClass::Dict |
| 381 | + .to_specialized_instance(&db, [KnownClass::Str.to_instance(&db), list_of_int])], |
| 382 | + ); |
| 383 | + assert_eq!( |
| 384 | + specialization_depth(&db, set_of_dict_of_str_and_list_of_int), |
| 385 | + 3 |
| 386 | + ); |
| 387 | + |
| 388 | + let union_type_1 = |
| 389 | + UnionType::from_elements(&db, [list_of_list_of_list_of_int, list_of_list_of_int]); |
| 390 | + assert_eq!(specialization_depth(&db, union_type_1), 3); |
| 391 | + |
| 392 | + let union_type_2 = |
| 393 | + UnionType::from_elements(&db, [list_of_list_of_int, list_of_list_of_list_of_int]); |
| 394 | + assert_eq!(specialization_depth(&db, union_type_2), 3); |
| 395 | + |
| 396 | + let tuple_of_tuple_of_int = Type::heterogeneous_tuple( |
| 397 | + &db, |
| 398 | + [Type::heterogeneous_tuple( |
| 399 | + &db, |
| 400 | + [KnownClass::Int.to_instance(&db)], |
| 401 | + )], |
| 402 | + ); |
| 403 | + assert_eq!(specialization_depth(&db, tuple_of_tuple_of_int), 2); |
| 404 | + |
| 405 | + let tuple_of_list_of_int_and_str = KnownClass::Tuple |
| 406 | + .to_specialized_instance(&db, [list_of_int, KnownClass::Str.to_instance(&db)]); |
| 407 | + assert_eq!(specialization_depth(&db, tuple_of_list_of_int_and_str), 1); |
| 408 | + |
| 409 | + let list_of_union_of_lists = KnownClass::List.to_specialized_instance( |
| 410 | + &db, |
| 411 | + [UnionType::from_elements( |
| 412 | + &db, |
| 413 | + [ |
| 414 | + KnownClass::List |
| 415 | + .to_specialized_instance(&db, [KnownClass::Int.to_instance(&db)]), |
| 416 | + KnownClass::List |
| 417 | + .to_specialized_instance(&db, [KnownClass::Str.to_instance(&db)]), |
| 418 | + KnownClass::List |
| 419 | + .to_specialized_instance(&db, [KnownClass::Bytes.to_instance(&db)]), |
| 420 | + ], |
| 421 | + )], |
| 422 | + ); |
| 423 | + assert_eq!(specialization_depth(&db, list_of_union_of_lists), 2); |
| 424 | + } |
| 425 | +} |
0 commit comments