@@ -717,7 +717,7 @@ impl<'db> DataclassParams<'db> {
717717#[ derive( Copy , Clone , Debug , PartialEq , Eq , Hash , salsa:: Update , get_size2:: GetSize ) ]
718718pub enum Type < ' db > {
719719 /// The dynamic type: a statically unknown set of values
720- Dynamic ( DynamicType ) ,
720+ Dynamic ( DynamicType < ' db > ) ,
721721 /// The empty set of values
722722 Never ,
723723 /// A specific function object
@@ -827,12 +827,12 @@ impl<'db> Type<'db> {
827827 Self :: Dynamic ( DynamicType :: Unknown )
828828 }
829829
830- pub ( crate ) fn divergent ( ) -> Self {
831- Self :: Dynamic ( DynamicType :: Divergent )
830+ pub ( crate ) fn divergent ( scope : Option < ScopeId < ' db > > ) -> Self {
831+ Self :: Dynamic ( DynamicType :: Divergent ( DivergentType { scope } ) )
832832 }
833833
834834 pub ( crate ) const fn is_divergent ( & self ) -> bool {
835- matches ! ( self , Type :: Dynamic ( DynamicType :: Divergent ) )
835+ matches ! ( self , Type :: Dynamic ( DynamicType :: Divergent ( _ ) ) )
836836 }
837837
838838 pub const fn is_unknown ( & self ) -> bool {
@@ -1044,7 +1044,7 @@ impl<'db> Type<'db> {
10441044 }
10451045 }
10461046
1047- pub ( crate ) const fn as_dynamic ( self ) -> Option < DynamicType > {
1047+ pub ( crate ) const fn as_dynamic ( self ) -> Option < DynamicType < ' db > > {
10481048 match self {
10491049 Type :: Dynamic ( dynamic_type) => Some ( dynamic_type) ,
10501050 _ => None ,
@@ -1058,7 +1058,7 @@ impl<'db> Type<'db> {
10581058 }
10591059 }
10601060
1061- pub ( crate ) const fn expect_dynamic ( self ) -> DynamicType {
1061+ pub ( crate ) const fn expect_dynamic ( self ) -> DynamicType < ' db > {
10621062 self . as_dynamic ( ) . expect ( "Expected a Type::Dynamic variant" )
10631063 }
10641064
@@ -1639,8 +1639,8 @@ impl<'db> Type<'db> {
16391639 // In some specific situations, `Any`/`Unknown`/`@Todo` can be simplified out of unions and intersections,
16401640 // but this is not true for divergent types (and moving this case any lower down appears to cause
16411641 // "too many cycle iterations" panics).
1642- ( Type :: Dynamic ( DynamicType :: Divergent ) , _)
1643- | ( _, Type :: Dynamic ( DynamicType :: Divergent ) ) => {
1642+ ( Type :: Dynamic ( DynamicType :: Divergent ( _ ) ) , _)
1643+ | ( _, Type :: Dynamic ( DynamicType :: Divergent ( _ ) ) ) => {
16441644 ConstraintSet :: from ( relation. is_assignability ( ) )
16451645 }
16461646
@@ -2457,8 +2457,8 @@ impl<'db> Type<'db> {
24572457 match ( self , other) {
24582458 // The `Divergent` type is a special type that is not equivalent to other kinds of dynamic types,
24592459 // which prevents `Divergent` from being eliminated during union reduction.
2460- ( Type :: Dynamic ( _) , Type :: Dynamic ( DynamicType :: Divergent ) )
2461- | ( Type :: Dynamic ( DynamicType :: Divergent ) , Type :: Dynamic ( _) ) => {
2460+ ( Type :: Dynamic ( _) , Type :: Dynamic ( DynamicType :: Divergent ( _ ) ) )
2461+ | ( Type :: Dynamic ( DynamicType :: Divergent ( _ ) ) , Type :: Dynamic ( _) ) => {
24622462 ConstraintSet :: from ( false )
24632463 }
24642464 ( Type :: Dynamic ( _) , Type :: Dynamic ( _) ) => ConstraintSet :: from ( true ) ,
@@ -6656,13 +6656,7 @@ impl<'db> Type<'db> {
66566656 match self {
66576657 Type :: TypeVar ( bound_typevar) => match type_mapping {
66586658 TypeMapping :: Specialization ( specialization) => {
6659- let ty = specialization. get ( db, bound_typevar) . unwrap_or ( self ) ;
6660-
6661- if exceeds_max_specialization_depth ( db, ty) {
6662- Type :: divergent ( )
6663- } else {
6664- ty
6665- }
6659+ specialization. get ( db, bound_typevar) . unwrap_or ( self ) . fallback_to_divergent ( db)
66666660 }
66676661 TypeMapping :: PartialSpecialization ( partial) => {
66686662 partial. get ( db, bound_typevar) . unwrap_or ( self )
@@ -7220,6 +7214,20 @@ impl<'db> Type<'db> {
72207214 _ => None ,
72217215 }
72227216 }
7217+
7218+ pub ( super ) fn has_divergent_type ( self , db : & ' db dyn Db , div : Type < ' db > ) -> bool {
7219+ any_over_type ( db, self , & |ty| ty == div, false )
7220+ }
7221+
7222+ /// If the specialization depth of `self` exceeds the maximum limit allowed,
7223+ /// return `Divergent`. Otherwise, return `self`.
7224+ pub ( super ) fn fallback_to_divergent ( self , db : & ' db dyn Db ) -> Type < ' db > {
7225+ if exceeds_max_specialization_depth ( db, self ) {
7226+ Type :: divergent ( None )
7227+ } else {
7228+ self
7229+ }
7230+ }
72237231}
72247232
72257233impl < ' db > From < & Type < ' db > > for Type < ' db > {
@@ -7657,8 +7665,19 @@ impl<'db> KnownInstanceType<'db> {
76577665 }
76587666}
76597667
7668+ /// A type that is determined to be divergent during recursive type inference.
7669+ /// This type must never be eliminated by dynamic type reduction
7670+ /// (e.g. `Divergent` is assignable to `@Todo`, but `@Todo | Divergent` must not be reducted to `@Todo`).
7671+ /// Otherwise, type inference cannot converge properly.
7672+ /// For detailed properties of this type, see the unit test at the end of the file.
7673+ #[ derive( Copy , Clone , Debug , Eq , Hash , PartialEq , salsa:: Update , get_size2:: GetSize ) ]
7674+ pub struct DivergentType < ' db > {
7675+ /// The scope where this divergence was detected.
7676+ scope : Option < ScopeId < ' db > > ,
7677+ }
7678+
76607679#[ derive( Copy , Clone , Debug , Eq , Hash , PartialEq , salsa:: Update , get_size2:: GetSize ) ]
7661- pub enum DynamicType {
7680+ pub enum DynamicType < ' db > {
76627681 /// An explicitly annotated `typing.Any`
76637682 Any ,
76647683 /// An unannotated value, or a dynamic type resulting from an error
@@ -7682,24 +7701,20 @@ pub enum DynamicType {
76827701 /// A special Todo-variant for `Unpack[Ts]`, so that we can treat it specially in `Generic[Unpack[Ts]]`
76837702 TodoUnpack ,
76847703 /// A type that is determined to be divergent during type inference for a recursive function.
7685- /// This type must never be eliminated by dynamic type reduction
7686- /// (e.g. `Divergent` is assignable to `@Todo`, but `@Todo | Divergent` must not be reducted to `@Todo`).
7687- /// Otherwise, type inference cannot converge properly.
7688- /// For detailed properties of this type, see the unit test at the end of the file.
7689- Divergent ,
7704+ Divergent ( DivergentType < ' db > ) ,
76907705}
76917706
7692- impl DynamicType {
7707+ impl DynamicType < ' _ > {
76937708 fn normalized ( self ) -> Self {
7694- if matches ! ( self , Self :: Divergent ) {
7709+ if matches ! ( self , Self :: Divergent ( _ ) ) {
76957710 self
76967711 } else {
76977712 Self :: Any
76987713 }
76997714 }
77007715}
77017716
7702- impl std:: fmt:: Display for DynamicType {
7717+ impl std:: fmt:: Display for DynamicType < ' _ > {
77037718 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
77047719 match self {
77057720 DynamicType :: Any => f. write_str ( "Any" ) ,
@@ -7728,7 +7743,7 @@ impl std::fmt::Display for DynamicType {
77287743 f. write_str ( "@Todo" )
77297744 }
77307745 }
7731- DynamicType :: Divergent => f. write_str ( "Divergent" ) ,
7746+ DynamicType :: Divergent ( _ ) => f. write_str ( "Divergent" ) ,
77327747 }
77337748 }
77347749}
@@ -11676,6 +11691,8 @@ pub(crate) mod tests {
1167611691 use super :: * ;
1167711692 use crate :: db:: tests:: { TestDbBuilder , setup_db} ;
1167811693 use crate :: place:: { typing_extensions_symbol, typing_symbol} ;
11694+ use crate :: semantic_index:: FileScopeId ;
11695+ use ruff_db:: files:: system_path_to_file;
1167911696 use ruff_db:: system:: DbWithWritableSystem as _;
1168011697 use ruff_python_ast:: PythonVersion ;
1168111698 use test_case:: test_case;
@@ -11762,9 +11779,14 @@ pub(crate) mod tests {
1176211779
1176311780 #[ test]
1176411781 fn divergent_type ( ) {
11765- let db = setup_db ( ) ;
11782+ let mut db = setup_db ( ) ;
11783+
11784+ db. write_dedented ( "src/foo.py" , "" ) . unwrap ( ) ;
11785+ let file = system_path_to_file ( & db, "src/foo.py" ) . unwrap ( ) ;
11786+ let file_scope_id = FileScopeId :: global ( ) ;
11787+ let scope = file_scope_id. to_scope_id ( & db, file) ;
1176611788
11767- let div = Type :: divergent ( ) ;
11789+ let div = Type :: Dynamic ( DynamicType :: Divergent ( DivergentType { scope : Some ( scope ) } ) ) ;
1176811790
1176911791 // The `Divergent` type must not be eliminated in union with other dynamic types,
1177011792 // as this would prevent detection of divergent type inference using `Divergent`.
0 commit comments