Skip to content

Commit 965f40f

Browse files
committed
Use builder state in the semantic/emit builder as well
1 parent bb0fc0d commit 965f40f

4 files changed

Lines changed: 219 additions & 529 deletions

File tree

src/compiler/builder.ts

Lines changed: 116 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,118 @@ namespace ts {
77
BuilderKindEmitAndSemanticDiagnostics
88
}
99

10-
export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder;
11-
export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder;
12-
export function createBuilder(host: BuilderHost, builderKind: BuilderKind) {
13-
/**
14-
* State corresponding to all the file references and shapes of the module etc
15-
*/
16-
const state = createBuilderStateOld({
17-
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(),
18-
createHash: host.createHash,
19-
onUpdateProgramInitialized,
20-
onSourceFileAdd: addToChangedFilesSet,
21-
onSourceFileChanged: path => { addToChangedFilesSet(path); deleteSemanticDiagnostics(path); },
22-
onSourceFileRemoved: deleteSemanticDiagnostics
23-
});
24-
10+
interface BuilderStateWithChangedFiles extends BuilderState {
2511
/**
2612
* Cache of semantic diagnostics for files with their Path being the key
2713
*/
28-
const semanticDiagnosticsPerFile = createMap<ReadonlyArray<Diagnostic>>();
29-
14+
semanticDiagnosticsPerFile: Map<ReadonlyArray<Diagnostic>> | undefined;
3015
/**
3116
* The map has key by source file's path that has been changed
3217
*/
33-
const changedFilesSet = createMap<true>();
34-
18+
changedFilesSet: Map<true>;
3519
/**
3620
* Set of affected files being iterated
3721
*/
38-
let affectedFiles: ReadonlyArray<SourceFile> | undefined;
22+
affectedFiles: ReadonlyArray<SourceFile> | undefined;
3923
/**
4024
* Current index to retrieve affected file from
4125
*/
42-
let affectedFilesIndex = 0;
26+
affectedFilesIndex: number | undefined;
4327
/**
4428
* Current changed file for iterating over affected files
4529
*/
46-
let currentChangedFilePath: Path | undefined;
30+
currentChangedFilePath: Path | undefined;
4731
/**
4832
* Map of file signatures, with key being file path, calculated while getting current changed file's affected files
4933
* These will be commited whenever the iteration through affected files of current changed file is complete
5034
*/
51-
const currentAffectedFilesSignatures = createMap<string>();
35+
currentAffectedFilesSignatures: Map<string> | undefined;
5236
/**
5337
* Already seen affected files
5438
*/
55-
const seenAffectedFiles = createMap<true>();
39+
seenAffectedFiles: Map<true> | undefined;
40+
}
41+
42+
function hasSameKeys<T, U>(map1: ReadonlyMap<T> | undefined, map2: ReadonlyMap<U> | undefined) {
43+
if (map1 === undefined) {
44+
return map2 === undefined;
45+
}
46+
if (map2 === undefined) {
47+
return map1 === undefined;
48+
}
49+
// Has same size and every key is present in both maps
50+
return map1.size === map2.size && !forEachKey(map1, key => !map2.has(key));
51+
}
52+
53+
/**
54+
* Create the state so that we can iterate on changedFiles/affected files
55+
*/
56+
function createBuilderStateWithChangedFiles(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly<BuilderStateWithChangedFiles>): BuilderStateWithChangedFiles {
57+
const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderStateWithChangedFiles;
58+
const compilerOptions = newProgram.getCompilerOptions();
59+
if (!compilerOptions.outFile && !compilerOptions.out) {
60+
state.semanticDiagnosticsPerFile = createMap<ReadonlyArray<Diagnostic>>();
61+
}
62+
state.changedFilesSet = createMap<true>();
63+
const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState);
64+
const canCopySemanticDiagnostics = useOldState && oldState.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile;
65+
if (useOldState) {
66+
// Verify the sanity of old state
67+
if (!oldState.currentChangedFilePath) {
68+
Debug.assert(!oldState.affectedFiles && (!oldState.currentAffectedFilesSignatures || !oldState.currentAffectedFilesSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated");
69+
}
70+
if (canCopySemanticDiagnostics) {
71+
Debug.assert(!forEachKey(oldState.changedFilesSet, path => oldState.semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files");
72+
}
73+
74+
// Copy old state's changed files set
75+
copyEntries(oldState.changedFilesSet, state.changedFilesSet);
76+
}
77+
78+
// Update changed files and copy semantic diagnostics if we can
79+
const referencedMap = state.referencedMap;
80+
const oldReferencedMap = useOldState && oldState.referencedMap;
81+
state.fileInfos.forEach((info, sourceFilePath) => {
82+
let oldInfo: Readonly<BuilderState.FileInfo>;
83+
let newReferences: BuilderState.ReferencedSet;
84+
85+
// if not using old state, every file is changed
86+
if (!useOldState ||
87+
// File wasnt present in old state
88+
!(oldInfo = oldState.fileInfos.get(sourceFilePath)) ||
89+
// versions dont match
90+
oldInfo.version !== info.version ||
91+
// Referenced files changed
92+
!hasSameKeys(newReferences = referencedMap && referencedMap.get(sourceFilePath), oldReferencedMap && oldReferencedMap.get(sourceFilePath)) ||
93+
// Referenced file was deleted in the new program
94+
newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState.fileInfos.has(path))) {
95+
// Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated
96+
state.changedFilesSet.set(sourceFilePath, true);
97+
}
98+
else if (canCopySemanticDiagnostics) {
99+
// Unchanged file copy diagnostics
100+
const diagnostics = oldState.semanticDiagnosticsPerFile.get(sourceFilePath);
101+
if (diagnostics) {
102+
state.semanticDiagnosticsPerFile.set(sourceFilePath, diagnostics);
103+
}
104+
}
105+
});
106+
107+
return state;
108+
}
109+
110+
export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder;
111+
export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder;
112+
export function createBuilder(host: BuilderHost, builderKind: BuilderKind) {
113+
/**
114+
* Create the canonical file name for identity
115+
*/
116+
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
117+
/**
118+
* Computing hash to for signature verification
119+
*/
120+
const computeHash = host.createHash || identity;
121+
let state: BuilderStateWithChangedFiles;
56122

57123
switch (builderKind) {
58124
case BuilderKind.BuilderKindSemanticDiagnostics:
@@ -81,55 +147,12 @@ namespace ts {
81147
};
82148
}
83149

84-
/**
85-
* Initialize changedFiles, affected files set, cached diagnostics, signatures
86-
*/
87-
function onUpdateProgramInitialized(isModuleEmitChanged: boolean) {
88-
if (isModuleEmitChanged) {
89-
// Changes in the module emit, clear out everything and initialize as if first time
90-
91-
// Clear file information and semantic diagnostics
92-
semanticDiagnosticsPerFile.clear();
93-
94-
// Clear changed files and affected files information
95-
changedFilesSet.clear();
96-
affectedFiles = undefined;
97-
currentChangedFilePath = undefined;
98-
currentAffectedFilesSignatures.clear();
99-
}
100-
else {
101-
if (currentChangedFilePath) {
102-
// Remove the diagnostics for all the affected files since we should resume the state such that
103-
// the whole iteration on currentChangedFile never happened
104-
affectedFiles.forEach(sourceFile => deleteSemanticDiagnostics(sourceFile.path));
105-
affectedFiles = undefined;
106-
currentAffectedFilesSignatures.clear();
107-
}
108-
else {
109-
// Verify the sanity of old state
110-
Debug.assert(!affectedFiles && !currentAffectedFilesSignatures.size, "Cannot reuse if only few affected files of currentChangedFile were iterated");
111-
}
112-
Debug.assert(!forEachKey(changedFilesSet, path => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files");
113-
}
114-
}
115-
116-
/**
117-
* Add file to the changed files set
118-
*/
119-
function addToChangedFilesSet(path: Path) {
120-
changedFilesSet.set(path, true);
121-
}
122-
123-
function deleteSemanticDiagnostics(path: Path) {
124-
semanticDiagnosticsPerFile.delete(path);
125-
}
126-
127150
/**
128151
* Update current state to reflect new program
129152
* Updates changed files, references, file infos etc which happens through the state callbacks
130153
*/
131154
function updateProgram(newProgram: Program) {
132-
state.updateProgram(newProgram);
155+
state = createBuilderStateWithChangedFiles(newProgram, getCanonicalFileName, state);
133156
}
134157

135158
/**
@@ -140,11 +163,15 @@ namespace ts {
140163
*/
141164
function getNextAffectedFile(programOfThisState: Program, cancellationToken: CancellationToken | undefined): SourceFile | Program | undefined {
142165
while (true) {
166+
const { affectedFiles } = state;
143167
if (affectedFiles) {
168+
const { seenAffectedFiles, semanticDiagnosticsPerFile } = state;
169+
let { affectedFilesIndex } = state;
144170
while (affectedFilesIndex < affectedFiles.length) {
145171
const affectedFile = affectedFiles[affectedFilesIndex];
146172
if (!seenAffectedFiles.has(affectedFile.path)) {
147173
// Set the next affected file as seen and remove the cached semantic diagnostics
174+
state.affectedFilesIndex = affectedFilesIndex;
148175
semanticDiagnosticsPerFile.delete(affectedFile.path);
149176
return affectedFile;
150177
}
@@ -153,16 +180,16 @@ namespace ts {
153180
}
154181

155182
// Remove the changed file from the change set
156-
changedFilesSet.delete(currentChangedFilePath);
157-
currentChangedFilePath = undefined;
183+
state.changedFilesSet.delete(state.currentChangedFilePath);
184+
state.currentChangedFilePath = undefined;
158185
// Commit the changes in file signature
159-
state.updateSignaturesFromCache(currentAffectedFilesSignatures);
160-
currentAffectedFilesSignatures.clear();
161-
affectedFiles = undefined;
186+
BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures);
187+
state.currentAffectedFilesSignatures.clear();
188+
state.affectedFiles = undefined;
162189
}
163190

164191
// Get next changed file
165-
const nextKey = changedFilesSet.keys().next();
192+
const nextKey = state.changedFilesSet.keys().next();
166193
if (nextKey.done) {
167194
// Done
168195
return undefined;
@@ -172,16 +199,17 @@ namespace ts {
172199
// With --out or --outFile all outputs go into single file
173200
// so operations are performed directly on program, return program
174201
if (compilerOptions.outFile || compilerOptions.out) {
175-
Debug.assert(semanticDiagnosticsPerFile.size === 0);
202+
Debug.assert(!state.semanticDiagnosticsPerFile);
176203
return programOfThisState;
177204
}
178205

179206
// Get next batch of affected files
180-
currentAffectedFilesSignatures.clear();
181-
affectedFiles = state.getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures);
182-
currentChangedFilePath = nextKey.value as Path;
183-
semanticDiagnosticsPerFile.delete(currentChangedFilePath);
184-
affectedFilesIndex = 0;
207+
state.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures || createMap();
208+
state.affectedFiles = BuilderState.getFilesAffectedBy(state, programOfThisState, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures);
209+
state.currentChangedFilePath = nextKey.value as Path;
210+
state.semanticDiagnosticsPerFile.delete(nextKey.value as Path);
211+
state.affectedFilesIndex = 0;
212+
state.seenAffectedFiles = state.seenAffectedFiles || createMap<true>();
185213
}
186214
}
187215

@@ -191,11 +219,11 @@ namespace ts {
191219
*/
192220
function doneWithAffectedFile(programOfThisState: Program, affected: SourceFile | Program) {
193221
if (affected === programOfThisState) {
194-
changedFilesSet.clear();
222+
state.changedFilesSet.clear();
195223
}
196224
else {
197-
seenAffectedFiles.set((<SourceFile>affected).path, true);
198-
affectedFilesIndex++;
225+
state.seenAffectedFiles.set((affected as SourceFile).path, true);
226+
state.affectedFilesIndex++;
199227
}
200228
}
201229

@@ -277,10 +305,10 @@ namespace ts {
277305
* Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files
278306
*/
279307
function getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic> {
280-
Debug.assert(!affectedFiles || affectedFiles[affectedFilesIndex - 1] !== sourceFile || !semanticDiagnosticsPerFile.has(sourceFile.path));
308+
Debug.assert(!state.affectedFiles || state.affectedFiles[state.affectedFilesIndex - 1] !== sourceFile || !state.semanticDiagnosticsPerFile.has(sourceFile.path));
281309
const compilerOptions = programOfThisState.getCompilerOptions();
282310
if (compilerOptions.outFile || compilerOptions.out) {
283-
Debug.assert(semanticDiagnosticsPerFile.size === 0);
311+
Debug.assert(!state.semanticDiagnosticsPerFile);
284312
// We dont need to cache the diagnostics just return them from program
285313
return programOfThisState.getSemanticDiagnostics(sourceFile, cancellationToken);
286314
}
@@ -302,23 +330,23 @@ namespace ts {
302330
*/
303331
function getSemanticDiagnosticsOfFile(program: Program, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic> {
304332
const path = sourceFile.path;
305-
const cachedDiagnostics = semanticDiagnosticsPerFile.get(path);
333+
const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path);
306334
// Report the semantic diagnostics from the cache if we already have those diagnostics present
307335
if (cachedDiagnostics) {
308336
return cachedDiagnostics;
309337
}
310338

311339
// Diagnostics werent cached, get them from program, and cache the result
312340
const diagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken);
313-
semanticDiagnosticsPerFile.set(path, diagnostics);
341+
state.semanticDiagnosticsPerFile.set(path, diagnostics);
314342
return diagnostics;
315343
}
316344

317345
/**
318346
* Get all the dependencies of the sourceFile
319347
*/
320348
function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile) {
321-
return state.getAllDependencies(programOfThisState, sourceFile);
349+
return BuilderState.getAllDependencies(state, programOfThisState, sourceFile);
322350
}
323351
}
324352
}

0 commit comments

Comments
 (0)