mirror of https://github.com/astral-sh/ruff
[ty] Move `all_members`, and related types/routines, out of `ide_support.rs` (#21695)
This commit is contained in:
parent
644096ea8a
commit
ac2552b11b
|
|
@ -11,7 +11,7 @@ use crate::module_resolver::{KnownModule, Module, list_modules, resolve_module};
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::scope::FileScopeId;
|
use crate::semantic_index::scope::FileScopeId;
|
||||||
use crate::semantic_index::semantic_index;
|
use crate::semantic_index::semantic_index;
|
||||||
use crate::types::ide_support::{Member, all_declarations_and_bindings, all_members};
|
use crate::types::list_members::{Member, all_members, all_members_of_scope};
|
||||||
use crate::types::{Type, binding_type, infer_scope_types};
|
use crate::types::{Type, binding_type, infer_scope_types};
|
||||||
use crate::{Db, resolve_real_shadowable_module};
|
use crate::{Db, resolve_real_shadowable_module};
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ impl<'db> SemanticModel<'db> {
|
||||||
|
|
||||||
for (file_scope, _) in index.ancestor_scopes(file_scope) {
|
for (file_scope, _) in index.ancestor_scopes(file_scope) {
|
||||||
for memberdef in
|
for memberdef in
|
||||||
all_declarations_and_bindings(self.db, file_scope.to_scope_id(self.db, self.file))
|
all_members_of_scope(self.db, file_scope.to_scope_id(self.db, self.file))
|
||||||
{
|
{
|
||||||
members.insert(
|
members.insert(
|
||||||
memberdef.member.name,
|
memberdef.member.name,
|
||||||
|
|
@ -221,12 +221,13 @@ impl<'db> SemanticModel<'db> {
|
||||||
let mut completions = vec![];
|
let mut completions = vec![];
|
||||||
for (file_scope, _) in index.ancestor_scopes(file_scope) {
|
for (file_scope, _) in index.ancestor_scopes(file_scope) {
|
||||||
completions.extend(
|
completions.extend(
|
||||||
all_declarations_and_bindings(self.db, file_scope.to_scope_id(self.db, self.file))
|
all_members_of_scope(self.db, file_scope.to_scope_id(self.db, self.file)).map(
|
||||||
.map(|memberdef| Completion {
|
|memberdef| Completion {
|
||||||
name: memberdef.member.name,
|
name: memberdef.member.name,
|
||||||
ty: Some(memberdef.member.ty),
|
ty: Some(memberdef.member.ty),
|
||||||
builtin: false,
|
builtin: false,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Builtins are available in all scopes.
|
// Builtins are available in all scopes.
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ mod generics;
|
||||||
pub mod ide_support;
|
pub mod ide_support;
|
||||||
mod infer;
|
mod infer;
|
||||||
mod instance;
|
mod instance;
|
||||||
|
pub mod list_members;
|
||||||
mod member;
|
mod member;
|
||||||
mod mro;
|
mod mro;
|
||||||
mod narrow;
|
mod narrow;
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ use crate::types::{
|
||||||
DataclassParams, FieldInstance, KnownBoundMethodType, KnownClass, KnownInstanceType,
|
DataclassParams, FieldInstance, KnownBoundMethodType, KnownClass, KnownInstanceType,
|
||||||
MemberLookupPolicy, NominalInstanceType, PropertyInstanceType, SpecialFormType,
|
MemberLookupPolicy, NominalInstanceType, PropertyInstanceType, SpecialFormType,
|
||||||
TrackedConstraintSet, TypeAliasType, TypeContext, TypeVarVariance, UnionBuilder, UnionType,
|
TrackedConstraintSet, TypeAliasType, TypeContext, TypeVarVariance, UnionBuilder, UnionType,
|
||||||
WrapperDescriptorKind, enums, ide_support, todo_type,
|
WrapperDescriptorKind, enums, list_members, todo_type,
|
||||||
};
|
};
|
||||||
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
||||||
use ruff_python_ast::{self as ast, ArgOrKeyword, PythonVersion};
|
use ruff_python_ast::{self as ast, ArgOrKeyword, PythonVersion};
|
||||||
|
|
@ -888,7 +888,7 @@ impl<'db> Bindings<'db> {
|
||||||
if let [Some(ty)] = overload.parameter_types() {
|
if let [Some(ty)] = overload.parameter_types() {
|
||||||
overload.set_return_type(Type::heterogeneous_tuple(
|
overload.set_return_type(Type::heterogeneous_tuple(
|
||||||
db,
|
db,
|
||||||
ide_support::all_members(db, *ty)
|
list_members::all_members(db, *ty)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.sorted()
|
.sorted()
|
||||||
.map(|member| Type::string_literal(db, &member.name)),
|
.map(|member| Type::string_literal(db, &member.name)),
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ use crate::types::diagnostic::{
|
||||||
};
|
};
|
||||||
use crate::types::display::DisplaySettings;
|
use crate::types::display::DisplaySettings;
|
||||||
use crate::types::generics::{GenericContext, InferableTypeVars};
|
use crate::types::generics::{GenericContext, InferableTypeVars};
|
||||||
use crate::types::ide_support::all_members;
|
use crate::types::list_members::all_members;
|
||||||
use crate::types::narrow::ClassInfoConstraintFunction;
|
use crate::types::narrow::ClassInfoConstraintFunction;
|
||||||
use crate::types::signatures::{CallableSignature, Signature};
|
use crate::types::signatures::{CallableSignature, Signature};
|
||||||
use crate::types::visitor::any_over_type;
|
use crate::types::visitor::any_over_type;
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,16 @@
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::place::{
|
use crate::place::builtins_module_scope;
|
||||||
Place, builtins_module_scope, imported_symbol, place_from_bindings, place_from_declarations,
|
|
||||||
};
|
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::definition::DefinitionKind;
|
use crate::semantic_index::definition::DefinitionKind;
|
||||||
use crate::semantic_index::scope::ScopeId;
|
use crate::semantic_index::{attribute_scopes, global_scope, semantic_index, use_def_map};
|
||||||
use crate::semantic_index::{
|
|
||||||
attribute_scopes, global_scope, place_table, semantic_index, use_def_map,
|
|
||||||
};
|
|
||||||
use crate::types::call::{CallArguments, MatchedArgument};
|
use crate::types::call::{CallArguments, MatchedArgument};
|
||||||
use crate::types::generics::Specialization;
|
|
||||||
use crate::types::signatures::Signature;
|
use crate::types::signatures::Signature;
|
||||||
use crate::types::{CallDunderError, SubclassOfInner, UnionType};
|
use crate::types::{CallDunderError, UnionType};
|
||||||
use crate::types::{
|
use crate::types::{CallableTypes, ClassBase, KnownClass, Type, TypeContext};
|
||||||
CallableTypes, ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type, TypeContext,
|
use crate::{Db, DisplaySettings, HasType, SemanticModel};
|
||||||
TypeVarBoundOrConstraints, class::CodeGeneratorKind,
|
|
||||||
};
|
|
||||||
use crate::{Db, DisplaySettings, HasType, NameKind, SemanticModel};
|
|
||||||
use ruff_db::files::FileRange;
|
use ruff_db::files::FileRange;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_python_ast::name::Name;
|
|
||||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
@ -29,497 +18,6 @@ use rustc_hash::FxHashSet;
|
||||||
pub use resolve_definition::{ImportAliasResolution, ResolvedDefinition, map_stub_definition};
|
pub use resolve_definition::{ImportAliasResolution, ResolvedDefinition, map_stub_definition};
|
||||||
use resolve_definition::{find_symbol_in_scope, resolve_definition};
|
use resolve_definition::{find_symbol_in_scope, resolve_definition};
|
||||||
|
|
||||||
// `__init__`, `__repr__`, `__eq__`, `__ne__` and `__hash__` are always included via `object`,
|
|
||||||
// so we don't need to list them here.
|
|
||||||
const SYNTHETIC_DATACLASS_ATTRIBUTES: &[&str] = &[
|
|
||||||
"__lt__",
|
|
||||||
"__le__",
|
|
||||||
"__gt__",
|
|
||||||
"__ge__",
|
|
||||||
"__replace__",
|
|
||||||
"__setattr__",
|
|
||||||
"__delattr__",
|
|
||||||
"__slots__",
|
|
||||||
"__weakref__",
|
|
||||||
"__match_args__",
|
|
||||||
"__dataclass_fields__",
|
|
||||||
"__dataclass_params__",
|
|
||||||
];
|
|
||||||
|
|
||||||
pub(crate) fn all_declarations_and_bindings<'db>(
|
|
||||||
db: &'db dyn Db,
|
|
||||||
scope_id: ScopeId<'db>,
|
|
||||||
) -> impl Iterator<Item = MemberWithDefinition<'db>> + 'db {
|
|
||||||
let use_def_map = use_def_map(db, scope_id);
|
|
||||||
let table = place_table(db, scope_id);
|
|
||||||
|
|
||||||
use_def_map
|
|
||||||
.all_end_of_scope_symbol_declarations()
|
|
||||||
.filter_map(move |(symbol_id, declarations)| {
|
|
||||||
let place_result = place_from_declarations(db, declarations);
|
|
||||||
let definition = place_result.single_declaration;
|
|
||||||
place_result
|
|
||||||
.ignore_conflicting_declarations()
|
|
||||||
.place
|
|
||||||
.ignore_possibly_undefined()
|
|
||||||
.map(|ty| {
|
|
||||||
let symbol = table.symbol(symbol_id);
|
|
||||||
let member = Member {
|
|
||||||
name: symbol.name().clone(),
|
|
||||||
ty,
|
|
||||||
};
|
|
||||||
MemberWithDefinition { member, definition }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.chain(use_def_map.all_end_of_scope_symbol_bindings().filter_map(
|
|
||||||
move |(symbol_id, bindings)| {
|
|
||||||
// It's not clear to AG how to using a bindings
|
|
||||||
// iterator here to get the correct definition for
|
|
||||||
// this binding. Below, we look through all bindings
|
|
||||||
// with a definition and only take one if there is
|
|
||||||
// exactly one. I don't think this can be wrong, but
|
|
||||||
// it's probably omitting definitions in some cases.
|
|
||||||
let mut definition = None;
|
|
||||||
for binding in bindings.clone() {
|
|
||||||
if let Some(def) = binding.binding.definition() {
|
|
||||||
if definition.is_some() {
|
|
||||||
definition = None;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
definition = Some(def);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
place_from_bindings(db, bindings)
|
|
||||||
.ignore_possibly_undefined()
|
|
||||||
.map(|ty| {
|
|
||||||
let symbol = table.symbol(symbol_id);
|
|
||||||
let member = Member {
|
|
||||||
name: symbol.name().clone(),
|
|
||||||
ty,
|
|
||||||
};
|
|
||||||
MemberWithDefinition { member, definition }
|
|
||||||
})
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AllMembers<'db> {
|
|
||||||
members: FxHashSet<Member<'db>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> AllMembers<'db> {
|
|
||||||
fn of(db: &'db dyn Db, ty: Type<'db>) -> Self {
|
|
||||||
let mut all_members = Self {
|
|
||||||
members: FxHashSet::default(),
|
|
||||||
};
|
|
||||||
all_members.extend_with_type(db, ty);
|
|
||||||
all_members
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extend_with_type(&mut self, db: &'db dyn Db, ty: Type<'db>) {
|
|
||||||
match ty {
|
|
||||||
Type::Union(union) => self.members.extend(
|
|
||||||
union
|
|
||||||
.elements(db)
|
|
||||||
.iter()
|
|
||||||
.map(|ty| AllMembers::of(db, *ty).members)
|
|
||||||
.reduce(|acc, members| acc.intersection(&members).cloned().collect())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
),
|
|
||||||
|
|
||||||
Type::Intersection(intersection) => self.members.extend(
|
|
||||||
intersection
|
|
||||||
.positive(db)
|
|
||||||
.iter()
|
|
||||||
.map(|ty| AllMembers::of(db, *ty).members)
|
|
||||||
.reduce(|acc, members| acc.union(&members).cloned().collect())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
),
|
|
||||||
|
|
||||||
Type::NominalInstance(instance) => {
|
|
||||||
let (class_literal, specialization) = instance.class(db).class_literal(db);
|
|
||||||
self.extend_with_instance_members(db, ty, class_literal);
|
|
||||||
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::NewTypeInstance(newtype) => {
|
|
||||||
self.extend_with_type(db, Type::instance(db, newtype.base_class_type(db)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::ClassLiteral(class_literal) if class_literal.is_typed_dict(db) => {
|
|
||||||
self.extend_with_type(db, KnownClass::TypedDictFallback.to_class_literal(db));
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::GenericAlias(generic_alias) if generic_alias.is_typed_dict(db) => {
|
|
||||||
self.extend_with_type(db, KnownClass::TypedDictFallback.to_class_literal(db));
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::SubclassOf(subclass_of_type) if subclass_of_type.is_typed_dict(db) => {
|
|
||||||
self.extend_with_type(db, KnownClass::TypedDictFallback.to_class_literal(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(metaclass) = class_literal.metaclass(db) {
|
|
||||||
self.extend_with_class_members(db, ty, metaclass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::GenericAlias(generic_alias) => {
|
|
||||||
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);
|
|
||||||
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
|
|
||||||
self.extend_with_class_members(db, ty, metaclass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
|
||||||
SubclassOfInner::Dynamic(_) => {
|
|
||||||
self.extend_with_type(db, KnownClass::Type.to_instance(db));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if let Some(class_type) = subclass_of_type.subclass_of().into_class(db) {
|
|
||||||
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 => {
|
|
||||||
self.extend_with_type(db, Type::object());
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::TypeAlias(alias) => self.extend_with_type(db, alias.value_type(db)),
|
|
||||||
|
|
||||||
Type::TypeVar(bound_typevar) => {
|
|
||||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
|
||||||
None => {
|
|
||||||
self.extend_with_type(db, Type::object());
|
|
||||||
}
|
|
||||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
|
||||||
self.extend_with_type(db, bound);
|
|
||||||
}
|
|
||||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
|
||||||
self.members.extend(
|
|
||||||
constraints
|
|
||||||
.elements(db)
|
|
||||||
.iter()
|
|
||||||
.map(|ty| AllMembers::of(db, *ty).members)
|
|
||||||
.reduce(|acc, members| {
|
|
||||||
acc.intersection(&members).cloned().collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::IntLiteral(_)
|
|
||||||
| Type::BooleanLiteral(_)
|
|
||||||
| Type::StringLiteral(_)
|
|
||||||
| Type::BytesLiteral(_)
|
|
||||||
| Type::EnumLiteral(_)
|
|
||||||
| Type::LiteralString
|
|
||||||
| Type::PropertyInstance(_)
|
|
||||||
| Type::FunctionLiteral(_)
|
|
||||||
| Type::BoundMethod(_)
|
|
||||||
| Type::KnownBoundMethod(_)
|
|
||||||
| Type::WrapperDescriptor(_)
|
|
||||||
| Type::DataclassDecorator(_)
|
|
||||||
| Type::DataclassTransformer(_)
|
|
||||||
| Type::Callable(_)
|
|
||||||
| Type::ProtocolInstance(_)
|
|
||||||
| Type::SpecialForm(_)
|
|
||||||
| Type::KnownInstance(_)
|
|
||||||
| Type::BoundSuper(_)
|
|
||||||
| Type::TypeIs(_) => match ty.to_meta_type(db) {
|
|
||||||
Type::ClassLiteral(class_literal) => {
|
|
||||||
self.extend_with_class_members(db, ty, class_literal);
|
|
||||||
}
|
|
||||||
Type::SubclassOf(subclass_of) => {
|
|
||||||
if let Some(class) = subclass_of.subclass_of().into_class(db) {
|
|
||||||
self.extend_with_class_members(db, ty, class.class_literal(db).0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::GenericAlias(generic_alias) => {
|
|
||||||
let class_literal = generic_alias.origin(db);
|
|
||||||
self.extend_with_class_members(db, ty, class_literal);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
|
|
||||||
Type::TypedDict(_) => {
|
|
||||||
if let Type::ClassLiteral(class_literal) = ty.to_meta_type(db) {
|
|
||||||
self.extend_with_class_members(db, ty, class_literal);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Type::ClassLiteral(class) =
|
|
||||||
KnownClass::TypedDictFallback.to_class_literal(db)
|
|
||||||
{
|
|
||||||
self.extend_with_instance_members(db, ty, class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::ModuleLiteral(literal) => {
|
|
||||||
self.extend_with_type(db, KnownClass::ModuleType.to_instance(db));
|
|
||||||
let module = literal.module(db);
|
|
||||||
|
|
||||||
let Some(file) = module.file(db) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let module_scope = global_scope(db, file);
|
|
||||||
let use_def_map = use_def_map(db, module_scope);
|
|
||||||
let place_table = place_table(db, module_scope);
|
|
||||||
|
|
||||||
for (symbol_id, _) in use_def_map.all_end_of_scope_symbol_declarations() {
|
|
||||||
let symbol_name = place_table.symbol(symbol_id).name();
|
|
||||||
let Place::Defined(ty, _, _) =
|
|
||||||
imported_symbol(db, file, symbol_name, None).place
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter private symbols from stubs if they appear to be internal types
|
|
||||||
let is_stub_file = file.path(db).extension() == Some("pyi");
|
|
||||||
let is_private_symbol = match NameKind::classify(symbol_name) {
|
|
||||||
NameKind::Dunder | NameKind::Normal => false,
|
|
||||||
NameKind::Sunder => true,
|
|
||||||
};
|
|
||||||
if is_private_symbol && is_stub_file {
|
|
||||||
match ty {
|
|
||||||
Type::NominalInstance(instance)
|
|
||||||
if matches!(
|
|
||||||
instance.known_class(db),
|
|
||||||
Some(
|
|
||||||
KnownClass::TypeVar
|
|
||||||
| KnownClass::TypeVarTuple
|
|
||||||
| KnownClass::ParamSpec
|
|
||||||
| KnownClass::UnionType
|
|
||||||
)
|
|
||||||
) =>
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Type::ClassLiteral(class) if class.is_protocol(db) => continue,
|
|
||||||
Type::KnownInstance(
|
|
||||||
KnownInstanceType::TypeVar(_)
|
|
||||||
| KnownInstanceType::TypeAliasType(_)
|
|
||||||
| KnownInstanceType::UnionType(_)
|
|
||||||
| KnownInstanceType::Literal(_)
|
|
||||||
| KnownInstanceType::Annotated(_),
|
|
||||||
) => continue,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.members.insert(Member {
|
|
||||||
name: symbol_name.clone(),
|
|
||||||
ty,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
self.members
|
|
||||||
.extend(literal.available_submodule_attributes(db).filter_map(
|
|
||||||
|submodule_name| {
|
|
||||||
let ty = literal.resolve_submodule(db, &submodule_name)?;
|
|
||||||
let name = submodule_name.clone();
|
|
||||||
Some(Member { name, ty })
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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_scope = parent.body_scope(db);
|
|
||||||
for memberdef in all_declarations_and_bindings(db, parent_scope) {
|
|
||||||
let result = ty.member(db, memberdef.member.name.as_str());
|
|
||||||
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
self.members.insert(Member {
|
|
||||||
name: memberdef.member.name,
|
|
||||||
ty,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extend_with_instance_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 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) {
|
|
||||||
for place_expr in index.place_table(function_scope_id).members() {
|
|
||||||
let Some(name) = place_expr.as_instance_attribute() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let result = ty.member(db, name);
|
|
||||||
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
self.members.insert(Member {
|
|
||||||
name: Name::new(name),
|
|
||||||
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 memberdef in all_declarations_and_bindings(db, class_body_scope) {
|
|
||||||
let result = ty.member(db, memberdef.member.name.as_str());
|
|
||||||
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
self.members.insert(Member {
|
|
||||||
name: memberdef.member.name,
|
|
||||||
ty,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extend_with_synthetic_members(
|
|
||||||
&mut self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
ty: Type<'db>,
|
|
||||||
class_literal: ClassLiteral<'db>,
|
|
||||||
specialization: Option<Specialization<'db>>,
|
|
||||||
) {
|
|
||||||
match CodeGeneratorKind::from_class(db, class_literal, specialization) {
|
|
||||||
Some(CodeGeneratorKind::NamedTuple) => {
|
|
||||||
if ty.is_nominal_instance() {
|
|
||||||
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_instance(db));
|
|
||||||
} else {
|
|
||||||
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_class_literal(db));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(CodeGeneratorKind::TypedDict) => {}
|
|
||||||
Some(CodeGeneratorKind::DataclassLike(_)) => {
|
|
||||||
for attr in SYNTHETIC_DATACLASS_ATTRIBUTES {
|
|
||||||
if let Place::Defined(synthetic_member, _, _) = ty.member(db, attr).place {
|
|
||||||
self.members.insert(Member {
|
|
||||||
name: Name::from(*attr),
|
|
||||||
ty: synthetic_member,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A member of a type with an optional definition.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
||||||
pub struct MemberWithDefinition<'db> {
|
|
||||||
pub member: Member<'db>,
|
|
||||||
pub definition: Option<Definition<'db>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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<'db>> {
|
|
||||||
AllMembers::of(db, ty).members
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the primary definition kind for a name expression within a specific file.
|
/// Get the primary definition kind for a name expression within a specific file.
|
||||||
/// Returns the first definition kind that is reachable for this name in its scope.
|
/// Returns the first definition kind that is reachable for this name in its scope.
|
||||||
/// This is useful for IDE features like semantic tokens.
|
/// This is useful for IDE features like semantic tokens.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,516 @@
|
||||||
|
//! Routines and types to list all members present on a given type or in a given scope.
|
||||||
|
//!
|
||||||
|
//! These two concepts are closely related, since listing all members of a given
|
||||||
|
//! module-literal type requires listing all members in the module's scope, and
|
||||||
|
//! listing all members on a nominal-instance type or a class-literal type requires
|
||||||
|
//! listing all members in the class's body scope.
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use ruff_python_ast::name::Name;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Db, NameKind,
|
||||||
|
place::{Place, imported_symbol, place_from_bindings, place_from_declarations},
|
||||||
|
semantic_index::{
|
||||||
|
attribute_scopes, definition::Definition, global_scope, place_table, scope::ScopeId,
|
||||||
|
semantic_index, use_def_map,
|
||||||
|
},
|
||||||
|
types::{
|
||||||
|
ClassBase, ClassLiteral, KnownClass, KnownInstanceType, SubclassOfInner, Type,
|
||||||
|
TypeVarBoundOrConstraints, class::CodeGeneratorKind, generics::Specialization,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Iterate over all declarations and bindings in the given scope.
|
||||||
|
pub(crate) fn all_members_of_scope<'db>(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
scope_id: ScopeId<'db>,
|
||||||
|
) -> impl Iterator<Item = MemberWithDefinition<'db>> + 'db {
|
||||||
|
let use_def_map = use_def_map(db, scope_id);
|
||||||
|
let table = place_table(db, scope_id);
|
||||||
|
|
||||||
|
use_def_map
|
||||||
|
.all_end_of_scope_symbol_declarations()
|
||||||
|
.filter_map(move |(symbol_id, declarations)| {
|
||||||
|
let place_result = place_from_declarations(db, declarations);
|
||||||
|
let definition = place_result.single_declaration;
|
||||||
|
place_result
|
||||||
|
.ignore_conflicting_declarations()
|
||||||
|
.place
|
||||||
|
.ignore_possibly_undefined()
|
||||||
|
.map(|ty| {
|
||||||
|
let symbol = table.symbol(symbol_id);
|
||||||
|
let member = Member {
|
||||||
|
name: symbol.name().clone(),
|
||||||
|
ty,
|
||||||
|
};
|
||||||
|
MemberWithDefinition { member, definition }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.chain(use_def_map.all_end_of_scope_symbol_bindings().filter_map(
|
||||||
|
move |(symbol_id, bindings)| {
|
||||||
|
// It's not clear to AG how to using a bindings
|
||||||
|
// iterator here to get the correct definition for
|
||||||
|
// this binding. Below, we look through all bindings
|
||||||
|
// with a definition and only take one if there is
|
||||||
|
// exactly one. I don't think this can be wrong, but
|
||||||
|
// it's probably omitting definitions in some cases.
|
||||||
|
let mut definition = None;
|
||||||
|
for binding in bindings.clone() {
|
||||||
|
if let Some(def) = binding.binding.definition() {
|
||||||
|
if definition.is_some() {
|
||||||
|
definition = None;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
definition = Some(def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
place_from_bindings(db, bindings)
|
||||||
|
.ignore_possibly_undefined()
|
||||||
|
.map(|ty| {
|
||||||
|
let symbol = table.symbol(symbol_id);
|
||||||
|
let member = Member {
|
||||||
|
name: symbol.name().clone(),
|
||||||
|
ty,
|
||||||
|
};
|
||||||
|
MemberWithDefinition { member, definition }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// `__init__`, `__repr__`, `__eq__`, `__ne__` and `__hash__` are always included via `object`,
|
||||||
|
// so we don't need to list them here.
|
||||||
|
const SYNTHETIC_DATACLASS_ATTRIBUTES: &[&str] = &[
|
||||||
|
"__lt__",
|
||||||
|
"__le__",
|
||||||
|
"__gt__",
|
||||||
|
"__ge__",
|
||||||
|
"__replace__",
|
||||||
|
"__setattr__",
|
||||||
|
"__delattr__",
|
||||||
|
"__slots__",
|
||||||
|
"__weakref__",
|
||||||
|
"__match_args__",
|
||||||
|
"__dataclass_fields__",
|
||||||
|
"__dataclass_params__",
|
||||||
|
];
|
||||||
|
|
||||||
|
struct AllMembers<'db> {
|
||||||
|
members: FxHashSet<Member<'db>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> AllMembers<'db> {
|
||||||
|
fn of(db: &'db dyn Db, ty: Type<'db>) -> Self {
|
||||||
|
let mut all_members = Self {
|
||||||
|
members: FxHashSet::default(),
|
||||||
|
};
|
||||||
|
all_members.extend_with_type(db, ty);
|
||||||
|
all_members
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_with_type(&mut self, db: &'db dyn Db, ty: Type<'db>) {
|
||||||
|
match ty {
|
||||||
|
Type::Union(union) => self.members.extend(
|
||||||
|
union
|
||||||
|
.elements(db)
|
||||||
|
.iter()
|
||||||
|
.map(|ty| AllMembers::of(db, *ty).members)
|
||||||
|
.reduce(|acc, members| acc.intersection(&members).cloned().collect())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
),
|
||||||
|
|
||||||
|
Type::Intersection(intersection) => self.members.extend(
|
||||||
|
intersection
|
||||||
|
.positive(db)
|
||||||
|
.iter()
|
||||||
|
.map(|ty| AllMembers::of(db, *ty).members)
|
||||||
|
.reduce(|acc, members| acc.union(&members).cloned().collect())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
),
|
||||||
|
|
||||||
|
Type::NominalInstance(instance) => {
|
||||||
|
let (class_literal, specialization) = instance.class(db).class_literal(db);
|
||||||
|
self.extend_with_instance_members(db, ty, class_literal);
|
||||||
|
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::NewTypeInstance(newtype) => {
|
||||||
|
self.extend_with_type(db, Type::instance(db, newtype.base_class_type(db)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::ClassLiteral(class_literal) if class_literal.is_typed_dict(db) => {
|
||||||
|
self.extend_with_type(db, KnownClass::TypedDictFallback.to_class_literal(db));
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::GenericAlias(generic_alias) if generic_alias.is_typed_dict(db) => {
|
||||||
|
self.extend_with_type(db, KnownClass::TypedDictFallback.to_class_literal(db));
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::SubclassOf(subclass_of_type) if subclass_of_type.is_typed_dict(db) => {
|
||||||
|
self.extend_with_type(db, KnownClass::TypedDictFallback.to_class_literal(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(metaclass) = class_literal.metaclass(db) {
|
||||||
|
self.extend_with_class_members(db, ty, metaclass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::GenericAlias(generic_alias) => {
|
||||||
|
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);
|
||||||
|
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
|
||||||
|
self.extend_with_class_members(db, ty, metaclass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
||||||
|
SubclassOfInner::Dynamic(_) => {
|
||||||
|
self.extend_with_type(db, KnownClass::Type.to_instance(db));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(class_type) = subclass_of_type.subclass_of().into_class(db) {
|
||||||
|
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 => {
|
||||||
|
self.extend_with_type(db, Type::object());
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::TypeAlias(alias) => self.extend_with_type(db, alias.value_type(db)),
|
||||||
|
|
||||||
|
Type::TypeVar(bound_typevar) => {
|
||||||
|
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||||
|
None => {
|
||||||
|
self.extend_with_type(db, Type::object());
|
||||||
|
}
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||||
|
self.extend_with_type(db, bound);
|
||||||
|
}
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||||
|
self.members.extend(
|
||||||
|
constraints
|
||||||
|
.elements(db)
|
||||||
|
.iter()
|
||||||
|
.map(|ty| AllMembers::of(db, *ty).members)
|
||||||
|
.reduce(|acc, members| {
|
||||||
|
acc.intersection(&members).cloned().collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::IntLiteral(_)
|
||||||
|
| Type::BooleanLiteral(_)
|
||||||
|
| Type::StringLiteral(_)
|
||||||
|
| Type::BytesLiteral(_)
|
||||||
|
| Type::EnumLiteral(_)
|
||||||
|
| Type::LiteralString
|
||||||
|
| Type::PropertyInstance(_)
|
||||||
|
| Type::FunctionLiteral(_)
|
||||||
|
| Type::BoundMethod(_)
|
||||||
|
| Type::KnownBoundMethod(_)
|
||||||
|
| Type::WrapperDescriptor(_)
|
||||||
|
| Type::DataclassDecorator(_)
|
||||||
|
| Type::DataclassTransformer(_)
|
||||||
|
| Type::Callable(_)
|
||||||
|
| Type::ProtocolInstance(_)
|
||||||
|
| Type::SpecialForm(_)
|
||||||
|
| Type::KnownInstance(_)
|
||||||
|
| Type::BoundSuper(_)
|
||||||
|
| Type::TypeIs(_) => match ty.to_meta_type(db) {
|
||||||
|
Type::ClassLiteral(class_literal) => {
|
||||||
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
|
}
|
||||||
|
Type::SubclassOf(subclass_of) => {
|
||||||
|
if let Some(class) = subclass_of.subclass_of().into_class(db) {
|
||||||
|
self.extend_with_class_members(db, ty, class.class_literal(db).0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::GenericAlias(generic_alias) => {
|
||||||
|
let class_literal = generic_alias.origin(db);
|
||||||
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::TypedDict(_) => {
|
||||||
|
if let Type::ClassLiteral(class_literal) = ty.to_meta_type(db) {
|
||||||
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Type::ClassLiteral(class) =
|
||||||
|
KnownClass::TypedDictFallback.to_class_literal(db)
|
||||||
|
{
|
||||||
|
self.extend_with_instance_members(db, ty, class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::ModuleLiteral(literal) => {
|
||||||
|
self.extend_with_type(db, KnownClass::ModuleType.to_instance(db));
|
||||||
|
let module = literal.module(db);
|
||||||
|
|
||||||
|
let Some(file) = module.file(db) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let module_scope = global_scope(db, file);
|
||||||
|
let use_def_map = use_def_map(db, module_scope);
|
||||||
|
let place_table = place_table(db, module_scope);
|
||||||
|
|
||||||
|
for (symbol_id, _) in use_def_map.all_end_of_scope_symbol_declarations() {
|
||||||
|
let symbol_name = place_table.symbol(symbol_id).name();
|
||||||
|
let Place::Defined(ty, _, _) =
|
||||||
|
imported_symbol(db, file, symbol_name, None).place
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter private symbols from stubs if they appear to be internal types
|
||||||
|
let is_stub_file = file.path(db).extension() == Some("pyi");
|
||||||
|
let is_private_symbol = match NameKind::classify(symbol_name) {
|
||||||
|
NameKind::Dunder | NameKind::Normal => false,
|
||||||
|
NameKind::Sunder => true,
|
||||||
|
};
|
||||||
|
if is_private_symbol && is_stub_file {
|
||||||
|
match ty {
|
||||||
|
Type::NominalInstance(instance)
|
||||||
|
if matches!(
|
||||||
|
instance.known_class(db),
|
||||||
|
Some(
|
||||||
|
KnownClass::TypeVar
|
||||||
|
| KnownClass::TypeVarTuple
|
||||||
|
| KnownClass::ParamSpec
|
||||||
|
| KnownClass::UnionType
|
||||||
|
)
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Type::ClassLiteral(class) if class.is_protocol(db) => continue,
|
||||||
|
Type::KnownInstance(
|
||||||
|
KnownInstanceType::TypeVar(_)
|
||||||
|
| KnownInstanceType::TypeAliasType(_)
|
||||||
|
| KnownInstanceType::UnionType(_)
|
||||||
|
| KnownInstanceType::Literal(_)
|
||||||
|
| KnownInstanceType::Annotated(_),
|
||||||
|
) => continue,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.members.insert(Member {
|
||||||
|
name: symbol_name.clone(),
|
||||||
|
ty,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.members
|
||||||
|
.extend(literal.available_submodule_attributes(db).filter_map(
|
||||||
|
|submodule_name| {
|
||||||
|
let ty = literal.resolve_submodule(db, &submodule_name)?;
|
||||||
|
let name = submodule_name.clone();
|
||||||
|
Some(Member { name, ty })
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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_scope = parent.body_scope(db);
|
||||||
|
for memberdef in all_members_of_scope(db, parent_scope) {
|
||||||
|
let result = ty.member(db, memberdef.member.name.as_str());
|
||||||
|
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
self.members.insert(Member {
|
||||||
|
name: memberdef.member.name,
|
||||||
|
ty,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_with_instance_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 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) {
|
||||||
|
for place_expr in index.place_table(function_scope_id).members() {
|
||||||
|
let Some(name) = place_expr.as_instance_attribute() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let result = ty.member(db, name);
|
||||||
|
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
self.members.insert(Member {
|
||||||
|
name: Name::new(name),
|
||||||
|
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 memberdef in all_members_of_scope(db, class_body_scope) {
|
||||||
|
let result = ty.member(db, memberdef.member.name.as_str());
|
||||||
|
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
self.members.insert(Member {
|
||||||
|
name: memberdef.member.name,
|
||||||
|
ty,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_with_synthetic_members(
|
||||||
|
&mut self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
ty: Type<'db>,
|
||||||
|
class_literal: ClassLiteral<'db>,
|
||||||
|
specialization: Option<Specialization<'db>>,
|
||||||
|
) {
|
||||||
|
match CodeGeneratorKind::from_class(db, class_literal, specialization) {
|
||||||
|
Some(CodeGeneratorKind::NamedTuple) => {
|
||||||
|
if ty.is_nominal_instance() {
|
||||||
|
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_instance(db));
|
||||||
|
} else {
|
||||||
|
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_class_literal(db));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(CodeGeneratorKind::TypedDict) => {}
|
||||||
|
Some(CodeGeneratorKind::DataclassLike(_)) => {
|
||||||
|
for attr in SYNTHETIC_DATACLASS_ATTRIBUTES {
|
||||||
|
if let Place::Defined(synthetic_member, _, _) = ty.member(db, attr).place {
|
||||||
|
self.members.insert(Member {
|
||||||
|
name: Name::from(*attr),
|
||||||
|
ty: synthetic_member,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A member of a type or scope, with an optional definition.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
pub struct MemberWithDefinition<'db> {
|
||||||
|
pub member: Member<'db>,
|
||||||
|
pub definition: Option<Definition<'db>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A member of a type or scope.
|
||||||
|
///
|
||||||
|
/// In the context of the [`all_members`] routine, 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<'db>> {
|
||||||
|
AllMembers::of(db, ty).members
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ use crate::{
|
||||||
report_overridden_final_method,
|
report_overridden_final_method,
|
||||||
},
|
},
|
||||||
function::{FunctionDecorators, FunctionType, KnownFunction},
|
function::{FunctionDecorators, FunctionType, KnownFunction},
|
||||||
ide_support::{MemberWithDefinition, all_declarations_and_bindings},
|
list_members::{MemberWithDefinition, all_members_of_scope},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -53,8 +53,7 @@ pub(super) fn check_class<'db>(context: &InferContext<'db, '_>, class: ClassLite
|
||||||
}
|
}
|
||||||
|
|
||||||
let class_specialized = class.identity_specialization(db);
|
let class_specialized = class.identity_specialization(db);
|
||||||
let own_class_members: FxHashSet<_> =
|
let own_class_members: FxHashSet<_> = all_members_of_scope(db, class.body_scope(db)).collect();
|
||||||
all_declarations_and_bindings(db, class.body_scope(db)).collect();
|
|
||||||
|
|
||||||
for member in own_class_members {
|
for member in own_class_members {
|
||||||
check_class_declaration(context, configuration, class_specialized, &member);
|
check_class_declaration(context, configuration, class_specialized, &member);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue