pyrefly
pyrefly copied to clipboard
[Bug][CLI]: `dict` -> `TypedDict` narrowing fails in `Union` context
Describe the Bug
Thank you for your work!
When assigning a dictionary literal to a variable typed as Union[SomeTypedDict, ...], Pyrefly raises an error, even though the dictoronary can be narrowed to SomeTypedDict.
Example:
from typing import TypedDict
class Foo(TypedDict):
key: str
# ok
bar: Foo = {
"key": "",
}
# error here: dict[str, str] is not assignable to str | TypedDict[Foo]
baz: Foo | str = {
"key": "",
}
Codebase
Sandbox: https://pyrefly.org/sandbox/?code=GYJw9gtgBALgngBwJYDsDmUkQWEMoAqiApgCYAiSAxjAFC1UA2AhgM6tQBiYYAFEQjKUaASgBctKFKgBrYnDFRWMEPQDEUMDNoAjZiEXcwUALxQA3pOkAiOXGuLr1gDS0AvuqjEQ4EFAAW3sSKpNQwANrKIM5KKgC6mBwoYPhsrEhoKMw6jMSwxlFQAD6EJBRh4UZxuswAXoY8xbF%20ZpbSULbyDh0u7kA
Other Attempts
No response
So I think the root cause of this is that our contextual subtyping doesn't work super well with unions, but due to the way TypedDicts are handled, it never works for TypedDicts in unions while it can sometimes work for other types.
We're definitely going to fix this but we may not get to it before PyCon
This is not contextual subtyping, it's due to the subset relation.
from typing import TypedDict, Literal, Any
class Foo(TypedDict):
key1: str
key2: str
bar: Foo = {"key1": "", "key2": ""}
def f1(x: dict[Any, Any]): pass
def f2(x: dict[str, str]): pass
def f3(x: dict[str, bool]): pass
f1(bar) # should work
f2(bar) # should work
f3(bar) # should fail
Actually, I think this is more nuanced. My reproduction is based around the same subset piece, but we really need subset-but-not-mutable, I think. I don't really understand quite enough, so will talk to someone.
@ndmitchell it seems to me that the original example is about contextual typing. Your reproduction case looks wrong to me, because we should never be able to pass a TypedDict into a position that expects a dict type, so all of the calls should fail, including f1 and f2.
I'm going to take a look at the contextual typing part of this.
Discussed with Sam, I'm going to look into this.