[ty] Move class_member to member module (#20837)

## Summary

Move the `class_member` function to the `member` module. This allows us
to move the `member` module into the `types` module and to reduce the
visibility of its contents to `pub(super)`. The drawback is that we need
to make `place::place_by_id` public.

## Test Plan

Pure refactoring.
This commit is contained in:
David Peter 2025-10-13 10:58:37 +02:00 committed by GitHub
parent f715d70be1
commit 565dbf3c9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 126 additions and 124 deletions

View file

@ -34,7 +34,6 @@ mod db;
mod dunder_all;
pub mod lint;
pub(crate) mod list;
mod member;
mod module_name;
mod module_resolver;
mod node_key;

View file

@ -1,73 +0,0 @@
use crate::{
place::{Place, PlaceAndQualifiers},
types::Type,
};
/// The return type of certain member-lookup operations. Contains information
/// about the type, type qualifiers, boundness/declaredness, and additional
/// metadata (e.g. whether or not the member was declared)
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
pub(crate) struct Member<'db> {
/// Type, qualifiers, and boundness information of this member
pub(crate) inner: PlaceAndQualifiers<'db>,
/// Whether or not this member was explicitly declared (e.g. `attr: int = 1`
/// on the class body or `self.attr: int = 1` in a class method), or if the
/// type was inferred (e.g. `attr = 1` on the class body or `self.attr = 1`
/// in a class method).
pub(crate) is_declared: bool,
}
impl Default for Member<'_> {
fn default() -> Self {
Member::inferred(PlaceAndQualifiers::default())
}
}
impl<'db> Member<'db> {
/// Create a new [`Member`] whose type was inferred (rather than explicitly declared).
pub(crate) fn inferred(inner: PlaceAndQualifiers<'db>) -> Self {
Self {
inner,
is_declared: false,
}
}
/// Create a new [`Member`] whose type was explicitly declared (rather than inferred).
pub(crate) fn declared(inner: PlaceAndQualifiers<'db>) -> Self {
Self {
inner,
is_declared: true,
}
}
/// Create a new [`Member`] whose type was explicitly and definitively declared, i.e.
/// there is no control flow path in which it might be possibly undeclared.
pub(crate) fn definitely_declared(ty: Type<'db>) -> Self {
Self::declared(Place::bound(ty).into())
}
/// Represents the absence of a member.
pub(crate) fn unbound() -> Self {
Self::inferred(PlaceAndQualifiers::default())
}
/// Returns `true` if the inner place is unbound (i.e. there is no such member).
pub(crate) fn is_unbound(&self) -> bool {
self.inner.place.is_unbound()
}
/// Returns the inner type, unless it is definitely unbound.
pub(crate) fn ignore_possibly_unbound(&self) -> Option<Type<'db>> {
self.inner.place.ignore_possibly_unbound()
}
/// Map a type transformation function over the type of this member.
#[must_use]
pub(crate) fn map_type(self, f: impl FnOnce(Type<'db>) -> Type<'db>) -> Self {
Self {
inner: self.inner.map_type(f),
is_declared: self.is_declared,
}
}
}

View file

@ -1,7 +1,6 @@
use ruff_db::files::File;
use crate::dunder_all::dunder_all_names;
use crate::member::Member;
use crate::module_resolver::{KnownModule, file_to_module};
use crate::semantic_index::definition::{Definition, DefinitionState};
use crate::semantic_index::place::{PlaceExprRef, ScopedPlaceId};
@ -233,50 +232,6 @@ pub(crate) fn place<'db>(
)
}
/// Infer the public type of a class member/symbol (its type as seen from outside its scope) in the given
/// `scope`.
pub(crate) fn class_member<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Member<'db> {
place_table(db, scope)
.symbol_id(name)
.map(|symbol_id| {
let place_and_quals = place_by_id(
db,
scope,
symbol_id.into(),
RequiresExplicitReExport::No,
ConsideredDefinitions::EndOfScope,
);
if !place_and_quals.place.is_unbound() && !place_and_quals.is_init_var() {
// Trust the declared type if we see a class-level declaration
return Member::declared(place_and_quals);
}
if let PlaceAndQualifiers {
place: Place::Type(ty, _),
qualifiers,
} = place_and_quals
{
// Otherwise, we need to check if the symbol has bindings
let use_def = use_def_map(db, scope);
let bindings = use_def.end_of_scope_symbol_bindings(symbol_id);
let inferred = place_from_bindings_impl(db, bindings, RequiresExplicitReExport::No);
// TODO: we should not need to calculate inferred type second time. This is a temporary
// solution until the notion of Boundness and Declaredness is split. See #16036, #16264
Member::inferred(match inferred {
Place::Unbound => Place::Unbound.with_qualifiers(qualifiers),
Place::Type(_, boundness) => {
Place::Type(ty, boundness).with_qualifiers(qualifiers)
}
})
} else {
Member::unbound()
}
})
.unwrap_or_default()
}
/// Infers the public type of an explicit module-global symbol as seen from within the same file.
///
/// Note that all global scopes also include various "implicit globals" such as `__name__`,
@ -701,7 +656,7 @@ fn place_cycle_initial<'db>(
}
#[salsa::tracked(cycle_fn=place_cycle_recover, cycle_initial=place_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
fn place_by_id<'db>(
pub(crate) fn place_by_id<'db>(
db: &'db dyn Db,
scope: ScopeId<'db>,
place_id: ScopedPlaceId,

View file

@ -89,6 +89,7 @@ mod generics;
pub mod ide_support;
mod infer;
mod instance;
mod member;
mod mro;
mod narrow;
mod protocol_class;

View file

