From 1a96a146ed7bbb1af2ad568d4eacb1809525e6db Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Jun 2015 10:52:19 -0700 Subject: [PATCH 1/4] 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, From df13b8ffd199223881daf9231cdd63ec6f6b716f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Jun 2015 11:43:16 -0700 Subject: [PATCH 2/4] Update comment. --- src/compiler/checker.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3fd917bc00e..0fcc478d2b1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11684,6 +11684,9 @@ namespace ts { var cancellationToken: CancellationTokenObject; function getDiagnostics(sourceFile: SourceFile, ct: CancellationTokenObject): Diagnostic[] { try { + // Record the cancellation token so it can be checked later on during checkSourceElement. + // Do this in a finally block so we can ensure that it gets reset back to nothing after + // this call is done. cancellationToken = ct; return getDiagnosticsWorker(sourceFile); } From c9c0f3a4adc2bbe031c402ab70f6707828ff8e35 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Jun 2015 12:04:26 -0700 Subject: [PATCH 3/4] PR feedback. --- src/compiler/checker.ts | 6 +++--- src/compiler/core.ts | 21 ------------------- src/compiler/program.ts | 22 ++++++++++---------- src/compiler/types.ts | 22 +++++++++++--------- src/harness/fourslash.ts | 10 ++++----- src/harness/harnessLanguageService.ts | 29 ++++++++++++--------------- src/services/navigateTo.ts | 2 +- src/services/services.ts | 21 ++++++++++++++++++- src/services/shims.ts | 4 ++-- src/services/signatureHelp.ts | 2 +- 10 files changed, 69 insertions(+), 70 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0fcc478d2b1..8fd7f1a75d9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -167,7 +167,7 @@ namespace ts { return checker; - function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationTokenObject) { + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { // 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, cancellationToken); @@ -11681,8 +11681,8 @@ namespace ts { } } - var cancellationToken: CancellationTokenObject; - function getDiagnostics(sourceFile: SourceFile, ct: CancellationTokenObject): Diagnostic[] { + var cancellationToken: CancellationToken; + function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { try { // Record the cancellation token so it can be checked later on during checkSourceElement. // Do this in a finally block so we can ensure that it gets reset back to nothing after diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 49371dbccd2..52eae6e37c1 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -798,25 +798,4 @@ namespace ts { Debug.assert(false, message); } } -} - -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 443f5f92b49..f0277e60d0d 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -104,7 +104,7 @@ namespace ts { }; } - export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[] { + export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[] { let diagnostics = program.getOptionsDiagnostics(cancellationToken).concat( program.getSyntacticDiagnostics(sourceFile, cancellationToken), program.getGlobalDiagnostics(cancellationToken), @@ -233,11 +233,11 @@ namespace ts { return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); } - function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationTokenObject): EmitResult { + function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { return runWithCancellationToken(() => emitWorker(this, sourceFile, writeFileCallback, cancellationToken)); } - function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationTokenObject): EmitResult { + function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken): EmitResult { // If the noEmitOnError flag is set, then check if we have any errors so far. If so, // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we // get any preEmit diagnostics, not just the ones @@ -272,8 +272,8 @@ namespace ts { function getDiagnosticsHelper( sourceFile: SourceFile, - getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationTokenObject) => Diagnostic[], - cancellationToken: CancellationTokenObject): Diagnostic[] { + getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[], + cancellationToken: CancellationToken): Diagnostic[] { if (sourceFile) { return getDiagnostics(sourceFile, cancellationToken); } @@ -289,19 +289,19 @@ namespace ts { return sortAndDeduplicateDiagnostics(allDiagnostics); } - function getSyntacticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getSyntacticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); } - function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); } - function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken); } - function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return sourceFile.parseDiagnostics; } @@ -321,7 +321,7 @@ namespace ts { } } - function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return runWithCancellationToken(() => { let typeChecker = getDiagnosticsProducingTypeChecker(); @@ -334,7 +334,7 @@ namespace ts { }); } - function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return runWithCancellationToken(() => { if (!isDeclarationFile(sourceFile)) { let resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 799c291f46a..662c10f4235 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, cancellationToken?: CancellationTokenObject): EmitResult; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult; - getOptionsDiagnostics(cancellationToken?: CancellationTokenObject): Diagnostic[]; - getGlobalDiagnostics(cancellationToken?: CancellationTokenObject): Diagnostic[]; - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[]; - getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[]; - getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[]; + getOptionsDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; + getGlobalDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): 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, cancellationToken?: CancellationTokenObject): Diagnostic[]; + /* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; /* @internal */ getGlobalDiagnostics(): Diagnostic[]; - /* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): EmitResolver; + /* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationToken): EmitResolver; /* @internal */ getNodeCount(): number; /* @internal */ getIdentifierCount(): number; @@ -2052,14 +2052,18 @@ namespace ts { verticalTab = 0x0B, // \v } + export class OperationCanceledException { } + export interface CancellationToken { isCancellationRequested(): boolean; + + /** @throws OperationCanceledException if isCancellationRequested is true */ + throwIfCancellationRequested(): void; } export interface CompilerHost { getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile; getDefaultLibFileName(options: CompilerOptions): string; - getCancellationToken? (): CancellationToken; writeFile: WriteFileCallback; getCurrentDirectory(): string; getCanonicalFileName(fileName: string): string; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 5915698462c..41dda5bc9b3 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -190,14 +190,14 @@ module FourSlash { return "\nMarker: " + currentTestState.lastKnownMarker + "\nChecking: " + msg + "\n\n"; } - export class TestCancellationToken implements ts.CancellationToken { + export class TestCancellationToken implements ts.HostCancellationToken { // 0 - cancelled // >0 - not cancelled // <0 - not cancelled and value denotes number of isCancellationRequested after which token become cancelled - private static NotCancelled: number = -1; - private numberOfCallsBeforeCancellation: number = TestCancellationToken.NotCancelled; - public isCancellationRequested(): boolean { + private static NotCanceled: number = -1; + private numberOfCallsBeforeCancellation: number = TestCancellationToken.NotCanceled; + public isCancellationRequested(): boolean { if (this.numberOfCallsBeforeCancellation < 0) { return false; } @@ -216,7 +216,7 @@ module FourSlash { } public resetCancelled(): void { - this.numberOfCallsBeforeCancellation = TestCancellationToken.NotCancelled; + this.numberOfCallsBeforeCancellation = TestCancellationToken.NotCanceled; } } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 8eb77817533..6cb92df5948 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -103,14 +103,11 @@ module Harness.LanguageService { } } - class CancellationToken { - public static None: CancellationToken = new CancellationToken(null); - - constructor(private cancellationToken: ts.CancellationToken) { - } + class DefaultHostCancellationToken implements ts.HostCancellationToken { + public static Instance = new DefaultHostCancellationToken(); public isCancellationRequested() { - return this.cancellationToken && this.cancellationToken.isCancellationRequested(); + return false; } } @@ -124,8 +121,8 @@ module Harness.LanguageService { export class LanguageServiceAdapterHost { protected fileNameToScript: ts.Map = {}; - constructor(protected cancellationToken: ts.CancellationToken = CancellationToken.None, - protected settings = ts.getDefaultCompilerOptions()) { + constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, + protected settings = ts.getDefaultCompilerOptions()) { } public getNewLine(): string { @@ -173,8 +170,8 @@ module Harness.LanguageService { /// Native adapter class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost { - getCompilationSettings(): ts.CompilerOptions { return this.settings; } - getCancellationToken(): ts.CancellationToken { return this.cancellationToken; } + getCompilationSettings() { return this.settings; } + getCancellationToken() { return this.cancellationToken; } getCurrentDirectory(): string { return ""; } getDefaultLibFileName(): string { return ""; } getScriptFileNames(): string[] { return this.getFilenames(); } @@ -194,7 +191,7 @@ module Harness.LanguageService { export class NativeLanugageServiceAdapter implements LanguageServiceAdapter { private host: NativeLanguageServiceHost; - constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { this.host = new NativeLanguageServiceHost(cancellationToken, options); } getHost() { return this.host; } @@ -206,7 +203,7 @@ module Harness.LanguageService { /// Shim adapter class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost, ts.CoreServicesShimHost { private nativeHost: NativeLanguageServiceHost; - constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { super(cancellationToken, options); this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options); } @@ -218,7 +215,7 @@ module Harness.LanguageService { positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToLineAndCharacter(fileName, position); } getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); } - getCancellationToken(): ts.CancellationToken { return this.nativeHost.getCancellationToken(); } + getCancellationToken(): ts.HostCancellationToken { return this.nativeHost.getCancellationToken(); } getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); } getDefaultLibFileName(): string { return this.nativeHost.getDefaultLibFileName(); } getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); } @@ -399,7 +396,7 @@ module Harness.LanguageService { export class ShimLanugageServiceAdapter implements LanguageServiceAdapter { private host: ShimLanguageServiceHost; private factory: ts.TypeScriptServicesFactory; - constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { this.host = new ShimLanguageServiceHost(cancellationToken, options); this.factory = new TypeScript.Services.TypeScriptServicesFactory(); } @@ -446,7 +443,7 @@ module Harness.LanguageService { class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost { private client: ts.server.SessionClient; - constructor(cancellationToken: ts.CancellationToken, settings: ts.CompilerOptions) { + constructor(cancellationToken: ts.HostCancellationToken, settings: ts.CompilerOptions) { super(cancellationToken, settings); } @@ -575,7 +572,7 @@ module Harness.LanguageService { export class ServerLanugageServiceAdapter implements LanguageServiceAdapter { private host: SessionClientHost; private client: ts.server.SessionClient; - constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { // This is the main host that tests use to direct tests var clientHost = new SessionClientHost(cancellationToken, options); var client = new ts.server.SessionClient(clientHost); diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index a4cc7ec2ad5..bc506bc22f9 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -2,7 +2,7 @@ namespace ts.NavigateTo { type RawNavigateToItem = { name: string; fileName: string; matchKind: PatternMatchKind; isCaseSensitive: boolean; declaration: Declaration }; - export function getNavigateToItems(program: Program, cancellationToken: CancellationTokenObject, searchValue: string, maxResultCount: number): NavigateToItem[] { + export function getNavigateToItems(program: Program, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number): NavigateToItem[] { let patternMatcher = createPatternMatcher(searchValue); let rawItems: RawNavigateToItem[] = []; diff --git a/src/services/services.ts b/src/services/services.ts index f7f7dfcf6b3..efb7a61a8dd 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -944,6 +944,10 @@ namespace ts { } } + export interface HostCancellationToken { + isCancellationRequested(): boolean; + } + // // Public interface of the host of a language service instance. // @@ -955,7 +959,7 @@ namespace ts { getScriptVersion(fileName: string): string; getScriptSnapshot(fileName: string): IScriptSnapshot; getLocalizedDiagnosticMessages?(): any; - getCancellationToken?(): CancellationToken; + getCancellationToken?(): HostCancellationToken; getCurrentDirectory(): string; getDefaultLibFileName(options: CompilerOptions): string; log? (s: string): void; @@ -2379,6 +2383,21 @@ namespace ts { return ScriptElementKind.unknown; } + class CancellationTokenObject implements CancellationToken { + constructor(private cancellationToken: HostCancellationToken) { + } + + public isCancellationRequested() { + return this.cancellationToken && this.cancellationToken.isCancellationRequested(); + } + + public throwIfCancellationRequested(): void { + if (this.isCancellationRequested()) { + throw new OperationCanceledException(); + } + } + } + export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry = createDocumentRegistry()): LanguageService { let syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); let ruleProvider: formatting.RulesProvider; diff --git a/src/services/shims.ts b/src/services/shims.ts index 2e8b3eb774d..f56cef82f9d 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -51,7 +51,7 @@ namespace ts { getScriptVersion(fileName: string): string; getScriptSnapshot(fileName: string): ScriptSnapshotShim; getLocalizedDiagnosticMessages(): string; - getCancellationToken(): CancellationToken; + getCancellationToken(): HostCancellationToken; getCurrentDirectory(): string; getDefaultLibFileName(options: string): string; getNewLine?(): string; @@ -326,7 +326,7 @@ namespace ts { } } - public getCancellationToken(): CancellationToken { + public getCancellationToken(): HostCancellationToken { return this.shimHost.getCancellationToken(); } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index fce4e2c3025..44b022a7b12 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -178,7 +178,7 @@ namespace ts.SignatureHelp { argumentCount: number; } - export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, cancellationToken: CancellationTokenObject): SignatureHelpItems { + export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, cancellationToken: CancellationToken): SignatureHelpItems { let typeChecker = program.getTypeChecker(); // Decide whether to show signature help From 3a26cd21f99771258f257d183bdfc389e780614c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 6 Jul 2015 15:31:22 -0700 Subject: [PATCH 4/4] Adding comments. --- src/compiler/checker.ts | 12 +++++++++++- src/compiler/program.ts | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 308fe38de71..3ef2c6ada2a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22,6 +22,17 @@ namespace ts { } export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker { + // Cancellation that controls whether or not we can cancel in the middle of type checking. + // In general cancelling is *not* safe for the type checker. We might be in the middle of + // computing something, and we will leave our internals in an inconsistent state. Callers + // who set the cancellation token should catch if a cancellation exception occurs, and + // should throw away and create a new TypeChecker. + // + // Currently we only support setting the cancellation token when getting diagnostics. This + // is because diagnostics can be quite expensive, and we want to allow hosts to bail out if + // they no longer need the information (for example, if the user started editing again). + let cancellationToken: CancellationToken; + let Symbol = objectAllocator.getSymbolConstructor(); let Type = objectAllocator.getTypeConstructor(); let Signature = objectAllocator.getSignatureConstructor(); @@ -13321,7 +13332,6 @@ namespace ts { } } - var cancellationToken: CancellationToken; function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { try { // Record the cancellation token so it can be checked later on during checkSourceElement. diff --git a/src/compiler/program.ts b/src/compiler/program.ts index ae4180cf8be..28efaf24cca 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -313,6 +313,13 @@ namespace ts { 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. + // + // Note: we are overly agressive here. We do not actually *have* to throw away + // the "noDiagnosticsTypeChecker". However, for simplicity, i'd like to keep + // the lifetimes of these two TypeCheckers the same. Also, we generally only + // cancel when the user has made a change anyways. And, in that case, we (the + // program instance) will get thrown away anyways. So trying to keep one of + // these type checkers alive doesn't serve much purpose. noDiagnosticsTypeChecker = undefined; diagnosticsProducingTypeChecker = undefined; }