Skip to content

TypedDict built with ** unpacking isn't inferred inside a tuple when the declared return type is a union of tuples #5184

Description

@john-parton

Describe the Bug

When a dict literal built with ** unpacking is returned inside a tuple, Pyrefly checks it against the expected TypedDict correctly if the declared return type is a single tuple type. If the declared return type is a union of tuple types, the dict literal falls back to a plain dict[str, ...] and the return is rejected.

from typing import Literal, TypedDict


class A(TypedDict):
    x: int


class B(A):
    token: str | None


def single(a: A) -> tuple[Literal[200], B]:
    return 200, {**a, "token": None}  # OK


def union_no_unpack(a: A) -> tuple[Literal[200], B] | tuple[Literal[400], str]:
    return 200, {"x": a["x"], "token": None}  # OK


def union_with_unpack(a: A) -> tuple[Literal[200], B] | tuple[Literal[400], str]:
    return 200, {**a, "token": None}  # error
ERROR Returned type `tuple[Literal[200], dict[str, object | None]]` is not assignable to declared return type `tuple[Literal[200], B] | tuple[Literal[400], str]` [bad-return]

Only the combination fails. Each of these passes:

  • ** unpacking with a single tuple return type;
  • a union of tuples with a dict literal that has no **;
  • a bare return {**a, "token": None} against -> B.

Pyright and basedpyright accept all three functions.

This comes up constantly in web frameworks that return (status, body) tuples. For example, a django-ninja view annotated -> tuple[Literal[200], OkJson] | tuple[Literal[400], ErrorJson] that extends a service result with one extra key. The current workaround is binding the dict to an annotated local just to give Pyrefly the hint, which other checkers don't need.

It looks related to #4913 (dict-literal arguments don't try every member of a union) and #4404 (** unpacking defeats TypedDict inference in dict literals). It may share a root cause with either, but neither covers this case.

Version: pyrefly 1.3.2

Sandbox Link

No response

(Only applicable for extension issues) IDE Information

No response


I used Claude Opus 5.5 to help find this behavior and draft this report; the repro was verified locally against pyrefly 1.3.2, Pyright and basedpyright.

Activity

  1. Sufiyan-MSA commented on Oct 10, 2026

    @Sufiyan-MSA

    Please assign me to this issue. I'd like to work on it!

  2. added theissue type on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions