ty icon indicating copy to clipboard operation
ty copied to clipboard

Instantiating a `list` or `set` loses literal generic type information

Open Avasam opened this issue 2 weeks ago • 5 comments

Summary

Feel free to rename or mark as duplicate if there's a pre-existing issue that better describes the root cause. The closest related I found was maybe https://github.com/astral-sh/ty/issues/1648 / https://github.com/astral-sh/ty/issues/1576

Passing a list of literals to list or set loses type information with ty. Whereas both mypy and pyright keep the literal generic types:

  • https://mypy-play.net/?gist=698f5d996f794a1a02536cebb8fe47a1
  • https://play.ty.dev/7f70524b-6288-4669-8627-62926ed7bad5
  • pyright playground Image

Version

ty 0.0.1-alpha.32

Avasam avatar Dec 09 '25 00:12 Avasam

@ibraheemdev I think we had previously discussed in https://discord.com/channels/1039017663004942429/1279201882337705994/1437881888789364838 that we should not promote in this case, because the argument x in list(x) is already an invariant type (list), so we should assume that if it is specialized to a literal type, that must originate from an explicit annotation (as it does in this case), or else we'd have promoted on the construction of that type.

Did we just not fully implement that plan yet? Do we have another issue tracking that?

carljm avatar Dec 09 '25 00:12 carljm

list takes an Iterable[T] argument, so I believe this is just https://github.com/astral-sh/ty/issues/1576, and should be fixed by https://github.com/astral-sh/ruff/pull/21747.

ibraheemdev avatar Dec 10 '25 20:12 ibraheemdev

It doesn't look like https://github.com/astral-sh/ruff/pull/21747 changes the behavior of the OP example here, so there must be something additional going on. We can revisit after landing that one, though.

carljm avatar Dec 10 '25 20:12 carljm

Ah right, the issue here is unrelated. We consider the variance of the type parameter in the argument type, not parameter type, for literal promotion. There seems to be something else going on here:

from typing import Iterable, Literal
from collections.abc import MutableSequence

class X[T](MutableSequence[T]):
    def __init__(self, iterable: Iterable[T], /) -> None: ...

class Y[T]:
    def __init__(self, iterable: Iterable[T], /) -> None: ...

def _(x: list[Literal[1]]):
    reveal_type(X(x))  # revealed: X[int]
    reveal_type(Y(x))  # revealed: Y[Literal[1]]

ibraheemdev avatar Dec 10 '25 21:12 ibraheemdev

It looks like we actually do consider the variance in the parameter type instead of the argument type, and this was just hidden by the tests using a bivariant type.

ibraheemdev avatar Dec 20 '25 01:12 ibraheemdev