Skip to content

Commit c63d6d1

Browse files
committed
Avoid double-resolving modified files
1 parent 86d7031 commit c63d6d1

4 files changed

Lines changed: 54 additions & 24 deletions

File tree

src/compiler/core.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,15 @@ namespace ts {
251251
}
252252
}
253253

254+
export function zipToMap<T>(keys: string[], values: T[]): Map<T> {
255+
Debug.assert(keys.length === values.length);
256+
const map = createMap<T>();
257+
for (let i = 0; i < keys.length; ++i) {
258+
map.set(keys[i], values[i]);
259+
}
260+
return map;
261+
}
262+
254263
/**
255264
* Iterates through `array` by index and performs the callback on each element of array until the callback
256265
* returns a falsey value, then returns false.

src/compiler/program.ts

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,24 @@ namespace ts {
491491
return resolveModuleNamesWorker(moduleNames, containingFile);
492492
}
493493

494-
// at this point we know at least one of the following hold:
494+
const oldSourceFile = oldProgramState.program && oldProgramState.program.getSourceFile(containingFile);
495+
if (oldSourceFile !== file && file.resolvedModules) {
496+
// `file` was created for the new program.
497+
//
498+
// We only set `file.resolvedModules` via work from the current function,
499+
// so it is defined iff we already called the current function on `file`.
500+
// That call happened no later than the creation of the `file` object,
501+
// which per above occured during the current program creation.
502+
// Since we model program creation as an atomic operation w/r/t IO,
503+
// it is safe to reuse resolutions from the earlier call.
504+
const result: ResolvedModuleFull[] = [];
505+
for (const moduleName of moduleNames) {
506+
const resolvedModule = file.resolvedModules.get(moduleName);
507+
result.push(resolvedModule);
508+
}
509+
return result;
510+
}
511+
// At this point, we know at least one of the following hold:
495512
// - file has local declarations for ambient modules
496513
// - old program state is available
497514
// With this information, we can infer some module resolutions without performing resolution.
@@ -511,7 +528,6 @@ namespace ts {
511528
/** A transient placeholder used to mark predicted resolution in the result list. */
512529
const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = <any>{};
513530

514-
const oldSourceFile = oldProgramState.program && oldProgramState.program.getSourceFile(containingFile);
515531

516532
for (let i = 0; i < moduleNames.length; i++) {
517533
const moduleName = moduleNames[i];
@@ -620,7 +636,7 @@ namespace ts {
620636
return oldProgram.structureIsReused = StructureIsReused.Not;
621637
}
622638

623-
Debug.assert(!(oldProgram.structureIsReused & (StructureIsReused.Completely | StructureIsReused.ModulesInUneditedFiles)));
639+
Debug.assert(!(oldProgram.structureIsReused & (StructureIsReused.Completely | StructureIsReused.SafeModules)));
624640

625641
// there is an old program, check if we can reuse its structure
626642
const oldRootNames = oldProgram.getRootFileNames();
@@ -653,32 +669,34 @@ namespace ts {
653669
filePaths.push(newSourceFile.path);
654670

655671
if (oldSourceFile !== newSourceFile) {
672+
// The `newSourceFile` object was created for the new program.
673+
656674
if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) {
657675
// value of no-default-lib has changed
658676
// this will affect if default library is injected into the list of files
659-
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
677+
oldProgram.structureIsReused = StructureIsReused.SafeModules;
660678
}
661679

662680
// check tripleslash references
663681
if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) {
664682
// tripleslash references has changed
665-
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
683+
oldProgram.structureIsReused = StructureIsReused.SafeModules;
666684
}
667685

668686
// check imports and module augmentations
669687
collectExternalModuleReferences(newSourceFile);
670688
if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) {
671689
// imports has changed
672-
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
690+
oldProgram.structureIsReused = StructureIsReused.SafeModules;
673691
}
674692
if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) {
675693
// moduleAugmentations has changed
676-
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
694+
oldProgram.structureIsReused = StructureIsReused.SafeModules;
677695
}
678696

679697
if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) {
680698
// 'types' references has changed
681-
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
699+
oldProgram.structureIsReused = StructureIsReused.SafeModules;
682700
}
683701

