diff --git a/pineforge_codegen/codegen/visit_stmt.py b/pineforge_codegen/codegen/visit_stmt.py index b9173b8..1e22e59 100644 --- a/pineforge_codegen/codegen/visit_stmt.py +++ b/pineforge_codegen/codegen/visit_stmt.py @@ -100,7 +100,6 @@ ) from ..symbols import TypeSpec from .tables import ( - SKIP_VAR_TYPES, TA_RETURNS_BOOL, TA_TUPLE_FIELDS, MATRIX_RETURNING_METHODS, @@ -356,16 +355,19 @@ def _visit_var_decl(self, node: VarDecl, lines: list[str], pad: str) -> None: lines.append(f"{pad}{cpp_type} {safe};") return - # Skip visual function assignments — but still emit declaration for - # table function results since the var may be used later + # Visual/drawing function assignments (line.new, label.new, box.new, + # table.new, ...) are no-ops in a backtest, but the assigned variable may + # still be referenced later (e.g. pushed into an array, or used as a + # handle by sibling set_* calls). Emit a default-valued local declaration + # so those references compile; the value is inert. Global members are + # already declared at class scope, so only locals need this. (Previously + # only `table` results were declared, which dropped loop-local line/label + # handles and produced "use of undeclared identifier".) if isinstance(node.value, FuncCall) and self._is_skip_expr(node.value): - func_name, namespace = self._resolve_callee(node.value.callee) - if namespace in SKIP_VAR_TYPES: - # Emit var with default value so references don't fail - if not is_global_member: - cpp_type = self._type_for_decl(node) - default = "0" if cpp_type in ("int", "double") else ('std::string("")' if cpp_type == "std::string" else "false") - lines.append(f"{pad}{cpp_type} {safe} = {default};") + if not is_global_member: + cpp_type = self._type_for_decl(node) + default = "0" if cpp_type in ("int", "double") else ('std::string("")' if cpp_type == "std::string" else "false") + lines.append(f"{pad}{cpp_type} {safe} = {default};") return # TA call diff --git a/tests/test_codegen_drawing_handles.py b/tests/test_codegen_drawing_handles.py new file mode 100644 index 0000000..f4c4478 --- /dev/null +++ b/tests/test_codegen_drawing_handles.py @@ -0,0 +1,33 @@ +"""Regression: loop-local drawing-object handles must be declared. + +Drawing calls (line.new / label.new / box.new / table.new ...) are no-ops in a +backtest, but the variable they're assigned to may still be referenced (e.g. +pushed into an `array`). When the assignment lives in a for-loop body the +handle is a local; it must be declared (as an inert default) or the generated +C++ references an undeclared identifier. +""" + +from pineforge_codegen import transpile + + +def _cpp(body: str) -> str: + return transpile('//@version=6\nstrategy("t")\n' + body + "\n") + + +def test_line_handle_in_loop_is_declared(): + cpp = _cpp( + "var a = array.new()\n" + "for k = 1 to 3\n" + " ln = line.new(bar_index, close, bar_index + 1, close)\n" + " array.push(a, ln)" + ) + assert "double ln" in cpp or "auto ln" in cpp + + +def test_label_handle_in_loop_is_declared(): + cpp = _cpp( + "for k = 1 to 3\n" + " lb = label.new(bar_index, high, 'x')\n" + " label.delete(lb)" + ) + assert "double lb" in cpp or "auto lb" in cpp