diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cf35c1c02f5..1dcbde1f9d0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -130,6 +130,7 @@ module ts { var globalRegExpType: ObjectType; var tupleTypes: Map = {}; + var unionTypes: Map = {}; var stringLiteralTypes: Map = {}; var emitExtends = false; @@ -1136,6 +1137,9 @@ module ts { else if (type.flags & TypeFlags.Tuple) { writeTupleType(type); } + else if (type.flags & TypeFlags.Union) { + writeUnionType(type); + } else if (type.flags & TypeFlags.Anonymous) { writeAnonymousType(type, allowFunctionOrConstructorTypeLiteral); } @@ -1153,18 +1157,21 @@ module ts { } } - function writeTypeList(types: Type[]) { + function writeTypeList(types: Type[], union?: boolean) { for (var i = 0; i < types.length; i++) { if (i > 0) { - writePunctuation(writer, SyntaxKind.CommaToken); + if (union) { + writeSpace(writer); + } + writePunctuation(writer, union ? SyntaxKind.BarToken : SyntaxKind.CommaToken); writeSpace(writer); } - writeType(types[i], /*allowFunctionOrConstructorTypeLiteral*/ true); + writeType(types[i], /*allowFunctionOrConstructorTypeLiteral*/ !union); } } function writeTypeReference(type: TypeReference) { - if (type.target === globalArrayType && !(flags & TypeFormatFlags.WriteArrayAsGenericType)) { + if (type.target === globalArrayType && !(flags & TypeFormatFlags.WriteArrayAsGenericType) && !(type.typeArguments[0].flags & TypeFlags.Union)) { // If we are writing array element type the arrow style signatures are not allowed as // we need to surround it by curlies, e.g. { (): T; }[]; as () => T[] would mean something different writeType(type.typeArguments[0], /*allowFunctionOrConstructorTypeLiteral*/ false); @@ -1185,6 +1192,10 @@ module ts { writePunctuation(writer, SyntaxKind.CloseBracketToken); } + function writeUnionType(type: UnionType) { + writeTypeList(type.types, /*union*/ true); + } + function writeAnonymousType(type: ObjectType, allowFunctionOrConstructorTypeLiteral: boolean) { // Always use 'typeof T' for type of class, enum, and module objects if (type.symbol && type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { @@ -1730,6 +1741,15 @@ module ts { return links.type; } + function getTypeOfUnionProperty(symbol: Symbol): Type { + var links = getSymbolLinks(symbol); + if (!links.type) { + var types = map(links.unionType.types, t => getTypeOfSymbol(getPropertyOfType(getApparentType(t), symbol.name) || undefinedSymbol)); + links.type = getUnionType(types); + } + return links.type; + } + function getTypeOfSymbol(symbol: Symbol): Type { if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { return getTypeOfVariableOrParameterOrProperty(symbol); @@ -1749,6 +1769,9 @@ module ts { if (symbol.flags & SymbolFlags.Instantiated) { return getTypeOfInstantiatedSymbol(symbol); } + if (symbol.flags & SymbolFlags.UnionProperty) { + return getTypeOfUnionProperty(symbol); + } return unknownType; } @@ -2045,6 +2068,71 @@ module ts { setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType); } + function signatureListsIdentical(s: Signature[], t: Signature[]): boolean { + if (s.length !== t.length) return false; + for (var i = 0; i < s.length; i++) { + if (!compareSignatures(s[i], t[i], false, isTypeIdenticalTo)) return false; + } + return true; + } + + function getUnionSignatures(types: Type[], kind: SignatureKind): Signature[] { + var signatureLists = map(types, t => getSignaturesOfType(t, kind)); + var baseSignatures = signatureLists[0]; + for (var i = 0; i < baseSignatures.length; i++) { + if (baseSignatures[i].typeParameters) return emptyArray; + } + for (var i = 1; i < signatureLists.length; i++) { + if (!signatureListsIdentical(baseSignatures, signatureLists[i])) return emptyArray; + } + var result = map(baseSignatures, cloneSignature); + for (var i = 0; i < result.length; i++) { + var s = result[i]; + s.resolvedReturnType = undefined; + s.unionSignatures = map(signatureLists, signatures => signatures[i]); + } + return result; + } + + function getUnionIndexType(types: Type[], kind: IndexKind): Type { + var indexTypes: Type[] = []; + for (var i = 0; i < types.length; i++) { + var indexType = getIndexTypeOfType(types[i], kind); + if (!indexType) return undefined; + indexTypes.push(indexType); + } + return getUnionType(indexTypes); + } + + function resolveUnionTypeMembers(type: UnionType) { + var types: Type[] = []; + forEach(type.types, t => { + var apparentType = getApparentType(t); + if (!contains(types, apparentType)) { + types.push(apparentType); + } + }); + if (types.length <= 1) { + var res = types.length ? resolveObjectTypeMembers(types[0]) : emptyObjectType; + setObjectTypeMembers(type, res.members, res.callSignatures, res.constructSignatures, res.stringIndexType, res.numberIndexType); + return; + } + var members: SymbolTable = {}; + forEach(getPropertiesOfType(types[0]), prop => { + for (var i = 1; i < types.length; i++) { + if (!getPropertyOfType(types[i], prop.name)) return; + } + var symbol = createSymbol(SymbolFlags.UnionProperty | SymbolFlags.Transient, prop.name); + symbol.unionType = type; + members[prop.name] = symbol; + }); + var callSignatures = getUnionSignatures(types, SignatureKind.Call); + var constructSignatures = getUnionSignatures(types, SignatureKind.Construct); + var stringIndexType = getUnionIndexType(types, IndexKind.String); + var numberIndexType = getUnionIndexType(types, IndexKind.Number); + setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexType, numberIndexType); + } + function resolveAnonymousTypeMembers(type: ObjectType) { var symbol = type.symbol; if (symbol.flags & SymbolFlags.TypeLiteral) { @@ -2093,6 +2181,9 @@ module ts { else if (type.flags & TypeFlags.Tuple) { resolveTupleTypeMembers(type); } + else if (type.flags & TypeFlags.Union) { + resolveUnionTypeMembers(type); + } else { resolveTypeReferenceMembers(type); } @@ -2253,6 +2344,9 @@ module ts { if (signature.target) { var type = instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper); } + else if (signature.unionSignatures) { + var type = getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature)); + } else { var type = getReturnTypeFromBody(signature.declaration); } @@ -2561,6 +2655,70 @@ module ts { return links.resolvedType; } + function addTypeToSortedSet(sortedSet: Type[], type: Type) { + if (type.flags & TypeFlags.Union) { + addTypesToSortedSet(sortedSet, (type).types); + } + else { + var i = 0; + var id = type.id; + while (i < sortedSet.length && sortedSet[i].id < id) i++; + if (i === sortedSet.length || sortedSet[i].id !== id) { + sortedSet.splice(i, 0, type); + } + } + } + + function addTypesToSortedSet(sortedTypes: Type[], types: Type[]) { + for (var i = 0, len = types.length; i < len; i++) { + addTypeToSortedSet(sortedTypes, types[i]); + } + } + + function isSubtypeOfAny(candidate: Type, types: Type[]): boolean { + for (var i = 0, len = types.length; i < len; i++) { + if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) return true; + } + return false; + } + + function removeSubtypes(types: Type[]) { + var i = types.length; + while (i > 0) { + i--; + if (isSubtypeOfAny(types[i], types)) { + types.splice(i, 1); + } + } + } + + function getUnionType(types: Type[]): Type { + if (types.length === 0) { + return emptyObjectType; + } + var sortedTypes: Type[] = []; + addTypesToSortedSet(sortedTypes, types); + removeSubtypes(sortedTypes); + if (sortedTypes.length === 1) { + return sortedTypes[0]; + } + var id = getTypeListId(sortedTypes); + var type = unionTypes[id]; + if (!type) { + type = unionTypes[id] = createObjectType(TypeFlags.Union); + type.types = sortedTypes; + } + return type; + } + + function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { + var links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode)); + } + return links.resolvedType; + } + function getTypeFromTypeLiteralNode(node: TypeLiteralNode): Type { var links = getNodeLinks(node); if (!links.resolvedType) { @@ -2607,6 +2765,8 @@ module ts { return getTypeFromArrayTypeNode(node); case SyntaxKind.TupleType: return getTypeFromTupleTypeNode(node); + case SyntaxKind.UnionType: + return getTypeFromUnionTypeNode(node); case SyntaxKind.TypeLiteral: return getTypeFromTypeLiteralNode(node); // This function assumes that an identifier or qualified name is a type expression @@ -2771,6 +2931,9 @@ module ts { if (type.flags & TypeFlags.Tuple) { return createTupleType(instantiateList((type).elementTypes, mapper, instantiateType)); } + if (type.flags & TypeFlags.Union) { + return getUnionType(instantiateList((type).types, mapper, instantiateType)); + } } return type; } @@ -2932,7 +3095,7 @@ module ts { errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2); } - function isRelatedTo(source: Type, target: Type, reportErrors: boolean): boolean { + function isRelatedTo(source: Type, target: Type, reportErrors?: boolean): boolean { return isRelatedToWithCustomErrors(source, target, reportErrors, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined); } @@ -2953,8 +3116,17 @@ module ts { if (source === numberType && target.flags & TypeFlags.Enum) return true; } } - - if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) { + if (source.flags & TypeFlags.Union) { + if (unionTypeRelatedToType(source, target, reportErrors)) { + return true; + } + } + else if (target.flags & TypeFlags.Union) { + if (typeRelatedToUnionType(source, target, reportErrors)) { + return true; + } + } + else if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) { if (typeParameterRelatedTo(source, target, reportErrors)) { return true; } @@ -2971,7 +3143,7 @@ module ts { // Report structural errors only if we haven't reported any errors yet var reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo; // identity relation does not use apparent type - var sourceOrApparentType = relation === identityRelation ? source : getApparentType(source); + var sourceOrApparentType = relation === identityRelation ? source : getApparentType(source); if (sourceOrApparentType.flags & TypeFlags.ObjectType && target.flags & TypeFlags.ObjectType && objectTypeRelatedTo(sourceOrApparentType, target, reportStructuralErrors)) { errorInfo = saveErrorInfo; @@ -2992,6 +3164,26 @@ module ts { return false; } + function typeRelatedToUnionType(source: Type, target: UnionType, reportErrors: boolean): boolean { + var targetTypes = target.types; + for (var i = 0, len = targetTypes.length; i < len; i++) { + if (isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1)) { + return true; + } + } + return false; + } + + function unionTypeRelatedToType(source: UnionType, target: Type, reportErrors: boolean): boolean { + var sourceTypes = source.types; + for (var i = 0, len = sourceTypes.length; i < len; i++) { + if (!isRelatedTo(sourceTypes[i], target, reportErrors)) { + return false; + } + } + return true; + } + function typesRelatedTo(sources: Type[], targets: Type[], reportErrors: boolean): boolean { for (var i = 0, len = sources.length; i < len; i++) { if (!isRelatedTo(sources[i], targets[i], reportErrors)) return false; @@ -3093,34 +3285,12 @@ module ts { function propertiesRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): boolean { if (relation === identityRelation) { - return propertiesAreIdenticalTo(source, target, reportErrors); + return propertiesIdenticalTo(source, target, reportErrors); } - else { - return propertiesAreSubtypeOrAssignableTo(source, target, reportErrors); - } - } - - function propertiesAreIdenticalTo(source: ObjectType, target: ObjectType, reportErrors: boolean): boolean { - var sourceProperties = getPropertiesOfType(source); - var targetProperties = getPropertiesOfType(target); - if (sourceProperties.length !== targetProperties.length) { - return false; - } - for (var i = 0, len = sourceProperties.length; i < len; ++i) { - var sourceProp = sourceProperties[i]; - var targetProp = getPropertyOfType(target, sourceProp.name); - if (!targetProp || !isPropertyIdenticalToRecursive(sourceProp, targetProp, reportErrors, isRelatedTo)) { - return false; - } - } - return true; - } - - function propertiesAreSubtypeOrAssignableTo(source: ApparentType, target: ObjectType, reportErrors: boolean): boolean { var properties = getPropertiesOfType(target); for (var i = 0; i < properties.length; i++) { var targetProp = properties[i]; - var sourceProp = getPropertyOfApparentType(source, targetProp.name); + var sourceProp = getPropertyOfApparentType(source, targetProp.name); if (sourceProp !== targetProp) { if (!sourceProp) { if (!isOptionalProperty(targetProp)) { @@ -3193,80 +3363,26 @@ module ts { return true; } - function signaturesRelatedTo(source: ObjectType, target: ObjectType, kind: SignatureKind, reportErrors: boolean): boolean { - if (relation === identityRelation) { - return areSignaturesIdenticalTo(source, target, kind, reportErrors); - } - else { - return areSignaturesSubtypeOrAssignableTo(source, target, kind, reportErrors); - } - } - - function areSignaturesIdenticalTo(source: ObjectType, target: ObjectType, kind: SignatureKind, reportErrors: boolean): boolean { - var sourceSignatures = getSignaturesOfType(source, kind); - var targetSignatures = getSignaturesOfType(target, kind); - if (sourceSignatures.length !== targetSignatures.length) { + function propertiesIdenticalTo(source: ObjectType, target: ObjectType, reportErrors: boolean): boolean { + var sourceProperties = getPropertiesOfType(source); + var targetProperties = getPropertiesOfType(target); + if (sourceProperties.length !== targetProperties.length) { return false; } - - for (var i = 0, len = sourceSignatures.length; i < len; ++i) { - if (!isSignatureIdenticalTo(sourceSignatures[i], targetSignatures[i], reportErrors)) { + for (var i = 0, len = sourceProperties.length; i < len; ++i) { + var sourceProp = sourceProperties[i]; + var targetProp = getPropertyOfType(target, sourceProp.name); + if (!targetProp || !isPropertyIdenticalToRecursive(sourceProp, targetProp, reportErrors, isRelatedTo)) { return false; } } - return true; } - function isSignatureIdenticalTo(source: Signature, target: Signature, reportErrors: boolean): boolean { - if (source === target) { - return true; + function signaturesRelatedTo(source: ObjectType, target: ObjectType, kind: SignatureKind, reportErrors: boolean): boolean { + if (relation === identityRelation) { + return signaturesIdenticalTo(source, target, kind, reportErrors); } - - if (source.hasRestParameter !== target.hasRestParameter) { - return false; - } - - if (source.parameters.length !== target.parameters.length) { - return false; - } - - if (source.minArgumentCount !== target.minArgumentCount) { - return false; - } - - if (source.typeParameters && target.typeParameters) { - if (source.typeParameters.length !== target.typeParameters.length) { - return false; - } - - for (var i = 0, len = source.typeParameters.length; i < len; ++i) { - if (!isRelatedTo(source.typeParameters[i], target.typeParameters[i], reportErrors)) { - return false; - } - } - } - else if (source.typeParameters || source.typeParameters) { - return false; - } - - // Spec 1.0 Section 3.8.3 & 3.8.4: - // M and N (the signatures) are instantiated using type Any as the type argument for all type parameters declared by M and N - source = getErasedSignature(source); - target = getErasedSignature(target); - for (var i = 0, len = source.parameters.length; i < len; i++) { - var s = source.hasRestParameter && i === len - 1 ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]); - var t = target.hasRestParameter && i === len - 1 ? getRestTypeOfSignature(target) : getTypeOfSymbol(target.parameters[i]); - if (!isRelatedTo(s, t, reportErrors)) { - return false; - } - } - var t = getReturnTypeOfSignature(target); - var s = getReturnTypeOfSignature(source); - return isRelatedTo(s, t, reportErrors); - } - - function areSignaturesSubtypeOrAssignableTo(source: ObjectType, target: ObjectType, kind: SignatureKind, reportErrors: boolean): boolean { if (target === anyFunctionType || source === anyFunctionType) return true; var sourceSignatures = getSignaturesOfType(source, kind); var targetSignatures = getSignaturesOfType(target, kind); @@ -3278,7 +3394,7 @@ module ts { for (var j = 0; j < sourceSignatures.length; j++) { var s = sourceSignatures[j]; if (!s.hasStringLiterals || source.flags & TypeFlags.FromSignature) { - if (isSignatureSubtypeOrAssignableTo(s, t, localErrors)) { + if (signatureRelatedTo(s, t, localErrors)) { errorInfo = saveErrorInfo; continue outer; } @@ -3292,15 +3408,13 @@ module ts { return true; } - function isSignatureSubtypeOrAssignableTo(source: Signature, target: Signature, reportErrors: boolean): boolean { + function signatureRelatedTo(source: Signature, target: Signature, reportErrors: boolean): boolean { if (source === target) { return true; } - if (!target.hasRestParameter && source.minArgumentCount > target.parameters.length) { return false; } - var sourceMax = source.parameters.length; var targetMax = target.parameters.length; var checkCount: number; @@ -3346,71 +3460,117 @@ module ts { return isRelatedTo(s, t, reportErrors); } + function signaturesIdenticalTo(source: ObjectType, target: ObjectType, kind: SignatureKind, reportErrors: boolean): boolean { + var sourceSignatures = getSignaturesOfType(source, kind); + var targetSignatures = getSignaturesOfType(target, kind); + if (sourceSignatures.length !== targetSignatures.length) { + return false; + } + for (var i = 0, len = sourceSignatures.length; i < len; ++i) { + if (!compareSignatures(sourceSignatures[i], targetSignatures[i], /*returnTypes*/ true, isRelatedTo)) { + return false; + } + } + return true; + } + function stringIndexTypesRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): boolean { if (relation === identityRelation) { - return areIndexTypesIdenticalTo(IndexKind.String, source, target, reportErrors); + return indexTypesIdenticalTo(IndexKind.String, source, target, reportErrors); } - else { - var targetType = getIndexTypeOfType(target, IndexKind.String); - if (targetType) { - var sourceType = getIndexTypeOfType(source, IndexKind.String); - if (!sourceType) { - if (reportErrors) { - reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); - } - return false; - } - if (!isRelatedTo(sourceType, targetType, reportErrors)) { - if (reportErrors) { - reportError(Diagnostics.Index_signatures_are_incompatible_Colon); - } - return false; + var targetType = getIndexTypeOfType(target, IndexKind.String); + if (targetType) { + var sourceType = getIndexTypeOfType(source, IndexKind.String); + if (!sourceType) { + if (reportErrors) { + reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); } + return false; + } + if (!isRelatedTo(sourceType, targetType, reportErrors)) { + if (reportErrors) { + reportError(Diagnostics.Index_signatures_are_incompatible_Colon); + } + return false; } - return true; } + return true; } function numberIndexTypesRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): boolean { if (relation === identityRelation) { - return areIndexTypesIdenticalTo(IndexKind.Number, source, target, reportErrors); + return indexTypesIdenticalTo(IndexKind.Number, source, target, reportErrors); } - else { - var targetType = getIndexTypeOfType(target, IndexKind.Number); - if (targetType) { - var sourceStringType = getIndexTypeOfType(source, IndexKind.String); - var sourceNumberType = getIndexTypeOfType(source, IndexKind.Number); - if (!(sourceStringType || sourceNumberType)) { - if (reportErrors) { - reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); - } - return false; - } - if (sourceStringType && sourceNumberType) { - // If we know for sure we're testing both string and numeric index types then only report errors from the second one - var compatible = isRelatedTo(sourceStringType, targetType, false) || isRelatedTo(sourceNumberType, targetType, reportErrors); - } - else { - var compatible = isRelatedTo(sourceStringType || sourceNumberType, targetType, reportErrors); - } - if (!compatible) { - if (reportErrors) { - reportError(Diagnostics.Index_signatures_are_incompatible_Colon); - } - return false; + var targetType = getIndexTypeOfType(target, IndexKind.Number); + if (targetType) { + var sourceStringType = getIndexTypeOfType(source, IndexKind.String); + var sourceNumberType = getIndexTypeOfType(source, IndexKind.Number); + if (!(sourceStringType || sourceNumberType)) { + if (reportErrors) { + reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); } + return false; + } + if (sourceStringType && sourceNumberType) { + // If we know for sure we're testing both string and numeric index types then only report errors from the second one + var compatible = isRelatedTo(sourceStringType, targetType, false) || isRelatedTo(sourceNumberType, targetType, reportErrors); + } + else { + var compatible = isRelatedTo(sourceStringType || sourceNumberType, targetType, reportErrors); + } + if (!compatible) { + if (reportErrors) { + reportError(Diagnostics.Index_signatures_are_incompatible_Colon); + } + return false; } - return true; } + return true; } - function areIndexTypesIdenticalTo(indexKind: IndexKind, source: ObjectType, target: ObjectType, reportErrors: boolean): boolean { + function indexTypesIdenticalTo(indexKind: IndexKind, source: ObjectType, target: ObjectType, reportErrors: boolean): boolean { var targetType = getIndexTypeOfType(target, indexKind); var sourceType = getIndexTypeOfType(source, indexKind); return (!sourceType && !targetType) || (sourceType && targetType && isRelatedTo(sourceType, targetType, reportErrors)); } } + function compareSignatures(source: Signature, target: Signature, returnTypes: boolean, compareTypes: (s: Type, t: Type) => boolean): boolean { + if (source === target) { + return true; + } + if (source.parameters.length !== target.parameters.length || + source.minArgumentCount !== target.minArgumentCount || + source.hasRestParameter !== target.hasRestParameter) { + return false; + } + if (source.typeParameters && target.typeParameters) { + if (source.typeParameters.length !== target.typeParameters.length) { + return false; + } + for (var i = 0, len = source.typeParameters.length; i < len; ++i) { + if (!compareTypes(source.typeParameters[i], target.typeParameters[i])) { + return false; + } + } + } + else if (source.typeParameters || source.typeParameters) { + return false; + } + // Spec 1.0 Section 3.8.3 & 3.8.4: + // M and N (the signatures) are instantiated using type Any as the type argument for all type parameters declared by M and N + source = getErasedSignature(source); + target = getErasedSignature(target); + for (var i = 0, len = source.parameters.length; i < len; i++) { + var s = source.hasRestParameter && i === len - 1 ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]); + var t = target.hasRestParameter && i === len - 1 ? getRestTypeOfSignature(target) : getTypeOfSymbol(target.parameters[i]); + if (!compareTypes(s, t)) { + return false; + } + } + return !returnTypes || compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); + } + function isSupertypeOfEach(candidate: Type, types: Type[]): boolean { for (var i = 0, len = types.length; i < len; i++) { if (candidate !== types[i] && !isTypeSubtypeOf(types[i], candidate)) return false; @@ -3418,11 +3578,13 @@ module ts { return true; } - function getBestCommonType(types: Type[], contextualType?: Type, candidatesOnly?: boolean): Type { - if (contextualType && isSupertypeOfEach(contextualType, types)) return contextualType; - return forEach(types, t => isSupertypeOfEach(t, types) ? t : undefined) || (candidatesOnly ? undefined : emptyObjectType); + function getCommonSupertype(types: Type[]): Type { + return forEach(types, t => isSupertypeOfEach(t, types) ? t : undefined); } + function getBestCommonType(types: Type[], contextualType?: Type): Type { + return contextualType && isSupertypeOfEach(contextualType, types) ? contextualType : getUnionType(types); } + function isTypeOfObjectLiteral(type: Type): boolean { return (type.flags & TypeFlags.Anonymous) && type.symbol && (type.symbol.flags & SymbolFlags.ObjectLiteral) ? true : false; } @@ -3439,10 +3601,13 @@ module ts { } /* If we are widening on a literal, then we may need to the 'node' parameter for reporting purposes */ - function getWidenedType(type: Type, supressNoImplicitAnyErrors?: boolean): Type { + function getWidenedType(type: Type, suppressNoImplicitAnyErrors?: boolean): Type { if (type.flags & (TypeFlags.Undefined | TypeFlags.Null)) { return anyType; } + if (type.flags & TypeFlags.Union) { + return getWidenedTypeOfUnion(type); + } if (isTypeOfObjectLiteral(type)) { return getWidenedTypeOfObjectLiteral(type); } @@ -3451,6 +3616,10 @@ module ts { } return type; + function getWidenedTypeOfUnion(type: Type): Type { + return getUnionType(map((type).types, t => getWidenedType(t, suppressNoImplicitAnyErrors))); + } + function getWidenedTypeOfObjectLiteral(type: Type): Type { var properties = getPropertiesOfType(type); if (properties.length) { @@ -3461,8 +3630,7 @@ module ts { var widenedType = getWidenedType(propType); if (propType !== widenedType) { propTypeWasWidened = true; - - if (!supressNoImplicitAnyErrors && program.getCompilerOptions().noImplicitAny && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { + if (!suppressNoImplicitAnyErrors && compilerOptions.noImplicitAny && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, p.name, typeToString(widenedType)); } } @@ -3492,10 +3660,8 @@ module ts { function getWidenedTypeOfArrayLiteral(type: Type): Type { var elementType = (type).typeArguments[0]; - var widenedType = getWidenedType(elementType, supressNoImplicitAnyErrors); - + var widenedType = getWidenedType(elementType, suppressNoImplicitAnyErrors); type = elementType !== widenedType ? createArrayType(widenedType) : type; - return type; } } @@ -3646,9 +3812,19 @@ module ts { function getInferredType(context: InferenceContext, index: number): Type { var result = context.inferredTypes[index]; if (!result) { - var commonType = getWidenedType(getBestCommonType(context.inferences[index])); + var inferences = context.inferences[index]; + if (inferences.length) { + // Find type that is supertype of all others + var supertype = getCommonSupertype(inferences); + // Infer widened supertype, or the undefined type for no common supertype + var inferredType = supertype ? getWidenedType(supertype) : undefinedType; + } + else { + // Infer the empty object type when no inferences were made + inferredType = emptyObjectType; + } var constraint = getConstraintOfTypeParameter(context.typeParameters[index]); - var result = constraint && !isTypeAssignableTo(commonType, constraint) ? constraint : commonType; + var result = constraint && !isTypeAssignableTo(inferredType, constraint) ? constraint : inferredType; context.inferredTypes[index] = result; } return result; @@ -4083,10 +4259,7 @@ module ts { return createTupleType(elementTypes); } var contextualElementType = contextualType && !isInferentialContext(contextualMapper) ? getIndexTypeOfType(contextualType, IndexKind.Number) : undefined; - var elementType = getBestCommonType(uniqueElements(elementTypes), contextualElementType, true); - if (!elementType) { - elementType = elements.length ? emptyObjectType : undefinedType; - } + var elementType = elements.length || contextualElementType ? getBestCommonType(deduplicate(elementTypes), contextualElementType) : undefinedType; return createArrayType(elementType); } @@ -4380,7 +4553,9 @@ module ts { } } } - return getInferredTypes(context); + var inferredTypes = getInferredTypes(context); + // Inference has failed if the undefined type is in list of inferences + return contains(inferredTypes, undefinedType) ? undefined : inferredTypes; } function checkTypeArguments(signature: Signature, typeArguments: TypeNode[]): Type[] { @@ -4405,7 +4580,6 @@ module ts { if (arg.kind === SyntaxKind.OmittedExpression) { continue; } - var paramType = getTypeAtPosition(signature, i); // String literals get string literal types unless we're reporting errors var argType = arg.kind === SyntaxKind.StringLiteral && !reportErrors ? @@ -4441,26 +4615,30 @@ module ts { } } var relation = candidates.length === 1 ? assignableRelation : subtypeRelation; + var lastCandidate: Signature; while (true) { for (var i = 0; i < candidates.length; i++) { if (!signatureHasCorrectArity(node, candidates[i])) { continue; } - while (true) { - var candidateWithCorrectArity = candidates[i]; - if (candidateWithCorrectArity.typeParameters) { + var candidate = candidates[i]; + if (candidate.typeParameters) { var typeArguments = node.typeArguments ? - checkTypeArguments(candidateWithCorrectArity, node.typeArguments) : - inferTypeArguments(candidateWithCorrectArity, args, excludeArgument); - candidateWithCorrectArity = getSignatureInstantiation(candidateWithCorrectArity, typeArguments); + checkTypeArguments(candidate, node.typeArguments) : + inferTypeArguments(candidate, args, excludeArgument); + if (!typeArguments) { + break; + } + candidate = getSignatureInstantiation(candidate, typeArguments); } - if (!checkApplicableSignature(node, candidateWithCorrectArity, relation, excludeArgument, /*reportErrors*/ false)) { + lastCandidate = candidate; + if (!checkApplicableSignature(node, candidate, relation, excludeArgument, /*reportErrors*/ false)) { break; } var index = excludeArgument ? indexOf(excludeArgument, true) : -1; if (index < 0) { - return candidateWithCorrectArity; + return candidate; } excludeArgument[index] = false; } @@ -4475,8 +4653,8 @@ module ts { // no arguments excluded from assignability checks. // If candidate is undefined, it means that no candidates had a suitable arity. In that case, // skip the checkApplicableSignature check. - if (candidateWithCorrectArity) { - checkApplicableSignature(node, candidateWithCorrectArity, relation, /*excludeArgument*/ undefined, /*reportErrors*/ true); + if (lastCandidate) { + checkApplicableSignature(node, lastCandidate, relation, /*excludeArgument*/ undefined, /*reportErrors*/ true); } else { error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target); @@ -4563,7 +4741,9 @@ module ts { // but is a subtype of the Function interface, the call is an untyped function call. In an // untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual // types are provided for the argument expressions, and the result is always of type Any. - if ((funcType === anyType) || (!callSignatures.length && !constructSignatures.length && isTypeAssignableTo(funcType, globalFunctionType))) { + // We exclude union types because we may have a union of function types that happen to have + // no common signatures. + if (funcType === anyType || (!callSignatures.length && !constructSignatures.length && !(funcType.flags & TypeFlags.Union) && isTypeAssignableTo(funcType, globalFunctionType))) { if (node.typeArguments) { error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); } @@ -4723,7 +4903,7 @@ module ts { // Try to return the best common type if we have any return expressions. if (types.length > 0) { - var commonType = getBestCommonType(types, /*contextualType:*/ undefined, /*candidatesOnly:*/ true); + var commonType = getCommonSupertype(types); if (!commonType) { error(func, Diagnostics.No_best_common_type_exists_among_return_expressions); @@ -5118,16 +5298,7 @@ module ts { var type1 = checkExpression(node.whenTrue, contextualMapper); var type2 = checkExpression(node.whenFalse, contextualMapper); var contextualType = isInferentialContext(contextualMapper) ? undefined : getContextualType(node); - var resultType = getBestCommonType([type1, type2], contextualType, true); - if (!resultType) { - if (contextualType) { - error(node, Diagnostics.No_best_common_type_exists_between_0_1_and_2, typeToString(contextualType), typeToString(type1), typeToString(type2)); - } - else { - error(node, Diagnostics.No_best_common_type_exists_between_0_and_1, typeToString(type1), typeToString(type2)); - } - resultType = emptyObjectType; - } + var resultType = getBestCommonType([type1, type2], contextualType); return resultType; } @@ -5520,6 +5691,10 @@ module ts { forEach(node.elementTypes, checkSourceElement); } + function checkUnionType(node: UnionTypeNode) { + forEach(node.types, checkSourceElement); + } + function isPrivateWithinAmbient(node: Node): boolean { return (node.flags & NodeFlags.Private) && isInAmbientContext(node); } @@ -6782,6 +6957,8 @@ module ts { return checkArrayType(node); case SyntaxKind.TupleType: return checkTupleType(node); + case SyntaxKind.TupleType: + return checkUnionType(node); case SyntaxKind.FunctionDeclaration: return checkFunctionDeclaration(node); case SyntaxKind.Block: diff --git a/src/compiler/core.ts b/src/compiler/core.ts index fb2bde52453..8d8ea66d86b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -82,7 +82,7 @@ module ts { return array1.concat(array2); } - export function uniqueElements(array: T[]): T[] { + export function deduplicate(array: T[]): T[] { if (array) { var result: T[] = []; for (var i = 0, len = array.length; i < len; i++) { diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index ba6f742c4c1..266d0a14c4b 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -179,8 +179,6 @@ module ts { The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_or_an_enum_type: { code: 2363, category: DiagnosticCategory.Error, key: "The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type." }, Invalid_left_hand_side_of_assignment_expression: { code: 2364, category: DiagnosticCategory.Error, key: "Invalid left-hand side of assignment expression." }, Operator_0_cannot_be_applied_to_types_1_and_2: { code: 2365, category: DiagnosticCategory.Error, key: "Operator '{0}' cannot be applied to types '{1}' and '{2}'." }, - No_best_common_type_exists_between_0_1_and_2: { code: 2366, category: DiagnosticCategory.Error, key: "No best common type exists between '{0}', '{1}', and '{2}'." }, - No_best_common_type_exists_between_0_and_1: { code: 2367, category: DiagnosticCategory.Error, key: "No best common type exists between '{0}' and '{1}'." }, Type_parameter_name_cannot_be_0: { code: 2368, category: DiagnosticCategory.Error, key: "Type parameter name cannot be '{0}'" }, A_parameter_property_is_only_allowed_in_a_constructor_implementation: { code: 2369, category: DiagnosticCategory.Error, key: "A parameter property is only allowed in a constructor implementation." }, A_rest_parameter_must_be_of_an_array_type: { code: 2370, category: DiagnosticCategory.Error, key: "A rest parameter must be of an array type." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 0480cd3000c..cc2a752fce7 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -708,14 +708,6 @@ "category": "Error", "code": 2365 }, - "No best common type exists between '{0}', '{1}', and '{2}'.": { - "category": "Error", - "code": 2366 - }, - "No best common type exists between '{0}' and '{1}'.": { - "category": "Error", - "code": 2367 - }, "Type parameter name cannot be '{0}'": { "category": "Error", "code": 2368 diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b06326f6925..cb6c9a37cd9 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -232,6 +232,8 @@ module ts { return child((node).elementType); case SyntaxKind.TupleType: return children((node).elementTypes); + case SyntaxKind.UnionType: + return children((node).types); case SyntaxKind.ArrayLiteral: return children((node).elements); case SyntaxKind.ObjectLiteral: @@ -1728,9 +1730,9 @@ module ts { } } - function parseType(): TypeNode { + function parseNonUnionType(): TypeNode { var type = parseNonArrayType(); - while (type && !scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) { + while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) { parseExpected(SyntaxKind.CloseBracketToken); var node = createNode(SyntaxKind.ArrayType, type.pos); node.elementType = type; @@ -1739,6 +1741,22 @@ module ts { return type; } + function parseType(): TypeNode { + var type = parseNonUnionType(); + if (token === SyntaxKind.BarToken) { + var types = >[type]; + types.pos = type.pos; + while (parseOptional(SyntaxKind.BarToken)) { + types.push(parseNonUnionType()); + } + types.end = getNodeEnd(); + var node = createNode(SyntaxKind.UnionType, type.pos); + node.types = types; + type = finishNode(node); + } + return type; + } + function parseTypeAnnotation(): TypeNode { return parseOptional(SyntaxKind.ColonToken) ? parseType() : undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index dcc42bf3358..0c322b49d25 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -154,6 +154,7 @@ module ts { TypeLiteral, ArrayType, TupleType, + UnionType, // Expression ArrayLiteral, ObjectLiteral, @@ -224,7 +225,7 @@ module ts { FirstFutureReservedWord = ImplementsKeyword, LastFutureReservedWord = YieldKeyword, FirstTypeNode = TypeReference, - LastTypeNode = TupleType, + LastTypeNode = UnionType, FirstPunctuation = OpenBraceToken, LastPunctuation = CaretEqualsToken, FirstToken = EndOfFileToken, @@ -337,6 +338,10 @@ module ts { elementTypes: NodeArray; } + export interface UnionTypeNode extends TypeNode { + types: NodeArray; + } + export interface StringLiteralTypeNode extends TypeNode { text: string; } @@ -728,19 +733,20 @@ module ts { ConstructSignature = 0x00010000, // Construct signature IndexSignature = 0x00020000, // Index signature TypeParameter = 0x00040000, // Type parameter + UnionProperty = 0x00080000, // Property in union type // Export markers (see comment in declareModuleMember in binder) - ExportValue = 0x00080000, // Exported value marker - ExportType = 0x00100000, // Exported type marker - ExportNamespace = 0x00200000, // Exported namespace marker + ExportValue = 0x00100000, // Exported value marker + ExportType = 0x00200000, // Exported type marker + ExportNamespace = 0x00400000, // Exported namespace marker - Import = 0x00400000, // Import - Instantiated = 0x00800000, // Instantiated symbol - Merged = 0x01000000, // Merged symbol (created during program binding) - Transient = 0x02000000, // Transient symbol (created during type check) - Prototype = 0x04000000, // Symbol for the prototype property (without source code representation) + Import = 0x00800000, // Import + Instantiated = 0x01000000, // Instantiated symbol + Merged = 0x02000000, // Merged symbol (created during program binding) + Transient = 0x04000000, // Transient symbol (created during type check) + Prototype = 0x08000000, // Prototype property (no source representation) - Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor, + Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor | UnionProperty, Type = Class | Interface | Enum | TypeLiteral | ObjectLiteral | TypeParameter, Namespace = ValueModule | NamespaceModule, Module = ValueModule | NamespaceModule, @@ -798,6 +804,7 @@ module ts { mapper?: TypeMapper; // Type mapper for instantiation alias referenced?: boolean; // True if alias symbol has been referenced as a value exportAssignSymbol?: Symbol; // Symbol exported from external module + unionType?: UnionType; // Containing union type for union property } export interface TransientSymbol extends Symbol, SymbolLinks { } @@ -845,13 +852,14 @@ module ts { Interface = 0x00000800, // Interface Reference = 0x00001000, // Generic type reference Tuple = 0x00002000, // Tuple - Anonymous = 0x00004000, // Anonymous - FromSignature = 0x00008000, // Created for signature assignment check + Union = 0x00004000, // Union + Anonymous = 0x00008000, // Anonymous + FromSignature = 0x00010000, // Created for signature assignment check Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null, StringLike = String | StringLiteral, NumberLike = Number | Enum, - ObjectType = Class | Interface | Reference | Tuple | Anonymous + ObjectType = Class | Interface | Reference | Tuple | Union | Anonymous } // Properties common to all types @@ -909,6 +917,10 @@ module ts { baseArrayType: TypeReference; // Array where T is best common type of element types } + export interface UnionType extends ObjectType { + types: Type[]; // Constituent types + } + // Resolved object type export interface ResolvedObjectType extends ObjectType { members: SymbolTable; // Properties by name @@ -941,6 +953,7 @@ module ts { hasStringLiterals: boolean; // True if specialized target?: Signature; // Instantiation target mapper?: TypeMapper; // Instantiation mapper + unionSignatures?: Signature[]; // Underlying signatures of a union signature erasedSignatureCache?: Signature; // Erased version of signature (deferred) isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison }