[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:
Shunsuke Shibayama 2025-10-16 03:19:19 +09:00 committed by GitHub
parent 4b7f184ab7
commit 9de34e7ac1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 681 additions and 548 deletions

View file

@ -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));
} }

View file

@ -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,
} }
} }
} }

View file

@ -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
}, },
) )
}, },

View file

@ -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)

View file

@ -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)),

View file

@ -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;
} }

View file

@ -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()

View file

@ -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

View file

@ -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;

View file

@ -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 {

View file

@ -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",

View file

@ -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)
{ {

View file

@ -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()),
} }
} }
} }

View file

@ -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

View file

@ -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()
} }

View file

@ -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, _)| {