[ty] Synthesize __or__ and __ror__ for TypedDict to avoid widening to dict[str, object]#23408
[ty] Synthesize __or__ and __ror__ for TypedDict to avoid widening to dict[str, object]#23408charliermarsh wants to merge 1 commit intomainfrom
__or__ and __ror__ for TypedDict to avoid widening to dict[str, object]#23408Conversation
Typing conformance resultsNo changes detected ✅ |
|
Memory usage reportMemory usage unchanged ✅ |
sharkdp
left a comment
There was a problem hiding this comment.
Thank you. I'm not convinced this change is correct.
|
|
||
| def _(val: MyDict) -> None: | ||
| result = val | {"foo": 2} | ||
| reveal_type(result) # revealed: dict[str, int | str] |
There was a problem hiding this comment.
If we're inferring dict[str, int | str] here, that's not going to solve the case in astral-sh/ty#2798, right?
| // Overload 2: `(self, value: dict[str, Any]) -> dict[str, VT]` | ||
| Signature::new( | ||
| Parameters::new( | ||
| db, | ||
| [ | ||
| Parameter::positional_only(Some(Name::new_static("self"))) | ||
| .with_annotated_type(instance_ty), | ||
| Parameter::positional_only(Some(Name::new_static("value"))) | ||
| .with_annotated_type(dict_param_ty), | ||
| ], | ||
| ), | ||
| dict_return_ty, | ||
| ), |
There was a problem hiding this comment.
That seems wrong? Consider
class MyDict(TypedDict):
foo: int
bar: str
val = MyDict(foo=1, bar="hello")
reveal_type(val | {"baz": None})With the change here, we infer dict[str, int | str], but the resulting dict contains a value of type None.
| ## PEP 584 merge operator | ||
|
|
||
| The `|` operator on `TypedDict` instances (PEP 584) preserves the `TypedDict` type when merging with | ||
| the same type, and returns a more specific `dict[str, VT]` type when merging with a plain `dict`. |
There was a problem hiding this comment.
dict[str, VT] seems less specific than the TypedDict type. It's also not clear what VT refers to here (only if you read the implementation below). So maybe dict[str, <union of value types>].
|
I mostly agree with @sharkdp's comments, except for #23408 (comment). For a given from typing import TypedDict
class TD(TypedDict):
x: int
y: str... I think we want to synthesize these overloads for @overload
def __or__(self, other: TD'1) -> Self: ...
@overload
def __or__(self, other: dict[str, Any]) -> dict[str, object]: ...where from typing import TypedDict, NotRequired
class TD'1(TypedDict):
x: NotRequired[int]
y: NotRequired[str]For the second overload, it's tempting to think that we could infer |
Summary
Closes astral-sh/ty#2798.