From 43f158d4185ce3432edadee7aefc299dc641435b Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 27 Oct 2015 16:17:26 -0700 Subject: [PATCH] Added "comparability" relation. It's currently equivalent to assignability. --- src/compiler/checker.ts | 47 +++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f64c25217bd..cebbe9380e3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -211,6 +211,7 @@ namespace ts { let subtypeRelation: Map = {}; let assignableRelation: Map = {}; + let comparableRelation: Map = {}; let identityRelation: Map = {}; // This is for caching the result of getSymbolDisplayBuilder. Do not access directly. @@ -4738,6 +4739,14 @@ namespace ts { return checkTypeAssignableTo(source, target, /*errorNode*/ undefined); } + /** + * This is *not* a bi-directional relationship. + * If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'. + */ + function isTypeComparableTo(source: Type, target: Type): boolean { + return checkTypeComparableTo(source, target, /*errorNode*/ undefined); + } + function checkTypeSubtypeOf(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { return checkTypeRelatedTo(source, target, subtypeRelation, errorNode, headMessage, containingMessageChain); } @@ -4746,6 +4755,14 @@ namespace ts { return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain); } + /** + * This is *not* a bi-directional relationship. + * If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'. + */ + function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { + return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); + } + function isSignatureAssignableTo(source: Signature, target: Signature): boolean { let sourceType = getOrCreateTypeFromSignature(source); let targetType = getOrCreateTypeFromSignature(target); @@ -4756,7 +4773,7 @@ namespace ts { * Checks if 'source' is related to 'target' (e.g.: is a assignable to). * @param source The left-hand-side of the relation. * @param target The right-hand-side of the relation. - * @param relation The relation considered. One of 'identityRelation', 'assignableRelation', or 'subTypeRelation'. + * @param relation The relation considered. One of 'identityRelation', 'assignableRelation', 'subTypeRelation', or 'comparableRelation'. * Used as both to determine which checks are performed and as a cache of previously computed results. * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. @@ -4781,6 +4798,7 @@ namespace ts { Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); + const isAssignableOrComparableRelation = relation === assignableRelation || relation === comparableRelation; let result = isRelatedTo(source, target, errorNode !== undefined, headMessage); if (overflow) { error(errorNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target)); @@ -4834,7 +4852,7 @@ namespace ts { if (source === nullType && target !== undefinedType) return Ternary.True; if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True; if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True; - if (relation === assignableRelation) { + if (isAssignableOrComparableRelation) { if (isTypeAny(source)) return Ternary.True; if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True; } @@ -4942,8 +4960,8 @@ namespace ts { } if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union || source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) { - if (result = eachTypeRelatedToSomeType(source, target)) { - if (result &= eachTypeRelatedToSomeType(target, source)) { + if (result = eachTypeRelatedToSomeType(source, target, /*reportErrors*/ false)) { + if (result &= eachTypeRelatedToSomeType(target, source, /*reportErrors*/ false)) { return result; } } @@ -4958,7 +4976,7 @@ namespace ts { function isKnownProperty(type: Type, name: string): boolean { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) || + if (isAssignableOrComparableRelation && (type === globalObjectType || resolved.properties.length === 0) || resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) { return true; } @@ -4992,11 +5010,11 @@ namespace ts { return false; } - function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary { + function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { let result = Ternary.True; let sourceTypes = source.types; for (let sourceType of sourceTypes) { - let related = typeRelatedToSomeType(sourceType, target, false); + let related = typeRelatedToSomeType(sourceType, target, reportErrors); if (!related) { return Ternary.False; } @@ -9381,8 +9399,9 @@ namespace ts { let targetType = getTypeFromTypeNode(node.type); if (produceDiagnostics && targetType !== unknownType) { let widenedType = getWidenedType(exprType); - if (!(isTypeAssignableTo(targetType, widenedType))) { - checkTypeAssignableTo(exprType, targetType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other); + + if (!isTypeComparableTo(targetType, widenedType)) { + checkTypeComparableTo(exprType, targetType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other); } } return targetType; @@ -10225,7 +10244,7 @@ namespace ts { case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: - if (!isTypeAssignableTo(leftType, rightType) && !isTypeAssignableTo(rightType, leftType)) { + if (!isTypeComparableTo(leftType, rightType) && !isTypeComparableTo(rightType, leftType)) { reportOperatorError(); } return booleanType; @@ -12689,12 +12708,12 @@ namespace ts { if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) { let caseClause = clause; - // TypeScript 1.0 spec (April 2014):5.9 + // TypeScript 1.0 spec (April 2014): 5.9 // In a 'switch' statement, each 'case' expression must be of a type that is assignable to or from the type of the 'switch' expression. let caseType = checkExpression(caseClause.expression); - if (!isTypeAssignableTo(expressionType, caseType)) { - // check 'expressionType isAssignableTo caseType' failed, try the reversed check and report errors if it fails - checkTypeAssignableTo(caseType, expressionType, caseClause.expression, /*headMessage*/ undefined); + + if (!isTypeComparableTo(expressionType, caseType)) { + checkTypeComparableTo(caseType, expressionType, caseClause.expression, /*headMessage*/ undefined); } } forEach(clause.statements, checkSourceElement);