mirror of
https://github.com/microsoft/pyright.git
synced 2024-10-06 12:57:14 +03:00
Fixed bug in logic that validates subtyping relationships between TypeIs[T]
, TypeGuard[T]
and bool
when used in a return type of a callable. This addresses #8521. (#8523)
This commit is contained in:
parent
258e2754bd
commit
21bfd0f5bb
@ -8349,7 +8349,11 @@ export function createTypeEvaluator(
|
||||
);
|
||||
|
||||
if (
|
||||
!isTypeSame(assertedType, arg0TypeResult.type, { treatAnySameAsUnknown: true, ignorePseudoGeneric: true })
|
||||
!isTypeSame(assertedType, arg0TypeResult.type, {
|
||||
treatAnySameAsUnknown: true,
|
||||
ignorePseudoGeneric: true,
|
||||
ignoreTypeGuard: true,
|
||||
})
|
||||
) {
|
||||
const srcDestTypes = printSrcDestTypes(arg0TypeResult.type, assertedType, { expandTypeAlias: true });
|
||||
|
||||
@ -23389,6 +23393,29 @@ export function createTypeEvaluator(
|
||||
}
|
||||
}
|
||||
|
||||
// If the type is a bool created with a `TypeGuard` or `TypeIs`, it is
|
||||
// considered a subtype of `bool`.
|
||||
if (destType.priv.typeGuardType) {
|
||||
if (!srcType.priv.typeGuardType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TypeGuard and TypeIs are not subtypes of each other.
|
||||
if (!destType.priv.isStrictTypeGuard !== !srcType.priv.isStrictTypeGuard) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return assignType(
|
||||
destType.priv.typeGuardType,
|
||||
srcType.priv.typeGuardType,
|
||||
diag?.createAddendum(),
|
||||
/* destTypeVarContext */ undefined,
|
||||
/* srcTypeVarContext */ undefined,
|
||||
flags,
|
||||
recursionCount
|
||||
);
|
||||
}
|
||||
|
||||
for (let ancestorIndex = inheritanceChain.length - 1; ancestorIndex >= 0; ancestorIndex--) {
|
||||
const ancestorType = inheritanceChain[ancestorIndex];
|
||||
|
||||
|
@ -117,6 +117,7 @@ export interface TypeSameOptions {
|
||||
ignoreTypeFlags?: boolean;
|
||||
ignoreConditions?: boolean;
|
||||
ignoreTypedDictNarrowEntries?: boolean;
|
||||
ignoreTypeGuard?: boolean;
|
||||
treatAnySameAsUnknown?: boolean;
|
||||
}
|
||||
|
||||
@ -3211,6 +3212,24 @@ export function isTypeSame(type1: Type, type2: Type, options: TypeSameOptions =
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!options.ignoreTypeGuard) {
|
||||
// If one is a type guard and the other is not, they are not the same.
|
||||
if (!type1.priv.typeGuardType !== !classType2.priv.typeGuardType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type1.priv.typeGuardType && classType2.priv.typeGuardType) {
|
||||
// TypeIs and TypeGuard are not the equivalent.
|
||||
if (!type1.priv.isStrictTypeGuard !== !classType2.priv.isStrictTypeGuard) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isTypeSame(type1.priv.typeGuardType, classType2.priv.typeGuardType, options, recursionCount)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.ignorePseudoGeneric || !ClassType.isPseudoGenericClass(type1)) {
|
||||
// Make sure the type args match.
|
||||
if (type1.priv.tupleTypeArguments && classType2.priv.tupleTypeArguments) {
|
||||
@ -3340,7 +3359,12 @@ export function isTypeSame(type1: Type, type2: Type, options: TypeSameOptions =
|
||||
if (
|
||||
!return1Type ||
|
||||
!return2Type ||
|
||||
!isTypeSame(return1Type, return2Type, { ...options, ignoreTypeFlags: false }, recursionCount)
|
||||
!isTypeSame(
|
||||
return1Type,
|
||||
return2Type,
|
||||
{ ...options, ignoreTypeFlags: false, ignoreTypeGuard: false },
|
||||
recursionCount
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
# This sample tests overload matching in cases where the match
|
||||
# is ambiguous due to an Any or Unknown argument.
|
||||
|
||||
# pyright: reportMissingModuleSource=false
|
||||
|
||||
from typing import Any, Generic, Literal, TypeVar, overload
|
||||
from typing_extensions import ( # pyright: ignore[reportMissingModuleSource]
|
||||
LiteralString,
|
||||
)
|
||||
from typing_extensions import LiteralString, TypeIs
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
@ -338,3 +338,22 @@ def func19(a: ClassC, b: list, c: Any):
|
||||
my_list2: list[int] = []
|
||||
v1 = a.method1("hi", my_list2)
|
||||
reveal_type(v1, expected_text="float")
|
||||
|
||||
|
||||
@overload
|
||||
def overload11(x: str) -> TypeIs[str]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def overload11(x: int) -> TypeIs[int]:
|
||||
...
|
||||
|
||||
|
||||
def overload11(x: Any) -> Any:
|
||||
return True
|
||||
|
||||
|
||||
def func20(val: Any):
|
||||
if overload11(val):
|
||||
reveal_type(val, expected_text="Any")
|
||||
|
@ -1,7 +1,10 @@
|
||||
# This sample tests the TypeIs form.
|
||||
|
||||
# pyright: reportMissingModuleSource=false
|
||||
|
||||
from typing import Any, Callable, Collection, Literal, Mapping, Sequence, TypeVar, Union
|
||||
from typing_extensions import TypeIs # pyright: ignore[reportMissingModuleSource]
|
||||
|
||||
from typing_extensions import TypeIs
|
||||
|
||||
|
||||
def is_str1(val: Union[str, int]) -> TypeIs[str]:
|
||||
|
36
packages/pyright-internal/src/tests/samples/typeIs2.py
Normal file
36
packages/pyright-internal/src/tests/samples/typeIs2.py
Normal file
@ -0,0 +1,36 @@
|
||||
# This sample tests the subtyping relationships between TypeIs, TypeGuard,
|
||||
# and bool.
|
||||
|
||||
# pyright: reportMissingModuleSource=false
|
||||
|
||||
from typing import Callable
|
||||
|
||||
from typing_extensions import TypeGuard, TypeIs
|
||||
|
||||
TypeIsInt = Callable[..., TypeIs[int]]
|
||||
TypeIsFloat = Callable[..., TypeIs[float]]
|
||||
BoolReturn = Callable[..., bool]
|
||||
TypeGuardInt = Callable[..., TypeGuard[int]]
|
||||
|
||||
|
||||
def func1(v1: TypeIsInt, v2: TypeIsFloat, v3: BoolReturn, v4: TypeGuardInt):
|
||||
a1: TypeIsInt = v1
|
||||
a2: TypeIsInt = v2 # Should generate an error
|
||||
a3: TypeIsInt = v3 # Should generate an error
|
||||
a4: TypeIsInt = v4 # Should generate an error
|
||||
|
||||
b1: TypeIsFloat = v1 # Should generate an error
|
||||
b2: TypeIsFloat = v2
|
||||
b3: TypeIsFloat = v3 # Should generate an error
|
||||
b4: TypeIsFloat = v4 # Should generate an error
|
||||
|
||||
c1: BoolReturn = v1
|
||||
c2: BoolReturn = v2
|
||||
c3: BoolReturn = v3
|
||||
c4: BoolReturn = v4
|
||||
|
||||
d1: TypeGuardInt = v1 # Should generate an error
|
||||
d2: TypeGuardInt = v2 # Should generate an error
|
||||
d3: TypeGuardInt = v3 # Should generate an error
|
||||
d4: TypeGuardInt = v4
|
||||
|
@ -128,6 +128,11 @@ test('TypeIs1', () => {
|
||||
TestUtils.validateResults(analysisResults, 2);
|
||||
});
|
||||
|
||||
test('TypeIs2', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIs2.py']);
|
||||
TestUtils.validateResults(analysisResults, 9);
|
||||
});
|
||||
|
||||
test('Never1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['never1.py']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user