It looks like you've hit the nail on the head here. From what I understand of the typing module, I don't believe this is possible currently, unless someone can actually prove otherwise. The reason is that the Tuple[x, ...] syntax, which represents a variadic tuple, also known as a variable-length tuple, actually represents a homogeneous collection of elements, similar to a list which is annotated as List[x] for example.
So ostensibly, this limitation also carries over to the case when we define T as a TypeVar, and want to annotate the return type as Tuple[T, ...]. This unfortunately still means the return type is homogenous, and unfortunately it can't vary the element types depending on what the type of T is here, even though it is exactly what we desire in this case.
For example, here's a mostly thorough test I did to confirm:
from typing import Generic, TypeVar, Tuple
I = TypeVar("I")
O = TypeVar("O")
class Base(Generic[I, O]):
def __init__(self, input: I) -> None:
self.input = input
def convert(self) -> O:
...
class ToStr(Base[int, str]): ...
class ToInt(Base[str, int]): ...
def convert(*c: Base[I, O]) -> Tuple[O, ...]: # I don't known how to annotate the return type
return tuple(c.convert() for c in c)
s = convert(ToStr(1), ToInt("2")) # type should be (str, int)
# s[1]. # suggestions still show `str` as first one, which is wrong here
As you can see, that doesn't work out. The TypeVar O is bound to the type of O for the first element, str in this case.
I think, that unfortunately the closest you can get for type hinting purposes is using the below explicit syntax:
tup = ToStr(1).convert(), ToInt('2').convert()
This type checks correctly, at least in Pycharm. The IDE correctly infers the type as tuple[str, int] in this case. The downside here of course is that it's a bit unwieldy, and the syntax is a bit overly verbose. But it's the best solution I can think of at the moment, that at least does appear to work as desired.