python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.42k stars 2.82k forks source link

Having a function that takes `Optional[T]` as an input and returns `T` (A type var) works incorrectly if T is an union #17383

Open superosku opened 4 months ago

superosku commented 4 months ago

Bug Report

Having a function that takes Optional[T] as an input and returns T (A type var) works incorrectly if T is an union.

To Reproduce

from typing import TypeVar

class A:
    pass

class B:
    pass

T = TypeVar("T")

def make_non_optional(value: T | None) -> T:
    if value is None:
        raise ValueError("Value is None")
    return value

def make_non_optimal_simple_example() -> A:
    my_value: A | None = None
    return make_non_optional(my_value)  # This works as expected

def make_non_optimal_complex_example_broken() -> A | B:
    my_value: A | B | None = None
    return make_non_optional(my_value)  # error: Incompatible return value type (got "object", expected "A | B")  [return-value]

Expected Behavior

When calling make_non_optional(my_value) with A | B | None the returned type should be A | B

Actual Behavior

It has object type as the return type

Your Environment

Mypy version used:

 $ mypy --version
mypy 1.10.0 (compiled: yes)

Python version used:

 $ python --version
Python 3.12.2

Mypy command-line flags: none

Mypy configuration options from mypy.ini (and other config files): none

erictraut commented 4 months ago

This behavior stems from the fact that mypy's constraint solver uses a join operator rather than unions. The join of A and B is object, which explains the error message you're seeing here.