From e586d6f1f175b31b8986fa7956c69ae040f05820 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 5 Jun 2022 22:04:19 -0700 Subject: [PATCH] Fixed false positive error that occurs when using an `issubclass` type guard to narrow a type to an abstract base class. The resulting type should be instantiable without receiving a "cannot instantiate ABC" error. --- .../src/analyzer/typeGuards.ts | 18 ++++++++++++++++-- .../tests/samples/typeNarrowingIsinstance8.py | 19 +++++++++++++++++++ .../src/tests/typeEvaluator1.test.ts | 6 ++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 packages/pyright-internal/src/tests/samples/typeNarrowingIsinstance8.py diff --git a/packages/pyright-internal/src/analyzer/typeGuards.ts b/packages/pyright-internal/src/analyzer/typeGuards.ts index 76df7d345..c68e92376 100644 --- a/packages/pyright-internal/src/analyzer/typeGuards.ts +++ b/packages/pyright-internal/src/analyzer/typeGuards.ts @@ -1060,7 +1060,14 @@ function narrowTypeForIsInstance( ]) as ClassType; } - filteredTypes.push(isInstanceCheck ? ClassType.cloneAsInstance(newClassType) : newClassType); + const newClassInstanceType = ClassType.cloneAsInstance(newClassType); + + // If this is a issubclass check, we do a double conversion from instantiable + // to instance back to instantiable to make sure that the includeSubclasses flag + // gets cleared. + filteredTypes.push( + isInstanceCheck ? newClassInstanceType : ClassType.cloneAsInstantiable(newClassInstanceType) + ); } } } else if (isTypeVar(filterType) && TypeBase.isInstantiable(filterType)) { @@ -1198,7 +1205,14 @@ function narrowTypeForIsInstance( combineTypes(classTypeList.map((classType) => convertToInstance(classType))) ); } else { - anyOrUnknownSubstitutions.push(combineTypes(classTypeList)); + // We perform a double conversion from instance to instantiable + // here to make sure that the includeSubclasses flag is cleared + // if it's a class. + anyOrUnknownSubstitutions.push( + combineTypes( + classTypeList.map((classType) => convertToInstantiable(convertToInstance(classType))) + ) + ); } anyOrUnknown.push(subtype); diff --git a/packages/pyright-internal/src/tests/samples/typeNarrowingIsinstance8.py b/packages/pyright-internal/src/tests/samples/typeNarrowingIsinstance8.py new file mode 100644 index 000000000..ba539af8b --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/typeNarrowingIsinstance8.py @@ -0,0 +1,19 @@ +# This sample tests the case where an issubclass type guard narrows +# to an abstract base class. When attempting to instantiate the +# class, there should be no "cannot instantiate ABC" error. + +# pyright: strict + +from abc import ABC, abstractmethod +from typing import Any + + +class Base(ABC): + @abstractmethod + def f(self) -> None: + ... + + +def func(cls: Any): + assert issubclass(cls, Base) + _ = cls() diff --git a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts index 59b168f94..6301ef267 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts @@ -374,6 +374,12 @@ test('TypeNarrowingIsinstance7', () => { TestUtils.validateResults(analysisResults, 0); }); +test('TypeNarrowingIsinstance8', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowingIsinstance8.py']); + + TestUtils.validateResults(analysisResults, 0); +}); + test('TypeNarrowingTupleLength1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowingTupleLength1.py']);