mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-19 04:07:36 +03:00
Enhanced the isinstance
type narrowing logic so it filters types based on the number of entries in a tuple if the type derives from a tuple. This addresses https://github.com/microsoft/pyright/issues/5346. (#5348)
Co-authored-by: Eric Traut <erictr@microsoft.com>
This commit is contained in:
parent
37535a3171
commit
ed73c0e965
@ -1258,45 +1258,47 @@ function narrowTypeForIsInstance(
|
||||
// we haven't learned anything new about the variable type.
|
||||
filteredTypes.push(addConditionToType(varType, constraints));
|
||||
} else if (filterIsSubclass) {
|
||||
// If the variable type is a superclass of the isinstance
|
||||
// filter, we can narrow the type to the subclass.
|
||||
let specializedFilterType = filterType;
|
||||
|
||||
// Try to retain the type arguments for the filter type. This is
|
||||
// important because a specialized version of the filter cannot
|
||||
// be passed to isinstance or issubclass.
|
||||
if (isClass(filterType)) {
|
||||
if (
|
||||
ClassType.isSpecialBuiltIn(filterType) ||
|
||||
filterType.details.typeParameters.length > 0
|
||||
) {
|
||||
const typeVarContext = new TypeVarContext(getTypeVarScopeId(filterType));
|
||||
const unspecializedFilterType = ClassType.cloneForSpecialization(
|
||||
filterType,
|
||||
/* typeArguments */ undefined,
|
||||
/* isTypeArgumentExplicit */ false
|
||||
);
|
||||
if (evaluator.assignType(varType, filterType)) {
|
||||
// If the variable type is a superclass of the isinstance
|
||||
// filter, we can narrow the type to the subclass.
|
||||
let specializedFilterType = filterType;
|
||||
|
||||
// Try to retain the type arguments for the filter type. This is
|
||||
// important because a specialized version of the filter cannot
|
||||
// be passed to isinstance or issubclass.
|
||||
if (isClass(filterType)) {
|
||||
if (
|
||||
populateTypeVarContextBasedOnExpectedType(
|
||||
evaluator,
|
||||
unspecializedFilterType,
|
||||
varType,
|
||||
typeVarContext,
|
||||
/* liveTypeVarScopes */ undefined,
|
||||
errorNode.start
|
||||
)
|
||||
ClassType.isSpecialBuiltIn(filterType) ||
|
||||
filterType.details.typeParameters.length > 0
|
||||
) {
|
||||
specializedFilterType = applySolvedTypeVars(
|
||||
unspecializedFilterType,
|
||||
typeVarContext,
|
||||
{ unknownIfNotFound: true }
|
||||
) as ClassType;
|
||||
const typeVarContext = new TypeVarContext(getTypeVarScopeId(filterType));
|
||||
const unspecializedFilterType = ClassType.cloneForSpecialization(
|
||||
filterType,
|
||||
/* typeArguments */ undefined,
|
||||
/* isTypeArgumentExplicit */ false
|
||||
);
|
||||
|
||||
if (
|
||||
populateTypeVarContextBasedOnExpectedType(
|
||||
evaluator,
|
||||
unspecializedFilterType,
|
||||
varType,
|
||||
typeVarContext,
|
||||
/* liveTypeVarScopes */ undefined,
|
||||
errorNode.start
|
||||
)
|
||||
) {
|
||||
specializedFilterType = applySolvedTypeVars(
|
||||
unspecializedFilterType,
|
||||
typeVarContext,
|
||||
{ unknownIfNotFound: true }
|
||||
) as ClassType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filteredTypes.push(addConditionToType(specializedFilterType, constraints));
|
||||
filteredTypes.push(addConditionToType(specializedFilterType, constraints));
|
||||
}
|
||||
} else if (
|
||||
allowIntersections &&
|
||||
!ClassType.isFinal(varType) &&
|
||||
|
@ -0,0 +1,63 @@
|
||||
# This sample tests the case where a filter (guard) type has a subtype
|
||||
# relationship to the type of the variable being filtered but the
|
||||
# type arguments sometimes mean that it cannot be a subtype.
|
||||
|
||||
from typing import Generic, NamedTuple, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class NT1(NamedTuple, Generic[T]):
|
||||
pass
|
||||
|
||||
|
||||
def func1(val: NT1[str] | tuple[int, int]):
|
||||
if isinstance(val, NT1):
|
||||
reveal_type(val, expected_text="NT1[str]")
|
||||
else:
|
||||
reveal_type(val, expected_text="tuple[int, int]")
|
||||
|
||||
|
||||
class NT2(NamedTuple, Generic[T]):
|
||||
a: T
|
||||
b: str
|
||||
|
||||
|
||||
def func2(val: NT2[str] | tuple[int, int]):
|
||||
if isinstance(val, NT2):
|
||||
reveal_type(val, expected_text="NT2[str]")
|
||||
else:
|
||||
reveal_type(val, expected_text="tuple[int, int]")
|
||||
|
||||
|
||||
def func3(val: NT2[str] | tuple[int, str]):
|
||||
if isinstance(val, NT2):
|
||||
reveal_type(val, expected_text="NT2[str] | NT2[Unknown]")
|
||||
else:
|
||||
reveal_type(val, expected_text="tuple[int, str]")
|
||||
|
||||
|
||||
class NT3(NamedTuple, Generic[T]):
|
||||
a: T
|
||||
b: T
|
||||
|
||||
|
||||
def func4(val: NT3[str] | tuple[int, int]):
|
||||
if isinstance(val, NT3):
|
||||
reveal_type(val, expected_text="NT3[str] | NT3[Unknown]")
|
||||
else:
|
||||
reveal_type(val, expected_text="tuple[int, int]")
|
||||
|
||||
|
||||
def func5(val: NT3[str] | tuple[str, str, str]):
|
||||
if isinstance(val, NT3):
|
||||
reveal_type(val, expected_text="NT3[str]")
|
||||
else:
|
||||
reveal_type(val, expected_text="tuple[str, str, str]")
|
||||
|
||||
|
||||
def func6(val: NT3[str] | tuple[str, ...]):
|
||||
if isinstance(val, NT3):
|
||||
reveal_type(val, expected_text="NT3[str] | NT3[Unknown]")
|
||||
else:
|
||||
reveal_type(val, expected_text="tuple[str, ...]")
|
@ -442,10 +442,13 @@ test('TypeNarrowingIsinstance16', () => {
|
||||
});
|
||||
|
||||
test('TypeNarrowingIsinstance17', () => {
|
||||
// This test requires Python 3.10 because it uses PEP 604 notation for unions.
|
||||
const configOptions = new ConfigOptions('.');
|
||||
configOptions.defaultPythonVersion = PythonVersion.V3_10;
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowingIsinstance17.py'], configOptions);
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowingIsinstance17.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('TypeNarrowingIsinstance18', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowingIsinstance18.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user