mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
Added support for argument dictionary unpacking when the type of the unpacked object supports the SupportsKeysAndGetItem protocol. Previously, the object needed to be an explicit subclass of Mapping. This addresses https://github.com/microsoft/pyright/issues/4826.
This commit is contained in:
parent
02e3e96522
commit
903fd3cee3
@ -435,23 +435,42 @@ function assignClassToProtocolInternal(
|
||||
});
|
||||
|
||||
// If the dest protocol has type parameters, make sure the source type arguments match.
|
||||
if (typesAreConsistent && destType.details.typeParameters.length > 0 && destType.typeArguments) {
|
||||
if (typesAreConsistent && destType.details.typeParameters.length > 0) {
|
||||
// Create a specialized version of the protocol defined by the dest and
|
||||
// make sure the resulting type args can be assigned.
|
||||
const specializedDestProtocol = applySolvedTypeVars(genericDestType, genericDestTypeVarContext) as ClassType;
|
||||
|
||||
if (
|
||||
!evaluator.verifyTypeArgumentsAssignable(
|
||||
destType,
|
||||
specializedDestProtocol,
|
||||
diag,
|
||||
destTypeVarContext,
|
||||
srcTypeVarContext,
|
||||
flags,
|
||||
recursionCount
|
||||
)
|
||||
if (destType.typeArguments) {
|
||||
if (
|
||||
!evaluator.verifyTypeArgumentsAssignable(
|
||||
destType,
|
||||
specializedDestProtocol,
|
||||
diag,
|
||||
destTypeVarContext,
|
||||
srcTypeVarContext,
|
||||
flags,
|
||||
recursionCount
|
||||
)
|
||||
) {
|
||||
typesAreConsistent = false;
|
||||
}
|
||||
} else if (
|
||||
destTypeVarContext &&
|
||||
destType.details.typeParameters.length > 0 &&
|
||||
specializedDestProtocol.typeArguments &&
|
||||
!destTypeVarContext.isLocked()
|
||||
) {
|
||||
typesAreConsistent = false;
|
||||
// Populate the typeVar map with type arguments of the source.
|
||||
const srcTypeArgs = specializedDestProtocol.typeArguments;
|
||||
for (let i = 0; i < destType.details.typeParameters.length; i++) {
|
||||
const typeArgType = i < srcTypeArgs.length ? srcTypeArgs[i] : UnknownType.create();
|
||||
destTypeVarContext.setTypeVarType(
|
||||
destType.details.typeParameters[i],
|
||||
/* narrowBound */ undefined,
|
||||
/* narrowBoundNoLiterals */ undefined,
|
||||
typeArgType
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10131,7 +10131,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
// Now consume any keyword arguments.
|
||||
while (argIndex < argList.length) {
|
||||
if (argList[argIndex].argumentCategory === ArgumentCategory.UnpackedDictionary) {
|
||||
// Verify that the type used in this expression is a Mapping[str, T].
|
||||
// Verify that the type used in this expression is a SupportsKeysAndGetItem[str, T].
|
||||
const argType = getTypeOfArgument(argList[argIndex]).type;
|
||||
if (isAnyOrUnknown(argType)) {
|
||||
unpackedDictionaryArgType = argType;
|
||||
@ -10221,7 +10221,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const mappingType = getTypingType(errorNode, 'Mapping');
|
||||
let mappingType = getTypeshedType(errorNode, 'SupportsKeysAndGetItem');
|
||||
if (!mappingType) {
|
||||
mappingType = getTypingType(errorNode, 'Mapping');
|
||||
}
|
||||
const strObjType = getBuiltInObject(errorNode, 'str');
|
||||
|
||||
if (
|
||||
|
52
packages/pyright-internal/src/tests/samples/call9.py
Normal file
52
packages/pyright-internal/src/tests/samples/call9.py
Normal file
@ -0,0 +1,52 @@
|
||||
# This sample tests the case where a dictionary expansion operator
|
||||
# is used in a call. The type checker should verify that the
|
||||
# type supports a SupportsKeyAndGetItem protocol.
|
||||
|
||||
from typing import Any, Generic, TypeVar, Mapping, KeysView
|
||||
|
||||
|
||||
class MyMapping(Mapping[str, Any]):
|
||||
...
|
||||
|
||||
|
||||
class StrRecord:
|
||||
def __getitem__(self, __key: str) -> str:
|
||||
...
|
||||
|
||||
def keys(self) -> KeysView[str]:
|
||||
...
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class GenericRecord(Generic[T]):
|
||||
def __getitem__(self, __key: str) -> T:
|
||||
...
|
||||
|
||||
def keys(self) -> KeysView[T]:
|
||||
...
|
||||
|
||||
|
||||
def func1(**kwargs: Any) -> None:
|
||||
...
|
||||
|
||||
|
||||
m = MyMapping()
|
||||
r = StrRecord()
|
||||
|
||||
|
||||
def func2(
|
||||
m: MyMapping,
|
||||
r: StrRecord,
|
||||
g: GenericRecord[str],
|
||||
mrg: MyMapping | StrRecord | GenericRecord[str],
|
||||
bad: GenericRecord[bytes],
|
||||
):
|
||||
func1(**m)
|
||||
func1(**r)
|
||||
func1(**g)
|
||||
func1(**mrg)
|
||||
|
||||
# This should generate an error.
|
||||
func1(**bad)
|
@ -717,6 +717,12 @@ test('Call8', () => {
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('Call9', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['call9.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 1);
|
||||
});
|
||||
|
||||
test('Function1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['function1.py']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user