Repository navigation
History of arrays and matrices; int into a float field - #157
Merged
Merged
Conversation
Cell.new(v = bar_index) into a float field did not compile: the record is built with a designated initializer, where C++ refuses to narrow a non-constant int or int64_t into a double. TradingView holds the number, an integer na as na (tests/fixtures/array_history_tv uctor_float, whose tape replays 336 of 336). The value is now converted where the field is a double; a double, an integer literal and a bare na keep their spelling. The fixture directory also holds this lane's TradingView evidence for the history of arrays and matrices. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
(a[1]).size(), array.size(a[1]) and (m[1]).get(0, 0) did not compile: a[1] lowered to the current array's element 1 and m[1] to a Series<double>. TradingView reads a[k] as a read-only copy of the collection as the variable left it at the end of the bar k bars back (its block's previous run, for a block's local), not the array the variable holds now: a var array's history is one element shorter than the array it keeps growing (tests/fixtures/array_history_tv, eight tapes). Such a variable keeps its own std::vector or matrix member, so every other operation keeps its C++; _PFCollectionHistory beside it keeps the copies, a slot opened at each execution of the declaration and closed with a copy at the bar's end. The analyzer annotates every read with its use: a built-in reads it; one that changes it stops the run with TradingView's RE10051 text; na() asks whether a copy exists; a for...in loop iterates the variable's current array, as TradingView's does; a variable bound to it or a function given it holds a copy, refused where that name is changed. A method on a na history stops the run (RE10052, RE10053). An object's array or matrix field read through the object's history kept the lowering it compiled to; its tape replays. Refused by name, located, in the analyzer: what TradingView refuses (the history of an object's array, matrix or map field, h.xs[1] and (h.xs)[1]: CE10290; a method straight after the history, now cited CE10011; an array where a number or a condition is expected: CE10123, CE10173, CE10101, CE10122, CE10009), and what PineForge does not keep, none of which compiled (function scope, a call's or a selection's history, request payloads, a block's var, a loop's local). test_collection_scope_isolation read slot[0] as an element of array.from, which TradingView refuses (CE10122); it now reads slot.get(0) and pins the refusal. The ahist_ref probe drops its array-or-na field, which PineForge cannot hold (a na array never compiled), and its tape is re-exported. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…rrowing The int-to-double conversion of a user-defined record's float field bound its value to `_pf_v`, the name the int narrowing's lambda binds, and test_e2e_security_epoch_int64's scan of narrowed epochs read `auto _pf_v = (...); if constexpr (std::is_floating_point_v<...>)` as one. It binds `_pf_w`. GCC only warns at the narrowing the pre-lane build emitted, and compiles it, reading an int na as -2147483648; test_the_pre_lane_build_did_not_ compile_it now compiles with -Werror=narrowing, under which Clang and GCC both refuse the pre-lane C++ and accept this build's. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
d1920c6 refused, by name, every array or matrix history read it did not lower, and some of them compiled before: str.tostring(a[1]) anywhere, na(a[1]) in a function, a request payload, a loop or a block's var, array.size(x[1]) of an array parameter, a variable bound to the history that only na() or str.tostring() read, and anything in a function nothing calls (the codegen emits none). TradingView accepts all of them. CollectionHistoryChecker.decide now gives every read one of three outcomes. Supported, as before, for a top-level variable or a block's. Refused, with TradingView's code, for what TradingView refuses (pine-facade, 2026-10-01): an array in a value slot of an array function (c.push(a[1]), a.includes(a[1]), array.new<float>(2, a[1]), matrix.new(..., a[1]): CE10123), nz() of a bound history, log.info's message, label.new's text, str.tostring of colors (CE10123), str.format of colors (CE10122), a scalar field (CE10173). Otherwise the lowering it had -- an element of the current array, or a parameter's current array, with a warning -- wherever that compiled, and refused by name where it did not. Which uses compiled before was measured: 5,286 synthetic probes, every use in every scope for every element type and offset, transpiled and compiled with a7c6473 and with this build, each shape compiled on TradingView. None that compiled before is refused or fails to compile now; 29 fail to compile on both builds for reasons history does not touch (a call in a dead function typing a live untyped helper, matrix.sum chains). The checker also: - keeps a history per declaration: two sibling blocks declaring the same name shared one, closed at the bar's end with whatever the member held, so the first block read the second block's array (ahist_sibling, a new TradingView tape: 336 of 336 exits differed; now none do). A declaration first closes the other declarations' histories; - refuses a change through a slice of the history (TradingView stops the run there, RE10051) and a binding read both as an array and through na(), which PineForge, holding no na array, cannot keep (the C++ did not compile); - walks a name's uses in its own scope, once per name: a script variable bound to the history was checked against every same-named variable of the script, and a diamond of helpers passing it on was walked once per call path; - keeps xs[k] := v (TradingView refuses it, CE10009/CE10013) as the element assignment it compiled to. The checker is built when the visit first meets an array or matrix history, so no other script pays for it. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
`for i = 0 to 1` / `pb = a[1]` / `r += pb.size()` declared the loop's C++ local `double pb` from the expression type inference, which read a history subscript as a number: `std::vector<double>` did not convert and `pb.size()` did not compile. A block's local is a member, typed where the members are registered, so only C++ locals (a loop's body, a while body) were wrong. `_infer_type` gives a supported history read its collection's C++ type. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The uses of a name bound to an array's history were found by walking the rest of the script for each binding, and a function's result by walking the whole script for its calls: 4,000 bindings took 57 s to transpile. The checker now indexes, when it is built, every statement's place in its list, each list's declarations, every identifier by name and every call by callee, and a name's reads are the indexed identifiers of its scope (the rest of its list, a nested list or loop that redeclares the name hiding it, and for the top level the functions and methods that do not bind the name): 7 s, where the same script binding array.copy(a) takes 4 s. Every one of the 5,286 probes of the use matrix transpiles to the same outcome, C++ and message as before. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
`Cell.new(1 + 2)` and `Cell.new(5 * 2)` initialized the float field with the int expression, a constant C++ narrows, and compiled; 991ab7e wrapped it in the run-time conversion. Such an expression (`_pure_int_literal_ value`) keeps its spelling when a double holds it exactly; one past 2**53 is still converted. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…function The independent review of c805757 found reads the checker kept on their earlier lowering that never compiled -- `array.sum(a[1])` in a function, a loop or a block's var, and the same reached through a selection's arm, an if's value, a function's result or a tuple's element -- and an earlier lowering it refused although it compiled: `array.size(array.copy(a[1]))`. A probe of every array function on an element of the current array, in a function, a loop and a block's var, for every element type, and on a parameter's current array, in a function and a method (1,225 probes, on a7c6473 and this build), gives the tables: a number element took no array function, but `array.copy`, whose vector of the element's size compiled where a namespace call or a loop's local took it; a `std::string` element took many (size, get, clear, pop, ...); a parameter's array took all but the ones returning a new array, and a string parameter's element-typed reads did not compile. `array.copy`'s verdict follows its direct consumer. A tuple element of a function's result follows the name each tuple declaration binds at its index. Also: - a later declaration's history member is `_pf_collection_hist_<n>_<name>`: `x`'s second declaration and a variable `x_1` both named theirs `_pf_collection_hist_x_1`, a duplicate member; - a method or function of the same name for another type no longer speaks for the collection's: `(a[1]).sz()` beside `method sz(Cell this)` was refused with that method's "has no fields". Over 6,511 probes no form that compiled on a7c6473 is refused or fails to compile; 33 fail on both builds for reasons history does not touch. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
`c.v := iv`, iv an int that is na on some bars, compiled (a plain C++ assignment converts the int) but stored the int na sentinel, -2147483648, where TradingView's field is na: a new TradingView tape, uassign_float (BINANCE:ETHUSDT.P 15), reads `na` on every third bar's close, and 112 of its 336 exits differed. A `:=` to a float field converts an int as the constructor does (991ab7e), an int na to na; a double, a literal and an int variable stored as a double keep their spelling. None of the 1,384 real-world sources' C++ changes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A second independent review found that whether the earlier lowering of an array function on a history compiled turns on how its value is used, where the tables keyed on the function alone: - a parameter's `array.sum(array.abs(x[1]))`, `array.size(array.copy( x[1]))`, `str.length(array.get(x[1], 0))` on a string array and the like compiled before, and 752eff6 refused them; - `f() => array.copy(a[1])` returned to its caller, a copy rendered or called a method on, a string element's `array.pop` or `array.get` returned by a function, and a string element's slice or `concat` argument did not compile before, and 752eff6 kept them; - an overloaded method given the history no longer compiled (its number overload took the element before); - `matrix.sum(m[1], m)` reached a result that never compiles. The tags now carry the consumer: an array function's new array is `:receiver` (a method on it), `:result` (a function returns it), `:render` or `:member` (a variable of the top level or a block holds it); a value is `:result` or `:bound`. Only the combinations the earlier build failed on are refused: `array.copy` of a number element keeps its earlier lowering for a namespace call's or a function's argument and a local; a string parameter's element read refuses a variable or a function's result taking it. An overloaded method given the history keeps the earlier lowering; `matrix.sum` of a history is refused by name. The reads of a name are indexed under each statement list holding them, so 3,000 sibling blocks binding one name take 2.5 s, not 7.2 s. Over the use matrix (5,286 probes), the per-function batteries (1,225) and both reviews' probes (666), on a7c6473 and this build: no form that compiled before is refused (but 14 TradingView rejects, CE10123 / CE10173 / CE10101), and every probe that does not compile fails on a7c6473 too. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…emits The checker kept the earlier lowering for every read in a callable it thought dead, on the premise that the codegen emits no function nothing calls from the top level. It does emit them: every method, called or not, and every function a call names, the call sitting in an uncalled function included (the analyzer visits every body and registers each function it sees called). An uncalled method's `(a[1]).size()` and `g() => (a[1]).size()` called only from an uncalled `f` kept the element lowering and did not compile, on a7c6473 as here. A function is dead now only when no call anywhere names it, the codegen's own rule; methods never are. A read in such a body is decided as any other (both shapes are refused by name). A read in a function no call names is still kept, except where its call hands it to an emitted function's untyped parameter, which then takes the earlier lowering's element: the use matrix's 20 shapes that failed on both builds (`sizeOf(v) => v.size()` given `x[1]` from an uncalled function) are refused by name; a string element passed on to `na(v)` or `str.tostring(v)` compiled there and still does. Over all 718 dead-function probes no form that compiled on a7c6473 is refused, and none fails to compile. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
… block The uses of a script variable bound to an array's history skipped every read in a function or a method that declared the name anywhere: `pb = a[1]` at the top level, then `f() => pb.push(1.0)` followed by a block declaring its own `pb`, hid the push, so the binding kept the earlier lowering and the C++ did not compile (as on a7c6473). A read in a callable is now the script variable's unless a parameter or a declaration before it, in its own statement list or an enclosing one of that body, binds the name -- the block scoping `_declaration_of` already used -- and the push is refused as TradingView stops there (RE10051). No other probe's outcome, C++ or message changes (7,177 transpiled before and after). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…scaped
An array's history member, `_pf_collection_hist_<name>` (a later
declaration's `_pf_collection_hist_<n>_<name>`), was outside the
emitter's reserved spellings: a script variable `_pf_collection_hist_x`
beside an array `x` whose history the script reads became a second member
of that name, an internal AssertionError ("duplicate generated
script-state member") where a7c6473's C++ did not compile either. The
pattern joins `_CPP_TEMPORARY_PATTERN`, so `_safe_name` escapes the
script's spelling (`pf_safe__pf_collection_hist_x`) and the script
compiles.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The int-to-double conversion of a float field (`Cell.new(v = ...)`, `c.v := ...`) tested is_na() on whatever integer it took, a bool too: the paired engine's na.hpp leaves bool out of its integer is_na template, so is_na(false) resolves to is_na(double) and a false stayed 0.0, but any is_na of a bool that tests the integer sentinel would read a false as na. A bool now converts straight to 1 or 0, as on a7c6473 (TradingView refuses a bool there: CE10123 in a constructor, CE10173 in an assignment). A trace over six bars pins `Cell.new(even)` and `d.v := even` at 1 and 0, never na. No real-world source's C++ uses the conversion. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Review cosmetics, the trivial ones: - HISTORICAL_CHANGE_MESSAGE, NA_ARRAY_MESSAGE and NA_MATRIX_MESSAGE were unused, their texts repeated in the C++ templates; the templates now take them (the emitted C++ is byte-identical); - a refusal of an array function's result named it "array function's result"; it names the call (`array.slice(a[1]) is an array, which TradingView refuses for operator + (CE10123)`); - a test keeps ARRAY_CHANGING_METHODS / MATRIX_CHANGING_METHODS equal to the codegen's COLLECTION_MUTATING_METHODS on each namespace. The third copy of the matrix-method lowering (`_matrix_history_method_expr` beside visit_call's two) stays: folding them is not a cosmetic change. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
`c = matrix.copy(m[1])`, `matrix.transpose(m[1])` and `matrix.submatrix(m[1], ...)` of a matrix<int>, <string>, <bool> or <color> declared `c` a matrix of floats: the receiver lookup that types a matrix function's new matrix saw no variable behind the history read, and the namespace form at the top level, in a block or in a loop did not compile (the method form, a `var` and a matrix<float> did). The read is now the variable's, whose copy has its type. A battery of the three functions on five element types, two forms and six scopes compiles but for a function's local (refused by name, as before); a7c6473 compiled none of them (a matrix history was a Series<double> there). Found by the full-battery compile at HEAD: three review probes now compile, no other probe's C++ changes, and no real-world source's C++ does. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The full-battery compile at HEAD (7,177 probes) found two classes of history read whose C++ does not compile, as on a7c6473: an element function's value of a string array's history that the earlier lowering reads (a character of the current array's string, held as a number, which most string consumers do not take: str.length, ==, a typed parameter, a push), and the history of a variable or a call the analyzer does not type as an array or a matrix (a method's or matrix.row's new array, a selection of arrays, a tuple's element, an array of drawings), which the checker never sees. The array history row and the test module's docstring name both instead of claiming that every read the earlier lowering could not compile is refused, and the row notes that a matrix function's new matrix takes the history's type. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Oct 2, 2026
* docs: changelog and version scoping for 1.0.1 1.0.1 pairs with engine v1.0.1, which changes only documentation since v1.0.0. The changelog section covers #156 and #157, the changes since 1.0.0 that a user can observe: - C++ that did not compile now compiles: the history of user-defined objects and drawings (#156) and of arrays and matrices (#157), and a non-constant int given to a float field (#157); - one value that came out silently wrong: an int na assigned to a float field was stored as -2147483648 and is na now (#157); - new compile-time refusals with TradingView's codes (CE10010, CE10011, CE10101, CE10122, CE10123, CE10173, CE10187, CE10290, CE10009), and the forms refused by name; - the "not covered" limits of both pull requests, the migration step (regenerate C++ and relink) and the pairing (1.0.1 supports only engine v1.0.1). The Python and JSON contract and the report keys do not change. The README, CONTRIBUTING, AGENTS.md / CLAUDE.md, docs/PUBLIC_CONTRACT.md and docs/pine-cap-activation.md name 1.0.1 where they named 1.0.0 as the current release or pair, as the README's release-lane note asks (the release section, the baseline paragraph, the engine src/source link, the @pf-trace note, the clone command and its output, the timing note, the explicit-attachment section, a 1.0.1 row in the pairing table). Lines that say what changed in 1.0.0 stay, and no line calls a version the latest. Checked on this tree's code (67892cd) against v1.0.0, CPython 3.14.6 on an Apple M4 Max: the engine corpus's 325 sources and the 277 gate fixtures transpile to byte-identical C++ (13 refusals, identical messages); the largest transpilation takes 0.048 s (0.049 s on v1.0.0); the quick-start SMA cross transpiles to the same C++. The tutorial run with that C++ against engine 6b45f510 (Linux arm64, GCC 13) prints the README's 9 trades, +738.20, and 13 trades with the old sizing declared. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * docs: the 1.0.1 code was graded before it merged, with no tier change Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QFVRQ4gcJCLbjfYqGtmy4k --------- 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.
Array and matrix history; an int given to a float field
Codegen emitted C++ that did not compile for the history of an array or a
matrix (
(a[1]).size(),array.size(a[1]),(m[1]).get(0, 0)), and for anint given to a
floatfield of a user-defined type (Cell.new(v = bar_index)).This branch lowers the first the way TradingView reads it, refuses the
spellings TradingView refuses, and keeps every form that compiled before.
Based on main after the object-history change (#156) merged.
What TradingView reads
Synthetic probes, compiled and run on TradingView, BINANCE:ETHUSDT.P 15,
2025-04-01 to 2025-04-08; ten tapes and their probes are in
tests/fixtures/array_history_tv(README: sha256 of every probe and tape).a[k]of an array or a matrix is a read-only copy of the collection as thevariable left it at the end of its scope's execution k executions back:
the bar k bars back for a top-level variable, its block's previous run for
a block's local. A
vararray's history is one element shorter than thearray it grows; a
varmatrix's holds the previous bar's value.bound to it stops the run (RE10051); a method on it before the variable
has a history stops the run (RE10052 for an array, RE10053 for a matrix).
for v in a[1]iterates the array the variable holds now;a[0]is thearray itself.
(
a[1].size(): CE10011); the history of an object's array, matrix or mapfield (
h.xs[1],(h.xs)[1]: CE10290); an array where a number, acondition, a string or an element is expected (CE10123: operators,
nz,math.*, value slots of array functions such asc.push(a[1]),array.new<float>(2, a[1]),log.info's message,label.new's text,str.tostringof colors; CE10173: a scalar variable or field; CE10101: anif or while condition; CE10122:
array.from,str.formatof colors;CE10009: a bare statement at the top level).
What changes
_PFCollectionHistorybeside it keeps the copies of one declaration. Each execution of the
declaration opens a slot and the bar's end closes it with a copy; a
sibling block's declaration of the same name first closes the other
block's history. The members' names (
_pf_collection_hist_<name>) are theemitter's: a script variable spelled so is escaped.
CollectionHistoryCheckergives every read one outcome:changes it,
na(), afor...inloop, a variable or a function argumentthat only reads it. A matrix function's new matrix
(
matrix.copy(mi[1])) has the variable's element type;or a parameter's current array, with a warning), wherever that compiled,
and refused by name where it did not, but for the two gaps under "Not in
this branch". Which uses compiled before was measured, not assumed:
every use of an array or matrix history and every array function, in
every scope and for every element type, was transpiled and compiled
with the previous build, and TradingView's compiler checked each use.
Whether the earlier lowering compiled often turns on how an array
function's value is consumed (a method on the new array, a function
returning it, a variable holding it), so the verdict follows the
consumer.
codegen emits every method and every function that some call names,
including a call inside a function nothing calls. A function that no
call names is never emitted, so its reads keep the earlier lowering.
Cell.new(v = bar_index)converts the int to a double, and an int na tona; an expression of int literals keeps its spelling.
c.v := ivconvertsthe same way: it used to compile but stored an int na as -2147483648,
where TradingView's field is na (
uassign_float: 112 of 336 exitsdiffered). A bool converts to 1 or 0 and is never na. TradingView refuses
a bool there: CE10123 in a constructor, CE10173 in an assignment.
Verification
built runtime, corpus): 4,977 passed, 33 skipped, 0 failed. The skips are
pre-lane reference commits absent from the test checkout and empty
parameter sets, as before.
ahist_ref,ahist_mtx,ahist_field,ahist_loop,ahist_more,ahist_sibling,uctor_float,uassign_float. The runtime stops with RE10051 / RE10052 / RE10053 whereTradingView stops.
branch's first version (one shared history per name),
ahist_siblingdiffered on 336 of 336 exits.
byte for byte, except two whose
.clear()lines change order betweenruns of the same build; in fresh processes they are identical.
matrix history use and every array function, in every scope and for
every element type, and 666 were written by two independent reviews. Each
probe was transpiled and compiled with this branch and with a7c6473:
240 are refused now, and TradingView's compiler rejected each of their
uses when probed (CE10123, CE10122, CE10173, CE10101). The uses are:
includes(52);argument of
nzormath.abs(2);str.tostringorstr.formatof an array of colors (121).compile before and compile now.
matrix.sum, na arrays,arrays of epochs, an object array's searches, overloads by element
type, and others);
b.concat(id2 = a), which has no history read; the keyword argument isdropped.
nothing calls, which types a live untyped helper) are now refused by
name.
Not in this branch
a selection is not kept (TradingView keeps one per call); such reads keep
the lowering they had where it compiled, and are refused otherwise.
not compile:
earlier lowering is kept (in a function, a method, a loop or a block
var), for examplef() => str.length(array.first(s[1])). That valueis a character of the current array's string and reaches the C++ as a
number. Most string consumers do not take it: the
str.*functionsother than
str.tostring/str.format,==, a typed parameter, apush, a label's text. A dedicated battery found 650 of 1,200 such
probes failing; none of them compiled before.
an array or a matrix: a method's or
matrix.row's new array, aselection of arrays, a tuple's element, an array of drawings.
matrix.rows()/columns()/elements_count()orstrategy.wintradesgiven to a float field does not compile, as before:the codegen types those reads as doubles.
str.tostringof an array (any array, history or not) is not lowered.variable has one holds an empty array where TradingView's is na (a method
on it stops TradingView's run), and one the script also tests with
na()is refused.
matrix.sum(m1, m2)and other matrix results used without a declared typedo not compile, history or not.
🤖 Generated with Claude Code