Skip to content

Commit

Permalink
Fix a Python 3.9 test failure.
Browse files Browse the repository at this point in the history
This fixes a test failure

in which pytype was unable to find the expected wrong-arg-types error in the
following snippet in 3.9:

from typing import Dict
def f(x, **kwargs: int):
  return kwargs
def g() -> Dict[str, float]:
  return __any_object__
f("", **g())  # wrong-arg-types

Before 3.9, when the return value of a function becomes the **kwargs input to
another function, that return value is used directly. For pytype, this means
that the ParameterizedClass(dict, {_K: str, _V: float}) object constructed as
the return type of g gets successfully passed into f.

Starting in 3.9, the return value is instead passed to the DICT_MERGE opcode
and merged into an empty dict, so the pytype object passed into f ends up being
an instance of a plain dict, with the instance parameters set to Instance(str)
and Instance(float). When this case is detected, we now reconstruct a
parameterized dict class to match the pre-3.9 behavior.

PiperOrigin-RevId: 398787107
  • Loading branch information
rchen152 committed Sep 24, 2021
1 parent e6fa192 commit 53a7c84
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 5 deletions.
4 changes: 4 additions & 0 deletions pytype/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ def build_map(self, node):
"""Create an empty VM dict."""
return abstract.Dict(self.vm).to_variable(node)

def build_map_class(self, node, type_params):
assert set(type_params) == {abstract_utils.K, abstract_utils.V}
return abstract.ParameterizedClass(self.dict_type, type_params, self.vm)

def build_tuple(self, node, content):
"""Create a VM tuple from the given sequence."""
return self.tuple_to_value(content).to_variable(node)
Expand Down
15 changes: 10 additions & 5 deletions pytype/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,11 +485,16 @@ def simplify(self, node, vm, match_signature=None):
# into starstarargs, so set starstarargs to None.
kwdict = starstarargs.data[0]
if kwdict.isinstance_Dict() and kwdict.could_contain_anything:
starstarargs = kwdict.cls.instantiate(node)
for new_kwdict in starstarargs.data:
for param in (abstract_utils.K, abstract_utils.V):
new_kwdict.merge_instance_type_parameter(
node, param, kwdict.get_instance_type_parameter(param, node))
cls = kwdict.cls
if cls.isinstance_PyTDClass():
# If cls is not already parameterized with the key and value types, we
# parameterize it now to preserve them.
params = {
name: vm.convert.merge_classes(kwdict.get_instance_type_parameter(
name, node).data)
for name in (abstract_utils.K, abstract_utils.V)}
cls = vm.convert.build_map_class(node, params)
starstarargs = cls.instantiate(node)
else:
starstarargs = None
starargs_as_tuple = self.starargs_as_tuple(node, vm)
Expand Down

0 comments on commit 53a7c84

Please sign in to comment.