Skip to content

Commit

Permalink
Handle effective type roots and type ref resolution sharing cache
Browse files Browse the repository at this point in the history
// TODO: update incremental tests to actually verify the cache
  • Loading branch information
sheetalkamat committed Oct 4, 2024
1 parent 3e4da7f commit 11866b4
Show file tree
Hide file tree
Showing 31 changed files with 754 additions and 1,702 deletions.
316 changes: 257 additions & 59 deletions src/compiler/moduleNameResolver.ts

Large diffs are not rendered by default.

100 changes: 95 additions & 5 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
createSymlinkCache,
createTypeChecker,
createTypeReferenceDirectiveResolutionCache,
createTypeRootsCacheKey,
CustomTransformers,
Debug,
DeclarationWithTypeParameterChildren,
Expand Down Expand Up @@ -117,6 +118,7 @@ import {
getDeclarationDiagnostics as ts_getDeclarationDiagnostics,
getDefaultLibFileName,
getDirectoryPath,
getEffectiveTypeRoots as ts_getEffectiveTypeRoots,
getEmitDeclarations,
getEmitModuleKind,
getEmitModuleResolutionKind,
Expand Down Expand Up @@ -320,6 +322,7 @@ import {
TypeChecker,
typeDirectiveIsEqualTo,
TypeReferenceDirectiveResolutionCache,
TypeRootsCacheKeyOrSpecifiedTypeRoots,
unprefixedNodeCoreModules,
VariableDeclaration,
VariableStatement,
Expand Down Expand Up @@ -1812,6 +1815,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
let projectReferenceRedirects: Map<Path, ResolvedProjectReference | false> | undefined;
let mapFromFileToProjectReferenceRedirects: Map<Path, Path> | undefined;
let mapFromToProjectReferenceRedirectSource: Map<Path, SourceOfProjectReferenceRedirect> | undefined;
let typeRootsCacheKeys: Map<Path | undefined, TypeRootsCacheKeyOrSpecifiedTypeRoots> | undefined;
let processingTypeRootsCacheKeys: Map<Path | undefined, TypeRootsCacheKeyOrSpecifiedTypeRoots> | undefined;
let effectiveRoots: Map<Path | undefined, readonly string[] | false> | undefined;

const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect?.() &&
!options.disableSourceOfProjectReferenceRedirect;
Expand All @@ -1825,6 +1831,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
forEachResolvedProjectReference,
});
const readFile = host.readFile.bind(host) as typeof host.readFile;
const hostGetEffectiveTypeRoots = host.getEffectiveTypeRoots;
const hostGetTypeRootsCacheKey = host.getTypeRootsCacheKey;
host.getEffectiveTypeRoots = getEffectiveTypeRoots;

tracing?.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
Expand Down Expand Up @@ -1883,6 +1892,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(
automaticTypeDirectiveNames,
getAutomaticTypeDirectiveContainingFile(options, currentDirectory),
getTypeRootsCacheKey,
);
for (let i = 0; i < automaticTypeDirectiveNames.length; i++) {
// under node16/nodenext module resolution, load `types`/ata include names as cjs resolution results by passing an `undefined` mode
Expand Down Expand Up @@ -1971,6 +1981,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
resolvedLibProcessing = undefined;
resolvedModulesProcessing = undefined;
resolvedTypeReferenceDirectiveNamesProcessing = undefined;
effectiveRoots = undefined;
processingTypeRootsCacheKeys = undefined;
host.getEffectiveTypeRoots = hostGetEffectiveTypeRoots;
host.getTypeRootsCacheKey = hostGetTypeRootsCacheKey;

const program: Program = {
getRootFileNames: () => rootNames,
Expand Down Expand Up @@ -2054,6 +2068,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
structureIsReused,
writeFile,
getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation),
getTypeRootsCacheKeys: () => typeRootsCacheKeys,
};

onProgramCreateComplete();
Expand Down Expand Up @@ -2251,6 +2266,51 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
return result;
}

function getEffectiveTypeRoots(options: CompilerOptions, redirect: ResolvedProjectReference | undefined) {
let result = effectiveRoots?.get(redirect?.sourceFile.path);
if (result === undefined) {
(effectiveRoots ??= new Map()).set(
redirect?.sourceFile.path,
result = (
hostGetEffectiveTypeRoots ?
hostGetEffectiveTypeRoots(options, redirect) :
ts_getEffectiveTypeRoots(options, host)
) ?? false,
);
}
return result || undefined;
}

