-
Notifications
You must be signed in to change notification settings - Fork 88
Implement let mutable
#3964
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
base: main
Are you sure you want to change the base?
Implement let mutable
#3964
Conversation
Parser Change ChecklistThis PR modifies the parser. Please check that the following tests are updated:
This test should have examples of every new bit of syntax you are adding. Feel free to just check the box if your PR does not actually change the syntax (because it is refactoring the parser, say). |
I've been leaving myself comments in the diff (all start with |
I think there are some tricky questions to be asked and answered wrt modes - let's discuss in other channels. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a quick pass over some of the places where you had questions - we'll chat more tomorrow.
typing/env.ml
Outdated
@@ -3298,6 +3299,9 @@ let walk_locks ~errors ~loc ~env ~item ~lid mode ty locks = | |||
|
|||
let lookup_ident_value ~errors ~use ~loc name env = | |||
match IdTbl.find_name_and_locks wrap_value ~mark:use name env.values with | |||
| Ok (_, locks, Val_bound {vda_description={val_kind=Val_mut}}) | |||
when List.exists (function Closure_lock _ | Escape_lock _ -> true | _ -> false) locks -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a general rule, it's good to avoid "catch-all" patterns and instead just list out all the cases. If we use a catch-all pattern, when someone adds a new lock in the future the compiler won't force them to consider how that lock should be treated here - it'll just silently get handled by the catch-all case. On the other hand, if we list out all the locks now, when someone adds a new lock in the future they'll get an incomplete pattern match warning and be forced to think through what to do.
(Once you list all the locks, this when guard will probably be big enough that it makes sense to pull out into its own function)
@@ -3298,6 +3299,9 @@ let walk_locks ~errors ~loc ~env ~item ~lid mode ty locks = | |||
|
|||
let lookup_ident_value ~errors ~use ~loc name env = | |||
match IdTbl.find_name_and_locks wrap_value ~mark:use name env.values with | |||
| Ok (_, locks, Val_bound {vda_description={val_kind=Val_mut}}) | |||
when List.exists (function Closure_lock _ | Escape_lock _ -> true | _ -> false) locks -> | |||
may_lookup_error errors loc env (Mutable_value_used_in_closure name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're probably going to want an error that reflects the context argument to the relevant lock, and then it would be good to write test cases that hit each one.
typing/env.mli
Outdated
@@ -257,6 +257,7 @@ type lookup_error = | |||
| Non_value_used_in_object of Longident.t * type_expr * Jkind.Violation.t | |||
| No_unboxed_version of Longident.t | |||
| Error_from_persistent_env of Persistent_env.error | |||
| Mutable_value_used_in_closure of string (* jra: Maybe rename this error/add other errors? *) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed - I think we'll want to generalize this now that we have more complicated locks - can discuss tomorrow.
typing/env.ml
Outdated
lookup_error loc env (Not_an_instance_variable name) | ||
Instance_variable (path, mut, cl_num, Subst.Lazy.force_type_expr desc.val_type) | ||
| Val_mut, Pident id -> | ||
let rec mode_of_locks mode = function (* jra: surely this is incorrect *) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an ongoing discussion with @riaqn about this, but my guess is we want to do something like the inverse of the walk_locks
function above. Note how that function is used by type_ident
in Typecore
: basically, it tells you how you need to change the mode of something to push it down through locks from its declaration site to a use site. Here, we're doing this in the other direction - we want to know what mode we should require something to have so that it is safe to push it up through the locks from the place where the value is to the place where the mutable variable we're sticking it in is declared.
typing/typecore.ml
Outdated
@@ -1151,6 +1157,8 @@ type pattern_variable = | |||
pv_id: Ident.t; | |||
pv_uid: Uid.t; | |||
pv_mode: Value.l; | |||
(* jra: I don't fully understand the difference between mutable_flag and mutability *) | |||
pv_mutable: mutable_flag; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite certain what you're referring to by "mutability" here, but I suspect there isn't a difference - we can discuss.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK - I've now read everything except the modes bits we're going to discuss with @riaqn.
let foo4_2 y = (* Can't sneak local out of non-local for loop body region *) | ||
let mutable x = [] in | ||
let build_loop () = | ||
for i = 1 to y do local_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the original implementation of the local mode and stack allocation, local_
was used in several different ways with its meaning determined by context. At the beginning or end of a region it meant to eliminate that region and use its parent. We now do that by writing exclave_
at the beginning of the region instead. So I think this one (and probably more in this file, which I will try to point out as I go) should be exclave_
instead. (It's actually still the case that local_
works for this purpose, but we're going to change that soon, and should make the tests forwards compatible).
Uses of local_
to check force the expression to have the local
mode still work that way, like the one on the line below this, so it's fine to leave that one.
val y_12 : t_12 = Foo_12 42 | ||
|}] | ||
|
||
(* Test 13: modes? *) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just leaving a note to remind us to revisit this after we chat with Zesen.
;; | ||
[%%expect{| | ||
val r_15 : float = 0.0009765625 | ||
|}] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the correct answer? Probably a comment documenting the expected result or a test whose result is more easy to calculate intuitively would be helpful.
| Ok sort -> sort | ||
| Error _ -> | ||
(* unreachable since [x <- e] was already type-checked, | ||
so [e] is representable *) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this isn't quite right. We're in the middle of checking x <- e
, here. It's unreachable because we checked ty
is representable when we created the binding for the mutable variable.
Which, actually, gives me an idea: could we just remember the sort of this variable in the environment rather than recomputing it here?
Most likely not done yet, but should be good for some code review