From f7dbced430410c5b444d1b1bb8b58efe77cfde93 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Sun, 23 Aug 2026 15:35:40 +0100 Subject: [PATCH 1/2] Fix optional-chain transform panic on tagged templates flattenChain stops walking when the chain head is a tagged template, but then unconditionally read chain.Expression(), which is unhandled for TaggedTemplateExpression. Use the template's tag as the chain base and re-tag the template onto the checked value when rebuilding the chain. --- .../estransforms/optionalchain.go | 15 ++++++++ ...ionalChainTaggedTemplateNoCrash.errors.txt | 25 +++++++++++++ .../optionalChainTaggedTemplateNoCrash.js | 20 +++++++++++ ...optionalChainTaggedTemplateNoCrash.symbols | 18 ++++++++++ .../optionalChainTaggedTemplateNoCrash.types | 35 +++++++++++++++++++ .../optionalChainTaggedTemplateNoCrash.ts | 8 +++++ 6 files changed, 121 insertions(+) create mode 100644 tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.js create mode 100644 tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.types create mode 100644 tsc/testdata/tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts diff --git a/tsc/internal/transformers/estransforms/optionalchain.go b/tsc/internal/transformers/estransforms/optionalchain.go index 33b269280f594..605795d8b38bf 100644 --- a/tsc/internal/transformers/estransforms/optionalchain.go +++ b/tsc/internal/transformers/estransforms/optionalchain.go @@ -140,6 +140,10 @@ func flattenChain(chain *ast.Node) flattenResult { debug.Assert(!isNonNullChain(chain)) links = append([]*ast.Node{chain}, links...) } + if ast.IsTaggedTemplateExpression(chain) { + // A tagged template has no expression; its tag is the base of the chain. + return flattenResult{chain.AsTaggedTemplateExpression().Tag, links} + } return flattenResult{chain.Expression(), links} } @@ -204,6 +208,17 @@ func (ch *optionalChainTransformer) visitOptionalExpression(node *ast.Node, capt ast.NodeFlagsNone, ) } + case ast.KindTaggedTemplateExpression: + // A tagged template can only be the head of the chain (flattenChain + // stops walking at it); re-tag its template onto the checked base. + t := segment.AsTaggedTemplateExpression() + rightExpression = ch.Factory().NewTaggedTemplateExpression( + rightExpression, + nil, /*questionDotToken*/ + t.TypeArguments, + ch.Visitor().VisitNode(t.Template), + ast.NodeFlagsNone, + ) } ch.EmitContext().SetOriginal(rightExpression, segment) } diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.errors.txt b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.errors.txt new file mode 100644 index 0000000000000..99da1c45595f9 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.errors.txt @@ -0,0 +1,25 @@ +repro.ts(1,1): error TS2304: Cannot find name 'e'. +repro.ts(1,7): error TS1005: ')' expected. +siblings.ts(2,5): error TS1358: Tagged template expressions are not permitted in an optional chain. +siblings.ts(3,5): error TS1358: Tagged template expressions are not permitted in an optional chain. +siblings.ts(4,4): error TS1358: Tagged template expressions are not permitted in an optional chain. + + +==== repro.ts (2 errors) ==== + e?.``( + ~ +!!! error TS2304: Cannot find name 'e'. + +!!! error TS1005: ')' expected. +==== siblings.ts (3 errors) ==== + declare var a: any; + a?.b`c`; + ~~~ +!!! error TS1358: Tagged template expressions are not permitted in an optional chain. + a?.b`c`(); + ~~~ +!!! error TS1358: Tagged template expressions are not permitted in an optional chain. + a?.``.x; + ~~ +!!! error TS1358: Tagged template expressions are not permitted in an optional chain. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.js b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.js new file mode 100644 index 0000000000000..a645cc57f6a35 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.js @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts] //// + +//// [repro.ts] +e?.``( +//// [siblings.ts] +declare var a: any; +a?.b`c`; +a?.b`c`(); +a?.``.x; + + +//// [repro.js] +"use strict"; +e === null || e === void 0 ? void 0 : e ``(); +//// [siblings.js] +"use strict"; +var _a; +(a === null || a === void 0 ? void 0 : a.b) `c`; +(_a = a === null || a === void 0 ? void 0 : a.b) === null || _a === void 0 ? void 0 : _a `c`(); +a === null || a === void 0 ? void 0 : a ``.x; diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.symbols b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.symbols new file mode 100644 index 0000000000000..ba91da4497847 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.symbols @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts] //// + +=== repro.ts === + +e?.``( +=== siblings.ts === +declare var a: any; +>a : Symbol(a, Decl(siblings.ts, 0, 11)) + +a?.b`c`; +>a : Symbol(a, Decl(siblings.ts, 0, 11)) + +a?.b`c`(); +>a : Symbol(a, Decl(siblings.ts, 0, 11)) + +a?.``.x; +>a : Symbol(a, Decl(siblings.ts, 0, 11)) + diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.types b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.types new file mode 100644 index 0000000000000..cd90b8ce9dd88 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.types @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts] //// + +=== repro.ts === +e?.``( +>e?.``( : any +>e?.`` : any +>e : any +>`` : "" + +=== siblings.ts === +declare var a: any; +>a : any + +a?.b`c`; +>a?.b`c` : any +>a?.b : any +>a : any +>b : any +>`c` : "c" + +a?.b`c`(); +>a?.b`c`() : any +>a?.b`c` : any +>a?.b : any +>a : any +>b : any +>`c` : "c" + +a?.``.x; +>a?.``.x : any +>a?.`` : any +>a : any +>`` : "" +>x : any + diff --git a/tsc/testdata/tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts b/tsc/testdata/tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts new file mode 100644 index 0000000000000..036f3c6af7dad --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts @@ -0,0 +1,8 @@ +// @target: es2017 +// @filename: repro.ts +e?.``( +// @filename: siblings.ts +declare var a: any; +a?.b`c`; +a?.b`c`(); +a?.``.x; From b4863a0a63a10175a13f771179ba712bf037e779 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Mon, 24 Aug 2026 20:04:05 +0100 Subject: [PATCH 2/2] Rename regression test and cover the untransformed target Address review feedback: drop the NoCrash suffix from the test name and add es2024 as a second target so the emit is also covered when optional chaining is not lowered. --- ...nTaggedTemplate(target=es2017).errors.txt} | 5 +-- ...onalChainTaggedTemplate(target=es2017).js} | 5 +-- ...hainTaggedTemplate(target=es2017).symbols} | 3 +- ...lChainTaggedTemplate(target=es2017).types} | 2 +- ...inTaggedTemplate(target=es2024).errors.txt | 26 ++++++++++++++ ...ionalChainTaggedTemplate(target=es2024).js | 20 +++++++++++ ...ChainTaggedTemplate(target=es2024).symbols | 19 ++++++++++ ...alChainTaggedTemplate(target=es2024).types | 35 +++++++++++++++++++ ...rash.ts => optionalChainTaggedTemplate.ts} | 4 ++- 9 files changed, 112 insertions(+), 7 deletions(-) rename tsc/testdata/baselines/reference/compiler/{optionalChainTaggedTemplateNoCrash.errors.txt => optionalChainTaggedTemplate(target=es2017).errors.txt} (91%) rename tsc/testdata/baselines/reference/compiler/{optionalChainTaggedTemplateNoCrash.js => optionalChainTaggedTemplate(target=es2017).js} (82%) rename tsc/testdata/baselines/reference/compiler/{optionalChainTaggedTemplateNoCrash.symbols => optionalChainTaggedTemplate(target=es2017).symbols} (74%) rename tsc/testdata/baselines/reference/compiler/{optionalChainTaggedTemplateNoCrash.types => optionalChainTaggedTemplate(target=es2017).types} (76%) create mode 100644 tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).js create mode 100644 tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).symbols create mode 100644 tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).types rename tsc/testdata/tests/cases/compiler/{optionalChainTaggedTemplateNoCrash.ts => optionalChainTaggedTemplate.ts} (78%) diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.errors.txt b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).errors.txt similarity index 91% rename from tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.errors.txt rename to tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).errors.txt index 99da1c45595f9..703ee75bc68d7 100644 --- a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).errors.txt @@ -1,5 +1,5 @@ repro.ts(1,1): error TS2304: Cannot find name 'e'. -repro.ts(1,7): error TS1005: ')' expected. +repro.ts(2,1): error TS1005: ')' expected. siblings.ts(2,5): error TS1358: Tagged template expressions are not permitted in an optional chain. siblings.ts(3,5): error TS1358: Tagged template expressions are not permitted in an optional chain. siblings.ts(4,4): error TS1358: Tagged template expressions are not permitted in an optional chain. @@ -9,7 +9,8 @@ siblings.ts(4,4): error TS1358: Tagged template expressions are not permitted in e?.``( ~ !!! error TS2304: Cannot find name 'e'. - + + !!! error TS1005: ')' expected. ==== siblings.ts (3 errors) ==== declare var a: any; diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.js b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).js similarity index 82% rename from tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.js rename to tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).js index a645cc57f6a35..a66557579d6f5 100644 --- a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.js +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).js @@ -1,7 +1,8 @@ -//// [tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts] //// +//// [tests/cases/compiler/optionalChainTaggedTemplate.ts] //// //// [repro.ts] -e?.``( +e?.``( + //// [siblings.ts] declare var a: any; a?.b`c`; diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.symbols b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).symbols similarity index 74% rename from tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.symbols rename to tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).symbols index ba91da4497847..f1def366b522e 100644 --- a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.symbols +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).symbols @@ -1,8 +1,9 @@ -//// [tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts] //// +//// [tests/cases/compiler/optionalChainTaggedTemplate.ts] //// === repro.ts === e?.``( + === siblings.ts === declare var a: any; >a : Symbol(a, Decl(siblings.ts, 0, 11)) diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.types b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).types similarity index 76% rename from tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.types rename to tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).types index cd90b8ce9dd88..ebe370e79967b 100644 --- a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplateNoCrash.types +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2017).types @@ -1,4 +1,4 @@ -//// [tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts] //// +//// [tests/cases/compiler/optionalChainTaggedTemplate.ts] //// === repro.ts === e?.``( diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).errors.txt b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).errors.txt new file mode 100644 index 0000000000000..703ee75bc68d7 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).errors.txt @@ -0,0 +1,26 @@ +repro.ts(1,1): error TS2304: Cannot find name 'e'. +repro.ts(2,1): error TS1005: ')' expected. +siblings.ts(2,5): error TS1358: Tagged template expressions are not permitted in an optional chain. +siblings.ts(3,5): error TS1358: Tagged template expressions are not permitted in an optional chain. +siblings.ts(4,4): error TS1358: Tagged template expressions are not permitted in an optional chain. + + +==== repro.ts (2 errors) ==== + e?.``( + ~ +!!! error TS2304: Cannot find name 'e'. + + +!!! error TS1005: ')' expected. +==== siblings.ts (3 errors) ==== + declare var a: any; + a?.b`c`; + ~~~ +!!! error TS1358: Tagged template expressions are not permitted in an optional chain. + a?.b`c`(); + ~~~ +!!! error TS1358: Tagged template expressions are not permitted in an optional chain. + a?.``.x; + ~~ +!!! error TS1358: Tagged template expressions are not permitted in an optional chain. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).js b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).js new file mode 100644 index 0000000000000..d84b438b9c953 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).js @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/optionalChainTaggedTemplate.ts] //// + +//// [repro.ts] +e?.``( + +//// [siblings.ts] +declare var a: any; +a?.b`c`; +a?.b`c`(); +a?.``.x; + + +//// [repro.js] +"use strict"; +e ``(); +//// [siblings.js] +"use strict"; +(a?.b) `c`; +(a?.b) `c`(); +a ``.x; diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).symbols b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).symbols new file mode 100644 index 0000000000000..f1def366b522e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).symbols @@ -0,0 +1,19 @@ +//// [tests/cases/compiler/optionalChainTaggedTemplate.ts] //// + +=== repro.ts === + +e?.``( + +=== siblings.ts === +declare var a: any; +>a : Symbol(a, Decl(siblings.ts, 0, 11)) + +a?.b`c`; +>a : Symbol(a, Decl(siblings.ts, 0, 11)) + +a?.b`c`(); +>a : Symbol(a, Decl(siblings.ts, 0, 11)) + +a?.``.x; +>a : Symbol(a, Decl(siblings.ts, 0, 11)) + diff --git a/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).types b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).types new file mode 100644 index 0000000000000..ebe370e79967b --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/optionalChainTaggedTemplate(target=es2024).types @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/optionalChainTaggedTemplate.ts] //// + +=== repro.ts === +e?.``( +>e?.``( : any +>e?.`` : any +>e : any +>`` : "" + +=== siblings.ts === +declare var a: any; +>a : any + +a?.b`c`; +>a?.b`c` : any +>a?.b : any +>a : any +>b : any +>`c` : "c" + +a?.b`c`(); +>a?.b`c`() : any +>a?.b`c` : any +>a?.b : any +>a : any +>b : any +>`c` : "c" + +a?.``.x; +>a?.``.x : any +>a?.`` : any +>a : any +>`` : "" +>x : any + diff --git a/tsc/testdata/tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts b/tsc/testdata/tests/cases/compiler/optionalChainTaggedTemplate.ts similarity index 78% rename from tsc/testdata/tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts rename to tsc/testdata/tests/cases/compiler/optionalChainTaggedTemplate.ts index 036f3c6af7dad..39609cdd1004c 100644 --- a/tsc/testdata/tests/cases/compiler/optionalChainTaggedTemplateNoCrash.ts +++ b/tsc/testdata/tests/cases/compiler/optionalChainTaggedTemplate.ts @@ -1,6 +1,8 @@ -// @target: es2017 +// @target: es2017, es2024 + // @filename: repro.ts e?.``( + // @filename: siblings.ts declare var a: any; a?.b`c`;