Skip to content
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

Treat widening type variables consistently #18456

Open
A5rocks opened this issue Jan 13, 2025 · 2 comments
Open

Treat widening type variables consistently #18456

A5rocks opened this issue Jan 13, 2025 · 2 comments
Labels
bug mypy got something wrong

Comments

@A5rocks
Copy link
Contributor

A5rocks commented Jan 13, 2025

Bug Report

Currently, mypy allows widening type variables without an error, then errors (sometimes, see below) at usage time. This seems backwards to me. I'm opening this based on a minimization from @sterliakov in #18451.

To Reproduce

There's several interesting snippets:

import typing

IntT = typing.TypeVar("IntT", bound=int)
StrT = typing.TypeVar("StrT", bound=str)
AnyT = typing.TypeVar("AnyT", bound=typing.Any)

class IntC(typing.Generic[IntT]): ...
class StrC(typing.Generic[StrT]): ...

AnyC = IntC[AnyT] | StrC[AnyT]

def run(_: AnyC[StrT]) -> StrT: ...  # E: Type argument "StrT" of "IntC" must be a subtype of "int"

In this case, I would expect an error on the AnyC type alias definition, and maybe at usage time. What about other methods of widening type variables?

import typing

IntT = typing.TypeVar("IntT", bound=int)
StrT = typing.TypeVar("StrT", bound=str)
AnyT = typing.TypeVar("AnyT", bound=typing.Any)

class IntC(typing.Generic[IntT]): ...

class AnyC(IntC[AnyT]):  # ! no error
    pass

def f(x: StrT) -> StrT:
    y: AnyC[StrT]  # !? no error
    return x

This version doesn't error at all! What I would expect is the behavior achieved by using object plus a class to widen:

import typing

IntT = typing.TypeVar("IntT", bound=int)
StrT = typing.TypeVar("StrT", bound=str)
AnyT = typing.TypeVar("AnyT", bound=object)

class IntC(typing.Generic[IntT]): ...
class StrC(typing.Generic[StrT]): ...

class AnyC1(IntC[AnyT]):  # E: Type argument "AnyT" of "IntC" must be a subtype of "int"  
    pass

AnyC2 = IntC[AnyT] | StrC[AnyT]  # ! no error

def f(x: StrT) -> StrT:
    y1: AnyC1[StrT]  # no error, this is fine
    y2: AnyC2[StrT]  # E: Type argument "StrT" of "IntC" must be a subtype of "int"
    return x

Expected Behavior

Consistency.

Actual Behavior

To summarize the above

method of widening bound=object bound=Any
type alias usage error usage error
class definition error no errors (!)

Your Environment

Everything was double checked in mypy playground.

  • Mypy version used: v1.14.1
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): N/A
  • Python version used: 3.12
@A5rocks A5rocks added the bug mypy got something wrong label Jan 13, 2025
@sterliakov
Copy link
Contributor

Perhaps this question is worth a python/typing discussion? It is rather typechecker-agnostic and concerns the meaning of annotations, not a specific implementation.

@A5rocks
Copy link
Contributor Author

A5rocks commented Jan 14, 2025

I think a good enough solution is to ensure that classes error at definition time and type aliases at usage time, based on the level of formality described in #18451. I think the most egregious issue here is that bound=Any typevar that prevents any errors.

However, changing is_subtype to is_proper_subtype in the relevant place leads to rejecting gradual types. I'm not sure the right approach here... I don't like current behavior but at least across mypy's test cases it's typical -- e.g. Callable[..., Any] instead of Callable[..., object] in a typevar bound (nooo!! why!). Maybe status quo is indeed the best.

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

No branches or pull requests

2 participants