Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treat 'yield;' as 'yield undefined;' #22297

Merged
3 commits merged into from
Mar 8, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 43 additions & 69 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18380,27 +18380,23 @@ namespace ts {

function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode): Type[] {
const aggregatedTypes: Type[] = [];
const functionFlags = getFunctionFlags(func);
const isAsync = (getFunctionFlags(func) & FunctionFlags.Async) !== 0;
forEachYieldExpression(<Block>func.body, yieldExpression => {
const expr = yieldExpression.expression;
if (expr) {
let type = checkExpressionCached(expr, checkMode);
if (yieldExpression.asteriskToken) {
// A yield* expression effectively yields everything that its operand yields
type = checkIteratedTypeOrElementType(type, yieldExpression.expression, /*allowStringInput*/ false, (functionFlags & FunctionFlags.Async) !== 0);
}
if (functionFlags & FunctionFlags.Async) {
type = checkAwaitedType(type, expr, yieldExpression.asteriskToken
? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member
: Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
}
pushIfUnique(aggregatedTypes, type);
}
pushIfUnique(aggregatedTypes, getYieldedTypeOfYieldExpression(yieldExpression, isAsync, checkMode));
});

return aggregatedTypes;
}

function getYieldedTypeOfYieldExpression(node: YieldExpression, isAsync: boolean, checkMode?: CheckMode): Type {
const errorNode = node.expression || node;
const expressionType = node.expression ? checkExpressionCached(node.expression, checkMode) : undefinedType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the refactoring that is part of this PR -- it make the code much easier to read. But I want to point out that this is the only line needed to fix the bug. The bug would get fixed a lot faster if we could just review a one-line fix and look at the refactor separately.

// A yield* expression effectively yields everything that its operand yields
const yieldedType = node.asteriskToken ? checkIteratedTypeOrElementType(expressionType, errorNode, /*allowStringInput*/ false, isAsync) : expressionType;
return !isAsync ? yieldedType : getAwaitedType(yieldedType, errorNode, node.asteriskToken
? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member
: Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
}

function isExhaustiveSwitchStatement(node: SwitchStatement): boolean {
if (!node.possiblyExhaustive) {
return false;
Expand Down Expand Up @@ -19353,62 +19349,40 @@ namespace ts {
}
}

if (node.expression) {
const func = getContainingFunction(node);
// If the user's code is syntactically correct, the func should always have a star. After all,
// we are in a yield context.
const functionFlags = func && getFunctionFlags(func);
if (node.asteriskToken) {
// Async generator functions prior to ESNext require the __await, __asyncDelegator,
// and __asyncValues helpers
if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator &&
languageVersion < ScriptTarget.ESNext) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes);
}

// Generator functions prior to ES2015 require the __values helper
if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Generator &&
languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.Values);
}
}

if (functionFlags & FunctionFlags.Generator) {
const expressionType = checkExpressionCached(node.expression);
let expressionElementType: Type;
const nodeIsYieldStar = !!node.asteriskToken;
if (nodeIsYieldStar) {
expressionElementType = checkIteratedTypeOrElementType(expressionType, node.expression, /*allowStringInput*/ false, (functionFlags & FunctionFlags.Async) !== 0);
}

// There is no point in doing an assignability check if the function
// has no explicit return type because the return type is directly computed
// from the yield expressions.
const returnType = getEffectiveReturnTypeNode(func);
if (returnType) {
const signatureElementType = getIteratedTypeOfGenerator(getTypeFromTypeNode(returnType), (functionFlags & FunctionFlags.Async) !== 0) || anyType;
if (nodeIsYieldStar) {
checkTypeAssignableTo(
functionFlags & FunctionFlags.Async
? getAwaitedType(expressionElementType, node.expression, Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)
: expressionElementType,
signatureElementType,
node.expression,
/*headMessage*/ undefined);
}
else {
checkTypeAssignableTo(
functionFlags & FunctionFlags.Async
? getAwaitedType(expressionType, node.expression, Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)
: expressionType,
signatureElementType,
node.expression,
/*headMessage*/ undefined);
}
}
const func = getContainingFunction(node);
const functionFlags = func ? getFunctionFlags(func) : FunctionFlags.Normal;

if (!(functionFlags & FunctionFlags.Generator)) {
// If the user's code is syntactically correct, the func should always have a star. After all, we are in a yield context.
return anyType;
}

if (node.asteriskToken) {
// Async generator functions prior to ESNext require the __await, __asyncDelegator,
// and __asyncValues helpers
if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator &&
languageVersion < ScriptTarget.ESNext) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes);
}

// Generator functions prior to ES2015 require the __values helper
if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Generator &&
languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.Values);
}
}

