diff --git a/tsc/internal/binder/binder.go b/tsc/internal/binder/binder.go index a95bb19ecdff7..cce1fec94be0d 100644 --- a/tsc/internal/binder/binder.go +++ b/tsc/internal/binder/binder.go @@ -1064,6 +1064,12 @@ func (b *Binder) bindDeferredExpandoAssignment(node *ast.Node) { if symbol = getInitializerSymbol(symbol); symbol != nil { if ast.HasDynamicName(node) { b.bindAnonymousDeclaration(node, ast.SymbolFlagsProperty|ast.SymbolFlagsAssignment, ast.InternalSymbolNameComputed) + // Point the anonymous computed assignment at the expando host. For top-level + // scripts, bindAnonymousDeclaration would otherwise set parent to a nil + // SourceFile symbol, which breaks getLateBoundSymbol under --noCheck. + if declSym := node.Symbol(); declSym != nil { + declSym.SetParent(symbol) + } b.addLateBoundAssignmentDeclarationToSymbol(node, symbol) } else { // We declare expandos only when there are no non-expando declarations for that name. diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 10ddb3c0c88b4..1b4af5469fbc3 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -14687,7 +14687,10 @@ 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) { + // Function/namespace expandos are stored on exports (see AssignmentDeclaration), + // not members. BinaryExpression declarations never have a static modifier, so + // Assignment is the signal to resolve exports. + if core.Some(symbol.Declarations(), ast.HasStaticModifier) || symbol.Flags()&ast.SymbolFlagsAssignment != 0 { c.getExportsOfSymbol(parent) } else { c.getMembersOfSymbol(parent) 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/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentInitializer.js b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentInitializer.js new file mode 100644 index 0000000000000..d051f353d4b16 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentInitializer.js @@ -0,0 +1,16 @@ +//// [tests/cases/compiler/noCheckExpandoElementAccessAssignmentInitializer.ts] //// + +//// [noCheckExpandoElementAccessAssignmentInitializer.ts] +const k = "bar"; +const foo = () => {}; +foo[k] = 1; + + + + +//// [noCheckExpandoElementAccessAssignmentInitializer.d.ts] +declare const k = "bar"; +declare function foo(): void; +declare namespace foo { + var bar: number; +} diff --git a/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentInitializer.symbols b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentInitializer.symbols new file mode 100644 index 0000000000000..4fb0506542cee --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentInitializer.symbols @@ -0,0 +1,13 @@ +//// [tests/cases/compiler/noCheckExpandoElementAccessAssignmentInitializer.ts] //// + +=== noCheckExpandoElementAccessAssignmentInitializer.ts === +const k = "bar"; +>k : Symbol(k, Decl(noCheckExpandoElementAccessAssignmentInitializer.ts, 0, 5)) + +const foo = () => {}; +>foo : Symbol(foo, Decl(noCheckExpandoElementAccessAssignmentInitializer.ts, 1, 5)) + +foo[k] = 1; +>foo : Symbol(foo, Decl(noCheckExpandoElementAccessAssignmentInitializer.ts, 1, 5)) +>k : Symbol(k, Decl(noCheckExpandoElementAccessAssignmentInitializer.ts, 0, 5)) + diff --git a/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentInitializer.types b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentInitializer.types new file mode 100644 index 0000000000000..321d46d610070 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentInitializer.types @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/noCheckExpandoElementAccessAssignmentInitializer.ts] //// + +=== noCheckExpandoElementAccessAssignmentInitializer.ts === +const k = "bar"; +>k : "bar" +>"bar" : "bar" + +const foo = () => {}; +>foo : { (): void; bar: number; } +>() => {} : { (): void; bar: number; } + +foo[k] = 1; +>foo[k] = 1 : 1 +>foo[k] : number +>foo : { (): void; bar: number; } +>k : "bar" +>1 : 1 + diff --git a/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentModule.js b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentModule.js new file mode 100644 index 0000000000000..bcbd119670ea3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentModule.js @@ -0,0 +1,15 @@ +//// [tests/cases/compiler/noCheckExpandoElementAccessAssignmentModule.ts] //// + +//// [noCheckExpandoElementAccessAssignmentModule.ts] +const k = "bar"; +export function foo() {} +foo[k] = 1; + + + + +//// [noCheckExpandoElementAccessAssignmentModule.d.ts] +export declare function foo(): void; +export declare namespace foo { + var bar: number; +} diff --git a/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentModule.symbols b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentModule.symbols new file mode 100644 index 0000000000000..ef07eb5027a9b --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentModule.symbols @@ -0,0 +1,13 @@ +//// [tests/cases/compiler/noCheckExpandoElementAccessAssignmentModule.ts] //// + +=== noCheckExpandoElementAccessAssignmentModule.ts === +const k = "bar"; +>k : Symbol(k, Decl(noCheckExpandoElementAccessAssignmentModule.ts, 0, 5)) + +export function foo() {} +>foo : Symbol(foo, Decl(noCheckExpandoElementAccessAssignmentModule.ts, 0, 16)) + +foo[k] = 1; +>foo : Symbol(foo, Decl(noCheckExpandoElementAccessAssignmentModule.ts, 0, 16)) +>k : Symbol(k, Decl(noCheckExpandoElementAccessAssignmentModule.ts, 0, 5)) + diff --git a/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentModule.types b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentModule.types new file mode 100644 index 0000000000000..67100918b2ad5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noCheckExpandoElementAccessAssignmentModule.types @@ -0,0 +1,17 @@ +//// [tests/cases/compiler/noCheckExpandoElementAccessAssignmentModule.ts] //// + +=== noCheckExpandoElementAccessAssignmentModule.ts === +const k = "bar"; +>k : "bar" +>"bar" : "bar" + +export function foo() {} +>foo : { (): void; bar: number; } + +foo[k] = 1; +>foo[k] = 1 : 1 +>foo[k] : number +>foo : { (): void; bar: number; } +>k : "bar" +>1 : 1 + 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() {} diff --git a/tsc/testdata/tests/cases/compiler/noCheckExpandoElementAccessAssignmentInitializer.ts b/tsc/testdata/tests/cases/compiler/noCheckExpandoElementAccessAssignmentInitializer.ts new file mode 100644 index 0000000000000..4bbe3e7a074ed --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/noCheckExpandoElementAccessAssignmentInitializer.ts @@ -0,0 +1,8 @@ +// @target: es2015 +// @noCheck: true +// @emitDeclarationOnly: true +// @declaration: true + +const k = "bar"; +const foo = () => {}; +foo[k] = 1; diff --git a/tsc/testdata/tests/cases/compiler/noCheckExpandoElementAccessAssignmentModule.ts b/tsc/testdata/tests/cases/compiler/noCheckExpandoElementAccessAssignmentModule.ts new file mode 100644 index 0000000000000..a5a5c41aa4140 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/noCheckExpandoElementAccessAssignmentModule.ts @@ -0,0 +1,8 @@ +// @target: es2015 +// @noCheck: true +// @emitDeclarationOnly: true +// @declaration: true + +const k = "bar"; +export function foo() {} +foo[k] = 1;