From 575c6392d4b6a4fd13b1e41b9ef40a3b7c2815d8 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 14 Jan 2025 12:05:11 -0800 Subject: [PATCH] Improved type narrowing for `issubclass` in the negative ("else") case when the subject type is `type` or `Any`. This addresses #9656. (#9704) --- .../src/analyzer/typeGuards.ts | 26 ++++++++++++------- .../tests/samples/typeNarrowingIsinstance1.py | 14 ++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/pyright-internal/src/analyzer/typeGuards.ts b/packages/pyright-internal/src/analyzer/typeGuards.ts index de9d4b6b35fd..d1dcc2c5519b 100644 --- a/packages/pyright-internal/src/analyzer/typeGuards.ts +++ b/packages/pyright-internal/src/analyzer/typeGuards.ts @@ -1249,20 +1249,15 @@ function narrowTypeForInstanceOrSubclassInternal( if (!isInstanceCheck) { const isTypeInstance = isClassInstance(subtype) && ClassType.isBuiltIn(subtype, 'type'); + // Handle metaclass instances specially. if (isMetaclassInstance(subtype) && !isTypeInstance) { - // Handle metaclass instances specially. adjFilterTypes = filterTypes.map((filterType) => convertToInstantiable(filterType)); } else { - const convSubtype = convertToInstance(subtype); + adjSubtype = convertToInstance(subtype); - // Handle type[Any] specially for this case. - if (isClassInstance(subtype) && ClassType.isBuiltIn(subtype, 'type') && isAnyOrUnknown(convSubtype)) { - adjSubtype = convertToInstance(evaluator.getObjectType()); - } else { - adjSubtype = convSubtype; + if (!isAnyOrUnknown(subtype) || isPositiveTest) { + resultRequiresAdj = true; } - - resultRequiresAdj = true; } } @@ -1276,7 +1271,18 @@ function narrowTypeForInstanceOrSubclassInternal( errorNode ); - return resultRequiresAdj ? convertToInstantiable(narrowedResult) : narrowedResult; + if (!resultRequiresAdj) { + return narrowedResult; + } + + if (isAnyOrUnknown(narrowedResult)) { + const typeClass = evaluator.getTypeClassType(); + if (typeClass) { + return ClassType.specialize(ClassType.cloneAsInstance(typeClass), [narrowedResult]); + } + } + + return convertToInstantiable(narrowedResult); }); return result; diff --git a/packages/pyright-internal/src/tests/samples/typeNarrowingIsinstance1.py b/packages/pyright-internal/src/tests/samples/typeNarrowingIsinstance1.py index 588db692f06f..832ce0dc6a29 100644 --- a/packages/pyright-internal/src/tests/samples/typeNarrowingIsinstance1.py +++ b/packages/pyright-internal/src/tests/samples/typeNarrowingIsinstance1.py @@ -135,6 +135,20 @@ def func6_2(ty: type[int] | int): reveal_type(ty, expected_text="int") +def func6_3(ty: type): + if issubclass(ty, str): + reveal_type(ty, expected_text="type[str]") + else: + reveal_type(ty, expected_text="type[Unknown]") + + +def func6_4(ty: Any): + if issubclass(ty, str): + reveal_type(ty, expected_text="type[str]") + else: + reveal_type(ty, expected_text="Any") + + # Test the handling of protocol classes that support runtime checking. def func7(a: Union[list[int], int]): if isinstance(a, Sized):