Skip to content

Simplification of unions involving type variables #1666

@sharkdp

Description

@sharkdp

Consider the following example:

from typing import TypeVar, Any

type IntOr[T: int] = int | T

def _(x: IntOr[Any]):
    reveal_type(x)

https://play.ty.dev/b378c2a3-d007-428d-8d59-a3a69f5614c0

ty reveals int here, but every other type checker reveals int | Any, which seems more reasonable? ty eagerly simplifies the int | T union to int, which is correct for every static T <: int... but leads to surprising results when T is explicitly specialized to a dynamic type.

This was prompted by a much more complex example in numpy's codebase:

class _SupportsDType(Protocol[_DTypeT_co]):
    @property
    def dtype(self) -> _DTypeT_co: ...

_DTypeLike = type[_ScalarT] | _SupportsDType[dtype[_ScalarT]]

_ScalarT has an upper bound of np.generic (= np.generic[Any]). And np.generic[…] does have a dtype property member. This currently leads us to treat type[_Scalar] as a subtype of _SupportsDType[…], and so that union gets simplified to _SupportsDType[dtype[_ScalarT]]. Later, _DTypeLike is explicitly specialized with _DTypeLike[Any], and it is expected that <class 'object'> should be assignable to _DTypeLike[Any] (which would be the case if type[Any] were still part of the union.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type propertiessubtyping, assignability, equivalence, and more

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions