From ff0d5b47fefa28fc029cde619f574214b080105a Mon Sep 17 00:00:00 2001 From: Joachim Breitner Date: Fri, 7 Dec 2018 16:56:20 +0100 Subject: [PATCH 1/2] Desguar all objects to NewObjE, and compile it properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit if we have to do it for some, lets do it for all! So in `desguar`, we now replace `ObjE` by `BlockE { LetD … ; Var D ; LetD o NewObjE }`, properly implementing the idea of an object as a bunch of closures. Object class functions are also desugared here. The class identity is lost. In light of getting rid of `instance-of` (#81) that seems fine for now. Actors cannot be compiled that way, so they survive untouched (as `ActorE` and `ActorClassD` to be explicit). The compiler now implements `NewObjE` so that `{x = y}` makes `x` an alias for `y`, so that mutable fields stay mutable. This fixes the semantics of `test/run-dfinity/async-obj-mut.as`. Yay! For now, *all* actor fields have this level of indirection. A further refinement may restrict that treatment only to mutable fields. Small wart: If the compiler finds that a mutable variable is not captured by a function, it will allocate it on the stack. If such a variable is put in `NewObjE`, the content will be put in a new box. This is fine for the output of the desguaring: If it was not captured, the local `x` is not referenced after the object has been created. But this is shaky ground, and may need to be revisited (probably by beefing up the `AllocHow` algorithm that determines whether to heap-allocate a variable). --- src/arrange_ir.ml | 6 +- src/compile.ml | 171 ++++++------------- src/desugar.ml | 52 ++++-- src/freevars_ir.ml | 4 +- src/ir.ml | 4 +- test/run-dfinity/ok/async-obj-mut.dvm-run.ok | 2 +- 6 files changed, 101 insertions(+), 138 deletions(-) diff --git a/src/arrange_ir.ml b/src/arrange_ir.ml index 1dd6f78b52c..f26a2633970 100644 --- a/src/arrange_ir.ml +++ b/src/arrange_ir.ml @@ -12,7 +12,7 @@ let rec exp e = match e.it with | RelE (e1, ro, e2) -> "RelE" $$ [exp e1; Arrange.relop ro; exp e2] | TupE es -> "TupE" $$ List.map exp es | ProjE (e, i) -> "ProjE" $$ [exp e; Atom (string_of_int i)] - | ObjE (s, i, efs) -> "ObjE" $$ [Arrange.obj_sort s; id i] @ List.map exp_field efs + | ActorE (i, efs) -> "ActorE" $$ [id i] @ List.map exp_field efs | DotE (e, n) -> "DotE" $$ [exp e; name n] | AssignE (e1, e2) -> "AssignE" $$ [exp e1; exp e2] | ArrayE es -> "ArrayE" $$ List.map exp es @@ -66,7 +66,7 @@ and dec d = match d.it with "FuncD" $$ [Atom (Value.string_of_call_conv cc); id i] @ List.map Arrange.typ_bind tp @ [pat p; Arrange.typ t; exp e] | TypD (i, tp, t) -> "TypD" $$ [id i] @ List.map Arrange.typ_bind tp @ [Arrange.typ t] - | ClassD (i, j, tp, s, p, i', efs) -> - "ClassD" $$ id i :: id j :: List.map Arrange.typ_bind tp @ [Arrange.obj_sort s; pat p; id i'] @ List.map exp_field efs + | ActorClassD (i, j, tp, p, i', efs) -> + "ActorClassD" $$ id i :: id j :: List.map Arrange.typ_bind tp @ [pat p; id i'] @ List.map exp_field efs and prog prog = "BlockE" $$ List.map dec prog.it diff --git a/src/compile.ml b/src/compile.ml index 160a7e19b93..fcedb4a5136 100644 --- a/src/compile.ml +++ b/src/compile.ml @@ -643,6 +643,7 @@ module Tagged = struct type tag = | Object + | ObjInd (* The indirection used in object *) | Array (* Also a tuple *) | Reference (* Either arrayref or funcref, no need to distinguish here *) | Int @@ -655,14 +656,15 @@ module Tagged = struct (* Lets leave out tag 0 to trap earlier on invalid memory *) let int_of_tag = function | Object -> 1l - | Array -> 2l - | Reference -> 3l - | Int -> 4l - | MutBox -> 5l - | Closure -> 6l - | Some -> 7l - | Text -> 8l - | Indirection -> 9l + | ObjInd -> 2l + | Array -> 3l + | Reference -> 4l + | Int -> 5l + | MutBox -> 6l + | Closure -> 7l + | Some -> 8l + | Text -> 9l + | Indirection -> 10l (* The tag *) let header_size = 1l @@ -711,6 +713,9 @@ module Var = struct compile_unboxed_const 0l (* number of parameters: none *) ] + let field_box env code = + Tagged.obj env Tagged.ObjInd [ code ] + (* Local variables may in general be mutable (or at least late-defined). So we need to add an indirection through the heap. We tag this indirection using Tagged.MutBox. @@ -721,7 +726,7 @@ module Var = struct let load = Heap.load_field mutbox_field let store = Heap.store_field mutbox_field - let add_local env name = + let _add_local env name = E.add_local_with_offset env name mutbox_field (* Stores the payload *) @@ -774,6 +779,12 @@ module Var = struct ( compile_null , fun env1 -> (E.add_local_deferred env1 var d, G.i_ Drop)) | None -> (G.i_ Unreachable, fun env1 -> (env1, G.i_ Unreachable)) + (* Returns a pointer to a heap allocated box for this. + (either a mutbox, if already mutable, or a freshly allocated box + *) + let get_val_ptr env var = match E.lookup_var env var with + | Some (HeapInd (i, 1l)) -> G.i_ (GetLocal (nr i)) + | _ -> field_box env (get_val env var) end (* Var *) module Opt = struct @@ -845,7 +856,7 @@ module AllocHow = struct | FuncD ((Type.Call Type.Sharable, _, _, _), _, _, _, _, _) -> map_of_set LocalImmut d (* Static functions and classes *) | FuncD _ when is_static env how0 f -> M.empty - | ClassD _ when is_static env how0 f -> M.empty + | ActorClassD _ when is_static env how0 f -> M.empty (* Everything else needs at least a local *) | _ -> map_of_set LocalImmut d in @@ -1176,84 +1187,8 @@ module Object = struct Int32.of_int (Hashtbl.hash s) module FieldEnv = Env.Make(String) - let lit env this_name_opt class_option fs = - let name_pos_map = - fs |> - (* We could store only public fields in the object, but - then we need to allocate separate boxes for the non-public ones: - List.filter (fun (_, priv, f) -> priv.it = Public) |> - *) - List.map (fun ({it = Syntax.Name s;_} as n,_,_,_) -> (hash_field_name n, s)) |> - List.sort compare |> - List.mapi (fun i (_h,n) -> (n,Int32.of_int i)) |> - List.fold_left (fun m (n,i) -> FieldEnv.add n i m) FieldEnv.empty in - - let sz = Int32.of_int (FieldEnv.cardinal name_pos_map) in - - (* Allocate memory *) - let (set_ri, get_ri, ri) = new_local_ env "obj" in - Heap.alloc env (Int32.add header_size (Int32.mul 2l sz)) ^^ - set_ri ^^ - - (* Set tag *) - get_ri ^^ - Tagged.store Tagged.Object ^^ - - (* Write the class field *) - get_ri ^^ - (match class_option with - | Some class_instrs -> class_instrs - | None -> compile_unboxed_const 1l ) ^^ - Heap.store_field class_position ^^ - - (* Set size *) - get_ri ^^ - compile_unboxed_const sz ^^ - Heap.store_field size_field ^^ - - let is_public_field {it = Syntax.Name n; _} = - FieldEnv.mem n name_pos_map in - let hash_position env {it = Syntax.Name n; _} = - let i = FieldEnv.find n name_pos_map in - Int32.add header_size (Int32.mul 2l i) in - let field_position env {it = Syntax.Name n; _} = - let i = FieldEnv.find n name_pos_map in - Int32.add header_size (Int32.add (Int32.mul 2l i) 1l) in - - (* Bind the fields in the envrionment *) - let mk_field_ptr env (name, id, _, _) = - E.reuse_local_with_offset env id.it ri (field_position env name) in - let env1 = List.fold_left mk_field_ptr env fs in - - (* An extra indirection for the 'this' pointer, if present *) - let (env2, this_code) = match this_name_opt with - | Some name -> let (env2, ti) = Var.add_local env1 name.it in - (env2, Tagged.obj env1 Tagged.MutBox [ get_ri ] ^^ - G.i_ (SetLocal (nr ti))) - | None -> (env1, G.nop) in - this_code ^^ - - (* Write all the fields *) - let init_field (name, _, _, mk_is) : G.t = - if is_public_field name - then - (* Write the hash *) - get_ri ^^ - compile_unboxed_const (hash_field_name name) ^^ - Heap.store_field (hash_position env name) ^^ - (* Write the value *) - get_ri ^^ - mk_is env2 ^^ - Heap.store_field (field_position env name) - else G.nop - in - G.concat_map init_field fs ^^ - - (* Return the pointer to the object *) - get_ri (* This is for non-recursive objects, i.e. ObjNewE *) - (* TODO: Remove duplication with above *) let lit_raw env fs = let name_pos_map = fs |> @@ -1287,8 +1222,6 @@ module Object = struct compile_unboxed_const sz ^^ Heap.store_field size_field ^^ - let is_public_field {it = Syntax.Name n; _} = - FieldEnv.mem n name_pos_map in let hash_position env {it = Syntax.Name n; _} = let i = FieldEnv.find n name_pos_map in Int32.add header_size (Int32.mul 2l i) in @@ -1298,17 +1231,14 @@ module Object = struct (* Write all the fields *) let init_field (name, mk_is) : G.t = - if is_public_field name - then - (* Write the hash *) - get_ri ^^ - compile_unboxed_const (hash_field_name name) ^^ - Heap.store_field (hash_position env name) ^^ - (* Write the value *) - get_ri ^^ - mk_is env ^^ - Heap.store_field (field_position env name) - else G.nop + (* Write the hash *) + get_ri ^^ + compile_unboxed_const (hash_field_name name) ^^ + Heap.store_field (hash_position env name) ^^ + (* Write the pointer to the indirection *) + get_ri ^^ + mk_is env ^^ + Heap.store_field (field_position env name) in G.concat_map init_field fs ^^ @@ -1341,6 +1271,9 @@ module Object = struct G.i_ (Compare (Wasm.Values.I32 Wasm.Ast.I32Op.Eq)) ^^ G.if_ [] ( get_f ^^ + compile_add_const Heap.word_size ^^ + (* dereference the indirection *) + load_ptr ^^ compile_add_const Heap.word_size ^^ set_r ) G.nop @@ -1583,8 +1516,9 @@ module Array = struct ] ^^ set_ni ^^ - Object.lit env1 None None - [ (nr_ (Syntax.Name "next"), nr_ "next", nr_ Syntax.Public, fun _ -> get_ni) ] + Object.lit_raw env1 + [ (nr_ (Syntax.Name "next"), + fun _ -> Var.field_box env get_ni) ] ) in E.define_built_in env "array_keys_next" @@ -2078,6 +2012,12 @@ module Serialization = struct get_x ^^ Opt.project ^^ G.i_ (Call (nr (E.built_in env "serialize_go"))) ) + ; Tagged.ObjInd, + G.i_ Drop ^^ + Tagged.obj env Tagged.ObjInd [ + get_x ^^ Heap.load_field 1l ^^ + G.i_ (Call (nr (E.built_in env "serialize_go"))) + ] ; Tagged.Array, begin let (set_len, get_len) = new_local env "len" in @@ -2236,6 +2176,9 @@ module Serialization = struct ; Tagged.Some, G.i_ Drop ^^ compile_unboxed_const (Int32.mul 2l Heap.word_size) + ; Tagged.ObjInd, + G.i_ Drop ^^ + compile_unboxed_const (Int32.mul 2l Heap.word_size) ; Tagged.MutBox, G.i_ Drop ^^ compile_unboxed_const (Int32.mul 2l Heap.word_size) @@ -2289,15 +2232,17 @@ module Serialization = struct get_x ^^ Tagged.branch_default env [] G.nop [ Tagged.MutBox, - (* Adust pointer *) compile_add_const (Int32.mul Heap.word_size Var.mutbox_field) ^^ set_ptr_loc ^^ mk_code get_ptr_loc ; Tagged.Some, - (* Adust pointer *) compile_add_const (Int32.mul Heap.word_size Opt.payload_field) ^^ set_ptr_loc ^^ mk_code get_ptr_loc + ; Tagged.ObjInd, + compile_add_const (Int32.mul Heap.word_size 1l) ^^ + set_ptr_loc ^^ + mk_code get_ptr_loc ; Tagged.Array, (* x still on the stack *) Heap.load_field Array.len_field ^^ @@ -2313,7 +2258,6 @@ module Serialization = struct (* x still on the stack *) Heap.load_field Object.size_field ^^ - (* Adjust fields *) from_0_to_n env (fun get_i -> get_i ^^ compile_mul_const 2l ^^ @@ -2328,7 +2272,7 @@ module Serialization = struct ; Tagged.Closure, (* x still on the stack *) Heap.load_field Closure.len_field ^^ - (* Adjust fields *) + from_0_to_n env (fun get_i -> get_i ^^ compile_add_const Closure.header_size ^^ @@ -3085,13 +3029,7 @@ and compile_exp (env : E.t) exp = match exp.it with compile_exp env e1 ^^ (* offset to tuple (an array) *) Array.load_n (Int32.of_int n) | ArrayE es -> Array.lit env (List.map (compile_exp env) es) - | ObjE ({ it = Type.Object _ (*sharing*); _}, name, fs) -> (* TBR - really the same for local and shared? *) - let fs' = List.map - (fun (f : Ir.exp_field) -> - (f.it.name, f.it.id, f.it.priv, fun env -> compile_exp env f.it.exp) - ) fs in - Object.lit env (Some name) None fs' - | ObjE ({ it = Type.Actor; _}, name, fs) -> + | ActorE (name, fs) -> let captured = Freevars_ir.exp exp in let prelude_names = find_prelude_names env in if Freevars_ir.M.is_empty (Freevars_ir.diff captured prelude_names) @@ -3162,9 +3100,9 @@ and compile_exp (env : E.t) exp = match exp.it with compile_exp env e ^^ Var.set_val env name.it ^^ compile_unit - | NewObjE ({ it = Type.Object _ (*sharing*); _}, fs) -> (* TBR - really the same for local and shared? *) + | NewObjE ({ it = Type.Object _ (*sharing*); _}, fs) -> let fs' = List.map - (fun (name, id) -> (name, fun env -> Var.get_val env id.it)) + (fun (name, id) -> (name, fun env -> Var.get_val_ptr env id.it)) fs in Object.lit_raw env fs' | _ -> todo "compile_exp" (Arrange_ir.exp exp) (G.i_ Unreachable) @@ -3320,7 +3258,8 @@ and compile_dec last pre_env how dec : E.t * G.t * (E.t -> G.t) = match dec.it w let mk_body env1 _ = compile_exp env1 e in Closure.dec pre_env how last name cc captured mk_pat mk_body dec.at - (* Classes are desguared to functions and objects. *) + (* Should be desugared for object classes , but not for actor classes yet *) + (* | ClassD (name, _, typ_params, s, p, self, efs) -> let captured = Freevars_ir.captured_exp_fields p efs in let mk_pat env1 = compile_mono_pat env1 AllocHow.M.empty p in @@ -3335,6 +3274,8 @@ and compile_dec last pre_env how dec : E.t * G.t * (E.t -> G.t) = match dec.it w For functions it is the function id (shifted to never class with pointers) *) Object.lit env1 (Some self) (Some compile_fun_identifier) fs' in Closure.dec pre_env how last name (Value.local_cc 1 1) captured mk_pat mk_body dec.at + *) + | _ -> todo "compile_dec" (Arrange_ir.dec dec) (pre_env, G.nop, fun _ -> G.i_ Unreachable) and compile_decs env decs : G.t = snd (compile_decs_block env true decs) diff --git a/src/desugar.ml b/src/desugar.ml index afb752fc97b..ca1bf1890d4 100644 --- a/src/desugar.ml +++ b/src/desugar.ml @@ -1,13 +1,11 @@ +open Source module S = Syntax module I = Ir - (* Combinators used in the desguaring *) -let true_lit : Ir.exp = - Source.(I.LitE (S.BoolLit true) @@ no_region) -let false_lit : Ir.exp = - Source.(I.LitE (S.BoolLit false) @@ no_region) +let true_lit : Ir.exp = I.LitE (S.BoolLit true) @@ no_region +let false_lit : Ir.exp = I.LitE (S.BoolLit false) @@ no_region let apply_sign op l = Syntax.(match op, l with @@ -18,13 +16,13 @@ let apply_sign op l = Syntax.(match op, l with ) -let phrase f x = Source.(f x.it @@ x.at) -let phrase' f x = Source.(f x.note x.it @@ x.at) +let phrase f x = f x.it @@ x.at +let phrase' f x = f x.at x.note x.it @@ x.at let rec exps es = List.map exp es - and exp e = phrase exp' e - and exp' = function + and exp e = phrase' exp' e + and exp' at note = function | S.PrimE p -> I.PrimE p | S.VarE i -> I.VarE i | S.LitE l -> I.LitE !l @@ -34,13 +32,14 @@ let | S.TupE es -> I.TupE (exps es) | S.ProjE (e, i) -> I.ProjE (exp e, i) | S.OptE e -> I.OptE (exp e) - | S.ObjE (s, i, es) -> I.ObjE (s, i, exp_fields es) + | S.ObjE ({it = Type.Object _; _}, i, es) -> build_obj at None i es + | S.ObjE ({it = Type.Actor; _}, i, es) -> I.ActorE (i, exp_fields es) | S.DotE (e, n) -> I.DotE (exp e, n) | S.AssignE (e1, e2) -> I.AssignE (exp e1, exp e2) | S.ArrayE es -> I.ArrayE (exps es) | S.IdxE (e1, e2) -> I.IdxE (exp e1, exp e2) | S.CallE (e1, inst, e2) -> - let cc = Value.call_conv_of_typ e1.Source.note.S.note_typ in + let cc = Value.call_conv_of_typ e1.note.S.note_typ in I.CallE (cc, exp e1, inst, exp e2) | S.BlockE ds -> I.BlockE (decs ds) | S.NotE e -> I.IfE (exp e, false_lit, true_lit) @@ -59,12 +58,32 @@ let | S.AwaitE e -> I.AwaitE (exp e) | S.AssertE e -> I.AssertE (exp e) | S.IsE (e1, e2) -> I.IsE (exp e1, exp e2) - | S.AnnotE (e, _) -> exp' e.Source.it + | S.AnnotE (e, _) -> exp' at note e.it | S.DecE d -> I.BlockE [dec d] | S.DeclareE (i, t, e) -> I.DeclareE (i, t, exp e) | S.DefineE (i, m, e) -> I.DefineE (i, m, exp e) | S.NewObjE (s, fs) -> I.NewObjE (s, fs) + and field_to_dec (f : S.exp_field) : Ir.dec = + match f.it.S.mut.it with + | S.Const -> I.LetD (I.VarP f.it.S.id @@ no_region, exp f.it.S.exp) @@ f.at + | S.Var -> I.VarD (f.it.S.id, exp f.it.S.exp) @@ f.at + + and field_to_obj_entry (f : S.exp_field) = + match f.it.S.priv.it with + | S.Private -> [] + | S.Public -> [ (f.it.S.name, f.it.S.id) ] + + and build_obj at class_id self_id es = + I.BlockE ( + List.map field_to_dec es @ + [ I.LetD ( + I.VarP self_id @@ at, + I.NewObjE + (Type.Object Type.Local @@ at, + List.concat (List.map field_to_obj_entry es)) @@ at + ) @@ at ]) + and exp_fields fs = List.map exp_field fs and exp_field f = phrase exp_field' f and exp_field' (f : S.exp_field') = @@ -72,7 +91,7 @@ let and decs ds = List.map dec ds and dec d = phrase' dec' d - and dec' n = function + and dec' at n = function | S.ExpD e -> I.ExpD (exp e) | S.LetD (p, e) -> I.LetD (pat p, exp e) | S.VarD (i, e) -> I.VarD (i, exp e) @@ -80,7 +99,10 @@ let let cc = Value.call_conv_of_typ n.S.note_typ in I.FuncD (cc, i, tp, pat p, ty, exp e) | S.TypD (i, ty, t) -> I.TypD (i, ty, t) - | S.ClassD (i1, i2, ty, s, p, i3, es) -> I.ClassD (i1, i2, ty, s, pat p, i3, exp_fields es) + | S.ClassD (fun_id, typ_id, tp, s, p, self_id, es) -> + let cc = Value.call_conv_of_typ n.S.note_typ in + I.FuncD (cc, fun_id, tp, pat p, S.PrimT "dummy" @@ at, + build_obj at (Some fun_id) self_id es @@ at) and cases cs = List.map case cs @@ -97,7 +119,7 @@ let | S.TupP ps -> I.TupP (pats ps) | S.OptP p -> I.OptP (pat p) | S.AltP (p1, p2) -> I.AltP (pat p1, pat p2) - | S.AnnotP (p, _) -> pat' p.Source.it + | S.AnnotP (p, _) -> pat' p.it and prog p = phrase decs p diff --git a/src/freevars_ir.ml b/src/freevars_ir.ml index 29d813c9d3d..9f77b9c9608 100644 --- a/src/freevars_ir.ml +++ b/src/freevars_ir.ml @@ -67,7 +67,7 @@ let rec exp e : f = match e.it with | RelE (e1, ro, e2) -> exps [e1; e2] | TupE es -> exps es | ProjE (e, i) -> exp e - | ObjE (s, i, efs) -> close (exp_fields efs) // i.it + | ActorE (i, efs) -> close (exp_fields efs) // i.it | DotE (e, i) -> exp e | AssignE (e1, e2) -> exps [e1; e2] | ArrayE es -> exps es @@ -123,7 +123,7 @@ and dec d = match d.it with | FuncD (cc, i, tp, p, t, e) -> (M.empty, S.singleton i.it) +++ under_lambda (exp e /// pat p) | TypD (i, tp, t) -> (M.empty, S.empty) - | ClassD (i, l, tp, s, p, i', efs) -> + | ActorClassD (i, l, tp, p, i', efs) -> (M.empty, S.singleton i.it) +++ under_lambda (close (exp_fields efs) /// pat p // i'.it) (* The variables captured by a function. May include the function itself! *) diff --git a/src/ir.ml b/src/ir.ml index 5d770b82768..73c6fd84578 100644 --- a/src/ir.ml +++ b/src/ir.ml @@ -22,7 +22,7 @@ and exp' = | TupE of exp list (* tuple *) | ProjE of exp * int (* tuple projection *) | OptE of exp (* option injection *) - | ObjE of Syntax.obj_sort * Syntax.id * exp_field list (* object *) + | ActorE of Syntax.id * exp_field list (* actor *) | DotE of exp * Syntax.name (* object projection *) | AssignE of exp * exp (* assignment *) | ArrayE of exp list (* array *) @@ -61,7 +61,7 @@ and dec' = | VarD of Syntax.id * exp (* mutable *) | FuncD of Value.call_conv * Syntax.id * Syntax.typ_bind list * pat * Syntax.typ * exp (* function *) | TypD of Syntax.id * Syntax.typ_bind list * Syntax.typ (* type *) - | ClassD of Syntax.id (*term id*) * Syntax.id (*type id*) * Syntax.typ_bind list * Syntax.obj_sort * pat * Syntax.id * exp_field list (* class *) + | ActorClassD of Syntax.id (*term id*) * Syntax.id (*type id*) * Syntax.typ_bind list * pat * Syntax.id * exp_field list (* Program *) diff --git a/test/run-dfinity/ok/async-obj-mut.dvm-run.ok b/test/run-dfinity/ok/async-obj-mut.dvm-run.ok index 8ea216342c4..98f29c526f9 100644 --- a/test/run-dfinity/ok/async-obj-mut.dvm-run.ok +++ b/test/run-dfinity/ok/async-obj-mut.dvm-run.ok @@ -1,3 +1,3 @@ 123 done creating -344 +345 From 3c2ae3a3738d86e127001bd6bbde366de357a717 Mon Sep 17 00:00:00 2001 From: Joachim Breitner Date: Fri, 14 Dec 2018 11:21:25 +0100 Subject: [PATCH 2/2] Desguaring of Objects: Conclude with `ExpD self` needed since 9c0fa1fbb985e8c37b0470b2c4916ffaeed56fb2. --- src/desugar.ml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/desugar.ml b/src/desugar.ml index 470860c8ac5..3bd3372d2f3 100644 --- a/src/desugar.ml +++ b/src/desugar.ml @@ -82,7 +82,8 @@ let I.NewObjE (Type.Object Type.Local @@ at, List.concat (List.map field_to_obj_entry es)) @@ at - ) @@ at ]) + ) @@ at; + I.ExpD (I.VarE self_id @@ at) @@ at]) and exp_fields fs = List.map exp_field fs and exp_field f = phrase exp_field' f