mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-03 07:04:37 +00:00
[ty] Fix handling of metaclasses in object.<CURSOR>
completions
Basically, we weren't quite using `Type::member` in every case correctly. Specifically, this example from @sharkdp: ``` class Meta(type): @property def meta_attr(self) -> int: return 0 class C(metaclass=Meta): ... C.<CURSOR> ``` While we would return `C.meta_attr` here, we were claiming its type was `property`. But its type should be `int`. Ref https://github.com/astral-sh/ruff/pull/19216#discussion_r2197065241
This commit is contained in:
parent
3560f86450
commit
f7973ac870
2 changed files with 176 additions and 18 deletions
|
@ -95,21 +95,21 @@ impl<'db> AllMembers<'db> {
|
|||
}
|
||||
|
||||
Type::ClassLiteral(class_literal) => {
|
||||
self.extend_with_class_members(db, class_literal);
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
|
||||
if let Type::ClassLiteral(meta_class_literal) = ty.to_meta_type(db) {
|
||||
self.extend_with_class_members(db, meta_class_literal);
|
||||
self.extend_with_class_members(db, ty, meta_class_literal);
|
||||
}
|
||||
}
|
||||
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
let class_literal = generic_alias.origin(db);
|
||||
self.extend_with_class_members(db, class_literal);
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
}
|
||||
|
||||
Type::SubclassOf(subclass_of_type) => {
|
||||
if let Some(class_literal) = subclass_of_type.subclass_of().into_class() {
|
||||
self.extend_with_class_members(db, class_literal.class_literal(db).0);
|
||||
self.extend_with_class_members(db, ty, class_literal.class_literal(db).0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,11 +136,11 @@ impl<'db> AllMembers<'db> {
|
|||
| Type::BoundSuper(_)
|
||||
| Type::TypeIs(_) => match ty.to_meta_type(db) {
|
||||
Type::ClassLiteral(class_literal) => {
|
||||
self.extend_with_class_members(db, class_literal);
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
}
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
let class_literal = generic_alias.origin(db);
|
||||
self.extend_with_class_members(db, class_literal);
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
@ -214,16 +214,41 @@ impl<'db> AllMembers<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
fn extend_with_class_members(&mut self, db: &'db dyn Db, class_literal: ClassLiteral<'db>) {
|
||||
/// Add members from `class_literal` (including following its
|
||||
/// parent classes).
|
||||
///
|
||||
/// `ty` should be the original type that we're adding members for.
|
||||
/// For example, in:
|
||||
///
|
||||
/// ```text
|
||||
/// class Meta(type):
|
||||
/// @property
|
||||
/// def meta_attr(self) -> int:
|
||||
/// return 0
|
||||
///
|
||||
/// class C(metaclass=Meta): ...
|
||||
///
|
||||
/// C.<CURSOR>
|
||||
/// ```
|
||||
///
|
||||
/// then `class_literal` might be `Meta`, but `ty` should be the
|
||||
/// type of `C`. This ensures that the descriptor protocol is
|
||||
/// correctly used (or not used) to get the type of each member of
|
||||
/// `C`.
|
||||
fn extend_with_class_members(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
ty: Type<'db>,
|
||||
class_literal: ClassLiteral<'db>,
|
||||
) {
|
||||
for parent in class_literal
|
||||
.iter_mro(db, None)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.map(|class| class.class_literal(db).0)
|
||||
{
|
||||
let parent_ty = Type::ClassLiteral(parent);
|
||||
let parent_scope = parent.body_scope(db);
|
||||
for Member { name, .. } in all_declarations_and_bindings(db, parent_scope) {
|
||||
let result = parent_ty.member(db, name.as_str());
|
||||
let result = ty.member(db, name.as_str());
|
||||
let Some(ty) = result.place.ignore_possibly_unbound() else {
|
||||
continue;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue