From 5cb1fc06968426bbd37e93bc9420bffa5b75f33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 2 Oct 2022 22:56:10 +0200 Subject: [PATCH] Narrow intersected classes using `instanceof` --- src/compiler/checker.ts | 13 ++++--- .../instanceofNarrowIntersection.symbols | 36 +++++++++++++++++++ .../instanceofNarrowIntersection.types | 32 +++++++++++++++++ .../compiler/instanceofNarrowIntersection.ts | 18 ++++++++++ 4 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/instanceofNarrowIntersection.symbols create mode 100644 tests/baselines/reference/instanceofNarrowIntersection.types create mode 100644 tests/cases/compiler/instanceofNarrowIntersection.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0e4f940238cd3..56de7b9a475d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17775,7 +17775,7 @@ namespace ts { target.flags & TypeFlags.Union ? some((target as UnionType).types, t => isTypeDerivedFrom(source, t)) : source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : - target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) : + target === globalFunctionType ? isFunctionObjectType(source) : hasBaseType(source, getTargetType(target)) || (isArrayType(target) && !isReadonlyArrayType(target) && isTypeDerivedFrom(source, globalReadonlyArrayType)); } @@ -23945,10 +23945,13 @@ namespace ts { return isTypeAssignableTo(assignedType, reducedType) ? reducedType : declaredType; } - function isFunctionObjectType(type: ObjectType): boolean { + function isFunctionObjectType(type: Type): boolean { + if (!(type.flags & TypeFlags.StructuredType)) { + return false; + } // We do a quick check for a "bind" property before performing the more expensive subtype // check. This gives us a quicker out in the common case where an object type is not a function. - const resolved = resolveStructuredTypeMembers(type); + const resolved = resolveStructuredTypeMembers(type as StructuredType); return !!(resolved.callSignatures.length || resolved.constructSignatures.length || resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType)); } @@ -23996,7 +23999,7 @@ namespace ts { if (flags & TypeFlags.Object) { return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : - isFunctionObjectType(type as ObjectType) ? + isFunctionObjectType(type) ? strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } @@ -34629,7 +34632,7 @@ namespace ts { declKind !== AssignmentDeclarationKind.ModuleExports && declKind !== AssignmentDeclarationKind.Prototype && !isEmptyObjectType(rightType) && - !isFunctionObjectType(rightType as ObjectType) && + !isFunctionObjectType(rightType) && !(getObjectFlags(rightType) & ObjectFlags.Class)) { // don't check assignability of module.exports=, C.prototype=, or expando types because they will necessarily be incomplete checkAssignmentOperator(rightType); diff --git a/tests/baselines/reference/instanceofNarrowIntersection.symbols b/tests/baselines/reference/instanceofNarrowIntersection.symbols new file mode 100644 index 0000000000000..5646e4d9c48d2 --- /dev/null +++ b/tests/baselines/reference/instanceofNarrowIntersection.symbols @@ -0,0 +1,36 @@ +=== tests/cases/compiler/instanceofNarrowIntersection.ts === +// repro #50844 + +interface InstanceOne { +>InstanceOne : Symbol(InstanceOne, Decl(instanceofNarrowIntersection.ts, 0, 0)) + + one(): void +>one : Symbol(InstanceOne.one, Decl(instanceofNarrowIntersection.ts, 2, 23)) +} +interface InstanceTwo { +>InstanceTwo : Symbol(InstanceTwo, Decl(instanceofNarrowIntersection.ts, 4, 1)) + + two(): void +>two : Symbol(InstanceTwo.two, Decl(instanceofNarrowIntersection.ts, 5, 23)) +} + +declare const instance: InstanceOne | InstanceTwo +>instance : Symbol(instance, Decl(instanceofNarrowIntersection.ts, 9, 13)) +>InstanceOne : Symbol(InstanceOne, Decl(instanceofNarrowIntersection.ts, 0, 0)) +>InstanceTwo : Symbol(InstanceTwo, Decl(instanceofNarrowIntersection.ts, 4, 1)) + +declare const SomeCls: { new (): InstanceOne } & { foo: true } +>SomeCls : Symbol(SomeCls, Decl(instanceofNarrowIntersection.ts, 10, 13)) +>InstanceOne : Symbol(InstanceOne, Decl(instanceofNarrowIntersection.ts, 0, 0)) +>foo : Symbol(foo, Decl(instanceofNarrowIntersection.ts, 10, 50)) + +if (instance instanceof SomeCls) { +>instance : Symbol(instance, Decl(instanceofNarrowIntersection.ts, 9, 13)) +>SomeCls : Symbol(SomeCls, Decl(instanceofNarrowIntersection.ts, 10, 13)) + + instance.one() +>instance.one : Symbol(InstanceOne.one, Decl(instanceofNarrowIntersection.ts, 2, 23)) +>instance : Symbol(instance, Decl(instanceofNarrowIntersection.ts, 9, 13)) +>one : Symbol(InstanceOne.one, Decl(instanceofNarrowIntersection.ts, 2, 23)) +} + diff --git a/tests/baselines/reference/instanceofNarrowIntersection.types b/tests/baselines/reference/instanceofNarrowIntersection.types new file mode 100644 index 0000000000000..d588cad75fadc --- /dev/null +++ b/tests/baselines/reference/instanceofNarrowIntersection.types @@ -0,0 +1,32 @@ +=== tests/cases/compiler/instanceofNarrowIntersection.ts === +// repro #50844 + +interface InstanceOne { + one(): void +>one : () => void +} +interface InstanceTwo { + two(): void +>two : () => void +} + +declare const instance: InstanceOne | InstanceTwo +>instance : InstanceOne | InstanceTwo + +declare const SomeCls: { new (): InstanceOne } & { foo: true } +>SomeCls : (new () => InstanceOne) & { foo: true; } +>foo : true +>true : true + +if (instance instanceof SomeCls) { +>instance instanceof SomeCls : boolean +>instance : InstanceOne | InstanceTwo +>SomeCls : (new () => InstanceOne) & { foo: true; } + + instance.one() +>instance.one() : void +>instance.one : () => void +>instance : InstanceOne +>one : () => void +} + diff --git a/tests/cases/compiler/instanceofNarrowIntersection.ts b/tests/cases/compiler/instanceofNarrowIntersection.ts new file mode 100644 index 0000000000000..c420d36dc134a --- /dev/null +++ b/tests/cases/compiler/instanceofNarrowIntersection.ts @@ -0,0 +1,18 @@ +// @strict: true +// @noEmit: true + +// repro #50844 + +interface InstanceOne { + one(): void +} +interface InstanceTwo { + two(): void +} + +declare const instance: InstanceOne | InstanceTwo +declare const SomeCls: { new (): InstanceOne } & { foo: true } + +if (instance instanceof SomeCls) { + instance.one() +}