Skip to content

Commit 1ee465c

Browse files
authored
Fix --export-ref-info with type variable with value restrictions (#15285)
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.
1 parent caf4787 commit 1ee465c

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

mypy/refinfo.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from mypy.nodes import (
66
LDEF,
77
Expression,
8+
FuncDef,
89
MemberExpr,
910
MypyFile,
1011
NameExpr,
@@ -39,6 +40,14 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
3940
super().visit_member_expr(expr)
4041
self.record_ref_expr(expr)
4142

43+
def visit_func_def(self, func: FuncDef) -> None:
44+
if func.expanded:
45+
for item in func.expanded:
46+
if isinstance(item, FuncDef):
47+
super().visit_func_def(item)
48+
else:
49+
super().visit_func_def(func)
50+
4251
def record_ref_expr(self, expr: RefExpr) -> None:
4352
fullname = None
4453
if expr.kind != LDEF and "." in expr.fullname:

mypy/test/test_ref_info.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Test exporting line-level reference information (undocumented feature)"""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
import os
7+
import sys
8+
9+
from mypy import build
10+
from mypy.modulefinder import BuildSource
11+
from mypy.options import Options
12+
from mypy.test.config import test_temp_dir
13+
from mypy.test.data import DataDrivenTestCase, DataSuite
14+
from mypy.test.helpers import assert_string_arrays_equal
15+
16+
17+
class RefInfoSuite(DataSuite):
18+
required_out_section = True
19+
files = ["ref-info.test"]
20+
21+
def run_case(self, testcase: DataDrivenTestCase) -> None:
22+
options = Options()
23+
options.use_builtins_fixtures = True
24+
options.show_traceback = True
25+
options.export_ref_info = True # This is the flag we are testing
26+
27+
src = "\n".join(testcase.input)
28+
result = build.build(
29+
sources=[BuildSource("main", None, src)], options=options, alt_lib_path=test_temp_dir
30+
)
31+
assert not result.errors
32+
33+
major, minor = sys.version_info[:2]
34+
ref_path = os.path.join(options.cache_dir, f"{major}.{minor}", "__main__.refs.json")
35+
36+
with open(ref_path) as refs_file:
37+
data = json.load(refs_file)
38+
39+
a = []
40+
for item in data:
41+
a.append(f"{item['line']}:{item['column']}:{item['target']}")
42+
43+
assert_string_arrays_equal(
44+
testcase.output, a, f"Invalid output ({testcase.file}, line {testcase.line})"
45+
)

test-data/unit/ref-info.test

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
[case testCallGlobalFunction]
2+
def f() -> None:
3+
g()
4+
5+
def g() -> None:
6+
pass
7+
[out]
8+
2:4:__main__.g
9+
10+
[case testCallMethod]
11+
def f() -> None:
12+
c = C()
13+
if int():
14+
c.method()
15+
16+
class C:
17+
def method(self) -> None: pass
18+
[out]
19+
2:8:__main__.C
20+
3:7:builtins.int
21+
4:8:__main__.C.method
22+
23+
[case testCallStaticMethod]
24+
class C:
25+
def f(self) -> None:
26+
C.static()
27+
self.static()
28+
29+
@classmethod
30+
def cm(cls) -> None:
31+
cls.static()
32+
33+
@staticmethod
34+
def static() -> None: pass
35+
[builtins fixtures/classmethod.pyi]
36+
[out]
37+
3:8:__main__.C
38+
3:8:__main__.C.static
39+
4:8:__main__.C.static
40+
8:8:__main__.C.static
41+
42+
[case testCallClassMethod]
43+
class C:
44+
def f(self) -> None:
45+
C.cm()
46+
self.cm()
47+
48+
@classmethod
49+
def cm(cls) -> None:
50+
cls.cm()
51+
[builtins fixtures/classmethod.pyi]
52+
[out]
53+
3:8:__main__.C
54+
3:8:__main__.C.cm
55+
4:8:__main__.C.cm
56+
8:8:__main__.C.cm
57+
58+
[case testTypeVarWithValueRestriction]
59+
from typing import TypeVar
60+
61+
T = TypeVar("T", "C", "D")
62+
63+
def f(o: T) -> None:
64+
f(o)
65+
o.m()
66+
o.x
67+
68+
class C:
69+
x: int
70+
def m(self) -> None: pass
71+
72+
class D:
73+
x: str
74+
def m(self) -> None: pass
75+
[out]
76+
3:4:typing.TypeVar
77+
3:0:__main__.T
78+
6:4:__main__.f
79+
7:4:__main__.C.m
80+
8:4:__main__.C.x
81+
6:4:__main__.f
82+
7:4:__main__.D.m
83+
8:4:__main__.D.x

test-data/unit/typexport-basic.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,21 @@ CallExpr(7) : builtins.str
10551055
NameExpr(7) : def (x: builtins.str) -> builtins.str
10561056
NameExpr(7) : S
10571057

1058+
[case testTypeVariableWithValueRestrictionInFunction]
1059+
## NameExpr
1060+
from typing import TypeVar
1061+
1062+
T = TypeVar("T", int, str)
1063+
1064+
def f(x: T) -> T:
1065+
y = 1
1066+
return x
1067+
[out]
1068+
NameExpr(7) : builtins.int
1069+
NameExpr(7) : builtins.int
1070+
NameExpr(8) : builtins.int
1071+
NameExpr(8) : builtins.str
1072+
10581073

10591074
-- Binary operations
10601075
-- -----------------

0 commit comments

Comments
 (0)