From 629aa68504a92647639946f80e6a31e25451310b Mon Sep 17 00:00:00 2001 From: rechen Date: Fri, 14 Aug 2020 12:58:09 -0700 Subject: [PATCH] Copy the CALL_FUNCTION opcode's flags to CALL_METHOD. 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 --- pytype/pyc/opcodes.py | 2 +- pytype/tests/py3/test_methods.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pytype/pyc/opcodes.py b/pytype/pyc/opcodes.py index dfcc04836..780009398 100644 --- a/pytype/pyc/opcodes.py +++ b/pytype/pyc/opcodes.py @@ -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__ = () diff --git a/pytype/tests/py3/test_methods.py b/pytype/tests/py3/test_methods.py index fcb524452..fa7435eae 100644 --- a/pytype/tests/py3/test_methods.py +++ b/pytype/tests/py3/test_methods.py @@ -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__")