Skip to content
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

Prevent reassigning and mutating lifted objects #3069

Closed
ekeren opened this issue Jun 26, 2023 · 7 comments · Fixed by #6258 or #6303
Closed

Prevent reassigning and mutating lifted objects #3069

ekeren opened this issue Jun 26, 2023 · 7 comments · Fixed by #6258 or #6303
Assignees
Labels
🐛 bug Something isn't working 🛠️ compiler Compiler

Comments

@ekeren
Copy link
Collaborator

ekeren commented Jun 26, 2023

Following #3064

It is now possible to capture reassignable/mutable objects
We need to prevent these captured object from being reassigned/modified in inflight

mergify bot pushed a commit that referenced this issue Jun 26, 2023
Prior to this change you where not able to capture any reassignable and mutable instances, this limitation was too restricting

Consider the following example where I wanted to have an `onUpdate` `preflight` api that sets a member  `onUpdateCallback`. The `onUpdateCallback` should be called in `inflight`. 

The only way to achieve this is by setting the `onUpdateCallback` on `init`.
After this change I should be able to make `onUpdateCallback` reassignable  

```ts (wing)
bring cloud;
class KeyValueStore {
  bucket: cloud.Bucket;
  var onUpdateCallback : inflight (str): void;
  init(store: cloud.Bucket) {
    this.bucket = store;
    this.onUpdateCallback = inflight (k: str) => {};
  }

  onUpdate(fn: inflight (str):void){
    this.onUpdateCallback = fn;
  }

  inflight get(key: str): Json {
    return this.bucket.getJson(key);
  }
  inflight set(key: str, value: Json): void {
    this.onUpdateCallback(key);
    this.bucket.putJson(key, value);
  }
}

let kv = new KeyValueStore(new cloud.Bucket());
let counter = new cloud.Counter();
kv.onUpdate(inflight (key: str): void => {
  counter.inc(1, key);
});

test "main" {
  kv.set("k", Json { 
    value: "v" 
  });
  kv.get("k");
  kv.get("k");
  kv.get("k2");
  assert(counter.peek("k") == 2);
  assert(counter.peek("k2") == 1);
}
``` 

## Leftovers 

* #3069

## Checklist

- [x] Title matches [Winglang's style guide](https://docs.winglang.io/contributors/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [ ] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Monada Contribution License](https://docs.winglang.io/terms-and-policies/contribution-license.html)*.
@Chriscbr Chriscbr added the 🐛 bug Something isn't working label Jun 27, 2023
@Chriscbr
Copy link
Contributor

Chriscbr commented Jun 27, 2023

example 1: Reassignment after a value has been lifted should be a compile time error

let var x = 1;

test "increment x" {
  x = x + 1;
  log("${x}");
}

example 2: Performing mutable operations after a value has been lifted should be a compile-time error

let arr = MutArray<num>[];

test "push item" {
  arr.push(123);
  log("${arr.length}");
}

example 3: These rules should also apply when operations are applied to members of classes

class Person {
  var name: str;
  pets: MutArray<str>;
  init() {
    this.name = "Bob";
    this.pets = MutArray<str>["whiskers", "fluffy"];
  }
  inflight changeName() {
    this.name = "Alice"; // error
  }
  inflight addPet(pet: str) {
    this.pets.push(pet); // error
  }
}

let person = new Person();

test "make changes" {
  person.pets.push("fido"); // error
  person.name = "Alice"; // error
}

@github-actions
Copy link

Hi,

This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days.
Feel free to re-open this issue when there's an update or relevant information to be added.
Thanks!

@github-actions github-actions bot added the Stale label Aug 27, 2023
@Chriscbr Chriscbr changed the title prevent reassigning and mutating lifted objects Prevent reassigning and mutating lifted objects Oct 24, 2023
@Chriscbr
Copy link
Contributor

Chriscbr commented Oct 24, 2023

Can we implement a mechanism in our type system so that when a mutable type defined in preflight is lifted into inflight, it is converted into its immutable variant? This would capture the idea that when a mutable collection is introduced to inflight, it gets "frozen" according whatever its last state in preflight was.

for example:

let x = MutArray<str>["hello", "world"];

inflight () => {
  let x0 = x.at(0); // "x" is type Array<str> in this context
  log(x0);
  x.push("!"); // error, since x is Array<str>
};

Inspired by #3174

@eladb
Copy link
Contributor

eladb commented Oct 24, 2023

Maybe, if an object has a lift(): T2 method, the compiler implicitly calls it and infers the types.

So now, we can implement shape shifting generically

@Chriscbr Chriscbr added this to the Winglang Stable Release milestone Mar 5, 2024
@yoav-steinberg
Copy link
Contributor

yoav-steinberg commented Apr 17, 2024

Note that inflight reassignment to a preflight variable part of this issue was handled in #3177, so we now have this compiler error:

class Person {
  pub var name: str;
  new() {
    this.name = "Bob";
  }
  inflight changeName() {
    this.name = "Alice"; // error: Variable cannot be reassigned from inflight
  }
}

let person = new Person();

test "make changes" {
  person.name = "Alice"; // error: Variable cannot be reassigned from inflight
}

@yoav-steinberg yoav-steinberg self-assigned this Apr 17, 2024
@mergify mergify bot closed this as completed in #6258 Apr 17, 2024
mergify bot pushed a commit that referenced this issue Apr 17, 2024
fixes #3069
The type checker will now modify the type of preflight expressions that are being used inflight to a non-mutable version of the same type: `MutArray` -> `Array`, `MutJson` -> `Json`...

Note that since we change the type of the expression we don't get descriptive error telling us that we're trying to mutate a lifted mutable object, instead we just get the same error we'd expect if the lifted object was non-mutable to begin with. This isn't ideal but it makes the fix simpler. Let me know what you think...

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [x] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
@monadabot
Copy link
Contributor

Congrats! 🚀 This was released in Wing 0.70.26.

mergify bot pushed a commit that referenced this issue Apr 24, 2024
…tempt (#6303)

Fixes #3069 (2nd attempt, instead of #6258)
The type checker will now modify the type of preflight expressions that are being used inflight to a non-mutable version of the same type: MutArray -> Array, MutJson -> Json...

Note that since we change the type of the expression we don't get descriptive error telling us that we're trying to mutate a lifted mutable object, instead we just get the same error we'd expect if the lifted object was non-mutable to begin with. This isn't ideal but it makes the fix simpler. Let me know what you think...

To get this working we needed to do the following changes to how we evaluate the phases of reference expressions:
* The phase of phase independent properties inflight will be inflight. This makes sure inflight calls like `preflight_array.copyMut()` will be we evaluated inflight so the side effect of turning the result to immutable won't happen. This is a major change to lifting because inflight calls like `preflight_array.at(0)` will also be executed inflight (no matter what args are passed to the call).
* There's a special exception to the above, struct fields: we need to keep them phase independent and not turn them to inflight when accessing them inflight. This is required so we can lift qualify methods called on struct field when those fields are preflight objects. This needs to be addressed in the future, because somehow struct fields being "phase independent" doesn't seem correct.
* Index access (`[i]`) phase calculation was wrong and didn't take into account the phase of the index. When the index phase is inflight the expression must be inflight too. When the index phase is independent and the instance is preflight we want to lift the entire expression and not only the instance so it can be correctly lift-qualified.
* All this means we don't need the (admittedly beautiful) logic of only turning phase independent method calls to inflight calls when they are passed some inflight state (instance or arg). Now we always treat them as inflight. This cleaned up some code.

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [x] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
@monadabot
Copy link
Contributor

Congrats! 🚀 This was released in Wing 0.72.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment