Skip to content

Commit add8b49

Browse files
authored
Merge pull request #19671 from Microsoft/nominalInstanceof
Improved handling of structurally identical classes
2 parents f67a9ba + 81326ac commit add8b49

8 files changed

+44
-40
lines changed

src/compiler/checker.ts

+28-22
Original file line numberDiff line numberDiff line change
@@ -7440,9 +7440,12 @@ namespace ts {
74407440
return false;
74417441
}
74427442

7443-
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
7444-
for (const type of types) {
7445-
if (candidate !== type && isTypeSubtypeOf(candidate, type)) {
7443+
function isSubtypeOfAny(source: Type, targets: Type[]): boolean {
7444+
for (const target of targets) {
7445+
if (source !== target && isTypeSubtypeOf(source, target) && (
7446+
!(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) ||
7447+
!(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) ||
7448+
isTypeDerivedFrom(source, target))) {
74467449
return true;
74477450
}
74487451
}
@@ -8565,12 +8568,19 @@ namespace ts {
85658568
return isTypeRelatedTo(source, target, assignableRelation);
85668569
}
85678570

8568-
// A type S is considered to be an instance of a type T if S and T are the same type or if S is a
8569-
// subtype of T but not structurally identical to T. This specifically means that two distinct but
8570-
// structurally identical types (such as two classes) are not considered instances of each other.
8571-
function isTypeInstanceOf(source: Type, target: Type): boolean {
8572-
return getTargetType(source) === getTargetType(target) || isTypeSubtypeOf(source, target) && !isTypeIdenticalTo(source, target);
8573-
}
8571+
// An object type S is considered to be derived from an object type T if
8572+
// S is a union type and every constituent of S is derived from T,
8573+
// T is a union type and S is derived from at least one constituent of T, or
8574+
// T is one of the global types Object and Function and S is a subtype of T, or
8575+
// T occurs directly or indirectly in an 'extends' clause of S.
8576+
// Note that this check ignores type parameters and only considers the
8577+
// inheritance hierarchy.
8578+
function isTypeDerivedFrom(source: Type, target: Type): boolean {
8579+
return source.flags & TypeFlags.Union ? every((<UnionType>source).types, t => isTypeDerivedFrom(t, target)) :
8580+
target.flags & TypeFlags.Union ? some((<UnionType>target).types, t => isTypeDerivedFrom(source, t)) :
8581+
target === globalObjectType || target === globalFunctionType ? isTypeSubtypeOf(source, target) :
8582+
hasBaseType(source, getTargetType(target));
8583+
}
85748584

85758585
/**
85768586
* This is *not* a bi-directional relationship.
@@ -9604,7 +9614,7 @@ namespace ts {
96049614
if (relation === identityRelation) {
96059615
return propertiesIdenticalTo(source, target);
96069616
}
9607-
const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source);
9617+
const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source);
96089618
const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties);
96099619
if (unmatchedProperty) {
96109620
if (reportErrors) {
@@ -10312,6 +10322,11 @@ namespace ts {
1031210322
!(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);
1031310323
}
1031410324

10325+
function isEmptyArrayLiteralType(type: Type): boolean {
10326+
const elementType = isArrayType(type) ? (<TypeReference>type).typeArguments[0] : undefined;
10327+
return elementType === undefinedWideningType || elementType === neverType;
10328+
}
10329+
1031510330
function isTupleLikeType(type: Type): boolean {
1031610331
return !!getPropertyOfType(type, "0" as __String);
1031710332
}
@@ -12415,7 +12430,7 @@ namespace ts {
1241512430
}
1241612431

1241712432
if (targetType) {
12418-
return getNarrowedType(type, targetType, assumeTrue, isTypeInstanceOf);
12433+
return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
1241912434
}
1242012435

1242112436
return type;
@@ -13883,7 +13898,6 @@ namespace ts {
1388313898
type.pattern = node;
1388413899
return type;
1388513900
}
13886-
const contextualType = getApparentTypeOfContextualType(node);
1388713901
if (contextualType && contextualTypeIsTupleLikeType(contextualType)) {
1388813902
const pattern = contextualType.pattern;
1388913903
// If array literal is contextually typed by a binding pattern or an assignment pattern, pad the resulting
@@ -18087,14 +18101,6 @@ namespace ts {
1808718101
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
1808818102
}
1808918103

18090-
function getBestChoiceType(type1: Type, type2: Type): Type {
18091-
const firstAssignableToSecond = isTypeAssignableTo(type1, type2);
18092-
const secondAssignableToFirst = isTypeAssignableTo(type2, type1);
18093-
return secondAssignableToFirst && !firstAssignableToSecond ? type1 :
18094-
firstAssignableToSecond && !secondAssignableToFirst ? type2 :
18095-
getUnionType([type1, type2], /*subtypeReduction*/ true);
18096-
}
18097-
1809818104
function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) {
1809918105
return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, checkMode, node);
1810018106
}
@@ -18231,7 +18237,7 @@ namespace ts {
1823118237
leftType;
1823218238
case SyntaxKind.BarBarToken:
1823318239
return getTypeFacts(leftType) & TypeFacts.Falsy ?
18234-
getBestChoiceType(removeDefinitelyFalsyTypes(leftType), rightType) :
18240+
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], /*subtypeReduction*/ true) :
1823518241
leftType;
1823618242
case SyntaxKind.EqualsToken:
1823718243
checkAssignmentOperator(rightType);
@@ -18391,7 +18397,7 @@ namespace ts {
1839118397
checkExpression(node.condition);
1839218398
const type1 = checkExpression(node.whenTrue, checkMode);
1839318399
const type2 = checkExpression(node.whenFalse, checkMode);
18394-
return getBestChoiceType(type1, type2);
18400+
return getUnionType([type1, type2], /*subtypeReduction*/ true);
1839518401
}
1839618402

1839718403
function checkTemplateExpression(node: TemplateExpression): Type {

src/compiler/factory.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2641,7 +2641,7 @@ namespace ts {
26412641
/**
26422642
* Gets a custom text range to use when emitting source maps.
26432643
*/
2644-
export function getSourceMapRange(node: Node) {
2644+
export function getSourceMapRange(node: Node): SourceMapRange {
26452645
const emitNode = node.emitNode;
26462646
return (emitNode && emitNode.sourceMapRange) || node;
26472647
}

tests/baselines/reference/arrayLiteralsWithRecursiveGenerics.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ var myList: MyList<number>;
5555
>MyList : MyList<T>
5656

5757
var xs = [list, myList]; // {}[]
58-
>xs : List<number>[]
59-
>[list, myList] : List<number>[]
58+
>xs : (List<number> | MyList<number>)[]
59+
>[list, myList] : (List<number> | MyList<number>)[]
6060
>list : List<number>
6161
>myList : MyList<number>
6262

tests/baselines/reference/fixSignatureCaching.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -866,9 +866,9 @@ define(function () {
866866
>'UnknownMobile' : "UnknownMobile"
867867

868868
isArray = ('isArray' in Array) ?
869-
>isArray = ('isArray' in Array) ? Array.isArray : function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : (value: any) => boolean
869+
>isArray = ('isArray' in Array) ? Array.isArray : function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : (arg: any) => arg is any[]
870870
>isArray : any
871-
>('isArray' in Array) ? Array.isArray : function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : (value: any) => boolean
871+
>('isArray' in Array) ? Array.isArray : function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : (arg: any) => arg is any[]
872872
>('isArray' in Array) : boolean
873873
>'isArray' in Array : boolean
874874
>'isArray' : "isArray"

tests/baselines/reference/narrowingGenericTypeFromInstanceof01.errors.txt

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
tests/cases/conformance/types/typeRelationships/instanceOf/narrowingGenericTypeFromInstanceof01.ts(13,17): error TS2345: Argument of type 'A<T> | B<T>' is not assignable to parameter of type 'A<T>'.
2-
Type 'B<T>' is not assignable to type 'A<T>'.
3-
Property 'a' is missing in type 'B<T>'.
1+
tests/cases/conformance/types/typeRelationships/instanceOf/narrowingGenericTypeFromInstanceof01.ts(13,17): error TS2345: Argument of type 'B<T>' is not assignable to parameter of type 'A<{}>'.
2+
Property 'a' is missing in type 'B<T>'.
43

54

65
==== tests/cases/conformance/types/typeRelationships/instanceOf/narrowingGenericTypeFromInstanceof01.ts (1 errors) ====
@@ -18,9 +17,8 @@ tests/cases/conformance/types/typeRelationships/instanceOf/narrowingGenericTypeF
1817
if (x instanceof B) {
1918
acceptA(x);
2019
~
21-
!!! error TS2345: Argument of type 'A<T> | B<T>' is not assignable to parameter of type 'A<T>'.
22-
!!! error TS2345: Type 'B<T>' is not assignable to type 'A<T>'.
23-
!!! error TS2345: Property 'a' is missing in type 'B<T>'.
20+
!!! error TS2345: Argument of type 'B<T>' is not assignable to parameter of type 'A<{}>'.
21+
!!! error TS2345: Property 'a' is missing in type 'B<T>'.
2422
}
2523

2624
if (x instanceof A) {

tests/baselines/reference/narrowingGenericTypeFromInstanceof01.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function test<T>(x: A<T> | B<T>) {
4343
acceptA(x);
4444
>acceptA(x) : any
4545
>acceptA : <T>(a: A<T>) => void
46-
>x : A<T> | B<T>
46+
>x : B<T>
4747
}
4848

4949
if (x instanceof A) {
@@ -65,7 +65,7 @@ function test<T>(x: A<T> | B<T>) {
6565
acceptB(x);
6666
>acceptB(x) : void
6767
>acceptB : <T>(b: B<T>) => void
68-
>x : A<T> | B<T>
68+
>x : B<T>
6969
}
7070

7171
if (x instanceof B) {
@@ -76,6 +76,6 @@ function test<T>(x: A<T> | B<T>) {
7676
acceptB(x);
7777
>acceptB(x) : void
7878
>acceptB : <T>(b: B<T>) => void
79-
>x : A<T> | B<T>
79+
>x : B<T>
8080
}
8181
}

tests/baselines/reference/nonContextuallyTypedLogicalOr.symbols

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ var e: Ellement;
2828
>Ellement : Symbol(Ellement, Decl(nonContextuallyTypedLogicalOr.ts, 3, 1))
2929

3030
(c || e).dummy;
31-
>(c || e).dummy : Symbol(Contextual.dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22))
31+
>(c || e).dummy : Symbol(dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22), Decl(nonContextuallyTypedLogicalOr.ts, 5, 20))
3232
>c : Symbol(c, Decl(nonContextuallyTypedLogicalOr.ts, 10, 3))
3333
>e : Symbol(e, Decl(nonContextuallyTypedLogicalOr.ts, 11, 3))
34-
>dummy : Symbol(Contextual.dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22))
34+
>dummy : Symbol(dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22), Decl(nonContextuallyTypedLogicalOr.ts, 5, 20))
3535

tests/baselines/reference/nonContextuallyTypedLogicalOr.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ var e: Ellement;
2929

3030
(c || e).dummy;
3131
>(c || e).dummy : any
32-
>(c || e) : Contextual
33-
>c || e : Contextual
32+
>(c || e) : Contextual | Ellement
33+
>c || e : Contextual | Ellement
3434
>c : Contextual
3535
>e : Ellement
3636
>dummy : any

0 commit comments

Comments
 (0)