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