@@ -41,6 +41,14 @@ impl<'db> Type<'db> {
4141 }
4242
4343 pub ( crate ) fn try_upcast_to_callable ( self , db : & ' db dyn Db ) -> Option < CallableTypes < ' db > > {
44+ self . try_upcast_to_callable_with_policy ( db, UpcastPolicy :: default ( ) )
45+ }
46+
47+ pub ( crate ) fn try_upcast_to_callable_with_policy (
48+ self ,
49+ db : & ' db dyn Db ,
50+ policy : UpcastPolicy ,
51+ ) -> Option < CallableTypes < ' db > > {
4452 match self {
4553 Type :: Callable ( callable) => Some ( CallableTypes :: one ( callable) ) ,
4654
@@ -68,7 +76,7 @@ impl<'db> Type<'db> {
6876 if let Place :: Defined ( place) = call_symbol
6977 && place. is_definitely_defined ( )
7078 {
71- place. ty . try_upcast_to_callable ( db)
79+ place. ty . try_upcast_to_callable_with_policy ( db, policy )
7280 } else {
7381 None
7482 }
@@ -79,16 +87,25 @@ impl<'db> Type<'db> {
7987
8088 Type :: GenericAlias ( alias) => Some ( ClassType :: Generic ( alias) . into_callable ( db) ) ,
8189
82- Type :: NewTypeInstance ( newtype) => {
83- newtype. concrete_base_type ( db) . try_upcast_to_callable ( db)
90+ Type :: NewTypeInstance ( newtype) => newtype
91+ . concrete_base_type ( db)
92+ . try_upcast_to_callable_with_policy ( db, policy) ,
93+
94+ Type :: SubclassOf ( subclass_of_ty) if policy == UpcastPolicy :: Sound => {
95+ Some ( CallableTypes :: one ( CallableType :: function_like (
96+ db,
97+ Signature :: new ( Parameters :: top ( ) , subclass_of_ty. to_instance ( db) ) ,
98+ ) ) )
8499 }
85100
86101 // TODO: This is unsound so in future we can consider an opt-in option to disable it.
87102 Type :: SubclassOf ( subclass_of_ty) => match subclass_of_ty. subclass_of ( ) {
88103 SubclassOfInner :: Class ( class) => Some ( class. into_callable ( db) ) ,
89104 SubclassOfInner :: TypeVar ( tvar) => match tvar. typevar ( db) . bound_or_constraints ( db) {
90105 Some ( TypeVarBoundOrConstraints :: UpperBound ( bound) ) => {
91- let upcast_callables = bound. to_meta_type ( db) . try_upcast_to_callable ( db) ?;
106+ let upcast_callables = bound
107+ . to_meta_type ( db)
108+ . try_upcast_to_callable_with_policy ( db, policy) ?;
92109 Some ( upcast_callables. map ( |callable| {
93110 let signatures = callable
94111 . signatures ( db)
@@ -104,8 +121,9 @@ impl<'db> Type<'db> {
104121 Some ( TypeVarBoundOrConstraints :: Constraints ( constraints) ) => {
105122 let mut callables = SmallVec :: new ( ) ;
106123 for constraint in constraints. elements ( db) {
107- let element_upcast =
108- constraint. to_meta_type ( db) . try_upcast_to_callable ( db) ?;
124+ let element_upcast = constraint
125+ . to_meta_type ( db)
126+ . try_upcast_to_callable_with_policy ( db, policy) ?;
109127 for callable in element_upcast. into_inner ( ) {
110128 let signatures = callable
111129 . signatures ( db)
@@ -134,7 +152,8 @@ impl<'db> Type<'db> {
134152 Type :: Union ( union) => {
135153 let mut callables = SmallVec :: new ( ) ;
136154 for element in union. elements ( db) {
137- let element_callable = element. try_upcast_to_callable ( db) ?;
155+ let element_callable =
156+ element. try_upcast_to_callable_with_policy ( db, policy) ?;
138157 callables. extend ( element_callable. into_inner ( ) ) ;
139158 }
140159 Some ( CallableTypes :: new ( callables) )
@@ -143,11 +162,13 @@ impl<'db> Type<'db> {
143162 Type :: LiteralValue ( literal) => match literal. kind ( ) {
144163 LiteralValueTypeKind :: Enum ( enum_literal) => enum_literal
145164 . enum_class_instance ( db)
146- . try_upcast_to_callable ( db) ,
165+ . try_upcast_to_callable_with_policy ( db, policy ) ,
147166 _ => None ,
148167 } ,
149168
150- Type :: TypeAlias ( alias) => alias. value_type ( db) . try_upcast_to_callable ( db) ,
169+ Type :: TypeAlias ( alias) => alias
170+ . value_type ( db)
171+ . try_upcast_to_callable_with_policy ( db, policy) ,
151172
152173 Type :: KnownBoundMethod ( method) => Some ( CallableTypes :: one ( CallableType :: new (
153174 db,
@@ -220,6 +241,50 @@ pub enum CallableTypeKind {
220241 ParamSpecValue ,
221242}
222243
244+ /// A "policy" enum that describes how `type[]` types should be upcast
245+ /// to `Callable` types.
246+ ///
247+ /// `type[T]` is generally considered assignable to
248+ /// `Callable[<constructor signature of T>, T]` in Python, and most
249+ /// type-checking in Python uses assignability rather than subtyping
250+ /// when determining whether to emit errors on code, so -- despite its
251+ /// scary name -- [`UpcastPolicy::Unsound`] is actually the policy that
252+ /// you probably want in most situations. We *have* to use
253+ /// [`UpcastPolicy::Sound`], however, when doing subtyping or redundancy
254+ /// checks, because constructor signatures in subclasses are not checked
255+ /// for Liskov substitutability: `type[S]` may not be a subtype of
256+ /// `Callable[<constructor signature of T>, T]` even if `S` is a subtype
257+ /// of `T`. If this unsoundness leaked into our union simplification or
258+ /// subtyping checks, it would ead to nontransitivity of subtyping,
259+ /// breaking fundamental assumptions in our model.
260+ #[ derive( Debug , Copy , Clone , PartialEq , Eq , Hash , Default ) ]
261+ pub ( crate ) enum UpcastPolicy {
262+ /// Only upcast types to callables in a sound fashion.
263+ ///
264+ /// This means that `type[T]` is upcast to `Top[Callable[..., T]]`
265+ /// rather than `Callable[<constructor signature of T>, T]`,
266+ /// since the former is sound while the latter is not.
267+ Sound ,
268+
269+ /// Allow unsound upcasts to callables, such as treating `type[T]` as
270+ /// `Callable[<constructor signature of T>, T`.
271+ #[ default]
272+ Unsound ,
273+ }
274+
275+ impl From < TypeRelation > for UpcastPolicy {
276+ fn from ( relation : TypeRelation ) -> Self {
277+ match relation {
278+ TypeRelation :: Subtyping
279+ | TypeRelation :: Redundancy { .. }
280+ | TypeRelation :: SubtypingAssuming => UpcastPolicy :: Sound ,
281+ TypeRelation :: Assignability | TypeRelation :: ConstraintSetAssignability => {
282+ UpcastPolicy :: Unsound
283+ }
284+ }
285+ }
286+ }
287+
223288/// This type represents the set of all callable objects with a certain, possibly overloaded,
224289/// signature.
225290///
0 commit comments