`TypedDict.copy()` falls back to `dict[str, object]`
Author: dangotbannedCreated Sep 19, 2026Updated Sep 19, 2026
Labelstypechecking
Describe the Bug
Calling copy on a TypedDict is not described in https://typing.python.org/en/latest/spec/typeddict.html#specific-operations
I've found that pyrefly, pyright and ty all disagree on how this should work with type variables.
from __future__ import annotations
from typing_extensions import TypedDict, assert_type, reveal_type
class A(TypedDict, closed=True):
a: int
class B(TypedDict, closed=True):
b: str
a = A(a=1)
b = B(b="b")
a_copy = a.copy()
b_copy = b.copy()
assert_type(a, A)
assert_type(b, B)
def copy_bound[T: A | B](obj: T) -> T:
result = obj.copy() # ty: ignore[invalid-argument-type]
reveal_type(result)
# pyright 'Type of "result" is "A* | B*"'
# ty 'Revealed type: `Unknown`'
# pyrefly 'revealed type: dict[str, object]'
return result
def copy_constrained[T: (A, B)](obj: T) -> T:
result = obj.copy()
reveal_type(result)
# pyright 'Type of "result" is "A* | B*"'
# ty 'Revealed type: `T@copy_constrained`'
# pyrefly 'revealed type: dict[str, object] & T'
return resultI'm reporting here as I found the pyrefly output the most surprising.
Maybe this needs to be defined in the typing spec?
My actual use case for this was a bound type variable, over a union with 20-30 members that all use closed=True.
Sandbox Link
(Only applicable for extension issues) IDE Information
No response
Source: facebook/pyrefly