From 848e5df613dbb41f4dcf1a49851e983b7a5f5309 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 27 May 2021 14:02:31 -0700 Subject: [PATCH 1/6] Make export-module and reference maps invertible Right now, we're enumerating all the entries to find out which keys map to a corresponding value. By maintaining a two-way map, we can convert this linear search into a map lookup and skip allocation of many, many iterator results. --- src/compiler/builder.ts | 80 ++++++++++++++--------- src/compiler/builderState.ts | 119 ++++++++++++++++++++++++++--------- src/compiler/utilities.ts | 22 +++++++ 3 files changed, 161 insertions(+), 60 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index c7911bbd61a3b..58c726db8707d 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -212,7 +212,7 @@ namespace ts { const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck; state.fileInfos.forEach((info, sourceFilePath) => { let oldInfo: Readonly | undefined; - let newReferences: BuilderState.ReferencedSet | undefined; + let newReferences: ReadonlySet | undefined; // if not using old state, every file is changed if (!useOldState || @@ -311,7 +311,7 @@ namespace ts { newState.affectedFilesIndex = state.affectedFilesIndex; newState.currentChangedFilePath = state.currentChangedFilePath; newState.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures && new Map(state.currentAffectedFilesSignatures); - newState.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap && new Map(state.currentAffectedFilesExportedModulesMap); + newState.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap && { exporting: state.currentAffectedFilesExportedModulesMap.exporting.clone(), nonExporting: new Set(state.currentAffectedFilesExportedModulesMap.nonExporting) }; newState.seenAffectedFiles = state.seenAffectedFiles && new Set(state.seenAffectedFiles); newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles; newState.semanticDiagnosticsFromOldState = state.semanticDiagnosticsFromOldState && new Set(state.semanticDiagnosticsFromOldState); @@ -384,7 +384,12 @@ namespace ts { // Get next batch of affected files if (!state.currentAffectedFilesSignatures) state.currentAffectedFilesSignatures = new Map(); if (state.exportedModulesMap) { - if (!state.currentAffectedFilesExportedModulesMap) state.currentAffectedFilesExportedModulesMap = new Map(); + if (!state.currentAffectedFilesExportedModulesMap) { + state.currentAffectedFilesExportedModulesMap = { + exporting: BuilderState.createTwoWayMap(), + nonExporting: new Set(), + }; + } } state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, nextKey.value, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap); state.currentChangedFilePath = nextKey.value; @@ -465,7 +470,7 @@ namespace ts { * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled, * Also we need to make sure signature is updated for these files */ - function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { + function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): void { removeSemanticDiagnosticsOf(state, path); if (!state.changedFilesSet.has(path)) { @@ -544,19 +549,19 @@ namespace ts { } Debug.assert(!!state.currentAffectedFilesExportedModulesMap); + const seenFileAndExportsOfFile = new Set(); // Go through exported modules from cache first // If exported modules has path, all files referencing file exported from are affected - forEachEntry(state.currentAffectedFilesExportedModulesMap, (exportedModules, exportedFromPath) => - exportedModules && - exportedModules.has(affectedFile.resolvedPath) && + state.currentAffectedFilesExportedModulesMap.exporting.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) ); // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected - forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) => - !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it - exportedModules.has(affectedFile.resolvedPath) && + state.exportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => + // If the cache had an updated value, skip + !state.currentAffectedFilesExportedModulesMap!.exporting.has(exportedFromPath) && + !state.currentAffectedFilesExportedModulesMap!.nonExporting.has(exportedFromPath) && forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) ); } @@ -564,16 +569,16 @@ namespace ts { /** * Iterate on files referencing referencedPath */ - function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Set, fn: (state: BuilderProgramState, filePath: Path) => void) { - forEachEntry(state.referencedMap!, (referencesInFile, filePath) => - referencesInFile.has(referencedPath) && forEachFileAndExportsOfFile(state, filePath, seenFileAndExportsOfFile, fn) + function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Set, fn: (state: BuilderProgramState, filePath: Path) => void): void { + state.referencedMap!.getKeys(referencedPath)?.forEach(filePath => + forEachFileAndExportsOfFile(state, filePath, seenFileAndExportsOfFile, fn) ); } /** * fn on file and iterate on anything that exports this file */ - function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Set, fn: (state: BuilderProgramState, filePath: Path) => void) { + function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Set, fn: (state: BuilderProgramState, filePath: Path) => void): void { if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) { return; } @@ -583,23 +588,20 @@ namespace ts { Debug.assert(!!state.currentAffectedFilesExportedModulesMap); // Go through exported modules from cache first // If exported modules has path, all files referencing file exported from are affected - forEachEntry(state.currentAffectedFilesExportedModulesMap, (exportedModules, exportedFromPath) => - exportedModules && - exportedModules.has(filePath) && + state.currentAffectedFilesExportedModulesMap.exporting.getKeys(filePath)?.forEach(exportedFromPath => forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) ); // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected - forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) => - !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it - exportedModules.has(filePath) && + state.exportedModulesMap!.getKeys(filePath)?.forEach(exportedFromPath => + // If the cache had an updated value, skip + !state.currentAffectedFilesExportedModulesMap!.exporting.has(exportedFromPath) && + !state.currentAffectedFilesExportedModulesMap!.nonExporting.has(exportedFromPath) && forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) ); // Remove diagnostics of files that import this file (without going to exports of referencing files) - - forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) => - referencesInFile.has(filePath) && + state.referencedMap!.getKeys(filePath)?.forEach(referencingFilePath => !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file fn(state, referencingFilePath) // Dont add to seen since this is not yet done with the export removal ); @@ -763,11 +765,19 @@ namespace ts { let exportedModulesMap: ProgramBuildInfoReferencedMap | undefined; if (state.exportedModulesMap) { exportedModulesMap = mapDefined(arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive), key => { - const newValue = state.currentAffectedFilesExportedModulesMap && state.currentAffectedFilesExportedModulesMap.get(key); + if (state.currentAffectedFilesExportedModulesMap) { + if (state.currentAffectedFilesExportedModulesMap.nonExporting.has(key)) { + return undefined; + } + + const newValue = state.currentAffectedFilesExportedModulesMap.exporting.get(key); + if (newValue) { + return [toFileId(key), toFileIdListId(newValue)]; + } + } + // Not in temporary cache, use existing value - if (newValue === undefined) return [toFileId(key), toFileIdListId(state.exportedModulesMap!.get(key)!)]; - // Value in cache and has updated value map, use that - else if (newValue) return [toFileId(key), toFileIdListId(newValue)]; + return [toFileId(key), toFileIdListId(state.exportedModulesMap!.get(key)!)]; }); } @@ -1251,8 +1261,8 @@ namespace ts { const state: ReusableBuilderProgramState = { fileInfos, compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {}, - referencedMap: toMapOfReferencedSet(program.referencedMap), - exportedModulesMap: toMapOfReferencedSet(program.exportedModulesMap), + referencedMap: toTwoWayMap(program.referencedMap), + exportedModulesMap: toTwoWayMap(program.exportedModulesMap), semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]), hasReusableDiagnostic: true, affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])), @@ -1300,8 +1310,16 @@ namespace ts { return filePathsSetList![fileIdsListId - 1]; } - function toMapOfReferencedSet(referenceMap: ProgramBuildInfoReferencedMap | undefined): ReadonlyESMap | undefined { - return referenceMap && arrayToMap(referenceMap, value => toFilePath(value[0]), value => toFilePathsSet(value[1])); + function toTwoWayMap(referenceMap: ProgramBuildInfoReferencedMap | undefined): BuilderState.TwoWayMap | undefined { + if (!referenceMap) { + return undefined; + } + + const map = BuilderState.createTwoWayMap(); + referenceMap.forEach(([fileId, fileIdListId]) => + map.set(toFilePath(fileId), toFilePathsSet(fileIdListId)) + ); + return map; } } diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index c150098fff9fb..bfe5c50add24b 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -21,12 +21,12 @@ namespace ts { * Otherwise undefined * Thus non undefined value indicates, module emit */ - readonly referencedMap?: ReadonlyESMap | undefined; + readonly referencedMap?: BuilderState.TwoWayMap | undefined; /** * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled * Otherwise undefined */ - readonly exportedModulesMap?: ReadonlyESMap | undefined; + readonly exportedModulesMap?: BuilderState.TwoWayMap | undefined; } export interface BuilderState { @@ -39,12 +39,12 @@ namespace ts { * Otherwise undefined * Thus non undefined value indicates, module emit */ - readonly referencedMap: ReadonlyESMap | undefined; + readonly referencedMap: BuilderState.TwoWayMap | undefined; /** * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled * Otherwise undefined */ - readonly exportedModulesMap: ESMap | undefined; + readonly exportedModulesMap: BuilderState.TwoWayMap | undefined; /** * true if file version is used as signature @@ -76,10 +76,65 @@ namespace ts { signature: string | undefined; affectsGlobalScope: boolean | undefined; } - /** - * Referenced files with values for the keys as referenced file's path to be true - */ - export type ReferencedSet = ReadonlySet; + + export interface TwoWayMap extends ESMap> { + getKeys(v: V): ReadonlySet | undefined; + clone(): TwoWayMap; + } + + export function createTwoWayMap(): TwoWayMap { + function create(forward: ESMap>, reverse: ESMap>): TwoWayMap { + const map: TwoWayMap = { + clear: () => { + forward.clear(); + reverse.clear(); + }, + delete: k => { + const set = forward.get(k); + if (!set) { + return false; + } + + set.forEach(v => deleteFromMultimap(reverse, v, k)); + forward.delete(k); + return true; + }, + entries: () => forward.entries(), + forEach: fn => forward.forEach(fn), + get: k => forward.get(k), + has: k => forward.has(k), + keys: () => forward.keys(), + set: (k, vSet) => { + const existingVSet = forward.get(k); + forward.set(k, vSet); + + existingVSet?.forEach(v => { + if (!vSet.has(v)) { + deleteFromMultimap(reverse, v, k); + } + }); + + vSet.forEach(v => { + if (!existingVSet?.has(v)) { + addToMultimap(reverse, v, k); + } + }); + + return map; + }, + get size() { return forward.size; }, + values: () => forward.values(), + + getKeys: v => reverse.get(v), + clone: () => create(new Map(forward), new Map(reverse)), + }; + + return map; + } + + return create(new Map>(), new Map>()); + } + /** * Compute the hash to store the shape of the file */ @@ -87,9 +142,12 @@ namespace ts { /** * Exported modules to from declaration emit being computed. - * This can contain false in the affected file path to specify that there are no exported module(types from other modules) for this file + * Entries in `nonExporting` indicate that there are no exported module(types from other modules) for the file */ - export type ComputingExportedModulesMap = ESMap; + export interface ComputingExportedModulesMap { + exporting: TwoWayMap; + nonExporting: Set; + } /** * Get the referencedFile from the imported module symbol @@ -201,7 +259,7 @@ namespace ts { /** * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed */ - export function canReuseOldState(newReferencedMap: ReadonlyESMap | undefined, oldState: Readonly | undefined) { + export function canReuseOldState(newReferencedMap: BuilderState.TwoWayMap | undefined, oldState: Readonly | undefined) { return oldState && !oldState.referencedMap === !newReferencedMap; } @@ -210,8 +268,8 @@ namespace ts { */ export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly, disableUseFileVersionAsSignature?: boolean): BuilderState { const fileInfos = new Map(); - const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? new Map() : undefined; - const exportedModulesMap = referencedMap ? new Map() : undefined; + const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createTwoWayMap() : undefined; + const exportedModulesMap = referencedMap ? createTwoWayMap() : undefined; const hasCalledUpdateShapeSignature = new Set(); const useOldState = canReuseOldState(referencedMap, oldState); @@ -262,8 +320,8 @@ namespace ts { // Dont need to backup allFiles info since its cache anyway return { fileInfos: new Map(state.fileInfos), - referencedMap: state.referencedMap && new Map(state.referencedMap), - exportedModulesMap: state.exportedModulesMap && new Map(state.exportedModulesMap), + referencedMap: state.referencedMap?.clone(), + exportedModulesMap: state.exportedModulesMap?.clone(), hasCalledUpdateShapeSignature: new Set(state.hasCalledUpdateShapeSignature), useFileVersionAsSignature: state.useFileVersionAsSignature, }; @@ -349,7 +407,12 @@ namespace ts { if (exportedModulesMapCache && latestSignature !== prevSignature) { // All the references in this file are exported const references = state.referencedMap ? state.referencedMap.get(sourceFile.resolvedPath) : undefined; - exportedModulesMapCache.set(sourceFile.resolvedPath, references || false); + if (references) { + exportedModulesMapCache.exporting.set(sourceFile.resolvedPath, references); + } + else { + exportedModulesMapCache.nonExporting.add(sourceFile.resolvedPath); + } } } cacheToUpdateSignature.set(sourceFile.resolvedPath, latestSignature); @@ -361,13 +424,18 @@ namespace ts { */ function updateExportedModules(sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ComputingExportedModulesMap) { if (!exportedModulesFromDeclarationEmit) { - exportedModulesMapCache.set(sourceFile.resolvedPath, false); + exportedModulesMapCache.nonExporting.add(sourceFile.resolvedPath); return; } let exportedModules: Set | undefined; exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFileFromImportedModuleSymbol(symbol))); - exportedModulesMapCache.set(sourceFile.resolvedPath, exportedModules || false); + if (exportedModules) { + exportedModulesMapCache.exporting.set(sourceFile.resolvedPath, exportedModules); + } + else { + exportedModulesMapCache.nonExporting.add(sourceFile.resolvedPath); + } function addExportedModule(exportedModulePath: Path | undefined) { if (exportedModulePath) { @@ -386,14 +454,8 @@ namespace ts { export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { if (exportedModulesMapCache) { Debug.assert(!!state.exportedModulesMap); - exportedModulesMapCache.forEach((exportedModules, path) => { - if (exportedModules) { - state.exportedModulesMap!.set(path, exportedModules); - } - else { - state.exportedModulesMap!.delete(path); - } - }); + exportedModulesMapCache.nonExporting.forEach(path => state.exportedModulesMap!.delete(path)); + exportedModulesMapCache.exporting.forEach((exportedModules, path) => state.exportedModulesMap!.set(path, exportedModules)); } } @@ -447,9 +509,8 @@ namespace ts { * Gets the files referenced by the the file path */ export function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { - return arrayFrom(mapDefinedIterator(state.referencedMap!.entries(), ([filePath, referencesInFile]) => - referencesInFile.has(referencedFilePath) ? filePath : undefined - )); + const keys = state.referencedMap!.getKeys(referencedFilePath); + return keys ? arrayFrom(keys.keys()) : []; } /** diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 83ee8d9da3606..93cb778d68659 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7284,4 +7284,26 @@ namespace ts { return (parent as SourceFile).statements; } } + + export function addToMultimap(map: ESMap>, k: K, v: V): void { + let set = map.get(k); + if (!set) { + set = new Set(); + map.set(k, set); + } + set.add(v); + } + + export function deleteFromMultimap(map: ESMap>, k: K, v: V, removeEmpty = true): boolean { + const set = map.get(k); + + if (set?.delete(v)) { + if (removeEmpty && !set.size) { + map.delete(k); + } + return true; + } + + return false; + } } From 9e6c43bf0303d96acfe610fcd44cc613b97c106c Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 2 Jun 2021 19:20:17 -0700 Subject: [PATCH 2/6] Fix lint error --- src/compiler/builderState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index bfe5c50add24b..260b74d8d926e 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -259,7 +259,7 @@ namespace ts { /** * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed */ - export function canReuseOldState(newReferencedMap: BuilderState.TwoWayMap | undefined, oldState: Readonly | undefined) { + export function canReuseOldState(newReferencedMap: TwoWayMap | undefined, oldState: Readonly | undefined) { return oldState && !oldState.referencedMap === !newReferencedMap; } From c824406d80b692e2b1e0cfef3e105b77f62b8aab Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 3 Jun 2021 14:51:28 -0700 Subject: [PATCH 3/6] Add some explanatory comments --- src/compiler/builder.ts | 2 ++ src/compiler/builderState.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 58c726db8707d..3204e08e9acbd 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -113,6 +113,8 @@ namespace ts { currentAffectedFilesSignatures: ESMap | undefined; /** * Newly computed visible to outside referencedSet + * We need to store the updates separately in case the in-progress build is cancelled + * and we need to roll back. */ currentAffectedFilesExportedModulesMap: BuilderState.ComputingExportedModulesMap | undefined; /** diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 260b74d8d926e..5751bffa54e74 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -43,6 +43,8 @@ namespace ts { /** * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled * Otherwise undefined + * + * This is equivalent to referencedMap, but for the emitted .d.ts file. */ readonly exportedModulesMap: BuilderState.TwoWayMap | undefined; From c1a4bd76721e43de1d2e35b61429bcf3e700e3fc Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 16 Jun 2021 15:21:28 -0700 Subject: [PATCH 4/6] Rename, drop type parameters, and add readonly variant --- src/compiler/builder.ts | 10 +++---- src/compiler/builderState.ts | 57 ++++++++++++++++++++++++++---------- src/compiler/utilities.ts | 22 -------------- 3 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 3204e08e9acbd..f833d9b12d9f9 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -388,7 +388,7 @@ namespace ts { if (state.exportedModulesMap) { if (!state.currentAffectedFilesExportedModulesMap) { state.currentAffectedFilesExportedModulesMap = { - exporting: BuilderState.createTwoWayMap(), + exporting: BuilderState.createManyToManyPathMap(), nonExporting: new Set(), }; } @@ -1263,8 +1263,8 @@ namespace ts { const state: ReusableBuilderProgramState = { fileInfos, compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {}, - referencedMap: toTwoWayMap(program.referencedMap), - exportedModulesMap: toTwoWayMap(program.exportedModulesMap), + referencedMap: toManyToManyPathMap(program.referencedMap), + exportedModulesMap: toManyToManyPathMap(program.exportedModulesMap), semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]), hasReusableDiagnostic: true, affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])), @@ -1312,12 +1312,12 @@ namespace ts { return filePathsSetList![fileIdsListId - 1]; } - function toTwoWayMap(referenceMap: ProgramBuildInfoReferencedMap | undefined): BuilderState.TwoWayMap | undefined { + function toManyToManyPathMap(referenceMap: ProgramBuildInfoReferencedMap | undefined): BuilderState.ManyToManyPathMap | undefined { if (!referenceMap) { return undefined; } - const map = BuilderState.createTwoWayMap(); + const map = BuilderState.createManyToManyPathMap(); referenceMap.forEach(([fileId, fileIdListId]) => map.set(toFilePath(fileId), toFilePathsSet(fileIdListId)) ); diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 5751bffa54e74..8f1444a1ae987 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -21,12 +21,12 @@ namespace ts { * Otherwise undefined * Thus non undefined value indicates, module emit */ - readonly referencedMap?: BuilderState.TwoWayMap | undefined; + readonly referencedMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; /** * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled * Otherwise undefined */ - readonly exportedModulesMap?: BuilderState.TwoWayMap | undefined; + readonly exportedModulesMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; } export interface BuilderState { @@ -39,14 +39,14 @@ namespace ts { * Otherwise undefined * Thus non undefined value indicates, module emit */ - readonly referencedMap: BuilderState.TwoWayMap | undefined; + readonly referencedMap: BuilderState.ReadonlyManyToManyPathMap | undefined; /** * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled * Otherwise undefined * * This is equivalent to referencedMap, but for the emitted .d.ts file. */ - readonly exportedModulesMap: BuilderState.TwoWayMap | undefined; + readonly exportedModulesMap: BuilderState.ManyToManyPathMap | undefined; /** * true if file version is used as signature @@ -79,14 +79,19 @@ namespace ts { affectsGlobalScope: boolean | undefined; } - export interface TwoWayMap extends ESMap> { - getKeys(v: V): ReadonlySet | undefined; - clone(): TwoWayMap; + export interface ReadonlyManyToManyPathMap extends ReadonlyESMap> { + getKeys(v: Path): ReadonlySet | undefined; + clone(): ManyToManyPathMap; } - export function createTwoWayMap(): TwoWayMap { - function create(forward: ESMap>, reverse: ESMap>): TwoWayMap { - const map: TwoWayMap = { + export interface ManyToManyPathMap extends ESMap> { + getKeys(v: Path): ReadonlySet | undefined; + clone(): ManyToManyPathMap; + } + + export function createManyToManyPathMap(): ManyToManyPathMap { + function create(forward: ESMap>, reverse: ESMap>): ManyToManyPathMap { + const map: ManyToManyPathMap = { clear: () => { forward.clear(); reverse.clear(); @@ -134,7 +139,29 @@ namespace ts { return map; } - return create(new Map>(), new Map>()); + return create(new Map>(), new Map>()); + } + + function addToMultimap(map: ESMap>, k: K, v: V): void { + let set = map.get(k); + if (!set) { + set = new Set(); + map.set(k, set); + } + set.add(v); + } + + function deleteFromMultimap(map: ESMap>, k: K, v: V, removeEmpty = true): boolean { + const set = map.get(k); + + if (set?.delete(v)) { + if (removeEmpty && !set.size) { + map.delete(k); + } + return true; + } + + return false; } /** @@ -147,7 +174,7 @@ namespace ts { * Entries in `nonExporting` indicate that there are no exported module(types from other modules) for the file */ export interface ComputingExportedModulesMap { - exporting: TwoWayMap; + exporting: ManyToManyPathMap; nonExporting: Set; } @@ -261,7 +288,7 @@ namespace ts { /** * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed */ - export function canReuseOldState(newReferencedMap: TwoWayMap | undefined, oldState: Readonly | undefined) { + export function canReuseOldState(newReferencedMap: ReadonlyManyToManyPathMap | undefined, oldState: Readonly | undefined) { return oldState && !oldState.referencedMap === !newReferencedMap; } @@ -270,8 +297,8 @@ namespace ts { */ export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly, disableUseFileVersionAsSignature?: boolean): BuilderState { const fileInfos = new Map(); - const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createTwoWayMap() : undefined; - const exportedModulesMap = referencedMap ? createTwoWayMap() : undefined; + const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createManyToManyPathMap() : undefined; + const exportedModulesMap = referencedMap ? createManyToManyPathMap() : undefined; const hasCalledUpdateShapeSignature = new Set(); const useOldState = canReuseOldState(referencedMap, oldState); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 93cb778d68659..83ee8d9da3606 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7284,26 +7284,4 @@ namespace ts { return (parent as SourceFile).statements; } } - - export function addToMultimap(map: ESMap>, k: K, v: V): void { - let set = map.get(k); - if (!set) { - set = new Set(); - map.set(k, set); - } - set.add(v); - } - - export function deleteFromMultimap(map: ESMap>, k: K, v: V, removeEmpty = true): boolean { - const set = map.get(k); - - if (set?.delete(v)) { - if (removeEmpty && !set.size) { - map.delete(k); - } - return true; - } - - return false; - } } From de9deff0d814b20223f439617895058c428f800e Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 16 Jun 2021 15:43:03 -0700 Subject: [PATCH 5/6] Simplify member list --- src/compiler/builder.ts | 12 ++--- src/compiler/builderState.ts | 45 +++++++++---------- .../unittests/tscWatch/incremental.ts | 4 +- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index f833d9b12d9f9..76172544c56dd 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -223,7 +223,7 @@ namespace ts { // versions dont match oldInfo.version !== info.version || // Referenced files changed - !hasSameKeys(newReferences = referencedMap && referencedMap.get(sourceFilePath), oldReferencedMap && oldReferencedMap.get(sourceFilePath)) || + !hasSameKeys(newReferences = referencedMap && referencedMap.getValues(sourceFilePath), oldReferencedMap && oldReferencedMap.getValues(sourceFilePath)) || // Referenced file was deleted in the new program newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState!.fileInfos.has(path))) { // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated @@ -562,7 +562,7 @@ namespace ts { // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected state.exportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => // If the cache had an updated value, skip - !state.currentAffectedFilesExportedModulesMap!.exporting.has(exportedFromPath) && + !state.currentAffectedFilesExportedModulesMap!.exporting.hasKey(exportedFromPath) && !state.currentAffectedFilesExportedModulesMap!.nonExporting.has(exportedFromPath) && forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) ); @@ -597,7 +597,7 @@ namespace ts { // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected state.exportedModulesMap!.getKeys(filePath)?.forEach(exportedFromPath => // If the cache had an updated value, skip - !state.currentAffectedFilesExportedModulesMap!.exporting.has(exportedFromPath) && + !state.currentAffectedFilesExportedModulesMap!.exporting.hasKey(exportedFromPath) && !state.currentAffectedFilesExportedModulesMap!.nonExporting.has(exportedFromPath) && forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) ); @@ -760,7 +760,7 @@ namespace ts { if (state.referencedMap) { referencedMap = arrayFrom(state.referencedMap.keys()).sort(compareStringsCaseSensitive).map(key => [ toFileId(key), - toFileIdListId(state.referencedMap!.get(key)!) + toFileIdListId(state.referencedMap!.getValues(key)!) ]); } @@ -772,14 +772,14 @@ namespace ts { return undefined; } - const newValue = state.currentAffectedFilesExportedModulesMap.exporting.get(key); + const newValue = state.currentAffectedFilesExportedModulesMap.exporting.getValues(key); if (newValue) { return [toFileId(key), toFileIdListId(newValue)]; } } // Not in temporary cache, use existing value - return [toFileId(key), toFileIdListId(state.exportedModulesMap!.get(key)!)]; + return [toFileId(key), toFileIdListId(state.exportedModulesMap!.getValues(key)!)]; }); } diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 8f1444a1ae987..82e2ae6ec95c4 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -79,24 +79,31 @@ namespace ts { affectsGlobalScope: boolean | undefined; } - export interface ReadonlyManyToManyPathMap extends ReadonlyESMap> { - getKeys(v: Path): ReadonlySet | undefined; + export interface ReadonlyManyToManyPathMap { clone(): ManyToManyPathMap; + forEach(action: (v: ReadonlySet, k: Path) => void): void; + getKeys(v: Path): ReadonlySet | undefined; + getValues(k: Path): ReadonlySet | undefined; + hasKey(k: Path): boolean; + keys(): Iterator; } - export interface ManyToManyPathMap extends ESMap> { - getKeys(v: Path): ReadonlySet | undefined; - clone(): ManyToManyPathMap; + export interface ManyToManyPathMap extends ReadonlyManyToManyPathMap { + deleteKey(k: Path): boolean; + set(k: Path, v: ReadonlySet): void; } export function createManyToManyPathMap(): ManyToManyPathMap { function create(forward: ESMap>, reverse: ESMap>): ManyToManyPathMap { const map: ManyToManyPathMap = { - clear: () => { - forward.clear(); - reverse.clear(); - }, - delete: k => { + clone: () => create(new Map(forward), new Map(reverse)), + forEach: fn => forward.forEach(fn), + getKeys: v => reverse.get(v), + getValues: k => forward.get(k), + hasKey: k => forward.has(k), + keys: () => forward.keys(), + + deleteKey: k => { const set = forward.get(k); if (!set) { return false; @@ -106,11 +113,6 @@ namespace ts { forward.delete(k); return true; }, - entries: () => forward.entries(), - forEach: fn => forward.forEach(fn), - get: k => forward.get(k), - has: k => forward.has(k), - keys: () => forward.keys(), set: (k, vSet) => { const existingVSet = forward.get(k); forward.set(k, vSet); @@ -129,11 +131,6 @@ namespace ts { return map; }, - get size() { return forward.size; }, - values: () => forward.values(), - - getKeys: v => reverse.get(v), - clone: () => create(new Map(forward), new Map(reverse)), }; return map; @@ -316,7 +313,7 @@ namespace ts { } // Copy old visible to outside files map if (useOldState) { - const exportedModules = oldState!.exportedModulesMap!.get(sourceFile.resolvedPath); + const exportedModules = oldState!.exportedModulesMap!.getValues(sourceFile.resolvedPath); if (exportedModules) { exportedModulesMap!.set(sourceFile.resolvedPath, exportedModules); } @@ -435,7 +432,7 @@ namespace ts { latestSignature = sourceFile.version; if (exportedModulesMapCache && latestSignature !== prevSignature) { // All the references in this file are exported - const references = state.referencedMap ? state.referencedMap.get(sourceFile.resolvedPath) : undefined; + const references = state.referencedMap ? state.referencedMap.getValues(sourceFile.resolvedPath) : undefined; if (references) { exportedModulesMapCache.exporting.set(sourceFile.resolvedPath, references); } @@ -483,7 +480,7 @@ namespace ts { export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { if (exportedModulesMapCache) { Debug.assert(!!state.exportedModulesMap); - exportedModulesMapCache.nonExporting.forEach(path => state.exportedModulesMap!.delete(path)); + exportedModulesMapCache.nonExporting.forEach(path => state.exportedModulesMap!.deleteKey(path)); exportedModulesMapCache.exporting.forEach((exportedModules, path) => state.exportedModulesMap!.set(path, exportedModules)); } } @@ -510,7 +507,7 @@ namespace ts { const path = queue.pop()!; if (!seenMap.has(path)) { seenMap.add(path); - const references = state.referencedMap.get(path); + const references = state.referencedMap.getValues(path); if (references) { const iterator = references.keys(); for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { diff --git a/src/testRunner/unittests/tscWatch/incremental.ts b/src/testRunner/unittests/tscWatch/incremental.ts index aaa58d6791493..147363447fd92 100644 --- a/src/testRunner/unittests/tscWatch/incremental.ts +++ b/src/testRunner/unittests/tscWatch/incremental.ts @@ -181,8 +181,8 @@ namespace ts.tscWatch { configFilePath: config.path }); - assert.equal(state.referencedMap!.size, 0); - assert.equal(state.exportedModulesMap!.size, 0); + assert.equal(arrayFrom(state.referencedMap!.keys()).length, 0); + assert.equal(arrayFrom(state.exportedModulesMap!.keys()).length, 0); assert.equal(state.semanticDiagnosticsPerFile!.size, 3); assert.deepEqual(state.semanticDiagnosticsPerFile!.get(libFile.path as Path), emptyArray); From 70e0b14822265f924706c040f8651d0b3079d05f Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 16 Jun 2021 16:16:06 -0700 Subject: [PATCH 6/6] Fold non-exporting behavior into custom map type --- src/compiler/builder.ts | 29 +++++++++----------- src/compiler/builderState.ts | 51 +++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 76172544c56dd..2ae3128d54c4e 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -45,7 +45,7 @@ namespace ts { /** * Newly computed visible to outside referencedSet */ - currentAffectedFilesExportedModulesMap?: Readonly | undefined; + currentAffectedFilesExportedModulesMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; /** * True if the semantic diagnostics were copied from the old state */ @@ -116,7 +116,7 @@ namespace ts { * We need to store the updates separately in case the in-progress build is cancelled * and we need to roll back. */ - currentAffectedFilesExportedModulesMap: BuilderState.ComputingExportedModulesMap | undefined; + currentAffectedFilesExportedModulesMap: BuilderState.ManyToManyPathMap | undefined; /** * Already seen affected files */ @@ -313,7 +313,7 @@ namespace ts { newState.affectedFilesIndex = state.affectedFilesIndex; newState.currentChangedFilePath = state.currentChangedFilePath; newState.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures && new Map(state.currentAffectedFilesSignatures); - newState.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap && { exporting: state.currentAffectedFilesExportedModulesMap.exporting.clone(), nonExporting: new Set(state.currentAffectedFilesExportedModulesMap.nonExporting) }; + newState.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap?.clone(); newState.seenAffectedFiles = state.seenAffectedFiles && new Set(state.seenAffectedFiles); newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles; newState.semanticDiagnosticsFromOldState = state.semanticDiagnosticsFromOldState && new Set(state.semanticDiagnosticsFromOldState); @@ -386,12 +386,7 @@ namespace ts { // Get next batch of affected files if (!state.currentAffectedFilesSignatures) state.currentAffectedFilesSignatures = new Map(); if (state.exportedModulesMap) { - if (!state.currentAffectedFilesExportedModulesMap) { - state.currentAffectedFilesExportedModulesMap = { - exporting: BuilderState.createManyToManyPathMap(), - nonExporting: new Set(), - }; - } + state.currentAffectedFilesExportedModulesMap ||= BuilderState.createManyToManyPathMap(); } state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, nextKey.value, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap); state.currentChangedFilePath = nextKey.value; @@ -555,15 +550,15 @@ namespace ts { const seenFileAndExportsOfFile = new Set(); // Go through exported modules from cache first // If exported modules has path, all files referencing file exported from are affected - state.currentAffectedFilesExportedModulesMap.exporting.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => + state.currentAffectedFilesExportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) ); // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected state.exportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => // If the cache had an updated value, skip - !state.currentAffectedFilesExportedModulesMap!.exporting.hasKey(exportedFromPath) && - !state.currentAffectedFilesExportedModulesMap!.nonExporting.has(exportedFromPath) && + !state.currentAffectedFilesExportedModulesMap!.hasKey(exportedFromPath) && + !state.currentAffectedFilesExportedModulesMap!.deletedKeys()?.has(exportedFromPath) && forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) ); } @@ -590,15 +585,15 @@ namespace ts { Debug.assert(!!state.currentAffectedFilesExportedModulesMap); // Go through exported modules from cache first // If exported modules has path, all files referencing file exported from are affected - state.currentAffectedFilesExportedModulesMap.exporting.getKeys(filePath)?.forEach(exportedFromPath => + state.currentAffectedFilesExportedModulesMap.getKeys(filePath)?.forEach(exportedFromPath => forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) ); // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected state.exportedModulesMap!.getKeys(filePath)?.forEach(exportedFromPath => // If the cache had an updated value, skip - !state.currentAffectedFilesExportedModulesMap!.exporting.hasKey(exportedFromPath) && - !state.currentAffectedFilesExportedModulesMap!.nonExporting.has(exportedFromPath) && + !state.currentAffectedFilesExportedModulesMap!.hasKey(exportedFromPath) && + !state.currentAffectedFilesExportedModulesMap!.deletedKeys()?.has(exportedFromPath) && forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) ); @@ -768,11 +763,11 @@ namespace ts { if (state.exportedModulesMap) { exportedModulesMap = mapDefined(arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive), key => { if (state.currentAffectedFilesExportedModulesMap) { - if (state.currentAffectedFilesExportedModulesMap.nonExporting.has(key)) { + if (state.currentAffectedFilesExportedModulesMap.deletedKeys()?.has(key)) { return undefined; } - const newValue = state.currentAffectedFilesExportedModulesMap.exporting.getValues(key); + const newValue = state.currentAffectedFilesExportedModulesMap.getValues(key); if (newValue) { return [toFileId(key), toFileIdListId(newValue)]; } diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 82e2ae6ec95c4..4194f1d21be35 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -86,6 +86,13 @@ namespace ts { getValues(k: Path): ReadonlySet | undefined; hasKey(k: Path): boolean; keys(): Iterator; + + /** + * The set of arguments to {@link deleteKeys} which have not subsequently + * been arguments to {@link set}. Note that a key does not have to have + * ever been in the map to appear in this set. + */ + deletedKeys(): ReadonlySet | undefined; } export interface ManyToManyPathMap extends ReadonlyManyToManyPathMap { @@ -94,16 +101,19 @@ namespace ts { } export function createManyToManyPathMap(): ManyToManyPathMap { - function create(forward: ESMap>, reverse: ESMap>): ManyToManyPathMap { + function create(forward: ESMap>, reverse: ESMap>, deleted: Set | undefined): ManyToManyPathMap { const map: ManyToManyPathMap = { - clone: () => create(new Map(forward), new Map(reverse)), + clone: () => create(new Map(forward), new Map(reverse), deleted && new Set(deleted)), forEach: fn => forward.forEach(fn), getKeys: v => reverse.get(v), getValues: k => forward.get(k), hasKey: k => forward.has(k), keys: () => forward.keys(), + deletedKeys: () => deleted, deleteKey: k => { + (deleted ||= new Set()).add(k); + const set = forward.get(k); if (!set) { return false; @@ -114,6 +124,8 @@ namespace ts { return true; }, set: (k, vSet) => { + deleted?.delete(k); + const existingVSet = forward.get(k); forward.set(k, vSet); @@ -136,7 +148,7 @@ namespace ts { return map; } - return create(new Map>(), new Map>()); + return create(new Map>(), new Map>(), /*deleted*/ undefined); } function addToMultimap(map: ESMap>, k: K, v: V): void { @@ -166,15 +178,6 @@ namespace ts { */ export type ComputeHash = ((data: string) => string) | undefined; - /** - * Exported modules to from declaration emit being computed. - * Entries in `nonExporting` indicate that there are no exported module(types from other modules) for the file - */ - export interface ComputingExportedModulesMap { - exporting: ManyToManyPathMap; - nonExporting: Set; - } - /** * Get the referencedFile from the imported module symbol */ @@ -356,7 +359,7 @@ namespace ts { /** * Gets the files affected by the path from the program */ - export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: ESMap, exportedModulesMapCache?: ComputingExportedModulesMap): readonly SourceFile[] { + export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: ESMap, exportedModulesMapCache?: ManyToManyPathMap): readonly SourceFile[] { // Since the operation could be cancelled, the signatures are always stored in the cache // They will be committed once it is safe to use them // eg when calling this api from tsserver, if there is no cancellation of the operation @@ -395,7 +398,7 @@ namespace ts { /** * Returns if the shape of the signature has changed since last emit */ - export function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: ESMap, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ComputingExportedModulesMap) { + export function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: ESMap, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ManyToManyPathMap) { Debug.assert(!!sourceFile); Debug.assert(!exportedModulesMapCache || !!state.exportedModulesMap, "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state"); @@ -434,10 +437,10 @@ namespace ts { // All the references in this file are exported const references = state.referencedMap ? state.referencedMap.getValues(sourceFile.resolvedPath) : undefined; if (references) { - exportedModulesMapCache.exporting.set(sourceFile.resolvedPath, references); + exportedModulesMapCache.set(sourceFile.resolvedPath, references); } else { - exportedModulesMapCache.nonExporting.add(sourceFile.resolvedPath); + exportedModulesMapCache.deleteKey(sourceFile.resolvedPath); } } } @@ -448,19 +451,19 @@ namespace ts { /** * Coverts the declaration emit result into exported modules map */ - function updateExportedModules(sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ComputingExportedModulesMap) { + function updateExportedModules(sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ManyToManyPathMap) { if (!exportedModulesFromDeclarationEmit) { - exportedModulesMapCache.nonExporting.add(sourceFile.resolvedPath); + exportedModulesMapCache.deleteKey(sourceFile.resolvedPath); return; } let exportedModules: Set | undefined; exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFileFromImportedModuleSymbol(symbol))); if (exportedModules) { - exportedModulesMapCache.exporting.set(sourceFile.resolvedPath, exportedModules); + exportedModulesMapCache.set(sourceFile.resolvedPath, exportedModules); } else { - exportedModulesMapCache.nonExporting.add(sourceFile.resolvedPath); + exportedModulesMapCache.deleteKey(sourceFile.resolvedPath); } function addExportedModule(exportedModulePath: Path | undefined) { @@ -477,11 +480,11 @@ namespace ts { * Updates the exported modules from cache into state's exported modules map * This should be called whenever it is safe to commit the state of the builder */ - export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { + export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ManyToManyPathMap | undefined) { if (exportedModulesMapCache) { Debug.assert(!!state.exportedModulesMap); - exportedModulesMapCache.nonExporting.forEach(path => state.exportedModulesMap!.deleteKey(path)); - exportedModulesMapCache.exporting.forEach((exportedModules, path) => state.exportedModulesMap!.set(path, exportedModules)); + exportedModulesMapCache.deletedKeys()?.forEach(path => state.exportedModulesMap!.deleteKey(path)); + exportedModulesMapCache.forEach((exportedModules, path) => state.exportedModulesMap!.set(path, exportedModules)); } } @@ -612,7 +615,7 @@ namespace ts { /** * When program emits modular code, gets the files affected by the sourceFile whose shape has changed */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: ESMap, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { + function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: ESMap, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache: ManyToManyPathMap | undefined) { if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) { return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); }