Skip to content

Fix type inference for TypedDict unpacking in dictionary displays - #11611

Open
Henry Su (hsusul) wants to merge 2 commits into
microsoft:mainfrom
hsusul:fix/typeddict-unpacking-dict-display
Open

Fix type inference for TypedDict unpacking in dictionary displays#11611
Henry Su (hsusul) wants to merge 2 commits into
microsoft:mainfrom
hsusul:fix/typeddict-unpacking-dict-display

Conversation

@hsusul

Copy link
Copy Markdown
Contributor

Summary of Changes

When a TypedDict instance is unpacked inside a dictionary display (e.g., {**td} or {**td1, **td2}) without an expected TypedDict target type context (or under strictDictionaryInference), Pyright previously inferred the value type as Unknown.

Cause

In getKeyAndValueTypesFromDictionary, when evaluating ParseNodeType.DictionaryExpandEntry for a TypedDict expression:

  1. knownItems were only added to keyTypes and valueTypes if entry.isRequired || entry.isProvided. However, outside an expected TypedDict context (where isProvided is false), isRequired or isProvided checks excluded optional fields from key/value inference.
  2. When expectedTypedDictEntries was undefined, getObjectType() (object) was unconditionally appended to valueTypes as a fallback extraItems?.valueType. When combined with other types without strictDictionaryInference, combineTypes fell back to Unknown or broadened to object.

Fix

Updated getKeyAndValueTypesFromDictionary in typeEvaluator.ts:

  • Include all knownItems when expectedTypedDictEntries is undefined (!expectedTypedDictEntries || entry.isRequired || entry.isProvided).
  • Only push extraItems to keyTypes/valueTypes if tdEntries.extraItems is explicitly present, avoiding pushing an unnecessary object fallback type.

Test Coverage

  • Added sample test packages/pyright-internal/src/tests/samples/typedDict28.py covering unpacking homogeneous, heterogeneous, optional, and multiple TypedDict instances into dictionary displays.
  • Added unit test TypedDict28 to packages/pyright-internal/src/tests/typeEvaluator7.test.ts.

Validation Results

  • npm run check: Passed (Syncpack lint, ESLint, Prettier)
  • npm run typecheck: Passed (tsc --noEmit across all workspace packages)
  • npx jest src/tests/typeEvaluator7.test.ts: 168 tests passed
  • git diff --check: Passed cleanly with no whitespace errors

@StellaHuang95

Stella Huang (StellaHuang95) commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

🔒 Automated review in progress — Stella Huang (@StellaHuang95) is auto-reviewing this PR.

});

if (!expectedTypedDictEntries) {
if (tdEntries.extraItems) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue · Please address or respond

Changing this guard makes the block run when an expected TypedDict target is present. Closed TypedDicts populate extraItems with Never, so unpacking one into an expected TypedDict now contributes a non-literal str key that assignToTypedDict rejects, producing a new assignment error. Retain the context gate while removing only the fallback: if (!expectedTypedDictEntries && tdEntries.extraItems) { ... }.


def test_multiple(td1: HomogeneousTD, td2: HeterogeneousTD, td3: OptionalTD):
res4 = {**td1, **td2, **td3}
reveal_type(res4, expected_text="dict[str, int | str | float]")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning · Non-blocking recommendation

The added cases do not cover the changed extraItems path or the expected-TypedDict context that currently regresses. Add coverage for unpacking a closed TypedDict and a PEP 728 extra_items TypedDict into a typed target, plus a non-strict inference case to cover the stated default-mode behavior.

@StellaHuang95

Copy link
Copy Markdown
Collaborator

Please update the corresponding Pylance evaluator and add product-level regression coverage; it still has the original optional-field guard and object fallback, so Pylance users would continue to see incorrect inference.

@StellaHuang95 Stella Huang (StellaHuang95) added the review-auto:changes-requested Automated review: posted blocking findings to address. label Aug 10, 2026
…losed/extra_items TypedDict coverage & fourslash test
@hsusul

Copy link
Copy Markdown
Contributor Author

Thank you for the detailed review Stella Huang (@StellaHuang95)! I have updated the PR with all requested changes:

  1. extraItems Guard in typeEvaluator.ts: Retained the !expectedTypedDictEntries guard (if (!expectedTypedDictEntries && tdEntries.extraItems)) so that closed TypedDicts with extraItems (Never) unpacked into an expected TypedDict target context do not push a non-literal str key or trigger assignToTypedDict assignment errors.
  2. Expanded Test Coverage:
    • Added test coverage in typedDict28.py for unpacking closed TypedDicts (closed=True) and PEP 728 extra_items TypedDicts into typed target TypedDicts.
    • Added typedDict28.py test cases under default mode non-strict inference.
    • Added typedDict29.py sample test specifically verifying strictDictionaryInference = true behavior for heterogeneous, optional, multiple, and extra_items TypedDict unpacking into dictionary displays.
  3. Product-Level / Language-Server Regression Coverage:
    • Added product-level fourslash regression test hover.typedDictUnpacking.dictDisplay.fourslash.ts to verify hover and type inference across language server / Pylance integration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@StellaHuang95 Stella Huang (StellaHuang95) added review-auto:approved Automated review: no blocking findings (approval posted). and removed review-auto:changes-requested Automated review: posted blocking findings to address. labels Aug 10, 2026

@rchiodo Rich Chiodo (rchiodo) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@rchiodo Rich Chiodo (rchiodo) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-auto:approved Automated review: no blocking findings (approval posted).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants