|
5 | 5 | from __future__ import annotations |
6 | 6 |
|
7 | 7 | 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 |
9 | 10 | from typing import TYPE_CHECKING |
10 | 11 |
|
11 | 12 | from ..errors import TypedSyntaxError |
12 | 13 | from ..symbols import SymbolVisitor |
13 | 14 | from .effects import NarrowingEffect |
14 | | -from .module_table import ModuleTable |
15 | 15 | 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 | +) |
18 | 31 |
|
19 | 32 | if TYPE_CHECKING: |
20 | 33 | from .compiler import Compiler |
@@ -44,29 +57,105 @@ def __init__( |
44 | 57 | self._type_info = type_info |
45 | 58 |
|
46 | 59 | def visit(self, node: AST, *args: object) -> NarrowingEffect | None: |
47 | | - ret = super().visit(node, *args) |
48 | 60 | 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) |
56 | 62 | # pyre-fixme[16]: Optional type has no attribute `lookup`. |
57 | 63 | declared_type = self._type_info.lookup(node, self.modules, self.type_env) |
58 | 64 |
|
59 | 65 | if declared_type is None: |
60 | 66 | 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 |
62 | 71 | 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 | + |
68 | 142 | return ret |
69 | 143 |
|
| 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 | + |
70 | 159 | def visitReturn(self, node: Return) -> None: |
71 | 160 | self.set_terminal_kind(node, TerminalKind.RaiseOrReturn) |
72 | 161 | if node.value is not None: |
|
0 commit comments