From 2603d92a52618403d081a9be9845fc39161023e2 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 22 May 2023 16:14:59 +0100 Subject: [PATCH 1/4] Add test case --- test-data/unit/typexport-basic.test | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test-data/unit/typexport-basic.test b/test-data/unit/typexport-basic.test index cd4071eb14ee..0dcd0098f177 100644 --- a/test-data/unit/typexport-basic.test +++ b/test-data/unit/typexport-basic.test @@ -1055,6 +1055,21 @@ CallExpr(7) : builtins.str NameExpr(7) : def (x: builtins.str) -> builtins.str NameExpr(7) : S +[case testTypeVariableWithValueRestrictionInFunction] +## NameExpr +from typing import TypeVar + +T = TypeVar("T", int, str) + +def f(x: T) -> T: + y = 1 + return x +[out] +NameExpr(7) : builtins.int +NameExpr(7) : builtins.int +NameExpr(8) : builtins.int +NameExpr(8) : builtins.str + -- Binary operations -- ----------------- From b78b0843b5ad82fdc329f706aacc4b1466f5bfc2 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 22 May 2023 16:52:41 +0100 Subject: [PATCH 2/4] Fix --export-ref-info with type variable with value restrictions The regular function body has no type information if the function uses a type variable with a value restriction in its signature. Instead look at the expanded versions of the function body. This will produce duplicate references for some expressions, but that seems benign. Also add a foundation for writing tests for --export-ref-info and add a few test cases. --- mypy/refinfo.py | 9 ++++ mypy/test/test_ref_info.py | 46 ++++++++++++++++++++ test-data/unit/ref-info.test | 83 ++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 mypy/test/test_ref_info.py create mode 100644 test-data/unit/ref-info.test diff --git a/mypy/refinfo.py b/mypy/refinfo.py index e3a5ef3dc6b5..a5b92832bb7e 100644 --- a/mypy/refinfo.py +++ b/mypy/refinfo.py @@ -5,6 +5,7 @@ from mypy.nodes import ( LDEF, Expression, + FuncDef, MemberExpr, MypyFile, NameExpr, @@ -39,6 +40,14 @@ def visit_member_expr(self, expr: MemberExpr) -> None: super().visit_member_expr(expr) self.record_ref_expr(expr) + def visit_func_def(self, func: FuncDef) -> None: + if func.expanded: + for item in func.expanded: + if isinstance(item, FuncDef): + super().visit_func_def(item) + else: + super().visit_func_def(func) + def record_ref_expr(self, expr: RefExpr) -> None: fullname = None if expr.kind != LDEF and "." in expr.fullname: diff --git a/mypy/test/test_ref_info.py b/mypy/test/test_ref_info.py new file mode 100644 index 000000000000..d676f48a5906 --- /dev/null +++ b/mypy/test/test_ref_info.py @@ -0,0 +1,46 @@ +"""Test exporting line-level reference information (undocumented feature)""" + +from __future__ import annotations + +import json +import os +import sys +import tempfile + +from mypy import build +from mypy.modulefinder import BuildSource +from mypy.test.data import DataDrivenTestCase, DataSuite +from mypy.options import Options +from mypy.test.config import test_temp_dir +from mypy.test.helpers import assert_string_arrays_equal + + +class RefInfoSuite(DataSuite): + required_out_section = True + files = ["ref-info.test"] + + def run_case(self, testcase: DataDrivenTestCase) -> None: + options = Options() + options.use_builtins_fixtures = True + options.show_traceback = True + options.export_ref_info = True # This is the flag we are testing + + src = "\n".join(testcase.input) + result = build.build( + sources=[BuildSource("main", None, src)], options=options, alt_lib_path=test_temp_dir + ) + assert not result.errors + + major, minor = sys.version_info[:2] + ref_path = os.path.join(options.cache_dir, f"{major}.{minor}", "__main__.refs.json") + + with open(ref_path) as refs_file: + data = json.load(refs_file) + + a = [] + for item in data: + a.append(f"{item['line']}:{item['column']}:{item['target']}") + + assert_string_arrays_equal( + testcase.output, a, f"Invalid output ({testcase.file}, line {testcase.line})" + ) diff --git a/test-data/unit/ref-info.test b/test-data/unit/ref-info.test new file mode 100644 index 000000000000..05426130d272 --- /dev/null +++ b/test-data/unit/ref-info.test @@ -0,0 +1,83 @@ +[case testCallGlobalFunction] +def f() -> None: + g() + +def g() -> None: + pass +[out] +2:4:__main__.g + +[case testCallMethod] +def f() -> None: + c = C() + if int(): + c.method() + +class C: + def method(self) -> None: pass +[out] +2:8:__main__.C +3:7:builtins.int +4:8:__main__.C.method + +[case testCallStaticMethod] +class C: + def f(self) -> None: + C.static() + self.static() + + @classmethod + def cm(cls) -> None: + cls.static() + + @staticmethod + def static() -> None: pass +[builtins fixtures/classmethod.pyi] +[out] +3:8:__main__.C +3:8:__main__.C.static +4:8:__main__.C.static +8:8:__main__.C.static + +[case testCallClassMethod] +class C: + def f(self) -> None: + C.cm() + self.cm() + + @classmethod + def cm(cls) -> None: + cls.cm() +[builtins fixtures/classmethod.pyi] +[out] +3:8:__main__.C +3:8:__main__.C.cm +4:8:__main__.C.cm +8:8:__main__.C.cm + +[case testTypeVarWithValueRestriction] +from typing import TypeVar + +T = TypeVar("T", "C", "D") + +def f(o: T) -> None: + f(o) + o.m() + o.x + +class C: + x: int + def m(self) -> None: pass + +class D: + x: str + def m(self) -> None: pass +[out] +3:4:typing.TypeVar +3:0:__main__.T +6:4:__main__.f +7:4:__main__.C.m +8:4:__main__.C.x +6:4:__main__.f +7:4:__main__.D.m +8:4:__main__.D.x From 9b885863649d078bc42dc98054a6b06970146cde Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 16:20:41 +0000 Subject: [PATCH 3/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/test/test_ref_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/test/test_ref_info.py b/mypy/test/test_ref_info.py index d676f48a5906..14076914db8a 100644 --- a/mypy/test/test_ref_info.py +++ b/mypy/test/test_ref_info.py @@ -9,9 +9,9 @@ from mypy import build from mypy.modulefinder import BuildSource -from mypy.test.data import DataDrivenTestCase, DataSuite from mypy.options import Options from mypy.test.config import test_temp_dir +from mypy.test.data import DataDrivenTestCase, DataSuite from mypy.test.helpers import assert_string_arrays_equal From 827499f2fde7ec4bb81b799ef7a71044b9ddc01c Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 22 May 2023 17:26:32 +0100 Subject: [PATCH 4/4] Fix lint --- mypy/test/test_ref_info.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/test/test_ref_info.py b/mypy/test/test_ref_info.py index 14076914db8a..05052e491657 100644 --- a/mypy/test/test_ref_info.py +++ b/mypy/test/test_ref_info.py @@ -5,7 +5,6 @@ import json import os import sys -import tempfile from mypy import build from mypy.modulefinder import BuildSource