Skip to content

Commit 7fce775

Browse files
committed
Infer return types from yield and yield* expressions
1 parent 44777b9 commit 7fce775

File tree

4 files changed

+185
-93
lines changed

4 files changed

+185
-93
lines changed

src/compiler/checker.ts

Lines changed: 46 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -7260,16 +7260,35 @@ module ts {
72607260
}
72617261
else {
72627262
// Aggregate the types of expressions within all the return statements.
7263-
let types = checkAndAggregateReturnExpressionTypes(<Block>func.body, contextualMapper);
7264-
if (types.length === 0) {
7265-
return voidType;
7263+
let types: Type[];
7264+
if (func.asteriskToken) {
7265+
types = checkAndAggregateYieldOperandTypes(<Block>func.body, contextualMapper);
7266+
if (types.length === 0) {
7267+
return createIterableIteratorType(anyType);
7268+
}
7269+
}
7270+
else {
7271+
types = checkAndAggregateReturnExpressionTypes(<Block>func.body, contextualMapper);
7272+
if (types.length === 0) {
7273+
return voidType;
7274+
}
72667275
}
72677276
// When return statements are contextually typed we allow the return type to be a union type. Otherwise we require the
72687277
// return expressions to have a best common supertype.
72697278
type = contextualSignature ? getUnionType(types) : getCommonSupertype(types);
72707279
if (!type) {
7271-
error(func, Diagnostics.No_best_common_type_exists_among_return_expressions);
7272-
return unknownType;
7280+
if (func.asteriskToken) {
7281+
error(func, Diagnostics.No_best_common_type_exists_among_yield_expressions);
7282+
return createIterableIteratorType(unknownType);
7283+
}
7284+
else {
7285+
error(func, Diagnostics.No_best_common_type_exists_among_return_expressions);
7286+
return unknownType;
7287+
}
7288+
}
7289+
7290+
if (func.asteriskToken) {
7291+
type = createIterableIteratorType(type);
72737292
}
72747293
}
72757294
if (!contextualSignature) {
@@ -7278,7 +7297,28 @@ module ts {
72787297
return getWidenedType(type);
72797298
}
72807299

7281-
/// Returns a set of types relating to every return expression relating to a function block.
7300+
function checkAndAggregateYieldOperandTypes(body: Block, contextualMapper?: TypeMapper): Type[] {
7301+
let aggregatedTypes: Type[] = [];
7302+
7303+
forEachYieldExpression(body, yieldExpression => {
7304+
let expr = yieldExpression.expression;
7305+
if (expr) {
7306+
let type = checkExpressionCached(expr, contextualMapper);
7307+
7308+
if (yieldExpression.asteriskToken) {
7309+
// A yield* expression effectively yields everything that its operand yields
7310+
type = checkIteratedType(type, yieldExpression.expression);
7311+
}
7312+
7313+
if (!contains(aggregatedTypes, type)) {
7314+
aggregatedTypes.push(type);
7315+
}
7316+
}
7317+
});
7318+
7319+
return aggregatedTypes;
7320+
}
7321+
72827322
function checkAndAggregateReturnExpressionTypes(body: Block, contextualMapper?: TypeMapper): Type[] {
72837323
let aggregatedTypes: Type[] = [];
72847324

@@ -11253,93 +11293,6 @@ module ts {
1125311293
return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments;
1125411294
}
1125511295

11256-
function isTypeNode(node: Node): boolean {
11257-
if (SyntaxKind.FirstTypeNode <= node.kind && node.kind <= SyntaxKind.LastTypeNode) {
11258-
return true;
11259-
}
11260-
11261-
switch (node.kind) {
11262-
case SyntaxKind.AnyKeyword:
11263-
case SyntaxKind.NumberKeyword:
11264-
case SyntaxKind.StringKeyword:
11265-
case SyntaxKind.BooleanKeyword:
11266-
case SyntaxKind.SymbolKeyword:
11267-
return true;
11268-
case SyntaxKind.VoidKeyword:
11269-
return node.parent.kind !== SyntaxKind.VoidExpression;
11270-
case SyntaxKind.StringLiteral:
11271-
// Specialized signatures can have string literals as their parameters' type names
11272-
return node.parent.kind === SyntaxKind.Parameter;
11273-
case SyntaxKind.ExpressionWithTypeArguments:
11274-
return true;
11275-
11276-
// Identifiers and qualified names may be type nodes, depending on their context. Climb
11277-
// above them to find the lowest container
11278-
case SyntaxKind.Identifier:
11279-
// If the identifier is the RHS of a qualified name, then it's a type iff its parent is.
11280-
if (node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node) {
11281-
node = node.parent;
11282-
}
11283-
else if (node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node) {
11284-
node = node.parent;
11285-
}
11286-
// fall through
11287-
case SyntaxKind.QualifiedName:
11288-
case SyntaxKind.PropertyAccessExpression:
11289-
// At this point, node is either a qualified name or an identifier
11290-
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression,
11291-
"'node' was expected to be a qualified name, identifier or property access in 'isTypeNode'.");
11292-
11293-
let parent = node.parent;
11294-
if (parent.kind === SyntaxKind.TypeQuery) {
11295-
return false;
11296-
}
11297-
// Do not recursively call isTypeNode on the parent. In the example:
11298-
//
11299-
// let a: A.B.C;
11300-
//
11301-
// Calling isTypeNode would consider the qualified name A.B a type node. Only C or
11302-
// A.B.C is a type node.
11303-
if (SyntaxKind.FirstTypeNode <= parent.kind && parent.kind <= SyntaxKind.LastTypeNode) {
11304-
return true;
11305-
}
11306-
switch (parent.kind) {
11307-
case SyntaxKind.ExpressionWithTypeArguments:
11308-
return true;
11309-
case SyntaxKind.TypeParameter:
11310-
return node === (<TypeParameterDeclaration>parent).constraint;
11311-
case SyntaxKind.PropertyDeclaration:
11312-
case SyntaxKind.PropertySignature:
11313-
case SyntaxKind.Parameter:
11314-
case SyntaxKind.VariableDeclaration:
11315-
return node === (<VariableLikeDeclaration>parent).type;
11316-
case SyntaxKind.FunctionDeclaration:
11317-
case SyntaxKind.FunctionExpression:
11318-
case SyntaxKind.ArrowFunction:
11319-
case SyntaxKind.Constructor:
11320-
case SyntaxKind.MethodDeclaration:
11321-
case SyntaxKind.MethodSignature:
11322-
case SyntaxKind.GetAccessor:
11323-
case SyntaxKind.SetAccessor:
11324-
return node === (<FunctionLikeDeclaration>parent).type;
11325-
case SyntaxKind.CallSignature:
11326-
case SyntaxKind.ConstructSignature:
11327-
case SyntaxKind.IndexSignature:
11328-
return node === (<SignatureDeclaration>parent).type;
11329-
case SyntaxKind.TypeAssertionExpression:
11330-
return node === (<TypeAssertion>parent).type;
11331-
case SyntaxKind.CallExpression:
11332-
case SyntaxKind.NewExpression:
11333-
return (<CallExpression>parent).typeArguments && indexOf((<CallExpression>parent).typeArguments, node) >= 0;
11334-
case SyntaxKind.TaggedTemplateExpression:
11335-
// TODO (drosen): TaggedTemplateExpressions may eventually support type arguments.
11336-
return false;
11337-
}
11338-
}
11339-
11340-
return false;
11341-
}
11342-
1134311296
function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment {
1134411297
while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) {
1134511298
nodeOnRightSide = <QualifiedName>nodeOnRightSide.parent;

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ module ts {
367367
A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments: { code: 2500, category: DiagnosticCategory.Error, key: "A class can only implement an identifier/qualified-name with optional type arguments." },
368368
A_rest_element_cannot_contain_a_binding_pattern: { code: 2501, category: DiagnosticCategory.Error, key: "A rest element cannot contain a binding pattern." },
369369
A_return_statement_cannot_specify_a_value_in_a_generator_function: { code: 2502, category: DiagnosticCategory.Error, key: "A return statement cannot specify a value in a generator function." },
370+
No_best_common_type_exists_among_yield_expressions: { code: 2503, category: DiagnosticCategory.Error, key: "No best common type exists among yield expressions." },
370371
Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." },
371372
Type_parameter_0_of_exported_class_has_or_is_using_private_name_1: { code: 4002, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported class has or is using private name '{1}'." },
372373
Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1: { code: 4004, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported interface has or is using private name '{1}'." },

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,10 @@
14571457
"category": "Error",
14581458
"code": 2502
14591459
},
1460+
"No best common type exists among yield expressions.": {
1461+
"category": "Error",
1462+
"code": 2503
1463+
},
14601464

14611465
"Import declaration '{0}' is using private name '{1}'.": {
14621466
"category": "Error",

src/compiler/utilities.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,93 @@ module ts {
408408

409409
export let fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*<reference\s+path\s*=\s*)('|")(.+?)\2.*?\/>/
410410

411+
export function isTypeNode(node: Node): boolean {
412+
if (SyntaxKind.FirstTypeNode <= node.kind && node.kind <= SyntaxKind.LastTypeNode) {
413+
return true;
414+
}
415+
416+
switch (node.kind) {
417+
case SyntaxKind.AnyKeyword:
418+
case SyntaxKind.NumberKeyword:
419+
case SyntaxKind.StringKeyword:
420+
case SyntaxKind.BooleanKeyword:
421+
case SyntaxKind.SymbolKeyword:
422+
return true;
423+
case SyntaxKind.VoidKeyword:
424+
return node.parent.kind !== SyntaxKind.VoidExpression;
425+
case SyntaxKind.StringLiteral:
426+
// Specialized signatures can have string literals as their parameters' type names
427+
return node.parent.kind === SyntaxKind.Parameter;
428+
case SyntaxKind.ExpressionWithTypeArguments:
429+
return true;
430+
431+
// Identifiers and qualified names may be type nodes, depending on their context. Climb
432+
// above them to find the lowest container
433+
case SyntaxKind.Identifier:
434+
// If the identifier is the RHS of a qualified name, then it's a type iff its parent is.
435+
if (node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node) {
436+
node = node.parent;
437+
}
438+
else if (node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node) {
439+
node = node.parent;
440+
}
441+
// fall through
442+
case SyntaxKind.QualifiedName:
443+
case SyntaxKind.PropertyAccessExpression:
444+
// At this point, node is either a qualified name or an identifier
445+
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression,
446+
"'node' was expected to be a qualified name, identifier or property access in 'isTypeNode'.");
447+
448+
let parent = node.parent;
449+
if (parent.kind === SyntaxKind.TypeQuery) {
450+
return false;
451+
}
452+
// Do not recursively call isTypeNode on the parent. In the example:
453+
//
454+
// let a: A.B.C;
455+
//
456+
// Calling isTypeNode would consider the qualified name A.B a type node. Only C or
457+
// A.B.C is a type node.
458+
if (SyntaxKind.FirstTypeNode <= parent.kind && parent.kind <= SyntaxKind.LastTypeNode) {
459+
return true;
460+
}
461+
switch (parent.kind) {
462+
case SyntaxKind.ExpressionWithTypeArguments:
463+
return true;
464+
case SyntaxKind.TypeParameter:
465+
return node === (<TypeParameterDeclaration>parent).constraint;
466+
case SyntaxKind.PropertyDeclaration:
467+
case SyntaxKind.PropertySignature:
468+
case SyntaxKind.Parameter:
469+
case SyntaxKind.VariableDeclaration:
470+
return node === (<VariableLikeDeclaration>parent).type;
471+
case SyntaxKind.FunctionDeclaration:
472+
case SyntaxKind.FunctionExpression:
473+
case SyntaxKind.ArrowFunction:
474+
case SyntaxKind.Constructor:
475+
case SyntaxKind.MethodDeclaration:
476+
case SyntaxKind.MethodSignature:
477+
case SyntaxKind.GetAccessor:
478+
case SyntaxKind.SetAccessor:
479+
return node === (<FunctionLikeDeclaration>parent).type;
480+
case SyntaxKind.CallSignature:
481+
case SyntaxKind.ConstructSignature:
482+
case SyntaxKind.IndexSignature:
483+
return node === (<SignatureDeclaration>parent).type;
484+
case SyntaxKind.TypeAssertionExpression:
485+
return node === (<TypeAssertion>parent).type;
486+
case SyntaxKind.CallExpression:
487+
case SyntaxKind.NewExpression:
488+
return (<CallExpression>parent).typeArguments && indexOf((<CallExpression>parent).typeArguments, node) >= 0;
489+
case SyntaxKind.TaggedTemplateExpression:
490+
// TODO (drosen): TaggedTemplateExpressions may eventually support type arguments.
491+
return false;
492+
}
493+
}
494+
495+
return false;
496+
}
497+
411498
// Warning: This has the same semantics as the forEach family of functions,
412499
// in that traversal terminates in the event that 'visitor' supplies a truthy value.
413500
export function forEachReturnStatement<T>(body: Block, visitor: (stmt: ReturnStatement) => T): T {
@@ -438,6 +525,52 @@ module ts {
438525
}
439526
}
440527

528+
export function forEachYieldExpression(body: Block, visitor: (expr: YieldExpression) => void): void {
529+
530+
return traverse(body);
531+
532+
function traverse(node: Node): void {
533+
// Yield expressions may occur in decorators
534+
if (node.decorators) {
535+
forEach(node.decorators, traverse);
536+
}
537+
538+
switch (node.kind) {
539+
case SyntaxKind.YieldExpression:
540+
visitor(<YieldExpression>node);
541+
let operand = (<YieldExpression>node).expression;
542+
if (operand) {
543+
traverse(operand);
544+
}
545+
case SyntaxKind.EnumDeclaration:
546+
case SyntaxKind.InterfaceDeclaration:
547+
case SyntaxKind.ModuleDeclaration:
548+
case SyntaxKind.TypeAliasDeclaration:
549+
// These are not allowed inside a generator now, but eventually they may be allowed
550+
// as local types. Regardless, any yield statements contained within them should be
551+
// skipped in this traversal.
552+
return;
553+
case SyntaxKind.ClassDeclaration:
554+
// A class declaration/expression may extend a yield expression
555+
forEach((<ClassDeclaration>node).heritageClauses, traverse);
556+
return;
557+
default:
558+
if (isFunctionLike(node)) {
559+
let name = (<FunctionLikeDeclaration>node).name;
560+
if (name && name.kind === SyntaxKind.ComputedPropertyName) {
561+
traverse((<ComputedPropertyName>name).expression);
562+
return;
563+
}
564+
}
565+
else if (!isTypeNode(node)) {
566+
// This is the general case, which should include mostly expressions and statements.
567+
// Also includes NodeArrays.
568+
forEachChild(node, traverse);
569+
}
570+
}
571+
}
572+
}
573+
441574
export function isVariableLike(node: Node): boolean {
442575
if (node) {
443576
switch (node.kind) {
@@ -736,6 +869,7 @@ module ts {
736869
case SyntaxKind.TemplateExpression:
737870
case SyntaxKind.NoSubstitutionTemplateLiteral:
738871
case SyntaxKind.OmittedExpression:
872+
case SyntaxKind.YieldExpression:
739873
return true;
740874
case SyntaxKind.QualifiedName:
741875
while (node.parent.kind === SyntaxKind.QualifiedName) {

0 commit comments

Comments
 (0)