684702
// tentatively approve the file
@@ -695,7 +713,7 @@ namespace ts {
695713

696714
modifiedFilePaths = modifiedSourceFiles.map(f => f.newFile.path);
697715
// try to verify results of module resolution
698-
modifiedSourceFilesLoop: for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) {
716+
for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) {
699717
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory);
700718
if (resolveModuleNamesWorker) {
701719
const moduleNames = map(concatenate(newSourceFile.imports, newSourceFile.moduleAugmentations), getTextOfLiteral);
@@ -704,8 +722,11 @@ namespace ts {
704722
// ensure that module resolution results are still correct
705723
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
706724
if (resolutionsChanged) {
707-
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
708-
continue modifiedSourceFilesLoop;
725+
oldProgram.structureIsReused = StructureIsReused.SafeModules;
726+
newSourceFile.resolvedModules = zipToMap(moduleNames, resolutions);
727+
}
728+
else {
729+
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
709730
}
710731
}
711732
if (resolveTypeReferenceDirectiveNamesWorker) {
@@ -714,13 +735,13 @@ namespace ts {
714735
// ensure that types resolutions are still correct
715736
const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
716737
if (resolutionsChanged) {
717-
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
718-
continue modifiedSourceFilesLoop;
738+
oldProgram.structureIsReused = StructureIsReused.SafeModules;
739+
newSourceFile.resolvedTypeReferenceDirectiveNames = zipToMap(typesReferenceDirectives, resolutions);
740+
}
741+
else {
742+
newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames;
719743
}
720744
}
721-
// pass the cache of module/types resolutions from the old source file
722-
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
723-
newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames;
724745
}
725746

726747
if (oldProgram.structureIsReused !== StructureIsReused.Completely) {

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2412,7 +2412,7 @@ namespace ts {
24122412
/* @internal */
24132413
export const enum StructureIsReused {
24142414
Completely,
2415-
ModulesInUneditedFiles,
2415+
SafeModules,
24162416
Not
24172417
}
24182418

src/harness/unittests/reuseProgramStructure.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ namespace ts {
261261
`;
262262
files[0].text = files[0].text.updateReferences(newReferences);
263263
});
264-
assert.isTrue(program_1.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
264+
assert.isTrue(program_1.structureIsReused === StructureIsReused.SafeModules);
265265
});
266266

267267
it("fails if change affects type references", () => {
@@ -281,7 +281,7 @@ namespace ts {
281281
updateProgram(program_1, ["a.ts"], { target }, files => {
282282
files[2].text = files[2].text.updateImportsAndExports("import x from 'b'");
283283
});
284-
assert.isTrue(program_1.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
284+
assert.isTrue(program_1.structureIsReused === StructureIsReused.SafeModules);
285285
});
286286

287287
it("fails if change affects type directives", () => {
@@ -293,7 +293,7 @@ namespace ts {
293293
/// <reference types="typerefs1" />`;
294294
files[0].text = files[0].text.updateReferences(newReferences);
295295
});
296-
assert.isTrue(program_1.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
296+
assert.isTrue(program_1.structureIsReused === StructureIsReused.SafeModules);
297297
});
298298

299299
it("fails if module kind changes", () => {
@@ -340,7 +340,7 @@ namespace ts {
340340
const program_3 = updateProgram(program_2, ["a.ts"], options, files => {
341341
files[0].text = files[0].text.updateImportsAndExports("");
342342
});
343-
assert.isTrue(program_2.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
343+
assert.isTrue(program_2.structureIsReused === StructureIsReused.SafeModules);
344344
checkResolvedModulesCache(program_3, "a.ts", /*expectedContent*/ undefined);
345345

346346
const program_4 = updateProgram(program_3, ["a.ts"], options, files => {
@@ -349,7 +349,7 @@ namespace ts {
349349
`;
350350
files[0].text = files[0].text.updateImportsAndExports(newImports);
351351
});
352-
assert.isTrue(program_3.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
352+
assert.isTrue(program_3.structureIsReused === StructureIsReused.SafeModules);
353353
checkResolvedModulesCache(program_4, "a.ts", createMapFromTemplate({ "b": createResolvedModule("b.ts"), "c": undefined }));
354354
});
355355

@@ -378,7 +378,7 @@ namespace ts {
378378
files[0].text = files[0].text.updateReferences("");
379379
});
380380

381-
assert.isTrue(program_2.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
381+
assert.isTrue(program_2.structureIsReused === StructureIsReused.SafeModules);
382382
checkResolvedTypeDirectivesCache(program_3, "/a.ts", /*expectedContent*/ undefined);
383383

384384
updateProgram(program_3, ["/a.ts"], options, files => {
@@ -387,7 +387,7 @@ namespace ts {
387387
`;
388388
files[0].text = files[0].text.updateReferences(newReferences);
389389
});
390-
assert.isTrue(program_3.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
390+
assert.isTrue(program_3.structureIsReused === StructureIsReused.SafeModules);
391391
checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMapFromTemplate({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }));
392392
});
393393

0 commit comments

Comments
 (0)