mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
Fixed recent regression that incorrectly narrowed the type of kwargs
when used in a type guard of the form if "a" in kwargs
. This addresses #7731. (#7747)
This commit is contained in:
parent
419a8f41c1
commit
73d894c1be
@ -45,6 +45,7 @@ import {
|
||||
isModule,
|
||||
isNever,
|
||||
isOverloadedFunction,
|
||||
isParamSpec,
|
||||
isSameWithoutLiteralValue,
|
||||
isTypeSame,
|
||||
isTypeVar,
|
||||
@ -2197,46 +2198,56 @@ function narrowTypeForTypedDictKey(
|
||||
literalKey: ClassType,
|
||||
isPositiveTest: boolean
|
||||
): Type {
|
||||
const narrowedType = evaluator.mapSubtypesExpandTypeVars(referenceType, /* options */ undefined, (subtype) => {
|
||||
if (isClassInstance(subtype) && ClassType.isTypedDictClass(subtype)) {
|
||||
const entries = getTypedDictMembersForClass(evaluator, subtype, /* allowNarrowed */ true);
|
||||
const tdEntry = entries.knownItems.get(literalKey.literalValue as string) ?? entries.extraItems;
|
||||
|
||||
if (isPositiveTest) {
|
||||
if (!tdEntry) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If the entry is currently not required and not marked provided, we can mark
|
||||
// it as provided after this guard expression confirms it is.
|
||||
if (tdEntry.isRequired || tdEntry.isProvided) {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
const newNarrowedEntriesMap = new Map<string, TypedDictEntry>(subtype.typedDictNarrowedEntries ?? []);
|
||||
|
||||
// Add the new entry.
|
||||
newNarrowedEntriesMap.set(literalKey.literalValue as string, {
|
||||
valueType: tdEntry.valueType,
|
||||
isReadOnly: tdEntry.isReadOnly,
|
||||
isRequired: false,
|
||||
isProvided: true,
|
||||
});
|
||||
|
||||
// Clone the TypedDict object with the new entries.
|
||||
return ClassType.cloneAsInstance(
|
||||
ClassType.cloneForNarrowedTypedDictEntries(
|
||||
ClassType.cloneAsInstantiable(subtype),
|
||||
newNarrowedEntriesMap
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return tdEntry !== undefined && (tdEntry.isRequired || tdEntry.isProvided) ? undefined : subtype;
|
||||
const narrowedType = evaluator.mapSubtypesExpandTypeVars(
|
||||
referenceType,
|
||||
/* options */ undefined,
|
||||
(subtype, unexpandedSubtype) => {
|
||||
if (isParamSpec(unexpandedSubtype)) {
|
||||
return unexpandedSubtype;
|
||||
}
|
||||
}
|
||||
|
||||
return subtype;
|
||||
});
|
||||
if (isClassInstance(subtype) && ClassType.isTypedDictClass(subtype)) {
|
||||
const entries = getTypedDictMembersForClass(evaluator, subtype, /* allowNarrowed */ true);
|
||||
const tdEntry = entries.knownItems.get(literalKey.literalValue as string) ?? entries.extraItems;
|
||||
|
||||
if (isPositiveTest) {
|
||||
if (!tdEntry) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If the entry is currently not required and not marked provided, we can mark
|
||||
// it as provided after this guard expression confirms it is.
|
||||
if (tdEntry.isRequired || tdEntry.isProvided) {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
const newNarrowedEntriesMap = new Map<string, TypedDictEntry>(
|
||||
subtype.typedDictNarrowedEntries ?? []
|
||||
);
|
||||
|
||||
// Add the new entry.
|
||||
newNarrowedEntriesMap.set(literalKey.literalValue as string, {
|
||||
valueType: tdEntry.valueType,
|
||||
isReadOnly: tdEntry.isReadOnly,
|
||||
isRequired: false,
|
||||
isProvided: true,
|
||||
});
|
||||
|
||||
// Clone the TypedDict object with the new entries.
|
||||
return ClassType.cloneAsInstance(
|
||||
ClassType.cloneForNarrowedTypedDictEntries(
|
||||
ClassType.cloneAsInstantiable(subtype),
|
||||
newNarrowedEntriesMap
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return tdEntry !== undefined && (tdEntry.isRequired || tdEntry.isProvided) ? undefined : subtype;
|
||||
}
|
||||
}
|
||||
|
||||
return subtype;
|
||||
}
|
||||
);
|
||||
|
||||
return narrowedType;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
# This sample tests type narrowing for the "in" operator.
|
||||
|
||||
from typing import Literal, TypeVar, TypedDict
|
||||
from typing import Callable, Generic, Literal, ParamSpec, TypeVar, TypedDict
|
||||
import random
|
||||
|
||||
|
||||
@ -160,3 +160,17 @@ def func12(v: T1):
|
||||
reveal_type(v, expected_text="TD1*")
|
||||
else:
|
||||
reveal_type(v, expected_text="TD2*")
|
||||
|
||||
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
class Container(Generic[P]):
|
||||
def __init__(self, func: Callable[P, str]) -> None:
|
||||
self.func = func
|
||||
|
||||
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> str:
|
||||
if "data" in kwargs:
|
||||
raise ValueError("data is not allowed in kwargs")
|
||||
|
||||
return self.func(*args, **kwargs)
|
||||
|
Loading…
Reference in New Issue
Block a user