Skip to content

Support TypeIs[Protocol] #18981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
makukha opened this issue Apr 27, 2025 · 2 comments
Closed

Support TypeIs[Protocol] #18981

makukha opened this issue Apr 27, 2025 · 2 comments
Labels

Comments

@makukha
Copy link

makukha commented Apr 27, 2025

Feature

Add support for Protocol as TypeIs parameter.

Pitch

Both class types and protocols support isinstance for type narrowing, but TypeIs seems to be not supported for protocols because of mypy error: Narrowed type "Proto" is not a subtype of input type "X".

Adding protocols support in TypeIs would:

  • Enable writing general type guard functions without subclassing,
  • Provide feature parity between classes (isinstance + TypeIs) and protocols (isinstance only).

Using only isinstance (with @runtime_checkable protocols) is not sufficient, because type guard functions can do more strict runtime checks, and provide TypeIs for static type checkers.

Consider the example below, where hinting X.y as mandatory requires subclassing X instead of declaring independent protocol:

from dataclasses import dataclass
from typing import Protocol, TypeIs, assert_type

@dataclass
class X:
    y: int | None

# Current behavior: have to subclass X

class XHasY(X):
    y: int

def has_y_(obj: X) -> TypeIs[XHasY]:
    return getattr(obj, 'y', None) is not None

x = X(1)
if has_y_(x):
    assert_type(x, XHasY)

# Desired behaviour:

class HasY(Protocol):
    y: int

def has_y(obj: X) -> TypeIs[HasY]:  # mypy error
    return getattr(obj, 'y', None) is not None

x = X(1)
if has_y(x):
    assert_type(x, HasY)

The desired behavior produces mypy error

demo.py:25: error: Narrowed type "HasY" is not a subtype of input type "X"  [narrowed-type-not-subtype]

If mypy error can be easily fixed, this feature could be useful addition to Python type narrowing.

@JelleZijlstra
Copy link
Member

That error is correct and in line with how TypeIs is specified: the narrowed type must be a subtype of the non-narrowed type. You can likely get your desired behavior by annotating the parameter to has_y as object.

Similar example:

from dataclasses import dataclass
from typing import Protocol, assert_type
from typing_extensions import TypeIs

class X:
    def f(self) -> int: return 0
class Y:
    def g(self) -> int: return 0

class HasF(Protocol):
    def f(self) -> int: ...

def has_f(obj: object) -> TypeIs[HasF]:
    return callable(getattr(obj, 'f', None))

def c(o: X | Y):
    if has_f(o):
        assert_type(o, X)

@JelleZijlstra JelleZijlstra closed this as not planned Won't fix, can't repro, duplicate, stale Apr 27, 2025
@makukha
Copy link
Author

makukha commented Apr 27, 2025

That error is correct and in line with how TypeIs is specified: the narrowed type must be a subtype of the non-narrowed type. You can likely get your desired behavior by annotating the parameter to has_y as object.

@JelleZijlstra thank you so much for the clarification! Using object is more than enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants