Skip to content

Overload + generic functions + union arguments = inconsistent results (wrong overload is matched?) #18321

Open
@sbrudenell

Description

@sbrudenell

Bug Report

If I have an @overloaded generic function, and call that function with a variable having union type, I get inconsistent results.

To Reproduce

from typing import TypeVar                                                      
from typing import overload                                                     
from typing_extensions import assert_type                                       
                                                                                
_T = TypeVar("_T")                                                              
                                                                                
@overload                                                                       
def makelist(a: list[_T]) -> list[_T]: ...                                      
@overload                                                                       
def makelist(a: _T) -> list[_T]: ...                                            
def makelist(a: list[_T] | _T) -> list[_T]:                                     
    if isinstance(a, list):                                                     
        return a                                                                
    return [a]                                                                  
                                                                                
# no error                                                                      
assert_type(makelist("a"), list[str])                                           
# no error                                                                      
assert_type(makelist(["a"]), list[str])                                         
                                                                                
arg: str | list[str] = "a"                                                      
# error: Expression is of type "list[str | list[str]]", not "list[str]" [assert-type]
assert_type(makelist(arg), list[str])                                           
                                                                                
def want_list_of_str(a: list[str]) -> None:                                     
    ...                                                                         
                                                                                
# no error, despite error above                                                 
want_list_of_str(makelist(arg))                                                 
list_of_str: list[str] = makelist(arg)       

Expected Behavior

I expect that if the argument has type str | list[str], then makelist(arg) should be of type list[str].

Reason: the first @overload (def makelist(a: list[_T]) -> list[_T]: ...) should match list[str]. The second @overload (def makelist(a: _T) -> list[_T]: ...) should match str. So in all cases of the argument's union type, the return type should be list[str].

I also expect mypy to be consistent about expression types. Since mypy says makelist(str_or_list_of_str) has type list[str | list[str]], then list_of_str: list[str] = makelist(str_or_list_of_str) should raise an error.

Actual Behavior

$ mypy t.py
t.py:23: error: Expression is of type "list[str | list[str]]", not "list[str]"  [assert-type]

Your Environment

  • Mypy version used: mypy 1.14.0 (compiled: yes)
  • Mypy command-line flags: mypy
  • Mypy configuration options from pyproject.toml:
[tool.mypy]
strict = true
  • Python version used: Python 3.10.12

If I had to guess, it seems like mypy is wrongly matching my str | list[str] argument to the broad overload (def makelist(a: _T) -> list[_T]: ...). But I don't understand why it's not consistent.

#17331 also involves overloads and generics. Perhaps it's related.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions