Fixed bug that resulted in a false negative when dealing with types that are conditioned on constrained or bound TypeVars.

This commit is contained in:
Eric Traut 2021-10-02 19:02:55 -07:00
parent 5e38fabc3a
commit 350de179ed
4 changed files with 97 additions and 31 deletions

View File

@ -18782,47 +18782,23 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return true;
}
// If the dest is a constrained type variable and all of the types in
// the source are constrained using that same type variable and have
// compatible types, we'll consider it assignable.
const destTypeVar = destType;
if (
findSubtype(srcType, (srcSubtype) => {
if (isTypeSame(destTypeVar, srcSubtype, /* ignorePseudoGeneric */ true)) {
return false;
}
if (
getTypeCondition(srcSubtype)?.find(
(constraint) => constraint.typeVarName === TypeVarType.getNameWithScope(destTypeVar)
)
) {
if (
destTypeVar.details.constraints.length === 0 ||
destTypeVar.details.constraints.some((constraintType) => {
return canAssignType(constraintType, srcSubtype, new DiagnosticAddendum());
})
) {
return false;
}
}
return true;
}) === undefined
) {
// If the dest is a constrained or bound type variable and all of the
// types in the source are conditioned on that same type variable
// and have compatible types, we'll consider it assignable.
if (canAssignConditionalTypeToTypeVar(destType, srcType, recursionCount + 1)) {
return true;
}
// If the dest is a variadic type variable, and the source is a tuple
// with a single entry that is the same variadic type variable, it's a match.
if (
isVariadicTypeVar(destTypeVar) &&
isVariadicTypeVar(destType) &&
isClassInstance(srcType) &&
isTupleClass(srcType) &&
srcType.tupleTypeArguments &&
srcType.tupleTypeArguments.length === 1
) {
if (isTypeSame(destTypeVar, srcType.tupleTypeArguments[0])) {
if (isTypeSame(destType, srcType.tupleTypeArguments[0])) {
return true;
}
}
@ -19574,6 +19550,60 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return false;
}
function canAssignConditionalTypeToTypeVar(destType: TypeVarType, srcType: Type, recursionCount: number): boolean {
// The subType is assignable only if all of its subtypes are assignable.
return !findSubtype(srcType, (srcSubtype) => {
if (isTypeSame(destType, srcSubtype, /* ignorePseudoGeneric */ true, recursionCount + 1)) {
return false;
}
const destTypeVarName = TypeVarType.getNameWithScope(destType);
// Determine which conditions on this type apply to this type variable.
// There might be more than one of them.
const applicableConditions = (getTypeCondition(srcSubtype) ?? []).filter(
(constraint) => constraint.typeVarName === destTypeVarName
);
// If there are no applicable conditions, it's not assignable.
if (applicableConditions.length === 0) {
return true;
}
return !applicableConditions.some((condition) => {
if (destType.details.boundType) {
assert(condition.constraintIndex === 0);
return isTypeSame(
destType.details.boundType,
TypeBase.cloneForCondition(srcSubtype, /* condition */ undefined),
/* ignorePseudoGeneric */ true,
recursionCount + 1
);
}
if (destType.details.constraints.length > 0) {
assert(condition.constraintIndex < destType.details.constraints.length);
const typeVarConstraint = destType.details.constraints[condition.constraintIndex];
assert(typeVarConstraint !== undefined);
return canAssignType(
typeVarConstraint,
srcSubtype,
new DiagnosticAddendum(),
/* typeVarMap */ undefined,
/* flags */ undefined,
recursionCount + 1
);
}
// This is a non-bound and non-constrained type variable with a matching condition.
assert(condition.constraintIndex === 0);
return true;
});
});
}
// Synthesize a function that represents the constructor for this class
// taking into consideration the __init__ and __new__ methods.
function createFunctionFromConstructor(classType: ClassType): FunctionType | OverloadedFunctionType | undefined {

View File

@ -0,0 +1,30 @@
# This sample tests for proper handling of constrained or bound TypeVars.
from typing import Literal, TypeVar
class IntSubclass1(int):
pass
_T1 = TypeVar("_T1", int, IntSubclass1)
def add1(value: _T1) -> _T1:
t1: Literal["int*"] = reveal_type(value + 1)
# This should generate an error
return value + 5
class IntSubclass2(int):
def __add__(self, value: object) -> "IntSubclass2":
...
_T2 = TypeVar("_T2", int, IntSubclass2)
def add2(value: _T2) -> _T2:
t1: Literal["int* | IntSubclass2*"] = reveal_type(value + 1)
return value + 5

View File

@ -68,7 +68,7 @@ def test2(o: Foo) -> None:
T = TypeVar("T", int, str, Callable[[], int], Callable[[], str])
def test3(v: T) -> T:
def test3(v: T) -> Union[T, int, str]:
if callable(v):
t1: Literal["() -> int | () -> str"] = reveal_type(v)
t2: Literal["int* | str*"] = reveal_type(v())

View File

@ -703,6 +703,12 @@ test('GenericTypes66', () => {
TestUtils.validateResults(analysisResults, 1);
});
test('GenericTypes67', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['genericTypes67.py']);
TestUtils.validateResults(analysisResults, 1);
});
test('Protocol1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocol1.py']);