Skip to content

Commit 94a185c

Browse files
DinoVmeta-codesync[bot]
authored andcommitted
More Pyrefly binding support
Summary: The static compiler has some additional metadata beyond types that it needs. This fills in those details so that we're able to get _pytree compiling. Also includes a fix for protocol methods so that we don't complain that they have the wrong return type. Reviewed By: martindemello Differential Revision: D96958741 fbshipit-source-id: 065f10c319dfe08c0a0c2c7cfe3b9a4407934afe
1 parent d36eec1 commit 94a185c

5 files changed

Lines changed: 145 additions & 23 deletions

File tree

cinderx/PythonLib/cinderx/compiler/static/pyrefly_compiler.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
import ast
55
import sys
6+
from ast import AST
67
from typing import Callable, Iterable
78

9+
from cinderx.compiler.errors import TypedSyntaxError
810
from cinderx.compiler.static.pyrefly_info import EMPTY_TYPE_INFO, Pyrefly
911
from cinderx.compiler.static.pyrefly_type_binder import PyreflyTypeBinder
1012
from cinderx.compiler.static.type_binder import TypeBinder
@@ -56,6 +58,39 @@ def get_flags(
5658
override_flags
5759
)
5860

61+
def add_module(
62+
self,
63+
name: str,
64+
filename: str,
65+
tree: AST,
66+
source: str | bytes | ast.Module | ast.Expression | ast.Interactive,
67+
optimize: int,
68+
) -> ast.Module:
69+
from cinderx.compiler.optimizer import AstOptimizer
70+
from cinderx.compiler.static.declaration_visitor import DeclarationVisitor
71+
72+
optimized = AstOptimizer(optimize=optimize > 0).visit(tree)
73+
assert isinstance(optimized, ast.Module)
74+
tree = optimized
75+
76+
self.ast_cache[source] = tree
77+
78+
validate_classes = not self.decl_visitors
79+
decl_visit = DeclarationVisitor(name, filename, self, optimize)
80+
self.decl_visitors.append(decl_visit)
81+
decl_visit.visit(tree)
82+
decl_visit.finish_bind()
83+
84+
if validate_classes:
85+
while self.decl_visitors:
86+
decl_visit = self.decl_visitors.popleft()
87+
try:
88+
decl_visit.module.validate_overrides()
89+
except TypedSyntaxError:
90+
pass
91+
92+
return tree
93+
5994
# pyre-ignore[14]: Pyre thinks the `compiler: Compiler` argument is inconsistent
6095
def make_type_binder(
6196
self,

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,6 @@ def resolve_classname(
254254
return result.inexact_type()
255255
elif isinstance(result, Value):
256256
return result.klass
257-
return None
258257

259258
# Try well-known types (e.g. __static__.int64)
260259
well_known = type_env.name_to_type.get(qname)
@@ -266,7 +265,7 @@ def resolve_classname(
266265
builtins = modules.get("builtins")
267266
if builtins is not None:
268267
# pyre-fixme[61]: `parts` is undefined, or not always defined.
269-
result = builtins.get_child(parts[0], "builtins")
268+
result = builtins.get_child(qname, "builtins")
270269
if isinstance(result, Class):
271270
return result
272271

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

Lines changed: 107 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,29 @@
55
from __future__ import annotations
66

77
import ast
8-
from ast import AST, Name, Return
8+
from ast import AST, Attribute, Call, Compare, Constant, Expr, Name, Return
9+
from collections.abc import Sequence
910
from typing import TYPE_CHECKING
1011

1112
from ..errors import TypedSyntaxError
1213
from ..symbols import SymbolVisitor
1314
from .effects import NarrowingEffect
14-
from .module_table import ModuleTable
1515
from .pyrefly_info import PyreflyTypeInfo
16-
from .type_binder import TerminalKind, TypeBinder
17-
from .types import CInstance, Class, CType
16+
from .type_binder import (
17+
PRESERVE_REFINED_FIELDS,
18+
PreserveRefinedFields,
19+
TerminalKind,
20+
TypeBinder,
21+
)
22+
from .types import (
23+
# CInstance,
24+
# Class,
25+
# CType,
26+
Dataclass,
27+
DataclassField,
28+
ModuleInstance,
29+
TypeDescr,
30+
)
1831

1932
if TYPE_CHECKING:
2033
from .compiler import Compiler
@@ -44,29 +57,105 @@ def __init__(
4457
self._type_info = type_info
4558

4659
def visit(self, node: AST, *args: object) -> NarrowingEffect | None:
47-
ret = super().visit(node, *args)
4860
if isinstance(node, ast.expr) and self._type_info is not None:
49-
# If the parent visitor already resolved to a C primitive type
50-
# (e.g. `x: int64 = 0` promotes 0 to int64, or a Name referencing
51-
# a CType like int64), don't override with the pyrefly-inferred type.
52-
existing_type = self.get_type(node)
53-
if isinstance(existing_type, (CInstance, CType)):
54-
return ret
55-
61+
ret = super().generic_visit(node, *args)
5662
# pyre-fixme[16]: Optional type has no attribute `lookup`.
5763
declared_type = self._type_info.lookup(node, self.modules, self.type_env)
5864

5965
if declared_type is None:
6066
declared_type = self.type_env.dynamic.instance
61-
67+
if isinstance(node, (ast.List, ast.ListComp)):
68+
declared_type = self.type_env.list.instance
69+
elif isinstance(node, (ast.Dict, ast.DictComp)):
70+
declared_type = self.type_env.dict.instance
6271
self.set_type(node, declared_type)
63-
if isinstance(node, Name) and isinstance(node.ctx, ast.Store):
64-
try:
65-
self.declare_local(node.id, declared_type)
66-
except TypedSyntaxError:
67-
pass # already declared, just update the type
72+
73+
if isinstance(node, Compare):
74+
for op in node.ops:
75+
self.set_type(op, self.type_env.DYNAMIC)
76+
77+
# Name: set PreserveRefinedFields (always), declare locals for
78+
# Store context, and set TypeDescr for module-level names so
79+
# that bind_call can emit direct invocations. When pyrefly
80+
# doesn't resolve the type, fall back to the module table so
81+
# CinderX-specific types (e.g. ModuleInstance) are preserved.
82+
elif isinstance(node, Name):
83+
self.set_node_data(node, PreserveRefinedFields, PRESERVE_REFINED_FIELDS)
84+
if isinstance(node.ctx, ast.Store):
85+
try:
86+
self.declare_local(node.id, declared_type)
87+
except TypedSyntaxError:
88+
pass # already declared, just update the type
89+
mod_typ, descr = self.module.resolve_name_with_descr(
90+
node.id, self.context_qualname
91+
)
92+
if descr is not None:
93+
self.set_node_data(node, TypeDescr, descr)
94+
if (
95+
mod_typ is not None
96+
and declared_type is self.type_env.dynamic.instance
97+
):
98+
self.set_type(node, mod_typ)
99+
100+
# Attribute: when the base is a ModuleInstance, set TypeDescr
101+
# for direct access and call bind_attr to resolve from the
102+
# CinderX module table — this ensures CinderX-specific types
103+
# (e.g. DataclassFieldFunction, DataclassDecorator) are used.
104+
# Set PreserveRefinedFields when the attribute is refinable.
105+
elif isinstance(node, Attribute):
106+
base = self.get_type(node.value)
107+
if isinstance(base, ModuleInstance):
108+
self.set_node_data(
109+
node, TypeDescr, ((base.module_name,), node.attr)
110+
)
111+
# Always call bind_attr for module attributes so that
112+
# CinderX-specific types (e.g. DataclassFieldFunction,
113+
# DataclassDecorator) are used regardless of what
114+
# pyrefly resolved.
115+
base.bind_attr(node, self, None)
116+
if self.is_refinable(node):
117+
self.set_node_data(
118+
node, PreserveRefinedFields, PRESERVE_REFINED_FIELDS
119+
)
120+
121+
# Call: invoke bind_call on the func's type to populate
122+
# ArgMapping (and ClassCallInfo for class instantiation).
123+
elif isinstance(node, Call):
124+
self.get_type(node.func).bind_call(node, self, None)
125+
# When pyrefly compiles modules that aren't normally
126+
# statically compiled, dataclasses.field() may resolve
127+
# to DataclassField via the CinderX module table.
128+
# In non-Dataclass classes (e.g. decorated with
129+
# @deprecated on top of @dataclass), this causes type
130+
# errors in visitAnnAssign. Reset to dynamic and mark
131+
# the class for non-static compilation in that case.
132+
if isinstance(self.get_type(node), DataclassField) and not (
133+
isinstance(self.scope, ast.ClassDef)
134+
and isinstance(self.get_type(self.scope), Dataclass)
135+
):
136+
self.set_type(node, declared_type)
137+
if isinstance(self.scope, ast.ClassDef):
138+
self.module.compile_non_static.add(self.scope)
139+
else:
140+
return super().visit(node, *args)
141+
68142
return ret
69143

144+
def visit_check_terminal(self, nodes: Sequence[ast.stmt]) -> TerminalKind:
145+
# Treat a body consisting of just `...` (Ellipsis) as a terminal
146+
# statement, so that stub functions like Protocol methods don't
147+
# trigger "can implicitly return None" errors.
148+
if (
149+
len(nodes) == 1
150+
and isinstance(nodes[0], Expr)
151+
and isinstance(nodes[0].value, Constant)
152+
and nodes[0].value.value is ...
153+
):
154+
self.visit(nodes[0])
155+
self.set_terminal_kind(nodes[0], TerminalKind.RaiseOrReturn)
156+
return TerminalKind.RaiseOrReturn
157+
return super().visit_check_terminal(nodes)
158+
70159
def visitReturn(self, node: Return) -> None:
71160
self.set_terminal_kind(node, TerminalKind.RaiseOrReturn)
72161
if node.value is not None:

cinderx/PythonLib/test_cinderx/test_compiler/test_static/pyreflytests/method_call.test.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,5 @@
66
def verify(test: PyreBinderTests, code: CodeType) -> None:
77
"""Verify that the compiled code includes INVOKE_METHOD."""
88

9-
# TODO: Enable this when things are working
109
g = test.find_code(code, "g")
11-
test.assertInBytecode(g, "INVOKE_METHOD")
10+
test.assertInBytecode(g, "INVOKE_FUNCTION")

cinderx/PythonLib/test_cinderx/test_compiler/test_static/pyreflytests/property.test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ def verify(test: PyreBinderTests, code: CodeType) -> None:
77
"""Verify that the compiled code includes INVOKE_METHOD."""
88

99
g = test.find_code(code, "g")
10-
test.assertInBytecode(g, "INVOKE_METHOD")
10+
test.assertInBytecode(g, "INVOKE_FUNCTION")

0 commit comments

Comments
 (0)