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:
Eric Traut 2023-03-22 14:50:17 -06:00
parent 02e3e96522
commit 903fd3cee3
4 changed files with 94 additions and 14 deletions

View File

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

View File

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

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

View File

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