Skip to content

Does replacing a type variable make a subclass incompatible? #11605

Closed
@A5rocks

Description

@A5rocks

Bug Report

Replace a type variable with another one (by specializing and then another generic). Is this type safe?

To Reproduce

Here's some pretty minimal code:

import typing

class A:
  pass

class B:
  pass

T = typing.TypeVar("T", bound=A)
V = typing.TypeVar("V", bound=B)

_WhateverT = typing.TypeVar("_WhateverT", bound="Whatever[typing.Any]")
_WhateverPartTwoT = typing.TypeVar("_WhateverPartTwoT", bound="WhateverPartTwo[typing.Any]")

class Whatever(typing.Generic[T]):
  def something(self: _WhateverT) -> _WhateverT:
    return self
  def foo(self) -> T:
    pass

# here we specialize `Whatever` (only allow `A` to be passed in), and then generalize once again.
class WhateverPartTwo(Whatever[A], typing.Generic[V]):
  def something(self: _WhateverPartTwoT) -> _WhateverPartTwoT:
    return self
  def blah(self) -> V:
    pass

Expected Behavior

I'm not sure, honestly. My gut instinct is that mypy is right here and LSP is being violated... but I can't think of any sequence of functions to run to get type unsafety without resorting to typing.Any which brings its own unsafety. Additionally, pyright does not mark this as an error (though that just may be me failing to use pyright correctly...).

Actual Behavior

main.py:20: error: Return type "WhateverPartTwo[V]" of "something" incompatible with return type "WhateverPartTwo[A]" in supertype "Whatever"
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: latest (0.910) and master
  • Mypy command-line flags: none (mypy-playground)
  • Mypy configuration options from mypy.ini (and other config files): none (mypy-playground)
  • Python version used: 3.10
  • Operating system and version: windows and whatever mypy-playground is using

in simpler (?) terms

I have Thing<A: upper bound 1>, specialize it to be SpecializedThing, then generic something in it to get CoolSpecializedThing<B: upper bound 2>. Is that OK, does CoolSpecializedThing work?

But confounding the question more, I have a something<T: Thing<A>>(self: T) -> T and I reimplement that on CoolSpecializedThing to get something<T: CoolSpecializedThing<B>>(self: T) -> T.

Normally with subclassing, this is fine:

import typing

AT = typing.TypeVar("AT", bound="A")
BT = typing.TypeVar("BT", bound="B")

class A:
  def whatever(self: AT) -> AT: ...

class B:
  def whatever(self: BT) -> BT: ...

And since CoolSpecializedThing is a subclass of Thing this should work right??

And yet the type variables have different upper bounds so like..... arghhhh. (I can't replace Thing<whatever> with CoolSpecializedThing<whatever> as the upper bounds of the type variables are different)

(I'll be honest, I'm mainly opening this issue here because I'm curious about this, I'm certain I can work around this one way or another).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions