2

I have the following example.

from typing import Union, TypeVar, Dict, Sequence

IdentifierSymbol = TypeVar("IdentifierSymbol", str, int)

def f(ids: Sequence[IdentifierSymbol]) -> Dict[int, IdentifierSymbol]:
    return dict(zip(range(len(ids)), ids))


def g(ids: Union[Sequence[int], Sequence[str]]) -> int:
    x = f(ids)
    return 1

PyLance (I guess using mypy inside) complains with the following in the line x=f(ids):

Argument of type "Sequence[int] | Sequence[str]" cannot be assigned to parameter "ids" of type "Sequence[IdentifierSymbol@f]" in function "f"
  Type "Sequence[int] | Sequence[str]" cannot be assigned to type "Sequence[IdentifierSymbol@f]"
    TypeVar "_T_co@Sequence" is covariant
      Type "str" is not compatible with constrained type "int" PylancereportGeneralTypeIssues

I am not sure how to interpret this, but it sounds like the problem is that int is not compatible with str. But why is this an issue in this case? I have either a sequence of strs or a sequence of ints, where does the compatibility between int and str come in?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
SMeznaric
  • 430
  • 3
  • 13
  • Note that mypy fails with a different error here: ``Value of type variable "IdentifierSymbol" of "f" cannot be "object"``. IIRC MyPy resolves a variable that can be inhabited by two types via the supertype instead of Union. – MisterMiyagi Feb 24 '21 at 14:17
  • 1
    Does this answer your question? [Why does mypy infer the common base type instead of the union of all contained types?](https://stackoverflow.com/questions/57452652/why-does-mypy-infer-the-common-base-type-instead-of-the-union-of-all-contained-t) – MisterMiyagi Feb 24 '21 at 14:18
  • @MisterMiyagi Huh I guess PyLance has its own type checker then. Surprised about that. It could well be that Union[Sequence[int], Sequence[str]] resolves to Sequence[object] or something like that and then doesn't find anything. – SMeznaric Feb 24 '21 at 14:41

1 Answers1

4

As for mypy, then in the current version (0.800), this does not work due to the peculiarities of deducing type variables (TypeVar): when its type is deduced from multiple types (including used in Union and Sequence), it is reduced to their closest common base type. In this case, there will be an error when this common base type is not included in the TypeVar constraint list.

This behavior may change in future versions.

For example:

class A: ...

class B(A): ...

class C(A): ...

T = TypeVar("T")

def foo(var1: T, var2: T) -> T:
    return var1
    
reveal_type(foo(B(), C()))  # Revealed type is 'main.A*'

def bar(var: T) -> T:
     return var

def baz(var: Union[A, B]):
    reveal_type(bar(var))  # Revealed type is 'main.A*'

def qux(var: Sequence[T]) -> T:
    return var[0]

def quux(var: Tuple[A, B]):
    reveal_type(qux(var))  # Revealed type is 'main.A*'

def quuux(var: Union[Sequence[A], Sequence[B]]):
    reveal_type(qux(var))  # Revealed type is 'main.A*'

You need to use other type annotations, for example make g function also generic. Perhaps someone will suggest a more elegant solution.

def g(ids: Sequence[IdentifierSymbol]) -> int:
    x = f(ids)
    return 1
alex_noname
  • 26,459
  • 5
  • 69
  • 86
  • Oh that's interesting, so basically my Union[Sequence[int], Sequence[str]] basically becomes Sequence[object] or some such? Bizarre. It looks like this also happens if I overload the initial function instead of using TypeVar, so I guess Union is doing some weird stuff? Sadly PyLance doesn't like it if I use TypeVar only once in the function signature. But it does make the type warning in the function body go away. – SMeznaric Feb 24 '21 at 14:38
  • "Sadly PyLance doesn't like it if I use TypeVar only once in the function signature." This rule is somewhat obscure for me. I would disable it by `# type: ignore` – alex_noname Feb 25 '21 at 09:49