From 4e8d414dd14f7282ddbc85a9207dae06f68160d3 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 26 Jan 2017 18:51:13 +0300 Subject: [PATCH 1/4] Convention for test data which uses APIs defined by Python stubs Currently we check only for syntax errors in stub files. The idea is to add test data for static analyzers similar to how it's done in DefinitelyTyped for TypeScript. --- CONTRIBUTING.md | 2 ++ test_data/stdlib/2and3/collections_test.py | 14 ++++++++++++++ tests/mypy_test.py | 2 +- tests/pytype_test.py | 8 +++++++- 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 test_data/stdlib/2and3/collections_test.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf1eaa689eb7..616538dab9e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,8 @@ are important to the project's success. * [Contact us](#discussion) before starting significant work. * IMPORTANT: For new libraries, [get permission from the library owner first](#adding-a-new-library). * Create your stubs [conforming to the coding style](#stub-file-coding-style). + * Add tests for your stubs to `test_data`. These tests are not meant + to be executable. Type checkers analyze them statically. * Make sure `runtests.sh` passes cleanly on Mypy, pytype, and flake8. 4. [Submit your changes](#submitting-changes): * Open a pull request diff --git a/test_data/stdlib/2and3/collections_test.py b/test_data/stdlib/2and3/collections_test.py new file mode 100644 index 000000000000..114bac7ad1df --- /dev/null +++ b/test_data/stdlib/2and3/collections_test.py @@ -0,0 +1,14 @@ +from collections import namedtuple + + +def test_collections_namedtuple(): + # type: () -> None + Point = namedtuple('Point', 'x y') + p = Point(1, 2) + + p._replace(y=3.14) + p._asdict() + p.x, p.y + p[0] + p[1] + p.index(1) + Point._make([('x', 1), ('y', 3.14)]) diff --git a/tests/mypy_test.py b/tests/mypy_test.py index d474009e4d01..6a4e53510e60 100755 --- a/tests/mypy_test.py +++ b/tests/mypy_test.py @@ -63,7 +63,7 @@ def libpath(major, minor): versions.append('2and3') paths = [] for v in versions: - for top in ['stdlib', 'third_party']: + for top in ['stdlib', 'third_party', 'test_data/stdlib', 'test_data/third_party']: p = os.path.join(top, v) if os.path.isdir(p): paths.append(p) diff --git a/tests/pytype_test.py b/tests/pytype_test.py index e7475c0be115..60cc4ae029f6 100755 --- a/tests/pytype_test.py +++ b/tests/pytype_test.py @@ -70,7 +70,7 @@ def pytype_test(args): print("Cannot run pytd. Did you install pytype?") return 0, 0 - wanted = re.compile(r"stdlib/(2|2\.7|2and3)/.*\.pyi$") + wanted = re.compile(r"stdlib/(2|2\.7|2and3)/.*\.pyi?$") skipped = re.compile("(%s)$" % "|".join(load_blacklist())) files = [] @@ -80,6 +80,12 @@ def pytype_test(args): if wanted.search(f) and not skipped.search(f): files.append(f) + for root, _, filenames in os.walk("test_data/stdlib"): + for f in sorted(filenames): + f = os.path.join(root, f) + if wanted.search(f) and not skipped.search(f): + files.append(f) + running_tests = collections.deque() max_code, runs, errors = 0, 0, 0 print("Running pytype tests...") From 43317c788454ab07d816f718b2a00a17c1a279ba Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 26 Jan 2017 18:51:26 +0300 Subject: [PATCH 2/4] Methods of typing.NamedTuple --- stdlib/2/typing.pyi | 15 ++++++++++++--- stdlib/3/typing.pyi | 15 ++++++++++++--- test_data/stdlib/2and3/typing_test.py | 14 ++++++++++++++ test_data/stdlib/3.6/typing_test.py | 16 ++++++++++++++++ 4 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 test_data/stdlib/2and3/typing_test.py create mode 100644 test_data/stdlib/3.6/typing_test.py diff --git a/stdlib/2/typing.pyi b/stdlib/2/typing.pyi index e591333f3d34..b7da4d0d6bdd 100644 --- a/stdlib/2/typing.pyi +++ b/stdlib/2/typing.pyi @@ -355,8 +355,17 @@ def cast(tp: Type[_T], obj: Any) -> _T: ... # Type constructors -# NamedTuple is special-cased in the type checker; the initializer is ignored. -def NamedTuple(typename: str, fields: Iterable[Tuple[str, Any]], *, - verbose: bool = ..., rename: bool = ...) -> Type[tuple]: ... +# NamedTuple is special-cased in the type checker +class NamedTuple(tuple): + _fields = ... # type: Tuple[str, ...] + + def __init__(self, typename: str, fields: Iterable[Tuple[str, Any]], *, + verbose: bool = ..., rename: bool = ...) -> None: ... + + @classmethod + def _make(cls, iterable: Iterable[Any]) -> NamedTuple: ... + + def _asdict(self) -> dict: ... + def _replace(self, **kwargs: Any) -> NamedTuple: ... def NewType(name: str, tp: Type[_T]) -> Type[_T]: ... diff --git a/stdlib/3/typing.pyi b/stdlib/3/typing.pyi index c48f4a8b18c9..01fd87f841e8 100644 --- a/stdlib/3/typing.pyi +++ b/stdlib/3/typing.pyi @@ -462,8 +462,17 @@ def cast(tp: Type[_T], obj: Any) -> _T: ... # Type constructors -# NamedTuple is special-cased in the type checker; the initializer is ignored. -def NamedTuple(typename: str, fields: Iterable[Tuple[str, Any]], *, - verbose: bool = ..., rename: bool = ..., module: str = None) -> Type[tuple]: ... +# NamedTuple is special-cased in the type checker +class NamedTuple(tuple): + _fields = ... # type: Tuple[str, ...] + + def __init__(self, typename: str, fields: Iterable[Tuple[str, Any]], *, + verbose: bool = ..., rename: bool = ..., module: Any = ...) -> None: ... + + @classmethod + def _make(cls, iterable: Iterable[Any]) -> NamedTuple: ... + + def _asdict(self) -> dict: ... + def _replace(self, **kwargs: Any) -> NamedTuple: ... def NewType(name: str, tp: Type[_T]) -> Type[_T]: ... diff --git a/test_data/stdlib/2and3/typing_test.py b/test_data/stdlib/2and3/typing_test.py new file mode 100644 index 000000000000..e2015ff431ab --- /dev/null +++ b/test_data/stdlib/2and3/typing_test.py @@ -0,0 +1,14 @@ +from typing import NamedTuple + + +def test_typing_namedtuple(): + # type: () -> None + Point = NamedTuple('Point', [('x', float), ('y', float)]) + p = Point(1, 2) + + p._replace(y=3.14) + p._asdict() + p.x, p.y + p[0] + p[1] + p.index(1) + Point._make([('x', 1), ('y', 3.14)]) diff --git a/test_data/stdlib/3.6/typing_test.py b/test_data/stdlib/3.6/typing_test.py new file mode 100644 index 000000000000..6ee0c0794cb1 --- /dev/null +++ b/test_data/stdlib/3.6/typing_test.py @@ -0,0 +1,16 @@ +from typing import NamedTuple + + +class Point(NamedTuple): + x: float + y: float + + +def test_typing_namedtuple(): + # type: () -> None + p = Point(1, 2) + p._asdict() + p.x, p.y + p[0] + p[1] + p.index(1) + Point._make([('x', 1), ('y', 3.14)]) From 601da3e43566f8727f0d7658c25bccb145396c76 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 26 Jan 2017 19:12:18 +0300 Subject: [PATCH 3/4] Temporarily disabled checking test_data/ due to a bug in pytype --- tests/pytype_test.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/pytype_test.py b/tests/pytype_test.py index 60cc4ae029f6..ec292bf2cd86 100755 --- a/tests/pytype_test.py +++ b/tests/pytype_test.py @@ -80,11 +80,14 @@ def pytype_test(args): if wanted.search(f) and not skipped.search(f): files.append(f) - for root, _, filenames in os.walk("test_data/stdlib"): - for f in sorted(filenames): - f = os.path.join(root, f) - if wanted.search(f) and not skipped.search(f): - files.append(f) + # XXX: Temporarily disabled checking test_data/ due to a bug + # in pytype related to handling type hints in function comments + + # for root, _, filenames in os.walk("test_data/stdlib"): + # for f in sorted(filenames): + # f = os.path.join(root, f) + # if wanted.search(f) and not skipped.search(f): + # files.append(f) running_tests = collections.deque() max_code, runs, errors = 0, 0, 0 From 7b2c12c5d1a9df9a64feefaad5c8604ed5a54ade Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 26 Jan 2017 19:34:31 +0300 Subject: [PATCH 4/4] Added object.__sizeof__ --- stdlib/2/__builtin__.pyi | 1 + stdlib/3/builtins.pyi | 1 + test_data/stdlib/2and3/builtins_test.py | 8 ++++++++ 3 files changed, 10 insertions(+) create mode 100644 test_data/stdlib/2and3/builtins_test.py diff --git a/stdlib/2/__builtin__.pyi b/stdlib/2/__builtin__.pyi index 82b8e7890df5..0c2357bccfa9 100644 --- a/stdlib/2/__builtin__.pyi +++ b/stdlib/2/__builtin__.pyi @@ -43,6 +43,7 @@ class object: def __format__(self, format_spec: str) -> str: ... def __getattribute__(self, name: str) -> Any: ... def __delattr__(self, name: str) -> None: ... + def __sizeof__(self) -> int: ... class type: __bases__ = ... # type: Tuple[type, ...] diff --git a/stdlib/3/builtins.pyi b/stdlib/3/builtins.pyi index 8068890bdfaa..7a215bdc50cf 100644 --- a/stdlib/3/builtins.pyi +++ b/stdlib/3/builtins.pyi @@ -46,6 +46,7 @@ class object: def __format__(self, format_spec: str) -> str: ... def __getattribute__(self, name: str) -> Any: ... def __delattr__(self, name: str) -> None: ... + def __sizeof__(self) -> int: ... if sys.version_info >= (3, 6): def __init_subclass__(cls) -> None: ... diff --git a/test_data/stdlib/2and3/builtins_test.py b/test_data/stdlib/2and3/builtins_test.py new file mode 100644 index 000000000000..118f30f085a7 --- /dev/null +++ b/test_data/stdlib/2and3/builtins_test.py @@ -0,0 +1,8 @@ +def test_object_sizeof(): + # type: () -> None + + class C(object): + pass + + object().__sizeof__() + C().__sizeof__()