python / cpython

The Python programming language
https://www.python.org
Other
63.77k stars 30.54k forks source link

Unpacking TypeAliasType to tuple of types using * errors at runtime, Unpack doesn't #126085

Open alwaysmpe opened 1 month ago

alwaysmpe commented 1 month ago

Bug report

Bug description:

Discussed in microsoft/pyright#9343 @JelleZijlstra asked me to open this issue.

I'm writing classes using variadic generic code and as a minimal example I have the below code which raises no type errors on pyright playground and is crashing at runtime which I believe is a bug in cpython. The code:

from __future__ import annotations
from typing import Unpack, assert_type, TypeAlias
type FooArgT = tuple[str, str]
# deprecated but works at runtime
FooArgAliasT: TypeAlias = tuple[str, str]

class Foo[*Arg]:
    pass

type FooT = Foo[str, *FooArgT]
type FooUT = Foo[str, Unpack[FooArgT]]

class BarAU(Foo[str, *FooArgAliasT]):
    pass

class BarU(Foo[str, Unpack[FooArgT]]):
    pass

def foo_fn(t_arg: FooT, ut_arg: FooUT):
    assert_type(t_arg, FooT)
    assert_type(t_arg, FooUT)
    assert_type(t_arg, Foo[str, Unpack[FooArgT]])
    assert_type(t_arg, Foo[str, *FooArgT])
    assert_type(ut_arg, FooT)
    assert_type(ut_arg, FooUT)
    assert_type(ut_arg, Foo[str, Unpack[FooArgT]])
    assert_type(ut_arg, Foo[str, *FooArgT])

class Bar(Foo[str, *FooArgT]):
    pass

When I run this locally I get

Traceback (most recent call last):
  File "...", line 31, in <module>
    class Bar(Foo[str, *FooArgT]):
                  ^^^^^^^^^^^^^
TypeError: Value after * must be an iterable, not typing.TypeAliasType

By my understanding, the 3 class declarations are equivalent, pyright checks pass and from my understanding of the documentation cpython should allow unpacking of the aliased TypeVarTuple.

I'm running python3.13.0 in wsl installed using pyenv, output of python -V -V

Python 3.13.0 (main, Oct 24 2024, 00:12:39) [GCC 11.4.0]

CPython versions tested on:

3.13 3.14

Operating systems tested on:

Linux

JelleZijlstra commented 1 month ago

To fix this, we can add a tp_iter slot to TypeAliasType that produces Unpack[self]. Something very similar already exists for TypeVarTuple; in fact we can probably reuse the same C function. If anyone reading this is interested, please file a PR! Otherwise I'll do it myself at some point.

I reclassified this as a feature request because it's adding a new capability to TypeAliasType, which we only do in new feature versions. However, we'll be able to backport this behavior to typing-extensions for the benefit of earlier Python versions.

alwaysmpe commented 1 month ago

I'll have a look but make zero promises as I've never touched the cpython code base before so anyone else is welcome.

Eclips4 commented 1 month ago

I'll have a look but make zero promises as I've never touched the cpython code base before so anyone else is welcome.

FYI, you need to modify Objects/typevarobject.c file (add a tp_iter slot to _PyTypeAlias_Type). See typevartuple_iter as reference implementation.

JelleZijlstra commented 1 month ago

I'd actually recommend to use typevartuple_iter directly; it looks like it should work unchanged. However, the function should be renamed for clarity in that case.