const isAsync = (functionFlags & FunctionFlags.Async) !== 0;
const yieldedType = getYieldedTypeOfYieldExpression(node, isAsync);
// There is no point in doing an assignability check if the function
// has no explicit return type because the return type is directly computed
// from the yield expressions.
const returnType = getEffectiveReturnTypeNode(func);
if (returnType) {
const signatureElementType = getIteratedTypeOfGenerator(getTypeFromTypeNode(returnType), isAsync) || anyType;
checkTypeAssignableTo(yieldedType, signatureElementType, node.expression || node, /*headMessage*/ undefined);
}

// Both yield and yield* expressions have type 'any'
return anyType;
}
Expand Down
10 changes: 5 additions & 5 deletions tests/baselines/reference/FunctionDeclaration9_es6.types
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
=== tests/cases/conformance/es6/functionDeclarations/FunctionDeclaration9_es6.ts ===
function * foo() {
>foo : () => IterableIterator<any>
>foo : () => IterableIterator<undefined>

var v = { [yield]: foo }
>v : { [x: number]: () => IterableIterator<any>; }
>{ [yield]: foo } : { [x: number]: () => IterableIterator<any>; }
>[yield] : () => IterableIterator<any>
>v : { [x: number]: () => IterableIterator<undefined>; }
>{ [yield]: foo } : { [x: number]: () => IterableIterator<undefined>; }
>[yield] : () => IterableIterator<undefined>
>yield : any
>foo : () => IterableIterator<any>
>foo : () => IterableIterator<undefined>
}
2 changes: 1 addition & 1 deletion tests/baselines/reference/YieldExpression13_es6.types
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
=== tests/cases/conformance/es6/yieldExpressions/YieldExpression13_es6.ts ===
function* foo() { yield }
>foo : () => IterableIterator<any>
>foo : () => IterableIterator<undefined>
>yield : any

