Repository navigation
Inline input.*() as a TA length behaves like its bound spelling (fixes #132) - #133
Merged
Merged
Conversation
… end to end (C2 W1) Issue #132: since 0.8.1 `ta.ema(close, input.int(9, "fast"))` is refused ("Unsupported TA constructor length ... neither a compile-time constant nor derived from an input") while `len = input.int(9, "fast")` + `ta.ema(close, len)` is accepted. tests/test_e2e_inline_input_ta_length.py drives each case through the interfaces a client uses: pineforge-app's transpile_json glue (gate/glue.py) in a subprocess, the emitted TU built as a strategy shared library against the engine runtime (the corpus/CMakeLists.txt recipe), and the engine's scripts/run_strategy.py C-ABI harness over the corpus ETH-USDT 1m feed, resampled to 15m by the engine's own resampler into a temp dir. Every case compares the inline spelling with its variable-bound twin: byte-identical engine_trades.csv at the defaults and under an inputs.json override, which must move the trades, and identical input manifests. 74 cases: one per TA constructor with constructor arguments (a guard test ties the list to TA_CLASS_MAP - TA_NO_CTOR), the issue strategy verbatim, every argument form of the inline call (named/reordered defval and title, minval..display, options, plain input(), punctuation in the title, an empty title), arithmetic and math.* over inline inputs, a derived binding, and TA sites reached through request.security, user functions (keyword argument, security inside the helper, a title equal to the parameter name) and a loop. Four more pin that a length which is otherwise a series stays refused with the same diagnostic when an inline input is one of its leaves. At this commit all 74 inline-vs-bound cases fail: 68 are refused with the issue's diagnostic, the `var` twin of one is refused too, and 5 transpile but wrong -- `input.int(defval=9, title="fast")` emits `ta::EMA(get_input_int("", 0))`, `input.int(9, title="fast")` reads key "", a title spelling a derived name ("m", with `m = 2 * 3`) reads key "(2 * 3)", and none of the inline inputs reaches the manifest. The refusal pins and the registry guard pass. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…ound spelling (C2 W2) Root cause. 6545234 (#29, first shipped in 0.8.1) added the TA-constructor guard in _emit_constructor: a ctor arg must fold to a literal or expand, through _runtime_ctor_arg_for_reset, to a runtime expression whose every identifier token is input-backed, a known constant or structural. Ctor args travel as Pine source spellings, and the token scan read an inline call's own argument text -- `input`, `int`, the title `fast` -- as unknown names, so the length was refused. Before that commit 0.7.5-0.8.0 "accepted" it by sizing both EMAs at 1 with no runtime reset (ta::EMA(1): fast == slow == close, a strategy that can never trade). 8390364 later let the bare word `input` through, which admitted argument-free spellings such as input(15) but not a title; and the analyzer's _expr_to_str dropped keyword arguments, so `input.int(defval=9, title="fast")` reached codegen as `input.int()` and silently became ta::EMA(get_input_int("", 0)). Fix: * pine_spelling.py: string-literal-aware helpers -- the Pine literal of a string, identifier substitution that leaves string contents alone, the extent of each inline input call, and a full spelling of an input call. * analyzer _expr_to_str spells an input call with its keyword arguments, escapes string literals and spells list literals (options=[...]). * codegen: an inline input call is admitted exactly when its bound spelling `v = <call>` would make v input-backed (_is_stable_inline_input: not a source input, constant defval; _input_default_value is now the one rule for both). The reset gate masks each admitted call to the bare word `input`; the placeholder, precalc and prepare_script_run sizes read its defval (_fold_inline_input_defaults), as a bound name does through _known_vars; derived-length tracking (`n = input.int(9, "fast") * 2`, `var n = input.int(9, "fast")`) spells it in full and counts it as an input reference; _expand_derived no longer rewrites a title that happens to spell a derived name. * The input manifest lists every global-scope input call, inline ones too, one entry per call site in source order, titled with the key the C++ reads the input by. The inline spelling now emits the bound spelling's C++ except for the bound twin's own member and its source-line call-site ids. A length that is otherwise a series (a bar field, bar_index, a source input) is still refused with the same diagnostic. Evidence: tests/test_e2e_inline_input_ta_length.py 79 passed (was 74 failed); all 312 corpus/validation generated.cpp re-transpile byte-identical; the manifest of two corpus probes gains the inputs their C++ already reads (analyzer-anvil-percent-costs-01: "Stop %", "Target %"; na-ledger-history-anchor-01: "Entry Deviation %"). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…ounts (C2 W3) Adds "Known codegen quirks" entry 10: an inline input.*() call is one leaf of a TA length -- pine_spelling.py keeps it whole in the ctor-arg spelling, the codegen masks its argument text out of every identifier scan, it is admitted exactly when its bound spelling would be input-backed, a length that is otherwise a series stays refused, and the manifest lists every global-scope input call with the key the C++ reads it by. Lists pine_spelling.py in the file map. Re-measured on 2026-09-23 with the engine at engine-main fc7aad62 (corpus gitlink 9182e3d), Eigen and a built runtime: 2937 passed, 2 skipped (the two documented skips), 0 failed; without a compile environment 1952 passed, 674 skipped. The corpus sweep is unchanged at 314 passed. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
luisleo526
marked this pull request as ready for review
September 23, 2026 09:31
luisleo526
added a commit
that referenced
this pull request
Sep 23, 2026
…s the member's key (C3 W4) var n = input.int(9) with no title: the member reads the input under "n" (a var initializer lends its name to the untitled call; the manifest lists "n"), but the TA runtime reset read it under "", so overriding n never resized ta.ema(close, n). Since 097f499 (#54, which turned the shape from a refusal into an accepted TA length), _collect_stable_var_scalars recorded the var's initializer as a derived expression, and the reset re-lowered that spelling as an inline input call with no enclosing name. #133 spells that call with its title and keyword arguments, which fixed the titled form, but an untitled call still has no name to key by. A never-reassigned var bound straight to an input call holds the input's value on every bar, exactly like n = input.int(9), so it is now registered the same way (_collect_known_var): input-backed, read through its call node. Every reader then keys it by _get_input_title(call, var_name=n), and the constructor placeholder is the default (9) as for the plain binding instead of 1. The titled var binding takes the same path; var n = input(9), refused before, is admitted with it. An input whose default is not a constant keeps the derived-expression path. No corpus probe binds an input with var: the 312 re-transpile byte-identical. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #132.
What
Since v0.8.1 an inline
input.*()call used as a TA constructor length (ta.ema(close, input.int(9, "fast"))) was refused with "neither a compile-time constant nor derived from an input", while binding the input to a variable first worked. Inline inputs are now one leaf of a TA length, exactly like their bound spelling, for every TA constructor:pine_spelling.py(new) keeps an inline input call whole inside the constructor-arg strings; the analyzer spells input calls with their keyword arguments and escaped strings.v = <call>would be input-backed (not a source input, constant default), hides the call's argument text from the identifier scans, and uses the default for the placeholder and precalculation sizes; the runtime reset reads the live input by its title.Root cause:
6545234("resolve input-derived TA lengths", #29, first in v0.8.1) added the guard; the identifier scan read the inline call's own text (input,int, the title) as unknown names. v0.7.5–v0.8.0 "accepted" the pattern only by sizing both EMAs at 1 with no runtime reset — the issue's strategy could never trade on those versions either.8390364(v0.9.0) later let a bareinput(15)through but still refused a titled call, and keyword arguments were dropped silently.Evidence
tests/test_e2e_inline_input_ta_length.py, 79 cases): transpile through the app's glue (gate/glue.pytranspile_json) → compile against the engine (mainfc7aad62) → run on the ETH 15m feed (222,295 bars). Every TA constructor with a length (sma … vwap bands, 52 families), keyword/positional/every-parameter forms, arithmetic andmath.*over inputs,request.security, user functions, loops: trades sha256-identical to the bound spelling at defaults and under an input override, the override changes the trades, and the input manifests are identical. Before the fix: 74 failed / 5 passed (68 refused with the issue's diagnostic, 5 silently miscompiled). After: 79 passed.9182e3d) givesidentical=312 changed=0— the C++ is byte-identical; only two probes' input manifests gain entries their C++ already read by title.fc7aad62:exp-r5-c2-inline-input-20260923, 72/72 cases, 0 failed; candidate39c3092a…; offline band1 +0/−0 band2 +0/−0 (score 0, hard regressions 0, coverage lost 0); pr_gatepineforge-pr-gate-8m7nx→ neutral, accepted at net 0.Found on the way (not fixed here)
ta.change(src, n)ignoresn(the length only bounds the history; the emittedcompute(src)always looks back 1 bar) — an existing silent miscompile; no corpus probe usesn > 1. Tracked for a follow-up lane."or\produces invalid C++ (bound and inline alike).var n = input.int(9)without a title: the member reads key"n"but the TA reset reads"", so an override does not resize the indicator.A release that carries this fix is the owner's call.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LifAW9e5J6gFx7gatuxETE