@@ -1007,58 +1007,81 @@ def unify_generic_callable(type: CallableType, target: CallableType,
1007
1007
1008
1008
1009
1009
def restrict_subtype_away (t : Type , s : Type , * , ignore_promotions : bool = False ) -> Type :
1010
- """Return t minus s.
1010
+ """Return t minus s for runtime type assertions .
1011
1011
1012
1012
If we can't determine a precise result, return a supertype of the
1013
1013
ideal result (just t is a valid result).
1014
1014
1015
1015
This is used for type inference of runtime type checks such as
1016
- isinstance.
1017
-
1018
- Currently this just removes elements of a union type.
1016
+ isinstance(). Currently this just removes elements of a union type.
1019
1017
"""
1020
1018
if isinstance (t , UnionType ):
1021
- # Since runtime type checks will ignore type arguments, erase the types.
1022
- erased_s = erase_type (s )
1023
- # TODO: Implement more robust support for runtime isinstance() checks,
1024
- # see issue #3827
1025
1019
new_items = [item for item in t .relevant_items ()
1026
- if (not (is_proper_subtype (erase_type (item ), erased_s ,
1027
- ignore_promotions = ignore_promotions ) or
1028
- is_proper_subtype (item , erased_s ,
1029
- ignore_promotions = ignore_promotions ))
1030
- or isinstance (item , AnyType ))]
1020
+ if (isinstance (item , AnyType ) or
1021
+ not covers_at_runtime (item , s , ignore_promotions ))]
1031
1022
return UnionType .make_union (new_items )
1032
1023
else :
1033
1024
return t
1034
1025
1035
1026
1036
- def is_proper_subtype (left : Type , right : Type , * , ignore_promotions : bool = False ) -> bool :
1027
+ def covers_at_runtime (item : Type , supertype : Type , ignore_promotions : bool ) -> bool :
1028
+ """Will isinstance(item, supertype) always return True at runtime?"""
1029
+ # Since runtime type checks will ignore type arguments, erase the types.
1030
+ supertype = erase_type (supertype )
1031
+ if is_proper_subtype (erase_type (item ), supertype , ignore_promotions = ignore_promotions ,
1032
+ erase_instances = True ):
1033
+ return True
1034
+ if isinstance (supertype , Instance ) and supertype .type .is_protocol :
1035
+ # TODO: Implement more robust support for runtime isinstance() checks, see issue #3827.
1036
+ if is_proper_subtype (item , supertype , ignore_promotions = ignore_promotions ):
1037
+ return True
1038
+ if isinstance (item , TypedDictType ) and isinstance (supertype , Instance ):
1039
+ # Special case useful for selecting TypedDicts from unions using isinstance(x, dict).
1040
+ if supertype .type .fullname () == 'builtins.dict' :
1041
+ return True
1042
+ # TODO: Add more special cases.
1043
+ return False
1044
+
1045
+
1046
+ def is_proper_subtype (left : Type , right : Type , * , ignore_promotions : bool = False ,
1047
+ erase_instances : bool = False ) -> bool :
1037
1048
"""Is left a proper subtype of right?
1038
1049
1039
1050
For proper subtypes, there's no need to rely on compatibility due to
1040
1051
Any types. Every usable type is a proper subtype of itself.
1052
+
1053
+ If erase_instances is True, erase left instance *after* mapping it to supertype
1054
+ (this is useful for runtime isinstance() checks).
1041
1055
"""
1042
1056
if isinstance (right , UnionType ) and not isinstance (left , UnionType ):
1043
- return any ([is_proper_subtype (left , item , ignore_promotions = ignore_promotions )
1057
+ return any ([is_proper_subtype (left , item , ignore_promotions = ignore_promotions ,
1058
+ erase_instances = erase_instances )
1044
1059
for item in right .items ])
1045
- return left .accept (ProperSubtypeVisitor (right , ignore_promotions = ignore_promotions ))
1060
+ return left .accept (ProperSubtypeVisitor (right , ignore_promotions = ignore_promotions ,
1061
+ erase_instances = erase_instances ))
1046
1062
1047
1063
1048
1064
class ProperSubtypeVisitor (TypeVisitor [bool ]):
1049
- def __init__ (self , right : Type , * , ignore_promotions : bool = False ) -> None :
1065
+ def __init__ (self , right : Type , * ,
1066
+ ignore_promotions : bool = False ,
1067
+ erase_instances : bool = False ) -> None :
1050
1068
self .right = right
1051
1069
self .ignore_promotions = ignore_promotions
1070
+ self .erase_instances = erase_instances
1052
1071
self ._subtype_kind = ProperSubtypeVisitor .build_subtype_kind (
1053
1072
ignore_promotions = ignore_promotions ,
1073
+ erase_instances = erase_instances ,
1054
1074
)
1055
1075
1056
1076
@staticmethod
1057
- def build_subtype_kind (* , ignore_promotions : bool = False ) -> SubtypeKind :
1058
- return (True , ignore_promotions )
1077
+ def build_subtype_kind (* , ignore_promotions : bool = False ,
1078
+ erase_instances : bool = False ) -> SubtypeKind :
1079
+ return True , ignore_promotions , erase_instances
1059
1080
1060
1081
def _is_proper_subtype (self , left : Type , right : Type ) -> bool :
1061
- return is_proper_subtype (left , right , ignore_promotions = self .ignore_promotions )
1082
+ return is_proper_subtype (left , right ,
1083
+ ignore_promotions = self .ignore_promotions ,
1084
+ erase_instances = self .erase_instances )
1062
1085
1063
1086
def visit_unbound_type (self , left : UnboundType ) -> bool :
1064
1087
# This can be called if there is a bad type annotation. The result probably
@@ -1107,6 +1130,10 @@ def check_argument(leftarg: Type, rightarg: Type, variance: int) -> bool:
1107
1130
return mypy .sametypes .is_same_type (leftarg , rightarg )
1108
1131
# Map left type to corresponding right instances.
1109
1132
left = map_instance_to_supertype (left , right .type )
1133
+ if self .erase_instances :
1134
+ erased = erase_type (left )
1135
+ assert isinstance (erased , Instance )
1136
+ left = erased
1110
1137
1111
1138
nominal = all (check_argument (ta , ra , tvar .variance ) for ta , ra , tvar in
1112
1139
zip (left .args , right .args , right .type .defn .type_vars ))
0 commit comments