2 changes: 1 addition & 1 deletion tests/baselines/reference/YieldExpression3_es6.types
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== tests/cases/conformance/es6/yieldExpressions/YieldExpression3_es6.ts ===
function* foo() {
>foo : () => IterableIterator<any>
>foo : () => IterableIterator<undefined>

yield
>yield : any
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/YieldExpression4_es6.types
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== tests/cases/conformance/es6/yieldExpressions/YieldExpression4_es6.ts ===
function* foo() {
>foo : () => IterableIterator<any>
>foo : () => IterableIterator<undefined>

yield;
>yield : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class C2 {
>C2 : C2

async * f() {
>f : () => AsyncIterableIterator<any>
>f : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class C2 {
>C2 : C2

async * f() {
>f : () => AsyncIterableIterator<any>
>f : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class C2 {
>C2 : C2

async * f() {
>f : () => AsyncIterableIterator<any>
>f : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ async function * f1() {
}
=== tests/cases/conformance/emitter/es2015/asyncGenerators/F2.ts ===
async function * f2() {
>f2 : () => AsyncIterableIterator<any>
>f2 : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ async function * f1() {
}
=== tests/cases/conformance/emitter/es5/asyncGenerators/F2.ts ===
async function * f2() {
>f2 : () => AsyncIterableIterator<any>
>f2 : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ async function * f1() {
}
=== tests/cases/conformance/emitter/esnext/asyncGenerators/F2.ts ===
async function * f2() {
>f2 : () => AsyncIterableIterator<any>
>f2 : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const f1 = async function * () {
}
=== tests/cases/conformance/emitter/es2015/asyncGenerators/F2.ts ===
const f2 = async function * () {
>f2 : () => AsyncIterableIterator<any>
>async function * () { const x = yield;} : () => AsyncIterableIterator<any>
>f2 : () => AsyncIterableIterator<undefined>
>async function * () { const x = yield;} : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const f1 = async function * () {
}
=== tests/cases/conformance/emitter/es5/asyncGenerators/F2.ts ===
const f2 = async function * () {
>f2 : () => AsyncIterableIterator<any>
>async function * () { const x = yield;} : () => AsyncIterableIterator<any>
>f2 : () => AsyncIterableIterator<undefined>
>async function * () { const x = yield;} : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const f1 = async function * () {
}
=== tests/cases/conformance/emitter/esnext/asyncGenerators/F2.ts ===
const f2 = async function * () {
>f2 : () => AsyncIterableIterator<any>
>async function * () { const x = yield;} : () => AsyncIterableIterator<any>
>f2 : () => AsyncIterableIterator<undefined>
>async function * () { const x = yield;} : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const o1 = {
}
=== tests/cases/conformance/emitter/es2015/asyncGenerators/O2.ts ===
const o2 = {
>o2 : { f(): AsyncIterableIterator<any>; }
>{ async * f() { const x = yield; }} : { f(): AsyncIterableIterator<any>; }
>o2 : { f(): AsyncIterableIterator<undefined>; }
>{ async * f() { const x = yield; }} : { f(): AsyncIterableIterator<undefined>; }

async * f() {
>f : () => AsyncIterableIterator<any>
>f : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const o1 = {
}
=== tests/cases/conformance/emitter/es5/asyncGenerators/O2.ts ===
const o2 = {
>o2 : { f(): AsyncIterableIterator<any>; }
>{ async * f() { const x = yield; }} : { f(): AsyncIterableIterator<any>; }
>o2 : { f(): AsyncIterableIterator<undefined>; }
>{ async * f() { const x = yield; }} : { f(): AsyncIterableIterator<undefined>; }

async * f() {
>f : () => AsyncIterableIterator<any>
>f : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const o1 = {
}
=== tests/cases/conformance/emitter/esnext/asyncGenerators/O2.ts ===
const o2 = {
>o2 : { f(): AsyncIterableIterator<any>; }
>{ async * f() { const x = yield; }} : { f(): AsyncIterableIterator<any>; }
>o2 : { f(): AsyncIterableIterator<undefined>; }
>{ async * f() { const x = yield; }} : { f(): AsyncIterableIterator<undefined>; }

async * f() {
>f : () => AsyncIterableIterator<any>
>f : () => AsyncIterableIterator<undefined>

const x = yield;
>x : any
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/generatorES6InAMDModule.types
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== tests/cases/compiler/generatorES6InAMDModule.ts ===
export function* foo() {
>foo : () => IterableIterator<any>
>foo : () => IterableIterator<undefined>

yield
>yield : any
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/generatorES6_1.types
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== tests/cases/compiler/generatorES6_1.ts ===
function* foo() {
>foo : () => IterableIterator<any>
>foo : () => IterableIterator<undefined>

yield
>yield : any
Expand Down
9 changes: 0 additions & 9 deletions tests/baselines/reference/generatorTypeCheck48.errors.txt

This file was deleted.

2 changes: 1 addition & 1 deletion tests/baselines/reference/generatorTypeCheck48.types
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck48.ts ===
function* g() {
>g : () => IterableIterator<any>
>g : () => IterableIterator<undefined>

yield;
>yield : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class C13 {
>C13 : C13

async * f() {
>f : () => AsyncIterableIterator<any>
>f : () => AsyncIterableIterator<undefined>

yield;
>yield : any
Expand Down Expand Up @@ -250,7 +250,7 @@ class C22 {
>C22 : C22

async * f() {
>f : () => AsyncIterableIterator<any>
>f : () => AsyncIterableIterator<undefined>

const x = { [yield]: 1 };
>x : { [x: number]: number; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ async function * f12() {
}
=== tests/cases/conformance/parser/ecmascriptnext/asyncGenerators/yieldIsOk.ts ===
async function * f13() {
>f13 : () => AsyncIterableIterator<any>
>f13 : () => AsyncIterableIterator<undefined>

yield;
>yield : any
Expand Down Expand Up @@ -157,7 +157,7 @@ async function * f20() {
}
=== tests/cases/conformance/parser/ecmascriptnext/asyncGenerators/yieldInNestedComputedPropertyIsOk.ts ===
async function * f21() {
>f21 : () => AsyncIterableIterator<any>
>f21 : () => AsyncIterableIterator<undefined>

const x = { [yield]: 1 };
>x : { [x: number]: number; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ const f12 = async function * () {
};
=== tests/cases/conformance/parser/ecmascriptnext/asyncGenerators/yieldIsOk.ts ===
const f13 = async function * () {
>f13 : () => AsyncIterableIterator<any>
>async function * () { yield;} : () => AsyncIterableIterator<any>
>f13 : () => AsyncIterableIterator<undefined>
>async function * () { yield;} : () => AsyncIterableIterator<undefined>

yield;
>yield : any
Expand Down Expand Up @@ -198,8 +198,8 @@ const f20 = async function * () {
};
=== tests/cases/conformance/parser/ecmascriptnext/asyncGenerators/yieldInNestedComputedPropertyIsOk.ts ===
const f21 = async function *() {
>f21 : () => AsyncIterableIterator<any>
>async function *() { const x = { [yield]: 1 };} : () => AsyncIterableIterator<any>
>f21 : () => AsyncIterableIterator<undefined>
>async function *() { const x = { [yield]: 1 };} : () => AsyncIterableIterator<undefined>

const x = { [yield]: 1 };
>x : { [x: number]: number; }
Expand Down
Loading