Skip to content

Commit

Permalink
Resolutions cache stays for lifetime..
Browse files Browse the repository at this point in the history
  • Loading branch information
sheetalkamat committed Jul 8, 2024
1 parent 04b994f commit ce2e752
Show file tree
Hide file tree
Showing 35 changed files with 533 additions and 369 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
300 changes: 165 additions & 135 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
2 changes: 1 addition & 1 deletion 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -277,26 +277,7 @@ File '/user/username/package.json' does not exist according to earlier cached lo
File '/user/package.json' does not exist according to earlier cached lookups.
File '/package.json' does not exist according to earlier cached lookups.
======== Resolving module 'pkg' from '/user/username/projects/myproject/a.ts'. ========
Explicitly specified module resolution kind: 'Node16'.
Resolving in ESM mode with conditions 'import', 'types', 'node'.
File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups.
File '/user/username/projects/package.json' does not exist according to earlier cached lookups.
File '/user/username/package.json' does not exist according to earlier cached lookups.
File '/user/package.json' does not exist according to earlier cached lookups.
File '/package.json' does not exist according to earlier cached lookups.
Loading module 'pkg' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration.
Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.
File '/user/username/projects/myproject/node_modules/pkg/package.json' exists according to earlier cached lookups.
Entering conditional exports.
Matched 'exports' condition 'import'.
Using 'exports' subpath '.' with target './import.js'.
File name '/user/username/projects/myproject/node_modules/pkg/import.js' has a '.js' extension - stripping it.
File '/user/username/projects/myproject/node_modules/pkg/import.ts' does not exist.
File '/user/username/projects/myproject/node_modules/pkg/import.tsx' does not exist.
File '/user/username/projects/myproject/node_modules/pkg/import.d.ts' exists - use it as a name resolution result.
Resolved under condition 'import'.
Exiting conditional exports.
Resolving real path for '/user/username/projects/myproject/node_modules/pkg/import.d.ts', result '/user/username/projects/myproject/node_modules/pkg/import.d.ts'.
Resolution for module 'pkg' was found in cache from location '/user/username/projects/myproject'.
======== Module name 'pkg' was successfully resolved to '/user/username/projects/myproject/node_modules/pkg/import.d.ts' with Package ID 'pkg/[email protected]'. ========
File '/user/username/projects/myproject/node_modules/pkg/package.json' exists according to earlier cached lookups.
File '/user/username/projects/myproject/package.json' does not exist according to earlier cached lookups.
Expand Down
Loading

0 comments on commit ce2e752

Please sign in to comment.