Skip to content

Commit 1a96a14

Browse files
Make type-checking cancellable.
1 parent 2905f41 commit 1a96a14

File tree

6 files changed

+129
-69
lines changed

6 files changed

+129
-69
lines changed

src/compiler/checker.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,10 @@ namespace ts {
159159
}
160160
};
161161

162-
function getEmitResolver(sourceFile?: SourceFile) {
162+
function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationTokenObject) {
163163
// Ensure we have all the type information in place for this file so that all the
164164
// emitter questions of this resolver will return the right information.
165-
getDiagnostics(sourceFile);
165+
getDiagnostics(sourceFile, cancellationToken);
166166
return emitResolver;
167167
}
168168

@@ -11405,8 +11405,24 @@ namespace ts {
1140511405
}
1140611406

1140711407
function checkSourceElement(node: Node): void {
11408-
if (!node) return;
11409-
switch (node.kind) {
11408+
if (!node) {
11409+
return;
11410+
}
11411+
11412+
let kind = node.kind;
11413+
if (cancellationToken) {
11414+
// Only bother checking on a few construct kinds. We don't want to be excessivly
11415+
// hitting the cancellation token on every node we check.
11416+
switch (kind) {
11417+
case SyntaxKind.ModuleDeclaration:
11418+
case SyntaxKind.ClassDeclaration:
11419+
case SyntaxKind.InterfaceDeclaration:
11420+
case SyntaxKind.FunctionDeclaration:
11421+
cancellationToken.throwIfCancellationRequested();
11422+
}
11423+
}
11424+
11425+
switch (kind) {
1141011426
case SyntaxKind.TypeParameter:
1141111427
return checkTypeParameter(<TypeParameterDeclaration>node);
1141211428
case SyntaxKind.Parameter:
@@ -11661,7 +11677,18 @@ namespace ts {
1166111677
}
1166211678
}
1166311679

11664-
function getDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
11680+
var cancellationToken: CancellationTokenObject;
11681+
function getDiagnostics(sourceFile: SourceFile, ct: CancellationTokenObject): Diagnostic[] {
11682+
try {
11683+
cancellationToken = ct;
11684+
return getDiagnosticsWorker(sourceFile);
11685+
}
11686+
finally {
11687+
cancellationToken = undefined;
11688+
}
11689+
}
11690+
11691+
function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] {
1166511692
throwIfNonDiagnosticsProducing();
1166611693
if (sourceFile) {
1166711694
checkSourceFile(sourceFile);

src/compiler/core.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,3 +799,24 @@ namespace ts {
799799
}
800800
}
801801
}
802+
803+
namespace ts {
804+
export class OperationCanceledException { }
805+
806+
export class CancellationTokenObject {
807+
public static None: CancellationTokenObject = new CancellationTokenObject(null)
808+
809+
constructor(private cancellationToken: CancellationToken) {
810+
}
811+
812+
public isCancellationRequested() {
813+
return this.cancellationToken && this.cancellationToken.isCancellationRequested();
814+
}
815+
816+
public throwIfCancellationRequested(): void {
817+
if (this.isCancellationRequested()) {
818+
throw new OperationCanceledException();
819+
}
820+
}
821+
}
822+
}

src/compiler/program.ts

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,13 @@ namespace ts {
104104
};
105105
}
106106

107-
export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile): Diagnostic[] {
108-
let diagnostics = program.getSyntacticDiagnostics(sourceFile).concat(program.getGlobalDiagnostics()).concat(program.getSemanticDiagnostics(sourceFile));
107+
export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[] {
108+
let diagnostics = program.getSyntacticDiagnostics(sourceFile, cancellationToken).concat(
109+
program.getGlobalDiagnostics(cancellationToken)).concat(
110+
program.getSemanticDiagnostics(sourceFile, cancellationToken));
109111

110112
if (program.getCompilerOptions().declaration) {
111-
diagnostics.concat(program.getDeclarationDiagnostics(sourceFile));
113+
diagnostics.concat(program.getDeclarationDiagnostics(sourceFile, cancellationToken));
112114
}
113115

114116
return sortAndDeduplicateDiagnostics(diagnostics);
@@ -230,10 +232,15 @@ namespace ts {
230232
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false));
231233
}
232234

