-
-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Borrows in generic code can violate uniqueness constraints #713
Comments
In theory something like this would work: when type-checking a method body, we set some sort of "borrowed" flag for type parameters whenever they're borrowed, either directly or by passing it to an argument of another method that borrows it. When we then assign a The problem with this approach is that it requires at least two passes over all method bodies: one to set the flag for direct borrows (e.g. through |
Here's another way this can be triggered: if you have some |
Another similar case: we allow pattern matching against e.g.
To prevent this from happening we have to disallow binding |
To see how many places there are where we explicitly use diff --git a/compiler/src/type_check/expressions.rs b/compiler/src/type_check/expressions.rs
index 6573bb59..31567c34 100644
--- a/compiler/src/type_check/expressions.rs
+++ b/compiler/src/type_check/expressions.rs
@@ -3069,6 +3069,16 @@ impl<'a> CheckMethodBody<'a> {
) -> TypeRef {
let expr = self.expression(&mut node.value, scope);
+ // TODO: remove
+ if expr.is_type_parameter(self.db()) {
+ self.state.diagnostics.warn(
+ DiagnosticId::InvalidType,
+ "ref with type parameter that might violate uni T",
+ self.file(),
+ node.location.clone(),
+ );
+ }
+
if !expr.allow_as_ref(self.db()) {
self.state.diagnostics.error(
DiagnosticId::InvalidType,
@@ -3112,6 +3122,16 @@ impl<'a> CheckMethodBody<'a> {
let expr = self.expression(&mut node.value, scope);
+ // TODO: remove
+ if expr.is_type_parameter(self.db()) {
+ self.state.diagnostics.warn(
+ DiagnosticId::InvalidType,
+ "mut with type parameter that might violate uni T",
+ self.file(),
+ node.location.clone(),
+ );
+ }
+
if !expr.allow_as_mut(self.db()) {
self.state.diagnostics.error(
DiagnosticId::InvalidType, Running the test suite then produces the following warnings:
I was expected a lot more warnings, so perhaps requiring the equivalent of A challenge here is when we deal with closures and captures. Take this code for example:
Here the closure captures |
Pony's approach to this is to introduce a variety of reference capabilities and ways to convert between them. However, the resulting complexity of this makes the language difficult to use, which is likely why it never really caught on. |
We could potentially remove the need for explicit Given a generic type
Closures here complicate things: since they may mutate the captured data we can't infer |
If we go down the path of inference, we can avoid using multiple passes using an approach like this: We maintain a map of
The mapping would be:
When processing The downside is that this won't work reliable when dealing with type parameters inside generic types, such as this:
Here the issue is that we don't pass The other approach is to process methods multiple times and record method dependencies. That is, when we encounter a call to a method we've yet to process, we store the caller as a dependency of the callee (e.g. |
I read through Functional Ownership through Fractional Uniqueness hoping it would perhaps provide some extra insight, but alas it felt like a paper with a lot of words but not a lot of actually useful content, though perhaps I just don't grok it. |
Also read through Flexible recovery of uniqueness and immutability, but it basically just describes the Pony model using different terms and some extra fluff. |
We'll need to fix this for #780 if we want to have any chance at allocating data on the stack, as doing so will introduce another scenario in which we may violate One issue with adding We can handle most cases by disallowing |
For inference, I think we can take a simpler approach:
The nice thing about this approach is that we should be able to cut down the amount of explicit |
Some additional observations:
These checks would need to be performed after type checking a method body, such that all types in the body are complete. What this doesn't guard against is aliasing inside the method body though, i.e. something like this:
This however might in fact be fine: based on observation 1, if The downside is that for each call we need to recursively check all argument and return types for the presence of a |
Please describe the bug
When using generic code, it's possible to borrow a generic type parameter that ends up being assigned a
uni T
value, which then allows the borrow to outlive the unique value and violate the uniqueness constraints.Please list the exact steps necessary to reproduce the bug
Consider this code:
Here
ex
ends up being of typeExample[uni Array[Int]]
, with theborrow
field storing aref Array[Int]
, violating the constraints. We can then exploit that usingex.value := Option.None
, giving us anOption[uni Array[Int]]
while theExample
value still stores a borrow to theuni Array[Int]
.I'm not sure yet what the solution here is. If we introduce something similar to the
mut
constraint for type parameters to allow unique values, then we basically end up not being able to use them anywhere or we won't be able to use borrows. Basically it ends up being a massive pain.Operating system
Fedora 40
Inko version
main
Rust version
1.77.2
The text was updated successfully, but these errors were encountered: