diff --git a/crates/ty_python_semantic/src/lib.rs b/crates/ty_python_semantic/src/lib.rs index e0619d9f20..dbe07aa600 100644 --- a/crates/ty_python_semantic/src/lib.rs +++ b/crates/ty_python_semantic/src/lib.rs @@ -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; diff --git a/crates/ty_python_semantic/src/member.rs b/crates/ty_python_semantic/src/member.rs deleted file mode 100644 index 0c078d1fb1..0000000000 --- a/crates/ty_python_semantic/src/member.rs +++ /dev/null @@ -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> { - 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, - } - } -} diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index 7495373b09..7e8820cb77 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -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, diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index faed47aacf..1c3aef00c5 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -89,6 +89,7 @@ mod generics; pub mod ide_support; mod infer; mod instance; +mod member; mod mro; mod narrow; mod protocol_class; diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index fd03dba47b..3fe625397a 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -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 diff --git a/crates/ty_python_semantic/src/types/member.rs b/crates/ty_python_semantic/src/types/member.rs new file mode 100644 index 0000000000..3c541b1035 --- /dev/null +++ b/crates/ty_python_semantic/src/types/member.rs @@ -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> { + 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() +}