Skip to content

Commit 86c5f7a

Browse files
DinoVmeta-codesync[bot]
authored andcommitted
Add support for method calls
Summary: Adds support for method calls and an associated test. Reviewed By: martindemello Differential Revision: D96232153 fbshipit-source-id: 0574d560b23f695f4afaf3c73eef172850491d05
1 parent e34b453 commit 86c5f7a

7 files changed

Lines changed: 296 additions & 85 deletions

File tree

cinderx/PythonLib/cinderx/compiler/static/pyrefly_info.py

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@
66

77
import json
88
import os
9-
from ast import AST
9+
from ast import AST, Attribute
1010
from dataclasses import dataclass
1111
from typing import TypedDict
1212

1313
from cinderx.compiler.static.module_table import ModuleTable
14-
from cinderx.compiler.static.types import Class, TypeEnvironment, Value
14+
from cinderx.compiler.static.types import (
15+
Class,
16+
Function,
17+
MethodType,
18+
TypeEnvironment,
19+
Value,
20+
)
1521

1622

1723
class Location(TypedDict):
@@ -93,65 +99,69 @@ def __init__(self, data: TypeInfo) -> None:
9399
key = LocationInfo.from_location(entry["loc"])
94100
self._locations[key] = entry["type"]
95101

96-
def _type_to_str(self, type_index: int) -> str:
97-
"""Convert a type_table entry to a Python annotation string."""
98-
entry = self._type_table[type_index]
99-
kind = entry["kind"]
100-
if kind == "literal":
101-
return ""
102-
elif kind == "class":
103-
qname = str(entry["qname"])
104-
args = entry.get("args", [])
105-
assert isinstance(args, list)
106-
if not args:
107-
return qname
108-
arg_strs = [self._type_to_str(a) for a in args]
109-
if any(not s for s in arg_strs):
110-
return qname
111-
return f"{qname}[{', '.join(arg_strs)}]"
112-
elif kind == "callable":
113-
params = entry.get("params", [])
114-
assert isinstance(params, list)
115-
ret = entry.get("return_type")
116-
param_strs = [self._type_to_str(p) for p in params]
117-
ret_str = self._type_to_str(ret) if isinstance(ret, int) else ""
118-
if any(not s for s in param_strs) or not ret_str:
119-
return ""
120-
return f"Callable[[{', '.join(param_strs)}], {ret_str}]"
121-
return ""
122-
123102
def _lookup(self, node: AST) -> int | None:
124103
"""Look up the type_table index for an AST node by its source position."""
125104
key = LocationInfo.from_node(node)
126105
return self._locations.get(key)
127106

128-
def lookup(self, node: AST) -> str:
129-
"""Look up the type string for an AST node by its source position."""
107+
def lookup(
108+
self,
109+
node: AST,
110+
modules: dict[str, ModuleTable],
111+
type_env: TypeEnvironment,
112+
) -> Value | None:
113+
"""Look up the type string for an AST node by its source position.
114+
115+
For now, we only try to get type information for class instances
116+
and literals, disregarding any type parameters, bound methods, and
117+
doing nothing if we see a some other type_info kind like a callable.
118+
"""
130119
type_index = self._lookup(node)
131120
if type_index is None:
132-
return ""
133-
return self._type_to_str(type_index)
121+
return None
134122

135-
def _get_type_qname(self, type_index: TypeKind) -> str:
123+
entry = self._type_table[type_index]
124+
# Try non-types
125+
if entry["kind"] == "bound_method":
126+
defining_class_qname = str(entry["defining_class"])
127+
resolved_class = self.resolve_classname(
128+
defining_class_qname, modules, type_env
129+
)
130+
if resolved_class is not None and isinstance(node, Attribute):
131+
member = resolved_class.get_member(node.attr)
132+
if isinstance(member, Function):
133+
return MethodType(
134+
resolved_class.type_name,
135+
member.node,
136+
node.value,
137+
member,
138+
)
139+
140+
# Fallback to types
141+
return self.lookup_type(type_index, modules, type_env)
142+
143+
def lookup_type(
144+
self,
145+
type_index: TypeKind,
146+
modules: dict[str, ModuleTable],
147+
type_env: TypeEnvironment,
148+
) -> Value | None:
136149
entry = self._type_table[type_index]
137150
if entry["kind"] == "class":
138-
# Ignore the generic args
139-
return str(entry["qname"])
151+
qname = str(entry["qname"])
152+
resolved = self.resolve_classname(qname, modules, type_env)
153+
if resolved is not None:
154+
return resolved.instance
140155
elif entry["kind"] == "literal":
141156
if "promoted_type" in entry:
142-
return self._get_type_qname(entry["promoted_type"])
143-
return ""
144-
145-
def lookup_typename(self, node: AST) -> str:
146-
"""Look up the qname for an AST node if its type is a simple class.
157+
return self.lookup_type(entry["promoted_type"], modules, type_env)
158+
elif entry["kind"] == "other_form":
159+
if entry["qname"] == "None":
160+
resolved = self.resolve_classname("builtins.None", modules, type_env)
161+
if resolved is not None:
162+
return resolved.instance
147163

148-
We treat generic classes as their unparametrised "base" version,
149-
e.g. A[T] -> A
150-
"""
151-
type_index = self._lookup(node)
152-
if type_index is None:
153-
return ""
154-
return self._get_type_qname(type_index)
164+
return None
155165

156166
@classmethod
157167
def load_json(cls, json_path: str) -> PyreflyTypeInfo:

cinderx/PythonLib/cinderx/compiler/static/pyrefly_type_binder.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,7 @@ def __init__(
4646
def visit(self, node: AST, *args: object) -> NarrowingEffect | None:
4747
ret = super().visit(node, *args)
4848
if isinstance(node, ast.expr) and self._type_info is not None:
49-
# For now, we only try to get type information for class instances
50-
# and literals, disregarding any type parameters, and doing nothing
51-
# if we see a some other type_info kind like a callable.
52-
classname = self._type_info.lookup_typename(node)
53-
declared_type = None
54-
if classname:
55-
resolved = self._type_info.resolve_classname(
56-
classname, self.modules, self.type_env
57-
)
58-
if resolved is not None:
59-
declared_type = resolved.instance
49+
declared_type = self._type_info.lookup(node, self.modules, self.type_env)
6050

6151
if declared_type is None:
6252
declared_type = self.type_env.dynamic.instance
Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,55 @@
11
{
22
"type_table": [
33
{
4-
"kind": "class",
5-
"qname": "cinderx.PythonLib.test_cinderx.test_compiler.test_static.pyreflytests.simple.C",
4+
"kind": "other_form",
5+
"qname": "None",
66
"args": [],
7-
"hash": 8563325631336090432
7+
"hash": 3543722185832507401
88
},
99
{
10-
"kind": "literal",
11-
"value": "42",
12-
"hash": 8390065111661787820
10+
"kind": "callable",
11+
"params": [],
12+
"return_type": 0,
13+
"defining_func": "cinderx.PythonLib.test_cinderx.test_compiler.test_static.pyreflytests.call.f",
14+
"hash": 14932184105665906002
1315
}
1416
],
1517
"locations": [
1618
{
1719
"loc": {
18-
"start_line": 3,
19-
"start_col": 8,
20-
"end_line": 3,
21-
"end_col": 12
20+
"start_line": 1,
21+
"start_col": 11,
22+
"end_line": 1,
23+
"end_col": 15
24+
},
25+
"type": 0
26+
},
27+
{
28+
"loc": {
29+
"start_line": 5,
30+
"start_col": 11,
31+
"end_line": 5,
32+
"end_col": 15
2233
},
2334
"type": 0
2435
},
2536
{
2637
"loc": {
27-
"start_line": 3,
28-
"start_col": 17,
29-
"end_line": 3,
30-
"end_col": 19
38+
"start_line": 6,
39+
"start_col": 11,
40+
"end_line": 6,
41+
"end_col": 14
42+
},
43+
"type": 0
44+
},
45+
{
46+
"loc": {
47+
"start_line": 6,
48+
"start_col": 11,
49+
"end_line": 6,
50+
"end_col": 12
3151
},
3252
"type": 1
3353
}
3454
]
35-
}
55+
}

cinderx/PythonLib/test_cinderx/test_compiler/test_static/pyreflytests/len.json

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,103 @@
22
"type_table": [
33
{
44
"kind": "class",
5-
"qname": "cinderx.PythonLib.test_cinderx.test_compiler.test_static.pyreflytests.simple.C",
5+
"qname": "builtins.int",
66
"args": [],
7-
"hash": 8563325631336090432
7+
"hash": 7437843174258217660
88
},
99
{
10-
"kind": "literal",
11-
"value": "42",
12-
"hash": 8390065111661787820
10+
"kind": "other_form",
11+
"qname": "typing.Type",
12+
"args": [
13+
0
14+
],
15+
"hash": 6510812805515236383
16+
},
17+
{
18+
"kind": "other_form",
19+
"qname": "typing.Any",
20+
"args": [],
21+
"hash": 16900020518876608212
22+
},
23+
{
24+
"kind": "class",
25+
"qname": "builtins.dict",
26+
"args": [
27+
2,
28+
2
29+
],
30+
"hash": 6944757485787016286
31+
},
32+
{
33+
"kind": "class",
34+
"qname": "typing.Sized",
35+
"args": [],
36+
"hash": 13039512047080031574
37+
},
38+
{
39+
"kind": "callable",
40+
"params": [
41+
4
42+
],
43+
"return_type": 0,
44+
"defining_func": "builtins.len",
45+
"hash": 10918511340362036114
1346
}
1447
],
1548
"locations": [
1649
{
1750
"loc": {
18-
"start_line": 3,
51+
"start_line": 1,
52+
"start_col": 11,
53+
"end_line": 1,
54+
"end_col": 14
55+
},
56+
"type": 1
57+
},
58+
{
59+
"loc": {
60+
"start_line": 2,
61+
"start_col": 4,
62+
"end_line": 2,
63+
"end_col": 5
64+
},
65+
"type": 3
66+
},
67+
{
68+
"loc": {
69+
"start_line": 2,
1970
"start_col": 8,
71+
"end_line": 2,
72+
"end_col": 10
73+
},
74+
"type": 3
75+
},
76+
{
77+
"loc": {
78+
"start_line": 3,
79+
"start_col": 11,
2080
"end_line": 3,
21-
"end_col": 12
81+
"end_col": 17
2282
},
2383
"type": 0
2484
},
2585
{
2686
"loc": {
2787
"start_line": 3,
28-
"start_col": 17,
88+
"start_col": 11,
2989
"end_line": 3,
30-
"end_col": 19
90+
"end_col": 14
3191
},
32-
"type": 1
92+
"type": 5
93+
},
94+
{
95+
"loc": {
96+
"start_line": 3,
97+
"start_col": 15,
98+
"end_line": 3,
99+
"end_col": 16
100+
},
101+
"type": 3
33102
}
34103
]
35-
}
104+
}

0 commit comments

Comments
 (0)