Fixed a bug in the type narrowing for the "x is <class>" type guard pattern when <class> is a specific class T, as opposed to a variable of type type[T]. This addresses #8264.

This commit is contained in:
Eric Traut 2024-06-29 11:04:30 -07:00
parent fa0f6b7349
commit d55be983e5
2 changed files with 63 additions and 23 deletions

View File

@ -2463,33 +2463,69 @@ function narrowTypeForClassComparison(
isPositiveTest: boolean isPositiveTest: boolean
): Type { ): Type {
return mapSubtypes(referenceType, (subtype) => { return mapSubtypes(referenceType, (subtype) => {
const concreteSubtype = evaluator.makeTopLevelTypeVarsConcrete(subtype); let concreteSubtype = evaluator.makeTopLevelTypeVarsConcrete(subtype);
if (isPositiveTest) { if (isPositiveTest) {
if (isNoneInstance(concreteSubtype)) { if (isNoneInstance(concreteSubtype)) {
return undefined; return isNoneTypeClass(classType) ? classType : undefined;
} }
if (isClassInstance(concreteSubtype) && TypeBase.isInstance(subtype)) { if (
if (ClassType.isBuiltIn(concreteSubtype, 'type')) { isClassInstance(concreteSubtype) &&
return classType; TypeBase.isInstance(subtype) &&
ClassType.isBuiltIn(concreteSubtype, 'type')
) {
concreteSubtype =
concreteSubtype.typeArguments && concreteSubtype.typeArguments.length > 0
? convertToInstantiable(concreteSubtype.typeArguments[0])
: UnknownType.create();
}
if (isAnyOrUnknown(concreteSubtype)) {
return classType;
}
if (isClass(concreteSubtype)) {
if (TypeBase.isInstance(concreteSubtype)) {
return ClassType.isBuiltIn(concreteSubtype, 'object') ? classType : undefined;
} }
return undefined; const isSuperType = isIsinstanceFilterSuperclass(
} evaluator,
subtype,
concreteSubtype,
classType,
classType,
/* isInstanceCheck */ false
);
if (isInstantiableClass(concreteSubtype) && ClassType.isFinal(concreteSubtype)) { if (!classType.includeSubclasses) {
if ( // Handle the case where the LHS and RHS operands are specific
!ClassType.isSameGenericClass(concreteSubtype, classType) && // classes, as opposed to types that represent classes and their
!isIsinstanceFilterSuperclass( // subclasses.
if (!concreteSubtype.includeSubclasses) {
return ClassType.isSameGenericClass(concreteSubtype, classType) ? classType : undefined;
}
const isSubType = isIsinstanceFilterSubclass(
evaluator, evaluator,
subtype,
concreteSubtype, concreteSubtype,
classType, classType,
classType,
/* isInstanceCheck */ false /* isInstanceCheck */ false
) );
) {
if (isSuperType) {
return classType;
}
if (isSubType) {
return addConditionToType(classType, getTypeCondition(concreteSubtype));
}
return undefined;
}
if (ClassType.isFinal(concreteSubtype) && !isSuperType) {
return undefined; return undefined;
} }
} }

View File

@ -5,17 +5,14 @@ from typing import Any, TypeVar, final
@final @final
class A: class A: ...
...
@final @final
class B: class B: ...
...
class C: class C: ...
...
def func1(x: type[A] | type[B] | None | int): def func1(x: type[A] | type[B] | None | int):
@ -34,7 +31,7 @@ def func2(x: type[A] | type[B] | None | int, y: type[A]):
def func3(x: type[A] | type[B] | Any): def func3(x: type[A] | type[B] | Any):
if x is A: if x is A:
reveal_type(x, expected_text="type[A] | Any") reveal_type(x, expected_text="type[A]")
else: else:
reveal_type(x, expected_text="type[B] | Any") reveal_type(x, expected_text="type[B] | Any")
@ -51,7 +48,7 @@ T = TypeVar("T")
def func5(x: type[A] | type[B] | type[T]) -> type[A] | type[B] | type[T]: def func5(x: type[A] | type[B] | type[T]) -> type[A] | type[B] | type[T]:
if x is A: if x is A:
reveal_type(x, expected_text="type[A] | type[T@func5]") reveal_type(x, expected_text="type[A] | type[A]*")
else: else:
reveal_type(x, expected_text="type[B] | type[T@func5]") reveal_type(x, expected_text="type[B] | type[T@func5]")
@ -63,3 +60,10 @@ def func6(x: type):
reveal_type(x, expected_text="type[str]") reveal_type(x, expected_text="type[str]")
else: else:
reveal_type(x, expected_text="type") reveal_type(x, expected_text="type")
def func7(x: type[A | B]):
if x is A:
reveal_type(x, expected_text="type[A]")
else:
reveal_type(x, expected_text="type[B]")