function getTypeRootsCacheKey(options: CompilerOptions, redirect: ResolvedProjectReference | undefined): TypeRootsCacheKeyOrSpecifiedTypeRoots {
let result = typeRootsCacheKeys?.get(redirect?.sourceFile.path);
if (result === undefined) {
(typeRootsCacheKeys ??= new Map()).set(
redirect?.sourceFile.path,
result = processingTypeRootsCacheKeys?.get(redirect?.sourceFile.path) ??
getTypeRootsCacheKeyWorker(options, redirect) ??
false,
);
}
return result;
}

function getProcessingTypeRootsCacheKey(options: CompilerOptions, redirect: ResolvedProjectReference | undefined): TypeRootsCacheKeyOrSpecifiedTypeRoots {
let result = processingTypeRootsCacheKeys?.get(redirect?.sourceFile.path);
if (result === undefined) {
(processingTypeRootsCacheKeys ??= new Map()).set(
redirect?.sourceFile.path,
result = getTypeRootsCacheKeyWorker(options, redirect) ?? false,
);
}
return result;
}

function getTypeRootsCacheKeyWorker(options: CompilerOptions, redirect: ResolvedProjectReference | undefined): TypeRootsCacheKeyOrSpecifiedTypeRoots {
return hostGetTypeRootsCacheKey ?
hostGetTypeRootsCacheKey(options, redirect) :
createTypeRootsCacheKey(options, redirect, host);
}

function getRedirectReferenceForResolution(file: SourceFile) {
const redirect = getResolvedProjectReferenceToRedirect(file.originalFileName);
if (redirect || !isDeclarationFileName(file.originalFileName)) return redirect;
Expand Down Expand Up @@ -2346,9 +2406,21 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
});
}

function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: readonly FileReference[], containingFile: SourceFile): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[];
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: readonly string[], containingFile: string): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[];
function resolveTypeReferenceDirectiveNamesReusingOldState<T extends string | FileReference>(typeDirectiveNames: readonly T[], containingFile: string | SourceFile): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[] {
function resolveTypeReferenceDirectiveNamesReusingOldState(
typeDirectiveNames: readonly FileReference[],
containingFile: SourceFile,
getTypeRootsCacheKey: (options: CompilerOptions, redirects: ResolvedProjectReference | undefined) => TypeRootsCacheKeyOrSpecifiedTypeRoots,
): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[];
function resolveTypeReferenceDirectiveNamesReusingOldState(
typeDirectiveNames: readonly string[],
containingFile: string,
getTypeRootsCacheKey: (options: CompilerOptions, redirects: ResolvedProjectReference | undefined) => TypeRootsCacheKeyOrSpecifiedTypeRoots,
): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[];
function resolveTypeReferenceDirectiveNamesReusingOldState<T extends string | FileReference>(
typeDirectiveNames: readonly T[],
containingFile: string | SourceFile,
getTypeRootsCacheKey: (options: CompilerOptions, redirects: ResolvedProjectReference | undefined) => TypeRootsCacheKeyOrSpecifiedTypeRoots,
): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[] {
const containingSourceFile = !isString(containingFile) ? containingFile : undefined;
return resolveNamesReusingOldState({
entries: typeDirectiveNames,
Expand All @@ -2367,6 +2439,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
containingSourceFile ?
containingSourceFile === oldProgram?.getSourceFile(containingSourceFile.fileName) && !hasInvalidatedResolutions(containingSourceFile.path) :
!hasInvalidatedResolutions(toPath(containingFile as string)),
getTypeRootsCacheKey,
});
}

Expand All @@ -2393,6 +2466,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
getResolved: (oldResolution: Resolution) => ResolutionWithResolvedFileName | undefined;
canReuseResolutionsInFile: () => boolean;
resolveToOwnAmbientModule?: true;
getTypeRootsCacheKey?: (options: CompilerOptions, redirects: ResolvedProjectReference | undefined) => TypeRootsCacheKeyOrSpecifiedTypeRoots;
}

