Skip to content

Commit

Permalink
Merge pull request #609 from google/google_sync
Browse files Browse the repository at this point in the history
Google sync
  • Loading branch information
rchen152 committed Jun 26, 2020
2 parents 7f6df73 + f3f5f04 commit e9ff943
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version 2020.06.26
* Treat objects as True in a boolean context, unless explicitly overridden.
* If cls is the class argument of Foo.__new__, treat `cls is Foo` as ambiguous.
* Add basic support for third-party flax dataclasses.
* Autodetect number of jobs with --jobs auto.

Version 2020.06.01
* Update typeshed pin to commit 5fe6a5b from May 18.
* Support callback protocols.
Expand Down
2 changes: 1 addition & 1 deletion pytype/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# pylint: skip-file
__version__ = '2020.06.01'
__version__ = '2020.06.26'
9 changes: 1 addition & 8 deletions pytype/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from pytype import function
from pytype import metrics
from pytype import output
from pytype import special_builtins
from pytype import state as frame_state
from pytype import vm
from pytype.overlays import typing_overlay
Expand Down Expand Up @@ -176,8 +175,6 @@ def maybe_analyze_method(self, node, val, cls=None):
else:
for f in method.iter_signature_functions():
node, args = self.create_method_arguments(node, f)
if f.is_classmethod and cls:
args = self._maybe_fix_classmethod_cls_arg(node, cls, f, args)
node, _ = self.call_function_with_args(node, val, args)
return node

Expand Down Expand Up @@ -230,11 +227,7 @@ def analyze_method_var(self, node0, name, var, cls=None):
def bind_method(self, node, name, methodvar, instance_var):
bound = self.program.NewVariable()
for m in methodvar.Data(node):
if isinstance(m, special_builtins.ClassMethodInstance):
m = m.func.data[0]
is_cls = True
else:
is_cls = (m.isinstance_InterpreterFunction() and m.is_classmethod)
is_cls = False
bound.AddBinding(m.property_get(instance_var, is_cls), [], node)
return bound

Expand Down
2 changes: 2 additions & 0 deletions pytype/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ def value_to_pytd_type(self, node, v, seen, view):
elif isinstance(v, typing_overlay.TypeVar):
return pytd.NamedType("__builtin__.type")
elif isinstance(v, dataclass_overlay.FieldInstance):
if not v.default:
return pytd.AnythingType()
return pytd_utils.JoinTypes(
self.value_to_pytd_type(node, d, seen, view)
for d in v.default.data)
Expand Down
32 changes: 32 additions & 0 deletions pytype/tests/py3/test_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,38 @@ def f(x: Any):
x.upper()
""")

def test_non_init_annotation(self):
ty, errors = self.InferWithErrors("""
from typing import List
class Foo:
def __init__(self):
# This annotation should be used when inferring the attribute type.
self.x = [] # type: List[int]
def f1(self):
# This annotation should be applied to the attribute value but ignored
# when inferring the attribute type.
self.x = [] # type: List[str]
return self.x
def f2(self):
# This assignment should be checked against the __init__ annotation.
self.x = [''] # annotation-type-mismatch[e]
def f3(self):
# The return type should reflect all assignments, even ones that
# violate the __init__ annotation.
return self.x
""")
self.assertTypesMatchPytd(ty, """
from typing import List, Union
class Foo:
x: List[int]
def __init__(self) -> None: ...
def f1(self) -> List[str]: ...
def f2(self) -> None: ...
def f3(self) -> List[Union[int, str]]: ...
""")
self.assertErrorRegexes(
errors, {"e": r"Annotation: List\[int\].*Assignment: List\[str\]"})


class TestAttributesPython3FeatureTest(test_base.TargetPython3FeatureTest):
"""Tests for attributes over target code using Python 3 features."""
Expand Down
15 changes: 15 additions & 0 deletions pytype/tests/py3/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,4 +480,19 @@ class Foo(object):
def __init__(self, x: bool, y: int, z: str) -> None: ...
""")

def test_redefine_field(self):
# Tests that pytype can infer types for this (simplified) snippet of code
# from flax.struct.py.
ty = self.Infer("""
import dataclasses
def field(**kwargs):
return dataclasses.field(**kwargs)
""")
self.assertTypesMatchPytd(ty, """
from typing import Any
dataclasses: module
def field(**kwargs) -> Any: ...
""")


test_base.main(globals(), __name__ == "__main__")
2 changes: 2 additions & 0 deletions pytype/tests/test_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class Foo(object):
def bar(cls) -> None: ...
""")

@test_base.skip("Temporary rollback")
def test_factory_classmethod(self):
ty = self.Infer("""
class Foo(object):
Expand All @@ -180,6 +181,7 @@ class Foo:
def factory(cls: Type[_TFoo], *args, **kwargs) -> _TFoo: ...
""")

@test_base.skip("Temporary rollback")
def test_classmethod_return_inference(self):
ty = self.Infer("""
class Foo(object):
Expand Down
1 change: 1 addition & 0 deletions pytype/tests/test_cmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ class Foo:
def __new__(cls: Type[_TFoo], *args, **kwargs) -> _TFoo: ...
""")

@test_base.skip("Temporary rollback")
def test_class_factory(self):
# The assert should not block inference of the return type, since cls could
# be a subclass of Foo
Expand Down
7 changes: 5 additions & 2 deletions pytype/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,10 @@ def _apply_annotation(
if annotations_dict is not None:
if annotations_dict is self.current_annotated_locals:
self._record_local(state.node, op, name, typ, orig_val)
else:
elif name not in annotations_dict or not annotations_dict[name].typ:
# When updating non-local annotations, we only record the first one
# encountered so that if, say, an instance attribute is annotated in
# both __init__ and another method, the __init__ annotation is used.
self._update_annotations_dict(
state.node, op, name, typ, orig_val, annotations_dict)
if typ is None and name in annotations_dict:
Expand Down Expand Up @@ -1601,7 +1604,7 @@ def _is_classmethod_cls_arg(self, var):
return False

func = self.frame.func.data
if func.is_classmethod or func.name.rsplit(".")[-1] == "__new__":
if func.name.rsplit(".")[-1] == "__new__":
is_cls = not set(var.data) - set(self.frame.first_posarg.data)
return is_cls
return False
Expand Down

0 comments on commit e9ff943

Please sign in to comment.