refactor
Collect multiple `AttributeAssignmentResult` errors into `AttributeAssignmentResults`.
This commit is contained in:
@@ -2,7 +2,6 @@ use infer::nearest_enclosing_class;
|
||||
use itertools::Either;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
|
||||
use std::ops::BitAnd;
|
||||
use std::slice::Iter;
|
||||
|
||||
use bitflags::bitflags;
|
||||
@@ -176,58 +175,72 @@ impl AttributeKind {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct AttributeAssignmentResults<'db>(FxOrderSet<AttributeAssignmentResult<'db>>);
|
||||
|
||||
impl<'db> IntoIterator for AttributeAssignmentResults<'db> {
|
||||
type Item = AttributeAssignmentResult<'db>;
|
||||
type IntoIter = ordermap::set::IntoIter<AttributeAssignmentResult<'db>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> AttributeAssignmentResults<'db> {
|
||||
fn empty() -> Self {
|
||||
Self(FxOrderSet::default())
|
||||
}
|
||||
|
||||
pub(crate) fn is_not_err(&self) -> bool {
|
||||
self.0.iter().all(AttributeAssignmentResult::is_not_err)
|
||||
}
|
||||
|
||||
pub(crate) fn is_possibly_unbound(&self) -> bool {
|
||||
self.0
|
||||
.iter()
|
||||
.any(AttributeAssignmentResult::is_possibly_unbound)
|
||||
}
|
||||
|
||||
fn insert(&mut self, result: AttributeAssignmentResult<'db>) {
|
||||
self.0.insert(result);
|
||||
}
|
||||
|
||||
fn and(mut self, result: AttributeAssignmentResult<'db>) -> Self {
|
||||
if result.is_err() {
|
||||
self.0
|
||||
.retain(|result| !matches!(result, AttributeAssignmentResult::Ok));
|
||||
}
|
||||
self.insert(result);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum AttributeAssignmentResult<'db> {
|
||||
Ok,
|
||||
PossiblyUnbound,
|
||||
TypeMismatch(Type<'db>),
|
||||
TypeMismatchAndPossiblyUnbound(Type<'db>),
|
||||
TwoTypeMismatch(Type<'db>, Type<'db>),
|
||||
TwoTypeMismatchAndPossiblyUnbound(Type<'db>, Type<'db>),
|
||||
CannotAssign,
|
||||
CannotAssignToClassVar,
|
||||
CannotAssignToInstanceAttr,
|
||||
ReadOnlyProperty,
|
||||
FailToSet,
|
||||
FailToSetAndPossiblyUnbound,
|
||||
FailToSetAndTypeMismatch(Type<'db>),
|
||||
FailToSetAndTypeMismatchAndPossiblyUnbound(Type<'db>),
|
||||
FailToSetAttr,
|
||||
SetAttrReturnsNeverOrNoReturn,
|
||||
Unresolved,
|
||||
}
|
||||
|
||||
impl BitAnd<Boundness> for AttributeAssignmentResult<'_> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Boundness) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Self::Ok, Boundness::PossiblyUnbound) => Self::PossiblyUnbound,
|
||||
(Self::TypeMismatch(ty), Boundness::PossiblyUnbound) => {
|
||||
Self::TypeMismatchAndPossiblyUnbound(ty)
|
||||
}
|
||||
(Self::FailToSet, Boundness::PossiblyUnbound) => Self::FailToSetAndPossiblyUnbound,
|
||||
(Self::FailToSetAndTypeMismatch(ty), Boundness::PossiblyUnbound) => {
|
||||
Self::FailToSetAndTypeMismatchAndPossiblyUnbound(ty)
|
||||
}
|
||||
(other, _) => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AttributeAssignmentResult<'_> {
|
||||
pub(crate) const fn is_not_err(&self) -> bool {
|
||||
matches!(self, Self::Ok | Self::PossiblyUnbound)
|
||||
}
|
||||
|
||||
pub(crate) const fn is_err(&self) -> bool {
|
||||
!self.is_not_err()
|
||||
}
|
||||
|
||||
pub(crate) const fn is_possibly_unbound(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::PossiblyUnbound
|
||||
| Self::TypeMismatchAndPossiblyUnbound(_)
|
||||
| Self::TwoTypeMismatchAndPossiblyUnbound(_, _)
|
||||
| Self::FailToSetAndPossiblyUnbound
|
||||
| Self::FailToSetAndTypeMismatchAndPossiblyUnbound(_)
|
||||
)
|
||||
matches!(self, Self::PossiblyUnbound)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4445,52 +4458,7 @@ impl<'db> Type<'db> {
|
||||
db: &'db dyn Db,
|
||||
attribute: &str,
|
||||
value_ty: Type<'db>,
|
||||
) -> AttributeAssignmentResult<'db> {
|
||||
let and = |l, r| match (l, r) {
|
||||
(AttributeAssignmentResult::Ok, AttributeAssignmentResult::Ok) => {
|
||||
AttributeAssignmentResult::Ok
|
||||
}
|
||||
(AttributeAssignmentResult::Ok, AttributeAssignmentResult::PossiblyUnbound) => {
|
||||
AttributeAssignmentResult::PossiblyUnbound
|
||||
}
|
||||
(AttributeAssignmentResult::Ok, AttributeAssignmentResult::TypeMismatch(ty)) => {
|
||||
AttributeAssignmentResult::TypeMismatch(ty)
|
||||
}
|
||||
(
|
||||
AttributeAssignmentResult::Ok,
|
||||
AttributeAssignmentResult::TypeMismatchAndPossiblyUnbound(ty),
|
||||
) => AttributeAssignmentResult::TypeMismatchAndPossiblyUnbound(ty),
|
||||
(AttributeAssignmentResult::FailToSet, AttributeAssignmentResult::Ok) => {
|
||||
AttributeAssignmentResult::FailToSet
|
||||
}
|
||||
(AttributeAssignmentResult::FailToSet, AttributeAssignmentResult::PossiblyUnbound) => {
|
||||
AttributeAssignmentResult::FailToSetAndPossiblyUnbound
|
||||
}
|
||||
(AttributeAssignmentResult::FailToSet, AttributeAssignmentResult::TypeMismatch(ty)) => {
|
||||
AttributeAssignmentResult::FailToSetAndTypeMismatch(ty)
|
||||
}
|
||||
(
|
||||
AttributeAssignmentResult::FailToSet,
|
||||
AttributeAssignmentResult::TypeMismatchAndPossiblyUnbound(ty),
|
||||
) => AttributeAssignmentResult::FailToSetAndTypeMismatchAndPossiblyUnbound(ty),
|
||||
(AttributeAssignmentResult::TypeMismatch(ty), AttributeAssignmentResult::Ok) => {
|
||||
AttributeAssignmentResult::TypeMismatch(ty)
|
||||
}
|
||||
(
|
||||
AttributeAssignmentResult::TypeMismatch(ty),
|
||||
AttributeAssignmentResult::PossiblyUnbound,
|
||||
) => AttributeAssignmentResult::TypeMismatchAndPossiblyUnbound(ty),
|
||||
(
|
||||
AttributeAssignmentResult::TypeMismatch(l),
|
||||
AttributeAssignmentResult::TypeMismatch(r),
|
||||
) => AttributeAssignmentResult::TwoTypeMismatch(l, r),
|
||||
(
|
||||
AttributeAssignmentResult::TypeMismatch(l),
|
||||
AttributeAssignmentResult::TypeMismatchAndPossiblyUnbound(r),
|
||||
) => AttributeAssignmentResult::TwoTypeMismatchAndPossiblyUnbound(l, r),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
) -> AttributeAssignmentResults<'db> {
|
||||
let ensure_assignable_to = |attr_ty| -> AttributeAssignmentResult {
|
||||
if value_ty.is_assignable_to(db, attr_ty) {
|
||||
AttributeAssignmentResult::Ok
|
||||
@@ -4499,45 +4467,45 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
};
|
||||
|
||||
let mut results = AttributeAssignmentResults::empty();
|
||||
|
||||
match self {
|
||||
Type::Union(union) => {
|
||||
let mut boundness = Boundness::Bound;
|
||||
if union.elements(db).iter().all(|elem| {
|
||||
let res = elem.validate_attribute_assignment(db, attribute, value_ty);
|
||||
if res.is_possibly_unbound() {
|
||||
boundness = Boundness::PossiblyUnbound;
|
||||
results.insert(AttributeAssignmentResult::PossiblyUnbound);
|
||||
}
|
||||
res.is_not_err()
|
||||
}) {
|
||||
AttributeAssignmentResult::Ok & boundness
|
||||
results.and(AttributeAssignmentResult::Ok)
|
||||
} else {
|
||||
AttributeAssignmentResult::TypeMismatch(self) & boundness
|
||||
results.and(AttributeAssignmentResult::TypeMismatch(self))
|
||||
}
|
||||
}
|
||||
|
||||
Type::Intersection(intersection) => {
|
||||
let mut boundness = Boundness::Bound;
|
||||
// TODO: Handle negative intersection elements
|
||||
if intersection.positive(db).iter().any(|elem| {
|
||||
let res = elem.validate_attribute_assignment(db, attribute, value_ty);
|
||||
if res.is_possibly_unbound() {
|
||||
boundness = Boundness::PossiblyUnbound;
|
||||
results.insert(AttributeAssignmentResult::PossiblyUnbound);
|
||||
}
|
||||
res.is_not_err()
|
||||
}) {
|
||||
AttributeAssignmentResult::Ok & boundness
|
||||
results.and(AttributeAssignmentResult::Ok)
|
||||
} else {
|
||||
AttributeAssignmentResult::TypeMismatch(self) & boundness
|
||||
results.and(AttributeAssignmentResult::TypeMismatch(self))
|
||||
}
|
||||
}
|
||||
|
||||
// Super instances do not allow attribute assignment
|
||||
Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Super) => {
|
||||
AttributeAssignmentResult::CannotAssign
|
||||
results.and(AttributeAssignmentResult::CannotAssign)
|
||||
}
|
||||
Type::BoundSuper(_) => AttributeAssignmentResult::CannotAssign,
|
||||
Type::BoundSuper(_) => results.and(AttributeAssignmentResult::CannotAssign),
|
||||
|
||||
Type::Dynamic(..) | Type::Never => AttributeAssignmentResult::Ok,
|
||||
Type::Dynamic(..) | Type::Never => results.and(AttributeAssignmentResult::Ok),
|
||||
|
||||
Type::NominalInstance(..)
|
||||
| Type::ProtocolInstance(_)
|
||||
@@ -4594,72 +4562,79 @@ impl<'db> Type<'db> {
|
||||
};
|
||||
|
||||
match setattr_dunder_call_result {
|
||||
Ok(result) => check_setattr_return_type(result),
|
||||
Err(CallDunderError::PossiblyUnbound(result)) => {
|
||||
check_setattr_return_type(*result)
|
||||
Ok(bindings) => results.and(check_setattr_return_type(bindings)),
|
||||
Err(CallDunderError::PossiblyUnbound(bindings)) => {
|
||||
results.and(check_setattr_return_type(*bindings))
|
||||
}
|
||||
Err(CallDunderError::CallError(..)) => {
|
||||
results.and(AttributeAssignmentResult::FailToSetAttr)
|
||||
}
|
||||
Err(CallDunderError::CallError(..)) => AttributeAssignmentResult::FailToSetAttr,
|
||||
Err(CallDunderError::MethodNotAvailable) => {
|
||||
match self.class_member(db, attribute.into()) {
|
||||
meta_attr @ PlaceAndQualifiers { .. } if meta_attr.is_class_var() => {
|
||||
AttributeAssignmentResult::CannotAssignToClassVar
|
||||
results.and(AttributeAssignmentResult::CannotAssignToClassVar)
|
||||
}
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Type(meta_attr_ty, meta_attr_boundness),
|
||||
qualifiers: _,
|
||||
} => {
|
||||
if is_read_only() {
|
||||
AttributeAssignmentResult::ReadOnlyProperty
|
||||
results.and(AttributeAssignmentResult::ReadOnlyProperty)
|
||||
} else {
|
||||
let assignable_to_meta_attr =
|
||||
if let Place::Type(meta_dunder_set, _) =
|
||||
meta_attr_ty.class_member(db, "__set__".into()).place
|
||||
// Check if it is assignable to the meta attribute type.
|
||||
if let Place::Type(meta_dunder_set, _) =
|
||||
meta_attr_ty.class_member(db, "__set__".into()).place
|
||||
{
|
||||
let successful_call = meta_dunder_set
|
||||
.try_call(
|
||||
db,
|
||||
&CallArgumentTypes::positional([
|
||||
meta_attr_ty,
|
||||
self,
|
||||
value_ty,
|
||||
]),
|
||||
)
|
||||
.is_ok();
|
||||
|
||||
if successful_call {
|
||||
results.insert(AttributeAssignmentResult::Ok);
|
||||
} else {
|
||||
results.insert(AttributeAssignmentResult::FailToSet);
|
||||
}
|
||||
} else {
|
||||
results.insert(ensure_assignable_to(meta_attr_ty));
|
||||
}
|
||||
|
||||
// Check if it is assignable to the instance attribute type.
|
||||
if meta_attr_boundness == Boundness::PossiblyUnbound {
|
||||
let (assignable, boundness) = if let Place::Type(
|
||||
instance_attr_ty,
|
||||
instance_attr_boundness,
|
||||
) =
|
||||
self.instance_member(db, attribute).place
|
||||
{
|
||||
let successful_call = meta_dunder_set
|
||||
.try_call(
|
||||
db,
|
||||
&CallArgumentTypes::positional([
|
||||
meta_attr_ty,
|
||||
self,
|
||||
value_ty,
|
||||
]),
|
||||
)
|
||||
.is_ok();
|
||||
|
||||
if successful_call {
|
||||
AttributeAssignmentResult::Ok
|
||||
} else {
|
||||
AttributeAssignmentResult::FailToSet
|
||||
}
|
||||
} else {
|
||||
ensure_assignable_to(meta_attr_ty)
|
||||
};
|
||||
|
||||
let assignable_to_instance_attribute =
|
||||
if meta_attr_boundness == Boundness::PossiblyUnbound {
|
||||
let (assignable, boundness) = if let Place::Type(
|
||||
instance_attr_ty,
|
||||
(
|
||||
ensure_assignable_to(instance_attr_ty),
|
||||
instance_attr_boundness,
|
||||
) =
|
||||
self.instance_member(db, attribute).place
|
||||
{
|
||||
(
|
||||
ensure_assignable_to(instance_attr_ty),
|
||||
instance_attr_boundness,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
AttributeAssignmentResult::Ok,
|
||||
Boundness::PossiblyUnbound,
|
||||
)
|
||||
};
|
||||
|
||||
assignable & boundness
|
||||
)
|
||||
} else {
|
||||
AttributeAssignmentResult::Ok
|
||||
(
|
||||
AttributeAssignmentResult::Ok,
|
||||
Boundness::PossiblyUnbound,
|
||||
)
|
||||
};
|
||||
|
||||
and(assignable_to_meta_attr, assignable_to_instance_attribute)
|
||||
results.insert(assignable);
|
||||
|
||||
if boundness == Boundness::PossiblyUnbound {
|
||||
results
|
||||
.insert(AttributeAssignmentResult::PossiblyUnbound);
|
||||
}
|
||||
} else {
|
||||
results.insert(AttributeAssignmentResult::Ok);
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4670,15 +4645,16 @@ impl<'db> Type<'db> {
|
||||
if let Place::Type(instance_attr_ty, instance_attr_boundness) =
|
||||
self.instance_member(db, attribute).place
|
||||
{
|
||||
if instance_attr_boundness == Boundness::PossiblyUnbound {
|
||||
results.insert(AttributeAssignmentResult::PossiblyUnbound);
|
||||
}
|
||||
if is_read_only() {
|
||||
AttributeAssignmentResult::ReadOnlyProperty
|
||||
& instance_attr_boundness
|
||||
results.and(AttributeAssignmentResult::ReadOnlyProperty)
|
||||
} else {
|
||||
ensure_assignable_to(instance_attr_ty)
|
||||
& instance_attr_boundness
|
||||
results.and(ensure_assignable_to(instance_attr_ty))
|
||||
}
|
||||
} else {
|
||||
AttributeAssignmentResult::Unresolved
|
||||
results.and(AttributeAssignmentResult::Unresolved)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4692,7 +4668,8 @@ impl<'db> Type<'db> {
|
||||
place: Place::Type(meta_attr_ty, meta_attr_boundness),
|
||||
qualifiers: _,
|
||||
} => {
|
||||
let assignable_to_meta_attr = if let Place::Type(meta_dunder_set, _) =
|
||||
// Check if it is assignable to the meta attribute type.
|
||||
if let Place::Type(meta_dunder_set, _) =
|
||||
meta_attr_ty.class_member(db, "__set__".into()).place
|
||||
{
|
||||
let successful_call = meta_dunder_set
|
||||
@@ -4703,33 +4680,37 @@ impl<'db> Type<'db> {
|
||||
.is_ok();
|
||||
|
||||
if successful_call {
|
||||
AttributeAssignmentResult::Ok
|
||||
results.insert(AttributeAssignmentResult::Ok);
|
||||
} else {
|
||||
AttributeAssignmentResult::FailToSet
|
||||
results.insert(AttributeAssignmentResult::FailToSet);
|
||||
}
|
||||
} else {
|
||||
ensure_assignable_to(meta_attr_ty)
|
||||
};
|
||||
results.insert(ensure_assignable_to(meta_attr_ty));
|
||||
}
|
||||
|
||||
let assignable_to_class_attr =
|
||||
if meta_attr_boundness == Boundness::PossiblyUnbound {
|
||||
let (assignable, boundness) =
|
||||
if let Place::Type(class_attr_ty, class_attr_boundness) = self
|
||||
.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 {
|
||||
(AttributeAssignmentResult::Ok, Boundness::PossiblyUnbound)
|
||||
};
|
||||
// Check if it is assignable to the class attribute type.
|
||||
if meta_attr_boundness == Boundness::PossiblyUnbound {
|
||||
let (assignable, boundness) =
|
||||
if let Place::Type(class_attr_ty, class_attr_boundness) = self
|
||||
.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 {
|
||||
(AttributeAssignmentResult::Ok, Boundness::PossiblyUnbound)
|
||||
};
|
||||
|
||||
assignable & boundness
|
||||
} else {
|
||||
AttributeAssignmentResult::Ok
|
||||
};
|
||||
if boundness == Boundness::PossiblyUnbound {
|
||||
results.insert(AttributeAssignmentResult::PossiblyUnbound);
|
||||
}
|
||||
|
||||
and(assignable_to_meta_attr, assignable_to_class_attr)
|
||||
results.insert(assignable);
|
||||
} else {
|
||||
results.insert(AttributeAssignmentResult::Ok);
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Unbound,
|
||||
@@ -4740,7 +4721,10 @@ impl<'db> Type<'db> {
|
||||
.expect("called on Type::ClassLiteral or Type::SubclassOf")
|
||||
.place
|
||||
{
|
||||
ensure_assignable_to(class_attr_ty) & class_attr_boundness
|
||||
if class_attr_boundness == Boundness::PossiblyUnbound {
|
||||
results.insert(AttributeAssignmentResult::PossiblyUnbound);
|
||||
}
|
||||
results.and(ensure_assignable_to(class_attr_ty))
|
||||
} else {
|
||||
let attribute_is_bound_on_instance =
|
||||
self.to_instance(db).is_some_and(|instance| {
|
||||
@@ -4749,9 +4733,9 @@ impl<'db> Type<'db> {
|
||||
|
||||
// Attribute is declared or bound on instance. Forbid access from the class object
|
||||
if attribute_is_bound_on_instance {
|
||||
AttributeAssignmentResult::CannotAssignToInstanceAttr
|
||||
results.and(AttributeAssignmentResult::CannotAssignToInstanceAttr)
|
||||
} else {
|
||||
AttributeAssignmentResult::Unresolved
|
||||
results.and(AttributeAssignmentResult::Unresolved)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4761,12 +4745,12 @@ impl<'db> Type<'db> {
|
||||
Type::ModuleLiteral(module) => {
|
||||
if let Place::Type(attr_ty, _) = module.static_member(db, attribute).place {
|
||||
if value_ty.is_assignable_to(db, attr_ty) {
|
||||
AttributeAssignmentResult::Ok
|
||||
results.and(AttributeAssignmentResult::Ok)
|
||||
} else {
|
||||
AttributeAssignmentResult::TypeMismatch(attr_ty)
|
||||
results.and(AttributeAssignmentResult::TypeMismatch(attr_ty))
|
||||
}
|
||||
} else {
|
||||
AttributeAssignmentResult::Unresolved
|
||||
results.and(AttributeAssignmentResult::Unresolved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3279,147 +3279,99 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
attribute: &str,
|
||||
value_ty: Type<'db>,
|
||||
) {
|
||||
match object_ty.validate_attribute_assignment(self.db(), attribute, value_ty) {
|
||||
AttributeAssignmentResult::Ok => {}
|
||||
AttributeAssignmentResult::PossiblyUnbound => {
|
||||
report_possibly_unbound_attribute(&self.context, target, attribute, object_ty);
|
||||
}
|
||||
ref res @ (AttributeAssignmentResult::TypeMismatch(target_ty)
|
||||
| AttributeAssignmentResult::TypeMismatchAndPossiblyUnbound(target_ty)) => {
|
||||
if res.is_possibly_unbound() {
|
||||
for result in object_ty.validate_attribute_assignment(self.db(), attribute, value_ty) {
|
||||
match result {
|
||||
AttributeAssignmentResult::Ok => {}
|
||||
AttributeAssignmentResult::PossiblyUnbound => {
|
||||
report_possibly_unbound_attribute(&self.context, target, attribute, object_ty);
|
||||
}
|
||||
// TODO: This is not a very helpful error message for union/intersection, as it does not include the underlying reason
|
||||
// why the assignment is invalid. This would be a good use case for sub-diagnostics.
|
||||
report_invalid_attribute_assignment(
|
||||
&self.context,
|
||||
target.into(),
|
||||
target_ty,
|
||||
value_ty,
|
||||
attribute,
|
||||
);
|
||||
}
|
||||
ref res @ (AttributeAssignmentResult::TwoTypeMismatch(l_target, r_target)
|
||||
| AttributeAssignmentResult::TwoTypeMismatchAndPossiblyUnbound(
|
||||
l_target,
|
||||
r_target,
|
||||
)) => {
|
||||
if res.is_possibly_unbound() {
|
||||
report_possibly_unbound_attribute(&self.context, target, attribute, object_ty);
|
||||
}
|
||||
report_invalid_attribute_assignment(
|
||||
&self.context,
|
||||
target.into(),
|
||||
l_target,
|
||||
value_ty,
|
||||
attribute,
|
||||
);
|
||||
report_invalid_attribute_assignment(
|
||||
&self.context,
|
||||
target.into(),
|
||||
r_target,
|
||||
value_ty,
|
||||
attribute,
|
||||
);
|
||||
}
|
||||
AttributeAssignmentResult::CannotAssign => {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot assign to attribute `{attribute}` on type `{}`",
|
||||
object_ty.display(self.db()),
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::CannotAssignToClassVar => {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ATTRIBUTE_ACCESS, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot assign to ClassVar `{attribute}` \
|
||||
from an instance of type `{ty}`",
|
||||
ty = object_ty.display(self.db()),
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::CannotAssignToInstanceAttr => {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ATTRIBUTE_ACCESS, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot assign to instance attribute \
|
||||
`{attribute}` from the class object `{ty}`",
|
||||
ty = object_ty.display(self.db()),
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::ReadOnlyProperty => {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Property `{attribute}` defined in `{ty}` is read-only",
|
||||
ty = object_ty.display(self.db()),
|
||||
));
|
||||
}
|
||||
}
|
||||
res @ (AttributeAssignmentResult::FailToSet
|
||||
| AttributeAssignmentResult::FailToSetAndPossiblyUnbound) => {
|
||||
if res.is_possibly_unbound() {
|
||||
report_possibly_unbound_attribute(&self.context, target, attribute, object_ty);
|
||||
}
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
// TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Invalid assignment to data descriptor attribute \
|
||||
`{attribute}` on type `{}` with custom `__set__` method",
|
||||
object_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
ref res @ (AttributeAssignmentResult::FailToSetAndTypeMismatch(target_ty)
|
||||
| AttributeAssignmentResult::FailToSetAndTypeMismatchAndPossiblyUnbound(
|
||||
target_ty,
|
||||
)) => {
|
||||
if res.is_possibly_unbound() {
|
||||
report_possibly_unbound_attribute(&self.context, target, attribute, object_ty);
|
||||
}
|
||||
report_invalid_attribute_assignment(
|
||||
&self.context,
|
||||
target.into(),
|
||||
target_ty,
|
||||
value_ty,
|
||||
attribute,
|
||||
);
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
// TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Invalid assignment to data descriptor attribute \
|
||||
`{attribute}` on type `{}` with custom `__set__` method",
|
||||
object_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::FailToSetAttr => {
|
||||
if let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Can not assign object of type `{}` to attribute \
|
||||
`{attribute}` on type `{}` with \
|
||||
custom `__setattr__` method.",
|
||||
value_ty.display(self.db()),
|
||||
object_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::SetAttrReturnsNeverOrNoReturn => {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot assign to attribute `{attribute}` on type `{}` \
|
||||
whose `__setattr__` method returns `Never`/`NoReturn`",
|
||||
object_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::Unresolved => {
|
||||
if let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Unresolved attribute `{}` on type `{}`.",
|
||||
AttributeAssignmentResult::TypeMismatch(target_ty) => {
|
||||
// TODO: This is not a very helpful error message for union/intersection, as it does not include the underlying reason
|
||||
// why the assignment is invalid. This would be a good use case for sub-diagnostics.
|
||||
report_invalid_attribute_assignment(
|
||||
&self.context,
|
||||
target.into(),
|
||||
target_ty,
|
||||
value_ty,
|
||||
attribute,
|
||||
object_ty.display(self.db())
|
||||
));
|
||||
);
|
||||
}
|
||||
AttributeAssignmentResult::CannotAssign => {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot assign to attribute `{attribute}` on type `{}`",
|
||||
object_ty.display(self.db()),
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::CannotAssignToClassVar => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ATTRIBUTE_ACCESS, target)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot assign to ClassVar `{attribute}` \
|
||||
from an instance of type `{ty}`",
|
||||
ty = object_ty.display(self.db()),
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::CannotAssignToInstanceAttr => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ATTRIBUTE_ACCESS, target)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot assign to instance attribute \
|
||||
`{attribute}` from the class object `{ty}`",
|
||||
ty = object_ty.display(self.db()),
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::ReadOnlyProperty => {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Property `{attribute}` defined in `{ty}` is read-only",
|
||||
ty = object_ty.display(self.db()),
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::FailToSet => {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
// TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Invalid assignment to data descriptor attribute \
|
||||
`{attribute}` on type `{}` with custom `__set__` method",
|
||||
object_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::FailToSetAttr => {
|
||||
if let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Can not assign object of type `{}` to attribute \
|
||||
`{attribute}` on type `{}` with \
|
||||
custom `__setattr__` method.",
|
||||
value_ty.display(self.db()),
|
||||
object_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::SetAttrReturnsNeverOrNoReturn => {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot assign to attribute `{attribute}` on type `{}` \
|
||||
whose `__setattr__` method returns `Never`/`NoReturn`",
|
||||
object_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
AttributeAssignmentResult::Unresolved => {
|
||||
if let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Unresolved attribute `{}` on type `{}`.",
|
||||
attribute,
|
||||
object_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user