From 1a96a146ed7bbb1af2ad568d4eacb1809525e6db Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Jun 2015 10:52:19 -0700 Subject: [PATCH] Make type-checking cancellable. --- src/compiler/checker.ts | 37 +++++++++++++--- src/compiler/core.ts | 21 +++++++++ src/compiler/program.ts | 95 +++++++++++++++++++++++++++------------- src/compiler/types.ts | 16 +++---- src/harness/runner.ts | 1 - src/services/services.ts | 30 +++---------- 6 files changed, 130 insertions(+), 70 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index efc3e32166e..c550c703336 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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(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); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index ced477eeeec..49371dbccd2 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -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(); + } + } + } +} \ No newline at end of file diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 800e2238a54..df976f17ae9 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -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(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[] { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c39426a2e22..b230b2c5feb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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; diff --git a/src/harness/runner.ts b/src/harness/runner.ts index 42c2d5667c4..1e9c6470421 100644 --- a/src/harness/runner.ts +++ b/src/harness/runner.ts @@ -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)); diff --git a/src/services/services.ts b/src/services/services.ts index bed1f9b6bb9..86540ccd3e5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -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,