Repository navigation
History of user-defined objects and drawing references - #156
Merged
Merged
Conversation
A variable of a user-defined type or a drawing type holds a reference,
and its history holds the references it held (TradingView tapes in
tests/fixtures/udt_history_tv). Such a history was declared
Series<double> and the handles pushed into it, which did not compile:
it is now a Series of the handles (Series<Cell>, Series<Box>; na is
T{}) for globals, var objects, block and function locals, history
parameters and method receivers. The history of an expression whose
value is an object (a field holding one, a call's result, a ternary's
selection) is the reference it produced at its previous evaluation,
kept on every execution below a lazy edge where that is safe. A field
chain now visits its receiver, so (o[1]).inner.v reads the history of
o instead of the current object.
== and != compare line and label references by identity, as
TradingView does; every other object or drawing comparison and a
comparison with na are refused as TradingView refuses them (CE10123,
CE10187). Refused as TradingView refuses them: a field or a method
straight after the history operator (c[1].v, CE10011; b[1].get_top(),
CE10010) and the history of a value field (c.v[1], CE10290). Refused
here: reference history inside a request.security expression, and the
history of a chart.point whose fields the script changes.
w9dg-pin-holders2 (a box carried by s := s[1]) now replays.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…s field stops the run The probes TradingView's compiler refused are refused with its codes, udth_na_field (now an int field, re-checked on TradingView: RE10041) stops the engine's run as TradingView's stops, and the matrix history test spells its read as TradingView takes it, (m[1]).get(0, 0), which lowers to the C++ m[1].get(0, 0) did. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Review follow-ups. A function's history-read var object at two call sites declared its per-call-site copies Series<double>; they hold the handles now, as the base member does. The prepass typed the history Series of an expression over a typed parameter (f(Outer p) => p.inner[1], a selection of two parameters) without the parameter bound, which left it unregistered (an AssertionError at the hoist) or a Series<double>: the reference check binds the owner's parameters, emission reads the type the member was declared with, and only a registered read is hoisted. The history of such an expression is the reference type in _infer_type too, so a local holding it declares the handle. A request.security with no expression (CE10165 on TradingView) lowers to na again instead of crashing the reference-history check. TradingView's udth_fn2 tape pins the three shapes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A drawing parameter or method receiver whose history is read is a const Series<Box>& in the emitted callable, and the lexical drawing types admitted a parameter only when its C++ type was Box or Box&, so (x[1]).get_top() and (this[1]).get_top() found no drawing receiver and emitted None(). The series parameter's element type admits it too. TradingView reads the box the parameter or receiver held at the previous call (udth_drawparam, next commit's tape). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A callable takes a line, box, label, linefill or chart.point parameter as T&, and a history read (f(b[1])), a history-read variable's current slot (f(b), emitted b[0]), a new drawing (f(box.new(...))) or a user call's result is a C++ prvalue no T& takes: "non-const lvalue reference cannot bind to a temporary", for plain functions and for a typed method's receiver ((b[1]).topM(), b.topM()) alike. Such a call is staged as a fresh array's is (_ordered_user_call_expr): each argument binds to a named forwarding reference, in source order. The handle is an arena id and Pine forbids reassigning a parameter, so the callee reads the drawing the argument names. A variable, a field and a collection element are lvalues and keep their C++. TradingView's udth_drawparam tape (BINANCE:ETHUSDT.P 15) pins these shapes and the previous fix's: 336 of 336 exits replay. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
udth_expr's reads sit in arms its na() guards skip on the first bar only, so its README overstated what the tape shows about skipped arms. TradingView's udth_lazy tape skips the arm on every even bar: each odd bar's (o.inner[1]).v, (mk(bar_index * 7)[1]).v and selection read the even bar before it, where the bars that took the arm would read two bars back. The codegen's every-execution push below a lazy edge already reads them so (336 of 336 exits, on bf9134f too); the README now says what each tape shows. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The review read a parameter's history at a call site that skips bars as counting calls in TradingView. TradingView's udth_clock tape says otherwise: at a call on every third bar an object parameter's x[2] and a float parameter's y[2] read the argument of the call at or before two bars back, one slot per chart bar holding the last call's value (bar 4's call on bar 7, not bar 1's) -- the clock #109 gave plain functions' parameters, which the object parameters of this lane share: the tape replays 336 of 336. udth_clock_method shows a method receiver on the same clock, which the codegen does not keep: typed methods kept their own bridge, which pushes a receiver built at the call once per call, so from the third call on it reads three bars further back (111 of 336 closes). The gap is older than this lane and is pinned, not fixed here. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A method whose receiver's history is read is emitted once per call site, and the copy of the receiver's Series member was declared from its escaped spelling (pf_safe_this), which names no symbol: a Series<double> beside its base member's Series<Cell>. Both members are unused (the method takes the receiver as a parameter); the copy now has its base's type. Every other copy keeps its declaration. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Drawing parameters' and receivers' built-in methods, temporary drawings passed to drawing parameters, the parameter clock at a call site that skips bars and the receiver gap pinned beside it, skipped lazy arms, and the eleven replayed tapes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This was referenced Oct 1, 2026
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.
History references on user-defined objects and drawing references
codegen 1.0.0 emitted C++ that did not compile for the history of an object: a variable of a user-defined type or of a drawing type whose history was read (
obj[1]) was declaredSeries<double>and the object handles were pushed into it. The four probes of the blog fact-check (t13_ne_hist,t14a_udt_hist_typed,t14b_udt_hist_untyped,t14c_udt_hist_var) failed with "no viable conversion from 'Cell' to 'double'" and "no member named 'push' in 'Acc'".What TradingView does
Checked against TradingView itself (synthetic probes and their TradingView trade exports in
tests/fixtures/udt_history_tv, see its README) and the Pine v6 User Manual:obj[k]is the reference the variable held k bars back, na before the first bar, and(obj[k]).fieldreads that object as it is now:(acc[1]).totalof avarobject equalsacc.total, and a change made throughc[1]shows through(c[2])on the next bar (udth_ref). Boxes, lines and labels alike, a box deleted throughb[1]included (udth_box,udth_box2).ifblock the history counts the executions of the scope (udth_fn,udth_method,udth_fn2). At a call site that skips bars, a parameter's and a receiver's history is one slot per chart bar holding the last call's value:x[2]at a call on every third bar reads the call of two bars back, not two calls back (udth_clock,udth_clock_method).(this[1]).get_top(), is the previous call's box, and a drawing passed to a drawing parameter is the drawing itself, a history read (f(b[1])), avarbox's history, a new box and a line beside its history included (udth_drawparam).o.inner[1],f()[1],(c ? a : b)[1]) is the reference it produced at its previous evaluation (udth_expr), and below a lazy edge it is kept on every bar, the bars a ternary's selector skips the arm included (udth_lazy: the arm is skipped on every even bar, and each odd bar reads the even bar before it).==/!=compare line and label references by identity, na included (udth_line_eq); TradingView refuses them for every other reference type (CE10123) and a comparison with na (CE10187).c[1].v(CE10011) andb[1].get_top()(CE10010) -- "Migration guide to v6", History of UDT fields: "use the syntax(myObject[10]).field- ensure the object's historical reference is wrapped in parentheses, otherwise it is invalid" -- and the history of a field holding a value,c.v[1]and(c.v)[1](CE10290). A field read of a na object stops the run (RE10041). So the four blog probes are TradingView compile errors as written; their parenthesized forms are the valid spelling.The change
Seriesof its handles (Series<Cell>,Series<Box>; na is the handle'sT{}): globals,varobjects, block and function locals (with their per-call-site copies), history parameters and method receivers.(c[1]).vreads the arena record the handle names; a drawing parameter's or receiver's history takes its built-in methods ((x[1]).get_top()).T&-- a history read, a history-read variable's current slot, a new drawing or chart point, a user call's result -- is bound through a staged call, as a fresh array already is. Variables, fields and collection elements keep their C++.(o[1]).inner.vregistered no history and silently read the current object.==/!=of two lines or two labels compare handle ids.x[k].field/x[k].method()(CE10011 / CE10010, hint(x[k]).field), reference comparisons other than line/label (CE10123) and with na (CE10187), the history of a value field (CE10290). Refused here (they never compiled): the history of a reference inside arequest.securityexpression (TradingView reads the requested timeframe's objects, which PineForge does not keep), and the history of achart.pointvariable whose fields the script changes (PineForge holds a point as a value).CLAUDE.md/AGENTS.mdgain an "Object and drawing history" support-contract row.No refusal applies to a script that compiled before: each one either is a spelling TradingView rejects or produced C++ that did not compile.
Tests
tests/test_udt_history.py: every shape compiles (42 shapes: globals, var, locals, parameters, receivers, drawing parameters' and receivers' methods, temporary drawings passed to drawing parameters, call results, selections, fields, boxes, lines, labels, chart points, two call sites), the emitted Series types, and every refusal with its TradingView code, including the probes TradingView's compiler refused.tests/test_e2e_udt_history.py: eleven TradingView tapes replay exit for exit (336 of 336 each); bar-by-bar traces ofobj[1],(obj[1]).field, a change through history,l != l[1]and a drawing parameter's and receiver's history read with a built-in method; a na object's field stopping the run; the pre-change build failing to compile every tape probe; and the receiver-clock gap below, pinned.tests/test_e2e_drawing_lifetime.py:w9dg-pin-holders2(a box carried bys := s[1]), evidence-only until now because it did not compile, replays TradingView's tape.tests/test_codegen_matrix_typed.py: the matrix history test spells its read(m[1]).get(0, 0), which lowers to the same C++ as before;m[1].get(0, 0)is a TradingView compile error.Run against main 01b4ee3, the new tests fail 76 and error 12 of 103; the 15 that pass there are the tape-content checks, the pre-change-build checks and three shapes that already compiled (a
varbox's history, a requested field value, a request without an expression). After the change all pass.Gates
The full suite on a shared x86-64 build host (Ubuntu, GCC 13), against engine 6b45f510 (v1.0.0 and two docs commits), at this branch's head: pytest 4783 passed, 0 failed, 33 skipped (28 need an older commit the shipped checkout's history lacks, 5 empty parameter sets), junit tests=4816 failures=0; tests/test_compile_corpus.py 314 passed, 0 skipped; the Pyodide gate's fixtures PARITY OK over 277 (ok 264/264, err 13 rejected as expected).
Population: the transpiler's C++ for every one of the 1,384 unique population sources is byte-identical on main and on this branch's head (also on its earlier head bf9134f), transpiled in fresh processes; within one long census process two sources differ only by the known ordering flake of two
.clear()lines. None of them reads an object's or drawing's history. An experiment on bf9134f measured the 208 population probes (58 sources) that declare a user-defined type, across 17 lanes: all 208 excellent, each with the tier and match % of the active baseline; the later commits leave every population source's C++ unchanged.Not covered:
this[2]reads three bars further back (udth_clock_method, 111 of 336 closes; pinned by a test). Typed methods kept their own bridge when fix: hold last values for history-reading UDF series parameters at skipped callsites #109 moved plain functions' parameters to TradingView's clock; this lane's object parameters already have it (udth_clockreplays).floatfield (Cell.new(v = bar_index)) narrows in a braced initializer and does not compile.(a[1]).size(),(m[1]).get(0, 0)) and a method on an operator expression ((close * 2).m()) emitNone.h.xs[1]on an array field keeps its current-collection lowering although TradingView refuses it (CE10290), as it refuses a drawing field'sh.ln[1], which PineForge already refused.Seriesmembers for history-read parameters (a receiver'spf_safe_thisand its call-site copies) remain, as for every parameter type before; their copies are now typed like their base.🤖 Generated with Claude Code