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:
Eric Traut 2020-10-04 23:29:35 -07:00
parent 99ed73aef9
commit e0fcd6da43
7 changed files with 629 additions and 378 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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)
);
}
}
}

View File

@ -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 {

View File

@ -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']);

View File

@ -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)

View File

@ -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"]

View File

@ -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)