skip to Main Content

VSCode Pylance v2023.9.20 with strict checking is giving me type error on first function call, but not the second. Only difference is that in first argument is wrapped in list.

def listTupleFunc(arg: list[tuple[str, tuple[str, ...]]]) -> None:
    pass
listTuple = [('x', ('a', 'b'))]
listTupleFunc(listTuple) # Error here

def tupleOnlyFunc(arg: tuple[str, tuple[str, ...]]) -> None:
    pass
tupleOnly = ('x', ('a', 'b'))
tupleOnlyFunc(tupleOnly) # OK

And the error message is:

Argument of type "list[tuple[Literal['x'], tuple[Literal['a'], Literal['b']]]]" cannot be assigned to parameter "arg" of type "list[tuple[str, tuple[str, ...]]]" in function "listTupleFunc"
  "list[tuple[Literal['x'], tuple[Literal['a'], Literal['b']]]]" is incompatible with "list[tuple[str, tuple[str, ...]]]"
    Type parameter "_T@list" is invariant, but "tuple[Literal['x'], tuple[Literal['a'], Literal['b']]]" is not the same as "tuple[str, tuple[str, ...]]"

If I change so that some of the literals are formatted, suprisingly the error disappears:

# These work OK somehow
listTuple = [(f'x', ('a', f'b'))]  
listTuple = [(f'x', (f'a', 'b'))]  
# But these don't
listTuple = [('x', (f'a', f'b'))]
listTuple = [(f'x', ('a', 'b'))]

Is this a bug in Pylance or am I missing something obvious? How do I get rid of the error?

2

Answers


  1. You can cast the listTuple to required type.

    def listTupleFunc(arg: list[tuple[str, tuple[str, ...]]]) -> None:
        pass
    listTuple: list[tuple[str, tuple[str, ...]]] = [('x', ('a', 'b'))]
    listTupleFunc(listTuple) # No error!
    
    def tupleOnlyFunc(arg: tuple[str, tuple[str, ...]]) -> None:
        pass
    tupleOnly = ('x', ('a', 'b'))
    tupleOnlyFunc(tupleOnly) # OK
    

    This removes the error. PyLance expects these hard coded literals as being incompetable with the general definition you used.

    Login or Signup to reply.
  2. So, the error message is quite descriptive. When you do just:

    listTuple = [('x', ('a', 'b'))]
    

    Then the type inference rules of pyright infer:

    list[tuple[Literal['x'], tuple[Literal['a'], Literal['b']]]]
    

    Now, you may argue, "but tuple[Literal['x'], tuple[Literal['a'], Literal['b']] is a subtype of tuple[str, tuple[str, ...]], it should be accepted here!

    But look at the rest of the error message, it points out, list is invariant. So if you have list[T], and list[S] and S is a subtype of T, then list[S] is not a subtype of list[T]! as you might expect

    People tend to expect parametrized types to be covariant.

    list objects have to be invariant because they are mutable (read more about this in PEP 484).

    Consider the following toy example:

    class T:
        pass
    
    class S(T):
        pass
    
    s = S() # inferring type S
    reveal_type(s)
    
    t: T = s # OK!
    
    data_s = [S(), S()] # inferring type list[s]
    reveal_type(data_s)
    
    data_t: list[T] =  data_s # Whoops!
    

    Here is what pyright says on my machine:

    jarrivillaga-mbp16-2019:scratch jarrivillaga$ pyright test_typing.py 
    No configuration file found.
    No pyproject.toml file found.
    stubPath /Users/jarrivillaga/scratch/typings is not a valid directory.
    Assuming Python platform Darwin
    Searching for source files
    Found 1 source file
    /Users/jarrivillaga/scratch/test_typing.py
      /Users/jarrivillaga/scratch/test_typing.py:8:13 - info: Type of "s" is "S"
      /Users/jarrivillaga/scratch/test_typing.py:13:13 - info: Type of "data_s" is "list[S]"
      /Users/jarrivillaga/scratch/test_typing.py:15:20 - error: Expression of type "list[S]" cannot be assigned to declared type "list[T]"
        TypeVar "_T@list" is invariant
          "S" is incompatible with "T" (reportGeneralTypeIssues)
    1 error, 0 warnings, 2 infos 
    Completed in 0.495sec
    

    So one solution is to annotate the variable explicitly with the super type and don’t rely on type inference, in the toy example:

    data_s: list[T] = [S(), S()] # don't rely on inference!
    

    Or for your code:

    listTuple: tuple[str, tuple[str, ...]] = [('x', ('a', 'b'))]
    

    Another alternative, if you don’t need to rely on any of the mutator methods for list (like .append or .pop, or mlist[i] = whatever) then you can use collections.abc.Sequence, which is covariant:

    from collections.abc import Sequence
    
    class T:
        pass
    
    class S(T):
        pass
    
    s = S() # inferring type S
    reveal_type(s)
    
    t: T = s # OK!
    
    data_s = [S(), S()] # inferring type list[s]
    reveal_type(data_s)
    
    data_t: Sequence[T] =  data_s # covariance, yay!
    

    Now pyright is satisfied:

    jarrivillaga-mbp16-2019:scratch jarrivillaga$ pyright test_typing.py 
    No configuration file found.
    No pyproject.toml file found.
    stubPath /Users/jarrivillaga/scratch/typings is not a valid directory.
    Assuming Python platform Darwin
    Searching for source files
    Found 1 source file
    /Users/jarrivillaga/scratch/test_typing.py
      /Users/jarrivillaga/scratch/test_typing.py:10:13 - info: Type of "s" is "S"
      /Users/jarrivillaga/scratch/test_typing.py:15:13 - info: Type of "data_s" is "list[S]"
    0 errors, 0 warnings, 2 infos 
    Completed in 0.489sec
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search