mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
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:
parent
cd483dcb90
commit
3875288287
@ -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(
|
||||
|
@ -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}
|
||||
|
Loading…
Reference in New Issue
Block a user