[red-knot] fix detecting a metaclass on a not-explicitly-specialized generic base (#17621)

## Summary

After https://github.com/astral-sh/ruff/pull/17620 (which this PR is
based on), I was looking at other call sites of `Type::into_class_type`,
and I began to feel that _all_ of them were currently buggy due to
silently skipping unspecialized generic class literal types (though in
some cases the bug hadn't shown up yet because we don't understand
legacy generic classes from typeshed), and in every case they would be
better off if an unspecialized generic class literal were implicitly
specialized with the default specialization (which is the usual Python
typing semantics for an unspecialized reference to a generic class),
instead of silently skipped.

So I changed the method to implicitly apply the default specialization,
and added a test that previously failed for detecting metaclasses on an
unspecialized generic base.

I also renamed the method to `to_class_type`, because I feel we have a
strong naming convention where `Type::into_foo` is always a trivial
`const fn` that simply returns `Some()` if the type is of variant `Foo`
and `None` otherwise. Even the existing method (with it handling both
`GenericAlias` and `ClassLiteral`, and distinguishing kinds of
`ClassLiteral`) was stretching this convention, and the new version
definitely breaks that envelope.

## Test Plan

Added a test that failed before this PR.
This commit is contained in:
Carl Meyer 2025-04-25 06:55:54 -07:00 committed by GitHub
parent 4c3f389598
commit fa88989ef0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 35 additions and 14 deletions

View file

@ -53,6 +53,25 @@ class B(A): ...
reveal_type(B.__class__) # revealed: Literal[M]
```
## Linear inheritance with PEP 695 generic class
The same is true if the base with the metaclass is a generic class.
```toml
[environment]
python-version = "3.13"
```
```py
class M(type): ...
class A[T](metaclass=M): ...
class B(A): ...
class C(A[int]): ...
reveal_type(B.__class__) # revealed: Literal[M]
reveal_type(C.__class__) # revealed: Literal[M]
```
## Conflict (1)
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its