mirror of
https://github.com/astral-sh/ruff
synced 2026-01-21 13:30:49 -05:00
[ty] Store un-widened type in Place (#22093)
## Summary See: https://github.com/astral-sh/ruff/pull/22025#discussion_r2632724156
This commit is contained in:
@@ -61,6 +61,33 @@ impl TypeOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a place's type should be widened with `Unknown` when accessed publicly.
|
||||
///
|
||||
/// For undeclared public symbols (e.g., class attributes without type annotations),
|
||||
/// the gradual typing guarantee requires that we consider them as potentially
|
||||
/// modified externally, so their type is widened to a union with `Unknown`.
|
||||
///
|
||||
/// This enum tracks whether such widening should be applied, allowing callers
|
||||
/// to access either the raw inferred type or the widened public type.
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, get_size2::GetSize)]
|
||||
pub(crate) enum Widening {
|
||||
/// The type should not be widened with `Unknown`.
|
||||
#[default]
|
||||
None,
|
||||
/// The type should be widened with `Unknown` when accessed publicly.
|
||||
WithUnknown,
|
||||
}
|
||||
|
||||
impl Widening {
|
||||
/// Apply widening to the type if this is `WithUnknown`.
|
||||
pub(crate) fn apply_if_needed<'db>(self, db: &'db dyn Db, ty: Type<'db>) -> Type<'db> {
|
||||
match self {
|
||||
Self::None => ty,
|
||||
Self::WithUnknown => UnionType::from_elements(db, [Type::unknown(), ty]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a place lookup, which can either be a (possibly undefined) type
|
||||
/// or a completely undefined place.
|
||||
///
|
||||
@@ -83,28 +110,38 @@ impl TypeOrigin {
|
||||
///
|
||||
/// If we look up places in this scope, we would get the following results:
|
||||
/// ```rs
|
||||
/// 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),
|
||||
/// 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, Copy, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||
pub(crate) enum Place<'db> {
|
||||
Defined(Type<'db>, TypeOrigin, Definedness),
|
||||
Defined(Type<'db>, TypeOrigin, Definedness, Widening),
|
||||
Undefined,
|
||||
}
|
||||
|
||||
impl<'db> Place<'db> {
|
||||
/// 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::Defined(ty.into(), TypeOrigin::Inferred, Definedness::AlwaysDefined)
|
||||
Place::Defined(
|
||||
ty.into(),
|
||||
TypeOrigin::Inferred,
|
||||
Definedness::AlwaysDefined,
|
||||
Widening::None,
|
||||
)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
Place::Defined(
|
||||
ty.into(),
|
||||
TypeOrigin::Declared,
|
||||
Definedness::AlwaysDefined,
|
||||
Widening::None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructor that creates a [`Place`] with a [`crate::types::TodoType`] type
|
||||
@@ -115,6 +152,7 @@ impl<'db> Place<'db> {
|
||||
todo_type!(message),
|
||||
TypeOrigin::Inferred,
|
||||
Definedness::AlwaysDefined,
|
||||
Widening::None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -128,7 +166,18 @@ impl<'db> Place<'db> {
|
||||
/// 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::Defined(ty, _, _) => Some(*ty),
|
||||
Place::Defined(ty, _, _, _) => Some(*ty),
|
||||
Place::Undefined => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the type of the place without widening applied.
|
||||
///
|
||||
/// The stored type is always the unwidened type. Widening (union with `Unknown`)
|
||||
/// is applied lazily when converting to `LookupResult`.
|
||||
pub(crate) fn unwidened_type(&self) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Place::Defined(ty, _, _, _) => Some(*ty),
|
||||
Place::Undefined => None,
|
||||
}
|
||||
}
|
||||
@@ -143,7 +192,20 @@ impl<'db> Place<'db> {
|
||||
#[must_use]
|
||||
pub(crate) fn map_type(self, f: impl FnOnce(Type<'db>) -> Type<'db>) -> Place<'db> {
|
||||
match self {
|
||||
Place::Defined(ty, origin, definedness) => Place::Defined(f(ty), origin, definedness),
|
||||
Place::Defined(ty, origin, definedness, widening) => {
|
||||
Place::Defined(f(ty), origin, definedness, widening)
|
||||
}
|
||||
Place::Undefined => Place::Undefined,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the widening mode for this place.
|
||||
#[must_use]
|
||||
pub(crate) fn with_widening(self, widening: Widening) -> Place<'db> {
|
||||
match self {
|
||||
Place::Defined(ty, origin, definedness, _) => {
|
||||
Place::Defined(ty, origin, definedness, widening)
|
||||
}
|
||||
Place::Undefined => Place::Undefined,
|
||||
}
|
||||
}
|
||||
@@ -161,21 +223,24 @@ 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::Defined(Type::Union(union), origin, definedness) => union
|
||||
Place::Defined(Type::Union(union), origin, definedness, widening) => union
|
||||
.map_with_boundness(db, |elem| {
|
||||
Place::Defined(*elem, origin, definedness).try_call_dunder_get(db, owner)
|
||||
Place::Defined(*elem, origin, definedness, widening)
|
||||
.try_call_dunder_get(db, owner)
|
||||
}),
|
||||
|
||||
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(Type::Intersection(intersection), origin, definedness, widening) => {
|
||||
intersection.map_with_boundness(db, |elem| {
|
||||
Place::Defined(*elem, origin, definedness, widening)
|
||||
.try_call_dunder_get(db, owner)
|
||||
})
|
||||
}
|
||||
|
||||
Place::Defined(self_ty, origin, definedness) => {
|
||||
Place::Defined(self_ty, origin, definedness, widening) => {
|
||||
if let Some((dunder_get_return_ty, _)) =
|
||||
self_ty.try_call_dunder_get(db, Type::none(db), owner)
|
||||
{
|
||||
Place::Defined(dunder_get_return_ty, origin, definedness)
|
||||
Place::Defined(dunder_get_return_ty, origin, definedness, widening)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
@@ -186,7 +251,7 @@ impl<'db> Place<'db> {
|
||||
}
|
||||
|
||||
pub(crate) const fn is_definitely_bound(&self) -> bool {
|
||||
matches!(self, Place::Defined(_, _, Definedness::AlwaysDefined))
|
||||
matches!(self, Place::Defined(_, _, Definedness::AlwaysDefined, _))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +265,7 @@ impl<'db> From<LookupResult<'db>> for PlaceAndQualifiers<'db> {
|
||||
type_and_qualifiers.inner_type(),
|
||||
TypeOrigin::Inferred,
|
||||
Definedness::PossiblyUndefined,
|
||||
Widening::None,
|
||||
)
|
||||
.with_qualifiers(type_and_qualifiers.qualifiers()),
|
||||
}
|
||||
@@ -220,7 +286,7 @@ impl<'db> LookupError<'db> {
|
||||
db: &'db dyn Db,
|
||||
fallback: PlaceAndQualifiers<'db>,
|
||||
) -> LookupResult<'db> {
|
||||
let fallback = fallback.into_lookup_result();
|
||||
let fallback = fallback.into_lookup_result(db);
|
||||
match (&self, &fallback) {
|
||||
(LookupError::Undefined(_), _) => fallback,
|
||||
(LookupError::PossiblyUndefined { .. }, Err(LookupError::Undefined(_))) => Err(self),
|
||||
@@ -645,18 +711,27 @@ impl<'db> PlaceAndQualifiers<'db> {
|
||||
/// Transform place and qualifiers into a [`LookupResult`],
|
||||
/// 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> {
|
||||
///
|
||||
/// For places marked with `Widening::WithUnknown`, this applies the gradual typing guarantee
|
||||
/// by creating a union with `Unknown`.
|
||||
pub(crate) fn into_lookup_result(self, db: &'db dyn Db) -> LookupResult<'db> {
|
||||
match self {
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(ty, origin, Definedness::AlwaysDefined),
|
||||
place: Place::Defined(ty, origin, Definedness::AlwaysDefined, widening),
|
||||
qualifiers,
|
||||
} => Ok(TypeAndQualifiers::new(ty, origin, qualifiers)),
|
||||
} => {
|
||||
let ty = widening.apply_if_needed(db, ty);
|
||||
Ok(TypeAndQualifiers::new(ty, origin, qualifiers))
|
||||
}
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(ty, origin, Definedness::PossiblyUndefined),
|
||||
place: Place::Defined(ty, origin, Definedness::PossiblyUndefined, widening),
|
||||
qualifiers,
|
||||
} => Err(LookupError::PossiblyUndefined(TypeAndQualifiers::new(
|
||||
ty, origin, qualifiers,
|
||||
))),
|
||||
} => {
|
||||
let ty = widening.apply_if_needed(db, ty);
|
||||
Err(LookupError::PossiblyUndefined(TypeAndQualifiers::new(
|
||||
ty, origin, qualifiers,
|
||||
)))
|
||||
}
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Undefined,
|
||||
qualifiers,
|
||||
@@ -664,17 +739,18 @@ impl<'db> PlaceAndQualifiers<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely unwrap the place and the qualifiers into a [`TypeQualifiers`].
|
||||
/// Safely unwrap the place and the qualifiers into a [`TypeAndQualifiers`].
|
||||
///
|
||||
/// If the place is definitely unbound or possibly unbound, it will be transformed into a
|
||||
/// [`LookupError`] and `diagnostic_fn` will be applied to the error value before returning
|
||||
/// the result of `diagnostic_fn` (which will be a [`TypeQualifiers`]). This allows the caller
|
||||
/// the result of `diagnostic_fn` (which will be a [`TypeAndQualifiers`]). This allows the caller
|
||||
/// to ensure that a diagnostic is emitted if the place is possibly or definitely unbound.
|
||||
pub(crate) fn unwrap_with_diagnostic(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
diagnostic_fn: impl FnOnce(LookupError<'db>) -> TypeAndQualifiers<'db>,
|
||||
) -> TypeAndQualifiers<'db> {
|
||||
self.into_lookup_result().unwrap_or_else(diagnostic_fn)
|
||||
self.into_lookup_result(db).unwrap_or_else(diagnostic_fn)
|
||||
}
|
||||
|
||||
/// Fallback (partially or fully) to another place if `self` is partially or fully unbound.
|
||||
@@ -693,7 +769,7 @@ impl<'db> PlaceAndQualifiers<'db> {
|
||||
db: &'db dyn Db,
|
||||
fallback_fn: impl FnOnce() -> PlaceAndQualifiers<'db>,
|
||||
) -> Self {
|
||||
self.into_lookup_result()
|
||||
self.into_lookup_result(db)
|
||||
.or_else(|lookup_error| lookup_error.or_fall_back_to(db, fallback_fn()))
|
||||
.into()
|
||||
}
|
||||
@@ -707,9 +783,15 @@ impl<'db> PlaceAndQualifiers<'db> {
|
||||
let place = match (previous_place.place, self.place) {
|
||||
// In fixed-point iteration of type inference, the member type must be monotonically widened and not "oscillate".
|
||||
// Here, monotonicity is guaranteed by pre-unioning the type of the previous iteration into the current result.
|
||||
(Place::Defined(prev_ty, _, _), Place::Defined(ty, origin, definedness)) => {
|
||||
Place::Defined(ty.cycle_normalized(db, prev_ty, cycle), origin, definedness)
|
||||
}
|
||||
(
|
||||
Place::Defined(prev_ty, _, _, _),
|
||||
Place::Defined(ty, origin, definedness, widening),
|
||||
) => Place::Defined(
|
||||
ty.cycle_normalized(db, prev_ty, cycle),
|
||||
origin,
|
||||
definedness,
|
||||
widening,
|
||||
),
|
||||
// If a `Place` in the current cycle is `Defined` but `Undefined` in the previous cycle,
|
||||
// that means that its definedness depends on the truthiness of the previous cycle value.
|
||||
// In this case, the definedness of the current cycle `Place` is set to `PossiblyUndefined`.
|
||||
@@ -717,14 +799,17 @@ impl<'db> PlaceAndQualifiers<'db> {
|
||||
// so convergence is guaranteed without resorting to this handling.
|
||||
// However, the handling described above may reduce the exactness of reachability analysis,
|
||||
// so it may be better to remove it. In that case, this branch is necessary.
|
||||
(Place::Undefined, Place::Defined(ty, origin, _definedness)) => Place::Defined(
|
||||
ty.recursive_type_normalized(db, cycle),
|
||||
origin,
|
||||
Definedness::PossiblyUndefined,
|
||||
),
|
||||
(Place::Undefined, Place::Defined(ty, origin, _definedness, widening)) => {
|
||||
Place::Defined(
|
||||
ty.recursive_type_normalized(db, cycle),
|
||||
origin,
|
||||
Definedness::PossiblyUndefined,
|
||||
widening,
|
||||
)
|
||||
}
|
||||
// If a `Place` that was `Defined(Divergent)` in the previous cycle is actually found to be unreachable in the current cycle,
|
||||
// it is set to `Undefined` (because the cycle initial value does not include meaningful reachability information).
|
||||
(Place::Defined(ty, origin, _definedness), Place::Undefined) => {
|
||||
(Place::Defined(ty, origin, _definedness, widening), Place::Undefined) => {
|
||||
if cycle.head_ids().any(|id| ty == Type::divergent(id)) {
|
||||
Place::Undefined
|
||||
} else {
|
||||
@@ -732,6 +817,7 @@ impl<'db> PlaceAndQualifiers<'db> {
|
||||
ty.recursive_type_normalized(db, cycle),
|
||||
origin,
|
||||
Definedness::PossiblyUndefined,
|
||||
widening,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -814,30 +900,32 @@ 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::Defined(Type::Dynamic(DynamicType::Unknown), origin, definedness),
|
||||
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 {
|
||||
Place::Defined(inferred, origin, boundness) => Place::Defined(
|
||||
Place::Defined(inferred, origin, boundness, _) => Place::Defined(
|
||||
UnionType::from_elements(db, [Type::unknown(), inferred]),
|
||||
origin,
|
||||
boundness,
|
||||
Widening::None,
|
||||
)
|
||||
.with_qualifiers(qualifiers),
|
||||
Place::Undefined => {
|
||||
Place::Defined(Type::unknown(), origin, definedness).with_qualifiers(qualifiers)
|
||||
Place::Defined(Type::unknown(), origin, definedness, Widening::None)
|
||||
.with_qualifiers(qualifiers)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Place is declared, trust the declared type
|
||||
place_and_quals @ PlaceAndQualifiers {
|
||||
place: Place::Defined(_, _, Definedness::AlwaysDefined),
|
||||
place: Place::Defined(_, _, Definedness::AlwaysDefined, _),
|
||||
qualifiers: _,
|
||||
} => place_and_quals,
|
||||
// Place is possibly declared
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(declared_ty, origin, Definedness::PossiblyUndefined),
|
||||
place: Place::Defined(declared_ty, origin, Definedness::PossiblyUndefined, _),
|
||||
qualifiers,
|
||||
} => {
|
||||
let bindings = all_considered_bindings();
|
||||
@@ -850,10 +938,15 @@ pub(crate) fn place_by_id<'db>(
|
||||
// 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::Defined(declared_ty, origin, Definedness::AlwaysDefined)
|
||||
Place::Defined(
|
||||
declared_ty,
|
||||
origin,
|
||||
Definedness::AlwaysDefined,
|
||||
Widening::None,
|
||||
)
|
||||
}
|
||||
// Place is possibly undeclared and (possibly) bound
|
||||
Place::Defined(inferred_ty, origin, boundness) => Place::Defined(
|
||||
Place::Defined(inferred_ty, origin, boundness, _) => Place::Defined(
|
||||
UnionType::from_elements(db, [inferred_ty, declared_ty]),
|
||||
origin,
|
||||
if boundness_analysis == BoundnessAnalysis::AssumeBound {
|
||||
@@ -861,12 +954,13 @@ pub(crate) fn place_by_id<'db>(
|
||||
} else {
|
||||
boundness
|
||||
},
|
||||
Widening::None,
|
||||
),
|
||||
};
|
||||
|
||||
PlaceAndQualifiers { place, qualifiers }
|
||||
}
|
||||
// Place is undeclared, return the union of `Unknown` with the inferred type
|
||||
// Place is undeclared, infer the type from bindings
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Undefined,
|
||||
qualifiers: _,
|
||||
@@ -877,8 +971,10 @@ pub(crate) fn place_by_id<'db>(
|
||||
place_from_bindings_impl(db, bindings, requires_explicit_reexport).place;
|
||||
|
||||
if boundness_analysis == BoundnessAnalysis::AssumeBound {
|
||||
if let Place::Defined(ty, origin, Definedness::PossiblyUndefined) = inferred {
|
||||
inferred = Place::Defined(ty, origin, Definedness::AlwaysDefined);
|
||||
if let Place::Defined(ty, origin, Definedness::PossiblyUndefined, widening) =
|
||||
inferred
|
||||
{
|
||||
inferred = Place::Defined(ty, origin, Definedness::AlwaysDefined, widening);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -926,10 +1022,10 @@ pub(crate) fn place_by_id<'db>(
|
||||
{
|
||||
inferred.into()
|
||||
} else {
|
||||
// Widen the inferred type of undeclared public symbols by unioning with `Unknown`
|
||||
inferred
|
||||
.map_type(|ty| UnionType::from_elements(db, [Type::unknown(), ty]))
|
||||
.into()
|
||||
// Gradual typing guarantee: Mark undeclared public symbols for widening.
|
||||
// The actual union with `Unknown` is applied lazily when converting to
|
||||
// LookupResult via `into_lookup_result`.
|
||||
inferred.with_widening(Widening::WithUnknown).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1166,11 +1262,16 @@ fn place_from_bindings_impl<'db>(
|
||||
};
|
||||
|
||||
match deleted_reachability {
|
||||
Truthiness::AlwaysFalse => Place::Defined(ty, TypeOrigin::Inferred, boundness),
|
||||
Truthiness::AlwaysTrue => Place::Undefined,
|
||||
Truthiness::Ambiguous => {
|
||||
Place::Defined(ty, TypeOrigin::Inferred, Definedness::PossiblyUndefined)
|
||||
Truthiness::AlwaysFalse => {
|
||||
Place::Defined(ty, TypeOrigin::Inferred, boundness, Widening::None)
|
||||
}
|
||||
Truthiness::AlwaysTrue => Place::Undefined,
|
||||
Truthiness::Ambiguous => Place::Defined(
|
||||
ty,
|
||||
TypeOrigin::Inferred,
|
||||
Definedness::PossiblyUndefined,
|
||||
Widening::None,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
Place::Undefined
|
||||
@@ -1399,9 +1500,13 @@ fn place_from_declarations_impl<'db>(
|
||||
},
|
||||
};
|
||||
|
||||
let place_and_quals =
|
||||
Place::Defined(declared.inner_type(), TypeOrigin::Declared, boundness)
|
||||
.with_qualifiers(declared.qualifiers());
|
||||
let place_and_quals = Place::Defined(
|
||||
declared.inner_type(),
|
||||
TypeOrigin::Declared,
|
||||
boundness,
|
||||
Widening::None,
|
||||
)
|
||||
.with_qualifiers(declared.qualifiers());
|
||||
|
||||
if let Some(conflicting) = conflicting {
|
||||
PlaceFromDeclarationsResult::conflict(place_and_quals, conflicting, first_declaration)
|
||||
@@ -1455,7 +1560,7 @@ mod implicit_globals {
|
||||
use crate::types::{KnownClass, MemberLookupPolicy, Parameter, Parameters, Signature, Type};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use super::{Place, place_from_declarations};
|
||||
use super::{Place, Widening, place_from_declarations};
|
||||
|
||||
pub(crate) fn module_type_implicit_global_declaration<'db>(
|
||||
db: &'db dyn Db,
|
||||
@@ -1518,6 +1623,7 @@ mod implicit_globals {
|
||||
.to_specialized_instance(db, [Type::any(), KnownClass::Int.to_instance(db)]),
|
||||
TypeOrigin::Inferred,
|
||||
Definedness::PossiblyUndefined,
|
||||
Widening::None,
|
||||
)
|
||||
.into(),
|
||||
|
||||
@@ -1539,6 +1645,7 @@ mod implicit_globals {
|
||||
Type::function_like_callable(db, signature),
|
||||
TypeOrigin::Inferred,
|
||||
Definedness::PossiblyUndefined,
|
||||
Widening::None,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
@@ -1740,19 +1847,21 @@ mod tests {
|
||||
let unbound = || Place::Undefined.with_qualifiers(TypeQualifiers::empty());
|
||||
|
||||
let possibly_unbound_ty1 = || {
|
||||
Place::Defined(ty1, Inferred, PossiblyUndefined)
|
||||
Place::Defined(ty1, Inferred, PossiblyUndefined, Widening::None)
|
||||
.with_qualifiers(TypeQualifiers::empty())
|
||||
};
|
||||
let possibly_unbound_ty2 = || {
|
||||
Place::Defined(ty2, Inferred, PossiblyUndefined)
|
||||
Place::Defined(ty2, Inferred, PossiblyUndefined, Widening::None)
|
||||
.with_qualifiers(TypeQualifiers::empty())
|
||||
};
|
||||
|
||||
let bound_ty1 = || {
|
||||
Place::Defined(ty1, Inferred, AlwaysDefined).with_qualifiers(TypeQualifiers::empty())
|
||||
Place::Defined(ty1, Inferred, AlwaysDefined, Widening::None)
|
||||
.with_qualifiers(TypeQualifiers::empty())
|
||||
};
|
||||
let bound_ty2 = || {
|
||||
Place::Defined(ty2, Inferred, AlwaysDefined).with_qualifiers(TypeQualifiers::empty())
|
||||
Place::Defined(ty2, Inferred, AlwaysDefined, Widening::None)
|
||||
.with_qualifiers(TypeQualifiers::empty())
|
||||
};
|
||||
|
||||
// Start from an unbound symbol
|
||||
@@ -1773,7 +1882,8 @@ mod tests {
|
||||
Place::Defined(
|
||||
UnionType::from_elements(&db, [ty1, ty2]),
|
||||
Inferred,
|
||||
PossiblyUndefined
|
||||
PossiblyUndefined,
|
||||
Widening::None
|
||||
)
|
||||
.into()
|
||||
);
|
||||
@@ -1782,7 +1892,8 @@ mod tests {
|
||||
Place::Defined(
|
||||
UnionType::from_elements(&db, [ty1, ty2]),
|
||||
Inferred,
|
||||
AlwaysDefined
|
||||
AlwaysDefined,
|
||||
Widening::None
|
||||
)
|
||||
.into()
|
||||
);
|
||||
@@ -1800,7 +1911,7 @@ mod tests {
|
||||
fn assert_bound_string_symbol<'db>(db: &'db dyn Db, symbol: Place<'db>) {
|
||||
assert!(matches!(
|
||||
symbol,
|
||||
Place::Defined(Type::NominalInstance(_), _, Definedness::AlwaysDefined)
|
||||
Place::Defined(Type::NominalInstance(_), _, Definedness::AlwaysDefined, _)
|
||||
));
|
||||
assert_eq!(symbol.expect_type(), KnownClass::Str.to_instance(db));
|
||||
}
|
||||
|
||||
@@ -957,11 +957,13 @@ impl ReachabilityConstraints {
|
||||
_,
|
||||
_,
|
||||
crate::place::Definedness::AlwaysDefined,
|
||||
_,
|
||||
) => Truthiness::AlwaysTrue,
|
||||
crate::place::Place::Defined(
|
||||
_,
|
||||
_,
|
||||
crate::place::Definedness::PossiblyUndefined,
|
||||
_,
|
||||
) => Truthiness::Ambiguous,
|
||||
crate::place::Place::Undefined => Truthiness::AlwaysFalse,
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ pub(crate) use self::signatures::{CallableSignature, Signature};
|
||||
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
|
||||
pub use crate::diagnostic::add_inferred_python_version_hint_to_diagnostic;
|
||||
use crate::place::{
|
||||
Definedness, Place, PlaceAndQualifiers, TypeOrigin, imported_symbol, known_module_symbol,
|
||||
Definedness, Place, PlaceAndQualifiers, TypeOrigin, Widening, imported_symbol,
|
||||
known_module_symbol,
|
||||
};
|
||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||
use crate::semantic_index::place::ScopedPlaceId;
|
||||
@@ -1855,7 +1856,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
.place;
|
||||
|
||||
if let Place::Defined(ty, _, Definedness::AlwaysDefined) = call_symbol {
|
||||
if let Place::Defined(ty, _, Definedness::AlwaysDefined, _) = call_symbol {
|
||||
ty.try_upcast_to_callable(db)
|
||||
} else {
|
||||
None
|
||||
@@ -3677,13 +3678,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::Defined(attribute_type, _, _) => member.has_disjoint_type_from(
|
||||
db,
|
||||
attribute_type,
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
),
|
||||
Place::Defined(attribute_type, _, _, _) => member
|
||||
.has_disjoint_type_from(
|
||||
db,
|
||||
attribute_type,
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
),
|
||||
Place::Undefined => ConstraintSet::from(false),
|
||||
}
|
||||
})
|
||||
@@ -4654,9 +4656,10 @@ 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::Defined(_, _, _) = 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::Defined(_, _, _)) =
|
||||
} else if let Some(place @ Place::Defined(_, _, _, _)) =
|
||||
self.find_name_in_mro(db, name).map(|inner| inner.place)
|
||||
{
|
||||
place
|
||||
@@ -4717,7 +4720,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
let descr_get = self.class_member(db, "__get__".into()).place;
|
||||
|
||||
if let Place::Defined(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| {
|
||||
@@ -4762,12 +4765,12 @@ impl<'db> Type<'db> {
|
||||
//
|
||||
// The same is true for `Never`.
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(Type::Dynamic(_) | Type::Never, _, _),
|
||||
place: Place::Defined(Type::Dynamic(_) | Type::Never, _, _, _),
|
||||
qualifiers: _,
|
||||
} => (attribute, AttributeKind::DataDescriptor),
|
||||
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(Type::Union(union), origin, boundness),
|
||||
place: Place::Defined(Type::Union(union), origin, boundness, widening),
|
||||
qualifiers,
|
||||
} => (
|
||||
union
|
||||
@@ -4777,6 +4780,7 @@ impl<'db> Type<'db> {
|
||||
.map_or(*elem, |(ty, _)| ty),
|
||||
origin,
|
||||
boundness,
|
||||
widening,
|
||||
)
|
||||
})
|
||||
.with_qualifiers(qualifiers),
|
||||
@@ -4792,7 +4796,7 @@ impl<'db> Type<'db> {
|
||||
),
|
||||
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(Type::Intersection(intersection), origin, boundness),
|
||||
place: Place::Defined(Type::Intersection(intersection), origin, boundness, widening),
|
||||
qualifiers,
|
||||
} => (
|
||||
intersection
|
||||
@@ -4802,6 +4806,7 @@ impl<'db> Type<'db> {
|
||||
.map_or(*elem, |(ty, _)| ty),
|
||||
origin,
|
||||
boundness,
|
||||
widening,
|
||||
)
|
||||
})
|
||||
.with_qualifiers(qualifiers),
|
||||
@@ -4810,14 +4815,14 @@ impl<'db> Type<'db> {
|
||||
),
|
||||
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(attribute_ty, origin, boundness),
|
||||
place: Place::Defined(attribute_ty, origin, boundness, widening),
|
||||
qualifiers: _,
|
||||
} => {
|
||||
if let Some((return_ty, attribute_kind)) =
|
||||
attribute_ty.try_call_dunder_get(db, instance, owner)
|
||||
{
|
||||
(
|
||||
Place::Defined(return_ty, origin, boundness).into(),
|
||||
Place::Defined(return_ty, origin, boundness, widening).into(),
|
||||
attribute_kind,
|
||||
)
|
||||
} else {
|
||||
@@ -4910,14 +4915,14 @@ 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::Defined(_, _, _), _, Place::Undefined) => {
|
||||
(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::Defined(_, _, Definedness::AlwaysDefined),
|
||||
meta_attr @ Place::Defined(_, _, Definedness::AlwaysDefined, _),
|
||||
AttributeKind::DataDescriptor,
|
||||
_,
|
||||
) => meta_attr.with_qualifiers(meta_attr_qualifiers),
|
||||
@@ -4926,13 +4931,14 @@ impl<'db> Type<'db> {
|
||||
// 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::Defined(meta_attr_ty, meta_origin, Definedness::PossiblyUndefined),
|
||||
Place::Defined(meta_attr_ty, meta_origin, Definedness::PossiblyUndefined, _),
|
||||
AttributeKind::DataDescriptor,
|
||||
Place::Defined(fallback_ty, fallback_origin, fallback_boundness),
|
||||
Place::Defined(fallback_ty, fallback_origin, fallback_boundness, fallback_widening),
|
||||
) => Place::Defined(
|
||||
UnionType::from_elements(db, [meta_attr_ty, fallback_ty]),
|
||||
meta_origin.merge(fallback_origin),
|
||||
fallback_boundness,
|
||||
fallback_widening,
|
||||
)
|
||||
.with_qualifiers(meta_attr_qualifiers.union(fallback_qualifiers)),
|
||||
|
||||
@@ -4945,9 +4951,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::Defined(_, _, _),
|
||||
Place::Defined(_, _, _, _),
|
||||
AttributeKind::NormalOrNonDataDescriptor,
|
||||
fallback @ Place::Defined(_, _, Definedness::AlwaysDefined),
|
||||
fallback @ Place::Defined(_, _, Definedness::AlwaysDefined, _),
|
||||
) if policy == InstanceFallbackShadowsNonDataDescriptor::Yes => {
|
||||
fallback.with_qualifiers(fallback_qualifiers)
|
||||
}
|
||||
@@ -4956,13 +4962,14 @@ 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::Defined(meta_attr_ty, meta_origin, meta_attr_boundness),
|
||||
Place::Defined(meta_attr_ty, meta_origin, meta_attr_boundness, _),
|
||||
AttributeKind::NormalOrNonDataDescriptor,
|
||||
Place::Defined(fallback_ty, fallback_origin, fallback_boundness),
|
||||
Place::Defined(fallback_ty, fallback_origin, fallback_boundness, fallback_widening),
|
||||
) => Place::Defined(
|
||||
UnionType::from_elements(db, [meta_attr_ty, fallback_ty]),
|
||||
meta_origin.merge(fallback_origin),
|
||||
meta_attr_boundness.max(fallback_boundness),
|
||||
fallback_widening,
|
||||
)
|
||||
.with_qualifiers(meta_attr_qualifiers.union(fallback_qualifiers)),
|
||||
|
||||
@@ -5330,11 +5337,11 @@ impl<'db> Type<'db> {
|
||||
|
||||
match result {
|
||||
member @ PlaceAndQualifiers {
|
||||
place: Place::Defined(_, _, Definedness::AlwaysDefined),
|
||||
place: Place::Defined(_, _, Definedness::AlwaysDefined, _),
|
||||
qualifiers: _,
|
||||
} => member,
|
||||
member @ PlaceAndQualifiers {
|
||||
place: Place::Defined(_, _, Definedness::PossiblyUndefined),
|
||||
place: Place::Defined(_, _, Definedness::PossiblyUndefined, _),
|
||||
qualifiers: _,
|
||||
} => member
|
||||
.or_fall_back_to(db, custom_getattribute_result)
|
||||
@@ -6461,7 +6468,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
.place
|
||||
{
|
||||
Place::Defined(dunder_callable, _, boundness) => {
|
||||
Place::Defined(dunder_callable, _, boundness, _) => {
|
||||
let mut bindings = dunder_callable.bindings(db);
|
||||
bindings.replace_callable_type(dunder_callable, self);
|
||||
if boundness == Definedness::PossiblyUndefined {
|
||||
@@ -6609,7 +6616,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
.place
|
||||
{
|
||||
Place::Defined(dunder_callable, _, boundness) => {
|
||||
Place::Defined(dunder_callable, _, boundness, _) => {
|
||||
let bindings = dunder_callable
|
||||
.bindings(db)
|
||||
.match_parameters(db, argument_types)
|
||||
@@ -7212,7 +7219,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
let new_call_outcome = new_method.and_then(|new_method| {
|
||||
match new_method.place.try_call_dunder_get(db, self_type) {
|
||||
Place::Defined(new_method, _, boundness) => {
|
||||
Place::Defined(new_method, _, boundness, _) => {
|
||||
let argument_types = argument_types.with_self(Some(self_type));
|
||||
let result = new_method
|
||||
.bindings(db)
|
||||
@@ -7240,7 +7247,7 @@ impl<'db> Type<'db> {
|
||||
.place
|
||||
{
|
||||
Place::Undefined => Err(CallDunderError::MethodNotAvailable),
|
||||
Place::Defined(dunder_callable, _, boundness) => {
|
||||
Place::Defined(dunder_callable, _, boundness, _) => {
|
||||
let bindings = dunder_callable
|
||||
.bindings(db)
|
||||
.with_constructor_instance_type(init_ty);
|
||||
@@ -10656,7 +10663,7 @@ impl<'db> TypeVarConstraints<'db> {
|
||||
Place::Undefined => {
|
||||
possibly_unbound = true;
|
||||
}
|
||||
Place::Defined(ty_member, member_origin, member_boundness) => {
|
||||
Place::Defined(ty_member, member_origin, member_boundness, _) => {
|
||||
origin = origin.merge(member_origin);
|
||||
if member_boundness == Definedness::PossiblyUndefined {
|
||||
possibly_unbound = true;
|
||||
@@ -10679,6 +10686,7 @@ impl<'db> TypeVarConstraints<'db> {
|
||||
} else {
|
||||
Definedness::AlwaysDefined
|
||||
},
|
||||
Widening::None,
|
||||
)
|
||||
},
|
||||
qualifiers,
|
||||
@@ -11793,7 +11801,7 @@ impl<'db> BoolError<'db> {
|
||||
);
|
||||
if let Some((func_span, parameter_span)) = not_boolable_type
|
||||
.member(context.db(), "__bool__")
|
||||
.into_lookup_result()
|
||||
.into_lookup_result(context.db())
|
||||
.ok()
|
||||
.and_then(|quals| quals.inner_type().parameter_span(context.db(), None))
|
||||
{
|
||||
@@ -11821,7 +11829,7 @@ impl<'db> BoolError<'db> {
|
||||
);
|
||||
if let Some((func_span, return_type_span)) = not_boolable_type
|
||||
.member(context.db(), "__bool__")
|
||||
.into_lookup_result()
|
||||
.into_lookup_result(context.db())
|
||||
.ok()
|
||||
.and_then(|quals| quals.inner_type().function_spans(context.db()))
|
||||
.and_then(|spans| Some((spans.name, spans.return_type?)))
|
||||
@@ -13445,14 +13453,15 @@ 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::Defined(getattr_type, origin, boundness) = getattr_symbol.place {
|
||||
if let Place::Defined(getattr_type, origin, boundness, widening) = 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 PlaceAndQualifiers {
|
||||
place: Place::Defined(outcome.return_type(db), origin, boundness),
|
||||
place: Place::Defined(outcome.return_type(db), origin, boundness, widening),
|
||||
qualifiers: TypeQualifiers::FROM_MODULE_GETATTR,
|
||||
};
|
||||
}
|
||||
@@ -13972,7 +13981,7 @@ impl<'db> UnionType<'db> {
|
||||
Place::Undefined => {
|
||||
possibly_unbound = true;
|
||||
}
|
||||
Place::Defined(ty_member, member_origin, member_boundness) => {
|
||||
Place::Defined(ty_member, member_origin, member_boundness, _) => {
|
||||
origin = origin.merge(member_origin);
|
||||
if member_boundness == Definedness::PossiblyUndefined {
|
||||
possibly_unbound = true;
|
||||
@@ -13997,6 +14006,7 @@ impl<'db> UnionType<'db> {
|
||||
} else {
|
||||
Definedness::AlwaysDefined
|
||||
},
|
||||
Widening::None,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -14022,7 +14032,7 @@ impl<'db> UnionType<'db> {
|
||||
Place::Undefined => {
|
||||
possibly_unbound = true;
|
||||
}
|
||||
Place::Defined(ty_member, member_origin, member_boundness) => {
|
||||
Place::Defined(ty_member, member_origin, member_boundness, _) => {
|
||||
origin = origin.merge(member_origin);
|
||||
if member_boundness == Definedness::PossiblyUndefined {
|
||||
possibly_unbound = true;
|
||||
@@ -14047,6 +14057,7 @@ impl<'db> UnionType<'db> {
|
||||
} else {
|
||||
Definedness::AlwaysDefined
|
||||
},
|
||||
Widening::None,
|
||||
)
|
||||
},
|
||||
qualifiers,
|
||||
@@ -14399,7 +14410,7 @@ impl<'db> IntersectionType<'db> {
|
||||
let ty_member = transform_fn(&ty);
|
||||
match ty_member {
|
||||
Place::Undefined => {}
|
||||
Place::Defined(ty_member, member_origin, member_boundness) => {
|
||||
Place::Defined(ty_member, member_origin, member_boundness, _) => {
|
||||
origin = origin.merge(member_origin);
|
||||
all_unbound = false;
|
||||
if member_boundness == Definedness::AlwaysDefined {
|
||||
@@ -14422,6 +14433,7 @@ impl<'db> IntersectionType<'db> {
|
||||
} else {
|
||||
Definedness::PossiblyUndefined
|
||||
},
|
||||
Widening::None,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -14445,7 +14457,7 @@ impl<'db> IntersectionType<'db> {
|
||||
qualifiers |= new_qualifiers;
|
||||
match member {
|
||||
Place::Undefined => {}
|
||||
Place::Defined(ty_member, member_origin, member_boundness) => {
|
||||
Place::Defined(ty_member, member_origin, member_boundness, _) => {
|
||||
origin = origin.merge(member_origin);
|
||||
all_unbound = false;
|
||||
if member_boundness == Definedness::AlwaysDefined {
|
||||
@@ -14469,6 +14481,7 @@ impl<'db> IntersectionType<'db> {
|
||||
} else {
|
||||
Definedness::PossiblyUndefined
|
||||
},
|
||||
Widening::None,
|
||||
)
|
||||
},
|
||||
qualifiers,
|
||||
|
||||
@@ -1022,7 +1022,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::Defined(ty, _, Definedness::AlwaysDefined) => {
|
||||
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
|
||||
@@ -1032,7 +1032,7 @@ impl<'db> Bindings<'db> {
|
||||
ty
|
||||
}
|
||||
}
|
||||
Place::Defined(ty, _, Definedness::PossiblyUndefined) => {
|
||||
Place::Defined(ty, _, Definedness::PossiblyUndefined, _) => {
|
||||
union_with_default(ty)
|
||||
}
|
||||
Place::Undefined => default,
|
||||
@@ -2833,7 +2833,7 @@ impl<'a, 'db> ArgumentMatcher<'a, 'db> {
|
||||
)
|
||||
.place
|
||||
{
|
||||
Place::Defined(getitem_method, _, Definedness::AlwaysDefined) => getitem_method
|
||||
Place::Defined(getitem_method, _, Definedness::AlwaysDefined, _) => getitem_method
|
||||
.try_call(db, &CallArguments::positional([Type::unknown()]))
|
||||
.ok()
|
||||
.map_or_else(Type::unknown, |bindings| bindings.return_type(db)),
|
||||
@@ -3439,7 +3439,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
)
|
||||
.place
|
||||
{
|
||||
Place::Defined(keys_method, _, Definedness::AlwaysDefined) => keys_method
|
||||
Place::Defined(keys_method, _, Definedness::AlwaysDefined, _) => keys_method
|
||||
.try_call(self.db, &CallArguments::none())
|
||||
.ok()
|
||||
.and_then(|bindings| {
|
||||
@@ -3485,10 +3485,14 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
)
|
||||
.place
|
||||
{
|
||||
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)),
|
||||
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)
|
||||
})
|
||||
}
|
||||
_ => Type::unknown(),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -46,8 +46,8 @@ use crate::types::{
|
||||
use crate::{
|
||||
Db, FxIndexMap, FxIndexSet, FxOrderSet, Program,
|
||||
place::{
|
||||
Definedness, LookupError, LookupResult, Place, PlaceAndQualifiers, known_module_symbol,
|
||||
place_from_bindings, place_from_declarations,
|
||||
Definedness, LookupError, LookupResult, Place, PlaceAndQualifiers, Widening,
|
||||
known_module_symbol, place_from_bindings, place_from_declarations,
|
||||
},
|
||||
semantic_index::{
|
||||
attribute_assignments,
|
||||
@@ -1179,7 +1179,7 @@ impl<'db> ClassType<'db> {
|
||||
)
|
||||
.place;
|
||||
|
||||
if let Place::Defined(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
|
||||
@@ -1242,7 +1242,7 @@ impl<'db> ClassType<'db> {
|
||||
// If the class defines an `__init__` method, then we synthesize a callable type with the
|
||||
// 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::Defined(ty, _, _) =
|
||||
let synthesized_dunder_init_callable = if let Place::Defined(ty, _, _, _) =
|
||||
dunder_init_function_symbol
|
||||
{
|
||||
let signature = match ty {
|
||||
@@ -1317,7 +1317,7 @@ impl<'db> ClassType<'db> {
|
||||
)
|
||||
.place;
|
||||
|
||||
if let Place::Defined(Type::FunctionLiteral(mut new_function), _, _) =
|
||||
if let Place::Defined(Type::FunctionLiteral(mut new_function), _, _, _) =
|
||||
new_function_symbol
|
||||
{
|
||||
if let Some(class_generic_context) = class_generic_context {
|
||||
@@ -2243,7 +2243,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
|
||||
(
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(ty, _, _),
|
||||
place: Place::Defined(ty, _, _, _),
|
||||
qualifiers,
|
||||
},
|
||||
Some(dynamic_type),
|
||||
@@ -2360,7 +2360,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
// For enum classes, `nonmember(value)` creates a non-member attribute.
|
||||
// At runtime, the enum metaclass unwraps the value, so accessing the attribute
|
||||
// returns the inner value, not the `nonmember` wrapper.
|
||||
if let Some(ty) = member.inner.place.ignore_possibly_undefined() {
|
||||
if let Some(ty) = member.inner.place.unwidened_type() {
|
||||
if let Some(value_ty) = try_unwrap_nonmember_value(db, ty) {
|
||||
if is_enum_class_by_inheritance(db, self) {
|
||||
return Member::definitely_declared(value_ty);
|
||||
@@ -2462,7 +2462,8 @@ impl<'db> ClassLiteral<'db> {
|
||||
}
|
||||
|
||||
let dunder_set = field_ty.class_member(db, "__set__".into());
|
||||
if let Place::Defined(dunder_set, _, Definedness::AlwaysDefined) = 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
|
||||
@@ -3367,7 +3368,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
}
|
||||
ClassBase::Class(class) => {
|
||||
if let member @ PlaceAndQualifiers {
|
||||
place: Place::Defined(ty, origin, boundness),
|
||||
place: Place::Defined(ty, origin, boundness, _),
|
||||
qualifiers,
|
||||
} = class.own_instance_member(db, name).inner
|
||||
{
|
||||
@@ -3419,8 +3420,13 @@ impl<'db> ClassLiteral<'db> {
|
||||
Definedness::PossiblyUndefined
|
||||
};
|
||||
|
||||
Place::Defined(union.build(), TypeOrigin::Inferred, boundness)
|
||||
.with_qualifiers(union_qualifiers)
|
||||
Place::Defined(
|
||||
union.build(),
|
||||
TypeOrigin::Inferred,
|
||||
boundness,
|
||||
Widening::None,
|
||||
)
|
||||
.with_qualifiers(union_qualifiers)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3773,7 +3779,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
|
||||
match declared_and_qualifiers {
|
||||
PlaceAndQualifiers {
|
||||
place: mut declared @ Place::Defined(declared_ty, _, declaredness),
|
||||
place: mut declared @ Place::Defined(declared_ty, _, declaredness, _),
|
||||
qualifiers,
|
||||
} => {
|
||||
// For the purpose of finding instance attributes, ignore `ClassVar`
|
||||
@@ -3818,6 +3824,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
UnionType::from_elements(db, [declared_ty, implicit_ty]),
|
||||
TypeOrigin::Declared,
|
||||
declaredness,
|
||||
Widening::None,
|
||||
)
|
||||
.with_qualifiers(qualifiers),
|
||||
}
|
||||
@@ -3858,6 +3865,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
UnionType::from_elements(db, [declared_ty, implicit_ty]),
|
||||
TypeOrigin::Declared,
|
||||
declaredness,
|
||||
Widening::None,
|
||||
)
|
||||
.with_qualifiers(qualifiers),
|
||||
}
|
||||
@@ -5160,15 +5168,16 @@ impl KnownClass {
|
||||
) -> Result<ClassLiteral<'_>, KnownClassLookupError<'_>> {
|
||||
let symbol = known_module_symbol(db, self.canonical_module(db), self.name(db)).place;
|
||||
match symbol {
|
||||
Place::Defined(Type::ClassLiteral(class_literal), _, Definedness::AlwaysDefined) => {
|
||||
Place::Defined(Type::ClassLiteral(class_literal), _, Definedness::AlwaysDefined, _) => {
|
||||
Ok(class_literal)
|
||||
}
|
||||
Place::Defined(
|
||||
Type::ClassLiteral(class_literal),
|
||||
_,
|
||||
Definedness::PossiblyUndefined,
|
||||
_,
|
||||
) => Err(KnownClassLookupError::ClassPossiblyUnbound { class_literal }),
|
||||
Place::Defined(found_type, _, _) => {
|
||||
Place::Defined(found_type, _, _, _) => {
|
||||
Err(KnownClassLookupError::SymbolNotAClass { found_type })
|
||||
}
|
||||
Place::Undefined => Err(KnownClassLookupError::ClassNotFound),
|
||||
@@ -6047,7 +6056,7 @@ enum SlotsKind {
|
||||
|
||||
impl SlotsKind {
|
||||
fn from(db: &dyn Db, base: ClassLiteral) -> Self {
|
||||
let Place::Defined(slots_ty, _, bound) = base
|
||||
let Place::Defined(slots_ty, _, bound, _) = base
|
||||
.own_class_member(db, base.inherited_generic_context(db), None, "__slots__")
|
||||
.inner
|
||||
.place
|
||||
|
||||
@@ -3969,8 +3969,9 @@ pub(super) fn report_invalid_method_override<'db>(
|
||||
.place
|
||||
};
|
||||
|
||||
if let Place::Defined(Type::FunctionLiteral(subclass_function), _, _) = class_member(subclass)
|
||||
&& let Place::Defined(Type::FunctionLiteral(superclass_function), _, _) =
|
||||
if let Place::Defined(Type::FunctionLiteral(subclass_function), _, _, _) =
|
||||
class_member(subclass)
|
||||
&& let Place::Defined(Type::FunctionLiteral(superclass_function), _, _, _) =
|
||||
class_member(superclass)
|
||||
&& let Ok(superclass_function_kind) =
|
||||
MethodDecorator::try_from_fn_type(db, superclass_function)
|
||||
|
||||
@@ -859,7 +859,8 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
f.with_type(KnownClass::MethodWrapperType.to_class_literal(self.db))
|
||||
.write_str("method-wrapper")?;
|
||||
f.write_str(" '")?;
|
||||
if let Place::Defined(member_ty, _, _) = class_ty.member(self.db, member_name).place
|
||||
if let Place::Defined(member_ty, _, _, _) =
|
||||
class_ty.member(self.db, member_name).place
|
||||
{
|
||||
f.with_type(member_ty).write_str(member_name)?;
|
||||
} else {
|
||||
|
||||
@@ -76,7 +76,7 @@ pub(crate) fn enum_metadata<'db>(
|
||||
let ignore_place = place_from_bindings(db, ignore_bindings).place;
|
||||
|
||||
match ignore_place {
|
||||
Place::Defined(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_`.
|
||||
@@ -113,7 +113,7 @@ pub(crate) fn enum_metadata<'db>(
|
||||
Place::Undefined => {
|
||||
return None;
|
||||
}
|
||||
Place::Defined(ty, _, _) => {
|
||||
Place::Defined(ty, _, _, _) => {
|
||||
let special_case = match ty {
|
||||
Type::Callable(_) | Type::FunctionLiteral(_) => {
|
||||
// Some types are specifically disallowed for enum members.
|
||||
@@ -196,9 +196,9 @@ pub(crate) fn enum_metadata<'db>(
|
||||
.place;
|
||||
|
||||
match dunder_get {
|
||||
Place::Undefined | Place::Defined(Type::Dynamic(_), _, _) => ty,
|
||||
Place::Undefined | Place::Defined(Type::Dynamic(_), _, _, _) => ty,
|
||||
|
||||
Place::Defined(_, _, _) => {
|
||||
Place::Defined(_, _, _, _) => {
|
||||
// Descriptors are not considered members.
|
||||
return None;
|
||||
}
|
||||
@@ -233,7 +233,7 @@ pub(crate) fn enum_metadata<'db>(
|
||||
|
||||
match declared {
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(Type::Dynamic(DynamicType::Unknown), _, _),
|
||||
place: Place::Defined(Type::Dynamic(DynamicType::Unknown), _, _, _),
|
||||
qualifiers,
|
||||
} if qualifiers.contains(TypeQualifiers::FINAL) => {}
|
||||
PlaceAndQualifiers {
|
||||
@@ -243,7 +243,7 @@ pub(crate) fn enum_metadata<'db>(
|
||||
// Undeclared attributes are considered members
|
||||
}
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(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
|
||||
@@ -319,34 +319,6 @@ pub(crate) fn try_unwrap_nonmember_value<'db>(db: &'db dyn Db, ty: Type<'db>) ->
|
||||
.unwrap_or(Type::unknown()),
|
||||
)
|
||||
}
|
||||
Type::Union(union) => {
|
||||
// TODO: This is a hack. The proper fix is to avoid unioning Unknown from
|
||||
// declarations into Place when we have concrete bindings.
|
||||
//
|
||||
// For now, we filter out Unknown and expect exactly one nonmember type
|
||||
// to remain. If there are other non-Unknown types mixed in, we bail out.
|
||||
let mut non_unknown = union.elements(db).iter().filter(|elem| !elem.is_unknown());
|
||||
|
||||
let first = non_unknown.next()?;
|
||||
|
||||
// Ensure there's exactly one non-Unknown element.
|
||||
if non_unknown.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Type::NominalInstance(instance) = first {
|
||||
if instance.has_known_class(db, KnownClass::Nonmember) {
|
||||
return Some(
|
||||
first
|
||||
.member(db, "value")
|
||||
.place
|
||||
.ignore_possibly_undefined()
|
||||
.unwrap_or(Type::unknown()),
|
||||
);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||
.name
|
||||
.scoped_use_id(db, scope);
|
||||
|
||||
let Place::Defined(Type::FunctionLiteral(previous_type), _, Definedness::AlwaysDefined) =
|
||||
let Place::Defined(Type::FunctionLiteral(previous_type), _, Definedness::AlwaysDefined, _) =
|
||||
place_from_bindings(db, use_def.bindings_at_use(use_id)).place
|
||||
else {
|
||||
return None;
|
||||
|
||||
@@ -1093,12 +1093,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let mut public_functions = FxIndexSet::default();
|
||||
|
||||
for place in overloaded_function_places {
|
||||
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()),
|
||||
)
|
||||
.place
|
||||
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()),
|
||||
)
|
||||
.place
|
||||
{
|
||||
if function.file(self.db()) != self.file() {
|
||||
// If the function is not in this file, we don't need to check it.
|
||||
@@ -1660,7 +1664,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
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) =
|
||||
if let Place::Defined(ty, _, Definedness::AlwaysDefined, _) =
|
||||
value_type.member(db, attr).place
|
||||
{
|
||||
// TODO: also consider qualifiers on the attribute
|
||||
@@ -4806,7 +4810,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK,
|
||||
) {
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(attr_ty, _, _),
|
||||
place: Place::Defined(attr_ty, _, _, _),
|
||||
qualifiers: _,
|
||||
} => attr_ty.is_callable_type(),
|
||||
_ => false,
|
||||
@@ -4880,7 +4884,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
false
|
||||
}
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(meta_attr_ty, _, meta_attr_boundness),
|
||||
place: Place::Defined(meta_attr_ty, _, meta_attr_boundness, _),
|
||||
qualifiers,
|
||||
} => {
|
||||
if invalid_assignment_to_final(self, qualifiers) {
|
||||
@@ -4888,7 +4892,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
|
||||
let assignable_to_meta_attr =
|
||||
if let Place::Defined(meta_dunder_set, _, _) =
|
||||
if let Place::Defined(meta_dunder_set, _, _, _) =
|
||||
meta_attr_ty.class_member(db, "__set__".into()).place
|
||||
{
|
||||
// TODO: We could use the annotated parameter type of `__set__` as
|
||||
@@ -4931,7 +4935,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
{
|
||||
let (assignable, boundness) = if let PlaceAndQualifiers {
|
||||
place:
|
||||
Place::Defined(instance_attr_ty, _, instance_attr_boundness),
|
||||
Place::Defined(
|
||||
instance_attr_ty,
|
||||
_,
|
||||
instance_attr_boundness,
|
||||
_,
|
||||
),
|
||||
qualifiers,
|
||||
} =
|
||||
object_ty.instance_member(db, attribute)
|
||||
@@ -4975,7 +4984,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
} => {
|
||||
if let PlaceAndQualifiers {
|
||||
place:
|
||||
Place::Defined(instance_attr_ty, _, instance_attr_boundness),
|
||||
Place::Defined(instance_attr_ty, _, instance_attr_boundness, _),
|
||||
qualifiers,
|
||||
} = object_ty.instance_member(db, attribute)
|
||||
{
|
||||
@@ -5021,7 +5030,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..) => {
|
||||
match object_ty.class_member(db, attribute.into()) {
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(meta_attr_ty, _, meta_attr_boundness),
|
||||
place: Place::Defined(meta_attr_ty, _, meta_attr_boundness, _),
|
||||
qualifiers,
|
||||
} => {
|
||||
// We may have to perform multi-inference if the meta attribute is possibly unbound.
|
||||
@@ -5032,40 +5041,41 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
return false;
|
||||
}
|
||||
|
||||
let assignable_to_meta_attr = if let Place::Defined(meta_dunder_set, _, _) =
|
||||
meta_attr_ty.class_member(db, "__set__".into()).place
|
||||
{
|
||||
// TODO: We could use the annotated parameter type of `__set__` as
|
||||
// type context here.
|
||||
let dunder_set_result = meta_dunder_set.try_call(
|
||||
db,
|
||||
&CallArguments::positional([meta_attr_ty, object_ty, value_ty]),
|
||||
);
|
||||
let assignable_to_meta_attr =
|
||||
if let Place::Defined(meta_dunder_set, _, _, _) =
|
||||
meta_attr_ty.class_member(db, "__set__".into()).place
|
||||
{
|
||||
// TODO: We could use the annotated parameter type of `__set__` as
|
||||
// type context here.
|
||||
let dunder_set_result = meta_dunder_set.try_call(
|
||||
db,
|
||||
&CallArguments::positional([meta_attr_ty, object_ty, value_ty]),
|
||||
);
|
||||
|
||||
if emit_diagnostics {
|
||||
if let Err(dunder_set_failure) = dunder_set_result.as_ref() {
|
||||
report_bad_dunder_set_call(
|
||||
&self.context,
|
||||
dunder_set_failure,
|
||||
attribute,
|
||||
object_ty,
|
||||
target,
|
||||
);
|
||||
if emit_diagnostics {
|
||||
if let Err(dunder_set_failure) = dunder_set_result.as_ref() {
|
||||
report_bad_dunder_set_call(
|
||||
&self.context,
|
||||
dunder_set_failure,
|
||||
attribute,
|
||||
object_ty,
|
||||
target,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dunder_set_result.is_ok()
|
||||
} else {
|
||||
let value_ty =
|
||||
infer_value_ty(self, TypeContext::new(Some(meta_attr_ty)));
|
||||
ensure_assignable_to(self, value_ty, meta_attr_ty)
|
||||
};
|
||||
dunder_set_result.is_ok()
|
||||
} else {
|
||||
let value_ty =
|
||||
infer_value_ty(self, TypeContext::new(Some(meta_attr_ty)));
|
||||
ensure_assignable_to(self, value_ty, meta_attr_ty)
|
||||
};
|
||||
|
||||
let assignable_to_class_attr = if meta_attr_boundness
|
||||
== Definedness::PossiblyUndefined
|
||||
{
|
||||
let (assignable, boundness) =
|
||||
if let Place::Defined(class_attr_ty, _, class_attr_boundness) =
|
||||
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")
|
||||
@@ -5102,7 +5112,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
..
|
||||
} => {
|
||||
if let PlaceAndQualifiers {
|
||||
place: Place::Defined(class_attr_ty, _, class_attr_boundness),
|
||||
place: Place::Defined(class_attr_ty, _, class_attr_boundness, _),
|
||||
qualifiers,
|
||||
} = object_ty
|
||||
.find_name_in_mro(db, attribute)
|
||||
@@ -5176,7 +5186,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
} else {
|
||||
module.static_member(db, attribute)
|
||||
};
|
||||
if let Place::Defined(attr_ty, _, _) = sym.place {
|
||||
if let Place::Defined(attr_ty, _, _, _) = sym.place {
|
||||
let value_ty = infer_value_ty(self, TypeContext::new(Some(attr_ty)));
|
||||
|
||||
let assignable = value_ty.is_assignable_to(db, attr_ty);
|
||||
@@ -6777,7 +6787,7 @@ 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::Defined(ty, _, boundness),
|
||||
place: Place::Defined(ty, _, boundness, _),
|
||||
qualifiers,
|
||||
} = module_ty.member(self.db(), name)
|
||||
{
|
||||
@@ -9296,7 +9306,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
|
||||
let ty =
|
||||
resolved_after_fallback.unwrap_with_diagnostic(|lookup_error| match lookup_error {
|
||||
resolved_after_fallback.unwrap_with_diagnostic(db, |lookup_error| match lookup_error {
|
||||
LookupError::Undefined(qualifiers) => {
|
||||
self.report_unresolved_reference(name_node);
|
||||
TypeAndQualifiers::new(Type::unknown(), TypeOrigin::Inferred, qualifiers)
|
||||
@@ -9418,7 +9428,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
let (parent_place, _use_id) = self.infer_local_place_load(parent_expr, expr_ref);
|
||||
if let Place::Defined(_, _, _) = parent_place {
|
||||
if let Place::Defined(_, _, _, _) = parent_place {
|
||||
return Place::Undefined.into();
|
||||
}
|
||||
}
|
||||
@@ -9561,7 +9571,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
});
|
||||
// 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::Defined(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, Definedness::AlwaysDefined);
|
||||
@@ -9815,7 +9826,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
ast::ExprRef::Attribute(attribute),
|
||||
);
|
||||
constraint_keys.extend(keys);
|
||||
if let Place::Defined(ty, _, Definedness::AlwaysDefined) = resolved.place {
|
||||
if let Place::Defined(ty, _, Definedness::AlwaysDefined, _) = resolved.place {
|
||||
assigned_type = Some(ty);
|
||||
}
|
||||
}
|
||||
@@ -9836,7 +9847,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
});
|
||||
|
||||
let attr_name = &attr.id;
|
||||
let resolved_type = fallback_place.unwrap_with_diagnostic(|lookup_err| match lookup_err {
|
||||
let resolved_type = fallback_place.unwrap_with_diagnostic(db, |lookup_err| match lookup_err {
|
||||
LookupError::Undefined(_) => {
|
||||
let fallback = || {
|
||||
TypeAndQualifiers::new(
|
||||
@@ -11439,7 +11450,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::Defined(contains_dunder, _, Definedness::AlwaysDefined) => {
|
||||
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]))
|
||||
@@ -11639,7 +11650,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
ast::ExprRef::Subscript(subscript),
|
||||
);
|
||||
constraint_keys.extend(keys);
|
||||
if let Place::Defined(ty, _, Definedness::AlwaysDefined) = 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());
|
||||
@@ -12671,7 +12682,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
match dunder_class_getitem_method {
|
||||
Place::Undefined => {}
|
||||
Place::Defined(ty, _, boundness) => {
|
||||
Place::Defined(ty, _, boundness, _) => {
|
||||
if boundness == Definedness::PossiblyUndefined {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, value_node)
|
||||
|
||||
@@ -324,7 +324,7 @@ 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::Defined(ty, _, _) =
|
||||
let Place::Defined(ty, _, _, _) =
|
||||
imported_symbol(db, file, symbol_name, None).place
|
||||
else {
|
||||
continue;
|
||||
@@ -493,7 +493,7 @@ impl<'db> AllMembers<'db> {
|
||||
Some(CodeGeneratorKind::TypedDict) => {}
|
||||
Some(CodeGeneratorKind::DataclassLike(_)) => {
|
||||
for attr in SYNTHETIC_DATACLASS_ATTRIBUTES {
|
||||
if let Place::Defined(synthetic_member, _, _) = ty.member(db, attr).place {
|
||||
if let Place::Defined(synthetic_member, _, _, _) = ty.member(db, attr).place {
|
||||
self.members.insert(Member {
|
||||
name: Name::from(*attr),
|
||||
ty: synthetic_member,
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(super) fn class_member<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str
|
||||
}
|
||||
|
||||
if let PlaceAndQualifiers {
|
||||
place: Place::Defined(ty, _, _),
|
||||
place: Place::Defined(ty, _, _, _),
|
||||
qualifiers,
|
||||
} = place_and_quals
|
||||
{
|
||||
@@ -82,8 +82,9 @@ pub(super) fn class_member<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str
|
||||
Member {
|
||||
inner: match inferred {
|
||||
Place::Undefined => Place::Undefined.with_qualifiers(qualifiers),
|
||||
Place::Defined(_, origin, boundness) => {
|
||||
Place::Defined(ty, origin, boundness).with_qualifiers(qualifiers)
|
||||
Place::Defined(_, origin, boundness, widening) => {
|
||||
Place::Defined(ty, origin, boundness, widening)
|
||||
.with_qualifiers(qualifiers)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ fn check_class_declaration<'db>(
|
||||
first_reachable_definition,
|
||||
} = member;
|
||||
|
||||
let Place::Defined(type_on_subclass_instance, _, _) =
|
||||
let Place::Defined(type_on_subclass_instance, _, _, _) =
|
||||
Type::instance(db, class).member(db, &member.name).place
|
||||
else {
|
||||
return;
|
||||
@@ -190,7 +190,7 @@ fn check_class_declaration<'db>(
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
let Place::Defined(superclass_type, _, _) = Type::instance(db, superclass)
|
||||
let Place::Defined(superclass_type, _, _, _) = Type::instance(db, superclass)
|
||||
.member(db, &member.name)
|
||||
.place
|
||||
else {
|
||||
|
||||
@@ -738,7 +738,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
let attribute_type = if self.name == "__call__" {
|
||||
other
|
||||
} else {
|
||||
let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) = other
|
||||
let Place::Defined(attribute_type, _, Definedness::AlwaysDefined, _) = other
|
||||
.invoke_descriptor_protocol(
|
||||
db,
|
||||
self.name,
|
||||
@@ -783,10 +783,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::Defined(_, _, Definedness::AlwaysDefined)
|
||||
Place::Defined(_, _, Definedness::AlwaysDefined, _)
|
||||
)),
|
||||
ProtocolMemberKind::Other(member_type) => {
|
||||
let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) =
|
||||
let Place::Defined(attribute_type, _, Definedness::AlwaysDefined, _) =
|
||||
other.member(db, self.name).place
|
||||
else {
|
||||
return ConstraintSet::from(false);
|
||||
|
||||
Reference in New Issue
Block a user