mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[ty] Add type information to all_members
API
Since we generally need (so far) to get the type information of each suggestion to figure out its boundness anyway, we might as well expose it here. Completions want to use this information to enhance the metadata on each suggestion for a more pleasant user experience. For the most part, this was pretty straight-forward. The most exciting part was in computing the types for instance attributes. I'm not 100% sure it's correct or is the best way to do it.
This commit is contained in:
parent
79fe538458
commit
fea84e8777
4 changed files with 269 additions and 102 deletions
|
@ -1223,33 +1223,33 @@ quux.<CURSOR>
|
|||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.completions_without_builtins(), @r"
|
||||
bar
|
||||
baz
|
||||
foo
|
||||
__annotations__
|
||||
__class__
|
||||
__delattr__
|
||||
__dict__
|
||||
__dir__
|
||||
__doc__
|
||||
__eq__
|
||||
__format__
|
||||
__getattribute__
|
||||
__getstate__
|
||||
__hash__
|
||||
__init__
|
||||
__init_subclass__
|
||||
__module__
|
||||
__ne__
|
||||
__new__
|
||||
__reduce__
|
||||
__reduce_ex__
|
||||
__repr__
|
||||
__setattr__
|
||||
__sizeof__
|
||||
__str__
|
||||
__subclasshook__
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
bar :: Unknown | Literal[2]
|
||||
baz :: Unknown | Literal[3]
|
||||
foo :: Unknown | Literal[1]
|
||||
__annotations__ :: dict[str, Any]
|
||||
__class__ :: type
|
||||
__delattr__ :: bound method object.__delattr__(name: str, /) -> None
|
||||
__dict__ :: dict[str, Any]
|
||||
__dir__ :: bound method object.__dir__() -> Iterable[str]
|
||||
__doc__ :: str | None
|
||||
__eq__ :: bound method object.__eq__(value: object, /) -> bool
|
||||
__format__ :: bound method object.__format__(format_spec: str, /) -> str
|
||||
__getattribute__ :: bound method object.__getattribute__(name: str, /) -> Any
|
||||
__getstate__ :: bound method object.__getstate__() -> object
|
||||
__hash__ :: bound method object.__hash__() -> int
|
||||
__init__ :: bound method Quux.__init__() -> Unknown
|
||||
__init_subclass__ :: bound method object.__init_subclass__() -> None
|
||||
__module__ :: str
|
||||
__ne__ :: bound method object.__ne__(value: object, /) -> bool
|
||||
__new__ :: bound method object.__new__() -> Self
|
||||
__reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
__repr__ :: bound method object.__repr__() -> str
|
||||
__setattr__ :: bound method object.__setattr__(name: str, value: Any, /) -> None
|
||||
__sizeof__ :: bound method object.__sizeof__() -> int
|
||||
__str__ :: bound method object.__str__() -> str
|
||||
__subclasshook__ :: bound method type.__subclasshook__(subclass: type, /) -> bool
|
||||
");
|
||||
}
|
||||
|
||||
|
@ -1268,33 +1268,33 @@ quux.b<CURSOR>
|
|||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.completions_without_builtins(), @r"
|
||||
bar
|
||||
baz
|
||||
foo
|
||||
__annotations__
|
||||
__class__
|
||||
__delattr__
|
||||
__dict__
|
||||
__dir__
|
||||
__doc__
|
||||
__eq__
|
||||
__format__
|
||||
__getattribute__
|
||||
__getstate__
|
||||
__hash__
|
||||
__init__
|
||||
__init_subclass__
|
||||
__module__
|
||||
__ne__
|
||||
__new__
|
||||
__reduce__
|
||||
__reduce_ex__
|
||||
__repr__
|
||||
__setattr__
|
||||
__sizeof__
|
||||
__str__
|
||||
__subclasshook__
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
bar :: Unknown | Literal[2]
|
||||
baz :: Unknown | Literal[3]
|
||||
foo :: Unknown | Literal[1]
|
||||
__annotations__ :: dict[str, Any]
|
||||
__class__ :: type
|
||||
__delattr__ :: bound method object.__delattr__(name: str, /) -> None
|
||||
__dict__ :: dict[str, Any]
|
||||
__dir__ :: bound method object.__dir__() -> Iterable[str]
|
||||
__doc__ :: str | None
|
||||
__eq__ :: bound method object.__eq__(value: object, /) -> bool
|
||||
__format__ :: bound method object.__format__(format_spec: str, /) -> str
|
||||
__getattribute__ :: bound method object.__getattribute__(name: str, /) -> Any
|
||||
__getstate__ :: bound method object.__getstate__() -> object
|
||||
__hash__ :: bound method object.__hash__() -> int
|
||||
__init__ :: bound method Quux.__init__() -> Unknown
|
||||
__init_subclass__ :: bound method object.__init_subclass__() -> None
|
||||
__module__ :: str
|
||||
__ne__ :: bound method object.__ne__(value: object, /) -> bool
|
||||
__new__ :: bound method object.__new__() -> Self
|
||||
__reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
__repr__ :: bound method object.__repr__() -> str
|
||||
__setattr__ :: bound method object.__setattr__(name: str, value: Any, /) -> None
|
||||
__sizeof__ :: bound method object.__sizeof__() -> int
|
||||
__str__ :: bound method object.__str__() -> str
|
||||
__subclasshook__ :: bound method type.__subclasshook__(subclass: type, /) -> bool
|
||||
");
|
||||
}
|
||||
|
||||
|
@ -1321,6 +1321,89 @@ class Quux:
|
|||
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_attributes1() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
class Quux:
|
||||
some_attribute: int = 1
|
||||
|
||||
def __init__(self):
|
||||
self.foo = 1
|
||||
self.bar = 2
|
||||
self.baz = 3
|
||||
|
||||
def some_method(self) -> int:
|
||||
return 1
|
||||
|
||||
@property
|
||||
def some_property(self) -> int:
|
||||
return 1
|
||||
|
||||
@classmethod
|
||||
def some_class_method(self) -> int:
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def some_static_method(self) -> int:
|
||||
return 1
|
||||
|
||||
Quux.<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
mro :: def mro(self) -> list[type]
|
||||
some_attribute :: int
|
||||
some_class_method :: bound method <class 'Quux'>.some_class_method() -> int
|
||||
some_method :: def some_method(self) -> int
|
||||
some_property :: property
|
||||
some_static_method :: def some_static_method(self) -> int
|
||||
__annotations__ :: dict[str, Any]
|
||||
__base__ :: type | None
|
||||
__bases__ :: tuple[type, ...]
|
||||
__basicsize__ :: int
|
||||
__call__ :: def __call__(self, *args: Any, **kwds: Any) -> Any
|
||||
__class__ :: <class 'type'>
|
||||
__delattr__ :: def __delattr__(self, name: str, /) -> None
|
||||
__dict__ :: MappingProxyType[str, Any]
|
||||
__dictoffset__ :: int
|
||||
__dir__ :: def __dir__(self) -> Iterable[str]
|
||||
__doc__ :: str | None
|
||||
__eq__ :: def __eq__(self, value: object, /) -> bool
|
||||
__flags__ :: int
|
||||
__format__ :: def __format__(self, format_spec: str, /) -> str
|
||||
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
|
||||
__getstate__ :: def __getstate__(self) -> object
|
||||
__hash__ :: def __hash__(self) -> int
|
||||
__init__ :: def __init__(self) -> Unknown
|
||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
||||
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[<class 'type'>, <class 'object'>]
|
||||
__name__ :: str
|
||||
__ne__ :: def __ne__(self, value: object, /) -> bool
|
||||
__new__ :: def __new__(cls) -> Self
|
||||
__or__ :: def __or__(self, value: Any, /) -> UnionType
|
||||
__prepare__ :: bound method <class 'type'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
|
||||
__qualname__ :: str
|
||||
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
__repr__ :: def __repr__(self) -> str
|
||||
__ror__ :: def __ror__(self, value: Any, /) -> UnionType
|
||||
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
|
||||
__sizeof__ :: def __sizeof__(self) -> int
|
||||
__str__ :: def __str__(self) -> str
|
||||
__subclasscheck__ :: def __subclasscheck__(self, subclass: type, /) -> bool
|
||||
__subclasses__ :: def __subclasses__(self: Self) -> list[Self]
|
||||
__subclasshook__ :: bound method <class 'object'>.__subclasshook__(subclass: type, /) -> bool
|
||||
__text_signature__ :: str | None
|
||||
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||
__weakrefoffset__ :: int
|
||||
");
|
||||
}
|
||||
|
||||
// We don't yet take function parameters into account.
|
||||
#[test]
|
||||
fn call_prefix1() {
|
||||
|
@ -2366,7 +2449,22 @@ importlib.<CURSOR>
|
|||
self.completions_if(|c| !c.builtin)
|
||||
}
|
||||
|
||||
fn completions_without_builtins_with_types(&self) -> String {
|
||||
self.completions_if_snapshot(
|
||||
|c| !c.builtin,
|
||||
|c| format!("{} :: {}", c.name, c.ty.display(&self.db)),
|
||||
)
|
||||
}
|
||||
|
||||
fn completions_if(&self, predicate: impl Fn(&Completion) -> bool) -> String {
|
||||
self.completions_if_snapshot(predicate, |c| c.name.as_str().to_string())
|
||||
}
|
||||
|
||||
fn completions_if_snapshot(
|
||||
&self,
|
||||
predicate: impl Fn(&Completion) -> bool,
|
||||
snapshot: impl Fn(&Completion) -> String,
|
||||
) -> String {
|
||||
let completions = completion(&self.db, self.cursor.file, self.cursor.offset);
|
||||
if completions.is_empty() {
|
||||
return "<No completions found>".to_string();
|
||||
|
@ -2374,7 +2472,7 @@ importlib.<CURSOR>
|
|||
let included = completions
|
||||
.iter()
|
||||
.filter(|label| predicate(label))
|
||||
.map(|completion| completion.name.as_str().to_string())
|
||||
.map(snapshot)
|
||||
.collect::<Vec<String>>();
|
||||
if included.is_empty() {
|
||||
// It'd be nice to include the actual number of
|
||||
|
|
|
@ -73,7 +73,7 @@ impl<'db> SemanticModel<'db> {
|
|||
.into_iter()
|
||||
.map(|member| Completion {
|
||||
name: member.name,
|
||||
ty: None,
|
||||
ty: member.ty,
|
||||
builtin,
|
||||
})
|
||||
.collect()
|
||||
|
@ -86,7 +86,7 @@ impl<'db> SemanticModel<'db> {
|
|||
.into_iter()
|
||||
.map(|member| Completion {
|
||||
name: member.name,
|
||||
ty: None,
|
||||
ty: member.ty,
|
||||
builtin: false,
|
||||
})
|
||||
.collect()
|
||||
|
@ -122,7 +122,7 @@ impl<'db> SemanticModel<'db> {
|
|||
all_declarations_and_bindings(self.db, file_scope.to_scope_id(self.db, self.file))
|
||||
.map(|member| Completion {
|
||||
name: member.name,
|
||||
ty: None,
|
||||
ty: member.ty,
|
||||
builtin: false,
|
||||
}),
|
||||
);
|
||||
|
@ -172,8 +172,8 @@ impl NameKind {
|
|||
pub struct Completion<'db> {
|
||||
/// The label shown to the user for this suggestion.
|
||||
pub name: Name,
|
||||
/// The type of this completion, if available.
|
||||
pub ty: Option<Type<'db>>,
|
||||
/// The type of this completion.
|
||||
pub ty: Type<'db>,
|
||||
/// Whether this suggestion came from builtins or not.
|
||||
///
|
||||
/// At time of writing (2025-06-26), this information
|
||||
|
|
|
@ -2048,7 +2048,11 @@ impl<'db> ClassLiteral<'db> {
|
|||
|
||||
/// A helper function for `instance_member` that looks up the `name` attribute only on
|
||||
/// this class, not on its superclasses.
|
||||
fn own_instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
pub(crate) fn own_instance_member(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
// TODO: There are many things that are not yet implemented here:
|
||||
// - `typing.Final`
|
||||
// - Proper diagnostics
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use crate::module_resolver::resolve_module;
|
||||
use crate::place::{Place, imported_symbol, place_from_bindings, place_from_declarations};
|
||||
use crate::semantic_index::definition::DefinitionKind;
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
|
@ -14,7 +17,7 @@ use rustc_hash::FxHashSet;
|
|||
pub(crate) fn all_declarations_and_bindings<'db>(
|
||||
db: &'db dyn Db,
|
||||
scope_id: ScopeId<'db>,
|
||||
) -> impl Iterator<Item = Member> + 'db {
|
||||
) -> impl Iterator<Item = Member<'db>> + 'db {
|
||||
let use_def_map = use_def_map(db, scope_id);
|
||||
let table = place_table(db, scope_id);
|
||||
|
||||
|
@ -24,12 +27,12 @@ pub(crate) fn all_declarations_and_bindings<'db>(
|
|||
place_from_declarations(db, declarations)
|
||||
.ok()
|
||||
.and_then(|result| {
|
||||
result.place.ignore_possibly_unbound().and_then(|_| {
|
||||
result.place.ignore_possibly_unbound().and_then(|ty| {
|
||||
table
|
||||
.place_expr(symbol_id)
|
||||
.as_name()
|
||||
.cloned()
|
||||
.map(|name| Member { name })
|
||||
.map(|name| Member { name, ty })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -39,23 +42,23 @@ pub(crate) fn all_declarations_and_bindings<'db>(
|
|||
.filter_map(move |(symbol_id, bindings)| {
|
||||
place_from_bindings(db, bindings)
|
||||
.ignore_possibly_unbound()
|
||||
.and_then(|_| {
|
||||
.and_then(|ty| {
|
||||
table
|
||||
.place_expr(symbol_id)
|
||||
.as_name()
|
||||
.cloned()
|
||||
.map(|name| Member { name })
|
||||
.map(|name| Member { name, ty })
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
struct AllMembers {
|
||||
members: FxHashSet<Member>,
|
||||
struct AllMembers<'db> {
|
||||
members: FxHashSet<Member<'db>>,
|
||||
}
|
||||
|
||||
impl AllMembers {
|
||||
fn of<'db>(db: &'db dyn Db, ty: Type<'db>) -> Self {
|
||||
impl<'db> AllMembers<'db> {
|
||||
fn of(db: &'db dyn Db, ty: Type<'db>) -> Self {
|
||||
let mut all_members = Self {
|
||||
members: FxHashSet::default(),
|
||||
};
|
||||
|
@ -63,7 +66,7 @@ impl AllMembers {
|
|||
all_members
|
||||
}
|
||||
|
||||
fn extend_with_type<'db>(&mut self, db: &'db dyn Db, ty: Type<'db>) {
|
||||
fn extend_with_type(&mut self, db: &'db dyn Db, ty: Type<'db>) {
|
||||
match ty {
|
||||
Type::Union(union) => self.members.extend(
|
||||
union
|
||||
|
@ -85,7 +88,6 @@ impl AllMembers {
|
|||
|
||||
Type::NominalInstance(instance) => {
|
||||
let (class_literal, _specialization) = instance.class.class_literal(db);
|
||||
self.extend_with_class_members(db, class_literal);
|
||||
self.extend_with_instance_members(db, class_literal);
|
||||
}
|
||||
|
||||
|
@ -191,6 +193,7 @@ impl AllMembers {
|
|||
|
||||
self.members.insert(Member {
|
||||
name: place_table.place_expr(symbol_id).expect_name().clone(),
|
||||
ty,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -198,71 +201,133 @@ impl AllMembers {
|
|||
self.members.extend(
|
||||
imported_modules(db, literal.importing_file(db))
|
||||
.iter()
|
||||
.filter_map(|submodule_name| submodule_name.relative_to(module_name))
|
||||
.filter_map(|relative_submodule_name| {
|
||||
Some(Member {
|
||||
name: Name::from(relative_submodule_name.components().next()?),
|
||||
.filter_map(|submodule_name| {
|
||||
let module = resolve_module(db, submodule_name)?;
|
||||
let ty = Type::module_literal(db, file, &module);
|
||||
Some((submodule_name, ty))
|
||||
})
|
||||
.filter_map(|(submodule_name, ty)| {
|
||||
let relative = submodule_name.relative_to(module_name)?;
|
||||
Some((relative, ty))
|
||||
})
|
||||
.filter_map(|(relative_submodule_name, ty)| {
|
||||
let name = Name::from(relative_submodule_name.components().next()?);
|
||||
Some(Member { name, ty })
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_with_declarations_and_bindings(&mut self, db: &dyn Db, scope_id: ScopeId) {
|
||||
self.members
|
||||
.extend(all_declarations_and_bindings(db, scope_id));
|
||||
}
|
||||
|
||||
fn extend_with_class_members<'db>(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
class_literal: ClassLiteral<'db>,
|
||||
) {
|
||||
fn extend_with_class_members(&mut self, db: &'db dyn 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);
|
||||
self.extend_with_declarations_and_bindings(db, parent_scope);
|
||||
for Member { name, .. } in all_declarations_and_bindings(db, parent_scope) {
|
||||
let result = parent_ty.member(db, name.as_str());
|
||||
let Some(ty) = result.place.ignore_possibly_unbound() else {
|
||||
continue;
|
||||
};
|
||||
self.members.insert(Member { name, ty });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_with_instance_members<'db>(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
class_literal: ClassLiteral<'db>,
|
||||
) {
|
||||
fn extend_with_instance_members(&mut self, db: &'db dyn 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_instance = Type::instance(db, parent.default_specialization(db));
|
||||
let class_body_scope = parent.body_scope(db);
|
||||
let file = class_body_scope.file(db);
|
||||
let index = semantic_index(db, file);
|
||||
for function_scope_id in attribute_scopes(db, class_body_scope) {
|
||||
let place_table = index.place_table(function_scope_id);
|
||||
self.members.extend(
|
||||
place_table
|
||||
.instance_attributes()
|
||||
.cloned()
|
||||
.map(|name| Member { name }),
|
||||
);
|
||||
for place_expr in place_table.places() {
|
||||
let Some(name) = place_expr.as_instance_attribute() else {
|
||||
continue;
|
||||
};
|
||||
let result = parent_instance.member(db, name.as_str());
|
||||
let Some(ty) = result.place.ignore_possibly_unbound() else {
|
||||
continue;
|
||||
};
|
||||
self.members.insert(Member {
|
||||
name: name.clone(),
|
||||
ty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This is very similar to `extend_with_class_members`,
|
||||
// but uses the type of the class instance to query the
|
||||
// class member. This gets us the right type for each
|
||||
// member, e.g., `SomeClass.__delattr__` is not a bound
|
||||
// method, but `instance_of_SomeClass.__delattr__` is.
|
||||
for Member { name, .. } in all_declarations_and_bindings(db, class_body_scope) {
|
||||
let result = parent_instance.member(db, name.as_str());
|
||||
let Some(ty) = result.place.ignore_possibly_unbound() else {
|
||||
continue;
|
||||
};
|
||||
self.members.insert(Member { name, ty });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Member {
|
||||
/// A member of a type.
|
||||
///
|
||||
/// This represents a single item in (ideally) the list returned by
|
||||
/// `dir(object)`.
|
||||
///
|
||||
/// The equality, comparison and hashing traits implemented for
|
||||
/// this type are done so by taking only the name into account. At
|
||||
/// present, this is because we assume the name is enough to uniquely
|
||||
/// identify each attribute on an object. This is perhaps complicated
|
||||
/// by overloads, but they only get represented by one member for
|
||||
/// now. Moreover, it is convenient to be able to sort collections of
|
||||
/// members, and a `Type` currently (as of 2025-07-09) has no way to do
|
||||
/// ordered comparisons.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Member<'db> {
|
||||
pub name: Name,
|
||||
pub ty: Type<'db>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Member<'_> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Member<'_> {}
|
||||
|
||||
impl<'db> PartialEq for Member<'db> {
|
||||
fn eq(&self, rhs: &Member<'db>) -> bool {
|
||||
self.name == rhs.name
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> Ord for Member<'db> {
|
||||
fn cmp(&self, rhs: &Member<'db>) -> Ordering {
|
||||
self.name.cmp(&rhs.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> PartialOrd for Member<'db> {
|
||||
fn partial_cmp(&self, rhs: &Member<'db>) -> Option<Ordering> {
|
||||
Some(self.cmp(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
/// List all members of a given type: anything that would be valid when accessed
|
||||
/// as an attribute on an object of the given type.
|
||||
pub fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet<Member> {
|
||||
pub fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet<Member<'db>> {
|
||||
AllMembers::of(db, ty).members
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue