Skip to content

Commit 9c093c1

Browse files
authored
Do not reuse ambient module name resolution from other files while determining if resolution can be reused (#59243)
1 parent 4641004 commit 9c093c1

17 files changed

+640
-105
lines changed

src/compiler/checker.ts

-5
Original file line numberDiff line numberDiff line change
@@ -1794,11 +1794,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
17941794
tryGetMemberInModuleExports: (name, symbol) => tryGetMemberInModuleExports(escapeLeadingUnderscores(name), symbol),
17951795
tryGetMemberInModuleExportsAndProperties: (name, symbol) => tryGetMemberInModuleExportsAndProperties(escapeLeadingUnderscores(name), symbol),
17961796
tryFindAmbientModule: moduleName => tryFindAmbientModule(moduleName, /*withAugmentations*/ true),
1797-
tryFindAmbientModuleWithoutAugmentations: moduleName => {
1798-
// we deliberately exclude augmentations
1799-
// since we are only interested in declarations of the module itself
1800-
return tryFindAmbientModule(moduleName, /*withAugmentations*/ false);
1801-
},
18021797
getApparentType,
18031798
getUnionType,
18041799
isTypeAssignableTo,

src/compiler/diagnosticMessages.json

-4
Original file line numberDiff line numberDiff line change
@@ -5113,10 +5113,6 @@
51135113
"category": "Message",
51145114
"code": 6144
51155115
},
5116-
"Module '{0}' was resolved as ambient module declared in '{1}' since this file was not modified.": {
5117-
"category": "Message",
5118-
"code": 6145
5119-
},
51205116
"Specify the JSX factory function to use when targeting 'react' JSX emit, e.g. 'React.createElement' or 'h'.": {
51215117
"category": "Message",
51225118
"code": 6146

src/compiler/program.ts

+24-59
Original file line numberDiff line numberDiff line change
@@ -1545,7 +1545,6 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
15451545
let commonSourceDirectory: string;
15461546
let typeChecker: TypeChecker;
15471547
let classifiableNames: Set<__String>;
1548-
const ambientModuleNameToUnmodifiedFileName = new Map<string, string>();
15491548
let fileReasons = createMultiMap<Path, FileIncludeReason>();
15501549
let filesWithReferencesProcessed: Set<Path> | undefined;
15511550
let fileReasonsToChain: Map<Path, FileReasonToChainCache> | undefined;
@@ -2250,52 +2249,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
22502249
canReuseResolutionsInFile: () =>
22512250
containingFile === oldProgram?.getSourceFile(containingFile.fileName) &&
22522251
!hasInvalidatedResolutions(containingFile.path),
2253-
isEntryResolvingToAmbientModule: moduleNameResolvesToAmbientModule,
2252+
resolveToOwnAmbientModule: true,
22542253
});
22552254
}
22562255

2257-
function moduleNameResolvesToAmbientModule(moduleName: StringLiteralLike, file: SourceFile) {
2258-
// We know moduleName resolves to an ambient module provided that moduleName:
2259-
// - is in the list of ambient modules locally declared in the current source file.
2260-
// - resolved to an ambient module in the old program whose declaration is in an unmodified file
2261-
// (so the same module declaration will land in the new program)
2262-
if (contains(file.ambientModuleNames, moduleName.text)) {
2263-
if (isTraceEnabled(options, host)) {
2264-
trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName.text, getNormalizedAbsolutePath(file.originalFileName, currentDirectory));
2265-
}
2266-
return true;
2267-
}
2268-
else {
2269-
return moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, file);
2270-
}
2271-
}
2272-
2273-
// If we change our policy of rechecking failed lookups on each program create,
2274-
// we should adjust the value returned here.
2275-
function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: StringLiteralLike, file: SourceFile): boolean {
2276-
const resolutionToFile = oldProgram?.getResolvedModule(file, moduleName.text, getModeForUsageLocation(file, moduleName))?.resolvedModule;
2277-
const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName);
2278-
if (resolutionToFile && resolvedFile) {
2279-
// In the old program, we resolved to an ambient module that was in the same
2280-
// place as we expected to find an actual module file.
2281-
// We actually need to return 'false' here even though this seems like a 'true' case
2282-
// because the normal module resolution algorithm will find this anyway.
2283-
return false;
2284-
}
2285-
2286-
// at least one of declarations should come from non-modified source file
2287-
const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName.text);
2288-
2289-
if (!unmodifiedFile) {
2290-
return false;
2291-
}
2292-
2293-
if (isTraceEnabled(options, host)) {
2294-
trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName.text, unmodifiedFile);
2295-
}
2296-
return true;
2297-
}
2298-
22992256
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: readonly FileReference[], containingFile: SourceFile): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[];
23002257
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: readonly string[], containingFile: string): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[];
23012258
function resolveTypeReferenceDirectiveNamesReusingOldState<T extends string | FileReference>(typeDirectiveNames: readonly T[], containingFile: string | SourceFile): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[] {
@@ -2333,7 +2290,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
23332290
getResolutionFromOldProgram: (name: string, mode: ResolutionMode) => Resolution | undefined;
23342291
getResolved: (oldResolution: Resolution) => ResolutionWithResolvedFileName | undefined;
23352292
canReuseResolutionsInFile: () => boolean;
2336-
isEntryResolvingToAmbientModule?: (entry: Entry, containingFile: SourceFileOrString) => boolean;
2293+
resolveToOwnAmbientModule?: true;
23372294
}
23382295

