8000 Refactor plugin system and special case TypedDict get and int.__pow__ by JukkaL · Pull Request #3501 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Refactor plugin system and special case TypedDict get and int.__pow__ #3501

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 7, 2017
Merged
Prev Previous commit
Next Next commit
TypedDict get tweaks
Some of the tests are adapted from #2620 by @rowillia.
  • Loading branch information
JukkaL committed Jun 6, 2017
commit 2cf5cd72057c20a06d521d1354f625ac808d9d76
20 changes: 17 additions & 3 deletions mypy/checkexpr.py
8000
Original file line number Diff line number Diff line change
Expand Up @@ -1848,13 +1848,14 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
# an error, but returns the TypedDict type that matches the literal it found
# that would cause a second error when that TypedDict type is returned upstream
# to avoid the second error, we always return TypedDict type that was requested
if isinstance(self.type_context[-1], TypedDictType):
typeddict_context = self.find_typeddict_context(self.type_context[-1])
if typeddict_context:
self.check_typeddict_call_with_dict(
callee=self.type_context[-1],
callee=typeddict_context,
kwargs=e,
context=e
)
return self.type_context[-1].copy_modified()
return typeddict_context.copy_modified()

# Collect function arguments, watching out for **expr.
args = [] # type: List[Expression] # Regular "key: value"
Expand Down Expand Up @@ -1905,6 +1906,19 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
self.check_call(method, [arg], [nodes.ARG_POS], arg)
return rv

def find_typeddict_context(self, context: Type) -> Optional[TypedDictType]:
if isinstance(context, TypedDictType):
return context
elif isinstance(context, UnionType):
items = []
for item in context.items:
item_context = self.find_typeddict_context(item)
if item_context:
items.append(item_context)
if len(items) == 1:
return items[0]
return None

def visit_lambda_expr(self, e: LambdaExpr) -> Type:
"""Type check lambda expression."""
inferred_type, type_override = self.infer_lambda_type_using_context(e)
Expand Down
3 changes: 0 additions & 3 deletions mypy/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,6 @@ def typed_dict_get_callback(
else:
context.msg.typeddict_item_name_not_found(object_type, key, context.context)
return AnyType()
else:
context.msg.typeddict_item_name_must_be_string_literal(object_type, context.context)
return AnyType()
return inferred_return_type


Expand Down
22 changes: 20 additions & 2 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -784,8 +784,8 @@ d.get('x', 1, 2) # E: No overload variant of "get" of "Mapping" matches argument
x = d.get('z') # E: 'z' is not a valid item name; expected one of ['x', 'y']
reveal_type(x) # E: Revealed type is 'Any'
s = ''
y = d.get(s) # E: Cannot prove expression is a valid item name; expected one of ['x', 'y']
reveal_type(y) # E: Revealed type is 'Any'
y = d.get(s)
reveal_type(y) # E: Revealed type is 'builtins.object*'
[builtins fixtures/dict.pyi]
[typing fixtures/typing-full.pyi]

Expand All @@ -795,3 +795,21 @@ D = TypedDict('D', {'x': int, 'y': str})
d: D
d.bad(1) # E: "D" has no attribute "bad"
[builtins fixtures/dict.pyi]

[case testTypedDictChainedGetMethodWithDictFallback]
from mypy_extensions import TypedDict
D = TypedDict('D', {'x': int, 'y': str})
E = TypedDict('E', {'d': D})
p = E(d=D(x=0, y=''))
reveal_type(p.get('d', {'x': 1, 'y': ''})) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=__main__.D)'
p.get('d', {}) # E: Expected items ['x', 'y'] but found [].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't there a use case where people write p.get('d', {}).get('x') and expect int or None?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but as mentioned in the PR description, handling it safely requires a way of specifying that some TypedDict items may be missing, and we don't have that feature yet. The return type of p.get('d', {}) should be a TypedDict that is like D but where both 'x' and 'y' may be missing. I'll add support for this in a separate PR once we can agree on the syntax (or I can add support for this as an mypy internal feature without public syntax so that missing keys are only generated through type inference).

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

[case testTypedDictGetDefaultParameterStillTypeChecked]
from mypy_extensions import TypedDict
TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int})
p = TaggedPoint(type='2d', x=42, y=1337)
p.get('x', 1 + 'y') # E: Unsupported operand types for + ("int" and "str")
[builtins fixtures/dict.pyi]
[typing fixtures/typing-full.pyi]
0