Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

More precise return types forTypedDict.get#19897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from1 commit
Commits
Show all changes
15 commits
Select commitHold shift + click to select a range
27e50e2
improved TypedDict.get inference
randolf-scholzAug 11, 2025
e708813
use original DictExpr test isntead of type-based
randolf-scholzSep 21, 2025
63224f2
removed second overload
randolf-scholzSep 22, 2025
808f593
added unit test for 19902
randolf-scholzSep 22, 2025
de8effe
Clarify overload comments in checkmember.py
randolf-scholzSep 23, 2025
4ebe192
check total/non-toal against union of keys
randolf-scholzSep 23, 2025
a9d2388
added incremental test
randolf-scholzSep 23, 2025
16c97ba
use appropriate namespace for TypedDict.get variable
randolf-scholzSep 23, 2025
628a0be
Merge branch 'master' into special_case_typeddict_get
randolf-scholzSep 23, 2025
443f6c7
Merge branch 'master' into special_case_typeddict_get
randolf-scholzSep 27, 2025
6bf9401
Merge branch 'master' into special_case_typeddict_get
randolf-scholzOct 4, 2025
cbbbd4d
Merge branch 'master' into special_case_typeddict_get
randolf-scholzOct 11, 2025
63f4975
revert reveal_type changes
randolf-scholzOct 12, 2025
f3e8556
Merge branch 'master' into special_case_typeddict_get
randolf-scholzOct 12, 2025
a0fbcee
Remove extra whitespace
ilevkivskyiOct 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PrevPrevious commit
NextNext commit
revert reveal_type changes
  • Loading branch information
