Follow the emission invariant for translator changes. The historical acceptance of defensive placeholder fallthroughs below is superseded; an upstream check does not authorize a placeholder in the emitter. Existing placeholders are open defects, even behind an upstream refusal. Preserve compilation for TradingView-valid scripts that already transpile and compile while implementing their semantics, as the invariant requires.
Historical record. Each section below is dated and describes the tree of its date, not
main. The Phase B audit ran on 2026-05-28, before the first public release, so itsfile:linereferences point into that tree. Since then,color.from_gradienthas become a visual-only stub that transpiles with a warning (it is no longer rejected), and the 2026-09-25request.securityfollow-up is superseded: a request of another symbol whose value can reach a trade now reads the feed a requests manifest pins for it or stops the run where its value is read (one that reaches only plots, alerts, tables or logs lowers tonawith a warning), and a?:symbol that can reach a trade registers the symbol the run computes (seeCHANGELOG.md, "Libraries and requests for outside data").AGENTS.mddescribes the current support contracts.
Tracks fallthrough paths in pineforge_codegen/codegen/visit_call.py and
pineforge_codegen/codegen/visit_expr.py that emit a literal "false" /
"0" / "na<double>()" for an unrecognized construct, and whether the
analyzer (support_checker.py) already rejects the construct before
codegen ever sees it.
The historical audit counted a gap only when both were true:
- The fallthrough is reachable from a valid Pine v6 construct.
- The fallthrough produces silently-wrong behavior (not a defensive sentinel that the engine ignores).
The audit treated a fallthrough behind an analyzer refusal as a defensive safety net. Under the emission invariant, such a fallback must raise an internal error that becomes a located refusal instead of emitting a value, subject to the invariant's non-regression precedence.
The historical audit reported no additional gaps beyond those it closed. That conclusion used the narrower criteria above; it does not close the defensive-placeholder defects identified below.
Ran grep -n 'return "false"\|return "0"' pineforge_codegen/codegen/visit_call.py pineforge_codegen/codegen/visit_expr.py
and triaged each of the 34 matches against the analyzer's rejection
tables (HARD_REJECT_FUNC, HARD_REJECT_NAMESPACE, NOT_YET_FUNC,
UNSUPPORTED_BARE_FUNCS, UNSUPPORTED_NAMESPACES,
UNSUPPORTED_MEMBERS, UNSUPPORTED_NAMESPACE_VARS,
SUPPORTED_TIMEFRAME_FUNC, SUPPORTED_SYMINFO, SUPPORTED_MATH,
SUPPORTED_STR, SUPPORTED_TA, SUPPORTED_ARRAY, SUPPORTED_MAP,
SUPPORTED_COLOR_CONST, SUPPORTED_COLOR_FUNC, SUPPORTED_LOG,
SUPPORTED_RUNTIME_FUNC).
| Location | Construct | Status |
|---|---|---|
visit_expr.py:140 |
ColorLiteral → "0" |
Valid color literals lower to int64 constants. A zero substituted for a malformed literal is a placeholder defect. |
visit_expr.py:218 |
strategy.short → "false" |
Known enum constant. |
visit_expr.py:288 |
strategy.fixed → "0" |
Known qty-type constant. |
visit_expr.py:339 |
chart.is_* non-standard chart types → "false" |
Correct semantics (batch is always standard OHLCV). |
visit_expr.py:340-345 |
chart.<unknown> → raise ValueError |
An internal guard must become a located refusal under the emission invariant. A bare exception is an error-reporting defect. |
visit_expr.py:371-372 |
timeframe.isticks → "false"; other timeframe.* → "0" |
The known isticks branch describes batch data. The unknown-member catch-all is a placeholder defect even if upstream checks prevent it from running. |
visit_expr.py:385,388 |
barstate.isrealtime → "false"; other barstate.* → "false" |
The known historical-bar value is separate from the unknown-member catch-all, which is a placeholder defect. |
visit_expr.py:396,405,412,417,423 |
backadjustment.*, settlement_as_close.*, adjustment.*, dayofweek.* catch-alls |
Known constants have defined values; unknown-member defaults are placeholder defects. An engine accepting an int does not establish Pine semantics. |
visit_expr.py:443 |
session.<unknown> → "false" |
Known members being enumerated does not justify a value for an unknown member. The catch-all is a placeholder defect. |
visit_expr.py:448 |
syminfo.<unknown> → "0" |
The historical whitelist blocked unknown members upstream; the emitter placeholder is still a defect. |
visit_expr.py:459 |
color.<unknown> → "0" |
The historical whitelist covered known constants; an unknown-member placeholder is still a defect. |
visit_expr.py:461 |
SKIP_NAMESPACES (table/label/line/box/polyline/chart/linefill/display/size/position) → "0" |
The audit treated these as visual omissions. Substituting zero for a missing value lowering is a defect; each supported member needs its semantics. |
visit_expr.py:484-507 |
strategy.oca.<unknown>, strategy.direction.<unknown>, strategy.commission.<unknown>, strategy.closedtrades.<unknown>, strategy.opentrades.<unknown> catch-alls |
Known enum values do not define unknown members. The catch-all defaults are placeholder defects. |
visit_call.py:357 |
map.<unknown>(...) → "0" |
The historical whitelist rejected unknown functions upstream; the emitter placeholder is still a defect. |
visit_call.py:404 |
array.<unknown>(...) → "0" |
The historical whitelist rejected unknown functions upstream; the emitter placeholder is still a defect. |
visit_call.py:411-414 |
SKIP_NAMESPACES/SKIP_VAR_TYPES/SKIP_FUNC_NAMES → "0" |
The audit treated these as visual omissions. A missing lowering remains a defect even when its result is usually ignored. |
visit_call.py:664,689 |
timestamp() with 0 or 1 positional args → "0" |
Arity must be checked against the applicable overload, including the string overload. A placeholder zero is not an arity check or a timestamp lowering. |
visit_call.py:693 |
barssince(...) → "0" |
The historical checker rejected this bare call upstream; the emitter placeholder is still a defect. |
visit_call.py:809-815 |
timeframe.<unknown>(...) → raise ValueError |
The historical whitelist rejected unknown functions upstream. The internal guard must become a located refusal. |
visit_call.py:1057 |
strategy.opentrades.exit_time(idx) → "0" |
Accepting zero in the engine does not establish Pine semantics. Apply the accessor grammar and return semantics; any placeholder remains a defect. |
visit_call.py:1088 |
Generic strategy.<unknown>(...) → "0" |
The historical checker rejected unknown functions upstream; the emitter placeholder is still a defect. |
visit_call.py:1096-1109 |
color.new/r/g/b/t/rgb/from_gradient with missing args → "0" |
Missing required arguments need signature checks, not placeholder values. The historical rejection of color.from_gradient has since changed, as noted above. |
The historical audit closed the reachable fallthroughs it identified with analyzer checks. Its remaining known-constant branches are distinct from defensive placeholders and missing visual lowerings. The latter remain defects under the emission invariant, even if an upstream check blocks them or the engine ignores the result.
Two raise ValueError guards (visit_call.py:812, visit_expr.py:342)
were added to expose internal failures. Those errors still need conversion
to located refusals where the emission invariant permits refusal.
The audit's original conclusion of no further work does not resolve these defects or authorize new refusals of TradingView-valid, compiling scripts.
lab tv probes on BINANCE:ETHUSDT.P, 15 minutes, 2025-04-01 through
2025-04-08 used the operation's result in each order signal. An earlier
unobserved probe let TradingView eliminate the operation and was discarded.
| Operation | TradingView evidence | PineForge behavior |
|---|---|---|
Primitive array<float> slice, then write through slice and source |
32/32 entries signal both-alias; covered tape SHA-256 fbd83309262bfd1a8a06e8f3cc82228fad1b1358b03b135b200246d5f4cfb7c0 |
The same source run against the built engine signals no-alias. Both syntax forms emit a located warning in transpile_full / transpile_json. |
array.fill(a, 9, 2, 1) on [1,2,3] |
32/32 entries signal 123 (no-op); covered tape SHA-256 83243e37dd66678e1b9127baac2ba42ef74815d31097c0c2095a08efffca575e |
Guarded no-op, without invalid STL iterator arithmetic. |
array.slice(a, 2, 1) |
request_error RE10044, “Index 'from' should be less than index 'to'.” |
Runtime error with the same message. |
Negative index_from or index_to in fill / slice |
Four observed probes return request_error RE10045, index -1 out of bounds for size 3. |
Both endpoints reject before iterator arithmetic; existing guard was correct. |
The slice copy remains a semantic gap. A safe alias requires a collection view
whose source storage survives slice lifetime and mutations, including source
resizing and temporary receivers. The current std::vector representation is
used throughout codegen, so the implementation preserves the compiling
lowering and warns at each slice call. The probe establishes that primitive elements alias
in TradingView; the earlier “UDTs only” possibility is refuted.
The support checker now indexes declarations, reads and rebinds by lexical
binding for the security symbol argument. A local sym inside a function no
longer taints an unrelated global sym; a rebind of the global inside an if
still disqualifies it. A TradingView probe with the local rebind and a global
chart-symbol request recorded 32/32 chart-feed entries (covered tape SHA-256
8a0492f71ffc489e45c3eb80907bf796658719e2ef7efa7c0df312e8eeb28917).
A second TradingView probe chose BINANCE:BTCUSDT.P in the true ternary arm
on an ETH chart and recorded 32/32 alternate-feed entries (covered tape
SHA-256 a9aa27e13b64260eba08cafec3e345ddc7e06f378f5c6b94e5f6955eb32abaf3).
The generated security evaluation has no alternate-symbol feed. The checker
therefore tests both ternary arms, including arms reached through aliases,
and warns when any path can select another symbol. It preserves previously
accepted scripts and their current-symbol lowering; an unconditional alternate
symbol remains rejected. A legacy bare-name admission that lexical resolution
finds unsafe also remains accepted with a warning. These approximations
remain semantic defects to fix while preserving compilation.
The 314 public corpus strategies all still transpile, and their emitted C++ hashes are unchanged. There is no public TU or public trade tape to regrade for this change; the changed behavior is diagnostic or accepts a formerly over-rejected source.