Skip to content

Commit

Permalink
feat: allow access to Self and its public methods from actor init…
Browse files Browse the repository at this point in the history
…ialisers (#4719)

This PR accomplishes following improvements:
- allow `actor Self { ... Self ... }` in the actor initialiser (similarly for `actor class`)
- ditto for accessing all public methods `Self.method` from the actor initialiser
- accessing these from under lambdas without making the respective methods undefined when using them

The latter should allow setting up timers: `addTimer(b, period, Self.method)` from actor initialiser.

Also fixes the hole in definedness analysis (#4731).

-------------
_NOTES_:
- calling `Self.method` from actor initialiser is prohibited by the capability checker
- accessing `Self.method` from actor initialiser is currently allowed when `method` is not defined yet, and results in a descriptor (pair)
- comparison (in terms of `shared`) of the internal methods with external methods is trapping in the interpreter, but should work in the deployed actor.

There is an idea inside to fix these problems.

----
TODO:
- adapt `renaming.ml`? — nope, I don't have business in IR
- [x] test `actor class` too
- [x] test clashing `Self` — there is already `self-shadow.mo`
- [x] `test/run/issue1464ok.mo` fails — fixed snafu
- [x] deal with `FIXME`s
- [x] `shared` method equality in the interpreters
- [x] `shared` call viability checking (in interpreters)
- [x] fix #4731
- [x] write issue about the mixed (pair/closure) `shared func` comparison problem — here: #4732
- [x] resolve the `Promise.is_fulfilled` problem — #4735
- [x] check the "For actors, this may be too permissive" comment in the code — irrelevant, removed!
- [ ] try out the "crazy idea" mentioned in the feedback
  • Loading branch information
ggreif authored Oct 17, 2024
1 parent d8e8726 commit 0a0eb38
Show file tree
Hide file tree
Showing 31 changed files with 240 additions and 109 deletions.
9 changes: 9 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Motoko compiler changelog

## 0.13.2 (2024-10-1X)

* motoko (`moc`)

* Made the `actor`'s _self_ identifier available in the toplevel block. This also allows using
functions that refer to _self_ from the initialiser (e.g. calls to `setTimer`) (#4720).

* bugfix: `actor <exp>` now correctly performs definedness tracking (#4731).

## 0.13.1 (2024-10-07)

* motoko (`moc`)
Expand Down
38 changes: 22 additions & 16 deletions src/ir_interpreter/interpret_ir.ml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ let find id env =
with Not_found ->
trap no_region "unbound identifier %s" id

let lookup_actor env at aid id =
match V.Env.find_opt aid !(env.actor_env) with
| None -> trap at "Unknown actor \"%s\"" aid
| Some actor_value ->
let fs = V.as_obj actor_value in
match V.Env.find_opt id fs with
| None -> trap at "Actor \"%s\" has no method \"%s\"" aid id
| Some field_value -> field_value

(* Tracing *)

let trace_depth = ref 0
Expand Down Expand Up @@ -311,6 +320,9 @@ and interpret_exp_mut env exp (k : V.value V.cont) =
interpret_exps env es [] (fun vs ->
match p, vs with
| CallPrim typs, [v1; v2] ->
let v1 = match v1 with
| V.(Tup [Blob aid; Text id]) -> lookup_actor env exp.at aid id
| _ -> v1 in
let call_conv, f = V.as_func v1 in
check_call_conv (List.hd es) call_conv;
check_call_conv_arg env exp v2 call_conv;
Expand All @@ -320,7 +332,7 @@ and interpret_exp_mut env exp (k : V.value V.cont) =
k (try Operator.unop op ot v1 with Invalid_argument s -> trap exp.at "%s" s)
| BinPrim (ot, op), [v1; v2] ->
k (try Operator.binop op ot v1 v2 with _ ->
trap exp.at "arithmetic overflow")
trap exp.at "arithmetic overflow")
| RelPrim (ot, op), [v1; v2] ->
k (Operator.relop op ot v1 v2)
| TupPrim, exps ->
Expand All @@ -335,16 +347,8 @@ and interpret_exp_mut env exp (k : V.value V.cont) =
let fs = V.as_obj v1 in
k (try find n fs with _ -> assert false)
| ActorDotPrim n, [v1] ->
let id = V.as_blob v1 in
begin match V.Env.find_opt id !(env.actor_env) with
(* not quite correct: On the platform, you can invoke and get a reject *)
| None -> trap exp.at "Unknown actor \"%s\"" id
| Some actor_value ->
let fs = V.as_obj actor_value in
match V.Env.find_opt n fs with
| None -> trap exp.at "Actor \"%s\" has no method \"%s\"" id n
| Some field_value -> k field_value
end
(* delay error handling to the point when the method gets applied *)
k V.(Tup [v1; Text n])
| ArrayPrim (mut, _), vs ->
let vs' =
match mut with
Expand Down Expand Up @@ -447,6 +451,9 @@ and interpret_exp_mut env exp (k : V.value V.cont) =
let e = V.Tup [V.Variant ("canister_reject", V.unit); v1] in
Scheduler.queue (fun () -> reject e)
| ICCallPrim, [v1; v2; kv; rv; cv] ->
let v1 = match v1 with
| V.(Tup [Blob aid; Text id]) -> lookup_actor env exp.at aid id
| _ -> v1 in
let call_conv, f = V.as_func v1 in
check_call_conv (List.hd es) call_conv;
check_call_conv_arg env exp v2 call_conv;
Expand All @@ -463,7 +470,7 @@ and interpret_exp_mut env exp (k : V.value V.cont) =
in
k (V.Obj ve)
| SelfRef _, [] ->
k (V.Blob env.self)
k (context env)
| SystemTimePrim, [] ->
k (V.Nat64 (Numerics.Nat64.of_int 42))
| SystemCyclesRefundedPrim, [] -> (* faking it *)
Expand Down Expand Up @@ -572,13 +579,13 @@ and interpret_exp_mut env exp (k : V.value V.cont) =

and interpret_actor env ds fs k =
let self = V.fresh_id () in
let env0 = {env with self = self} in
let self' = V.Blob self in
let ve = declare_decs ds V.Env.empty in
let env' = adjoin_vals env0 ve in
let env' = adjoin_vals { env with self } ve in
interpret_decs env' ds (fun _ ->
let obj = interpret_fields env' fs in
env.actor_env := V.Env.add self obj !(env.actor_env);
k (V.Blob self)
k self'
)

and interpret_lexp env lexp (k : (V.value ref) V.cont) =
Expand Down Expand Up @@ -676,7 +683,6 @@ and declare_pats pats ve : val_env =
let ve' = declare_pat pat in
declare_pats pats' (V.Env.adjoin ve ve')


and define_id env id v =
Lib.Promise.fulfill (find id env.vals) v

Expand Down
2 changes: 1 addition & 1 deletion src/lib/lib.mli
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ end

module Seq :
sig
val for_all : ('a -> bool) -> 'a Seq.t -> bool
val for_all : ('a -> bool) -> 'a Seq.t -> bool (* 4.14 *)
end

module Option :
Expand Down
9 changes: 5 additions & 4 deletions src/lowering/desugar.ml
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ and exp' at note = function
(breakE "!" (nullE()))
(* case ? v : *)
(varP v) (varE v) ty).it
| S.ObjBlockE (s, _t, dfs) ->
obj_block at s None dfs note.Note.typ
| S.ObjBlockE (s, (self_id_opt, _), dfs) ->
obj_block at s self_id_opt dfs note.Note.typ
| S.ObjE (bs, efs) ->
obj note.Note.typ efs bs
| S.TagE (c, e) -> (tagE c.it (exp e)).it
Expand Down Expand Up @@ -562,7 +562,8 @@ and build_actor at ts self_id es obj_typ =
[expD (assignE state (nullE()))]
in
let ds' = match self_id with
| Some n -> with_self n.it obj_typ ds
| Some n ->
with_self n.it obj_typ ds
| None -> ds in
let meta =
I.{ candid = candid;
Expand Down Expand Up @@ -1145,7 +1146,7 @@ let transform_unit_body (u : S.comp_unit_body) : Ir.comp_unit =
let actor_expression = build_actor u.at [] self_id fields u.note.S.note_typ in
begin match actor_expression with
| I.ActorE (ds, fs, u, t) ->
I.ActorU (None, ds, fs, u, t)
I.ActorU (None, ds, fs, u, t)
| _ -> assert false
end

Expand Down
8 changes: 7 additions & 1 deletion src/mo_def/arrange.ml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ module Make (Cfg : Config) = struct
| FromCandidE e -> "FromCandidE" $$ [exp e]
| TupE es -> "TupE" $$ exps es
| ProjE (e, i) -> "ProjE" $$ [exp e; Atom (string_of_int i)]
| ObjBlockE (s, t, dfs) -> "ObjBlockE" $$ [obj_sort s; match t with None -> Atom "_" | Some t -> typ t] @ List.map dec_field dfs
| ObjBlockE (s, nt, dfs) -> "ObjBlockE" $$ [obj_sort s;
match nt with
| None, None -> Atom "_"
| None, Some t -> typ t
| Some id, Some t -> id.it $$ [Atom ":"; typ t]
| Some id, None -> Atom id.it
] @ List.map dec_field dfs
| ObjE ([], efs) -> "ObjE" $$ List.map exp_field efs
| ObjE (bases, efs) -> "ObjE" $$ exps bases @ [Atom "with"] @ List.map exp_field efs
| DotE (e, x) -> "DotE" $$ [exp e; id x]
Expand Down
4 changes: 2 additions & 2 deletions src/mo_def/compUnit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ let obj_decs obj_sort at note id_opt fields =
match id_opt with
| None -> [
{ it = ExpD {
it = ObjBlockE ( { it = obj_sort; at; note = () }, None, fields);
it = ObjBlockE ( { it = obj_sort; at; note = () }, (None, None), fields);
at;
note };
at; note }]
| Some id -> [
{ it = LetD (
{ it = VarP id; at; note = note.note_typ },
{ it = ObjBlockE ({ it = obj_sort; at; note = () }, None, fields);
{ it = ObjBlockE ({ it = obj_sort; at; note = () }, (None, None), fields);
at; note; },
None);
at; note
Expand Down
2 changes: 1 addition & 1 deletion src/mo_def/syntax.ml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ and exp' =
| OptE of exp (* option injection *)
| DoOptE of exp (* option monad *)
| BangE of exp (* scoped option projection *)
| ObjBlockE of obj_sort * typ option * dec_field list (* object block *)
| ObjBlockE of obj_sort * (id option * typ option) * dec_field list (* object block *)
| ObjE of exp list * exp_field list (* record literal/extension *)
| TagE of id * exp (* variant *)
| DotE of exp * id (* object projection *)
Expand Down
92 changes: 51 additions & 41 deletions src/mo_frontend/definedness.ml
Original file line number Diff line number Diff line change
Expand Up @@ -84,55 +84,55 @@ let rec exp msgs e : f = match e.it with
(* Or anything that is occurring in a call (as this may call a closure): *)
| CallE (e1, ts, e2) -> eagerify (exps msgs [e1; e2])
(* And break, return, throw can be thought of as calling a continuation: *)
| BreakE (i, e) -> eagerify (exp msgs e)
| RetE e -> eagerify (exp msgs e)
| BreakE (_, e)
| RetE e
| ThrowE e -> eagerify (exp msgs e)
(* Uses are delayed by function expressions *)
| FuncE (_, sp, tp, p, t, _, e) ->
delayify ((exp msgs e /// pat msgs p) /// shared_pat msgs sp)
delayify ((exp msgs e /// pat msgs p) /// shared_pat msgs sp)
| ObjBlockE (s, (self_id_opt, _), dfs) ->
group msgs (add_self self_id_opt s (dec_fields msgs dfs))
(* The rest remaining cases just collect the uses of subexpressions: *)
| LitE _ | ActorUrlE _
| LitE _
| PrimE _ | ImportE _ -> M.empty
| UnE (_, uo, e) -> exp msgs e
| BinE (_, e1, bo, e2)-> exps msgs [e1; e2]
| RelE (_, e1, ro, e2)-> exps msgs [e1; e2]
| ShowE (_, e) -> exp msgs e
| ToCandidE es -> exps msgs es
| FromCandidE e -> exp msgs e
| TupE es -> exps msgs es
| ProjE (e, i) -> exp msgs e
| ObjBlockE (s, _, dfs) ->
(* For actors, this may be too permissive; to be revised when we work on actors again *)
group msgs (dec_fields msgs dfs)
| ObjE (bases, efs) -> exps msgs bases ++ exp_fields msgs efs
| DotE (e, i) -> exp msgs e
| AssignE (e1, e2) -> exps msgs [e1; e2]
| ArrayE (m, es) -> exps msgs es
| IdxE (e1, e2) -> exps msgs [e1; e2]
| TupE es
| ArrayE (_, es)
| ToCandidE es -> exps msgs es
| BlockE ds -> group msgs (decs msgs ds)
| NotE e -> exp msgs e
| AndE (e1, e2) -> exps msgs [e1; e2]
| OrE (e1, e2) -> exps msgs [e1; e2]
| ImpliesE (e1, e2) -> exps msgs [e1; e2]
| OldE e -> exp msgs e
| IfE (e1, e2, e3) -> exps msgs [e1; e2; e3]
| SwitchE (e, cs) -> exp msgs e ++ cases msgs cs
| SwitchE (e, cs)
| TryE (e, cs, None) -> exp msgs e ++ cases msgs cs
| TryE (e, cs, Some f)-> exps msgs [e; f] ++ cases msgs cs
| WhileE (e1, e2) -> exps msgs [e1; e2]
| LoopE (e1, None) -> exp msgs e1
| LoopE (e1, Some e2) -> exps msgs [e1; e2]
| LoopE (e1, Some e2)
| WhileE (e1, e2)
| AssignE (e1, e2)
| IdxE (e1, e2)
| BinE (_, e1, _, e2)
| RelE (_, e1, _, e2)
| AndE (e1, e2)
| OrE (e1, e2)
| ImpliesE (e1, e2) -> exps msgs [e1; e2]
| ForE (p, e1, e2) -> exp msgs e1 ++ (exp msgs e2 /// pat msgs p)
| LabelE (i, t, e) -> exp msgs e
| DebugE e -> exp msgs e
| AsyncE (_, _, e) -> exp msgs e
| AwaitE (_, e) -> exp msgs e
| AssertE (_, e) -> exp msgs e
| AnnotE (e, t) -> exp msgs e
| OptE e -> exp msgs e
| DoOptE e -> exp msgs e
| BangE e -> exp msgs e
| TagE (_, e) -> exp msgs e
| UnE (_, _, e)
| ShowE (_, e)
| FromCandidE e
| DotE (e, _)
| ProjE (e, _)
| NotE e
| OldE e
| LabelE (_, _, e)
| DebugE e
| AsyncE (_, _, e)
| AwaitE (_, e)
| AssertE (_, e)
| AnnotE (e, _)
| OptE e
| DoOptE e
| BangE e
| TagE (_, e)
| ActorUrlE e
| IgnoreE e -> exp msgs e

and exps msgs es : f = unions (exp msgs) es
Expand Down Expand Up @@ -179,11 +179,22 @@ and dec msgs d = match d.it with
| TypD (i, tp, t) -> (M.empty, S.empty)
| ClassD (csp, i, tp, p, t, s, i', dfs) ->
(M.empty, S.singleton i.it) +++ delayify (
group msgs (dec_fields msgs dfs @ class_self d.at i') /// pat msgs p /// shared_pat msgs csp
group msgs (add_self (Some i') s (dec_fields msgs dfs)) /// pat msgs p /// shared_pat msgs csp
)

(* The class self binding is treated as defined at the very end of the group *)
and class_self at i : group = [(at, S.singleton i.it, S.empty, S.empty)]
(* The self binding, if any, is treated as defined at the very beginning or end of the group,
depending on sort and shadowing *)
and add_self self_id_opt s group =
match self_id_opt with
| None -> group
| Some i ->
if List.exists (fun (at, defs, _, _) -> S.mem i.it defs) group
then group (* shadowed, ignore *)
else (* not shadowed, consider self ... *)
let item = (i.at, S.singleton i.it, S.empty, S.empty) in
match s.it with
| Type.Actor -> item :: group (* ... defined early *)
| _ -> group @ [item] (* ... defined late *)

and decs msgs decs : group =
(* Annotate the declarations with the analysis results *)
Expand Down Expand Up @@ -242,4 +253,3 @@ let check_lib lib =
ignore (group msgs (decs msgs (imp_ds @ ds)));
Some ()
)

9 changes: 5 additions & 4 deletions src/mo_frontend/parser.mly
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,13 @@ let share_dec_field (df : dec_field) =
}
| _ -> df

and objblock s ty dec_fields =
and objblock s id ty dec_fields =
List.iter (fun df ->
match df.it.vis.it, df.it.dec.it with
| Public _, ClassD (_, id, _, _, _, _, _, _) when is_anon_id id ->
syntax_error df.it.dec.at "M0158" "a public class cannot be anonymous, please provide a name"
| _ -> ()) dec_fields;
ObjBlockE(s, ty, dec_fields)
ObjBlockE(s, (id, ty), dec_fields)

%}

Expand Down Expand Up @@ -872,12 +872,13 @@ dec_nonvar :
let named, x = xf sort $sloc in
let e =
if s.it = Type.Actor then
let id = if named then Some x else None in
AwaitE
(Type.Fut,
AsyncE(Type.Fut, scope_bind (anon_id "async" (at $sloc)) (at $sloc),
objblock s t (List.map share_dec_field efs) @? at $sloc)
objblock s id t (List.map share_dec_field efs) @? at $sloc)
@? at $sloc) @? at $sloc
else objblock s t efs @? at $sloc
else objblock s None t efs @? at $sloc
in
let_or_exp named x e.it e.at }
| sp=shared_pat_opt FUNC xf=id_opt
Expand Down
2 changes: 1 addition & 1 deletion src/mo_frontend/typing.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1382,7 +1382,7 @@ and infer_exp'' env exp : T.typ =
in
let t = infer_obj env' obj_sort.it dec_fields exp.at in
begin match env.pre, typ_opt with
| false, Some typ ->
| false, (_, Some typ) ->
let t' = check_typ env' typ in
if not (T.sub t t') then
local_error env exp.at "M0192"
Expand Down
Loading

0 comments on commit 0a0eb38

Please sign in to comment.