From 01de8bef3eb4554f6ba4bec0f6b351b1ac4c86f9 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 6 Jan 2026 12:24:51 -0500 Subject: [PATCH] [ty] Add named fields for `Place` enum (#22172) ## Summary Mechanical refactor to migrate this enum to named fields. No functional changes. See: https://github.com/astral-sh/ruff/pull/22093#discussion_r2636050127. --------- Co-authored-by: Claude Opus 4.5 --- crates/ty_python_semantic/src/place.rs | 391 +++++++++++------- .../reachability_constraints.rs | 20 +- crates/ty_python_semantic/src/types.rs | 295 +++++++++---- .../ty_python_semantic/src/types/call/bind.rs | 44 +- crates/ty_python_semantic/src/types/class.rs | 109 +++-- .../src/types/diagnostic.rs | 14 +- .../ty_python_semantic/src/types/display.rs | 4 +- crates/ty_python_semantic/src/types/enums.rs | 33 +- .../ty_python_semantic/src/types/function.rs | 9 +- .../src/types/infer/builder.rs | 248 ++++++----- .../src/types/list_members.rs | 11 +- crates/ty_python_semantic/src/types/member.rs | 11 +- .../ty_python_semantic/src/types/overrides.rs | 13 +- .../src/types/protocol_class.rs | 23 +- .../ty_python_semantic/src/types/relation.rs | 19 +- 15 files changed, 791 insertions(+), 453 deletions(-) diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index f61c24cca9..7734d3a75f 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -88,6 +88,45 @@ impl Widening { } } +/// A defined place with its type, origin, definedness, and widening information. +#[derive(Debug, Clone, Copy, PartialEq, Eq, salsa::Update, get_size2::GetSize)] +pub(crate) struct DefinedPlace<'db> { + pub(crate) ty: Type<'db>, + pub(crate) origin: TypeOrigin, + pub(crate) definedness: Definedness, + pub(crate) widening: Widening, +} + +impl<'db> DefinedPlace<'db> { + pub(crate) fn new(ty: Type<'db>) -> Self { + Self { + ty, + origin: TypeOrigin::Inferred, + definedness: Definedness::AlwaysDefined, + widening: Widening::None, + } + } + + pub(crate) fn with_origin(mut self, origin: TypeOrigin) -> Self { + self.origin = origin; + self + } + + pub(crate) fn with_definedness(mut self, definedness: Definedness) -> Self { + self.definedness = definedness; + self + } + + pub(crate) fn with_widening(mut self, widening: Widening) -> Self { + self.widening = widening; + self + } + + pub(crate) const fn is_definitely_defined(&self) -> bool { + matches!(self.definedness, Definedness::AlwaysDefined) + } +} + /// The result of a place lookup, which can either be a (possibly undefined) type /// or a completely undefined place. /// @@ -110,50 +149,35 @@ impl Widening { /// /// If we look up places in this scope, we would get the following results: /// ```rs -/// bound: Place::Defined(Literal[1], TypeOrigin::Inferred, Definedness::AlwaysDefined, _), -/// declared: Place::Defined(int, TypeOrigin::Declared, Definedness::AlwaysDefined, _), -/// possibly_unbound: Place::Defined(Literal[2], TypeOrigin::Inferred, Definedness::PossiblyUndefined, _), -/// possibly_undeclared: Place::Defined(int, TypeOrigin::Declared, Definedness::PossiblyUndefined, _), -/// bound_or_declared: Place::Defined(Literal[1], TypeOrigin::Inferred, Definedness::PossiblyUndefined, _), +/// bound: Place::Defined(DefinedPlace { ty: Literal[1], origin: TypeOrigin::Inferred, definedness: Definedness::AlwaysDefined, .. }), +/// declared: Place::Defined(DefinedPlace { ty: int, origin: TypeOrigin::Declared, definedness: Definedness::AlwaysDefined, .. }), +/// possibly_unbound: Place::Defined(DefinedPlace { ty: Literal[2], origin: TypeOrigin::Inferred, definedness: Definedness::PossiblyUndefined, .. }), +/// possibly_undeclared: Place::Defined(DefinedPlace { ty: int, origin: TypeOrigin::Declared, definedness: Definedness::PossiblyUndefined, .. }), +/// bound_or_declared: Place::Defined(DefinedPlace { ty: Literal[1], origin: TypeOrigin::Inferred, definedness: Definedness::PossiblyUndefined, .. }), /// non_existent: Place::Undefined, /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, salsa::Update, get_size2::GetSize)] pub(crate) enum Place<'db> { - Defined(Type<'db>, TypeOrigin, Definedness, Widening), + Defined(DefinedPlace<'db>), Undefined, } impl<'db> Place<'db> { /// Constructor that creates a [`Place`] with type origin [`TypeOrigin::Inferred`] and definedness [`Definedness::AlwaysDefined`]. pub(crate) fn bound(ty: impl Into>) -> Self { - Place::Defined( - ty.into(), - TypeOrigin::Inferred, - Definedness::AlwaysDefined, - Widening::None, - ) + Place::Defined(DefinedPlace::new(ty.into())) } /// Constructor that creates a [`Place`] with type origin [`TypeOrigin::Declared`] and definedness [`Definedness::AlwaysDefined`]. pub(crate) fn declared(ty: impl Into>) -> Self { - Place::Defined( - ty.into(), - TypeOrigin::Declared, - Definedness::AlwaysDefined, - Widening::None, - ) + Place::Defined(DefinedPlace::new(ty.into()).with_origin(TypeOrigin::Declared)) } /// Constructor that creates a [`Place`] with a [`crate::types::TodoType`] type /// and definedness [`Definedness::AlwaysDefined`]. #[allow(unused_variables)] // Only unused in release builds pub(crate) fn todo(message: &'static str) -> Self { - Place::Defined( - todo_type!(message), - TypeOrigin::Inferred, - Definedness::AlwaysDefined, - Widening::None, - ) + Place::Defined(DefinedPlace::new(todo_type!(message))) } pub(crate) fn is_undefined(&self) -> bool { @@ -166,7 +190,7 @@ impl<'db> Place<'db> { /// if there is at least one control-flow path where the place is defined, return the type. pub(crate) fn ignore_possibly_undefined(&self) -> Option> { match self { - Place::Defined(ty, _, _, _) => Some(*ty), + Place::Defined(defined) => Some(defined.ty), Place::Undefined => None, } } @@ -177,7 +201,7 @@ impl<'db> Place<'db> { /// is applied lazily when converting to `LookupResult`. pub(crate) fn unwidened_type(&self) -> Option> { match self { - Place::Defined(ty, _, _, _) => Some(*ty), + Place::Defined(defined) => Some(defined.ty), Place::Undefined => None, } } @@ -192,20 +216,19 @@ impl<'db> Place<'db> { #[must_use] pub(crate) fn map_type(self, f: impl FnOnce(Type<'db>) -> Type<'db>) -> Place<'db> { match self { - Place::Defined(ty, origin, definedness, widening) => { - Place::Defined(f(ty), origin, definedness, widening) - } + Place::Defined(defined) => Place::Defined(DefinedPlace { + ty: f(defined.ty), + ..defined + }), Place::Undefined => Place::Undefined, } } /// Set the widening mode for this place. #[must_use] - pub(crate) fn with_widening(self, widening: Widening) -> Place<'db> { + pub(crate) fn with_widening(self, new_widening: Widening) -> Place<'db> { match self { - Place::Defined(ty, origin, definedness, _) => { - Place::Defined(ty, origin, definedness, widening) - } + Place::Defined(defined) => Place::Defined(defined.with_widening(new_widening)), Place::Undefined => Place::Undefined, } } @@ -223,24 +246,32 @@ impl<'db> Place<'db> { /// This is used to resolve (potential) descriptor attributes. pub(crate) fn try_call_dunder_get(self, db: &'db dyn Db, owner: Type<'db>) -> Place<'db> { match self { - Place::Defined(Type::Union(union), origin, definedness, widening) => union - .map_with_boundness(db, |elem| { - Place::Defined(*elem, origin, definedness, widening) - .try_call_dunder_get(db, owner) - }), + Place::Defined( + place @ DefinedPlace { + ty: Type::Union(union), + .. + }, + ) => union.map_with_boundness(db, |elem| { + Place::Defined(DefinedPlace { ty: *elem, ..place }).try_call_dunder_get(db, owner) + }), - Place::Defined(Type::Intersection(intersection), origin, definedness, widening) => { - intersection.map_with_boundness(db, |elem| { - Place::Defined(*elem, origin, definedness, widening) - .try_call_dunder_get(db, owner) - }) - } + Place::Defined( + place @ DefinedPlace { + ty: Type::Intersection(intersection), + .. + }, + ) => intersection.map_with_boundness(db, |elem| { + Place::Defined(DefinedPlace { ty: *elem, ..place }).try_call_dunder_get(db, owner) + }), - Place::Defined(self_ty, origin, definedness, widening) => { + Place::Defined(defined) => { if let Some((dunder_get_return_ty, _)) = - self_ty.try_call_dunder_get(db, Type::none(db), owner) + defined.ty.try_call_dunder_get(db, Type::none(db), owner) { - Place::Defined(dunder_get_return_ty, origin, definedness, widening) + Place::Defined(DefinedPlace { + ty: dunder_get_return_ty, + ..defined + }) } else { self } @@ -251,7 +282,13 @@ impl<'db> Place<'db> { } pub(crate) const fn is_definitely_bound(&self) -> bool { - matches!(self, Place::Defined(_, _, Definedness::AlwaysDefined, _)) + matches!( + self, + Place::Defined(DefinedPlace { + definedness: Definedness::AlwaysDefined, + .. + }) + ) } } @@ -262,10 +299,8 @@ impl<'db> From> for PlaceAndQualifiers<'db> { .with_qualifiers(type_and_qualifiers.qualifiers()), Err(LookupError::Undefined(qualifiers)) => Place::Undefined.with_qualifiers(qualifiers), Err(LookupError::PossiblyUndefined(type_and_qualifiers)) => Place::Defined( - type_and_qualifiers.inner_type(), - TypeOrigin::Inferred, - Definedness::PossiblyUndefined, - Widening::None, + DefinedPlace::new(type_and_qualifiers.inner_type()) + .with_definedness(Definedness::PossiblyUndefined), ) .with_qualifiers(type_and_qualifiers.qualifiers()), } @@ -717,20 +752,17 @@ impl<'db> PlaceAndQualifiers<'db> { pub(crate) fn into_lookup_result(self, db: &'db dyn Db) -> LookupResult<'db> { match self { PlaceAndQualifiers { - place: Place::Defined(ty, origin, Definedness::AlwaysDefined, widening), + place: Place::Defined(place), qualifiers, } => { - let ty = widening.apply_if_needed(db, ty); - Ok(TypeAndQualifiers::new(ty, origin, qualifiers)) - } - PlaceAndQualifiers { - place: Place::Defined(ty, origin, Definedness::PossiblyUndefined, widening), - qualifiers, - } => { - let ty = widening.apply_if_needed(db, ty); - Err(LookupError::PossiblyUndefined(TypeAndQualifiers::new( - ty, origin, qualifiers, - ))) + let ty = place.widening.apply_if_needed(db, place.ty); + let type_and_qualifiers = TypeAndQualifiers::new(ty, place.origin, qualifiers); + match place.definedness { + Definedness::AlwaysDefined => Ok(type_and_qualifiers), + Definedness::PossiblyUndefined => { + Err(LookupError::PossiblyUndefined(type_and_qualifiers)) + } + } } PlaceAndQualifiers { place: Place::Undefined, @@ -783,15 +815,10 @@ impl<'db> PlaceAndQualifiers<'db> { let place = match (previous_place.place, self.place) { // In fixed-point iteration of type inference, the member type must be monotonically widened and not "oscillate". // Here, monotonicity is guaranteed by pre-unioning the type of the previous iteration into the current result. - ( - Place::Defined(prev_ty, _, _, _), - Place::Defined(ty, origin, definedness, widening), - ) => Place::Defined( - ty.cycle_normalized(db, prev_ty, cycle), - origin, - definedness, - widening, - ), + (Place::Defined(prev), Place::Defined(current)) => Place::Defined(DefinedPlace { + ty: current.ty.cycle_normalized(db, prev.ty, cycle), + ..current + }), // If a `Place` in the current cycle is `Defined` but `Undefined` in the previous cycle, // that means that its definedness depends on the truthiness of the previous cycle value. // In this case, the definedness of the current cycle `Place` is set to `PossiblyUndefined`. @@ -799,26 +826,22 @@ impl<'db> PlaceAndQualifiers<'db> { // so convergence is guaranteed without resorting to this handling. // However, the handling described above may reduce the exactness of reachability analysis, // so it may be better to remove it. In that case, this branch is necessary. - (Place::Undefined, Place::Defined(ty, origin, _definedness, widening)) => { - Place::Defined( - ty.recursive_type_normalized(db, cycle), - origin, - Definedness::PossiblyUndefined, - widening, - ) - } + (Place::Undefined, Place::Defined(current)) => Place::Defined(DefinedPlace { + ty: current.ty.recursive_type_normalized(db, cycle), + definedness: Definedness::PossiblyUndefined, + ..current + }), // If a `Place` that was `Defined(Divergent)` in the previous cycle is actually found to be unreachable in the current cycle, // it is set to `Undefined` (because the cycle initial value does not include meaningful reachability information). - (Place::Defined(ty, origin, _definedness, widening), Place::Undefined) => { - if cycle.head_ids().any(|id| ty == Type::divergent(id)) { + (Place::Defined(prev), Place::Undefined) => { + if cycle.head_ids().any(|id| prev.ty == Type::divergent(id)) { Place::Undefined } else { - Place::Defined( - ty.recursive_type_normalized(db, cycle), - origin, - Definedness::PossiblyUndefined, - widening, - ) + Place::Defined(DefinedPlace { + ty: prev.ty.recursive_type_normalized(db, cycle), + definedness: Definedness::PossiblyUndefined, + ..prev + }) } } (Place::Undefined, Place::Undefined) => Place::Undefined, @@ -900,32 +923,56 @@ pub(crate) fn place_by_id<'db>( // Handle bare `ClassVar` annotations by falling back to the union of `Unknown` and the // inferred type. PlaceAndQualifiers { - place: Place::Defined(Type::Dynamic(DynamicType::Unknown), origin, definedness, _), + place: + Place::Defined(DefinedPlace { + ty: Type::Dynamic(DynamicType::Unknown), + origin, + definedness, + .. + }), qualifiers, } if qualifiers.contains(TypeQualifiers::CLASS_VAR) => { let bindings = all_considered_bindings(); match place_from_bindings_impl(db, bindings, requires_explicit_reexport).place { - Place::Defined(inferred, origin, boundness, _) => Place::Defined( - UnionType::from_elements(db, [Type::unknown(), inferred]), + Place::Defined(DefinedPlace { + ty: inferred, origin, - boundness, - Widening::None, - ) + definedness: boundness, + .. + }) => Place::Defined(DefinedPlace { + ty: UnionType::from_elements(db, [Type::unknown(), inferred]), + origin, + definedness: boundness, + widening: Widening::None, + }) + .with_qualifiers(qualifiers), + Place::Undefined => Place::Defined(DefinedPlace { + ty: Type::unknown(), + origin, + definedness, + widening: Widening::None, + }) .with_qualifiers(qualifiers), - Place::Undefined => { - Place::Defined(Type::unknown(), origin, definedness, Widening::None) - .with_qualifiers(qualifiers) - } } } // Place is declared, trust the declared type place_and_quals @ PlaceAndQualifiers { - place: Place::Defined(_, _, Definedness::AlwaysDefined, _), + place: + Place::Defined(DefinedPlace { + definedness: Definedness::AlwaysDefined, + .. + }), qualifiers: _, } => place_and_quals, // Place is possibly declared PlaceAndQualifiers { - place: Place::Defined(declared_ty, origin, Definedness::PossiblyUndefined, _), + place: + Place::Defined(DefinedPlace { + ty: declared_ty, + origin, + definedness: Definedness::PossiblyUndefined, + .. + }), qualifiers, } => { let bindings = all_considered_bindings(); @@ -938,24 +985,29 @@ pub(crate) fn place_by_id<'db>( // TODO: We probably don't want to report `AlwaysDefined` here. This requires a bit of // design work though as we might want a different behavior for stubs and for // normal modules. - Place::Defined( - declared_ty, + Place::Defined(DefinedPlace { + ty: declared_ty, origin, - Definedness::AlwaysDefined, - Widening::None, - ) + definedness: Definedness::AlwaysDefined, + widening: Widening::None, + }) } // Place is possibly undeclared and (possibly) bound - Place::Defined(inferred_ty, origin, boundness, _) => Place::Defined( - UnionType::from_elements(db, [inferred_ty, declared_ty]), + Place::Defined(DefinedPlace { + ty: inferred_ty, origin, - if boundness_analysis == BoundnessAnalysis::AssumeBound { + definedness: boundness, + .. + }) => Place::Defined(DefinedPlace { + ty: UnionType::from_elements(db, [inferred_ty, declared_ty]), + origin, + definedness: if boundness_analysis == BoundnessAnalysis::AssumeBound { Definedness::AlwaysDefined } else { boundness }, - Widening::None, - ), + widening: Widening::None, + }), }; PlaceAndQualifiers { place, qualifiers } @@ -971,10 +1023,11 @@ pub(crate) fn place_by_id<'db>( place_from_bindings_impl(db, bindings, requires_explicit_reexport).place; if boundness_analysis == BoundnessAnalysis::AssumeBound { - if let Place::Defined(ty, origin, Definedness::PossiblyUndefined, widening) = - inferred - { - inferred = Place::Defined(ty, origin, Definedness::AlwaysDefined, widening); + if let Place::Defined(defined) = inferred { + if defined.definedness == Definedness::PossiblyUndefined { + inferred = + Place::Defined(defined.with_definedness(Definedness::AlwaysDefined)); + } } } @@ -1263,14 +1316,11 @@ fn place_from_bindings_impl<'db>( match deleted_reachability { Truthiness::AlwaysFalse => { - Place::Defined(ty, TypeOrigin::Inferred, boundness, Widening::None) + Place::Defined(DefinedPlace::new(ty).with_definedness(boundness)) } Truthiness::AlwaysTrue => Place::Undefined, Truthiness::Ambiguous => Place::Defined( - ty, - TypeOrigin::Inferred, - Definedness::PossiblyUndefined, - Widening::None, + DefinedPlace::new(ty).with_definedness(Definedness::PossiblyUndefined), ), } } else { @@ -1501,10 +1551,9 @@ fn place_from_declarations_impl<'db>( }; let place_and_quals = Place::Defined( - declared.inner_type(), - TypeOrigin::Declared, - boundness, - Widening::None, + DefinedPlace::new(declared.inner_type()) + .with_origin(TypeOrigin::Declared) + .with_definedness(boundness), ) .with_qualifiers(declared.qualifiers()); @@ -1554,13 +1603,13 @@ mod implicit_globals { use crate::Program; use crate::db::Db; - use crate::place::{Definedness, PlaceAndQualifiers, TypeOrigin}; + use crate::place::{Definedness, PlaceAndQualifiers}; use crate::semantic_index::symbol::Symbol; use crate::semantic_index::{place_table, use_def_map}; use crate::types::{KnownClass, MemberLookupPolicy, Parameter, Parameters, Signature, Type}; use ruff_python_ast::PythonVersion; - use super::{Place, Widening, place_from_declarations}; + use super::{DefinedPlace, Place, place_from_declarations}; pub(crate) fn module_type_implicit_global_declaration<'db>( db: &'db dyn Db, @@ -1618,14 +1667,16 @@ mod implicit_globals { // Created lazily by the warnings machinery; may be absent. // Model as possibly-unbound to avoid false negatives. - "__warningregistry__" => Place::Defined( - KnownClass::Dict - .to_specialized_instance(db, [Type::any(), KnownClass::Int.to_instance(db)]), - TypeOrigin::Inferred, - Definedness::PossiblyUndefined, - Widening::None, - ) - .into(), + "__warningregistry__" => { + Place::Defined( + DefinedPlace::new(KnownClass::Dict.to_specialized_instance( + db, + [Type::any(), KnownClass::Int.to_instance(db)], + )) + .with_definedness(Definedness::PossiblyUndefined), + ) + .into() + } // Marked as possibly-unbound as it is only present in the module namespace // if at least one global symbol is annotated in the module. @@ -1642,10 +1693,8 @@ mod implicit_globals { )), ); Place::Defined( - Type::function_like_callable(db, signature), - TypeOrigin::Inferred, - Definedness::PossiblyUndefined, - Widening::None, + DefinedPlace::new(Type::function_like_callable(db, signature)) + .with_definedness(Definedness::PossiblyUndefined), ) .into() } @@ -1847,21 +1896,41 @@ mod tests { let unbound = || Place::Undefined.with_qualifiers(TypeQualifiers::empty()); let possibly_unbound_ty1 = || { - Place::Defined(ty1, Inferred, PossiblyUndefined, Widening::None) - .with_qualifiers(TypeQualifiers::empty()) + Place::Defined(DefinedPlace { + ty: ty1, + origin: Inferred, + definedness: PossiblyUndefined, + widening: Widening::None, + }) + .with_qualifiers(TypeQualifiers::empty()) }; let possibly_unbound_ty2 = || { - Place::Defined(ty2, Inferred, PossiblyUndefined, Widening::None) - .with_qualifiers(TypeQualifiers::empty()) + Place::Defined(DefinedPlace { + ty: ty2, + origin: Inferred, + definedness: PossiblyUndefined, + widening: Widening::None, + }) + .with_qualifiers(TypeQualifiers::empty()) }; let bound_ty1 = || { - Place::Defined(ty1, Inferred, AlwaysDefined, Widening::None) - .with_qualifiers(TypeQualifiers::empty()) + Place::Defined(DefinedPlace { + ty: ty1, + origin: Inferred, + definedness: AlwaysDefined, + widening: Widening::None, + }) + .with_qualifiers(TypeQualifiers::empty()) }; let bound_ty2 = || { - Place::Defined(ty2, Inferred, AlwaysDefined, Widening::None) - .with_qualifiers(TypeQualifiers::empty()) + Place::Defined(DefinedPlace { + ty: ty2, + origin: Inferred, + definedness: AlwaysDefined, + widening: Widening::None, + }) + .with_qualifiers(TypeQualifiers::empty()) }; // Start from an unbound symbol @@ -1879,22 +1948,22 @@ mod tests { ); assert_eq!( possibly_unbound_ty1().or_fall_back_to(&db, possibly_unbound_ty2), - Place::Defined( - UnionType::from_elements(&db, [ty1, ty2]), - Inferred, - PossiblyUndefined, - Widening::None - ) + Place::Defined(DefinedPlace { + ty: UnionType::from_elements(&db, [ty1, ty2]), + origin: Inferred, + definedness: PossiblyUndefined, + widening: Widening::None + }) .into() ); assert_eq!( possibly_unbound_ty1().or_fall_back_to(&db, bound_ty2), - Place::Defined( - UnionType::from_elements(&db, [ty1, ty2]), - Inferred, - AlwaysDefined, - Widening::None - ) + Place::Defined(DefinedPlace { + ty: UnionType::from_elements(&db, [ty1, ty2]), + origin: Inferred, + definedness: AlwaysDefined, + widening: Widening::None + }) .into() ); @@ -1911,7 +1980,11 @@ mod tests { fn assert_bound_string_symbol<'db>(db: &'db dyn Db, symbol: Place<'db>) { assert!(matches!( symbol, - Place::Defined(Type::NominalInstance(_), _, Definedness::AlwaysDefined, _) + Place::Defined(DefinedPlace { + ty: Type::NominalInstance(_), + definedness: Definedness::AlwaysDefined, + .. + }) )); assert_eq!(symbol.expect_type(), KnownClass::Str.to_instance(db)); } diff --git a/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs index e76a83e47a..ef6fb07481 100644 --- a/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs @@ -953,18 +953,14 @@ impl ReachabilityConstraints { ) .place { - crate::place::Place::Defined( - _, - _, - crate::place::Definedness::AlwaysDefined, - _, - ) => Truthiness::AlwaysTrue, - crate::place::Place::Defined( - _, - _, - crate::place::Definedness::PossiblyUndefined, - _, - ) => Truthiness::Ambiguous, + crate::place::Place::Defined(crate::place::DefinedPlace { + definedness: crate::place::Definedness::AlwaysDefined, + .. + }) => Truthiness::AlwaysTrue, + crate::place::Place::Defined(crate::place::DefinedPlace { + definedness: crate::place::Definedness::PossiblyUndefined, + .. + }) => Truthiness::Ambiguous, crate::place::Place::Undefined => Truthiness::AlwaysFalse, } } diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index c24f375b6a..a447601574 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -37,8 +37,8 @@ pub(crate) use self::signatures::{CallableSignature, Signature}; pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType}; pub use crate::diagnostic::add_inferred_python_version_hint_to_diagnostic; use crate::place::{ - Definedness, Place, PlaceAndQualifiers, TypeOrigin, Widening, builtins_module_scope, - imported_symbol, known_module_symbol, + DefinedPlace, Definedness, Place, PlaceAndQualifiers, TypeOrigin, Widening, + builtins_module_scope, imported_symbol, known_module_symbol, }; use crate::semantic_index::definition::{Definition, DefinitionKind}; use crate::semantic_index::place::ScopedPlaceId; @@ -1849,8 +1849,10 @@ impl<'db> Type<'db> { ) .place; - if let Place::Defined(ty, _, Definedness::AlwaysDefined, _) = call_symbol { - ty.try_upcast_to_callable(db) + if let Place::Defined(place) = call_symbol + && place.is_definitely_defined() + { + place.ty.try_upcast_to_callable(db) } else { None } @@ -2540,10 +2542,9 @@ impl<'db> Type<'db> { fn static_member(&self, db: &'db dyn Db, name: &str) -> Place<'db> { if let Type::ModuleLiteral(module) = self { module.static_member(db, name).place - } else if let place @ Place::Defined(_, _, _, _) = self.class_member(db, name.into()).place - { + } else if let place @ Place::Defined(_) = self.class_member(db, name.into()).place { place - } else if let Some(place @ Place::Defined(_, _, _, _)) = + } else if let Some(place @ Place::Defined(_)) = self.find_name_in_mro(db, name).map(|inner| inner.place) { place @@ -2604,7 +2605,12 @@ impl<'db> Type<'db> { let descr_get = self.class_member(db, "__get__".into()).place; - if let Place::Defined(descr_get, _, descr_get_boundness, _) = descr_get { + if let Place::Defined(DefinedPlace { + ty: descr_get, + definedness: descr_get_boundness, + .. + }) = descr_get + { let return_ty = descr_get .try_call(db, &CallArguments::positional([self, instance, owner])) .map(|bindings| { @@ -2649,23 +2655,34 @@ impl<'db> Type<'db> { // // The same is true for `Never`. PlaceAndQualifiers { - place: Place::Defined(Type::Dynamic(_) | Type::Never, _, _, _), + place: + Place::Defined(DefinedPlace { + ty: Type::Dynamic(_) | Type::Never, + .. + }), qualifiers: _, } => (attribute, AttributeKind::DataDescriptor), PlaceAndQualifiers { - place: Place::Defined(Type::Union(union), origin, boundness, widening), + place: + Place::Defined(DefinedPlace { + ty: Type::Union(union), + origin, + definedness: boundness, + widening, + }), qualifiers, } => ( union .map_with_boundness(db, |elem| { - Place::Defined( - elem.try_call_dunder_get(db, instance, owner) + Place::Defined(DefinedPlace { + ty: elem + .try_call_dunder_get(db, instance, owner) .map_or(*elem, |(ty, _)| ty), origin, - boundness, + definedness: boundness, widening, - ) + }) }) .with_qualifiers(qualifiers), // TODO: avoid the duplication here: @@ -2680,18 +2697,25 @@ impl<'db> Type<'db> { ), PlaceAndQualifiers { - place: Place::Defined(Type::Intersection(intersection), origin, boundness, widening), + place: + Place::Defined(DefinedPlace { + ty: Type::Intersection(intersection), + origin, + definedness: boundness, + widening, + }), qualifiers, } => ( intersection .map_with_boundness(db, |elem| { - Place::Defined( - elem.try_call_dunder_get(db, instance, owner) + Place::Defined(DefinedPlace { + ty: elem + .try_call_dunder_get(db, instance, owner) .map_or(*elem, |(ty, _)| ty), origin, - boundness, + definedness: boundness, widening, - ) + }) }) .with_qualifiers(qualifiers), // TODO: Discover data descriptors in intersections. @@ -2699,14 +2723,26 @@ impl<'db> Type<'db> { ), PlaceAndQualifiers { - place: Place::Defined(attribute_ty, origin, boundness, widening), + place: + Place::Defined(DefinedPlace { + ty: attribute_ty, + origin, + definedness: boundness, + widening, + }), qualifiers: _, } => { if let Some((return_ty, attribute_kind)) = attribute_ty.try_call_dunder_get(db, instance, owner) { ( - Place::Defined(return_ty, origin, boundness, widening).into(), + Place::Defined(DefinedPlace { + ty: return_ty, + origin, + definedness: boundness, + widening, + }) + .into(), attribute_kind, ) } else { @@ -2799,14 +2835,17 @@ impl<'db> Type<'db> { match (meta_attr, meta_attr_kind, fallback) { // The fallback type is unbound, so we can just return `meta_attr` unconditionally, // no matter if it's data descriptor, a non-data descriptor, or a normal attribute. - (meta_attr @ Place::Defined(_, _, _, _), _, Place::Undefined) => { + (meta_attr @ Place::Defined(_), _, Place::Undefined) => { meta_attr.with_qualifiers(meta_attr_qualifiers) } // `meta_attr` is the return type of a data descriptor and definitely bound, so we // return it. ( - meta_attr @ Place::Defined(_, _, Definedness::AlwaysDefined, _), + meta_attr @ Place::Defined(DefinedPlace { + definedness: Definedness::AlwaysDefined, + .. + }), AttributeKind::DataDescriptor, _, ) => meta_attr.with_qualifiers(meta_attr_qualifiers), @@ -2815,15 +2854,25 @@ impl<'db> Type<'db> { // meta-type is possibly-unbound. This means that we "fall through" to the next // stage of the descriptor protocol and union with the fallback type. ( - Place::Defined(meta_attr_ty, meta_origin, Definedness::PossiblyUndefined, _), + Place::Defined(DefinedPlace { + ty: meta_attr_ty, + origin: meta_origin, + definedness: Definedness::PossiblyUndefined, + .. + }), AttributeKind::DataDescriptor, - Place::Defined(fallback_ty, fallback_origin, fallback_boundness, fallback_widening), - ) => Place::Defined( - UnionType::from_elements(db, [meta_attr_ty, fallback_ty]), - meta_origin.merge(fallback_origin), - fallback_boundness, - fallback_widening, - ) + Place::Defined(DefinedPlace { + ty: fallback_ty, + origin: fallback_origin, + definedness: fallback_boundness, + widening: fallback_widening, + }), + ) => Place::Defined(DefinedPlace { + ty: UnionType::from_elements(db, [meta_attr_ty, fallback_ty]), + origin: meta_origin.merge(fallback_origin), + definedness: fallback_boundness, + widening: fallback_widening, + }) .with_qualifiers(meta_attr_qualifiers.union(fallback_qualifiers)), // `meta_attr` is *not* a data descriptor. This means that the `fallback` type has @@ -2835,9 +2884,12 @@ impl<'db> Type<'db> { // would require us to statically infer if an instance attribute is always set, which // is something we currently don't attempt to do. ( - Place::Defined(_, _, _, _), + Place::Defined(_), AttributeKind::NormalOrNonDataDescriptor, - fallback @ Place::Defined(_, _, Definedness::AlwaysDefined, _), + fallback @ Place::Defined(DefinedPlace { + definedness: Definedness::AlwaysDefined, + .. + }), ) if policy == InstanceFallbackShadowsNonDataDescriptor::Yes => { fallback.with_qualifiers(fallback_qualifiers) } @@ -2846,15 +2898,25 @@ impl<'db> Type<'db> { // unbound or the policy argument is `No`. In both cases, the `fallback` type does // not completely shadow the non-data descriptor, so we build a union of the two. ( - Place::Defined(meta_attr_ty, meta_origin, meta_attr_boundness, _), + Place::Defined(DefinedPlace { + ty: meta_attr_ty, + origin: meta_origin, + definedness: meta_attr_boundness, + .. + }), AttributeKind::NormalOrNonDataDescriptor, - Place::Defined(fallback_ty, fallback_origin, fallback_boundness, fallback_widening), - ) => Place::Defined( - UnionType::from_elements(db, [meta_attr_ty, fallback_ty]), - meta_origin.merge(fallback_origin), - meta_attr_boundness.max(fallback_boundness), - fallback_widening, - ) + Place::Defined(DefinedPlace { + ty: fallback_ty, + origin: fallback_origin, + definedness: fallback_boundness, + widening: fallback_widening, + }), + ) => Place::Defined(DefinedPlace { + ty: UnionType::from_elements(db, [meta_attr_ty, fallback_ty]), + origin: meta_origin.merge(fallback_origin), + definedness: meta_attr_boundness.max(fallback_boundness), + widening: fallback_widening, + }) .with_qualifiers(meta_attr_qualifiers.union(fallback_qualifiers)), // If the attribute is not found on the meta-type, we simply return the fallback. @@ -3218,11 +3280,19 @@ impl<'db> Type<'db> { match result { member @ PlaceAndQualifiers { - place: Place::Defined(_, _, Definedness::AlwaysDefined, _), + place: + Place::Defined(DefinedPlace { + definedness: Definedness::AlwaysDefined, + .. + }), qualifiers: _, } => member, member @ PlaceAndQualifiers { - place: Place::Defined(_, _, Definedness::PossiblyUndefined, _), + place: + Place::Defined(DefinedPlace { + definedness: Definedness::PossiblyUndefined, + .. + }), qualifiers: _, } => member .or_fall_back_to(db, custom_getattribute_result) @@ -4361,7 +4431,11 @@ impl<'db> Type<'db> { ) .place { - Place::Defined(dunder_callable, _, boundness, _) => { + Place::Defined(DefinedPlace { + ty: dunder_callable, + definedness: boundness, + .. + }) => { let mut bindings = dunder_callable.bindings(db); bindings.replace_callable_type(dunder_callable, self); if boundness == Definedness::PossiblyUndefined { @@ -4514,7 +4588,11 @@ impl<'db> Type<'db> { ) .place { - Place::Defined(dunder_callable, _, boundness, _) => { + Place::Defined(DefinedPlace { + ty: dunder_callable, + definedness: boundness, + .. + }) => { let bindings = dunder_callable .bindings(db) .match_parameters(db, argument_types) @@ -5091,18 +5169,26 @@ impl<'db> Type<'db> { new_method.as_ref().map(|method| &method.place), &init_method.place, ) { - (Some(Place::Defined(new_method, ..)), Place::Undefined) => Some( + (Some(Place::Defined(DefinedPlace { ty: new_method, .. })), Place::Undefined) => Some( new_method .bindings(db) .map(|binding| binding.with_bound_type(self_type)), ), - (Some(Place::Undefined) | None, Place::Defined(init_method, ..)) => { - Some(init_method.bindings(db)) - } + ( + Some(Place::Undefined) | None, + Place::Defined(DefinedPlace { + ty: init_method, .. + }), + ) => Some(init_method.bindings(db)), - (Some(Place::Defined(new_method, ..)), Place::Defined(init_method, ..)) => { - let callable = UnionType::from_elements(db, [new_method, init_method]); + ( + Some(Place::Defined(DefinedPlace { ty: new_method, .. })), + Place::Defined(DefinedPlace { + ty: init_method, .. + }), + ) => { + let callable = UnionType::from_elements(db, [*new_method, *init_method]); let new_method_bindings = new_method .bindings(db) @@ -5121,7 +5207,11 @@ impl<'db> Type<'db> { let new_call_outcome = new_method.and_then(|new_method| { match new_method.place.try_call_dunder_get(db, self_type) { - Place::Defined(new_method, _, boundness, _) => { + Place::Defined(DefinedPlace { + ty: new_method, + definedness: boundness, + .. + }) => { let argument_types = argument_types.with_self(Some(self_type)); let result = new_method .bindings(db) @@ -5149,7 +5239,11 @@ impl<'db> Type<'db> { .place { Place::Undefined => Err(CallDunderError::MethodNotAvailable), - Place::Defined(dunder_callable, _, boundness, _) => { + Place::Defined(DefinedPlace { + ty: dunder_callable, + definedness: boundness, + .. + }) => { let bindings = dunder_callable .bindings(db) .with_constructor_instance_type(init_ty); @@ -8624,7 +8718,12 @@ impl<'db> TypeVarConstraints<'db> { Place::Undefined => { possibly_unbound = true; } - Place::Defined(ty_member, member_origin, member_boundness, _) => { + Place::Defined(DefinedPlace { + ty: ty_member, + origin: member_origin, + definedness: member_boundness, + .. + }) => { origin = origin.merge(member_origin); if member_boundness == Definedness::PossiblyUndefined { possibly_unbound = true; @@ -8639,16 +8738,16 @@ impl<'db> TypeVarConstraints<'db> { place: if all_unbound { Place::Undefined } else { - Place::Defined( - builder.build(), + Place::Defined(DefinedPlace { + ty: builder.build(), origin, - if possibly_unbound { + definedness: if possibly_unbound { Definedness::PossiblyUndefined } else { Definedness::AlwaysDefined }, - Widening::None, - ) + widening: Widening::None, + }) }, qualifiers, } @@ -11227,15 +11326,17 @@ impl<'db> ModuleLiteralType<'db> { // if it exists. First, we need to look up the `__getattr__` function in the module's scope. if let Some(file) = self.module(db).file(db) { let getattr_symbol = imported_symbol(db, file, "__getattr__", None); - if let Place::Defined(getattr_type, origin, boundness, widening) = getattr_symbol.place - { + if let Place::Defined(place) = getattr_symbol.place { // If we found a __getattr__ function, try to call it with the name argument - if let Ok(outcome) = getattr_type.try_call( + if let Ok(outcome) = place.ty.try_call( db, &CallArguments::positional([Type::string_literal(db, name)]), ) { return PlaceAndQualifiers { - place: Place::Defined(outcome.return_type(db), origin, boundness, widening), + place: Place::Defined(DefinedPlace { + ty: outcome.return_type(db), + ..place + }), qualifiers: TypeQualifiers::FROM_MODULE_GETATTR, }; } @@ -11772,7 +11873,12 @@ impl<'db> UnionType<'db> { Place::Undefined => { possibly_unbound = true; } - Place::Defined(ty_member, member_origin, member_boundness, _) => { + Place::Defined(DefinedPlace { + ty: ty_member, + origin: member_origin, + definedness: member_boundness, + .. + }) => { origin = origin.merge(member_origin); if member_boundness == Definedness::PossiblyUndefined { possibly_unbound = true; @@ -11787,18 +11893,18 @@ impl<'db> UnionType<'db> { if all_unbound { Place::Undefined } else { - Place::Defined( - builder + Place::Defined(DefinedPlace { + ty: builder .recursively_defined(self.recursively_defined(db)) .build(), origin, - if possibly_unbound { + definedness: if possibly_unbound { Definedness::PossiblyUndefined } else { Definedness::AlwaysDefined }, - Widening::None, - ) + widening: Widening::None, + }) } } @@ -11823,7 +11929,12 @@ impl<'db> UnionType<'db> { Place::Undefined => { possibly_unbound = true; } - Place::Defined(ty_member, member_origin, member_boundness, _) => { + Place::Defined(DefinedPlace { + ty: ty_member, + origin: member_origin, + definedness: member_boundness, + .. + }) => { origin = origin.merge(member_origin); if member_boundness == Definedness::PossiblyUndefined { possibly_unbound = true; @@ -11838,18 +11949,18 @@ impl<'db> UnionType<'db> { place: if all_unbound { Place::Undefined } else { - Place::Defined( - builder + Place::Defined(DefinedPlace { + ty: builder .recursively_defined(self.recursively_defined(db)) .build(), origin, - if possibly_unbound { + definedness: if possibly_unbound { Definedness::PossiblyUndefined } else { Definedness::AlwaysDefined }, - Widening::None, - ) + widening: Widening::None, + }) }, qualifiers, } @@ -12407,7 +12518,12 @@ impl<'db> IntersectionType<'db> { let ty_member = transform_fn(&ty); match ty_member { Place::Undefined => {} - Place::Defined(ty_member, member_origin, member_boundness, _) => { + Place::Defined(DefinedPlace { + ty: ty_member, + origin: member_origin, + definedness: member_boundness, + .. + }) => { origin = origin.merge(member_origin); all_unbound = false; if member_boundness == Definedness::AlwaysDefined { @@ -12422,16 +12538,16 @@ impl<'db> IntersectionType<'db> { if all_unbound { Place::Undefined } else { - Place::Defined( - builder.build(), + Place::Defined(DefinedPlace { + ty: builder.build(), origin, - if any_definitely_bound { + definedness: if any_definitely_bound { Definedness::AlwaysDefined } else { Definedness::PossiblyUndefined }, - Widening::None, - ) + widening: Widening::None, + }) } } @@ -12454,7 +12570,12 @@ impl<'db> IntersectionType<'db> { qualifiers |= new_qualifiers; match member { Place::Undefined => {} - Place::Defined(ty_member, member_origin, member_boundness, _) => { + Place::Defined(DefinedPlace { + ty: ty_member, + origin: member_origin, + definedness: member_boundness, + .. + }) => { origin = origin.merge(member_origin); all_unbound = false; if member_boundness == Definedness::AlwaysDefined { @@ -12470,16 +12591,16 @@ impl<'db> IntersectionType<'db> { place: if all_unbound { Place::Undefined } else { - Place::Defined( - builder.build(), + Place::Defined(DefinedPlace { + ty: builder.build(), origin, - if any_definitely_bound { + definedness: if any_definitely_bound { Definedness::AlwaysDefined } else { Definedness::PossiblyUndefined }, - Widening::None, - ) + widening: Widening::None, + }) }, qualifiers, } diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 78f29de858..152d2070bf 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -23,7 +23,7 @@ use smallvec::{SmallVec, smallvec, smallvec_inline}; use super::{Argument, CallArguments, CallError, CallErrorKind, InferContext, Signature, Type}; use crate::db::Db; use crate::dunder_all::dunder_all_names; -use crate::place::{Definedness, Place, known_module_symbol}; +use crate::place::{DefinedPlace, Definedness, Place, known_module_symbol}; use crate::types::call::arguments::{Expansion, is_expandable_type}; use crate::types::constraints::ConstraintSet; use crate::types::diagnostic::{ @@ -1022,7 +1022,11 @@ impl<'db> Bindings<'db> { // TODO: we could emit a diagnostic here (if default is not set) overload.set_return_type( match instance_ty.static_member(db, attr_name.value(db)) { - Place::Defined(ty, _, Definedness::AlwaysDefined, _) => { + Place::Defined(DefinedPlace { + ty, + definedness: Definedness::AlwaysDefined, + .. + }) => { if ty.is_dynamic() { // Here, we attempt to model the fact that an attribute lookup on // a dynamic type could fail @@ -1032,9 +1036,11 @@ impl<'db> Bindings<'db> { ty } } - Place::Defined(ty, _, Definedness::PossiblyUndefined, _) => { - union_with_default(ty) - } + Place::Defined(DefinedPlace { + ty, + definedness: Definedness::PossiblyUndefined, + .. + }) => union_with_default(ty), Place::Undefined => default, }, ); @@ -2829,7 +2835,11 @@ impl<'a, 'db> ArgumentMatcher<'a, 'db> { ) .place { - Place::Defined(getitem_method, _, Definedness::AlwaysDefined, _) => getitem_method + Place::Defined(DefinedPlace { + ty: getitem_method, + definedness: Definedness::AlwaysDefined, + .. + }) => getitem_method .try_call(db, &CallArguments::positional([Type::unknown()])) .ok() .map_or_else(Type::unknown, |bindings| bindings.return_type(db)), @@ -3436,7 +3446,11 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { ) .place { - Place::Defined(keys_method, _, Definedness::AlwaysDefined, _) => keys_method + Place::Defined(DefinedPlace { + ty: keys_method, + definedness: Definedness::AlwaysDefined, + .. + }) => keys_method .try_call(self.db, &CallArguments::none()) .ok() .and_then(|bindings| { @@ -3482,14 +3496,14 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { ) .place { - Place::Defined(keys_method, _, Definedness::AlwaysDefined, _) => { - keys_method - .try_call(self.db, &CallArguments::positional([Type::unknown()])) - .ok() - .map_or_else(Type::unknown, |bindings| { - bindings.return_type(self.db) - }) - } + Place::Defined(DefinedPlace { + ty: getitem_method, + definedness: Definedness::AlwaysDefined, + .. + }) => getitem_method + .try_call(self.db, &CallArguments::positional([Type::unknown()])) + .ok() + .map_or_else(Type::unknown, |bindings| bindings.return_type(self.db)), _ => Type::unknown(), }, ) diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 3c1864decc..e69d0a6fc4 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -8,7 +8,7 @@ use super::{ SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, function::FunctionType, }; -use crate::place::TypeOrigin; +use crate::place::{DefinedPlace, TypeOrigin}; use crate::semantic_index::definition::{Definition, DefinitionState}; use crate::semantic_index::scope::{NodeWithScopeKind, Scope, ScopeKind}; use crate::semantic_index::symbol::Symbol; @@ -1186,8 +1186,10 @@ impl<'db> ClassType<'db> { ) .place; - if let Place::Defined(Type::BoundMethod(metaclass_dunder_call_function), _, _, _) = - metaclass_dunder_call_function_symbol + if let Place::Defined(DefinedPlace { + ty: Type::BoundMethod(metaclass_dunder_call_function), + .. + }) = metaclass_dunder_call_function_symbol { // TODO: this intentionally diverges from step 1 in // https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable @@ -1249,7 +1251,7 @@ impl<'db> ClassType<'db> { // If the class defines an `__init__` method, then we synthesize a callable type with the // same parameters as the `__init__` method after it is bound, and with the return type of // the concrete type of `Self`. - let synthesized_dunder_init_callable = if let Place::Defined(ty, _, _, _) = + let synthesized_dunder_init_callable = if let Place::Defined(DefinedPlace { ty, .. }) = dunder_init_function_symbol { let signature = match ty { @@ -1324,8 +1326,10 @@ impl<'db> ClassType<'db> { ) .place; - if let Place::Defined(Type::FunctionLiteral(mut new_function), _, _, _) = - new_function_symbol + if let Place::Defined(DefinedPlace { + ty: Type::FunctionLiteral(mut new_function), + .. + }) = new_function_symbol { if let Some(class_generic_context) = class_generic_context { new_function = @@ -2264,7 +2268,7 @@ impl<'db> ClassLiteral<'db> { ( PlaceAndQualifiers { - place: Place::Defined(ty, _, _, _), + place: Place::Defined(DefinedPlace { ty, .. }), qualifiers, }, Some(dynamic_type), @@ -2513,8 +2517,11 @@ impl<'db> ClassLiteral<'db> { } let dunder_set = field_ty.class_member(db, "__set__".into()); - if let Place::Defined(dunder_set, _, Definedness::AlwaysDefined, _) = - dunder_set.place + if let Place::Defined(DefinedPlace { + ty: dunder_set, + definedness: Definedness::AlwaysDefined, + .. + }) = dunder_set.place { // The descriptor handling below is guarded by this not-dynamic check, because // dynamic types like `Any` are valid (data) descriptors: since they have all @@ -3426,7 +3433,13 @@ impl<'db> ClassLiteral<'db> { } ClassBase::Class(class) => { if let member @ PlaceAndQualifiers { - place: Place::Defined(ty, origin, boundness, _), + place: + Place::Defined(DefinedPlace { + ty, + origin, + definedness: boundness, + .. + }), qualifiers, } = class.own_instance_member(db, name).inner { @@ -3478,12 +3491,12 @@ impl<'db> ClassLiteral<'db> { Definedness::PossiblyUndefined }; - Place::Defined( - union.build(), - TypeOrigin::Inferred, - boundness, - Widening::None, - ) + Place::Defined(DefinedPlace { + ty: union.build(), + origin: TypeOrigin::Inferred, + definedness: boundness, + widening: Widening::None, + }) .with_qualifiers(union_qualifiers) } } @@ -3848,7 +3861,12 @@ impl<'db> ClassLiteral<'db> { match declared_and_qualifiers { PlaceAndQualifiers { - place: mut declared @ Place::Defined(declared_ty, _, declaredness, _), + place: + mut declared @ Place::Defined(DefinedPlace { + ty: declared_ty, + definedness: declaredness, + .. + }), qualifiers, } => { // For the purpose of finding instance attributes, ignore `ClassVar` @@ -3889,12 +3907,15 @@ impl<'db> ClassLiteral<'db> { } } else { Member { - inner: Place::Defined( - UnionType::from_elements(db, [declared_ty, implicit_ty]), - TypeOrigin::Declared, - declaredness, - Widening::None, - ) + inner: Place::Defined(DefinedPlace { + ty: UnionType::from_elements( + db, + [declared_ty, implicit_ty], + ), + origin: TypeOrigin::Declared, + definedness: declaredness, + widening: Widening::None, + }) .with_qualifiers(qualifiers), } } @@ -3930,12 +3951,15 @@ impl<'db> ClassLiteral<'db> { .ignore_possibly_undefined() { Member { - inner: Place::Defined( - UnionType::from_elements(db, [declared_ty, implicit_ty]), - TypeOrigin::Declared, - declaredness, - Widening::None, - ) + inner: Place::Defined(DefinedPlace { + ty: UnionType::from_elements( + db, + [declared_ty, implicit_ty], + ), + origin: TypeOrigin::Declared, + definedness: declaredness, + widening: Widening::None, + }) .with_qualifiers(qualifiers), } } else { @@ -5256,16 +5280,17 @@ impl KnownClass { ) -> Result, KnownClassLookupError<'_>> { let symbol = known_module_symbol(db, self.canonical_module(db), self.name(db)).place; match symbol { - Place::Defined(Type::ClassLiteral(class_literal), _, Definedness::AlwaysDefined, _) => { - Ok(class_literal) - } - Place::Defined( - Type::ClassLiteral(class_literal), - _, - Definedness::PossiblyUndefined, - _, - ) => Err(KnownClassLookupError::ClassPossiblyUnbound { class_literal }), - Place::Defined(found_type, _, _, _) => { + Place::Defined(DefinedPlace { + ty: Type::ClassLiteral(class_literal), + definedness: Definedness::AlwaysDefined, + .. + }) => Ok(class_literal), + Place::Defined(DefinedPlace { + ty: Type::ClassLiteral(class_literal), + definedness: Definedness::PossiblyUndefined, + .. + }) => Err(KnownClassLookupError::ClassPossiblyUnbound { class_literal }), + Place::Defined(DefinedPlace { ty: found_type, .. }) => { Err(KnownClassLookupError::SymbolNotAClass { found_type }) } Place::Undefined => Err(KnownClassLookupError::ClassNotFound), @@ -6144,7 +6169,11 @@ enum SlotsKind { impl SlotsKind { fn from(db: &dyn Db, base: ClassLiteral) -> Self { - let Place::Defined(slots_ty, _, bound, _) = base + let Place::Defined(DefinedPlace { + ty: slots_ty, + definedness: bound, + .. + }) = base .own_class_member(db, base.inherited_generic_context(db), None, "__slots__") .inner .place diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index f60cf81142..5b782f2a0d 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -8,7 +8,7 @@ use super::{ use crate::diagnostic::did_you_mean; use crate::diagnostic::format_enumeration; use crate::lint::{Level, LintRegistryBuilder, LintStatus}; -use crate::place::Place; +use crate::place::{DefinedPlace, Place}; use crate::semantic_index::definition::{Definition, DefinitionKind}; use crate::semantic_index::place::{PlaceTable, ScopedPlaceId}; use crate::semantic_index::{global_scope, place_table, use_def_map}; @@ -4058,10 +4058,14 @@ pub(super) fn report_invalid_method_override<'db>( .place }; - if let Place::Defined(Type::FunctionLiteral(subclass_function), _, _, _) = - class_member(subclass) - && let Place::Defined(Type::FunctionLiteral(superclass_function), _, _, _) = - class_member(superclass) + if let Place::Defined(DefinedPlace { + ty: Type::FunctionLiteral(subclass_function), + .. + }) = class_member(subclass) + && let Place::Defined(DefinedPlace { + ty: Type::FunctionLiteral(superclass_function), + .. + }) = class_member(superclass) && let Ok(superclass_function_kind) = MethodDecorator::try_from_fn_type(db, superclass_function) && let Ok(subclass_function_kind) = MethodDecorator::try_from_fn_type(db, subclass_function) diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index f19a99e863..c8a0840296 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -14,7 +14,7 @@ use ruff_text_size::{TextLen, TextRange, TextSize}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::Db; -use crate::place::Place; +use crate::place::{DefinedPlace, Place}; use crate::semantic_index::definition::Definition; use crate::types::class::{ClassLiteral, ClassType, GenericAlias}; use crate::types::function::{FunctionType, OverloadLiteral}; @@ -887,7 +887,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> { f.with_type(KnownClass::MethodWrapperType.to_class_literal(self.db)) .write_str("method-wrapper")?; f.write_str(" '")?; - if let Place::Defined(member_ty, _, _, _) = + if let Place::Defined(DefinedPlace { ty: member_ty, .. }) = class_ty.member(self.db, member_name).place { f.with_type(member_ty).write_str(member_name)?; diff --git a/crates/ty_python_semantic/src/types/enums.rs b/crates/ty_python_semantic/src/types/enums.rs index f91a4567cf..206d7f21f1 100644 --- a/crates/ty_python_semantic/src/types/enums.rs +++ b/crates/ty_python_semantic/src/types/enums.rs @@ -3,7 +3,9 @@ use rustc_hash::FxHashMap; use crate::{ Db, FxIndexMap, - place::{Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations}, + place::{ + DefinedPlace, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations, + }, semantic_index::{place_table, use_def_map}, types::{ ClassBase, ClassLiteral, DynamicType, EnumLiteralType, KnownClass, MemberLookupPolicy, @@ -76,9 +78,10 @@ pub(crate) fn enum_metadata<'db>( let ignore_place = place_from_bindings(db, ignore_bindings).place; match ignore_place { - Place::Defined(Type::StringLiteral(ignored_names), _, _, _) => { - Some(ignored_names.value(db).split_ascii_whitespace().collect()) - } + Place::Defined(DefinedPlace { + ty: Type::StringLiteral(ignored_names), + .. + }) => Some(ignored_names.value(db).split_ascii_whitespace().collect()), // TODO: support the list-variant of `_ignore_`. _ => None, } @@ -113,7 +116,7 @@ pub(crate) fn enum_metadata<'db>( Place::Undefined => { return None; } - Place::Defined(ty, _, _, _) => { + Place::Defined(DefinedPlace { ty, .. }) => { let special_case = match ty { Type::Callable(_) | Type::FunctionLiteral(_) => { // Some types are specifically disallowed for enum members. @@ -193,9 +196,13 @@ pub(crate) fn enum_metadata<'db>( .place; match dunder_get { - Place::Undefined | Place::Defined(Type::Dynamic(_), _, _, _) => ty, + Place::Undefined + | Place::Defined(DefinedPlace { + ty: Type::Dynamic(_), + .. + }) => ty, - Place::Defined(_, _, _, _) => { + Place::Defined(_) => { // Descriptors are not considered members. return None; } @@ -230,7 +237,11 @@ pub(crate) fn enum_metadata<'db>( match declared { PlaceAndQualifiers { - place: Place::Defined(Type::Dynamic(DynamicType::Unknown), _, _, _), + place: + Place::Defined(DefinedPlace { + ty: Type::Dynamic(DynamicType::Unknown), + .. + }), qualifiers, } if qualifiers.contains(TypeQualifiers::FINAL) => {} PlaceAndQualifiers { @@ -240,7 +251,11 @@ pub(crate) fn enum_metadata<'db>( // Undeclared attributes are considered members } PlaceAndQualifiers { - place: Place::Defined(Type::NominalInstance(instance), _, _, _), + place: + Place::Defined(DefinedPlace { + ty: Type::NominalInstance(instance), + .. + }), .. } if instance.has_known_class(db, KnownClass::Member) => { // If the attribute is specifically declared with `enum.member`, it is considered a member diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 1ae86a08df..8e91a13db6 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -59,7 +59,7 @@ use ruff_python_ast::{self as ast, ParameterWithDefault}; use ruff_text_size::Ranged; use ty_module_resolver::{KnownModule, ModuleName, file_to_module, resolve_module}; -use crate::place::{Definedness, Place, place_from_bindings}; +use crate::place::{DefinedPlace, Definedness, Place, place_from_bindings}; use crate::semantic_index::ast_ids::HasScopedUseId; use crate::semantic_index::definition::Definition; use crate::semantic_index::scope::ScopeId; @@ -376,8 +376,11 @@ impl<'db> OverloadLiteral<'db> { .name .scoped_use_id(db, scope); - let Place::Defined(Type::FunctionLiteral(previous_type), _, Definedness::AlwaysDefined, _) = - place_from_bindings(db, use_def.bindings_at_use(use_id)).place + let Place::Defined(DefinedPlace { + ty: Type::FunctionLiteral(previous_type), + definedness: Definedness::AlwaysDefined, + .. + }) = place_from_bindings(db, use_def.bindings_at_use(use_id)).place else { return None; }; diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 5700cad844..ee4ce80c9c 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -27,10 +27,11 @@ use super::{ use crate::diagnostic::format_enumeration; use crate::node_key::NodeKey; use crate::place::{ - ConsideredDefinitions, Definedness, LookupError, Place, PlaceAndQualifiers, TypeOrigin, - builtins_module_scope, builtins_symbol, class_body_implicit_symbol, explicit_global_symbol, - global_symbol, module_type_implicit_global_declaration, module_type_implicit_global_symbol, - place, place_from_bindings, place_from_declarations, typing_extensions_symbol, + ConsideredDefinitions, DefinedPlace, Definedness, LookupError, Place, PlaceAndQualifiers, + TypeOrigin, builtins_module_scope, builtins_symbol, class_body_implicit_symbol, + explicit_global_symbol, global_symbol, module_type_implicit_global_declaration, + module_type_implicit_global_symbol, place, place_from_bindings, place_from_declarations, + typing_extensions_symbol, }; use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey; use crate::semantic_index::ast_ids::{HasScopedUseId, ScopedUseId}; @@ -1187,12 +1188,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let mut public_functions = FxIndexSet::default(); for place in overloaded_function_places { - if let Place::Defined( - Type::FunctionLiteral(function), - _, - Definedness::AlwaysDefined, - _, - ) = place_from_bindings( + if let Place::Defined(DefinedPlace { + ty: Type::FunctionLiteral(function), + definedness: Definedness::AlwaysDefined, + .. + }) = place_from_bindings( self.db(), use_def.end_of_scope_symbol_bindings(place.as_symbol().unwrap()), ) @@ -1725,8 +1725,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if let AnyNodeRef::ExprAttribute(ast::ExprAttribute { value, attr, .. }) = node { let value_type = self.infer_maybe_standalone_expression(value, TypeContext::default()); - if let Place::Defined(ty, _, Definedness::AlwaysDefined, _) = - value_type.member(db, attr).place + if let Place::Defined(DefinedPlace { + ty, + definedness: Definedness::AlwaysDefined, + .. + }) = value_type.member(db, attr).place { // TODO: also consider qualifiers on the attribute Some(ty) @@ -4831,7 +4834,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, ) { PlaceAndQualifiers { - place: Place::Defined(attr_ty, _, _, _), + place: Place::Defined(DefinedPlace { ty: attr_ty, .. }), qualifiers: _, } => attr_ty.is_callable_type(), _ => false, @@ -4883,50 +4886,61 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { false } PlaceAndQualifiers { - place: Place::Defined(meta_attr_ty, _, meta_attr_boundness, _), + place: + Place::Defined(DefinedPlace { + ty: meta_attr_ty, + definedness: meta_attr_boundness, + .. + }), qualifiers, } => { if invalid_assignment_to_final(self, qualifiers) { return false; } - let assignable_to_meta_attr = - if let Place::Defined(meta_dunder_set, _, _, _) = - meta_attr_ty.class_member(db, "__set__".into()).place - { - // TODO: We could use the annotated parameter type of `__set__` as - // type context here. - let dunder_set_result = meta_dunder_set.try_call( - db, - &CallArguments::positional([meta_attr_ty, object_ty, value_ty]), - ); + let assignable_to_meta_attr = if let Place::Defined(DefinedPlace { + ty: meta_dunder_set, + .. + }) = + meta_attr_ty.class_member(db, "__set__".into()).place + { + // TODO: We could use the annotated parameter type of `__set__` as + // type context here. + let dunder_set_result = meta_dunder_set.try_call( + db, + &CallArguments::positional([meta_attr_ty, object_ty, value_ty]), + ); - if emit_diagnostics { - if let Err(dunder_set_failure) = dunder_set_result.as_ref() { - report_bad_dunder_set_call( - &self.context, - dunder_set_failure, - attribute, - object_ty, - target, - ); - } + if emit_diagnostics { + if let Err(dunder_set_failure) = dunder_set_result.as_ref() { + report_bad_dunder_set_call( + &self.context, + dunder_set_failure, + attribute, + object_ty, + target, + ); } + } - dunder_set_result.is_ok() - } else { - let value_ty = - infer_value_ty(self, TypeContext::new(Some(meta_attr_ty))); + dunder_set_result.is_ok() + } else { + let value_ty = + infer_value_ty(self, TypeContext::new(Some(meta_attr_ty))); - ensure_assignable_to(self, value_ty, meta_attr_ty) - }; + ensure_assignable_to(self, value_ty, meta_attr_ty) + }; let assignable_to_instance_attribute = if meta_attr_boundness == Definedness::PossiblyUndefined { let (assignable, boundness) = if let PlaceAndQualifiers { place: - Place::Defined(instance_attr_ty, _, instance_attr_boundness, _), + Place::Defined(DefinedPlace { + ty: instance_attr_ty, + definedness: instance_attr_boundness, + .. + }), qualifiers, } = object_ty.instance_member(db, attribute) @@ -4967,7 +4981,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .. } => { if let PlaceAndQualifiers { - place: Place::Defined(instance_attr_ty, _, instance_attr_boundness, _), + place: + Place::Defined(DefinedPlace { + ty: instance_attr_ty, + definedness: instance_attr_boundness, + .. + }), qualifiers, } = object_ty.instance_member(db, attribute) { @@ -5032,7 +5051,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..) => { match object_ty.class_member(db, attribute.into()) { PlaceAndQualifiers { - place: Place::Defined(meta_attr_ty, _, meta_attr_boundness, _), + place: + Place::Defined(DefinedPlace { + ty: meta_attr_ty, + definedness: meta_attr_boundness, + .. + }), qualifiers, } => { // We may have to perform multi-inference if the meta attribute is possibly unbound. @@ -5043,55 +5067,59 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return false; } - let assignable_to_meta_attr = - if let Place::Defined(meta_dunder_set, _, _, _) = - meta_attr_ty.class_member(db, "__set__".into()).place - { - // TODO: We could use the annotated parameter type of `__set__` as - // type context here. - let dunder_set_result = meta_dunder_set.try_call( - db, - &CallArguments::positional([meta_attr_ty, object_ty, value_ty]), - ); + let assignable_to_meta_attr = if let Place::Defined(DefinedPlace { + ty: meta_dunder_set, + .. + }) = + meta_attr_ty.class_member(db, "__set__".into()).place + { + // TODO: We could use the annotated parameter type of `__set__` as + // type context here. + let dunder_set_result = meta_dunder_set.try_call( + db, + &CallArguments::positional([meta_attr_ty, object_ty, value_ty]), + ); - if emit_diagnostics { - if let Err(dunder_set_failure) = dunder_set_result.as_ref() { - report_bad_dunder_set_call( - &self.context, - dunder_set_failure, - attribute, - object_ty, - target, - ); - } + if emit_diagnostics { + if let Err(dunder_set_failure) = dunder_set_result.as_ref() { + report_bad_dunder_set_call( + &self.context, + dunder_set_failure, + attribute, + object_ty, + target, + ); } + } - dunder_set_result.is_ok() - } else { - let value_ty = - infer_value_ty(self, TypeContext::new(Some(meta_attr_ty))); - ensure_assignable_to(self, value_ty, meta_attr_ty) - }; + dunder_set_result.is_ok() + } else { + let value_ty = + infer_value_ty(self, TypeContext::new(Some(meta_attr_ty))); + ensure_assignable_to(self, value_ty, meta_attr_ty) + }; let assignable_to_class_attr = if meta_attr_boundness == Definedness::PossiblyUndefined { - let (assignable, boundness) = - if let Place::Defined(class_attr_ty, _, class_attr_boundness, _) = - object_ty - .find_name_in_mro(db, attribute) - .expect("called on Type::ClassLiteral or Type::SubclassOf") - .place - { - let value_ty = - infer_value_ty(self, TypeContext::new(Some(class_attr_ty))); - ( - ensure_assignable_to(self, value_ty, class_attr_ty), - class_attr_boundness, - ) - } else { - (true, Definedness::PossiblyUndefined) - }; + let (assignable, boundness) = if let Place::Defined(DefinedPlace { + ty: class_attr_ty, + definedness: class_attr_boundness, + .. + }) = object_ty + .find_name_in_mro(db, attribute) + .expect("called on Type::ClassLiteral or Type::SubclassOf") + .place + { + let value_ty = + infer_value_ty(self, TypeContext::new(Some(class_attr_ty))); + ( + ensure_assignable_to(self, value_ty, class_attr_ty), + class_attr_boundness, + ) + } else { + (true, Definedness::PossiblyUndefined) + }; if boundness == Definedness::PossiblyUndefined { report_possibly_missing_attribute( @@ -5114,7 +5142,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .. } => { if let PlaceAndQualifiers { - place: Place::Defined(class_attr_ty, _, class_attr_boundness, _), + place: + Place::Defined(DefinedPlace { + ty: class_attr_ty, + definedness: class_attr_boundness, + .. + }), qualifiers, } = object_ty .find_name_in_mro(db, attribute) @@ -5188,7 +5221,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } else { module.static_member(db, attribute) }; - if let Place::Defined(attr_ty, _, _, _) = sym.place { + if let Place::Defined(DefinedPlace { ty: attr_ty, .. }) = sym.place { let value_ty = infer_value_ty(self, TypeContext::new(Some(attr_ty))); let assignable = value_ty.is_assignable_to(db, attr_ty); @@ -6791,7 +6824,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // First try loading the requested attribute from the module. if !import_is_self_referential { if let PlaceAndQualifiers { - place: Place::Defined(ty, _, boundness, _), + place: + Place::Defined(DefinedPlace { + ty, + definedness: boundness, + .. + }), qualifiers, } = module_ty.member(self.db(), name) { @@ -9495,7 +9533,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } let (parent_place, _use_id) = self.infer_local_place_load(parent_expr, expr_ref); - if let Place::Defined(_, _, _, _) = parent_place { + if let Place::Defined(_) = parent_place { return Place::Undefined.into(); } } @@ -9638,7 +9676,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }); // We could have `Place::Undefined` here, despite the checks above, for example if // this scope contains a `del` statement but no binding or declaration. - if let Place::Defined(type_, _, boundness, _) = local_place_and_qualifiers.place + if let Place::Defined(DefinedPlace { + ty: type_, + definedness: boundness, + .. + }) = local_place_and_qualifiers.place { nonlocal_union_builder.add_in_place(type_); // `ConsideredDefinitions::AllReachable` never returns PossiblyUnbound @@ -9893,7 +9935,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ast::ExprRef::Attribute(attribute), ); constraint_keys.extend(keys); - if let Place::Defined(ty, _, Definedness::AlwaysDefined, _) = resolved.place { + if let Place::Defined(DefinedPlace { + ty, + definedness: Definedness::AlwaysDefined, + .. + }) = resolved.place + { assigned_type = Some(ty); } } @@ -11549,7 +11596,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let contains_dunder = right.class_member(db, "__contains__".into()).place; let compare_result_opt = match contains_dunder { - Place::Defined(contains_dunder, _, Definedness::AlwaysDefined, _) => { + Place::Defined(DefinedPlace { + ty: contains_dunder, + definedness: Definedness::AlwaysDefined, + .. + }) => { // If `__contains__` is available, it is used directly for the membership test. contains_dunder .try_call(db, &CallArguments::positional([right, left])) @@ -11786,7 +11837,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ast::ExprRef::Subscript(subscript), ); constraint_keys.extend(keys); - if let Place::Defined(ty, _, Definedness::AlwaysDefined, _) = place.place { + if let Place::Defined(DefinedPlace { + ty, + definedness: Definedness::AlwaysDefined, + .. + }) = place.place + { // Even if we can obtain the subscript type based on the assignments, we still perform default type inference // (to store the expression type and to report errors). let slice_ty = self.infer_expression(slice, TypeContext::default()); @@ -12818,7 +12874,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { match dunder_class_getitem_method { Place::Undefined => {} - Place::Defined(ty, _, boundness, _) => { + Place::Defined(DefinedPlace { + ty, + definedness: boundness, + .. + }) => { if boundness == Definedness::PossiblyUndefined { if let Some(builder) = context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, value_node) diff --git a/crates/ty_python_semantic/src/types/list_members.rs b/crates/ty_python_semantic/src/types/list_members.rs index 63fcaad7e9..fff93548d8 100644 --- a/crates/ty_python_semantic/src/types/list_members.rs +++ b/crates/ty_python_semantic/src/types/list_members.rs @@ -13,7 +13,8 @@ use rustc_hash::FxHashSet; use crate::{ Db, NameKind, place::{ - Place, PlaceWithDefinition, imported_symbol, place_from_bindings, place_from_declarations, + DefinedPlace, Place, PlaceWithDefinition, imported_symbol, place_from_bindings, + place_from_declarations, }, semantic_index::{ attribute_scopes, definition::Definition, global_scope, place_table, scope::ScopeId, @@ -325,7 +326,7 @@ impl<'db> AllMembers<'db> { 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, _, _, _) = + let Place::Defined(DefinedPlace { ty, .. }) = imported_symbol(db, file, symbol_name, None).place else { continue; @@ -494,7 +495,11 @@ impl<'db> AllMembers<'db> { Some(CodeGeneratorKind::TypedDict) => {} Some(CodeGeneratorKind::DataclassLike(_)) => { for attr in SYNTHETIC_DATACLASS_ATTRIBUTES { - if let Place::Defined(synthetic_member, _, _, _) = ty.member(db, attr).place { + if let Place::Defined(DefinedPlace { + ty: synthetic_member, + .. + }) = ty.member(db, attr).place + { self.members.insert(Member { name: Name::from(*attr), ty: synthetic_member, diff --git a/crates/ty_python_semantic/src/types/member.rs b/crates/ty_python_semantic/src/types/member.rs index 33a6d7c559..c573754746 100644 --- a/crates/ty_python_semantic/src/types/member.rs +++ b/crates/ty_python_semantic/src/types/member.rs @@ -1,7 +1,7 @@ use crate::Db; use crate::place::{ - ConsideredDefinitions, Place, PlaceAndQualifiers, RequiresExplicitReExport, place_by_id, - place_from_bindings, + ConsideredDefinitions, DefinedPlace, Place, PlaceAndQualifiers, RequiresExplicitReExport, + place_by_id, place_from_bindings, }; use crate::semantic_index::{place_table, scope::ScopeId, use_def_map}; use crate::types::Type; @@ -68,7 +68,7 @@ pub(super) fn class_member<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str } if let PlaceAndQualifiers { - place: Place::Defined(ty, _, _, _), + place: Place::Defined(DefinedPlace { ty, .. }), qualifiers, } = place_and_quals { @@ -82,9 +82,8 @@ pub(super) fn class_member<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str Member { inner: match inferred { Place::Undefined => Place::Undefined.with_qualifiers(qualifiers), - Place::Defined(_, origin, boundness, widening) => { - Place::Defined(ty, origin, boundness, widening) - .with_qualifiers(qualifiers) + Place::Defined(place) => { + Place::Defined(DefinedPlace { ty, ..place }).with_qualifiers(qualifiers) } }, } diff --git a/crates/ty_python_semantic/src/types/overrides.rs b/crates/ty_python_semantic/src/types/overrides.rs index bf6f292e56..e0aef55b62 100644 --- a/crates/ty_python_semantic/src/types/overrides.rs +++ b/crates/ty_python_semantic/src/types/overrides.rs @@ -10,7 +10,7 @@ use rustc_hash::FxHashSet; use crate::{ Db, lint::LintId, - place::Place, + place::{DefinedPlace, Place}, semantic_index::{ definition::DefinitionKind, place::ScopedPlaceId, place_table, scope::ScopeId, symbol::ScopedSymbolId, use_def_map, @@ -110,8 +110,10 @@ fn check_class_declaration<'db>( first_reachable_definition, } = member; - let Place::Defined(type_on_subclass_instance, _, _, _) = - Type::instance(db, class).member(db, &member.name).place + let Place::Defined(DefinedPlace { + ty: type_on_subclass_instance, + .. + }) = Type::instance(db, class).member(db, &member.name).place else { return; }; @@ -190,7 +192,10 @@ fn check_class_declaration<'db>( .unwrap_or_default(); } - let Place::Defined(superclass_type, _, _, _) = Type::instance(db, superclass) + let Place::Defined(DefinedPlace { + ty: superclass_type, + .. + }) = Type::instance(db, superclass) .member(db, &member.name) .place else { diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 4dd717e084..453ddd071b 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -10,7 +10,10 @@ use crate::types::relation::{HasRelationToVisitor, IsDisjointVisitor, TypeRelati use crate::types::{CallableTypeKind, TypeContext}; use crate::{ Db, FxOrderSet, - place::{Definedness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations}, + place::{ + DefinedPlace, Definedness, Place, PlaceAndQualifiers, place_from_bindings, + place_from_declarations, + }, semantic_index::{definition::Definition, place::ScopedPlaceId, place_table, use_def_map}, types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, @@ -738,7 +741,11 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { let attribute_type = if self.name == "__call__" { other } else { - let Place::Defined(attribute_type, _, Definedness::AlwaysDefined, _) = other + let Place::Defined(DefinedPlace { + ty: attribute_type, + definedness: Definedness::AlwaysDefined, + .. + }) = other .invoke_descriptor_protocol( db, self.name, @@ -783,11 +790,17 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { // TODO: consider the types of the attribute on `other` for property members ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!( other.member(db, self.name).place, - Place::Defined(_, _, Definedness::AlwaysDefined, _) + Place::Defined(DefinedPlace { + definedness: Definedness::AlwaysDefined, + .. + }) )), ProtocolMemberKind::Other(member_type) => { - let Place::Defined(attribute_type, _, Definedness::AlwaysDefined, _) = - other.member(db, self.name).place + let Place::Defined(DefinedPlace { + ty: attribute_type, + definedness: Definedness::AlwaysDefined, + .. + }) = other.member(db, self.name).place else { return ConstraintSet::from(false); }; diff --git a/crates/ty_python_semantic/src/types/relation.rs b/crates/ty_python_semantic/src/types/relation.rs index b0938999a4..7955dfe61d 100644 --- a/crates/ty_python_semantic/src/types/relation.rs +++ b/crates/ty_python_semantic/src/types/relation.rs @@ -1,6 +1,6 @@ use ruff_python_ast::name::Name; -use crate::place::Place; +use crate::place::{DefinedPlace, Place}; use crate::types::constraints::{IteratorConstraintsExtension, OptionConstraintsExtension}; use crate::types::enums::is_single_member_enum; use crate::types::{ @@ -1940,14 +1940,15 @@ impl<'db> Type<'db> { disjointness_visitor.visit((self, other), || { protocol.interface(db).members(db).when_any(db, |member| { match other.member(db, member.name()).place { - Place::Defined(attribute_type, _, _, _) => member - .has_disjoint_type_from( - db, - attribute_type, - inferable, - disjointness_visitor, - relation_visitor, - ), + Place::Defined(DefinedPlace { + ty: attribute_type, .. + }) => member.has_disjoint_type_from( + db, + attribute_type, + inferable, + disjointness_visitor, + relation_visitor, + ), Place::Undefined => ConstraintSet::from(false), } })