File tree 2 files changed +55
-9
lines changed
2 files changed +55
-9
lines changed Original file line number Diff line number Diff line change @@ -892,15 +892,20 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
892
892
return False
893
893
for name , l , r in left .zip (right ):
894
894
# TODO: should we pass on the full subtype_context here and below?
895
- if self .proper_subtype :
896
- check = is_same_type (l , r )
895
+ right_readonly = name in right .readonly_keys
896
+ if not right_readonly :
897
+ if self .proper_subtype :
898
+ check = is_same_type (l , r )
899
+ else :
900
+ check = is_equivalent (
901
+ l ,
902
+ r ,
903
+ ignore_type_params = self .subtype_context .ignore_type_params ,
904
+ options = self .options ,
905
+ )
897
906
else :
898
- check = is_equivalent (
899
- l ,
900
- r ,
901
- ignore_type_params = self .subtype_context .ignore_type_params ,
902
- options = self .options ,
903
- )
907
+ # Read-only items behave covariantly
908
+ check = self ._is_subtype (l , r )
904
909
if not check :
905
910
return False
906
911
# Non-required key is not compatible with a required key since
@@ -917,7 +922,7 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
917
922
# Readonly fields check:
918
923
#
919
924
# A = TypedDict('A', {'x': ReadOnly[int]})
920
- # B = TypedDict('A ', {'x': int})
925
+ # B = TypedDict('B ', {'x': int})
921
926
# def reset_x(b: B) -> None:
922
927
# b['x'] = 0
923
928
#
Original file line number Diff line number Diff line change @@ -3988,3 +3988,44 @@ class TP(TypedDict):
3988
3988
k: ReadOnly # E: "ReadOnly[]" must have exactly one type argument
3989
3989
[builtins fixtures/dict.pyi]
3990
3990
[typing fixtures/typing-typeddict.pyi]
3991
+
3992
+ [case testTypedDictReadOnlyCovariant]
3993
+ from typing import ReadOnly, TypedDict, Union
3994
+
3995
+ class A(TypedDict):
3996
+ a: ReadOnly[Union[int, str]]
3997
+
3998
+ class A2(TypedDict):
3999
+ a: ReadOnly[int]
4000
+
4001
+ class B(TypedDict):
4002
+ a: int
4003
+
4004
+ class B2(TypedDict):
4005
+ a: Union[int, str]
4006
+
4007
+ class B3(TypedDict):
4008
+ a: int
4009
+
4010
+ def fa(a: A) -> None: ...
4011
+ def fa2(a: A2) -> None: ...
4012
+
4013
+ b: B = {"a": 1}
4014
+ fa(b)
4015
+ fa2(b)
4016
+ b2: B2 = {"a": 1}
4017
+ fa(b2)
4018
+ fa2(b2) # E: Argument 1 to "fa2" has incompatible type "B2"; expected "A2"
4019
+
4020
+ class C(TypedDict):
4021
+ a: ReadOnly[Union[int, str]]
4022
+ b: Union[str, bytes]
4023
+
4024
+ class D(TypedDict):
4025
+ a: int
4026
+ b: str
4027
+
4028
+ d: D = {"a": 1, "b": "x"}
4029
+ c: C = d # E: Incompatible types in assignment (expression has type "D", variable has type "C")
4030
+ [builtins fixtures/dict.pyi]
4031
+ [typing fixtures/typing-typeddict.pyi]
You can’t perform that action at this time.
0 commit comments