@randolf-scholz
randolf-scholz committedOct 12, 2025
commit63f49759f40316d2ef7ea1ad4c02199fa3bee92f
2 changes: 1 addition & 1 deletionmypy/checkexpr.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1502,7 +1502,7 @@ def check_call_expr_with_callee_type(
def check_union_call_expr(self, e: CallExpr, object_type: UnionType, member: str) -> Type:
"""Type check calling a member expression where the base type is a union."""
res: list[Type] = []
for typ in object_type.relevant_items():
for typ inflatten_nested_unions(object_type.relevant_items()):
# Member access errors are already reported when visiting the member expression.
with self.msg.filter_errors():
item = analyze_member_access(
Expand Down
72 changes: 0 additions & 72 deletionsmypy/checkmember.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,6 @@
from mypy.meet import is_overlapping_types
from mypy.messages import MessageBuilder
from mypy.nodes import (
ARG_OPT,
ARG_POS,
ARG_STAR,
ARG_STAR2,
Expand DownExpand Up@@ -69,7 +68,6 @@
TypedDictType,
TypeOfAny,
TypeType,
TypeVarId,
TypeVarLikeType,
TypeVarTupleType,
TypeVarType,
Expand DownExpand Up@@ -1406,76 +1404,6 @@ def analyze_typeddict_access(
fallback=mx.chk.named_type("builtins.function"),
name=name,
)
elif name == "get":
# synthesize TypedDict.get() overloads
str_type = mx.chk.named_type("builtins.str")
fn_type = mx.chk.named_type("builtins.function")
object_type = mx.chk.named_type("builtins.object")
type_info = typ.fallback.type
# type variable for default value
tvar = TypeVarType(
"T",
f"{type_info.fullname}.get.T",
id=TypeVarId(-1, namespace=f"{type_info.fullname}.get"),
values=[],
upper_bound=object_type,
default=AnyType(TypeOfAny.from_omitted_generics),
)
# generate the overloads
overloads: list[CallableType] = []
for key, value_type in typ.items.items():
key_type = LiteralType(key, fallback=str_type)

if key in typ.required_keys:
# If the key is required, we know it must be present in the TypedDict.
# def (K, object=...) -> V
overload = CallableType(
arg_types=[key_type, object_type],
arg_kinds=[ARG_POS, ARG_OPT],
arg_names=[None, None],
ret_type=value_type,
fallback=fn_type,
name=name,
)
overloads.append(overload)
else:
# The key is not required, but if it is present, we know its type.
# def (K) -> V | None (implicit default)
overload = CallableType(
arg_types=[key_type],
arg_kinds=[ARG_POS],
arg_names=[None],
ret_type=UnionType.make_union([value_type, NoneType()]),
fallback=fn_type,
name=name,
)
overloads.append(overload)

# def [T](K, T) -> V | T (explicit default)
overload = CallableType(
variables=[tvar],
arg_types=[key_type, tvar],
arg_kinds=[ARG_POS, ARG_POS],
arg_names=[None, None],
ret_type=UnionType.make_union([value_type, tvar]),
fallback=fn_type,
name=name,
)
overloads.append(overload)

# finally, add fallback overload when a key is used that is not in the TypedDict
# TODO: add support for extra items (PEP 728)
# def (str, object=...) -> object
fallback_overload = CallableType(
arg_types=[str_type, object_type],
arg_kinds=[ARG_POS, ARG_OPT],
arg_names=[None, None],
ret_type=object_type,
fallback=fn_type,
name=name,
)
overloads.append(fallback_overload)
return Overloaded(overloads)
return _analyze_member_access(name, typ.fallback, mx, override_info)


Expand Down
46 changes: 43 additions & 3 deletionsmypy/plugins/default.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -120,9 +120,9 @@ def get_function_signature_hook(
def get_method_signature_hook(
self, fullname: str
) -> Callable[[MethodSigContext], FunctionLike] | None:
# NOTE: signatures for `__setitem__`, `__delitem__` and `get` are
# defined in checkmember.py/analyze_typeddict_access
if fullname in TD_SETDEFAULT_NAMES:
if fullname == "typing.Mapping.get":
return typed_dict_get_signature_callback
elif fullname in TD_SETDEFAULT_NAMES:
return typed_dict_setdefault_signature_callback
elif fullname in TD_POP_NAMES:
return typed_dict_pop_signature_callback
Expand DownExpand Up@@ -212,6 +212,46 @@ def get_class_decorator_hook_2(
return None


def typed_dict_get_signature_callback(ctx: MethodSigContext) -> CallableType:
"""Try to infer a better signature type for TypedDict.get.

This is used to get better type context for the second argument that
depends on a TypedDict value type.
"""
signature = ctx.default_signature
if (
isinstance(ctx.type, TypedDictType)
and len(ctx.args) == 2
and len(ctx.args[0]) == 1
and isinstance(ctx.args[0][0], StrExpr)
and len(signature.arg_types) == 2
and len(signature.variables) == 1
and len(ctx.args[1]) == 1
):
key = ctx.args[0][0].value
value_type = get_proper_type(ctx.type.items.get(key))
ret_type = signature.ret_type
if value_type:
default_arg = ctx.args[1][0]
if (
isinstance(value_type, TypedDictType)
and isinstance(default_arg, DictExpr)
and len(default_arg.items) == 0
):
# Caller has empty dict {} as default for typed dict.
value_type = value_type.copy_modified(required_keys=set())
# Tweak the signature to include the value type as context. It's
# only needed for type inference since there's a union with a type
# variable that accepts everything.
tv = signature.variables[0]
assert isinstance(tv, TypeVarType)
return signature.copy_modified(
arg_types=[signature.arg_types[0], make_simplified_union([value_type, tv])],
ret_type=ret_type,
)
return signature


def typed_dict_get_callback(ctx: MethodContext) -> Type:
"""Infer a precise return type for TypedDict.get with literal first argument."""
if (
Expand Down
36 changes: 22 additions & 14 deletionstest-data/unit/check-typeddict.test
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1007,12 +1007,12 @@ class D(TypedDict):
b: NotRequired[str]

def test(d: D) -> None:
reveal_type(d.get) # N: Revealed type is\
"Overload(\
def (Literal['a'], builtins.object =) -> builtins.int, \
def (Literal['b']) -> Union[builtins.str, None], \
def [T] (Literal['b'], T`-1) -> Union[builtins.str, T`-1], \
def (builtins.str, builtins.object =) -> builtins.object)"
reveal_type(d.get) # N: Revealed type is"Overload(def (k: builtins.str) -> builtins.object, def (builtins.str, builtins.object) -> builtins.object, def [V] (builtins.str, V`4) -> builtins.object)"






[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]
Expand DownExpand Up@@ -1155,21 +1155,29 @@ reveal_type(d.get('x', a)) # N: Revealed type is "Union[builtins.list[builtins.i
from typing import TypedDict
D = TypedDict('D', {'x': int, 'y': str})
d: D
d.get() # E: All overload variants of "get" require at least one argument \
d.get() # E: All overload variants of "get"of "Mapping"require at least one argument \
# N: Possible overload variants: \
# N: def get(Literal['x'], object = ..., /) ->int \
# N: def get(Literal['y'], object = ..., /) ->str \
# N: def get(str,object = ..., /) -> object
d.get('x', 1, 2) # E: No overload variant of "get" matches argument types "str", "int", "int" \
# N: def get(self, k: str) ->object \
# N: def get(self, str, object, /) ->object \
# N: def[V]get(self,str,V, /) -> object
d.get('x', 1, 2) # E: No overload variant of "get"of "Mapping"matches argument types "str", "int", "int" \
# N: Possible overload variants: \
# N: def get(Literal['x'], object = ..., /) ->int \
# N: def get(Literal['y'], object = ..., /) ->str \
# N: def get(str,object = ..., /) -> object
# N: def get(self, k: str) ->object \
# N: def get(self, str, object, /) ->object \
# N: def[V]get(self,str,Union[int, V], /) -> object
x = d.get('z')
reveal_type(x) # N: Revealed type is "builtins.object"
s = ''
y = d.get(s)
reveal_type(y) # N: Revealed type is "builtins.object"








[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]

Expand Down
4 changes: 3 additions & 1 deletiontest-data/unit/fixtures/typing-typeddict.pyi
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,7 +56,9 @@ class Mapping(Iterable[T], Generic[T, T_co], metaclass=ABCMeta):
@overload
def get(self, k: T) -> Optional[T_co]: pass
@overload
def get(self, k: T, default: Union[T_co, V]) -> Union[T_co, V]: pass
def get(self, k: T, default: T_co, /) -> Optional[T_co]: pass # type: ignore[misc]
@overload
def get(self, k: T, default: V, /) -> Union[T_co, V]: pass
def values(self) -> Iterable[T_co]: pass # Approximate return type
def __len__(self) -> int: ...
def __contains__(self, arg: object) -> int: pass
Expand Down
18 changes: 8 additions & 10 deletionstest-data/unit/pythoneval.test
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1056,22 +1056,20 @@ def test_not_total(d: D_not_total) -> None:
_testTypedDictGet.py:8: note: Revealed type is "builtins.int"
_testTypedDictGet.py:9: note: Revealed type is "builtins.str"
_testTypedDictGet.py:10: note: Revealed type is "builtins.object"
_testTypedDictGet.py:11: error: All overload variants of "get" require at least one argument
_testTypedDictGet.py:11: error: All overload variants of "get"of "Mapping"require at least one argument
_testTypedDictGet.py:11: note: Possible overload variants:
_testTypedDictGet.py:11: note: def get(Literal['x'], object = ..., /) ->int
_testTypedDictGet.py:11: note: def get(Literal['y'], object = ..., /) ->str
_testTypedDictGet.py:11: note: def get(str, object = ..., /) -> object
_testTypedDictGet.py:11: note: def get(self, str, /) ->object
_testTypedDictGet.py:11: note: def get(self, str, /, default: object) ->object
_testTypedDictGet.py:11: note: def[_T]get(self, str, /, default: _T) -> object
_testTypedDictGet.py:13: note: Revealed type is "builtins.object"
_testTypedDictGet.py:16: note: Revealed type is "Union[builtins.int, None]"
_testTypedDictGet.py:17: note: Revealed type is "Union[builtins.str, None]"
_testTypedDictGet.py:18: note: Revealed type is "builtins.object"
_testTypedDictGet.py:19: error: All overload variants of "get" require at least one argument
_testTypedDictGet.py:19: error: All overload variants of "get"of "Mapping"require at least one argument
_testTypedDictGet.py:19: note: Possible overload variants:
_testTypedDictGet.py:19: note: def get(Literal['x'], /) -> Optional[int]
_testTypedDictGet.py:19: note: def [T] get(Literal['x'], T, /) -> Union[int, T]
_testTypedDictGet.py:19: note: def get(Literal['y'], /) -> Optional[str]
_testTypedDictGet.py:19: note: def [T] get(Literal['y'], T, /) -> Union[str, T]
_testTypedDictGet.py:19: note: def get(str, object = ..., /) -> object
_testTypedDictGet.py:19: note: def get(self, str, /) -> object
_testTypedDictGet.py:19: note: def get(self, str, /, default: object) -> object
_testTypedDictGet.py:19: note: def [_T] get(self, str, /, default: _T) -> object
_testTypedDictGet.py:21: note: Revealed type is "builtins.object"

[case testTypedDictMappingMethods]
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp