mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 16:06:39 +03:00
Added support for isinstance
and issubclass
type narrowing where the variable type and the test type have no apparent relationship. The type evaluator synthesizes a class that is a subclass of both types — effectively an "intersection" of the two.
This commit is contained in:
parent
4c96bcfe58
commit
c78f6db84b
@ -17891,7 +17891,27 @@ export function createTypeEvaluator(
|
||||
const classTypeList = getIsInstanceClassTypes(arg1Type);
|
||||
if (classTypeList) {
|
||||
return (type: Type) => {
|
||||
return narrowTypeForIsInstance(type, classTypeList, isInstanceCheck, isPositiveTest);
|
||||
const narrowedType = narrowTypeForIsInstance(
|
||||
type,
|
||||
classTypeList,
|
||||
isInstanceCheck,
|
||||
isPositiveTest,
|
||||
/* allowIntersections */ false,
|
||||
testExpression
|
||||
);
|
||||
if (!isNever(narrowedType)) {
|
||||
return narrowedType;
|
||||
}
|
||||
|
||||
// Try again with intersection types allowed.
|
||||
return narrowTypeForIsInstance(
|
||||
type,
|
||||
classTypeList,
|
||||
isInstanceCheck,
|
||||
isPositiveTest,
|
||||
/* allowIntersections */ true,
|
||||
testExpression
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -18018,7 +18038,9 @@ export function createTypeEvaluator(
|
||||
type: Type,
|
||||
classTypeList: (ClassType | TypeVarType | NoneType)[],
|
||||
isInstanceCheck: boolean,
|
||||
isPositiveTest: boolean
|
||||
isPositiveTest: boolean,
|
||||
allowIntersections: boolean,
|
||||
errorNode: ExpressionNode
|
||||
): Type {
|
||||
const expandedTypes = mapSubtypes(type, (subtype) => {
|
||||
return transformPossibleRecursiveTypeAlias(subtype);
|
||||
@ -18084,15 +18106,34 @@ export function createTypeEvaluator(
|
||||
// If the variable type is a superclass of the isinstance
|
||||
// filter, we can narrow the type to the subclass.
|
||||
filteredTypes.push(addConditionToType(filterType, constraints));
|
||||
} else if (
|
||||
ClassType.isProtocolClass(concreteFilterType) &&
|
||||
ClassType.isProtocolClass(varType)
|
||||
) {
|
||||
// If both the filter type and the variable type are protocol classes,
|
||||
// the correct answer is an intersection of the two types, but we don't
|
||||
// support intersections in the type system, so the best we can do
|
||||
// is to choose one.
|
||||
filteredTypes.push(filterType);
|
||||
} else if (allowIntersections) {
|
||||
// The two types appear to have no relation. It's possible that the
|
||||
// two types are protocols or the program is expecting one type to
|
||||
// be a mix-in class used with the other. In this case, we'll
|
||||
// synthesize a new class type that represents an intersection of
|
||||
// the two types.
|
||||
const className = `<subclass of ${varType.details.name} and ${concreteFilterType.details.name}>`;
|
||||
const fileInfo = getFileInfo(errorNode);
|
||||
const newClassType = ClassType.createInstantiable(
|
||||
className,
|
||||
getClassFullName(errorNode, fileInfo.moduleName, className),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
ClassTypeFlags.None,
|
||||
getTypeSourceId(errorNode),
|
||||
/* declaredMetaclass */ undefined,
|
||||
varType.details.effectiveMetaclass,
|
||||
varType.details.docString
|
||||
);
|
||||
newClassType.details.baseClasses = [
|
||||
ClassType.cloneAsInstantiable(varType),
|
||||
concreteFilterType,
|
||||
];
|
||||
computeMroLinearization(newClassType);
|
||||
|
||||
filteredTypes.push(
|
||||
isInstanceCheck ? ClassType.cloneAsInstance(newClassType) : newClassType
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (isTypeVar(filterType) && TypeBase.isInstantiable(filterType)) {
|
||||
|
@ -10,6 +10,6 @@ def f(v: Any) -> bool:
|
||||
if isinstance(v, Iterable):
|
||||
t_v1: Literal["Iterable[Unknown]"] = reveal_type(v)
|
||||
if isinstance(v, Sized):
|
||||
t_v2: Literal["Sized"] = reveal_type(v)
|
||||
t_v2: Literal["<subclass of Iterable and Sized>"] = reveal_type(v)
|
||||
return True
|
||||
return False
|
||||
|
@ -0,0 +1,69 @@
|
||||
# This sample tests the handling of isinstance and issubclass type
|
||||
# narrowing in the case where there is no overlap between the
|
||||
# value type and the test type.
|
||||
|
||||
from typing import Literal, Type
|
||||
|
||||
|
||||
class A:
|
||||
a_val: int
|
||||
|
||||
|
||||
class B:
|
||||
b_val: int
|
||||
|
||||
|
||||
class C:
|
||||
c_val: int
|
||||
|
||||
|
||||
def func1(val: A):
|
||||
if isinstance(val, B):
|
||||
val.a_val
|
||||
val.b_val
|
||||
|
||||
# This should generate an error
|
||||
val.c_val
|
||||
|
||||
t1: Literal["<subclass of A and B>"] = reveal_type(val)
|
||||
|
||||
if isinstance(val, C):
|
||||
val.a_val
|
||||
val.b_val
|
||||
val.c_val
|
||||
t2: Literal["<subclass of <subclass of A and B> and C>"] = reveal_type(val)
|
||||
|
||||
else:
|
||||
val.a_val
|
||||
|
||||
# This should generate an error
|
||||
val.b_val
|
||||
|
||||
t3: Literal["A"] = reveal_type(val)
|
||||
|
||||
|
||||
def func2(val: Type[A]):
|
||||
if issubclass(val, B):
|
||||
val.a_val
|
||||
val.b_val
|
||||
|
||||
# This should generate an error
|
||||
val.c_val
|
||||
|
||||
t1: Literal["Type[<subclass of A and B>]"] = reveal_type(val)
|
||||
|
||||
if issubclass(val, C):
|
||||
val.a_val
|
||||
val.b_val
|
||||
val.c_val
|
||||
t2: Literal[
|
||||
"Type[<subclass of <subclass of A and B> and C>]"
|
||||
] = reveal_type(val)
|
||||
|
||||
else:
|
||||
val.a_val
|
||||
|
||||
# This should generate an error
|
||||
val.b_val
|
||||
|
||||
t3: Literal["Type[A]"] = reveal_type(val)
|
@ -352,6 +352,12 @@ test('TypeNarrowing22', () => {
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('TypeNarrowing23', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowing23.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 4);
|
||||
});
|
||||
|
||||
test('ReturnTypes1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['returnTypes1.py']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user