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