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