Added "comparability" relation. It's currently equivalent to assignability.

This commit is contained in:
Daniel Rosenwasser
2015-10-27 16:17:26 -07:00
parent 7426aca392
commit 43f158d418
+33 -14
View File
@@ -211,6 +211,7 @@ namespace ts {
let subtypeRelation: Map<RelationComparisonResult> = {};
let assignableRelation: Map<RelationComparisonResult> = {};
let comparableRelation: Map<RelationComparisonResult> = {};
let identityRelation: Map<RelationComparisonResult> = {};
// 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(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target)) {
if (result &= eachTypeRelatedToSomeType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>source)) {
if (result = eachTypeRelatedToSomeType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target, /*reportErrors*/ false)) {
if (result &= eachTypeRelatedToSomeType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>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 = <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);