You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Dict-literal arguments don't try all members of a union of dict[...] types #4913
ERROR sandbox.py:9:23-24: `Literal[1]` is not assignable to dict value type `list[int]` [[bad-assignment](https://pyrefly.org/en/docs/error-kinds/#bad-assignment)]ERROR sandbox.py:9:31-32: `Literal[2]` is not assignable to dict value type `list[int]` [[bad-assignment](https://pyrefly.org/en/docs/error-kinds/#bad-assignment)]
I think this should pass the dict[K, S1] part?
If we write it as
d: dict[str, int] = {"a": 1, "b": 2}
reveal_type(Box(d)) # same dict, via a pre-typed variable
I'd like to work on this. I traced it to dict-literal hint decomposition:
The trigger is a union member whose key and value are both type variables (dict[K, S1]); the bound on K doesn't matter. dict[K, int] and dict[str, S1] already work, and an unbounded dict[str, list[int]] | dict[KU, S1] fails the same way.
In dict_items_infer (and the DictComp path), the decompose closure returns None when both the key and value hints are unresolved. infer_with_decomposed_hint treats None as "this member isn't a dict", so dict[K, S1] is skipped before inference runs. We then fall back to dict[str, list[int]], which matched with errors.
Sets don't hit this because set elements use best-effort hint coercion, so the erroring fallback is never recorded.
Plan: have decompose_dict return None only when the hint can't be a dict. A dict hint with placeholder vars should still be decomposed even when the key and value hints are unresolved, since inferring against it is what pins those vars. Uninformative var-free members such as object keep being skipped, so a more informative sibling like dict[str, float] still wins. I'll add regression tests for the issue example (both union orders), the dict-comprehension path, and the unchanged cases.
Activity
Divyansh151005 commentedon Oct 28, 2026
#claim
I'd like to work on this. I traced it to dict-literal hint decomposition:
dict[K, S1]); the bound onKdoesn't matter.dict[K, int]anddict[str, S1]already work, and an unboundeddict[str, list[int]] | dict[KU, S1]fails the same way.dict_items_infer(and theDictComppath), the decompose closure returnsNonewhen both the key and value hints are unresolved.infer_with_decomposed_hinttreatsNoneas "this member isn't a dict", sodict[K, S1]is skipped before inference runs. We then fall back todict[str, list[int]], which matched with errors.Plan: have
decompose_dictreturnNoneonly when the hint can't be a dict. A dict hint with placeholder vars should still be decomposed even when the key and value hints are unresolved, since inferring against it is what pins those vars. Uninformative var-free members such asobjectkeep being skipped, so a more informative sibling likedict[str, float]still wins. I'll add regression tests for the issue example (both union orders), the dict-comprehension path, and the unchanged cases.github-actions commentedon Oct 28, 2026
@Divyansh151005 you've claimed this issue — it's now assigned to you. Thanks for picking it up! 🎉