Skip to content

fix(pineToJS): allow the _ discard identifier to be declared multiple times - #302

Merged
alexgrover merged 2 commits into
devfrom
fix/underscore-discard-redeclaration
Sep 17, 2026
Merged

alexgrover merged 2 commits into
devfrom
fix/underscore-discard-redeclaration

Conversation

@alexgrover

@alexgrover alexgrover commented Sep 7, 2026

Copy link
Copy Markdown
Member

Problem

Pine Script treats _ as a write-only discard identifier. Per the TradingView docs (Variable declarations → Using an underscore (_) as an identifier):

You can assign any number of values to a _ identifier anywhere in the script, even if the current scope already has such an assignment.

PineTS only de-duplicated _ within a single tuple, so any script that declared _ more than once in the same scope failed at the JS stage:

//@version=5
indicator("t")
[_, s1, _] = ta.macd(close, 12, 26, 9)
[_, s2, _] = ta.macd(close, 5, 10, 3)   // SyntaxError: Identifier '_' has already been declared
_ = ta.sma(close, 10)                    // same

Reproduced failures (before the fix):

  • two tuple declarations re-using _ in the global scope
  • the same inside a user function body
  • plain _ = expr repeated
  • tuple _ followed by plain _
  • _ in an if branch followed by another _ declaration after the if

Fix

src/transpiler/pineToJS/codegen.ts: every _ declaration target — plain declarations, tuple declarations, and for [..] in headers — is renamed to a fresh _$N placeholder via a shared renameDiscardTargets / freshDiscardName helper. $ is not a legal Pine identifier character, so the placeholders can never collide with user variables (the same _$N scheme the codegen already uses for reserved-word renames).

Generated JS for the script above:

let [_$0, s1, _$1] = ta.macd(close, 12, 26, 9);
let [_$2, s2, _$3] = ta.macd(close, 5, 10, 3);
let _$4 = ta.sma(close, 10);

The two previous copy-pasted intra-tuple dedup loops are replaced by the helper.

_ stays write-only

TradingView also says "A value assigned to such a variable cannot be accessed" — reading _ is a compile error there (Undeclared identifier "_"). Because every declaration target is renamed, a read of _ in the generated JS is a genuinely undeclared identifier and fails with ReferenceError: _ is not defined — the same way PineTS surfaces every other undeclared identifier. (Before this PR the first _ in a tuple kept its name, so plot(_) silently returned the discarded value.) Tests pin this behavior.

Tests

New tests/transpiler/underscore-discard-identifier.test.ts (11 tests):

  • 7 positive cases covering all the scenarios above plus the TradingView docs ta.bb example and a for-in loop body. Expected values come from the same computations written with uniquely-named variables (already-supported Pine), so the tests assert correctness of the resulting series, not just "no throw". All failed before the fix with Identifier '_' has already been declared.
  • 3 negative cases asserting that reading _ (plot(_), _ + 1, inside a function body) throws.
  • 1 codegen check that no bare _ declaration survives and each placeholder is unique.

Full suite: npm test -- --run → 175 files / 1863 tests passed, 0 failures.

Notes

  • var [_, a, _] = ... is still rejected by the parser (Expected identifier after var). This matches TradingView, which does not allow var/varip on tuple declarations.
  • _ := 5 (reassigning _) is not caught. This is pre-existing general behavior — any := to an undeclared name (e.g. foo := 5) currently becomes a sloppy-mode global rather than an error — and is not specific to _, so it is left out of this PR.
  • No runtime-transpiler (stage 2) changes.

…imes

Pine Script treats `_` as a write-only discard identifier that may be
declared any number of times, in any scope, including several times in
the same scope (TradingView docs, "Using an underscore (_) as an
identifier"). JavaScript forbids re-declaring a let/var binding in one
scope, so scripts such as

    [_, s1, _] = ta.macd(close, 12, 26, 9)
    [_, s2, _] = ta.macd(close, 5, 10, 3)
    _ = ta.sma(close, 10)

failed with `SyntaxError: Identifier '_' has already been declared`.

The codegen previously only de-duplicated `_` *within* a single tuple.
It now renames every `_` declaration target (plain declarations, tuple
declarations and `for [..] in` headers) to a fresh `_$N` placeholder.
`$` is not a legal Pine identifier character, so the placeholders cannot
collide with user variables.

Adds tests/transpiler/underscore-discard-identifier.test.ts as a
regression guard; expected values come from the same computations
written with uniquely-named variables.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Sep 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_46a2a17f-2b19-4949-8fb3-0a45881df53f)

TradingView rejects any read of `_` (Undeclared identifier). PineTS surfaces every undeclared identifier as a runtime ReferenceError; these tests pin that `_` behaves the same and never silently resolves to a discarded value.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Sep 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_c0040e94-1cdf-4edd-922d-b58ff2f21b95)

@alexgrover
alexgrover merged commit 6a22e5d into dev Sep 17, 2026
2 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant