From 074d19b799631d221130ae8f7584387d643c1598 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 30 Oct 2020 18:50:07 -0700 Subject: [PATCH] Report implicit any error for 'yield' result with no contextual type --- src/compiler/checker.ts | 13 +- src/compiler/diagnosticMessages.json | 4 + src/compiler/utilities.ts | 38 +++++ .../reference/generatorImplicitAny.errors.txt | 40 +++++ .../reference/generatorImplicitAny.js | 54 ++++++- .../reference/generatorImplicitAny.symbols | 57 +++++++ .../reference/generatorImplicitAny.types | 87 +++++++++++ .../generatorReturnTypeInference.errors.txt | 139 ++++++++++++++++++ ...torReturnTypeInferenceNonStrict.errors.txt | 5 +- .../reference/generatorTypeCheck50.errors.txt | 9 ++ .../yieldExpressionInControlFlow.errors.txt | 5 +- .../yieldExpressionInFlowLoop.errors.txt | 13 ++ .../generators/generatorImplicitAny.ts | 32 +++- 13 files changed, 490 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/generatorImplicitAny.errors.txt create mode 100644 tests/baselines/reference/generatorReturnTypeInference.errors.txt create mode 100644 tests/baselines/reference/generatorTypeCheck50.errors.txt create mode 100644 tests/baselines/reference/yieldExpressionInFlowLoop.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f34024bddb90c..316f7dcbf35a4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30635,8 +30635,17 @@ namespace ts { return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, isAsync) || anyType; } - - return getContextualIterationType(IterationTypeKind.Next, func) || anyType; + let type = getContextualIterationType(IterationTypeKind.Next, func); + if (!type) { + type = anyType; + if (produceDiagnostics && noImplicitAny && !expressionResultIsUnused(node)) { + const contextualType = getContextualType(node); + if (!contextualType || isTypeAny(contextualType)) { + error(node, Diagnostics.yield_expression_implicitly_results_in_an_any_type_because_its_containing_generator_lacks_a_return_type_annotation); + } + } + } + return type; } function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 95d40754f37ab..fc0453ae4ea4c 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4948,6 +4948,10 @@ "category": "Error", "code": 7056 }, + "'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation.": { + "category": "Error", + "code": 7057 + }, "You cannot rename this element.": { "category": "Error", diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 3326de196896b..fbe83929deca2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6973,4 +6973,42 @@ namespace ts { return bindParentToChildIgnoringJSDoc(child, parent) || bindJSDoc(child); } } + + /** + * Indicates whether the result of an `Expression` will be unused. + * + * NOTE: This requires a node with a valid `parent` pointer. + */ + export function expressionResultIsUnused(node: Expression): boolean { + Debug.assertIsDefined(node.parent); + while (true) { + const parent: Node = node.parent; + // walk up parenthesized expressions, but keep a pointer to the top-most parenthesized expression + if (isParenthesizedExpression(parent)) { + node = parent; + continue; + } + // result is unused in an expression statement, `void` expression, or the initializer or incrementer of a `for` loop + if (isExpressionStatement(parent) || + isVoidExpression(parent) || + isForStatement(parent) && (parent.initializer === node || parent.incrementor === node)) { + return true; + } + if (isCommaListExpression(parent)) { + // left side of comma is always unused + if (node !== last(parent.elements)) return true; + // right side of comma is unused if parent is unused + node = parent; + continue; + } + if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.CommaToken) { + // left side of comma is always unused + if (node === parent.left) return true; + // right side of comma is unused if parent is unused + node = parent; + continue; + } + return false; + } + } } diff --git a/tests/baselines/reference/generatorImplicitAny.errors.txt b/tests/baselines/reference/generatorImplicitAny.errors.txt new file mode 100644 index 0000000000000..37972b1242d02 --- /dev/null +++ b/tests/baselines/reference/generatorImplicitAny.errors.txt @@ -0,0 +1,40 @@ +tests/cases/conformance/generators/generatorImplicitAny.ts(8,19): error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. +tests/cases/conformance/generators/generatorImplicitAny.ts(26,7): error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. + + +==== tests/cases/conformance/generators/generatorImplicitAny.ts (2 errors) ==== + function* g() {} + + // https://github.com/microsoft/TypeScript/issues/35105 + declare function noop(): void; + declare function f(value: T): void; + + function* g2() { + const value = yield; // error: implicit any + ~~~~~ +!!! error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. + } + + function* g3() { + const value: string = yield; // ok, contextually typed by `value`. + } + + function* g4() { + yield; // ok, result is unused + yield, noop(); // ok, result is unused + noop(), yield, noop(); // ok, result is unused + (yield); // ok, result is unused + (yield, noop()), noop(); // ok, result is unused + for(yield; false; yield); // ok, results are unused + void (yield); // ok, results are unused + } + + function* g5() { + f(yield); // error: implicit any + ~~~~~ +!!! error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. + } + + function* g6() { + f(yield); // ok, contextually typed by f + } \ No newline at end of file diff --git a/tests/baselines/reference/generatorImplicitAny.js b/tests/baselines/reference/generatorImplicitAny.js index ee12d8052b831..76e889a8c69c8 100644 --- a/tests/baselines/reference/generatorImplicitAny.js +++ b/tests/baselines/reference/generatorImplicitAny.js @@ -1,5 +1,57 @@ //// [generatorImplicitAny.ts] -function* g() {} +function* g() {} + +// https://github.com/microsoft/TypeScript/issues/35105 +declare function noop(): void; +declare function f(value: T): void; + +function* g2() { + const value = yield; // error: implicit any +} + +function* g3() { + const value: string = yield; // ok, contextually typed by `value`. +} + +function* g4() { + yield; // ok, result is unused + yield, noop(); // ok, result is unused + noop(), yield, noop(); // ok, result is unused + (yield); // ok, result is unused + (yield, noop()), noop(); // ok, result is unused + for(yield; false; yield); // ok, results are unused + void (yield); // ok, results are unused +} + +function* g5() { + f(yield); // error: implicit any +} + +function* g6() { + f(yield); // ok, contextually typed by f +} //// [generatorImplicitAny.js] function* g() { } +function* g2() { + const value = yield; // error: implicit any +} +function* g3() { + const value = yield; // ok, contextually typed by `value`. +} +function* g4() { + yield; // ok, result is unused + yield, noop(); // ok, result is unused + noop(), yield, noop(); // ok, result is unused + (yield); // ok, result is unused + (yield, noop()), noop(); // ok, result is unused + for (yield; false; yield) + ; // ok, results are unused + void (yield); // ok, results are unused +} +function* g5() { + f(yield); // error: implicit any +} +function* g6() { + f(yield); // ok, contextually typed by f +} diff --git a/tests/baselines/reference/generatorImplicitAny.symbols b/tests/baselines/reference/generatorImplicitAny.symbols index 3c3929e9ab37c..9d2061919d73d 100644 --- a/tests/baselines/reference/generatorImplicitAny.symbols +++ b/tests/baselines/reference/generatorImplicitAny.symbols @@ -2,3 +2,60 @@ function* g() {} >g : Symbol(g, Decl(generatorImplicitAny.ts, 0, 0)) +// https://github.com/microsoft/TypeScript/issues/35105 +declare function noop(): void; +>noop : Symbol(noop, Decl(generatorImplicitAny.ts, 0, 16)) + +declare function f(value: T): void; +>f : Symbol(f, Decl(generatorImplicitAny.ts, 3, 30)) +>T : Symbol(T, Decl(generatorImplicitAny.ts, 4, 19)) +>value : Symbol(value, Decl(generatorImplicitAny.ts, 4, 22)) +>T : Symbol(T, Decl(generatorImplicitAny.ts, 4, 19)) + +function* g2() { +>g2 : Symbol(g2, Decl(generatorImplicitAny.ts, 4, 38)) + + const value = yield; // error: implicit any +>value : Symbol(value, Decl(generatorImplicitAny.ts, 7, 9)) +} + +function* g3() { +>g3 : Symbol(g3, Decl(generatorImplicitAny.ts, 8, 1)) + + const value: string = yield; // ok, contextually typed by `value`. +>value : Symbol(value, Decl(generatorImplicitAny.ts, 11, 9)) +} + +function* g4() { +>g4 : Symbol(g4, Decl(generatorImplicitAny.ts, 12, 1)) + + yield; // ok, result is unused + yield, noop(); // ok, result is unused +>noop : Symbol(noop, Decl(generatorImplicitAny.ts, 0, 16)) + + noop(), yield, noop(); // ok, result is unused +>noop : Symbol(noop, Decl(generatorImplicitAny.ts, 0, 16)) +>noop : Symbol(noop, Decl(generatorImplicitAny.ts, 0, 16)) + + (yield); // ok, result is unused + (yield, noop()), noop(); // ok, result is unused +>noop : Symbol(noop, Decl(generatorImplicitAny.ts, 0, 16)) +>noop : Symbol(noop, Decl(generatorImplicitAny.ts, 0, 16)) + + for(yield; false; yield); // ok, results are unused + void (yield); // ok, results are unused +} + +function* g5() { +>g5 : Symbol(g5, Decl(generatorImplicitAny.ts, 22, 1)) + + f(yield); // error: implicit any +>f : Symbol(f, Decl(generatorImplicitAny.ts, 3, 30)) +} + +function* g6() { +>g6 : Symbol(g6, Decl(generatorImplicitAny.ts, 26, 1)) + + f(yield); // ok, contextually typed by f +>f : Symbol(f, Decl(generatorImplicitAny.ts, 3, 30)) +} diff --git a/tests/baselines/reference/generatorImplicitAny.types b/tests/baselines/reference/generatorImplicitAny.types index a63d19b7fbe06..56d4d8e8634a6 100644 --- a/tests/baselines/reference/generatorImplicitAny.types +++ b/tests/baselines/reference/generatorImplicitAny.types @@ -2,3 +2,90 @@ function* g() {} >g : () => Generator +// https://github.com/microsoft/TypeScript/issues/35105 +declare function noop(): void; +>noop : () => void + +declare function f(value: T): void; +>f : (value: T) => void +>value : T + +function* g2() { +>g2 : () => Generator + + const value = yield; // error: implicit any +>value : any +>yield : any +} + +function* g3() { +>g3 : () => Generator + + const value: string = yield; // ok, contextually typed by `value`. +>value : string +>yield : any +} + +function* g4() { +>g4 : () => Generator + + yield; // ok, result is unused +>yield : any + + yield, noop(); // ok, result is unused +>yield, noop() : void +>yield : any +>noop() : void +>noop : () => void + + noop(), yield, noop(); // ok, result is unused +>noop(), yield, noop() : void +>noop(), yield : any +>noop() : void +>noop : () => void +>yield : any +>noop() : void +>noop : () => void + + (yield); // ok, result is unused +>(yield) : any +>yield : any + + (yield, noop()), noop(); // ok, result is unused +>(yield, noop()), noop() : void +>(yield, noop()) : void +>yield, noop() : void +>yield : any +>noop() : void +>noop : () => void +>noop() : void +>noop : () => void + + for(yield; false; yield); // ok, results are unused +>yield : any +>false : false +>yield : any + + void (yield); // ok, results are unused +>void (yield) : undefined +>(yield) : any +>yield : any +} + +function* g5() { +>g5 : () => Generator + + f(yield); // error: implicit any +>f(yield) : void +>f : (value: T) => void +>yield : any +} + +function* g6() { +>g6 : () => Generator + + f(yield); // ok, contextually typed by f +>f(yield) : void +>f : (value: T) => void +>yield : any +} diff --git a/tests/baselines/reference/generatorReturnTypeInference.errors.txt b/tests/baselines/reference/generatorReturnTypeInference.errors.txt new file mode 100644 index 0000000000000..ae19c4769a01e --- /dev/null +++ b/tests/baselines/reference/generatorReturnTypeInference.errors.txt @@ -0,0 +1,139 @@ +tests/cases/conformance/generators/generatorReturnTypeInference.ts(72,15): error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. + + +==== tests/cases/conformance/generators/generatorReturnTypeInference.ts (1 errors) ==== + declare const iterableIterator: IterableIterator; + declare const generator: Generator; + declare const never: never; + + function* g000() { // Generator + } + + // 'yield' iteration type inference + function* g001() { // Generator + yield; + } + + function* g002() { // Generator + yield 1; + } + + function* g003() { // Generator + yield* []; + } + + function* g004() { // Generator + yield* iterableIterator; + } + + function* g005() { // Generator + const x = yield* generator; + } + + function* g006() { // Generator<1 | 2, void, unknown> + yield 1; + yield 2; + } + + function* g007() { // Generator + yield never; + } + + // 'return' iteration type inference + function* g102() { // Generator + return 1; + } + + function* g103() { // Generator + if (Math.random()) return 1; + return 2; + } + + function* g104() { // Generator + return never; + } + + // 'next' iteration type inference + function* g201() { // Generator + let a: string = yield 1; + } + + function* g202() { // Generator<1 | 2, void, never> + let a: string = yield 1; + let b: number = yield 2; + } + + declare function f1(x: string): void; + declare function f1(x: number): void; + + function* g203() { // Generator + const x = f1(yield 1); + } + + declare function f2(x: T): T; + + function* g204() { // Generator + const x = f2(yield 1); + ~~~~~~~ +!!! error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. + } + + // mixed iteration types inference + + function* g301() { // Generator + yield; + return; + } + + function* g302() { // Generator + yield 1; + return; + } + + function* g303() { // Generator + yield; + return "a"; + } + + function* g304() { // Generator + yield 1; + return "a"; + } + + function* g305() { // Generator<1 | 2, "a" | "b", unknown> + if (Math.random()) yield 1; + yield 2; + if (Math.random()) return "a"; + return "b"; + } + + function* g306() { // Generator + const a: "hi" = yield 1; + return true; + } + + function* g307() { // Generator + const a: T = yield 0; + return a; + } + + function* g308(x: T) { // Generator + const a: T = yield x; + return a; + } + + function* g309(x: T, y: U) { // Generator + const a: V = yield x; + return y; + } + + function* g310() { // Generator + const [a = 1, b = 2] = yield; + } + + function* g311() { // Generator + yield* (function*() { + const y: string = yield; + })(); + } + \ No newline at end of file diff --git a/tests/baselines/reference/generatorReturnTypeInferenceNonStrict.errors.txt b/tests/baselines/reference/generatorReturnTypeInferenceNonStrict.errors.txt index ebab0e585c73d..99451129064aa 100644 --- a/tests/baselines/reference/generatorReturnTypeInferenceNonStrict.errors.txt +++ b/tests/baselines/reference/generatorReturnTypeInferenceNonStrict.errors.txt @@ -1,12 +1,13 @@ tests/cases/conformance/generators/generatorReturnTypeInferenceNonStrict.ts(9,11): error TS7055: 'g001', which lacks return-type annotation, implicitly has an 'any' yield type. tests/cases/conformance/generators/generatorReturnTypeInferenceNonStrict.ts(17,11): error TS7055: 'g003', which lacks return-type annotation, implicitly has an 'any' yield type. +tests/cases/conformance/generators/generatorReturnTypeInferenceNonStrict.ts(74,15): error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. tests/cases/conformance/generators/generatorReturnTypeInferenceNonStrict.ts(79,11): error TS7055: 'g301', which lacks return-type annotation, implicitly has an 'any' yield type. tests/cases/conformance/generators/generatorReturnTypeInferenceNonStrict.ts(89,11): error TS7055: 'g303', which lacks return-type annotation, implicitly has an 'any' yield type. tests/cases/conformance/generators/generatorReturnTypeInferenceNonStrict.ts(126,11): error TS7055: 'g310', which lacks return-type annotation, implicitly has an 'any' yield type. tests/cases/conformance/generators/generatorReturnTypeInferenceNonStrict.ts(131,10): error TS7025: Generator implicitly has yield type 'any' because it does not yield any values. Consider supplying a return type annotation. -==== tests/cases/conformance/generators/generatorReturnTypeInferenceNonStrict.ts (6 errors) ==== +==== tests/cases/conformance/generators/generatorReturnTypeInferenceNonStrict.ts (7 errors) ==== declare const iterableIterator: IterableIterator; declare const generator: Generator; declare const never: never; @@ -85,6 +86,8 @@ tests/cases/conformance/generators/generatorReturnTypeInferenceNonStrict.ts(131, function* g204() { // Generator const x = f2(yield 1); + ~~~~~~~ +!!! error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. } // mixed iteration types inference diff --git a/tests/baselines/reference/generatorTypeCheck50.errors.txt b/tests/baselines/reference/generatorTypeCheck50.errors.txt new file mode 100644 index 0000000000000..7e417c7992d90 --- /dev/null +++ b/tests/baselines/reference/generatorTypeCheck50.errors.txt @@ -0,0 +1,9 @@ +tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck50.ts(2,11): error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. + + +==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck50.ts (1 errors) ==== + function* g() { + yield yield; + ~~~~~ +!!! error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. + } \ No newline at end of file diff --git a/tests/baselines/reference/yieldExpressionInControlFlow.errors.txt b/tests/baselines/reference/yieldExpressionInControlFlow.errors.txt index b571231172d53..824909b03ac2c 100644 --- a/tests/baselines/reference/yieldExpressionInControlFlow.errors.txt +++ b/tests/baselines/reference/yieldExpressionInControlFlow.errors.txt @@ -1,12 +1,15 @@ tests/cases/conformance/es6/yieldExpressions/alsoFails.ts(3,9): error TS7034: Variable 'o' implicitly has type 'any[]' in some locations where its type cannot be determined. tests/cases/conformance/es6/yieldExpressions/alsoFails.ts(5,20): error TS7005: Variable 'o' implicitly has an 'any[]' type. +tests/cases/conformance/es6/yieldExpressions/bug25149.js(4,13): error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. -==== tests/cases/conformance/es6/yieldExpressions/bug25149.js (0 errors) ==== +==== tests/cases/conformance/es6/yieldExpressions/bug25149.js (1 errors) ==== function* f() { var o while (true) { o = yield o + ~~~~~~~ +!!! error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. } } diff --git a/tests/baselines/reference/yieldExpressionInFlowLoop.errors.txt b/tests/baselines/reference/yieldExpressionInFlowLoop.errors.txt new file mode 100644 index 0000000000000..0c3180e877b5a --- /dev/null +++ b/tests/baselines/reference/yieldExpressionInFlowLoop.errors.txt @@ -0,0 +1,13 @@ +tests/cases/compiler/yieldExpressionInFlowLoop.ts(4,18): error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. + + +==== tests/cases/compiler/yieldExpressionInFlowLoop.ts (1 errors) ==== + function* f() { + let result; + while (1) { + result = yield result; + ~~~~~~~~~~~~ +!!! error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation. + } + } + \ No newline at end of file diff --git a/tests/cases/conformance/generators/generatorImplicitAny.ts b/tests/cases/conformance/generators/generatorImplicitAny.ts index 441107defe55c..fa06ae0410402 100644 --- a/tests/cases/conformance/generators/generatorImplicitAny.ts +++ b/tests/cases/conformance/generators/generatorImplicitAny.ts @@ -3,4 +3,34 @@ // @noImplicitReturns: true // @noImplicitAny: true -function* g() {} \ No newline at end of file +function* g() {} + +// https://github.com/microsoft/TypeScript/issues/35105 +declare function noop(): void; +declare function f(value: T): void; + +function* g2() { + const value = yield; // error: implicit any +} + +function* g3() { + const value: string = yield; // ok, contextually typed by `value`. +} + +function* g4() { + yield; // ok, result is unused + yield, noop(); // ok, result is unused + noop(), yield, noop(); // ok, result is unused + (yield); // ok, result is unused + (yield, noop()), noop(); // ok, result is unused + for(yield; false; yield); // ok, results are unused + void (yield); // ok, results are unused +} + +function* g5() { + f(yield); // error: implicit any +} + +function* g6() { + f(yield); // ok, contextually typed by f +} \ No newline at end of file