function resolveNamesReusingOldState<Entry, SourceFileOrString, SourceFileOrUndefined extends SourceFile | undefined, Resolution>({
Expand All @@ -2407,6 +2481,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
getResolved,
canReuseResolutionsInFile,
resolveToOwnAmbientModule,
getTypeRootsCacheKey,
}: ResolveNamesReusingOldStateInput<Entry, SourceFileOrString, SourceFileOrUndefined, Resolution>): readonly Resolution[] {
if (!entries.length) {
onReusedResolutions?.(
Expand All @@ -2417,7 +2492,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
);
return emptyArray;
}
// Ensure typeRootsCacheKey is cached
getTypeRootsCacheKey?.(redirectedReference?.commandLine.options ?? options, redirectedReference);
if (structureIsReused === StructureIsReused.Not && (!resolveToOwnAmbientModule || !containingSourceFile!.ambientModuleNames.length)) {
host.getTypeRootsCacheKey = getTypeRootsCacheKey;
// If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules,
// the best we can do is fallback to the default logic.
return resolutionWorker(
Expand Down Expand Up @@ -2494,6 +2572,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
);
return result!;
}
host.getTypeRootsCacheKey = getTypeRootsCacheKey;
const resolutions = resolutionWorker(
unknownEntries,
containingFile,
Expand Down Expand Up @@ -2703,7 +2782,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
);
if (resolutionsChanged) structureIsReused = StructureIsReused.SafeModules;
const typesReferenceDirectives = newSourceFile.typeReferenceDirectives;
const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesReusingOldState(typesReferenceDirectives, newSourceFile);
const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesReusingOldState(
typesReferenceDirectives,
newSourceFile,
getProcessingTypeRootsCacheKey,
);
(resolvedTypeReferenceDirectiveNamesProcessing ??= new Map()).set(newSourceFile.path, typeReferenceResolutions);
// ensure that types resolutions are still correct
const typeReferenceResolutionsChanged = hasChangesInResolutions(
Expand Down Expand Up @@ -2778,6 +2861,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
resolvedTypeReferenceDirectiveNames = oldProgram.resolvedTypeReferenceDirectiveNames;
resolvedLibReferences = oldProgram.resolvedLibReferences;
packageMap = oldProgram.getCurrentPackagesMap();
typeRootsCacheKeys = oldProgram.getTypeRootsCacheKeys();

return StructureIsReused.Completely;
}
Expand Down Expand Up @@ -4057,7 +4141,13 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}

const resolutions = resolvedTypeReferenceDirectiveNamesProcessing?.get(file.path) ||
resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectives, file);
resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectives, file, getTypeRootsCacheKey);
if (resolutions.length && resolvedTypeReferenceDirectiveNamesProcessing?.get(file.path)) {
// Ensure type reference key is cached from processing to actual
const redirect = getRedirectReferenceForResolution(file);
const value = processingTypeRootsCacheKeys?.get(redirect?.sourceFile.path);
if (value !== undefined) (typeRootsCacheKeys ??= new Map()).set(redirect?.sourceFile.path, value);
}
const resolutionsInFile = createModeAwareCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>();
(resolvedTypeReferenceDirectiveNames ??= new Map()).set(file.path, resolutionsInFile);
for (let index = 0; index < typeDirectives.length; index++) {
Expand Down
15 changes: 13 additions & 2 deletions src/compiler/resolutionCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import {
trace,
TypeReferenceDirectiveResolutionCache,
typeReferenceResolutionNameAndModeGetter,
TypeRootsCacheKeyOrSpecifiedTypeRoots,
WatchDirectoryFlags,
} from "./_namespaces/ts.js";

Expand Down Expand Up @@ -360,6 +361,7 @@ function resolveModuleNameUsingGlobalCache(
mode,
getDirectoryPath(containingFile),
redirectedReference,
undefined,
primary.globalCacheResolution.globalResult,
primary,
);
Expand Down Expand Up @@ -658,16 +660,25 @@ export function createResolutionCache(

function compactCaches(newProgram: Program | undefined) {
const availableOptions = new Set<CompilerOptions>();
const availableTypeCacheKeys = new Map<CompilerOptions, Set<TypeRootsCacheKeyOrSpecifiedTypeRoots>>();
if (newProgram) {
availableOptions.add(newProgram.getCompilerOptions());
const key = newProgram.getTypeRootsCacheKeys()?.get(/*key*/ undefined);
if (key !== undefined) availableTypeCacheKeys.set(newProgram.getCompilerOptions(), new Set([key]));
newProgram.forEachResolvedProjectReference(ref => {
availableOptions.add(ref.commandLine.options);
const key = newProgram.getTypeRootsCacheKeys()?.get(ref.sourceFile.path);
if (key !== undefined) {
const existing = availableTypeCacheKeys.get(ref.commandLine.options);
if (existing) existing.add(key);
else availableTypeCacheKeys.set(ref.commandLine.options, new Set([key]));
}
});
}
moduleResolutionCache.compact(availableOptions, /*skipOptionsToRedirectsKeyCleanup*/ true);
typeReferenceDirectiveResolutionCache.compact(availableOptions);
typeReferenceDirectiveResolutionCache.compact(availableOptions, /*skipOptionsToRedirectsKeyCleanup*/ false, availableTypeCacheKeys);
libraryResolutionCache.compact();
sharedCache.compactCaches(availableOptions, cache);
sharedCache.compactCaches(availableOptions, availableTypeCacheKeys, cache);
}

function gcModuleOrTypeRefCache(
Expand Down
Loading

0 comments on commit 11866b4

Please sign in to comment.