Make type-checking cancellable.

This commit is contained in:
Cyrus Najmabadi
2015-06-18 10:52:19 -07:00
parent 2905f41e7d
commit 1a96a146ed
6 changed files with 130 additions and 70 deletions
+32 -5
View File
@@ -159,10 +159,10 @@ namespace ts {
}
};
function getEmitResolver(sourceFile?: SourceFile) {
function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationTokenObject) {
// Ensure we have all the type information in place for this file so that all the
// emitter questions of this resolver will return the right information.
getDiagnostics(sourceFile);
getDiagnostics(sourceFile, cancellationToken);
return emitResolver;
}
@@ -11405,8 +11405,24 @@ namespace ts {
}
function checkSourceElement(node: Node): void {
if (!node) return;
switch (node.kind) {
if (!node) {
return;
}
let kind = node.kind;
if (cancellationToken) {
// Only bother checking on a few construct kinds. We don't want to be excessivly
// hitting the cancellation token on every node we check.
switch (kind) {
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.FunctionDeclaration:
cancellationToken.throwIfCancellationRequested();
}
}
switch (kind) {
case SyntaxKind.TypeParameter:
return checkTypeParameter(<TypeParameterDeclaration>node);
case SyntaxKind.Parameter:
@@ -11661,7 +11677,18 @@ namespace ts {
}
}
function getDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
var cancellationToken: CancellationTokenObject;
function getDiagnostics(sourceFile: SourceFile, ct: CancellationTokenObject): Diagnostic[] {
try {
cancellationToken = ct;
return getDiagnosticsWorker(sourceFile);
}
finally {
cancellationToken = undefined;
}
}
function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] {
throwIfNonDiagnosticsProducing();
if (sourceFile) {
checkSourceFile(sourceFile);
+21
View File
@@ -799,3 +799,24 @@ namespace ts {
}
}
}
namespace ts {
export class OperationCanceledException { }
export class CancellationTokenObject {
public static None: CancellationTokenObject = new CancellationTokenObject(null)
constructor(private cancellationToken: CancellationToken) {
}
public isCancellationRequested() {
return this.cancellationToken && this.cancellationToken.isCancellationRequested();
}
public throwIfCancellationRequested(): void {
if (this.isCancellationRequested()) {
throw new OperationCanceledException();
}
}
}
}
+64 -31
View File
@@ -104,11 +104,13 @@ namespace ts {
};
}
export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile): Diagnostic[] {
let diagnostics = program.getSyntacticDiagnostics(sourceFile).concat(program.getGlobalDiagnostics()).concat(program.getSemanticDiagnostics(sourceFile));
export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[] {
let diagnostics = program.getSyntacticDiagnostics(sourceFile, cancellationToken).concat(
program.getGlobalDiagnostics(cancellationToken)).concat(
program.getSemanticDiagnostics(sourceFile, cancellationToken));
if (program.getCompilerOptions().declaration) {
diagnostics.concat(program.getDeclarationDiagnostics(sourceFile));
diagnostics.concat(program.getDeclarationDiagnostics(sourceFile, cancellationToken));
}
return sortAndDeduplicateDiagnostics(diagnostics);
@@ -230,10 +232,15 @@ namespace ts {
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false));
}
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback): EmitResult {
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationTokenObject): EmitResult {
return runWithCancellationToken(() => emitWorker(this, sourceFile, writeFileCallback, cancellationToken));
}
function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationTokenObject): EmitResult {
// If the noEmitOnError flag is set, then check if we have any errors so far. If so,
// immediately bail out.
if (options.noEmitOnError && getPreEmitDiagnostics(this).length > 0) {
// immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we
// get any preEmit diagnostics, not just the ones
if (options.noEmitOnError && getPreEmitDiagnostics(program, /*sourceFile:*/ undefined, cancellationToken).length > 0) {
return { diagnostics: [], sourceMaps: undefined, emitSkipped: true };
}
@@ -262,53 +269,79 @@ namespace ts {
return filesByName.get(fileName);
}
function getDiagnosticsHelper(sourceFile: SourceFile, getDiagnostics: (sourceFile: SourceFile) => Diagnostic[]): Diagnostic[] {
function getDiagnosticsHelper(
sourceFile: SourceFile,
getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationTokenObject) => Diagnostic[],
cancellationToken: CancellationTokenObject): Diagnostic[] {
if (sourceFile) {
return getDiagnostics(sourceFile);
return getDiagnostics(sourceFile, cancellationToken);
}
let allDiagnostics: Diagnostic[] = [];
forEach(program.getSourceFiles(), sourceFile => {
addRange(allDiagnostics, getDiagnostics(sourceFile));
if (cancellationToken) {
cancellationToken.throwIfCancellationRequested();
}
addRange(allDiagnostics, getDiagnostics(sourceFile, cancellationToken));
});
return sortAndDeduplicateDiagnostics(allDiagnostics);
}
function getSyntacticDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile);
function getSyntacticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken);
}
function getSemanticDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile);
function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken);
}
function getDeclarationDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile);
function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken);
}
function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] {
function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
return sourceFile.parseDiagnostics;
}
function getSemanticDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] {
let typeChecker = getDiagnosticsProducingTypeChecker();
function runWithCancellationToken<T>(func: () => T): T {
try {
return func();
}
catch (e) {
if (e instanceof OperationCanceledException) {
// We were canceled while performing the operation. Because our type checker
// might be a bad state, we need to throw it away.
noDiagnosticsTypeChecker = undefined;
diagnosticsProducingTypeChecker = undefined;
}
Debug.assert(!!sourceFile.bindDiagnostics);
let bindDiagnostics = sourceFile.bindDiagnostics;
let checkDiagnostics = typeChecker.getDiagnostics(sourceFile);
let programDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
return bindDiagnostics.concat(checkDiagnostics).concat(programDiagnostics);
throw e;
}
}
function getDeclarationDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] {
if (!isDeclarationFile(sourceFile)) {
let resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile);
// Don't actually write any files since we're just getting diagnostics.
var writeFile: WriteFileCallback = () => { };
return ts.getDeclarationDiagnostics(getEmitHost(writeFile), resolver, sourceFile);
}
function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
return runWithCancellationToken(() => {
let typeChecker = getDiagnosticsProducingTypeChecker();
Debug.assert(!!sourceFile.bindDiagnostics);
let bindDiagnostics = sourceFile.bindDiagnostics;
let checkDiagnostics = typeChecker.getDiagnostics(sourceFile, cancellationToken);
let programDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
return bindDiagnostics.concat(checkDiagnostics).concat(programDiagnostics);
});
}
function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
return runWithCancellationToken(() => {
if (!isDeclarationFile(sourceFile)) {
let resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken);
// Don't actually write any files since we're just getting diagnostics.
var writeFile: WriteFileCallback = () => { };
return ts.getDeclarationDiagnostics(getEmitHost(writeFile), resolver, sourceFile);
}
});
}
function getCompilerOptionsDiagnostics(): Diagnostic[] {
+8 -8
View File
@@ -1210,13 +1210,13 @@ namespace ts {
* used for writing the JavaScript and declaration files. Otherwise, the writeFile parameter
* will be invoked when writing the JavaScript and declaration files.
*/
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult;
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationTokenObject): EmitResult;
getSyntacticDiagnostics(sourceFile?: SourceFile): Diagnostic[];
getGlobalDiagnostics(): Diagnostic[];
getSemanticDiagnostics(sourceFile?: SourceFile): Diagnostic[];
getDeclarationDiagnostics(sourceFile?: SourceFile): Diagnostic[];
/* @internal */ getCompilerOptionsDiagnostics(): Diagnostic[];
getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
getGlobalDiagnostics(cancellationToken?: CancellationTokenObject): Diagnostic[];
getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
/* @internal */ getCompilerOptionsDiagnostics(cancellationToken?: CancellationTokenObject): Diagnostic[];
/**
* Gets a type checker that can be used to semantically analyze source fils in the program.
@@ -1324,9 +1324,9 @@ namespace ts {
getExportsOfModule(moduleSymbol: Symbol): Symbol[];
// Should not be called directly. Should only be accessed through the Program instance.
/* @internal */ getDiagnostics(sourceFile?: SourceFile): Diagnostic[];
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
/* @internal */ getGlobalDiagnostics(): Diagnostic[];
/* @internal */ getEmitResolver(sourceFile?: SourceFile): EmitResolver;
/* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): EmitResolver;
/* @internal */ getNodeCount(): number;
/* @internal */ getIdentifierCount(): number;
-1
View File
@@ -49,7 +49,6 @@ if (testConfigFile !== '') {
if (!option) {
continue;
}
ts.sys.write("Option: " + option + "\r\n");
switch (option) {
case 'compiler':
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
+5 -25
View File
@@ -1610,26 +1610,6 @@ namespace ts {
};
}
export class OperationCanceledException { }
export class CancellationTokenObject {
public static None: CancellationTokenObject = new CancellationTokenObject(null)
constructor(private cancellationToken: CancellationToken) {
}
public isCancellationRequested() {
return this.cancellationToken && this.cancellationToken.isCancellationRequested();
}
public throwIfCancellationRequested(): void {
if (this.isCancellationRequested()) {
throw new OperationCanceledException();
}
}
}
// Cache host information about scrip Should be refreshed
// at each language service public entry point, since we don't know when
// set of scripts handled by the host changes.
@@ -2600,7 +2580,7 @@ namespace ts {
function getSyntacticDiagnostics(fileName: string) {
synchronizeHostData();
return program.getSyntacticDiagnostics(getValidSourceFile(fileName));
return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken);
}
/**
@@ -2622,13 +2602,13 @@ namespace ts {
// Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file.
// Therefore only get diagnostics for given file.
let semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile);
let semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken);
if (!program.getCompilerOptions().declaration) {
return semanticDiagnostics;
}
// If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface
let declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile);
let declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken);
return concatenate(semanticDiagnostics, declarationDiagnostics);
}
@@ -2790,7 +2770,7 @@ namespace ts {
function getCompilerOptionsDiagnostics() {
synchronizeHostData();
return program.getGlobalDiagnostics();
return program.getGlobalDiagnostics(cancellationToken);
}
/// Completion
@@ -5732,7 +5712,7 @@ namespace ts {
});
}
let emitOutput = program.emit(sourceFile, writeFile);
let emitOutput = program.emit(sourceFile, writeFile, cancellationToken);
return {
outputFiles,