@pineforge/codegen-pyodide 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/pineforge_codegen/analyzer/base.py +797 -45
- package/pineforge_codegen/analyzer/call_handlers.py +368 -68
- package/pineforge_codegen/analyzer/contracts.py +91 -0
- package/pineforge_codegen/analyzer/diagnostics.py +18 -4
- package/pineforge_codegen/analyzer/tables.py +20 -2
- package/pineforge_codegen/analyzer/types.py +53 -0
- package/pineforge_codegen/codegen/__init__.py +4 -0
- package/pineforge_codegen/codegen/base.py +1613 -111
- package/pineforge_codegen/codegen/drawing.py +560 -0
- package/pineforge_codegen/codegen/emit_top.py +536 -45
- package/pineforge_codegen/codegen/input.py +31 -0
- package/pineforge_codegen/codegen/security.py +791 -39
- package/pineforge_codegen/codegen/ta.py +87 -1
- package/pineforge_codegen/codegen/tables.py +115 -10
- package/pineforge_codegen/codegen/types.py +478 -46
- package/pineforge_codegen/codegen/visit_call.py +331 -75
- package/pineforge_codegen/codegen/visit_expr.py +116 -14
- package/pineforge_codegen/codegen/visit_stmt.py +208 -23
- package/pineforge_codegen/parser.py +129 -29
- package/pineforge_codegen/signatures.py +2 -2
- package/pineforge_codegen/support_checker.py +320 -19
- package/pineforge_codegen-0.9.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +7 -10
- package/pineforge_codegen-0.8.0.tar.gz +0 -0
|
@@ -37,6 +37,20 @@ class TACallSite:
|
|
|
37
37
|
returns_tuple: bool # e.g., MACD, supertrend
|
|
38
38
|
node: Any = None # the FuncCall AST node
|
|
39
39
|
is_static: bool = False # true if global scope & arguments are recursively static
|
|
40
|
+
# Name of the user function that OWNS this site, or None for a top-level
|
|
41
|
+
# site. For an ORIGINAL site this is the function whose body textually
|
|
42
|
+
# contains the ``ta.*`` call. For a CLONE minted in
|
|
43
|
+
# ``_handle_user_func_call`` this is the CALLEE (the function being
|
|
44
|
+
# called, NOT the caller whose body visit triggered the clone) -- the
|
|
45
|
+
# clone belongs to the callee's per-call-site namespace.
|
|
46
|
+
#
|
|
47
|
+
# The codegen's dead-code pass keys off this rather than off
|
|
48
|
+
# ``func_ta_ranges`` slices, because a function's slice can include
|
|
49
|
+
# clones of ANOTHER (live) function's sites (minted while visiting a
|
|
50
|
+
# caller's body). Marking such a borrowed clone dead would leave the
|
|
51
|
+
# owning callee's emitted clone body referencing undeclared members
|
|
52
|
+
# (regression: quantbyboji-nq-hma-midday ``_ta_change_*_cs1``).
|
|
53
|
+
owner_func: str | None = None
|
|
40
54
|
|
|
41
55
|
|
|
42
56
|
@dataclass
|
|
@@ -65,6 +79,21 @@ class FuncInfo:
|
|
|
65
79
|
# ``Sample s = build_sample(...)`` then ``s.score()`` dispatches
|
|
66
80
|
# correctly. Probe: data/validation/udt-method-probe-20-udt-return-from-func.
|
|
67
81
|
udt_return_type: str | None = None
|
|
82
|
+
# Parallel to ``node.params``; each entry is a ``TypeSpec`` (or ``None``)
|
|
83
|
+
# carrying UDT / drawing-handle / precise-scalar typing that the coarse
|
|
84
|
+
# ``param_types`` (PineType) cannot represent. Populated from the
|
|
85
|
+
# function's declared parameter type hints (authoritative) and, for
|
|
86
|
+
# untyped params, from the call-site argument type. The codegen prefers
|
|
87
|
+
# this over ``param_types`` when emitting each parameter's C++ type so a
|
|
88
|
+
# ``pivot hi`` parameter emits as ``pivot hi`` (not ``double hi``) and an
|
|
89
|
+
# untyped ``s`` used as a string emits as ``std::string s``.
|
|
90
|
+
param_type_specs: list = field(default_factory=list)
|
|
91
|
+
# ``TypeSpec`` of the function's return value when it is a collection the
|
|
92
|
+
# coarse ``return_type`` (PineType) cannot represent — today this covers
|
|
93
|
+
# array-returning functions (``buildPDLevels() => array.from(...)`` ->
|
|
94
|
+
# ``std::vector<double>``). UDT / drawing-handle returns use
|
|
95
|
+
# ``udt_return_type``; tuple returns use ``returns_tuple``.
|
|
96
|
+
return_type_spec: Any = None
|
|
68
97
|
|
|
69
98
|
|
|
70
99
|
@dataclass
|
|
@@ -72,6 +101,13 @@ class FixnanCallSite:
|
|
|
72
101
|
"""Per-call-site state for ``fixnan(...)`` (one previous-value member each)."""
|
|
73
102
|
member_name: str # e.g., "_prev_fixnan_1"
|
|
74
103
|
pine_type: Any # PineType
|
|
104
|
+
node: Any = None # the FuncCall AST node (for variant-aware lookup)
|
|
105
|
+
# Name of the user function that OWNS this site, or None for a top-level
|
|
106
|
+
# site. Mirrors ``TACallSite.owner_func``: the codegen's dead-code pass
|
|
107
|
+
# and per-variant clone logic key off this so a fixnan site minted inside
|
|
108
|
+
# a dead caller's body but cloned for a live callee survives, and each
|
|
109
|
+
# emitted function variant references its OWN fixnan member.
|
|
110
|
+
owner_func: str | None = None
|
|
75
111
|
|
|
76
112
|
|
|
77
113
|
@dataclass
|
|
@@ -104,9 +140,33 @@ class SecurityCallInfo:
|
|
|
104
140
|
gaps: Any = None
|
|
105
141
|
lookahead: Any = None
|
|
106
142
|
ta_range: Any = None
|
|
143
|
+
# request.security(ticker.heikinashi(syminfo.tickerid), ...) — the chart's own
|
|
144
|
+
# symbol read as Heikin-Ashi candles. The engine applies the HA OHLC transform
|
|
145
|
+
# to each (aggregated) bar before evaluating the security expression; the
|
|
146
|
+
# expression's close/open/high/low therefore see HA values. Same-symbol only.
|
|
147
|
+
heikinashi: bool = False
|
|
107
148
|
depends_on_mutable_globals: bool = False
|
|
108
149
|
mutable_globals: tuple[str, ...] = ()
|
|
109
150
|
is_lower_tf_array: bool = False
|
|
151
|
+
# Name of the user function whose body contains this call ("" at global
|
|
152
|
+
# scope). A ``request.security(sym, tf, ...)`` whose ``tf`` is that
|
|
153
|
+
# function's parameter cannot be resolved at class scope (the security
|
|
154
|
+
# evaluator is a class method, not the function body) — the codegen resolves
|
|
155
|
+
# such a param-tf from the function's call sites instead.
|
|
156
|
+
containing_func: str = ""
|
|
157
|
+
# Set when this SecurityCallInfo is one of N per-call-site CLONES of a
|
|
158
|
+
# single source request.security(...) whose ``timeframe`` is a UDF
|
|
159
|
+
# parameter called from >= 2 sites with >= 2 distinct literal timeframes
|
|
160
|
+
# (e.g. ``scoreFromRange(tf) => request.security(sym, tf, ...)`` called as
|
|
161
|
+
# ``scoreFromRange("15")``, ``scoreFromRange("240")``, ...). Each clone's
|
|
162
|
+
# ``timeframe`` is rewritten to a literal StringLiteral for its call site
|
|
163
|
+
# and gets its own unique ``sec_id``; ``callsite_idx`` is the SAME index
|
|
164
|
+
# ``func_call_cs_map`` assigns that call site for the existing per-
|
|
165
|
+
# call-site UDF-body-cloning mechanism, so the codegen can pick the right
|
|
166
|
+
# clone's ``sec_id`` while emitting that call site's specialized function
|
|
167
|
+
# body (``self._active_call_site_idx``). None for an ordinary
|
|
168
|
+
# (non-cloned) security call.
|
|
169
|
+
callsite_idx: int | None = None
|
|
110
170
|
|
|
111
171
|
|
|
112
172
|
@dataclass
|
|
@@ -125,6 +185,14 @@ class AnalyzerContext:
|
|
|
125
185
|
var_members: list = field(default_factory=list) # [(name, PineType, init_expr_str)]
|
|
126
186
|
func_infos: list = field(default_factory=list)
|
|
127
187
|
fixnan_sites: list = field(default_factory=list)
|
|
188
|
+
# Per-function fixnan site ownership (func_name -> list of indices into
|
|
189
|
+
# ``fixnan_sites``). Used by the codegen to clone fixnan members per
|
|
190
|
+
# call-site variant and to skip fixnan state owned by dead functions.
|
|
191
|
+
func_fixnan_indices: dict = field(default_factory=dict)
|
|
192
|
+
# (func_name, cs_idx) -> {orig_member_name: cloned_member_name}. Like
|
|
193
|
+
# ``func_cs_ta_clone_names`` but for fixnan: populated only when the
|
|
194
|
+
# default ``{base}_cs{cs_idx}`` clone name collides.
|
|
195
|
+
func_cs_fixnan_clone_names: dict = field(default_factory=dict)
|
|
128
196
|
strategy_params: dict = field(default_factory=dict)
|
|
129
197
|
diagnostics: list = field(default_factory=list) # warnings
|
|
130
198
|
filename: str = "<stdin>"
|
|
@@ -135,6 +203,21 @@ class AnalyzerContext:
|
|
|
135
203
|
func_ta_ranges: dict = field(default_factory=dict) # func_name -> (start_idx, end_idx)
|
|
136
204
|
func_call_cs_map: dict = field(default_factory=dict) # call_node_id -> (func_name, call_site_index)
|
|
137
205
|
func_call_site_counts: dict = field(default_factory=dict) # func_name -> int
|
|
206
|
+
# Functions that need per-call-site body cloning PURELY because they
|
|
207
|
+
# contain a request.security(...) whose timeframe is a UDF parameter
|
|
208
|
+
# called with >= 2 distinct literal timeframes (the security-tf-
|
|
209
|
+
# monomorphization case), even though they have no TA call sites or
|
|
210
|
+
# series/var members of their own (the usual trigger for cloning). The
|
|
211
|
+
# codegen's function-emission gate ORs this in alongside has_ta/
|
|
212
|
+
# has_series so self._active_call_site_idx is actually set while it
|
|
213
|
+
# emits each call site's body — required for the per-clone
|
|
214
|
+
# SecurityCallInfo.callsite_idx disambiguation in visit_call.py to work.
|
|
215
|
+
func_security_clone_only: set = field(default_factory=set)
|
|
216
|
+
# (func_name, cs_idx) -> {orig_member_name: cloned_member_name}. Populated by
|
|
217
|
+
# the analyzer ONLY for clones whose default ``{base}_cs{cs_idx}`` name would
|
|
218
|
+
# collide with a clone minted through another enclosing function; lets codegen
|
|
219
|
+
# use the disambiguated name instead of re-deriving a colliding one.
|
|
220
|
+
func_cs_ta_clone_names: dict = field(default_factory=dict)
|
|
138
221
|
# UDT / enum definitions:
|
|
139
222
|
udt_defs: dict = field(default_factory=dict) # type_name -> {field_name: PineType}
|
|
140
223
|
enum_defs: dict = field(default_factory=dict) # enum_name -> [member names]
|
|
@@ -145,12 +228,20 @@ class AnalyzerContext:
|
|
|
145
228
|
# Per-function var_members + series_vars (used when emitting per-function call-site variants):
|
|
146
229
|
func_var_members: dict = field(default_factory=dict)
|
|
147
230
|
func_series_vars: dict = field(default_factory=dict)
|
|
231
|
+
# Per-function array-return TypeSpec (see FuncInfo.return_type_spec).
|
|
232
|
+
func_return_type_specs: dict = field(default_factory=dict)
|
|
148
233
|
# var_name -> UDT type name for variables instantiated via TypeName.new(...)
|
|
149
234
|
udt_var_types: dict[str, str] = field(default_factory=dict)
|
|
150
235
|
# var_name -> structured collection/UDT type metadata
|
|
151
236
|
collection_types: dict[str, TypeSpec] = field(default_factory=dict)
|
|
152
237
|
# UDT name -> field_name -> structured type metadata
|
|
153
238
|
udt_field_type_specs: dict[str, dict[str, TypeSpec]] = field(default_factory=dict)
|
|
239
|
+
# id(block_node) -> {raw_var_name: scope_unique_member_name} for block-scoped
|
|
240
|
+
# ``var``/``varip`` declarations whose raw name collides with a same-named
|
|
241
|
+
# block var in a sibling scope. Codegen activates the rename via
|
|
242
|
+
# ``_active_var_remap`` while emitting that block's statements so reads/writes
|
|
243
|
+
# of the var resolve to the disambiguated member.
|
|
244
|
+
block_var_renames: dict[int, dict[str, str]] = field(default_factory=dict)
|
|
154
245
|
# ``// @pf-trace name=expr`` pragmas in source order. Populated by
|
|
155
246
|
# :func:`pineforge_codegen.pragmas.extract_pf_trace_pragmas` from
|
|
156
247
|
# the original source text and attached after :class:`Analyzer`
|
|
@@ -89,8 +89,22 @@ class DiagnosticsHelper:
|
|
|
89
89
|
loc,
|
|
90
90
|
)
|
|
91
91
|
|
|
92
|
+
# Non-atomic node kinds whose serialized infix form must be parenthesized
|
|
93
|
+
# when used as an operand, so the string re-parses to the SAME tree. A TA
|
|
94
|
+
# ctor-arg string produced here is later re-parsed and lowered by the
|
|
95
|
+
# codegen (``_runtime_ctor_arg_for_reset``); a flattened ``(a - b) / c``
|
|
96
|
+
# would otherwise silently reassociate under C++ precedence.
|
|
97
|
+
_NONATOMIC_EXPR_NODES = (BinOp, UnaryOp, Ternary)
|
|
98
|
+
|
|
99
|
+
def _operand_to_str(self, node: ASTNode) -> str:
|
|
100
|
+
s = self._expr_to_str(node)
|
|
101
|
+
if isinstance(node, self._NONATOMIC_EXPR_NODES):
|
|
102
|
+
return f"({s})"
|
|
103
|
+
return s
|
|
104
|
+
|
|
92
105
|
def _expr_to_str(self, node: ASTNode) -> str:
|
|
93
|
-
"""Convert an expression node to a
|
|
106
|
+
"""Convert an expression node to a string that re-parses to the same
|
|
107
|
+
tree (grouping preserved for non-atomic operands)."""
|
|
94
108
|
if isinstance(node, NumberLiteral):
|
|
95
109
|
return str(node.value)
|
|
96
110
|
if isinstance(node, StringLiteral):
|
|
@@ -104,9 +118,9 @@ class DiagnosticsHelper:
|
|
|
104
118
|
if isinstance(node, MemberAccess):
|
|
105
119
|
return f"{self._expr_to_str(node.object)}.{node.member}"
|
|
106
120
|
if isinstance(node, BinOp):
|
|
107
|
-
return f"{self.
|
|
121
|
+
return f"{self._operand_to_str(node.left)} {node.op} {self._operand_to_str(node.right)}"
|
|
108
122
|
if isinstance(node, UnaryOp):
|
|
109
|
-
return f"{node.op}{self.
|
|
123
|
+
return f"{node.op}{self._operand_to_str(node.operand)}"
|
|
110
124
|
if isinstance(node, FuncCall):
|
|
111
125
|
args = ", ".join(self._expr_to_str(a) for a in node.args)
|
|
112
126
|
callee_str = self._expr_to_str(node.callee)
|
|
@@ -114,5 +128,5 @@ class DiagnosticsHelper:
|
|
|
114
128
|
if isinstance(node, Subscript):
|
|
115
129
|
return f"{self._expr_to_str(node.object)}[{self._expr_to_str(node.index)}]"
|
|
116
130
|
if isinstance(node, Ternary):
|
|
117
|
-
return f"{self.
|
|
131
|
+
return f"{self._operand_to_str(node.condition)} ? {self._operand_to_str(node.true_val)} : {self._operand_to_str(node.false_val)}"
|
|
118
132
|
return "<?>"
|
|
@@ -110,7 +110,7 @@ TA_CLASS_MAP = {
|
|
|
110
110
|
TA_PERIOD_ARG = {
|
|
111
111
|
"sma": 1, "ema": 1, "rma": 1, "rsi": 1, "atr": 0,
|
|
112
112
|
"highest": 1, "lowest": 1, "change": 1,
|
|
113
|
-
"wma": 1, "hma": 1,
|
|
113
|
+
"wma": 1, "hma": 1,
|
|
114
114
|
# Task 6
|
|
115
115
|
"sum": 1,
|
|
116
116
|
# Task 7 Batch 1
|
|
@@ -119,7 +119,7 @@ TA_PERIOD_ARG = {
|
|
|
119
119
|
"mom": 1, "roc": 1, "rising": 1, "falling": 1, "cci": 1,
|
|
120
120
|
# cum has no period arg — handled in TA_NO_CTOR
|
|
121
121
|
# Task 7 Batch 3
|
|
122
|
-
"
|
|
122
|
+
"median": 1, "highestbars": 1, "lowestbars": 1,
|
|
123
123
|
# Batch 4
|
|
124
124
|
"cmo": 1, "cog": 1, "correlation": 2,
|
|
125
125
|
"percentile_nearest_rank": 1, "percentile_linear_interpolation": 1,
|
|
@@ -130,6 +130,14 @@ TA_PERIOD_ARG = {
|
|
|
130
130
|
|
|
131
131
|
# Functions that return tuples
|
|
132
132
|
TA_TUPLE_RETURNS = {"macd", "supertrend", "dmi", "bb", "kc", "vwap_bands"}
|
|
133
|
+
TA_TUPLE_ELEMENT_COUNTS = {
|
|
134
|
+
"macd": 3,
|
|
135
|
+
"supertrend": 2,
|
|
136
|
+
"dmi": 3,
|
|
137
|
+
"bb": 3,
|
|
138
|
+
"kc": 3,
|
|
139
|
+
"vwap_bands": 3,
|
|
140
|
+
}
|
|
133
141
|
|
|
134
142
|
# Functions with multiple constructor args
|
|
135
143
|
TA_MULTI_CTOR = {
|
|
@@ -156,6 +164,16 @@ TA_MULTI_CTOR = {
|
|
|
156
164
|
"bbw": [1, 2], # length, mult
|
|
157
165
|
"kcw": [1, 2], # length, mult
|
|
158
166
|
"tr": [0], # handle_na (compile-time bool)
|
|
167
|
+
"stdev": [1, 2], # length, biased
|
|
168
|
+
"variance": [1, 2], # length, biased
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Compute-arg indices: which positional args are forwarded to ``.compute()``.
|
|
172
|
+
# Entries here override the default analyzer behavior of forwarding every
|
|
173
|
+
# non-constructor argument.
|
|
174
|
+
TA_COMPUTE_ARGS = {
|
|
175
|
+
"stdev": [0],
|
|
176
|
+
"variance": [0],
|
|
159
177
|
}
|
|
160
178
|
|
|
161
179
|
# No-state functions (no constructor args, stateless or self-contained)
|
|
@@ -45,6 +45,12 @@ from ..ast_nodes import (
|
|
|
45
45
|
)
|
|
46
46
|
from ..symbols import PineType, TypeSpec
|
|
47
47
|
|
|
48
|
+
# Drawing-objects-as-data type names (spec §4.1). Defined locally — the
|
|
49
|
+
# analyzer must not import from ``codegen`` (codegen imports analyzer, so the
|
|
50
|
+
# reverse would be a cycle). Mirrors codegen.tables.DRAWING_TYPE_TO_CPP keys.
|
|
51
|
+
_DRAWING_TYPE_NAMES = frozenset({"line", "box", "label", "linefill", "chart.point"})
|
|
52
|
+
_DRAWING_NS = frozenset({"line", "box", "label", "linefill"})
|
|
53
|
+
|
|
48
54
|
|
|
49
55
|
class TypeHelper:
|
|
50
56
|
"""Pine type-hint / expression inference.
|
|
@@ -100,8 +106,28 @@ class TypeHelper:
|
|
|
100
106
|
return TypeSpec.map(key, val)
|
|
101
107
|
if hint in self._udt_fields:
|
|
102
108
|
return TypeSpec.udt(hint)
|
|
109
|
+
# Drawing-objects-as-data (P3): scalar ``line``/``box``/``label``/
|
|
110
|
+
# ``linefill``/``chart.point`` carry the handle identity via a udt
|
|
111
|
+
# TypeSpec. Without this the analyzer field-spec filter (base.py ~847)
|
|
112
|
+
# erases a scalar drawing field, collapsing it to double. Drawing names
|
|
113
|
+
# are NOT in _udt_fields.
|
|
114
|
+
if hint in _DRAWING_TYPE_NAMES:
|
|
115
|
+
return TypeSpec.udt(hint)
|
|
103
116
|
return None
|
|
104
117
|
|
|
118
|
+
def _param_type_specs_from_def(self, func_def) -> list:
|
|
119
|
+
"""Per-parameter ``TypeSpec`` (or ``None``) from a function's DECLARED
|
|
120
|
+
parameter type hints — the authoritative source for typed params
|
|
121
|
+
(``pivot hi``, ``string tf``, ``line[] arr``). Untyped params are
|
|
122
|
+
``None`` here so regular-function call-site inference can fill them.
|
|
123
|
+
"""
|
|
124
|
+
hints = (getattr(func_def, "annotations", None) or {}).get("param_type_hints", [])
|
|
125
|
+
specs: list = []
|
|
126
|
+
for i in range(len(func_def.params)):
|
|
127
|
+
hint = hints[i] if i < len(hints) else None
|
|
128
|
+
specs.append(self._type_spec_from_hint(hint) if hint else None)
|
|
129
|
+
return specs
|
|
130
|
+
|
|
105
131
|
def _template_args_from_call(self, node: FuncCall) -> list[str]:
|
|
106
132
|
callee = node.callee
|
|
107
133
|
ann = getattr(callee, "annotations", None) or {}
|
|
@@ -116,6 +142,17 @@ class TypeHelper:
|
|
|
116
142
|
func = cal.member if isinstance(cal, MemberAccess) else None
|
|
117
143
|
ns = cal.object.name if isinstance(cal, MemberAccess) and isinstance(cal.object, Identifier) else None
|
|
118
144
|
targs = self._template_args_from_call(value)
|
|
145
|
+
# Drawing-objects-as-data return typing: *.new / *.copy -> handle of
|
|
146
|
+
# the self-type; linefill.get_line* -> line; chart.point.* -> point.
|
|
147
|
+
if ns in _DRAWING_NS:
|
|
148
|
+
if func in ("new", "copy"):
|
|
149
|
+
return TypeSpec.udt(ns)
|
|
150
|
+
if ns == "linefill" and func in ("get_line1", "get_line2"):
|
|
151
|
+
return TypeSpec.udt("line")
|
|
152
|
+
if (isinstance(cal, MemberAccess) and isinstance(cal.object, MemberAccess)
|
|
153
|
+
and isinstance(cal.object.object, Identifier)
|
|
154
|
+
and cal.object.object.name == "chart" and cal.object.member == "point"):
|
|
155
|
+
return TypeSpec.udt("chart.point")
|
|
119
156
|
if ns == "array" and func in ("new", "new_float", "new_int", "new_bool", "new_string", "from"):
|
|
120
157
|
if func == "new_float":
|
|
121
158
|
return TypeSpec.array(TypeSpec.primitive("float"))
|
|
@@ -194,10 +231,26 @@ class TypeHelper:
|
|
|
194
231
|
return recv_spec.element
|
|
195
232
|
if func == "eigenvalues":
|
|
196
233
|
return TypeSpec.array(TypeSpec.primitive("float"))
|
|
234
|
+
# Drawing method-form: a.copy() -> same handle; lf.get_line*() -> line.
|
|
235
|
+
if (recv_spec is not None and recv_spec.kind == "udt"
|
|
236
|
+
and recv_spec.name in _DRAWING_TYPE_NAMES):
|
|
237
|
+
if func == "copy":
|
|
238
|
+
return recv_spec
|
|
239
|
+
if recv_spec.name == "linefill" and func in ("get_line1", "get_line2"):
|
|
240
|
+
return TypeSpec.udt("line")
|
|
197
241
|
if isinstance(value, Identifier):
|
|
198
242
|
sym = self._symbols.resolve(value.name)
|
|
199
243
|
if sym is not None and sym.type_spec is not None:
|
|
200
244
|
return sym.type_spec
|
|
245
|
+
if isinstance(value, FuncCall):
|
|
246
|
+
# User-function return spec (e.g. an array-returning
|
|
247
|
+
# ``buildPDLevels() => array.from(...)``), so a caller's
|
|
248
|
+
# ``allLevels = buildPDLevels()`` infers an array TypeSpec.
|
|
249
|
+
cal = value.callee
|
|
250
|
+
fname = cal.member if isinstance(cal, MemberAccess) else (
|
|
251
|
+
cal.name if isinstance(cal, Identifier) else None)
|
|
252
|
+
if fname and fname in getattr(self, "_func_return_type_specs", {}):
|
|
253
|
+
return self._func_return_type_specs[fname]
|
|
201
254
|
if isinstance(value, MemberAccess):
|
|
202
255
|
owner = self._type_spec_from_expr(value.object)
|
|
203
256
|
if owner is not None and owner.kind == "udt" and owner.name:
|
|
@@ -41,6 +41,8 @@ from .tables import (
|
|
|
41
41
|
SYMINFO_MEMBER_MAP,
|
|
42
42
|
COLOR_CONST_MAP,
|
|
43
43
|
ARRAY_METHODS,
|
|
44
|
+
ARRAY_DRAWING_NEW_CTORS,
|
|
45
|
+
ARRAY_NEW_CTORS,
|
|
44
46
|
MAP_METHODS,
|
|
45
47
|
MATRIX_METHODS,
|
|
46
48
|
MATRIX_METHOD_KWARGS,
|
|
@@ -69,6 +71,8 @@ __all__ = [
|
|
|
69
71
|
"SYMINFO_MEMBER_MAP",
|
|
70
72
|
"COLOR_CONST_MAP",
|
|
71
73
|
"ARRAY_METHODS",
|
|
74
|
+
"ARRAY_DRAWING_NEW_CTORS",
|
|
75
|
+
"ARRAY_NEW_CTORS",
|
|
72
76
|
"MAP_METHODS",
|
|
73
77
|
"MATRIX_METHODS",
|
|
74
78
|
"MATRIX_METHOD_KWARGS",
|