diff --git a/doc/md/examples/grammar.txt b/doc/md/examples/grammar.txt index dd92631b14c..5d20ddf28e0 100644 --- a/doc/md/examples/grammar.txt +++ b/doc/md/examples/grammar.txt @@ -14,7 +14,7 @@ ::= 'object' - 'persistent'? 'actor' + 'persistent'? 'actor' ('[' ']')? 'module' ::= diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs index 3f6f0e9735e..eb45fa8672d 100644 --- a/rts/motoko-rts/src/persistence.rs +++ b/rts/motoko-rts/src/persistence.rs @@ -206,6 +206,22 @@ pub unsafe fn register_stable_type( (*metadata).stable_type.assign(mem, &new_type); } +/// Register the stable actor type on canister initialization and upgrade. +/// The type is stored in the persistent metadata memory for later retrieval on canister upgrades. +/// The `new_type` value points to a blob encoding the new stable actor type. +#[ic_mem_fn] +pub unsafe fn assign_stable_type( + mem: &mut M, + new_candid_data: Value, + new_type_offsets: Value, +) { + assert_eq!(new_candid_data.tag(), TAG_BLOB_B); + assert_eq!(new_type_offsets.tag(), TAG_BLOB_B); + let new_type = TypeDescriptor::new(new_candid_data, new_type_offsets); + let metadata = PersistentMetadata::get(); + (*metadata).stable_type.assign(mem, &new_type); +} + pub(crate) unsafe fn stable_type_descriptor() -> &'static mut TypeDescriptor { let metadata = PersistentMetadata::get(); &mut (*metadata).stable_type diff --git a/src/codegen/compile_classical.ml b/src/codegen/compile_classical.ml index a16282ca4db..1061c7d344a 100644 --- a/src/codegen/compile_classical.ml +++ b/src/codegen/compile_classical.ml @@ -11616,6 +11616,15 @@ and compile_prim_invocation (env : E.t) ae p es at = SR.Vanilla, StableMem.get_mem_size env ^^ BigNum.from_word64 env + | OtherPrim "rts_in_install", [] -> (* classical specific *) + assert (not !Flags.enhanced_orthogonal_persistence); + SR.Vanilla, + StableMem.stable64_size env ^^ + G.i (Test (Wasm_exts.Values.I64 I64Op.Eqz)) ^^ + G.if1 I32Type + (Bool.lit true) + (Bool.lit false) + (* Regions *) | OtherPrim "regionNew", [] -> diff --git a/src/codegen/compile_enhanced.ml b/src/codegen/compile_enhanced.ml index 61d21e839f5..1db86ce8a57 100644 --- a/src/codegen/compile_enhanced.ml +++ b/src/codegen/compile_enhanced.ml @@ -1131,6 +1131,7 @@ module RTS = struct E.add_func_import env "rts" "allocation_barrier" [I64Type] [I64Type]; E.add_func_import env "rts" "running_gc" [] [I32Type]; E.add_func_import env "rts" "register_stable_type" [I64Type; I64Type] []; + E.add_func_import env "rts" "assign_stable_type" [I64Type; I64Type] []; E.add_func_import env "rts" "load_stable_actor" [] [I64Type]; E.add_func_import env "rts" "save_stable_actor" [I64Type] []; E.add_func_import env "rts" "free_stable_actor" [] []; @@ -8694,6 +8695,10 @@ module EnhancedOrthogonalPersistence = struct create_type_descriptor env actor_type ^^ E.call_import env "rts" "register_stable_type" + let assign_stable_type env actor_type = + create_type_descriptor env actor_type ^^ + E.call_import env "rts" "assign_stable_type" + let load_old_field env field get_old_actor = if field.Type.typ = Type.(Opt Any) then (* A stable variable may have been promoted to type `Any`: Therefore, drop its former content. *) @@ -8733,6 +8738,7 @@ module EnhancedOrthogonalPersistence = struct free_stable_actor env let save env actor_type = + assign_stable_type env actor_type ^^ IC.get_actor_to_persist env ^^ save_stable_actor env ^^ NewStableMemory.backup env ^^ @@ -8751,6 +8757,7 @@ module EnhancedOrthogonalPersistence = struct let initialize env actor_type = register_stable_type env actor_type + end (* EnhancedOrthogonalPersistence *) (* As fallback when doing persistent memory layout changes. *) @@ -9981,12 +9988,12 @@ module IncrementalGraphStabilization = struct let partial_destabilization_on_upgrade env actor_type = (* TODO: Verify that the post_upgrade hook cannot be directly called by the IC *) (* Garbage collection is disabled in `start_graph_destabilization` until destabilization has completed. *) - GraphCopyStabilization.start_graph_destabilization env actor_type ^^ + GraphCopyStabilization.start_graph_destabilization env actor_type.Ir.pre ^^ get_destabilized_actor env ^^ compile_test I64Op.Eqz ^^ E.if0 begin - destabilization_increment env actor_type ^^ + destabilization_increment env actor_type.Ir.pre ^^ get_destabilized_actor env ^^ (E.if0 G.nop @@ -10018,7 +10025,7 @@ module IncrementalGraphStabilization = struct }) | _ -> () end - + let load env = get_destabilized_actor env ^^ compile_test I64Op.Eqz ^^ @@ -10026,14 +10033,14 @@ module IncrementalGraphStabilization = struct get_destabilized_actor env (* Upgrade costs are already record in RTS for graph-copy-based (de-)stabilization. *) - let define_methods env actor_type = + let define_methods env (actor_type : Ir.stable_actor_typ) = define_async_stabilization_reply_callback env; define_async_stabilization_reject_callback env; export_async_stabilization_method env; - export_stabilize_before_upgrade_method env actor_type; + export_stabilize_before_upgrade_method env actor_type.Ir.post; define_async_destabilization_reply_callback env; define_async_destabilization_reject_callback env; - export_async_destabilization_method env actor_type; + export_async_destabilization_method env actor_type.Ir.pre; export_destabilize_after_upgrade_method env; end (* IncrementalGraphStabilization *) @@ -11678,6 +11685,15 @@ and compile_prim_invocation (env : E.t) ae p es at = SR.Vanilla, StableMem.get_mem_size env ^^ BigNum.from_word64 env + | OtherPrim "rts_in_install", [] -> (* EOP specific *) + assert (!Flags.enhanced_orthogonal_persistence); + SR.Vanilla, + EnhancedOrthogonalPersistence.load_stable_actor env ^^ + compile_test I64Op.Eqz ^^ + E.if1 I64Type + (Bool.lit true) + (Bool.lit false) + (* Regions *) | OtherPrim "regionNew", [] -> diff --git a/src/docs/extract.ml b/src/docs/extract.ml index 92cfffeca63..e1fdd2f7d59 100644 --- a/src/docs/extract.ml +++ b/src/docs/extract.ml @@ -140,7 +140,7 @@ struct _; } -> ( match rhs with - | Source.{ it = Syntax.ObjBlockE (sort, _, fields); _ } -> + | Source.{ it = Syntax.ObjBlockE (sort, _, _, fields); _ } -> let mk_field_xref xref = mk_xref (Xref.XClass (name, xref)) in Some ( mk_xref (Xref.XType name), @@ -155,7 +155,7 @@ struct ) | Source.{ it = Syntax.VarD ({ it = name; _ }, rhs); _ } -> ( match rhs with - | Source.{ it = Syntax.ObjBlockE (sort, _, fields); _ } -> + | Source.{ it = Syntax.ObjBlockE (sort, _, _, fields); _ } -> let mk_field_xref xref = mk_xref (Xref.XClass (name, xref)) in Some ( mk_xref (Xref.XType name), @@ -184,7 +184,15 @@ struct { it = Syntax.ClassD - (shared_pat, name, type_args, ctor, _, obj_sort, _, fields); + ( shared_pat, + exp_opt, + name, + type_args, + ctor, + _, + obj_sort, + _, + fields ); _; } -> let mk_field_xref xref = mk_xref (Xref.XClass (name.it, xref)) in diff --git a/src/docs/namespace.ml b/src/docs/namespace.ml index 29f3d4d47e8..c5861cb03d6 100644 --- a/src/docs/namespace.ml +++ b/src/docs/namespace.ml @@ -32,7 +32,7 @@ let from_module = | Syntax.ExpD _ -> acc | Syntax.LetD ( { it = Syntax.VarP id; _ }, - { it = Syntax.ObjBlockE (_, _, decs); _ }, + { it = Syntax.ObjBlockE (_, _, _, decs); _ }, _ ) -> let mk_nested x = mk_xref (Xref.XNested (id.it, x)) in { @@ -69,7 +69,7 @@ let from_module = (mk_xref (Xref.XValue id.it), None) acc.values; } - | Syntax.ClassD (_, id, _, _, _, _, _, _) -> + | Syntax.ClassD (_, _, id, _, _, _, _, _, _) -> { acc with types = StringMap.add id.it (mk_xref (Xref.XType id.it)) acc.types; diff --git a/src/gen-grammar/grammar.sed b/src/gen-grammar/grammar.sed index 45d4698f044..4f8be9b2b70 100644 --- a/src/gen-grammar/grammar.sed +++ b/src/gen-grammar/grammar.sed @@ -12,7 +12,7 @@ s//ID/g /^ ::=/,+2d /^ ::=/,+2d /^ ::=/,+2d -/^ ::=/,+2d +/^ ::=/,+5d /.*PRIM.*/d /^ ::=/,+2d /^ ::=/,+2d diff --git a/src/ir_def/arrange_ir.ml b/src/ir_def/arrange_ir.ml index b0f110029fb..941c4aab947 100644 --- a/src/ir_def/arrange_ir.ml +++ b/src/ir_def/arrange_ir.ml @@ -43,7 +43,7 @@ and system { meta; preupgrade; postupgrade; heartbeat; timer; inspect; stable_re "Timer" $$ [exp timer]; "Inspect" $$ [exp inspect]; "StableRecord" $$ [exp stable_record]; - "StableType" $$ [typ stable_type] + "StableType" $$ [typ stable_type.pre; typ stable_type.post] ] and lexp le = match le.it with diff --git a/src/ir_def/check_ir.ml b/src/ir_def/check_ir.ml index 37e84da5623..5725317ef03 100644 --- a/src/ir_def/check_ir.ml +++ b/src/ir_def/check_ir.ml @@ -241,7 +241,7 @@ let rec check_typ env typ : unit = if not (Lib.List.is_strictly_ordered T.compare_field fields) then error env no_region "variant type's fields are not distinct and sorted %s" (T.string_of_typ typ) | T.Mut typ -> - error env no_region "unexpected T.Mut" + error env no_region "unexpected T.Mut %s" (T.string_of_typ typ) | T.Typ c -> error env no_region "unexpected T.Typ" @@ -834,7 +834,7 @@ let rec check_exp env (exp:Ir.exp) : unit = typ heartbeat <: T.unit; typ timer <: T.unit; typ inspect <: T.unit; - typ stable_record <: stable_type; + typ stable_record <: stable_type.post; check (T.is_obj t0) "bad annotation (object type expected)"; let (s0, tfs0) = T.as_obj t0 in let val_tfs0 = List.filter (fun tf -> not (T.is_typ tf.T.typ)) tfs0 in @@ -1184,7 +1184,7 @@ let check_comp_unit env = function typ heartbeat <: T.unit; typ timer <: T.unit; typ inspect <: T.unit; - typ stable_record <: stable_type; + typ stable_record <: stable_type.post; check (T.is_obj t0) "bad annotation (object type expected)"; let (s0, tfs0) = T.as_obj t0 in let val_tfs0 = List.filter (fun tf -> not (T.is_typ tf.T.typ)) tfs0 in diff --git a/src/ir_def/construct.ml b/src/ir_def/construct.ml index 6564490287e..bca8c8af216 100644 --- a/src/ir_def/construct.ml +++ b/src/ir_def/construct.ml @@ -129,6 +129,7 @@ let primE prim es = | OtherPrim "rts_max_stack_size" -> T.nat | OtherPrim "rts_callback_table_count" -> T.nat | OtherPrim "rts_callback_table_size" -> T.nat + | OtherPrim "rts_in_install" -> T.bool | _ -> assert false (* implement more as needed *) in let eff = map_max_effs eff es in @@ -790,8 +791,42 @@ let objE sort typ_flds flds = in go [] [] [] flds + let recordE flds = objE T.Object [] flds +let objectE sort flds (tfs : T.field list) = + let rec go ds fields = function + | [] -> + blockE + (List.rev ds) + (newObjE sort fields + (T.Obj (sort, List.sort T.compare_field tfs))) + | (lab, exp)::flds -> + let v, typ, ds = + match T.lookup_val_field_opt lab tfs with + | None -> assert false + | Some typ -> + if T.is_mut typ + then + let v = fresh_var lab typ in + v, typ, varD v exp :: ds + else + match exp.it with + | VarE (Const, v) -> + var v typ, typ, ds + | _ -> + let v = fresh_var lab typ in + v, typ, letD v exp :: ds + in + let field = { + it = {name = lab; var = id_of_var v}; + at = no_region; + note = typ + } in + go ds (field::fields) flds + in + go [] [] flds + let check_call_perform_status success mk_failure = ifE (callE diff --git a/src/ir_def/construct.mli b/src/ir_def/construct.mli index 26387ef5097..3fd690004a4 100644 --- a/src/ir_def/construct.mli +++ b/src/ir_def/construct.mli @@ -147,6 +147,8 @@ val (-*-) : exp -> exp -> exp (* application *) val objE : obj_sort -> (lab * con) list -> (lab * exp) list -> exp +val objectE : obj_sort -> (lab * exp) list -> field list -> exp + (* Records *) val recordE : (lab * exp) list -> exp diff --git a/src/ir_def/ir.ml b/src/ir_def/ir.ml index 192a7453446..68843f83ccc 100644 --- a/src/ir_def/ir.ml +++ b/src/ir_def/ir.ml @@ -77,6 +77,8 @@ and exp' = | NewObjE of Type.obj_sort * field list * Type.typ (* make an object *) | TryE of exp * case list * (id * Type.typ) option (* try/catch/cleanup *) +and stable_actor_typ = { pre: Type.typ; post: Type.typ } + and system = { meta : meta; (* TODO: use option expressions for (some or all of) these *) @@ -86,7 +88,7 @@ and system = { timer : exp; (* TODO: use an option type: (Default of exp | UserDefined of exp) option *) inspect : exp; stable_record: exp; - stable_type: Type.typ; + stable_type: stable_actor_typ; } and candid = { @@ -242,7 +244,7 @@ type actor_type = { transient_actor_type: Type.typ; (* record of stable actor fields used for persistence, the fields are without mutability distinctions *) - stable_actor_type: Type.typ + stable_actor_type: stable_actor_typ } (* Program *) diff --git a/src/ir_passes/async.ml b/src/ir_passes/async.ml index b3b52989e7a..0a76bd612f8 100644 --- a/src/ir_passes/async.ml +++ b/src/ir_passes/async.ml @@ -451,7 +451,7 @@ let transform prog = timer = t_exp timer; inspect = t_exp inspect; stable_record = t_exp stable_record; - stable_type = t_typ stable_type; + stable_type = {pre = t_typ stable_type.pre; post = t_typ stable_type.post}; }, t_typ typ) | NewObjE (sort, ids, t) -> @@ -532,7 +532,10 @@ let transform prog = timer = t_exp timer; inspect = t_exp inspect; stable_record = t_exp stable_record; - stable_type = t_typ stable_type; + stable_type = { + pre = t_typ stable_type.pre; + post = t_typ stable_type.post + } }, t_typ t) diff --git a/src/ir_passes/erase_typ_field.ml b/src/ir_passes/erase_typ_field.ml index 2b5daf75d70..c628572b921 100644 --- a/src/ir_passes/erase_typ_field.ml +++ b/src/ir_passes/erase_typ_field.ml @@ -135,7 +135,10 @@ let transform prog = timer = t_exp timer; inspect = t_exp inspect; stable_record = t_exp stable_record; - stable_type = t_typ stable_type; + stable_type = { + pre = t_typ stable_type.pre; + post = t_typ stable_type.post + } }, t_typ typ) @@ -220,7 +223,10 @@ let transform prog = timer = t_exp timer; inspect = t_exp inspect; stable_record = t_exp stable_record; - stable_type = t_typ stable_type; + stable_type = { + pre = t_typ stable_type.pre; + post = t_typ stable_type.post + } }, t_typ t) and t_prog (cu, flavor) = (t_comp_unit cu, { flavor with has_typ_field = false } ) diff --git a/src/lang_utils/error_codes.ml b/src/lang_utils/error_codes.ml index 5d7c4b1f70f..98fed4560a3 100644 --- a/src/lang_utils/error_codes.ml +++ b/src/lang_utils/error_codes.ml @@ -203,5 +203,10 @@ let error_codes : (string * string option) list = "M0197", Some([%blob "lang_utils/error_codes/M0197.md"]); (* `system` capability required *) "M0198", Some([%blob "lang_utils/error_codes/M0198.md"]); (* Unused field pattern warning *) "M0199", Some([%blob "lang_utils/error_codes/M0199.md"]); (* Deprecate experimental stable memory *) - "M0200", Some([%blob "lang_utils/error_codes/M0200.md"]) (* Cannot determine subtyping or equality *) + "M0200", Some([%blob "lang_utils/error_codes/M0200.md"]); (* Cannot determine subtyping or equality *) + "M0201", None; (* Migration produces/consumes non-stable object *) + "M0202", None; (* Migration produces/consume non-object type *) + "M0203", None; (* Migration expression is not a function *) + "M0204", None; (* Migration produces field of wrong type *) + "M0205", None; (* Migration produces unexpected field *) ] diff --git a/src/languageServer/declaration_index.ml b/src/languageServer/declaration_index.ml index fcc9b865e81..8dee6cdd7e4 100644 --- a/src/languageServer/declaration_index.ml +++ b/src/languageServer/declaration_index.ml @@ -251,7 +251,7 @@ let populate_definitions (project_root : string) (libs : Syntax.lib list) let is_type_def dec_field = match dec_field.it.Syntax.dec.it with | Syntax.TypD (typ_id, _, _) -> Some typ_id - | Syntax.ClassD (_, typ_id, _, _, _, _, _, _) -> Some typ_id + | Syntax.ClassD (_, _, typ_id, _, _, _, _, _, _) -> Some typ_id | _ -> None in let extract_binders env (pat : Syntax.pat) = gather_pat env pat in diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 7049a6d8a6c..2c5bca1395e 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -90,8 +90,9 @@ and exp' at note = function (breakE "!" (nullE())) (* case ? v : *) (varP v) (varE v) ty).it - | S.ObjBlockE (s, (self_id_opt, _), dfs) -> - obj_block at s self_id_opt dfs note.Note.typ + | S.ObjBlockE (s, exp_opt, (self_id_opt, _), dfs) -> + let eo = Option.map exp exp_opt in + obj_block at s eo 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 @@ -117,7 +118,7 @@ and exp' at note = function | T.Shared (ss, {it = S.WildP; _} ) -> (* don't bother with ctxt pat *) (T.Shared ss, None) | T.Shared (ss, sp) -> (T.Shared ss, Some sp) in - let args, wrap, control, res_tys = to_args note.Note.typ po p in + let args, _, wrap, control, res_tys = to_args note.Note.typ po None p in let tbs' = typ_binds tbs in let vars = List.map (fun (tb : I.typ_bind) -> T.Con (tb.it.I.con, [])) tbs' in let tys = List.map (T.open_ vars) res_tys in @@ -326,12 +327,12 @@ and mut m = match m.it with | S.Const -> Ir.Const | S.Var -> Ir.Var -and obj_block at s self_id dfs obj_typ = +and obj_block at s exp_opt self_id dfs obj_typ = match s.it with | T.Object | T.Module -> build_obj at s.it self_id dfs obj_typ | T.Actor -> - build_actor at [] self_id dfs obj_typ + build_actor at [] exp_opt self_id dfs obj_typ | T.Memory -> assert false and build_field {T.lab; T.typ;_} = @@ -477,7 +478,7 @@ and export_runtime_information self_id = let scope_con2 = Cons.fresh "T2" (Abs ([], Any)) in let bind1 = typ_arg scope_con1 Scope scope_bound in let bind2 = typ_arg scope_con2 Scope scope_bound in - let gc_strategy = + let gc_strategy = let open Mo_config in let strategy = match !Flags.gc_strategy with | Flags.Default -> "default" @@ -513,26 +514,26 @@ and export_runtime_information self_id = (asyncE T.Fut bind2 (blockE ([ letD caller (primE I.ICCallerPrim []); - expD (ifE (orE + expD (ifE (orE (primE (I.RelPrim (principal, Operator.EqOp)) [varE caller; selfRefE principal]) (primE (I.OtherPrim "is_controller") [varE caller])) - (unitE()) + (unitE()) (primE (Ir.OtherPrim "trap") [textE "Unauthorized call of __motoko_runtime_information"])) ] @ - (List.map2 (fun field (_, load_info, _) -> + (List.map2 (fun field (_, load_info, _) -> letD field load_info ) fields information)) (newObjE T.Object - (List.map2 (fun field (name, _, typ) -> - { it = Ir.{name; var = id_of_var field}; at = no_region; note = typ }) + (List.map2 (fun field (name, _, typ) -> + { it = Ir.{name; var = id_of_var field}; at = no_region; note = typ }) fields information ) ret_typ)) (Con (scope_con1, [])))) )], [{ it = I.{ name = lab; var = v }; at = no_region; note = typ }]) -and build_actor at ts self_id es obj_typ = +and build_actor at ts (exp_opt : Ir.exp option) self_id es obj_typ = let candid = build_candid ts obj_typ in let fs = build_fields obj_typ in let es = List.filter (fun ef -> is_not_typD ef.it.S.dec) es in @@ -541,24 +542,100 @@ and build_actor at ts self_id es obj_typ = let pairs = List.map2 stabilize stabs ds in let idss = List.map fst pairs in let ids = List.concat idss in - let sig_ = List.sort T.compare_field - (List.map (fun (i,t) -> T.{lab = i; typ = t; src = empty_src}) ids) + let stab_fields = List.sort T.compare_field + (List.map (fun (i, t) -> T.{lab = i; typ = t; src = empty_src}) ids) in - let fields = List.map (fun (i,t) -> T.{lab = i; typ = T.Opt (T.as_immut t); src = T.empty_src}) ids in + let mem_fields = + List.map + (fun tf -> {tf with T.typ = T.Opt (T.as_immut tf.T.typ) } ) + stab_fields in let mk_ds = List.map snd pairs in - let ty = T.Obj (T.Memory, List.sort T.compare_field fields) in - let state = fresh_var "state" (T.Mut (T.Opt ty)) in - let get_state = fresh_var "getState" (T.Func(T.Local, T.Returns, [], [], [ty])) in + let mem_ty = T.Obj (T.Memory, mem_fields) in + let state = fresh_var "state" (T.Mut (T.Opt mem_ty)) in + let get_state = fresh_var "getState" (T.Func(T.Local, T.Returns, [], [], [mem_ty])) in let ds = List.map (fun mk_d -> mk_d get_state) mk_ds in + let sig_, stable_type, migration = match exp_opt with + | None -> + T.Single stab_fields, + I.{pre = mem_ty; post = mem_ty}, + primE (I.ICStableRead mem_ty) [] (* as before *) + | Some exp0 -> + let e = exp0 in + let dom, rng = T.as_mono_func_sub (e.note.Note.typ) in + let (_dom_sort, dom_fields) = T.as_obj (T.normalize dom) in + let (_rng_sort, rng_fields) = T.as_obj (T.promote rng) in + let stab_fields_pre = + List.sort T.compare_field + (dom_fields @ + (List.filter_map + (fun tf -> + match T.lookup_val_field_opt tf.T.lab dom_fields, + T.lookup_val_field_opt tf.T.lab rng_fields with + | Some _, _ (* ignore consumed (overridden) *) + | _, Some _ -> (* ignore produced (provided) *) + None + | None, None -> + (* retain others *) + Some tf) + stab_fields)) + in + let mem_fields_pre = + List.map + (fun tf -> { tf with T.typ = T.Opt (T.as_immut tf.T.typ) }) + stab_fields_pre + in + let mem_ty_pre = T.Obj (T.Memory, mem_fields_pre) in + let v = fresh_var "v" mem_ty_pre in + let v_dom = fresh_var "v_dom" dom in + let v_rng = fresh_var "v_rng" rng in + T.PrePost (stab_fields_pre, stab_fields), + I.{pre = mem_ty_pre; post = mem_ty}, + ifE (primE (I.OtherPrim "rts_in_install") []) + (primE (I.ICStableRead mem_ty) []) + (blockE [ + letD v (primE (I.ICStableRead mem_ty_pre) []); + letD v_dom + (objectE T.Object + (List.map + (fun T.{lab=i;typ=t;_} -> + let vi = fresh_var ("v_"^i) (T.as_immut t) in + (i, + switch_optE (dotE (varE v) i (T.Opt (T.as_immut t))) + (primE (Ir.OtherPrim "trap") + [textE (Printf.sprintf + "stable variable `%s` of type `%s` expected but not found" + i (T.string_of_typ t))]) + (varP vi) (varE vi) + (T.as_immut t))) + dom_fields) + dom_fields); + letD v_rng (callE e [] (varE v_dom)) + ] + (objectE T.Memory + (List.map + (fun T.{lab=i;typ=t;_} -> + i, + match T.lookup_val_field_opt i rng_fields with + | Some t -> (* produced by migration *) + optE (dotE (varE v_rng) i (T.as_immut t)) (* wrap in ?_*) + | None -> (* not produced by migration *) + match T.lookup_val_field_opt i dom_fields with + | Some t -> + (* consumed by migration (not produced) *) + nullE() (* TBR: could also reuse if compatible *) + | None -> dotE (varE v) i t) + mem_fields) + mem_fields)) + in let ds = - varD state (optE (primE (I.ICStableRead ty) [])) + varD state (optE migration) :: nary_funcD get_state [] - (let v = fresh_var "v" ty in + (let v = fresh_var "v" mem_ty in switch_optE (immuteE (varE state)) (unreachableE ()) (varP v) (varE v) - ty) + mem_ty) :: ds @ @@ -566,32 +643,32 @@ and build_actor at ts self_id es obj_typ = in let ds' = match self_id with | Some n -> - with_self n.it obj_typ ds + with_self n.it obj_typ ds | None -> ds in let meta = I.{ candid = candid; sig_ = T.string_of_stab_sig sig_} in let with_stable_vars wrap = - let vs = fresh_vars "v" (List.map (fun f -> f.T.typ) fields) in + let vs = fresh_vars "v" (List.map (fun f -> f.T.typ) mem_fields) in blockE ((match call_system_func_opt "preupgrade" es obj_typ with | Some call -> [ expD call] | None -> []) @ [letP (seqP (List.map varP vs)) (* dereference any mutable vars, option 'em all *) - (seqE (List.map (fun (i,t) -> optE (varE (var i t))) ids))]) + (seqE (List.map (fun tf -> optE (varE (var tf.T.lab tf.T.typ))) stab_fields))]) (wrap (newObjE T.Memory (List.map2 (fun f v -> { it = I.{name = f.T.lab; var = id_of_var v}; at = no_region; note = f.T.typ } - ) fields vs) - ty)) in + ) mem_fields vs) + mem_ty)) in let footprint_d, footprint_f = export_footprint self_id (with_stable_vars Fun.id) in let runtime_info_d, runtime_info_f = export_runtime_information self_id in I.(ActorE (footprint_d @ runtime_info_d @ ds', footprint_f @ runtime_info_f @ fs, { meta; - preupgrade = (primE (I.ICStableWrite ty) []); + preupgrade = (primE (I.ICStableWrite mem_ty) []); postupgrade = (match call_system_func_opt "postupgrade" es obj_typ with | Some call -> call @@ -613,7 +690,7 @@ and build_actor at ts self_id es obj_typ = | Some call -> call | None -> tupE []); stable_record = with_stable_vars (fun e -> e); - stable_type = ty; + stable_type = stable_type }, obj_typ)) @@ -812,7 +889,7 @@ and dec' at n = function end | S.VarD (i, e) -> I.VarD (i.it, e.note.S.note_typ, exp e) | S.TypD _ -> assert false - | S.ClassD (sp, id, tbs, p, _t_opt, s, self_id, dfs) -> + | S.ClassD (sp, exp_opt, id, tbs, p, _t_opt, s, self_id, dfs) -> let id' = {id with note = ()} in let sort, _, _, _, _ = Type.as_func n.S.note_typ in let op = match sp.it with @@ -833,19 +910,19 @@ and dec' at n = function | _ -> assert false in let varPat = {it = I.VarP id'.it; at = at; note = fun_typ } in - let args, wrap, control, _n_res = to_args n.S.note_typ op p in + let args, eo, wrap, control, _n_res = to_args n.S.note_typ op exp_opt p in let body = if s.it = T.Actor then let (_, _, obj_typ) = T.as_async rng_typ in let c = Cons.fresh T.default_scope_var (T.Abs ([], T.scope_bound)) in asyncE T.Fut (typ_arg c T.Scope T.scope_bound) (* TBR *) - (wrap { it = obj_block at s (Some self_id) dfs (T.promote obj_typ); + (wrap { it = obj_block at s eo (Some self_id) dfs (T.promote obj_typ); at = at; note = Note.{def with typ = obj_typ } }) (List.hd inst) else wrap - { it = obj_block at s (Some self_id) dfs rng_typ; + { it = obj_block at s eo (Some self_id) dfs rng_typ; at = at; note = Note.{ def with typ = rng_typ } } in @@ -903,7 +980,7 @@ and pat_fields pfs = List.map pat_field pfs and pat_field pf = phrase (fun S.{id; pat=p} -> I.{name=id.it; pat=pat p}) pf -and to_args typ po p : Ir.arg list * (Ir.exp -> Ir.exp) * T.control * T.typ list = +and to_args typ po exp_opt p : Ir.arg list * Ir.exp option * (Ir.exp -> Ir.exp) * T.control * T.typ list = let mergeE ds e = match e.it with @@ -932,11 +1009,14 @@ and to_args typ po p : Ir.arg list * (Ir.exp -> Ir.exp) * T.control * T.typ list | _ -> p in - (* In source, the context pattern is outside the argument pattern, - but in the IR, parameters are bound first. So if there is a context pattern, - we _must_ create fresh names for the parameters and bind the actual parameters - inside the wrapper. *) - let must_wrap = po <> None in + (* + In source, the optional shared pattern and migration expression + are outside the argument pattern, but in the IR, parameters are + bound first. So if there is either a shared pattern or migration + expression, we _must_ create fresh names for the parameters and + bind the actual parameters inside the wrapper. + *) + let must_wrap = po <> None || exp_opt <> None in let to_arg p : (Ir.arg * (Ir.exp -> Ir.exp)) = match (pat_unannot p).it with @@ -978,9 +1058,23 @@ and to_args typ po p : Ir.arg list * (Ir.exp -> Ir.exp) * T.control * T.typ list (fun e -> mergeE [letP (pat p) (tupE (List.map varE vs))] e) in + let eo, wrap_exp_opt = + match exp_opt with + | None -> + None, + fun e -> wrap e + | Some exp0 -> + let v = fresh_var "migration" exp0.note.S.note_typ in + Some (varE v), + fun e -> + mergeE + [letD v (exp exp0)] + (wrap e) + in + let wrap_po e = match po with - | None -> wrap e + | None -> wrap_exp_opt e | Some p -> let v = fresh_var "caller" T.caller in mergeE @@ -991,7 +1085,7 @@ and to_args typ po p : Ir.arg list * (Ir.exp -> Ir.exp) * T.control * T.typ list at = no_region; note = T.caller }] T.ctxt)] - (wrap e) + (wrap_exp_opt e) in let wrap_under_async e = @@ -1008,7 +1102,7 @@ and to_args typ po p : Ir.arg list * (Ir.exp -> Ir.exp) * T.control * T.typ list else wrap_po e in - args, wrap_under_async, control, res_tys + args, eo, wrap_under_async, control, res_tys type import_declaration = Ir.dec list @@ -1023,7 +1117,7 @@ let import_compiled_class (lib : S.comp_unit) wasm : import_declaration = let f = lib.note.filename in let { body; _ } = lib.it in let id = match body.it with - | S.ActorClassU (_, id, _, _, _, _, _) -> id.it + | S.ActorClassU (_, _, id, _, _, _, _, _) -> id.it | _ -> assert false in let fun_typ = T.normalize body.note.S.note_typ in @@ -1062,7 +1156,7 @@ let import_compiled_class (lib : S.comp_unit) wasm : import_declaration = (callE (varE install_actor_helper) cs' (tupE [ install_arg; - boolE ((!Mo_config.Flags.enhanced_orthogonal_persistence)); + boolE ((!Mo_config.Flags.enhanced_orthogonal_persistence)); varE wasm_blob; primE (Ir.SerializePrim ts1') [seqE (List.map varE vs)]]))) (primE (Ir.CastPrim (T.principal, t_actor)) [varE principal])) @@ -1118,12 +1212,12 @@ let transform_unit_body (u : S.comp_unit_body) : Ir.comp_unit = I.LibU ([], { it = build_obj u.at T.Module self_id fields u.note.S.note_typ; at = u.at; note = typ_note u.note}) - | S.ActorClassU (sp, typ_id, _tbs, p, _, self_id, fields) -> + | S.ActorClassU (sp, exp_opt, typ_id, _tbs, p, _, self_id, fields) -> let fun_typ = u.note.S.note_typ in let op = match sp.it with | T.Local -> None | T.Shared (_, p) -> Some p in - let args, wrap, control, _n_res = to_args fun_typ op p in + let args, eo, wrap, control, _n_res = to_args fun_typ op exp_opt p in let (ts, obj_typ) = match fun_typ with | T.Func(_s, _c, bds, ts1, [async_rng]) -> @@ -1134,7 +1228,7 @@ let transform_unit_body (u : S.comp_unit_body) : Ir.comp_unit = T.promote rng | _ -> assert false in - let actor_expression = build_actor u.at ts (Some self_id) fields obj_typ in + let actor_expression = build_actor u.at ts eo (Some self_id) fields obj_typ in let e = wrap { it = actor_expression; at = no_region; @@ -1145,8 +1239,9 @@ let transform_unit_body (u : S.comp_unit_body) : Ir.comp_unit = I.ActorU (Some args, ds, fs, u, t) | _ -> assert false end - | S.ActorU (self_id, fields) -> - let actor_expression = build_actor u.at [] self_id fields u.note.S.note_typ in + | S.ActorU (exp_opt, self_id, fields) -> + let eo = Option.map exp exp_opt in + let actor_expression = build_actor u.at [] eo 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) @@ -1182,7 +1277,7 @@ let import_unit (u : S.comp_unit) : import_declaration = raise (Invalid_argument "Desugar: Cannot import actor") | I.ActorU (Some as_, ds, fs, up, actor_t) -> let id = match body.it with - | S.ActorClassU (_, id, _, _, _, _, _) -> id.it + | S.ActorClassU (_, _, id, _, _, _, _, _) -> id.it | _ -> assert false in let s, cntrl, tbs, ts1, ts2 = T.as_func t in diff --git a/src/mo_def/arrange.ml b/src/mo_def/arrange.ml index 12cb3fecb10..6b89e198bcd 100644 --- a/src/mo_def/arrange.ml +++ b/src/mo_def/arrange.ml @@ -69,7 +69,10 @@ 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, nt, dfs) -> "ObjBlockE" $$ [obj_sort s; + | ObjBlockE (s, eo, nt, dfs) -> "ObjBlockE" $$ [obj_sort s; + (match eo with + | None -> Atom "_" + | Some e -> exp e); match nt with | None, None -> Atom "_" | None, Some t -> typ t @@ -267,8 +270,11 @@ module Make (Cfg : Config) = struct | VarD (x, e) -> "VarD" $$ [id x; exp e] | TypD (x, tp, t) -> "TypD" $$ [id x] @ List.map typ_bind tp @ [typ t] - | ClassD (sp, x, tp, p, rt, s, i', dfs) -> - "ClassD" $$ shared_pat sp :: id x :: List.map typ_bind tp @ [ + | ClassD (sp, eo, x, tp, p, rt, s, i', dfs) -> + "ClassD" $$ + shared_pat sp :: + (match eo with None -> Atom "_" | Some e -> exp e) :: + id x :: List.map typ_bind tp @ [ pat p; (match rt with None -> Atom "_" | Some t -> typ t); obj_sort s; id i' diff --git a/src/mo_def/compUnit.ml b/src/mo_def/compUnit.ml index a71aafa29de..5d8f7f3fa9a 100644 --- a/src/mo_def/compUnit.ml +++ b/src/mo_def/compUnit.ml @@ -9,20 +9,20 @@ let (@~) it at = Source.annotate Const it at let is_actor_def e = let open Source in match e.it with - | AwaitE (Type.Fut, { it = AsyncE (Type.Fut, _, {it = ObjBlockE ({ it = Type.Actor; _}, _t, _fields); _ }) ; _ }) -> true + | AwaitE (Type.Fut, { it = AsyncE (Type.Fut, _, {it = ObjBlockE ({ it = Type.Actor; _}, _eo, _t, _fields); _ }) ; _ }) -> true | _ -> false let as_actor_def e = let open Source in match e.it with - | AwaitE (Type.Fut, { it = AsyncE (Type.Fut, _, {it = ObjBlockE ({ it = Type.Actor; _}, _t, fields); note; at }) ; _ }) -> - fields, note, at + | AwaitE (Type.Fut, { it = AsyncE (Type.Fut, _, {it = ObjBlockE ({ it = Type.Actor;_}, eo, _t, fields); note; at }) ; _ }) -> + eo, fields, note, at | _ -> assert false let is_module_def e = let open Source in match e.it with - | ObjBlockE ({ it = Type.Module; _}, _, _) -> true + | ObjBlockE ({ it = Type.Module; _}, _, _, _) -> true | _ -> false (* Happens after parsing, before type checking *) @@ -42,20 +42,20 @@ let comp_unit_of_prog as_lib (prog : prog) : comp_unit = go (i :: imports) ds' (* terminal expressions *) - | [{it = ExpD ({it = ObjBlockE ({it = Type.Module; _}, _t, fields); _} as e); _}] when as_lib -> + | [{it = ExpD ({it = ObjBlockE ({it = Type.Module; _}, _eo, _t, fields); _} as e); _}] when as_lib -> finish imports { it = ModuleU (None, fields); note = e.note; at = e.at } | [{it = ExpD e; _} ] when is_actor_def e -> - let fields, note, at = as_actor_def e in - finish imports { it = ActorU (None, fields); note; at } - | [{it = ClassD (sp, tid, tbs, p, typ_ann, {it = Type.Actor;_}, self_id, fields); _} as d] -> + let eo, fields, note, at = as_actor_def e in + finish imports { it = ActorU (eo, None, fields); note; at } + | [{it = ClassD (sp, eo, tid, tbs, p, typ_ann, {it = Type.Actor;_}, self_id, fields); _} as d] -> assert (List.length tbs > 0); - finish imports { it = ActorClassU (sp, tid, tbs, p, typ_ann, self_id, fields); note = d.note; at = d.at } + finish imports { it = ActorClassU (sp, eo, tid, tbs, p, typ_ann, self_id, fields); note = d.note; at = d.at } (* let-bound terminal expressions *) - | [{it = LetD ({it = VarP i1; _}, ({it = ObjBlockE ({it = Type.Module; _}, _t, fields); _} as e), _); _}] when as_lib -> + | [{it = LetD ({it = VarP i1; _}, ({it = ObjBlockE ({it = Type.Module; _}, _eo, _t, fields); _} as e), _); _}] when as_lib -> finish imports { it = ModuleU (Some i1, fields); note = e.note; at = e.at } | [{it = LetD ({it = VarP i1; _}, e, _); _}] when is_actor_def e -> - let fields, note, at = as_actor_def e in - finish imports { it = ActorU (Some i1, fields); note; at } + let eo, fields, note, at = as_actor_def e in + finish imports { it = ActorU (eo, Some i1, fields); note; at } (* Everything else is a program *) | ds' -> @@ -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, None), fields); + it = ObjBlockE ( { it = obj_sort; at; note = () }, None, (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, None), fields); + { it = ObjBlockE ({ it = obj_sort; at; note = () }, None, (None, None), fields); at; note; }, None); at; note @@ -116,8 +116,8 @@ let decs_of_lib (cu : comp_unit) = match cub.it with | ModuleU (id_opt, fields) -> obj_decs Type.Module cub.at cub.note id_opt fields - | ActorClassU (csp, i, tbs, p, t, i', efs) -> - [{ it = ClassD (csp, i, tbs, p, t, { it = Type.Actor; at = no_region; note = ()}, i', efs); + | ActorClassU (csp, eo, i, tbs, p, t, i', efs) -> + [{ it = ClassD (csp, eo, i, tbs, p, t,{ it = Type.Actor; at = no_region; note = ()}, i', efs); at = cub.at; note = cub.note;}]; | ProgU _ diff --git a/src/mo_def/syntax.ml b/src/mo_def/syntax.ml index 5917938b628..c8aac7b0d26 100644 --- a/src/mo_def/syntax.ml +++ b/src/mo_def/syntax.ml @@ -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 * (id option * typ option) * dec_field list (* object block *) + | ObjBlockE of obj_sort * exp option * (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 *) @@ -223,7 +223,7 @@ and dec' = | VarD of id * exp (* mutable *) | TypD of typ_id * typ_bind list * typ (* type *) | ClassD of (* class *) - sort_pat * typ_id * typ_bind list * pat * typ option * obj_sort * id * dec_field list + sort_pat * exp option * typ_id * typ_bind list * pat * typ option * obj_sort * id * dec_field list (* Program (pre unit detection) *) @@ -235,7 +235,11 @@ and prog' = dec list (* Signatures (stable variables) *) type stab_sig = (stab_sig', prog_note) Source.annotated_phrase -and stab_sig' = (dec list * typ_field list) (* type declarations & stable actor fields *) +and stab_sig' = (dec list * stab_body) (* type declarations & stable actor fields *) +and stab_body = stab_body' Source.phrase (* type declarations & stable actor fields *) +and stab_body' = + | Single of typ_field list + | PrePost of typ_field list * typ_field list (* Compilation units *) @@ -245,10 +249,10 @@ and import' = pat * string * resolved_import ref type comp_unit_body = (comp_unit_body', typ_note) Source.annotated_phrase and comp_unit_body' = | ProgU of dec list (* main programs *) - | ActorU of id option * dec_field list (* main IC actor *) + | ActorU of exp option * id option * dec_field list (* main IC actor *) | ModuleU of id option * dec_field list (* module library *) | ActorClassU of (* IC actor class, main or library *) - sort_pat * typ_id * typ_bind list * pat * typ option * id * dec_field list + sort_pat * exp option * typ_id * typ_bind list * pat * typ option * id * dec_field list type comp_unit = (comp_unit', prog_note) Source.annotated_phrase and comp_unit' = { diff --git a/src/mo_frontend/definedness.ml b/src/mo_frontend/definedness.ml index e920c325390..806f0c5488e 100644 --- a/src/mo_frontend/definedness.ml +++ b/src/mo_frontend/definedness.ml @@ -90,7 +90,11 @@ let rec exp msgs e : f = match e.it with (* Uses are delayed by function expressions *) | FuncE (_, sp, tp, p, t, _, e) -> delayify ((exp msgs e /// pat msgs p) /// shared_pat msgs sp) - | ObjBlockE (s, (self_id_opt, _), dfs) -> + | ObjBlockE (s, eo, (self_id_opt, _), dfs) -> + (* TBR: treatment of eo *) + (match eo with + | None -> M.empty + | Some e1 -> eagerify (exp msgs e1)) ++ group msgs (add_self self_id_opt s (dec_fields msgs dfs)) (* The rest remaining cases just collect the uses of subexpressions: *) | LitE _ @@ -177,8 +181,14 @@ and dec msgs d = match d.it with | LetD (p, e, Some f) -> pat msgs p +++ exp msgs e +++ exp msgs f | VarD (i, e) -> (M.empty, S.singleton i.it) +++ exp msgs e | TypD (i, tp, t) -> (M.empty, S.empty) - | ClassD (csp, i, tp, p, t, s, i', dfs) -> - (M.empty, S.singleton i.it) +++ delayify ( + | ClassD (csp, eo, i, tp, p, t, s, i', dfs) -> + ((M.empty, S.singleton i.it) +++ + (* TBR: treatment of eo *) + (match eo with + | None -> M.empty + | Some e -> delayify (exp msgs e /// shared_pat msgs csp)) + ) +++ + delayify ( group msgs (add_self (Some i') s (dec_fields msgs dfs)) /// pat msgs p /// shared_pat msgs csp ) diff --git a/src/mo_frontend/effect.ml b/src/mo_frontend/effect.ml index 0ed989d7bc6..e0c3c5f0882 100644 --- a/src/mo_frontend/effect.ml +++ b/src/mo_frontend/effect.ml @@ -99,8 +99,9 @@ let rec infer_effect_exp (exp:Syntax.exp) : T.eff = map_max_effs effect_exp exps | BlockE decs -> map_max_effs effect_dec decs - | ObjBlockE (sort, _, dfs) -> - infer_effect_dec_fields dfs + | ObjBlockE (sort, eo, _, dfs) -> + let e = match eo with None -> T.Triv | Some exp -> effect_exp exp in + max_eff e (infer_effect_dec_fields dfs) | ObjE (bases, efs) -> let bases = map_max_effs effect_exp bases in let fields = infer_effect_exp_fields efs in diff --git a/src/mo_frontend/parser.mly b/src/mo_frontend/parser.mly index daaf62cf7ca..8da131177a5 100644 --- a/src/mo_frontend/parser.mly +++ b/src/mo_frontend/parser.mly @@ -108,7 +108,7 @@ let is_sugared_func_or_module dec = match dec.it with | LetD({it = VarP _; _} as pat, exp, None) -> dec.at = pat.at && pat.at = exp.at && (match exp.it with - | ObjBlockE (sort, _, _) -> + | ObjBlockE (sort, _, _, _) -> sort.it = Type.Module | FuncE _ -> true @@ -207,13 +207,13 @@ let share_dec_field default_stab (df : dec_field) = } -and objblock s id ty dec_fields = +and objblock s eo 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 -> + | 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, (id, ty), dec_fields) + ObjBlockE(s, eo, (id, ty), dec_fields) %} @@ -371,13 +371,17 @@ seplist1(X, SEP) : | ACTOR { Type.Actor @@ at $sloc } | MODULE {Type.Module @@ at $sloc } +%inline migration : + | LBRACKET e=exp(ob) RBRACKET { Some e } + | (* empty *) { None } + %inline obj_sort : - | OBJECT { (false, Type.Object @@ at $sloc) } - | po=persistent ACTOR { (po, Type.Actor @@ at $sloc) } - | MODULE { (false, Type.Module @@ at $sloc) } + | OBJECT { (false, Type.Object @@ at $sloc, None) } + | po=persistent ACTOR eo=migration { (po, Type.Actor @@ at $sloc, eo) } + | MODULE { (false, Type.Module @@ at $sloc, None) } %inline obj_sort_opt : - | (* empty *) { (false, Type.Object @@ no_region) } + | (* empty *) { (false, Type.Object @@ no_region, None) } | ds=obj_sort { ds } %inline query: @@ -887,7 +891,7 @@ dec_nonvar : | TYPE x=typ_id tps=type_typ_params_opt EQ t=typ { TypD(x, tps, t) @? at $sloc } | ds=obj_sort xf=id_opt t=annot_opt EQ? efs=obj_body - { let (persistent, s) = ds in + { let (persistent, s, eo) = ds in let sort = Type.(match s.it with | Actor -> "actor" | Module -> "module" | Object -> "object" | _ -> assert false) in @@ -899,9 +903,9 @@ dec_nonvar : AwaitE (Type.Fut, AsyncE(Type.Fut, scope_bind (anon_id "async" (at $sloc)) (at $sloc), - objblock s id t (List.map (share_dec_field default_stab) efs) @? at $sloc) + objblock s eo id t (List.map (share_dec_field default_stab) efs) @? at $sloc) @? at $sloc) @? at $sloc - else objblock s None t efs @? at $sloc + else objblock s eo None t efs @? at $sloc in let_or_exp named x e.it e.at } | sp=shared_pat_opt FUNC xf=id_opt @@ -914,7 +918,7 @@ dec_nonvar : let_or_exp named x (func_exp x.it sp tps p t is_sugar e) (at $sloc) } | sp=shared_pat_opt ds=obj_sort_opt CLASS xf=typ_id_opt tps=typ_params_opt p=pat_plain t=annot_opt cb=class_body - { let (persistent, s) = ds in + { let (persistent, s, eo) = ds in let x, dfs = cb in let dfs', tps', t' = if s.it = Type.Actor then @@ -925,7 +929,7 @@ dec_nonvar : ensure_async_typ t) else (dfs, tps, t) in - ClassD(sp, xf "class" $sloc, tps', p, t', s, x, dfs') @? at $sloc } + ClassD(sp, eo, xf "class" $sloc, tps', p, t', s, x, dfs') @? at $sloc } dec : | d=dec_var @@ -993,7 +997,22 @@ stab_field : parse_stab_sig : | start ds=seplist(typ_dec, semicolon) ACTOR LCURLY sfs=seplist(stab_field, semicolon) RCURLY { let trivia = !triv_table in - fun filename -> { it = (ds, sfs); at = at $sloc; note = { filename; trivia }} + let sigs = Single sfs in + fun filename -> { + it = (ds, {it = sigs; at = at $sloc; note = ()}); + at = at $sloc; + note = + { filename; trivia }} + } + | start ds=seplist(typ_dec, semicolon) + ACTOR LPAR LCURLY sfs_pre=seplist(stab_field, semicolon) RCURLY COMMA + LCURLY sfs_post=seplist(stab_field, semicolon) RCURLY RPAR + { let trivia = !triv_table in + let sigs = PrePost(sfs_pre, sfs_post) in + fun filename -> + { it = (ds, {it = sigs; at = at $sloc; note = ()}); + at = at $sloc; + note = { filename; trivia } } } %% diff --git a/src/mo_frontend/stability.ml b/src/mo_frontend/stability.ml index b3069f5e307..d074cf8f2f5 100644 --- a/src/mo_frontend/stability.ml +++ b/src/mo_frontend/stability.ml @@ -31,16 +31,18 @@ let error_sub s tf1 tf2 = (* Relaxed rules with enhanced orthogonal persistence for more flexible upgrades. - Mutability of stable fields can be changed because they are never aliased. - - Stable fields can be dropped, however, with a warning of potential data loss. + - Stable fields can be dropped, however, with a warning of potential data loss. For this, we give up the transitivity property of upgrades. - Upgrade transitivity means that an upgrade from a program A to B and then from B to C - should have the same effect as directly upgrading from A to C. If B discards a field - and C re-adds it, this transitivity is no longer maintained. However, rigorous upgrade + Upgrade transitivity means that an upgrade from a program A to B and then from B to C + should have the same effect as directly upgrading from A to C. If B discards a field + and C re-adds it, this transitivity is no longer maintained. However, rigorous upgrade transitivity was also not guaranteed before, since B may contain initialization logic or pre-/post-upgrade hooks that alter the stable data. *) -let match_stab_sig tfs1 tfs2 : unit Diag.result = +let match_stab_sig sig1 sig2 : unit Diag.result = + let tfs1 = post sig1 in + let tfs2 = pre sig2 in (* Assume that tfs1 and tfs2 are sorted. *) let res = Diag.with_message_store (fun s -> let rec go tfs1 tfs2 = match tfs1, tfs2 with @@ -59,7 +61,7 @@ let match_stab_sig tfs1 tfs2 : unit Diag.result = | -1 -> (* dropped field is allowed with warning, recurse on tfs1' *) warning_discard s tf1; - go tfs1' tfs2 + go tfs1' tfs2 | _ -> go tfs1 tfs2' (* new field ok, recurse on tfs2' *) ) @@ -68,8 +70,8 @@ let match_stab_sig tfs1 tfs2 : unit Diag.result = (* cross check with simpler definition *) match res with | Ok _ -> - assert (Type.match_stab_sig tfs1 tfs2); + assert (Type.match_stab_sig sig1 sig2); res | Error _ -> - assert (not (Type.match_stab_sig tfs1 tfs2)); + assert (not (Type.match_stab_sig sig1 sig2)); res diff --git a/src/mo_frontend/stability.mli b/src/mo_frontend/stability.mli index 8568f20103b..620fb4de244 100644 --- a/src/mo_frontend/stability.mli +++ b/src/mo_frontend/stability.mli @@ -6,4 +6,4 @@ open Mo_types c.f. (simpler) Types.match_sig. *) -val match_stab_sig : Type.field list -> Type.field list -> unit Diag.result +val match_stab_sig : Type.stab_sig -> Type.stab_sig -> unit Diag.result diff --git a/src/mo_frontend/static.ml b/src/mo_frontend/static.ml index 08f3871784d..5b7f69e9998 100644 --- a/src/mo_frontend/static.ml +++ b/src/mo_frontend/static.ml @@ -22,7 +22,7 @@ let err m at = at "M0014" "type" - "non-static expression in library or module") + "non-static expression in library, module or migration expression") let pat_err m at = let open Diag in @@ -44,8 +44,10 @@ let rec exp m e = match e.it with | Const -> List.iter (exp m) es | Var -> err m e.at end - | ObjBlockE (_, _, dfs) -> dec_fields m dfs - | ObjE (bases, efs) -> List.iter (exp m) bases; exp_fields m efs + | ObjBlockE (_, eo, _, dfs) -> + Option.iter (exp m) eo; dec_fields m dfs + | ObjE (bases, efs) -> + List.iter (exp m) bases; exp_fields m efs (* Variable access. Dangerous, due to loops. *) | (VarE _ | ImportE _) -> () diff --git a/src/mo_frontend/traversals.ml b/src/mo_frontend/traversals.ml index aba776444b3..8f866009d8f 100644 --- a/src/mo_frontend/traversals.ml +++ b/src/mo_frontend/traversals.ml @@ -54,8 +54,8 @@ let rec over_exp (f : exp -> exp) (exp : exp) : exp = match exp.it with f { exp with it = ArrayE (x, List.map (over_exp f) exps) } | BlockE ds -> f { exp with it = BlockE (List.map (over_dec f) ds) } - | ObjBlockE (x, t, dfs) -> - f { exp with it = ObjBlockE (x, t, List.map (over_dec_field f) dfs) } + | ObjBlockE (x, eo, t, dfs) -> + f { exp with it = ObjBlockE (x, Option.map (over_exp f) eo, t, List.map (over_dec_field f) dfs) } | ObjE (bases, efs) -> f { exp with it = ObjE (List.map (over_exp f) bases, List.map (over_exp_field f) efs) } | IfE (exp1, exp2, exp3) -> @@ -76,8 +76,8 @@ and over_dec (f : exp -> exp) (d : dec) : dec = match d.it with { d with it = VarD (x, over_exp f e)} | LetD (x, e, fail) -> { d with it = LetD (x, over_exp f e, Option.map (over_exp f) fail)} - | ClassD (sp, cid, tbs, p, t_o, s, id, dfs) -> - { d with it = ClassD (sp, cid, tbs, p, t_o, s, id, List.map (over_dec_field f) dfs)} + | ClassD (sp, eo, cid, tbs, p, t_o, s, id, dfs) -> + { d with it = ClassD (sp, Option.map (over_exp f) eo, cid, tbs, p, t_o, s, id, List.map (over_dec_field f) dfs)} and over_dec_field (f : exp -> exp) (df : dec_field) : dec_field = { df with it = { df.it with dec = over_dec f df.it.dec } } diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index eb0c2c36051..99eb56ee045 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -1042,7 +1042,7 @@ let rec is_explicit_exp e = | ObjE (bases, efs) -> List.(for_all is_explicit_exp bases && for_all (fun (ef : exp_field) -> is_explicit_exp ef.it.exp) efs) - | ObjBlockE (_, _, dfs) -> + | ObjBlockE (_, _e_opt, _, dfs) -> List.for_all (fun (df : dec_field) -> is_explicit_dec df.it.dec) dfs | ArrayE (_, es) -> List.exists is_explicit_exp es | SwitchE (e1, cs) -> @@ -1059,7 +1059,7 @@ and is_explicit_dec d = match d.it with | ExpD e | LetD (_, e, _) | VarD (_, e) -> is_explicit_exp e | TypD _ -> true - | ClassD (_, _, _, p, _, _, _, dfs) -> + | ClassD (_, _, _, _, p, _, _, _, dfs) -> is_explicit_pat p && List.for_all (fun (df : dec_field) -> is_explicit_dec df.it.dec) dfs @@ -1358,7 +1358,8 @@ and infer_exp'' env exp : T.typ = "expected tuple type, but expression produces type%a" display_typ_expand t1 ) - | ObjBlockE (obj_sort, typ_opt, dec_fields) -> + | ObjBlockE (obj_sort, exp_opt, typ_opt, dec_fields) -> + let _typ_opt = infer_migration env exp_opt in if obj_sort.it = T.Actor then begin error_in [Flags.WASIMode; Flags.WasmMode] env exp.at "M0068" "actors are not supported"; @@ -1367,7 +1368,7 @@ and infer_exp'' env exp : T.typ = error_in [Flags.ICMode; Flags.RefMode] env exp.at "M0069" "non-toplevel actor; an actor can only be declared at the toplevel of a program" | _ -> () - end; + end; let env' = if obj_sort.it = T.Actor then { env with @@ -1375,7 +1376,7 @@ and infer_exp'' env exp : T.typ = async = C.SystemCap C.top_cap } else env in - let t = infer_obj env' obj_sort.it dec_fields exp.at in + let t = infer_obj env' obj_sort.it exp_opt dec_fields exp.at in begin match env.pre, typ_opt with | false, (_, Some typ) -> let t' = check_typ env' typ in @@ -2426,7 +2427,7 @@ and pub_dec src dec xs : visibility_env = | ExpD _ -> xs | LetD (pat, _, _) -> pub_pat src pat xs | VarD (id, _) -> pub_val_id src id xs - | ClassD (_, id, _, _, _, _, _, _) -> + | ClassD (_, _, id, _, _, _, _, _, _) -> pub_val_id src {id with note = ()} (pub_typ_id src id xs) | TypD (id, _, _) -> pub_typ_id src id xs @@ -2494,7 +2495,7 @@ and is_typ_dec dec : bool = match dec.it with | TypD _ -> true | _ -> false -and infer_obj env s dec_fields at : T.typ = +and infer_obj env s exp_opt dec_fields at : T.typ = let private_fields = let scope = List.filter (fun field -> is_private field.it.vis) dec_fields |> List.map (fun field -> field.it.dec) @@ -2543,7 +2544,8 @@ and infer_obj env s dec_fields at : T.typ = end; if s = T.Module then Static.dec_fields env.msgs dec_fields; check_system_fields env s scope tfs dec_fields; - check_stab env s scope dec_fields; + let stab_tfs = check_stab env s scope dec_fields in + check_migration env stab_tfs exp_opt end; t @@ -2587,6 +2589,92 @@ and stable_pat pat = | AnnotP (pat', _) -> stable_pat pat' | _ -> false +and infer_migration env exp_opt = + match exp_opt with + | Some exp -> + Some (infer_exp_promote { env with async = C.NullCap; rets = None; labs = T.Env.empty } exp) + | None -> None + +and check_migration env (stab_tfs : T.field list) exp_opt = + match exp_opt with + | None -> () + | Some exp -> + Static.exp env.msgs exp; (* preclude side effects *) + let check_fields desc typ = + match typ with + | T.Obj(T.Object, tfs) -> + if not (T.stable typ) then + local_error env exp.at "M0201" + "expected stable type, but migration expression %s non-stable type%a" + desc + display_typ_expand typ; + tfs + | _ -> + local_error env exp.at "M0202" + "expected object type, but migration expression %s non-object type%a" + desc + display_typ_expand typ; + [] + in + let typ = exp.note.note_typ in + let (dom_tfs, rng_tfs) = + try + let sort, tbs, t_dom, t_rng = T.as_func_sub T.Local 0 typ in + if sort <> T.Local || tbs <> [] then raise (Invalid_argument ""); + (check_fields "consumes" (T.normalize t_dom), + check_fields "produces" (T.promote t_rng)) + with Invalid_argument _ -> + local_error env exp.at "M0203" + "expected non-generic, local function type, but migration expression produces type%a" + display_typ_expand typ; + ([], []) + in + List.iter + (fun tf -> + match T.lookup_val_field_opt tf.T.lab rng_tfs with + | None -> () + | Some typ -> + if not (T.sub (T.as_immut typ) (T.as_immut tf.T.typ)) then + local_error env exp.at "M0204" + "migration expression produces field `%s` of type %a\n, not the expected type%a" + tf.T.lab + display_typ_expand typ + display_typ_expand tf.T.typ) stab_tfs; + (* Construct the pre signature *) + let pre_tfs = List.sort T.compare_field + dom_tfs @ + (List.filter_map + (fun tf -> + match T.lookup_val_field_opt tf.T.lab dom_tfs, T.lookup_val_field_opt tf.T.lab rng_tfs with + | _, Some _ (* ignore consumed (overridden) *) + | Some _, _ -> (* ignore produced (provided) *) + None + | None, None -> + (* retain others *) + Some tf) + stab_tfs) + in + (* Check for duplicates and hash collisions in pre-signature *) + let pre_ids = List.map (fun tf -> T.{it = tf.lab; at = tf.src.region; note = ()}) pre_tfs in + check_ids env "pre actor type" "stable variable" pre_ids; + (* Reject any fields in range not in post signature (unintended data loss) *) + let stab_ids = List.map (fun tf -> tf.T.lab) stab_tfs in + List.iter (fun T.{lab;typ;src} -> + match typ with + | T.Typ c -> () + | _ -> + match T.lookup_val_field_opt lab stab_tfs with + | Some _ -> () + | None -> + local_error env (Option.get exp_opt).at "M0205" + "migration expression produces unexpected field `%s` of type %a\n%s\n%s" + lab + display_typ_expand typ + (Suggest.suggest_id "field" lab stab_ids) + "The actor should declare a corresponding `stable` field.") + rng_tfs + + and check_stab env sort scope dec_fields = let check_stable id at = match T.Env.find_opt id scope.Scope.val_env with @@ -2596,7 +2684,7 @@ and check_stab env sort scope dec_fields = if not (T.stable t1) then local_error env at "M0131" "variable %s is declared stable but has non-stable type%a" id - display_typ t1 + display_typ t1; in let idss = List.map (fun df -> match sort, df.it.stab, df.it.dec.it with @@ -2619,8 +2707,16 @@ and check_stab env sort scope dec_fields = [] | _ -> []) dec_fields in - check_ids env "actor type" "stable variable" (List.concat idss) - + let ids = List.concat idss in + check_ids env "actor type" "stable variable" ids; + List.sort T.compare_field + (List.map + (fun id -> + let typ, _, _ = T.Env.find id.it scope.Scope.val_env in + T.{ lab = id.it; + typ; + src = { depr = None; region = id.at }}) + ids) (* Blocks and Declarations *) @@ -2633,7 +2729,7 @@ and infer_block env decs at check_unused : T.typ * Scope.scope = | Flags.(ICMode | RefMode) -> List.fold_left (fun ve' dec -> match dec.it with - | ClassD(_, id, _, _, _, { it = T.Actor; _}, _, _) -> + | ClassD(_, _, id, _, _, _, { it = T.Actor; _}, _, _) -> T.Env.mapi (fun id' (typ, at, kind, avl) -> (typ, at, kind, if id' = id.it then Unavailable else avl)) ve' | _ -> ve') env'.vals decs @@ -2682,11 +2778,12 @@ and infer_dec env dec : T.typ = | VarD (_, exp) -> if not env.pre then ignore (infer_exp env exp); T.unit - | ClassD (shared_pat, id, typ_binds, pat, typ_opt, obj_sort, self_id, dec_fields) -> + | ClassD (shared_pat, exp_opt, id, typ_binds, pat, typ_opt, obj_sort, self_id, dec_fields) -> let (t, _, _, _) = T.Env.find id.it env.vals in if not env.pre then begin let c = T.Env.find id.it env.typs in let ve0 = check_class_shared_pat env shared_pat obj_sort in + let _typ_opt = infer_migration (adjoin_vals env ve0) exp_opt in let cs, tbs, te, ce = check_typ_binds env typ_binds in let env' = adjoin_typs env te ce in let in_actor = obj_sort.it = T.Actor in @@ -2711,7 +2808,7 @@ and infer_dec env dec : T.typ = } in let initial_usage = enter_scope env''' in - let t' = infer_obj { env''' with check_unused = true } obj_sort.it dec_fields dec.at in + let t' = infer_obj { env''' with check_unused = true } obj_sort.it exp_opt dec_fields dec.at in leave_scope env ve initial_usage; match typ_opt, obj_sort.it with | None, _ -> () @@ -2808,8 +2905,8 @@ and gather_dec env scope dec : Scope.t = (* TODO: generalize beyond let = *) | LetD ( {it = VarP id; _}, - ( {it = ObjBlockE (obj_sort, _, dec_fields); at; _} - | {it = AwaitE (_,{ it = AsyncE (_, _, {it = ObjBlockE ({ it = Type.Actor; _} as obj_sort, _, dec_fields); at; _}) ; _ }); _ }), + ( {it = ObjBlockE (obj_sort, _, _, dec_fields); at; _} + | {it = AwaitE (_,{ it = AsyncE (_, _, {it = ObjBlockE ({ it = Type.Actor; _} as obj_sort, _, _, dec_fields); at; _}) ; _ }); _ }), _ ) -> let decs = List.map (fun df -> df.it.dec) dec_fields in @@ -2827,7 +2924,7 @@ and gather_dec env scope dec : Scope.t = } | LetD (pat, _, _) -> Scope.adjoin_val_env scope (gather_pat env scope.Scope.val_env pat) | VarD (id, _) -> Scope.adjoin_val_env scope (gather_id env scope.Scope.val_env id Scope.Declaration) - | TypD (id, binds, _) | ClassD (_, id, binds, _, _, _, _, _) -> + | TypD (id, binds, _) | ClassD (_, _, id, binds, _, _, _, _, _) -> let open Scope in if T.Env.mem id.it scope.typ_env then error_duplicate env "type " id; @@ -2896,8 +2993,8 @@ and infer_dec_typdecs env dec : Scope.t = (* TODO: generalize beyond let = *) | LetD ( {it = VarP id; _}, - ( {it = ObjBlockE (obj_sort, _t, dec_fields); at; _} - | {it = AwaitE (_, { it = AsyncE (_, _, {it = ObjBlockE ({ it = Type.Actor; _} as obj_sort, _t, dec_fields); at; _}) ; _ }); _ }), + ( {it = ObjBlockE (obj_sort, _exp_opt, _t, dec_fields); at; _} + | {it = AwaitE (_, { it = AsyncE (_, _, {it = ObjBlockE ({ it = Type.Actor; _} as obj_sort, _exp_opt, _t, dec_fields); at; _}) ; _ }); _ }), _ ) -> let decs = List.map (fun {it = {vis; dec; _}; _} -> dec) dec_fields in @@ -2929,7 +3026,8 @@ and infer_dec_typdecs env dec : Scope.t = typ_env = T.Env.singleton id.it c; con_env = infer_id_typdecs env dec.at id c k; } - | ClassD (shared_pat, id, binds, pat, _typ_opt, obj_sort, self_id, dec_fields) -> + | ClassD (shared_pat, exp_opt, id, binds, pat, _typ_opt, obj_sort, self_id, dec_fields) -> + (*TODO exp_opt *) let c = T.Env.find id.it env.typs in let ve0 = check_class_shared_pat {env with pre = true} shared_pat obj_sort in let cs, tbs, te, ce = check_typ_binds {env with pre = true} binds in @@ -2945,7 +3043,7 @@ and infer_dec_typdecs env dec : Scope.t = async = async_cap; in_actor} in - let t = infer_obj { env'' with check_unused = false } obj_sort.it dec_fields dec.at in + let t = infer_obj { env'' with check_unused = false } obj_sort.it exp_opt dec_fields dec.at in let k = T.Def (T.close_binds class_cs class_tbs, T.close class_cs t) in check_closed env id k dec.at; Scope.{ empty with @@ -2982,8 +3080,8 @@ and infer_dec_valdecs env dec : Scope.t = (* TODO: generalize beyond let = *) | LetD ( {it = VarP id; _} as pat, - ( {it = ObjBlockE (obj_sort, _t, dec_fields); at; _} - | {it = AwaitE (_, { it = AsyncE (_, _, {it = ObjBlockE ({ it = Type.Actor; _} as obj_sort, _t, dec_fields); at; _}) ; _ }); _ }), + ( {it = ObjBlockE (obj_sort, _exp_opt, _t, dec_fields); at; _} + | {it = AwaitE (_, { it = AsyncE (_, _, {it = ObjBlockE ({ it = Type.Actor; _} as obj_sort, _exp_opt, _t, dec_fields); at; _}) ; _ }); _ }), _ ) -> let decs = List.map (fun df -> df.it.dec) dec_fields in @@ -3012,7 +3110,7 @@ and infer_dec_valdecs env dec : Scope.t = typ_env = T.Env.singleton id.it c; con_env = T.ConSet.singleton c; } - | ClassD (_shared_pat, id, typ_binds, pat, _, obj_sort, _, _) -> + | ClassD (_shared_pat, _exp_opt, id, typ_binds, pat, _, obj_sort, _, _) -> if obj_sort.it = T.Actor then begin error_in [Flags.WASIMode; Flags.WasmMode] env dec.at "M0138" "actor classes are not supported"; if not env.in_prog then @@ -3066,7 +3164,7 @@ let is_actor_dec d = match d.it with | ExpD e | LetD (_, e, _) -> CompUnit.is_actor_def e - | ClassD (shared_pat, id, typ_binds, pat, typ_opt, obj_sort, self_id, dec_fields) -> + | ClassD (shared_pat, exp_opt, id, typ_binds, pat, typ_opt, obj_sort, self_id, dec_fields) -> obj_sort.it = T.Actor | _ -> false @@ -3121,7 +3219,7 @@ let check_lib scope pkg_opt lib : Scope.t Diag.result = warn env r "M0142" "deprecated syntax: an imported library should be a module or named actor class" end; typ - | ActorClassU (sp, id, tbs, p, _, self_id, dec_fields) -> + | ActorClassU (sp, exp_opt, id, tbs, p, _, self_id, dec_fields) -> if is_anon_id id then error env cub.at "M0143" "bad import: imported actor class cannot be anonymous"; let cs = List.map (fun tb -> Option.get tb.note) tbs in @@ -3153,7 +3251,7 @@ let check_lib scope pkg_opt lib : Scope.t Diag.result = ) lib ) -let check_stab_sig scope sig_ : (T.field list) Diag.result = +let check_stab_sig scope sig_ : T.stab_sig Diag.result = Diag.with_message_store (fun msgs -> recover_opt @@ -3161,25 +3259,31 @@ let check_stab_sig scope sig_ : (T.field list) Diag.result = let env = env_of_scope msgs scope in let scope = infer_block_decs env decs sig_.at in let env1 = adjoin env scope in - check_ids env "object type" "field" - (List.filter_map (fun (field : typ_field) -> - match field.it with ValF (id, _, _) -> Some id | _ -> None) - sfs); - check_ids env "object type" "type field" - (List.filter_map (fun (field : typ_field) -> - match field.it with TypF (id, _, _) -> Some id | _ -> None) - sfs); - let _ = List.map (check_typ_field {env1 with pre = true} T.Object) sfs in - let fs = List.map (check_typ_field {env1 with pre = false} T.Object) sfs in - List.iter (fun (field : Syntax.typ_field) -> - match field.it with - | TypF _ -> () - | ValF (id, typ, _) -> - if not (T.stable typ.note) then - error env id.at "M0131" "variable %s is declared stable but has non-stable type%a" - id.it - display_typ typ.note) - sfs; - List.sort T.compare_field fs + let check_fields sfs = + check_ids env "object type" "field" + (List.filter_map (fun (field : typ_field) -> + match field.it with ValF (id, _, _) -> Some id | _ -> None) + sfs); + check_ids env "object type" "type field" + (List.filter_map (fun (field : typ_field) -> + match field.it with TypF (id, _, _) -> Some id | _ -> None) + sfs); + let _ = List.map (check_typ_field {env1 with pre = true} T.Object) sfs in + let fs = List.map (check_typ_field {env1 with pre = false} T.Object) sfs in + List.iter (fun (field : Syntax.typ_field) -> + match field.it with + | TypF _ -> () + | ValF (id, typ, _) -> + if not (T.stable typ.note) then + error env id.at "M0131" "variable %s is declared stable but has non-stable type%a" + id.it + display_typ typ.note) + sfs; + List.sort T.compare_field fs + in + match sfs.it with + | Single sfs -> T.Single (check_fields sfs) + | PrePost (pre,post) -> + T.PrePost (check_fields pre, check_fields post) ) sig_.it ) diff --git a/src/mo_frontend/typing.mli b/src/mo_frontend/typing.mli index be0d8b40b7e..2bb01f47ea7 100644 --- a/src/mo_frontend/typing.mli +++ b/src/mo_frontend/typing.mli @@ -10,6 +10,6 @@ val infer_prog : ?viper_mode:bool -> scope -> string option -> Async_cap.async_c val check_lib : scope -> string option -> Syntax.lib -> scope Diag.result val check_actors : ?viper_mode:bool -> ?check_actors:bool -> scope -> Syntax.prog list -> unit Diag.result -val check_stab_sig : scope -> Syntax.stab_sig -> (field list) Diag.result +val check_stab_sig : scope -> Syntax.stab_sig -> Type.stab_sig Diag.result val heartbeat_type : typ diff --git a/src/mo_interpreter/interpret.ml b/src/mo_interpreter/interpret.ml index ec3a30736cb..6bb27f3f486 100644 --- a/src/mo_interpreter/interpret.ml +++ b/src/mo_interpreter/interpret.ml @@ -491,7 +491,8 @@ and interpret_exp_mut env exp (k : V.value V.cont) = | _ -> assert false) | ProjE (exp1, n) -> interpret_exp env exp1 (fun v1 -> k (List.nth (V.as_tup v1) n)) - | ObjBlockE (obj_sort, (self_id_opt, _), dec_fields) -> + | ObjBlockE (obj_sort, _exp_opt, (self_id_opt, _), dec_fields) -> + (* NB: we ignore the migration expression _exp_opt *) interpret_obj env obj_sort.it self_id_opt dec_fields k | ObjE (exp_bases, exp_fields) -> let fields fld_env = interpret_exp_fields env exp_fields fld_env (fun env -> k (V.Obj env)) in @@ -946,7 +947,7 @@ and declare_dec dec : val_env = | TypD _ -> V.Env.empty | LetD (pat, _, _) -> declare_pat pat | VarD (id, _) -> declare_id id - | ClassD (_, id, _, _, _, _, _, _) -> declare_id {id with note = ()} + | ClassD (_, _eo, id, _, _, _, _, _, _) -> declare_id {id with note = ()} and declare_decs decs ve : val_env = match decs with @@ -976,7 +977,8 @@ and interpret_dec env dec (k : V.value V.cont) = ) | TypD _ -> k V.unit - | ClassD (shared_pat, id, _typbinds, pat, _typ_opt, obj_sort, id', dec_fields) -> + | ClassD (shared_pat, _eo, id, _typbinds, pat, _typ_opt, obj_sort, id', dec_fields) -> + (* NB: we ignore the migration expression _eo *) let f = interpret_func env id.it shared_pat pat (fun env' k' -> if obj_sort.it <> T.Actor then let env'' = adjoin_vals env' (declare_id id') in @@ -1088,7 +1090,8 @@ let import_lib env lib = match cub.it with | Syntax.ModuleU _ -> Fun.id - | Syntax.ActorClassU (_sp, id, _tbs, _p, _typ, _self_id, _dec_fields) -> + | Syntax.ActorClassU (_sp, _eo, id, _tbs, _p, _typ, _self_id, _dec_fields) -> + (* NB: we ignore the migration expression _eo *) fun v -> V.Obj (V.Env.from_list [ (id.it, v); ("system", diff --git a/src/mo_types/type.ml b/src/mo_types/type.ml index 3c0b25609ff..32e8cae27d8 100644 --- a/src/mo_types/type.ml +++ b/src/mo_types/type.ml @@ -72,6 +72,11 @@ and kind = let empty_src = {depr = None; region = Source.no_region} +(* Stable signatures *) +type stab_sig = + | Single of field list + | PrePost of field list * field list + (* Efficient comparison *) let tag_prim = function | Null -> 0 @@ -1754,11 +1759,15 @@ and pp_kind ppf k = pp_kind' vs ppf k and pp_stab_sig ppf sig_ = + let all_fields = match sig_ with + | Single tfs -> tfs + | PrePost (pre, post) -> pre @ post + in let cs = List.fold_right (cons_field false) (* false here ^ means ignore unreferenced Typ c components that would produce unreferenced bindings when unfolded *) - sig_ ConSet.empty in + all_fields ConSet.empty in let vs = vs_of_cs cs in let ds = let cs' = ConSet.filter (fun c -> @@ -1776,15 +1785,22 @@ and pp_stab_sig ppf sig_ = typ = Typ c; src = empty_src }) ds) in - let pp_stab_fields ppf sig_ = - fprintf ppf "@[%s{@;<0 0>%a@;<0 -2>}@]" - (string_of_obj_sort Actor) - (pp_print_list ~pp_sep:semi (pp_stab_field vs)) sig_ + let pp_stab_actor ppf sig_ = + match sig_ with + | Single tfs -> + fprintf ppf "@[%s{@;<0 0>%a@;<0 -2>}@]" + (string_of_obj_sort Actor) + (pp_print_list ~pp_sep:semi (pp_stab_field vs)) tfs + | PrePost (pre, post) -> + fprintf ppf "@[%s({@;<0 0>%a@;<0 -2>}, {@;<0 0>%a@;<0 -2>}) @]" + (string_of_obj_sort Actor) + (pp_print_list ~pp_sep:semi (pp_stab_field vs)) pre + (pp_print_list ~pp_sep:semi (pp_stab_field vs)) post in fprintf ppf "@[%a%a%a;@]" - (pp_print_list ~pp_sep:semi (pp_field vs)) fs - (if fs = [] then fun ppf () -> () else semi) () - pp_stab_fields sig_ + (pp_print_list ~pp_sep:semi (pp_field vs)) fs + (if fs = [] then fun ppf () -> () else semi) () + pp_stab_actor sig_ let rec pp_typ_expand' vs ppf t = match t with @@ -1850,7 +1866,20 @@ let _ = str := string_of_typ (* Stable signatures *) -let rec match_stab_sig tfs1 tfs2 = +let pre = function + | Single tfs -> tfs + | PrePost (tfs, _) -> tfs + +let post = function + | Single tfs -> tfs + | PrePost (_, tfs) -> tfs + +let rec match_stab_sig sig1 sig2 = + let tfs1 = post sig1 in + let tfs2 = pre sig2 in + match_stab_fields tfs1 tfs2 + +and match_stab_fields tfs1 tfs2 = (* Assume that tfs1 and tfs2 are sorted. *) match tfs1, tfs2 with | [], _ | _, [] -> @@ -1860,16 +1889,19 @@ let rec match_stab_sig tfs1 tfs2 = (match compare_field tf1 tf2 with | 0 -> sub (as_immut tf1.typ) (as_immut tf2.typ) && - match_stab_sig tfs1' tfs2' + match_stab_fields tfs1' tfs2' | -1 -> (* dropped field ok *) - match_stab_sig tfs1' tfs2 + match_stab_fields tfs1' tfs2 | _ -> (* new field ok *) - match_stab_sig tfs1 tfs2' + match_stab_fields tfs1 tfs2' ) -let string_of_stab_sig fields : string = +let string_of_stab_sig stab_sig : string = let module Pretty = MakePretty(ParseableStamps) in - "// Version: 1.0.0\n" ^ - Format.asprintf "@[%a@]@\n" (fun ppf -> Pretty.pp_stab_sig ppf) fields + (match stab_sig with + | Single _ -> "// Version: 1.0.0\n" + | PrePost _ -> "// Version: 2.0.0\n") ^ + Format.asprintf "@[%a@]@\n" (fun ppf -> Pretty.pp_stab_sig ppf) stab_sig + diff --git a/src/mo_types/type.mli b/src/mo_types/type.mli index b94c9eb7468..cba21c47984 100644 --- a/src/mo_types/type.mli +++ b/src/mo_types/type.mli @@ -262,9 +262,16 @@ val scope_bind : bind (* Signatures *) -val match_stab_sig : field list -> field list -> bool +type stab_sig = + | Single of field list + | PrePost of field list * field list -val string_of_stab_sig : field list -> string +val pre : stab_sig -> field list +val post : stab_sig -> field list + +val match_stab_sig : stab_sig -> stab_sig -> bool + +val string_of_stab_sig : stab_sig -> string val motoko_runtime_information_type : typ diff --git a/src/mo_values/prim.ml b/src/mo_values/prim.ml index a9cd01ad965..d82e90cafa3 100644 --- a/src/mo_values/prim.ml +++ b/src/mo_values/prim.ml @@ -216,6 +216,8 @@ let prim trap = in k (Text str) | "print" -> fun _ v k -> Printf.printf "%s\n%!" (as_text v); k unit | "trap" -> fun _ v k -> trap.trap ("explicit trap: " ^ (as_text v)) + | "rts_in_install" -> + fun _ v k -> as_unit v; k (Bool true) (* no upgrades in interpreters *) | "rts_version" -> fun _ v k -> as_unit v; k (Text "0.1") | ( "rts_memory_size" | "rts_heap_size" diff --git a/src/pipeline/pipeline.ml b/src/pipeline/pipeline.ml index 02c4ad7815c..5ba844e2ee1 100644 --- a/src/pipeline/pipeline.ml +++ b/src/pipeline/pipeline.ml @@ -284,7 +284,13 @@ let validate_stab_sig s : unit Diag.result = let* p2 = parse_stab_sig s name in let* s1 = Typing.check_stab_sig initial_stat_env0 p1 in let* s2 = Typing.check_stab_sig initial_stat_env0 p2 in - Stability.match_stab_sig s1 s2 + Type.(match s1, s2 with + | Single s1, Single s2 -> + Stability.match_stab_sig (Single s1) (Single s2) + | PrePost (pre1, post1), PrePost (pre2, post2) -> + let* () = Stability.match_stab_sig (Single pre1) (Single pre2) in + Stability.match_stab_sig (Single post1) (Single post2) + | _, _ -> assert false) (* The prim module *) diff --git a/src/viper/prep.ml b/src/viper/prep.ml index b2d019eaecf..7155c85aecc 100644 --- a/src/viper/prep.ml +++ b/src/viper/prep.ml @@ -137,10 +137,11 @@ let mono_dec_fields (dfs : dec_field list) : dec_field list = let prep_unit (u : comp_unit) : comp_unit = let { imports; body } = u.it in match body.it with - | ActorU(id_opt, decs) -> + | ActorU(id_opt, exp_opt, decs) -> + (* TODO exp_opt (also below) *) let decs' = mono_dec_fields decs in (* let _ = List.map (fun g -> print_endline (string_of_mono_goal g)) goals in *) - let body' = ActorU(id_opt, decs') in + let body' = ActorU(id_opt, exp_opt, decs') in (* let _ = List.map (fun d -> print_endline (Wasm.Sexpr.to_string 80 (Arrange.dec_field d))) decs' in *) { u with it = {imports; body = { body with it = body' } } } | _ -> u diff --git a/src/viper/trans.ml b/src/viper/trans.ml index ef19b23bd57..4df72efd8e0 100644 --- a/src/viper/trans.ml +++ b/src/viper/trans.ml @@ -232,7 +232,8 @@ let rec unit reqs (u : M.comp_unit) : prog Diag.result = and unit' reqs (u : M.comp_unit) : prog = let { M.imports; M.body } = u.it in match body.it with - | M.ActorU(id_opt, decs) -> + | M.ActorU(eo_, id_opt, decs) -> + (* TODO eo *) let ctxt = { self = None; imports = tr_imports imports; diff --git a/src/viper/traversals.ml b/src/viper/traversals.ml index bdf02154c6e..0d8b279bcf8 100644 --- a/src/viper/traversals.ml +++ b/src/viper/traversals.ml @@ -48,7 +48,7 @@ let rec over_exp (v : visitor) (exp : exp) : exp = | TupE exps -> { exp with it = TupE (List.map (over_exp v) exps) } | ArrayE (x, exps) -> { exp with it = ArrayE (x, List.map (over_exp v) exps) } | BlockE ds -> { exp with it = BlockE (List.map (over_dec v) ds) } - | ObjBlockE (x, (n, t), dfs) -> { exp with it = ObjBlockE (x, (n, Option.map (over_typ v) t), List.map (over_dec_field v) dfs) } + | ObjBlockE (x, eo, (n, t), dfs) -> { exp with it = ObjBlockE (x, Option.map (over_exp v) eo, (n, Option.map (over_typ v) t), List.map (over_dec_field v) dfs) } | ObjE (bases, efs) -> { exp with it = ObjE (List.map (over_exp v) bases, List.map (over_exp_field v) efs) } | IfE (exp1, exp2, exp3) -> { exp with it = IfE(over_exp v exp1, over_exp v exp2, over_exp v exp3) } | TryE (exp1, cases, exp2) -> { exp with it = TryE (over_exp v exp1, List.map (over_case v) cases, Option.map (over_exp v) exp2) } @@ -76,7 +76,7 @@ and over_dec (v : visitor) (d : dec) : dec = | ExpD e -> { d with it = ExpD (over_exp v e)} | VarD (x, e) -> { d with it = VarD (x, over_exp v e)} | LetD (p, e, fail) -> { d with it = LetD (over_pat v p, over_exp v e, Option.map (over_exp v) fail)} - | ClassD (sp, cid, tbs, p, t_o, s, id, dfs) -> { d with it = ClassD (sp, cid, tbs, over_pat v p, Option.map (over_typ v) t_o, s, id, List.map (over_dec_field v) dfs)}) + | ClassD (sp, e_o, cid, tbs, p, t_o, s, id, dfs) -> { d with it = ClassD (sp, Option.map (over_exp v) e_o, cid, tbs, over_pat v p, Option.map (over_typ v) t_o, s, id, List.map (over_dec_field v) dfs)}) and over_dec_field (v : visitor) (df : dec_field) : dec_field = { df with it = { df.it with dec = over_dec v df.it.dec } } diff --git a/test/cmp/files/version0.most b/test/cmp/files/version0.most new file mode 100644 index 00000000000..8fdff56881b --- /dev/null +++ b/test/cmp/files/version0.most @@ -0,0 +1,6 @@ +// Version: 1.0.0 +actor { + stable var one : [var Nat]; + stable var two : [var Text]; + stable var zero : Nat +}; diff --git a/test/cmp/files/version1.most b/test/cmp/files/version1.most new file mode 100644 index 00000000000..9836f96fc95 --- /dev/null +++ b/test/cmp/files/version1.most @@ -0,0 +1,9 @@ +// Version: 2.0.0 +actor ({ + stable var one : [var Nat]; + stable var two : [var Text]; + stable var zero : Nat +}, { + stable var three : [var (Nat, Text)]; + stable var zero : Nat +}) ; diff --git a/test/cmp/files/version2.most b/test/cmp/files/version2.most new file mode 100644 index 00000000000..d8e0d5a7405 --- /dev/null +++ b/test/cmp/files/version2.most @@ -0,0 +1,8 @@ +// Version: 2.0.0 +actor ({ + stable var three : [var (Nat, Text)]; + stable var zero : Nat +}, { + stable var four : [var (Nat, Text)]; + stable var zero : Nat +}) ; diff --git a/test/cmp/files/version3.most b/test/cmp/files/version3.most new file mode 100644 index 00000000000..3611fe2a391 --- /dev/null +++ b/test/cmp/files/version3.most @@ -0,0 +1,8 @@ +// Version: 2.0.0 +actor ({ + stable var four : [var (Nat, Text)]; + stable var zero : Nat +}, { + stable var four : [var (Text, Nat)]; + stable var zero : Nat +}) ; diff --git a/test/cmp/files/version4.most b/test/cmp/files/version4.most new file mode 100644 index 00000000000..28d509a78d7 --- /dev/null +++ b/test/cmp/files/version4.most @@ -0,0 +1,8 @@ +// Version: 2.0.0 +actor ({ + stable var four : [var (Text, Nat)]; + stable var zero : Nat +}, { + stable var four : [var (Text, Nat, Bool)]; + stable var zero : Nat +}) ; diff --git a/test/cmp/files/version5.most b/test/cmp/files/version5.most new file mode 100644 index 00000000000..736dab96745 --- /dev/null +++ b/test/cmp/files/version5.most @@ -0,0 +1,5 @@ +// Version: 1.0.0 +actor { + stable var four : [var (Text, Nat, Bool)]; + stable var zero : Nat +}; diff --git a/test/cmp/neg-version4-3.cmp b/test/cmp/neg-version4-3.cmp new file mode 100644 index 00000000000..f3b33ded17f --- /dev/null +++ b/test/cmp/neg-version4-3.cmp @@ -0,0 +1 @@ +files/version4.most files/version3.most diff --git a/test/cmp/neg-version5-4.cmp b/test/cmp/neg-version5-4.cmp new file mode 100644 index 00000000000..994972df033 --- /dev/null +++ b/test/cmp/neg-version5-4.cmp @@ -0,0 +1 @@ +files/version5.most files/version4.most diff --git a/test/cmp/ok/neg-version4-3.cmp.ok b/test/cmp/ok/neg-version4-3.cmp.ok new file mode 100644 index 00000000000..e0051a9dc15 --- /dev/null +++ b/test/cmp/ok/neg-version4-3.cmp.ok @@ -0,0 +1,5 @@ +(unknown location): Compatibility error [M0170], stable variable four of previous type + var [var (Text, Nat, Bool)] +cannot be consumed at new type + var [var (Nat, Text)] +FALSE diff --git a/test/cmp/ok/neg-version5-4.cmp.ok b/test/cmp/ok/neg-version5-4.cmp.ok new file mode 100644 index 00000000000..5346b63216a --- /dev/null +++ b/test/cmp/ok/neg-version5-4.cmp.ok @@ -0,0 +1,5 @@ +(unknown location): Compatibility error [M0170], stable variable four of previous type + var [var (Text, Nat, Bool)] +cannot be consumed at new type + var [var (Text, Nat)] +FALSE diff --git a/test/cmp/ok/version0-1.cmp.ok b/test/cmp/ok/version0-1.cmp.ok new file mode 100644 index 00000000000..ef2f5130b85 --- /dev/null +++ b/test/cmp/ok/version0-1.cmp.ok @@ -0,0 +1 @@ +TRUE diff --git a/test/cmp/ok/version1-0.cmp.ok b/test/cmp/ok/version1-0.cmp.ok new file mode 100644 index 00000000000..9d9769197a3 --- /dev/null +++ b/test/cmp/ok/version1-0.cmp.ok @@ -0,0 +1,4 @@ +(unknown location): warning [M0169], stable variable three of previous type + var [var (Nat, Text)] + will be discarded. This may cause data loss. Are you sure? +TRUE diff --git a/test/cmp/ok/version1-2.cmp.ok b/test/cmp/ok/version1-2.cmp.ok new file mode 100644 index 00000000000..ef2f5130b85 --- /dev/null +++ b/test/cmp/ok/version1-2.cmp.ok @@ -0,0 +1 @@ +TRUE diff --git a/test/cmp/ok/version2-1.cmp.ok b/test/cmp/ok/version2-1.cmp.ok new file mode 100644 index 00000000000..57fbc9fe067 --- /dev/null +++ b/test/cmp/ok/version2-1.cmp.ok @@ -0,0 +1,4 @@ +(unknown location): warning [M0169], stable variable four of previous type + var [var (Nat, Text)] + will be discarded. This may cause data loss. Are you sure? +TRUE diff --git a/test/cmp/ok/version2-3.cmp.ok b/test/cmp/ok/version2-3.cmp.ok new file mode 100644 index 00000000000..ef2f5130b85 --- /dev/null +++ b/test/cmp/ok/version2-3.cmp.ok @@ -0,0 +1 @@ +TRUE diff --git a/test/cmp/ok/version3-2.cmp.ok b/test/cmp/ok/version3-2.cmp.ok new file mode 100644 index 00000000000..ba4b2acb7c6 --- /dev/null +++ b/test/cmp/ok/version3-2.cmp.ok @@ -0,0 +1,4 @@ +(unknown location): warning [M0169], stable variable four of previous type + var [var (Text, Nat)] + will be discarded. This may cause data loss. Are you sure? +TRUE diff --git a/test/cmp/ok/version3-4.cmp.ok b/test/cmp/ok/version3-4.cmp.ok new file mode 100644 index 00000000000..ef2f5130b85 --- /dev/null +++ b/test/cmp/ok/version3-4.cmp.ok @@ -0,0 +1 @@ +TRUE diff --git a/test/cmp/ok/version4-5.cmp.ok b/test/cmp/ok/version4-5.cmp.ok new file mode 100644 index 00000000000..ef2f5130b85 --- /dev/null +++ b/test/cmp/ok/version4-5.cmp.ok @@ -0,0 +1 @@ +TRUE diff --git a/test/cmp/version0-1.cmp b/test/cmp/version0-1.cmp new file mode 100644 index 00000000000..9748962a19d --- /dev/null +++ b/test/cmp/version0-1.cmp @@ -0,0 +1 @@ +files/version0.most files/version1.most diff --git a/test/cmp/version1-0.cmp b/test/cmp/version1-0.cmp new file mode 100644 index 00000000000..a6bc23ce3e7 --- /dev/null +++ b/test/cmp/version1-0.cmp @@ -0,0 +1 @@ +files/version1.most files/version0.most diff --git a/test/cmp/version1-2.cmp b/test/cmp/version1-2.cmp new file mode 100644 index 00000000000..8e2e7985c04 --- /dev/null +++ b/test/cmp/version1-2.cmp @@ -0,0 +1 @@ +files/version1.most files/version2.most diff --git a/test/cmp/version2-1.cmp b/test/cmp/version2-1.cmp new file mode 100644 index 00000000000..b22cbe3b2ee --- /dev/null +++ b/test/cmp/version2-1.cmp @@ -0,0 +1 @@ +files/version2.most files/version1.most diff --git a/test/cmp/version2-3.cmp b/test/cmp/version2-3.cmp new file mode 100644 index 00000000000..b66c88c6eb4 --- /dev/null +++ b/test/cmp/version2-3.cmp @@ -0,0 +1 @@ +files/version2.most files/version3.most diff --git a/test/cmp/version3-2.cmp b/test/cmp/version3-2.cmp new file mode 100644 index 00000000000..0ef77b06b68 --- /dev/null +++ b/test/cmp/version3-2.cmp @@ -0,0 +1 @@ +files/version3.most files/version2.most diff --git a/test/cmp/version3-4.cmp b/test/cmp/version3-4.cmp new file mode 100644 index 00000000000..96d8065408f --- /dev/null +++ b/test/cmp/version3-4.cmp @@ -0,0 +1 @@ +files/version3.most files/version4.most diff --git a/test/cmp/version4-5.cmp b/test/cmp/version4-5.cmp new file mode 100644 index 00000000000..5ee1651e674 --- /dev/null +++ b/test/cmp/version4-5.cmp @@ -0,0 +1 @@ +files/version4.most files/version5.most diff --git a/test/fail/migration-bad.mo b/test/fail/migration-bad.mo new file mode 100644 index 00000000000..cb83d20aa77 --- /dev/null +++ b/test/fail/migration-bad.mo @@ -0,0 +1,26 @@ +// test migration function restrictions + +actor [()] a = { // reject, not a function +}; + +actor [func (x:T) : T {x}] b = { // reject, a generic function +}; + +actor [func () : {} {{}}] c = { // reject, domain is not a record +}; + +actor [func ({}) : () {}] d = { // reject, co-domain is not a record +}; + +actor [func ({f:()->()}) : () {}] e = { // reject domain is unstable +}; + +actor [func () : {f:()->()}{ {f = func(){}} }] + f = { // reject, co-domain is unstable + stable let f : Any = () +}; + +actor [(func () : ({} -> {}) {}) ()] // reject, not static + g = { + stable let f : Any = () +}; diff --git a/test/fail/migration-more.mo b/test/fail/migration-more.mo new file mode 100644 index 00000000000..fb12a0974a5 --- /dev/null +++ b/test/fail/migration-more.mo @@ -0,0 +1,4 @@ +actor [ func(n:Nat) : Int {n} ] // reject - expect function on records +{ + +}; diff --git a/test/fail/migration.mo b/test/fail/migration.mo new file mode 100644 index 00000000000..08867838196 --- /dev/null +++ b/test/fail/migration.mo @@ -0,0 +1,22 @@ +import Prim "mo:prim"; + +actor [ func({unstable1 : () -> () }) : + { unstable2 : () -> (); // not stable + var three : Text; // wrong type, reject + var versoin : (); // unrequired/mispelled, reject + } + { { var three = ""; + var unused = (); + var versoin = (); + unstable2 = func () {}; + } + }] { + + stable var version = 0; + + stable var three : [var (Nat, Text)] = [var]; + + public func check(): async() { + Prim.debugPrint (debug_show {three}); + } +}; diff --git a/test/fail/ok/bad-import.tc.ok b/test/fail/ok/bad-import.tc.ok index c6d5510da5a..cc314007d39 100644 --- a/test/fail/ok/bad-import.tc.ok +++ b/test/fail/ok/bad-import.tc.ok @@ -1 +1 @@ -lib/actor.mo:1.1-1.9: type error [M0014], non-static expression in library or module +lib/actor.mo:1.1-1.9: type error [M0014], non-static expression in library, module or migration expression diff --git a/test/fail/ok/issue-3585a.tc.ok b/test/fail/ok/issue-3585a.tc.ok index 5d03ffc9b2b..b6374e36f3b 100644 --- a/test/fail/ok/issue-3585a.tc.ok +++ b/test/fail/ok/issue-3585a.tc.ok @@ -1 +1 @@ -issue-3585/Record.mo:2.21-2.30: type error [M0014], non-static expression in library or module +issue-3585/Record.mo:2.21-2.30: type error [M0014], non-static expression in library, module or migration expression diff --git a/test/fail/ok/issue-3585b.tc.ok b/test/fail/ok/issue-3585b.tc.ok index d98af1d1d53..d07eaf75a32 100644 --- a/test/fail/ok/issue-3585b.tc.ok +++ b/test/fail/ok/issue-3585b.tc.ok @@ -1 +1 @@ -issue-3585/Object.mo:2.27-2.36: type error [M0014], non-static expression in library or module +issue-3585/Object.mo:2.27-2.36: type error [M0014], non-static expression in library, module or migration expression diff --git a/test/fail/ok/issue-3585c.tc.ok b/test/fail/ok/issue-3585c.tc.ok index a90ca6348e3..46f76ff2621 100644 --- a/test/fail/ok/issue-3585c.tc.ok +++ b/test/fail/ok/issue-3585c.tc.ok @@ -1 +1 @@ -issue-3585/Field.mo:2.10-2.19: type error [M0014], non-static expression in library or module +issue-3585/Field.mo:2.10-2.19: type error [M0014], non-static expression in library, module or migration expression diff --git a/test/fail/ok/migration-bad.tc.ok b/test/fail/ok/migration-bad.tc.ok new file mode 100644 index 00000000000..396eed68c16 --- /dev/null +++ b/test/fail/ok/migration-bad.tc.ok @@ -0,0 +1,19 @@ +migration-bad.mo:3.8-3.10: type error [M0203], expected non-generic, local function type, but migration expression produces type + () +migration-bad.mo:6.8-6.29: type error [M0203], expected non-generic, local function type, but migration expression produces type + T -> T +migration-bad.mo:9.8-9.25: type error [M0202], expected object type, but migration expression consumes non-object type + () +migration-bad.mo:12.8-12.25: type error [M0202], expected object type, but migration expression produces non-object type + () +migration-bad.mo:15.8-15.33: type error [M0202], expected object type, but migration expression produces non-object type + () +migration-bad.mo:15.8-15.33: type error [M0201], expected stable type, but migration expression consumes non-stable type + {f : () -> ()} +migration-bad.mo:18.8-18.46: type error [M0201], expected stable type, but migration expression produces non-stable type + {f : () -> ()} +migration-bad.mo:18.8-18.46: type error [M0202], expected object type, but migration expression consumes non-object type + () +migration-bad.mo:23.30-23.32: type error [M0136], empty block cannot produce expected type + {} -> {} +migration-bad.mo:23.8-23.36: type error [M0014], non-static expression in library, module or migration expression diff --git a/test/fail/ok/migration-bad.tc.ret.ok b/test/fail/ok/migration-bad.tc.ret.ok new file mode 100644 index 00000000000..69becfa16f9 --- /dev/null +++ b/test/fail/ok/migration-bad.tc.ret.ok @@ -0,0 +1 @@ +Return code 1 diff --git a/test/fail/ok/migration-more.tc.ok b/test/fail/ok/migration-more.tc.ok new file mode 100644 index 00000000000..e5f7955c886 --- /dev/null +++ b/test/fail/ok/migration-more.tc.ok @@ -0,0 +1,4 @@ +migration-more.mo:1.9-1.30: type error [M0202], expected object type, but migration expression produces non-object type + Int +migration-more.mo:1.9-1.30: type error [M0202], expected object type, but migration expression consumes non-object type + Nat diff --git a/test/fail/ok/migration-more.tc.ret.ok b/test/fail/ok/migration-more.tc.ret.ok new file mode 100644 index 00000000000..69becfa16f9 --- /dev/null +++ b/test/fail/ok/migration-more.tc.ret.ok @@ -0,0 +1 @@ +Return code 1 diff --git a/test/fail/ok/migration.tc.ok b/test/fail/ok/migration.tc.ok new file mode 100644 index 00000000000..e7c8d79aa9f --- /dev/null +++ b/test/fail/ok/migration.tc.ok @@ -0,0 +1,17 @@ +migration.mo:3.9-13.9: type error [M0201], expected stable type, but migration expression produces non-stable type + {var three : Text; unstable2 : () -> (); var versoin : ()} +migration.mo:3.9-13.9: type error [M0201], expected stable type, but migration expression consumes non-stable type + {unstable1 : () -> ()} +migration.mo:3.9-13.9: type error [M0204], migration expression produces field `three` of type + var Text +, not the expected type + var [var (Nat, Text)] +migration.mo:3.9-13.9: type error [M0205], migration expression produces unexpected field `unstable2` of type + () -> () + +The actor should declare a corresponding `stable` field. +migration.mo:3.9-13.9: type error [M0205], migration expression produces unexpected field `versoin` of type + var () + +Did you mean field version? +The actor should declare a corresponding `stable` field. diff --git a/test/fail/ok/migration.tc.ret.ok b/test/fail/ok/migration.tc.ret.ok new file mode 100644 index 00000000000..69becfa16f9 --- /dev/null +++ b/test/fail/ok/migration.tc.ret.ok @@ -0,0 +1 @@ +Return code 1 diff --git a/test/fail/ok/non_static_module.tc.ok b/test/fail/ok/non_static_module.tc.ok index 48ff8b1d088..2899d902a7c 100644 --- a/test/fail/ok/non_static_module.tc.ok +++ b/test/fail/ok/non_static_module.tc.ok @@ -1,12 +1,12 @@ -non_static_module.mo:20.12-20.15: type error [M0014], non-static expression in library or module -non_static_module.mo:21.12-21.19: type error [M0014], non-static expression in library or module -non_static_module.mo:22.4-22.13: type error [M0014], non-static expression in library or module -non_static_module.mo:3.11-3.14: type error [M0014], non-static expression in library or module -non_static_module.mo:4.11-4.18: type error [M0014], non-static expression in library or module -non_static_module.mo:5.3-5.12: type error [M0014], non-static expression in library or module -non_static_module.mo:9.12-9.15: type error [M0014], non-static expression in library or module -non_static_module.mo:10.12-10.19: type error [M0014], non-static expression in library or module -non_static_module.mo:11.4-11.13: type error [M0014], non-static expression in library or module -non_static_module.mo:20.12-20.15: type error [M0014], non-static expression in library or module -non_static_module.mo:21.12-21.19: type error [M0014], non-static expression in library or module -non_static_module.mo:22.4-22.13: type error [M0014], non-static expression in library or module +non_static_module.mo:20.12-20.15: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:21.12-21.19: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:22.4-22.13: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:3.11-3.14: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:4.11-4.18: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:5.3-5.12: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:9.12-9.15: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:10.12-10.19: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:11.4-11.13: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:20.12-20.15: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:21.12-21.19: type error [M0014], non-static expression in library, module or migration expression +non_static_module.mo:22.4-22.13: type error [M0014], non-static expression in library, module or migration expression diff --git a/test/fail/ok/not-static.tc.ok b/test/fail/ok/not-static.tc.ok index 3bb203942b4..4e0dbf55f82 100644 --- a/test/fail/ok/not-static.tc.ok +++ b/test/fail/ok/not-static.tc.ok @@ -1 +1 @@ -lib/not-static.mo:1.1-1.36: type error [M0014], non-static expression in library or module +lib/not-static.mo:1.1-1.36: type error [M0014], non-static expression in library, module or migration expression diff --git a/test/run-drun/migration-scoping.mo b/test/run-drun/migration-scoping.mo new file mode 100644 index 00000000000..30fb4e126ff --- /dev/null +++ b/test/run-drun/migration-scoping.mo @@ -0,0 +1,14 @@ +import Prim "mo:prim"; +// compilation only test (correct lexical scoping) +// migration expression can access pattern variables +// migration expression doesn't capture class parameters +shared(v) actor [func ({}) : {} { Prim.debugPrint (debug_show v); {} }] class + C(v : Bool) = { + assert v +}; + +//SKIP run +//SKIP run-ir +//SKIP run-low +//SKIP ic-ref-run +//SKIP drun-run diff --git a/test/run-drun/ok/test_oneway.tc.ok b/test/run-drun/ok/test_oneway.tc.ok index 87784d994da..7d79119833d 100644 --- a/test/run-drun/ok/test_oneway.tc.ok +++ b/test/run-drun/ok/test_oneway.tc.ok @@ -1,2 +1,2 @@ -oneway.mo:2.1-69.2: type error [M0014], non-static expression in library or module -oneway.mo:71.9-71.15: type error [M0014], non-static expression in library or module +oneway.mo:2.1-69.2: type error [M0014], non-static expression in library, module or migration expression +oneway.mo:71.9-71.15: type error [M0014], non-static expression in library, module or migration expression diff --git a/test/run-drun/ok/upgrade-class-migration.drun.ok b/test/run-drun/ok/upgrade-class-migration.drun.ok new file mode 100644 index 00000000000..9588ca9b499 --- /dev/null +++ b/test/run-drun/ok/upgrade-class-migration.drun.ok @@ -0,0 +1,29 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +debug.print: Version 0 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {one = [var 1, 2, 3, 4]; two = [var "1", "2", "3", "4"]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 +debug.print: {migration = {new = {three = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]}; old = {one = [var 1, 2, 3, 4]; two = [var "1", "2", "3", "4"]}}} +debug.print: Version 1 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {three = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 +debug.print: {migration = {new = {four = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]}; old = {three = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]}}} +debug.print: Version 2 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {four = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 +debug.print: {migration = {new = {four = [var ("1", 1), ("2", 2), ("3", 3), ("4", 4)]}; old = {four = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]}}} +debug.print: Version 3 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {four = [var ("1", 1), ("2", 2), ("3", 3), ("4", 4)]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 +debug.print: {migration = {new = {four = [var ("1", 1, false), ("2", 2, false), ("3", 3, false), ("4", 4, false)]}; old = {four = [var ("1", 1), ("2", 2), ("3", 3), ("4", 4)]}}} +debug.print: Version 4 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {four = [var ("1", 1, false), ("2", 2, false), ("3", 3, false), ("4", 4, false)]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 +debug.print: Version 5 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {four = [var ("1", 1, false), ("2", 2, false), ("3", 3, false), ("4", 4, false)]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/upgrade-migration.drun.ok b/test/run-drun/ok/upgrade-migration.drun.ok new file mode 100644 index 00000000000..9588ca9b499 --- /dev/null +++ b/test/run-drun/ok/upgrade-migration.drun.ok @@ -0,0 +1,29 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +debug.print: Version 0 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {one = [var 1, 2, 3, 4]; two = [var "1", "2", "3", "4"]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 +debug.print: {migration = {new = {three = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]}; old = {one = [var 1, 2, 3, 4]; two = [var "1", "2", "3", "4"]}}} +debug.print: Version 1 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {three = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 +debug.print: {migration = {new = {four = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]}; old = {three = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]}}} +debug.print: Version 2 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {four = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 +debug.print: {migration = {new = {four = [var ("1", 1), ("2", 2), ("3", 3), ("4", 4)]}; old = {four = [var (1, "1"), (2, "2"), (3, "3"), (4, "4")]}}} +debug.print: Version 3 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {four = [var ("1", 1), ("2", 2), ("3", 3), ("4", 4)]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 +debug.print: {migration = {new = {four = [var ("1", 1, false), ("2", 2, false), ("3", 3, false), ("4", 4, false)]}; old = {four = [var ("1", 1), ("2", 2), ("3", 3), ("4", 4)]}}} +debug.print: Version 4 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {four = [var ("1", 1, false), ("2", 2, false), ("3", 3, false), ("4", 4, false)]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 +debug.print: Version 5 +ingress Completed: Reply: 0x4449444c0000 +debug.print: {four = [var ("1", 1, false), ("2", 2, false), ("3", 3, false), ("4", 4, false)]; zero = 0} +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/upgrade-class-migration.drun b/test/run-drun/upgrade-class-migration.drun new file mode 100644 index 00000000000..195ce764e7b --- /dev/null +++ b/test/run-drun/upgrade-class-migration.drun @@ -0,0 +1,13 @@ +# SKIP ic-ref-run +install $ID upgrade-class-migration/version0.mo "" +ingress $ID check "DIDL\x00\x00" +upgrade $ID upgrade-class-migration/version1.mo "" +ingress $ID check "DIDL\x00\x00" +upgrade $ID upgrade-class-migration/version2.mo "" +ingress $ID check "DIDL\x00\x00" +upgrade $ID upgrade-class-migration/version3.mo "" +ingress $ID check "DIDL\x00\x00" +upgrade $ID upgrade-class-migration/version4.mo "" +ingress $ID check "DIDL\x00\x00" +upgrade $ID upgrade-class-migration/version5.mo "" +ingress $ID check "DIDL\x00\x00" diff --git a/test/run-drun/upgrade-class-migration/Migration1.mo b/test/run-drun/upgrade-class-migration/Migration1.mo new file mode 100644 index 00000000000..6a333847982 --- /dev/null +++ b/test/run-drun/upgrade-class-migration/Migration1.mo @@ -0,0 +1,27 @@ +import Prim "mo:prim"; +module { + + func zip(ts : [var T], us: [var U]) : [var (T, U)] { + let size = if (ts.size() <= us.size()) ts.size() else us.size(); + if (size == 0) return [var]; + let res = Prim.Array_init<(T,U)>(size, (ts[0], us[0])); + for (i in res.keys()) { + res[i] := (ts[i], us[i]); + }; + res + }; + + public func run( old : { + var one : [var Nat]; + var two : [var Text]; + } ) : + { var three : [var (Nat, Text)] } { + let new = + { + var three = zip(old.one, old.two); + }; + Prim.debugPrint(debug_show{migration={old;new}}); + new + } + +} diff --git a/test/run-drun/upgrade-class-migration/Migration2.mo b/test/run-drun/upgrade-class-migration/Migration2.mo new file mode 100644 index 00000000000..756349340b1 --- /dev/null +++ b/test/run-drun/upgrade-class-migration/Migration2.mo @@ -0,0 +1,15 @@ +import Prim "mo:prim"; + +module { + + public func run( old : { var three : [var (Nat, Text)] } ) : + { var four : [var (Nat, Text)] } { + let new = + { + var four = old.three; + }; + Prim.debugPrint(debug_show {migration = {old; new}}); + new + } + +} diff --git a/test/run-drun/upgrade-class-migration/Migration3.mo b/test/run-drun/upgrade-class-migration/Migration3.mo new file mode 100644 index 00000000000..fbae500cb4b --- /dev/null +++ b/test/run-drun/upgrade-class-migration/Migration3.mo @@ -0,0 +1,25 @@ +import Prim "mo:prim"; + +module { + + func swap(tus : [var (T, U)]) : [var (U, T)] { + let size = tus.size(); + if (size == 0) return [var]; + let res = Prim.Array_init<(U, T)>(size, (tus[0].1, tus[0].0)); + for (i in res.keys()) { + res[i] := (tus[i].1, tus[i].0); + }; + res + }; + + public func run( old : { var four : [var (Nat, Text)] } ) : + { var four : [var (Text, Nat)] } { + let new = + { + var four = swap(old.four); + }; + Prim.debugPrint(debug_show {migration = {old; new}}); + new + } + +} diff --git a/test/run-drun/upgrade-class-migration/Migration4.mo b/test/run-drun/upgrade-class-migration/Migration4.mo new file mode 100644 index 00000000000..81d7747dc70 --- /dev/null +++ b/test/run-drun/upgrade-class-migration/Migration4.mo @@ -0,0 +1,25 @@ +import Prim "mo:prim"; + +module { + + func extend(tus : [var (T, U)], v: V) : [var (T, U, V)] { + let size = tus.size(); + if (size == 0) return [var]; + let res = Prim.Array_init<(T, U, V)>(size, (tus[0].0, tus[0].1, v)); + for (i in res.keys()) { + res[i] := (tus[i].0, tus[i].1, v); + }; + res + }; + + public func run( old : { var four : [var (Text, Nat)] } ) : + { var four : [var (Text, Nat, Bool)] } { + let new = + { + var four = extend(old.four, false); + }; + Prim.debugPrint(debug_show {migration = {old; new}}); + new + } + +} diff --git a/test/run-drun/upgrade-class-migration/version0.mo b/test/run-drun/upgrade-class-migration/version0.mo new file mode 100644 index 00000000000..54211887f9b --- /dev/null +++ b/test/run-drun/upgrade-class-migration/version0.mo @@ -0,0 +1,14 @@ +import Prim "mo:prim"; + +actor class C() { + Prim.debugPrint("Version 0"); + + stable var zero : Nat = 0; + + stable var one : [var Nat] = [var 1, 2, 3, 4]; + stable var two : [var Text] = [var "1", "2", "3", "4"]; + + public func check(): async() { + Prim.debugPrint (debug_show {zero;one;two}) + }; +}; diff --git a/test/run-drun/upgrade-class-migration/version1.mo b/test/run-drun/upgrade-class-migration/version1.mo new file mode 100644 index 00000000000..67da95c3d19 --- /dev/null +++ b/test/run-drun/upgrade-class-migration/version1.mo @@ -0,0 +1,17 @@ +import Prim "mo:prim"; +import Migration "Migration1"; + +// test merging fields `one` and `two` into field `three`, dropping `one` and `two` and preserving `zero`. +actor [Migration.run] class C() { + + Prim.debugPrint("Version 1"); + + stable var zero : Nat = Prim.trap "unreachable"; // inherited + assert zero == 0; + + stable var three : [var (Nat, Text)] = [var]; + + public func check(): async() { + Prim.debugPrint (debug_show {zero; three}); + } +}; diff --git a/test/run-drun/upgrade-class-migration/version2.mo b/test/run-drun/upgrade-class-migration/version2.mo new file mode 100644 index 00000000000..aa6f1300124 --- /dev/null +++ b/test/run-drun/upgrade-class-migration/version2.mo @@ -0,0 +1,17 @@ +import Prim "mo:prim"; +import Migration "Migration2"; + +// Rename stable field `three` to `four` +actor [Migration.run] class C() { + + Prim.debugPrint("Version 2"); + + stable var zero : Nat = Prim.trap "unreachable"; // inherited + assert zero == 0; + + stable var four : [var (Nat, Text)] = [var]; + + public func check(): async() { + Prim.debugPrint(debug_show{zero; four}); + } +}; diff --git a/test/run-drun/upgrade-class-migration/version3.mo b/test/run-drun/upgrade-class-migration/version3.mo new file mode 100644 index 00000000000..f1561055972 --- /dev/null +++ b/test/run-drun/upgrade-class-migration/version3.mo @@ -0,0 +1,17 @@ +import Prim "mo:prim"; +import Migration "Migration3"; + +// Swap nested pairs in `four`, changing type +actor [Migration.run] class C() { + + Prim.debugPrint("Version 3"); + + stable var zero : Nat = Prim.trap "unreachable"; // inherited + assert zero == 0; + + stable var four : [var (Text, Nat)] = [var]; + + public func check(): async() { + Prim.debugPrint(debug_show{zero; four}); + } +}; diff --git a/test/run-drun/upgrade-class-migration/version4.mo b/test/run-drun/upgrade-class-migration/version4.mo new file mode 100644 index 00000000000..39580f7c03c --- /dev/null +++ b/test/run-drun/upgrade-class-migration/version4.mo @@ -0,0 +1,17 @@ +import Prim "mo:prim"; +import Migration "Migration4"; + +// test adding a nested field, changing type +actor [Migration.run] class C() { + + Prim.debugPrint("Version 4"); + + stable var zero : Nat = Prim.trap "unreachable"; // inherited + assert zero == 0; + + stable var four : [var (Text, Nat, Bool)] = [var]; + + public func check(): async() { + Prim.debugPrint(debug_show{zero; four}); + } +}; diff --git a/test/run-drun/upgrade-class-migration/version5.mo b/test/run-drun/upgrade-class-migration/version5.mo new file mode 100644 index 00000000000..8ba5c9c1d9d --- /dev/null +++ b/test/run-drun/upgrade-class-migration/version5.mo @@ -0,0 +1,16 @@ +import Prim "mo:prim"; + +// drops the migration expression from version4.mo +actor class C() { + + Prim.debugPrint("Version 5"); + + stable var zero : Nat = Prim.trap "unreachable"; // inherited + assert zero == 0; + + stable var four : [var (Text, Nat, Bool)] = [var]; + + public func check(): async() { + Prim.debugPrint(debug_show{zero; four}); + } +}; diff --git a/test/run-drun/upgrade-migration.drun b/test/run-drun/upgrade-migration.drun new file mode 100644 index 00000000000..80552f62738 --- /dev/null +++ b/test/run-drun/upgrade-migration.drun @@ -0,0 +1,13 @@ +# SKIP ic-ref-run +install $ID upgrade-migration/version0.mo "" +ingress $ID check "DIDL\x00\x00" +upgrade $ID upgrade-migration/version1.mo "" +ingress $ID check "DIDL\x00\x00" +upgrade $ID upgrade-migration/version2.mo "" +ingress $ID check "DIDL\x00\x00" +upgrade $ID upgrade-migration/version3.mo "" +ingress $ID check "DIDL\x00\x00" +upgrade $ID upgrade-migration/version4.mo "" +ingress $ID check "DIDL\x00\x00" +upgrade $ID upgrade-migration/version5.mo "" +ingress $ID check "DIDL\x00\x00" diff --git a/test/run-drun/upgrade-migration/Migration1.mo b/test/run-drun/upgrade-migration/Migration1.mo new file mode 100644 index 00000000000..6a333847982 --- /dev/null +++ b/test/run-drun/upgrade-migration/Migration1.mo @@ -0,0 +1,27 @@ +import Prim "mo:prim"; +module { + + func zip(ts : [var T], us: [var U]) : [var (T, U)] { + let size = if (ts.size() <= us.size()) ts.size() else us.size(); + if (size == 0) return [var]; + let res = Prim.Array_init<(T,U)>(size, (ts[0], us[0])); + for (i in res.keys()) { + res[i] := (ts[i], us[i]); + }; + res + }; + + public func run( old : { + var one : [var Nat]; + var two : [var Text]; + } ) : + { var three : [var (Nat, Text)] } { + let new = + { + var three = zip(old.one, old.two); + }; + Prim.debugPrint(debug_show{migration={old;new}}); + new + } + +} diff --git a/test/run-drun/upgrade-migration/Migration2.mo b/test/run-drun/upgrade-migration/Migration2.mo new file mode 100644 index 00000000000..756349340b1 --- /dev/null +++ b/test/run-drun/upgrade-migration/Migration2.mo @@ -0,0 +1,15 @@ +import Prim "mo:prim"; + +module { + + public func run( old : { var three : [var (Nat, Text)] } ) : + { var four : [var (Nat, Text)] } { + let new = + { + var four = old.three; + }; + Prim.debugPrint(debug_show {migration = {old; new}}); + new + } + +} diff --git a/test/run-drun/upgrade-migration/Migration3.mo b/test/run-drun/upgrade-migration/Migration3.mo new file mode 100644 index 00000000000..fbae500cb4b --- /dev/null +++ b/test/run-drun/upgrade-migration/Migration3.mo @@ -0,0 +1,25 @@ +import Prim "mo:prim"; + +module { + + func swap(tus : [var (T, U)]) : [var (U, T)] { + let size = tus.size(); + if (size == 0) return [var]; + let res = Prim.Array_init<(U, T)>(size, (tus[0].1, tus[0].0)); + for (i in res.keys()) { + res[i] := (tus[i].1, tus[i].0); + }; + res + }; + + public func run( old : { var four : [var (Nat, Text)] } ) : + { var four : [var (Text, Nat)] } { + let new = + { + var four = swap(old.four); + }; + Prim.debugPrint(debug_show {migration = {old; new}}); + new + } + +} diff --git a/test/run-drun/upgrade-migration/Migration4.mo b/test/run-drun/upgrade-migration/Migration4.mo new file mode 100644 index 00000000000..81d7747dc70 --- /dev/null +++ b/test/run-drun/upgrade-migration/Migration4.mo @@ -0,0 +1,25 @@ +import Prim "mo:prim"; + +module { + + func extend(tus : [var (T, U)], v: V) : [var (T, U, V)] { + let size = tus.size(); + if (size == 0) return [var]; + let res = Prim.Array_init<(T, U, V)>(size, (tus[0].0, tus[0].1, v)); + for (i in res.keys()) { + res[i] := (tus[i].0, tus[i].1, v); + }; + res + }; + + public func run( old : { var four : [var (Text, Nat)] } ) : + { var four : [var (Text, Nat, Bool)] } { + let new = + { + var four = extend(old.four, false); + }; + Prim.debugPrint(debug_show {migration = {old; new}}); + new + } + +} diff --git a/test/run-drun/upgrade-migration/version0.mo b/test/run-drun/upgrade-migration/version0.mo new file mode 100644 index 00000000000..7f0207b7d17 --- /dev/null +++ b/test/run-drun/upgrade-migration/version0.mo @@ -0,0 +1,14 @@ +import Prim "mo:prim"; + +actor { + Prim.debugPrint("Version 0"); + + stable var zero : Nat = 0; + + stable var one : [var Nat] = [var 1, 2, 3, 4]; + stable var two : [var Text] = [var "1", "2", "3", "4"]; + + public func check(): async() { + Prim.debugPrint (debug_show {zero;one;two}) + }; +}; diff --git a/test/run-drun/upgrade-migration/version1.mo b/test/run-drun/upgrade-migration/version1.mo new file mode 100644 index 00000000000..721dd1124c2 --- /dev/null +++ b/test/run-drun/upgrade-migration/version1.mo @@ -0,0 +1,17 @@ +import Prim "mo:prim"; +import Migration "Migration1"; + +// test merging fields `one` and `two` into field `three`, dropping `one` and `two` and preserving `zero`. +actor [Migration.run] { + + Prim.debugPrint("Version 1"); + + stable var zero : Nat = Prim.trap "unreachable"; // inherited + assert zero == 0; + + stable var three : [var (Nat, Text)] = [var]; + + public func check(): async() { + Prim.debugPrint (debug_show {zero; three}); + } +}; diff --git a/test/run-drun/upgrade-migration/version2.mo b/test/run-drun/upgrade-migration/version2.mo new file mode 100644 index 00000000000..fdb2c37055c --- /dev/null +++ b/test/run-drun/upgrade-migration/version2.mo @@ -0,0 +1,17 @@ +import Prim "mo:prim"; +import Migration "Migration2"; + +// Rename stable field `three` to `four` +actor [Migration.run] { + + Prim.debugPrint("Version 2"); + + stable var zero : Nat = Prim.trap "unreachable"; // inherited + assert zero == 0; + + stable var four : [var (Nat, Text)] = [var]; + + public func check(): async() { + Prim.debugPrint(debug_show{zero; four}); + } +}; diff --git a/test/run-drun/upgrade-migration/version3.mo b/test/run-drun/upgrade-migration/version3.mo new file mode 100644 index 00000000000..9227e98a6e3 --- /dev/null +++ b/test/run-drun/upgrade-migration/version3.mo @@ -0,0 +1,17 @@ +import Prim "mo:prim"; +import Migration "Migration3"; + +// Swap nested pairs in `four`, changing type +actor [Migration.run] { + + Prim.debugPrint("Version 3"); + + stable var zero : Nat = Prim.trap "unreachable"; // inherited + assert zero == 0; + + stable var four : [var (Text, Nat)] = [var]; + + public func check(): async() { + Prim.debugPrint(debug_show{zero; four}); + } +}; diff --git a/test/run-drun/upgrade-migration/version4.mo b/test/run-drun/upgrade-migration/version4.mo new file mode 100644 index 00000000000..4a5364e484d --- /dev/null +++ b/test/run-drun/upgrade-migration/version4.mo @@ -0,0 +1,17 @@ +import Prim "mo:prim"; +import Migration "Migration4"; + +// test adding a nested field, changing type +actor [Migration.run] { + + Prim.debugPrint("Version 4"); + + stable var zero : Nat = Prim.trap "unreachable"; // inherited + assert zero == 0; + + stable var four : [var (Text, Nat, Bool)] = [var]; + + public func check(): async() { + Prim.debugPrint(debug_show{zero; four}); + } +}; diff --git a/test/run-drun/upgrade-migration/version5.mo b/test/run-drun/upgrade-migration/version5.mo new file mode 100644 index 00000000000..4b383a4523d --- /dev/null +++ b/test/run-drun/upgrade-migration/version5.mo @@ -0,0 +1,16 @@ +import Prim "mo:prim"; + +// drops the migration expression from version4.mo +actor { + + Prim.debugPrint("Version 5"); + + stable var zero : Nat = Prim.trap "unreachable"; // inherited + assert zero == 0; + + stable var four : [var (Text, Nat, Bool)] = [var]; + + public func check(): async() { + Prim.debugPrint(debug_show{zero; four}); + } +};