mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-21 05:07:18 +03:00
Fixed bug that caused type narrowing for assignments not to be applied when the source of the assignment was a call to a constructor. Improved type narrowing for assignments when destination is declared with one or more "Any" type arguments. Improved bidirectional type inference for list and dict types when destination type is a union that contains one or more specialized list or dict types. Improved support for generic recursive type aliases. Improved bidirectional type inference for list and dict types when destination type is a wider protocol type (like Iterable, Mapping, Sequence, etc.).
This commit is contained in:
parent
99ed73aef9
commit
e0fcd6da43
File diff suppressed because it is too large
Load Diff
@ -370,10 +370,16 @@ export function transformPossibleRecursiveTypeAlias(type: Type | undefined): Typ
|
||||
export function transformPossibleRecursiveTypeAlias(type: Type | undefined): Type | undefined {
|
||||
if (type) {
|
||||
if (isTypeVar(type) && type.details.recursiveTypeAliasName && type.details.boundType) {
|
||||
if (TypeBase.isInstance(type)) {
|
||||
return convertToInstance(type.details.boundType);
|
||||
const unspecializedType = TypeBase.isInstance(type)
|
||||
? convertToInstance(type.details.boundType)
|
||||
: type.details.boundType;
|
||||
|
||||
if (!type.typeAliasInfo?.typeArguments || !type.details.recursiveTypeParameters) {
|
||||
return unspecializedType;
|
||||
}
|
||||
return type.details.boundType;
|
||||
|
||||
const typeVarMap = buildTypeVarMap(type.details.recursiveTypeParameters, type.typeAliasInfo.typeArguments);
|
||||
return specializeType(unspecializedType, typeVarMap);
|
||||
}
|
||||
}
|
||||
|
||||
@ -643,6 +649,26 @@ export function specializeType(
|
||||
}
|
||||
|
||||
if (isTypeVar(type)) {
|
||||
// Handle recursive type aliases specially. In particular,
|
||||
// we need to specialize type arguments for generic recursive
|
||||
// type aliases.
|
||||
if (type.details.recursiveTypeAliasName) {
|
||||
if (!type.typeAliasInfo?.typeArguments) {
|
||||
return type;
|
||||
}
|
||||
|
||||
const typeArgs = type.typeAliasInfo.typeArguments.map((typeArg) =>
|
||||
specializeType(typeArg, typeVarMap, /* makeConcrete */ false, recursionLevel + 1)
|
||||
);
|
||||
|
||||
return TypeBase.cloneForTypeAlias(
|
||||
type,
|
||||
type.typeAliasInfo.aliasName,
|
||||
type.typeAliasInfo.typeParameters,
|
||||
typeArgs
|
||||
);
|
||||
}
|
||||
|
||||
if (typeVarMap) {
|
||||
const replacementType = typeVarMap.getTypeVar(type);
|
||||
if (replacementType) {
|
||||
@ -1712,8 +1738,18 @@ export function requiresSpecialization(type: Type, recursionCount = 0): boolean
|
||||
}
|
||||
|
||||
case TypeCategory.TypeVar: {
|
||||
// If this is a recursive type alias, don't treat it like other TypeVars.
|
||||
return type.details.recursiveTypeAliasName === undefined;
|
||||
// Most TypeVar types need to be specialized.
|
||||
if (!type.details.recursiveTypeAliasName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If this is a recursive type alias, it may need to be specialized
|
||||
// if it has generic type arguments.
|
||||
if (type.typeAliasInfo?.typeArguments) {
|
||||
return type.typeAliasInfo.typeArguments.some((typeArg) =>
|
||||
requiresSpecialization(typeArg, recursionCount + 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1272,6 +1272,9 @@ export interface TypeVarDetails {
|
||||
|
||||
// Used for recursive type aliases.
|
||||
recursiveTypeAliasName?: string;
|
||||
|
||||
// Type parameters for a recursive type alias.
|
||||
recursiveTypeParameters?: TypeVarType[];
|
||||
}
|
||||
|
||||
export interface TypeVarType extends TypeBase {
|
||||
|
@ -353,7 +353,13 @@ test('TypeNarrowing17', () => {
|
||||
test('TypeNarrowing18', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowing18.py']);
|
||||
|
||||
validateResults(analysisResults, 0, 0, 10);
|
||||
validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('TypeNarrowing19', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowing19.py']);
|
||||
|
||||
validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('CircularBaseClass', () => {
|
||||
@ -1125,7 +1131,7 @@ test('TypeAlias6', () => {
|
||||
test('TypeAlias7', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeAlias7.py']);
|
||||
|
||||
validateResults(analysisResults, 2);
|
||||
validateResults(analysisResults, 3);
|
||||
});
|
||||
|
||||
test('TypeAlias8', () => {
|
||||
@ -2376,6 +2382,12 @@ test('Constructor1', () => {
|
||||
validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('Constructor2', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructor2.py']);
|
||||
|
||||
validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('Constructor3', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructor3.py']);
|
||||
|
||||
|
@ -11,4 +11,4 @@ def func_or(a: Optional[Dict[str, Any]]):
|
||||
|
||||
def func_and():
|
||||
a: Optional[Dict[str, Any]] = True and dict()
|
||||
t1: Literal["Dict[str, Any]"] = reveal_type(a)
|
||||
t1: Literal["dict[str, Any]"] = reveal_type(a)
|
||||
|
@ -3,16 +3,17 @@
|
||||
|
||||
from typing import List, TypeVar, Union
|
||||
|
||||
_T2 = TypeVar("_T2", str, int)
|
||||
_T1 = TypeVar("_T1", str, int)
|
||||
_T2 = TypeVar("_T2")
|
||||
|
||||
GenericTypeAlias1 = List[Union["GenericTypeAlias1", _T2]]
|
||||
GenericTypeAlias1 = List[Union["GenericTypeAlias1[_T1]", _T1]]
|
||||
|
||||
SpecializedTypeAlias1 = GenericTypeAlias1[str]
|
||||
|
||||
a1: SpecializedTypeAlias1 = ["hi", ["hi", "hi"]]
|
||||
|
||||
# This should generate an error because int doesn't match the
|
||||
# constraint of the TypeVar _T2.
|
||||
# constraint of the TypeVar _T1.
|
||||
SpecializedClass2 = GenericTypeAlias1[float]
|
||||
|
||||
b1: GenericTypeAlias1[str] = ["hi", "bye", [""], [["hi"]]]
|
||||
@ -20,3 +21,12 @@ b1: GenericTypeAlias1[str] = ["hi", "bye", [""], [["hi"]]]
|
||||
# This should generate an error.
|
||||
b2: GenericTypeAlias1[str] = ["hi", [2.4]]
|
||||
|
||||
|
||||
GenericTypeAlias2 = List[Union["GenericTypeAlias2[_T1, _T2]", _T1, _T2]]
|
||||
|
||||
c2: GenericTypeAlias2[str, int] = [[3, ["hi"]], "hi"]
|
||||
|
||||
c3: GenericTypeAlias2[str, float] = [[3, ["hi", 3.4, [3.4]]], "hi"]
|
||||
|
||||
# This should generate an error because a float is a type mismatch.
|
||||
c4: GenericTypeAlias2[str, int] = [[3, ["hi", 3, [3.4]]], "hi"]
|
||||
|
@ -0,0 +1,19 @@
|
||||
# This sample tests type narrowing for assignments
|
||||
# where the source contains Unknown or Any type
|
||||
# arguments.
|
||||
|
||||
from typing import Any, Dict, Literal
|
||||
|
||||
|
||||
def func1(struct: Dict[Any, Any]):
|
||||
a1: Dict[str, Any] = struct
|
||||
t1: Literal["Dict[str, Any]"] = reveal_type(a1)
|
||||
|
||||
|
||||
def func2(struct: Any):
|
||||
a1: Dict[Any, str] = struct
|
||||
t1: Literal["Dict[Any, str]"] = reveal_type(a1)
|
||||
|
||||
if isinstance(struct, Dict):
|
||||
a2: Dict[str, Any] = struct
|
||||
t2: Literal["Dict[str, Any]"] = reveal_type(a2)
|
Loading…
Reference in New Issue
Block a user