mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
Correct contextual typing with union types
This commit is contained in:
parent
eee1602b70
commit
83d9aed005
@ -4480,6 +4480,54 @@ module ts {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Apply a mapping function to a contextual type and return the resulting type. If the contextual type
|
||||
// is a union type, the mapping function is applied to each constituent type and a union of the resulting
|
||||
// types is returned.
|
||||
function applyToContextualType(type: Type, mapper: (t: Type) => Type): Type {
|
||||
if (!(type.flags & TypeFlags.Union)) {
|
||||
return mapper(type);
|
||||
}
|
||||
var types = (<UnionType>type).types;
|
||||
var mappedType: Type;
|
||||
var mappedTypes: Type[];
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
var t = mapper(types[i]);
|
||||
if (t) {
|
||||
if (!mappedType) {
|
||||
mappedType = t;
|
||||
}
|
||||
else if (!mappedTypes) {
|
||||
mappedTypes = [mappedType, t];
|
||||
}
|
||||
else {
|
||||
mappedTypes.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
return mappedTypes ? getUnionType(mappedTypes) : mappedType;
|
||||
}
|
||||
|
||||
function getTypeOfPropertyOfContextualType(type: Type, name: string) {
|
||||
return applyToContextualType(type, t => {
|
||||
var prop = getPropertyOfType(t, name);
|
||||
return prop ? getTypeOfSymbol(prop) : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
function getIndexTypeOfContextualType(type: Type, kind: IndexKind) {
|
||||
return applyToContextualType(type, t => getIndexTypeOfType(t, kind));
|
||||
}
|
||||
|
||||
// Return true if the given contextual type is a tuple-like type
|
||||
function contextualTypeIsTupleType(type: Type): boolean {
|
||||
return !!(type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, t => getPropertyOfType(t, "0")) : getPropertyOfType(type, "0"));
|
||||
}
|
||||
|
||||
// Return true if the given contextual type provides an index signature of the given kind
|
||||
function contextualTypeHasIndexSignature(type: Type, kind: IndexKind): boolean {
|
||||
return !!(type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, t => getIndexTypeOfType(t, kind)) : getIndexTypeOfType(type, kind));
|
||||
}
|
||||
|
||||
// In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of
|
||||
// the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one
|
||||
// exists. Otherwise, it is the type of the string index signature in T, if one exists.
|
||||
@ -4489,11 +4537,9 @@ module ts {
|
||||
var type = getContextualType(objectLiteral);
|
||||
var name = declaration.name.text;
|
||||
if (type && name) {
|
||||
var prop = getPropertyOfType(type, name);
|
||||
if (prop) {
|
||||
return getTypeOfSymbol(prop);
|
||||
}
|
||||
return isNumericName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String);
|
||||
return getTypeOfPropertyOfContextualType(type, name) ||
|
||||
isNumericName(name) && getIndexTypeOfContextualType(type, IndexKind.Number) ||
|
||||
getIndexTypeOfContextualType(type, IndexKind.String);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@ -4506,11 +4552,7 @@ module ts {
|
||||
var type = getContextualType(arrayLiteral);
|
||||
if (type) {
|
||||
var index = indexOf(arrayLiteral.elements, node);
|
||||
var prop = getPropertyOfType(type, "" + index);
|
||||
if (prop) {
|
||||
return getTypeOfSymbol(prop);
|
||||
}
|
||||
return getIndexTypeOfType(type, IndexKind.Number);
|
||||
return getTypeOfPropertyOfContextualType(type, "" + index) || getIndexTypeOfContextualType(type, IndexKind.Number);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@ -4528,7 +4570,6 @@ module ts {
|
||||
// We cannot answer semantic questions within a with block, do not proceed any further
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (node.contextualType) {
|
||||
return node.contextualType;
|
||||
}
|
||||
@ -4558,18 +4599,45 @@ module ts {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Return the single non-generic signature in the given type, or undefined if none exists
|
||||
function getNonGenericSignature(type: Type): Signature {
|
||||
var signatures = getSignaturesOfType(type, SignatureKind.Call);
|
||||
if (signatures.length !== 1) {
|
||||
return undefined;
|
||||
}
|
||||
var signature = signatures[0];
|
||||
if (signature.typeParameters) {
|
||||
return undefined;
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
// Return the contextual signature for a given expression node. A contextual type provides a
|
||||
// contextual signature if it has a single call signature and if that call signature is non-generic.
|
||||
// If the contextual type is a union type and each constituent type that has a contextual signature
|
||||
// provides the same contextual signature, then the union type provides that contextual signature.
|
||||
function getContextualSignature(node: Expression): Signature {
|
||||
var type = getContextualType(node);
|
||||
if (type) {
|
||||
var signatures = getSignaturesOfType(type, SignatureKind.Call);
|
||||
if (signatures.length === 1) {
|
||||
var signature = signatures[0];
|
||||
if (!signature.typeParameters) {
|
||||
return signature;
|
||||
if (!type) {
|
||||
return undefined;
|
||||
}
|
||||
if (!(type.flags & TypeFlags.Union)) {
|
||||
return getNonGenericSignature(type);
|
||||
}
|
||||
var result: Signature;
|
||||
var types = (<UnionType>type).types;
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
var signature = getNonGenericSignature(types[i]);
|
||||
if (signature) {
|
||||
if (!result) {
|
||||
result = signature;
|
||||
}
|
||||
else if (!compareSignatures(result, signature, /*compareReturnTypes*/ true, isTypeIdenticalTo)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Presence of a contextual type mapper indicates inferential typing, except the identityMapper object is
|
||||
@ -4579,24 +4647,16 @@ module ts {
|
||||
}
|
||||
|
||||
function checkArrayLiteral(node: ArrayLiteral, contextualMapper?: TypeMapper): Type {
|
||||
var contextualType = getContextualType(node);
|
||||
var elements = node.elements;
|
||||
var elementTypes: Type[] = [];
|
||||
var isTupleLiteral: boolean = false;
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
if (contextualType && getPropertyOfType(contextualType, "" + i)) {
|
||||
isTupleLiteral = true;
|
||||
}
|
||||
var element = elements[i];
|
||||
var type = element.kind !== SyntaxKind.OmittedExpression ? checkExpression(element, contextualMapper) : undefinedType;
|
||||
elementTypes.push(type);
|
||||
if (!elements.length) {
|
||||
return createArrayType(undefinedType);
|
||||
}
|
||||
if (isTupleLiteral) {
|
||||
var elementTypes = map(elements, e => checkExpression(e, contextualMapper));
|
||||
var contextualType = getContextualType(node);
|
||||
if (contextualType && contextualTypeIsTupleType(contextualType)) {
|
||||
return createTupleType(elementTypes);
|
||||
}
|
||||
var contextualElementType = contextualType && !isInferentialContext(contextualMapper) ? getIndexTypeOfType(contextualType, IndexKind.Number) : undefined;
|
||||
var elementType = elements.length || contextualElementType ? getBestCommonType(elementTypes, contextualElementType) : undefinedType;
|
||||
return createArrayType(elementType);
|
||||
return createArrayType(getUnionType(elementTypes));
|
||||
}
|
||||
|
||||
function isNumericName(name: string) {
|
||||
@ -4607,7 +4667,6 @@ module ts {
|
||||
var members = node.symbol.members;
|
||||
var properties: SymbolTable = {};
|
||||
var contextualType = getContextualType(node);
|
||||
|
||||
for (var id in members) {
|
||||
if (hasProperty(members, id)) {
|
||||
var member = members[id];
|
||||
@ -4645,21 +4704,19 @@ module ts {
|
||||
return createAnonymousType(node.symbol, properties, emptyArray, emptyArray, stringIndexType, numberIndexType);
|
||||
|
||||
function getIndexType(kind: IndexKind) {
|
||||
if (contextualType) {
|
||||
var indexType = getIndexTypeOfType(contextualType, kind);
|
||||
if (indexType) {
|
||||
var propTypes: Type[] = [];
|
||||
for (var id in properties) {
|
||||
if (hasProperty(properties, id)) {
|
||||
if (kind === IndexKind.String || isNumericName(id)) {
|
||||
var type = getTypeOfSymbol(properties[id]);
|
||||
if (!contains(propTypes, type)) propTypes.push(type);
|
||||
}
|
||||
if (contextualType && contextualTypeHasIndexSignature(contextualType, kind)) {
|
||||
var propTypes: Type[] = [];
|
||||
for (var id in properties) {
|
||||
if (hasProperty(properties, id)) {
|
||||
if (kind === IndexKind.String || isNumericName(id)) {
|
||||
var type = getTypeOfSymbol(properties[id]);
|
||||
if (!contains(propTypes, type)) propTypes.push(type);
|
||||
}
|
||||
}
|
||||
return getBestCommonType(propTypes, isInferentialContext(contextualMapper) ? undefined : indexType);
|
||||
}
|
||||
return propTypes.length ? getUnionType(propTypes) : undefinedType;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5249,11 +5306,12 @@ module ts {
|
||||
}
|
||||
|
||||
function getReturnTypeFromBody(func: FunctionDeclaration, contextualMapper?: TypeMapper): Type {
|
||||
var contextualSignature = getContextualSignature(func);
|
||||
if (func.body.kind !== SyntaxKind.FunctionBlock) {
|
||||
var unwidenedType = checkAndMarkExpression(func.body, contextualMapper);
|
||||
var widenedType = getWidenedType(unwidenedType);
|
||||
|
||||
if (fullTypeCheck && compilerOptions.noImplicitAny && widenedType !== unwidenedType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) {
|
||||
if (fullTypeCheck && compilerOptions.noImplicitAny && !contextualSignature && widenedType !== unwidenedType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) {
|
||||
error(func, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeToString(widenedType));
|
||||
}
|
||||
|
||||
@ -5267,7 +5325,7 @@ module ts {
|
||||
if (types.length > 0) {
|
||||
// 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.
|
||||
var commonType = getContextualSignature(func) ? getUnionType(types) : getCommonSupertype(types);
|
||||
var commonType = contextualSignature ? getUnionType(types) : getCommonSupertype(types);
|
||||
if (!commonType) {
|
||||
error(func, Diagnostics.No_best_common_type_exists_among_return_expressions);
|
||||
|
||||
@ -5277,7 +5335,7 @@ module ts {
|
||||
var widenedType = getWidenedType(commonType);
|
||||
|
||||
// Check and report for noImplicitAny if the best common type implicitly gets widened to an 'any'/arrays-of-'any' type.
|
||||
if (fullTypeCheck && compilerOptions.noImplicitAny && widenedType !== commonType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) {
|
||||
if (fullTypeCheck && compilerOptions.noImplicitAny && !contextualSignature && widenedType !== commonType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) {
|
||||
var typeName = typeToString(widenedType);
|
||||
|
||||
if (func.name) {
|
||||
@ -5776,6 +5834,8 @@ module ts {
|
||||
return checkBinaryExpression(<BinaryExpression>node, contextualMapper);
|
||||
case SyntaxKind.ConditionalExpression:
|
||||
return checkConditionalExpression(<ConditionalExpression>node, contextualMapper);
|
||||
case SyntaxKind.OmittedExpression:
|
||||
return undefinedType;
|
||||
}
|
||||
return unknownType;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user