233-
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback): EmitResult {
235+
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationTokenObject): EmitResult {
236+
return runWithCancellationToken(() => emitWorker(this, sourceFile, writeFileCallback, cancellationToken));
237+
}
238+
239+
function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationTokenObject): EmitResult {
234240
// If the noEmitOnError flag is set, then check if we have any errors so far. If so,
235-
// immediately bail out.
236-
if (options.noEmitOnError && getPreEmitDiagnostics(this).length > 0) {
241+
// immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we
242+
// get any preEmit diagnostics, not just the ones
243+
if (options.noEmitOnError && getPreEmitDiagnostics(program, /*sourceFile:*/ undefined, cancellationToken).length > 0) {
237244
return { diagnostics: [], sourceMaps: undefined, emitSkipped: true };
238245
}
239246

@@ -262,53 +269,79 @@ namespace ts {
262269
return filesByName.get(fileName);
263270
}
264271

265-
function getDiagnosticsHelper(sourceFile: SourceFile, getDiagnostics: (sourceFile: SourceFile) => Diagnostic[]): Diagnostic[] {
272+
function getDiagnosticsHelper(
273+
sourceFile: SourceFile,
274+
getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationTokenObject) => Diagnostic[],
275+
cancellationToken: CancellationTokenObject): Diagnostic[] {
266276
if (sourceFile) {
267-
return getDiagnostics(sourceFile);
277+
return getDiagnostics(sourceFile, cancellationToken);
268278
}
269279

270280
let allDiagnostics: Diagnostic[] = [];
271281
forEach(program.getSourceFiles(), sourceFile => {
272-
addRange(allDiagnostics, getDiagnostics(sourceFile));
282+
if (cancellationToken) {
283+
cancellationToken.throwIfCancellationRequested();
284+
}
285+
addRange(allDiagnostics, getDiagnostics(sourceFile, cancellationToken));
273286
});
274287

275288
return sortAndDeduplicateDiagnostics(allDiagnostics);
276289
}
277290

278-
function getSyntacticDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
279-
return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile);
291+
function getSyntacticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
292+
return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken);
280293
}
281294

282-
function getSemanticDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
283-
return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile);
295+
function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
296+
return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken);
284297
}
285298

286-
function getDeclarationDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
287-
return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile);
299+
function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
300+
return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken);
288301
}
289302

290-
function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] {
303+
function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
291304
return sourceFile.parseDiagnostics;
292305
}
293306

294-
function getSemanticDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] {
295-
let typeChecker = getDiagnosticsProducingTypeChecker();
307+
function runWithCancellationToken<T>(func: () => T): T {
308+
try {
309+
return func();
310+
}
311+
catch (e) {
312+
if (e instanceof OperationCanceledException) {
313+
// We were canceled while performing the operation. Because our type checker
314+
// might be a bad state, we need to throw it away.
315+
noDiagnosticsTypeChecker = undefined;
316+
diagnosticsProducingTypeChecker = undefined;
317+
}
296318

297-
Debug.assert(!!sourceFile.bindDiagnostics);
298-
let bindDiagnostics = sourceFile.bindDiagnostics;
299-
let checkDiagnostics = typeChecker.getDiagnostics(sourceFile);
300-
let programDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
319+
throw e;
320+
}
321+
}
322+
323+
function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
324+
return runWithCancellationToken(() => {
325+
let typeChecker = getDiagnosticsProducingTypeChecker();
326+
327+
Debug.assert(!!sourceFile.bindDiagnostics);
328+
let bindDiagnostics = sourceFile.bindDiagnostics;
329+
let checkDiagnostics = typeChecker.getDiagnostics(sourceFile, cancellationToken);
330+
let programDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
301331

302-
return bindDiagnostics.concat(checkDiagnostics).concat(programDiagnostics);
332+
return bindDiagnostics.concat(checkDiagnostics).concat(programDiagnostics);
333+
});
303334
}
304335

305-
function getDeclarationDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] {
306-
if (!isDeclarationFile(sourceFile)) {
307-
let resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile);
308-
// Don't actually write any files since we're just getting diagnostics.
309-
var writeFile: WriteFileCallback = () => { };
310-
return ts.getDeclarationDiagnostics(getEmitHost(writeFile), resolver, sourceFile);
311-
}
336+
function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
337+
return runWithCancellationToken(() => {
338+
if (!isDeclarationFile(sourceFile)) {
339+
let resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken);
340+
// Don't actually write any files since we're just getting diagnostics.
341+
var writeFile: WriteFileCallback = () => { };
342+
return ts.getDeclarationDiagnostics(getEmitHost(writeFile), resolver, sourceFile);
343+
}
344+
});
312345
}
313346

314347
function getCompilerOptionsDiagnostics(): Diagnostic[] {

src/compiler/types.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,13 +1210,13 @@ namespace ts {
12101210
* used for writing the JavaScript and declaration files. Otherwise, the writeFile parameter
12111211
* will be invoked when writing the JavaScript and declaration files.
12121212
*/
1213-
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult;
1213+
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationTokenObject): EmitResult;
12141214

1215-
getSyntacticDiagnostics(sourceFile?: SourceFile): Diagnostic[];
1216-
getGlobalDiagnostics(): Diagnostic[];
1217-
getSemanticDiagnostics(sourceFile?: SourceFile): Diagnostic[];
1218-
getDeclarationDiagnostics(sourceFile?: SourceFile): Diagnostic[];
1219-
/* @internal */ getCompilerOptionsDiagnostics(): Diagnostic[];
1215+
getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
1216+
getGlobalDiagnostics(cancellationToken?: CancellationTokenObject): Diagnostic[];
1217+
getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
1218+
getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
1219+
/* @internal */ getCompilerOptionsDiagnostics(cancellationToken?: CancellationTokenObject): Diagnostic[];
12201220

12211221
/**
12221222
* Gets a type checker that can be used to semantically analyze source fils in the program.
@@ -1324,9 +1324,9 @@ namespace ts {
13241324
getExportsOfModule(moduleSymbol: Symbol): Symbol[];
13251325

13261326
// Should not be called directly. Should only be accessed through the Program instance.
1327-
/* @internal */ getDiagnostics(sourceFile?: SourceFile): Diagnostic[];
1327+
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
13281328
/* @internal */ getGlobalDiagnostics(): Diagnostic[];
1329-
/* @internal */ getEmitResolver(sourceFile?: SourceFile): EmitResolver;
1329+
/* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): EmitResolver;
13301330

13311331
/* @internal */ getNodeCount(): number;
13321332
/* @internal */ getIdentifierCount(): number;

src/harness/runner.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ if (testConfigFile !== '') {
4949
if (!option) {
5050
continue;
5151
}
52-
ts.sys.write("Option: " + option + "\r\n");
5352
switch (option) {
5453
case 'compiler':
5554
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));

src/services/services.ts

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1610,26 +1610,6 @@ namespace ts {
16101610
};
16111611
}
16121612

1613-
export class OperationCanceledException { }
1614-
1615-
export class CancellationTokenObject {
1616-
1617-
public static None: CancellationTokenObject = new CancellationTokenObject(null)
1618-
1619-
constructor(private cancellationToken: CancellationToken) {
1620-
}
1621-
1622-
public isCancellationRequested() {
1623-
return this.cancellationToken && this.cancellationToken.isCancellationRequested();
1624-
}
1625-
1626-
public throwIfCancellationRequested(): void {
1627-
if (this.isCancellationRequested()) {
1628-
throw new OperationCanceledException();
1629-
}
1630-
}
1631-
}
1632-
16331613
// Cache host information about scrip Should be refreshed
16341614
// at each language service public entry point, since we don't know when
16351615
// set of scripts handled by the host changes.
@@ -2600,7 +2580,7 @@ namespace ts {
26002580
function getSyntacticDiagnostics(fileName: string) {
26012581
synchronizeHostData();
26022582

2603-
return program.getSyntacticDiagnostics(getValidSourceFile(fileName));
2583+
return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken);
26042584
}
26052585

26062586
/**
@@ -2622,13 +2602,13 @@ namespace ts {
26222602
// Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file.
26232603
// Therefore only get diagnostics for given file.
26242604

2625-
let semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile);
2605+
let semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken);
26262606
if (!program.getCompilerOptions().declaration) {
26272607
return semanticDiagnostics;
26282608
}
26292609

26302610
// If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface
2631-
let declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile);
2611+
let declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken);
26322612
return concatenate(semanticDiagnostics, declarationDiagnostics);
26332613
}
26342614

@@ -2790,7 +2770,7 @@ namespace ts {
27902770

27912771
function getCompilerOptionsDiagnostics() {
27922772
synchronizeHostData();
2793-
return program.getGlobalDiagnostics();
2773+
return program.getGlobalDiagnostics(cancellationToken);
27942774
}
27952775

27962776
/// Completion
@@ -5732,7 +5712,7 @@ namespace ts {
57325712
});
57335713
}
57345714

5735-
let emitOutput = program.emit(sourceFile, writeFile);
5715+
let emitOutput = program.emit(sourceFile, writeFile, cancellationToken);
57365716

57375717
return {
57385718
outputFiles,

0 commit comments

Comments
 (0)