23392296
function resolveNamesReusingOldState<Entry, SourceFileOrString, SourceFileOrUndefined extends SourceFile | undefined, Resolution>({
@@ -2346,10 +2303,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
23462303
getResolutionFromOldProgram,
23472304
getResolved,
23482305
canReuseResolutionsInFile,
2349-
isEntryResolvingToAmbientModule,
2306+
resolveToOwnAmbientModule,
23502307
}: ResolveNamesReusingOldStateInput<Entry, SourceFileOrString, SourceFileOrUndefined, Resolution>): readonly Resolution[] {
23512308
if (!entries.length) return emptyArray;
2352-
if (structureIsReused === StructureIsReused.Not && (!isEntryResolvingToAmbientModule || !containingSourceFile!.ambientModuleNames.length)) {
2309+
if (structureIsReused === StructureIsReused.Not && (!resolveToOwnAmbientModule || !containingSourceFile!.ambientModuleNames.length)) {
23532310
// If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules,
23542311
// the best we can do is fallback to the default logic.
23552312
return resolutionWorker(
@@ -2394,14 +2351,27 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
23942351
continue;
23952352
}
23962353
}
2397-
if (isEntryResolvingToAmbientModule?.(entry, containingFile)) {
2398-
(result ??= new Array(entries.length))[i] = emptyResolution;
2399-
}
2400-
else {
2401-
// Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.
2402-
(unknownEntries ??= []).push(entry);
2403-
(unknownEntryIndices ??= []).push(i);
2354+
if (resolveToOwnAmbientModule) {
2355+
const name = nameAndModeGetter.getName(entry);
2356+
// We know moduleName resolves to an ambient module provided that moduleName:
2357+
// - is in the list of ambient modules locally declared in the current source file.
2358+
if (contains(containingSourceFile!.ambientModuleNames, name)) {
2359+
if (isTraceEnabled(options, host)) {
2360+
trace(
2361+
host,
2362+
Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1,
2363+
name,
2364+
getNormalizedAbsolutePath(containingSourceFile!.originalFileName, currentDirectory),
2365+
);
2366+
}
2367+
(result ??= new Array(entries.length))[i] = emptyResolution;
2368+
continue;
2369+
}
24042370
}
2371+
2372+
// Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.
2373+
(unknownEntries ??= []).push(entry);
2374+
(unknownEntryIndices ??= []).push(i);
24052375
}
24062376

24072377
if (!unknownEntries) return result!;
@@ -2586,11 +2556,6 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
25862556
// add file to the modified list so that we will resolve it later
25872557
modifiedSourceFiles.push(newSourceFile);
25882558
}
2589-
else {
2590-
for (const moduleName of oldSourceFile.ambientModuleNames) {
2591-
ambientModuleNameToUnmodifiedFileName.set(moduleName, oldSourceFile.fileName);
2592-
}
2593-
}
25942559

25952560
// if file has passed all checks it should be safe to reuse it
25962561
newSourceFiles.push(newSourceFile);

src/compiler/resolutionCache.ts

+7-15
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
CompilerOptions,
77
createModeAwareCache,
88
createModuleResolutionCache,
9-
createMultiMap,
109
createTypeReferenceDirectiveResolutionCache,
1110
createTypeReferenceResolutionLoader,
1211
Debug,
@@ -572,7 +571,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
572571
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
573572
let filesWithInvalidatedResolutions: Set<Path> | undefined;
574573
let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyMap<Path, readonly string[]> | undefined;
575-
const nonRelativeExternalModuleResolutions = createMultiMap<string, ResolutionWithFailedLookupLocations>();
574+
const nonRelativeExternalModuleResolutions = new Set<ResolutionWithFailedLookupLocations>();
576575

577576
const resolutionsWithFailedLookups = new Set<ResolutionWithFailedLookupLocations>();
578577
const resolutionsWithOnlyAffectingLocations = new Set<ResolutionWithFailedLookupLocations>();
@@ -755,8 +754,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
755754
libraryResolutionCache.clearAllExceptPackageJsonInfoCache();
756755
// perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
757756
// (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
758-
nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions);
759-
nonRelativeExternalModuleResolutions.clear();
757+
watchFailedLookupLocationOfNonRelativeModuleResolutions();
760758
isSymlinkCache.clear();
761759
}
762760

@@ -776,8 +774,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
776774
function finishCachingPerDirectoryResolution(newProgram: Program | undefined, oldProgram: Program | undefined) {
777775
filesWithInvalidatedNonRelativeUnresolvedImports = undefined;
778776
allModuleAndTypeResolutionsAreInvalidated = false;
779-
nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions);
780-
nonRelativeExternalModuleResolutions.clear();
777+
watchFailedLookupLocationOfNonRelativeModuleResolutions();
781778
// Update file watches
782779
if (newProgram !== oldProgram) {
783780
cleanupLibResolutionWatching(newProgram);
@@ -1103,7 +1100,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
11031100
watchFailedLookupLocationOfResolution(resolution);
11041101
}
11051102
else {
1106-
nonRelativeExternalModuleResolutions.add(name, resolution);
1103+
nonRelativeExternalModuleResolutions.add(resolution);
11071104
}
11081105
const resolved = getResolutionWithResolvedFileName(resolution);
11091106
if (resolved && resolved.resolvedFileName) {
@@ -1236,14 +1233,9 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
12361233
packageJsonMap?.delete(resolutionHost.toPath(path));
12371234
}
12381235

1239-
function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) {
1240-
const program = resolutionHost.getCurrentProgram();
1241-
if (!program || !program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name)) {
1242-
resolutions.forEach(watchFailedLookupLocationOfResolution);
1243-
}
1244-
else {
1245-
resolutions.forEach(resolution => watchAffectingLocationsOfResolution(resolution, /*addToResolutionsWithOnlyAffectingLocations*/ true));
1246-
}
1236+
function watchFailedLookupLocationOfNonRelativeModuleResolutions() {
1237+
nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfResolution);
1238+
nonRelativeExternalModuleResolutions.clear();
12471239
}
12481240

12491241
function createDirectoryWatcherForPackageDir(

src/compiler/types.ts

-1
Original file line numberDiff line numberDiff line change
@@ -5202,7 +5202,6 @@ export interface TypeChecker {
52025202
/** @internal */ createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: SignatureDeclaration): IndexInfo;
52035203
/** @internal */ isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasToMarkVisible: boolean): SymbolAccessibilityResult;
52045204
/** @internal */ tryFindAmbientModule(moduleName: string): Symbol | undefined;
5205-
/** @internal */ tryFindAmbientModuleWithoutAugmentations(moduleName: string): Symbol | undefined;
52065205

52075206
/** @internal */ getSymbolWalker(accept?: (symbol: Symbol) => boolean): SymbolWalker;
52085207

src/testRunner/unittests/helpers/tscWatch.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ export interface TscWatchCompileChange<T extends ts.BuilderProgram = ts.EmitAndS
4040
programs: readonly CommandLineProgram[],
4141
watchOrSolution: WatchOrSolution<T>,
4242
) => void;
43-
// TODO:: sheetal: Needing these fields are technically issues that need to be fixed later
44-
skipStructureCheck?: true;
4543
}
4644
export interface TscWatchCheckOptions {
4745
baselineSourceMap?: boolean;
@@ -214,7 +212,7 @@ export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanti
214212
});
215213

216214
if (edits) {
217-
for (const { caption, edit, timeouts, skipStructureCheck } of edits) {
215+
for (const { caption, edit, timeouts } of edits) {
218216
applyEdit(sys, baseline, edit, caption);
219217
timeouts(sys, programs, watchOrSolution);
220218
programs = watchBaseline({
@@ -225,7 +223,7 @@ export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanti
225223
baselineSourceMap,
226224
baselineDependencies,
227225
caption,
228-
resolutionCache: !skipStructureCheck ? (watchOrSolution as ts.WatchOfConfigFile<T> | undefined)?.getResolutionCache?.() : undefined,
226+
resolutionCache: (watchOrSolution as ts.WatchOfConfigFile<T> | undefined)?.getResolutionCache?.(),
229227
useSourceOfProjectReferenceRedirect,
230228
});
231229
}

src/testRunner/unittests/reuseProgramStructure.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ describe("unittests:: reuseProgramStructure:: General", () => {
367367
runBaseline("fetches imports after npm install", baselines);
368368
});
369369

370-
it("can reuse ambient module declarations from non-modified files", () => {
370+
it("should not reuse ambient module declarations from non-modified files", () => {
371371
const files = [
372372
{ name: "/a/b/app.ts", text: SourceText.New("", "import * as fs from 'fs'", "") },
373373
{ name: "/a/b/node.d.ts", text: SourceText.New("", "", "declare module 'fs' {}") },
@@ -387,7 +387,7 @@ describe("unittests:: reuseProgramStructure:: General", () => {
387387
f[1].text = f[1].text.updateProgram("declare var process: any");
388388
});
389389
baselineProgram(baselines, program3);
390-
runBaseline("can reuse ambient module declarations from non-modified files", baselines);
390+
runBaseline("should not reuse ambient module declarations from non-modified files", baselines);
391391
});
392392

393393
it("can reuse module resolutions from non-modified files", () => {

src/testRunner/unittests/tscWatch/moduleResolution.ts

+73-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
libFile,
1414
} from "../helpers/virtualFileSystemWithWatch.js";
1515

16-
describe("unittests:: tsc-watch:: moduleResolution", () => {
16+
describe("unittests:: tsc-watch:: moduleResolution::", () => {
1717
verifyTscWatch({
1818
scenario: "moduleResolution",
1919
subScenario: `watches for changes to package-json main fields`,
@@ -640,4 +640,76 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
640640
},
641641
],
642642
});
643+
644+
verifyTscWatch({
645+
scenario: "moduleResolution",
646+
subScenario: "ambient module names are resolved correctly",
647+
commandLineArgs: ["-w", "--extendedDiagnostics", "--explainFiles"],
648+
sys: () =>
649+
createWatchedSystem({
650+
"/home/src/project/tsconfig.json": jsonToReadableText({
651+
compilerOptions: {
652+
noEmit: true,
653+
traceResolution: true,
654+
},
655+
include: ["**/*.ts"],
656+
}),
657+
"/home/src/project/witha/node_modules/mymodule/index.d.ts": Utils.dedent`
658+
declare module 'mymodule' {
659+
export function readFile(): void;
660+
}
661+
declare module 'mymoduleutils' {
662+
export function promisify(): void;
663+
}
664+
`,
665+
"/home/src/project/witha/a.ts": Utils.dedent`
666+
import { readFile } from 'mymodule';
667+
import { promisify, promisify2 } from 'mymoduleutils';
668+
readFile();
669+
promisify();
670+
promisify2();
671+
`,
672+
"/home/src/project/withb/node_modules/mymodule/index.d.ts": Utils.dedent`
673+
declare module 'mymodule' {
674+
export function readFile(): void;
675+
}
676+
declare module 'mymoduleutils' {
677+
export function promisify2(): void;
678+
}
679+
`,
680+
"/home/src/project/withb/b.ts": Utils.dedent`
681+
import { readFile } from 'mymodule';
682+
import { promisify, promisify2 } from 'mymoduleutils';
683+
readFile();
684+
promisify();
685+
promisify2();
686+
`,
687+
[libFile.path]: libFile.content,
688+
}, { currentDirectory: "/home/src/project" }),
689+
edits: [
690+
{
691+
caption: "remove a file that will remove module augmentation",
692+
edit: sys => {
693+
sys.replaceFileText("/home/src/project/withb/b.ts", `import { readFile } from 'mymodule';`, "");
694+
sys.deleteFile("/home/src/project/withb/node_modules/mymodule/index.d.ts");
695+
},
696+
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
697+
},
698+
{
699+
caption: "write a file that will add augmentation",
700+
edit: sys => {
701+
sys.ensureFileOrFolder({
702+
path: "/home/src/project/withb/node_modules/mymoduleutils/index.d.ts",
703+
content: Utils.dedent`
704+
declare module 'mymoduleutils' {
705+
export function promisify2(): void;
706+
}
707+
`,
708+
});
709+
sys.replaceFileText("/home/src/project/withb/b.ts", `readFile();`, "");
710+
},
711+
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
712+
},
713+
],
714+
});
643715
});

0 commit comments

Comments
 (0)