From d2341e3f2977088d09a6f0063960a3d40d245cdf Mon Sep 17 00:00:00 2001 From: andre-fajar-n Date: Sun, 11 Oct 2026 08:44:36 +0700 Subject: [PATCH] fix: Fix nil pointer panic in getLateBoundSymbol under --noCheck --- tsc/internal/checker/checker.go | 12 ++++++++---- .../noCheckExpandoElementAccessAssignment.js | 16 ++++++++++++++++ ...oCheckExpandoElementAccessAssignment.symbols | 13 +++++++++++++ .../noCheckExpandoElementAccessAssignment.types | 17 +++++++++++++++++ .../noCheckExpandoElementAccessAssignment.ts | 8 ++++++++ 5 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.js create mode 100644 tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.types create mode 100644 tsc/testdata/tests/cases/compiler/noCheckExpandoElementAccessAssignment.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 345ea00ce1437..b9208e3fb4ebd 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -14691,10 +14691,14 @@ func (c *Checker) getLateBoundSymbol(symbol *ast.Symbol) *ast.Symbol { if links.lateSymbol == nil && core.Some(symbol.Declarations(), c.hasLateBindableName) { // force late binding of members/exports. This will set the late-bound symbol parent := c.getMergedSymbol(symbol.Parent()) - if core.Some(symbol.Declarations(), ast.HasStaticModifier) { - c.getExportsOfSymbol(parent) - } else { - c.getMembersOfSymbol(parent) + // Parent can be nil for top-level script expandos (SourceFile has no symbol) when + // checking was skipped (--noCheck) and late binding has not run yet. + if parent != nil { + if core.Some(symbol.Declarations(), ast.HasStaticModifier) { + c.getExportsOfSymbol(parent) + } else { + c.getMembersOfSymbol(parent) + } } } if links.lateSymbol == nil { diff --git a/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.js b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.js new file mode 100644 index 0000000000000..0b1f387e18dfc --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.js @@ -0,0 +1,16 @@ +//// [tests/cases/compiler/noCheckExpandoElementAccessAssignment.ts] //// + +//// [noCheckExpandoElementAccessAssignment.ts] +const k = "bar"; +foo[k] = 1; +function foo() {} + + + + +//// [noCheckExpandoElementAccessAssignment.d.ts] +declare const k = "bar"; +declare function foo(): void; +declare namespace foo { + var bar: number; +} diff --git a/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.symbols b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.symbols new file mode 100644 index 0000000000000..6915aec55afa0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.symbols @@ -0,0 +1,13 @@ +//// [tests/cases/compiler/noCheckExpandoElementAccessAssignment.ts] //// + +=== noCheckExpandoElementAccessAssignment.ts === +const k = "bar"; +>k : Symbol(k, Decl(noCheckExpandoElementAccessAssignment.ts, 0, 5)) + +foo[k] = 1; +>foo : Symbol(foo, Decl(noCheckExpandoElementAccessAssignment.ts, 1, 11)) +>k : Symbol(k, Decl(noCheckExpandoElementAccessAssignment.ts, 0, 5)) + +function foo() {} +>foo : Symbol(foo, Decl(noCheckExpandoElementAccessAssignment.ts, 1, 11)) + diff --git a/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.types b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.types new file mode 100644 index 0000000000000..4dbd3c67ddde0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignment.types @@ -0,0 +1,17 @@ +//// [tests/cases/compiler/noCheckExpandoElementAccessAssignment.ts] //// + +=== noCheckExpandoElementAccessAssignment.ts === +const k = "bar"; +>k : "bar" +>"bar" : "bar" + +foo[k] = 1; +>foo[k] = 1 : 1 +>foo[k] : number +>foo : { (): void; bar: number; } +>k : "bar" +>1 : 1 + +function foo() {} +>foo : { (): void; bar: number; } + diff --git a/tsc/testdata/tests/cases/compiler/noCheckExpandoElementAccessAssignment.ts b/tsc/testdata/tests/cases/compiler/noCheckExpandoElementAccessAssignment.ts new file mode 100644 index 0000000000000..5cf68fc81395b --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/noCheckExpandoElementAccessAssignment.ts @@ -0,0 +1,8 @@ +// @target: es2015 +// @noCheck: true +// @emitDeclarationOnly: true +// @declaration: true + +const k = "bar"; +foo[k] = 1; +function foo() {}