@ -7,7 +7,6 @@ use super::{
function::FunctionType, infer_expression_type, infer_unpack_types,
};
use crate::FxOrderMap;
use crate::member::Member;
use crate::module_resolver::KnownModule;
use crate::semantic_index::definition::{Definition, DefinitionState};
use crate::semantic_index::scope::{NodeWithScopeKind, Scope};
@ -22,6 +21,7 @@ use crate::types::enums::enum_metadata;
use crate::types::function::{DataclassTransformerParams, KnownFunction};
use crate::types::generics::{GenericContext, Specialization, walk_specialization};
use crate::types::infer::nearest_enclosing_class;
use crate::types::member::{Member, class_member};
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::typed_dict::typed_dict_params_from_class_def;
@ -37,8 +37,8 @@ use crate::{
Db, FxIndexMap, FxOrderSet, Program,
module_resolver::file_to_module,
place::{
Boundness, LookupError, LookupResult, Place, PlaceAndQualifiers, class_member,
known_module_symbol, place_from_bindings, place_from_declarations,
Boundness, LookupError, LookupResult, Place, PlaceAndQualifiers, known_module_symbol,
place_from_bindings, place_from_declarations,
},
semantic_index::{
attribute_assignments,
@ -3272,7 +3272,7 @@ impl<'db> ClassLiteral<'db> {
/// A helper function for `instance_member` that looks up the `name` attribute only on
/// this class, not on its superclasses.
pub(crate) fn own_instance_member(self, db: &'db dyn Db, name: &str) -> Member<'db> {
fn own_instance_member(self, db: &'db dyn Db, name: &str) -> Member<'db> {
// TODO: There are many things that are not yet implemented here:
// - `typing.Final`
// - Proper diagnostics

View file

@ -0,0 +1,120 @@
use super::Type;
use crate::Db;
use crate::place::{
ConsideredDefinitions, Place, PlaceAndQualifiers, RequiresExplicitReExport, place_by_id,
place_from_bindings,
};
use crate::semantic_index::{place_table, scope::ScopeId, use_def_map};
/// The return type of certain member-lookup operations. Contains information
/// about the type, type qualifiers, boundness/declaredness, and additional
/// metadata (e.g. whether or not the member was declared)
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
pub(super) struct Member<'db> {
/// Type, qualifiers, and boundness information of this member
pub(super) inner: PlaceAndQualifiers<'db>,
/// Whether or not this member was explicitly declared (e.g. `attr: int = 1`
/// on the class body or `self.attr: int = 1` in a class method), or if the
/// type was inferred (e.g. `attr = 1` on the class body or `self.attr = 1`
/// in a class method).
pub(super) is_declared: bool,
}
impl Default for Member<'_> {
fn default() -> Self {
Member::inferred(PlaceAndQualifiers::default())
}
}
impl<'db> Member<'db> {
/// Create a new [`Member`] whose type was inferred (rather than explicitly declared).
pub(super) fn inferred(inner: PlaceAndQualifiers<'db>) -> Self {
Self {
inner,
is_declared: false,
}
}
/// Create a new [`Member`] whose type was explicitly declared (rather than inferred).
pub(super) fn declared(inner: PlaceAndQualifiers<'db>) -> Self {
Self {
inner,
is_declared: true,
}
}
/// Create a new [`Member`] whose type was explicitly and definitively declared, i.e.
/// there is no control flow path in which it might be possibly undeclared.
pub(super) fn definitely_declared(ty: Type<'db>) -> Self {
Self::declared(Place::bound(ty).into())
}
/// Represents the absence of a member.
pub(super) fn unbound() -> Self {
Self::inferred(PlaceAndQualifiers::default())
}
/// Returns `true` if the inner place is unbound (i.e. there is no such member).
pub(super) fn is_unbound(&self) -> bool {
self.inner.place.is_unbound()
}
/// Returns the inner type, unless it is definitely unbound.
pub(super) fn ignore_possibly_unbound(&self) -> Option<Type<'db>> {
self.inner.place.ignore_possibly_unbound()
}
/// Map a type transformation function over the type of this member.
#[must_use]
pub(super) fn map_type(self, f: impl FnOnce(Type<'db>) -> Type<'db>) -> Self {
Self {
inner: self.inner.map_type(f),
is_declared: self.is_declared,
}
}
}
/// Infer the public type of a class member/symbol (its type as seen from outside its scope) in the given
/// `scope`.
pub(super) fn class_member<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Member<'db> {
place_table(db, scope)
.symbol_id(name)
.map(|symbol_id| {
let place_and_quals = place_by_id(
db,
scope,
symbol_id.into(),
RequiresExplicitReExport::No,
ConsideredDefinitions::EndOfScope,
);
if !place_and_quals.place.is_unbound() && !place_and_quals.is_init_var() {
// Trust the declared type if we see a class-level declaration
return Member::declared(place_and_quals);
}
if let PlaceAndQualifiers {
place: Place::Type(ty, _),
qualifiers,
} = place_and_quals
{
// Otherwise, we need to check if the symbol has bindings
let use_def = use_def_map(db, scope);
let bindings = use_def.end_of_scope_symbol_bindings(symbol_id);
let inferred = place_from_bindings(db, bindings);
// TODO: we should not need to calculate inferred type second time. This is a temporary
// solution until the notion of Boundness and Declaredness is split. See #16036, #16264
Member::inferred(match inferred {
Place::Unbound => Place::Unbound.with_qualifiers(qualifiers),
Place::Type(_, boundness) => {
Place::Type(ty, boundness).with_qualifiers(qualifiers)
}
})
} else {
Member::unbound()
}
})
.unwrap_or_default()
}