Skip to content

Allow Any-typed tuple indexes as dynamic class bases - #22002

Open
00200200 wants to merge 2 commits into
python:masterfrom
00200200:fix-21998-starargs-any-base
Open

00200200 wants to merge 2 commits into
python:masterfrom
00200200:fix-21998-starargs-any-base

Conversation

@00200200

@00200200 00200200 commented Sep 17, 2026

Copy link
Copy Markdown

Fixes #21998

class C(args[1]) failed even when that slot is Any. Names of type Any already work as dynamic bases; tuple indexes didn't. I allow Any / type / Type[Any] indexes and still reject everything else.

x = args[1]; class C(x) still needs an annotation — bases are resolved before inference.

Tested with the new cases in check-classes.test (pytest mypy/test/testcheck.py).

Mypy already accepts a name of type Any as a base class, but treated
args[1] as a generic type application, so *args entries typed as Any
were rejected. Index into a known tuple type in base-class position and
reuse the existing Any/type/Type[Any] rule without relaxing non-Any bases.
@github-actions

Copy link
Copy Markdown
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@willy-b

willy-b commented Sep 17, 2026

Copy link
Copy Markdown

Can you check this additional test case I generated by modifying one of yours to add nesting (added alongside the ones you added) passes as it should? (I don't think that it does in your current version)

[case testSubclassAnyFromNestedUnpackedStarArgs]
# https://github.com/python/mypy/issues/21998
# flags: --python-version 3.12
from typing import Any

def from_unpacked(*args: *tuple[int, *tuple[Any]]) -> None:
    reveal_type(args) # N: Revealed type is "tuple[builtins.int, Any]"
    class Sub(args[1]):
        pass

def from_unpacked_type_annotation(*args: *tuple[int, *tuple[Any]]) -> None:
    reveal_type(args) # N: Revealed type is "tuple[builtins.int, Any]"
    x: type = args[1]
    class Sub(x):
        pass
[builtins fixtures/tuple.pyi]

pyright 1.1.414 says that the following associated standalone snippet is fine and outputs the expected type for the nested unpack (and pyrefly 1.3.1 also agrees it typechecks):

from typing import Any

def from_unpacked(*args: *tuple[int, *tuple[Any]]) -> None:
    reveal_type(args) # N: Revealed type is "tuple[builtins.int, Any]"
    class Sub(args[1]):
        pass

def from_unpacked_type_annotation(*args: *tuple[int, *tuple[Any]]) -> None:
    reveal_type(args) # N: Revealed type is "tuple[builtins.int, Any]"
    x: type = args[1]
    class Sub(x):
        pass

Thanks

Flatten Unpack[Tuple[...]] before reading a literal index so
*tuple[int, *tuple[Any]] is treated like tuple[int, Any].
@00200200

Copy link
Copy Markdown
Author

The nested case failed because unpack-in-tuple short-circuited indexing. Nested Unpack[Tuple[...]] is flattened first now, so *tuple[int, *tuple[Any]] is treated like tuple[int, Any] and args[1] is a valid dynamic base. Added that test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants