mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-31 12:05:57 +00:00 
			
		
		
		
	[ty] refactor Place (#20871)
				
					
				
			## Summary Part of astral-sh/ty#1341 The following changes will be made to `Place`. * Introduce `TypeOrigin` * `Place::Type` -> `Place::Defined` * `Place::Unbound` -> `Place::Undefined` * `Boundness` -> `Definedness` `TypeOrigin::Declared`+`Definedness::PossiblyUndefined` are patterns that weren't considered before, but this PR doesn't address them yet, only refactors. ## Test Plan Refactoring
This commit is contained in:
		
							parent
							
								
									4b7f184ab7
								
							
						
					
					
						commit
						9de34e7ac1
					
				
					 16 changed files with 681 additions and 548 deletions
				
			
		|  | @ -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<Type<'db>>) -> 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<Type<'db>>) -> 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<Type<'db>> { | ||||
|     /// 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<Type<'db>> { | ||||
|         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<LookupResult<'db>> 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<LookupResult<'db>> 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<Type<'db>> { | ||||
|         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(<union of self-type and fallback-type>, Boundness::Bound)`
 | ||||
|     ///       return `Place(<union of self-type and fallback-type>, Definedness::AlwaysDefined)`
 | ||||
|     ///    4. Else, if `self` is possibly unbound and `fallback` is possibly unbound,
 | ||||
|     ///       return `Place(<union of self-type and fallback-type>, Boundness::PossiblyUnbound)`
 | ||||
|     ///       return `Place(<union of self-type and fallback-type>, 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 = <unbound>` 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 = <unbound>
 | ||||
|  | @ -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)); | ||||
|     } | ||||
|  |  | |||
|  | @ -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, | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -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( | ||||
|                             return Place::bound(Type::EnumLiteral(EnumLiteralType::new( | ||||
|                                 db, | ||||
|                                 enum_class, | ||||
|                                 resolved_name, | ||||
|                                 )), | ||||
|                                 Boundness::Bound, | ||||
|                             ) | ||||
|                             ))) | ||||
|                             .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<Type<'db>> 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 | ||||
|                     }, | ||||
|                 ) | ||||
|             }, | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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)), | ||||
|  |  | |||
|  | @ -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<Type<'db>> = 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), | ||||
|                     if let member @ PlaceAndQualifiers { | ||||
|                         place: Place::Defined(ty, origin, boundness), | ||||
|                         qualifiers, | ||||
|                             }, | ||||
|                         is_declared, | ||||
|                     } = class.own_instance_member(db, name) | ||||
|                     } = 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: <annotation> = …
 | ||||
| 
 | ||||
|                 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 { | ||||
|         Member { | ||||
|             inner: if is_attribute_bound { | ||||
|                 Place::bound(union_of_inferred_types.build()).with_qualifiers(qualifiers) | ||||
|             } else { | ||||
|             Place::Unbound.with_qualifiers(qualifiers) | ||||
|         }) | ||||
|                 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<ClassLiteral<'_>, 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; | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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",
 | ||||
|  |  | |||
|  | @ -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,18 +1490,20 @@ 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() | ||||
|         { | ||||
|         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::Type(ty, Boundness::Bound) = value_type.member(db, attr).place { | ||||
|                     if let Place::Defined(ty, _, Definedness::AlwaysDefined) = | ||||
|                         value_type.member(db, attr).place | ||||
|                     { | ||||
|                         // TODO: also consider qualifiers on the attribute
 | ||||
|                         ty | ||||
|                     } else { | ||||
|  | @ -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 | ||||
|                                 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) | ||||
|                         { | ||||
|  |  | |||
|  | @ -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()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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<Type<'db>> { | ||||
|         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<Type<'db>> { | ||||
|         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() | ||||
|             } | ||||
|  |  | |||
|  | @ -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, _)| { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Shunsuke Shibayama
						Shunsuke Shibayama