Skip to content

Commit aebb127

Browse files
authored
Fixed a bug that results in a false positive error when calling a function with an unpacked TypedDict with extra_items when additional keyword args are passed. This addresses #10352. (#10357)
1 parent 7bcff6b commit aebb127

6 files changed

Lines changed: 29 additions & 15 deletions

File tree

packages/pyright-internal/src/analyzer/checker.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,7 +2685,10 @@ export class Checker extends ParseTreeWalker {
26852685
return false;
26862686
}
26872687

2688-
let flags = AssignTypeFlags.SkipReturnTypeCheck | AssignTypeFlags.OverloadOverlap;
2688+
let flags =
2689+
AssignTypeFlags.SkipReturnTypeCheck |
2690+
AssignTypeFlags.OverloadOverlap |
2691+
AssignTypeFlags.DisallowExtraKwargsForTd;
26892692
if (partialOverlap) {
26902693
flags |= AssignTypeFlags.PartialOverloadOverlap;
26912694
}
@@ -2745,7 +2748,10 @@ export class Checker extends ParseTreeWalker {
27452748
implBound,
27462749
diag,
27472750
constraints,
2748-
AssignTypeFlags.SkipReturnTypeCheck | AssignTypeFlags.Contravariant | AssignTypeFlags.SkipSelfClsTypeCheck
2751+
AssignTypeFlags.SkipReturnTypeCheck |
2752+
AssignTypeFlags.Contravariant |
2753+
AssignTypeFlags.SkipSelfClsTypeCheck |
2754+
AssignTypeFlags.DisallowExtraKwargsForTd
27492755
);
27502756

27512757
// Now check the return types.

packages/pyright-internal/src/analyzer/parameterUtils.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,16 +277,23 @@ export function getParamListDetails(type: FunctionType, options?: ParamListDetai
277277
);
278278
});
279279

280-
const extraItemsType = paramType.shared.typedDictEntries.extraItems?.valueType ?? AnyType.create();
280+
const extraItemsType = paramType.shared.typedDictEntries.extraItems?.valueType;
281+
282+
let addKwargsForExtraItems: boolean;
283+
if (extraItemsType) {
284+
addKwargsForExtraItems = !isNever(extraItemsType);
285+
} else {
286+
addKwargsForExtraItems = !options?.disallowExtraKwargsForTd;
287+
}
281288

282289
// Unless the TypedDict is completely closed (i.e. is not allowed to
283290
// have any extra items), add a virtual **kwargs parameter to represent
284291
// any additional items.
285-
if (!isNever(extraItemsType) && !options?.disallowExtraKwargsForTd) {
292+
if (addKwargsForExtraItems) {
286293
addVirtualParam(
287294
FunctionParam.create(
288295
ParamCategory.KwargsDict,
289-
extraItemsType,
296+
extraItemsType ?? AnyType.create(),
290297
FunctionParamFlags.TypeDeclared,
291298
'kwargs'
292299
),

packages/pyright-internal/src/analyzer/typeEvaluator.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26342,8 +26342,12 @@ export function createTypeEvaluator(
2634226342
srcType = FunctionType.cloneRemoveParamSpecArgsKwargs(srcType);
2634326343
}
2634426344

26345-
const destParamDetails = getParamListDetails(destType);
26346-
const srcParamDetails = getParamListDetails(srcType);
26345+
const destParamDetails = getParamListDetails(destType, {
26346+
disallowExtraKwargsForTd: (flags & AssignTypeFlags.DisallowExtraKwargsForTd) !== 0,
26347+
});
26348+
const srcParamDetails = getParamListDetails(srcType, {
26349+
disallowExtraKwargsForTd: (flags & AssignTypeFlags.DisallowExtraKwargsForTd) !== 0,
26350+
});
2634726351

2634826352
adjustSourceParamDetailsForDestVariadic(
2634926353
isContra ? destParamDetails : srcParamDetails,

packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,10 @@ export const enum AssignTypeFlags {
622622
// Normally a protocol class object cannot be used as a source type. This
623623
// option overrides this behavior.
624624
AllowProtocolClassSource = 1 << 16,
625+
626+
// When assigning callables, should a kwargs with an unpacked TypedDict
627+
// disallow additional named arguments if it does not have extraItems?
628+
DisallowExtraKwargsForTd = 1 << 17,
625629
}
626630

627631
export interface TypeEvaluator {

packages/pyright-internal/src/tests/samples/typedDictClosed2.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,6 @@ def func2(**kwargs: Unpack[Movie2]) -> None: ...
2424

2525
func2(name="")
2626

27-
# It's not clear whether this is allowed from the spec,
28-
# but based on a reading of PEP 692, extra (non-specified)
29-
# items should not be allowed as explicit keyword arguments.
30-
# This is consistent with the original TypedDict PEP
31-
# that disallows extra items to be present in a literal dict
32-
# expression that is assigned to a TypedDict type.
33-
# We'll therefore assume this should generate an error.
3427
func2(name="", foo=1)
3528

3629
# This should generate an error.

packages/pyright-internal/src/tests/typeEvaluator5.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ test('TypedDictClosed2', () => {
365365
configOptions.diagnosticRuleSet.enableExperimentalFeatures = true;
366366

367367
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typedDictClosed2.py'], configOptions);
368-
TestUtils.validateResults(analysisResults, 5);
368+
TestUtils.validateResults(analysisResults, 4);
369369
});
370370

371371
test('TypedDictClosed3', () => {

0 commit comments

Comments
 (0)