mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:35:58 +00:00
Handle maybe-unbound __iadd__
-like operators in augmented assignments (#14044)
## Summary One of the follow-ups from augmented assignment inference, now that `Type::Unbound` has been removed. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
099f077311
commit
487941ea66
2 changed files with 29 additions and 9 deletions
|
@ -85,7 +85,7 @@ f = Foo()
|
||||||
# that `Foo.__iadd__` may be unbound as additional context.
|
# that `Foo.__iadd__` may be unbound as additional context.
|
||||||
f += "Hello, world!"
|
f += "Hello, world!"
|
||||||
|
|
||||||
reveal_type(f) # revealed: int
|
reveal_type(f) # revealed: int | @Todo
|
||||||
```
|
```
|
||||||
|
|
||||||
## Partially bound with `__add__`
|
## Partially bound with `__add__`
|
||||||
|
@ -104,8 +104,7 @@ class Foo:
|
||||||
f = Foo()
|
f = Foo()
|
||||||
f += "Hello, world!"
|
f += "Hello, world!"
|
||||||
|
|
||||||
# TODO(charlie): This should be `int | str`, since `__iadd__` may be unbound.
|
reveal_type(f) # revealed: int | str
|
||||||
reveal_type(f) # revealed: int
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Partially bound target union
|
## Partially bound target union
|
||||||
|
|
|
@ -1550,14 +1550,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
// If the target defines, e.g., `__iadd__`, infer the augmented assignment as a call to that
|
// If the target defines, e.g., `__iadd__`, infer the augmented assignment as a call to that
|
||||||
// dunder.
|
// dunder.
|
||||||
if let Type::Instance(class) = target_type {
|
if let Type::Instance(class) = target_type {
|
||||||
if let Some(class_member) = class.class_member(self.db, op.in_place_dunder()).as_type()
|
if let Symbol::Type(class_member, boundness) =
|
||||||
|
class.class_member(self.db, op.in_place_dunder())
|
||||||
{
|
{
|
||||||
// TODO: Handle the case where boundness is `MayBeUnbound`: fall back
|
|
||||||
// to the binary-op behavior below and union the result with calling
|
|
||||||
// the possibly-unbound in-place dunder.
|
|
||||||
|
|
||||||
let call = class_member.call(self.db, &[target_type, value_type]);
|
let call = class_member.call(self.db, &[target_type, value_type]);
|
||||||
return match call.return_ty_result(
|
let augmented_return_ty = match call.return_ty_result(
|
||||||
self.db,
|
self.db,
|
||||||
AnyNodeRef::StmtAugAssign(assignment),
|
AnyNodeRef::StmtAugAssign(assignment),
|
||||||
&mut self.diagnostics,
|
&mut self.diagnostics,
|
||||||
|
@ -1576,6 +1573,30 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
e.return_ty()
|
e.return_ty()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return match boundness {
|
||||||
|
Boundness::Bound => augmented_return_ty,
|
||||||
|
Boundness::MayBeUnbound => {
|
||||||
|
let left_ty = target_type;
|
||||||
|
let right_ty = value_type;
|
||||||
|
|
||||||
|
let binary_return_ty = self.infer_binary_expression_type(left_ty, right_ty, *op)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
self.diagnostics.add(
|
||||||
|
assignment.into(),
|
||||||
|
"unsupported-operator",
|
||||||
|
format_args!(
|
||||||
|
"Operator `{op}=` is unsupported between objects of type `{}` and `{}`",
|
||||||
|
left_ty.display(self.db),
|
||||||
|
right_ty.display(self.db)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Type::Unknown
|
||||||
|
});
|
||||||
|
|
||||||
|
UnionType::from_elements(self.db, [augmented_return_ty, binary_return_ty])
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue