Skip to content

Commit 798fa05

Browse files
Perform weak type checks when performing comparisons.
1 parent cdf1ab2 commit 798fa05

File tree

1 file changed

+34
-15
lines changed

1 file changed

+34
-15
lines changed

src/compiler/checker.ts

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,7 @@ namespace ts {
877877
const subtypeRelation = createMap<RelationComparisonResult>();
878878
const assignableRelation = createMap<RelationComparisonResult>();
879879
const comparableRelation = createMap<RelationComparisonResult>();
880+
const castableRelation = createMap<RelationComparisonResult>();
880881
const identityRelation = createMap<RelationComparisonResult>();
881882
const enumRelation = createMap<RelationComparisonResult>();
882883

@@ -13398,6 +13399,18 @@ namespace ts {
1339813399
hasBaseType(source, getTargetType(target));
1339913400
}
1340013401

13402+
/**
13403+
* This is *not* a bi-directional relationship.
13404+
* If one needs to check both directions for castability, use a second call to this function or 'checkTypeCastableTo'.
13405+
*
13406+
* A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T.
13407+
* It is used to check following cases:
13408+
* - the type of an expression in a type assertion with the type being asserted.
13409+
*/
13410+
function isTypeCastableTo(source: Type, target: Type): boolean {
13411+
return isTypeRelatedTo(source, target, castableRelation);
13412+
}
13413+
1340113414
/**
1340213415
* This is *not* a bi-directional relationship.
1340313416
* If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'.
@@ -13406,7 +13419,6 @@ namespace ts {
1340613419
* It is used to check following cases:
1340713420
* - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`).
1340813421
* - the types of `case` clause expressions and their respective `switch` expressions.
13409-
* - the type of an expression in a type assertion with the type being asserted.
1341013422
*/
1341113423
function isTypeComparableTo(source: Type, target: Type): boolean {
1341213424
return isTypeRelatedTo(source, target, comparableRelation);
@@ -13839,6 +13851,14 @@ namespace ts {
1383913851
return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer);
1384013852
}
1384113853

13854+
/**
13855+
* This is *not* a bi-directional relationship.
13856+
* If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'.
13857+
*/
13858+
function checkTypeCastableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
13859+
return checkTypeRelatedTo(source, target, castableRelation, errorNode, headMessage, containingMessageChain);
13860+
}
13861+
1384213862
/**
1384313863
* This is *not* a bi-directional relationship.
1384413864
* If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'.
@@ -14137,7 +14157,7 @@ namespace ts {
1413714157
if (s & TypeFlags.Undefined && (!strictNullChecks || t & (TypeFlags.Undefined | TypeFlags.Void))) return true;
1413814158
if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) return true;
1413914159
if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) return true;
14140-
if (relation === assignableRelation || relation === comparableRelation) {
14160+
if (relation === assignableRelation || relation === comparableRelation || relation === castableRelation) {
1414114161
if (s & TypeFlags.Any) return true;
1414214162
// Type number or any numeric literal type is assignable to any numeric enum type or any
1414314163
// numeric enum literal type. This rule exists for backwards compatibility reasons because
@@ -14156,7 +14176,7 @@ namespace ts {
1415614176
target = (<FreshableType>target).regularType;
1415714177
}
1415814178
if (source === target ||
14159-
relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) ||
14179+
(relation === comparableRelation || relation === castableRelation) && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) ||
1416014180
relation !== identityRelation && isSimpleTypeRelatedTo(source, target, relation)) {
1416114181
return true;
1416214182
}
@@ -14180,7 +14200,7 @@ namespace ts {
1418014200
* Checks if 'source' is related to 'target' (e.g.: is a assignable to).
1418114201
* @param source The left-hand-side of the relation.
1418214202
* @param target The right-hand-side of the relation.
14183-
* @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'.
14203+
* @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', 'comparableRelation', or 'castableRelation'.
1418414204
* Used as both to determine which checks are performed and as a cache of previously computed results.
1418514205
* @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used.
1418614206
* @param headMessage If the error chain should be prepended by a head message, then headMessage will be used.
@@ -14414,7 +14434,7 @@ namespace ts {
1441414434
}
1441514435

1441614436
if (!message) {
14417-
if (relation === comparableRelation) {
14437+
if (relation === comparableRelation || relation === castableRelation) {
1441814438
message = Diagnostics.Type_0_is_not_comparable_to_type_1;
1441914439
}
1442014440
else if (sourceType === targetType) {
@@ -14524,7 +14544,7 @@ namespace ts {
1452414544
return isIdenticalTo(source, target);
1452514545
}
1452614546

14527-
if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) ||
14547+
if ((relation === comparableRelation || relation === castableRelation) && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) ||
1452814548
isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
1452914549

1453014550
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
@@ -14538,8 +14558,7 @@ namespace ts {
1453814558
return Ternary.False;
1453914559
}
1454014560
}
14541-
14542-
const isPerformingCommonPropertyChecks = relation !== comparableRelation && !isApparentIntersectionConstituent &&
14561+
const isPerformingCommonPropertyChecks = relation !== castableRelation && !isApparentIntersectionConstituent &&
1454314562
source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType &&
1454414563
target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) &&
1454514564
(getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source));
@@ -14566,7 +14585,7 @@ namespace ts {
1456614585
// we need to deconstruct unions before intersections (because unions are always at the top),
1456714586
// and we need to handle "each" relations before "some" relations for the same kind of type.
1456814587
if (source.flags & TypeFlags.Union) {
14569-
result = relation === comparableRelation ?
14588+
result = relation === comparableRelation || relation === castableRelation ?
1457014589
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), isIntersectionConstituent) :
1457114590
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
1457214591
}
@@ -14698,7 +14717,7 @@ namespace ts {
1469814717
}
1469914718
if (isExcessPropertyCheckTarget(target)) {
1470014719
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
14701-
if ((relation === assignableRelation || relation === comparableRelation) &&
14720+
if ((relation === assignableRelation || relation === comparableRelation || relation === castableRelation) &&
1470214721
(isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) {
1470314722
return false;
1470414723
}
@@ -15439,7 +15458,7 @@ namespace ts {
1543915458
// related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice
1544015459
// that S and T are contra-variant whereas X and Y are co-variant.
1544115460
function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary {
15442-
const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) :
15461+
const modifiersRelated = relation === comparableRelation || relation === castableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) :
1544315462
getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target));
1544415463
if (modifiersRelated) {
1544515464
let result: Ternary;
@@ -15655,7 +15674,7 @@ namespace ts {
1565515674
return Ternary.False;
1565615675
}
1565715676
// When checking for comparability, be more lenient with optional properties.
15658-
if (relation !== comparableRelation && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) {
15677+
if ((relation !== comparableRelation && relation !== castableRelation) && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) {
1565915678
// TypeScript 1.0 spec (April 2014): 3.8.3
1566015679
// S is a subtype of a type T, and T is a supertype of S if ...
1566115680
// S' and T are object types and, for each member M in T..
@@ -15852,7 +15871,7 @@ namespace ts {
1585215871
// in the context of the target signature before checking the relationship. Ideally we'd do
1585315872
// this regardless of the number of signatures, but the potential costs are prohibitive due
1585415873
// to the quadratic nature of the logic below.
15855-
const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks;
15874+
const eraseGenerics = relation === comparableRelation || relation === castableRelation || !!compilerOptions.noStrictGenericChecks;
1585615875
result = signatureRelatedTo(sourceSignatures[0], targetSignatures[0], eraseGenerics, reportErrors, incompatibleReporter(sourceSignatures[0], targetSignatures[0]));
1585715876
}
1585815877
else {
@@ -25234,8 +25253,8 @@ namespace ts {
2523425253
const targetType = getTypeFromTypeNode(type);
2523525254
if (produceDiagnostics && targetType !== errorType) {
2523625255
const widenedType = getWidenedType(exprType);
25237-
if (!isTypeComparableTo(targetType, widenedType)) {
25238-
checkTypeComparableTo(exprType, targetType, errNode,
25256+
if (!isTypeCastableTo(targetType, widenedType)) {
25257+
checkTypeCastableTo(exprType, targetType, errNode,
2523925258
Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first);
2524025259
}
2524125260
}

0 commit comments

Comments
 (0)