Fixed regression that results in a false positive when a traditional type alias (not using PEP 695 syntax) defines a union and is then used as a second argument to an isinstance or issubclass call. This addresses #8302. (#8303)

This commit is contained in:
Eric Traut 2024-07-03 12:27:02 -07:00 committed by GitHub
parent 93ac52f0b2
commit d7c5d0691a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 39 additions and 12 deletions

View File

@ -4617,6 +4617,14 @@ export function createTypeEvaluator(
return type;
}
// Isinstance treats traditional (non-PEP 695) type aliases that are unions
// as tuples of classes rather than unions.
if ((flags & EvalFlags.IsinstanceParam) !== 0) {
if (isUnion(type) && type.typeAliasInfo && !type.typeAliasInfo.isPep695Syntax) {
return type;
}
}
return type.specialForm ?? type;
}
@ -12055,7 +12063,8 @@ export function createTypeEvaluator(
EvalFlags.NoParamSpec |
EvalFlags.NoTypeVarTuple |
EvalFlags.NoFinal |
EvalFlags.NoSpecialize
EvalFlags.NoSpecialize |
EvalFlags.IsinstanceParam
: EvalFlags.NoFinal | EvalFlags.NoSpecialize;
const exprTypeResult = getTypeOfExpression(
argParam.argument.valueExpression,

View File

@ -158,6 +158,10 @@ export const enum EvalFlags {
// other container classes).
StripTupleLiterals = 1 << 28,
// Interpret the expression using the specialized behaviors associated
// with the second argument to isinstance and issubclass calls.
IsinstanceParam = 1 << 29,
// Defaults used for evaluating the LHS of a call expression.
CallBaseDefaults = NoSpecialize,

View File

@ -1,7 +1,7 @@
# This sample exercises the type analyzer's isinstance type narrowing logic.
from types import NoneType
from typing import Generic, Protocol, Sized, TypeVar, Union, Any, runtime_checkable
from typing import Any, Generic, Protocol, Sized, TypeVar, Union, runtime_checkable
S = TypeVar("S")
T = TypeVar("T")
@ -151,11 +151,25 @@ def handler(node: Base1) -> Any:
reveal_type(node.value, expected_text="Sub1_1")
def func8(a: int | list[int] | dict[str, int] | None):
if isinstance(
a,
(str, (int, list, type(None))),
):
def func8a(a: int | list[int] | dict[str, int] | None):
if isinstance(a, (str, (int, list, type(None)))):
reveal_type(a, expected_text="int | list[int] | None")
else:
reveal_type(a, expected_text="dict[str, int]")
def func8b(a: int | list[int] | dict[str, int] | None):
if isinstance(a, str | int | list | type(None)):
reveal_type(a, expected_text="int | list[int] | None")
else:
reveal_type(a, expected_text="dict[str, int]")
TA1 = str | int | list | None
def func8c(a: int | list[int] | dict[str, int] | None):
if isinstance(a, TA1):
reveal_type(a, expected_text="int | list[int] | None")
else:
reveal_type(a, expected_text="dict[str, int]")
@ -200,12 +214,12 @@ def func11(x: Proto1):
reveal_type(x, expected_text="Proto1")
TA1 = list["TA2"] | dict[str, "TA2"]
TA2 = str | TA1
TA2 = list["TA3"] | dict[str, "TA3"]
TA3 = str | TA2
def func12(x: TA2) -> None:
def func12(x: TA3) -> None:
if isinstance(x, dict):
reveal_type(x, expected_text="dict[str, str | list[TA2] | dict[str, TA2]]")
reveal_type(x, expected_text="dict[str, str | list[TA3] | dict[str, TA3]]")
else:
reveal_type(x, expected_text="str | list[str | list[TA2] | dict[str, TA2]]")
reveal_type(x, expected_text="str | list[str | list[TA3] | dict[str, TA3]]")