Skip to content

Commit f515135

Browse files
committed
Get empty dict issue
1 parent 3b00002 commit f515135

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

mypy/checkexpr.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,32 @@ def module_type(self, node: MypyFile) -> Instance:
473473

474474
def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
475475
"""Type check a call expression."""
476+
if (
477+
self.refers_to_typeddict(e.callee)
478+
or isinstance(e.callee, IndexExpr)
479+
and self.refers_to_typeddict(e.callee.base)
480+
):
481+
typeddict_callable = get_proper_type(self.accept(e.callee, is_callee=True))
482+
if isinstance(typeddict_callable, CallableType):
483+
typeddict_type = get_proper_type(typeddict_callable.ret_type)
484+
assert isinstance(typeddict_type, TypedDictType)
485+
return self.check_typeddict_call(
486+
e, typeddict_type, typeddict_callable
487+
)
488+
489+
# Add logic to handle the `get` method
490+
if isinstance(e.callee, MemberExpr) and e.callee.name == 'get':
491+
dict_type = self.accept(e.callee.expr)
492+
if isinstance(dict_type, Instance) and dict_type.type.fullname == 'builtins.dict':
493+
key_type = self.accept(e.args[0])
494+
if len(e.args) == 2:
495+
default_type = self.accept(e.args[1])
496+
return UnionType.make_union([dict_type.args[1], default_type])
497+
return UnionType.make_union([dict_type.args[1], NoneType()])
498+
elif isinstance(dict_type, Instance) and dict_type.type.fullname == 'builtins.dict':
499+
# Handle empty dictionary case
500+
return AnyType(TypeOfAny.special_form)
501+
476502
if e.analyzed:
477503
if isinstance(e.analyzed, NamedTupleExpr) and not e.analyzed.is_typed:
478504
# Type check the arguments, but ignore the results. This relies

mypy/checkmember.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from typing import TYPE_CHECKING, Callable, Sequence, cast
6-
6+
from mypy.nodes import ARG_POS, ARG_OPT
77
from mypy import meet, message_registry, subtypes
88
from mypy.erasetype import erase_typevars
99
from mypy.expandtype import (
@@ -203,6 +203,11 @@ def analyze_member_access(
203203
no_deferral=no_deferral,
204204
is_self=is_self,
205205
)
206+
207+
if name == 'get' and isinstance(typ, Instance) and typ.type.fullname == 'builtins.dict':
208+
# Handle overload resolution for dict.get
209+
return analyze_dict_get(typ, context)
210+
206211
result = _analyze_member_access(name, typ, mx, override_info)
207212
possible_literal = get_proper_type(result)
208213
if (
@@ -214,7 +219,17 @@ def analyze_member_access(
214219
else:
215220
return result
216221

217-
222+
def analyze_dict_get(self, typ: Instance, context: Context) -> Type:
223+
key_type = typ.args[0]
224+
value_type = typ.args[1]
225+
return CallableType(
226+
[key_type, value_type],
227+
[ARG_POS, ARG_OPT],
228+
[None, None],
229+
UnionType.make_union([value_type, NoneType()]),
230+
self.named_type('builtins.function')
231+
)
232+
218233
def _analyze_member_access(
219234
name: str, typ: Type, mx: MemberContext, override_info: TypeInfo | None = None
220235
) -> Type:

test-data/unit/check-dict-get.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[case testDictGetEmpty]
2+
def testDictGetEmpty() -> None:
3+
x = {}.get("x")
4+
reveal_type(x) # N: Revealed type is "None"
5+
6+
[case testDictGetWithDefault]
7+
def testDictGetWithDefault() -> None:
8+
x = {}.get("x", 42)
9+
reveal_type(x) # N: Revealed type is "int"
10+
11+
[case testDictGetExistingKey]
12+
def testDictGetExistingKey() -> None:
13+
x = {"a": 1}.get("a")
14+
reveal_type(x) # N: Revealed type is "int"
15+
16+
[case testDictGetMissingKey]
17+
def testDictGetMissingKey() -> None:
18+
x = {"a": 1}.get("b")
19+
reveal_type(x) # N: Revealed type is "None"

0 commit comments

Comments
 (0)