@@ -65,6 +65,7 @@ use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signat
65
65
use crate :: types:: tuple:: TupleSpec ;
66
66
pub ( crate ) use crate :: types:: typed_dict:: { TypedDictParams , TypedDictType , walk_typed_dict_type} ;
67
67
use crate :: types:: variance:: { TypeVarVariance , VarianceInferable } ;
68
+ use crate :: types:: visitor:: any_over_type;
68
69
use crate :: unpack:: EvaluationMode ;
69
70
pub use crate :: util:: diagnostics:: add_inferred_python_version_hint_to_diagnostic;
70
71
use crate :: { Db , FxOrderSet , Module , Program } ;
@@ -602,7 +603,7 @@ impl From<DataclassTransformerParams> for DataclassParams {
602
603
#[ derive( Copy , Clone , Debug , PartialEq , Eq , Hash , salsa:: Update , get_size2:: GetSize ) ]
603
604
pub enum Type < ' db > {
604
605
/// The dynamic type: a statically unknown set of values
605
- Dynamic ( DynamicType ) ,
606
+ Dynamic ( DynamicType < ' db > ) ,
606
607
/// The empty set of values
607
608
Never ,
608
609
/// A specific function object
@@ -885,14 +886,14 @@ impl<'db> Type<'db> {
885
886
}
886
887
}
887
888
888
- pub ( crate ) const fn into_dynamic ( self ) -> Option < DynamicType > {
889
+ pub ( crate ) const fn into_dynamic ( self ) -> Option < DynamicType < ' db > > {
889
890
match self {
890
891
Type :: Dynamic ( dynamic_type) => Some ( dynamic_type) ,
891
892
_ => None ,
892
893
}
893
894
}
894
895
895
- pub ( crate ) const fn expect_dynamic ( self ) -> DynamicType {
896
+ pub ( crate ) const fn expect_dynamic ( self ) -> DynamicType < ' db > {
896
897
self . into_dynamic ( )
897
898
. expect ( "Expected a Type::Dynamic variant" )
898
899
}
@@ -1857,6 +1858,10 @@ impl<'db> Type<'db> {
1857
1858
}
1858
1859
1859
1860
match ( self , other) {
1861
+ // The `Divergent` type is a special type that is not equivalent to other kinds of dynamic types,
1862
+ // which prevents `Divergent` from being eliminated during union reduction.
1863
+ ( Type :: Dynamic ( _) , Type :: Dynamic ( DynamicType :: Divergent ( _) ) )
1864
+ | ( Type :: Dynamic ( DynamicType :: Divergent ( _) ) , Type :: Dynamic ( _) ) => C :: unsatisfiable ( db) ,
1860
1865
( Type :: Dynamic ( _) , Type :: Dynamic ( _) ) => C :: always_satisfiable ( db) ,
1861
1866
1862
1867
( Type :: SubclassOf ( first) , Type :: SubclassOf ( second) ) => {
@@ -6566,6 +6571,14 @@ impl<'db> Type<'db> {
6566
6571
_ => None ,
6567
6572
}
6568
6573
}
6574
+
6575
+ #[ allow( unused) ]
6576
+ pub ( super ) fn has_divergent_type ( self , db : & ' db dyn Db , div : Type < ' db > ) -> bool {
6577
+ any_over_type ( db, self , & |ty| match ty {
6578
+ Type :: Dynamic ( DynamicType :: Divergent ( _) ) => ty == div,
6579
+ _ => false ,
6580
+ } )
6581
+ }
6569
6582
}
6570
6583
6571
6584
impl < ' db > From < & Type < ' db > > for Type < ' db > {
@@ -6960,8 +6973,19 @@ impl<'db> KnownInstanceType<'db> {
6960
6973
}
6961
6974
}
6962
6975
6963
- #[ derive( Copy , Clone , Debug , Eq , Hash , PartialEq , get_size2:: GetSize ) ]
6964
- pub enum DynamicType {
6976
+ /// A type that is determined to be divergent during recursive type inference.
6977
+ /// This type must never be eliminated by dynamic type reduction
6978
+ /// (e.g. `Divergent` is assignable to `@Todo`, but `@Todo | Divergent` must not be reducted to `@Todo`).
6979
+ /// Otherwise, type inference cannot converge properly.
6980
+ /// For detailed properties of this type, see the unit test at the end of the file.
6981
+ #[ derive( Copy , Clone , Debug , Eq , Hash , PartialEq , salsa:: Update , get_size2:: GetSize ) ]
6982
+ pub struct DivergentType < ' db > {
6983
+ /// The scope where this divergence was detected.
6984
+ scope : ScopeId < ' db > ,
6985
+ }
6986
+
6987
+ #[ derive( Copy , Clone , Debug , Eq , Hash , PartialEq , salsa:: Update , get_size2:: GetSize ) ]
6988
+ pub enum DynamicType < ' db > {
6965
6989
/// An explicitly annotated `typing.Any`
6966
6990
Any ,
6967
6991
/// An unannotated value, or a dynamic type resulting from an error
@@ -6984,16 +7008,21 @@ pub enum DynamicType {
6984
7008
TodoTypeAlias ,
6985
7009
/// A special Todo-variant for `Unpack[Ts]`, so that we can treat it specially in `Generic[Unpack[Ts]]`
6986
7010
TodoUnpack ,
7011
+ /// A type that is determined to be divergent during type inference for a recursive function.
7012
+ Divergent ( DivergentType < ' db > ) ,
6987
7013
}
6988
7014
6989
- impl DynamicType {
6990
- #[ expect( clippy:: unused_self) ]
7015
+ impl DynamicType < ' _ > {
6991
7016
fn normalized ( self ) -> Self {
6992
- Self :: Any
7017
+ if matches ! ( self , Self :: Divergent ( _) ) {
7018
+ self
7019
+ } else {
7020
+ Self :: Any
7021
+ }
6993
7022
}
6994
7023
}
6995
7024
6996
- impl std:: fmt:: Display for DynamicType {
7025
+ impl std:: fmt:: Display for DynamicType < ' _ > {
6997
7026
fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
6998
7027
match self {
6999
7028
DynamicType :: Any => f. write_str ( "Any" ) ,
@@ -7022,6 +7051,7 @@ impl std::fmt::Display for DynamicType {
7022
7051
f. write_str ( "@Todo" )
7023
7052
}
7024
7053
}
7054
+ DynamicType :: Divergent ( _) => f. write_str ( "Divergent" ) ,
7025
7055
}
7026
7056
}
7027
7057
}
@@ -10290,7 +10320,7 @@ impl BoundSuperError<'_> {
10290
10320
10291
10321
#[ derive( Debug , Copy , Clone , Hash , PartialEq , Eq , get_size2:: GetSize ) ]
10292
10322
pub enum SuperOwnerKind < ' db > {
10293
- Dynamic ( DynamicType ) ,
10323
+ Dynamic ( DynamicType < ' db > ) ,
10294
10324
Class ( ClassType < ' db > ) ,
10295
10325
Instance ( NominalInstanceType < ' db > ) ,
10296
10326
}
@@ -10656,6 +10686,7 @@ pub(crate) mod tests {
10656
10686
use super :: * ;
10657
10687
use crate :: db:: tests:: { TestDbBuilder , setup_db} ;
10658
10688
use crate :: place:: { global_symbol, typing_extensions_symbol, typing_symbol} ;
10689
+ use crate :: semantic_index:: FileScopeId ;
10659
10690
use ruff_db:: files:: system_path_to_file;
10660
10691
use ruff_db:: parsed:: parsed_module;
10661
10692
use ruff_db:: system:: DbWithWritableSystem as _;
@@ -10801,4 +10832,53 @@ pub(crate) mod tests {
10801
10832
. is_todo( )
10802
10833
) ;
10803
10834
}
10835
+
10836
+ #[ test]
10837
+ fn divergent_type ( ) {
10838
+ let mut db = setup_db ( ) ;
10839
+
10840
+ db. write_dedented ( "src/foo.py" , "" ) . unwrap ( ) ;
10841
+ let file = system_path_to_file ( & db, "src/foo.py" ) . unwrap ( ) ;
10842
+ let file_scope_id = FileScopeId :: global ( ) ;
10843
+ let scope = file_scope_id. to_scope_id ( & db, file) ;
10844
+
10845
+ let div = Type :: Dynamic ( DynamicType :: Divergent ( DivergentType { scope } ) ) ;
10846
+
10847
+ // The `Divergent` type must not be eliminated in union with other dynamic types,
10848
+ // as this would prevent detection of divergent type inference using `Divergent`.
10849
+ let union = UnionType :: from_elements ( & db, [ Type :: unknown ( ) , div] ) ;
10850
+ assert_eq ! ( union . display( & db) . to_string( ) , "Unknown | Divergent" ) ;
10851
+
10852
+ let union = UnionType :: from_elements ( & db, [ div, Type :: unknown ( ) ] ) ;
10853
+ assert_eq ! ( union . display( & db) . to_string( ) , "Divergent | Unknown" ) ;
10854
+
10855
+ let union = UnionType :: from_elements ( & db, [ div, Type :: unknown ( ) , todo_type ! ( "1" ) ] ) ;
10856
+ assert_eq ! ( union . display( & db) . to_string( ) , "Divergent | Unknown" ) ;
10857
+
10858
+ assert ! ( div. is_equivalent_to( & db, div) ) ;
10859
+ assert ! ( !div. is_equivalent_to( & db, Type :: unknown( ) ) ) ;
10860
+ assert ! ( !Type :: unknown( ) . is_equivalent_to( & db, div) ) ;
10861
+
10862
+ // The `object` type has a good convergence property, that is, its union with all other types is `object`.
10863
+ // (e.g. `object | tuple[Divergent] == object`, `object | tuple[object] == object`)
10864
+ // So we can safely eliminate `Divergent`.
10865
+ let union = UnionType :: from_elements ( & db, [ div, KnownClass :: Object . to_instance ( & db) ] ) ;
10866
+ assert_eq ! ( union . display( & db) . to_string( ) , "object" ) ;
10867
+
10868
+ let union = UnionType :: from_elements ( & db, [ KnownClass :: Object . to_instance ( & db) , div] ) ;
10869
+ assert_eq ! ( union . display( & db) . to_string( ) , "object" ) ;
10870
+
10871
+ // The same can be said about intersections for the `Never` type.
10872
+ let intersection = IntersectionBuilder :: new ( & db)
10873
+ . add_positive ( Type :: Never )
10874
+ . add_positive ( div)
10875
+ . build ( ) ;
10876
+ assert_eq ! ( intersection. display( & db) . to_string( ) , "Never" ) ;
10877
+
10878
+ let intersection = IntersectionBuilder :: new ( & db)
10879
+ . add_positive ( div)
10880
+ . add_positive ( Type :: Never )
10881
+ . build ( ) ;
10882
+ assert_eq ! ( intersection. display( & db) . to_string( ) , "Never" ) ;
10883
+ }
10804
10884
}
0 commit comments