Fixed a bug that results in a false positive error when bidirectional type inference is used for a dictionary comprehension when the expected type is a union. This addresses #7741. (#7751)

This commit is contained in:
Eric Traut 2024-04-23 00:01:47 -07:00 committed by GitHub
parent cd483dcb90
commit 3875288287
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 95 additions and 44 deletions

View File

@ -13200,6 +13200,7 @@ export function createTypeEvaluator(
const keyTypes: TypeResultWithNode[] = [];
const valueTypes: TypeResultWithNode[] = [];
let isIncomplete = false;
let typeErrors = false;
// Handle TypedDict's as a special case.
if (ClassType.isTypedDictClass(concreteExpectedType)) {
@ -13210,22 +13211,26 @@ export function createTypeEvaluator(
const expectedTypedDictEntries = getTypedDictMembersForClass(evaluatorInterface, concreteExpectedType);
// Infer the key and value types if possible.
if (
getKeyAndValueTypesFromDictionary(
node,
keyTypes,
valueTypes,
/* forceStrictInference */ true,
/* isValueTypeInvariant */ true,
/* expectedKeyType */ undefined,
/* expectedValueType */ undefined,
expectedTypedDictEntries,
expectedDiagAddendum
)
) {
const keyValueTypeResult = getKeyAndValueTypesFromDictionary(
node,
keyTypes,
valueTypes,
/* forceStrictInference */ true,
/* isValueTypeInvariant */ true,
/* expectedKeyType */ undefined,
/* expectedValueType */ undefined,
expectedTypedDictEntries,
expectedDiagAddendum
);
if (keyValueTypeResult.isIncomplete) {
isIncomplete = true;
}
if (keyValueTypeResult.typeErrors) {
typeErrors = true;
}
const resultTypedDict = assignToTypedDict(
evaluatorInterface,
concreteExpectedType,
@ -13297,22 +13302,26 @@ export function createTypeEvaluator(
}
// Infer the key and value types if possible.
if (
getKeyAndValueTypesFromDictionary(
node,
keyTypes,
valueTypes,
/* forceStrictInference */ true,
isValueTypeInvariant,
expectedKeyType,
expectedValueType,
undefined,
expectedDiagAddendum
)
) {
const keyValueResult = getKeyAndValueTypesFromDictionary(
node,
keyTypes,
valueTypes,
/* forceStrictInference */ true,
isValueTypeInvariant,
expectedKeyType,
expectedValueType,
undefined,
expectedDiagAddendum
);
if (keyValueResult.isIncomplete) {
isIncomplete = true;
}
if (keyValueResult.typeErrors) {
typeErrors = true;
}
const specializedKeyType = inferTypeArgFromExpectedEntryType(
makeInferenceContext(expectedKeyType),
keyTypes.map((result) => result.type),
@ -13328,7 +13337,7 @@ export function createTypeEvaluator(
}
const type = getBuiltInObject(node, 'dict', [specializedKeyType, specializedValueType]);
return { type, isIncomplete };
return { type, isIncomplete, typeErrors };
}
// Attempts to infer the type of a dictionary statement. If hasExpectedType
@ -13343,20 +13352,25 @@ export function createTypeEvaluator(
let isEmptyContainer = false;
let isIncomplete = false;
let typeErrors = false;
// Infer the key and value types if possible.
if (
getKeyAndValueTypesFromDictionary(
node,
keyTypeResults,
valueTypeResults,
/* forceStrictInference */ hasExpectedType,
/* isValueTypeInvariant */ false
)
) {
const keyValueResult = getKeyAndValueTypesFromDictionary(
node,
keyTypeResults,
valueTypeResults,
/* forceStrictInference */ hasExpectedType,
/* isValueTypeInvariant */ false
);
if (keyValueResult.isIncomplete) {
isIncomplete = true;
}
if (keyValueResult.typeErrors) {
typeErrors = true;
}
// Strip any literal values.
const keyTypes = keyTypeResults.map((t) => stripLiteralValue(t.type));
const valueTypes = valueTypeResults.map((t) => stripLiteralValue(t.type));
@ -13399,7 +13413,7 @@ export function createTypeEvaluator(
}
}
return { type, isIncomplete };
return { type, isIncomplete, typeErrors };
}
function getKeyAndValueTypesFromDictionary(
@ -13412,8 +13426,9 @@ export function createTypeEvaluator(
expectedValueType?: Type,
expectedTypedDictEntries?: TypedDictEntries,
expectedDiagAddendum?: DiagnosticAddendum
): boolean {
): TypeResult {
let isIncomplete = false;
let typeErrors = false;
// Infer the key and value types if possible.
node.entries.forEach((entryNode, index) => {
@ -13432,6 +13447,10 @@ export function createTypeEvaluator(
isIncomplete = true;
}
if (keyTypeResult.typeErrors) {
typeErrors = true;
}
const keyType = keyTypeResult.type;
if (!keyTypeResult.isIncomplete && !keyTypeResult.typeErrors) {
@ -13493,6 +13512,10 @@ export function createTypeEvaluator(
isIncomplete = true;
}
if (valueTypeResult.typeErrors) {
typeErrors = true;
}
if (forceStrictInference || index < maxEntriesToUseForInference) {
// If an existing key has the same literal type, delete the previous
// key since we're overwriting it here.
@ -13546,6 +13569,10 @@ export function createTypeEvaluator(
isIncomplete = true;
}
if (unexpandedTypeResult.typeErrors) {
typeErrors = true;
}
const unexpandedType = unexpandedTypeResult.type;
if (isAnyOrUnknown(unexpandedType)) {
addUnknown = false;
@ -13630,6 +13657,10 @@ export function createTypeEvaluator(
isIncomplete = true;
}
if (dictEntryTypeResult.typeErrors) {
typeErrors = true;
}
// The result should be a tuple.
if (isClassInstance(dictEntryType) && isTupleClass(dictEntryType)) {
const typeArgs = dictEntryType.tupleTypeArguments?.map((t) => t.type);
@ -13651,7 +13682,7 @@ export function createTypeEvaluator(
}
});
return isIncomplete;
return { type: AnyType.create(), isIncomplete, typeErrors };
}
function getTypeOfListOrSet(

View File

@ -1,14 +1,34 @@
# This sample tests dictionary inference logic.
from typing import Mapping
from typing import Mapping, TypeAlias, TypeVar
T = TypeVar("T")
def f(mapping: Mapping[str | bytes, int]):
def func1(mapping: Mapping[str | bytes, int]):
return mapping
f({"x": 1})
f({b"x": 1})
func1({"x": 1})
func1({b"x": 1})
# This should generate an error.
f({3: 1})
func1({3: 1})
RecursiveMapping: TypeAlias = (
int | Mapping[int, "RecursiveMapping"] | Mapping[str, "RecursiveMapping"]
)
class HasName:
name: str | None
def func2(x: T | None) -> T:
assert x is not None
return x
def func3(v: list[HasName]) -> RecursiveMapping:
return {func2(x.name): 1 for x in v}