Return-context TypeVar inference silently resolves to Any where argument-only inference errors
維護者通常 1 天內回覆
還沒有人認領這個 Issue。
評估
研究方向
從 issue 中的獨立重現開始,將直接指派與巢狀在有註解的 Sequence[Result[Any]] 回傳值中的呼叫進行比較。追蹤 mypy 的雙向推論和 TypeVar 約束處理,以判定為什麼預期型別會抑制引數衝突。完成的標準是兩個相同的呼叫都回報不相容的 TypeVar 推論,而不是靜默解析為 Result[Any]。
由索引模型根據 Issue 內容生成。
描述
Bug Report
When a generic function solves a TypeVar from its first positional argument and propagates it (invariantly) to *args, mypy correctly rejects a call where the arguments disagree — unless that same call sits in a position with an expected type of Sequence[SomeGeneric[Any]] (e.g. returned inside a list literal from an annotated function).
In that context, mypy silently resolves the TypeVar to Any instead of reporting the conflict, using the exact same fallback type it otherwise reports as an error one call-shape away.
To Reproduce
from typing import Any, Generic, Sequence, TypeVar
T = TypeVar("T", bound="Base")
class Base:
pass
class A(Base):
pass
class B(Base):
pass
class Box(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value
class Result(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value
def combine(first: Box[T], *rest: Box[T]) -> Result[T]:
return Result(first.value)
mismatched = combine(Box(A()), Box(B())) # EXPECTED: errors here...
reveal_type(mismatched) # ... Undefined behavior (?): but Revealed type is "Result[Any]"
def scenario() -> Sequence[Result[Any]]:
return [reveal_type(combine(Box(A()), Box(B())))] # BUG: 0 error
A and B are unrelated siblings of Base — neither is a subtype of the other, so no valid T exists for either call. Both calls are identical; only the surrounding context differs.
Expected Behavior
Both calls should be rejected the same way, since neither actually has a valid T. At minimum, the second call shouldn't pass silently just because it happens to sit inside a Sequence[Result[Any]]-typed return.
Actual Behavior
main.py:34: error: Cannot infer value of type parameter "T" of "combine" [misc]
main.py:35: note: Revealed type is "Result[Any]"
main.py:39: note: Revealed type is "Result[Any]"
Found 1 error in 1 file (checked 1 source file)
- Line 34 (bare assignment, no expected-type context): mypy detects the conflict and errors — but still assigns a concrete type to the LHS rather than
Never/an error type.reveal_typeon line 35 shows it fell back toResult[Any]. - Line 39 (identical call, nested in a list literal returned from a function annotated
-> Sequence[Result[Any]]): mypy uses that outer expected type as extra context while solvingT, lands onT=Any, and the exact same argument conflict as line 34 goes completely unreported.reveal_type(transparent to inference, so it doesn't perturb the context) confirms it's the sameResult[Any]fallback — just silent this time.
So mypy's own error-recovery type for the unsolvable case (Any) is exactly the value that lets the same construct through unchecked one call shape later, purely because of where the call happens to be textually nested.
Cross-checked against pyright/Pylance on the same standalone call (mismatched = combine(Box(A()), Box(B()))), basic mode:
error: Argument of type "Box[B]" cannot be assigned to parameter "rest" of type "Box[T@combine]" in function "combine"
"Box[B]" is incompatible with "Box[A]"
Type parameter "T@Box" is invariant, but "B" is not the same as "A"
information: Type of "mismatched" is "Result[A]"
pyright anchors T on the first argument (T=A) for the bare-assignment case and correctly flags the incompatible rest argument with a precise, per-argument diagnostic — better than mypy's vaguer "cannot infer" here. But on the second case — the actual one this report is about — pyright has the exact same hole: no error, and it resolves the call to Result[Any] too. So this isn't a mypy-only defect — it looks like a blind spot shared by both implementations specifically when an invariant TypeVar's resolution is handed off to an Any-containing expected-type context, even though both tools correctly catch the identical conflict when no such context is present.
Your Environment
- Mypy version used: 2.3.1
- Mypy command-line flags:
--strict(also reproduces without it) - Mypy configuration options from
mypy.ini(and other config files): none — reproduces from a baremypy repro.py - Python version used: 3.14
Synthesis
mismatched = combine(Box(A()), Box(B())) # mypy errors: cannot infer T
return [combine(Box(A()), Box(B()))] # identical call, no error, inside `-> Sequence[Result[Any]]`
T is solved invariantly from two occurrences of Box[T] with incompatible types. Alone,
mypy correctly rejects the call. Nested in a list literal flowing into an annotated
Sequence[Result[Any]] return, the identical call is silently accepted. Pyright shows the
same behavior.
Why
Both checkers do bidirectional ("local") type inference: a type is synthesized
bottom-up from an expression, but an expected type from the surrounding context (here,
the annotated return type) also flows top-down to help pick type arguments unification
alone can't resolve — the lineage of Pierce & Turner's local type inference (background
context, not a verified quote). That channel exists to disambiguate, not to
overrule a conflict synthesis already found — but here it does exactly that: the
arguments alone already produce an unsatisfiable constraint on T, yet the expected-type
channel reverses the verdict for the identical call.
The likely mechanism: gradual typing's consistency relation (~, connecting Any to
every type) is reflexive and symmetric but explicitly not transitive (Siek & Taha,
2006, confirmed against source) — Any ~ A and Any ~ B do not license A ~ B. The
symptom matches an inference pass that lets each occurrence of T unify against the
Any-containing expected type independently, without cross-checking that the two results
agree — i.e. treating a non-transitive relation as if it composed. (This mechanism is a
diagnostic hypothesis from the symptom, not confirmed by reading either checker's
constraint-solving source.)
Symptom-level evidence for the same conclusion: the identical call gets two different
verdicts based purely on syntactic nesting, which means the checker's merge of
"constraints from arguments" and "constraints from the expected type" isn't confluent —
and both mypy and Pyright hit the exact same hole, suggesting a structural gap in how
bidirectional inference commonly combines with gradual typing's Any, not a mypy-specific
slip.
Bottom line
A concrete instance of known friction between bidirectional/local type inference and
gradual typing's non-transitive Any relation — both major Python type checkers reverse an
otherwise-correct rejection purely based on surrounding syntactic context.
Playground
https://mypy-play.net/?mypy=latest&python=3.14&gist=23e464afa5e3dc1e6d4b7ee873419937
- 主要語言
- Python
- 星號
- 20.7k
- 分支
- 3.3k
- 平均合併
- 1 天 16 小時
- 30 天內合併 PR
- 96
環境準備
- 沒有 Dockerfile 或 Docker Compose 檔案
- 有 Pull Request 範本
- 閱讀貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
python/mypy 的其他 Issue
-
bug
難度 2/5 1-3 小時 新手友好度 78/100
維護者通常 1 天內回覆
-
`stubgen --inspect-mode` crashes with `AttributeError on PEP 604 union syntax (X | Y)` *(mypy 2.1.0)*可能已有人在做 @ayushdoesdev 於 68 天前認領。 未關閉bug
難度 2/5 1-3 小時 新手友好度 76/100
維護者通常 1 天內回覆
-
Inconsistent description of `strict_optional`可能已有人在做 @Joosboy 於 84 天前認領。 未關閉documentation
難度 2/5 1-3 小時 新手友好度 72/100
維護者通常 1 天內回覆
-
Cannot set `output` in `mypy.ini`可能重新可做 關聯的 PR 已關閉且未合併。 未關閉bug topic-configuration topic-error-reporting
難度 2/5 1-3 小時 新手友好度 68/100
維護者通常 1 天內回覆
-
bug topic-attrs
難度 2/5 1-3 小時 新手友好度 62/100
維護者通常 1 天內回覆
相似的 Issue
-
難度 1/5 1 小時以內 新手友好度 60/100
521xueweihan/HelloGitHub#3924 ·
-
難度 2/5 1-3 小時 新手友好度 67/100
wilbowes/EchoMuse#869 · 1 則留言 ·
維護者通常 1 天內回覆
-
難度 1/5 1 小時以內 新手友好度 85/100
-
namespace operations
難度 1/5 1 小時以內 新手友好度 72/100
EclipseFdn/open-vsx.org#14043 ·
維護者通常 1 天內回覆
-
test: TestServeUntilStale races the server's close against the client's sendall (BrokenPipeError under load)可能已有人在做 @evoludigit 今天認領。 未關閉
難度 1/5 1 小時以內 新手友好度 89/100
維護者通常 1 天內回覆