Fixed a bug that results in a false positive error under certain circumstances when a TypeVar or TypeVarTuple is being solved with literal values in both an invariant and non-invariant context. This addresses #7562. (#7629)

This commit is contained in:
Eric Traut 2024-04-06 14:41:22 -07:00 committed by GitHub
parent 4149eb3eda
commit 628e246887
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 53 additions and 36 deletions

View File

@ -251,7 +251,7 @@ export function assignTypeToTypeVar(
if (!curWideTypeBound && !destType.details.isSynthesizedSelf) {
curWideTypeBound = destType.details.boundType;
}
const curNarrowTypeBound = curEntry?.narrowBound;
let curNarrowTypeBound = curEntry?.narrowBound;
let newNarrowTypeBound = curNarrowTypeBound;
let newWideTypeBound = curWideTypeBound;
const diagAddendum = diag ? new DiagnosticAddendum() : undefined;
@ -383,8 +383,15 @@ export function assignTypeToTypeVar(
if (!curNarrowTypeBound || isTypeSame(destType, curNarrowTypeBound)) {
// There was previously no narrow bound. We've now established one.
newNarrowTypeBound = adjSrcType;
} else if (!isTypeSame(curNarrowTypeBound, adjSrcType, {}, recursionCount)) {
if (isAnyOrUnknown(adjSrcType) && curEntry.tupleTypes) {
} else if (isTypeSame(curNarrowTypeBound, adjSrcType, {}, recursionCount)) {
// If this is an invariant context and there is currently no wide type bound
// established, use the "no literals" version of the narrow type bounds rather
// than a version that has literals.
if (!newWideTypeBound && isInvariant && curEntry?.narrowBoundNoLiterals) {
newNarrowTypeBound = curEntry.narrowBoundNoLiterals;
}
} else {
if (isAnyOrUnknown(adjSrcType) && curEntry?.tupleTypes) {
// Handle the tuple case specially. If Any or Unknown is assigned
// during the construction of a tuple, the resulting tuple type must
// be tuple[Any, ...], which is compatible with any tuple.
@ -474,6 +481,14 @@ export function assignTypeToTypeVar(
newNarrowTypeBound = widenedType;
} else {
const objectType = evaluator.getObjectType();
// If this is an invariant context and there is currently no wide type bound
// established, use the "no literals" version of the narrow type bounds rather
// than a version that has literals.
if (!newWideTypeBound && isInvariant && curEntry?.narrowBoundNoLiterals) {
curNarrowTypeBound = curEntry.narrowBoundNoLiterals;
}
const curSolvedNarrowTypeBound = applySolvedTypeVars(curNarrowTypeBound, typeVarContext);
// In some extreme edge cases, the narrow type bound can become
@ -548,6 +563,10 @@ export function assignTypeToTypeVar(
}
}
if (!newWideTypeBound && isInvariant) {
newWideTypeBound = newNarrowTypeBound;
}
// If there's a bound type, make sure the source is assignable to it.
if (destType.details.boundType) {
const updatedType = (newNarrowTypeBound || newWideTypeBound)!;

View File

@ -10,49 +10,40 @@ T = TypeVar("T")
S = TypeVar("S", covariant=True)
class ParentA:
...
class ParentA: ...
class ChildA(ParentA, Generic[T]):
def __init__(self, a: T) -> None:
...
def __init__(self, a: T) -> None: ...
def func1(arg1: ParentA, arg2: ParentA):
...
def func1(arg1: ParentA, arg2: ParentA): ...
func1(ChildA(1), ChildA(2))
class ParentB(Generic[T]):
...
class ParentB(Generic[T]): ...
class ChildB(ParentB[T]):
def __init__(self, a: T) -> None:
...
def __init__(self, a: T) -> None: ...
def func2(arg1: ParentB[T], arg2: ParentB[T]) -> T:
...
def func2(arg1: ParentB[T], arg2: ParentB[T]) -> T: ...
x1 = func2(ChildB(""), ChildB(1.2))
reveal_type(x1, expected_text="str | float")
# This should generate an error.
func2(ChildB(""), ChildB(1.2))
class ClassC(Generic[S]):
def __new__(cls, item: S) -> "ClassC[S]":
...
def __new__(cls, item: S) -> "ClassC[S]": ...
def __call__(self, obj: Any) -> S:
...
def __call__(self, obj: Any) -> S: ...
def func3(func1: Callable[..., T], func2: Callable[..., T]) -> T:
...
def func3(func1: Callable[..., T], func2: Callable[..., T]) -> T: ...
x2 = func3(ClassC(""), ClassC(1))
@ -61,18 +52,14 @@ reveal_type(x2, expected_text="str | int")
class ClassD(Generic[S]):
@overload
def __new__(cls, item: S, /) -> ClassD[S]:
...
def __new__(cls, item: S, /) -> ClassD[S]: ...
@overload
def __new__(cls, item: S, __item2: S, /) -> ClassD[tuple[S, S]]:
...
def __new__(cls, item: S, __item2: S, /) -> ClassD[tuple[S, S]]: ...
def __new__(cls, *items: Any) -> Any:
...
def __new__(cls, *items: Any) -> Any: ...
def __call__(self, obj: Any) -> Any:
...
def __call__(self, obj: Any) -> Any: ...
func3(ClassD(""), ClassD(""))

View File

@ -13,14 +13,13 @@ class NT1(NamedTuple, Generic[_T1]):
c: list[_T1]
reveal_type(NT1(3, 4, ["hi"]), expected_text="NT1[str | int]")
reveal_type(NT1(3, 4, ["hi"]), expected_text="NT1[int | str]")
reveal_type(NT1(3, 4, []), expected_text="NT1[int]")
reveal_type(NT1(3.4, 4, [1, 2]), expected_text="NT1[float]")
reveal_type(NT1(3.4, 4, [2j]), expected_text="NT1[complex]")
class NT2(NT1[str]):
...
class NT2(NT1[str]): ...
reveal_type(NT2("", 4, []), expected_text="NT2")

View File

@ -1,7 +1,7 @@
# This sample tests the TypeVar matching logic related to
# variadic type variables.
from typing import Any, Generic, TypeVar
from typing import Any, Generic, Literal, TypeVar, overload
from typing_extensions import ( # pyright: ignore[reportMissingModuleSource]
TypeVarTuple,
Unpack,
@ -85,3 +85,15 @@ def func2(p1: tuple[str, int], p2: list[str]):
def func3(x: Array[Unpack[_Xs]]) -> Array[Unpack[_Xs]]:
y: Array[Unpack[tuple[Any, ...]]] = x
return x
@overload
def func4(signal: Array[*_Xs], *args: *_Xs) -> None: ...
@overload
def func4(signal: str, *args: Any) -> None: ...
def func4(signal: Array[*_Xs] | str, *args: *_Xs) -> None: ...
def func5(a1: Array[Literal["a", "b"]], a2: Array[Literal["a"], Literal["b"]]):
func4(a1, "a")
func4(a2, "a", "b")

View File

@ -1674,7 +1674,7 @@ test('Constructor27', () => {
test('Constructor28', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructor28.py']);
TestUtils.validateResults(analysisResults, 0);
TestUtils.validateResults(analysisResults, 1);
});
test('Constructor29', () => {