@pineforge/codegen-pyodide 0.9.0 → 0.10.1
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/__init__.py +3 -0
- package/pineforge_codegen/analyzer/base.py +3999 -140
- package/pineforge_codegen/analyzer/call_handlers.py +662 -48
- package/pineforge_codegen/analyzer/contracts.py +104 -7
- package/pineforge_codegen/analyzer/types.py +951 -14
- package/pineforge_codegen/codegen/base.py +2359 -249
- package/pineforge_codegen/codegen/drawing.py +94 -14
- package/pineforge_codegen/codegen/emit_top.py +732 -172
- package/pineforge_codegen/codegen/security.py +1456 -150
- package/pineforge_codegen/codegen/ta.py +435 -99
- package/pineforge_codegen/codegen/tables.py +224 -77
- package/pineforge_codegen/codegen/types.py +1196 -215
- package/pineforge_codegen/codegen/visit_call.py +1168 -99
- package/pineforge_codegen/codegen/visit_expr.py +354 -33
- package/pineforge_codegen/codegen/visit_stmt.py +1144 -200
- package/pineforge_codegen/finite_ta_length.py +798 -0
- package/pineforge_codegen/method_binding.py +163 -0
- package/pineforge_codegen/parser.py +48 -9
- package/pineforge_codegen/signatures.py +4 -4
- package/pineforge_codegen/support_checker.py +194 -19
- package/pineforge_codegen/symbols.py +65 -0
- package/pineforge_codegen-0.10.1.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +9 -11
- package/pineforge_codegen-0.9.0.tar.gz +0 -0
|
@@ -34,11 +34,11 @@ tables it needs come from ``codegen/tables.py``.
|
|
|
34
34
|
from __future__ import annotations
|
|
35
35
|
|
|
36
36
|
from ..ast_nodes import (
|
|
37
|
-
BinOp, BoolLiteral, ExprStmt, FuncCall, FuncDef, Identifier, IfStmt,
|
|
37
|
+
ASTNode, Assignment, BinOp, BoolLiteral, ExprStmt, FuncCall, FuncDef, Identifier, IfStmt,
|
|
38
38
|
MemberAccess, NaLiteral, NumberLiteral, StringLiteral, SwitchStmt,
|
|
39
|
-
Ternary, TupleLiteral, UnaryOp, VarDecl,
|
|
39
|
+
Subscript, Ternary, TupleLiteral, UnaryOp, VarDecl,
|
|
40
40
|
)
|
|
41
|
-
from ..symbols import PineType, TypeSpec
|
|
41
|
+
from ..symbols import PineType, TypeSpec, method_receiver_type_name
|
|
42
42
|
from .. import signatures as sigs
|
|
43
43
|
from .tables import (
|
|
44
44
|
ARRAY_DRAWING_NEW_CTORS,
|
|
@@ -47,6 +47,7 @@ from .tables import (
|
|
|
47
47
|
BAR_FIELDS,
|
|
48
48
|
DRAWING_NS,
|
|
49
49
|
DRAWING_TYPE_TO_CPP,
|
|
50
|
+
MATRIX_RETURNING_METHODS,
|
|
50
51
|
PINE_TYPE_TO_CPP,
|
|
51
52
|
TA_RETURNS_BOOL,
|
|
52
53
|
)
|
|
@@ -145,7 +146,7 @@ class TypeInferer:
|
|
|
145
146
|
if spec.kind == "array":
|
|
146
147
|
return f"std::vector<{self._type_spec_to_cpp(spec.element)}>"
|
|
147
148
|
if spec.kind == "map":
|
|
148
|
-
return f"
|
|
149
|
+
return f"PineMap<{self._type_spec_to_cpp(spec.key)}, {self._type_spec_to_cpp(spec.value)}>"
|
|
149
150
|
if spec.kind == "matrix":
|
|
150
151
|
elem = self._type_spec_to_cpp(spec.element)
|
|
151
152
|
if spec.element.kind == "primitive" and spec.element.name == "float":
|
|
@@ -153,17 +154,97 @@ class TypeInferer:
|
|
|
153
154
|
return f"PineGenericMatrix<{elem}>"
|
|
154
155
|
return "double"
|
|
155
156
|
|
|
157
|
+
def _udt_record_cpp_type(self, type_name: str) -> str:
|
|
158
|
+
"""Generated backing-record type for one user-defined object type."""
|
|
159
|
+
return getattr(self, "_udt_record_cpp_names", {}).get(
|
|
160
|
+
type_name, f"_PFUdtRecord_{type_name}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _udt_arena_member_name(self, type_name: str) -> str:
|
|
164
|
+
"""Per-strategy arena member owning one user-defined object's records."""
|
|
165
|
+
return getattr(self, "_udt_arena_member_names", {}).get(
|
|
166
|
+
type_name, f"_pf_udt_{self._safe_name(type_name)}"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def _udt_arena_cpp_type(self, type_name: str) -> str:
|
|
170
|
+
"""Generated arena specialization for one user-defined object type."""
|
|
171
|
+
return (
|
|
172
|
+
f"{getattr(self, '_udt_arena_template_cpp_name', '_PFUdtArena')}<"
|
|
173
|
+
f"{type_name}, "
|
|
174
|
+
f"{self._udt_record_cpp_type(type_name)}>"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def _checkpoint_traits_cpp_type(self) -> str:
|
|
178
|
+
"""Collision-safe generated checkpoint trait template name."""
|
|
179
|
+
return getattr(
|
|
180
|
+
self, "_checkpoint_traits_cpp_name", "_PFCheckpointTraits"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def _udt_direct_array_fields(self, type_name: str) -> tuple[str, ...]:
|
|
184
|
+
"""Direct array-valued fields whose UDT copy semantics are unsupported.
|
|
185
|
+
|
|
186
|
+
Maps, matrices, and nested UDTs are numeric/shared-ID handles, so an
|
|
187
|
+
outer record copy is correctly shallow. Arrays still lower directly to
|
|
188
|
+
``std::vector<T>``; copying such a record would silently deep-copy the
|
|
189
|
+
vector and diverge from Pine object identity. Keep this boundary narrow
|
|
190
|
+
and explicit until arrays themselves have a handle representation.
|
|
191
|
+
"""
|
|
192
|
+
return tuple(
|
|
193
|
+
field_name
|
|
194
|
+
for field_name, spec in self._udt_field_type_specs.get(
|
|
195
|
+
type_name, {}
|
|
196
|
+
).items()
|
|
197
|
+
if spec is not None and spec.kind == "array"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def _reject_unsupported_udt_copy(self, type_name: str, node) -> None:
|
|
201
|
+
"""Fail closed before copying a record with direct vector storage."""
|
|
202
|
+
fields = self._udt_direct_array_fields(type_name)
|
|
203
|
+
if not fields:
|
|
204
|
+
return
|
|
205
|
+
rendered = ", ".join(fields)
|
|
206
|
+
self._codegen_error(
|
|
207
|
+
node,
|
|
208
|
+
f"{type_name}.copy() is unsupported because direct array field(s) "
|
|
209
|
+
f"{rendered} still use value storage",
|
|
210
|
+
hint=(
|
|
211
|
+
"Move the array outside the UDT and store a supported shared-ID "
|
|
212
|
+
"map/matrix or nested UDT handle until array fields gain handle "
|
|
213
|
+
"identity."
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
|
|
156
217
|
@staticmethod
|
|
157
|
-
def
|
|
218
|
+
def _is_nullable_collection_cpp_type(cpp_type: str | None) -> bool:
|
|
219
|
+
"""Whether ``cpp_type`` has a default-constructed Pine ``na`` ID.
|
|
220
|
+
|
|
221
|
+
Arrays still lower to ``std::vector`` and cannot distinguish a null ID
|
|
222
|
+
from a valid empty array. Keep them outside this predicate until their
|
|
223
|
+
runtime representation carries that distinction.
|
|
224
|
+
"""
|
|
225
|
+
return bool(
|
|
226
|
+
cpp_type
|
|
227
|
+
and (
|
|
228
|
+
cpp_type.startswith("PineMap<")
|
|
229
|
+
or cpp_type == "PineMatrix"
|
|
230
|
+
or cpp_type.startswith("PineGenericMatrix<")
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
def _default_for_type(self, cpp_type: str) -> str:
|
|
158
235
|
"""Default initialiser for a primitive C++ type (matches Pine ``na``)."""
|
|
159
236
|
if cpp_type == "std::string":
|
|
160
237
|
return 'std::string("")'
|
|
161
238
|
if cpp_type == "bool":
|
|
162
239
|
return "false"
|
|
163
|
-
if cpp_type
|
|
240
|
+
if cpp_type in ("int", "int64_t"):
|
|
164
241
|
return "0"
|
|
165
|
-
if cpp_type
|
|
242
|
+
if cpp_type in DRAWING_TYPE_TO_CPP.values():
|
|
243
|
+
return f"{cpp_type}{{}}"
|
|
244
|
+
if cpp_type.startswith("std::vector") or cpp_type.startswith("PineMap"):
|
|
166
245
|
return f"{cpp_type}()"
|
|
246
|
+
if self._is_nullable_collection_cpp_type(cpp_type):
|
|
247
|
+
return f"{cpp_type}{{}}"
|
|
167
248
|
return "0.0"
|
|
168
249
|
|
|
169
250
|
def _default_for_spec(self, spec: TypeSpec | None) -> str:
|
|
@@ -180,24 +261,217 @@ class TypeInferer:
|
|
|
180
261
|
return f"{DRAWING_TYPE_TO_CPP[spec.name]}{{}}"
|
|
181
262
|
return f"{spec.name}{{}}"
|
|
182
263
|
cpp_type = self._type_spec_to_cpp(spec)
|
|
183
|
-
if cpp_type.startswith("std::vector") or cpp_type.startswith("
|
|
264
|
+
if cpp_type.startswith("std::vector") or cpp_type.startswith("PineMap"):
|
|
184
265
|
return f"{cpp_type}()"
|
|
266
|
+
if self._is_nullable_collection_cpp_type(cpp_type):
|
|
267
|
+
return f"{cpp_type}{{}}"
|
|
185
268
|
return self._default_for_type(cpp_type)
|
|
186
269
|
|
|
270
|
+
def _collection_spec_for_name(self, name: str) -> TypeSpec | None:
|
|
271
|
+
"""Resolve collection metadata with lexical precedence.
|
|
272
|
+
|
|
273
|
+
Source-ordered callable locals shadow loop bindings and parameters once
|
|
274
|
+
their declaration has executed; before that point, loop/parameter
|
|
275
|
+
bindings shadow top-level/on_bar state. The process-wide raw-name maps
|
|
276
|
+
are retained for top-level compatibility, but no callable may infer a
|
|
277
|
+
collection kind from a same-named sibling callable.
|
|
278
|
+
"""
|
|
279
|
+
local_specs = getattr(self, "_current_func_collection_specs", {})
|
|
280
|
+
if name in local_specs:
|
|
281
|
+
return local_specs[name]
|
|
282
|
+
# A scalar/UDT local still shadows a same-named top-level collection.
|
|
283
|
+
# Returning None is semantically different from "not found locally": it
|
|
284
|
+
# prevents legacy global kind registries from resurrecting the hidden
|
|
285
|
+
# binding during member dispatch or alias analysis.
|
|
286
|
+
if name in getattr(self, "_current_func_collection_shadows", set()):
|
|
287
|
+
return None
|
|
288
|
+
loop_specs = getattr(self, "_current_loop_var_specs", None)
|
|
289
|
+
if loop_specs and name in loop_specs:
|
|
290
|
+
return loop_specs[name]
|
|
291
|
+
if name in getattr(self, "_current_loop_vars", set()):
|
|
292
|
+
return None
|
|
293
|
+
param_specs = getattr(self, "_current_func_param_specs", {})
|
|
294
|
+
if name in param_specs:
|
|
295
|
+
param_spec = param_specs[name]
|
|
296
|
+
# Keep the established unresolved/untyped-parameter compatibility
|
|
297
|
+
# route: scalar TypeSpecs inferred only from a call site did not
|
|
298
|
+
# historically mask a same-named top-level collection registry.
|
|
299
|
+
# Declared scalar/UDT parameters do shadow it, while inferred or
|
|
300
|
+
# declared collection parameters always carry their exact kind.
|
|
301
|
+
if (param_spec.kind in {"array", "map", "matrix", "udt"}
|
|
302
|
+
or name in getattr(
|
|
303
|
+
self, "_current_func_declared_param_names", set()
|
|
304
|
+
)):
|
|
305
|
+
return param_spec
|
|
306
|
+
# During callable emission this is the copy-on-write lexical overlay;
|
|
307
|
+
# outside a callable it is the live top-level registry, including
|
|
308
|
+
# aliases discovered by codegen after construction.
|
|
309
|
+
return self._collection_types.get(name)
|
|
310
|
+
|
|
311
|
+
def _collection_name_is_lexically_shadowed(self, name: str) -> bool:
|
|
312
|
+
"""Whether ``name`` is bound in the active callable/block scope.
|
|
313
|
+
|
|
314
|
+
A ``None`` collection lookup alone cannot distinguish a real scalar or
|
|
315
|
+
UDT tombstone from an absent name. Callers that otherwise fall back to
|
|
316
|
+
the analyzer's popped symbol table use this predicate to avoid
|
|
317
|
+
resurrecting a hidden top-level collection. Untyped parameters retain
|
|
318
|
+
their established compatibility path and are intentionally not tested
|
|
319
|
+
here.
|
|
320
|
+
"""
|
|
321
|
+
if name in getattr(self, "_current_loop_vars", set()):
|
|
322
|
+
return True
|
|
323
|
+
if name in getattr(self, "_current_func_collection_specs", {}):
|
|
324
|
+
return True
|
|
325
|
+
return name in getattr(self, "_current_func_collection_shadows", set())
|
|
326
|
+
|
|
327
|
+
def _activate_callable_collection_binding(
|
|
328
|
+
self, name: str, spec: TypeSpec | None
|
|
329
|
+
) -> None:
|
|
330
|
+
"""Install one callable-local binding after its declaration RHS.
|
|
331
|
+
|
|
332
|
+
Every raw kind marker is removed first. A collection installs its exact
|
|
333
|
+
TypeSpec; a scalar/UDT leaves a tombstone in the lexical shadow set.
|
|
334
|
+
Function and block entry already established copy-on-write state, so
|
|
335
|
+
this mutation is restored at the appropriate lexical boundary.
|
|
336
|
+
"""
|
|
337
|
+
self._array_vars.discard(name)
|
|
338
|
+
self._map_vars.discard(name)
|
|
339
|
+
self._matrix_specs.pop(name, None)
|
|
340
|
+
self._current_func_collection_specs.pop(name, None)
|
|
341
|
+
self._collection_types.pop(name, None)
|
|
342
|
+
self._current_func_collection_shadows.add(name)
|
|
343
|
+
if spec is None or spec.kind not in {"array", "map", "matrix"}:
|
|
344
|
+
return
|
|
345
|
+
self._current_func_collection_specs[name] = spec
|
|
346
|
+
self._collection_types[name] = spec
|
|
347
|
+
if spec.kind == "array":
|
|
348
|
+
self._array_vars.add(name)
|
|
349
|
+
elif spec.kind == "map":
|
|
350
|
+
self._map_vars.add(name)
|
|
351
|
+
else:
|
|
352
|
+
self._matrix_specs[name] = spec
|
|
353
|
+
|
|
354
|
+
def _collection_receiver_expr(self, name: str) -> str:
|
|
355
|
+
"""C++ receiver for an active collection identifier."""
|
|
356
|
+
pending = getattr(self, "_pending_decl_outer_alias", {})
|
|
357
|
+
if name in pending:
|
|
358
|
+
return pending[name]
|
|
359
|
+
safe = self._safe_name(name)
|
|
360
|
+
owner = getattr(self, "_active_func_name", None)
|
|
361
|
+
exact = (
|
|
362
|
+
self._func_var_storage_name(owner, name)
|
|
363
|
+
if owner is not None
|
|
364
|
+
else name
|
|
365
|
+
)
|
|
366
|
+
# Once an exact callable-local collection declaration has become
|
|
367
|
+
# lexically active, its per-written-callsite variant must read the
|
|
368
|
+
# same cloned member that its declaration-site initializer wrote.
|
|
369
|
+
# Before the declaration, ``_current_func_collection_specs`` has no
|
|
370
|
+
# entry, so an inherited same-named global still resolves normally.
|
|
371
|
+
if (
|
|
372
|
+
name in getattr(self, "_current_func_collection_specs", {})
|
|
373
|
+
and safe in self._active_var_remap
|
|
374
|
+
):
|
|
375
|
+
return self._active_var_remap[safe]
|
|
376
|
+
# Preserve byte-identical output for every non-qualified callable. A
|
|
377
|
+
# qualified persistent array becomes visible through the raw lexical
|
|
378
|
+
# spelling only after its VarDecl installs this active remap.
|
|
379
|
+
if exact != name:
|
|
380
|
+
return self._active_var_remap.get(safe, safe)
|
|
381
|
+
return safe
|
|
382
|
+
|
|
187
383
|
def _array_spec_for_name(self, name: str) -> TypeSpec:
|
|
188
384
|
"""Spec for ``array<...>`` variable ``name`` (falls back to array<float>)."""
|
|
189
|
-
spec = self.
|
|
385
|
+
spec = self._collection_spec_for_name(name)
|
|
190
386
|
if spec is not None and spec.kind == "array":
|
|
191
387
|
return spec
|
|
192
388
|
return TypeSpec.array(TypeSpec.primitive("float"))
|
|
193
389
|
|
|
194
390
|
def _map_spec_for_name(self, name: str) -> TypeSpec:
|
|
195
391
|
"""Spec for ``map<...>`` variable ``name`` (falls back to map<string, float>)."""
|
|
196
|
-
spec = self.
|
|
392
|
+
spec = self._collection_spec_for_name(name)
|
|
197
393
|
if spec is not None and spec.kind == "map":
|
|
198
394
|
return spec
|
|
199
395
|
return TypeSpec.map(TypeSpec.primitive("string"), TypeSpec.primitive("float"))
|
|
200
396
|
|
|
397
|
+
def _array_from_element_spec(self, node) -> TypeSpec | None:
|
|
398
|
+
"""Exact scalar element spec used only by ``array.from`` lowering.
|
|
399
|
+
|
|
400
|
+
This intentionally does not make BinOp TypeSpecs globally visible:
|
|
401
|
+
doing so changes unrelated float-band comparator output. Collection
|
|
402
|
+
construction needs the narrower fact so its vector type agrees with
|
|
403
|
+
the analyzer-captured declaration type, including lexical loop binders.
|
|
404
|
+
A primitive user-function result may have only the coarse ``PineType``
|
|
405
|
+
slot populated; recover that exact scalar here rather than silently
|
|
406
|
+
defaulting ``array.from(string_udf())`` to ``std::vector<double>``.
|
|
407
|
+
"""
|
|
408
|
+
if isinstance(node, BinOp):
|
|
409
|
+
left = self._array_from_element_spec(node.left)
|
|
410
|
+
right = self._array_from_element_spec(node.right)
|
|
411
|
+
if node.op in ("==", "!=", ">", "<", ">=", "<=", "and", "or"):
|
|
412
|
+
return TypeSpec.primitive("bool")
|
|
413
|
+
if (left is not None and right is not None
|
|
414
|
+
and left.kind == "primitive" and right.kind == "primitive"):
|
|
415
|
+
if left.name == "string" or right.name == "string":
|
|
416
|
+
return TypeSpec.primitive("string")
|
|
417
|
+
if node.op == "/" or left.name == "float" or right.name == "float":
|
|
418
|
+
return TypeSpec.primitive("float")
|
|
419
|
+
if left.name == "int" and right.name == "int":
|
|
420
|
+
return TypeSpec.primitive("int")
|
|
421
|
+
return None
|
|
422
|
+
spec = self._type_spec_from_expr(node)
|
|
423
|
+
if spec is not None:
|
|
424
|
+
return spec
|
|
425
|
+
if isinstance(node, FuncCall):
|
|
426
|
+
func_name, namespace = self._resolve_callee(node.callee)
|
|
427
|
+
if namespace is None:
|
|
428
|
+
func_info = self._func_info_map.get(func_name)
|
|
429
|
+
pine_type = getattr(func_info, "return_type", None)
|
|
430
|
+
primitive_name = {
|
|
431
|
+
PineType.INT: "int",
|
|
432
|
+
PineType.FLOAT: "float",
|
|
433
|
+
PineType.BOOL: "bool",
|
|
434
|
+
PineType.STRING: "string",
|
|
435
|
+
PineType.COLOR: "color",
|
|
436
|
+
}.get(pine_type)
|
|
437
|
+
if primitive_name is not None:
|
|
438
|
+
return TypeSpec.primitive(primitive_name)
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
@staticmethod
|
|
442
|
+
def _selection_terminal_expr(body: list[ASTNode] | None) -> ASTNode | None:
|
|
443
|
+
"""Return one if/switch branch's value expression, if present."""
|
|
444
|
+
if not body:
|
|
445
|
+
return None
|
|
446
|
+
terminal = body[-1]
|
|
447
|
+
return terminal.expr if isinstance(terminal, ExprStmt) else terminal
|
|
448
|
+
|
|
449
|
+
@staticmethod
|
|
450
|
+
def _selection_node_is_na(node: ASTNode | None) -> bool:
|
|
451
|
+
"""Whether a selection arm is explicit or implicit Pine ``na``."""
|
|
452
|
+
return (
|
|
453
|
+
node is None
|
|
454
|
+
or isinstance(node, NaLiteral)
|
|
455
|
+
or (isinstance(node, Identifier) and node.name == "na")
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
def _nullable_collection_selection_spec(
|
|
459
|
+
self,
|
|
460
|
+
branches: list[tuple[ASTNode | None, TypeSpec | None]],
|
|
461
|
+
) -> TypeSpec | None:
|
|
462
|
+
"""Unify compatible map/matrix selection arms around typed ``na``."""
|
|
463
|
+
concrete: list[TypeSpec] = []
|
|
464
|
+
for node, spec in branches:
|
|
465
|
+
if self._selection_node_is_na(node):
|
|
466
|
+
continue
|
|
467
|
+
if spec is None or spec.kind not in {"map", "matrix"}:
|
|
468
|
+
return None
|
|
469
|
+
concrete.append(spec)
|
|
470
|
+
if not concrete:
|
|
471
|
+
return None
|
|
472
|
+
first = concrete[0]
|
|
473
|
+
return first if all(spec == first for spec in concrete[1:]) else None
|
|
474
|
+
|
|
201
475
|
def _type_spec_from_expr(self, node) -> TypeSpec | None:
|
|
202
476
|
"""Best-effort TypeSpec inference for an expression node.
|
|
203
477
|
|
|
@@ -210,26 +484,139 @@ class TypeInferer:
|
|
|
210
484
|
if isinstance(node, StringLiteral):
|
|
211
485
|
return TypeSpec.primitive("string")
|
|
212
486
|
if isinstance(node, Identifier):
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
487
|
+
# An active lexical UDT/drawing binding must beat a same-spelled
|
|
488
|
+
# top-level primitive or collection. This is intentionally the
|
|
489
|
+
# exact lexical prefix of _identifier_udt_binding(), not its raw-
|
|
490
|
+
# name fallback: method receivers such as ``line ln`` otherwise
|
|
491
|
+
# inherit an unrelated global ``ln`` before dispatch is resolved.
|
|
492
|
+
lexical_drawing = getattr(self, "_lexical_drawing_types", {})
|
|
493
|
+
drawing_cpp = lexical_drawing.get(node.name)
|
|
494
|
+
if drawing_cpp is not None:
|
|
495
|
+
for pine_name, cpp_name in DRAWING_TYPE_TO_CPP.items():
|
|
496
|
+
if cpp_name == drawing_cpp:
|
|
497
|
+
return TypeSpec.udt(pine_name)
|
|
498
|
+
lexical_udts = getattr(self, "_lexical_udt_types", {})
|
|
499
|
+
lexical_udt = lexical_udts.get(node.name)
|
|
500
|
+
if lexical_udt in self._udt_defs:
|
|
501
|
+
return TypeSpec.udt(lexical_udt)
|
|
502
|
+
collection_spec = self._collection_spec_for_name(node.name)
|
|
503
|
+
# This resolver also carries exact primitive loop/parameter/global
|
|
504
|
+
# metadata despite its historical name. Preserve those scalar
|
|
505
|
+
# families here (notably bool for array.from); only arbitrary UDTs
|
|
506
|
+
# need the collision-safe lexical resolver below.
|
|
507
|
+
if (collection_spec is not None
|
|
508
|
+
and collection_spec.kind != "udt"):
|
|
509
|
+
return collection_spec
|
|
510
|
+
found_udt_binding, exact_udt = self._identifier_udt_binding(
|
|
511
|
+
node.name
|
|
512
|
+
)
|
|
513
|
+
if found_udt_binding:
|
|
514
|
+
return (
|
|
515
|
+
TypeSpec.udt(exact_udt)
|
|
516
|
+
if exact_udt is not None
|
|
517
|
+
else None
|
|
518
|
+
)
|
|
519
|
+
if self._collection_name_is_lexically_shadowed(node.name):
|
|
520
|
+
return None
|
|
229
521
|
sym = self.ctx.symbols.resolve(node.name)
|
|
230
|
-
if sym is not None
|
|
231
|
-
|
|
522
|
+
if sym is not None:
|
|
523
|
+
if getattr(sym, "type_spec", None) is not None:
|
|
524
|
+
return sym.type_spec
|
|
525
|
+
primitive_name = {
|
|
526
|
+
PineType.INT: "int",
|
|
527
|
+
PineType.FLOAT: "float",
|
|
528
|
+
PineType.BOOL: "bool",
|
|
529
|
+
PineType.STRING: "string",
|
|
530
|
+
PineType.COLOR: "color",
|
|
531
|
+
}.get(sym.pine_type)
|
|
532
|
+
if primitive_name is not None:
|
|
533
|
+
return TypeSpec.primitive(primitive_name)
|
|
534
|
+
return None
|
|
535
|
+
if isinstance(node, Subscript):
|
|
536
|
+
# History access preserves the scalar drawing-handle type. This
|
|
537
|
+
# is intentionally not generalized to arrays/maps: their
|
|
538
|
+
# subscripting rules are handled by the collection paths below.
|
|
539
|
+
receiver_spec = self._type_spec_from_expr(node.object)
|
|
540
|
+
if (receiver_spec is not None
|
|
541
|
+
and receiver_spec.kind == "udt"
|
|
542
|
+
and receiver_spec.name in DRAWING_TYPE_TO_CPP):
|
|
543
|
+
return receiver_spec
|
|
544
|
+
return None
|
|
545
|
+
if isinstance(node, Ternary):
|
|
546
|
+
true_spec = self._type_spec_from_expr(node.true_val)
|
|
547
|
+
false_spec = self._type_spec_from_expr(node.false_val)
|
|
548
|
+
collection_spec = self._nullable_collection_selection_spec([
|
|
549
|
+
(node.true_val, true_spec),
|
|
550
|
+
(node.false_val, false_spec),
|
|
551
|
+
])
|
|
552
|
+
if collection_spec is not None:
|
|
553
|
+
return collection_spec
|
|
554
|
+
if true_spec is not None and true_spec == false_spec:
|
|
555
|
+
return true_spec
|
|
556
|
+
if (true_spec is not None
|
|
557
|
+
and true_spec.kind == "udt"
|
|
558
|
+
and true_spec.name in DRAWING_TYPE_TO_CPP
|
|
559
|
+
and isinstance(node.false_val, NaLiteral)):
|
|
560
|
+
return true_spec
|
|
561
|
+
if (false_spec is not None
|
|
562
|
+
and false_spec.kind == "udt"
|
|
563
|
+
and false_spec.name in DRAWING_TYPE_TO_CPP
|
|
564
|
+
and isinstance(node.true_val, NaLiteral)):
|
|
565
|
+
return false_spec
|
|
566
|
+
return None
|
|
567
|
+
if isinstance(node, IfStmt):
|
|
568
|
+
true_node = self._selection_terminal_expr(node.body)
|
|
569
|
+
false_node = self._selection_terminal_expr(node.else_body)
|
|
570
|
+
true_spec = self._type_spec_from_expr(true_node)
|
|
571
|
+
false_spec = self._type_spec_from_expr(false_node)
|
|
572
|
+
collection_spec = self._nullable_collection_selection_spec([
|
|
573
|
+
(true_node, true_spec),
|
|
574
|
+
(false_node, false_spec),
|
|
575
|
+
])
|
|
576
|
+
if collection_spec is not None:
|
|
577
|
+
return collection_spec
|
|
578
|
+
true_is_na = (
|
|
579
|
+
isinstance(true_node, NaLiteral)
|
|
580
|
+
or (isinstance(true_node, Identifier)
|
|
581
|
+
and true_node.name == "na")
|
|
582
|
+
)
|
|
583
|
+
false_is_na = (
|
|
584
|
+
isinstance(false_node, NaLiteral)
|
|
585
|
+
or (isinstance(false_node, Identifier)
|
|
586
|
+
and false_node.name == "na")
|
|
587
|
+
)
|
|
588
|
+
if (true_spec is not None
|
|
589
|
+
and true_spec.kind == "udt"
|
|
590
|
+
and true_spec.name in DRAWING_TYPE_TO_CPP
|
|
591
|
+
and true_spec == false_spec):
|
|
592
|
+
return true_spec
|
|
593
|
+
if (true_spec is not None
|
|
594
|
+
and true_spec.kind == "udt"
|
|
595
|
+
and true_spec.name in DRAWING_TYPE_TO_CPP
|
|
596
|
+
and false_is_na):
|
|
597
|
+
return true_spec
|
|
598
|
+
if (false_spec is not None
|
|
599
|
+
and false_spec.kind == "udt"
|
|
600
|
+
and false_spec.name in DRAWING_TYPE_TO_CPP
|
|
601
|
+
and true_is_na):
|
|
602
|
+
return false_spec
|
|
232
603
|
return None
|
|
604
|
+
if isinstance(node, SwitchStmt):
|
|
605
|
+
branches: list[tuple[ASTNode | None, TypeSpec | None]] = []
|
|
606
|
+
for _case_expr, case_body in node.cases:
|
|
607
|
+
terminal = self._selection_terminal_expr(case_body)
|
|
608
|
+
branches.append((
|
|
609
|
+
terminal,
|
|
610
|
+
self._type_spec_from_expr(terminal),
|
|
611
|
+
))
|
|
612
|
+
default_terminal = self._selection_terminal_expr(
|
|
613
|
+
node.default_body
|
|
614
|
+
)
|
|
615
|
+
branches.append((
|
|
616
|
+
default_terminal,
|
|
617
|
+
self._type_spec_from_expr(default_terminal),
|
|
618
|
+
))
|
|
619
|
+
return self._nullable_collection_selection_spec(branches)
|
|
233
620
|
if isinstance(node, MemberAccess):
|
|
234
621
|
owner = self._type_spec_from_expr(node.object)
|
|
235
622
|
if owner is not None and owner.kind == "udt" and owner.name:
|
|
@@ -237,6 +624,73 @@ class TypeInferer:
|
|
|
237
624
|
return None
|
|
238
625
|
if isinstance(node, FuncCall):
|
|
239
626
|
func_name, namespace = self._resolve_callee(node.callee)
|
|
627
|
+
if namespace is None:
|
|
628
|
+
func_info = self._func_info_map.get(func_name)
|
|
629
|
+
return_spec = getattr(func_info, "return_type_spec", None)
|
|
630
|
+
if return_spec is not None:
|
|
631
|
+
return return_spec
|
|
632
|
+
udt_return = getattr(func_info, "udt_return_type", None)
|
|
633
|
+
if (udt_return in DRAWING_TYPE_TO_CPP
|
|
634
|
+
or udt_return in self._udt_defs):
|
|
635
|
+
return TypeSpec.udt(udt_return)
|
|
636
|
+
if isinstance(node.callee, MemberAccess):
|
|
637
|
+
receiver_spec = self._type_spec_from_expr(
|
|
638
|
+
node.callee.object
|
|
639
|
+
)
|
|
640
|
+
receiver_name = method_receiver_type_name(receiver_spec)
|
|
641
|
+
method_info = (
|
|
642
|
+
getattr(self, "_func_info_map", {}).get(
|
|
643
|
+
f"{receiver_name}.{node.callee.member}"
|
|
644
|
+
)
|
|
645
|
+
if receiver_name is not None
|
|
646
|
+
else None
|
|
647
|
+
)
|
|
648
|
+
if (
|
|
649
|
+
method_info is not None
|
|
650
|
+
and getattr(method_info, "is_udt_method", False)
|
|
651
|
+
):
|
|
652
|
+
return_spec = getattr(
|
|
653
|
+
method_info, "return_type_spec", None
|
|
654
|
+
)
|
|
655
|
+
if return_spec is not None:
|
|
656
|
+
return return_spec
|
|
657
|
+
udt_return = getattr(
|
|
658
|
+
method_info, "udt_return_type", None
|
|
659
|
+
)
|
|
660
|
+
if (
|
|
661
|
+
udt_return in DRAWING_TYPE_TO_CPP
|
|
662
|
+
or udt_return in self._udt_defs
|
|
663
|
+
):
|
|
664
|
+
return TypeSpec.udt(udt_return)
|
|
665
|
+
call_site_idx = self._callable_target_callsite_idx(
|
|
666
|
+
method_info,
|
|
667
|
+
node,
|
|
668
|
+
)
|
|
669
|
+
return_pine_type = (
|
|
670
|
+
self._callsite_callable_return_pine_type(
|
|
671
|
+
method_info,
|
|
672
|
+
call_site_idx,
|
|
673
|
+
)
|
|
674
|
+
)
|
|
675
|
+
primitive_name = {
|
|
676
|
+
PineType.INT: "int",
|
|
677
|
+
PineType.FLOAT: "float",
|
|
678
|
+
PineType.BOOL: "bool",
|
|
679
|
+
PineType.STRING: "string",
|
|
680
|
+
PineType.COLOR: "color",
|
|
681
|
+
}.get(return_pine_type)
|
|
682
|
+
return (
|
|
683
|
+
TypeSpec.primitive(primitive_name)
|
|
684
|
+
if primitive_name is not None
|
|
685
|
+
else None
|
|
686
|
+
)
|
|
687
|
+
if (
|
|
688
|
+
receiver_spec is not None
|
|
689
|
+
and receiver_spec.kind == "udt"
|
|
690
|
+
and receiver_spec.name in self._udt_defs
|
|
691
|
+
and node.callee.member == "copy"
|
|
692
|
+
):
|
|
693
|
+
return receiver_spec
|
|
240
694
|
# ticker.* constructors (inherit/standard/heikinashi) return a symbol
|
|
241
695
|
# string; without this the member-type inference defaults to double
|
|
242
696
|
# and a ``haTicker = ticker.heikinashi(...)`` global mis-declares as
|
|
@@ -255,6 +709,13 @@ class TypeInferer:
|
|
|
255
709
|
return TypeSpec.udt("chart.point")
|
|
256
710
|
if namespace == "str" and func_name == "split":
|
|
257
711
|
return TypeSpec.array(TypeSpec.primitive("string"))
|
|
712
|
+
if namespace == "matrix" and func_name == "new":
|
|
713
|
+
elem = (
|
|
714
|
+
self._type_spec_from_hint_name(targs[0])
|
|
715
|
+
if targs
|
|
716
|
+
else TypeSpec.primitive("float")
|
|
717
|
+
)
|
|
718
|
+
return TypeSpec.matrix(elem or TypeSpec.primitive("float"))
|
|
258
719
|
if namespace == "array" and func_name in (
|
|
259
720
|
"new", "new_float", "new_int", "new_bool", "new_string", "from",
|
|
260
721
|
) or (namespace == "array" and func_name in ARRAY_DRAWING_NEW_CTORS):
|
|
@@ -273,41 +734,93 @@ class TypeInferer:
|
|
|
273
734
|
if targs:
|
|
274
735
|
return TypeSpec.array(self._type_spec_from_hint_name(targs[0]) or TypeSpec.udt(targs[0]))
|
|
275
736
|
if func_name == "from" and node.args:
|
|
276
|
-
return TypeSpec.array(
|
|
737
|
+
return TypeSpec.array(
|
|
738
|
+
self._array_from_element_spec(node.args[0])
|
|
739
|
+
or TypeSpec.primitive("float")
|
|
740
|
+
)
|
|
277
741
|
return TypeSpec.array(TypeSpec.primitive("float"))
|
|
278
742
|
# Functional-form array element/copy accessors: the receiver is
|
|
279
743
|
# the first argument (``array.copy(arr)``), mirroring the
|
|
280
744
|
# method-form handling below (``arr.copy()``).
|
|
281
|
-
if (namespace == "array"
|
|
745
|
+
if (namespace == "array"
|
|
282
746
|
and func_name in ("copy", "slice", "get", "first", "last",
|
|
283
747
|
"pop", "shift", "remove")):
|
|
284
|
-
|
|
748
|
+
receiver_node = node.args[0] if node.args else node.kwargs.get("id")
|
|
749
|
+
arg_spec = self._type_spec_from_expr(receiver_node)
|
|
285
750
|
if arg_spec is not None and arg_spec.kind == "array":
|
|
286
751
|
if func_name in ("copy", "slice"):
|
|
287
752
|
return arg_spec
|
|
288
753
|
return arg_spec.element
|
|
754
|
+
if namespace == "matrix" and func_name in MATRIX_RETURNING_METHODS:
|
|
755
|
+
receiver_node = node.args[0] if node.args else node.kwargs.get("id")
|
|
756
|
+
receiver_spec = self._type_spec_from_expr(receiver_node)
|
|
757
|
+
if receiver_spec is not None and receiver_spec.kind == "matrix":
|
|
758
|
+
return receiver_spec
|
|
289
759
|
if namespace == "map" and func_name == "new":
|
|
290
760
|
key = self._type_spec_from_hint_name(targs[0]) if len(targs) > 0 else TypeSpec.primitive("string")
|
|
291
761
|
val = self._type_spec_from_hint_name(targs[1]) if len(targs) > 1 else TypeSpec.primitive("float")
|
|
292
762
|
return TypeSpec.map(key or TypeSpec.primitive("string"), val or TypeSpec.primitive("float"))
|
|
293
|
-
if namespace
|
|
763
|
+
if namespace == "map" and func_name in {
|
|
764
|
+
"put", "get", "remove", "contains", "size", "keys",
|
|
765
|
+
"values", "copy", "put_all", "clear",
|
|
766
|
+
} and node.args:
|
|
767
|
+
receiver_spec = self._type_spec_from_expr(node.args[0])
|
|
768
|
+
if receiver_spec is not None and receiver_spec.kind == "map":
|
|
769
|
+
if func_name in ("put", "get", "remove"):
|
|
770
|
+
return receiver_spec.value
|
|
771
|
+
if func_name == "keys":
|
|
772
|
+
return TypeSpec.array(
|
|
773
|
+
receiver_spec.key or TypeSpec.primitive("string")
|
|
774
|
+
)
|
|
775
|
+
if func_name == "values":
|
|
776
|
+
return TypeSpec.array(
|
|
777
|
+
receiver_spec.value or TypeSpec.primitive("float")
|
|
778
|
+
)
|
|
779
|
+
if func_name == "copy":
|
|
780
|
+
return receiver_spec
|
|
781
|
+
if func_name == "contains":
|
|
782
|
+
return TypeSpec.primitive("bool")
|
|
783
|
+
if func_name == "size":
|
|
784
|
+
return TypeSpec.primitive("int")
|
|
785
|
+
if namespace in self._udt_defs and func_name in {"new", "copy"}:
|
|
294
786
|
return TypeSpec.udt(namespace)
|
|
295
787
|
if isinstance(node.callee, MemberAccess):
|
|
296
788
|
recv_spec = self._type_spec_from_expr(node.callee.object)
|
|
789
|
+
member_name = node.callee.member
|
|
297
790
|
if recv_spec is not None and recv_spec.kind == "array":
|
|
298
|
-
if
|
|
791
|
+
if member_name in (
|
|
792
|
+
"get", "first", "last", "pop", "shift", "remove",
|
|
793
|
+
):
|
|
299
794
|
return recv_spec.element
|
|
300
|
-
if
|
|
795
|
+
if member_name in ("copy", "slice"):
|
|
301
796
|
return recv_spec
|
|
302
797
|
if recv_spec is not None and recv_spec.kind == "map":
|
|
303
|
-
if
|
|
798
|
+
if member_name in ("put", "get", "remove"):
|
|
304
799
|
return recv_spec.value
|
|
305
|
-
if
|
|
800
|
+
if member_name == "keys":
|
|
306
801
|
return TypeSpec.array(recv_spec.key or TypeSpec.primitive("string"))
|
|
307
|
-
if
|
|
802
|
+
if member_name == "values":
|
|
308
803
|
return TypeSpec.array(recv_spec.value or TypeSpec.primitive("float"))
|
|
804
|
+
if member_name == "copy":
|
|
805
|
+
return recv_spec
|
|
806
|
+
if member_name == "contains":
|
|
807
|
+
return TypeSpec.primitive("bool")
|
|
808
|
+
if member_name == "size":
|
|
809
|
+
return TypeSpec.primitive("int")
|
|
810
|
+
receiver_name = method_receiver_type_name(recv_spec)
|
|
811
|
+
if receiver_name is not None:
|
|
812
|
+
method_info = getattr(
|
|
813
|
+
self, "_func_info_map", {}
|
|
814
|
+
).get(
|
|
815
|
+
f"{receiver_name}.{member_name}"
|
|
816
|
+
)
|
|
817
|
+
return_spec = getattr(
|
|
818
|
+
method_info, "return_type_spec", None
|
|
819
|
+
)
|
|
820
|
+
if return_spec is not None:
|
|
821
|
+
return return_spec
|
|
309
822
|
if recv_spec is not None and recv_spec.kind == "matrix":
|
|
310
|
-
if func_name in
|
|
823
|
+
if func_name in MATRIX_RETURNING_METHODS:
|
|
311
824
|
return recv_spec
|
|
312
825
|
if func_name in ("row", "col"):
|
|
313
826
|
return TypeSpec.array(recv_spec.element)
|
|
@@ -325,6 +838,76 @@ class TypeInferer:
|
|
|
325
838
|
return TypeSpec.udt("line")
|
|
326
839
|
return None
|
|
327
840
|
|
|
841
|
+
def _identifier_udt_binding(
|
|
842
|
+
self, name: str
|
|
843
|
+
) -> tuple[bool, str | None]:
|
|
844
|
+
"""Return ``(binding_known, exact_udt_or_none)`` for ``name``.
|
|
845
|
+
|
|
846
|
+
``ctx.udt_var_types`` is a legacy raw-name registry: a declaration in
|
|
847
|
+
an unrelated callable can overwrite the type of a same-named global.
|
|
848
|
+
Every correctness-sensitive consumer must prefer the active lexical
|
|
849
|
+
binding (including scalar tombstones), then exact parameter/local and
|
|
850
|
+
direct-program metadata, before consulting that registry.
|
|
851
|
+
"""
|
|
852
|
+
known_udts = set(self._udt_defs) | set(DRAWING_TYPE_TO_CPP)
|
|
853
|
+
|
|
854
|
+
lexical_drawing = getattr(self, "_lexical_drawing_types", {})
|
|
855
|
+
if name in lexical_drawing and lexical_drawing[name] is not None:
|
|
856
|
+
cpp_type = lexical_drawing[name]
|
|
857
|
+
for pine_name, candidate_cpp in DRAWING_TYPE_TO_CPP.items():
|
|
858
|
+
if candidate_cpp == cpp_type:
|
|
859
|
+
return True, pine_name
|
|
860
|
+
|
|
861
|
+
lexical = getattr(self, "_lexical_udt_types", {})
|
|
862
|
+
if name in lexical:
|
|
863
|
+
candidate = lexical[name]
|
|
864
|
+
return True, candidate if candidate in known_udts else None
|
|
865
|
+
|
|
866
|
+
param_specs = getattr(self, "_current_func_param_specs", {})
|
|
867
|
+
param_spec = param_specs.get(name) or param_specs.get(
|
|
868
|
+
self._safe_name(name)
|
|
869
|
+
)
|
|
870
|
+
if (param_spec is not None
|
|
871
|
+
and param_spec.kind == "udt"
|
|
872
|
+
and param_spec.name in known_udts):
|
|
873
|
+
return True, param_spec.name
|
|
874
|
+
param_types = getattr(self, "_current_func_param_types", {})
|
|
875
|
+
if name in param_types or self._safe_name(name) in param_types:
|
|
876
|
+
return True, None
|
|
877
|
+
|
|
878
|
+
local_types = getattr(self, "_current_func_local_types", {})
|
|
879
|
+
if name in local_types:
|
|
880
|
+
local_cpp = local_types[name]
|
|
881
|
+
candidate = local_cpp.removesuffix("&").removesuffix("*")
|
|
882
|
+
return True, candidate if candidate in known_udts else None
|
|
883
|
+
|
|
884
|
+
param_udts = getattr(self, "_udt_param_udt", {})
|
|
885
|
+
candidate = param_udts.get(name) or param_udts.get(
|
|
886
|
+
self._safe_name(name)
|
|
887
|
+
)
|
|
888
|
+
if candidate in known_udts:
|
|
889
|
+
return True, candidate
|
|
890
|
+
|
|
891
|
+
global_types = getattr(self, "_global_udt_types", {})
|
|
892
|
+
if name in global_types:
|
|
893
|
+
candidate = global_types[name]
|
|
894
|
+
return True, candidate if candidate in known_udts else None
|
|
895
|
+
|
|
896
|
+
member_resolver = getattr(self, "_member_udt_type", None)
|
|
897
|
+
candidate = (
|
|
898
|
+
member_resolver(name)
|
|
899
|
+
if callable(member_resolver)
|
|
900
|
+
else self._udt_var_types.get(name)
|
|
901
|
+
)
|
|
902
|
+
if candidate is not None:
|
|
903
|
+
return True, candidate if candidate in known_udts else None
|
|
904
|
+
return False, None
|
|
905
|
+
|
|
906
|
+
def _identifier_udt_type(self, name: str) -> str | None:
|
|
907
|
+
"""Exact UDT type for ``name``; scalar tombstones return ``None``."""
|
|
908
|
+
_found, udt_type = self._identifier_udt_binding(name)
|
|
909
|
+
return udt_type
|
|
910
|
+
|
|
328
911
|
# ------------------------------------------------------------------
|
|
329
912
|
# Method lowering for collection types (used by visit_call paths)
|
|
330
913
|
# ------------------------------------------------------------------
|
|
@@ -402,42 +985,77 @@ class TypeInferer:
|
|
|
402
985
|
f"codegen: unhandled array method '{method}' — analyzer should have "
|
|
403
986
|
f"rejected. Add it to ARRAY_METHODS."
|
|
404
987
|
)
|
|
405
|
-
|
|
988
|
+
# Pine evaluates call arguments before entering the array
|
|
989
|
+
# calculation, even when an empty receiver makes the result ``na``.
|
|
990
|
+
# The empty guards in these methods must therefore consume a
|
|
991
|
+
# one-evaluation binding rather than leaving the original argument
|
|
992
|
+
# expression after the guard. Build the binding into
|
|
993
|
+
# ``lower_receiver`` so a temporary receiver is still evaluated
|
|
994
|
+
# first by ``_array_receiver_once_expr``.
|
|
995
|
+
eager_scalar_arg_methods = {
|
|
996
|
+
"stdev": (0,),
|
|
997
|
+
"variance": (0,),
|
|
998
|
+
"percentile_linear_interpolation": (0,),
|
|
999
|
+
"percentile_nearest_rank": (0,),
|
|
1000
|
+
}
|
|
1001
|
+
bound_args = list(args)
|
|
1002
|
+
arg_bindings: list[tuple[str, str]] = []
|
|
1003
|
+
occupied = "\n".join((array_expr, *args))
|
|
1004
|
+
counter = getattr(self, "_array_arg_counter", 0)
|
|
1005
|
+
for arg_index in eager_scalar_arg_methods.get(method, ()):
|
|
1006
|
+
if arg_index >= len(args):
|
|
1007
|
+
continue
|
|
1008
|
+
while True:
|
|
1009
|
+
token = f"__pf_array_arg_{counter}"
|
|
1010
|
+
counter += 1
|
|
1011
|
+
if token not in occupied:
|
|
1012
|
+
break
|
|
1013
|
+
bound_args[arg_index] = token
|
|
1014
|
+
arg_bindings.append((token, args[arg_index]))
|
|
1015
|
+
self._array_arg_counter = counter
|
|
1016
|
+
|
|
1017
|
+
def lower_receiver(recv: str) -> str:
|
|
1018
|
+
lowered = ARRAY_METHODS[method](recv, bound_args)
|
|
1019
|
+
for token, original in reversed(arg_bindings):
|
|
1020
|
+
lowered = (
|
|
1021
|
+
f"[&](){{ auto {token}=({original}); "
|
|
1022
|
+
f"return {lowered}; }}()"
|
|
1023
|
+
)
|
|
1024
|
+
return lowered
|
|
406
1025
|
|
|
407
1026
|
return self._array_receiver_once_expr(array_expr, args, lower_receiver)
|
|
408
1027
|
|
|
409
1028
|
def _map_method_expr(
|
|
410
1029
|
self, map_expr: str, method: str, args: list[str], spec: TypeSpec | None = None,
|
|
411
1030
|
) -> str:
|
|
412
|
-
"""Lower
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
)
|
|
1031
|
+
"""Lower a Pine map operation to the ordered, alias-preserving runtime.
|
|
1032
|
+
|
|
1033
|
+
``PineMap`` owns all missing-value, insertion-order and explicit-copy
|
|
1034
|
+
semantics. Keeping this lowering as a direct method delegation also
|
|
1035
|
+
guarantees that each receiver and argument expression occurs exactly
|
|
1036
|
+
once; the typed-parameter lane may still wrap arguments to impose
|
|
1037
|
+
Pine's source evaluation order.
|
|
1038
|
+
"""
|
|
421
1039
|
if method == "put":
|
|
422
|
-
return f"
|
|
1040
|
+
return f"{map_expr}.put({args[0]}, {args[1]})"
|
|
423
1041
|
if method == "get":
|
|
424
|
-
return f"
|
|
1042
|
+
return f"{map_expr}.get({args[0]})"
|
|
425
1043
|
if method == "remove":
|
|
426
|
-
return f"
|
|
1044
|
+
return f"{map_expr}.remove({args[0]})"
|
|
427
1045
|
if method == "contains":
|
|
428
|
-
return f"
|
|
1046
|
+
return f"{map_expr}.contains({args[0]})"
|
|
429
1047
|
if method == "size":
|
|
430
|
-
return f"
|
|
1048
|
+
return f"{map_expr}.size()"
|
|
431
1049
|
if method == "clear":
|
|
432
1050
|
return f"{map_expr}.clear()"
|
|
433
1051
|
if method == "keys":
|
|
434
|
-
return f"
|
|
1052
|
+
return f"{map_expr}.keys()"
|
|
435
1053
|
if method == "values":
|
|
436
|
-
return f"
|
|
1054
|
+
return f"{map_expr}.values()"
|
|
437
1055
|
if method == "copy":
|
|
438
|
-
return f"{
|
|
1056
|
+
return f"{map_expr}.copy()"
|
|
439
1057
|
if method == "put_all":
|
|
440
|
-
return f"{map_expr}.
|
|
1058
|
+
return f"{map_expr}.put_all({args[0]})"
|
|
441
1059
|
# Defensive: support_checker rejects any map.* method not in SUPPORTED_MAP
|
|
442
1060
|
# (derived from MAP_METHODS, which mirrors this if-chain). Reaching here
|
|
443
1061
|
# means the checker was bypassed or the tables drifted.
|
|
@@ -452,13 +1070,39 @@ class TypeInferer:
|
|
|
452
1070
|
|
|
453
1071
|
def _type_for_decl(self, node: VarDecl) -> str:
|
|
454
1072
|
"""Determine the C++ type for a ``VarDecl``: explicit hint, then symbol, then RHS inference."""
|
|
1073
|
+
def promote_wide_int(cpp_type: str) -> str:
|
|
1074
|
+
if cpp_type != "int":
|
|
1075
|
+
return cpp_type
|
|
1076
|
+
owner = self._func_info_map.get(
|
|
1077
|
+
getattr(self, "_active_func_name", "") or ""
|
|
1078
|
+
)
|
|
1079
|
+
if self._expr_returns_wide_int(
|
|
1080
|
+
node.value,
|
|
1081
|
+
owner,
|
|
1082
|
+
set(),
|
|
1083
|
+
getattr(self, "_active_call_site_idx", None),
|
|
1084
|
+
) or self._is_int64_builtin_init(node.name):
|
|
1085
|
+
return "int64_t"
|
|
1086
|
+
return cpp_type
|
|
1087
|
+
|
|
455
1088
|
if node.type_hint:
|
|
456
1089
|
spec = self._type_spec_from_hint_name(node.type_hint)
|
|
457
1090
|
if spec is not None:
|
|
458
|
-
return self._type_spec_to_cpp(spec)
|
|
1091
|
+
return promote_wide_int(self._type_spec_to_cpp(spec))
|
|
459
1092
|
if node.type_hint in self._udt_defs:
|
|
460
1093
|
return node.type_hint
|
|
461
|
-
return
|
|
1094
|
+
return promote_wide_int(
|
|
1095
|
+
PINE_TYPE_TO_CPP.get(node.type_hint, "double")
|
|
1096
|
+
)
|
|
1097
|
+
# The analyzer records exact callable-local collection bindings before
|
|
1098
|
+
# its lexical scopes are popped. In particular, an inferred local
|
|
1099
|
+
# such as ``selected = cond ? na : global_map`` has a map TypeSpec even
|
|
1100
|
+
# though generic C++ expression inference sees ``na`` as ``double``.
|
|
1101
|
+
# Consume nullable map/matrix forms here. Arrays still lack a runtime
|
|
1102
|
+
# null-ID representation and retain their established inference path.
|
|
1103
|
+
captured = self._callable_collection_bindings.get(id(node))
|
|
1104
|
+
if captured is not None and captured.kind in {"map", "matrix"}:
|
|
1105
|
+
return self._type_spec_to_cpp(captured)
|
|
462
1106
|
# Drawing handle local (L-N6): a hintless local whose RHS resolves to a
|
|
463
1107
|
# drawing udt must declare as the handle struct, not the analyzer's
|
|
464
1108
|
# scalar default. Covers ``ln = arr.get(i)``, alias ``b = a``, field read
|
|
@@ -475,6 +1119,12 @@ class TypeInferer:
|
|
|
475
1119
|
_dret = self._drawing_call_return_cpp(node.value)
|
|
476
1120
|
if _dret is not None:
|
|
477
1121
|
return _dret
|
|
1122
|
+
# Analyzer scopes are popped before codegen, so ctx.symbols.resolve may
|
|
1123
|
+
# find a same-named global instead of this active callable's plain
|
|
1124
|
+
# local. The local binding is known from the emitted body inventory;
|
|
1125
|
+
# infer it from its own RHS rather than borrowing the global's PineType.
|
|
1126
|
+
if node.name in getattr(self, "_current_func_locals", set()):
|
|
1127
|
+
return promote_wide_int(self._infer_type(node.value))
|
|
478
1128
|
sym = self.ctx.symbols.resolve(node.name)
|
|
479
1129
|
if sym is not None:
|
|
480
1130
|
inferred = self._infer_type(node.value)
|
|
@@ -482,8 +1132,8 @@ class TypeInferer:
|
|
|
482
1132
|
return inferred
|
|
483
1133
|
cpp_type = PINE_TYPE_TO_CPP.get(sym.pine_type, "double")
|
|
484
1134
|
if cpp_type != "double" or sym.pine_type != PineType.UNKNOWN:
|
|
485
|
-
return cpp_type
|
|
486
|
-
return self._infer_type(node.value)
|
|
1135
|
+
return promote_wide_int(cpp_type)
|
|
1136
|
+
return promote_wide_int(self._infer_type(node.value))
|
|
487
1137
|
|
|
488
1138
|
def _series_type_for(self, name: str) -> str:
|
|
489
1139
|
"""C++ element type for a series variable's history buffer."""
|
|
@@ -499,6 +1149,323 @@ class TypeInferer:
|
|
|
499
1149
|
return PINE_TYPE_TO_CPP.get(sym.pine_type, "double")
|
|
500
1150
|
return "double"
|
|
501
1151
|
|
|
1152
|
+
def _series_param_element_cpp_type(
|
|
1153
|
+
self,
|
|
1154
|
+
func_info,
|
|
1155
|
+
index: int,
|
|
1156
|
+
call_site_idx: int | None = None,
|
|
1157
|
+
) -> str:
|
|
1158
|
+
"""Canonical element type for one history-referenced UDF parameter.
|
|
1159
|
+
|
|
1160
|
+
A Pine parameter used as ``src[n]`` is a typed time series, not an
|
|
1161
|
+
implicitly-float series. The old emitter hard-coded every such
|
|
1162
|
+
parameter as ``Series<double>``. That happened to work for price
|
|
1163
|
+
sources, but rejected real ``series int``/``series bool`` bindings and
|
|
1164
|
+
let synthetic-expression bridges conceal the same mismatch by
|
|
1165
|
+
converting their values to double.
|
|
1166
|
+
|
|
1167
|
+
Pine ``int`` also carries epoch-millisecond builtins such as ``time``.
|
|
1168
|
+
Use the codegen's existing wide integer representation at this
|
|
1169
|
+
callable boundary so one declared ``int`` parameter can safely accept
|
|
1170
|
+
both ``bar_index`` and timestamp values without narrowing. Scalar
|
|
1171
|
+
integer values are widened when a synthetic bridge is required.
|
|
1172
|
+
"""
|
|
1173
|
+
declared_specs = list(
|
|
1174
|
+
getattr(self.ctx, "func_declared_param_type_specs", {}).get(
|
|
1175
|
+
func_info.name, ()
|
|
1176
|
+
)
|
|
1177
|
+
)
|
|
1178
|
+
declared_spec = (
|
|
1179
|
+
declared_specs[index] if index < len(declared_specs) else None
|
|
1180
|
+
)
|
|
1181
|
+
# A declared type is authoritative across every written call. Only a
|
|
1182
|
+
# truly untyped slot may consult the per-callsite specialization map.
|
|
1183
|
+
spec = declared_spec
|
|
1184
|
+
if spec is not None and spec.kind == "primitive":
|
|
1185
|
+
if spec.name == "int":
|
|
1186
|
+
return "int64_t"
|
|
1187
|
+
if spec.name == "bool":
|
|
1188
|
+
return "bool"
|
|
1189
|
+
if spec.name == "color":
|
|
1190
|
+
return "int"
|
|
1191
|
+
if spec.name == "float":
|
|
1192
|
+
return "double"
|
|
1193
|
+
|
|
1194
|
+
if call_site_idx is not None:
|
|
1195
|
+
variant = getattr(
|
|
1196
|
+
self.ctx, "func_callsite_param_types", {}
|
|
1197
|
+
).get((func_info.name, call_site_idx))
|
|
1198
|
+
pine_type = (
|
|
1199
|
+
variant[index]
|
|
1200
|
+
if variant is not None and index < len(variant)
|
|
1201
|
+
else PineType.UNKNOWN
|
|
1202
|
+
)
|
|
1203
|
+
if pine_type == PineType.INT:
|
|
1204
|
+
return "int64_t"
|
|
1205
|
+
if pine_type == PineType.BOOL:
|
|
1206
|
+
return "bool"
|
|
1207
|
+
if pine_type == PineType.COLOR:
|
|
1208
|
+
return "int"
|
|
1209
|
+
if pine_type == PineType.FLOAT:
|
|
1210
|
+
return "double"
|
|
1211
|
+
|
|
1212
|
+
specs = list(getattr(func_info, "param_type_specs", ()) or ())
|
|
1213
|
+
spec = specs[index] if index < len(specs) else None
|
|
1214
|
+
if spec is not None and spec.kind == "primitive":
|
|
1215
|
+
if spec.name == "int":
|
|
1216
|
+
return "int64_t"
|
|
1217
|
+
if spec.name == "bool":
|
|
1218
|
+
return "bool"
|
|
1219
|
+
if spec.name == "color":
|
|
1220
|
+
return "int"
|
|
1221
|
+
if spec.name == "float":
|
|
1222
|
+
return "double"
|
|
1223
|
+
param_types = list(getattr(func_info, "param_types", ()) or ())
|
|
1224
|
+
pine_type = param_types[index] if index < len(param_types) else None
|
|
1225
|
+
if pine_type == PineType.INT:
|
|
1226
|
+
return "int64_t"
|
|
1227
|
+
if pine_type == PineType.BOOL:
|
|
1228
|
+
return "bool"
|
|
1229
|
+
if pine_type == PineType.COLOR:
|
|
1230
|
+
return "int"
|
|
1231
|
+
return "double"
|
|
1232
|
+
|
|
1233
|
+
def _callsite_callable_return_pine_type(
|
|
1234
|
+
self, func_info, call_site_idx: int | None
|
|
1235
|
+
):
|
|
1236
|
+
if call_site_idx is not None:
|
|
1237
|
+
variant = getattr(
|
|
1238
|
+
self.ctx, "func_callsite_return_types", {}
|
|
1239
|
+
).get((func_info.name, call_site_idx))
|
|
1240
|
+
if variant is not None and variant != PineType.UNKNOWN:
|
|
1241
|
+
return variant
|
|
1242
|
+
return getattr(func_info, "return_type", PineType.UNKNOWN)
|
|
1243
|
+
|
|
1244
|
+
def _func_int_return_uses_wide_history(
|
|
1245
|
+
self,
|
|
1246
|
+
func_info,
|
|
1247
|
+
seen: set[str] | None = None,
|
|
1248
|
+
call_site_idx: int | None = None,
|
|
1249
|
+
) -> bool:
|
|
1250
|
+
"""Whether an integer callable must preserve the ``int64_t`` family.
|
|
1251
|
+
|
|
1252
|
+
History-referenced ``int`` parameters are one source of wide values,
|
|
1253
|
+
but not the only one. A parameterless helper can capture ``time[1]``
|
|
1254
|
+
directly, and an otherwise-pure wrapper can return that helper. The
|
|
1255
|
+
analyzer's Pine-level return type is intentionally just ``INT`` in all
|
|
1256
|
+
of those cases, so follow the terminal expression/call chain here to
|
|
1257
|
+
prevent the C++ callable boundary from narrowing epoch milliseconds.
|
|
1258
|
+
"""
|
|
1259
|
+
if self._callsite_callable_return_pine_type(
|
|
1260
|
+
func_info, call_site_idx
|
|
1261
|
+
) != PineType.INT:
|
|
1262
|
+
return False
|
|
1263
|
+
node = getattr(func_info, "node", None)
|
|
1264
|
+
if node is None:
|
|
1265
|
+
return False
|
|
1266
|
+
|
|
1267
|
+
if seen is None:
|
|
1268
|
+
seen = set()
|
|
1269
|
+
func_name = getattr(func_info, "name", "")
|
|
1270
|
+
if func_name in seen:
|
|
1271
|
+
return False
|
|
1272
|
+
seen = {*seen, func_name}
|
|
1273
|
+
|
|
1274
|
+
series_names = self.ctx.func_series_vars.get(func_info.name, set())
|
|
1275
|
+
if any(
|
|
1276
|
+
param in series_names
|
|
1277
|
+
and self._series_param_element_cpp_type(
|
|
1278
|
+
func_info, index, call_site_idx
|
|
1279
|
+
)
|
|
1280
|
+
== "int64_t"
|
|
1281
|
+
for index, param in enumerate(node.params)
|
|
1282
|
+
):
|
|
1283
|
+
return True
|
|
1284
|
+
|
|
1285
|
+
if not node.body:
|
|
1286
|
+
return False
|
|
1287
|
+
terminal = node.body[-1]
|
|
1288
|
+
if isinstance(terminal, ExprStmt):
|
|
1289
|
+
terminal = terminal.expr
|
|
1290
|
+
return self._expr_returns_wide_int(
|
|
1291
|
+
terminal, func_info, seen, call_site_idx
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
def _expr_returns_wide_int(
|
|
1295
|
+
self,
|
|
1296
|
+
expr,
|
|
1297
|
+
owner_info,
|
|
1298
|
+
seen: set[str],
|
|
1299
|
+
call_site_idx: int | None = None,
|
|
1300
|
+
) -> bool:
|
|
1301
|
+
"""Trace wide integer provenance through a callable return expression."""
|
|
1302
|
+
if expr is None:
|
|
1303
|
+
return False
|
|
1304
|
+
if isinstance(expr, Subscript):
|
|
1305
|
+
return self._expr_returns_wide_int(
|
|
1306
|
+
expr.object, owner_info, seen, call_site_idx
|
|
1307
|
+
)
|
|
1308
|
+
if isinstance(expr, Identifier):
|
|
1309
|
+
# A terminal local can be a narrow-looking alias for ``time[1]``.
|
|
1310
|
+
# Resolve its declaration inside this callable without consulting
|
|
1311
|
+
# the already-popped analyzer scope.
|
|
1312
|
+
node = getattr(owner_info, "node", None)
|
|
1313
|
+
if node is not None:
|
|
1314
|
+
if expr.name in node.params:
|
|
1315
|
+
return False
|
|
1316
|
+
local_key = (
|
|
1317
|
+
f"{getattr(owner_info, 'name', '')}:local:{expr.name}"
|
|
1318
|
+
)
|
|
1319
|
+
if local_key in seen:
|
|
1320
|
+
return False
|
|
1321
|
+
local_seen = {*seen, local_key}
|
|
1322
|
+
for child in self._walk_ast_list(node.body):
|
|
1323
|
+
if (
|
|
1324
|
+
isinstance(child, VarDecl)
|
|
1325
|
+
and child.name == expr.name
|
|
1326
|
+
and child.value is not None
|
|
1327
|
+
):
|
|
1328
|
+
if self._expr_returns_wide_int(
|
|
1329
|
+
child.value,
|
|
1330
|
+
owner_info,
|
|
1331
|
+
local_seen,
|
|
1332
|
+
call_site_idx,
|
|
1333
|
+
):
|
|
1334
|
+
return True
|
|
1335
|
+
if (
|
|
1336
|
+
isinstance(child, Assignment)
|
|
1337
|
+
and isinstance(child.target, Identifier)
|
|
1338
|
+
and child.target.name == expr.name
|
|
1339
|
+
):
|
|
1340
|
+
if self._expr_returns_wide_int(
|
|
1341
|
+
child.value,
|
|
1342
|
+
owner_info,
|
|
1343
|
+
local_seen,
|
|
1344
|
+
call_site_idx,
|
|
1345
|
+
):
|
|
1346
|
+
return True
|
|
1347
|
+
return self._expr_is_int64_builtin(expr)
|
|
1348
|
+
if self._expr_is_int64_builtin(expr):
|
|
1349
|
+
return True
|
|
1350
|
+
if isinstance(expr, FuncCall):
|
|
1351
|
+
func_name, namespace = self._resolve_callee(expr.callee)
|
|
1352
|
+
if namespace is None and func_name in {
|
|
1353
|
+
"int",
|
|
1354
|
+
"float",
|
|
1355
|
+
"nz",
|
|
1356
|
+
"fixnan",
|
|
1357
|
+
}:
|
|
1358
|
+
# Casts and Pine's missing-value wrappers preserve the wide
|
|
1359
|
+
# integer provenance needed by an int destination. ``nz`` may
|
|
1360
|
+
# obtain it from either the source or replacement argument.
|
|
1361
|
+
return any(
|
|
1362
|
+
self._expr_returns_wide_int(
|
|
1363
|
+
arg, owner_info, seen, call_site_idx
|
|
1364
|
+
)
|
|
1365
|
+
for arg in expr.args
|
|
1366
|
+
)
|
|
1367
|
+
if namespace == "math" and func_name in {"abs", "max", "min"}:
|
|
1368
|
+
return any(
|
|
1369
|
+
self._expr_returns_wide_int(
|
|
1370
|
+
arg, owner_info, seen, call_site_idx
|
|
1371
|
+
)
|
|
1372
|
+
for arg in expr.args
|
|
1373
|
+
)
|
|
1374
|
+
|
|
1375
|
+
callee_info = None
|
|
1376
|
+
if namespace is None and func_name:
|
|
1377
|
+
callee_info = self._func_info_map.get(func_name)
|
|
1378
|
+
elif isinstance(expr.callee, MemberAccess):
|
|
1379
|
+
receiver = expr.callee.object
|
|
1380
|
+
owner_node = getattr(owner_info, "node", None)
|
|
1381
|
+
if (
|
|
1382
|
+
getattr(owner_info, "is_udt_method", False)
|
|
1383
|
+
and owner_node is not None
|
|
1384
|
+
and owner_node.params
|
|
1385
|
+
and isinstance(receiver, Identifier)
|
|
1386
|
+
and receiver.name == owner_node.params[0]
|
|
1387
|
+
):
|
|
1388
|
+
receiver_specs = list(
|
|
1389
|
+
getattr(owner_info, "param_type_specs", ()) or ()
|
|
1390
|
+
)
|
|
1391
|
+
receiver_name = method_receiver_type_name(
|
|
1392
|
+
receiver_specs[0] if receiver_specs else None
|
|
1393
|
+
) or getattr(owner_info, "udt_type_name", None)
|
|
1394
|
+
if receiver_name is not None:
|
|
1395
|
+
callee_info = self._func_info_map.get(
|
|
1396
|
+
f"{receiver_name}.{expr.callee.member}"
|
|
1397
|
+
)
|
|
1398
|
+
if callee_info is None:
|
|
1399
|
+
receiver_spec = self._type_spec_from_expr(receiver)
|
|
1400
|
+
receiver_name = method_receiver_type_name(
|
|
1401
|
+
receiver_spec
|
|
1402
|
+
)
|
|
1403
|
+
if receiver_name is not None:
|
|
1404
|
+
callee_info = self._func_info_map.get(
|
|
1405
|
+
f"{receiver_name}.{expr.callee.member}"
|
|
1406
|
+
)
|
|
1407
|
+
return (
|
|
1408
|
+
callee_info is not None
|
|
1409
|
+
and self._func_int_return_uses_wide_history(
|
|
1410
|
+
callee_info,
|
|
1411
|
+
seen,
|
|
1412
|
+
call_site_idx=call_site_idx,
|
|
1413
|
+
)
|
|
1414
|
+
)
|
|
1415
|
+
if isinstance(expr, BinOp):
|
|
1416
|
+
return (
|
|
1417
|
+
self._expr_returns_wide_int(
|
|
1418
|
+
expr.left, owner_info, seen, call_site_idx
|
|
1419
|
+
)
|
|
1420
|
+
or self._expr_returns_wide_int(
|
|
1421
|
+
expr.right, owner_info, seen, call_site_idx
|
|
1422
|
+
)
|
|
1423
|
+
)
|
|
1424
|
+
if isinstance(expr, UnaryOp):
|
|
1425
|
+
return self._expr_returns_wide_int(
|
|
1426
|
+
expr.operand, owner_info, seen, call_site_idx
|
|
1427
|
+
)
|
|
1428
|
+
if isinstance(expr, Ternary):
|
|
1429
|
+
return (
|
|
1430
|
+
self._expr_returns_wide_int(
|
|
1431
|
+
expr.true_val, owner_info, seen, call_site_idx
|
|
1432
|
+
)
|
|
1433
|
+
or self._expr_returns_wide_int(
|
|
1434
|
+
expr.false_val, owner_info, seen, call_site_idx
|
|
1435
|
+
)
|
|
1436
|
+
)
|
|
1437
|
+
if isinstance(expr, IfStmt):
|
|
1438
|
+
branches = [expr.body, expr.else_body]
|
|
1439
|
+
return any(
|
|
1440
|
+
branch
|
|
1441
|
+
and self._expr_returns_wide_int(
|
|
1442
|
+
branch[-1].expr
|
|
1443
|
+
if isinstance(branch[-1], ExprStmt)
|
|
1444
|
+
else branch[-1],
|
|
1445
|
+
owner_info,
|
|
1446
|
+
seen,
|
|
1447
|
+
call_site_idx,
|
|
1448
|
+
)
|
|
1449
|
+
for branch in branches
|
|
1450
|
+
)
|
|
1451
|
+
if isinstance(expr, SwitchStmt):
|
|
1452
|
+
branches = [body for _, body in expr.cases]
|
|
1453
|
+
if expr.default_body:
|
|
1454
|
+
branches.append(expr.default_body)
|
|
1455
|
+
return any(
|
|
1456
|
+
branch
|
|
1457
|
+
and self._expr_returns_wide_int(
|
|
1458
|
+
branch[-1].expr
|
|
1459
|
+
if isinstance(branch[-1], ExprStmt)
|
|
1460
|
+
else branch[-1],
|
|
1461
|
+
owner_info,
|
|
1462
|
+
seen,
|
|
1463
|
+
call_site_idx,
|
|
1464
|
+
)
|
|
1465
|
+
for branch in branches
|
|
1466
|
+
)
|
|
1467
|
+
return False
|
|
1468
|
+
|
|
502
1469
|
def _expr_is_int64_builtin(self, expr) -> bool:
|
|
503
1470
|
"""True if ``expr`` is a top-level int64-returning Pine builtin: either a
|
|
504
1471
|
call to one of ``INT64_BUILTINS`` (``time(...)``, ``timestamp(...)``, …)
|
|
@@ -514,6 +1481,28 @@ class TypeInferer:
|
|
|
514
1481
|
return expr.name in INT64_BUILTIN_IDENTIFIERS
|
|
515
1482
|
return False
|
|
516
1483
|
|
|
1484
|
+
def _fixnan_site_cpp_type(self, site) -> str:
|
|
1485
|
+
"""Storage type for one fixnan previous-value slot.
|
|
1486
|
+
|
|
1487
|
+
``PineType.INT`` does not distinguish ordinary counters from epoch
|
|
1488
|
+
milliseconds, but fixnan stores the exact prior value. Inspect the
|
|
1489
|
+
authored argument's wide provenance so the state member cannot narrow
|
|
1490
|
+
``time[1]`` before an otherwise-int64 local receives it.
|
|
1491
|
+
"""
|
|
1492
|
+
cpp_type = PINE_TYPE_TO_CPP.get(site.pine_type, "double")
|
|
1493
|
+
if site.pine_type != PineType.INT:
|
|
1494
|
+
return cpp_type
|
|
1495
|
+
node = getattr(site, "node", None)
|
|
1496
|
+
args = getattr(node, "args", ()) if node is not None else ()
|
|
1497
|
+
owner_info = self._func_info_map.get(
|
|
1498
|
+
getattr(site, "owner_func", None) or ""
|
|
1499
|
+
)
|
|
1500
|
+
if args and self._expr_returns_wide_int(
|
|
1501
|
+
args[0], owner_info, set(), None
|
|
1502
|
+
):
|
|
1503
|
+
return "int64_t"
|
|
1504
|
+
return cpp_type
|
|
1505
|
+
|
|
517
1506
|
def _int64_reassign_targets(self) -> set[str]:
|
|
518
1507
|
"""Names of vars that are reassigned (``:=``/``=``) anywhere in the AST
|
|
519
1508
|
with an RHS that is a top-level int64-returning builtin. Cached on the
|
|
@@ -522,14 +1511,31 @@ class TypeInferer:
|
|
|
522
1511
|
cached = getattr(self, "_int64_reassign_cache", None)
|
|
523
1512
|
if cached is not None:
|
|
524
1513
|
return cached
|
|
525
|
-
from ..ast_nodes import Assignment
|
|
526
1514
|
targets: set[str] = set()
|
|
1515
|
+
# Callable locals need their lexical owner so a reassignment through a
|
|
1516
|
+
# wrapper/method returning wide history is recognized as well as a
|
|
1517
|
+
# direct ``time[1]`` RHS.
|
|
1518
|
+
for info in getattr(self.ctx, "func_infos", ()):
|
|
1519
|
+
node = getattr(info, "node", None)
|
|
1520
|
+
if node is None:
|
|
1521
|
+
continue
|
|
1522
|
+
for child in self._walk_ast_list(node.body):
|
|
1523
|
+
if (
|
|
1524
|
+
isinstance(child, Assignment)
|
|
1525
|
+
and isinstance(child.target, Identifier)
|
|
1526
|
+
and self._expr_returns_wide_int(
|
|
1527
|
+
child.value, info, set(), None
|
|
1528
|
+
)
|
|
1529
|
+
):
|
|
1530
|
+
targets.add(child.target.name)
|
|
527
1531
|
ast = getattr(self.ctx, "ast", None)
|
|
528
1532
|
if ast is not None:
|
|
529
1533
|
for node in self._walk_ast(ast):
|
|
530
1534
|
if (isinstance(node, Assignment)
|
|
531
1535
|
and isinstance(node.target, Identifier)
|
|
532
|
-
and self.
|
|
1536
|
+
and self._expr_returns_wide_int(
|
|
1537
|
+
node.value, None, set(), None
|
|
1538
|
+
)):
|
|
533
1539
|
targets.add(node.target.name)
|
|
534
1540
|
self._int64_reassign_cache = targets
|
|
535
1541
|
return targets
|
|
@@ -552,134 +1558,62 @@ class TypeInferer:
|
|
|
552
1558
|
return True
|
|
553
1559
|
return name in self._int64_reassign_targets()
|
|
554
1560
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
def _is_udt_lvalue(self, expr) -> str | None:
|
|
560
|
-
"""If ``expr`` is a *user-defined* UDT lvalue (a bare ``Identifier`` that
|
|
561
|
-
names a class-scope ``var``/global UDT member, e.g. ``wyckoffSwingLow``,
|
|
562
|
-
or an element selected from ``array<UDT>``), return its UDT type name;
|
|
563
|
-
else ``None``.
|
|
1561
|
+
def _na_reassign_cpp_type(self, name: str) -> str | None:
|
|
1562
|
+
"""Declared scalar C++ type of a ``:=`` reassignment target ``name``, so a
|
|
1563
|
+
bare-``na`` RHS (``x := na``) can be spelled ``na<T>()`` matching the
|
|
1564
|
+
member/local type instead of the default ``na<double>()``.
|
|
564
1565
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
return None
|
|
584
|
-
if not isinstance(expr, Identifier):
|
|
585
|
-
return None
|
|
586
|
-
udt_t = self._udt_var_types.get(expr.name)
|
|
587
|
-
if udt_t is None or udt_t not in self._udt_defs:
|
|
588
|
-
return None
|
|
589
|
-
if udt_t in DRAWING_TYPE_TO_CPP:
|
|
590
|
-
return None
|
|
591
|
-
# Must be a known global/class-scope member (not a function param or a
|
|
592
|
-
# plain local snapshot) for write-through to be observable.
|
|
593
|
-
if expr.name in getattr(self, "_current_func_locals", set()):
|
|
594
|
-
# A function-local of UDT type that is itself a persistent ``var``
|
|
595
|
-
# member still write-through aliases; but a plain inline local does
|
|
596
|
-
# not represent shared state. Only treat ``var`` func-locals (in
|
|
597
|
-
# func_var_members) as aliasable shared state.
|
|
598
|
-
fname = getattr(self, "_active_func_name", None)
|
|
599
|
-
var_locals = {n for n, _, _ in self.ctx.func_var_members.get(fname, [])} if fname else set()
|
|
600
|
-
if expr.name not in var_locals:
|
|
601
|
-
return None
|
|
602
|
-
return udt_t
|
|
603
|
-
|
|
604
|
-
def _udt_lvalue_selection_type(self, expr) -> str | None:
|
|
605
|
-
"""UDT type if ``expr`` is a UDT lvalue OR a ternary/switch whose every
|
|
606
|
-
selectable branch is a UDT lvalue of the SAME user-defined UDT type.
|
|
607
|
-
Returns ``None`` otherwise (so plain ``UDT a = b`` value-snapshots, calls,
|
|
608
|
-
``.new(...)`` ctors, and mixed/non-lvalue selections never alias)."""
|
|
609
|
-
direct = self._is_udt_lvalue(expr)
|
|
610
|
-
if direct is not None:
|
|
611
|
-
return direct
|
|
612
|
-
branches: list = []
|
|
613
|
-
if isinstance(expr, Ternary):
|
|
614
|
-
branches = [expr.true_val, expr.false_val]
|
|
615
|
-
elif isinstance(expr, SwitchStmt):
|
|
616
|
-
for _case_expr, stmts in (expr.cases or []):
|
|
617
|
-
if not stmts:
|
|
618
|
-
return None
|
|
619
|
-
last = stmts[-1]
|
|
620
|
-
branches.append(last.expr if isinstance(last, ExprStmt) else last)
|
|
621
|
-
if expr.default_body:
|
|
622
|
-
last = expr.default_body[-1]
|
|
623
|
-
branches.append(last.expr if isinstance(last, ExprStmt) else last)
|
|
624
|
-
else:
|
|
1566
|
+
Assigning a double quiet-NaN into an ``int``/``int64_t``/``bool`` member is
|
|
1567
|
+
undefined behaviour (NaN->int is unspecified; on ARM64 it saturates to 0,
|
|
1568
|
+
which is not the ``na<T>()`` sentinel) and defeats ``is_na<T>()``. Mirrors
|
|
1569
|
+
the member-declaration type logic (``base._emit_class_members`` /
|
|
1570
|
+
``_typed_na_init``): ``PINE_TYPE_TO_CPP`` plus the int->int64_t epoch
|
|
1571
|
+
promotion. Returns ``None`` for collections / UDT / drawing handles and
|
|
1572
|
+
for ``double`` (already the default lowering), so those paths are
|
|
1573
|
+
unchanged.
|
|
1574
|
+
"""
|
|
1575
|
+
# Maps and matrices use their default-constructed null ID for Pine
|
|
1576
|
+
# ``na``. Arrays still lack a nullable runtime representation.
|
|
1577
|
+
collection_spec = self._collection_spec_for_name(name)
|
|
1578
|
+
if (collection_spec is not None
|
|
1579
|
+
and collection_spec.kind in {"map", "matrix"}):
|
|
1580
|
+
return self._type_spec_to_cpp(collection_spec)
|
|
1581
|
+
if ((collection_spec is not None
|
|
1582
|
+
and collection_spec.kind == "array")
|
|
1583
|
+
or self._identifier_udt_type(name) is not None):
|
|
625
1584
|
return None
|
|
626
|
-
|
|
1585
|
+
cpp_type: str | None = None
|
|
1586
|
+
# 1. ``var`` member (class-scope OR function-local: both are recorded in
|
|
1587
|
+
# ctx.var_members). This is the authoritative declaration source.
|
|
1588
|
+
for vname, ptype, _init in self.ctx.var_members:
|
|
1589
|
+
if vname == name:
|
|
1590
|
+
cpp_type = PINE_TYPE_TO_CPP.get(ptype, "double")
|
|
1591
|
+
break
|
|
1592
|
+
# 2. Function-local plain (non-``var``) scalar: its declared type was
|
|
1593
|
+
# remembered at the VarDecl (``_type_for_decl``).
|
|
1594
|
+
if cpp_type is None:
|
|
1595
|
+
cpp_type = getattr(self, "_current_func_local_types", {}).get(name)
|
|
1596
|
+
# 3. Function parameter.
|
|
1597
|
+
if cpp_type is None:
|
|
1598
|
+
cpp_type = getattr(self, "_current_func_param_types", {}).get(name)
|
|
1599
|
+
# 4. Global-scope non-``var`` class member.
|
|
1600
|
+
if cpp_type is None:
|
|
1601
|
+
for gname, gptype in self.ctx.global_var_decls:
|
|
1602
|
+
if gname == name:
|
|
1603
|
+
cpp_type = PINE_TYPE_TO_CPP.get(gptype, "double")
|
|
1604
|
+
break
|
|
1605
|
+
if cpp_type is None:
|
|
627
1606
|
return None
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1607
|
+
# int -> int64_t promotion for epoch-ms builtins, mirroring the member
|
|
1608
|
+
# declaration so the na sentinel width matches the storage width.
|
|
1609
|
+
if cpp_type == "int" and self._is_int64_builtin_init(name):
|
|
1610
|
+
cpp_type = "int64_t"
|
|
1611
|
+
# Only the retypeable scalar types are meaningful; ``double`` already
|
|
1612
|
+
# lowers to ``na<double>()`` and everything else is left untouched.
|
|
1613
|
+
if cpp_type in ("int", "int64_t", "bool", "std::string"):
|
|
1614
|
+
return cpp_type
|
|
631
1615
|
return None
|
|
632
1616
|
|
|
633
|
-
def _udt_local_alias_kind(self, node: VarDecl) -> tuple[str, str] | None:
|
|
634
|
-
"""Decide whether a hintless/typed local UDT declaration must ALIAS the
|
|
635
|
-
global(s) it selects rather than value-copy (BUG C).
|
|
636
|
-
|
|
637
|
-
Returns ``("ref", udt_type)`` for a non-rebinding reference alias,
|
|
638
|
-
``("ptr", udt_type)`` for a pointer alias (the local is later reassigned
|
|
639
|
-
to a *different* UDT lvalue, which a C++ reference cannot do), or
|
|
640
|
-
``None`` to keep the existing value-copy semantics.
|
|
641
|
-
|
|
642
|
-
Conditions (all required):
|
|
643
|
-
* RHS is a UDT lvalue or a ternary/switch selecting same-typed UDT
|
|
644
|
-
lvalues (``_udt_lvalue_selection_type``).
|
|
645
|
-
* The local is MUTATED later in the enclosing function body
|
|
646
|
-
(``local.field := ...``) — a pure read-only snapshot needn't alias.
|
|
647
|
-
|
|
648
|
-
The mutation requirement is the safety guard: a local that is only read
|
|
649
|
-
keeps value semantics, and a local initialised from a non-lvalue (a
|
|
650
|
-
``.new()`` ctor, a function return, or a plain local copy) returns
|
|
651
|
-
``None`` here, preserving intentional independent-copy semantics."""
|
|
652
|
-
from ..ast_nodes import Assignment
|
|
653
|
-
body = getattr(self, "_current_func_body", None)
|
|
654
|
-
if body is None:
|
|
655
|
-
return None
|
|
656
|
-
udt_t = self._udt_lvalue_selection_type(node.value)
|
|
657
|
-
if udt_t is None:
|
|
658
|
-
return None
|
|
659
|
-
name = node.name
|
|
660
|
-
mutated = False
|
|
661
|
-
rebinds_to_other_lvalue = False
|
|
662
|
-
for stmt in self._walk_ast_list(body):
|
|
663
|
-
if not isinstance(stmt, Assignment):
|
|
664
|
-
continue
|
|
665
|
-
tgt = stmt.target
|
|
666
|
-
# Mutation through the local: ``p.field := ...``
|
|
667
|
-
if (isinstance(tgt, MemberAccess)
|
|
668
|
-
and isinstance(tgt.object, Identifier)
|
|
669
|
-
and tgt.object.name == name):
|
|
670
|
-
mutated = True
|
|
671
|
-
# Rebind of the local itself to another UDT lvalue: ``p := other``
|
|
672
|
-
elif isinstance(tgt, Identifier) and tgt.name == name:
|
|
673
|
-
if self._udt_lvalue_selection_type(stmt.value) is not None:
|
|
674
|
-
rebinds_to_other_lvalue = True
|
|
675
|
-
else:
|
|
676
|
-
# Reassigned to a non-lvalue (e.g. ``.new()`` / a copy):
|
|
677
|
-
# aliasing would be wrong; bail to value-copy.
|
|
678
|
-
return None
|
|
679
|
-
if not mutated:
|
|
680
|
-
return None
|
|
681
|
-
return ("ptr" if rebinds_to_other_lvalue else "ref"), udt_t
|
|
682
|
-
|
|
683
1617
|
# ------------------------------------------------------------------
|
|
684
1618
|
# BUG 2: collection (array / map / matrix) lvalue aliasing
|
|
685
1619
|
# ------------------------------------------------------------------
|
|
@@ -692,15 +1626,9 @@ class TypeInferer:
|
|
|
692
1626
|
if not isinstance(expr, Identifier):
|
|
693
1627
|
return None
|
|
694
1628
|
name = expr.name
|
|
695
|
-
|
|
696
|
-
return self._matrix_specs[name]
|
|
697
|
-
spec = self._collection_types.get(name)
|
|
1629
|
+
spec = self._collection_spec_for_name(name)
|
|
698
1630
|
if spec is not None and spec.kind in ("array", "map", "matrix"):
|
|
699
1631
|
return spec
|
|
700
|
-
if name in self._array_vars:
|
|
701
|
-
return self._array_spec_for_name(name)
|
|
702
|
-
if name in self._map_vars:
|
|
703
|
-
return self._map_spec_for_name(name)
|
|
704
1632
|
return None
|
|
705
1633
|
|
|
706
1634
|
def _collection_lvalue_selection_spec(self, expr):
|
|
@@ -708,7 +1636,7 @@ class TypeInferer:
|
|
|
708
1636
|
whose every selectable branch is a collection lvalue of the SAME C++
|
|
709
1637
|
type; ``None`` otherwise (so ``array.new(...)`` ctors, copies, function
|
|
710
1638
|
returns, and mixed selections keep value-copy semantics). Mirrors
|
|
711
|
-
|
|
1639
|
+
the former UDT alias-selection path."""
|
|
712
1640
|
direct = self._collection_lvalue_spec(expr)
|
|
713
1641
|
if direct is not None:
|
|
714
1642
|
return direct
|
|
@@ -768,22 +1696,6 @@ class TypeInferer:
|
|
|
768
1696
|
for s in stmts:
|
|
769
1697
|
yield from self._walk_ast(s)
|
|
770
1698
|
|
|
771
|
-
def _addr_of_udt_selection(self, expr, local_name: str):
|
|
772
|
-
"""Render the address-of form of a UDT lvalue selection for a pointer
|
|
773
|
-
alias (BUG C rebind case): ``other`` -> ``&(other)``;
|
|
774
|
-
``cond ? a : b`` -> ``(cond ? &(a) : &(b))``. The selectable branches are
|
|
775
|
-
guaranteed (by ``_udt_lvalue_selection_type``) to be UDT lvalues."""
|
|
776
|
-
if isinstance(expr, Identifier):
|
|
777
|
-
return f"&({self._safe_name(expr.name)})"
|
|
778
|
-
if isinstance(expr, Ternary):
|
|
779
|
-
cond = self._visit_expr(expr.condition)
|
|
780
|
-
t = self._addr_of_udt_selection(expr.true_val, local_name)
|
|
781
|
-
f = self._addr_of_udt_selection(expr.false_val, local_name)
|
|
782
|
-
return f"({cond} ? {t} : {f})"
|
|
783
|
-
# Switch selection: lower to nested ternaries over case equality. Rare in
|
|
784
|
-
# practice; fall back to address-of the whole lowered expression.
|
|
785
|
-
return f"&({self._visit_expr(expr)})"
|
|
786
|
-
|
|
787
1699
|
def _infer_cpp_type_for_security_elem(self, node) -> str:
|
|
788
1700
|
"""C++ type for one element of the ``request.security(..., expr, ...)`` payload.
|
|
789
1701
|
|
|
@@ -829,6 +1741,10 @@ class TypeInferer:
|
|
|
829
1741
|
and ternaries / if / switch expressions. Returns the string
|
|
830
1742
|
``"double"`` as the safe fallback when no narrower type can be
|
|
831
1743
|
determined."""
|
|
1744
|
+
if isinstance(node, Subscript) and isinstance(node.object, FuncCall):
|
|
1745
|
+
# A callable-result history read keeps the callable's scalar
|
|
1746
|
+
# family. Collection subscripts follow separate element-type paths.
|
|
1747
|
+
return self._infer_type(node.object)
|
|
832
1748
|
if isinstance(node, NumberLiteral):
|
|
833
1749
|
return "double" if isinstance(node.value, float) else "int"
|
|
834
1750
|
if isinstance(node, BoolLiteral):
|
|
@@ -842,7 +1758,8 @@ class TypeInferer:
|
|
|
842
1758
|
return "int64_t"
|
|
843
1759
|
if node.name in BAR_FIELDS or node.name in BAR_BUILTINS:
|
|
844
1760
|
return "double"
|
|
845
|
-
if node.name in self._known_vars
|
|
1761
|
+
if (node.name in self._known_vars
|
|
1762
|
+
and not self._known_var_is_lexically_shadowed(node.name)):
|
|
846
1763
|
val = self._known_vars[node.name]
|
|
847
1764
|
if isinstance(val, bool):
|
|
848
1765
|
return "bool"
|
|
@@ -852,10 +1769,16 @@ class TypeInferer:
|
|
|
852
1769
|
return "int"
|
|
853
1770
|
if isinstance(val, float):
|
|
854
1771
|
return "double"
|
|
1772
|
+
if node.name in getattr(
|
|
1773
|
+
self, "_current_func_series_param_types", {}
|
|
1774
|
+
):
|
|
1775
|
+
return self._current_func_series_param_types[node.name]
|
|
855
1776
|
if node.name in self._current_func_param_types:
|
|
856
1777
|
return self._current_func_param_types[node.name]
|
|
857
1778
|
if node.name in getattr(self, "_current_func_local_types", {}):
|
|
858
1779
|
return self._current_func_local_types[node.name]
|
|
1780
|
+
if node.name in getattr(self, "_current_loop_vars", set()):
|
|
1781
|
+
return "double"
|
|
859
1782
|
sym = self.ctx.symbols.resolve(node.name)
|
|
860
1783
|
if sym is not None and getattr(sym, "type_spec", None) is not None:
|
|
861
1784
|
return self._type_spec_to_cpp(sym.type_spec)
|
|
@@ -864,6 +1787,18 @@ class TypeInferer:
|
|
|
864
1787
|
return "double"
|
|
865
1788
|
if isinstance(node, FuncCall):
|
|
866
1789
|
func_name, namespace = self._resolve_callee(node.callee)
|
|
1790
|
+
# Nested trade-accessor calls bypass the flat namespace signature
|
|
1791
|
+
# table. Their textual metadata accessors return std::string from
|
|
1792
|
+
# the runtime, so hintless locals must not use the double fallback.
|
|
1793
|
+
if (isinstance(node.callee, MemberAccess)
|
|
1794
|
+
and isinstance(node.callee.object, MemberAccess)
|
|
1795
|
+
and isinstance(node.callee.object.object, Identifier)
|
|
1796
|
+
and node.callee.object.object.name == "strategy"
|
|
1797
|
+
and node.callee.object.member in ("closedtrades", "opentrades")
|
|
1798
|
+
and func_name in (
|
|
1799
|
+
"entry_id", "exit_id", "entry_comment", "exit_comment",
|
|
1800
|
+
)):
|
|
1801
|
+
return "std::string"
|
|
867
1802
|
# Drawing scalar getter return type (get_text -> std::string,
|
|
868
1803
|
# get_x* -> int64_t, get_y*/get_price/get_top/get_bottom -> double).
|
|
869
1804
|
if getattr(self, "_uses_drawing", False):
|
|
@@ -912,17 +1847,48 @@ class TypeInferer:
|
|
|
912
1847
|
recv_spec = self._type_spec_from_expr(node.callee.object)
|
|
913
1848
|
if recv_spec is not None and recv_spec.kind == "array" and member_name == "join":
|
|
914
1849
|
return "std::string"
|
|
915
|
-
|
|
916
|
-
|
|
1850
|
+
receiver_name = method_receiver_type_name(recv_spec)
|
|
1851
|
+
if receiver_name is not None:
|
|
1852
|
+
fi_u = self._func_info_map.get(
|
|
1853
|
+
f"{receiver_name}.{member_name}"
|
|
1854
|
+
)
|
|
917
1855
|
if fi_u is not None:
|
|
918
|
-
|
|
1856
|
+
call_site_idx = self._callable_target_callsite_idx(
|
|
1857
|
+
fi_u, node
|
|
1858
|
+
)
|
|
1859
|
+
if self._func_int_return_uses_wide_history(
|
|
1860
|
+
fi_u, call_site_idx=call_site_idx
|
|
1861
|
+
):
|
|
1862
|
+
return "int64_t"
|
|
1863
|
+
if getattr(fi_u, "return_type_spec", None) is not None:
|
|
1864
|
+
return self._type_spec_to_cpp(fi_u.return_type_spec)
|
|
1865
|
+
return PINE_TYPE_TO_CPP.get(
|
|
1866
|
+
self._callsite_callable_return_pine_type(
|
|
1867
|
+
fi_u, call_site_idx
|
|
1868
|
+
),
|
|
1869
|
+
"double",
|
|
1870
|
+
)
|
|
1871
|
+
if namespace is None and func_name in self._func_info_map:
|
|
1872
|
+
fi_u = self._func_info_map[func_name]
|
|
1873
|
+
call_site_idx = self._callable_target_callsite_idx(fi_u, node)
|
|
1874
|
+
if self._func_int_return_uses_wide_history(
|
|
1875
|
+
fi_u, call_site_idx=call_site_idx
|
|
1876
|
+
):
|
|
1877
|
+
return "int64_t"
|
|
919
1878
|
spec = self._type_spec_from_expr(node)
|
|
920
1879
|
if spec is not None:
|
|
921
1880
|
return self._type_spec_to_cpp(spec)
|
|
922
1881
|
if namespace in self._udt_defs and func_name == "new":
|
|
923
1882
|
return namespace
|
|
924
1883
|
if namespace is None and func_name in self._func_info_map:
|
|
925
|
-
|
|
1884
|
+
fi_u = self._func_info_map[func_name]
|
|
1885
|
+
return PINE_TYPE_TO_CPP.get(
|
|
1886
|
+
self._callsite_callable_return_pine_type(
|
|
1887
|
+
fi_u,
|
|
1888
|
+
self._callable_target_callsite_idx(fi_u, node),
|
|
1889
|
+
),
|
|
1890
|
+
"double",
|
|
1891
|
+
)
|
|
926
1892
|
site = self._get_ta_site(node)
|
|
927
1893
|
if site is not None:
|
|
928
1894
|
ta_name = self._ta_name_from_site(site)
|
|
@@ -965,6 +1931,11 @@ class TypeInferer:
|
|
|
965
1931
|
if spec is not None:
|
|
966
1932
|
return self._type_spec_to_cpp(spec)
|
|
967
1933
|
if isinstance(node, Ternary):
|
|
1934
|
+
ternary_spec = self._type_spec_from_expr(node)
|
|
1935
|
+
if (ternary_spec is not None
|
|
1936
|
+
and ternary_spec.kind == "udt"
|
|
1937
|
+
and ternary_spec.name in DRAWING_TYPE_TO_CPP):
|
|
1938
|
+
return self._type_spec_to_cpp(ternary_spec)
|
|
968
1939
|
tt = self._infer_type(node.true_val)
|
|
969
1940
|
ft = self._infer_type(node.false_val)
|
|
970
1941
|
if tt.startswith("std::vector") or ft.startswith("std::vector"):
|
|
@@ -981,6 +1952,11 @@ class TypeInferer:
|
|
|
981
1952
|
# Block-as-expression cases: read the type of the last statement of
|
|
982
1953
|
# the first branch / case; matches Pine semantics for ``x = if...``.
|
|
983
1954
|
if isinstance(node, IfStmt):
|
|
1955
|
+
block_spec = self._type_spec_from_expr(node)
|
|
1956
|
+
if (block_spec is not None
|
|
1957
|
+
and block_spec.kind == "udt"
|
|
1958
|
+
and block_spec.name in DRAWING_TYPE_TO_CPP):
|
|
1959
|
+
return self._type_spec_to_cpp(block_spec)
|
|
984
1960
|
if node.body:
|
|
985
1961
|
last = node.body[-1]
|
|
986
1962
|
if isinstance(last, ExprStmt):
|
|
@@ -1012,6 +1988,11 @@ class TypeInferer:
|
|
|
1012
1988
|
local_types: dict[str, str] = {}
|
|
1013
1989
|
for stmt in self._walk_ast(func_node):
|
|
1014
1990
|
if isinstance(stmt, VarDecl) and stmt.value is not None and stmt.name:
|
|
1991
|
+
captured = self._callable_collection_bindings.get(id(stmt))
|
|
1992
|
+
if (captured is not None
|
|
1993
|
+
and captured.kind in {"array", "map", "matrix"}):
|
|
1994
|
+
local_types[stmt.name] = self._type_spec_to_cpp(captured)
|
|
1995
|
+
continue
|
|
1015
1996
|
if stmt.type_hint:
|
|
1016
1997
|
spec = self._type_spec_from_hint_name(stmt.type_hint)
|
|
1017
1998
|
if spec is not None:
|