Skip to content

Commit

Permalink
Resolutions cache stays for lifetime..
Browse files Browse the repository at this point in the history
But when do we gc Resolution caches esp for non relative names etc?
Also TODO:: handle change in module reosolution options
  • Loading branch information
sheetalkamat committed Jul 3, 2024
1 parent ba00137 commit 1fb73da
Show file tree
Hide file tree
Showing 35 changed files with 549 additions and 382 deletions.
137 changes: 106 additions & 31 deletions src/compiler/moduleNameResolver.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,6 @@ import {
removeSuffix,
resolutionExtensionIsTSOrJson,
ResolutionMode,
ResolutionWithFailedLookupLocations,
resolveConfigFileProjectName,
ResolvedConfigFileName,
ResolvedModuleFull,
Expand Down Expand Up @@ -1968,6 +1967,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
getResolvedProjectReferenceByPath,
forEachResolvedProjectReference,
isSourceOfProjectReferenceRedirect,
getRedirectReferenceForResolution,
getRedirectReferenceForResolutionFromSourceOfProject,
emitBuildInfo,
fileExists,
Expand Down Expand Up @@ -4156,7 +4156,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
resolveModuleNamesReusingOldState(moduleNames, file);
Debug.assert(resolutions.length === moduleNames.length);
const optionsForFile = redirectedReference?.commandLine.options || options;
const resolutionsInFile = createModeAwareCache<ResolutionWithFailedLookupLocations>();
const resolutionsInFile = createModeAwareCache<ResolvedModuleWithFailedLookupLocations>();
(resolvedModules ??= new Map()).set(file.path, resolutionsInFile);
for (let index = 0; index < moduleNames.length; index++) {
const resolution = resolutions[index].resolvedModule;
Expand Down
318 changes: 174 additions & 144 deletions src/compiler/resolutionCache.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4817,6 +4817,7 @@ export interface Program extends ScriptReferenceHost {
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
/** @internal */ forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined): T | undefined;
/** @internal */ getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined;
/** @internal */ getRedirectReferenceForResolution(file: SourceFile): ResolvedProjectReference | undefined;
/** @internal */ getRedirectReferenceForResolutionFromSourceOfProject(filePath: Path): ResolvedProjectReference | undefined;
/** @internal */ isSourceOfProjectReferenceRedirect(fileName: string): boolean;
/** @internal */ getBuildInfo?(): BuildInfo;
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/watchPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
host.resolveLibrary.bind(host);
compilerHost.getModuleResolutionCache = host.resolveModuleNameLiterals || host.resolveModuleNames ?
maybeBind(host, host.getModuleResolutionCache) :
(() => resolutionCache.getModuleResolutionCache());
(() => resolutionCache.moduleResolutionCache);
const userProvidedResolution = !!host.resolveModuleNameLiterals || !!host.resolveTypeReferenceDirectiveReferences ||
!!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives;
// All resolutions are invalid if user provided resolutions and didnt supply hasInvalidatedResolutions
Expand Down
149 changes: 145 additions & 4 deletions src/harness/incrementalUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export function verifyResolutionCache(
const expected = ts.createResolutionCache(resolutionHostCacheHost, actual.rootDirForResolution);
expected.startCachingPerDirectoryResolution();

type ExpectedResolution = ts.CachedResolvedModuleWithFailedLookupLocations & ts.CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations;
type ExpectedResolution = ts.ResolvedModuleWithFailedLookupLocations & ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations;

const expectedToResolution = new Map<ExpectedResolution, ts.ResolutionWithFailedLookupLocations>();
const resolutionToExpected = new Map<ts.ResolutionWithFailedLookupLocations, ExpectedResolution>();
Expand All @@ -228,6 +228,8 @@ export function verifyResolutionCache(
resolutions,
getResolvedModuleFileName,
expected.resolvedModuleNames,
expected.moduleResolutionCache,
() => actualProgram.getRedirectReferenceForResolution(actualProgram.getSourceFileByPath(path)!),
(name, mode) => actualProgram.getResolvedModule(actualProgram.getSourceFileByPath(path)!, name, mode),
)
);
Expand All @@ -238,24 +240,36 @@ export function verifyResolutionCache(
resolutions,
getResolvedTypeRefFileName,
expected.resolvedTypeReferenceDirectives,
expected.typeReferenceDirectiveResolutionCache,
() =>
path !== inferredTypesPath ?
actualProgram.getRedirectReferenceForResolution(actualProgram.getSourceFileByPath(path)!) :
undefined,
(name, mode) =>
path !== inferredTypesPath ?
actualProgram.getResolvedTypeReferenceDirective(actualProgram.getSourceFileByPath(path)!, name, mode) :
actualProgram.getAutomaticTypeDirectiveResolutions().get(name, mode),
)
);
actual.resolvedLibraries.forEach((resolved, libFileName) => {
const libResolvedFrom = ts.getInferredLibraryNameResolveFrom(actualProgram.getCompilerOptions(), currentDirectory, libFileName);
const expectedResolution = collectResolution(
"Libs",
resolutionHostCacheHost.toPath(
ts.getInferredLibraryNameResolveFrom(actualProgram.getCompilerOptions(), currentDirectory, libFileName),
),
resolutionHostCacheHost.toPath(libResolvedFrom),
resolved,
getResolvedModuleFileName(resolved),
ts.getLibraryNameFromLibFileName(libFileName),
/*mode*/ undefined,
);
expected.resolvedLibraries.set(libFileName, expectedResolution);
ts.setPerDirectoryAndNonRelativeNameCacheResult(
expected.libraryResolutionCache,
libFileName,
undefined,
ts.getDirectoryPath(libResolvedFrom),
undefined,
expectedResolution,
);
});
// Check for resolutions in program but not in cache to empty resolutions
if (!userResolvedModuleNames) {
Expand Down Expand Up @@ -337,6 +351,11 @@ export function verifyResolutionCache(
`${projectName}:: Expected ResolutionsResolvedWithoutGlobalCache count ${expected.countResolutionsResolvedWithoutGlobalCache()} but got ${actual.countResolutionsResolvedWithoutGlobalCache()}`,
);

// Verify that caches are same:
verifyModuleOrTypeResolutionCache(expected.moduleResolutionCache, actual.moduleResolutionCache, "moduleResolutionCache");
verifyModuleOrTypeResolutionCache(expected.typeReferenceDirectiveResolutionCache, actual.typeReferenceDirectiveResolutionCache, "typeReferenceDirectiveResolutionCache");
verifyModuleOrTypeResolutionCache(expected.libraryResolutionCache, actual.libraryResolutionCache, "libraryResolutionCache");

// Stop watching resolutions to verify everything gets closed.
expected.startCachingPerDirectoryResolution();
actual.resolvedModuleNames.forEach((_resolutions, path) => expected.removeResolutionsOfFile(path));
Expand All @@ -353,6 +372,9 @@ export function verifyResolutionCache(
ts.Debug.assert(expected.fileWatchesOfAffectingLocations.size === 0, `${projectName}:: fileWatchesOfAffectingLocations should be released`);
ts.Debug.assert(expected.countResolutionsResolvedWithGlobalCache() === 0, `${projectName}:: ResolutionsResolvedWithGlobalCache should be cleared`);
ts.Debug.assert(expected.countResolutionsResolvedWithoutGlobalCache() === 0, `${projectName}:: ResolutionsResolvedWithoutGlobalCache should be cleared`);
verifyModuleOrTypeResolutionCacheIsEmpty(expected.moduleResolutionCache, "moduleResolutionCache");
verifyModuleOrTypeResolutionCacheIsEmpty(expected.typeReferenceDirectiveResolutionCache, "typeReferenceDirectiveResolutionCache");
verifyModuleOrTypeResolutionCacheIsEmpty(expected.libraryResolutionCache, "libraryResolutionCache");

function verifyResolutionIsInCache<T extends ts.ResolutionWithFailedLookupLocations>(
cacheType: string,
Expand Down Expand Up @@ -384,6 +406,8 @@ export function verifyResolutionCache(
cache: ts.ModeAwareCache<T> | undefined,
getResolvedFileName: (resolution: T) => string | undefined,
storeExpected: Map<ts.Path, ts.ModeAwareCache<ts.ResolutionWithFailedLookupLocations>>,
moduleOrTypeRefCache: ts.ModuleOrTypeReferenceResolutionCache<T>,
getRedirectReferenceForResolution: () => ts.ResolvedProjectReference | undefined,
getProgramResolutions: (name: string, mode: ts.ResolutionMode) => T | undefined,
) {
ts.Debug.assert(
Expand All @@ -396,6 +420,14 @@ export function verifyResolutionCache(
const expected = collectResolution(cacheType, fileName, resolved, resolvedFileName, name, mode);
if (!expectedCache) storeExpected.set(fileName, expectedCache = ts.createModeAwareCache());
expectedCache.set(name, mode, expected);
ts.setPerDirectoryAndNonRelativeNameCacheResult(
moduleOrTypeRefCache,
name,
mode,
ts.getDirectoryPath(fileName),
getRedirectReferenceForResolution(),
expected as unknown as T,
);
// Resolution in cache should be same as that is in program
ts.Debug.assert(
resolved === getProgramResolutions(name, mode),
Expand Down Expand Up @@ -526,6 +558,90 @@ export function verifyResolutionCache(
ts.Debug.assert(expected === actual, `${projectName}:: ${caption}`);
}, "dirPathToSymlinkPackageRefCount");
}

function verifyModuleOrTypeResolutionCache(
expected: ts.ModuleOrTypeReferenceResolutionCache<ts.ResolutionWithFailedLookupLocations>,
actual: ts.ModuleOrTypeReferenceResolutionCache<ts.ResolutionWithFailedLookupLocations>,
cacheType: string,
) {
verfiyCacheWithRedirects(
expected.directoryToModuleNameMap,
actual.directoryToModuleNameMap,
verifyDirectoryToModuleNameMap,
`${cacheType}:: directoryToModuleNameMap`,
);
verfiyCacheWithRedirects(
expected.moduleNameToDirectoryMap,
actual.moduleNameToDirectoryMap,
verifyModuleNameToDirectoryMap,
`${cacheType}:: moduleNameToDirectoryMap`,
);
}

function verifyDirectoryToModuleNameMap(
expected: ts.ModeAwareCache<ts.ResolutionWithFailedLookupLocations> | undefined,
actual: ts.ModeAwareCache<ts.ResolutionWithFailedLookupLocations> | undefined,
caption: string,
) {
verifyModeAwareCache(
expected,
actual,
verfiyResolution,
caption,
);
}

function verifyModuleNameToDirectoryMap(
expected: ts.PerNonRelativeNameCache<ts.ResolutionWithFailedLookupLocations> | undefined,
actual: ts.PerNonRelativeNameCache<ts.ResolutionWithFailedLookupLocations> | undefined,
caption: string,
) {
verifyMap(
expected?.directoryPathMap,
actual?.directoryPathMap,
verfiyResolution,
caption,
);
}

function verfiyResolution(
expected: ts.ResolutionWithFailedLookupLocations | undefined,
actual: ts.ResolutionWithFailedLookupLocations | undefined,
caption: string,
) {
ts.Debug.assert(
expectedToResolution.get(expected as ExpectedResolution) === actual,
`${projectName}:: ${caption} Expected resolution need to match in actual`,
);
}

function verifyModuleOrTypeResolutionCacheIsEmpty(
cache: ts.ModuleOrTypeReferenceResolutionCache<ts.ResolutionWithFailedLookupLocations>,
cacheType: string,
) {
verifyCacheWithRedirectsIsEmpty(
cache.directoryToModuleNameMap,
`${cacheType}:: directoryToModuleNameMap`,
);
verifyCacheWithRedirectsIsEmpty(
cache.moduleNameToDirectoryMap,
`${cacheType}:: moduleNameToDirectoryMap`,
);
}

function verifyCacheWithRedirectsIsEmpty<K, V>(
cache: ts.CacheWithRedirects<K, V>,
cacheType: string,
) {
ts.Debug.assert(
cache.getOwnMap().size === 0,
`${projectName}:: ${cacheType}:: ownMap should be empty`,
);
ts.Debug.assert(
cache.redirectsKeyToMap.size === 0,
`${projectName}:: ${cacheType}:: redirectsKeyToMap should be empty`,
);
}
}

function verifyMap<Key extends string, Expected, Actual>(
Expand Down Expand Up @@ -565,6 +681,31 @@ function verifyArray(
return verifySet(expected && new Set(expected), actual && new Set(actual), caption);
}

function verifyModeAwareCache<T>(
expected: ts.ModeAwareCache<T> | undefined,
actual: ts.ModeAwareCache<T> | undefined,
verifyValue: (expected: T | undefined, actual: T | undefined, caption: string) => void,
caption: string,
) {
expected?.forEach((expected, key, mode) => verifyValue(expected, actual?.get(key, mode), `${caption}:: ${key}:: ${mode}`));
actual?.forEach((actual, key, mode) => verifyValue(expected?.get(key, mode), actual, `${caption}:: ${key}:: ${mode}`));
}

function verfiyCacheWithRedirects<K extends string, V>(
expected: ts.CacheWithRedirects<K, V>,
actual: ts.CacheWithRedirects<K, V>,
verifyValue: (expected: V | undefined, actual: V | undefined, caption: string) => void,
cacheType: string,
) {
verifyMap(expected.getOwnMap(), actual.getOwnMap(), verifyValue, `${cacheType}:: ownMap`);
verifyMap(
expected.redirectsKeyToMap,
actual.redirectsKeyToMap,
(expected, actual, key) => verifyMap(expected, actual, verifyValue, key),
`${cacheType}:: redirectsKeyToMap`,
);
}

function verifyProgram(service: ts.server.ProjectService, project: ts.server.Project) {
if (service.serverMode === ts.LanguageServiceMode.Syntactic) return;
const options = project.getCompilerOptions();
Expand Down
13 changes: 8 additions & 5 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo

/** @internal */
getModuleResolutionCache(): ModuleResolutionCache | undefined {
return this.resolutionCache.getModuleResolutionCache();
return this.resolutionCache.moduleResolutionCache;
}

/** @internal */
Expand Down Expand Up @@ -1740,15 +1740,18 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
if (!newFile || (f.resolvedPath === f.path && newFile.resolvedPath !== f.path)) {
// new program does not contain this file - detach it from the project
// - remove resolutions only if the new program doesnt contain source file by the path (not resolvedPath since path is used for resolution)
this.detachScriptInfoFromProject(f.fileName, !!this.program.getSourceFileByPath(f.path), /*syncDirWatcherRemove*/ true);
this.detachScriptInfoFromProject(f.fileName, !!this.program.getSourceFileByPath(f.path));
}
}

oldProgram.forEachResolvedProjectReference(resolvedProjectReference => {
if (!this.program!.getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) {
this.detachScriptInfoFromProject(resolvedProjectReference.sourceFile.fileName, /*noRemoveResolution*/ undefined, /*syncDirWatcherRemove*/ true);
this.detachScriptInfoFromProject(resolvedProjectReference.sourceFile.fileName, /*noRemoveResolution*/ undefined);
}
});
// TODO: sheetal can we do this as part of onReleaseOldSourceFile and release parsedCommandLine?? any problem with that?
// gc caches if we removed some of the resolutions for files no longer in the program
this.resolutionCache.gcCaches();
}

// Update roots
Expand Down Expand Up @@ -1886,12 +1889,12 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
this.projectService.sendPerformanceEvent(kind, durationMs);
}

private detachScriptInfoFromProject(uncheckedFileName: string, noRemoveResolution?: boolean, syncDirWatcherRemove?: boolean) {
private detachScriptInfoFromProject(uncheckedFileName: string, noRemoveResolution?: boolean) {
const scriptInfoToDetach = this.projectService.getScriptInfo(uncheckedFileName);
if (scriptInfoToDetach) {
scriptInfoToDetach.detachFromProject(this);
if (!noRemoveResolution) {
this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path, syncDirWatcherRemove);
this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,20 +203,7 @@ Output::
Reusing resolution of module 'package-b' from '/workspace/packageC/index.ts' of old program, it was successfully resolved to '/workspace/packageB/index.d.ts'.
======== Resolving module 'package-b' from '/workspace/packageC/package.json'. ========
Module resolution kind is not specified, using 'Node10'.
Loading module 'package-b' from 'node_modules' folder, target file types: TypeScript, Declaration.
Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.
File '/workspace/packageC/node_modules/package-b/package.json' exists according to earlier cached lookups.
File '/workspace/packageC/node_modules/package-b.ts' does not exist.
File '/workspace/packageC/node_modules/package-b.tsx' does not exist.
File '/workspace/packageC/node_modules/package-b.d.ts' does not exist.
'package.json' does not have a 'typings' field.
'package.json' does not have a 'types' field.
'package.json' does not have a 'main' field.
File '/workspace/packageC/node_modules/package-b/index.ts' does not exist.
File '/workspace/packageC/node_modules/package-b/index.tsx' does not exist.
File '/workspace/packageC/node_modules/package-b/index.d.ts' exists - use it as a name resolution result.
Resolving real path for '/workspace/packageC/node_modules/package-b/index.d.ts', result '/workspace/packageB/index.d.ts'.
Resolution for module 'package-b' was found in cache from location '/workspace/packageC'.
======== Module name 'package-b' was successfully resolved to '/workspace/packageB/index.d.ts'. ========
======== Resolving module 'package-a' from '/workspace/packageC/package.json'. ========
Module resolution kind is not specified, using 'Node10'.
Expand Down
Loading

0 comments on commit 1fb73da

Please sign in to comment.