File tree Expand file tree Collapse file tree 2 files changed +55
-9
lines changed Expand file tree Collapse file tree 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:
892892 return False
893893 for name , l , r in left .zip (right ):
894894 # 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+ )
897906 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 )
904909 if not check :
905910 return False
906911 # Non-required key is not compatible with a required key since
@@ -917,7 +922,7 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
917922 # Readonly fields check:
918923 #
919924 # A = TypedDict('A', {'x': ReadOnly[int]})
920- # B = TypedDict('A ', {'x': int})
925+ # B = TypedDict('B ', {'x': int})
921926 # def reset_x(b: B) -> None:
922927 # b['x'] = 0
923928 #
Original file line number Diff line number Diff line change @@ -3988,3 +3988,44 @@ class TP(TypedDict):
39883988 k: ReadOnly # E: "ReadOnly[]" must have exactly one type argument
39893989[builtins fixtures/dict.pyi]
39903990[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