Skip to content

Commit 83d9aed

Browse files
committedOct 13, 2014
Correct contextual typing with union types
1 parent eee1602 commit 83d9aed

File tree

1 file changed

+107
-47
lines changed

1 file changed

+107
-47
lines changed
 

‎src/compiler/checker.ts

+107-47
Original file line numberDiff line numberDiff line change
@@ -4480,6 +4480,54 @@ module ts {
44804480
return undefined;
44814481
}
44824482

4483+
// Apply a mapping function to a contextual type and return the resulting type. If the contextual type
4484+
// is a union type, the mapping function is applied to each constituent type and a union of the resulting
4485+
// types is returned.
Has a conversation. Original line has a conversation.
4486+
function applyToContextualType(type: Type, mapper: (t: Type) => Type): Type {
4487+
if (!(type.flags & TypeFlags.Union)) {
4488+
return mapper(type);
4489+
}
4490+
var types = (<UnionType>type).types;
4491+
var mappedType: Type;
4492+
var mappedTypes: Type[];
4493+
for (var i = 0; i < types.length; i++) {
4494+
var t = mapper(types[i]);
4495+
if (t) {
4496+
if (!mappedType) {
4497+
mappedType = t;
4498+
}
4499+
else if (!mappedTypes) {
4500+
mappedTypes = [mappedType, t];
4501+
}
4502+
else {
4503+
mappedTypes.push(t);
4504+
}
Has a conversation. Original line has a conversation.
4505+
}
4506+
}
4507+
return mappedTypes ? getUnionType(mappedTypes) : mappedType;
4508+
}
4509+
4510+
function getTypeOfPropertyOfContextualType(type: Type, name: string) {
4511+
return applyToContextualType(type, t => {
4512+
var prop = getPropertyOfType(t, name);
4513+
return prop ? getTypeOfSymbol(prop) : undefined;
4514+
});
4515+
}
4516+
4517+
function getIndexTypeOfContextualType(type: Type, kind: IndexKind) {
4518+
return applyToContextualType(type, t => getIndexTypeOfType(t, kind));
4519+
}
4520+
4521+
// Return true if the given contextual type is a tuple-like type
4522+
function contextualTypeIsTupleType(type: Type): boolean {
Has a conversation. Original line has a conversation.
4523+
return !!(type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, t => getPropertyOfType(t, "0")) : getPropertyOfType(type, "0"));
4524+
}
4525+
4526+
// Return true if the given contextual type provides an index signature of the given kind
4527+
function contextualTypeHasIndexSignature(type: Type, kind: IndexKind): boolean {
4528+
return !!(type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, t => getIndexTypeOfType(t, kind)) : getIndexTypeOfType(type, kind));
4529+
}
4530+
44834531
// In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of
44844532
// the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one
44854533
// exists. Otherwise, it is the type of the string index signature in T, if one exists.
@@ -4489,11 +4537,9 @@ module ts {
44894537
var type = getContextualType(objectLiteral);
44904538
var name = declaration.name.text;
44914539
if (type && name) {
4492-
var prop = getPropertyOfType(type, name);
Has conversations. Original line has conversations.
4493-
if (prop) {
4494-
return getTypeOfSymbol(prop);
4495-
}
4496-
return isNumericName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String);
4540+
return getTypeOfPropertyOfContextualType(type, name) ||
4541+
isNumericName(name) && getIndexTypeOfContextualType(type, IndexKind.Number) ||
4542+
getIndexTypeOfContextualType(type, IndexKind.String);
44974543
}
44984544
return undefined;
44994545
}
@@ -4506,11 +4552,7 @@ module ts {
45064552
var type = getContextualType(arrayLiteral);
45074553
if (type) {
45084554
var index = indexOf(arrayLiteral.elements, node);
4509-
var prop = getPropertyOfType(type, "" + index);
4510-
if (prop) {
4511-
return getTypeOfSymbol(prop);
4512-
}
4513-
return getIndexTypeOfType(type, IndexKind.Number);
4555+
return getTypeOfPropertyOfContextualType(type, "" + index) || getIndexTypeOfContextualType(type, IndexKind.Number);
45144556
}
45154557
return undefined;
45164558
}
@@ -4528,7 +4570,6 @@ module ts {
45284570
// We cannot answer semantic questions within a with block, do not proceed any further
45294571
return undefined;
45304572
}
4531-
45324573
if (node.contextualType) {
45334574
return node.contextualType;
45344575
}
@@ -4558,18 +4599,45 @@ module ts {
45584599
return undefined;
45594600
}
45604601

4602+
// Return the single non-generic signature in the given type, or undefined if none exists
4603+
function getNonGenericSignature(type: Type): Signature {
Has conversations. Original line has conversations.
4604+
var signatures = getSignaturesOfType(type, SignatureKind.Call);
4605+
if (signatures.length !== 1) {
4606+
return undefined;
4607+
}
4608+
var signature = signatures[0];
4609+
if (signature.typeParameters) {
4610+
return undefined;
4611+
}
4612+
return signature;
4613+
}
4614+
4615+
// Return the contextual signature for a given expression node. A contextual type provides a
4616+
// contextual signature if it has a single call signature and if that call signature is non-generic.
4617+
// If the contextual type is a union type and each constituent type that has a contextual signature
4618+
// provides the same contextual signature, then the union type provides that contextual signature.
Has a conversation. Original line has a conversation.
45614619
function getContextualSignature(node: Expression): Signature {
45624620
var type = getContextualType(node);
4563-
if (type) {
4564-
var signatures = getSignaturesOfType(type, SignatureKind.Call);
4565-
if (signatures.length === 1) {
4566-
var signature = signatures[0];
4567-
if (!signature.typeParameters) {
4568-
return signature;
4621+
if (!type) {
4622+
return undefined;
4623+
}
4624+
if (!(type.flags & TypeFlags.Union)) {
4625+
return getNonGenericSignature(type);
4626+
}
4627+
var result: Signature;
4628+
var types = (<UnionType>type).types;
4629+
for (var i = 0; i < types.length; i++) {
4630+
var signature = getNonGenericSignature(types[i]);
4631+
if (signature) {
4632+
if (!result) {
4633+
result = signature;
4634+
}
4635+
else if (!compareSignatures(result, signature, /*compareReturnTypes*/ true, isTypeIdenticalTo)) {
Has conversations. Original line has conversations.
4636+
return undefined;
45694637
}
45704638
}
45714639
}
4572-
return undefined;
4640+
return result;
45734641
}
45744642

45754643
// Presence of a contextual type mapper indicates inferential typing, except the identityMapper object is
@@ -4579,24 +4647,16 @@ module ts {
45794647
}
45804648

45814649
function checkArrayLiteral(node: ArrayLiteral, contextualMapper?: TypeMapper): Type {
4582-
var contextualType = getContextualType(node);
45834650
var elements = node.elements;
4584-
var elementTypes: Type[] = [];
4585-
var isTupleLiteral: boolean = false;
4586-
for (var i = 0; i < elements.length; i++) {
4587-
if (contextualType && getPropertyOfType(contextualType, "" + i)) {
4588-
isTupleLiteral = true;
4589-
}
4590-
var element = elements[i];
4591-
var type = element.kind !== SyntaxKind.OmittedExpression ? checkExpression(element, contextualMapper) : undefinedType;
4592-
elementTypes.push(type);
4651+
if (!elements.length) {
4652+
return createArrayType(undefinedType);
45934653
}
4594-
if (isTupleLiteral) {
4654+
var elementTypes = map(elements, e => checkExpression(e, contextualMapper));
4655+
var contextualType = getContextualType(node);
4656+
if (contextualType && contextualTypeIsTupleType(contextualType)) {
Has conversations. Original line has conversations.
45954657
return createTupleType(elementTypes);
45964658
}
4597-
var contextualElementType = contextualType && !isInferentialContext(contextualMapper) ? getIndexTypeOfType(contextualType, IndexKind.Number) : undefined;
4598-
var elementType = elements.length || contextualElementType ? getBestCommonType(elementTypes, contextualElementType) : undefinedType;
4599-
return createArrayType(elementType);
4659+
return createArrayType(getUnionType(elementTypes));
46004660
}
46014661

46024662
function isNumericName(name: string) {
@@ -4607,7 +4667,6 @@ module ts {
46074667
var members = node.symbol.members;
46084668
var properties: SymbolTable = {};
46094669
var contextualType = getContextualType(node);
4610-
46114670
for (var id in members) {
46124671
if (hasProperty(members, id)) {
46134672
var member = members[id];
@@ -4645,21 +4704,19 @@ module ts {
46454704
return createAnonymousType(node.symbol, properties, emptyArray, emptyArray, stringIndexType, numberIndexType);
46464705

46474706
function getIndexType(kind: IndexKind) {
4648-
if (contextualType) {
Has a conversation. Original line has a conversation.
4649-
var indexType = getIndexTypeOfType(contextualType, kind);
4650-
if (indexType) {
4651-
var propTypes: Type[] = [];
4652-
for (var id in properties) {
4653-
if (hasProperty(properties, id)) {
4654-
if (kind === IndexKind.String || isNumericName(id)) {
4655-
var type = getTypeOfSymbol(properties[id]);
4656-
if (!contains(propTypes, type)) propTypes.push(type);
4657-
}
4707+
if (contextualType && contextualTypeHasIndexSignature(contextualType, kind)) {
4708+
var propTypes: Type[] = [];
4709+
for (var id in properties) {
4710+
if (hasProperty(properties, id)) {
4711+
if (kind === IndexKind.String || isNumericName(id)) {
4712+
var type = getTypeOfSymbol(properties[id]);
4713+
if (!contains(propTypes, type)) propTypes.push(type);
46584714
}
46594715
}
4660-
return getBestCommonType(propTypes, isInferentialContext(contextualMapper) ? undefined : indexType);
46614716
}
4717+
return propTypes.length ? getUnionType(propTypes) : undefinedType;
46624718
}
4719+
return undefined;
46634720
}
46644721
}
46654722

@@ -5249,11 +5306,12 @@ module ts {
52495306
}
52505307

52515308
function getReturnTypeFromBody(func: FunctionDeclaration, contextualMapper?: TypeMapper): Type {
5309+
var contextualSignature = getContextualSignature(func);
52525310
if (func.body.kind !== SyntaxKind.FunctionBlock) {
52535311
var unwidenedType = checkAndMarkExpression(func.body, contextualMapper);
52545312
var widenedType = getWidenedType(unwidenedType);
52555313

5256-
if (fullTypeCheck && compilerOptions.noImplicitAny && widenedType !== unwidenedType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) {
5314+
if (fullTypeCheck && compilerOptions.noImplicitAny && !contextualSignature && widenedType !== unwidenedType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) {
Has a conversation. Original line has a conversation.
52575315
error(func, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeToString(widenedType));
52585316
}
52595317

@@ -5267,7 +5325,7 @@ module ts {
52675325
if (types.length > 0) {
52685326
// When return statements are contextually typed we allow the return type to be a union type. Otherwise we require the
52695327
// return expressions to have a best common supertype.
5270-
var commonType = getContextualSignature(func) ? getUnionType(types) : getCommonSupertype(types);
5328+
var commonType = contextualSignature ? getUnionType(types) : getCommonSupertype(types);
52715329
if (!commonType) {
52725330
error(func, Diagnostics.No_best_common_type_exists_among_return_expressions);
52735331

@@ -5277,7 +5335,7 @@ module ts {
52775335
var widenedType = getWidenedType(commonType);
52785336

52795337
// Check and report for noImplicitAny if the best common type implicitly gets widened to an 'any'/arrays-of-'any' type.
5280-
if (fullTypeCheck && compilerOptions.noImplicitAny && widenedType !== commonType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) {
5338+
if (fullTypeCheck && compilerOptions.noImplicitAny && !contextualSignature && widenedType !== commonType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) {
Has a conversation. Original line has a conversation.
52815339
var typeName = typeToString(widenedType);
52825340

52835341
if (func.name) {
@@ -5776,6 +5834,8 @@ module ts {
57765834
return checkBinaryExpression(<BinaryExpression>node, contextualMapper);
57775835
case SyntaxKind.ConditionalExpression:
57785836
return checkConditionalExpression(<ConditionalExpression>node, contextualMapper);
5837+
case SyntaxKind.OmittedExpression:
5838+
return undefinedType;
57795839
}
57805840
return unknownType;
57815841
}

0 commit comments

Comments
 (0)
Please sign in to comment.