diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index 6423ccc7e7..d0fa8ba36c 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -21,83 +21,127 @@ pub(crate) use implicit_globals::{ }; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, get_size2::GetSize)] -pub(crate) enum Boundness { - Bound, - PossiblyUnbound, +pub(crate) enum Definedness { + AlwaysDefined, + PossiblyUndefined, } -impl Boundness { +impl Definedness { pub(crate) const fn max(self, other: Self) -> Self { match (self, other) { - (Boundness::Bound, _) | (_, Boundness::Bound) => Boundness::Bound, - (Boundness::PossiblyUnbound, Boundness::PossiblyUnbound) => Boundness::PossiblyUnbound, + (Definedness::AlwaysDefined, _) | (_, Definedness::AlwaysDefined) => { + Definedness::AlwaysDefined + } + (Definedness::PossiblyUndefined, Definedness::PossiblyUndefined) => { + Definedness::PossiblyUndefined + } } } } -/// The result of a place lookup, which can either be a (possibly unbound) type -/// or a completely unbound place. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, get_size2::GetSize)] +pub(crate) enum TypeOrigin { + Declared, + Inferred, +} + +impl TypeOrigin { + pub(crate) const fn is_declared(self) -> bool { + matches!(self, TypeOrigin::Declared) + } + + pub(crate) const fn merge(self, other: Self) -> Self { + match (self, other) { + (TypeOrigin::Declared, TypeOrigin::Declared) => TypeOrigin::Declared, + _ => TypeOrigin::Inferred, + } + } +} + +/// The result of a place lookup, which can either be a (possibly undefined) type +/// or a completely undefined place. +/// +/// If a place has both a binding and a declaration, the result of the binding is used. /// /// Consider this example: /// ```py /// bound = 1 +/// declared: int /// /// if flag: /// possibly_unbound = 2 +/// possibly_undeclared: int +/// +/// if flag: +/// bound_or_declared = 1 +/// else: +/// bound_or_declared: int /// ``` /// /// If we look up places in this scope, we would get the following results: /// ```rs -/// bound: Place::Type(Type::IntLiteral(1), Boundness::Bound), -/// possibly_unbound: Place::Type(Type::IntLiteral(2), Boundness::PossiblyUnbound), -/// non_existent: Place::Unbound, +/// 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), +/// non_existent: Place::Undefined, /// ``` #[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)] pub(crate) enum Place<'db> { - Type(Type<'db>, Boundness), - Unbound, + Defined(Type<'db>, TypeOrigin, Definedness), + Undefined, } impl<'db> Place<'db> { - /// Constructor that creates a `Place` with boundness [`Boundness::Bound`]. + /// Constructor that creates a [`Place`] with type origin [`TypeOrigin::Inferred`] and definedness [`Definedness::AlwaysDefined`]. pub(crate) fn bound(ty: impl Into>) -> Self { - Place::Type(ty.into(), Boundness::Bound) + Place::Defined(ty.into(), TypeOrigin::Inferred, Definedness::AlwaysDefined) + } + + /// 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) } /// Constructor that creates a [`Place`] with a [`crate::types::TodoType`] type - /// and boundness [`Boundness::Bound`]. + /// and definedness [`Definedness::AlwaysDefined`]. #[allow(unused_variables)] // Only unused in release builds pub(crate) fn todo(message: &'static str) -> Self { - Place::Type(todo_type!(message), Boundness::Bound) + Place::Defined( + todo_type!(message), + TypeOrigin::Inferred, + Definedness::AlwaysDefined, + ) } - pub(crate) fn is_unbound(&self) -> bool { - matches!(self, Place::Unbound) + pub(crate) fn is_undefined(&self) -> bool { + matches!(self, Place::Undefined) } - /// Returns the type of the place, ignoring possible unboundness. + /// Returns the type of the place, ignoring possible undefinedness. /// - /// If the place is *definitely* unbound, this function will return `None`. Otherwise, - /// if there is at least one control-flow path where the place is bound, return the type. - pub(crate) fn ignore_possibly_unbound(&self) -> Option> { + /// If the place is *definitely* undefined, this function will return `None`. Otherwise, + /// 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::Type(ty, _) => Some(*ty), - Place::Unbound => None, + Place::Defined(ty, _, _) => Some(*ty), + Place::Undefined => None, } } #[cfg(test)] #[track_caller] pub(crate) fn expect_type(self) -> Type<'db> { - self.ignore_possibly_unbound() - .expect("Expected a (possibly unbound) type, not an unbound place") + self.ignore_possibly_undefined() + .expect("Expected a (possibly undefined) type, not an undefined place") } #[must_use] pub(crate) fn map_type(self, f: impl FnOnce(Type<'db>) -> Type<'db>) -> Place<'db> { match self { - Place::Type(ty, boundness) => Place::Type(f(ty), boundness), - Place::Unbound => Place::Unbound, + Place::Defined(ty, origin, definedness) => Place::Defined(f(ty), origin, definedness), + Place::Undefined => Place::Undefined, } } @@ -114,46 +158,47 @@ 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::Type(Type::Union(union), boundness) => union.map_with_boundness(db, |elem| { - Place::Type(*elem, boundness).try_call_dunder_get(db, owner) - }), - - Place::Type(Type::Intersection(intersection), boundness) => intersection + Place::Defined(Type::Union(union), origin, definedness) => union .map_with_boundness(db, |elem| { - Place::Type(*elem, boundness).try_call_dunder_get(db, owner) + Place::Defined(*elem, origin, definedness).try_call_dunder_get(db, owner) }), - Place::Type(self_ty, boundness) => { + Place::Defined(Type::Intersection(intersection), origin, definedness) => intersection + .map_with_boundness(db, |elem| { + Place::Defined(*elem, origin, definedness).try_call_dunder_get(db, owner) + }), + + Place::Defined(self_ty, origin, definedness) => { if let Some((dunder_get_return_ty, _)) = self_ty.try_call_dunder_get(db, Type::none(db), owner) { - Place::Type(dunder_get_return_ty, boundness) + Place::Defined(dunder_get_return_ty, origin, definedness) } else { self } } - Place::Unbound => Place::Unbound, + Place::Undefined => Place::Undefined, } } pub(crate) const fn is_definitely_bound(&self) -> bool { - matches!(self, Place::Type(_, Boundness::Bound)) + matches!(self, Place::Defined(_, _, Definedness::AlwaysDefined)) } } impl<'db> From> for PlaceAndQualifiers<'db> { fn from(value: LookupResult<'db>) -> Self { match value { - Ok(type_and_qualifiers) => { - Place::Type(type_and_qualifiers.inner_type(), Boundness::Bound) - .with_qualifiers(type_and_qualifiers.qualifiers()) - } - Err(LookupError::Unbound(qualifiers)) => Place::Unbound.with_qualifiers(qualifiers), - Err(LookupError::PossiblyUnbound(type_and_qualifiers)) => { - Place::Type(type_and_qualifiers.inner_type(), Boundness::PossiblyUnbound) - .with_qualifiers(type_and_qualifiers.qualifiers()) - } + Ok(type_and_qualifiers) => Place::bound(type_and_qualifiers.inner_type()) + .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, + ) + .with_qualifiers(type_and_qualifiers.qualifiers()), } } } @@ -161,8 +206,8 @@ impl<'db> From> for PlaceAndQualifiers<'db> { /// Possible ways in which a place lookup can (possibly or definitely) fail. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub(crate) enum LookupError<'db> { - Unbound(TypeQualifiers), - PossiblyUnbound(TypeAndQualifiers<'db>), + Undefined(TypeQualifiers), + PossiblyUndefined(TypeAndQualifiers<'db>), } impl<'db> LookupError<'db> { @@ -174,15 +219,17 @@ impl<'db> LookupError<'db> { ) -> LookupResult<'db> { let fallback = fallback.into_lookup_result(); match (&self, &fallback) { - (LookupError::Unbound(_), _) => fallback, - (LookupError::PossiblyUnbound { .. }, Err(LookupError::Unbound(_))) => Err(self), - (LookupError::PossiblyUnbound(ty), Ok(ty2)) => Ok(TypeAndQualifiers::new( + (LookupError::Undefined(_), _) => fallback, + (LookupError::PossiblyUndefined { .. }, Err(LookupError::Undefined(_))) => Err(self), + (LookupError::PossiblyUndefined(ty), Ok(ty2)) => Ok(TypeAndQualifiers::new( UnionType::from_elements(db, [ty.inner_type(), ty2.inner_type()]), + ty.origin().merge(ty2.origin()), ty.qualifiers().union(ty2.qualifiers()), )), - (LookupError::PossiblyUnbound(ty), Err(LookupError::PossiblyUnbound(ty2))) => { - Err(LookupError::PossiblyUnbound(TypeAndQualifiers::new( + (LookupError::PossiblyUndefined(ty), Err(LookupError::PossiblyUndefined(ty2))) => { + Err(LookupError::PossiblyUndefined(TypeAndQualifiers::new( UnionType::from_elements(db, [ty.inner_type(), ty2.inner_type()]), + ty.origin().merge(ty2.origin()), ty.qualifiers().union(ty2.qualifiers()), ))) } @@ -236,7 +283,7 @@ pub(crate) fn place<'db>( /// /// Note that all global scopes also include various "implicit globals" such as `__name__`, /// `__doc__` and `__file__`. This function **does not** consider those symbols; it will return -/// `Place::Unbound` for them. Use the (currently test-only) `global_symbol` query to also include +/// `Place::Undefined` for them. Use the (currently test-only) `global_symbol` query to also include /// those additional symbols. /// /// Use [`imported_symbol`] to perform the lookup as seen from outside the file (e.g. via imports). @@ -313,7 +360,7 @@ pub(crate) fn imported_symbol<'db>( ) .or_fall_back_to(db, || { if name == "__getattr__" { - Place::Unbound.into() + Place::Undefined.into() } else if name == "__builtins__" { Place::bound(Type::any()).into() } else { @@ -324,7 +371,7 @@ pub(crate) fn imported_symbol<'db>( /// Lookup the type of `symbol` in the builtins namespace. /// -/// Returns `Place::Unbound` if the `builtins` module isn't available for some reason. +/// Returns `Place::Undefined` if the `builtins` module isn't available for some reason. /// /// Note that this function is only intended for use in the context of the builtins *namespace* /// and should not be used when a symbol is being explicitly imported from the `builtins` module @@ -354,7 +401,7 @@ pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> PlaceAndQua /// Lookup the type of `symbol` in a given known module. /// -/// Returns `Place::Unbound` if the given known module cannot be resolved for some reason. +/// Returns `Place::Undefined` if the given known module cannot be resolved for some reason. pub(crate) fn known_module_symbol<'db>( db: &'db dyn Db, known_module: KnownModule, @@ -370,7 +417,7 @@ pub(crate) fn known_module_symbol<'db>( /// Lookup the type of `symbol` in the `typing` module namespace. /// -/// Returns `Place::Unbound` if the `typing` module isn't available for some reason. +/// Returns `Place::Undefined` if the `typing` module isn't available for some reason. #[inline] #[cfg(test)] pub(crate) fn typing_symbol<'db>(db: &'db dyn Db, symbol: &str) -> PlaceAndQualifiers<'db> { @@ -379,7 +426,7 @@ pub(crate) fn typing_symbol<'db>(db: &'db dyn Db, symbol: &str) -> PlaceAndQuali /// Lookup the type of `symbol` in the `typing_extensions` module namespace. /// -/// Returns `Place::Unbound` if the `typing_extensions` module isn't available for some reason. +/// Returns `Place::Undefined` if the `typing_extensions` module isn't available for some reason. #[inline] pub(crate) fn typing_extensions_symbol<'db>( db: &'db dyn Db, @@ -479,7 +526,7 @@ impl<'db> PlaceFromDeclarationsResult<'db> { /// variable: ClassVar[int] /// ``` /// If we look up the declared type of `variable` in the scope of class `C`, we will get -/// the type `int`, a "declaredness" of [`Boundness::PossiblyUnbound`], and the information +/// the type `int`, a "declaredness" of [`Definedness::PossiblyUndefined`], and the information /// that this comes with a [`CLASS_VAR`] type qualifier. /// /// [`CLASS_VAR`]: crate::types::TypeQualifiers::CLASS_VAR @@ -492,7 +539,7 @@ pub(crate) struct PlaceAndQualifiers<'db> { impl Default for PlaceAndQualifiers<'_> { fn default() -> Self { PlaceAndQualifiers { - place: Place::Unbound, + place: Place::Undefined, qualifiers: TypeQualifiers::empty(), } } @@ -510,6 +557,21 @@ impl<'db> PlaceAndQualifiers<'db> { } } + pub(crate) fn unbound() -> Self { + PlaceAndQualifiers { + place: Place::Undefined, + qualifiers: TypeQualifiers::empty(), + } + } + + pub(crate) fn is_undefined(&self) -> bool { + self.place.is_undefined() + } + + pub(crate) fn ignore_possibly_undefined(&self) -> Option> { + self.place.ignore_possibly_undefined() + } + /// Returns `true` if the place has a `ClassVar` type qualifier. pub(crate) fn is_class_var(&self) -> bool { self.qualifiers.contains(TypeQualifiers::CLASS_VAR) @@ -541,7 +603,7 @@ impl<'db> PlaceAndQualifiers<'db> { PlaceAndQualifiers { place, qualifiers } if (qualifiers.contains(TypeQualifiers::FINAL) && place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .is_some_and(|ty| ty.is_unknown())) => { Some(*qualifiers) @@ -571,24 +633,24 @@ impl<'db> PlaceAndQualifiers<'db> { } /// Transform place and qualifiers into a [`LookupResult`], - /// a [`Result`] type in which the `Ok` variant represents a definitely bound place - /// and the `Err` variant represents a place that is either definitely or possibly unbound. + /// a [`Result`] type in which the `Ok` variant represents a definitely defined place + /// and the `Err` variant represents a place that is either definitely or possibly undefined. pub(crate) fn into_lookup_result(self) -> LookupResult<'db> { match self { PlaceAndQualifiers { - place: Place::Type(ty, Boundness::Bound), + place: Place::Defined(ty, origin, Definedness::AlwaysDefined), qualifiers, - } => Ok(TypeAndQualifiers::new(ty, qualifiers)), + } => Ok(TypeAndQualifiers::new(ty, origin, qualifiers)), PlaceAndQualifiers { - place: Place::Type(ty, Boundness::PossiblyUnbound), + place: Place::Defined(ty, origin, Definedness::PossiblyUndefined), qualifiers, - } => Err(LookupError::PossiblyUnbound(TypeAndQualifiers::new( - ty, qualifiers, + } => Err(LookupError::PossiblyUndefined(TypeAndQualifiers::new( + ty, origin, qualifiers, ))), PlaceAndQualifiers { - place: Place::Unbound, + place: Place::Undefined, qualifiers, - } => Err(LookupError::Unbound(qualifiers)), + } => Err(LookupError::Undefined(qualifiers)), } } @@ -612,9 +674,9 @@ impl<'db> PlaceAndQualifiers<'db> { /// 1. If `self` is definitely unbound, return the result of `fallback_fn()`. /// 2. Else, if `fallback` is definitely unbound, return `self`. /// 3. Else, if `self` is possibly unbound and `fallback` is definitely bound, - /// return `Place(, Boundness::Bound)` + /// return `Place(, Definedness::AlwaysDefined)` /// 4. Else, if `self` is possibly unbound and `fallback` is possibly unbound, - /// return `Place(, Boundness::PossiblyUnbound)` + /// return `Place(, Definedness::PossiblyUndefined)` #[must_use] pub(crate) fn or_fall_back_to( self, @@ -693,29 +755,30 @@ 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::Type(Type::Dynamic(DynamicType::Unknown), declaredness), + place: Place::Defined(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::Type(inferred, boundness) => Place::Type( + Place::Defined(inferred, origin, boundness) => Place::Defined( UnionType::from_elements(db, [Type::unknown(), inferred]), + origin, boundness, ) .with_qualifiers(qualifiers), - Place::Unbound => { - Place::Type(Type::unknown(), declaredness).with_qualifiers(qualifiers) + Place::Undefined => { + Place::Defined(Type::unknown(), origin, definedness).with_qualifiers(qualifiers) } } } // Place is declared, trust the declared type place_and_quals @ PlaceAndQualifiers { - place: Place::Type(_, Boundness::Bound), + place: Place::Defined(_, _, Definedness::AlwaysDefined), qualifiers: _, } => place_and_quals, // Place is possibly declared PlaceAndQualifiers { - place: Place::Type(declared_ty, Boundness::PossiblyUnbound), + place: Place::Defined(declared_ty, origin, Definedness::PossiblyUndefined), qualifiers, } => { let bindings = all_considered_bindings(); @@ -724,17 +787,18 @@ pub(crate) fn place_by_id<'db>( let place = match inferred { // Place is possibly undeclared and definitely unbound - Place::Unbound => { - // TODO: We probably don't want to report `Bound` here. This requires a bit of + Place::Undefined => { + // 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::Type(declared_ty, Boundness::Bound) + Place::Defined(declared_ty, origin, Definedness::AlwaysDefined) } // Place is possibly undeclared and (possibly) bound - Place::Type(inferred_ty, boundness) => Place::Type( + Place::Defined(inferred_ty, origin, boundness) => Place::Defined( UnionType::from_elements(db, [inferred_ty, declared_ty]), + origin, if boundness_analysis == BoundnessAnalysis::AssumeBound { - Boundness::Bound + Definedness::AlwaysDefined } else { boundness }, @@ -745,7 +809,7 @@ pub(crate) fn place_by_id<'db>( } // Place is undeclared, return the union of `Unknown` with the inferred type PlaceAndQualifiers { - place: Place::Unbound, + place: Place::Undefined, qualifiers: _, } => { let bindings = all_considered_bindings(); @@ -753,8 +817,8 @@ pub(crate) fn place_by_id<'db>( let mut inferred = place_from_bindings_impl(db, bindings, requires_explicit_reexport); if boundness_analysis == BoundnessAnalysis::AssumeBound { - if let Place::Type(ty, Boundness::PossiblyUnbound) = inferred { - inferred = Place::Type(ty, Boundness::Bound); + if let Place::Defined(ty, origin, Definedness::PossiblyUndefined) = inferred { + inferred = Place::Defined(ty, origin, Definedness::AlwaysDefined); } } @@ -1026,25 +1090,27 @@ fn place_from_bindings_impl<'db>( }; let boundness = match boundness_analysis { - BoundnessAnalysis::AssumeBound => Boundness::Bound, + BoundnessAnalysis::AssumeBound => Definedness::AlwaysDefined, BoundnessAnalysis::BasedOnUnboundVisibility => match unbound_visibility() { Some(Truthiness::AlwaysTrue) => { unreachable!( "If we have at least one binding, the implicit `unbound` binding should not be definitely visible" ) } - Some(Truthiness::AlwaysFalse) | None => Boundness::Bound, - Some(Truthiness::Ambiguous) => Boundness::PossiblyUnbound, + Some(Truthiness::AlwaysFalse) | None => Definedness::AlwaysDefined, + Some(Truthiness::Ambiguous) => Definedness::PossiblyUndefined, }, }; match deleted_reachability { - Truthiness::AlwaysFalse => Place::Type(ty, boundness), - Truthiness::AlwaysTrue => Place::Unbound, - Truthiness::Ambiguous => Place::Type(ty, Boundness::PossiblyUnbound), + Truthiness::AlwaysFalse => Place::Defined(ty, TypeOrigin::Inferred, boundness), + Truthiness::AlwaysTrue => Place::Undefined, + Truthiness::Ambiguous => { + Place::Defined(ty, TypeOrigin::Inferred, Definedness::PossiblyUndefined) + } } } else { - Place::Unbound + Place::Undefined } } @@ -1145,7 +1211,8 @@ impl<'db> DeclaredTypeBuilder<'db> { } fn build(mut self) -> DeclaredTypeAndConflictingTypes<'db> { - let type_and_quals = TypeAndQualifiers::new(self.inner.build(), self.qualifiers); + let type_and_quals = + TypeAndQualifiers::new(self.inner.build(), TypeOrigin::Declared, self.qualifiers); if self.conflicting_types.is_empty() { (type_and_quals, None) } else { @@ -1245,13 +1312,13 @@ fn place_from_declarations_impl<'db>( let boundness = match boundness_analysis { BoundnessAnalysis::AssumeBound => { if all_declarations_definitely_reachable { - Boundness::Bound + Definedness::AlwaysDefined } else { // For declarations, it is important to consider the possibility that they might only // be bound in one control flow path, while the other path contains a binding. In order // to even consider the bindings as well in `place_by_id`, we return `PossiblyUnbound` // here. - Boundness::PossiblyUnbound + Definedness::PossiblyUndefined } } BoundnessAnalysis::BasedOnUnboundVisibility => match undeclared_reachability { @@ -1260,13 +1327,14 @@ fn place_from_declarations_impl<'db>( "If we have at least one declaration, the implicit `unbound` binding should not be definitely visible" ) } - Truthiness::AlwaysFalse => Boundness::Bound, - Truthiness::Ambiguous => Boundness::PossiblyUnbound, + Truthiness::AlwaysFalse => Definedness::AlwaysDefined, + Truthiness::Ambiguous => Definedness::PossiblyUndefined, }, }; let place_and_quals = - Place::Type(declared.inner_type(), boundness).with_qualifiers(declared.qualifiers()); + Place::Defined(declared.inner_type(), TypeOrigin::Declared, boundness) + .with_qualifiers(declared.qualifiers()); if let Some(conflicting) = conflicting { PlaceFromDeclarationsResult::conflict(place_and_quals, conflicting) @@ -1279,7 +1347,7 @@ fn place_from_declarations_impl<'db>( } } else { PlaceFromDeclarationsResult { - place_and_quals: Place::Unbound.into(), + place_and_quals: Place::Undefined.into(), conflicting_types: None, single_declaration: None, } @@ -1314,7 +1382,7 @@ mod implicit_globals { use crate::Program; use crate::db::Db; - use crate::place::{Boundness, PlaceAndQualifiers}; + use crate::place::{Definedness, PlaceAndQualifiers, TypeOrigin}; use crate::semantic_index::symbol::Symbol; use crate::semantic_index::{place_table, use_def_map}; use crate::types::{CallableType, KnownClass, Parameter, Parameters, Signature, Type}; @@ -1330,16 +1398,16 @@ mod implicit_globals { .iter() .any(|module_type_member| module_type_member == name) { - return Place::Unbound.into(); + return Place::Undefined.into(); } let Type::ClassLiteral(module_type_class) = KnownClass::ModuleType.to_class_literal(db) else { - return Place::Unbound.into(); + return Place::Undefined.into(); }; let module_type_scope = module_type_class.body_scope(db); let place_table = place_table(db, module_type_scope); let Some(symbol_id) = place_table.symbol_id(name) else { - return Place::Unbound.into(); + return Place::Undefined.into(); }; place_from_declarations( db, @@ -1348,7 +1416,7 @@ mod implicit_globals { .ignore_conflicting_declarations() } - /// Looks up the type of an "implicit global symbol". Returns [`Place::Unbound`] if + /// Looks up the type of an "implicit global symbol". Returns [`Place::Undefined`] if /// `name` is not present as an implicit symbol in module-global namespaces. /// /// Implicit global symbols are symbols such as `__doc__`, `__name__`, and `__file__` @@ -1359,7 +1427,7 @@ mod implicit_globals { /// up in the global scope **from within the same file**. If the symbol is being looked up /// from outside the file (e.g. via imports), use [`super::imported_symbol`] (or fallback logic /// like the logic used in that function) instead. The reason is that this function returns - /// [`Place::Unbound`] for `__init__` and `__dict__` (which cannot be found in globals if + /// [`Place::Undefined`] for `__init__` and `__dict__` (which cannot be found in globals if /// the lookup is being done from the same file) -- but these symbols *are* available in the /// global scope if they're being imported **from a different file**. pub(crate) fn module_type_implicit_global_symbol<'db>( @@ -1378,10 +1446,11 @@ mod implicit_globals { // Created lazily by the warnings machinery; may be absent. // Model as possibly-unbound to avoid false negatives. - "__warningregistry__" => Place::Type( + "__warningregistry__" => Place::Defined( KnownClass::Dict .to_specialized_instance(db, [Type::any(), KnownClass::Int.to_instance(db)]), - Boundness::PossiblyUnbound, + TypeOrigin::Inferred, + Definedness::PossiblyUndefined, ) .into(), @@ -1398,9 +1467,10 @@ mod implicit_globals { [KnownClass::Str.to_instance(db), Type::any()], )), ); - Place::Type( + Place::Defined( CallableType::function_like(db, signature), - Boundness::PossiblyUnbound, + TypeOrigin::Inferred, + Definedness::PossiblyUndefined, ) .into() } @@ -1417,7 +1487,7 @@ mod implicit_globals { KnownClass::ModuleType.to_instance(db).member(db, name) } - _ => Place::Unbound.into(), + _ => Place::Undefined.into(), } } @@ -1545,7 +1615,7 @@ pub(crate) enum BoundnessAnalysis { /// `unbound` binding. In the example below, when analyzing the visibility of the /// `x = ` binding from the position of the end of the scope, it would be /// `Truthiness::Ambiguous`, because it could either be visible or not, depending on the - /// `flag()` return value. This would result in a `Boundness::PossiblyUnbound` for `x`. + /// `flag()` return value. This would result in a `Definedness::PossiblyUndefined` for `x`. /// /// ```py /// x = @@ -1563,21 +1633,30 @@ mod tests { #[test] fn test_symbol_or_fall_back_to() { - use Boundness::{Bound, PossiblyUnbound}; + use Definedness::{AlwaysDefined, PossiblyUndefined}; + use TypeOrigin::Inferred; let db = setup_db(); let ty1 = Type::IntLiteral(1); let ty2 = Type::IntLiteral(2); - let unbound = || Place::Unbound.with_qualifiers(TypeQualifiers::empty()); + let unbound = || Place::Undefined.with_qualifiers(TypeQualifiers::empty()); - let possibly_unbound_ty1 = - || Place::Type(ty1, PossiblyUnbound).with_qualifiers(TypeQualifiers::empty()); - let possibly_unbound_ty2 = - || Place::Type(ty2, PossiblyUnbound).with_qualifiers(TypeQualifiers::empty()); + let possibly_unbound_ty1 = || { + Place::Defined(ty1, Inferred, PossiblyUndefined) + .with_qualifiers(TypeQualifiers::empty()) + }; + let possibly_unbound_ty2 = || { + Place::Defined(ty2, Inferred, PossiblyUndefined) + .with_qualifiers(TypeQualifiers::empty()) + }; - let bound_ty1 = || Place::Type(ty1, Bound).with_qualifiers(TypeQualifiers::empty()); - let bound_ty2 = || Place::Type(ty2, Bound).with_qualifiers(TypeQualifiers::empty()); + let bound_ty1 = || { + Place::Defined(ty1, Inferred, AlwaysDefined).with_qualifiers(TypeQualifiers::empty()) + }; + let bound_ty2 = || { + Place::Defined(ty2, Inferred, AlwaysDefined).with_qualifiers(TypeQualifiers::empty()) + }; // Start from an unbound symbol assert_eq!(unbound().or_fall_back_to(&db, unbound), unbound()); @@ -1594,11 +1673,21 @@ mod tests { ); assert_eq!( possibly_unbound_ty1().or_fall_back_to(&db, possibly_unbound_ty2), - Place::Type(UnionType::from_elements(&db, [ty1, ty2]), PossiblyUnbound).into() + Place::Defined( + UnionType::from_elements(&db, [ty1, ty2]), + Inferred, + PossiblyUndefined + ) + .into() ); assert_eq!( possibly_unbound_ty1().or_fall_back_to(&db, bound_ty2), - Place::Type(UnionType::from_elements(&db, [ty1, ty2]), Bound).into() + Place::Defined( + UnionType::from_elements(&db, [ty1, ty2]), + Inferred, + AlwaysDefined + ) + .into() ); // Start from a definitely bound symbol @@ -1614,7 +1703,7 @@ mod tests { fn assert_bound_string_symbol<'db>(db: &'db dyn Db, symbol: Place<'db>) { assert!(matches!( symbol, - Place::Type(Type::NominalInstance(_), Boundness::Bound) + Place::Defined(Type::NominalInstance(_), _, 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 67b266b3ff..3d09733324 100644 --- a/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs @@ -917,13 +917,17 @@ impl ReachabilityConstraints { ) .place { - crate::place::Place::Type(_, crate::place::Boundness::Bound) => { - Truthiness::AlwaysTrue - } - crate::place::Place::Type(_, crate::place::Boundness::PossiblyUnbound) => { - Truthiness::Ambiguous - } - crate::place::Place::Unbound => Truthiness::AlwaysFalse, + crate::place::Place::Defined( + _, + _, + crate::place::Definedness::AlwaysDefined, + ) => Truthiness::AlwaysTrue, + crate::place::Place::Defined( + _, + _, + 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 3f8702c858..e603f66bc2 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -32,7 +32,7 @@ pub(crate) use self::signatures::{CallableSignature, Parameter, Parameters, Sign pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType}; use crate::module_name::ModuleName; use crate::module_resolver::{KnownModule, resolve_module}; -use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol}; +use crate::place::{Definedness, Place, PlaceAndQualifiers, TypeOrigin, imported_symbol}; use crate::semantic_index::definition::{Definition, DefinitionKind}; use crate::semantic_index::place::ScopedPlaceId; use crate::semantic_index::scope::ScopeId; @@ -1440,7 +1440,7 @@ impl<'db> Type<'db> { ) .place; - if let Place::Type(ty, Boundness::Bound) = call_symbol { + if let Place::Defined(ty, _, Definedness::AlwaysDefined) = call_symbol { ty.try_upcast_to_callable(db) } else { None @@ -2533,7 +2533,7 @@ impl<'db> Type<'db> { other .member(db, member.name()) .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .when_none_or(|attribute_type| { member.has_disjoint_type_from( db, @@ -2899,14 +2899,14 @@ 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::Type(attribute_type, _) => member.has_disjoint_type_from( + Place::Defined(attribute_type, _, _) => member.has_disjoint_type_from( db, attribute_type, inferable, disjointness_visitor, relation_visitor, ), - Place::Unbound => ConstraintSet::from(false), + Place::Undefined => ConstraintSet::from(false), } }) }) @@ -3136,7 +3136,7 @@ impl<'db> Type<'db> { MemberLookupPolicy::NO_INSTANCE_FALLBACK, ) .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .when_none_or(|dunder_call| { dunder_call .has_relation_to_impl( @@ -3458,7 +3458,7 @@ impl<'db> Type<'db> { ), (Some(KnownClass::FunctionType), "__set__" | "__delete__") => { // Hard code this knowledge, as we look up `__set__` and `__delete__` on `FunctionType` often. - Some(Place::Unbound.into()) + Some(Place::Undefined.into()) } (Some(KnownClass::Property), "__get__") => Some( Place::bound(Type::WrapperDescriptor( @@ -3511,7 +3511,7 @@ impl<'db> Type<'db> { // MRO of the class `object`. Type::NominalInstance(instance) if instance.has_known_class(db, KnownClass::Type) => { if policy.mro_no_object_fallback() { - Some(Place::Unbound.into()) + Some(Place::Undefined.into()) } else { KnownClass::Object .to_class_literal(db) @@ -3672,7 +3672,7 @@ impl<'db> Type<'db> { of type variable {} in inferable position", self.display(db) ); - Place::Unbound.into() + Place::Undefined.into() } Type::IntLiteral(_) => KnownClass::Int.to_instance(db).instance_member(db, name), @@ -3692,7 +3692,7 @@ impl<'db> Type<'db> { .to_instance(db) .instance_member(db, name), - Type::SpecialForm(_) | Type::KnownInstance(_) => Place::Unbound.into(), + Type::SpecialForm(_) | Type::KnownInstance(_) => Place::Undefined.into(), Type::PropertyInstance(_) => KnownClass::Property .to_instance(db) @@ -3710,10 +3710,10 @@ impl<'db> Type<'db> { // required, as `instance_member` is only called for instance-like types through `member`, // but we might want to add this in the future. Type::ClassLiteral(_) | Type::GenericAlias(_) | Type::SubclassOf(_) => { - Place::Unbound.into() + Place::Undefined.into() } - Type::TypedDict(_) => Place::Unbound.into(), + Type::TypedDict(_) => Place::Undefined.into(), Type::TypeAlias(alias) => alias.value_type(db).instance_member(db, name), } @@ -3726,9 +3726,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::Type(_, _) = 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::Type(_, _)) = + } else if let Some(place @ Place::Defined(_, _, _)) = self.find_name_in_mro(db, name).map(|inner| inner.place) { place @@ -3781,11 +3781,11 @@ impl<'db> Type<'db> { let descr_get = self.class_member(db, "__get__".into()).place; - if let Place::Type(descr_get, descr_get_boundness) = descr_get { + if let Place::Defined(descr_get, _, descr_get_boundness) = descr_get { let return_ty = descr_get .try_call(db, &CallArguments::positional([self, instance, owner])) .map(|bindings| { - if descr_get_boundness == Boundness::Bound { + if descr_get_boundness == Definedness::AlwaysDefined { bindings.return_type(db) } else { UnionType::from_elements(db, [bindings.return_type(db), self]) @@ -3824,19 +3824,20 @@ impl<'db> Type<'db> { // // The same is true for `Never`. PlaceAndQualifiers { - place: Place::Type(Type::Dynamic(_) | Type::Never, _), + place: Place::Defined(Type::Dynamic(_) | Type::Never, _, _), qualifiers: _, } => (attribute, AttributeKind::DataDescriptor), PlaceAndQualifiers { - place: Place::Type(Type::Union(union), boundness), + place: Place::Defined(Type::Union(union), origin, boundness), qualifiers, } => ( union .map_with_boundness(db, |elem| { - Place::Type( + Place::Defined( elem.try_call_dunder_get(db, instance, owner) .map_or(*elem, |(ty, _)| ty), + origin, boundness, ) }) @@ -3853,14 +3854,15 @@ impl<'db> Type<'db> { ), PlaceAndQualifiers { - place: Place::Type(Type::Intersection(intersection), boundness), + place: Place::Defined(Type::Intersection(intersection), origin, boundness), qualifiers, } => ( intersection .map_with_boundness(db, |elem| { - Place::Type( + Place::Defined( elem.try_call_dunder_get(db, instance, owner) .map_or(*elem, |(ty, _)| ty), + origin, boundness, ) }) @@ -3870,13 +3872,16 @@ impl<'db> Type<'db> { ), PlaceAndQualifiers { - place: Place::Type(attribute_ty, boundness), + place: Place::Defined(attribute_ty, origin, boundness), qualifiers: _, } => { if let Some((return_ty, attribute_kind)) = attribute_ty.try_call_dunder_get(db, instance, owner) { - (Place::Type(return_ty, boundness).into(), attribute_kind) + ( + Place::Defined(return_ty, origin, boundness).into(), + attribute_kind, + ) } else { (attribute, AttributeKind::NormalOrNonDataDescriptor) } @@ -3915,11 +3920,11 @@ impl<'db> Type<'db> { .iter_positive(db) .any(|ty| ty.is_data_descriptor_impl(db, any_of_union)), _ => { - !self.class_member(db, "__set__".into()).place.is_unbound() + !self.class_member(db, "__set__".into()).place.is_undefined() || !self .class_member(db, "__delete__".into()) .place - .is_unbound() + .is_undefined() } } } @@ -3967,25 +3972,28 @@ 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::Type(_, _), _, Place::Unbound) => { + (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::Type(_, Boundness::Bound), AttributeKind::DataDescriptor, _) => { - meta_attr.with_qualifiers(meta_attr_qualifiers) - } + ( + meta_attr @ Place::Defined(_, _, Definedness::AlwaysDefined), + AttributeKind::DataDescriptor, + _, + ) => meta_attr.with_qualifiers(meta_attr_qualifiers), // `meta_attr` is the return type of a data descriptor, but the attribute on the // 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::Type(meta_attr_ty, Boundness::PossiblyUnbound), + Place::Defined(meta_attr_ty, meta_origin, Definedness::PossiblyUndefined), AttributeKind::DataDescriptor, - Place::Type(fallback_ty, fallback_boundness), - ) => Place::Type( + Place::Defined(fallback_ty, fallback_origin, fallback_boundness), + ) => Place::Defined( UnionType::from_elements(db, [meta_attr_ty, fallback_ty]), + meta_origin.merge(fallback_origin), fallback_boundness, ) .with_qualifiers(meta_attr_qualifiers.union(fallback_qualifiers)), @@ -3999,9 +4007,9 @@ 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::Type(_, _), + Place::Defined(_, _, _), AttributeKind::NormalOrNonDataDescriptor, - fallback @ Place::Type(_, Boundness::Bound), + fallback @ Place::Defined(_, _, Definedness::AlwaysDefined), ) if policy == InstanceFallbackShadowsNonDataDescriptor::Yes => { fallback.with_qualifiers(fallback_qualifiers) } @@ -4010,17 +4018,18 @@ 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::Type(meta_attr_ty, meta_attr_boundness), + Place::Defined(meta_attr_ty, meta_origin, meta_attr_boundness), AttributeKind::NormalOrNonDataDescriptor, - Place::Type(fallback_ty, fallback_boundness), - ) => Place::Type( + Place::Defined(fallback_ty, fallback_origin, fallback_boundness), + ) => Place::Defined( UnionType::from_elements(db, [meta_attr_ty, fallback_ty]), + meta_origin.merge(fallback_origin), meta_attr_boundness.max(fallback_boundness), ) .with_qualifiers(meta_attr_qualifiers.union(fallback_qualifiers)), // If the attribute is not found on the meta-type, we simply return the fallback. - (Place::Unbound, _, fallback) => fallback.with_qualifiers(fallback_qualifiers), + (Place::Undefined, _, fallback) => fallback.with_qualifiers(fallback_qualifiers), } } @@ -4183,7 +4192,7 @@ impl<'db> Type<'db> { Type::ModuleLiteral(module) => module.static_member(db, name_str), // If a protocol does not include a member and the policy disables falling back to - // `object`, we return `Place::Unbound` here. This short-circuits attribute lookup + // `object`, we return `Place::Undefined` here. This short-circuits attribute lookup // before we find the "fallback to attribute access on `object`" logic later on // (otherwise we would infer that all synthesized protocols have `__getattribute__` // methods, and therefore that all synthesized protocols have all possible attributes.) @@ -4196,13 +4205,13 @@ impl<'db> Type<'db> { }) if policy.mro_no_object_fallback() && !protocol.interface().includes_member(db, name_str) => { - Place::Unbound.into() + Place::Undefined.into() } _ if policy.no_instance_fallback() => self.invoke_descriptor_protocol( db, name_str, - Place::Unbound.into(), + Place::Undefined.into(), InstanceFallbackShadowsNonDataDescriptor::No, policy, ), @@ -4230,7 +4239,7 @@ impl<'db> Type<'db> { { enum_metadata(db, enum_literal.enum_class(db)) .and_then(|metadata| metadata.members.get(enum_literal.name(db))) - .map_or_else(|| Place::Unbound, Place::bound) + .map_or_else(|| Place::Undefined, Place::bound) .into() } @@ -4275,7 +4284,7 @@ impl<'db> Type<'db> { .and_then(|instance| instance.known_class(db)), Some(KnownClass::ModuleType | KnownClass::GenericAlias) ) { - return Place::Unbound.into(); + return Place::Undefined.into(); } self.try_call_dunder( @@ -4286,14 +4295,14 @@ impl<'db> Type<'db> { ) .map(|outcome| Place::bound(outcome.return_type(db))) // TODO: Handle call errors here. - .unwrap_or(Place::Unbound) + .unwrap_or(Place::Undefined) .into() }; let custom_getattribute_result = || { // Avoid cycles when looking up `__getattribute__` if "__getattribute__" == name.as_str() { - return Place::Unbound.into(); + return Place::Undefined.into(); } // Typeshed has a `__getattribute__` method defined on `builtins.object` so we @@ -4307,29 +4316,29 @@ impl<'db> Type<'db> { ) .map(|outcome| Place::bound(outcome.return_type(db))) // TODO: Handle call errors here. - .unwrap_or(Place::Unbound) + .unwrap_or(Place::Undefined) .into() }; if result.is_class_var() && self.is_typed_dict() { // `ClassVar`s on `TypedDictFallback` can not be accessed on inhabitants of `SomeTypedDict`. // They can only be accessed on `SomeTypedDict` directly. - return Place::Unbound.into(); + return Place::Undefined.into(); } match result { member @ PlaceAndQualifiers { - place: Place::Type(_, Boundness::Bound), + place: Place::Defined(_, _, Definedness::AlwaysDefined), qualifiers: _, } => member, member @ PlaceAndQualifiers { - place: Place::Type(_, Boundness::PossiblyUnbound), + place: Place::Defined(_, _, Definedness::PossiblyUndefined), qualifiers: _, } => member .or_fall_back_to(db, custom_getattribute_result) .or_fall_back_to(db, custom_getattr_result), PlaceAndQualifiers { - place: Place::Unbound, + place: Place::Undefined, qualifiers: _, } => custom_getattribute_result().or_fall_back_to(db, custom_getattr_result), } @@ -4354,14 +4363,11 @@ impl<'db> Type<'db> { } { if let Some(metadata) = enum_metadata(db, enum_class) { if let Some(resolved_name) = metadata.resolve_member(&name) { - return Place::Type( - Type::EnumLiteral(EnumLiteralType::new( - db, - enum_class, - resolved_name, - )), - Boundness::Bound, - ) + return Place::bound(Type::EnumLiteral(EnumLiteralType::new( + db, + enum_class, + resolved_name, + ))) .into(); } } @@ -5367,15 +5373,15 @@ impl<'db> Type<'db> { ) .place { - Place::Type(dunder_callable, boundness) => { + Place::Defined(dunder_callable, _, boundness) => { let mut bindings = dunder_callable.bindings(db); bindings.replace_callable_type(dunder_callable, self); - if boundness == Boundness::PossiblyUnbound { + if boundness == Definedness::PossiblyUndefined { bindings.set_dunder_call_is_possibly_unbound(); } bindings } - Place::Unbound => CallableBinding::not_callable(self).into(), + Place::Undefined => CallableBinding::not_callable(self).into(), } } @@ -5488,17 +5494,17 @@ impl<'db> Type<'db> { ) .place { - Place::Type(dunder_callable, boundness) => { + Place::Defined(dunder_callable, _, boundness) => { let bindings = dunder_callable .bindings(db) .match_parameters(db, argument_types) .check_types(db, argument_types, &tcx)?; - if boundness == Boundness::PossiblyUnbound { + if boundness == Definedness::PossiblyUndefined { return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); } Ok(bindings) } - Place::Unbound => Err(CallDunderError::MethodNotAvailable), + Place::Undefined => Err(CallDunderError::MethodNotAvailable), } } @@ -6005,16 +6011,16 @@ impl<'db> Type<'db> { let new_method = self_type.lookup_dunder_new(db, ()); let new_call_outcome = new_method.and_then(|new_method| { match new_method.place.try_call_dunder_get(db, self_type) { - Place::Type(new_method, boundness) => { + Place::Defined(new_method, _, boundness) => { let result = new_method.try_call(db, argument_types.with_self(Some(self_type)).as_ref()); - if boundness == Boundness::PossiblyUnbound { + if boundness == Definedness::PossiblyUndefined { Some(Err(DunderNewCallError::PossiblyUnbound(result.err()))) } else { Some(result.map_err(DunderNewCallError::CallError)) } } - Place::Unbound => None, + Place::Undefined => None, } }); @@ -6034,7 +6040,7 @@ impl<'db> Type<'db> { | MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, ) .place - .is_unbound() + .is_undefined() { Some(init_ty.try_call_dunder(db, "__init__", argument_types, tcx)) } else { @@ -7743,18 +7749,23 @@ impl TypeQualifiers { #[derive(Clone, Debug, Copy, Eq, PartialEq, salsa::Update, get_size2::GetSize)] pub(crate) struct TypeAndQualifiers<'db> { inner: Type<'db>, + origin: TypeOrigin, qualifiers: TypeQualifiers, } impl<'db> TypeAndQualifiers<'db> { - pub(crate) fn new(inner: Type<'db>, qualifiers: TypeQualifiers) -> Self { - Self { inner, qualifiers } + pub(crate) fn new(inner: Type<'db>, origin: TypeOrigin, qualifiers: TypeQualifiers) -> Self { + Self { + inner, + origin, + qualifiers, + } } - /// Constructor that creates a [`TypeAndQualifiers`] instance with type `Unknown` and no qualifiers. - pub(crate) fn unknown() -> Self { + pub(crate) fn declared(inner: Type<'db>) -> Self { Self { - inner: Type::unknown(), + inner, + origin: TypeOrigin::Declared, qualifiers: TypeQualifiers::empty(), } } @@ -7764,6 +7775,10 @@ impl<'db> TypeAndQualifiers<'db> { self.inner } + pub(crate) fn origin(&self) -> TypeOrigin { + self.origin + } + /// Insert/add an additional type qualifier. pub(crate) fn add_qualifier(&mut self, qualifier: TypeQualifiers) { self.qualifiers |= qualifier; @@ -7775,15 +7790,6 @@ impl<'db> TypeAndQualifiers<'db> { } } -impl<'db> From> for TypeAndQualifiers<'db> { - fn from(inner: Type<'db>) -> Self { - Self { - inner, - qualifiers: TypeQualifiers::empty(), - } - } -} - /// Error struct providing information on type(s) that were deemed to be invalid /// in a type expression context, and the type we should therefore fallback to /// for the problematic type expression. @@ -7929,7 +7935,7 @@ impl<'db> InvalidTypeExpression<'db> { let Some(module_member_with_same_name) = ty .member(db, module_name_final_part) .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() else { return; }; @@ -10676,18 +10682,18 @@ 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::Type(getattr_type, boundness) = getattr_symbol.place { + if let Place::Defined(getattr_type, origin, boundness) = 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( db, &CallArguments::positional([Type::string_literal(db, name)]), ) { - return Place::Type(outcome.return_type(db), boundness).into(); + return Place::Defined(outcome.return_type(db), origin, boundness).into(); } } } - Place::Unbound.into() + Place::Undefined.into() } fn static_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { @@ -10722,7 +10728,7 @@ impl<'db> ModuleLiteralType<'db> { .unwrap_or_default(); // If the normal lookup failed, try to call the module's `__getattr__` function - if place_and_qualifiers.place.is_unbound() { + if place_and_qualifiers.place.is_undefined() { return self.try_module_getattr(db, name); } @@ -11119,14 +11125,16 @@ impl<'db> UnionType<'db> { let mut all_unbound = true; let mut possibly_unbound = false; + let mut origin = TypeOrigin::Declared; for ty in self.elements(db) { let ty_member = transform_fn(ty); match ty_member { - Place::Unbound => { + Place::Undefined => { possibly_unbound = true; } - Place::Type(ty_member, member_boundness) => { - if member_boundness == Boundness::PossiblyUnbound { + Place::Defined(ty_member, member_origin, member_boundness) => { + origin = origin.merge(member_origin); + if member_boundness == Definedness::PossiblyUndefined { possibly_unbound = true; } @@ -11137,14 +11145,15 @@ impl<'db> UnionType<'db> { } if all_unbound { - Place::Unbound + Place::Undefined } else { - Place::Type( + Place::Defined( builder.build(), + origin, if possibly_unbound { - Boundness::PossiblyUnbound + Definedness::PossiblyUndefined } else { - Boundness::Bound + Definedness::AlwaysDefined }, ) } @@ -11160,6 +11169,7 @@ impl<'db> UnionType<'db> { let mut all_unbound = true; let mut possibly_unbound = false; + let mut origin = TypeOrigin::Declared; for ty in self.elements(db) { let PlaceAndQualifiers { place: ty_member, @@ -11167,11 +11177,12 @@ impl<'db> UnionType<'db> { } = transform_fn(ty); qualifiers |= new_qualifiers; match ty_member { - Place::Unbound => { + Place::Undefined => { possibly_unbound = true; } - Place::Type(ty_member, member_boundness) => { - if member_boundness == Boundness::PossiblyUnbound { + Place::Defined(ty_member, member_origin, member_boundness) => { + origin = origin.merge(member_origin); + if member_boundness == Definedness::PossiblyUndefined { possibly_unbound = true; } @@ -11182,14 +11193,15 @@ impl<'db> UnionType<'db> { } PlaceAndQualifiers { place: if all_unbound { - Place::Unbound + Place::Undefined } else { - Place::Type( + Place::Defined( builder.build(), + origin, if possibly_unbound { - Boundness::PossiblyUnbound + Definedness::PossiblyUndefined } else { - Boundness::Bound + Definedness::AlwaysDefined }, ) }, @@ -11394,13 +11406,15 @@ impl<'db> IntersectionType<'db> { let mut all_unbound = true; let mut any_definitely_bound = false; + let mut origin = TypeOrigin::Declared; for ty in self.positive_elements_or_object(db) { let ty_member = transform_fn(&ty); match ty_member { - Place::Unbound => {} - Place::Type(ty_member, member_boundness) => { + Place::Undefined => {} + Place::Defined(ty_member, member_origin, member_boundness) => { + origin = origin.merge(member_origin); all_unbound = false; - if member_boundness == Boundness::Bound { + if member_boundness == Definedness::AlwaysDefined { any_definitely_bound = true; } @@ -11410,14 +11424,15 @@ impl<'db> IntersectionType<'db> { } if all_unbound { - Place::Unbound + Place::Undefined } else { - Place::Type( + Place::Defined( builder.build(), + origin, if any_definitely_bound { - Boundness::Bound + Definedness::AlwaysDefined } else { - Boundness::PossiblyUnbound + Definedness::PossiblyUndefined }, ) } @@ -11433,6 +11448,7 @@ impl<'db> IntersectionType<'db> { let mut all_unbound = true; let mut any_definitely_bound = false; + let mut origin = TypeOrigin::Declared; for ty in self.positive_elements_or_object(db) { let PlaceAndQualifiers { place: member, @@ -11440,10 +11456,11 @@ impl<'db> IntersectionType<'db> { } = transform_fn(&ty); qualifiers |= new_qualifiers; match member { - Place::Unbound => {} - Place::Type(ty_member, member_boundness) => { + Place::Undefined => {} + Place::Defined(ty_member, member_origin, member_boundness) => { + origin = origin.merge(member_origin); all_unbound = false; - if member_boundness == Boundness::Bound { + if member_boundness == Definedness::AlwaysDefined { any_definitely_bound = true; } @@ -11454,14 +11471,15 @@ impl<'db> IntersectionType<'db> { PlaceAndQualifiers { place: if all_unbound { - Place::Unbound + Place::Undefined } else { - Place::Type( + Place::Defined( builder.build(), + origin, if any_definitely_bound { - Boundness::Bound + Definedness::AlwaysDefined } else { - Boundness::PossiblyUnbound + Definedness::PossiblyUndefined }, ) }, diff --git a/crates/ty_python_semantic/src/types/builder.rs b/crates/ty_python_semantic/src/types/builder.rs index 28d589b5ae..944516c6e8 100644 --- a/crates/ty_python_semantic/src/types/builder.rs +++ b/crates/ty_python_semantic/src/types/builder.rs @@ -1255,7 +1255,7 @@ mod tests { let safe_uuid_class = known_module_symbol(&db, KnownModule::Uuid, "SafeUUID") .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .unwrap(); let literals = enum_member_literals(&db, safe_uuid_class.expect_class_literal(), None) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 41a392a88b..e7b8758271 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -15,7 +15,7 @@ use super::{Argument, CallArguments, CallError, CallErrorKind, InferContext, Sig use crate::Program; use crate::db::Db; use crate::dunder_all::dunder_all_names; -use crate::place::{Boundness, Place}; +use crate::place::{Definedness, Place}; use crate::types::call::arguments::{Expansion, is_expandable_type}; use crate::types::diagnostic::{ CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, @@ -839,7 +839,7 @@ 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::Type(ty, Boundness::Bound) => { + Place::Defined(ty, _, Definedness::AlwaysDefined) => { if ty.is_dynamic() { // Here, we attempt to model the fact that an attribute lookup on // a dynamic type could fail @@ -849,10 +849,10 @@ impl<'db> Bindings<'db> { ty } } - Place::Type(ty, Boundness::PossiblyUnbound) => { + Place::Defined(ty, _, Definedness::PossiblyUndefined) => { union_with_default(ty) } - Place::Unbound => default, + Place::Undefined => default, }, ); } @@ -2399,7 +2399,7 @@ impl<'a, 'db> ArgumentMatcher<'a, 'db> { ) .place }) { - Some(Place::Type(keys_method, Boundness::Bound)) => keys_method + Some(Place::Defined(keys_method, _, Definedness::AlwaysDefined)) => keys_method .try_call(db, &CallArguments::positional([Type::unknown()])) .ok() .map_or_else(Type::unknown, |bindings| bindings.return_type(db)), @@ -2717,7 +2717,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { ) .place { - Place::Type(keys_method, Boundness::Bound) => keys_method + Place::Defined(keys_method, _, Definedness::AlwaysDefined) => keys_method .try_call(self.db, &CallArguments::none()) .ok() .and_then(|bindings| { @@ -2762,7 +2762,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { ) .place { - Place::Type(keys_method, Boundness::Bound) => keys_method + 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)), diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 876f6d3930..64ba611a46 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -9,6 +9,7 @@ use super::{ }; use crate::FxOrderMap; use crate::module_resolver::KnownModule; +use crate::place::TypeOrigin; use crate::semantic_index::definition::{Definition, DefinitionState}; use crate::semantic_index::scope::{NodeWithScopeKind, Scope}; use crate::semantic_index::symbol::Symbol; @@ -42,7 +43,7 @@ use crate::{ Db, FxIndexMap, FxIndexSet, FxOrderSet, Program, module_resolver::file_to_module, place::{ - Boundness, LookupError, LookupResult, Place, PlaceAndQualifiers, known_module_symbol, + Definedness, LookupError, LookupResult, Place, PlaceAndQualifiers, known_module_symbol, place_from_bindings, place_from_declarations, }, semantic_index::{ @@ -749,7 +750,7 @@ impl<'db> ClassType<'db> { /// class that the lookup is being performed on, and not the class containing the (possibly /// inherited) member. /// - /// Returns [`Place::Unbound`] if `name` cannot be found in this class's scope + /// Returns [`Place::Undefined`] if `name` cannot be found in this class's scope /// directly. Use [`ClassType::class_member`] if you require a method that will /// traverse through the MRO until it finds the member. pub(super) fn own_class_member( @@ -1055,7 +1056,7 @@ impl<'db> ClassType<'db> { let (class_literal, specialization) = self.class_literal(db); if class_literal.is_typed_dict(db) { - return Place::Unbound.into(); + return Place::Undefined.into(); } class_literal @@ -1086,7 +1087,7 @@ impl<'db> ClassType<'db> { ) .place; - if let Place::Type(Type::BoundMethod(metaclass_dunder_call_function), _) = + if let Place::Defined(Type::BoundMethod(metaclass_dunder_call_function), _, _) = metaclass_dunder_call_function_symbol { // TODO: this intentionally diverges from step 1 in @@ -1105,7 +1106,7 @@ impl<'db> ClassType<'db> { .place; let dunder_new_signature = dunder_new_function_symbol - .ignore_possibly_unbound() + .ignore_possibly_undefined() .and_then(|ty| match ty { Type::FunctionLiteral(function) => Some(function.signature(db)), Type::Callable(callable) => Some(callable.signatures(db)), @@ -1156,7 +1157,7 @@ impl<'db> ClassType<'db> { // 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::Type(ty, _) = dunder_init_function_symbol { + if let Place::Defined(ty, _, _) = dunder_init_function_symbol { let signature = match ty { Type::FunctionLiteral(dunder_init_function) => { Some(dunder_init_function.signature(db)) @@ -1208,7 +1209,9 @@ impl<'db> ClassType<'db> { ) .place; - if let Place::Type(Type::FunctionLiteral(new_function), _) = new_function_symbol { + if let Place::Defined(Type::FunctionLiteral(new_function), _, _) = + new_function_symbol + { Type::Callable( new_function .into_bound_method_type(db, correct_return_type) @@ -2077,7 +2080,7 @@ impl<'db> ClassLiteral<'db> { let mut dynamic_type_to_intersect_with: Option> = None; let mut lookup_result: LookupResult<'db> = - Err(LookupError::Unbound(TypeQualifiers::empty())); + Err(LookupError::Undefined(TypeQualifiers::empty())); for superclass in mro_iter { match superclass { @@ -2153,7 +2156,7 @@ impl<'db> ClassLiteral<'db> { ( PlaceAndQualifiers { - place: Place::Type(ty, _), + place: Place::Defined(ty, _, _), qualifiers, }, Some(dynamic_type), @@ -2167,7 +2170,7 @@ impl<'db> ClassLiteral<'db> { ( PlaceAndQualifiers { - place: Place::Unbound, + place: Place::Undefined, qualifiers, }, Some(dynamic_type), @@ -2178,7 +2181,7 @@ impl<'db> ClassLiteral<'db> { /// Returns the inferred type of the class member named `name`. Only bound members /// or those marked as `ClassVars` are considered. /// - /// Returns [`Place::Unbound`] if `name` cannot be found in this class's scope + /// Returns [`Place::Undefined`] if `name` cannot be found in this class's scope /// directly. Use [`ClassLiteral::class_member`] if you require a method that will /// traverse through the MRO until it finds the member. pub(super) fn own_class_member( @@ -2190,8 +2193,8 @@ impl<'db> ClassLiteral<'db> { ) -> Member<'db> { if name == "__dataclass_fields__" && self.dataclass_params(db).is_some() { // Make this class look like a subclass of the `DataClassInstance` protocol - return Member::declared( - Place::bound(KnownClass::Dict.to_specialized_instance( + return Member { + inner: Place::declared(KnownClass::Dict.to_specialized_instance( db, [ KnownClass::Str.to_instance(db), @@ -2199,7 +2202,7 @@ impl<'db> ClassLiteral<'db> { ], )) .with_qualifiers(TypeQualifiers::CLASS_VAR), - ); + }; } if CodeGeneratorKind::NamedTuple.matches(db, self) { @@ -2243,7 +2246,7 @@ impl<'db> ClassLiteral<'db> { } }); - if member.is_unbound() { + if member.is_undefined() { if let Some(synthesized_member) = self.own_synthesized_member(db, specialization, name) { return Member::definitely_declared(synthesized_member); @@ -2308,7 +2311,8 @@ impl<'db> ClassLiteral<'db> { } let dunder_set = field_ty.class_member(db, "__set__".into()); - if let Place::Type(dunder_set, Boundness::Bound) = dunder_set.place { + if let Place::Defined(dunder_set, _, 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 // possible attributes, they also have a (callable) `__set__` method. The @@ -2429,7 +2433,7 @@ impl<'db> ClassLiteral<'db> { .to_class_literal(db) .as_class_literal()? .own_class_member(db, self.inherited_generic_context(db), None, name) - .ignore_possibly_unbound() + .ignore_possibly_undefined() .map(|ty| { ty.apply_type_mapping( db, @@ -2884,9 +2888,9 @@ impl<'db> ClassLiteral<'db> { continue; } - if let Some(attr_ty) = attr.place.ignore_possibly_unbound() { + if let Some(attr_ty) = attr.place.ignore_possibly_undefined() { let bindings = use_def.end_of_scope_symbol_bindings(symbol_id); - let mut default_ty = place_from_bindings(db, bindings).ignore_possibly_unbound(); + let mut default_ty = place_from_bindings(db, bindings).ignore_possibly_undefined(); default_ty = default_ty.map(|ty| ty.apply_optional_specialization(db, specialization)); @@ -2977,7 +2981,7 @@ impl<'db> ClassLiteral<'db> { name: &str, ) -> PlaceAndQualifiers<'db> { if self.is_typed_dict(db) { - return Place::Unbound.into(); + return Place::Undefined.into(); } let mut union = UnionBuilder::new(db); @@ -2995,17 +2999,13 @@ impl<'db> ClassLiteral<'db> { ); } ClassBase::Class(class) => { - if let Member { - inner: - member @ PlaceAndQualifiers { - place: Place::Type(ty, boundness), - qualifiers, - }, - is_declared, - } = class.own_instance_member(db, name) + if let member @ PlaceAndQualifiers { + place: Place::Defined(ty, origin, boundness), + qualifiers, + } = class.own_instance_member(db, name).inner { - if boundness == Boundness::Bound { - if is_declared { + if boundness == Definedness::AlwaysDefined { + if origin.is_declared() { // We found a definitely-declared attribute. Discard possibly collected // inferred types from subclasses and return the declared type. return member; @@ -3044,15 +3044,16 @@ impl<'db> ClassLiteral<'db> { } if union.is_empty() { - Place::Unbound.with_qualifiers(TypeQualifiers::empty()) + Place::Undefined.with_qualifiers(TypeQualifiers::empty()) } else { let boundness = if is_definitely_bound { - Boundness::Bound + Definedness::AlwaysDefined } else { - Boundness::PossiblyUnbound + Definedness::PossiblyUndefined }; - Place::Type(union.build(), boundness).with_qualifiers(union_qualifiers) + Place::Defined(union.build(), TypeOrigin::Inferred, boundness) + .with_qualifiers(union_qualifiers) } } @@ -3104,7 +3105,10 @@ impl<'db> ClassLiteral<'db> { if let Some(method_def) = method_scope.node().as_function() { let method_name = method_def.node(&module).name.as_str(); if let Some(Type::FunctionLiteral(method_type)) = - class_member(db, class_body_scope, method_name).ignore_possibly_unbound() + class_member(db, class_body_scope, method_name) + .inner + .place + .ignore_possibly_undefined() { let method_decorator = MethodDecorator::try_from_fn_type(db, method_type); if method_decorator != Ok(target_method_decorator) { @@ -3141,7 +3145,7 @@ impl<'db> ClassLiteral<'db> { // self.name: = … let annotation = declaration_type(db, declaration); - let annotation = Place::bound(annotation.inner).with_qualifiers( + let annotation = Place::declared(annotation.inner).with_qualifiers( annotation.qualifiers | TypeQualifiers::IMPLICIT_INSTANCE_ATTRIBUTE, ); @@ -3156,9 +3160,9 @@ impl<'db> ClassLiteral<'db> { index.expression(value), TypeContext::default(), ); - return Member::inferred( - Place::bound(inferred_ty).with_qualifiers(all_qualifiers), - ); + return Member { + inner: Place::bound(inferred_ty).with_qualifiers(all_qualifiers), + }; } // If there is no right-hand side, just record that we saw a `Final` qualifier @@ -3166,7 +3170,7 @@ impl<'db> ClassLiteral<'db> { continue; } - return Member::declared(annotation); + return Member { inner: annotation }; } } @@ -3358,11 +3362,13 @@ impl<'db> ClassLiteral<'db> { } } - Member::inferred(if is_attribute_bound { - Place::bound(union_of_inferred_types.build()).with_qualifiers(qualifiers) - } else { - Place::Unbound.with_qualifiers(qualifiers) - }) + Member { + inner: if is_attribute_bound { + Place::bound(union_of_inferred_types.build()).with_qualifiers(qualifiers) + } else { + Place::Undefined.with_qualifiers(qualifiers) + }, + } } /// A helper function for `instance_member` that looks up the `name` attribute only on @@ -3384,20 +3390,20 @@ impl<'db> ClassLiteral<'db> { match declared_and_qualifiers { PlaceAndQualifiers { - place: mut declared @ Place::Type(declared_ty, declaredness), + place: mut declared @ Place::Defined(declared_ty, _, declaredness), qualifiers, } => { // For the purpose of finding instance attributes, ignore `ClassVar` // declarations: if qualifiers.contains(TypeQualifiers::CLASS_VAR) { - declared = Place::Unbound; + declared = Place::Undefined; } if qualifiers.contains(TypeQualifiers::INIT_VAR) { // We ignore `InitVar` declarations on the class body, unless that attribute is overwritten // by an implicit assignment in a method if Self::implicit_attribute(db, body_scope, name, MethodDecorator::None) - .is_unbound() + .is_undefined() { return Member::unbound(); } @@ -3407,28 +3413,31 @@ impl<'db> ClassLiteral<'db> { let bindings = use_def.end_of_scope_symbol_bindings(symbol_id); let inferred = place_from_bindings(db, bindings); - let has_binding = !inferred.is_unbound(); + let has_binding = !inferred.is_undefined(); if has_binding { // The attribute is declared and bound in the class body. if let Some(implicit_ty) = Self::implicit_attribute(db, body_scope, name, MethodDecorator::None) - .ignore_possibly_unbound() + .ignore_possibly_undefined() { - if declaredness == Boundness::Bound { + if declaredness == Definedness::AlwaysDefined { // If a symbol is definitely declared, and we see // attribute assignments in methods of the class, // we trust the declared type. - Member::declared(declared.with_qualifiers(qualifiers)) + Member { + inner: declared.with_qualifiers(qualifiers), + } } else { - Member::declared( - Place::Type( + Member { + inner: Place::Defined( UnionType::from_elements(db, [declared_ty, implicit_ty]), + TypeOrigin::Declared, declaredness, ) .with_qualifiers(qualifiers), - ) + } } } else { // The symbol is declared and bound in the class body, @@ -3446,8 +3455,10 @@ impl<'db> ClassLiteral<'db> { // it is possibly-undeclared. In the latter case, we also // union with the inferred type from attribute assignments. - if declaredness == Boundness::Bound { - Member::declared(declared.with_qualifiers(qualifiers)) + if declaredness == Definedness::AlwaysDefined { + Member { + inner: declared.with_qualifiers(qualifiers), + } } else { if let Some(implicit_ty) = Self::implicit_attribute( db, @@ -3457,24 +3468,27 @@ impl<'db> ClassLiteral<'db> { ) .inner .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() { - Member::declared( - Place::Type( + Member { + inner: Place::Defined( UnionType::from_elements(db, [declared_ty, implicit_ty]), + TypeOrigin::Declared, declaredness, ) .with_qualifiers(qualifiers), - ) + } } else { - Member::declared(declared.with_qualifiers(qualifiers)) + Member { + inner: declared.with_qualifiers(qualifiers), + } } } } } PlaceAndQualifiers { - place: Place::Unbound, + place: Place::Undefined, qualifiers: _, } => { // The attribute is not *declared* in the class body. It could still be declared/bound @@ -3678,7 +3692,7 @@ impl<'db> VarianceInferable<'db> for ClassLiteral<'db> { .chain(attribute_places_and_qualifiers) .dedup() .filter_map(|(name, place_and_qual)| { - place_and_qual.place.ignore_possibly_unbound().map(|ty| { + place_and_qual.ignore_possibly_undefined().map(|ty| { let variance = if place_and_qual .qualifiers // `CLASS_VAR || FINAL` is really `all()`, but @@ -4636,14 +4650,18 @@ impl KnownClass { ) -> Result, KnownClassLookupError<'_>> { let symbol = known_module_symbol(db, self.canonical_module(db), self.name(db)).place; match symbol { - Place::Type(Type::ClassLiteral(class_literal), Boundness::Bound) => Ok(class_literal), - Place::Type(Type::ClassLiteral(class_literal), Boundness::PossiblyUnbound) => { - Err(KnownClassLookupError::ClassPossiblyUnbound { class_literal }) + Place::Defined(Type::ClassLiteral(class_literal), _, Definedness::AlwaysDefined) => { + Ok(class_literal) } - Place::Type(found_type, _) => { + Place::Defined( + Type::ClassLiteral(class_literal), + _, + Definedness::PossiblyUndefined, + ) => Err(KnownClassLookupError::ClassPossiblyUnbound { class_literal }), + Place::Defined(found_type, _, _) => { Err(KnownClassLookupError::SymbolNotAClass { found_type }) } - Place::Unbound => Err(KnownClassLookupError::ClassNotFound), + Place::Undefined => Err(KnownClassLookupError::ClassNotFound), } } @@ -5434,7 +5452,7 @@ enum SlotsKind { impl SlotsKind { fn from(db: &dyn Db, base: ClassLiteral) -> Self { - let Place::Type(slots_ty, bound) = base + let Place::Defined(slots_ty, _, bound) = base .own_class_member(db, base.inherited_generic_context(db), None, "__slots__") .inner .place @@ -5442,7 +5460,7 @@ impl SlotsKind { return Self::NotSpecified; }; - if matches!(bound, Boundness::PossiblyUnbound) { + if matches!(bound, Definedness::PossiblyUndefined) { return Self::Dynamic; } diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index debde1a54c..03e5d738b9 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -1713,7 +1713,7 @@ mod tests { let iterator_synthesized = typing_extensions_symbol(&db, "Iterator") .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .unwrap() .to_instance(&db) .unwrap() diff --git a/crates/ty_python_semantic/src/types/enums.rs b/crates/ty_python_semantic/src/types/enums.rs index 6ddb8de185..fc4e4855e3 100644 --- a/crates/ty_python_semantic/src/types/enums.rs +++ b/crates/ty_python_semantic/src/types/enums.rs @@ -92,7 +92,7 @@ pub(crate) fn enum_metadata<'db>( let ignore_place = place_from_bindings(db, ignore_bindings); match ignore_place { - Place::Type(Type::StringLiteral(ignored_names), _) => { + Place::Defined(Type::StringLiteral(ignored_names), _, _) => { Some(ignored_names.value(db).split_ascii_whitespace().collect()) } // TODO: support the list-variant of `_ignore_`. @@ -126,10 +126,10 @@ pub(crate) fn enum_metadata<'db>( let inferred = place_from_bindings(db, bindings); let value_ty = match inferred { - Place::Unbound => { + Place::Undefined => { return None; } - Place::Type(ty, _) => { + Place::Defined(ty, _, _) => { let special_case = match ty { Type::Callable(_) | Type::FunctionLiteral(_) => { // Some types are specifically disallowed for enum members. @@ -143,7 +143,7 @@ pub(crate) fn enum_metadata<'db>( Some(KnownClass::Member) => Some( ty.member(db, "value") .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .unwrap_or(Type::unknown()), ), @@ -178,9 +178,9 @@ pub(crate) fn enum_metadata<'db>( .place; match dunder_get { - Place::Unbound | Place::Type(Type::Dynamic(_), _) => ty, + Place::Undefined | Place::Defined(Type::Dynamic(_), _, _) => ty, - Place::Type(_, _) => { + Place::Defined(_, _, _) => { // Descriptors are not considered members. return None; } @@ -215,17 +215,17 @@ pub(crate) fn enum_metadata<'db>( match declared { PlaceAndQualifiers { - place: Place::Type(Type::Dynamic(DynamicType::Unknown), _), + place: Place::Defined(Type::Dynamic(DynamicType::Unknown), _, _), qualifiers, } if qualifiers.contains(TypeQualifiers::FINAL) => {} PlaceAndQualifiers { - place: Place::Unbound, + place: Place::Undefined, .. } => { // Undeclared attributes are considered members } PlaceAndQualifiers { - place: Place::Type(Type::NominalInstance(instance), _), + place: Place::Defined(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 a94c222a37..1456f12526 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 crate::module_resolver::{KnownModule, file_to_module}; -use crate::place::{Boundness, Place, place_from_bindings}; +use crate::place::{Definedness, Place, place_from_bindings}; use crate::semantic_index::ast_ids::HasScopedUseId; use crate::semantic_index::definition::Definition; use crate::semantic_index::scope::ScopeId; @@ -314,7 +314,7 @@ impl<'db> OverloadLiteral<'db> { .name .scoped_use_id(db, scope); - let Place::Type(Type::FunctionLiteral(previous_type), Boundness::Bound) = + let Place::Defined(Type::FunctionLiteral(previous_type), _, Definedness::AlwaysDefined) = place_from_bindings(db, use_def.bindings_at_use(use_id)) else { return None; diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index a5fe27877c..0dfa18dbed 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -42,7 +42,7 @@ pub(crate) fn all_declarations_and_bindings<'db>( place_result .ignore_conflicting_declarations() .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .map(|ty| { let symbol = table.symbol(symbol_id); let member = Member { @@ -71,7 +71,7 @@ pub(crate) fn all_declarations_and_bindings<'db>( } } place_from_bindings(db, bindings) - .ignore_possibly_unbound() + .ignore_possibly_undefined() .map(|ty| { let symbol = table.symbol(symbol_id); let member = Member { @@ -239,7 +239,8 @@ 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::Type(ty, _) = imported_symbol(db, file, symbol_name, None).place + let Place::Defined(ty, _, _) = + imported_symbol(db, file, symbol_name, None).place else { continue; }; @@ -327,7 +328,7 @@ impl<'db> AllMembers<'db> { let parent_scope = parent.body_scope(db); for memberdef in all_declarations_and_bindings(db, parent_scope) { let result = ty.member(db, memberdef.member.name.as_str()); - let Some(ty) = result.place.ignore_possibly_unbound() else { + let Some(ty) = result.place.ignore_possibly_undefined() else { continue; }; self.members.insert(Member { @@ -358,7 +359,7 @@ impl<'db> AllMembers<'db> { continue; }; let result = ty.member(db, name); - let Some(ty) = result.place.ignore_possibly_unbound() else { + let Some(ty) = result.place.ignore_possibly_undefined() else { continue; }; self.members.insert(Member { @@ -375,7 +376,7 @@ impl<'db> AllMembers<'db> { // method, but `instance_of_SomeClass.__delattr__` is. for memberdef in all_declarations_and_bindings(db, class_body_scope) { let result = ty.member(db, memberdef.member.name.as_str()); - let Some(ty) = result.place.ignore_possibly_unbound() else { + let Some(ty) = result.place.ignore_possibly_undefined() else { continue; }; self.members.insert(Member { diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 171fa42438..52a8119465 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -751,7 +751,7 @@ impl<'db> DefinitionInference<'db> { None } }) - .or_else(|| self.fallback_type().map(Into::into)) + .or_else(|| self.fallback_type().map(TypeAndQualifiers::declared)) .expect( "definition should belong to this TypeInference region and \ TypeInferenceBuilder should have inferred a type for it", diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 16b669e65a..be92acc9ab 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -22,7 +22,7 @@ use crate::module_resolver::{ }; use crate::node_key::NodeKey; use crate::place::{ - Boundness, ConsideredDefinitions, LookupError, Place, PlaceAndQualifiers, + ConsideredDefinitions, Definedness, LookupError, Place, PlaceAndQualifiers, TypeOrigin, builtins_module_scope, builtins_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, @@ -136,7 +136,11 @@ enum DeclaredAndInferredType<'db> { impl<'db> DeclaredAndInferredType<'db> { fn are_the_same_type(ty: Type<'db>) -> Self { - Self::AreTheSame(ty.into()) + Self::AreTheSame(TypeAndQualifiers::new( + ty, + TypeOrigin::Inferred, + TypeQualifiers::empty(), + )) } } @@ -957,7 +961,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let mut public_functions = FxHashSet::default(); for place in overloaded_function_places { - if let Place::Type(Type::FunctionLiteral(function), Boundness::Bound) = + if let Place::Defined(Type::FunctionLiteral(function), _, Definedness::AlwaysDefined) = place_from_bindings( self.db(), use_def.end_of_scope_symbol_bindings(place.as_symbol().unwrap()), @@ -1465,7 +1469,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } // Fall back to implicit module globals for (possibly) unbound names - if !matches!(place_and_quals.place, Place::Type(_, Boundness::Bound)) { + if !place_and_quals.place.is_definitely_bound() { if let PlaceExprRef::Symbol(symbol) = place { let symbol_id = place_id.expect_symbol(); @@ -1486,38 +1490,40 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let unwrap_declared_ty = || { resolved_place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .unwrap_or(Type::unknown()) }; // If the place is unbound and its an attribute or subscript place, fall back to normal // attribute/subscript inference on the root type. - let declared_ty = if resolved_place.is_unbound() && !place_table.place(place_id).is_symbol() - { - if let AnyNodeRef::ExprAttribute(ast::ExprAttribute { value, attr, .. }) = node { - let value_type = - self.infer_maybe_standalone_expression(value, TypeContext::default()); - if let Place::Type(ty, Boundness::Bound) = value_type.member(db, attr).place { - // TODO: also consider qualifiers on the attribute - ty + let declared_ty = + if resolved_place.is_undefined() && !place_table.place(place_id).is_symbol() { + 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 + { + // TODO: also consider qualifiers on the attribute + ty + } else { + unwrap_declared_ty() + } + } else if let AnyNodeRef::ExprSubscript( + subscript @ ast::ExprSubscript { + value, slice, ctx, .. + }, + ) = node + { + let value_ty = self.infer_expression(value, TypeContext::default()); + let slice_ty = self.infer_expression(slice, TypeContext::default()); + self.infer_subscript_expression_types(subscript, value_ty, slice_ty, *ctx) } else { unwrap_declared_ty() } - } else if let AnyNodeRef::ExprSubscript( - subscript @ ast::ExprSubscript { - value, slice, ctx, .. - }, - ) = node - { - let value_ty = self.infer_expression(value, TypeContext::default()); - let slice_ty = self.infer_expression(slice, TypeContext::default()); - self.infer_subscript_expression_types(subscript, value_ty, slice_ty, *ctx) } else { unwrap_declared_ty() - } - } else { - unwrap_declared_ty() - }; + }; if qualifiers.contains(TypeQualifiers::FINAL) { let mut previous_bindings = use_def.bindings_at_definition(binding); @@ -1585,7 +1591,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if value_ty .class_member(db, attr.id.clone()) .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .is_some_and(|ty| ty.may_be_data_descriptor(db)) { bound_ty = declared_ty; @@ -1644,14 +1650,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if scope.is_global() { module_type_implicit_global_symbol(self.db(), symbol.name()) } else { - Place::Unbound.into() + Place::Undefined.into() } } else { - Place::Unbound.into() + Place::Undefined.into() } }) .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .unwrap_or(Type::Never); let ty = if inferred_ty.is_assignable_to(self.db(), ty.inner_type()) { ty @@ -1663,7 +1669,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { inferred_ty.display(self.db()) )); } - TypeAndQualifiers::unknown() + TypeAndQualifiers::declared(Type::unknown()) }; self.declarations.insert(declaration, ty); } @@ -1702,7 +1708,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if let Some(module_type_implicit_declaration) = place .as_symbol() .map(|symbol| module_type_implicit_global_symbol(self.db(), symbol.name())) - .and_then(|place| place.place.ignore_possibly_unbound()) + .and_then(|place| place.place.ignore_possibly_undefined()) { let declared_type = declared_ty.inner_type(); if !declared_type @@ -2425,7 +2431,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let declared_and_inferred_ty = if let Some(default_ty) = default_ty { if default_ty.is_assignable_to(self.db(), declared_ty) { DeclaredAndInferredType::MightBeDifferent { - declared_ty: declared_ty.into(), + declared_ty: TypeAndQualifiers::declared(declared_ty), inferred_ty: UnionType::from_elements(self.db(), [declared_ty, default_ty]), } } else if (self.in_stub() @@ -3619,14 +3625,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK, ) { PlaceAndQualifiers { - place: Place::Type(attr_ty, _), + place: Place::Defined(attr_ty, _, _), qualifiers: _, } => attr_ty.is_callable_type(), _ => false, }; let member_exists = - !object_ty.member(db, attribute).place.is_unbound(); + !object_ty.member(db, attribute).place.is_undefined(); let msg = if !member_exists { format!( @@ -3693,7 +3699,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { false } PlaceAndQualifiers { - place: Place::Type(meta_attr_ty, meta_attr_boundness), + place: Place::Defined(meta_attr_ty, _, meta_attr_boundness), qualifiers, } => { if invalid_assignment_to_final(qualifiers) { @@ -3701,7 +3707,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } let assignable_to_meta_attr = - if let Place::Type(meta_dunder_set, _) = + if let Place::Defined(meta_dunder_set, _, _) = meta_attr_ty.class_member(db, "__set__".into()).place { let dunder_set_result = meta_dunder_set.try_call( @@ -3733,11 +3739,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; let assignable_to_instance_attribute = if meta_attr_boundness - == Boundness::PossiblyUnbound + == Definedness::PossiblyUndefined { let (assignable, boundness) = if let PlaceAndQualifiers { place: - Place::Type(instance_attr_ty, instance_attr_boundness), + Place::Defined(instance_attr_ty, _, instance_attr_boundness), qualifiers, } = object_ty.instance_member(db, attribute) @@ -3751,10 +3757,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { instance_attr_boundness, ) } else { - (true, Boundness::PossiblyUnbound) + (true, Definedness::PossiblyUndefined) }; - if boundness == Boundness::PossiblyUnbound { + if boundness == Definedness::PossiblyUndefined { report_possibly_missing_attribute( &self.context, target, @@ -3772,11 +3778,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } PlaceAndQualifiers { - place: Place::Unbound, + place: Place::Undefined, .. } => { if let PlaceAndQualifiers { - place: Place::Type(instance_attr_ty, instance_attr_boundness), + place: + Place::Defined(instance_attr_ty, _, instance_attr_boundness), qualifiers, } = object_ty.instance_member(db, attribute) { @@ -3784,7 +3791,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return false; } - if instance_attr_boundness == Boundness::PossiblyUnbound { + if instance_attr_boundness == Definedness::PossiblyUndefined { report_possibly_missing_attribute( &self.context, target, @@ -3818,14 +3825,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..) => { match object_ty.class_member(db, attribute.into()) { PlaceAndQualifiers { - place: Place::Type(meta_attr_ty, meta_attr_boundness), + place: Place::Defined(meta_attr_ty, _, meta_attr_boundness), qualifiers, } => { if invalid_assignment_to_final(qualifiers) { return false; } - let assignable_to_meta_attr = if let Place::Type(meta_dunder_set, _) = + let assignable_to_meta_attr = if let Place::Defined(meta_dunder_set, _, _) = meta_attr_ty.class_member(db, "__set__".into()).place { let dunder_set_result = meta_dunder_set.try_call( @@ -3851,20 +3858,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; let assignable_to_class_attr = if meta_attr_boundness - == Boundness::PossiblyUnbound + == Definedness::PossiblyUndefined { let (assignable, boundness) = - if let Place::Type(class_attr_ty, class_attr_boundness) = object_ty - .find_name_in_mro(db, attribute) - .expect("called on Type::ClassLiteral or Type::SubclassOf") - .place + 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 { (ensure_assignable_to(class_attr_ty), class_attr_boundness) } else { - (true, Boundness::PossiblyUnbound) + (true, Definedness::PossiblyUndefined) }; - if boundness == Boundness::PossiblyUnbound { + if boundness == Definedness::PossiblyUndefined { report_possibly_missing_attribute( &self.context, target, @@ -3881,11 +3889,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { assignable_to_meta_attr && assignable_to_class_attr } PlaceAndQualifiers { - place: Place::Unbound, + place: Place::Undefined, .. } => { if let PlaceAndQualifiers { - place: Place::Type(class_attr_ty, class_attr_boundness), + place: Place::Defined(class_attr_ty, _, class_attr_boundness), qualifiers, } = object_ty .find_name_in_mro(db, attribute) @@ -3895,7 +3903,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return false; } - if class_attr_boundness == Boundness::PossiblyUnbound { + if class_attr_boundness == Definedness::PossiblyUndefined { report_possibly_missing_attribute( &self.context, target, @@ -3911,7 +3919,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { !instance .instance_member(self.db(), attribute) .place - .is_unbound() + .is_undefined() }); // Attribute is declared or bound on instance. Forbid access from the class object @@ -3946,7 +3954,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } Type::ModuleLiteral(module) => { - if let Place::Type(attr_ty, _) = module.static_member(db, attribute).place { + if let Place::Defined(attr_ty, _, _) = module.static_member(db, attribute).place { let assignable = value_ty.is_assignable_to(db, attr_ty); if assignable { true @@ -5057,11 +5065,11 @@ 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::Type(ty, boundness), + place: Place::Defined(ty, _, boundness), qualifiers, } = module_ty.member(self.db(), name) { - if &alias.name != "*" && boundness == Boundness::PossiblyUnbound { + if &alias.name != "*" && boundness == Definedness::PossiblyUndefined { // TODO: Consider loading _both_ the attribute and any submodule and unioning them // together if the attribute exists but is possibly-unbound. if let Some(builder) = self @@ -5079,6 +5087,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { &DeclaredAndInferredType::MightBeDifferent { declared_ty: TypeAndQualifiers { inner: ty, + origin: TypeOrigin::Declared, qualifiers, }, inferred_ty: ty, @@ -5224,7 +5233,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } if !module_type_implicit_global_symbol(self.db(), name) .place - .is_unbound() + .is_undefined() { // This name is an implicit global like `__file__` (but not a built-in like `int`). continue; @@ -6934,7 +6943,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // (without infinite recursion if we're already in builtins.) .or_fall_back_to(db, || { if Some(self.scope()) == builtins_module_scope(db) { - Place::Unbound.into() + Place::Undefined.into() } else { builtins_symbol(db, symbol_name) } @@ -6951,7 +6960,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } typing_extensions_symbol(db, symbol_name) } else { - Place::Unbound.into() + Place::Undefined.into() } }); @@ -6961,11 +6970,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let ty = resolved_after_fallback.unwrap_with_diagnostic(|lookup_error| match lookup_error { - LookupError::Unbound(qualifiers) => { + LookupError::Undefined(qualifiers) => { self.report_unresolved_reference(name_node); - TypeAndQualifiers::new(Type::unknown(), qualifiers) + TypeAndQualifiers::new(Type::unknown(), TypeOrigin::Inferred, qualifiers) } - LookupError::PossiblyUnbound(type_when_bound) => { + LookupError::PossiblyUndefined(type_when_bound) => { if self.is_reachable(name_node) { report_possibly_unresolved_reference(&self.context, name_node); } @@ -6996,7 +7005,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.deferred_state.in_string_annotation(), "Expected the place table to create a place for every valid PlaceExpr node" ); - Place::Unbound + Place::Undefined }; (place, None) } else { @@ -7004,7 +7013,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .as_name_expr() .is_some_and(|name| name.is_invalid()) { - return (Place::Unbound, None); + return (Place::Undefined, None); } let use_id = expr_ref.scoped_use_id(db, scope); @@ -7064,7 +7073,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // enclosing scopes in this case. The one exception to this rule is the global fallback // in class bodies, which we already handled above. if symbol_resolves_locally { - return Place::Unbound.into(); + return Place::Undefined.into(); } for parent_id in place_table.parents(place_expr) { @@ -7082,8 +7091,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } let (parent_place, _use_id) = self.infer_local_place_load(parent_expr, expr_ref); - if let Place::Type(_, _) = parent_place { - return Place::Unbound.into(); + if let Place::Defined(_, _, _) = parent_place { + return Place::Undefined.into(); } } @@ -7154,13 +7163,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Don't fall back to non-eager place resolution. EnclosingSnapshotResult::NotFound => { if has_root_place_been_reassigned() { - return Place::Unbound.into(); + return Place::Undefined.into(); } continue; } EnclosingSnapshotResult::NoLongerInEagerContext => { if has_root_place_been_reassigned() { - return Place::Unbound.into(); + return Place::Undefined.into(); } } } @@ -7209,12 +7218,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { &constraint_keys, ) }); - // We could have Place::Unbound here, despite the checks above, for example if + // 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::Type(type_, boundness) = local_place_and_qualifiers.place { + if let Place::Defined(type_, _, boundness) = local_place_and_qualifiers.place { nonlocal_union_builder.add_in_place(type_); // `ConsideredDefinitions::AllReachable` never returns PossiblyUnbound - debug_assert_eq!(boundness, Boundness::Bound); + debug_assert_eq!(boundness, Definedness::AlwaysDefined); found_some_definition = true; } @@ -7223,20 +7232,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // declared but doesn't mark it `nonlocal`. The name is therefore resolved, // and we won't consider any scopes outside of this one. return if found_some_definition { - Place::Type(nonlocal_union_builder.build(), Boundness::Bound).into() + Place::bound(nonlocal_union_builder.build()).into() } else { - Place::Unbound.into() + Place::Undefined.into() }; } } } - PlaceAndQualifiers::from(Place::Unbound) + PlaceAndQualifiers::from(Place::Undefined) // No nonlocal binding? Check the module's explicit globals. // Avoid infinite recursion if `self.scope` already is the module's global scope. .or_fall_back_to(db, || { if file_scope_id.is_global() { - return Place::Unbound.into(); + return Place::Undefined.into(); } if !self.is_deferred() { @@ -7267,14 +7276,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } // There are no visible bindings / constraint here. EnclosingSnapshotResult::NotFound => { - return Place::Unbound.into(); + return Place::Undefined.into(); } EnclosingSnapshotResult::NoLongerInEagerContext => {} } } let Some(symbol) = place_expr.as_symbol() else { - return Place::Unbound.into(); + return Place::Undefined.into(); }; explicit_global_symbol(db, self.file(), symbol.name()).map_type(|ty| { @@ -7287,7 +7296,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }) }); - if let Some(ty) = place.place.ignore_possibly_unbound() { + if let Some(ty) = place.place.ignore_possibly_undefined() { self.check_deprecated(expr_ref, ty); } @@ -7360,11 +7369,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { Ok(MethodDecorator::ClassMethod) => !Type::instance(self.db(), class) .class_member(self.db(), id.clone()) .place - .is_unbound(), + .is_undefined(), Ok(MethodDecorator::None) => !Type::instance(self.db(), class) .member(self.db(), id) .place - .is_unbound(), + .is_undefined(), Ok(MethodDecorator::StaticMethod) | Err(()) => false, }; @@ -7421,7 +7430,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ast::ExprRef::Attribute(attribute), ); constraint_keys.extend(keys); - if let Place::Type(ty, Boundness::Bound) = resolved.place { + if let Place::Defined(ty, _, Definedness::AlwaysDefined) = resolved.place { assigned_type = Some(ty); } } @@ -7441,18 +7450,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fallback_place.map_type(|ty| { self.narrow_expr_with_applicable_constraints(attribute, ty, &constraint_keys) }).unwrap_with_diagnostic(|lookup_error| match lookup_error { - LookupError::Unbound(_) => { + LookupError::Undefined(_) => { let report_unresolved_attribute = self.is_reachable(attribute); if report_unresolved_attribute { let bound_on_instance = match value_type { Type::ClassLiteral(class) => { - !class.instance_member(db, None, attr).place.is_unbound() + !class.instance_member(db, None, attr).is_undefined() } Type::SubclassOf(subclass_of @ SubclassOfType { .. }) => { match subclass_of.subclass_of() { SubclassOfInner::Class(class) => { - !class.instance_member(db, attr).place.is_unbound() + !class.instance_member(db, attr).is_undefined() } SubclassOfInner::Dynamic(_) => unreachable!( "Attribute lookup on a dynamic `SubclassOf` type should always return a bound symbol" @@ -7487,9 +7496,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } - Type::unknown().into() + TypeAndQualifiers::new(Type::unknown(), TypeOrigin::Inferred, TypeQualifiers::empty()) } - LookupError::PossiblyUnbound(type_when_bound) => { + LookupError::PossiblyUndefined(type_when_bound) => { report_possibly_missing_attribute( &self.context, attribute, @@ -8064,7 +8073,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let rhs_reflected = right_class.member(self.db(), reflected_dunder).place; // TODO: if `rhs_reflected` is possibly unbound, we should union the two possible // Bindings together - if !rhs_reflected.is_unbound() + if !rhs_reflected.is_undefined() && rhs_reflected != left_class.member(self.db(), reflected_dunder).place { return right_ty @@ -8911,7 +8920,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let contains_dunder = right.class_member(db, "__contains__".into()).place; let compare_result_opt = match contains_dunder { - Place::Type(contains_dunder, Boundness::Bound) => { + Place::Defined(contains_dunder, _, Definedness::AlwaysDefined) => { // If `__contains__` is available, it is used directly for the membership test. contains_dunder .try_call(db, &CallArguments::positional([right, left])) @@ -9092,7 +9101,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ast::ExprRef::Subscript(subscript), ); constraint_keys.extend(keys); - if let Place::Type(ty, Boundness::Bound) = place.place { + if let Place::Defined(ty, _, 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()); @@ -9548,9 +9557,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let dunder_class_getitem_method = value_ty.member(db, "__class_getitem__").place; match dunder_class_getitem_method { - Place::Unbound => {} - Place::Type(ty, boundness) => { - if boundness == Boundness::PossiblyUnbound { + Place::Undefined => {} + Place::Defined(ty, _, 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/infer/builder/annotation_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs index e03f5ef8be..1e954d461e 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs @@ -1,6 +1,7 @@ use ruff_python_ast as ast; use super::{DeferredExpressionState, TypeInferenceBuilder}; +use crate::place::TypeOrigin; use crate::types::diagnostic::{INVALID_TYPE_FORM, report_invalid_arguments_to_annotated}; use crate::types::string_annotation::{ BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation, @@ -48,21 +49,31 @@ impl<'db> TypeInferenceBuilder<'db, '_> { builder: &TypeInferenceBuilder<'db, '_>, ) -> TypeAndQualifiers<'db> { match ty { - Type::SpecialForm(SpecialFormType::ClassVar) => { - TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::CLASS_VAR) - } - Type::SpecialForm(SpecialFormType::Final) => { - TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::FINAL) - } - Type::SpecialForm(SpecialFormType::Required) => { - TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::REQUIRED) - } - Type::SpecialForm(SpecialFormType::NotRequired) => { - TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::NOT_REQUIRED) - } - Type::SpecialForm(SpecialFormType::ReadOnly) => { - TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::READ_ONLY) - } + Type::SpecialForm(SpecialFormType::ClassVar) => TypeAndQualifiers::new( + Type::unknown(), + TypeOrigin::Declared, + TypeQualifiers::CLASS_VAR, + ), + Type::SpecialForm(SpecialFormType::Final) => TypeAndQualifiers::new( + Type::unknown(), + TypeOrigin::Declared, + TypeQualifiers::FINAL, + ), + Type::SpecialForm(SpecialFormType::Required) => TypeAndQualifiers::new( + Type::unknown(), + TypeOrigin::Declared, + TypeQualifiers::REQUIRED, + ), + Type::SpecialForm(SpecialFormType::NotRequired) => TypeAndQualifiers::new( + Type::unknown(), + TypeOrigin::Declared, + TypeQualifiers::NOT_REQUIRED, + ), + Type::SpecialForm(SpecialFormType::ReadOnly) => TypeAndQualifiers::new( + Type::unknown(), + TypeOrigin::Declared, + TypeQualifiers::READ_ONLY, + ), Type::ClassLiteral(class) if class.is_known(builder.db(), KnownClass::InitVar) => { if let Some(builder) = builder.context.report_lint(&INVALID_TYPE_FORM, annotation) @@ -70,10 +81,14 @@ impl<'db> TypeInferenceBuilder<'db, '_> { builder .into_diagnostic("`InitVar` may not be used without a type argument"); } - TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::INIT_VAR) + TypeAndQualifiers::new( + Type::unknown(), + TypeOrigin::Declared, + TypeQualifiers::INIT_VAR, + ) } - _ => ty - .in_type_expression( + _ => TypeAndQualifiers::declared( + ty.in_type_expression( builder.db(), builder.scope(), builder.typevar_binding_context, @@ -84,8 +99,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { annotation, builder.is_reachable(annotation), ) - }) - .into(), + }), + ), } } @@ -95,7 +110,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ast::Expr::StringLiteral(string) => self.infer_string_annotation_expression(string), // Annotation expressions also get special handling for `*args` and `**kwargs`. - ast::Expr::Starred(starred) => self.infer_starred_expression(starred).into(), + ast::Expr::Starred(starred) => { + TypeAndQualifiers::declared(self.infer_starred_expression(starred)) + } ast::Expr::BytesLiteral(bytes) => { if let Some(builder) = self @@ -104,7 +121,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { { builder.into_diagnostic("Type expressions cannot use bytes literal"); } - TypeAndQualifiers::unknown() + TypeAndQualifiers::declared(Type::unknown()) } ast::Expr::FString(fstring) => { @@ -112,7 +129,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { builder.into_diagnostic("Type expressions cannot use f-strings"); } self.infer_fstring_expression(fstring); - TypeAndQualifiers::unknown() + TypeAndQualifiers::declared(Type::unknown()) } ast::Expr::Attribute(attribute) => match attribute.ctx { @@ -121,20 +138,20 @@ impl<'db> TypeInferenceBuilder<'db, '_> { annotation, self, ), - ast::ExprContext::Invalid => TypeAndQualifiers::unknown(), - ast::ExprContext::Store | ast::ExprContext::Del => { - todo_type!("Attribute expression annotation in Store/Del context").into() - } + ast::ExprContext::Invalid => TypeAndQualifiers::declared(Type::unknown()), + ast::ExprContext::Store | ast::ExprContext::Del => TypeAndQualifiers::declared( + todo_type!("Attribute expression annotation in Store/Del context"), + ), }, ast::Expr::Name(name) => match name.ctx { ast::ExprContext::Load => { infer_name_or_attribute(self.infer_name_expression(name), annotation, self) } - ast::ExprContext::Invalid => TypeAndQualifiers::unknown(), - ast::ExprContext::Store | ast::ExprContext::Del => { - todo_type!("Name expression annotation in Store/Del context").into() - } + ast::ExprContext::Invalid => TypeAndQualifiers::declared(Type::unknown()), + ast::ExprContext::Store | ast::ExprContext::Del => TypeAndQualifiers::declared( + todo_type!("Name expression annotation in Store/Del context"), + ), }, ast::Expr::Subscript(subscript @ ast::ExprSubscript { value, slice, .. }) => { @@ -170,7 +187,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.infer_expression(argument, TypeContext::default()); } self.store_expression_type(slice, Type::unknown()); - TypeAndQualifiers::unknown() + TypeAndQualifiers::declared(Type::unknown()) } } else { report_invalid_arguments_to_annotated(&self.context, subscript); @@ -225,7 +242,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { got {num_arguments}", )); } - Type::unknown().into() + TypeAndQualifiers::declared(Type::unknown()) }; if slice.is_tuple_expr() { self.store_expression_type(slice, type_and_qualifiers.inner_type()); @@ -256,22 +273,24 @@ impl<'db> TypeInferenceBuilder<'db, '_> { got {num_arguments}", )); } - Type::unknown().into() + TypeAndQualifiers::declared(Type::unknown()) }; if slice.is_tuple_expr() { self.store_expression_type(slice, type_and_qualifiers.inner_type()); } type_and_qualifiers } - _ => self - .infer_subscript_type_expression_no_store(subscript, slice, value_ty) - .into(), + _ => TypeAndQualifiers::declared( + self.infer_subscript_type_expression_no_store(subscript, slice, value_ty), + ), } } // All other annotation expressions are (possibly) valid type expressions, so handle // them there instead. - type_expr => self.infer_type_expression_no_store(type_expr).into(), + type_expr => { + TypeAndQualifiers::declared(self.infer_type_expression_no_store(type_expr)) + } }; self.store_expression_type(annotation, annotation_ty.inner_type()); @@ -294,7 +313,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ), ) } - None => TypeAndQualifiers::unknown(), + None => TypeAndQualifiers::declared(Type::unknown()), } } } diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index e4a4948b62..69c6b8a165 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -1429,7 +1429,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { let ty = value_ty .member(self.db(), &attr.id) .place - .ignore_possibly_unbound() + .ignore_possibly_undefined() .unwrap_or(Type::unknown()); self.store_expression_type(parameters, ty); ty diff --git a/crates/ty_python_semantic/src/types/member.rs b/crates/ty_python_semantic/src/types/member.rs index 3c541b1035..4090533221 100644 --- a/crates/ty_python_semantic/src/types/member.rs +++ b/crates/ty_python_semantic/src/types/member.rs @@ -1,68 +1,40 @@ -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}; +use crate::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)] +/// about the type, type qualifiers, boundness/declaredness. +#[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize, Default)] 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()) + Self { + inner: PlaceAndQualifiers::unbound(), + } } - /// 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() + pub(super) fn definitely_declared(ty: Type<'db>) -> Self { + Self { + inner: Place::declared(ty).into(), + } } - /// Returns the inner type, unless it is definitely unbound. - pub(super) fn ignore_possibly_unbound(&self) -> Option> { - self.inner.place.ignore_possibly_unbound() + /// Returns `true` if the inner place is undefined (i.e. there is no such member). + pub(super) fn is_undefined(&self) -> bool { + self.inner.place.is_undefined() + } + + /// Returns the inner type, unless it is definitely undefined. + pub(super) fn ignore_possibly_undefined(&self) -> Option> { + self.inner.place.ignore_possibly_undefined() } /// Map a type transformation function over the type of this member. @@ -70,7 +42,6 @@ impl<'db> Member<'db> { 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, } } } @@ -89,13 +60,15 @@ pub(super) fn class_member<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str ConsideredDefinitions::EndOfScope, ); - if !place_and_quals.place.is_unbound() && !place_and_quals.is_init_var() { + if !place_and_quals.is_undefined() && !place_and_quals.is_init_var() { // Trust the declared type if we see a class-level declaration - return Member::declared(place_and_quals); + return Member { + inner: place_and_quals, + }; } if let PlaceAndQualifiers { - place: Place::Type(ty, _), + place: Place::Defined(ty, _, _), qualifiers, } = place_and_quals { @@ -106,12 +79,14 @@ pub(super) fn class_member<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str // 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) - } - }) + Member { + inner: match inferred { + Place::Undefined => Place::Undefined.with_qualifiers(qualifiers), + Place::Defined(_, origin, boundness) => { + Place::Defined(ty, origin, boundness).with_qualifiers(qualifiers) + } + }, + } } else { Member::unbound() } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index fdcb693508..eaa88bc793 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -9,7 +9,7 @@ use rustc_hash::FxHashMap; use crate::types::TypeContext; use crate::{ Db, FxOrderSet, - place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations}, + place::{Definedness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations}, semantic_index::{ SemanticIndex, definition::Definition, place::ScopedPlaceId, place_table, use_def_map, }, @@ -111,7 +111,7 @@ impl<'db> ProtocolClass<'db> { .into_place_and_conflicting_declarations() .0 .place - .is_unbound() + .is_undefined() }); if has_declaration { @@ -645,7 +645,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { ProtocolMemberKind::Method(method) => { // `__call__` members must be special cased for several reasons: // - // 1. Looking up `__call__` on the meta-type of a `Callable` type returns `Place::Unbound` currently + // 1. Looking up `__call__` on the meta-type of a `Callable` type returns `Place::Undefined` currently // 2. Looking up `__call__` on the meta-type of a function-literal type currently returns a type that // has an extremely vague signature (`(*args, **kwargs) -> Any`), which is not useful for protocol // checking. @@ -658,11 +658,11 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { }; attribute_type } else { - let Place::Type(attribute_type, Boundness::Bound) = other + let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) = other .invoke_descriptor_protocol( db, self.name, - Place::Unbound.into(), + Place::Undefined.into(), InstanceFallbackShadowsNonDataDescriptor::No, MemberLookupPolicy::default(), ) @@ -685,10 +685,10 @@ 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::Type(_, Boundness::Bound) + Place::Defined(_, _, Definedness::AlwaysDefined) )), ProtocolMemberKind::Other(member_type) => { - let Place::Type(attribute_type, Boundness::Bound) = + let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) = other.member(db, self.name).place else { return ConstraintSet::from(false); @@ -798,7 +798,7 @@ fn cached_protocol_interface<'db>( // type narrowing that uses `isinstance()` or `issubclass()` with // runtime-checkable protocols. for (symbol_id, bindings) in use_def_map.all_end_of_scope_symbol_bindings() { - let Some(ty) = place_from_bindings(db, bindings).ignore_possibly_unbound() else { + let Some(ty) = place_from_bindings(db, bindings).ignore_possibly_undefined() else { continue; }; direct_members.insert( @@ -809,7 +809,7 @@ fn cached_protocol_interface<'db>( for (symbol_id, declarations) in use_def_map.all_end_of_scope_symbol_declarations() { let place = place_from_declarations(db, declarations).ignore_conflicting_declarations(); - if let Some(new_type) = place.place.ignore_possibly_unbound() { + if let Some(new_type) = place.place.ignore_possibly_undefined() { direct_members .entry(symbol_id) .and_modify(|(ty, quals, _)| {