From 779f20d35822baef48af93b2bd95e84432432d18 Mon Sep 17 00:00:00 2001 From: Jimmy Lai Date: Tue, 17 Dec 2019 09:28:57 -0800 Subject: [PATCH 1/2] add test suite for pyre regression test --- libcst/tests/pyre/simple_class.json | 438 ++++++++++++++++++++++++++++ libcst/tests/pyre/simple_class.py | 23 ++ 2 files changed, 461 insertions(+) create mode 100644 libcst/tests/pyre/simple_class.json create mode 100644 libcst/tests/pyre/simple_class.py diff --git a/libcst/tests/pyre/simple_class.json b/libcst/tests/pyre/simple_class.json new file mode 100644 index 00000000..ebca4774 --- /dev/null +++ b/libcst/tests/pyre/simple_class.json @@ -0,0 +1,438 @@ +{ + "types": [ + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 11, + "column": 26 + }, + "stop": { + "line": 11, + "column": 29 + } + }, + "annotation": "typing.Type[int]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 22, + "column": 4 + }, + "stop": { + "line": 22, + "column": 8 + } + }, + "annotation": "libcst.tests.pyre.simple_class.Item" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 16, + "column": 44 + }, + "stop": { + "line": 16, + "column": 48 + } + }, + "annotation": "typing.Type[libcst.tests.pyre.simple_class.Item]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 22, + "column": 12 + }, + "stop": { + "line": 22, + "column": 17 + } + }, + "annotation": "typing.Sequence[libcst.tests.pyre.simple_class.Item]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 15, + "column": 6 + }, + "stop": { + "line": 15, + "column": 19 + } + }, + "annotation": "typing.Type[libcst.tests.pyre.simple_class.ItemCollector]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 16, + "column": 18 + }, + "stop": { + "line": 16, + "column": 22 + } + }, + "annotation": "libcst.tests.pyre.simple_class.ItemCollector" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 17, + "column": 16 + }, + "stop": { + "line": 17, + "column": 22 + } + }, + "annotation": "libcst.tests.pyre.simple_class.Item" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 21, + "column": 8 + }, + "stop": { + "line": 21, + "column": 17 + } + }, + "annotation": "libcst.tests.pyre.simple_class.ItemCollector" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 12, + "column": 22 + }, + "stop": { + "line": 12, + "column": 23 + } + }, + "annotation": "int" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 16, + "column": 27 + }, + "stop": { + "line": 16, + "column": 30 + } + }, + "annotation": "typing.Type[int]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 16, + "column": 35 + }, + "stop": { + "line": 16, + "column": 43 + } + }, + "annotation": "typing.Callable(typing.GenericMeta.__getitem__)[[typing.Type[Variable[typing._T_co](covariant)]], typing.Type[typing.Sequence[Variable[typing._T_co](covariant)]]]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 20, + "column": 0 + }, + "stop": { + "line": 20, + "column": 9 + } + }, + "annotation": "libcst.tests.pyre.simple_class.ItemCollector" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 17, + "column": 32 + }, + "stop": { + "line": 17, + "column": 37 + } + }, + "annotation": "typing.Type[range]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 16, + "column": 24 + }, + "stop": { + "line": 16, + "column": 25 + } + }, + "annotation": "int" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 11, + "column": 23 + }, + "stop": { + "line": 11, + "column": 24 + } + }, + "annotation": "int" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 16, + "column": 8 + }, + "stop": { + "line": 16, + "column": 17 + } + }, + "annotation": "typing.Callable(libcst.tests.pyre.simple_class.ItemCollector.get_items)[[Named(self, unknown), Named(n, int)], typing.Sequence[libcst.tests.pyre.simple_class.Item]]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 23, + "column": 4 + }, + "stop": { + "line": 23, + "column": 8 + } + }, + "annotation": "libcst.tests.pyre.simple_class.Item" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 17, + "column": 15 + }, + "stop": { + "line": 17, + "column": 41 + } + }, + "annotation": "typing.List[libcst.tests.pyre.simple_class.Item]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 21, + "column": 0 + }, + "stop": { + "line": 21, + "column": 5 + } + }, + "annotation": "typing.Sequence[libcst.tests.pyre.simple_class.Item]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 11, + "column": 17 + }, + "stop": { + "line": 11, + "column": 21 + } + }, + "annotation": "libcst.tests.pyre.simple_class.Item" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 10, + "column": 6 + }, + "stop": { + "line": 10, + "column": 10 + } + }, + "annotation": "typing.Type[libcst.tests.pyre.simple_class.Item]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 16, + "column": 35 + }, + "stop": { + "line": 16, + "column": 49 + } + }, + "annotation": "typing.Type[typing.Sequence[libcst.tests.pyre.simple_class.Item]]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 20, + "column": 12 + }, + "stop": { + "line": 20, + "column": 25 + } + }, + "annotation": "typing.Type[libcst.tests.pyre.simple_class.ItemCollector]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 21, + "column": 8 + }, + "stop": { + "line": 21, + "column": 27 + } + }, + "annotation": "typing.Callable(libcst.tests.pyre.simple_class.ItemCollector.get_items)[[Named(n, int)], typing.Sequence[libcst.tests.pyre.simple_class.Item]]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 21, + "column": 8 + }, + "stop": { + "line": 21, + "column": 29 + } + }, + "annotation": "typing.Sequence[libcst.tests.pyre.simple_class.Item]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 20, + "column": 12 + }, + "stop": { + "line": 20, + "column": 27 + } + }, + "annotation": "libcst.tests.pyre.simple_class.ItemCollector" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 17, + "column": 38 + }, + "stop": { + "line": 17, + "column": 39 + } + }, + "annotation": "int" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 17, + "column": 32 + }, + "stop": { + "line": 17, + "column": 40 + } + }, + "annotation": "range" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 12, + "column": 8 + }, + "stop": { + "line": 12, + "column": 12 + } + }, + "annotation": "libcst.tests.pyre.simple_class.Item" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 11, + "column": 8 + }, + "stop": { + "line": 11, + "column": 16 + } + }, + "annotation": "typing.Callable(libcst.tests.pyre.simple_class.Item.__init__)[[Named(self, unknown), Named(n, int)], unknown]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 17, + "column": 16 + }, + "stop": { + "line": 17, + "column": 20 + } + }, + "annotation": "typing.Type[libcst.tests.pyre.simple_class.Item]" + } + ] +} diff --git a/libcst/tests/pyre/simple_class.py b/libcst/tests/pyre/simple_class.py new file mode 100644 index 00000000..d3d32e41 --- /dev/null +++ b/libcst/tests/pyre/simple_class.py @@ -0,0 +1,23 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# pyre-strict +from typing import Sequence + + +class Item: + def __init__(self, n: int): + self.number = n + + +class ItemCollector: + def get_items(self, n: int) -> Sequence[Item]: + return [Item() for i in range(n)] + + +collector = ItemCollector() +items = collector.get_items() +for item in items: + item.number From a67dc670b83972f68a1e11bf78bd43aecd71a4c3 Mon Sep 17 00:00:00 2001 From: Jimmy Lai Date: Mon, 23 Dec 2019 15:49:45 -0800 Subject: [PATCH 2/2] upgrade Pyre to 0.0.38 --- .../commands/strip_strings_from_types.py | 4 +- libcst/tests/pyre/simple_class.json | 260 +++++++++++++++++- libcst/tests/test_pyre_integration.py | 134 ++++++++- requirements-dev.txt | 2 +- 4 files changed, 384 insertions(+), 16 deletions(-) diff --git a/libcst/codemod/commands/strip_strings_from_types.py b/libcst/codemod/commands/strip_strings_from_types.py index 9f9dee54..4230ef36 100644 --- a/libcst/codemod/commands/strip_strings_from_types.py +++ b/libcst/codemod/commands/strip_strings_from_types.py @@ -37,8 +37,8 @@ class StripStringsCommand(VisitorBasedCodemodCommand): qualname.name == "typing_extensions.Literal" for qualname in qualnames ), - ), - ), + ) + ) ) def leave_SimpleString( self, original_node: libcst.SimpleString, updated_node: libcst.SimpleString diff --git a/libcst/tests/pyre/simple_class.json b/libcst/tests/pyre/simple_class.json index 7c55af81..1be9c408 100644 --- a/libcst/tests/pyre/simple_class.json +++ b/libcst/tests/pyre/simple_class.json @@ -1,5 +1,19 @@ { "types": [ + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 7, + "column": 19 + }, + "stop": { + "line": 7, + "column": 27 + } + }, + "annotation": "typing.Type[typing.Sequence]" + }, { "location": { "path": "libcst/tests/pyre/simple_class.py", @@ -26,7 +40,7 @@ "column": 16 } }, - "annotation": "typing.Callable(libcst.tests.pyre.simple_class.Item.__init__)[[Named(self, unknown), Named(n, unknown)], unknown]" + "annotation": "typing.Callable(libcst.tests.pyre.simple_class.Item.__init__)[[Named(self, unknown), Named(n, int)], None]" }, { "location": { @@ -42,6 +56,20 @@ }, "annotation": "libcst.tests.pyre.simple_class.Item" }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 11, + "column": 23 + }, + "stop": { + "line": 11, + "column": 24 + } + }, + "annotation": "int" + }, { "location": { "path": "libcst/tests/pyre/simple_class.py", @@ -54,7 +82,21 @@ "column": 29 } }, - "annotation": "typing.Type[]" + "annotation": "typing.Type[int]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 11, + "column": 34 + }, + "stop": { + "line": 11, + "column": 38 + } + }, + "annotation": "None" }, { "location": { @@ -70,6 +112,20 @@ }, "annotation": "libcst.tests.pyre.simple_class.Item" }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 12, + "column": 22 + }, + "stop": { + "line": 12, + "column": 23 + } + }, + "annotation": "int" + }, { "location": { "path": "libcst/tests/pyre/simple_class.py", @@ -96,7 +152,7 @@ "column": 17 } }, - "annotation": "typing.Callable(libcst.tests.pyre.simple_class.ItemCollector.get_items)[[Named(self, unknown), Named(n, unknown)], unknown]" + "annotation": "typing.Callable(libcst.tests.pyre.simple_class.ItemCollector.get_items)[[Named(self, unknown), Named(n, int)], typing.Sequence[libcst.tests.pyre.simple_class.Item]]" }, { "location": { @@ -112,6 +168,20 @@ }, "annotation": "libcst.tests.pyre.simple_class.ItemCollector" }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 16, + "column": 24 + }, + "stop": { + "line": 16, + "column": 25 + } + }, + "annotation": "int" + }, { "location": { "path": "libcst/tests/pyre/simple_class.py", @@ -124,7 +194,35 @@ "column": 30 } }, - "annotation": "typing.Type[]" + "annotation": "typing.Type[int]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 16, + "column": 35 + }, + "stop": { + "line": 16, + "column": 43 + } + }, + "annotation": "typing.Callable(typing.GenericMeta.__getitem__)[[typing.Type[Variable[typing._T_co](covariant)]], typing.Type[typing.Sequence[Variable[typing._T_co](covariant)]]]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 16, + "column": 35 + }, + "stop": { + "line": 16, + "column": 49 + } + }, + "annotation": "typing.Type[typing.Sequence[libcst.tests.pyre.simple_class.Item]]" }, { "location": { @@ -182,6 +280,62 @@ }, "annotation": "libcst.tests.pyre.simple_class.Item" }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 17, + "column": 28 + }, + "stop": { + "line": 17, + "column": 29 + } + }, + "annotation": "int" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 17, + "column": 33 + }, + "stop": { + "line": 17, + "column": 38 + } + }, + "annotation": "typing.Type[range]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 17, + "column": 33 + }, + "stop": { + "line": 17, + "column": 41 + } + }, + "annotation": "range" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 17, + "column": 39 + }, + "stop": { + "line": 17, + "column": 40 + } + }, + "annotation": "int" + }, { "location": { "path": "libcst/tests/pyre/simple_class.py", @@ -210,6 +364,34 @@ }, "annotation": "typing.Type[libcst.tests.pyre.simple_class.ItemCollector]" }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 20, + "column": 12 + }, + "stop": { + "line": 20, + "column": 27 + } + }, + "annotation": "libcst.tests.pyre.simple_class.ItemCollector" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 21, + "column": 0 + }, + "stop": { + "line": 21, + "column": 5 + } + }, + "annotation": "typing.Sequence[libcst.tests.pyre.simple_class.Item]" + }, { "location": { "path": "libcst/tests/pyre/simple_class.py", @@ -224,6 +406,34 @@ }, "annotation": "libcst.tests.pyre.simple_class.ItemCollector" }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 21, + "column": 8 + }, + "stop": { + "line": 21, + "column": 27 + } + }, + "annotation": "typing.Callable(libcst.tests.pyre.simple_class.ItemCollector.get_items)[[Named(n, int)], typing.Sequence[libcst.tests.pyre.simple_class.Item]]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 21, + "column": 8 + }, + "stop": { + "line": 21, + "column": 30 + } + }, + "annotation": "typing.Sequence[libcst.tests.pyre.simple_class.Item]" + }, { "location": { "path": "libcst/tests/pyre/simple_class.py", @@ -237,6 +447,48 @@ } }, "annotation": "typing_extensions.Literal[3]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 22, + "column": 4 + }, + "stop": { + "line": 22, + "column": 8 + } + }, + "annotation": "libcst.tests.pyre.simple_class.Item" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 22, + "column": 12 + }, + "stop": { + "line": 22, + "column": 17 + } + }, + "annotation": "typing.Sequence[libcst.tests.pyre.simple_class.Item]" + }, + { + "location": { + "path": "libcst/tests/pyre/simple_class.py", + "start": { + "line": 23, + "column": 4 + }, + "stop": { + "line": 23, + "column": 8 + } + }, + "annotation": "libcst.tests.pyre.simple_class.Item" } ] } \ No newline at end of file diff --git a/libcst/tests/test_pyre_integration.py b/libcst/tests/test_pyre_integration.py index d964f7b3..f1a9a820 100644 --- a/libcst/tests/test_pyre_integration.py +++ b/libcst/tests/test_pyre_integration.py @@ -8,17 +8,125 @@ import json import subprocess from pathlib import Path -from typing import Tuple +from typing import Dict, List, Mapping, Optional, Sequence, Tuple, Union from mypy_extensions import TypedDict +import libcst as cst +from libcst.metadata import MetadataWrapper, PositionProvider +from libcst.testing.utils import UnitTest, data_provider -def _run_command(command: str) -> Tuple[str, str]: + +TEST_SUITE_PATH: Path = Path(__file__).parent / "pyre" + + +class TypeVerificationVisitor(cst.CSTVisitor): + METADATA_DEPENDENCIES = (PositionProvider,) + + def __init__( + self, lookup: Mapping[Tuple[int, int, int, int], str], test: UnitTest + ) -> None: + self.lookup = lookup + self.test = test + self.attributes: List[cst.Attribute] = [] # stack of Attribute + self.imports: List[Union[cst.Import, cst.ImportFrom]] = [] # stack of imports + self.annotations: List[cst.Annotation] = [] # stack of Annotation + super().__init__() + + def visit_Attribute(self, node: cst.Attribute) -> Optional[bool]: + pos = self.get_metadata(PositionProvider, node) + start = pos.start + end = pos.end + self.attributes.append(node) + tup = (start.line, start.column, end.line, end.column) + + # remove this if condition when the type issues are fixed. + if not any( + node.deep_equals(name) + for name in { + cst.Attribute(cst.Name("self"), attr=cst.Name("number")), + cst.Attribute(cst.Name("item"), attr=cst.Name("number")), + } + ): + self.test.assertIn( + tup, + self.lookup, + f"Attribute node {node} at {tup} found without inferred type.", + ) + + def leave_Attribute(self, original_node: cst.Attribute) -> None: + self.attributes.pop() + + def visit_Name(self, node: cst.Name) -> Optional[bool]: + if ( + len(self.imports) > 0 + or len(self.attributes) > 0 + or len(self.annotations) > 0 + ): + return + pos = self.get_metadata(PositionProvider, node) + start = pos.start + end = pos.end + tup = (start.line, start.column, end.line, end.column) + # remove this if condition when the type issues are fixed. + if not any( + node.deep_equals(name) and tup == _tup + for (name, _tup) in {(cst.Name("i"), (17, 21, 17, 22)),} + ): + self.test.assertIn( + tup, + self.lookup, + f"Name node {node.value} at {tup} found without inferred type.", + ) + + def visit_Import(self, node: cst.Import) -> Optional[bool]: + self.imports.append(node) + + def leave_Import(self, original_node: cst.Import) -> None: + self.imports.pop() + + def visit_ImportFrom(self, node: cst.ImportFrom) -> Optional[bool]: + self.imports.append(node) + + def leave_ImportFrom(self, original_node: cst.ImportFrom) -> None: + self.imports.pop() + + def visit_Annotation(self, node: cst.Annotation) -> Optional[bool]: + self.annotations.append(node) + + def leave_Annotation(self, original_node: cst.Annotation) -> None: + self.annotations.pop() + + +class PyreIntegrationTest(UnitTest): + @data_provider( + ( + (source_path, data_path) + for source_path, data_path in zip( + TEST_SUITE_PATH.glob("*.py"), TEST_SUITE_PATH.glob("*.json") + ) + ) + ) + def test_type_availability(self, source_path: Path, data_path: Path) -> None: + module = cst.parse_module(source_path.read_text()) + data: PyreData = json.loads(data_path.read_text()) + lookup: Dict[Tuple[int, int, int, int], str] = {} + for t in data["types"]: + loc = t["location"] + start = loc["start"] + stop = loc["stop"] + lookup[(start["line"], start["column"], stop["line"], stop["column"])] = t[ + "annotation" + ] + MetadataWrapper(module).visit(TypeVerificationVisitor(lookup, self)) + + +def _run_command(command: str) -> Tuple[str, str, int]: process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True ) stdout, stderr = process.communicate() - return stdout.decode(), stderr.decode() + return stdout.decode(), stderr.decode(), process.returncode class Position(TypedDict): @@ -37,6 +145,10 @@ class InferredType(TypedDict): annotation: str +class PyreData(TypedDict): + types: Sequence[InferredType] + + def _sort_by_position(data: InferredType) -> Tuple[int, int, int, int]: start = data["location"]["start"] stop = data["location"]["stop"] @@ -49,15 +161,19 @@ if __name__ == "__main__": print("start pyre server") stdout: str stderr: str - stdout, stderr = _run_command("pyre") - print(stdout) - print(stderr) + return_code: int + stdout, stderr, return_code = _run_command("pyre") + if return_code != 0: + print(stdout) + print(stderr) - p: Path = Path(__file__).parent / "pyre" - for path in (p).glob("*.py"): + for path in (TEST_SUITE_PATH).glob("*.py"): cmd = f'''pyre query "types(path='{path}')"''' print(cmd) - stdout, stderr = _run_command(cmd) + stdout, stderr, return_code = _run_command(cmd) + if return_code != 0: + print(stdout) + print(stderr) data = json.loads(stdout) data = data["response"][0] del data["path"] diff --git a/requirements-dev.txt b/requirements-dev.txt index eb17b364..7bdc4b6c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,6 +8,6 @@ git+https://github.com/jimmylai/sphinx.git@slots_type_annotation isort==4.3.20 jupyter==1.0.0 nbsphinx==0.4.2 -pyre-check==0.0.36 +pyre-check==0.0.38 sphinx-rtd-theme==0.4.3 prompt-toolkit==2.0.9