[ty] Include all members on `type` in autocompletion suggestions for `type[]` types (#21670)

This commit is contained in:
Alex Waygood 2025-11-27 19:29:25 +00:00 committed by GitHub
parent 666f488f1b
commit 53efc82989
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 62 additions and 12 deletions

View File

@ -153,6 +153,12 @@ static_assert(has_member(D, "meta_base_attr"))
static_assert(has_member(D, "meta_attr"))
static_assert(has_member(D, "base_attr"))
static_assert(has_member(D, "class_attr"))
def f(x: type[D]):
static_assert(has_member(x, "meta_base_attr"))
static_assert(has_member(x, "meta_attr"))
static_assert(has_member(x, "base_attr"))
static_assert(has_member(x, "class_attr"))
```
### Generic classes
@ -170,6 +176,40 @@ static_assert(has_member(C[int], "base_attr"))
static_assert(has_member(C[int](), "base_attr"))
```
Generic classes can also have metaclasses:
```py
class Meta(type):
FOO = 42
class E(Generic[T], metaclass=Meta): ...
static_assert(has_member(E[int], "FOO"))
def f(x: type[E[str]]):
static_assert(has_member(x, "FOO"))
```
### `type[Any]` and `Any`
`type[Any]` has all members of `type`.
```py
from typing import Any
from ty_extensions import has_member, static_assert
def f(x: type[Any]):
static_assert(has_member(x, "__base__"))
static_assert(has_member(x, "__qualname__"))
```
`Any` has all members of `object`, since it is a subtype of `object`:
```py
def f(x: Any):
static_assert(has_member(x, "__repr__"))
```
### Other instance-like types
```py

View File

@ -13,7 +13,7 @@ use crate::semantic_index::{
use crate::types::call::{CallArguments, MatchedArgument};
use crate::types::generics::Specialization;
use crate::types::signatures::Signature;
use crate::types::{CallDunderError, UnionType};
use crate::types::{CallDunderError, SubclassOfInner, UnionType};
use crate::types::{
CallableTypes, ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type, TypeContext,
TypeVarBoundOrConstraints, class::CodeGeneratorKind,
@ -161,9 +161,8 @@ impl<'db> AllMembers<'db> {
Type::ClassLiteral(class_literal) => {
self.extend_with_class_members(db, ty, class_literal);
self.extend_with_synthetic_members(db, ty, class_literal, None);
if let Type::ClassLiteral(meta_class_literal) = ty.to_meta_type(db) {
self.extend_with_class_members(db, ty, meta_class_literal);
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
self.extend_with_class_members(db, ty, metaclass);
}
}
@ -171,17 +170,28 @@ impl<'db> AllMembers<'db> {
let class_literal = generic_alias.origin(db);
self.extend_with_class_members(db, ty, class_literal);
self.extend_with_synthetic_members(db, ty, class_literal, None);
}
Type::SubclassOf(subclass_of_type) => {
if let Some(class_type) = subclass_of_type.subclass_of().into_class() {
let (class_literal, specialization) = class_type.class_literal(db);
self.extend_with_class_members(db, ty, class_literal);
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
self.extend_with_class_members(db, ty, metaclass);
}
}
Type::Dynamic(_) | Type::Never | Type::AlwaysTruthy | Type::AlwaysFalsy => {}
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
SubclassOfInner::Class(class_type) => {
let (class_literal, specialization) = class_type.class_literal(db);
self.extend_with_class_members(db, ty, class_literal);
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
self.extend_with_class_members(db, ty, metaclass);
}
}
SubclassOfInner::Dynamic(_) => {
self.extend_with_type(db, KnownClass::Type.to_instance(db));
}
},
Type::Dynamic(_) | Type::Never | Type::AlwaysTruthy | Type::AlwaysFalsy => {
self.extend_with_type(db, Type::object());
}
Type::TypeAlias(alias) => self.extend_with_type(db, alias.value_type(db)),