Skip to content

Commit 48aebcd

Browse files
authored
TypeReference directive reuse (#49750)
1 parent 1d96eb4 commit 48aebcd

File tree

13 files changed

+627
-72
lines changed

13 files changed

+627
-72
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
patternText, perfLogger, Push, readJson, removeExtension, removeFileExtension, removePrefix,
1616
ResolvedModuleWithFailedLookupLocations, ResolvedProjectReference, ResolvedTypeReferenceDirective,
1717
ResolvedTypeReferenceDirectiveWithFailedLookupLocations, some, sort, SourceFile, startsWith, stringContains,
18-
StringLiteralLike, supportedTSExtensionsFlat, toPath, tryExtractTSExtension, tryGetExtensionFromPath,
18+
StringLiteralLike, supportedTSExtensionsFlat, toFileNameLowerCase, toPath, tryExtractTSExtension, tryGetExtensionFromPath,
1919
tryParsePatterns, tryRemoveExtension, version, Version, versionMajorMinor, VersionRange,
2020
} from "./_namespaces/ts";
2121

@@ -795,9 +795,9 @@ export function createModeAwareCache<T>(): ModeAwareCache<T> {
795795
}
796796

797797
/** @internal */
798-
export function getResolutionName(entry: FileReference | StringLiteralLike) {
798+
export function getResolutionName(entry: string | FileReference | StringLiteralLike) {
799799
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
800-
return isStringLiteralLike(entry) ? entry.text : entry.fileName.toLowerCase();
800+
return !isString(entry) ? isStringLiteralLike(entry) ? entry.text : toFileNameLowerCase(entry.fileName) : entry;
801801
}
802802

803803
/** @internal */

src/compiler/program.ts

Lines changed: 107 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import {
5858
targetOptionDeclaration, toFileNameLowerCase, tokenToString, trace, tracing, trimStringEnd, TsConfigSourceFile,
5959
TypeChecker, typeDirectiveIsEqualTo, TypeReferenceDirectiveResolutionCache, UnparsedSource, VariableDeclaration,
6060
VariableStatement, walkUpParenthesizedExpressions, WriteFileCallback, WriteFileCallbackData,
61-
writeFileEnsuringDirectories, zipToModeAwareCache,
61+
writeFileEnsuringDirectories, zipToModeAwareCache, TypeReferenceDirectiveResolutionInfo, getResolvedTypeReferenceDirective,
6262
} from "./_namespaces/ts";
6363
import * as performance from "./_namespaces/ts.performance";
6464

@@ -564,8 +564,7 @@ export function loadWithTypeDirectiveCache<T>(names: string[] | readonly FileRef
564564
for (const name of names) {
565565
let result: T;
566566
const mode = getModeForFileReference(name, containingFileMode);
567-
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
568-
const strName = isString(name) ? name : name.fileName.toLowerCase();
567+
const strName = getResolutionName(name);
569568
const cacheKey = mode !== undefined ? `${mode}|${strName}` : strName;
570569
if (cache.has(cacheKey)) {
571570
result = cache.get(cacheKey)!;
@@ -1202,9 +1201,16 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
12021201
loadWithModeAwareCache(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo, loader);
12031202
}
12041203

1205-
let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined) => (ResolvedTypeReferenceDirective | undefined)[];
1204+
let actualResolveTypeReferenceDirectiveNamesWorker: (
1205+
typeDirectiveNames: string[] | readonly FileReference[],
1206+
containingFile: string,
1207+
redirectedReference: ResolvedProjectReference | undefined,
1208+
containingFileMode: SourceFile["impliedNodeFormat"] | undefined,
1209+
resolutionInfo: TypeReferenceDirectiveResolutionInfo | undefined,
1210+
) => (ResolvedTypeReferenceDirective | undefined)[];
12061211
if (host.resolveTypeReferenceDirectives) {
1207-
actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference, containingFileMode) => host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options, containingFileMode);
1212+
actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference, containingFileMode, resolutionInfo) =>
1213+
host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options, containingFileMode, resolutionInfo);
12081214
}
12091215
else {
12101216
typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache());
@@ -1318,7 +1324,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
13181324
// This containingFilename needs to match with the one used in managed-side
13191325
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory();
13201326
const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile);
1321-
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename);
1327+
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(typeReferences, containingFilename);
13221328
for (let i = 0; i < typeReferences.length; i++) {
13231329
// under node16/nodenext module resolution, load `types`/ata include names as cjs resolution results by passing an `undefined` mode
13241330
processTypeReferenceDirective(typeReferences[i], /*mode*/ undefined, resolutions[i], { kind: FileIncludeKind.AutomaticTypeDirectiveFile, typeReference: typeReferences[i], packageId: resolutions[i]?.packageId });
@@ -1516,14 +1522,14 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
15161522
return result;
15171523
}
15181524

1519-
function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] {
1525+
function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile, resolutionInfo: TypeReferenceDirectiveResolutionInfo | undefined): readonly (ResolvedTypeReferenceDirective | undefined)[] {
15201526
if (!typeDirectiveNames.length) return [];
15211527
const containingFileName = !isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile;
15221528
const redirectedReference = !isString(containingFile) ? getRedirectReferenceForResolution(containingFile) : undefined;
15231529
const containingFileMode = !isString(containingFile) ? containingFile.impliedNodeFormat : undefined;
15241530
tracing?.push(tracing.Phase.Program, "resolveTypeReferenceDirectiveNamesWorker", { containingFileName });
15251531
performance.mark("beforeResolveTypeReference");
1526-
const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference, containingFileMode);
1532+
const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference, containingFileMode, resolutionInfo);
15271533
performance.mark("afterResolveTypeReference");
15281534
performance.measure("ResolveTypeReference", "beforeResolveTypeReference", "afterResolveTypeReference");
15291535
tracing?.pop();
@@ -1759,6 +1765,96 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
17591765
}
17601766
}
17611767

1768+
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: readonly FileReference[], containingFile: SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[];
1769+
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: string[], containingFile: string): readonly (ResolvedTypeReferenceDirective | undefined)[];
1770+
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] {
1771+
if (structureIsReused === StructureIsReused.Not) {
1772+
// If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules,
1773+
// the best we can do is fallback to the default logic.
1774+
return resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFile, /*resolutionInfo*/ undefined);
1775+
}
1776+
1777+
const oldSourceFile = !isString(containingFile) ? oldProgram && oldProgram.getSourceFile(containingFile.fileName) : undefined;
1778+
if (!isString(containingFile)) {
1779+
if (oldSourceFile !== containingFile && containingFile.resolvedTypeReferenceDirectiveNames) {
1780+
// `file` was created for the new program.
1781+
//
1782+
// We only set `file.resolvedTypeReferenceDirectiveNames` via work from the current function,
1783+
// so it is defined iff we already called the current function on `file`.
1784+
// That call happened no later than the creation of the `file` object,
1785+
// which per above occurred during the current program creation.
1786+
// Since we assume the filesystem does not change during program creation,
1787+
// it is safe to reuse resolutions from the earlier call.
1788+
const result: (ResolvedTypeReferenceDirective | undefined)[] = [];
1789+
for (const typeDirectiveName of typeDirectiveNames as readonly FileReference[]) {
1790+
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
1791+
const resolvedTypeReferenceDirective = containingFile.resolvedTypeReferenceDirectiveNames.get(getResolutionName(typeDirectiveName), typeDirectiveName.resolutionMode || containingFile.impliedNodeFormat);
1792+
result.push(resolvedTypeReferenceDirective);
1793+
}
1794+
return result;
1795+
}
1796+
}
1797+
1798+
/** An ordered list of module names for which we cannot recover the resolution. */
1799+
let unknownTypeReferenceDirectiveNames: string[] | FileReference[] | undefined;
1800+
let result: (ResolvedTypeReferenceDirective | undefined)[] | undefined;
1801+
let reusedNames: (string | FileReference)[] | undefined;
1802+
const containingSourceFile = !isString(containingFile) ? containingFile : undefined;
1803+
const canReuseResolutions = !isString(containingFile) ?
1804+
containingFile === oldSourceFile && !hasInvalidatedResolutions(oldSourceFile.path) :
1805+
!hasInvalidatedResolutions(toPath(containingFile));
1806+
for (let i = 0; i < typeDirectiveNames.length; i++) {
1807+
const entry = typeDirectiveNames[i];
1808+
if (canReuseResolutions) {
1809+
const typeDirectiveName = getResolutionName(entry);
1810+
const mode = getModeForFileReference(entry, containingSourceFile?.impliedNodeFormat);
1811+
const oldResolvedTypeReferenceDirective = getResolvedTypeReferenceDirective(oldSourceFile, typeDirectiveName, mode);
1812+
if (oldResolvedTypeReferenceDirective) {
1813+
if (isTraceEnabled(options, host)) {
1814+
trace(host,
1815+
oldResolvedTypeReferenceDirective.packageId ?
1816+
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
1817+
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2,
1818+
typeDirectiveName,
1819+
!isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile,
1820+
oldResolvedTypeReferenceDirective.resolvedFileName,
1821+
oldResolvedTypeReferenceDirective.packageId && packageIdToString(oldResolvedTypeReferenceDirective.packageId)
1822+
);
1823+
}
1824+
(result ??= new Array(typeDirectiveNames.length))[i] = oldResolvedTypeReferenceDirective;
1825+
(reusedNames ??= []).push(entry);
1826+
continue;
1827+
}
1828+
}
1829+
// Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.
1830+
(unknownTypeReferenceDirectiveNames ??= []).push(entry as FileReference & string);
1831+
}
1832+
1833+
if (!unknownTypeReferenceDirectiveNames) return result || emptyArray;
1834+
const resolutions = resolveTypeReferenceDirectiveNamesWorker(
1835+
unknownTypeReferenceDirectiveNames,
1836+
containingFile,
1837+
{ names: unknownTypeReferenceDirectiveNames, reusedNames }
1838+
);
1839+
1840+
// Combine results of resolutions
1841+
if (!result) {
1842+
// There were no unresolved resolutions.
1843+
Debug.assert(resolutions.length === typeDirectiveNames.length);
1844+
return resolutions;
1845+
}
1846+
1847+
let j = 0;
1848+
for (let i = 0; i < result.length; i++) {
1849+
if (!result[i]) {
1850+
result[i] = resolutions[j];
1851+
j++;
1852+
}
1853+
}
1854+
Debug.assert(j === resolutions.length);
1855+
return result;
1856+
}
1857+
17621858
function canReuseProjectReferences(): boolean {
17631859
return !forEachProjectReference(
17641860
oldProgram!.getProjectReferences(),
@@ -1962,7 +2058,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
19622058
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
19632059
}
19642060
const typesReferenceDirectives = newSourceFile.typeReferenceDirectives;
1965-
const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile);
2061+
const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesReusingOldState(typesReferenceDirectives, newSourceFile);
19662062
// ensure that types resolutions are still correct
19672063
const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, newSourceFile, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
19682064
if (typeReferenceResolutionsChanged) {
@@ -3229,13 +3325,13 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
32293325
return;
32303326
}
32313327

3232-
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file);
3328+
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectives, file);
32333329
for (let index = 0; index < typeDirectives.length; index++) {
32343330
const ref = file.typeReferenceDirectives[index];
32353331
const resolvedTypeReferenceDirective = resolutions[index];
32363332
// store resolved type directive on the file
32373333
const fileName = toFileNameLowerCase(ref.fileName);
3238-
setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective);
3334+
setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective, getModeForFileReference(ref, file.impliedNodeFormat));
32393335
const mode = ref.resolutionMode || file.impliedNodeFormat;
32403336
if (mode && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) {
32413337
programDiagnostics.add(createDiagnosticForRange(file, ref, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext));

0 commit comments

Comments
 (0)