diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3a420ad441c..71c90ce7fc9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6714,11 +6714,12 @@ namespace ts { const links = getSymbolLinks(symbol); if (!links.lateSymbol && some(symbol.declarations, hasLateBindableName)) { // force late binding of members/exports. This will set the late-bound symbol + const parent = getMergedSymbol(symbol.parent)!; if (some(symbol.declarations, hasStaticModifier)) { - getExportsOfSymbol(symbol.parent!); + getExportsOfSymbol(parent); } else { - getMembersOfSymbol(symbol.parent!); + getMembersOfSymbol(parent); } } return links.lateSymbol || (links.lateSymbol = symbol); diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index d3ea4744e8a..6b73324e2f7 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -92,6 +92,7 @@ "unittests/tsbuild/amdModulesWithOut.ts", "unittests/tsbuild/emptyFiles.ts", "unittests/tsbuild/graphOrdering.ts", + "unittests/tsbuild/lateBoundSymbol.ts", "unittests/tsbuild/missingExtendedFile.ts", "unittests/tsbuild/outFile.ts", "unittests/tsbuild/referencesWithRootDirInParent.ts", diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 538ebd75cdb..589ba7a3021 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -285,8 +285,10 @@ Mismatch Actual(path, actual, expected): ${JSON.stringify(arrayFrom(mapDefinedIt let newFs: vfs.FileSystem; let actualReadFileMap: Map; let host: fakes.SolutionBuilderHost; + let beforeBuildTime: number; + let afterBuildTime: number; before(() => { - assert.equal(fs.statSync(lastProjectOutputJs).mtimeMs, firstBuildTime, "First build timestamp is correct"); + beforeBuildTime = fs.statSync(lastProjectOutputJs).mtimeMs; tick(); newFs = fs.shadow(); tick(); @@ -298,13 +300,17 @@ Mismatch Actual(path, actual, expected): ${JSON.stringify(arrayFrom(mapDefinedIt expectedBuildInfoFilesForSectionBaselines, modifyFs: incrementalModifyFs, })); - assert.equal(newFs.statSync(lastProjectOutputJs).mtimeMs, time(), "Second build timestamp is correct"); + afterBuildTime = newFs.statSync(lastProjectOutputJs).mtimeMs; }); after(() => { newFs = undefined!; actualReadFileMap = undefined!; host = undefined!; }); + it("verify build output times", () => { + assert.equal(beforeBuildTime, firstBuildTime, "First build timestamp is correct"); + assert.equal(afterBuildTime, time(), "Second build timestamp is correct"); + }); if (!baselineOnly || verifyDiagnostics) { it(`verify diagnostics`, () => { host.assertDiagnosticMessages(...(incrementalExpectedDiagnostics || emptyArray)); diff --git a/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts b/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts new file mode 100644 index 00000000000..ff9f1fc7c7a --- /dev/null +++ b/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts @@ -0,0 +1,47 @@ +namespace ts { + describe("unittests:: tsbuild:: lateBoundSymbol:: interface is merged and contains late bound member", () => { + let projFs: vfs.FileSystem; + const { time, tick } = getTime(); + before(() => { + projFs = loadProjectFromDisk("tests/projects/lateBoundSymbol", time); + }); + after(() => { + projFs = undefined!; // Release the contents + }); + + verifyTsbuildOutput({ + scenario: "interface is merged and contains late bound member", + projFs: () => projFs, + time, + tick, + proj: "lateBoundSymbol", + rootNames: ["/src/tsconfig.json"], + expectedMapFileNames: emptyArray, + lastProjectOutputJs: "/src/src/main.js", + outputFiles: [ + "/src/src/hkt.js", + "/src/src/main.js", + "/src/tsconfig.tsbuildinfo", + ], + initialBuild: { + modifyFs: noop, + expectedDiagnostics: [ + getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tsconfig.json", "src/src/hkt.js"], + [Diagnostics.Building_project_0, "/src/tsconfig.json"] + ] + }, + incrementalDtsUnchangedBuild: { + modifyFs: fs => replaceText(fs, "/src/src/main.ts", "const x = 10;", ""), + expectedDiagnostics: [ + getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tsconfig.json", "src/src/hkt.js", "src/src/main.ts"], + [Diagnostics.Building_project_0, "/src/tsconfig.json"], + [Diagnostics.Updating_unchanged_output_timestamps_of_project_0, "/src/tsconfig.json"] + ] + }, + baselineOnly: true, + verifyDiagnostics: true + }); + }); +} diff --git a/tests/baselines/reference/tsbuild/lateBoundSymbol/incremental-declaration-doesnt-change/interface-is-merged-and-contains-late-bound-member.js b/tests/baselines/reference/tsbuild/lateBoundSymbol/incremental-declaration-doesnt-change/interface-is-merged-and-contains-late-bound-member.js new file mode 100644 index 00000000000..89748694bbc --- /dev/null +++ b/tests/baselines/reference/tsbuild/lateBoundSymbol/incremental-declaration-doesnt-change/interface-is-merged-and-contains-late-bound-member.js @@ -0,0 +1,113 @@ +//// [/src/src/main.js] +"use strict"; +exports.__esModule = true; +var sym = Symbol(); + + +//// [/src/src/main.ts] +import { HKT } from "./hkt"; + +const sym = Symbol(); + +declare module "./hkt" { + interface HKT { + [sym]: { a: T } + } +} + +type A = HKT[typeof sym]; + +//// [/src/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "/lib/lib.es5.d.ts": { + "version": "/lib/lib.es5.d.ts", + "signature": "/lib/lib.es5.d.ts" + }, + "/lib/lib.es2015.d.ts": { + "version": "/lib/lib.es2015.d.ts", + "signature": "/lib/lib.es2015.d.ts" + }, + "/lib/lib.es2015.core.d.ts": { + "version": "/lib/lib.es2015.core.d.ts", + "signature": "/lib/lib.es2015.core.d.ts" + }, + "/lib/lib.es2015.collection.d.ts": { + "version": "/lib/lib.es2015.collection.d.ts", + "signature": "/lib/lib.es2015.collection.d.ts" + }, + "/lib/lib.es2015.generator.d.ts": { + "version": "/lib/lib.es2015.generator.d.ts", + "signature": "/lib/lib.es2015.generator.d.ts" + }, + "/lib/lib.es2015.iterable.d.ts": { + "version": "/lib/lib.es2015.iterable.d.ts", + "signature": "/lib/lib.es2015.iterable.d.ts" + }, + "/lib/lib.es2015.promise.d.ts": { + "version": "/lib/lib.es2015.promise.d.ts", + "signature": "/lib/lib.es2015.promise.d.ts" + }, + "/lib/lib.es2015.proxy.d.ts": { + "version": "/lib/lib.es2015.proxy.d.ts", + "signature": "/lib/lib.es2015.proxy.d.ts" + }, + "/lib/lib.es2015.reflect.d.ts": { + "version": "/lib/lib.es2015.reflect.d.ts", + "signature": "/lib/lib.es2015.reflect.d.ts" + }, + "/lib/lib.es2015.symbol.d.ts": { + "version": "/lib/lib.es2015.symbol.d.ts", + "signature": "/lib/lib.es2015.symbol.d.ts" + }, + "/lib/lib.es2015.symbol.wellknown.d.ts": { + "version": "/lib/lib.es2015.symbol.wellknown.d.ts", + "signature": "/lib/lib.es2015.symbol.wellknown.d.ts" + }, + "/src/src/hkt.ts": { + "version": "675797797", + "signature": "2373810515" + }, + "/src/src/main.ts": { + "version": "-27494779858", + "signature": "-7779857705" + } + }, + "options": { + "rootDir": "/src/src", + "lib": [ + "lib.es2015.d.ts" + ], + "incremental": true, + "configFilePath": "/src/tsconfig.json" + }, + "referencedMap": { + "/src/src/main.ts": [ + "/src/src/hkt.ts" + ] + }, + "exportedModulesMap": { + "/src/src/main.ts": [ + "/src/src/hkt.ts" + ] + }, + "semanticDiagnosticsPerFile": [ + "/lib/lib.es2015.collection.d.ts", + "/lib/lib.es2015.core.d.ts", + "/lib/lib.es2015.d.ts", + "/lib/lib.es2015.generator.d.ts", + "/lib/lib.es2015.iterable.d.ts", + "/lib/lib.es2015.promise.d.ts", + "/lib/lib.es2015.proxy.d.ts", + "/lib/lib.es2015.reflect.d.ts", + "/lib/lib.es2015.symbol.d.ts", + "/lib/lib.es2015.symbol.wellknown.d.ts", + "/lib/lib.es5.d.ts", + "/src/src/hkt.ts", + "/src/src/main.ts" + ] + }, + "version": "FakeTSVersion" +} + diff --git a/tests/baselines/reference/tsbuild/lateBoundSymbol/initial-Build/interface-is-merged-and-contains-late-bound-member.js b/tests/baselines/reference/tsbuild/lateBoundSymbol/initial-Build/interface-is-merged-and-contains-late-bound-member.js new file mode 100644 index 00000000000..e30b62fcca8 --- /dev/null +++ b/tests/baselines/reference/tsbuild/lateBoundSymbol/initial-Build/interface-is-merged-and-contains-late-bound-member.js @@ -0,0 +1,106 @@ +//// [/src/src/hkt.js] +"use strict"; +exports.__esModule = true; + + +//// [/src/src/main.js] +"use strict"; +exports.__esModule = true; +var sym = Symbol(); +var x = 10; + + +//// [/src/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "/lib/lib.es5.d.ts": { + "version": "/lib/lib.es5.d.ts", + "signature": "/lib/lib.es5.d.ts" + }, + "/lib/lib.es2015.d.ts": { + "version": "/lib/lib.es2015.d.ts", + "signature": "/lib/lib.es2015.d.ts" + }, + "/lib/lib.es2015.core.d.ts": { + "version": "/lib/lib.es2015.core.d.ts", + "signature": "/lib/lib.es2015.core.d.ts" + }, + "/lib/lib.es2015.collection.d.ts": { + "version": "/lib/lib.es2015.collection.d.ts", + "signature": "/lib/lib.es2015.collection.d.ts" + }, + "/lib/lib.es2015.generator.d.ts": { + "version": "/lib/lib.es2015.generator.d.ts", + "signature": "/lib/lib.es2015.generator.d.ts" + }, + "/lib/lib.es2015.iterable.d.ts": { + "version": "/lib/lib.es2015.iterable.d.ts", + "signature": "/lib/lib.es2015.iterable.d.ts" + }, + "/lib/lib.es2015.promise.d.ts": { + "version": "/lib/lib.es2015.promise.d.ts", + "signature": "/lib/lib.es2015.promise.d.ts" + }, + "/lib/lib.es2015.proxy.d.ts": { + "version": "/lib/lib.es2015.proxy.d.ts", + "signature": "/lib/lib.es2015.proxy.d.ts" + }, + "/lib/lib.es2015.reflect.d.ts": { + "version": "/lib/lib.es2015.reflect.d.ts", + "signature": "/lib/lib.es2015.reflect.d.ts" + }, + "/lib/lib.es2015.symbol.d.ts": { + "version": "/lib/lib.es2015.symbol.d.ts", + "signature": "/lib/lib.es2015.symbol.d.ts" + }, + "/lib/lib.es2015.symbol.wellknown.d.ts": { + "version": "/lib/lib.es2015.symbol.wellknown.d.ts", + "signature": "/lib/lib.es2015.symbol.wellknown.d.ts" + }, + "/src/src/hkt.ts": { + "version": "675797797", + "signature": "2373810515" + }, + "/src/src/main.ts": { + "version": "-28387946490", + "signature": "-7779857705" + } + }, + "options": { + "rootDir": "/src/src", + "lib": [ + "lib.es2015.d.ts" + ], + "incremental": true, + "configFilePath": "/src/tsconfig.json" + }, + "referencedMap": { + "/src/src/main.ts": [ + "/src/src/hkt.ts" + ] + }, + "exportedModulesMap": { + "/src/src/main.ts": [ + "/src/src/hkt.ts" + ] + }, + "semanticDiagnosticsPerFile": [ + "/lib/lib.es2015.collection.d.ts", + "/lib/lib.es2015.core.d.ts", + "/lib/lib.es2015.d.ts", + "/lib/lib.es2015.generator.d.ts", + "/lib/lib.es2015.iterable.d.ts", + "/lib/lib.es2015.promise.d.ts", + "/lib/lib.es2015.proxy.d.ts", + "/lib/lib.es2015.reflect.d.ts", + "/lib/lib.es2015.symbol.d.ts", + "/lib/lib.es2015.symbol.wellknown.d.ts", + "/lib/lib.es5.d.ts", + "/src/src/hkt.ts", + "/src/src/main.ts" + ] + }, + "version": "FakeTSVersion" +} + diff --git a/tests/baselines/reference/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.js b/tests/baselines/reference/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.js new file mode 100644 index 00000000000..bfb6f0a29c7 --- /dev/null +++ b/tests/baselines/reference/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.js @@ -0,0 +1,18 @@ +//// [uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts] +const FOO_SYMBOL = Symbol('Foo'); + +declare global { + interface Promise { + [FOO_SYMBOL]?: number; + } +} + +export function foo(p: Promise) { + p[FOO_SYMBOL] = 3; +} + +//// [uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.js] +const FOO_SYMBOL = Symbol('Foo'); +export function foo(p) { + p[FOO_SYMBOL] = 3; +} diff --git a/tests/baselines/reference/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.symbols b/tests/baselines/reference/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.symbols new file mode 100644 index 00000000000..17af30e8fac --- /dev/null +++ b/tests/baselines/reference/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.symbols @@ -0,0 +1,29 @@ +=== tests/cases/compiler/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts === +const FOO_SYMBOL = Symbol('Foo'); +>FOO_SYMBOL : Symbol(FOO_SYMBOL, Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 0, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +declare global { +>global : Symbol(global, Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 0, 33)) + + interface Promise { +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 2, 16)) +>T : Symbol(T, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 3, 22)) + + [FOO_SYMBOL]?: number; +>[FOO_SYMBOL] : Symbol(Promise[FOO_SYMBOL], Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 3, 26)) +>FOO_SYMBOL : Symbol(FOO_SYMBOL, Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 0, 5)) + } +} + +export function foo(p: Promise) { +>foo : Symbol(foo, Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 6, 1)) +>T : Symbol(T, Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 8, 20)) +>p : Symbol(p, Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 8, 23)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 2, 16)) +>T : Symbol(T, Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 8, 20)) + + p[FOO_SYMBOL] = 3; +>p : Symbol(p, Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 8, 23)) +>FOO_SYMBOL : Symbol(FOO_SYMBOL, Decl(uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts, 0, 5)) +} diff --git a/tests/baselines/reference/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.types b/tests/baselines/reference/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.types new file mode 100644 index 00000000000..62dcc0607ae --- /dev/null +++ b/tests/baselines/reference/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.types @@ -0,0 +1,28 @@ +=== tests/cases/compiler/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts === +const FOO_SYMBOL = Symbol('Foo'); +>FOO_SYMBOL : unique symbol +>Symbol('Foo') : unique symbol +>Symbol : SymbolConstructor +>'Foo' : "Foo" + +declare global { +>global : any + + interface Promise { + [FOO_SYMBOL]?: number; +>[FOO_SYMBOL] : number | undefined +>FOO_SYMBOL : unique symbol + } +} + +export function foo(p: Promise) { +>foo : (p: Promise) => void +>p : Promise + + p[FOO_SYMBOL] = 3; +>p[FOO_SYMBOL] = 3 : 3 +>p[FOO_SYMBOL] : number | undefined +>p : Promise +>FOO_SYMBOL : unique symbol +>3 : 3 +} diff --git a/tests/cases/compiler/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts b/tests/cases/compiler/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts new file mode 100644 index 00000000000..f79c2a36e86 --- /dev/null +++ b/tests/cases/compiler/uniqueSymbolAssignmentOnGlobalAugmentationSuceeds.ts @@ -0,0 +1,13 @@ +// @strict: true +// @target: es6 +const FOO_SYMBOL = Symbol('Foo'); + +declare global { + interface Promise { + [FOO_SYMBOL]?: number; + } +} + +export function foo(p: Promise) { + p[FOO_SYMBOL] = 3; +} \ No newline at end of file diff --git a/tests/projects/lateBoundSymbol/src/hkt.ts b/tests/projects/lateBoundSymbol/src/hkt.ts new file mode 100644 index 00000000000..2b844a9ab79 --- /dev/null +++ b/tests/projects/lateBoundSymbol/src/hkt.ts @@ -0,0 +1 @@ +export interface HKT { } \ No newline at end of file diff --git a/tests/projects/lateBoundSymbol/src/main.ts b/tests/projects/lateBoundSymbol/src/main.ts new file mode 100644 index 00000000000..e5b5fcc6daa --- /dev/null +++ b/tests/projects/lateBoundSymbol/src/main.ts @@ -0,0 +1,11 @@ +import { HKT } from "./hkt"; + +const sym = Symbol(); + +declare module "./hkt" { + interface HKT { + [sym]: { a: T } + } +} +const x = 10; +type A = HKT[typeof sym]; \ No newline at end of file diff --git a/tests/projects/lateBoundSymbol/tsconfig.json b/tests/projects/lateBoundSymbol/tsconfig.json new file mode 100644 index 00000000000..78bf9e23f00 --- /dev/null +++ b/tests/projects/lateBoundSymbol/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "rootDir": "src", + "lib": [ + "es2015" + ], + "incremental": true + } +} \ No newline at end of file