Skip to content

fix: prevent false unreachable error when comparing generic callables#21188

Open
Bahtya wants to merge 2 commits intopython:masterfrom
Bahtya:fix/unreachable-generic-callable-comparison
Open

fix: prevent false unreachable error when comparing generic callables#21188
Bahtya wants to merge 2 commits intopython:masterfrom
Bahtya:fix/unreachable-generic-callable-comparison

Conversation

@Bahtya
Copy link
Copy Markdown

@Bahtya Bahtya commented Apr 8, 2026

Problem

Fixes #21182

When comparing a generic Callable parameter with a generic function using ==, mypy incorrectly reported "Statement is unreachable" with --warn-unreachable:

from typing import Callable, TypeVar

T = TypeVar("T")
S = TypeVar("S")

def identity(x: T) -> T:
    return x

def msg(cmp_property: Callable[[T], S]) -> None:
    if cmp_property == identity:
        return  # ERROR: Statement is unreachable [unreachable]

This is a regression — the code was accepted in earlier mypy versions.

Root Cause

In conditional_types with from_equality=True, mypy uses shallow_erase_type_for_equality to erase generic type parameters from the proposed type before checking overlap. However:

  1. shallow_erase_type_for_equality only handled Instance types, not CallableType. So generic callables like def [T] (x: T) -> T were never erased.

  2. Only the proposed type was erased, not the current type. When is_overlapping_types was called with an unerased generic callable as current_type (e.g., the identity function with .variables=(T,)), is_callable_compatible tried to unify its type variables against the other callable's free type variables and failed, concluding the types don't overlap.

At runtime, generic type parameters are erased, so identity and cmp_property can absolutely be equal — one could be passed as the other.

Solution

Two changes:

1. Extend shallow_erase_type_for_equality to CallableType (mypy/erasetype.py)

  • Non-type-object callables with .variables have their arg types and return type replaced with Any and variables cleared
  • Type objects (class constructors) are excluded to preserve their identity for type narrowing

2. Erase both sides in the overlap check (mypy/checker.py)

  • In conditional_types, apply shallow_erase_type_for_equality to both current_type and proposed_type before calling is_overlapping_types

Testing

  • ✅ Original reproduction case from [1.20 regression] unreachable regression with generic Callable #21182 passes (no more false unreachable)
  • ✅ All 146 existing unreachable tests pass
  • ✅ All 298 narrowing tests pass
  • ✅ All 73 equality tests pass
  • ✅ All 198 meet/overlap tests pass
  • ✅ All 10 erase tests pass
  • ✅ Comprehensive test cases: comparing two generic callables, generic vs non-generic, != operator — all work correctly

Bahtya and others added 2 commits April 9, 2026 02:56
When comparing a generic Callable parameter with a generic function
using ==, mypy incorrectly concluded the types could never overlap
and marked the if-body as unreachable.

Fix by extending shallow_erase_type_for_equality to handle CallableType
and using erased current_type in the equality overlap checks.

Fixes python#21182

Signed-off-by: bahtya <bahtyar153@qq.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Diff from mypy_primer, showing the effect of this PR on open source code:

rotki (https://github.com/rotki/rotki)
- rotkehlchen/tests/integration/test_premium.py:844: error: Statement is unreachable  [unreachable]
+ rotkehlchen/tests/integration/test_premium.py:845: error: Statement is unreachable  [unreachable]
- rotkehlchen/tests/integration/test_premium.py:858: error: Statement is unreachable  [unreachable]
+ rotkehlchen/tests/integration/test_premium.py:859: error: Statement is unreachable  [unreachable]

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[1.20 regression] unreachable regression with generic Callable

1 participant