Repository navigation
Use the documented str.format_time defaults, add PF-E2081 and PF-E2087, reserve the generator's own member names, test helper-chosen request.security timeframes, and give function locals spelled like built-in variables their own type and history - #355
Conversation
TradingView refuses a call, or a reference to a user-declared variable, as the default of a field of a user-defined type (compile error CE10133). The new tests/test_field_default_refusal.py pins that refusal with located-error rows for calls, variables and operators or ternaries over them, and with paired controls whose complete C++ equals the C++ of the parent commit: literal defaults, na, a negative literal, constructor arguments and local initializers stay accepted. The existing tests whose scripts used such a default now pass the call or the variable to the constructor instead, or expect the refusal, and keep what they check. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A field default of a user-defined type that contains a function call, or an identifier bound to a user-declared variable, now raises PF-E2087 at the first token of the default expression, as TradingView does (compile error CE10133). Builtin members, builtin variables, calculations over literals, constructor arguments and local initializers are unaffected, and a default that reads a variable declared later keeps its PF-E2075 refusal. The code is added to the diagnostics catalog and its pin. The reference guide documents the rule and says that only a callable parameter default that names an earlier global is still accepted; the changelog lists the new refusal. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A script whose type field default called a function that reads a session flag's history used to reach PF-E5048. The analyzer now refuses every call in a type field default with PF-E2087, so that script no longer gets as far as the history check. The row leaves the parametrized PF-E5048 test, which is renamed for the method paths it still covers, and a new test pins that the same source is refused with exactly PF-E2087. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The paragraph added with the PF-E2087 refusal broke two of its lines at uneven widths. Rewrap it to the width of the paragraphs around it; no words change. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
An omitted format and an omitted timezone of str.format_time are documented to be the ISO 8601 pattern yyyy-MM-dd'T'HH:mm:ssZ and the exchange time zone (syminfo.timezone). The new translation rows pin the emitted C++ of the three call shapes that leave a slot out; the preservation rows pin that fully explicit calls and scripts without the call keep their translation byte for byte; the native rows pin the complete printed strings for known instants in a UTC and a New York exchange zone. The rows that change behaviour fail on the current translation, which still fills the slots with a date-only pattern and UTC. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… timezone When a str.format_time call leaves out the format, the translation now uses the ISO 8601 pattern yyyy-MM-dd'T'HH:mm:ssZ, and when it leaves out the timezone it uses the exchange time zone (syminfo.timezone), as the Pine User Manual documents. Before, the slots were filled with yyyy-MM-dd and UTC, so str.format_time(time) was a 10-character string where the documented result has 24 characters. Calls that give both arguments, by position or by keyword, translate exactly as before; passing "yyyy-MM-dd" and "UTC" explicitly restores the previous output. The changelog and the known-quirks page say so. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…hoice on every bar A script can pick the timeframe of request.security with a condition that reads a chart value and pass it through the parameter of a helper function, through a local alias, through a tuple helper, through two call sites of one helper, through nested helpers, or let the helper pick its own timeframe. The new native rows run each form and the equivalent script that requests every timeframe once at the top level and selects the value on the chart, on the same feed, with an entry quantity that carries the requested value, and require identical traces and trades. Each row also requires that both choices occur during the run, so the comparison cannot pass with one fixed timeframe. A helper called with a literal and one called with an input timeframe are controls. Structural rows check that every call site and arm registers one request. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… history store A local declared inside a function that a request.security expression calls may be spelled like a built-in variable (second, hour, close, bar_index, ...). It must keep the type its own declaration gives it, and its history must live in the helper store that matches that type: the string store for a string local, the numeric store for a numeric one. The new rows cover every built-in variable name the analyzer and the generator treat specially, as a numeric and a string local, with and without an explicit history capacity, plain and declared with var, and compare the store references with the same helper whose local has an ordinary name. The existing words test no longer leaves out the string local named second. Controls check that a helper which only reads the built-in calendar variables emits the same complete C++ as before. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…g for typing and history A string local named second in a requested helper kept its history in the numeric helper store, because the type of the declaration was looked up by name after the callable's scope had closed and the built-in variable's integer type won over the declaration's string type. The same lookup, and the analyzer and emitter rules that test a bare name against the built-in tables, also treated a local named like a price, bar or calendar variable as the built-in. The generator now recovers the declaration a name is bound to inside its own callable and uses that declaration's type, the analyzer records the history of such a local as the local's, and the name-based built-in shortcuts step aside when the identifier is bound to a declaration of the script. A built-in variable that a callable only reads, and a global that shares a built-in's spelling, keep the handling they had. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…functions Pine v6 removed the when argument of the strategy order functions. The refusal of a source that still passes it should say so instead of repeating the generic unknown keyword text. The updated rows expect a dedicated code, PF-E2081, with its own arguments and wording for when on the seven order functions in a version 6 source, at the same location as before; every other unknown name keeps PF-E2027 with its generic message, a version 5 source keeps its existing refusal, and valid order calls still translate. The catalog tests expect the new entry and an unchanged pin for PF-E2027. The rows fail on the current translator, which reports PF-E2027 for when. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…tions A strategy order call that passes when in a version 6 source is still refused at the keyword, but now with the dedicated code PF-E2081: the message says Pine v6 removed the argument and the hint shows the call placed inside an if statement. The generic PF-E2027 and its pinned template are unchanged and still report every other unknown name, on these functions and elsewhere; a source of another version keeps its existing refusal. The catalog, the pin, the public contract, the support contract page, the known-quirks page and the changelog describe the new code. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…d, not an internal error A local of a requested helper spelled like one of the generator's own state members (_security_helper_series_, _security_helper_series_str_) made transpile() raise an AssertionError from the duplicate-member check instead of producing C++. The script is valid Pine. The words test now uses both store names as helper locals, numeric and string, and compares the store references with a helper whose local has an ordinary name. A new census test reads the codegen sources for every fixed or numbered name that a generated declaration introduces and requires that an authored name of that spelling is escaped, with the families the emitter already allocates against authored names listed together with their reason; diagnostic prose and statement keywords are not declarations and are left out of the census. Behavioural rows run a global, a helper local and a function parameter of those spellings through transpile and compare the whole C++ with the control. Controls compare the complete C++ of scripts with ordinary names with the C++ of the parent commit. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…elper The safe-name allocator renames an authored identifier only when its spelling is in the reserved sets. The generator's own state members and helper types were missing from them, and several numbered member families (request members, precalculation members, per-request state, technical-analysis state, history members, closed-trade fold members, loop temporaries) keep a fixed numbering instead of allocating against authored names. A helper local spelled like one of them ended in the emitter's duplicate-member assertion, or hid a generated member. The fixed spellings are added to the emitter names and the temporaries, and the numbered families to the temporary pattern, so that such a name takes the same escape as any other name that collides with generated C++. Ordinary names keep their spelling. The duplicate-member assertion stays as the last line of defence. The history-member test that compares complete C++ now maps the two escaped spellings back before comparing. The public contract and the conventions guide state the rule, and the changelog lists the change. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ersion The refusal of a call or a variable as a type field default is a rule of Pine v6 code. The new rows pin the outcomes around it. A v5 main script is refused by the version check before any default is visited. A type exported by an imported v5 library keeps the outcome it had: call defaults and variable defaults are accepted, and a default that calls once for every new instance still reports the session history reason. The same library declared as v6 refuses the same defaults in the library file, and the same main script refuses a call default under v6 but not under v5. The reference rows compare the complete C++, or the diagnostic codes, with the reference commit that the other field default tests use. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The call and variable refusal (PF-E2087) now applies to a type declaration whose own Pine version is 6: the version recorded on a type that an imported library brings in, or the version of the script otherwise. A type exported by an imported v5 library keeps the handling it had before the rule. The library checker's own refusals still apply, other call defaults are accepted, and the session history reason (PF-E5048) is reported where it was reported. The support contracts guide, the pipeline page and the changelog say so. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…bserved The note in the known-quirks page now cites the test file that covers the PF-E2081 wording (tests/test_public_issue_286.py) and keeps the list of files that cover the general keyword rule as it was. The comment in the test that explains why a function body that nothing calls is asked only with the support checker now says what was observed (nothing is raised there on main either) and no longer names a cause that was not shown. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ntity rows to the call The tests now say that the expected strings of the str.format_time rows are hand-derived from the documented format and time-zone rules and were not measured on TradingView. Three new rows translate an omitted format and timezone inside a user function, a method and a request.security payload, check the documented defaults in the emitted call, and compile the result, which shows that the exchange zone member is in scope in each of them. The six rows that compare a translation with the one of an earlier commit no longer compare the whole translation unit. They fail when a changed line names format_time, they require the explicit call to be in both translations, and they require scripts without the call to emit none, so a change elsewhere in the translation no longer turns them red. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Brings in the omitted-format and omitted-timezone defaults of str.format_time (an omitted format is the ISO 8601 pattern, an omitted timezone is the exchange zone). No conflict. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Brings in the dedicated diagnostic PF-E2081 for the removed when argument of the strategy order functions in a version 6 source. The one conflict is in CHANGELOG.md, where both branches add a line at the top of the unreleased list; both lines are kept, in the order of the two merges. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Brings in the diagnostic PF-E2087 for a field of a user-defined type whose default is a function call or a variable that the script declares, in Pine v6 code, as TradingView refuses it. Literals, na, calculations over literals and builtin members keep their handling. No conflict. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Brings in one new test module and no product file: a script can choose the timeframe of request.security with a condition that reads a chart value and pass it through the parameter of a helper function, a local alias, a tuple helper, several call sites, nested helpers or a helper that chooses its own timeframe; the rows pin that each form follows the choice on every bar. No conflict. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Brings in the fix for a Pine variable spelled like one of the generator's own state members or helper names (for example _security_helper_series_), which raised an internal assertion or hid the generated member: those spellings are now reserved for the safe-name allocator. The one conflict is in CHANGELOG.md, where both sides add a line at the top of the unreleased list; both lines are kept, in the order of the merges. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…lone Add rows for the places the earlier change must leave alone or must change on purpose: a global and a parameter spelled like a built-in variable, built-in history reads in a helper and in a chart-path function, a sibling function that only reads the built-in variable, and the chart-path functions whose own local now wins over the built-in variable. Add census variants (history-free, typed, reassigned, declared in a block, loop variable, two call sites, tuple) that compare the emitted C++ with the same script written with a neutral local name. A string tuple element read with history stays refused, with the same diagnostic for every name. These rows compare emitted C++ only; nothing is compiled or run. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The first version of the comparison rewrote a map iterator member, a quoted text that the control also holds, and the escaped spelling of some names, and it assigned a value that referred to the local itself. Rewrite only recognised unqualified local identifiers (including the generated storage and escaped spellings), change a quoted text only where it equals the control's text in the same place, and assign constants in the reassignment variant. Add rows for the normaliser itself: it is the identity on the control, it keeps qualified members and shared text, it maps the escaped spellings, and it still reports a different series type, a missing call, a read of the built-in variable and a manifest note. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ignore the manifest note The comparison of a local spelled like a built-in variable with its neutral-named control now renames the local's own generated storage (this->x and GeneratedStrategy::x) like its other generated spellings, and leaves every other qualified member alone. The embedded manifest lists a note about the built-in variable when a script only declares a local of that name; that is a separate known difference, so the comparison with a control ignores the notes in that list and nothing else. Add rows for both rules, and a census row for a numeric tuple element read with history, which compares the complete C++ with the neutral-named control for every name. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…locals comparison The embedded capability document lists its open facts under the key unresolved, not solved, so the mask added in the previous commit never matched the real text and six rows still failed on a note about the built-in variable. Use the real key in the mask and in the rows that check it. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…iable its own private name The earlier fix only separated direct function-body locals that carry a history read, so the same collision still produced wrong series types, wrong history stores or undeclared storage for locals declared in nested blocks, locals reassigned with :=, loop variables, tuple elements and locals without any history read. Add one early pass that runs right after the analyzer resolves value bindings. It selects every function-local and method-local declaration whose spelling is a built-in variable name (taken from the existing built-in tables, no name list), gives it a private name in source order that cannot clash with an authored name or a clone name, and renames the declaration together with every use bound to it. Parameters, globals and reads that precede the declaration keep their bindings. A script without such a local is returned before anything is walked or changed. The authored spelling is kept as an annotation so that analyzer and generator diagnostics and the key of an untitled input still name the local as written. The name-dependent inventories are now built after the rename. The original late history separation stays for callable locals that need it. Add rows for method locals, persistent locals, the three for-in binder shapes, typed-local refusals and untitled inputs. Replace the changelog line. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… on the class fix The row for an untitled input on a persistent local looked for the float getter, while the generator emits the double getter for an input.float value; it failed for every name for that reason alone. The persistent-reassignment row used the built-in close as the assigned value, which for a local that is itself spelled close reads the local; it now assigns a constant, like the other reassignment rows. A numeric tuple element spelled like a built-in is no longer a preservation row. On the previous main its history was stored in the built-in's series type (Series<int> for second); the new pass gives it the ordinary floating storage. The shape stays pinned against the renamed control by the existing tuple rows. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ames that are both a variable and a namespace The earlier change respelled the placeholder of the refusal "Invalid object name" from node.name to a local name. The diagnostics catalog is keyed by the spelled template, so the catalog test found a template without a code. Restore the exact spelling. The refusal still decides on the authored spelling, because the rename gives the local a private name, and the restoration of authored names in diagnostics that already runs after the analysis puts the author's name back into the message. Add rows at the end of the scoped-locals tests for the three names that are built-in variables and also namespace names (dayofweek, time, timenow): a function-local that holds a user-defined-type instance is refused with exactly the message of the unrenamed script, and no private name appears in the message or in the exception text. An inventory row keeps the three names in step with the two name sets. A name that is not a namespace name (second) is not refused and matches the renamed control. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Brings in the documentation pull request that landed on main after the previous merge (the clarification of the unreleased fixes in the changelog and the numeric helper documentation, #353). No conflict and no hand edit. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…put of built-in-named locals Drop the manifest-note mask from the scoped-locals comparisons. Every comparison row now includes the embedded capability manifest; the mask and its self-tests are removed. Add rows for the private storage names: an authored name that looks like a private storage name, or like its call-site or nested-call clone, is reserved; allocation follows source order across several colliding declarations; a refusal that names an authored private-looking spelling keeps it unchanged. Add rows for diagnostics that name a renamed local: a generator refusal over all 26 names, an analyzer refusal for a matrix local read with history, and a request inside the declaring function (complete C++, diagnostics, requests and the embedded manifest). Add a compile test file built from the same source builders: the numeric tuple, typed and chart shapes for all 26 names and a neutral name, the remaining shapes for one name, the authored-collision scripts, and the string-with-history shapes, which do not compile for any name and are pinned to the compile outcome of the neutral name. Add rows for int, bool, color and array locals, switch and while bodies, and method locals in nested, typed and reassigned form, and a preservation row for history of the derived price variables. Expose the set of built-in spellings that the pass uses as a function and compare it with the test file's own inventory. The pass is unchanged. Reword the changelog line to say what is checked. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…tured-request comparison The first run of the captured-request rows found that for the three built-in names the support check flags as divergent (bar_index, last_bar_index, timenow) a function-local of that name gets the check's "diverges from TradingView semantics" warning at every read. The support check runs before scoping and reads names only, so a global or a parameter of that name gets the same warning; this change does not touch it. Drop exactly those warnings from the subject's list before comparing it with the neutral name's list, and require that they occur for the names in the check's table and for no other name. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…nd library functions Add a direct row for the helper that restores authored spellings in diagnostic text: exact bindings, both clone suffixes, spellings that are not bindings, a missing text, and empty or absent annotations. Compare the diagnostics (messages and hints) of every script shape of the scoped-locals tests, for all 26 built-in names, with those of the same script whose local has a neutral name; the support check's by-name divergence warning is the only difference allowed, and no diagnostic may carry a private spelling. Add rows for a library function, version 5 and version 6, that declares a local spelled like a built-in variable, called directly and inside a request. The inliner qualifies the local with the module and function names, so the pass has nothing to rename there and the rows pin that. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
There was a problem hiding this comment.
Graphify reviewed this change.
No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.
Not checked: tests were not run; formal verification proved 0 of 6 changed function(s) (6 not verified).
Formal verification. PR-changed functions: 0/6 verified (0 proven, 0 may-equivalent, 0 distinguished) · 6 not verified (6 unsupported).
Not verified on this run: \_visit\_TypeDecl (unsupported), \_resolve\_func\_args (unsupported), \_visit\_format\_time\_call (unsupported), intrinsic\_keyword\_diagnostics (unsupported), validate\_intrinsic\_keywords (unsupported), \_visit\_FuncCall (unsupported).
Graphify review — findings
Adds a Pine v6 PF-E2081 diagnostic for the removed when argument on the seven strategy order and cancel calls, pointing users to an if around the call, while other unknown keywords keep PF-E2027. The support-contract wording for what v5 sources report in this case is the part most exposed to drift from the documented v5 behaviour. Makes str.format_time fall back to TradingView's defaults when arguments are omitted: "yyyy-MM-dd'T'HH:mm:ssZ" for the format and syminfo.timezone for the timezone. Passing "yyyy-MM-dd" and "UTC" explicitly restores the old output, and the change needs C++ regeneration and a relink. Rejects type field defaults containing calls or user-declared variable references with PF-E2087 in v6 scripts and libraries, while v5 sources keep the earlier handling. Fixed generated state members, helper types and numbered member families, including history-bearing locals in requested helpers, now go through the C++ name allocator.
Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 4963 functions depend on the 1096 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
Analyzer— 105 callers, 15 callees - new:
_assert_unchanged()— 3 callers, 6 callees - worse:
_codegen_for()— 3 callers, 4 callees - new:
_spelling()— 1 callers, 8 callees
Verification — 4963 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 4294 function(s) in the blast radius were not formally verified this run
Cross-repo impact
Cross-repo impact
This change impacts 2 symbol(s) across 1 peer repo(s) — blast radius over derived cross-repo edges:
- pineforge-4pass/pineforge-hpo —
python/pineforge_hpo/transpile.py(via import_moniker, heuristic),tests/python/test_transpile.py(via import_moniker, heuristic)
1 peer repo(s) in this group are not yet indexed — cross-repo impact may be incomplete.
Cross-repo linkage is derived (SCIP-moniker matches are sound; name/contract matches are heuristic) and ADVISORY — it never affects this check's conclusion.
Docs that may be stale (advisory)
AGENTS.md§ Build and environment (lines 40-64): references changed symbolssourceAGENTS.md§ Layout (lines 65-80): references changed symbolsreferenceAGENTS.md§ Engine-enabled tests (selected cases now run in native PR CI) (lines 100-124): references changed symbolssourceAGENTS.md§ Emission invariant (lines 185-223): references changed symbolsreferenceAGENTS.md§ Conventions (lines 224-240): references changed symbolsreferenceCLAUDE.md§ Build and environment (lines 40-64): references changed symbolssourceCLAUDE.md§ Layout (lines 65-80): references changed symbolsreferenceCLAUDE.md§ Engine-enabled tests (selected cases now run in native PR CI) (lines 100-124): references changed symbolssourceCLAUDE.md§ Emission invariant (lines 185-223): references changed symbolsreferenceCLAUDE.md§ Conventions (lines 224-240): references changed symbolsreference
…and 10 more.
Formal verification
Could not verify: Could not verify \_visit\_TypeDecl.
The verifier did not have enough to check \_visit\_TypeDecl, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)
Could not verify: Could not verify \_resolve\_func\_args.
The verifier did not have enough to check \_resolve\_func\_args, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)
Could not verify: Could not verify \_visit\_format\_time\_call.
The verifier did not have enough to check \_visit\_format\_time\_call, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)
Could not verify: Could not verify intrinsic\_keyword\_diagnostics.
The verifier did not have enough to check intrinsic\_keyword\_diagnostics, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `intrinsic` is annotated `IntrinsicFunc | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify validate\_intrinsic\_keywords.
The verifier did not have enough to check validate\_intrinsic\_keywords, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `intrinsic` is annotated `IntrinsicFunc | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_visit\_FuncCall.
The verifier did not have enough to check \_visit\_FuncCall, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)
· 2 grounded finding(s) anchored inline below; 2 more finding(s) on lines outside this diff (see the check run).
| return tree | ||
|
|
||
|
|
||
| def _assert_unchanged(source, reference, tmp_path, *, libraries=None): |
There was a problem hiding this comment.
_assert_unchanged()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| ) | ||
|
|
||
|
|
||
| def _spelling(source, name): |
There was a problem hiding this comment.
_spelling()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
…arning control and the pinned receipt A control for the by-name divergence warning in the diagnostics rows: the warning must be present exactly for the three names that have it (in the shapes that read the name) and absent for the other 23, and the table of those names is pinned. A row for a tuple element read on the right of := to an int variable (it needs the table of declaration bindings to follow the rename). Rows for a drawing local and a user-defined-type local read with history. An invariant that no built-in name contains the clone markers. Rows that pin the confirmed-bar request receipt of a request inside the declaring function, which carries the private spelling of the local in its expression field. A compile row for a string variable without history for each of the 26 names. The changelog bullet no longer says that the local behaves like a local with a neutral name in every respect and lists the exceptions. Tests and one changelog bullet only. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ory in a requested helper A string variable named open, high, low, close, hl2, hlc3, hlcc4, ohlc4 or volume that is read with history inside a function called through request.security is refused on main with one error (request.security helper-parameter history currently requires a direct OHLC/time bar-series binding). The rows compare that refusal, its code and its message, with what main gives for the same script, for each of the nine names, and pin that nothing else is refused: the other 17 names, the nine names as numeric variables and without a history read, and the shapes that main accepted. Tests only. On the previous commit the refusal rows fail, because the built-in-named locals now take the path of an ordinary name. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…d with history in a requested helper A string variable named open, high, low, close, hl2, hlc3, hlcc4, ohlc4 or volume that is read with history inside a function called through request.security was refused on main with one error (request.security helper-parameter history currently requires a direct OHLC/time bar-series binding). The early pass that gives a function local spelled like a built-in variable a private name turned that refusal into accepted C++ that does not compile (a separate known defect: a string with a history read, used in a concatenation, is emitted as std::to_string applied to a string). The refusal is kept for exactly these nine names and the shapes where main refused; nothing else changes. Four existing tests compared these pairs with the neutral control and now expect the refusal for them (a few lines each, the pairs are named in the diff; no row and no assertion is removed), and the refusal rows of the previous commit now pass. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Brings in the fix for a declared local whose name is also a built-in variable: it keeps its own type, history storage and reads. Two files conflict with the earlier merges. CHANGELOG.md is resolved by keeping every bullet of both sides, in merge order. tests/test_helper_series_words.py is resolved by keeping the reference-word list of the generated-name merge (the two store names are valid locals there) with the combined comment. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Summary
This pull request combines separate branches on top of main with plain merges. Each section below describes one branch: what changes for a user, how it was tested, and what the tests do not claim. The branches touch different features; where they share a file (CHANGELOG.md, the diagnostic catalog and its pin, the support-contract page) the merge keeps every entry of every branch.
The numbers below describe the current head of this pull request. It was opened with sections 1 to 5; section 6 was merged into it afterwards, and the numbers of the verification section were measured again on the new head. If another branch is merged into this one before it lands, the description is updated and the numbers are measured again on that head.
1.
str.format_timefollows the documented defaults for an omitted format and timezoneWhen a
str.format_timecall leaves out the format, the translation now uses the ISO 8601 patternyyyy-MM-dd'T'HH:mm:ssZ; when it leaves out the timezone, it uses the exchange time zone (syminfo.timezone). The Pine Script User Manual documents both defaults (Time, Formatting dates and times). Before, the omitted slots were filled withyyyy-MM-ddandUTC, sostr.format_time(time)was a ten-character string where the documented result has 24 characters. A call that gives both arguments, by position or by keyword, translates exactly as before; passing"yyyy-MM-dd"and"UTC"explicitly restores the previous output. The change is two literals in_visit_format_time_call.Tests (
tests/test_e2e_keyword_hole_binding.py, 17 rows):request.securitypayload;format_time, an explicit call keeps its exact text, a script without the call emits none);With the two literals reverted, 10 of the 17 rows fail (the three text rows, the three compile rows and the four running rows) and 7 pass; with the change all 17 pass. The expected strings of the four running rows are derived by hand from the documented format and time-zone rules; they were not measured on TradingView.
Compatibility: a script that omits the format or the timezone of
str.format_timegets the documented string instead of the old one; every other script translates as before. A translation comparison of 1,216 sources (856 tracked Pine sources, 312 corpus strategies, 48 probes) between main and this branch alone changed exactly the five probes that omit a slot and none of the other 1,211 sources; the same comparison of main against itself changed none.2. A removed
whenargument has its own diagnostic,PF-E2081A strategy order call that passes
whenin a Pine v6 source is still refused at the keyword, but now with the dedicated codePF-E2081: the message says that Pine v6 removed the argument, and the hint shows the call placed inside anifstatement. The genericPF-E2027and its pinned template are unchanged and still report every other unknown name, on these functions and elsewhere; a source of another Pine version keeps its existing refusal. The catalog, the pin, the public contract, the support contract page, the known-quirks page and the changelog describe the new code.Tests (
tests/test_public_issue_286.py,tests/test_diagnostic_codes.py,tests/test_diagnostic_notes.py): with the old translator the updated rows fail (96 failed: 91 rows expectPF-E2081wherePF-E2027is reported, and 5 rows compare the declared catalog code set); with the change the same files pass (1,393 passed, 22 skipped). The pin digest ofPF-E2027and the pin ofPF-E2081were checked to pass; they were not seen to fail on the old translator.3. A call or a variable is refused as the default of a user-defined type field (
PF-E2087)Summary
TradingView refuses a function call, or a reference to a variable the script declares, as the default value of a field of a user-defined type (compile error CE10133, "The default value cannot be a function, variable or calculation."). PineForge accepted both and generated code that evaluated them when the type was constructed.
This change reports the new diagnostic
PF-E2087, "The default value cannot be a function, variable or calculation.", at the first token of the default expression, for types declared in Pine v6 code.What is refused and what is not
Refused: a field default that contains a function call (a user function, a method, or a builtin such as
array.new<int>(),map.new<string, int>()orarray.from(...)) or an identifier bound to a variable the script declares. This includes such a call or variable inside arithmetic, comparison, logical, unary and ternary expressions.Unchanged: literals,
na, negative literals, calculations over literals, builtin members and builtin variables such asclose(the rule looks at syntax and at the script's own declarations only, so they keep the handling they had), constructor arguments such asBook.new(total = counter), and local initializers. A default that reads a variable declared later in the script keeps its existingPF-E2075refusal.Pine versions
The refusal applies to a type declared in
//@version=6code: a v6 script, or a type that an imported v6 library exports. It is decided from the version of the declaration itself (the version recorded on an imported type, otherwise the script's). A type exported by an imported//@version=5library keeps the handling it had before this change, including the library checker's own refusals and, where it applied, the existing session-history reason (PF-E5048). A v5 main script is refused earlier by the version check, before any type default is read.Diagnostic code
PF-E2087is a new catalog entry, with its pin line written by hand.PF-E2083toPF-E2086belong to thestrategy.default_entry_qtyargument refusals and are not touched.Tests
tests/test_field_default_refusal.py: thirteen refusal rows that assert the code, arguments, message, absence of a hint, level, phase and the exact line and column, and paired controls whose complete C++ equals the C++ of the parent commit (literal defaults,na, a negative literal, constructor arguments, local initializers).tests/test_field_default_versions.py: the version rows. A v5 main script stops at the version check; a type exported by a v5 library with a call default or a variable default is still accepted, with complete C++ equal to the reference commit's; the session-history reason is still reported for a v5 library default that needs it; the same libraries declared as v6 are refused in the library file; and one source is refused under v6 but not under v5.test_codegen_ta_stable_var_literal.py,test_codegen_validation_fixes.py,test_e2e_session_history.py,test_session_history_in_method.py. None is dropped or skipped.Compatibility
A script that used a call or a declared variable as a field default in v6 code stops compiling, as it does not compile in TradingView. The generated C++ of every script that still compiles is unchanged. A population of 1507 translation contexts (all of them accepted) was translated on main and on this branch with identical generated C++ and no new refusal. A second set of 188 sources gave the same result on both: 78 translate with identical C++, and the other 110 are refused with the same unrelated message on both, so it is not an independent population for this rule. The full test suite was run on main and on this branch on a machine without the engine library or the corpus: the same two tests fail on both (their inputs are missing there), no test fails only on the branch, and the branch passes 86 more tests (its new ones).
Known limit
The behaviour of a type exported by a
//@version=5library is the behaviour before this change, not a TradingView measurement.4. Tests: a
request.securitytimeframe chosen through a helper follows the choice on every barTests only; no product file changes. A script can choose the timeframe of
request.securitywith a condition that reads a chart value, and pass that timeframe through the parameter of a helper function, through a local alias, through a tuple helper, through several call sites of one helper, through nested helpers, or let the helper choose its own timeframe. The new tests pin that each such form gives the same traces and trades, on every bar and at every call site, as the equivalent script that requests each timeframe once at the top level and selects the value on the chart. They compare PineForge with PineForge; they are not a comparison with a TradingView export.New
tests/test_e2e_security_helper_timeframe.py:The native rows skip when the native inputs are missing, like the neighbouring native test modules; in the combined test run below they ran.
5. A Pine name spelled like a generated state member or helper is escaped
A Pine variable whose name is spelled like one of the generator's own state members or helper names (for example
_security_helper_series_) made the translator raise an internalAssertionErrorfrom its duplicate-member check instead of producing C++ (the case that was reported: a local of a function thatrequest.securitycalls), or hid the generated member. The script is valid Pine. The names that the generator declares itself, and the numbered families that keep a fixed numbering, now take the same C++ escape as any other name that collides with generated C++. Ordinary names keep their spelling, and the duplicate-member check stays as the last line of defence.What changes:
pineforge_codegen/codegen/helpers.pyreserves, for the safe-name allocator, the fixed spellings of the generator's state members, helper types and exported methods, the fixed temporaries, and the numbered families that do not allocate against authored names (request results, precalculated vectors, per-request state, technical-analysis state, history members, closed-trade fold members, loop temporaries).docs/PUBLIC_CONTRACT.md,docs/reference/conventions.mdandCHANGELOG.mdstate the rule.Tests: the new
tests/test_generated_member_names.pyhas a census read from the codegen sources (every fixed or numbered name that a generated declaration introduces) that must be reserved, and behavioural rows that put each census name through a global variable, a helper local and a function parameter, numeric and string, and compare the complete C++ with the C++ of the same script under an ordinary name, after the escaped spelling is replaced by text.tests/test_helper_series_words.pyadds the two store names to the words used as helper locals;tests/test_e2e_session_history.pymaps the two escaped spellings back before it compares complete C++. The tests were written first and fail without the change (103 rows).Compatibility: scripts whose names do not collide emit identical C++. A population of 1,507 translation contexts (all accepted) and a second set of 188 sources (78 translate identically, 110 are refused with the same message on both trees) were translated on the parent commit of this branch and on the branch, before the branches were combined; the combined head is covered by the comparison and the test run below. The committed list of host members is unchanged (172 names) with the headers of the engine main at the time, and
test_committed_host_members_are_the_derived_setpasses on the combined head.Limits: the census lists the numbered families by hand, so it will not notice a new family; one family is not reserved yet: a script local spelled
_tuple_result_<N>_elem_<M>still collides with the generator's tuple bindings in a tuple assignment with series or global targets (a compile error, a spelling no one writes).6. A function or method local spelled like a built-in variable keeps its own type, history and reads
This section describes one branch of this pull request, merged in after the pull request was opened. Its numbers were measured on that branch against its own base, codegen main at
7d7681dc, before it was combined with sections 1 to 5. Where the text below speaks of the open fix for #350, that fix is section 5 of this pull request and the combination is the head that the verification section below covers.Summary
Reported in #351. A variable declared inside a function or a method whose name is also a built-in variable (
second,hour,close,bar_index, ...) was typed, stored and read as the built-in variable in several shapes. Inside a function thatrequest.securitycalls, a string variable namedsecondkept its history in the numeric helper store. A variable in a nested block, a variable reassigned with:=, a loop variable and a variable without any history read took the series type of the built-in variable, and the history of a variable namedhl2,hlc3,hlcc4orohlc4received the built-in variable's expression.One early pass now gives every such declaration its own private name before the type, storage and history inventories are built, so the declared variable wins everywhere. It covers all 26 built-in variable names that the analyzer or the generator treats specially; the names come from the existing tables and the pass holds no list of its own. The declaration shapes that have test rows are: a plain local, a local in a nested block, a
varorvariplocal reassigned with:=, afororfor ... invariable (all three binder shapes), a tuple element, a local in aswitchor awhilebody, anint,bool,color, array, label or user-defined-type local, in functions and in methods, typed or untyped, with or without a history read. Parameters, globals and reads of the built-in variable keep their meaning, and a function that only reads the built-in variable is unchanged. Scripts that declare no such variable emit identical C++. One group keeps main's behaviour on purpose: see "Compatibility".This does not make the script from #351 compile, and it does not make every shape work: see "Known limits". The main reason is a separate defect: a string variable with a history read, used in a concatenation, is emitted as
std::to_stringapplied to a string, and that C++ does not compile for any variable name, ordinary names included, on main at7d7681dc(the base of this branch) and on this branch: #354. The reporter's script still fails for that reason.What changes
pineforge_codegen/analyzer/callable_history_names.py: the new passseparate_builtin_callable_localsselects the declarations in functions and methods whose spelling is one of the built-in variable names, allocates<name>__pfhist<N>in source order so that it clashes neither with an authored name nor with a generated clone name, and renames each declaration together with every use that the analyzer's value-binding resolver bound to it. The authored spelling is kept as an annotation, andauthored_callable_textmaps a private name in a message back to it. The set of names is built by one function,_builtin_callable_local_names(), from the existing tables. The earlier separation of the history of direct callable locals stays.pineforge_codegen/analyzer/base.py: runs the pass right after the value bindings are resolved; the inventories that depend on names (the reassigned names, the method signatures) are built after the rename; analyzer diagnostics name the variable as the author wrote it; the refusal of a namespace name used for an object keeps deciding on the authored spelling, and its message text is unchanged. The history of such a variable is recorded as its own. The analyzer's name-only shortcuts for price and bar fields also step aside when an identifier is bound to a declaration of the script; after the early pass has renamed the declaration, that case does not arise in a function or a method, so these hunks are kept as a safeguard and no test row exercises them.pineforge_codegen/analyzer/value_bindings.py: keeps the table of declaration bindings in step with the rename (a tuple element read on the right of:=needs it: a row covers that) and charges the analysis budget.pineforge_codegen/codegen/types.pyandpineforge_codegen/codegen/visit_expr.py: the declaration that a name is bound to inside its own function is recovered and its type is used; the lookup by name after the function's scope has closed let the built-in variable's type win. The same step-aside rule applies to the price and bar field reads of the emitted code.pineforge_codegen/codegen/base.pyandpineforge_codegen/codegen/input.py: generator diagnostics and the key of an untitled input use the authored spelling, so no private name reaches a message or an input key.pineforge_codegen/codegen/security.pyandpineforge_codegen/codegen/tables.py: the narrow rule that keeps main's refusal (see "Kept as on main"). One constant,SECURITY_PRICE_LOCAL_NAMES, holds the nine price-field names; one method,_security_check_price_named_local_history, is called at the two places where a history read of a helper local meets its binding (the prepass walker and the expression builder), which is where main composed the refusal. It applies only to a declaration that the early pass renamed (it reads the pass's own record of the authored spelling, not the bare name), that is not declaredvar, and whose type is string; it raises the diagnostic that main gives, with the same code and message, and changes nothing else. A global, a parameter and a library local are untouched.CHANGELOG.md: one bullet.Tests
tests/test_helper_builtin_named_locals.py: every built-in variable name that the analyzer or the generator treats specially, as a numeric and as a string function variable, with and without an explicit history capacity, plain and declared withvar; each row compares the store references of the emitted code with the same function whose variable has an ordinary name and checks which helper store holds the history.tests/test_builtin_named_locals_scope.py: the 26 names come from the same tables as the pass, and a row compares the pass's own name set with them (and checks that no name contains the clone markers_csor__ni). For every name the complete C++ is compared with the C++ of the same script whose variable has an ordinary name, after the variable's own storage names are mapped back; the embedded legacy capability manifest is part of that comparison and nothing is masked. The shapes are: history-free, typed, reassigned with:=, nested block, two call sites, loop variable, numeric tuple element (each as a numeric and as a string variable where that applies), in a function called throughrequest.security, in a chart-path function and in a method;varip; the threefor ... inbinder shapes;int,bool,colorand array locals; a drawing local and a user-defined-type local read with history;switchandwhilebodies; method bodies with a nested block, a typed local and a reassigned local; a tuple element read on the right of:=to anintvariable. A string tuple element read with history is compared by its refusal text. Further rows:nz(<name>[1])and<name>[1]) and for each of the 26 names: itsexpressionfield is exactlynz(<name>__pfhist1[1])or<name>__pfhist1[1](the ordinary name's:nz(aa[1])andaa[1]), every other key and value of the receipt equals the ordinary name's, and the private spelling occurs nowhere else in it. So the embedded confirmed-bar receipt is not equal to the ordinary name's: the spelling of that one field differs, on purpose, and the rows pin it (see "Known limits");bar_index,last_bar_index,timenow) and forbidden for the other 23, in this test as in the test of the request that sits inside the declaring function;<name>__pfhist<N>spellings and their clone spellings, show that private names clash neither with authored names nor with each other, in source order, and the compile file compiles the same eight;authored_callable_text(exact bindings, both clone suffixes, unmapped spellings, a missing text), a generator refusal of a float local used as a matrix receiver, a typed local's refusal, and the key of an untitled input all carry the authored name;dayofweek,time,timenow) is still refused with the namespace message, without any private name in it;request.security): the inliner qualifies a library local with its module and function names, so the pass has nothing to rename there and the C++ equals the ordinary name's after the qualified spelling is mapped;7d7681dc) for a global and a parameter spelled like a built-in variable, for functions that only read built-in variables (history reads included, alsohl2,hlc3,hlcc4,ohlc4), and for a function that declares the variable beside one that reads the built-in variable.tests/test_builtin_named_locals_price_refusal.py(138 rows): for each of the nine price-field names, a string variable read with history inside a function called throughrequest.securityis refused with the diagnostic main gives (one error, codePF-E3011, the message below, located at the history read). Nine rows (one per name, the typed shape) compare level, code, message, line and column with what main's own tree gives for the same script, and 18 more rows (the nested block and the function called at two sites) check the diagnostic and its position. 111 rows show that nothing else is refused: the other 17 names as string variables in the same three shapes, the nine names as numeric variables and the nine names as string variables without a history read, each equal to the ordinary name's translation (87 rows), and the shapes that main accepted (24 rows: the nine names reassigned with:=and in a chart-path function,secondandtimein three shapes). Four existing tests that compared these pairs with the ordinary name now expect the refusal for exactly those pairs (27 rows of the requested-variants test, 27 rows of the diagnostics test, 18 rows of the helper-history test and the 3closepairs of the string compile rows); no row and no assertion was removed.tests/test_builtin_named_locals_compile.py: 173 compile attempts (a syntax check of the emitted C++ against the engine headers, with the repository's compile helper; the rows run where the helper finds an engine include tree and skip otherwise, like the other compile tests): the numeric tuple, typed and chart shapes for an ordinary name and for each of the 26 names (81); 20 further shapes for an ordinary name and forsecond(40); the eight allocation scripts (8); a string variable without a history read for an ordinary name and for each of the 26 names (27); the string-with-history shapes for an ordinary name and forsecond,closeandtime(17 attempts in 5 rows; the pairs ofclosein the typed, nested and two-site shapes check the refusal instead of compiling), where the rows pin that renaming keeps the compile outcome and the first compiler error, because those shapes do not compile for any name (see A string local with a history read, used in a concatenation, is emitted as std::to_string on a string (the C++ does not compile) #354 above).tests/test_helper_series_words.py: the string variable namedsecondis no longer left out of the rows.tests/test_builtin_named_locals_price_refusal.pythat pin the refusal fail withDID NOT RAISE CompileError: the script is accepted, and the C++ it gives does not compile (A string local with a history read, used in a concatenation, is emitted as std::to_string on a string (the C++ does not compile) #354). The 111 other rows of that file pass there, as they must. In the first round, on main at7d7681dcwith that round's test files added (a throwaway commit that was never pushed), 116 ids failed intests/test_helper_builtin_named_locals.pyandtests/test_helper_series_words.py;tests/test_helper_series_words.pyis unchanged since, andtests/test_helper_builtin_named_locals.pyhas gained the refusal branch for 18 rows, so that count is not re-measured.437b9069) is clean, and its merged tree gives the same 3,456 tests with none failing (the same 6 skipped for the engine library, no compile row skipped). A merge with the open fix for A Pine variable named like a generated script-state member ends in an uncaught AssertionError #350 conflicts in the same two files as before (CHANGELOG.md,tests/test_helper_series_words.py) and needs a hand resolution; that combination is not part of this change's tests. Tests of that other change need private fixtures and skip on a machine without them; they must run wherever the fixtures exist.Compatibility
0971c4b6...) was translated on main at7d7681dcand on this branch: all 1,507 translate on both, to identical C++ in every context, and none is newly refused. A second set of 188 sources gave the same on both: 110 are refused on both, the other 78 translate to identical C++, and none is newly refused. A count over every node below each function and method body (library sources parsed with the library parser) finds no such declaration in any of the 188 sources, in the 1,384 distinct sources of the population, or in the 7 distinct library sources, so these sets guard scripts without a collision and say nothing about scripts with one; the rows and the compile rows above do.:=, in a loop, in a tuple or without a history read now behaves exactly like a variable with an ordinary name instead of taking the built-in variable's series type or expression (a tuple element namedsecondwith a history read was stored asSeries<int>, it is nowSeries<double>). A string variable without a history read now compiles for each of the 26 names (a compile row per name); on main it did not compile for the eleven names measured (the nine price-field names,secondandtime).open,high,low,close,hl2,hlc3,hlcc4,ohlc4orvolumethat is read with history inside a function called throughrequest.securityis still refused, with the message it always got ("request.security helper-parameter history currently requires a direct OHLC/time bar-series binding", codePF-E3011): the early pass would otherwise turn that refusal into accepted C++ that does not compile (A string local with a history read, used in a concatenation, is emitted as std::to_string on a string (the C++ does not compile) #354). Rows pin the refusal for each of the nine names in three shapes (a typed variable, a variable in a nested block, a function called at two sites), compare its code and message with what main gives for the same script, and pin that nothing else is refused. A string local with a history read, used in a concatenation, is emitted as std::to_string on a string (the C++ does not compile) #354 is not fixed by this change and stays open for 1.11.1.varip,for ... in, a method), all declaring the same name, is refused for an ordinary name by the existing check "A callable history local ... shares raw generated Series state with another lexical declaration", and is accepted, with C++ that compiles, for each of the 26 built-in names, because each declaration now has its own private storage. The check itself is unchanged.Known limits
bar_index,last_bar_indexandtimenowthe support check still reports "diverges from TradingView semantics in PineForge." at every read of a local with that name, as it does for a global or a parameter with that name: it runs before scoping and looks at names only, and this change does not touch it. The rows require exactly those warnings for exactly those three names, in the shapes that read the name, and forbid them for the other 23.request.securitypayload is refused for every local name and for an ordinary name alike (a library rule, unchanged).request.securityin the declaring function, the confirmed-bar receipt'sexpressionfield carries the internal storage spelling when the payload can be rendered (nz(second__pfhist1[1])fornz(second[1])); rows pin it for all 26 names. Restoring the authored spelling there is a 1.11.1 item, next to A string local with a history read, used in a concatenation, is emitted as std::to_string on a string (the C++ does not compile) #354. Measured outside the suite on seven such scripts, for the compile outcome and the first compiler error only (no C++ equality was measured for these seven): four compile (a history read of the local undernz, a plain history read, a history read in a method, a history read in a nested block) and three do not, for the ordinary name too (the local passed to a built-in call or to a user function inside the request expression, or a tuple element: the request code does not see the function's local,'<name>' was not declared in this scope, a separate limit that this change does not touch). The four built-in names tried (second,close,hl2,bar_index) give the same compile outcome and the same first compiler error as the ordinary name in all seven.second(both are refused for every name alike, measured outside the suite); user-defined-type and drawing locals beyond the namespace-object rows, the object control and the rows for one drawing local and one user-defined-type local read with history inside a function; three more shapes of a string local of the nine price-field names, measured outside the suite on main and on this branch, without a row: avariplocal read with history in the requested helper is refused on both with the same error (the rule applies to it, as it did on main); a local passed as an argument to a second helper that reads its parameter with history is refused on both, but main gave the refusal described above (codePF-E3011) and this branch gives the error that an ordinary name gets (codePF-E3097, "derived helper-parameter history requires dependency-aware helper evaluation for this body"); a local read with history directly in arequest.securitypayload inside its own function is accepted on both, with different C++ because the local's history is now its own: for the nine names and forsecondthe C++ of main does not compile (the history is read from the built-in series), and the C++ of this branch compiles for all of them and for an ordinary name (a syntax check against the engine headers; nothing was run); and the analyzer's name-only shortcuts that this change keeps as a safeguard.Combined verification
The head was tested as one tree on a clean checkout with the engine built from main: 91 test files, 14,009 passed, 56 skipped, 0 failed. The 91 files are every test file that this pull request changes (18) and the test files that sit next to the changed code. Every skip is a private fixture that is not present on the machine. The new test modules of sections 3, 4, 5 and 6 and the keyword-hole module of section 1 ran without a skip (
test_field_default_refusal.py46 rows,test_field_default_versions.py39 rows,test_e2e_security_helper_timeframe.py18 rows,test_generated_member_names.py799 rows,test_e2e_keyword_hole_binding.py17 rows,test_helper_builtin_named_locals.py214 rows,test_builtin_named_locals_scope.py1784 rows,test_builtin_named_locals_compile.py161 rows,test_builtin_named_locals_price_refusal.py138 rows andtest_builtin_named_locals_receipt.py52 rows).Section 6 was merged into the head that the pull request was opened with. The merge has two conflicts, resolved by hand:
CHANGELOG.mdkeeps every bullet of both sides, in order;tests/test_helper_series_words.pykeeps the reference words that section 5 adds and the rows for the string variable namedsecondthat section 6 adds back, with a combined comment. Apart from those two files, the merge changes the same files, with the same changed lines, as the branch of section 6 does.A run of the whole tests directory on the head before section 6 was merged in, on a machine with the engine library built from main, gave 25,929 passed, 188 skipped and 0 failed. Of the skips, 179 are private fixtures that are not present on that machine, 6 are population sources that need an environment variable to be set, and 3 are empty parameter sets; the last nine are in test files that this pull request does not change. The whole directory has not been run on the combined head; hosted CI is reported by this pull request itself.
A translation comparison of 1,227 sources (856 tracked Pine sources, 312 corpus strategies, 59 probes) between main and this head changed exactly nine sources, all of them probes written for these branches: the five probes of section 1 that omit a slot, one source that made the translator crash before and now translates (section 5), one refusal that now reports
PF-E2081(section 2), one call default that is now refused withPF-E2087(section 3), and one probe of section 6, a string variable namedsecondin a function, which now keeps its own string history instead of the numeric history of the built-in variable. None of the 856 tracked sources and none of the 312 corpus strategies changed. The same comparison of main against itself changed none.Known limits
str.format_timerows are derived by hand from the documented rules; they are not TradingView measurements.//@version=5library is the behaviour before this change, not a TradingView measurement._tuple_result_<N>_elem_<M>locals in a tuple assignment with series or global targets (section 5).🤖 Generated with Claude Code