mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 20:25:23 -06:00
Infer return types from yield and yield* expressions
This commit is contained in:
parent
44777b9737
commit
7fce775149
@ -7260,16 +7260,35 @@ module ts {
|
||||
}
|
||||
else {
|
||||
// Aggregate the types of expressions within all the return statements.
|
||||
let types = checkAndAggregateReturnExpressionTypes(<Block>func.body, contextualMapper);
|
||||
if (types.length === 0) {
|
||||
return voidType;
|
||||
let types: Type[];
|
||||
if (func.asteriskToken) {
|
||||
types = checkAndAggregateYieldOperandTypes(<Block>func.body, contextualMapper);
|
||||
if (types.length === 0) {
|
||||
return createIterableIteratorType(anyType);
|
||||
}
|
||||
}
|
||||
else {
|
||||
types = checkAndAggregateReturnExpressionTypes(<Block>func.body, contextualMapper);
|
||||
if (types.length === 0) {
|
||||
return voidType;
|
||||
}
|
||||
}
|
||||
// When return statements are contextually typed we allow the return type to be a union type. Otherwise we require the
|
||||
// return expressions to have a best common supertype.
|
||||
type = contextualSignature ? getUnionType(types) : getCommonSupertype(types);
|
||||
if (!type) {
|
||||
error(func, Diagnostics.No_best_common_type_exists_among_return_expressions);
|
||||
return unknownType;
|
||||
if (func.asteriskToken) {
|
||||
error(func, Diagnostics.No_best_common_type_exists_among_yield_expressions);
|
||||
return createIterableIteratorType(unknownType);
|
||||
}
|
||||
else {
|
||||
error(func, Diagnostics.No_best_common_type_exists_among_return_expressions);
|
||||
return unknownType;
|
||||
}
|
||||
}
|
||||
|
||||
if (func.asteriskToken) {
|
||||
type = createIterableIteratorType(type);
|
||||
}
|
||||
}
|
||||
if (!contextualSignature) {
|
||||
@ -7278,7 +7297,28 @@ module ts {
|
||||
return getWidenedType(type);
|
||||
}
|
||||
|
||||
/// Returns a set of types relating to every return expression relating to a function block.
|
||||
function checkAndAggregateYieldOperandTypes(body: Block, contextualMapper?: TypeMapper): Type[] {
|
||||
let aggregatedTypes: Type[] = [];
|
||||
|
||||
forEachYieldExpression(body, yieldExpression => {
|
||||
let expr = yieldExpression.expression;
|
||||
if (expr) {
|
||||
let type = checkExpressionCached(expr, contextualMapper);
|
||||
|
||||
if (yieldExpression.asteriskToken) {
|
||||
// A yield* expression effectively yields everything that its operand yields
|
||||
type = checkIteratedType(type, yieldExpression.expression);
|
||||
}
|
||||
|
||||
if (!contains(aggregatedTypes, type)) {
|
||||
aggregatedTypes.push(type);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return aggregatedTypes;
|
||||
}
|
||||
|
||||
function checkAndAggregateReturnExpressionTypes(body: Block, contextualMapper?: TypeMapper): Type[] {
|
||||
let aggregatedTypes: Type[] = [];
|
||||
|
||||
@ -11253,93 +11293,6 @@ module ts {
|
||||
return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments;
|
||||
}
|
||||
|
||||
function isTypeNode(node: Node): boolean {
|
||||
if (SyntaxKind.FirstTypeNode <= node.kind && node.kind <= SyntaxKind.LastTypeNode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.AnyKeyword:
|
||||
case SyntaxKind.NumberKeyword:
|
||||
case SyntaxKind.StringKeyword:
|
||||
case SyntaxKind.BooleanKeyword:
|
||||
case SyntaxKind.SymbolKeyword:
|
||||
return true;
|
||||
case SyntaxKind.VoidKeyword:
|
||||
return node.parent.kind !== SyntaxKind.VoidExpression;
|
||||
case SyntaxKind.StringLiteral:
|
||||
// Specialized signatures can have string literals as their parameters' type names
|
||||
return node.parent.kind === SyntaxKind.Parameter;
|
||||
case SyntaxKind.ExpressionWithTypeArguments:
|
||||
return true;
|
||||
|
||||
// Identifiers and qualified names may be type nodes, depending on their context. Climb
|
||||
// above them to find the lowest container
|
||||
case SyntaxKind.Identifier:
|
||||
// If the identifier is the RHS of a qualified name, then it's a type iff its parent is.
|
||||
if (node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node) {
|
||||
node = node.parent;
|
||||
}
|
||||
else if (node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node) {
|
||||
node = node.parent;
|
||||
}
|
||||
// fall through
|
||||
case SyntaxKind.QualifiedName:
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
// At this point, node is either a qualified name or an identifier
|
||||
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression,
|
||||
"'node' was expected to be a qualified name, identifier or property access in 'isTypeNode'.");
|
||||
|
||||
let parent = node.parent;
|
||||
if (parent.kind === SyntaxKind.TypeQuery) {
|
||||
return false;
|
||||
}
|
||||
// Do not recursively call isTypeNode on the parent. In the example:
|
||||
//
|
||||
// let a: A.B.C;
|
||||
//
|
||||
// Calling isTypeNode would consider the qualified name A.B a type node. Only C or
|
||||
// A.B.C is a type node.
|
||||
if (SyntaxKind.FirstTypeNode <= parent.kind && parent.kind <= SyntaxKind.LastTypeNode) {
|
||||
return true;
|
||||
}
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.ExpressionWithTypeArguments:
|
||||
return true;
|
||||
case SyntaxKind.TypeParameter:
|
||||
return node === (<TypeParameterDeclaration>parent).constraint;
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.PropertySignature:
|
||||
case SyntaxKind.Parameter:
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return node === (<VariableLikeDeclaration>parent).type;
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
return node === (<FunctionLikeDeclaration>parent).type;
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
case SyntaxKind.IndexSignature:
|
||||
return node === (<SignatureDeclaration>parent).type;
|
||||
case SyntaxKind.TypeAssertionExpression:
|
||||
return node === (<TypeAssertion>parent).type;
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
return (<CallExpression>parent).typeArguments && indexOf((<CallExpression>parent).typeArguments, node) >= 0;
|
||||
case SyntaxKind.TaggedTemplateExpression:
|
||||
// TODO (drosen): TaggedTemplateExpressions may eventually support type arguments.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment {
|
||||
while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) {
|
||||
nodeOnRightSide = <QualifiedName>nodeOnRightSide.parent;
|
||||
|
||||
@ -367,6 +367,7 @@ module ts {
|
||||
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." },
|
||||
A_rest_element_cannot_contain_a_binding_pattern: { code: 2501, category: DiagnosticCategory.Error, key: "A rest element cannot contain a binding pattern." },
|
||||
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." },
|
||||
No_best_common_type_exists_among_yield_expressions: { code: 2503, category: DiagnosticCategory.Error, key: "No best common type exists among yield expressions." },
|
||||
Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." },
|
||||
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}'." },
|
||||
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}'." },
|
||||
|
||||
@ -1457,6 +1457,10 @@
|
||||
"category": "Error",
|
||||
"code": 2502
|
||||
},
|
||||
"No best common type exists among yield expressions.": {
|
||||
"category": "Error",
|
||||
"code": 2503
|
||||
},
|
||||
|
||||
"Import declaration '{0}' is using private name '{1}'.": {
|
||||
"category": "Error",
|
||||
|
||||
@ -408,6 +408,93 @@ module ts {
|
||||
|
||||
export let fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*<reference\s+path\s*=\s*)('|")(.+?)\2.*?\/>/
|
||||
|
||||
export function isTypeNode(node: Node): boolean {
|
||||
if (SyntaxKind.FirstTypeNode <= node.kind && node.kind <= SyntaxKind.LastTypeNode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.AnyKeyword:
|
||||
case SyntaxKind.NumberKeyword:
|
||||
case SyntaxKind.StringKeyword:
|
||||
case SyntaxKind.BooleanKeyword:
|
||||
case SyntaxKind.SymbolKeyword:
|
||||
return true;
|
||||
case SyntaxKind.VoidKeyword:
|
||||
return node.parent.kind !== SyntaxKind.VoidExpression;
|
||||
case SyntaxKind.StringLiteral:
|
||||
// Specialized signatures can have string literals as their parameters' type names
|
||||
return node.parent.kind === SyntaxKind.Parameter;
|
||||
case SyntaxKind.ExpressionWithTypeArguments:
|
||||
return true;
|
||||
|
||||
// Identifiers and qualified names may be type nodes, depending on their context. Climb
|
||||
// above them to find the lowest container
|
||||
case SyntaxKind.Identifier:
|
||||
// If the identifier is the RHS of a qualified name, then it's a type iff its parent is.
|
||||
if (node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node) {
|
||||
node = node.parent;
|
||||
}
|
||||
else if (node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node) {
|
||||
node = node.parent;
|
||||
}
|
||||
// fall through
|
||||
case SyntaxKind.QualifiedName:
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
// At this point, node is either a qualified name or an identifier
|
||||
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression,
|
||||
"'node' was expected to be a qualified name, identifier or property access in 'isTypeNode'.");
|
||||
|
||||
let parent = node.parent;
|
||||
if (parent.kind === SyntaxKind.TypeQuery) {
|
||||
return false;
|
||||
}
|
||||
// Do not recursively call isTypeNode on the parent. In the example:
|
||||
//
|
||||
// let a: A.B.C;
|
||||
//
|
||||
// Calling isTypeNode would consider the qualified name A.B a type node. Only C or
|
||||
// A.B.C is a type node.
|
||||
if (SyntaxKind.FirstTypeNode <= parent.kind && parent.kind <= SyntaxKind.LastTypeNode) {
|
||||
return true;
|
||||
}
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.ExpressionWithTypeArguments:
|
||||
return true;
|
||||
case SyntaxKind.TypeParameter:
|
||||
return node === (<TypeParameterDeclaration>parent).constraint;
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.PropertySignature:
|
||||
case SyntaxKind.Parameter:
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return node === (<VariableLikeDeclaration>parent).type;
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
return node === (<FunctionLikeDeclaration>parent).type;
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
case SyntaxKind.IndexSignature:
|
||||
return node === (<SignatureDeclaration>parent).type;
|
||||
case SyntaxKind.TypeAssertionExpression:
|
||||
return node === (<TypeAssertion>parent).type;
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
return (<CallExpression>parent).typeArguments && indexOf((<CallExpression>parent).typeArguments, node) >= 0;
|
||||
case SyntaxKind.TaggedTemplateExpression:
|
||||
// TODO (drosen): TaggedTemplateExpressions may eventually support type arguments.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Warning: This has the same semantics as the forEach family of functions,
|
||||
// in that traversal terminates in the event that 'visitor' supplies a truthy value.
|
||||
export function forEachReturnStatement<T>(body: Block, visitor: (stmt: ReturnStatement) => T): T {
|
||||
@ -438,6 +525,52 @@ module ts {
|
||||
}
|
||||
}
|
||||
|
||||
export function forEachYieldExpression(body: Block, visitor: (expr: YieldExpression) => void): void {
|
||||
|
||||
return traverse(body);
|
||||
|
||||
function traverse(node: Node): void {
|
||||
// Yield expressions may occur in decorators
|
||||
if (node.decorators) {
|
||||
forEach(node.decorators, traverse);
|
||||
}
|
||||
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.YieldExpression:
|
||||
visitor(<YieldExpression>node);
|
||||
let operand = (<YieldExpression>node).expression;
|
||||
if (operand) {
|
||||
traverse(operand);
|
||||
}
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
// These are not allowed inside a generator now, but eventually they may be allowed
|
||||
// as local types. Regardless, any yield statements contained within them should be
|
||||
// skipped in this traversal.
|
||||
return;
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
// A class declaration/expression may extend a yield expression
|
||||
forEach((<ClassDeclaration>node).heritageClauses, traverse);
|
||||
return;
|
||||
default:
|
||||
if (isFunctionLike(node)) {
|
||||
let name = (<FunctionLikeDeclaration>node).name;
|
||||
if (name && name.kind === SyntaxKind.ComputedPropertyName) {
|
||||
traverse((<ComputedPropertyName>name).expression);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!isTypeNode(node)) {
|
||||
// This is the general case, which should include mostly expressions and statements.
|
||||
// Also includes NodeArrays.
|
||||
forEachChild(node, traverse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isVariableLike(node: Node): boolean {
|
||||
if (node) {
|
||||
switch (node.kind) {
|
||||
@ -736,6 +869,7 @@ module ts {
|
||||
case SyntaxKind.TemplateExpression:
|
||||
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
case SyntaxKind.OmittedExpression:
|
||||
case SyntaxKind.YieldExpression:
|
||||
return true;
|
||||
case SyntaxKind.QualifiedName:
|
||||
while (node.parent.kind === SyntaxKind.QualifiedName) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user