Skip to content

Commit

Permalink
Copy the CALL_FUNCTION opcode's flags to CALL_METHOD.
Browse files Browse the repository at this point in the history
I can't find any documentation on what these flags should be, but CALL_FUNCTION
and CALL_METHOD are similar enough that it seems reasonable they'd have the
same flags.

This fixes the following 3.7 bug: we have a class whose initializer sets an
attribute to None, but before the attribute is used, another method is called
to set it to its true non-None value. If pytype hits maximum call depth and
skips this method, it thinks that the attribute's value is still None. The vm's
_has_strict_none_origins check filters out the None value, avoiding spurious
errors. In 3.7, _has_strict_none_origins fails to filter out the None because
methods are called via CALL_METHOD rather than CALL_FUNCTION, and CALL_METHOD's
different flags cause its carry_on_to_next() method to evaluate to False,
leading to pytype failing to add an extra node to the CFG after the method is
called, which messes up _has_strict_none_origins' reachability query.

PiperOrigin-RevId: 326713243
  • Loading branch information
rchen152 committed Aug 17, 2020
1 parent aedd9a9 commit 629aa68
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pytype/pyc/opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ class LOAD_METHOD(OpcodeWithArg): # Arg: Index in name list


class CALL_METHOD(OpcodeWithArg): # Arg: #args
FLAGS = HAS_ARGUMENT
FLAGS = HAS_NARGS|HAS_ARGUMENT|HAS_JUNKNOWN
__slots__ = ()


Expand Down
24 changes: 24 additions & 0 deletions pytype/tests/py3/test_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,29 @@ def f(self) -> collections.OrderedDict[str, foo.Foo]:
return __any_object__
""", pythonpath=[d.path])

def test_max_depth(self):
# pytype hits max depth in A.cmp() when trying to instantiate `other`,
# leading to the FromInt() call in __init__ being skipped and pytype
# thinking that other.x is None. However, pytype should not report an
# attribute error on other.Upper() because vm._has_strict_none_origins
# filters out the None.
self.Check("""
from typing import Union
class A:
def __init__(self, x: int):
self.x = None
self.FromInt(x)
def cmp(self, other: 'A') -> bool:
return self.Upper() < other.Upper()
def FromInt(self, x: int) -> None:
self.x = 'x'
def Upper(self) -> str:
return self.x.upper()
""", maximum_depth=2)


test_base.main(globals(), __name__ == "__main__")

0 comments on commit 629aa68

Please sign in to comment.