From a9b22a05756d771853f6fca706290d20e50b2af0 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 19 Jan 2026 18:12:01 +0000 Subject: [PATCH 01/52] sketch of code gen --- test/run-drun/data-view.mo | 88 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 test/run-drun/data-view.mo diff --git a/test/run-drun/data-view.mo b/test/run-drun/data-view.mo new file mode 100644 index 00000000000..6e373a40102 --- /dev/null +++ b/test/run-drun/data-view.mo @@ -0,0 +1,88 @@ +//MOC-FLAG --package core /home/crusso/motoko-core/src +import Map "mo:core/Map"; +import Set "mo:core/Set"; +import Nat "mo:core/Nat"; +import Iter "mo:core/Iter"; +import Order "mo:core/Order"; +import Text "mo:core/Text"; + +persistent actor { + + module MapView { + + public func view( + self : Map.Map, + compare : (implicit : (K,K) -> Order.Order), + ko : ?K, + count : Nat) : [(K, V)] { + let entries = switch ko { + case null { + self.entries() + }; + case (?k) { + self.entriesFrom(k) + }; + }; + entries.take(count).toArray(); + }; + }; + + let map : Map.Map = Map.empty(); + + public query func mapView(ko: ?Nat, count: Nat) : async [(Nat, Text)] { + map.view(Nat.compare, ko, count); + }; + + module SetView { + + public func view( + self : Set.Set, + compare : (implicit : (K,K) -> Order.Order), + ko : ?K, + count : Nat) : [K] { + let entries = switch ko { + case null { + self.values() + }; + case (?k) { + self.valuesFrom(k) + }; + }; + entries.take(count).toArray(); + }; + }; + + let set : Set.Set = Set.empty(); + + public query func setView(ko: ?Nat, count: Nat) : async [Nat] { + set.view(Nat.compare, ko, count); + }; + + module ArrayView { + + public func view( + self : [V], + io : ?Nat, + count : Nat) : [V] { + // TODO: use slice instead + let entries = switch io { + case null { + self.values() + }; + case (?io) { + self.values().drop(io) + }; + }; + entries.take(count).toArray(); + }; + }; + + let array : [(Nat, Text)] = []; + + public query func arrayView(ko: ?Nat, count: Nat) : async [(Nat, Text)] { + array.view(ko, count); + }; + + + +} From 923b1445d0d51e5a8705197b8659e4f0117fd35b Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Tue, 20 Jan 2026 12:52:32 +0000 Subject: [PATCH 02/52] curried versions, so viewer is just a function, defaulting to var.view() --- test/run-drun/data-view-curried.mo | 85 ++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 test/run-drun/data-view-curried.mo diff --git a/test/run-drun/data-view-curried.mo b/test/run-drun/data-view-curried.mo new file mode 100644 index 00000000000..8af5ad7bfbc --- /dev/null +++ b/test/run-drun/data-view-curried.mo @@ -0,0 +1,85 @@ +//MOC-FLAG --package core /home/crusso/motoko-core/src +import Map "mo:core/Map"; +import Set "mo:core/Set"; +import Nat "mo:core/Nat"; +import Iter "mo:core/Iter"; +import Order "mo:core/Order"; +import Text "mo:core/Text"; + +// curried version of data-view.mo + +persistent actor { + + module MapView { + + public func view(self : Map.Map, compare : (implicit : (K,K) -> Order.Order)) : (ko : ?K, count : Nat) -> [(K, V)]= + func (ko, count) { + let entries = switch ko { + case null { + self.entries() + }; + case (?k) { + self.entriesFrom(k) + }; + }; + entries.take(count).toArray(); + } + }; + + let map : Map.Map = Map.empty(); + + public query func mapView(ko: ?Nat, count: Nat) : async [(Nat, Text)] { + map.view(/*Nat.compare*/)(ko, count); + }; + + module SetView { + + public func view( + self : Set.Set, + compare : (implicit : (K,K) -> Order.Order)) : ( + ko : ?K, + count : Nat) -> [K] = + func (ko, count) { + let entries = switch ko { + case null { + self.values() + }; + case (?k) { + self.valuesFrom(k) + }; + }; + entries.take(count).toArray(); + }; + }; + + let set : Set.Set = Set.empty(); + + public query func setView(ko: ?Nat, count: Nat) : async [Nat] { + set.view(/*Nat.compare*/)(ko, count); + }; + + module ArrayView { + + public func view(self : [V]) : + (io : ?Nat, count : Nat) -> [V] = + func (io, count) { + // TODO: use slice instead + let entries = switch io { + case null { + self.values() + }; + case (?io) { + self.values().drop(io) + }; + }; + entries.take(count).toArray(); + }; + }; + + let array : [(Nat, Text)] = []; + + public query func arrayView(ko: ?Nat, count: Nat) : async [(Nat, Text)] { + array.view()(ko, count); + }; + +} From 3775ce611ad8be9f3afe9c4682d65ac821f6ee6c Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 21 Jan 2026 10:48:22 +0000 Subject: [PATCH 03/52] add infer_viwer --- src/mo_frontend/typing.ml | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 80dc772a517..f7175350734 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -3853,6 +3853,14 @@ and stable_pat pat = | AnnotP (pat', _) -> stable_pat pat' | _ -> false +and stable_id pat = + match pat.it with + | VarP id -> id + | ParP pat' + | AnnotP (pat', _) -> stable_id pat' + | _ -> assert false + + and infer_migration env obj_sort exp_opt = Option.map (fun exp -> @@ -4043,10 +4051,12 @@ and check_stab env sort scope dec_fields = | (T.Actor | T.Mixin), _ , IncludeD _ -> [] | (T.Actor | T.Mixin), Some {it = Stable; _}, VarD (id, _) -> check_stable id.it id.at; + infer_viewer env scope Var id; [id] | (T.Actor | T.Mixin), Some {it = Stable; _}, LetD (pat, _, _) when stable_pat pat -> let ids = T.Env.keys (gather_pat env Scope.empty pat).Scope.val_env in List.iter (fun id -> check_stable id pat.at) ids; + infer_viewer env scope Const (stable_id pat); List.map (fun id -> {it = id; at = pat.at; note = ()}) ids; | (T.Actor | T.Mixin), Some {it = Flexible; _} , (VarD _ | LetD _) -> [] | (T.Actor | T.Mixin), Some stab, _ -> @@ -4068,6 +4078,36 @@ and check_stab env sort scope dec_fields = src = {depr = None; track_region = id.at; region = id.at}}) ids) +and infer_viewer env scope mut id = + match Diag.with_message_store (recover_opt (fun msgs -> + (* let env_without_errors = {(adjoin env scope) with msgs } in *) + let env_without_errors = adjoin env scope in + let note() = empty_typ_note in + let at = id.at in + let dot_exp = + { it = DotE + ( {it = VarE {it = id.it; at ; note = (mut, None)}; + at; + note = note()}, + {it = "view"; note = (); at}, + ref None); + at; + note = note()} + in + let arg_exp = (false, ref {it = TupE []; at; note = note()}) in + let inst = {it = None; at; note = []} in + let exp = {it = CallE(None, dot_exp, inst, arg_exp); at; note = note()} in + let _t = infer_exp env_without_errors exp in + exp)) + with + | Error _ -> + info env id.at "viewer not found for %s" id.it; + () + | Ok (exp, _) -> + info env id.at "viewer found for %s" id.it; + () + + (* Blocks and Declarations *) and infer_block env decs at check_unused : T.typ * Scope.scope = From c5614cbd2aef695813d020bc13c4c4347a42ee1b Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 21 Jan 2026 16:25:01 +0000 Subject: [PATCH 04/52] working version (WIP) --- src/lowering/desugar.ml | 75 ++++++++++++++++++++++++++++++++------ src/mo_def/arrange.ml | 2 +- src/mo_def/syntax.ml | 4 +- src/mo_frontend/parser.mly | 14 +++---- src/mo_frontend/typing.ml | 14 ++++--- 5 files changed, 82 insertions(+), 27 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index a890dc656b1..3fca0356c90 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -505,7 +505,7 @@ and export_footprint self_id expr = let size = fresh_var "size" T.nat64 in let scope_con1 = Cons.fresh "T1" (Abs ([], scope_bound)) in let scope_con2 = Cons.fresh "T2" (Abs ([], Any)) in - let bind1 = typ_arg scope_con1 Scope scope_bound in + let bind1 = typ_arg scope_con1 Scope scope_bound in let bind2 = typ_arg scope_con2 Scope scope_bound in let ret_typ = T.Obj(Object,[{lab = "size"; typ = T.nat64; src = empty_src}]) in let caller = fresh_var "caller" caller in @@ -534,7 +534,7 @@ and export_runtime_information self_id = let v = "$"^lab in let scope_con1 = Cons.fresh "T1" (Abs ([], scope_bound)) in let scope_con2 = Cons.fresh "T2" (Abs ([], Any)) in - let bind1 = typ_arg scope_con1 Scope scope_bound 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 open Mo_config in @@ -589,7 +589,39 @@ and export_runtime_information self_id = ) ret_typ)) (Con (scope_con1, [])))) )], - [{ it = I.{ name = lab; var = v }; at = no_region; note = typ }]) + [{ it = I.{ name = lab; var = v }; at = no_region; note = typ }]) + +and export_view exp_opt id = + match exp_opt with + | None -> ([], [], []) + | Some view_exp -> + let open T in + let sort, _, tbs, ts1, ts2 = as_func view_exp.note.note_typ in + assert (tbs = []); + let vs = fresh_vars "param" ts1 in + let args = List.map arg_of_var vs in + let lab = "__view_"^id in + let v = "$"^lab in + let scope_con1 = Cons.fresh "T1" (Abs ([], scope_bound)) in + 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 typ = T.Func (Shared Query, Promises, [scope_bind], ts1, ts2) in + let caller = fresh_var "caller" caller in + ([ letD (var v typ) ( + funcE v (Shared Query) Promises [bind1] args ts2 ( + (asyncE T.Fut bind2 + (blockE [ + letD caller (primE I.ICCallerPrim []); + expD (assertE (orE (primE (I.RelPrim (principal, Operator.EqOp)) + [varE caller; selfRefE principal]) + (primE (I.OtherPrim "is_controller") [varE caller]))); + ] + (callE (exp view_exp) [] (tupE (List.map varE vs)))) + (Con (scope_con1, [])))) + )], + [T.{lab;typ; src = empty_src}], + [{ it = I.{ name = lab; var = v }; at = no_region; note = typ }]) and build_stabs (df : S.dec_field) : stab option list = match df.it.S.dec.it with | S.TypD _ -> [] @@ -607,14 +639,20 @@ and build_stabs (df : S.dec_field) : stab option list = match df.it.S.dec.it wit List.concat_map build_stabs decs | _ -> [df.it.S.stab] -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 +and build_actor at ts (exp_opt : Ir.exp option) self_id es obj_typ0 = + let fs0 = build_fields obj_typ0 in let stabs = List.concat_map build_stabs es in let ds = decs (List.map (fun ef -> ef.it.S.dec) es) in let pairs = List.map2 stabilize stabs ds in let idss = List.map fst pairs in let ids = List.concat idss in + let triples = List.map2 view stabs ds in + let view_ds = List.concat_map (fun (ds, _, _) -> ds) triples in + let view_fields = List.concat_map (fun (_, flds, _) -> flds) triples in + let view_fs = List.concat_map (fun (_, _, fs) -> fs) triples in + let (sort, tfs0) = T.as_obj obj_typ0 in + let obj_typ = T.Obj(sort, List.sort T.compare_field (tfs0@view_fields)) in + let fs = fs0@view_fs in let stab_fields = List.sort T.compare_field (List.map (fun (i, t) -> T.{lab = i; typ = t; src = empty_src}) ids) in @@ -627,6 +665,7 @@ and build_actor at ts (exp_opt : Ir.exp option) self_id es obj_typ = 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 candid = build_candid ts obj_typ in let sig_, stable_type, migration = match exp_opt with | None -> T.Single stab_fields, @@ -744,7 +783,8 @@ and build_actor at ts (exp_opt : Ir.exp option) self_id es obj_typ = 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, + (* let viewers_ds, viewers_vs = export_viewers *) + I.(ActorE (footprint_d @ runtime_info_d @ ds' @ view_ds, footprint_f @ runtime_info_f @ fs, { meta; preupgrade = (primE (I.ICStableWrite mem_ty) []); postupgrade = @@ -781,7 +821,7 @@ and stabilize stab_opt d = match s, d.it with | (S.Flexible, _) -> ([], fun _ -> d) - | (S.Stable, I.VarD(i, t, e)) -> + | (S.Stable viewer, I.VarD(i, t, e)) -> ([(i, T.Mut t)], fun get_state -> let v = fresh_var i t in @@ -790,8 +830,8 @@ and stabilize stab_opt d = e (varP v) (varE v) t)) - | (S.Stable, I.RefD _) -> assert false (* RefD cannot come from user code *) - | (S.Stable, I.LetD({it = I.VarP i; _} as p, e)) -> + | (S.Stable viewer, I.RefD _) -> assert false (* RefD cannot come from user code *) + | (S.Stable viewer, I.LetD({it = I.VarP i; _} as p, e)) -> let t = p.note in ([(i, t)], fun get_state -> @@ -801,7 +841,20 @@ and stabilize stab_opt d = e (varP v) (varE v) t)) - | (S.Stable, I.LetD _) -> + | (S.Stable viewer, I.LetD _) -> + assert false + +and view stab_opt d = + let s = match stab_opt with None -> S.Flexible | Some s -> s.it in + match s, d.it with + | (S.Flexible, _) -> + ([], [], []) + | (S.Stable viewer, I.VarD(i, t, e)) -> + export_view (!viewer) i + | (S.Stable viewer, I.RefD _) -> assert false (* RefD cannot come from user code *) + | (S.Stable viewer, I.LetD({it = I.VarP i; _}, e)) -> + export_view (!viewer) i + | (S.Stable viewer, I.LetD _) -> assert false and build_obj at s self_id dfs obj_typ = diff --git a/src/mo_def/arrange.ml b/src/mo_def/arrange.ml index d1bcffe28ad..047665d45af 100644 --- a/src/mo_def/arrange.ml +++ b/src/mo_def/arrange.ml @@ -230,7 +230,7 @@ module Make (Cfg : Config) = struct | Some s -> (match s.it with | Flexible -> Atom "Flexible" - | Stable -> Atom "Stable") + | Stable _ -> Atom "Stable") and typ_field (tf : typ_field) = source tf.at (match tf.it with | ValF (lab, t, m) -> "ValF" $$ [id lab; typ t; mut m] diff --git a/src/mo_def/syntax.ml b/src/mo_def/syntax.ml index f097eb1a443..36b6116ebe1 100644 --- a/src/mo_def/syntax.ml +++ b/src/mo_def/syntax.ml @@ -144,8 +144,6 @@ and vis' = let is_public vis = match vis.Source.it with Public _ -> true | _ -> false let is_private vis = match vis.Source.it with Private -> true | _ -> false -type stab = stab' Source.phrase -and stab' = Stable | Flexible type op_typ = Type.typ ref (* For overloaded resolution; initially Type.Pre. *) @@ -163,6 +161,8 @@ type sugar = bool (* Is the source of a function body a block ``, public functions as oneway, shared functions *) type id_ref = (string, mut' * exp option) Source.annotated_phrase +and stab = stab' Source.phrase +and stab' = Stable of exp option ref | Flexible and hole_sort = Named of string | Anon of int and exp = (exp', typ_note) Source.annotated_phrase and exp' = diff --git a/src/mo_frontend/parser.mly b/src/mo_frontend/parser.mly index e48bd5768eb..1ff8de26b1b 100644 --- a/src/mo_frontend/parser.mly +++ b/src/mo_frontend/parser.mly @@ -168,7 +168,7 @@ let share_stab default_stab stab_opt dec = | None -> (match dec.it with | VarD _ - | LetD _ -> Some default_stab + | LetD _ -> Some (default_stab ()) | _ -> None) | _ -> stab_opt @@ -184,7 +184,7 @@ let share_dec_field default_stab (df : dec_field) = | Public _ -> {df with it = {df.it with dec = share_dec df.it.dec; - stab = share_stab (Flexible @@ df.it.dec.at) df.it.stab df.it.dec}} + stab = share_stab (fun () -> Flexible @@ df.it.dec.at) df.it.stab df.it.dec}} | System -> ensure_system_cap df | _ when is_sugared_func_or_module (df.it.dec) -> {df with it = @@ -203,7 +203,7 @@ let share_dec_field default_stab (df : dec_field) = | TypD _ | MixinD _ | ClassD _ -> None - | _ -> Some default_stab) + | _ -> Some (default_stab())) | some -> some} } @@ -870,7 +870,7 @@ vis : stab : | (* empty *) { None } | FLEXIBLE { Some (Flexible @@ at $sloc) } - | STABLE { Some (Stable @@ at $sloc) } + | STABLE { Some ((Stable (ref None)) @@ at $sloc) } | TRANSIENT { Some (Flexible @@ at $sloc) } %inline persistent : @@ -964,7 +964,7 @@ dec_nonvar : let_or_exp named x (func_exp x.it sp tps p t is_sugar e) (at $sloc) } | eo=parenthetical_opt mk_d=obj_or_class_dec { mk_d eo } | MIXIN p=pat_plain dfs=obj_body { - let dfs = List.map (share_dec_field (Stable @@ no_region)) dfs in + let dfs = List.map (share_dec_field (fun () -> Stable (ref None) @@ no_region)) dfs in MixinD(p, dfs) @? at $sloc } | INCLUDE x=id e=exp(ob) { IncludeD(x, e, ref None) @? at $sloc } @@ -979,7 +979,7 @@ obj_or_class_dec : let named, x = xf sort $sloc in let e = if s.it = Type.Actor then - let default_stab = (if persistent.it then Stable else Flexible) @@ no_region in + let default_stab () = (if persistent.it then (Stable (ref None)) else Flexible) @@ no_region in let id = if named then Some x else None in AwaitE (Type.AwaitFut false, @@ -999,7 +999,7 @@ obj_or_class_dec : let x, dfs = cb in let dfs', tps', t' = if s.it = Type.Actor then - let default_stab = (if persistent.it then Stable else Flexible) @@ no_region in + let default_stab () = (if persistent.it then Stable (ref None) else Flexible) @@ no_region in (List.map (share_dec_field default_stab) dfs, ensure_scope_bind "" tps, (* Not declared async: insert AsyncT but deprecate in typing *) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index f7175350734..c32e645d17c 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4006,7 +4006,7 @@ and check_stable_defaults env sort dec_fields = warn env sort.note.at "M0217" "with flag --default-persistent-actors, the `persistent` keyword is redundant and can be removed"; List.iter (fun dec_field -> match dec_field.it.stab, dec_field.it.dec.it with - | Some {it = Stable; at; _}, (LetD _ | VarD _) -> + | Some {it = Stable _; at; _}, (LetD _ | VarD _) -> if at <> Source.no_region then warn env at "M0218" "redundant `stable` keyword, this declaration is implicitly stable" | _ -> ()) @@ -4049,14 +4049,14 @@ and check_stab env sort scope dec_fields = "misplaced stability declaration on field of non-actor"; [] | (T.Actor | T.Mixin), _ , IncludeD _ -> [] - | (T.Actor | T.Mixin), Some {it = Stable; _}, VarD (id, _) -> + | (T.Actor | T.Mixin), Some {it = Stable view; _}, VarD (id, _) -> check_stable id.it id.at; - infer_viewer env scope Var id; + infer_viewer env scope Var id view; [id] - | (T.Actor | T.Mixin), Some {it = Stable; _}, LetD (pat, _, _) when stable_pat pat -> + | (T.Actor | T.Mixin), Some {it = Stable view; _}, LetD (pat, _, _) when stable_pat pat -> let ids = T.Env.keys (gather_pat env Scope.empty pat).Scope.val_env in List.iter (fun id -> check_stable id pat.at) ids; - infer_viewer env scope Const (stable_id pat); + infer_viewer env scope Const (stable_id pat) view; List.map (fun id -> {it = id; at = pat.at; note = ()}) ids; | (T.Actor | T.Mixin), Some {it = Flexible; _} , (VarD _ | LetD _) -> [] | (T.Actor | T.Mixin), Some stab, _ -> @@ -4078,7 +4078,8 @@ and check_stab env sort scope dec_fields = src = {depr = None; track_region = id.at; region = id.at}}) ids) -and infer_viewer env scope mut id = +and infer_viewer env scope mut id viewer = + assert (!viewer = None); match Diag.with_message_store (recover_opt (fun msgs -> (* let env_without_errors = {(adjoin env scope) with msgs } in *) let env_without_errors = adjoin env scope in @@ -4105,6 +4106,7 @@ and infer_viewer env scope mut id = () | Ok (exp, _) -> info env id.at "viewer found for %s" id.it; + viewer := Some exp; () From 7ac47155f21d4a1b05740005b2c3a412f1efbf21 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 21 Jan 2026 16:38:07 +0000 Subject: [PATCH 05/52] disable debug spew --- src/mo_frontend/typing.ml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index c32e645d17c..6d0f49a2e23 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4081,8 +4081,8 @@ and check_stab env sort scope dec_fields = and infer_viewer env scope mut id viewer = assert (!viewer = None); match Diag.with_message_store (recover_opt (fun msgs -> - (* let env_without_errors = {(adjoin env scope) with msgs } in *) - let env_without_errors = adjoin env scope in + let env_without_errors = {(adjoin env scope) with msgs } in + (* let env_without_errors = adjoin env scope in *) (* uncomment to see errors *) let note() = empty_typ_note in let at = id.at in let dot_exp = @@ -4102,10 +4102,10 @@ and infer_viewer env scope mut id viewer = exp)) with | Error _ -> - info env id.at "viewer not found for %s" id.it; + (* info env id.at "viewer not found for %s" id.it; *) () | Ok (exp, _) -> - info env id.at "viewer found for %s" id.it; + (* info env id.at "viewer found for %s" id.it; *) viewer := Some exp; () From b115c08fe090178c1f4d7981c880537515983744 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 21 Jan 2026 21:03:46 +0000 Subject: [PATCH 06/52] reject inappropriate viewer --- src/lowering/desugar.ml | 8 ++++---- src/mo_frontend/typing.ml | 13 +++++++++---- test/run-drun/data-view-curried.mo | 6 ++++++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 3fca0356c90..8e05bac9829 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -613,9 +613,10 @@ and export_view exp_opt id = (asyncE T.Fut bind2 (blockE [ letD caller (primE I.ICCallerPrim []); - expD (assertE (orE (primE (I.RelPrim (principal, Operator.EqOp)) - [varE caller; selfRefE principal]) - (primE (I.OtherPrim "is_controller") [varE caller]))); +(* TODO: renable authentication + expD (assertE (orE (primE (I.RelPrim (principal, Operator.EqOp)) [varE caller; selfRefE principal]) + (primE (I.OtherPrim "is_controller") [varE caller]))); +*) ] (callE (exp view_exp) [] (tupE (List.map varE vs)))) (Con (scope_con1, [])))) @@ -783,7 +784,6 @@ and build_actor at ts (exp_opt : Ir.exp option) self_id es obj_typ0 = 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 - (* let viewers_ds, viewers_vs = export_viewers *) I.(ActorE (footprint_d @ runtime_info_d @ ds' @ view_ds, footprint_f @ runtime_info_f @ fs, { meta; preupgrade = (primE (I.ICStableWrite mem_ty) []); diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 6d0f49a2e23..4881f200c5c 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4081,8 +4081,8 @@ and check_stab env sort scope dec_fields = and infer_viewer env scope mut id viewer = assert (!viewer = None); match Diag.with_message_store (recover_opt (fun msgs -> - let env_without_errors = {(adjoin env scope) with msgs } in - (* let env_without_errors = adjoin env scope in *) (* uncomment to see errors *) + let env = {env with msgs} in (* don't record errors in outer env *) + let env = adjoin env scope in let note() = empty_typ_note in let at = id.at in let dot_exp = @@ -4098,8 +4098,13 @@ and infer_viewer env scope mut id viewer = let arg_exp = (false, ref {it = TupE []; at; note = note()}) in let inst = {it = None; at; note = []} in let exp = {it = CallE(None, dot_exp, inst, arg_exp); at; note = note()} in - let _t = infer_exp env_without_errors exp in - exp)) + let viewer_typ = infer_exp env exp in + (match T.normalize viewer_typ with + | T.Func(T.Local, T.Returns, [], ts1, ts2) -> + if List.for_all T.shared ts1 && List.for_all T.shared ts2 + then exp + else error env id.at "M0XXX" "viewer '%s.view()' has non-shared type" id.it + | _ -> error env id.at "M0XXX" "viewer '%s.view()' is not a function" id.it))) with | Error _ -> (* info env id.at "viewer not found for %s" id.it; *) diff --git a/test/run-drun/data-view-curried.mo b/test/run-drun/data-view-curried.mo index 8af5ad7bfbc..ae647b9874e 100644 --- a/test/run-drun/data-view-curried.mo +++ b/test/run-drun/data-view-curried.mo @@ -82,4 +82,10 @@ persistent actor { array.view()(ko, count); }; + + // here, [insible_array.view] produces a non-shared (mutable) type, omit viewer + // later maybe approximate by shared type. + let invisible_array : [[var Nat]] = []; + + } From 279f2516a4988167e04553d9f0e824ae0f633626 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 21 Jan 2026 21:57:56 +0000 Subject: [PATCH 07/52] reuse variable name for query name (cannot clash), support variables that have no view but are shared, renable auth --- src/lowering/desugar.ml | 24 +++++++++++++++++------- src/mo_frontend/typing.ml | 9 ++++++++- test/run-drun/data-view-curried.mo | 8 ++++++++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 8e05bac9829..89153ff1ea2 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -596,13 +596,24 @@ and export_view exp_opt id = | None -> ([], [], []) | Some view_exp -> let open T in - let sort, _, tbs, ts1, ts2 = as_func view_exp.note.note_typ in - assert (tbs = []); + let view_e = exp view_exp in + let ts1, ts2, mk_body = + match T.normalize view_exp.note.note_typ with + | T.Func(T.Local, _, tbs, ts1, ts2) -> + assert (tbs = []); + (* id.view() available *) + ts1, ts2, fun vs -> (callE view_e [] (tupE (List.map varE vs))) + | t -> + (* id, t shared *) + assert (T.shared t); + [], [t], fun _vs -> view_e + in + let vs = fresh_vars "param" ts1 in let args = List.map arg_of_var vs in - let lab = "__view_"^id in + let lab = id in (* we can just re-use id as lab since no other member can *) let v = "$"^lab in - let scope_con1 = Cons.fresh "T1" (Abs ([], scope_bound)) in + let scope_con1 = Cons.fresh "T1" (Abs ([], scope_bound)) in 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 @@ -612,13 +623,12 @@ and export_view exp_opt id = funcE v (Shared Query) Promises [bind1] args ts2 ( (asyncE T.Fut bind2 (blockE [ + (* authentication, self or controller only *) letD caller (primE I.ICCallerPrim []); -(* TODO: renable authentication expD (assertE (orE (primE (I.RelPrim (principal, Operator.EqOp)) [varE caller; selfRefE principal]) (primE (I.OtherPrim "is_controller") [varE caller]))); -*) ] - (callE (exp view_exp) [] (tupE (List.map varE vs)))) + (mk_body vs)) (Con (scope_con1, [])))) )], [T.{lab;typ; src = empty_src}], diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 4881f200c5c..43f1a66f337 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4108,7 +4108,14 @@ and infer_viewer env scope mut id viewer = with | Error _ -> (* info env id.at "viewer not found for %s" id.it; *) - () + (match T.Env.find_opt id.it scope.Scope.val_env with + | Some (typ, _, _) -> + let typ = T.as_immut typ in + if T.shared typ then + viewer := Some {it = VarE {it = id.it; at = id.at ; note = (mut, None)}; + at = id.at; + note = { empty_typ_note with note_typ = typ }} + | None -> assert false) | Ok (exp, _) -> (* info env id.at "viewer found for %s" id.it; *) viewer := Some exp; diff --git a/test/run-drun/data-view-curried.mo b/test/run-drun/data-view-curried.mo index ae647b9874e..1d477148d71 100644 --- a/test/run-drun/data-view-curried.mo +++ b/test/run-drun/data-view-curried.mo @@ -87,5 +87,13 @@ persistent actor { // later maybe approximate by shared type. let invisible_array : [[var Nat]] = []; + // shared values we can just display, sans viewer + var some_variant = #node (#leaf, 0, #leaf); + let some_record = {a=1;b ="hello"; c = true} ; + + // stable, non-shared values we can't just display in full, without viewer + let some_mutable_record = {var a = 1}; + } + From e14746eec7494f31e2ccc163572477c95ba97c46 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 21 Jan 2026 22:55:01 +0000 Subject: [PATCH 08/52] disable authentication for demo --- src/lowering/desugar.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 89153ff1ea2..df70b69827d 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -625,8 +625,8 @@ and export_view exp_opt id = (blockE [ (* authentication, self or controller only *) letD caller (primE I.ICCallerPrim []); - expD (assertE (orE (primE (I.RelPrim (principal, Operator.EqOp)) [varE caller; selfRefE principal]) - (primE (I.OtherPrim "is_controller") [varE caller]))); +(* expD (assertE (orE (primE (I.RelPrim (principal, Operator.EqOp)) [varE caller; selfRefE principal]) + (primE (I.OtherPrim "is_controller") [varE caller]))); *) ] (mk_body vs)) (Con (scope_con1, [])))) From 5e93c55d253032bf4cef9771e6e74b21127a4750 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 21 Jan 2026 23:00:47 +0000 Subject: [PATCH 09/52] fix moc.js --- src/js/astjs.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/astjs.ml b/src/js/astjs.ml index e9a3d40ca45..644d5486fea 100644 --- a/src/js/astjs.ml +++ b/src/js/astjs.ml @@ -612,7 +612,7 @@ module Make (Cfg : Config) = struct | Some s -> ( match s.it with | Flexible -> js_string "Flexible" - | Stable -> js_string "Stable") + | Stable _ -> js_string "Stable") and exp_field_js ef = let open Source in From 67a7aec4a3b2b5e43859d4a4ccf667d15cab8f63 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 2 Feb 2026 16:06:03 +0000 Subject: [PATCH 10/52] merge with master --- src/lowering/desugar.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index fc7529d5482..3562550a67f 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -686,7 +686,7 @@ and build_actor at ts (exp_opt : Ir.exp option) self_id es obj_typ0 = let view_fields = List.concat_map (fun (_, flds, _) -> flds) triples in let view_fs = List.concat_map (fun (_, _, fs) -> fs) triples in let (sort, tfs0) = T.as_obj obj_typ0 in - let obj_typ = T.Obj(sort, List.sort T.compare_field (tfs0@view_fields)) in + let obj_typ = T.Obj(sort, List.sort T.compare_field (tfs0@view_fields), []) in let fs = fs0@view_fs in let stab_fields = List.sort T.compare_field (List.map (fun (i, t) -> T.{lab = i; typ = t; src = empty_src}) ids) From 79df36bdd594f4efec506be50978d10d095d1af2 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 2 Feb 2026 17:46:03 +0000 Subject: [PATCH 11/52] removve old test --- test/run-drun/data-view.mo | 88 -------------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 test/run-drun/data-view.mo diff --git a/test/run-drun/data-view.mo b/test/run-drun/data-view.mo deleted file mode 100644 index 6e373a40102..00000000000 --- a/test/run-drun/data-view.mo +++ /dev/null @@ -1,88 +0,0 @@ -//MOC-FLAG --package core /home/crusso/motoko-core/src -import Map "mo:core/Map"; -import Set "mo:core/Set"; -import Nat "mo:core/Nat"; -import Iter "mo:core/Iter"; -import Order "mo:core/Order"; -import Text "mo:core/Text"; - -persistent actor { - - module MapView { - - public func view( - self : Map.Map, - compare : (implicit : (K,K) -> Order.Order), - ko : ?K, - count : Nat) : [(K, V)] { - let entries = switch ko { - case null { - self.entries() - }; - case (?k) { - self.entriesFrom(k) - }; - }; - entries.take(count).toArray(); - }; - }; - - let map : Map.Map = Map.empty(); - - public query func mapView(ko: ?Nat, count: Nat) : async [(Nat, Text)] { - map.view(Nat.compare, ko, count); - }; - - module SetView { - - public func view( - self : Set.Set, - compare : (implicit : (K,K) -> Order.Order), - ko : ?K, - count : Nat) : [K] { - let entries = switch ko { - case null { - self.values() - }; - case (?k) { - self.valuesFrom(k) - }; - }; - entries.take(count).toArray(); - }; - }; - - let set : Set.Set = Set.empty(); - - public query func setView(ko: ?Nat, count: Nat) : async [Nat] { - set.view(Nat.compare, ko, count); - }; - - module ArrayView { - - public func view( - self : [V], - io : ?Nat, - count : Nat) : [V] { - // TODO: use slice instead - let entries = switch io { - case null { - self.values() - }; - case (?io) { - self.values().drop(io) - }; - }; - entries.take(count).toArray(); - }; - }; - - let array : [(Nat, Text)] = []; - - public query func arrayView(ko: ?Nat, count: Nat) : async [(Nat, Text)] { - array.view(ko, count); - }; - - - -} From 55a2bbd9027a736002bb573a70a504474821f7cb Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 2 Feb 2026 17:48:05 +0000 Subject: [PATCH 12/52] mv test; add test output --- test/run-drun/{data-view-curried.mo => data-view.mo} | 0 test/run-drun/ok/data-view.drun-run.ok | 2 ++ test/run-drun/ok/data-view.tc.ok | 4 ++++ 3 files changed, 6 insertions(+) rename test/run-drun/{data-view-curried.mo => data-view.mo} (100%) create mode 100644 test/run-drun/ok/data-view.drun-run.ok create mode 100644 test/run-drun/ok/data-view.tc.ok diff --git a/test/run-drun/data-view-curried.mo b/test/run-drun/data-view.mo similarity index 100% rename from test/run-drun/data-view-curried.mo rename to test/run-drun/data-view.mo diff --git a/test/run-drun/ok/data-view.drun-run.ok b/test/run-drun/ok/data-view.drun-run.ok new file mode 100644 index 00000000000..a6f776f43c6 --- /dev/null +++ b/test/run-drun/ok/data-view.drun-run.ok @@ -0,0 +1,2 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/data-view.tc.ok b/test/run-drun/ok/data-view.tc.ok new file mode 100644 index 00000000000..082c5038ed8 --- /dev/null +++ b/test/run-drun/ok/data-view.tc.ok @@ -0,0 +1,4 @@ +data-view.mo:88.7-88.22: warning [M0194], unused identifier invisible_array (delete or rename to wildcard `_` or `_invisible_array`) +data-view.mo:91.7-91.19: warning [M0194], unused identifier some_variant (delete or rename to wildcard `_` or `_some_variant`) +data-view.mo:92.7-92.18: warning [M0194], unused identifier some_record (delete or rename to wildcard `_` or `_some_record`) +data-view.mo:95.7-95.26: warning [M0194], unused identifier some_mutable_record (delete or rename to wildcard `_` or `_some_mutable_record`) From 95ee3355828368d41f468400318536f4b953919c Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 2 Feb 2026 18:14:02 +0000 Subject: [PATCH 13/52] don't drop actor type fields in desugaring --- src/lowering/desugar.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 3562550a67f..c46197ee4ae 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -685,8 +685,8 @@ and build_actor at ts (exp_opt : Ir.exp option) self_id es obj_typ0 = let view_ds = List.concat_map (fun (ds, _, _) -> ds) triples in let view_fields = List.concat_map (fun (_, flds, _) -> flds) triples in let view_fs = List.concat_map (fun (_, _, fs) -> fs) triples in - let (sort, tfs0) = T.as_obj obj_typ0 in - let obj_typ = T.Obj(sort, List.sort T.compare_field (tfs0@view_fields), []) in + let (sort, tfs0, tfs1) = T.as_obj' obj_typ0 in + let obj_typ = T.Obj(sort, List.sort T.compare_field (tfs0@view_fields), tfs1) in let fs = fs0@view_fs in let stab_fields = List.sort T.compare_field (List.map (fun (i, t) -> T.{lab = i; typ = t; src = empty_src}) ids) From 59c3dcab83749352f9810a0d1b0ddbb3208aae83 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 2 Feb 2026 18:33:10 +0000 Subject: [PATCH 14/52] disable core dependent test for CI --- test/run-drun/{data-view.mo => data-view.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/run-drun/{data-view.mo => data-view.txt} (100%) diff --git a/test/run-drun/data-view.mo b/test/run-drun/data-view.txt similarity index 100% rename from test/run-drun/data-view.mo rename to test/run-drun/data-view.txt From f25f2162370b16d99c5d2344105f0dbf273b9647 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 2 Feb 2026 18:48:15 +0000 Subject: [PATCH 15/52] fix one --- src/mo_frontend/typing.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 7cd6f03d656..a8a70709c7b 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -3720,7 +3720,7 @@ and infer_obj env obj_sort exp_opt dec_fields at : T.typ = let _, scope = infer_block env decs at false in let t = object_of_scope env s dec_fields scope at in leave_scope env (private_identifiers scope.Scope.val_env) initial_usage; - let (_, fs) = T.as_obj t in + let (_, fs, _) = T.as_obj' t in if not env.pre then begin if s = T.Actor || s = T.Mixin then begin List.iter (fun T.{lab; typ; _} -> From 75710b673138cb928111b9f234e2c640d1d197bb Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Tue, 3 Feb 2026 12:33:47 +0000 Subject: [PATCH 16/52] enable core test --- test/run-drun/{data-view.txt => data-view-core.mo} | 0 test/run-drun/ok/data-view-core.drun-run.ok | 2 ++ test/run-drun/ok/data-view-core.tc.ok | 4 ++++ 3 files changed, 6 insertions(+) rename test/run-drun/{data-view.txt => data-view-core.mo} (100%) create mode 100644 test/run-drun/ok/data-view-core.drun-run.ok create mode 100644 test/run-drun/ok/data-view-core.tc.ok diff --git a/test/run-drun/data-view.txt b/test/run-drun/data-view-core.mo similarity index 100% rename from test/run-drun/data-view.txt rename to test/run-drun/data-view-core.mo diff --git a/test/run-drun/ok/data-view-core.drun-run.ok b/test/run-drun/ok/data-view-core.drun-run.ok new file mode 100644 index 00000000000..a6f776f43c6 --- /dev/null +++ b/test/run-drun/ok/data-view-core.drun-run.ok @@ -0,0 +1,2 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/data-view-core.tc.ok b/test/run-drun/ok/data-view-core.tc.ok new file mode 100644 index 00000000000..121ab3c87da --- /dev/null +++ b/test/run-drun/ok/data-view-core.tc.ok @@ -0,0 +1,4 @@ +data-view-core.mo:88.7-88.22: warning [M0194], unused identifier invisible_array (delete or rename to wildcard `_` or `_invisible_array`) +data-view-core.mo:91.7-91.19: warning [M0194], unused identifier some_variant (delete or rename to wildcard `_` or `_some_variant`) +data-view-core.mo:92.7-92.18: warning [M0194], unused identifier some_record (delete or rename to wildcard `_` or `_some_record`) +data-view-core.mo:95.7-95.26: warning [M0194], unused identifier some_mutable_record (delete or rename to wildcard `_` or `_some_mutable_record`) From a92dfee4fce56fc2a6c7f1809fc8a9bd91ae9bc1 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Tue, 3 Feb 2026 12:52:05 +0000 Subject: [PATCH 17/52] fixes --- test/run-drun/data-view-core.mo | 2 +- test/run-drun/ok/data-view.tc.ok | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/run-drun/data-view-core.mo b/test/run-drun/data-view-core.mo index 1d477148d71..7fc7dd31f6e 100644 --- a/test/run-drun/data-view-core.mo +++ b/test/run-drun/data-view-core.mo @@ -1,4 +1,4 @@ -//MOC-FLAG --package core /home/crusso/motoko-core/src +//MOC-FLAG --package core $MOTOKO_CORE import Map "mo:core/Map"; import Set "mo:core/Set"; import Nat "mo:core/Nat"; diff --git a/test/run-drun/ok/data-view.tc.ok b/test/run-drun/ok/data-view.tc.ok index 082c5038ed8..40159b709eb 100644 --- a/test/run-drun/ok/data-view.tc.ok +++ b/test/run-drun/ok/data-view.tc.ok @@ -1,4 +1,4 @@ -data-view.mo:88.7-88.22: warning [M0194], unused identifier invisible_array (delete or rename to wildcard `_` or `_invisible_array`) -data-view.mo:91.7-91.19: warning [M0194], unused identifier some_variant (delete or rename to wildcard `_` or `_some_variant`) -data-view.mo:92.7-92.18: warning [M0194], unused identifier some_record (delete or rename to wildcard `_` or `_some_record`) -data-view.mo:95.7-95.26: warning [M0194], unused identifier some_mutable_record (delete or rename to wildcard `_` or `_some_mutable_record`) +data-view.mo:22.7-22.22: warning [M0194], unused identifier invisible_array (delete or rename to wildcard `_` or `_invisible_array`) +data-view.mo:25.7-25.19: warning [M0194], unused identifier some_variant (delete or rename to wildcard `_` or `_some_variant`) +data-view.mo:26.7-26.18: warning [M0194], unused identifier some_record (delete or rename to wildcard `_` or `_some_record`) +data-view.mo:29.7-29.26: warning [M0194], unused identifier some_mutable_record (delete or rename to wildcard `_` or `_some_mutable_record`) From a2ec41717e265ca4eead8d0165171a4545babfb2 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 9 Feb 2026 13:04:45 +0000 Subject: [PATCH 18/52] experiment: data viewer (alternative approach) (#5820) --- src/idllib/arrange_idl.ml | 12 ++++++++++ src/lowering/desugar.ml | 48 +++++++++++++++++---------------------- src/mo_def/syntax.ml | 4 +++- src/mo_frontend/typing.ml | 22 +++++++++++++----- src/mo_idl/mo_to_idl.ml | 38 +++++++++++++++++++++++++++---- test/test-moc.js | 1 + 6 files changed, 87 insertions(+), 38 deletions(-) diff --git a/src/idllib/arrange_idl.ml b/src/idllib/arrange_idl.ml index 0b01f852cf9..89488970242 100644 --- a/src/idllib/arrange_idl.ml +++ b/src/idllib/arrange_idl.ml @@ -252,6 +252,18 @@ module Make (Cfg : Config) = struct kwd ppf ":"; str ppf x.it; pp_close_box ppf () + | Some {it=ClassT(args, {it = ServT ms;_ }); _} -> + pp_open_hbox ppf (); + kwd ppf "service"; + kwd ppf ":"; + pp_args ppf args; + str ppf " -> {"; + pp_open_vbox ppf 2; + List.iter (fun m -> pp_print_cut ppf (); pp_meth ppf m; str ppf ";") ms; + pp_print_break ppf 0 (-2); + str ppf "}"; + pp_close_box ppf (); + pp_close_box ppf () | Some {it=ClassT(args, t); _} -> pp_open_hbox ppf (); kwd ppf "service"; diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index c46197ee4ae..2f9d2827920 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -615,33 +615,31 @@ and export_runtime_information self_id = )], [{ it = I.{ name = lab; var = v }; at = no_region; note = typ }]) -and export_view exp_opt id = - match exp_opt with +and export_view viewer_opt = + match viewer_opt with | None -> ([], [], []) - | Some view_exp -> + | Some {viewer_body; viewer_field} -> let open T in - let view_e = exp view_exp in let ts1, ts2, mk_body = - match T.normalize view_exp.note.note_typ with - | T.Func(T.Local, _, tbs, ts1, ts2) -> - assert (tbs = []); + match (viewer_body, T.normalize viewer_field.typ) with + | DotViewV view_exp, T.Func(Shared Query, _, [_], ts1, ts2) -> (* id.view() available *) - ts1, ts2, fun vs -> (callE view_e [] (tupE (List.map varE vs))) - | t -> + ts1, ts2, fun vs -> callE (exp view_exp) [] (tupE (List.map varE vs)) + | DefaultV view_exp, T.Func(Shared Query, _, [_], [], [t]) -> (* id, t shared *) assert (T.shared t); - [], [t], fun _vs -> view_e + [], [t], fun _vs -> exp view_exp + | _ -> assert false in - let vs = fresh_vars "param" ts1 in let args = List.map arg_of_var vs in - let lab = id in (* we can just re-use id as lab since no other member can *) - let v = "$"^lab in + let lab = viewer_field.lab in + let v = fresh_id ("$"^lab) () in let scope_con1 = Cons.fresh "T1" (Abs ([], scope_bound)) in 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 typ = T.Func (Shared Query, Promises, [scope_bind], ts1, ts2) in + let typ = viewer_field.typ in let caller = fresh_var "caller" caller in ([ letD (var v typ) ( funcE v (Shared Query) Promises [bind1] args ts2 ( @@ -681,7 +679,7 @@ and build_actor at ts (exp_opt : Ir.exp option) self_id es obj_typ0 = let pairs = List.map2 stabilize stabs ds in let idss = List.map fst pairs in let ids = List.concat idss in - let triples = List.map2 view stabs ds in + let triples = List.map view stabs in let view_ds = List.concat_map (fun (ds, _, _) -> ds) triples in let view_fields = List.concat_map (fun (_, flds, _) -> flds) triples in let view_fs = List.concat_map (fun (_, _, fs) -> fs) triples in @@ -878,18 +876,14 @@ and stabilize stab_opt d = | (S.Stable viewer, I.LetD _) -> assert false -and view stab_opt d = - let s = match stab_opt with None -> S.Flexible | Some s -> s.it in - match s, d.it with - | (S.Flexible, _) -> - ([], [], []) - | (S.Stable viewer, I.VarD(i, t, e)) -> - export_view (!viewer) i - | (S.Stable viewer, I.RefD _) -> assert false (* RefD cannot come from user code *) - | (S.Stable viewer, I.LetD({it = I.VarP i; _}, e)) -> - export_view (!viewer) i - | (S.Stable viewer, I.LetD _) -> - assert false +and view stab_opt = + match stab_opt with + | None -> ([], [], []) + | Some stab -> + match stab.it with + | S.Flexible -> ([], [], []) + | S.Stable viewer -> + export_view (!viewer) and build_obj at s self_id dfs obj_typ = let fs = build_fields obj_typ in diff --git a/src/mo_def/syntax.ml b/src/mo_def/syntax.ml index b173b83daff..53cad66c4d5 100644 --- a/src/mo_def/syntax.ml +++ b/src/mo_def/syntax.ml @@ -178,8 +178,10 @@ let break_label kind (id_opt : id option) = type id_ref = (string, mut' * exp option) Source.annotated_phrase +and viewer_body = DotViewV of exp | DefaultV of exp +and viewer = {viewer_body : viewer_body; viewer_field : Type.field} and stab = stab' Source.phrase -and stab' = Stable of exp option ref | Flexible +and stab' = Stable of viewer option ref | Flexible and hole_sort = Named of string | Anon of int and exp = (exp', typ_note) Source.annotated_phrase and exp' = diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index a8a70709c7b..9c10b93eb27 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4075,7 +4075,12 @@ and infer_viewer env scope mut id viewer = (match T.normalize viewer_typ with | T.Func(T.Local, T.Returns, [], ts1, ts2) -> if List.for_all T.shared ts1 && List.for_all T.shared ts2 - then exp + then { viewer_body = DotViewV exp; + viewer_field = + T.{ lab = id.it; + typ = Func (Shared Query, Promises, [scope_bind], ts1, ts2); + src = empty_src }; + } else error env id.at "M0XXX" "viewer '%s.view()' has non-shared type" id.it | _ -> error env id.at "M0XXX" "viewer '%s.view()' is not a function" id.it))) with @@ -4085,13 +4090,18 @@ and infer_viewer env scope mut id viewer = | Some (typ, _, _) -> let typ = T.as_immut typ in if T.shared typ then - viewer := Some {it = VarE {it = id.it; at = id.at ; note = (mut, None)}; - at = id.at; - note = { empty_typ_note with note_typ = typ }} + viewer := Some { viewer_body = DefaultV + {it = VarE {it = id.it; at = id.at ; note = (mut, None)}; + at = id.at; + note = { empty_typ_note with note_typ = typ }}; + viewer_field = + T.{ lab = id.it; + typ = Func (Shared Query, Promises, [scope_bind], [], [typ]); + src = empty_src } } | None -> assert false) - | Ok (exp, _) -> + | Ok (exp_typ, _) -> (* info env id.at "viewer found for %s" id.it; *) - viewer := Some exp; + viewer := Some exp_typ; () diff --git a/src/mo_idl/mo_to_idl.ml b/src/mo_idl/mo_to_idl.ml index 0e652c885d9..18dc98cb27a 100644 --- a/src/mo_idl/mo_to_idl.ml +++ b/src/mo_idl/mo_to_idl.ml @@ -214,18 +214,48 @@ module MakeState() = struct dec::list ) !env [] + let rec gather_views acc dfs = + E.(match dfs with + | [] -> acc + | df :: dfs1 -> + match df.it.dec.it with + | E.IncludeD (_, _, include_note) -> + (match !include_note with + | Some note -> gather_views acc (note.decs @ dfs1) + | None -> gather_views acc dfs1) + | _ -> + (match df.it.stab with + | Some { it = Stable exp_ref; _} -> + (match !exp_ref with + None -> gather_views acc dfs1 + | Some {viewer_field;_} -> gather_views (viewer_field::acc) dfs1) + | _ -> + gather_views acc dfs1)) + + let extend_obj t tfs = + if tfs = [] then t else + match normalize t with + | Obj(s, tfs1, tfs2) -> + Obj(s, List.sort compare_field (tfs1 @ tfs), tfs2) + | _ -> assert false + let actor prog = let open E in let { body = cub; _ } = (CompUnit.comp_unit_of_prog false prog).it in match cub.it with | ProgU _ | ModuleU _ | MixinU _ -> None - | ActorU _ -> Some (typ cub.note.note_typ) - | ActorClassU _ -> + | ActorU (_, _, _, dfs) -> + let viewer_tfs = gather_views [] dfs in + let extended_actor_typ = extend_obj cub.note.note_typ viewer_tfs in + Some (typ extended_actor_typ) + | ActorClassU (_, _, _, _, _, _, _, _, dfs) -> (match normalize cub.note.note_typ with | Func (Local, Returns, [tb], ts1, [t2]) -> let args = List.map arg_typ (List.map (open_ [Non]) ts1) in - let (_, _, rng) = as_async (normalize (open_ [Non] t2)) in - let actor = typ rng in + let (_, _, actor_typ) = as_async (normalize (open_ [Non] t2)) in + let viewer_tfs = gather_views [] dfs in + let extended_actor_typ = extend_obj actor_typ viewer_tfs in + let actor = typ extended_actor_typ in Some (I.ClassT (args, actor) @@ cub.at) | _ -> assert false ) diff --git a/test/test-moc.js b/test/test-moc.js index 223e4eaf7d5..167b27f479a 100644 --- a/test/test-moc.js +++ b/test/test-moc.js @@ -192,6 +192,7 @@ type T = nat; service : { /// Function comment main: () -> (T) query; + x: () -> (T) query; } `.trim() + "\n"; assert.deepStrictEqual(Motoko.candid("ast.mo"), { From a841281114c221df15a856f020a33b0b4a877bee Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Thu, 12 Feb 2026 10:38:06 +0000 Subject: [PATCH 19/52] only reveal view methods in .did file; not custom sections. Then front-end can see them, but other canisters cannot --- src/lowering/desugar.ml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 2f9d2827920..b723fb2c1f6 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -681,7 +681,8 @@ and build_actor at ts (exp_opt : Ir.exp option) self_id es obj_typ0 = let ids = List.concat idss in let triples = List.map view stabs in let view_ds = List.concat_map (fun (ds, _, _) -> ds) triples in - let view_fields = List.concat_map (fun (_, flds, _) -> flds) triples in + (* let view_fields = List.concat_map (fun (_, flds, _) -> flds) triples in *) + let view_fields = [] in let view_fs = List.concat_map (fun (_, _, fs) -> fs) triples in let (sort, tfs0, tfs1) = T.as_obj' obj_typ0 in let obj_typ = T.Obj(sort, List.sort T.compare_field (tfs0@view_fields), tfs1) in From b4e2b512675bce46f2a7161168535409dc54aac8 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 18 Feb 2026 16:14:20 +0000 Subject: [PATCH 20/52] better error on access control failure --- src/lowering/desugar.ml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index b723fb2c1f6..4c84e1dbea5 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -647,8 +647,12 @@ and export_view viewer_opt = (blockE [ (* authentication, self or controller only *) letD caller (primE I.ICCallerPrim []); -(* expD (assertE (orE (primE (I.RelPrim (principal, Operator.EqOp)) [varE caller; selfRefE principal]) - (primE (I.OtherPrim "is_controller") [varE caller]))); *) + expD (ifE (orE + (primE (I.RelPrim (principal, Operator.EqOp)) [varE caller; selfRefE principal]) + (primE (I.OtherPrim "is_controller") [varE caller])) + (unitE()) + (primE (Ir.OtherPrim "trap") + [textE "Unauthorized caller (caller must be self or some controller)"])) ] (mk_body vs)) (Con (scope_con1, [])))) From 27b70fd7502642893094319514c78baa70d1838f Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 18 Feb 2026 17:15:07 +0000 Subject: [PATCH 21/52] add isAdmin(caller) access control checks when available --- src/lowering/desugar.ml | 20 +++++++++++++++----- src/mo_def/syntax.ml | 6 +++++- src/mo_frontend/typing.ml | 11 +++++++++-- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 4c84e1dbea5..5b6fc86df8b 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -618,7 +618,7 @@ and export_runtime_information self_id = and export_view viewer_opt = match viewer_opt with | None -> ([], [], []) - | Some {viewer_body; viewer_field} -> + | Some {viewer_body; viewer_field; viewer_isAdmin_available} -> let open T in let ts1, ts2, mk_body = match (viewer_body, T.normalize viewer_field.typ) with @@ -641,18 +641,28 @@ and export_view viewer_opt = let bind2 = typ_arg scope_con2 Scope scope_bound in let typ = viewer_field.typ in let caller = fresh_var "caller" caller in + let is_self_or_controller = + orE (primE (I.RelPrim (principal, Operator.EqOp)) [varE caller; selfRefE principal]) + (primE (I.OtherPrim "is_controller") [varE caller]) + in + let access_control = + if viewer_isAdmin_available then + let isAdmin = var "isAdmin" T.(Func(Local, Returns, [], [principal], [bool])) + in + orE is_self_or_controller + (callE (varE isAdmin) [] (varE caller)) + else is_self_or_controller + in ([ letD (var v typ) ( funcE v (Shared Query) Promises [bind1] args ts2 ( (asyncE T.Fut bind2 (blockE [ (* authentication, self or controller only *) letD caller (primE I.ICCallerPrim []); - expD (ifE (orE - (primE (I.RelPrim (principal, Operator.EqOp)) [varE caller; selfRefE principal]) - (primE (I.OtherPrim "is_controller") [varE caller])) + expD (ifE access_control (unitE()) (primE (Ir.OtherPrim "trap") - [textE "Unauthorized caller (caller must be self or some controller)"])) + [textE "Unauthorized caller (caller must be self, some controller or isAdmin(caller))"])) ] (mk_body vs)) (Con (scope_con1, [])))) diff --git a/src/mo_def/syntax.ml b/src/mo_def/syntax.ml index 53cad66c4d5..95756f7aeef 100644 --- a/src/mo_def/syntax.ml +++ b/src/mo_def/syntax.ml @@ -179,7 +179,11 @@ let break_label kind (id_opt : id option) = type id_ref = (string, mut' * exp option) Source.annotated_phrase and viewer_body = DotViewV of exp | DefaultV of exp -and viewer = {viewer_body : viewer_body; viewer_field : Type.field} +and viewer = { + viewer_body : viewer_body; + viewer_field : Type.field; + viewer_isAdmin_available : bool; + } and stab = stab' Source.phrase and stab' = Stable of viewer option ref | Flexible and hole_sort = Named of string | Anon of int diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 9c10b93eb27..df2c13cd551 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4053,8 +4053,13 @@ and check_stab env sort scope dec_fields = and infer_viewer env scope mut id viewer = assert (!viewer = None); + let viewer_isAdmin_available = + (match T.Env.find_opt "isAdmin" env.vals with + | Some (t, _, _, _) -> T.sub t (T.Func(T.Local, T.Returns, [], [T.principal], [T.bool])) + | _ -> false) + in match Diag.with_message_store (recover_opt (fun msgs -> - let env = {env with msgs} in (* don't record errors in outer env *) + let env = {env with msgs} in (* don't record errors in outer env *) let env = adjoin env scope in let note() = empty_typ_note in let at = id.at in @@ -4080,6 +4085,7 @@ and infer_viewer env scope mut id viewer = T.{ lab = id.it; typ = Func (Shared Query, Promises, [scope_bind], ts1, ts2); src = empty_src }; + viewer_isAdmin_available } else error env id.at "M0XXX" "viewer '%s.view()' has non-shared type" id.it | _ -> error env id.at "M0XXX" "viewer '%s.view()' is not a function" id.it))) @@ -4097,7 +4103,8 @@ and infer_viewer env scope mut id viewer = viewer_field = T.{ lab = id.it; typ = Func (Shared Query, Promises, [scope_bind], [], [typ]); - src = empty_src } } + src = empty_src }; + viewer_isAdmin_available} | None -> assert false) | Ok (exp_typ, _) -> (* info env id.at "viewer found for %s" id.it; *) From 85b16bc7c3bad4e732e1cb5a731c9210d04c4bed Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Thu, 19 Feb 2026 17:44:27 +0000 Subject: [PATCH 22/52] prefix stable var queries with __; fix resolution of isAdmin; mark isAdmin used --- src/mo_frontend/typing.ml | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 627a115567a..37f46941f9f 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -3771,7 +3771,6 @@ and infer_obj env obj_sort exp_opt dec_fields at : T.typ = let initial_usage = enter_scope env in let _, scope = infer_block env decs at false in let t = object_of_scope env s dec_fields scope at in - leave_scope env (private_identifiers scope.Scope.val_env) initial_usage; let (_, fs, _) = T.as_obj' t in if not env.pre then begin if s = T.Actor || s = T.Mixin then begin @@ -3802,6 +3801,7 @@ and infer_obj env obj_sort exp_opt dec_fields at : T.typ = let stab_tfs = check_stab env obj_sort scope dec_fields in check_migration env stab_tfs exp_opt end; + leave_scope env (private_identifiers scope.Scope.val_env) initial_usage; t and check_parenthetical env typ_opt = function @@ -4105,11 +4105,19 @@ and check_stab env sort scope dec_fields = and infer_viewer env scope mut id viewer = assert (!viewer = None); - let viewer_isAdmin_available = - (match T.Env.find_opt "isAdmin" env.vals with - | Some (t, _, _, _) -> T.sub t (T.Func(T.Local, T.Returns, [], [T.principal], [T.bool])) - | _ -> false) + let isAdmin_available () = + let isAdmin = "isAdmin" in + let env1 = adjoin env scope in + let available = + match T.Env.find_opt isAdmin env1.vals with + | Some (t, _, _, _) -> + T.sub t (T.Func(T.Local, T.Returns, [], [T.principal], [T.bool])) + | _ -> false + in + if available then use_identifier env1 isAdmin; + available in + let lab = "__" ^ id.it in match Diag.with_message_store (recover_opt (fun msgs -> let env = {env with msgs} in (* don't record errors in outer env *) let env = adjoin env scope in @@ -4132,15 +4140,16 @@ and infer_viewer env scope mut id viewer = (match T.normalize viewer_typ with | T.Func(T.Local, T.Returns, [], ts1, ts2) -> if List.for_all T.shared ts1 && List.for_all T.shared ts2 - then { viewer_body = DotViewV exp; + then + { viewer_body = DotViewV exp; viewer_field = - T.{ lab = id.it; + T.{ lab; typ = Func (Shared Query, Promises, [scope_bind], ts1, ts2); src = empty_src }; - viewer_isAdmin_available + viewer_isAdmin_available = isAdmin_available(); } - else error env id.at "M0XXX" "viewer '%s.view()' has non-shared type" id.it - | _ -> error env id.at "M0XXX" "viewer '%s.view()' is not a function" id.it))) + else raise Recover + | _ -> raise Recover))) with | Error _ -> (* info env id.at "viewer not found for %s" id.it; *) @@ -4153,10 +4162,10 @@ and infer_viewer env scope mut id viewer = at = id.at; note = { empty_typ_note with note_typ = typ }}; viewer_field = - T.{ lab = id.it; + T.{ lab; typ = Func (Shared Query, Promises, [scope_bind], [], [typ]); src = empty_src }; - viewer_isAdmin_available} + viewer_isAdmin_available = isAdmin_available() } | None -> assert false) | Ok (exp_typ, _) -> (* info env id.at "viewer found for %s" id.it; *) From bc7ce790bc7116c56a886f09a56943a29be9529e Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Fri, 20 Feb 2026 08:07:59 +0000 Subject: [PATCH 23/52] use no to prefix queries (avoiding any clash and enabling easy discovery) --- src/mo_frontend/typing.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 37f46941f9f..5c3986c5c3a 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4117,7 +4117,7 @@ and infer_viewer env scope mut id viewer = if available then use_identifier env1 isAdmin; available in - let lab = "__" ^ id.it in + let lab = "." ^ id.it in match Diag.with_message_store (recover_opt (fun msgs -> let env = {env with msgs} in (* don't record errors in outer env *) let env = adjoin env scope in From 89d3a97091079f132a2f63ebe410a1c6790134b2 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Fri, 20 Feb 2026 08:07:59 +0000 Subject: [PATCH 24/52] use . not __ to prefix queries (avoiding any clash and enabling easy discovery) --- src/mo_frontend/typing.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 37f46941f9f..5c3986c5c3a 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4117,7 +4117,7 @@ and infer_viewer env scope mut id viewer = if available then use_identifier env1 isAdmin; available in - let lab = "__" ^ id.it in + let lab = "." ^ id.it in match Diag.with_message_store (recover_opt (fun msgs -> let env = {env with msgs} in (* don't record errors in outer env *) let env = adjoin env scope in From f791e073a148531b475260fb2ef7ee59074e420b Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Fri, 20 Feb 2026 08:18:34 +0000 Subject: [PATCH 25/52] revert to __ (. was a bad idea) --- src/mo_frontend/typing.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 5c3986c5c3a..37f46941f9f 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4117,7 +4117,7 @@ and infer_viewer env scope mut id viewer = if available then use_identifier env1 isAdmin; available in - let lab = "." ^ id.it in + let lab = "__" ^ id.it in match Diag.with_message_store (recover_opt (fun msgs -> let env = {env with msgs} in (* don't record errors in outer env *) let env = adjoin env scope in From 6922bcf797c27cc187ba03f51bc6a0fc60d08bad Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 23 Feb 2026 11:46:16 +0000 Subject: [PATCH 26/52] update test output --- src/mo_frontend/typing.ml | 3 +++ test/run-drun/ok/data-view-core.tc.ok | 4 ---- test/run-drun/ok/data-view.tc.ok | 4 ---- 3 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 test/run-drun/ok/data-view-core.tc.ok delete mode 100644 test/run-drun/ok/data-view.tc.ok diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 37f46941f9f..6a4b0f809c8 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4118,6 +4118,9 @@ and infer_viewer env scope mut id viewer = available in let lab = "__" ^ id.it in + if T.(Env.mem lab (scope.Scope.val_env) || Lib.String.chop_prefix "__motoko" lab <> None) + then () (* avoid any clash with local or reserved `__motoko_XXX` members by omitting viewer *) + else match Diag.with_message_store (recover_opt (fun msgs -> let env = {env with msgs} in (* don't record errors in outer env *) let env = adjoin env scope in diff --git a/test/run-drun/ok/data-view-core.tc.ok b/test/run-drun/ok/data-view-core.tc.ok deleted file mode 100644 index 121ab3c87da..00000000000 --- a/test/run-drun/ok/data-view-core.tc.ok +++ /dev/null @@ -1,4 +0,0 @@ -data-view-core.mo:88.7-88.22: warning [M0194], unused identifier invisible_array (delete or rename to wildcard `_` or `_invisible_array`) -data-view-core.mo:91.7-91.19: warning [M0194], unused identifier some_variant (delete or rename to wildcard `_` or `_some_variant`) -data-view-core.mo:92.7-92.18: warning [M0194], unused identifier some_record (delete or rename to wildcard `_` or `_some_record`) -data-view-core.mo:95.7-95.26: warning [M0194], unused identifier some_mutable_record (delete or rename to wildcard `_` or `_some_mutable_record`) diff --git a/test/run-drun/ok/data-view.tc.ok b/test/run-drun/ok/data-view.tc.ok deleted file mode 100644 index 40159b709eb..00000000000 --- a/test/run-drun/ok/data-view.tc.ok +++ /dev/null @@ -1,4 +0,0 @@ -data-view.mo:22.7-22.22: warning [M0194], unused identifier invisible_array (delete or rename to wildcard `_` or `_invisible_array`) -data-view.mo:25.7-25.19: warning [M0194], unused identifier some_variant (delete or rename to wildcard `_` or `_some_variant`) -data-view.mo:26.7-26.18: warning [M0194], unused identifier some_record (delete or rename to wildcard `_` or `_some_record`) -data-view.mo:29.7-29.26: warning [M0194], unused identifier some_mutable_record (delete or rename to wildcard `_` or `_some_mutable_record`) From b5bfb07794f0d68405fe8a2672b23ccf1d84277c Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 23 Feb 2026 12:00:23 +0000 Subject: [PATCH 27/52] add compiler flag --generate-view-queries (disabled by default) --- src/exes/moc.ml | 4 ++++ src/mo_config/flags.ml | 1 + src/mo_frontend/typing.ml | 2 ++ test/run-drun/data-view-core.mo | 2 +- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/exes/moc.ml b/src/exes/moc.ml index 3fb19c5bae9..6175c053bfd 100644 --- a/src/exes/moc.ml +++ b/src/exes/moc.ml @@ -199,6 +199,10 @@ let argspec = Arg.Unit (fun () -> Flags.rtti := true), " enable experimental support for precise runtime type information (default with enhanced orthogonal persistence)"; + "--generate-view-queries", + Arg.Unit (fun () -> Flags.generate_view_queries := true), + " auto-generate queries for stable variables; preferring applicable .view() methods (default false)"; + "--rts-stack-pages", Arg.Int (fun pages -> Flags.rts_stack_pages := Some pages), " set maximum number of pages available for runtime system stack (default " ^ (Int.to_string Flags.rts_stack_pages_default) ^ ", only available with classical persistence)"; diff --git a/src/mo_config/flags.ml b/src/mo_config/flags.ml index acca38845df..85b2ac9d923 100644 --- a/src/mo_config/flags.ml +++ b/src/mo_config/flags.ml @@ -85,6 +85,7 @@ let experimental_stable_memory_default = 0 (* _ < 0: error; _ = 0: warn, _ > 0: let experimental_stable_memory = ref experimental_stable_memory_default let typechecker_combine_srcs = ref false (* useful for the language server *) let blob_import_placeholders = ref false (* when enabled, blob:file imports resolve as empty blobs *) +let generate_view_queries = ref false let default_warning_levels = M.empty |> M.add "M0223" Allow (* don't report redundant instantions *) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 6a4b0f809c8..4b42fe85b79 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4104,6 +4104,8 @@ and check_stab env sort scope dec_fields = ids) and infer_viewer env scope mut id viewer = + if not !Flags.generate_view_queries then () + else assert (!viewer = None); let isAdmin_available () = let isAdmin = "isAdmin" in diff --git a/test/run-drun/data-view-core.mo b/test/run-drun/data-view-core.mo index 7fc7dd31f6e..3944d913106 100644 --- a/test/run-drun/data-view-core.mo +++ b/test/run-drun/data-view-core.mo @@ -1,4 +1,4 @@ -//MOC-FLAG --package core $MOTOKO_CORE +//MOC-FLAG --generate-view-queries --package core $MOTOKO_CORE import Map "mo:core/Map"; import Set "mo:core/Set"; import Nat "mo:core/Nat"; From 696638dde431d002f30454e7f79bb4b053fc4998 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 23 Feb 2026 12:36:49 +0000 Subject: [PATCH 28/52] fix bug in flag logic; refactor for clarity --- src/mo_config/flags.ml | 2 +- src/mo_frontend/typing.ml | 139 +++++++++++++++++++------------------- 2 files changed, 72 insertions(+), 69 deletions(-) diff --git a/src/mo_config/flags.ml b/src/mo_config/flags.ml index 85b2ac9d923..7b7cff69e67 100644 --- a/src/mo_config/flags.ml +++ b/src/mo_config/flags.ml @@ -109,7 +109,7 @@ let is_warning_enabled code = not (is_warning_disabled code) let skip_gc_deprecation_warning = ref false let gc_strategy_to_str : gc_strategy -> string = fun gc_strategy -> - match gc_strategy with + match gc_strategy with | Copying -> "copying" | MarkCompact -> "compacting" | Generational -> "generational" diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 4b42fe85b79..ca14cc17e08 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4106,77 +4106,80 @@ and check_stab env sort scope dec_fields = and infer_viewer env scope mut id viewer = if not !Flags.generate_view_queries then () else - assert (!viewer = None); - let isAdmin_available () = - let isAdmin = "isAdmin" in - let env1 = adjoin env scope in - let available = - match T.Env.find_opt isAdmin env1.vals with - | Some (t, _, _, _) -> - T.sub t (T.Func(T.Local, T.Returns, [], [T.principal], [T.bool])) - | _ -> false - in - if available then use_identifier env1 isAdmin; - available - in - let lab = "__" ^ id.it in - if T.(Env.mem lab (scope.Scope.val_env) || Lib.String.chop_prefix "__motoko" lab <> None) - then () (* avoid any clash with local or reserved `__motoko_XXX` members by omitting viewer *) - else - match Diag.with_message_store (recover_opt (fun msgs -> - let env = {env with msgs} in (* don't record errors in outer env *) - let env = adjoin env scope in - let note() = empty_typ_note in - let at = id.at in - let dot_exp = - { it = DotE - ( {it = VarE {it = id.it; at ; note = (mut, None)}; - at; - note = note()}, - {it = "view"; note = (); at}, - ref None); - at; - note = note()} + begin + assert (!viewer = None); + let isAdmin_available () = + let isAdmin = "isAdmin" in + let env1 = adjoin env scope in + let available = + match T.Env.find_opt isAdmin env1.vals with + | Some (t, _, _, _) -> + T.sub t (T.Func(T.Local, T.Returns, [], [T.principal], [T.bool])) + | _ -> false + in + if available then use_identifier env1 isAdmin; + available in - let arg_exp = (false, ref {it = TupE []; at; note = note()}) in - let inst = {it = None; at; note = []} in - let exp = {it = CallE(None, dot_exp, inst, arg_exp); at; note = note()} in - let viewer_typ = infer_exp env exp in - (match T.normalize viewer_typ with - | T.Func(T.Local, T.Returns, [], ts1, ts2) -> - if List.for_all T.shared ts1 && List.for_all T.shared ts2 - then - { viewer_body = DotViewV exp; - viewer_field = - T.{ lab; + let lab = "__" ^ id.it in + if T.(Env.mem lab (scope.Scope.val_env) || Lib.String.chop_prefix "__motoko" lab <> None) + then () (* avoid any clash with local or reserved `__motoko_XXX` members by omitting viewer *) + else + let infer_dot_view = + Diag.with_message_store (recover_opt (fun msgs -> + let env = {env with msgs} in (* don't record errors in outer env *) + let env = adjoin env scope in + let note() = empty_typ_note in + let at = id.at in + let dot_exp = + { it = DotE + ( {it = VarE {it = id.it; at ; note = (mut, None)}; + at; + note = note()}, + {it = "view"; note = (); at}, + ref None); + at; + note = note()} + in + let arg_exp = (false, ref {it = TupE []; at; note = note()}) in + let inst = {it = None; at; note = []} in + let exp = {it = CallE(None, dot_exp, inst, arg_exp); at; note = note()} in + let viewer_typ = infer_exp env exp in + (match T.normalize viewer_typ with + | T.Func(T.Local, T.Returns, [], ts1, ts2) -> + if List.for_all T.shared ts1 && List.for_all T.shared ts2 + then + { viewer_body = DotViewV exp; + viewer_field = + T.{ lab; typ = Func (Shared Query, Promises, [scope_bind], ts1, ts2); src = empty_src }; - viewer_isAdmin_available = isAdmin_available(); - } - else raise Recover - | _ -> raise Recover))) - with - | Error _ -> - (* info env id.at "viewer not found for %s" id.it; *) - (match T.Env.find_opt id.it scope.Scope.val_env with - | Some (typ, _, _) -> - let typ = T.as_immut typ in - if T.shared typ then - viewer := Some { viewer_body = DefaultV - {it = VarE {it = id.it; at = id.at ; note = (mut, None)}; - at = id.at; - note = { empty_typ_note with note_typ = typ }}; - viewer_field = - T.{ lab; - typ = Func (Shared Query, Promises, [scope_bind], [], [typ]); - src = empty_src }; - viewer_isAdmin_available = isAdmin_available() } - | None -> assert false) - | Ok (exp_typ, _) -> - (* info env id.at "viewer found for %s" id.it; *) - viewer := Some exp_typ; - () - + viewer_isAdmin_available = isAdmin_available(); + } + else raise Recover + | _ -> raise Recover))) + in + match infer_dot_view with + | Ok (exp_typ, _) -> + (* info env id.at "viewer found for %s" id.it; *) + viewer := Some exp_typ; + () + | Error _ -> + (* info env id.at "viewer not found for %s" id.it; *) + (match T.Env.find_opt id.it scope.Scope.val_env with + | Some (typ, _, _) -> + let typ = T.as_immut typ in + if T.shared typ then + viewer := Some { viewer_body = DefaultV + {it = VarE {it = id.it; at = id.at ; note = (mut, None)}; + at = id.at; + note = { empty_typ_note with note_typ = typ }}; + viewer_field = + T.{ lab; + typ = Func (Shared Query, Promises, [scope_bind], [], [typ]); + src = empty_src }; + viewer_isAdmin_available = isAdmin_available() } + | None -> assert false) + end (* Blocks and Declarations *) From b06f119e4ded1f7d92bd12088a18c9bc96b5acda Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 4 Mar 2026 10:32:27 +0000 Subject: [PATCH 29/52] missing test file --- test/run-drun/ok/data-view-core.tc.ok | 1 + 1 file changed, 1 insertion(+) create mode 100644 test/run-drun/ok/data-view-core.tc.ok diff --git a/test/run-drun/ok/data-view-core.tc.ok b/test/run-drun/ok/data-view-core.tc.ok new file mode 100644 index 00000000000..1748ee733d0 --- /dev/null +++ b/test/run-drun/ok/data-view-core.tc.ok @@ -0,0 +1 @@ +data-view-core.mo:91.7-91.19: warning [M0244], variable some_variant is never reassigned, consider using `let` From e1498a88bb0a06ebfad8f553d7a7eaadc56bbbe4 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 4 Mar 2026 10:33:34 +0000 Subject: [PATCH 30/52] missing output --- test/run-drun/ok/data-view.tc.ok | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 test/run-drun/ok/data-view.tc.ok diff --git a/test/run-drun/ok/data-view.tc.ok b/test/run-drun/ok/data-view.tc.ok new file mode 100644 index 00000000000..358510a88b6 --- /dev/null +++ b/test/run-drun/ok/data-view.tc.ok @@ -0,0 +1,8 @@ +data-view.mo:23.7-23.22: warning [M0194], unused identifier: `invisible_array` +help: if this is intentional, prefix it with an underscore: `_invisible_array` +data-view.mo:26.7-26.19: warning [M0194], unused identifier: `some_variant` +help: if this is intentional, prefix it with an underscore: `_some_variant` +data-view.mo:27.7-27.18: warning [M0194], unused identifier: `some_record` +help: if this is intentional, prefix it with an underscore: `_some_record` +data-view.mo:30.7-30.26: warning [M0194], unused identifier: `some_mutable_record` +help: if this is intentional, prefix it with an underscore: `_some_mutable_record` From 4ebc2a6ead2e51846dee1a59c6190022885397be Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 4 Mar 2026 10:47:57 +0000 Subject: [PATCH 31/52] don't consider identifiers in generated queries used --- src/mo_frontend/typing.ml | 2 +- test/run-drun/ok/data-view-core.tc.ok | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index ab5b6dfca01..afb3db1d14e 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4171,7 +4171,7 @@ and infer_viewer env scope mut id viewer = else let infer_dot_view = Diag.with_message_store (recover_opt (fun msgs -> - let env = {env with msgs} in (* don't record errors in outer env *) + let env = {env with msgs; used_identifiers = ref T.Env.empty } in (* don't record errors in outer env *) let env = adjoin env scope in let note() = empty_typ_note in let at = id.at in diff --git a/test/run-drun/ok/data-view-core.tc.ok b/test/run-drun/ok/data-view-core.tc.ok index 1748ee733d0..195bbcc202b 100644 --- a/test/run-drun/ok/data-view-core.tc.ok +++ b/test/run-drun/ok/data-view-core.tc.ok @@ -1 +1,8 @@ -data-view-core.mo:91.7-91.19: warning [M0244], variable some_variant is never reassigned, consider using `let` +data-view-core.mo:88.7-88.22: warning [M0194], unused identifier: `invisible_array` +help: if this is intentional, prefix it with an underscore: `_invisible_array` +data-view-core.mo:91.7-91.19: warning [M0194], unused identifier: `some_variant` +help: if this is intentional, prefix it with an underscore: `_some_variant` +data-view-core.mo:92.7-92.18: warning [M0194], unused identifier: `some_record` +help: if this is intentional, prefix it with an underscore: `_some_record` +data-view-core.mo:95.7-95.26: warning [M0194], unused identifier: `some_mutable_record` +help: if this is intentional, prefix it with an underscore: `_some_mutable_record` From df47d39d6fbb20046b036b1cc7c17078f3d4fd77 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 4 Mar 2026 12:16:31 +0000 Subject: [PATCH 32/52] update test output --- test/run-drun/data-view-core.mo | 17 ++++++++++------- test/run-drun/ok/data-view-core.tc.ok | 20 ++++++++++++++++---- test/run-drun/ok/data-view.drun-run.ok | 11 +++++++++++ test/run-drun/ok/data-view.tc.ok | 10 +++++++--- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/test/run-drun/data-view-core.mo b/test/run-drun/data-view-core.mo index 3944d913106..75800b83294 100644 --- a/test/run-drun/data-view-core.mo +++ b/test/run-drun/data-view-core.mo @@ -6,8 +6,6 @@ import Iter "mo:core/Iter"; import Order "mo:core/Order"; import Text "mo:core/Text"; -// curried version of data-view.mo - persistent actor { module MapView { @@ -28,10 +26,12 @@ persistent actor { let map : Map.Map = Map.empty(); - public query func mapView(ko: ?Nat, count: Nat) : async [(Nat, Text)] { + /* generates: + public query func __map(ko: ?Nat, count: Nat) : async [(Nat, Text)] { map.view(/*Nat.compare*/)(ko, count); }; - + */ + module SetView { public func view( @@ -54,9 +54,11 @@ persistent actor { let set : Set.Set = Set.empty(); - public query func setView(ko: ?Nat, count: Nat) : async [Nat] { + /* generates + public query func __set(ko: ?Nat, count: Nat) : async [Nat] { set.view(/*Nat.compare*/)(ko, count); }; + */ module ArrayView { @@ -78,10 +80,11 @@ persistent actor { let array : [(Nat, Text)] = []; - public query func arrayView(ko: ?Nat, count: Nat) : async [(Nat, Text)] { + /* generates + public query func __array(ko: ?Nat, count: Nat) : async [(Nat, Text)] { array.view()(ko, count); }; - + */ // here, [insible_array.view] produces a non-shared (mutable) type, omit viewer // later maybe approximate by shared type. diff --git a/test/run-drun/ok/data-view-core.tc.ok b/test/run-drun/ok/data-view-core.tc.ok index 195bbcc202b..22bcc9d04f2 100644 --- a/test/run-drun/ok/data-view-core.tc.ok +++ b/test/run-drun/ok/data-view-core.tc.ok @@ -1,8 +1,20 @@ -data-view-core.mo:88.7-88.22: warning [M0194], unused identifier: `invisible_array` +data-view-core.mo:11.10-11.17: warning [M0194], unused identifier: `MapView` +help: if this is intentional, prefix it with an underscore: `_MapView` +data-view-core.mo:27.7-27.10: warning [M0194], unused identifier: `map` +help: if this is intentional, prefix it with an underscore: `_map` +data-view-core.mo:35.10-35.17: warning [M0194], unused identifier: `SetView` +help: if this is intentional, prefix it with an underscore: `_SetView` +data-view-core.mo:55.7-55.10: warning [M0194], unused identifier: `set` +help: if this is intentional, prefix it with an underscore: `_set` +data-view-core.mo:63.10-63.19: warning [M0194], unused identifier: `ArrayView` +help: if this is intentional, prefix it with an underscore: `_ArrayView` +data-view-core.mo:81.7-81.12: warning [M0194], unused identifier: `array` +help: if this is intentional, prefix it with an underscore: `_array` +data-view-core.mo:91.7-91.22: warning [M0194], unused identifier: `invisible_array` help: if this is intentional, prefix it with an underscore: `_invisible_array` -data-view-core.mo:91.7-91.19: warning [M0194], unused identifier: `some_variant` +data-view-core.mo:94.7-94.19: warning [M0194], unused identifier: `some_variant` help: if this is intentional, prefix it with an underscore: `_some_variant` -data-view-core.mo:92.7-92.18: warning [M0194], unused identifier: `some_record` +data-view-core.mo:95.7-95.18: warning [M0194], unused identifier: `some_record` help: if this is intentional, prefix it with an underscore: `_some_record` -data-view-core.mo:95.7-95.26: warning [M0194], unused identifier: `some_mutable_record` +data-view-core.mo:98.7-98.26: warning [M0194], unused identifier: `some_mutable_record` help: if this is intentional, prefix it with an underscore: `_some_mutable_record` diff --git a/test/run-drun/ok/data-view.drun-run.ok b/test/run-drun/ok/data-view.drun-run.ok index a6f776f43c6..844be8ccf26 100644 --- a/test/run-drun/ok/data-view.drun-run.ok +++ b/test/run-drun/ok/data-view.drun-run.ok @@ -1,2 +1,13 @@ ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 ingress Completed: Reply: 0x4449444c0000 +debug.print: [] +debug.print: #node(#leaf, 0, #leaf) +debug.print: {a = 1; b = "hello"; c = true} +debug.print: "user defined __override" +debug.print: IC0536: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister has no update method '__invisible_array'.. +Check that the method being called is exported by the target canister. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#method-not-found +debug.print: IC0536: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister has no update method '__some_mutable_record'.. +Check that the method being called is exported by the target canister. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#method-not-found +debug.print: IC0536: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister has no update method '__motoko_xxx'.. +Check that the method being called is exported by the target canister. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#method-not-found +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/data-view.tc.ok b/test/run-drun/ok/data-view.tc.ok index 358510a88b6..60ba415f67d 100644 --- a/test/run-drun/ok/data-view.tc.ok +++ b/test/run-drun/ok/data-view.tc.ok @@ -1,8 +1,12 @@ data-view.mo:23.7-23.22: warning [M0194], unused identifier: `invisible_array` help: if this is intentional, prefix it with an underscore: `_invisible_array` -data-view.mo:26.7-26.19: warning [M0194], unused identifier: `some_variant` +data-view.mo:27.7-27.19: warning [M0194], unused identifier: `some_variant` help: if this is intentional, prefix it with an underscore: `_some_variant` -data-view.mo:27.7-27.18: warning [M0194], unused identifier: `some_record` +data-view.mo:28.7-28.18: warning [M0194], unused identifier: `some_record` help: if this is intentional, prefix it with an underscore: `_some_record` -data-view.mo:30.7-30.26: warning [M0194], unused identifier: `some_mutable_record` +data-view.mo:31.7-31.26: warning [M0194], unused identifier: `some_mutable_record` help: if this is intentional, prefix it with an underscore: `_some_mutable_record` +data-view.mo:35.7-35.15: warning [M0194], unused identifier: `override` +help: if this is intentional, prefix it with an underscore: `_override` +data-view.mo:38.7-38.17: warning [M0194], unused identifier: `motoko_xxx` +help: if this is intentional, prefix it with an underscore: `_motoko_xxx` From d5679443a7c21f240e8586a0bf38cdf96e809f5e Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 4 Mar 2026 13:37:12 +0000 Subject: [PATCH 33/52] Apply suggestion from @crusso --- test/test-moc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test-moc.js b/test/test-moc.js index 07e2eb58fd2..a77c7e9cd68 100644 --- a/test/test-moc.js +++ b/test/test-moc.js @@ -192,7 +192,6 @@ type T = nat; service : { /// Function comment main: () -> (T) query; - x: () -> (T) query; } `.trim() + "\n"; assert.deepStrictEqual(Motoko.candid("ast.mo").code, candid) From 87a61d3f19e6aada831091709ad5e81d72884b0d Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 4 Mar 2026 13:44:36 +0000 Subject: [PATCH 34/52] test file --- test/run-drun/data-view.mo | 82 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 test/run-drun/data-view.mo diff --git a/test/run-drun/data-view.mo b/test/run-drun/data-view.mo new file mode 100644 index 00000000000..ff24c3a9959 --- /dev/null +++ b/test/run-drun/data-view.mo @@ -0,0 +1,82 @@ +// simplified version of data-view.mo that doesn't require core. +//MOC-FLAG --generate-view-queries +import Prim "mo:⛔"; +persistent actor Self { + + module ArrayView { + public func view(self : [var V]) : + (start : Nat, count : Nat) -> [V] = + func (start, count) { + Prim.Array_tabulate(count, func i { self[start+i] }); + } + }; + + let array : [var (Nat, Text)] = [var (1, "1"), (2,"2")]; + + /* generates */ + public query func __array(start:Nat, count: Nat) : async [(Nat, Text)] { + array.view()(start, count); + }; + + // here, [insible_array.view] produces a non-shared (mutable) type, omit viewer + // later maybe approximate by shared type. + let invisible_array : [[var Nat]] = []; + + // shared values we can just display, sans viewer + type Tree = { #leaf; #node : (Tree, Nat, Tree) }; + var some_variant = #node (#leaf, 0, #leaf); + let some_record = {a=1;b ="hello"; c = true} ; + + // stable, non-shared values we can't just display in full, without viewer + let some_mutable_record = {var a = 1}; + + public query func __override(): async Text { "user defined __override" }; + + let override = #override; + /* generates nothing as would clash with user-defined __override above" */ + + let motoko_xxx = #motoko_xxx; + /* generates nothing as would clash with reserved __motoko_ members" */ + + public func go() : async () { + let views = actor (debug_show (Prim.principalOfActor(Self))) : + actor { + /* generated */ + __array : shared query (Nat, Nat) -> async [(Nat, Text)]; + __some_variant: shared query () -> async Tree; + __some_record : shared query () -> async {a:Nat; b: Text; c : Bool}; + /* user-defined */ + __override : shared query () -> async Text; + /* unable to generate because of (potential) name clash, no viewer or non-shared type*/ + __invisible_array : shared query() -> async None; + __some_mutable_record : shared query() -> async None; + __motoko_xxx : shared query () -> async None; + + }; + Prim.debugPrint(debug_show (await views.__array(0,0))); + Prim.debugPrint(debug_show (await views.__some_variant())); + Prim.debugPrint(debug_show (await views.__some_record())); + Prim.debugPrint(debug_show (await views.__override())); // calls user-defined method + try { + await views.__invisible_array(); //fails with method not avialable + assert false; + } catch (e) { + Prim.debugPrint (Prim.errorMessage(e)); + }; + try { + await views.__some_mutable_record(); //fails with method not avialable + assert false; + } catch (e) { + Prim.debugPrint (Prim.errorMessage(e)); + }; + try { + await views.__motoko_xxx(); //fails with method not avialable + assert false; + } catch (e) { + Prim.debugPrint (Prim.errorMessage(e)); + } + + } + +} +//CALL ingress go "DIDL\x00\x00" From c9cdfe06149d92af246454c7413b16e2aa321445 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 4 Mar 2026 14:22:10 +0000 Subject: [PATCH 35/52] test option isAdmin functionality --- test/run-drun/data-view-admin.mo | 44 ++++++++++++++++++++ test/run-drun/data-view-admin/client.mo | 10 +++++ test/run-drun/ok/data-view-admin.drun-run.ok | 6 +++ test/run-drun/ok/data-view-admin.tc.ok | 4 ++ 4 files changed, 64 insertions(+) create mode 100644 test/run-drun/data-view-admin.mo create mode 100644 test/run-drun/data-view-admin/client.mo create mode 100644 test/run-drun/ok/data-view-admin.drun-run.ok create mode 100644 test/run-drun/ok/data-view-admin.tc.ok diff --git a/test/run-drun/data-view-admin.mo b/test/run-drun/data-view-admin.mo new file mode 100644 index 00000000000..e0486a77540 --- /dev/null +++ b/test/run-drun/data-view-admin.mo @@ -0,0 +1,44 @@ +// simplified version of data-view.mo that doesn't require core. +//MOC-FLAG --generate-view-queries +import Prim "mo:⛔"; +import Client "data-view-admin/client"; +persistent actor Self { + + // a stable variable with viewer __view : () -> async [Nat] + let view = [1,2,3]; + + transient var admin : ?Principal = null; + + // isAdmin, when declared, is used to control access to generated views + func isAdmin(caller : Principal) : Bool { + (?caller) == admin + }; + + public func go() : async () { + + let server = actor (debug_show (Prim.principalOfActor(Self))) : + actor { + __view : query () -> async [Nat]; + }; + + let client = await Client.Client(server); + + // call __view from client without admin rights (fails) + try { + Prim.debugPrint(debug_show (await client.test())); + } catch e { + Prim.debugPrint(debug_show (Prim.errorMessage(e))); + }; + + // set c1 as admin + admin := ?Prim.principalOfActor(client); + + // call __view from client with admin rights (succeeds) + try { + Prim.debugPrint(debug_show (await client.test())); + } catch e { + assert(false); + }; + } +} +//CALL ingress go "DIDL\x00\x00" diff --git a/test/run-drun/data-view-admin/client.mo b/test/run-drun/data-view-admin/client.mo new file mode 100644 index 00000000000..89ccca4885a --- /dev/null +++ b/test/run-drun/data-view-admin/client.mo @@ -0,0 +1,10 @@ +import Prim "mo:⛔"; + +// Use an actor class to create a third-party caller of __view query +actor class Client(server : actor { __view : query () -> async [Nat]}) { + + public func test() : async [Nat] { + await server.__view(); // should succeed/fail depening on admin rights + }; + +} diff --git a/test/run-drun/ok/data-view-admin.drun-run.ok b/test/run-drun/ok/data-view-admin.drun-run.ok new file mode 100644 index 00000000000..2f4cffe4b41 --- /dev/null +++ b/test/run-drun/ok/data-view-admin.drun-run.ok @@ -0,0 +1,6 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: "IC0503: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister called `ic0.trap` with message: 'Unauthorized caller (caller must be self, some controller or isAdmin(caller))'. +Consider gracefully handling failures from this canister or altering the canister to handle exceptions. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#trapped-explicitly" +debug.print: [1, 2, 3] +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/data-view-admin.tc.ok b/test/run-drun/ok/data-view-admin.tc.ok new file mode 100644 index 00000000000..4cf1ef46522 --- /dev/null +++ b/test/run-drun/ok/data-view-admin.tc.ok @@ -0,0 +1,4 @@ +data-view-admin.mo:8.7-8.11: warning [M0194], unused identifier: `view` +help: if this is intentional, prefix it with an underscore: `_view` +data-view-admin.mo:39.13-39.14: warning [M0194], unused identifier: `e` +help: if this is intentional, prefix it with an underscore: `_e` From 2a556d0028f8b29222120b1fe10e25d8b8e73248 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 4 Mar 2026 16:11:39 +0000 Subject: [PATCH 36/52] add more realistic sample --- test/run-drun/data-view-sample.mo | 302 ++++++++++++++++++ test/run-drun/data-view-sample/views.mo | 246 ++++++++++++++ test/run-drun/ok/data-view-sample.drun-run.ok | 5 + test/run-drun/ok/data-view-sample.tc.ok | 2 + 4 files changed, 555 insertions(+) create mode 100644 test/run-drun/data-view-sample.mo create mode 100644 test/run-drun/data-view-sample/views.mo create mode 100644 test/run-drun/ok/data-view-sample.drun-run.ok create mode 100644 test/run-drun/ok/data-view-sample.tc.ok diff --git a/test/run-drun/data-view-sample.mo b/test/run-drun/data-view-sample.mo new file mode 100644 index 00000000000..b164bbf2148 --- /dev/null +++ b/test/run-drun/data-view-sample.mo @@ -0,0 +1,302 @@ +//MOC-FLAG --generate-view-queries --package core $MOTOKO_CORE +import Map "mo:core/Map"; +import Nat "mo:core/Nat"; +import Nat32 "mo:core/Nat32"; +import Char "mo:core/Char"; +import Text "mo:core/Text"; +import Principal "mo:core/Principal"; +import Debug "mo:core/Debug"; + +// mixin providing views for Core data structures +import Views "data-view-sample/views"; + +persistent actor Self { + + include Views(); + + func isAdmin(p : Principal) : Bool { + Debug.print(debug_show (#isAdmin p)); + true; // for demo purposes only + }; + + // ═══════════════════════════════════════════════════════════════ + // Northwind Database — classic Microsoft sample data + // ═══════════════════════════════════════════════════════════════ + + // ── Categories ───────────────────────────────────────────────── + + let categories : Map.Map = Map.empty(); + + categories.add(1, {name = "Beverages"; description = "Soft drinks, coffees, teas, beers, and ales"}); + categories.add(2, {name = "Condiments"; description = "Sweet and savory sauces, relishes, spreads, and seasonings"}); + categories.add(3, {name = "Confections"; description = "Desserts, candies, and sweet breads"}); + categories.add(4, {name = "Dairy Products"; description = "Cheeses"}); + categories.add(5, {name = "Grains/Cereals"; description = "Breads, crackers, pasta, and cereal"}); + categories.add(6, {name = "Meat/Poultry"; description = "Prepared meats"}); + categories.add(7, {name = "Produce"; description = "Dried fruit and bean curd"}); + categories.add(8, {name = "Seafood"; description = "Seaweed and fish"}); + + // ── Suppliers ────────────────────────────────────────────────── + + let suppliers : Map.Map = Map.empty(); + + suppliers.add(1, {companyName = "Exotic Liquids"; contactName = "Charlotte Cooper"; city = "London"; country = "UK"}); + suppliers.add(2, {companyName = "New Orleans Cajun Delights"; contactName = "Shelley Burke"; city = "New Orleans"; country = "USA"}); + suppliers.add(3, {companyName = "Grandma Kelly's Homestead"; contactName = "Regina Murphy"; city = "Ann Arbor"; country = "USA"}); + suppliers.add(4, {companyName = "Tokyo Traders"; contactName = "Yoshi Nagase"; city = "Tokyo"; country = "Japan"}); + suppliers.add(5, {companyName = "Cooperativa de Quesos Las Cabras"; contactName = "Antonio del Valle Saavedra"; city = "Oviedo"; country = "Spain"}); + suppliers.add(6, {companyName = "Mayumi's"; contactName = "Mayumi Ohno"; city = "Osaka"; country = "Japan"}); + suppliers.add(7, {companyName = "Pavlova Ltd."; contactName = "Ian Devling"; city = "Melbourne"; country = "Australia"}); + suppliers.add(8, {companyName = "Specialty Biscuits Ltd."; contactName = "Peter Wilson"; city = "Manchester"; country = "UK"}); + suppliers.add(9, {companyName = "PB Knackebrod AB"; contactName = "Lars Peterson"; city = "Goteborg"; country = "Sweden"}); + suppliers.add(10, {companyName = "Refrescos Americanas LTDA"; contactName = "Carlos Diaz"; city = "Sao Paulo"; country = "Brazil"}); + + // ── Employees ────────────────────────────────────────────────── + + let employees : Map.Map = Map.empty(); + + employees.add(1, {lastName = "Davolio"; firstName = "Nancy"; title = "Sales Representative"; city = "Seattle"; country = "USA"}); + employees.add(2, {lastName = "Fuller"; firstName = "Andrew"; title = "Vice President Sales"; city = "Tacoma"; country = "USA"}); + employees.add(3, {lastName = "Leverling"; firstName = "Janet"; title = "Sales Representative"; city = "Kirkland"; country = "USA"}); + employees.add(4, {lastName = "Peacock"; firstName = "Margaret"; title = "Sales Representative"; city = "Redmond"; country = "USA"}); + employees.add(5, {lastName = "Buchanan"; firstName = "Steven"; title = "Sales Manager"; city = "London"; country = "UK"}); + employees.add(6, {lastName = "Suyama"; firstName = "Michael"; title = "Sales Representative"; city = "London"; country = "UK"}); + employees.add(7, {lastName = "King"; firstName = "Robert"; title = "Sales Representative"; city = "London"; country = "UK"}); + employees.add(8, {lastName = "Callahan"; firstName = "Laura"; title = "Inside Sales Coordinator"; city = "Seattle"; country = "USA"}); + employees.add(9, {lastName = "Dodsworth"; firstName = "Anne"; title = "Sales Representative"; city = "London"; country = "UK"}); + + // ── Customers ────────────────────────────────────────────────── + + let customers : Map.Map = Map.empty(); + + customers.add("ALFKI", {companyName = "Alfreds Futterkiste"; contactName = "Maria Anders"; contactTitle = "Sales Representative"; city = "Berlin"; country = "Germany"}); + customers.add("ANATR", {companyName = "Ana Trujillo Emparedados y helados"; contactName = "Ana Trujillo"; contactTitle = "Owner"; city = "Mexico D.F."; country = "Mexico"}); + customers.add("ANTON", {companyName = "Antonio Moreno Taqueria"; contactName = "Antonio Moreno"; contactTitle = "Owner"; city = "Mexico D.F."; country = "Mexico"}); + customers.add("AROUT", {companyName = "Around the Horn"; contactName = "Thomas Hardy"; contactTitle = "Sales Representative"; city = "London"; country = "UK"}); + customers.add("BERGS", {companyName = "Berglunds snabbkop"; contactName = "Christina Berglund"; contactTitle = "Order Administrator"; city = "Lulea"; country = "Sweden"}); + customers.add("BLAUS", {companyName = "Blauer See Delikatessen"; contactName = "Hanna Moos"; contactTitle = "Sales Representative"; city = "Mannheim"; country = "Germany"}); + customers.add("BLONP", {companyName = "Blondesddsl pere et fils"; contactName = "Frederique Citeaux"; contactTitle = "Marketing Manager"; city = "Strasbourg"; country = "France"}); + customers.add("BOLID", {companyName = "Bolido Comidas preparadas"; contactName = "Martin Sommer"; contactTitle = "Owner"; city = "Madrid"; country = "Spain"}); + customers.add("BONAP", {companyName = "Bon app'"; contactName = "Laurence Lebihan"; contactTitle = "Owner"; city = "Marseille"; country = "France"}); + customers.add("BOTTM", {companyName = "Bottom-Dollar Markets"; contactName = "Elizabeth Lincoln"; contactTitle = "Accounting Manager"; city = "Tsawassen"; country = "Canada"}); + customers.add("BSBEV", {companyName = "B's Beverages"; contactName = "Victoria Ashworth"; contactTitle = "Sales Representative"; city = "London"; country = "UK"}); + customers.add("CACTU", {companyName = "Cactus Comidas para llevar"; contactName = "Patricio Simpson"; contactTitle = "Sales Agent"; city = "Buenos Aires"; country = "Argentina"}); + customers.add("CENTC", {companyName = "Centro comercial Moctezuma"; contactName = "Francisco Chang"; contactTitle = "Marketing Manager"; city = "Mexico D.F."; country = "Mexico"}); + customers.add("CHOPS", {companyName = "Chop-suey Chinese"; contactName = "Yang Wang"; contactTitle = "Owner"; city = "Bern"; country = "Switzerland"}); + customers.add("COMMI", {companyName = "Comercio Mineiro"; contactName = "Pedro Afonso"; contactTitle = "Sales Associate"; city = "Sao Paulo"; country = "Brazil"}); + + // ── Products ─────────────────────────────────────────────────── + + let products : Map.Map = Map.empty(); + + products.add(1, {name = "Chai"; supplierId = 1; categoryId = 1; unitPrice = 18; unitsInStock = 39; discontinued = false}); + products.add(2, {name = "Chang"; supplierId = 1; categoryId = 1; unitPrice = 19; unitsInStock = 17; discontinued = false}); + products.add(3, {name = "Aniseed Syrup"; supplierId = 1; categoryId = 2; unitPrice = 10; unitsInStock = 13; discontinued = false}); + products.add(4, {name = "Chef Anton's Cajun Seasoning"; supplierId = 2; categoryId = 2; unitPrice = 22; unitsInStock = 53; discontinued = false}); + products.add(5, {name = "Chef Anton's Gumbo Mix"; supplierId = 2; categoryId = 2; unitPrice = 21; unitsInStock = 0; discontinued = true}); + products.add(6, {name = "Grandma's Boysenberry Spread"; supplierId = 3; categoryId = 2; unitPrice = 25; unitsInStock = 120; discontinued = false}); + products.add(7, {name = "Uncle Bob's Organic Dried Pears"; supplierId = 3; categoryId = 7; unitPrice = 30; unitsInStock = 15; discontinued = false}); + products.add(8, {name = "Northwoods Cranberry Sauce"; supplierId = 3; categoryId = 2; unitPrice = 40; unitsInStock = 6; discontinued = false}); + products.add(9, {name = "Mishi Kobe Niku"; supplierId = 4; categoryId = 6; unitPrice = 97; unitsInStock = 29; discontinued = true}); + products.add(10, {name = "Ikura"; supplierId = 4; categoryId = 8; unitPrice = 31; unitsInStock = 31; discontinued = false}); + products.add(11, {name = "Queso Cabrales"; supplierId = 5; categoryId = 4; unitPrice = 21; unitsInStock = 22; discontinued = false}); + products.add(12, {name = "Queso Manchego La Pastora"; supplierId = 5; categoryId = 4; unitPrice = 38; unitsInStock = 86; discontinued = false}); + products.add(13, {name = "Konbu"; supplierId = 6; categoryId = 8; unitPrice = 6; unitsInStock = 24; discontinued = false}); + products.add(14, {name = "Tofu"; supplierId = 6; categoryId = 7; unitPrice = 23; unitsInStock = 35; discontinued = false}); + products.add(15, {name = "Genen Shouyu"; supplierId = 6; categoryId = 2; unitPrice = 16; unitsInStock = 39; discontinued = false}); + products.add(16, {name = "Pavlova"; supplierId = 7; categoryId = 3; unitPrice = 17; unitsInStock = 29; discontinued = false}); + products.add(17, {name = "Alice Mutton"; supplierId = 7; categoryId = 6; unitPrice = 39; unitsInStock = 0; discontinued = true}); + products.add(18, {name = "Carnarvon Tigers"; supplierId = 7; categoryId = 8; unitPrice = 62; unitsInStock = 42; discontinued = false}); + products.add(19, {name = "Teatime Chocolate Biscuits"; supplierId = 8; categoryId = 3; unitPrice = 9; unitsInStock = 25; discontinued = false}); + products.add(20, {name = "Sir Rodney's Marmalade"; supplierId = 8; categoryId = 3; unitPrice = 81; unitsInStock = 40; discontinued = false}); + + // ── Orders ───────────────────────────────────────────────────── + + let orders : Map.Map = Map.empty(); + + orders.add(10248, {customerId = "ALFKI"; employeeId = 5; orderDate = "1996-07-04"; shipCity = "Berlin"; shipCountry = "Germany"}); + orders.add(10249, {customerId = "ANATR"; employeeId = 6; orderDate = "1996-07-05"; shipCity = "Mexico D.F."; shipCountry = "Mexico"}); + orders.add(10250, {customerId = "CACTU"; employeeId = 4; orderDate = "1996-07-08"; shipCity = "Buenos Aires"; shipCountry = "Argentina"}); + orders.add(10251, {customerId = "ALFKI"; employeeId = 3; orderDate = "1996-07-08"; shipCity = "Berlin"; shipCountry = "Germany"}); + orders.add(10252, {customerId = "BLONP"; employeeId = 4; orderDate = "1996-07-09"; shipCity = "Strasbourg"; shipCountry = "France"}); + orders.add(10253, {customerId = "CACTU"; employeeId = 3; orderDate = "1996-07-10"; shipCity = "Buenos Aires"; shipCountry = "Argentina"}); + orders.add(10254, {customerId = "CHOPS"; employeeId = 5; orderDate = "1996-07-11"; shipCity = "Bern"; shipCountry = "Switzerland"}); + orders.add(10255, {customerId = "BLAUS"; employeeId = 9; orderDate = "1996-07-12"; shipCity = "Mannheim"; shipCountry = "Germany"}); + orders.add(10256, {customerId = "AROUT"; employeeId = 3; orderDate = "1996-07-15"; shipCity = "London"; shipCountry = "UK"}); + orders.add(10257, {customerId = "BERGS"; employeeId = 4; orderDate = "1996-07-16"; shipCity = "Lulea"; shipCountry = "Sweden"}); + orders.add(10258, {customerId = "CENTC"; employeeId = 1; orderDate = "1996-07-17"; shipCity = "Mexico D.F."; shipCountry = "Mexico"}); + orders.add(10259, {customerId = "CENTC"; employeeId = 4; orderDate = "1996-07-18"; shipCity = "Mexico D.F."; shipCountry = "Mexico"}); + orders.add(10260, {customerId = "BOTTM"; employeeId = 4; orderDate = "1996-07-19"; shipCity = "Tsawassen"; shipCountry = "Canada"}); + orders.add(10261, {customerId = "CACTU"; employeeId = 4; orderDate = "1996-07-19"; shipCity = "Buenos Aires"; shipCountry = "Argentina"}); + orders.add(10262, {customerId = "BONAP"; employeeId = 8; orderDate = "1996-07-22"; shipCity = "Marseille"; shipCountry = "France"}); + orders.add(10263, {customerId = "BOTTM"; employeeId = 9; orderDate = "1996-07-23"; shipCity = "Tsawassen"; shipCountry = "Canada"}); + orders.add(10264, {customerId = "AROUT"; employeeId = 6; orderDate = "1996-07-24"; shipCity = "London"; shipCountry = "UK"}); + orders.add(10265, {customerId = "BLONP"; employeeId = 2; orderDate = "1996-07-25"; shipCity = "Strasbourg"; shipCountry = "France"}); + orders.add(10266, {customerId = "COMMI"; employeeId = 3; orderDate = "1996-07-26"; shipCity = "Sao Paulo"; shipCountry = "Brazil"}); + orders.add(10267, {customerId = "ALFKI"; employeeId = 4; orderDate = "1996-07-29"; shipCity = "Berlin"; shipCountry = "Germany"}); + + // ── Order Details ────────────────────────────────────────────── + + let orderDetails : Map.Map = Map.empty(); + + orderDetails.add(1, {orderId = 10248; productId = 11; unitPrice = 14; quantity = 12}); + orderDetails.add(2, {orderId = 10248; productId = 19; unitPrice = 9; quantity = 10}); + orderDetails.add(3, {orderId = 10248; productId = 1; unitPrice = 18; quantity = 5}); + orderDetails.add(4, {orderId = 10249; productId = 14; unitPrice = 23; quantity = 9}); + orderDetails.add(5, {orderId = 10249; productId = 2; unitPrice = 19; quantity = 40}); + orderDetails.add(6, {orderId = 10250; productId = 3; unitPrice = 10; quantity = 10}); + orderDetails.add(7, {orderId = 10250; productId = 6; unitPrice = 25; quantity = 5}); + orderDetails.add(8, {orderId = 10250; productId = 16; unitPrice = 17; quantity = 35}); + orderDetails.add(9, {orderId = 10251; productId = 1; unitPrice = 18; quantity = 15}); + orderDetails.add(10, {orderId = 10251; productId = 13; unitPrice = 6; quantity = 6}); + orderDetails.add(11, {orderId = 10252; productId = 20; unitPrice = 81; quantity = 40}); + orderDetails.add(12, {orderId = 10252; productId = 12; unitPrice = 38; quantity = 25}); + orderDetails.add(13, {orderId = 10253; productId = 10; unitPrice = 31; quantity = 20}); + orderDetails.add(14, {orderId = 10253; productId = 18; unitPrice = 62; quantity = 42}); + orderDetails.add(15, {orderId = 10254; productId = 1; unitPrice = 18; quantity = 15}); + orderDetails.add(16, {orderId = 10254; productId = 8; unitPrice = 40; quantity = 21}); + orderDetails.add(17, {orderId = 10255; productId = 4; unitPrice = 22; quantity = 20}); + orderDetails.add(18, {orderId = 10255; productId = 16; unitPrice = 17; quantity = 35}); + orderDetails.add(19, {orderId = 10256; productId = 7; unitPrice = 30; quantity = 12}); + orderDetails.add(20, {orderId = 10256; productId = 15; unitPrice = 16; quantity = 25}); + orderDetails.add(21, {orderId = 10257; productId = 11; unitPrice = 14; quantity = 6}); + orderDetails.add(22, {orderId = 10257; productId = 20; unitPrice = 81; quantity = 15}); + orderDetails.add(23, {orderId = 10258; productId = 2; unitPrice = 19; quantity = 50}); + orderDetails.add(24, {orderId = 10258; productId = 5; unitPrice = 21; quantity = 65}); + orderDetails.add(25, {orderId = 10259; productId = 14; unitPrice = 23; quantity = 12}); + orderDetails.add(26, {orderId = 10260; productId = 1; unitPrice = 18; quantity = 10}); + orderDetails.add(27, {orderId = 10260; productId = 11; unitPrice = 14; quantity = 4}); + orderDetails.add(28, {orderId = 10261; productId = 19; unitPrice = 9; quantity = 6}); + orderDetails.add(29, {orderId = 10261; productId = 3; unitPrice = 10; quantity = 20}); + orderDetails.add(30, {orderId = 10262; productId = 12; unitPrice = 38; quantity = 12}); + orderDetails.add(31, {orderId = 10262; productId = 16; unitPrice = 17; quantity = 15}); + orderDetails.add(32, {orderId = 10263; productId = 10; unitPrice = 31; quantity = 10}); + orderDetails.add(33, {orderId = 10263; productId = 18; unitPrice = 62; quantity = 24}); + orderDetails.add(34, {orderId = 10264; productId = 4; unitPrice = 22; quantity = 30}); + orderDetails.add(35, {orderId = 10265; productId = 6; unitPrice = 25; quantity = 10}); + orderDetails.add(36, {orderId = 10265; productId = 8; unitPrice = 40; quantity = 10}); + orderDetails.add(37, {orderId = 10266; productId = 14; unitPrice = 23; quantity = 8}); + orderDetails.add(38, {orderId = 10266; productId = 2; unitPrice = 19; quantity = 12}); + orderDetails.add(39, {orderId = 10267; productId = 1; unitPrice = 18; quantity = 25}); + orderDetails.add(40, {orderId = 10267; productId = 7; unitPrice = 30; quantity = 15}); + + // ── Unicode Character Table ──────────────────────────────────── + + func natToHex(n : Nat) : Text { + let hexChars : [Text] = [ + "0","1","2","3","4","5","6","7", + "8","9","A","B","C","D","E","F" + ]; + if (n == 0) return "0000"; + var result = ""; + var val = n; + while (val > 0) { + result := hexChars[val % 16] # result; + val := val / 16; + }; + while (result.size() < 4) { + result := "0" # result; + }; + result; + }; + + let unicode : Map.Map = Map.empty(); + + + // U+0000 to U+D7FF (before surrogate range) + for (i in Nat.range(0, 0xD7FF)) { + unicode.add(i, { + decimal = i; + hex = natToHex(i); + char = Char.toText(Char.fromNat32(Nat32.fromNat(i))); + }); + }; + +/* omit for test + // U+E000 to U+10FFFF (after surrogate range) + for (i in Nat.range(0xE000, 0x10FFFF)) { + unicode.add(i, { + decimal = i; + hex = natToHex(i); + char = Char.toText(Char.fromNat32(Nat32.fromNat(i))); + }); + }; +*/ + // ── Summary (non-paginated) ──────────────────────────────────── + + let summary = { + totalCategories = 8 : Nat; + totalSuppliers = 10 : Nat; + totalProducts = 20 : Nat; + totalCustomers = 15 : Nat; + totalEmployees = 9 : Nat; + totalOrders = 20 : Nat; + totalOrderDetails = 40 : Nat; + totalUnicode = 1_112_064 : Nat; + }; + + public func go() : async () { + let views = actor (debug_show (Principal.fromActor(Self))) : + actor { + __customers: query (ko: ?Text, count: ?Nat) -> + async [(Text, + { city: Text; + companyName: Text; + contactName: Text; + contactTitle: Text; + country: Text;})] + }; + Debug.print(debug_show (await views.__customers(null, null))); // show all customers from first key + Debug.print(debug_show (await views.__customers(?"BOTTM", ?4))); // show at most 4 customers from key "" + } + +} +//SKIP run +//SKIP run-ir +//SKIP run-low +//CALL ingress go "DIDL\x00\x00" + + + diff --git a/test/run-drun/data-view-sample/views.mo b/test/run-drun/data-view-sample/views.mo new file mode 100644 index 00000000000..8a3b5afa5f0 --- /dev/null +++ b/test/run-drun/data-view-sample/views.mo @@ -0,0 +1,246 @@ +import Map "mo:core/Map"; +import Set "mo:core/Set"; +import Nat "mo:core/Nat"; +import Iter "mo:core/Iter"; +import Order "mo:core/Order"; +import Text "mo:core/Text"; +import Array "mo:core/Array"; +import List "mo:core/List"; +import Stack "mo:core/Stack"; +import Queue "mo:core/Queue"; +import PureMap "mo:core/pure/Map"; +import PureSet "mo:core/pure/Set"; +import PureList "mo:core/pure/List"; +import PureQueue "mo:core/pure/Queue"; + +// custom self.view(...) methods, collected in a mixin for convenience. +mixin () { + + module MapView { + public func view(self : Map.Map, compare : (implicit : (K,K) -> Order.Order)) : (ko : ?K, count : ?Nat) -> [(K, V)] = + func (ko, count) { + let entries = switch ko { + case null { + self.entries() + }; + case (?k) { + self.entriesFrom(k) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + } + }; + + module SetView { + + public func view( + self : Set.Set, + compare : (implicit : (K,K) -> Order.Order)) : ( + ko : ?K, + count : ?Nat) -> [K] = + func (ko, count) { + let entries = switch ko { + case null { + self.values() + }; + case (?k) { + self.valuesFrom(k) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + }; + }; + + module ArrayView { + + public func view(self : [V]) : + (io : ?Nat, count : ?Nat) -> [V] = + func (io, count) { + let entries = switch io { + case null { + self.values() + }; + case (?io) { + self.values().drop(io) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + }; + }; + + module VarArrayView { + + public func view(self : [var V]) : + (io : ?Nat, count : ?Nat) -> [V] = + func (io, count) { + let entries = switch io { + case null { + self.values() + }; + case (?io) { + self.values().drop(io) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + }; + }; + + module ListView { + + public func view(self : List.List) : + (io : ?Nat, count : ?Nat) -> [V] = + func (io, count) { + let entries = switch io { + case null { + self.values() + }; + case (?io) { + self.values().drop(io) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + }; + }; + + module StackView { + + public func view(self : Stack.Stack) : + (io : ?Nat, count : ?Nat) -> [V] = + func (io, count) { + let entries = switch io { + case null { + self.values() + }; + case (?io) { + self.values().drop(io) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + }; + }; + + module QueueView { + + public func view(self : Queue.Queue) : + (io : ?Nat, count : ?Nat) -> [V] = + func (io, count) { + let entries = switch io { + case null { + self.values() + }; + case (?io) { + self.values().drop(io) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + }; + }; + + // ── Pure (immutable) collection viewers ────────────────────────── + // PureMap/PureSet use `dropWhile` with `compare` for key-based starts. + // PureList/PureQueue use index-based starts via iterator `.drop()`. + + module PureMapView { + + public func view(self : PureMap.Map, compare : (implicit : (K,K) -> Order.Order)) : + (ko : ?K, count : ?Nat) -> [(K, V)] = + func (ko, count) { + let entries = switch ko { + case null { + PureMap.entries(self) + }; + case (?k) { + PureMap.entries(self).dropWhile( + func((ek, _) : (K, V)) : Bool { compare(ek, k) == #less }) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + }; + }; + + module PureSetView { + + public func view(self : PureSet.Set, compare : (implicit : (K,K) -> Order.Order)) : + (ko : ?K, count : ?Nat) -> [K] = + func (ko, count) { + let entries = switch ko { + case null { + PureSet.values(self) + }; + case (?k) { + PureSet.values(self).dropWhile( + func(ek : K) : Bool { compare(ek, k) == #less }) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + }; + }; + + module PureListView { + + public func view(self : PureList.List) : + (io : ?Nat, count : ?Nat) -> [V] = + func (io, count) { + let entries = switch io { + case null { + PureList.values(self) + }; + case (?io) { + PureList.values(self).drop(io) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + }; + }; + + module PureQueueView { + + public func view(self : PureQueue.Queue) : + (io : ?Nat, count : ?Nat) -> [V] = + func (io, count) { + let entries = switch io { + case null { + PureQueue.values(self) + }; + case (?io) { + PureQueue.values(self).drop(io) + }; + }; + switch count { + case null { entries.toArray() }; + case (?c) { entries.take(c).toArray() }; + }; + }; + }; + +} diff --git a/test/run-drun/ok/data-view-sample.drun-run.ok b/test/run-drun/ok/data-view-sample.drun-run.ok new file mode 100644 index 00000000000..c4261fb1965 --- /dev/null +++ b/test/run-drun/ok/data-view-sample.drun-run.ok @@ -0,0 +1,5 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: [("ALFKI", {city = "Berlin"; companyName = "Alfreds Futterkiste"; contactName = "Maria Anders"; contactTitle = "Sales Representative"; country = "Germany"}), ("ANATR", {city = "Mexico D.F."; companyName = "Ana Trujillo Emparedados y helados"; contactName = "Ana Trujillo"; contactTitle = "Owner"; country = "Mexico"}), ("ANTON", {city = "Mexico D.F."; companyName = "Antonio Moreno Taqueria"; contactName = "Antonio Moreno"; contactTitle = "Owner"; country = "Mexico"}), ("AROUT", {city = "London"; companyName = "Around the Horn"; contactName = "Thomas Hardy"; contactTitle = "Sales Representative"; country = "UK"}), ("BERGS", {city = "Lulea"; companyName = "Berglunds snabbkop"; contactName = "Christina Berglund"; contactTitle = "Order Administrator"; country = "Sweden"}), ("BLAUS", {city = "Mannheim"; companyName = "Blauer See Delikatessen"; contactName = "Hanna Moos"; contactTitle = "Sales Representative"; country = "Germany"}), ("BLONP", {city = "Strasbourg"; companyName = "Blondesddsl pere et fils"; contactName = "Frederique Citeaux"; contactTitle = "Marketing Manager"; country = "France"}), ("BOLID", {city = "Madrid"; companyName = "Bolido Comidas preparadas"; contactName = "Martin Sommer"; contactTitle = "Owner"; country = "Spain"}), ("BONAP", {city = "Marseille"; companyName = "Bon app'"; contactName = "Laurence Lebihan"; contactTitle = "Owner"; country = "France"}), ("BOTTM", {city = "Tsawassen"; companyName = "Bottom-Dollar Markets"; contactName = "Elizabeth Lincoln"; contactTitle = "Accounting Manager"; country = "Canada"}), ("BSBEV", {city = "London"; companyName = "B's Beverages"; contactName = "Victoria Ashworth"; contactTitle = "Sales Representative"; country = "UK"}), ("CACTU", {city = "Buenos Aires"; companyName = "Cactus Comidas para llevar"; contactName = "Patricio Simpson"; contactTitle = "Sales Agent"; country = "Argentina"}), ("CENTC", {city = "Mexico D.F."; companyName = "Centro comercial Moctezuma"; contactName = "Francisco Chang"; contactTitle = "Marketing Manager"; country = "Mexico"}), ("CHOPS", {city = "Bern"; companyName = "Chop-suey Chinese"; contactName = "Yang Wang"; contactTitle = "Owner"; country = "Switzerland"}), ("COMMI", {city = "Sao Paulo"; companyName = "Comercio Mineiro"; contactName = "Pedro Afonso"; contactTitle = "Sales Associate"; country = "Brazil"})] +debug.print: [("BOTTM", {city = "Tsawassen"; companyName = "Bottom-Dollar Markets"; contactName = "Elizabeth Lincoln"; contactTitle = "Accounting Manager"; country = "Canada"}), ("BSBEV", {city = "London"; companyName = "B's Beverages"; contactName = "Victoria Ashworth"; contactTitle = "Sales Representative"; country = "UK"}), ("CACTU", {city = "Buenos Aires"; companyName = "Cactus Comidas para llevar"; contactName = "Patricio Simpson"; contactTitle = "Sales Agent"; country = "Argentina"}), ("CENTC", {city = "Mexico D.F."; companyName = "Centro comercial Moctezuma"; contactName = "Francisco Chang"; contactTitle = "Marketing Manager"; country = "Mexico"})] +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/data-view-sample.tc.ok b/test/run-drun/ok/data-view-sample.tc.ok new file mode 100644 index 00000000000..b2de0e667aa --- /dev/null +++ b/test/run-drun/ok/data-view-sample.tc.ok @@ -0,0 +1,2 @@ +data-view-sample.mo:269.7-269.14: warning [M0194], unused identifier: `summary` +help: if this is intentional, prefix it with an underscore: `_summary` From 1403e6ee0519e3bef2044643ccc0af292764ff33 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 4 Mar 2026 17:33:58 +0000 Subject: [PATCH 37/52] typos --- src/lowering/desugar.ml | 2 +- test/run-drun/data-view.mo | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 1e0f647a0b9..ac216477aa9 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -696,7 +696,7 @@ and export_view viewer_opt = expD (ifE access_control (unitE()) (primE (Ir.OtherPrim "trap") - [textE "Unauthorized caller (caller must be self, some controller or isAdmin(caller))"])) + [textE "Unauthorized caller (caller must be self, some controller or satisfy isAdmin(caller))"])) ] (mk_body vs)) (Con (scope_con1, [])))) diff --git a/test/run-drun/data-view.mo b/test/run-drun/data-view.mo index ff24c3a9959..f80b0c98d4a 100644 --- a/test/run-drun/data-view.mo +++ b/test/run-drun/data-view.mo @@ -58,19 +58,19 @@ persistent actor Self { Prim.debugPrint(debug_show (await views.__some_record())); Prim.debugPrint(debug_show (await views.__override())); // calls user-defined method try { - await views.__invisible_array(); //fails with method not avialable + await views.__invisible_array(); //fails with method not available assert false; } catch (e) { Prim.debugPrint (Prim.errorMessage(e)); }; try { - await views.__some_mutable_record(); //fails with method not avialable + await views.__some_mutable_record(); //fails with method not available assert false; } catch (e) { Prim.debugPrint (Prim.errorMessage(e)); }; try { - await views.__motoko_xxx(); //fails with method not avialable + await views.__motoko_xxx(); //fails with method not available assert false; } catch (e) { Prim.debugPrint (Prim.errorMessage(e)); From 9804aacc36de0a8f5f3ecf16a3f7133e028260dd Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Wed, 4 Mar 2026 17:43:42 +0000 Subject: [PATCH 38/52] fix and test viewer arity bug --- test/run-drun/data-view-arity.mo | 42 ++++++++++++++++++++ test/run-drun/ok/data-view-arity.drun-run.ok | 2 + test/run-drun/ok/data-view-arity.tc.ok | 16 ++++++++ 3 files changed, 60 insertions(+) create mode 100644 test/run-drun/data-view-arity.mo create mode 100644 test/run-drun/ok/data-view-arity.drun-run.ok create mode 100644 test/run-drun/ok/data-view-arity.tc.ok diff --git a/test/run-drun/data-view-arity.mo b/test/run-drun/data-view-arity.mo new file mode 100644 index 00000000000..79ac081aeb7 --- /dev/null +++ b/test/run-drun/data-view-arity.mo @@ -0,0 +1,42 @@ +// simplified version of data-view.mo that doesn't require core. +//MOC-FLAG --generate-view-queries +persistent actor Self { + + type T0 = {#T0}; + module View0 { + public func view(self : T0) : + () -> T0 = + func () = self + }; + + let v0 : T0 = #T0; + + type T1 = {#T1}; + module View1 { + public func view(self : T1) : + (t1:T1) -> T1 = + func (t1) = self + }; + + let v1 : T1 = #T1; + + + type T2 = {#T2}; + module View2 { + public func view(self : T2) : + (t1:T2, t2:T2) -> T2 = + func (t1, t2) = self + }; + + let v2 : T2 = #T2; + + type T3 = {#T3}; + module View3 { + public func view(self : T3) : + (t1:T2, t2:T2, t3: T3) -> T3 = + func (t1, t2, t3) = self + }; + + let v3 : T3 = #T3; + +} diff --git a/test/run-drun/ok/data-view-arity.drun-run.ok b/test/run-drun/ok/data-view-arity.drun-run.ok new file mode 100644 index 00000000000..a6f776f43c6 --- /dev/null +++ b/test/run-drun/ok/data-view-arity.drun-run.ok @@ -0,0 +1,2 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/data-view-arity.tc.ok b/test/run-drun/ok/data-view-arity.tc.ok new file mode 100644 index 00000000000..57cf9d25532 --- /dev/null +++ b/test/run-drun/ok/data-view-arity.tc.ok @@ -0,0 +1,16 @@ +data-view-arity.mo:6.10-6.15: warning [M0194], unused identifier: `View0` +help: if this is intentional, prefix it with an underscore: `_View0` +data-view-arity.mo:12.7-12.9: warning [M0194], unused identifier: `v0` +help: if this is intentional, prefix it with an underscore: `_v0` +data-view-arity.mo:15.10-15.15: warning [M0194], unused identifier: `View1` +help: if this is intentional, prefix it with an underscore: `_View1` +data-view-arity.mo:21.7-21.9: warning [M0194], unused identifier: `v1` +help: if this is intentional, prefix it with an underscore: `_v1` +data-view-arity.mo:25.10-25.15: warning [M0194], unused identifier: `View2` +help: if this is intentional, prefix it with an underscore: `_View2` +data-view-arity.mo:31.7-31.9: warning [M0194], unused identifier: `v2` +help: if this is intentional, prefix it with an underscore: `_v2` +data-view-arity.mo:34.10-34.15: warning [M0194], unused identifier: `View3` +help: if this is intentional, prefix it with an underscore: `_View3` +data-view-arity.mo:40.7-40.9: warning [M0194], unused identifier: `v3` +help: if this is intentional, prefix it with an underscore: `_v3` From 89e1e6a3204938428233e4d23ff8d9132fbbf271 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 9 Mar 2026 22:29:56 +0000 Subject: [PATCH 39/52] missing commits --- src/lowering/desugar.ml | 2 +- test/run-drun/ok/data-view-admin.drun-run.ok | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index ac216477aa9..c22c82a4a00 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -658,7 +658,7 @@ and export_view viewer_opt = match (viewer_body, T.normalize viewer_field.typ) with | DotViewV view_exp, T.Func(Shared Query, _, [_], ts1, ts2) -> (* id.view() available *) - ts1, ts2, fun vs -> callE (exp view_exp) [] (tupE (List.map varE vs)) + ts1, ts2, fun vs -> callE (exp view_exp) [] (seqE (List.map varE vs)) | DefaultV view_exp, T.Func(Shared Query, _, [_], [], [t]) -> (* id, t shared *) assert (T.shared t); diff --git a/test/run-drun/ok/data-view-admin.drun-run.ok b/test/run-drun/ok/data-view-admin.drun-run.ok index 2f4cffe4b41..d862a3dc4b4 100644 --- a/test/run-drun/ok/data-view-admin.drun-run.ok +++ b/test/run-drun/ok/data-view-admin.drun-run.ok @@ -1,6 +1,6 @@ ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 ingress Completed: Reply: 0x4449444c0000 -debug.print: "IC0503: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister called `ic0.trap` with message: 'Unauthorized caller (caller must be self, some controller or isAdmin(caller))'. +debug.print: "IC0503: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister called `ic0.trap` with message: 'Unauthorized caller (caller must be self, some controller or satisfy isAdmin(caller))'. Consider gracefully handling failures from this canister or altering the canister to handle exceptions. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#trapped-explicitly" debug.print: [1, 2, 3] ingress Completed: Reply: 0x4449444c0000 From a20aa81375a18c285f9ae2094cae61815378e186 Mon Sep 17 00:00:00 2001 From: Christoph Date: Thu, 12 Mar 2026 15:14:29 +0100 Subject: [PATCH 40/52] refactors infer_viewer by extracting some common bits (#5905) --- src/mo_frontend/typing.ml | 73 +++++++++++++++------------------------ 1 file changed, 27 insertions(+), 46 deletions(-) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 18f3da49b99..e38c9d786b5 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4162,6 +4162,7 @@ and infer_viewer env scope mut id viewer = if not !Flags.generate_view_queries then () else begin + let at = id.at in assert (!viewer = None); let isAdmin_available () = let isAdmin = "isAdmin" in @@ -4176,64 +4177,44 @@ and infer_viewer env scope mut id viewer = available in let lab = "__" ^ id.it in - if T.(Env.mem lab (scope.Scope.val_env) || Lib.String.chop_prefix "__motoko" lab <> None) + if T.Env.mem lab scope.Scope.val_env || String.starts_with ~prefix:"__motoko" lab then () (* avoid any clash with local or reserved `__motoko_XXX` members by omitting viewer *) else + let varE = VarE {it = id.it; at; note = (mut, None)} @? at in + let viewer_field args ret = + T.{ lab; typ = Func (Shared Query, Promises, [scope_bind], args, ret); src = empty_src } in let infer_dot_view = Diag.with_message_store (recover_opt (fun msgs -> - let env = {env with msgs; used_identifiers = ref T.Env.empty } in (* don't record errors in outer env *) - let env = adjoin env scope in - let note() = empty_typ_note in - let at = id.at in - let dot_exp = - { it = DotE - ( {it = VarE {it = id.it; at ; note = (mut, None)}; - at; - note = note()}, - {it = "view"; note = (); at}, - ref None); - at; - note = note()} - in - let arg_exp = (false, ref {it = TupE []; at; note = note()}) in - let inst = {it = None; at; note = []} in - let exp = {it = CallE(None, dot_exp, inst, arg_exp); at; note = note()} in - let viewer_typ = infer_exp env exp in - (match T.normalize viewer_typ with - | T.Func(T.Local, T.Returns, [], ts1, ts2) -> - if List.for_all T.shared ts1 && List.for_all T.shared ts2 - then + let env = {env with msgs; used_identifiers = ref T.Env.empty } in (* don't record errors in outer env *) + let env = adjoin env scope in + let dot_exp = DotE(varE, "view" @@ at, ref None) @? at in + let arg_exp = (false, ref (TupE [] @? at)) in + let inst = {it = None; at; note = []} in + let exp = CallE(None, dot_exp, inst, arg_exp) @? at in + let viewer_typ = infer_exp env exp in + match T.normalize viewer_typ with + | T.Func(T.Local, T.Returns, [], ts1, ts2) + when List.for_all T.shared ts1 && List.for_all T.shared ts2 -> { viewer_body = DotViewV exp; - viewer_field = - T.{ lab; - typ = Func (Shared Query, Promises, [scope_bind], ts1, ts2); - src = empty_src }; + viewer_field = viewer_field ts1 ts2; viewer_isAdmin_available = isAdmin_available(); } - else raise Recover - | _ -> raise Recover))) + | _ -> raise Recover)) in match infer_dot_view with | Ok (exp_typ, _) -> - (* info env id.at "viewer found for %s" id.it; *) - viewer := Some exp_typ; - () + (* info env at "viewer found for %s" id.it; *) + viewer := Some exp_typ | Error _ -> (* info env id.at "viewer not found for %s" id.it; *) - (match T.Env.find_opt id.it scope.Scope.val_env with - | Some (typ, _, _) -> - let typ = T.as_immut typ in - if T.shared typ then - viewer := Some { viewer_body = DefaultV - {it = VarE {it = id.it; at = id.at ; note = (mut, None)}; - at = id.at; - note = { empty_typ_note with note_typ = typ }}; - viewer_field = - T.{ lab; - typ = Func (Shared Query, Promises, [scope_bind], [], [typ]); - src = empty_src }; - viewer_isAdmin_available = isAdmin_available() } - | None -> assert false) + let (typ, _, _) = T.Env.find id.it scope.Scope.val_env in + let typ = T.as_immut typ in + if T.shared typ then + viewer := Some + { viewer_body = DefaultV(varE); + viewer_field = viewer_field [] [typ]; + viewer_isAdmin_available = isAdmin_available(); + } end (* Blocks and Declarations *) From 0a4f983c163b4135748761871f4b29e4787cd2c8 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 16 Mar 2026 15:03:54 +0000 Subject: [PATCH 41/52] undo dodgy refactoring that relies on eval order to infer typ --- src/mo_frontend/typing.ml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index e38c9d786b5..73bbd6dda05 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4180,13 +4180,13 @@ and infer_viewer env scope mut id viewer = if T.Env.mem lab scope.Scope.val_env || String.starts_with ~prefix:"__motoko" lab then () (* avoid any clash with local or reserved `__motoko_XXX` members by omitting viewer *) else - let varE = VarE {it = id.it; at; note = (mut, None)} @? at in let viewer_field args ret = T.{ lab; typ = Func (Shared Query, Promises, [scope_bind], args, ret); src = empty_src } in let infer_dot_view = Diag.with_message_store (recover_opt (fun msgs -> let env = {env with msgs; used_identifiers = ref T.Env.empty } in (* don't record errors in outer env *) let env = adjoin env scope in + let varE = VarE {it = id.it; at; note = (mut, None)} @? at in let dot_exp = DotE(varE, "view" @@ at, ref None) @? at in let arg_exp = (false, ref (TupE [] @? at)) in let inst = {it = None; at; note = []} in @@ -4210,6 +4210,11 @@ and infer_viewer env scope mut id viewer = let (typ, _, _) = T.Env.find id.it scope.Scope.val_env in let typ = T.as_immut typ in if T.shared typ then + let varE = + { (VarE {it = id.it; at; note = (mut, None)} @? at) + with note = { note_typ = typ; + note_eff = T.Triv} } + in viewer := Some { viewer_body = DefaultV(varE); viewer_field = viewer_field [] [typ]; From ac76d28cffcec22d1fb75bcba8e0123be5a614d0 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 16 Mar 2026 17:08:00 +0000 Subject: [PATCH 42/52] first go at to_stable approximation --- src/mo_types/type.ml | 38 ++++++++++++++++++++++++++++++++++++++ src/mo_types/type.mli | 2 ++ 2 files changed, 40 insertions(+) diff --git a/src/mo_types/type.ml b/src/mo_types/type.ml index b9e37ac4ae5..700f8eb384e 100644 --- a/src/mo_types/type.ml +++ b/src/mo_types/type.ml @@ -1006,6 +1006,44 @@ let is_local_async_func typ = let shared t = serializable false t let stable t = serializable true t +let to_shared t = + let seen = ref ConEnv.empty in + let rec go t = + begin + match t with + | Var _ | Pre -> t + | Prim Error -> assert false + | Prim Region -> Any + | Any | Non | Prim _ -> t + | Async _ -> assert false + | Weak t -> Any + | Mut t -> Any + | Con (c, ts) -> + (match ConEnv.find_opt c !seen with + | Some d -> Con (d, List.map go ts) + | None -> + match Cons.kind c with + | Abs _ -> Any + | Def(tbs, u) -> + let d = Cons.fresh (Cons.name c) (Def(tbs, Pre)) in + seen := ConEnv.add c d !seen; + let u1 = go u in + set_kind d (Def(tbs, u1)); + Con (d, List.map go ts)) + | Array t -> Array (go t) + | Opt t -> Opt (go t) + | Tup ts -> Tup (List.map go ts) + | Obj (s, fs, ts) -> + (match s with + | Actor -> t + | Module | Mixin -> assert false (* TODO(1452) make modules sharable *) + | Object | Memory -> + Obj(s, List.map (fun f -> { f with typ = go f.typ }) fs, ts)) + | Variant fs -> Variant (List.map (fun f -> {f with typ = go f.typ}) fs) + | Func (s, c, tbs, ts1, ts2) -> if is_shared_sort s then t else Any + | Named (n, t) -> go t + end + in go t (* Forward declare TODO: haul string_of_typ before the lub/glb business, if possible *) diff --git a/src/mo_types/type.mli b/src/mo_types/type.mli index 67a55c0f148..3699c0f8d76 100644 --- a/src/mo_types/type.mli +++ b/src/mo_types/type.mli @@ -231,6 +231,8 @@ val is_local_async_func : typ -> bool val stable : typ -> bool +val to_shared : typ -> typ + val inhabited : typ -> bool val singleton : typ -> bool From ea8dc11db96b7fdce5a37063c182b9a954e4357d Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 16 Mar 2026 17:24:07 +0000 Subject: [PATCH 43/52] tweak: remove isAdmin functionality (#5912) Backs out the support for user-define isAdmin predicates --- src/lowering/desugar.ml | 34 ++++++--------- src/mo_def/syntax.ml | 3 +- src/mo_frontend/typing.ml | 20 +-------- test/run-drun/data-view-admin.mo | 44 -------------------- test/run-drun/data-view-sample.mo | 7 +--- test/run-drun/ok/data-view-admin.drun-run.ok | 6 --- test/run-drun/ok/data-view-admin.tc.ok | 4 -- test/run-drun/ok/data-view-sample.tc.ok | 2 - 8 files changed, 18 insertions(+), 102 deletions(-) delete mode 100644 test/run-drun/data-view-admin.mo delete mode 100644 test/run-drun/ok/data-view-admin.drun-run.ok delete mode 100644 test/run-drun/ok/data-view-admin.tc.ok delete mode 100644 test/run-drun/ok/data-view-sample.tc.ok diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index c22c82a4a00..2a65fc41c03 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -652,7 +652,7 @@ and export_runtime_information self_id = and export_view viewer_opt = match viewer_opt with | None -> ([], [], []) - | Some {viewer_body; viewer_field; viewer_isAdmin_available} -> + | Some {viewer_body; viewer_field} -> let open T in let ts1, ts2, mk_body = match (viewer_body, T.normalize viewer_field.typ) with @@ -679,27 +679,19 @@ and export_view viewer_opt = orE (primE (I.RelPrim (principal, Operator.EqOp)) [varE caller; selfRefE principal]) (primE (I.OtherPrim "is_controller") [varE caller]) in - let access_control = - if viewer_isAdmin_available then - let isAdmin = var "isAdmin" T.(Func(Local, Returns, [], [principal], [bool])) - in - orE is_self_or_controller - (callE (varE isAdmin) [] (varE caller)) - else is_self_or_controller - in ([ letD (var v typ) ( - funcE v (Shared Query) Promises [bind1] args ts2 ( - (asyncE T.Fut bind2 - (blockE [ - (* authentication, self or controller only *) - letD caller (primE I.ICCallerPrim []); - expD (ifE access_control - (unitE()) - (primE (Ir.OtherPrim "trap") - [textE "Unauthorized caller (caller must be self, some controller or satisfy isAdmin(caller))"])) - ] - (mk_body vs)) - (Con (scope_con1, [])))) + funcE v (Shared Query) Promises [bind1] args ts2 ( + (asyncE T.Fut bind2 + (blockE [ + (* authentication, self or controller only *) + letD caller (primE I.ICCallerPrim []); + expD (ifE is_self_or_controller + (unitE()) + (primE (Ir.OtherPrim "trap") + [textE "Unauthorized caller (caller must be self or a controller)"])) + ] + (mk_body vs)) + (Con (scope_con1, [])))) )], [T.{lab;typ; src = empty_src}], [{ it = I.{ name = lab; var = v }; at = no_region; note = typ }]) diff --git a/src/mo_def/syntax.ml b/src/mo_def/syntax.ml index 27cd7ca0fc2..92ee2233607 100644 --- a/src/mo_def/syntax.ml +++ b/src/mo_def/syntax.ml @@ -181,8 +181,7 @@ type id_ref = (string, mut' * exp option) Source.annotated_phrase and viewer_body = DotViewV of exp | DefaultV of exp and viewer = { viewer_body : viewer_body; - viewer_field : Type.field; - viewer_isAdmin_available : bool; + viewer_field : Type.field } and stab = stab' Source.phrase and stab' = Stable of viewer option ref | Flexible diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 73bbd6dda05..4bb7795ef06 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4164,18 +4164,6 @@ and infer_viewer env scope mut id viewer = begin let at = id.at in assert (!viewer = None); - let isAdmin_available () = - let isAdmin = "isAdmin" in - let env1 = adjoin env scope in - let available = - match T.Env.find_opt isAdmin env1.vals with - | Some (t, _, _, _) -> - T.sub t (T.Func(T.Local, T.Returns, [], [T.principal], [T.bool])) - | _ -> false - in - if available then use_identifier env1 isAdmin; - available - in let lab = "__" ^ id.it in if T.Env.mem lab scope.Scope.val_env || String.starts_with ~prefix:"__motoko" lab then () (* avoid any clash with local or reserved `__motoko_XXX` members by omitting viewer *) @@ -4196,9 +4184,7 @@ and infer_viewer env scope mut id viewer = | T.Func(T.Local, T.Returns, [], ts1, ts2) when List.for_all T.shared ts1 && List.for_all T.shared ts2 -> { viewer_body = DotViewV exp; - viewer_field = viewer_field ts1 ts2; - viewer_isAdmin_available = isAdmin_available(); - } + viewer_field = viewer_field ts1 ts2 } | _ -> raise Recover)) in match infer_dot_view with @@ -4217,9 +4203,7 @@ and infer_viewer env scope mut id viewer = in viewer := Some { viewer_body = DefaultV(varE); - viewer_field = viewer_field [] [typ]; - viewer_isAdmin_available = isAdmin_available(); - } + viewer_field = viewer_field [] [typ] } end (* Blocks and Declarations *) diff --git a/test/run-drun/data-view-admin.mo b/test/run-drun/data-view-admin.mo deleted file mode 100644 index e0486a77540..00000000000 --- a/test/run-drun/data-view-admin.mo +++ /dev/null @@ -1,44 +0,0 @@ -// simplified version of data-view.mo that doesn't require core. -//MOC-FLAG --generate-view-queries -import Prim "mo:⛔"; -import Client "data-view-admin/client"; -persistent actor Self { - - // a stable variable with viewer __view : () -> async [Nat] - let view = [1,2,3]; - - transient var admin : ?Principal = null; - - // isAdmin, when declared, is used to control access to generated views - func isAdmin(caller : Principal) : Bool { - (?caller) == admin - }; - - public func go() : async () { - - let server = actor (debug_show (Prim.principalOfActor(Self))) : - actor { - __view : query () -> async [Nat]; - }; - - let client = await Client.Client(server); - - // call __view from client without admin rights (fails) - try { - Prim.debugPrint(debug_show (await client.test())); - } catch e { - Prim.debugPrint(debug_show (Prim.errorMessage(e))); - }; - - // set c1 as admin - admin := ?Prim.principalOfActor(client); - - // call __view from client with admin rights (succeeds) - try { - Prim.debugPrint(debug_show (await client.test())); - } catch e { - assert(false); - }; - } -} -//CALL ingress go "DIDL\x00\x00" diff --git a/test/run-drun/data-view-sample.mo b/test/run-drun/data-view-sample.mo index b164bbf2148..8cdbb7a884b 100644 --- a/test/run-drun/data-view-sample.mo +++ b/test/run-drun/data-view-sample.mo @@ -14,11 +14,6 @@ persistent actor Self { include Views(); - func isAdmin(p : Principal) : Bool { - Debug.print(debug_show (#isAdmin p)); - true; // for demo purposes only - }; - // ═══════════════════════════════════════════════════════════════ // Northwind Database — classic Microsoft sample data // ═══════════════════════════════════════════════════════════════ @@ -277,6 +272,8 @@ persistent actor Self { totalUnicode = 1_112_064 : Nat; }; + ignore summary; + public func go() : async () { let views = actor (debug_show (Principal.fromActor(Self))) : actor { diff --git a/test/run-drun/ok/data-view-admin.drun-run.ok b/test/run-drun/ok/data-view-admin.drun-run.ok deleted file mode 100644 index d862a3dc4b4..00000000000 --- a/test/run-drun/ok/data-view-admin.drun-run.ok +++ /dev/null @@ -1,6 +0,0 @@ -ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 -ingress Completed: Reply: 0x4449444c0000 -debug.print: "IC0503: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister called `ic0.trap` with message: 'Unauthorized caller (caller must be self, some controller or satisfy isAdmin(caller))'. -Consider gracefully handling failures from this canister or altering the canister to handle exceptions. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#trapped-explicitly" -debug.print: [1, 2, 3] -ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/data-view-admin.tc.ok b/test/run-drun/ok/data-view-admin.tc.ok deleted file mode 100644 index 4cf1ef46522..00000000000 --- a/test/run-drun/ok/data-view-admin.tc.ok +++ /dev/null @@ -1,4 +0,0 @@ -data-view-admin.mo:8.7-8.11: warning [M0194], unused identifier: `view` -help: if this is intentional, prefix it with an underscore: `_view` -data-view-admin.mo:39.13-39.14: warning [M0194], unused identifier: `e` -help: if this is intentional, prefix it with an underscore: `_e` diff --git a/test/run-drun/ok/data-view-sample.tc.ok b/test/run-drun/ok/data-view-sample.tc.ok deleted file mode 100644 index b2de0e667aa..00000000000 --- a/test/run-drun/ok/data-view-sample.tc.ok +++ /dev/null @@ -1,2 +0,0 @@ -data-view-sample.mo:269.7-269.14: warning [M0194], unused identifier: `summary` -help: if this is intentional, prefix it with an underscore: `_summary` From fac319681d6f89bbdb943b789bb66502bd67e50c Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 16 Mar 2026 18:01:31 +0000 Subject: [PATCH 44/52] first stab (WIP) --- src/lowering/desugar.ml | 12 +++++++++--- src/mo_frontend/typing.ml | 7 ++++--- src/mo_types/type.ml | 5 ++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 2a65fc41c03..5a0f0779489 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -657,12 +657,18 @@ and export_view viewer_opt = let ts1, ts2, mk_body = match (viewer_body, T.normalize viewer_field.typ) with | DotViewV view_exp, T.Func(Shared Query, _, [_], ts1, ts2) -> - (* id.view() available *) - ts1, ts2, fun vs -> callE (exp view_exp) [] (seqE (List.map varE vs)) + (* id.view() available *) + assert (List.for_all T.shared ts2); + ts1, ts2, fun vs -> + let v = fresh_var "ret" (T.seq ts2) in + letE v (callE (exp view_exp) [] (seqE (List.map varE vs))) + (varE v) | DefaultV view_exp, T.Func(Shared Query, _, [_], [], [t]) -> (* id, t shared *) assert (T.shared t); - [], [t], fun _vs -> exp view_exp + [], [t], fun _vs -> + let v = fresh_var "ret" t in + letE v (exp view_exp) (varE v) | _ -> assert false in let vs = fresh_vars "param" ts1 in diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 4bb7795ef06..6f59978e605 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4169,7 +4169,8 @@ and infer_viewer env scope mut id viewer = then () (* avoid any clash with local or reserved `__motoko_XXX` members by omitting viewer *) else let viewer_field args ret = - T.{ lab; typ = Func (Shared Query, Promises, [scope_bind], args, ret); src = empty_src } in + let shared_ret = List.map (fun ty -> if T.shared ty then ty else T.to_shared ty) ret in + T.{ lab; typ = Func (Shared Query, Promises, [scope_bind], args, shared_ret); src = empty_src } in let infer_dot_view = Diag.with_message_store (recover_opt (fun msgs -> let env = {env with msgs; used_identifiers = ref T.Env.empty } in (* don't record errors in outer env *) @@ -4182,7 +4183,7 @@ and infer_viewer env scope mut id viewer = let viewer_typ = infer_exp env exp in match T.normalize viewer_typ with | T.Func(T.Local, T.Returns, [], ts1, ts2) - when List.for_all T.shared ts1 && List.for_all T.shared ts2 -> + when List.for_all T.shared ts1 && List.for_all T.stable ts2 -> { viewer_body = DotViewV exp; viewer_field = viewer_field ts1 ts2 } | _ -> raise Recover)) @@ -4195,7 +4196,7 @@ and infer_viewer env scope mut id viewer = (* info env id.at "viewer not found for %s" id.it; *) let (typ, _, _) = T.Env.find id.it scope.Scope.val_env in let typ = T.as_immut typ in - if T.shared typ then + if T.stable typ then let varE = { (VarE {it = id.it; at; note = (mut, None)} @? at) with note = { note_typ = typ; diff --git a/src/mo_types/type.ml b/src/mo_types/type.ml index 700f8eb384e..bcd34a3a59a 100644 --- a/src/mo_types/type.ml +++ b/src/mo_types/type.ml @@ -1025,11 +1025,13 @@ let to_shared t = match Cons.kind c with | Abs _ -> Any | Def(tbs, u) -> + (* TODO: handle non-trivial bounds *) let d = Cons.fresh (Cons.name c) (Def(tbs, Pre)) in seen := ConEnv.add c d !seen; let u1 = go u in set_kind d (Def(tbs, u1)); Con (d, List.map go ts)) + | Array (Mut t) -> Any | Array t -> Array (go t) | Opt t -> Opt (go t) | Tup ts -> Tup (List.map go ts) @@ -1038,7 +1040,8 @@ let to_shared t = | Actor -> t | Module | Mixin -> assert false (* TODO(1452) make modules sharable *) | Object | Memory -> - Obj(s, List.map (fun f -> { f with typ = go f.typ }) fs, ts)) + Obj(s, + List.filter_map (fun f -> if is_mut f.typ then None else Some { f with typ = go f.typ }) fs, ts)) | Variant fs -> Variant (List.map (fun f -> {f with typ = go f.typ}) fs) | Func (s, c, tbs, ts1, ts2) -> if is_shared_sort s then t else Any | Named (n, t) -> go t From a5cd6a6ced5f1ad3d1ef8b2477bdeffa87310e1a Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 16 Mar 2026 19:27:05 +0000 Subject: [PATCH 45/52] refactor and use CastPrim not let --- src/lowering/desugar.ml | 18 ++++++++++-------- src/mo_frontend/typing.ml | 4 +++- src/mo_types/type.ml | 34 ++++++++++++++++++++++------------ src/mo_types/type.mli | 2 +- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 5a0f0779489..72d49021470 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -660,15 +660,17 @@ and export_view viewer_opt = (* id.view() available *) assert (List.for_all T.shared ts2); ts1, ts2, fun vs -> - let v = fresh_var "ret" (T.seq ts2) in - letE v (callE (exp view_exp) [] (seqE (List.map varE vs))) - (varE v) - | DefaultV view_exp, T.Func(Shared Query, _, [_], [], [t]) -> + let view_e = exp view_exp in + primE (Ir.CastPrim (view_e.note.Note.typ, T.seq ts2)) [view_e] + (* let v = fresh_var "ret" (T.seq ts2) in + letE v (callE (exp view_exp) [] (seqE (List.map varE vs))) + (varE v) *) + | DefaultV view_exp, T.Func(Shared Query, _, [_], [], ts2) -> (* id, t shared *) - assert (T.shared t); - [], [t], fun _vs -> - let v = fresh_var "ret" t in - letE v (exp view_exp) (varE v) + assert (List.for_all T.shared ts2); + [], ts2, fun _vs -> + let view_e = exp view_exp in + primE (Ir.CastPrim (view_e.note.Note.typ, T.seq ts2)) [view_e] | _ -> assert false in let vs = fresh_vars "param" ts1 in diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 6f59978e605..14a51ab115b 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4169,7 +4169,9 @@ and infer_viewer env scope mut id viewer = then () (* avoid any clash with local or reserved `__motoko_XXX` members by omitting viewer *) else let viewer_field args ret = - let shared_ret = List.map (fun ty -> if T.shared ty then ty else T.to_shared ty) ret in + (* approximate any stable to shared returns, if necessary *) + let shared_ret = + List.map (fun ty -> if T.shared ty then ty else T.shared_of_stable ty) ret in T.{ lab; typ = Func (Shared Query, Promises, [scope_bind], args, shared_ret); src = empty_src } in let infer_dot_view = Diag.with_message_store (recover_opt (fun msgs -> diff --git a/src/mo_types/type.ml b/src/mo_types/type.ml index bcd34a3a59a..3ee2b5d075d 100644 --- a/src/mo_types/type.ml +++ b/src/mo_types/type.ml @@ -1006,7 +1006,8 @@ let is_local_async_func typ = let shared t = serializable false t let stable t = serializable true t -let to_shared t = +let shared_of_stable t = + assert (stable t); let seen = ref ConEnv.empty in let rec go t = begin @@ -1020,17 +1021,19 @@ let to_shared t = | Mut t -> Any | Con (c, ts) -> (match ConEnv.find_opt c !seen with - | Some d -> Con (d, List.map go ts) + | Some c' -> Con (c', List.map go ts) | None -> match Cons.kind c with | Abs _ -> Any | Def(tbs, u) -> - (* TODO: handle non-trivial bounds *) - let d = Cons.fresh (Cons.name c) (Def(tbs, Pre)) in - seen := ConEnv.add c d !seen; - let u1 = go u in - set_kind d (Def(tbs, u1)); - Con (d, List.map go ts)) + (* copy constructor with approximated body *) + (* just weaken bounds to Any *) + let tbs' = List.map (fun tb -> {tb with bound = Any}) tbs in + let c' = Cons.fresh (Cons.name c) (Def(tbs', Pre)) in + seen := ConEnv.add c c' !seen; + let u' = go u in + set_kind c' (Def(tbs', u')); + Con (c', List.map go ts)) | Array (Mut t) -> Any | Array t -> Array (go t) | Opt t -> Opt (go t) @@ -1038,13 +1041,20 @@ let to_shared t = | Obj (s, fs, ts) -> (match s with | Actor -> t - | Module | Mixin -> assert false (* TODO(1452) make modules sharable *) + | Module | Mixin -> + assert false (* TODO(1452) make modules sharable *) | Object | Memory -> Obj(s, - List.filter_map (fun f -> if is_mut f.typ then None else Some { f with typ = go f.typ }) fs, ts)) + (* drop mutable fields, approx others *) + List.filter_map (fun f -> + if is_mut f.typ then + None else + Some { f with typ = go f.typ }) fs, ts)) | Variant fs -> Variant (List.map (fun f -> {f with typ = go f.typ}) fs) - | Func (s, c, tbs, ts1, ts2) -> if is_shared_sort s then t else Any - | Named (n, t) -> go t + | Func (s, c, tbs, ts1, ts2) -> + assert (is_shared_sort s); + t + | Named (n, t) -> Named (n, go t) end in go t diff --git a/src/mo_types/type.mli b/src/mo_types/type.mli index 3699c0f8d76..beca77475c2 100644 --- a/src/mo_types/type.mli +++ b/src/mo_types/type.mli @@ -231,7 +231,7 @@ val is_local_async_func : typ -> bool val stable : typ -> bool -val to_shared : typ -> typ +val shared_of_stable : typ -> typ val inhabited : typ -> bool val singleton : typ -> bool From 76b7b4c8bbe232670a9dc5e66512b53d29f6e3cc Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 16 Mar 2026 20:04:14 +0000 Subject: [PATCH 46/52] adjust test --- test/run-drun/data-view.mo | 29 ++++++++++---------------- test/run-drun/ok/data-view.drun-run.ok | 6 ++---- test/run-drun/ok/data-view.tc.ok | 10 ++++----- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/test/run-drun/data-view.mo b/test/run-drun/data-view.mo index f80b0c98d4a..264fabd6435 100644 --- a/test/run-drun/data-view.mo +++ b/test/run-drun/data-view.mo @@ -18,9 +18,9 @@ persistent actor Self { array.view()(start, count); }; - // here, [insible_array.view] produces a non-shared (mutable) type, omit viewer - // later maybe approximate by shared type. - let invisible_array : [[var Nat]] = []; + // here, [insible_array.view] produces a non-shared (mutable) type, + // approximate to [Any] + let non_shared_array : [[var Nat]] = []; // shared values we can just display, sans viewer type Tree = { #leaf; #node : (Tree, Nat, Tree) }; @@ -28,7 +28,9 @@ persistent actor Self { let some_record = {a=1;b ="hello"; c = true} ; // stable, non-shared values we can't just display in full, without viewer - let some_mutable_record = {var a = 1}; + // approximate to shared supertype { b : Nat; c : Any}, + // dropping mutable fields, promoting non-shared to Any + let some_mutable_record = {var a = 1; b = 0; c = [var 0]}; public query func __override(): async Text { "user defined __override" }; @@ -48,8 +50,8 @@ persistent actor Self { /* user-defined */ __override : shared query () -> async Text; /* unable to generate because of (potential) name clash, no viewer or non-shared type*/ - __invisible_array : shared query() -> async None; - __some_mutable_record : shared query() -> async None; + __non_shared_array : shared query() -> async [Any]; + __some_mutable_record : shared query() -> async {b : Nat; c : Any}; // drop a, approximate type of c __motoko_xxx : shared query () -> async None; }; @@ -57,18 +59,9 @@ persistent actor Self { Prim.debugPrint(debug_show (await views.__some_variant())); Prim.debugPrint(debug_show (await views.__some_record())); Prim.debugPrint(debug_show (await views.__override())); // calls user-defined method - try { - await views.__invisible_array(); //fails with method not available - assert false; - } catch (e) { - Prim.debugPrint (Prim.errorMessage(e)); - }; - try { - await views.__some_mutable_record(); //fails with method not available - assert false; - } catch (e) { - Prim.debugPrint (Prim.errorMessage(e)); - }; + // debug_show doesn't support Any values, so avoid those below + Prim.debugPrint(debug_show (await views.__non_shared_array()).size()); + Prim.debugPrint(debug_show (await views.__some_mutable_record()).b); try { await views.__motoko_xxx(); //fails with method not available assert false; diff --git a/test/run-drun/ok/data-view.drun-run.ok b/test/run-drun/ok/data-view.drun-run.ok index 844be8ccf26..7b4a440c604 100644 --- a/test/run-drun/ok/data-view.drun-run.ok +++ b/test/run-drun/ok/data-view.drun-run.ok @@ -4,10 +4,8 @@ debug.print: [] debug.print: #node(#leaf, 0, #leaf) debug.print: {a = 1; b = "hello"; c = true} debug.print: "user defined __override" -debug.print: IC0536: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister has no update method '__invisible_array'.. -Check that the method being called is exported by the target canister. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#method-not-found -debug.print: IC0536: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister has no update method '__some_mutable_record'.. -Check that the method being called is exported by the target canister. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#method-not-found +debug.print: 0 +debug.print: 0 debug.print: IC0536: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister has no update method '__motoko_xxx'.. Check that the method being called is exported by the target canister. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#method-not-found ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/data-view.tc.ok b/test/run-drun/ok/data-view.tc.ok index 60ba415f67d..58441ffbf07 100644 --- a/test/run-drun/ok/data-view.tc.ok +++ b/test/run-drun/ok/data-view.tc.ok @@ -1,12 +1,12 @@ -data-view.mo:23.7-23.22: warning [M0194], unused identifier: `invisible_array` -help: if this is intentional, prefix it with an underscore: `_invisible_array` +data-view.mo:23.7-23.23: warning [M0194], unused identifier: `non_shared_array` +help: if this is intentional, prefix it with an underscore: `_non_shared_array` data-view.mo:27.7-27.19: warning [M0194], unused identifier: `some_variant` help: if this is intentional, prefix it with an underscore: `_some_variant` data-view.mo:28.7-28.18: warning [M0194], unused identifier: `some_record` help: if this is intentional, prefix it with an underscore: `_some_record` -data-view.mo:31.7-31.26: warning [M0194], unused identifier: `some_mutable_record` +data-view.mo:33.7-33.26: warning [M0194], unused identifier: `some_mutable_record` help: if this is intentional, prefix it with an underscore: `_some_mutable_record` -data-view.mo:35.7-35.15: warning [M0194], unused identifier: `override` +data-view.mo:37.7-37.15: warning [M0194], unused identifier: `override` help: if this is intentional, prefix it with an underscore: `_override` -data-view.mo:38.7-38.17: warning [M0194], unused identifier: `motoko_xxx` +data-view.mo:40.7-40.17: warning [M0194], unused identifier: `motoko_xxx` help: if this is intentional, prefix it with an underscore: `_motoko_xxx` From a4878931411e75c10ee7eabd0ea8c0ce102bc76e Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 16 Mar 2026 20:36:34 +0000 Subject: [PATCH 47/52] fix broken casting in desugar.ml --- src/lowering/desugar.ml | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 72d49021470..a5ec9f27f49 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -657,20 +657,19 @@ and export_view viewer_opt = let ts1, ts2, mk_body = match (viewer_body, T.normalize viewer_field.typ) with | DotViewV view_exp, T.Func(Shared Query, _, [_], ts1, ts2) -> - (* id.view() available *) - assert (List.for_all T.shared ts2); - ts1, ts2, fun vs -> - let view_e = exp view_exp in - primE (Ir.CastPrim (view_e.note.Note.typ, T.seq ts2)) [view_e] - (* let v = fresh_var "ret" (T.seq ts2) in - letE v (callE (exp view_exp) [] (seqE (List.map varE vs))) - (varE v) *) + (* id.view() available *) + assert (List.for_all T.shared ts2); + ts1, ts2, fun vs -> + let view_e = + callE (exp view_exp) [] (seqE (List.map varE vs)) + in + primE (Ir.CastPrim (view_e.note.Note.typ, T.seq ts2)) [view_e] | DefaultV view_exp, T.Func(Shared Query, _, [_], [], ts2) -> (* id, t shared *) - assert (List.for_all T.shared ts2); - [], ts2, fun _vs -> - let view_e = exp view_exp in - primE (Ir.CastPrim (view_e.note.Note.typ, T.seq ts2)) [view_e] + assert (List.for_all T.shared ts2); + [], ts2, fun _vs -> + let view_e = exp view_exp in + primE (Ir.CastPrim (view_e.note.Note.typ, T.seq ts2)) [view_e] | _ -> assert false in let vs = fresh_vars "param" ts1 in From e93417d786c92d82057a7fed74ae595e5a0b9d7f Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Mon, 16 Mar 2026 22:19:36 +0000 Subject: [PATCH 48/52] test and fix set_king bug with recursive types --- src/mo_types/type.ml | 2 +- test/run-drun/data-view.mo | 38 ++++++++++++++++++++++++-- test/run-drun/ok/data-view.drun-run.ok | 3 ++ test/run-drun/ok/data-view.tc.ok | 16 +++++++---- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/mo_types/type.ml b/src/mo_types/type.ml index 3ee2b5d075d..99500085ddd 100644 --- a/src/mo_types/type.ml +++ b/src/mo_types/type.ml @@ -1029,7 +1029,7 @@ let shared_of_stable t = (* copy constructor with approximated body *) (* just weaken bounds to Any *) let tbs' = List.map (fun tb -> {tb with bound = Any}) tbs in - let c' = Cons.fresh (Cons.name c) (Def(tbs', Pre)) in + let c' = Cons.fresh (Cons.name c) (Abs(tbs', Pre)) in seen := ConEnv.add c c' !seen; let u' = go u in set_kind c' (Def(tbs', u')); diff --git a/test/run-drun/data-view.mo b/test/run-drun/data-view.mo index 264fabd6435..0ac1a718c16 100644 --- a/test/run-drun/data-view.mo +++ b/test/run-drun/data-view.mo @@ -18,7 +18,20 @@ persistent actor Self { array.view()(start, count); }; - // here, [insible_array.view] produces a non-shared (mutable) type, + // here, [array_of_non_shared.view] produces a non-shared result type, + // approximated to shared {b:{#B}; c : Any} entries + let array_of_non_shared : [var { var a : {#A}; b : {#B}; c : [var {#C}]}] = + [ var {var a = #A; b = #B; c = [var #C]} ]; + + /* generates */ + public query func __array_of_non_shared(start:Nat, count: Nat) : async [ + {b:{#B}; c : Any} // <- approximation + ] { + array_of_non_shared.view()(start, count); + }; + + + // here, [non_shared_array.view] produces a non-shared (mutable) type, // approximate to [Any] let non_shared_array : [[var Nat]] = []; @@ -32,6 +45,17 @@ persistent actor Self { // dropping mutable fields, promoting non-shared to Any let some_mutable_record = {var a = 1; b = 0; c = [var 0]}; + //recursive types: + type List = ?(T, List); + + // all shared + let some_list : List = ?(1,?(2,null)); + + // non-shared, approximated + let some_non_shared_list : List<{var a : Nat; b : Text}> = + ?({var a = 1; b = "1"}, + ?({var a = 2; b = "2"}, null)); + public query func __override(): async Text { "user defined __override" }; let override = #override; @@ -40,11 +64,16 @@ persistent actor Self { let motoko_xxx = #motoko_xxx; /* generates nothing as would clash with reserved __motoko_ members" */ + + public func go() : async () { let views = actor (debug_show (Prim.principalOfActor(Self))) : actor { - /* generated */ + /* generated .views */ __array : shared query (Nat, Nat) -> async [(Nat, Text)]; + __array_of_non_shared : shared query (Nat, Nat) -> async [ + {b:{#B}; c : Any} ]; + /* generated simple */ __some_variant: shared query () -> async Tree; __some_record : shared query () -> async {a:Nat; b: Text; c : Bool}; /* user-defined */ @@ -52,6 +81,8 @@ persistent actor Self { /* unable to generate because of (potential) name clash, no viewer or non-shared type*/ __non_shared_array : shared query() -> async [Any]; __some_mutable_record : shared query() -> async {b : Nat; c : Any}; // drop a, approximate type of c + __some_list : shared query () -> async List; + __some_non_shared_list : shared query () -> async List<{b:Text}>; __motoko_xxx : shared query () -> async None; }; @@ -60,8 +91,11 @@ persistent actor Self { Prim.debugPrint(debug_show (await views.__some_record())); Prim.debugPrint(debug_show (await views.__override())); // calls user-defined method // debug_show doesn't support Any values, so avoid those below + Prim.debugPrint(debug_show (await views.__array_of_non_shared(0,0)).size()); Prim.debugPrint(debug_show (await views.__non_shared_array()).size()); Prim.debugPrint(debug_show (await views.__some_mutable_record()).b); + Prim.debugPrint(debug_show (await views.__some_list())); + Prim.debugPrint(debug_show (await views.__some_non_shared_list())); try { await views.__motoko_xxx(); //fails with method not available assert false; diff --git a/test/run-drun/ok/data-view.drun-run.ok b/test/run-drun/ok/data-view.drun-run.ok index 7b4a440c604..9c17c1d0bc1 100644 --- a/test/run-drun/ok/data-view.drun-run.ok +++ b/test/run-drun/ok/data-view.drun-run.ok @@ -6,6 +6,9 @@ debug.print: {a = 1; b = "hello"; c = true} debug.print: "user defined __override" debug.print: 0 debug.print: 0 +debug.print: 0 +debug.print: ?(1, ?(2, null)) +debug.print: ?({b = "1"}, ?({b = "2"}, null)) debug.print: IC0536: Error from Canister rwlgt-iiaaa-aaaaa-aaaaa-cai: Canister has no update method '__motoko_xxx'.. Check that the method being called is exported by the target canister. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#method-not-found ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/data-view.tc.ok b/test/run-drun/ok/data-view.tc.ok index 58441ffbf07..153f7aa7215 100644 --- a/test/run-drun/ok/data-view.tc.ok +++ b/test/run-drun/ok/data-view.tc.ok @@ -1,12 +1,16 @@ -data-view.mo:23.7-23.23: warning [M0194], unused identifier: `non_shared_array` +data-view.mo:36.7-36.23: warning [M0194], unused identifier: `non_shared_array` help: if this is intentional, prefix it with an underscore: `_non_shared_array` -data-view.mo:27.7-27.19: warning [M0194], unused identifier: `some_variant` +data-view.mo:40.7-40.19: warning [M0194], unused identifier: `some_variant` help: if this is intentional, prefix it with an underscore: `_some_variant` -data-view.mo:28.7-28.18: warning [M0194], unused identifier: `some_record` +data-view.mo:41.7-41.18: warning [M0194], unused identifier: `some_record` help: if this is intentional, prefix it with an underscore: `_some_record` -data-view.mo:33.7-33.26: warning [M0194], unused identifier: `some_mutable_record` +data-view.mo:46.7-46.26: warning [M0194], unused identifier: `some_mutable_record` help: if this is intentional, prefix it with an underscore: `_some_mutable_record` -data-view.mo:37.7-37.15: warning [M0194], unused identifier: `override` +data-view.mo:52.7-52.16: warning [M0194], unused identifier: `some_list` +help: if this is intentional, prefix it with an underscore: `_some_list` +data-view.mo:55.7-55.27: warning [M0194], unused identifier: `some_non_shared_list` +help: if this is intentional, prefix it with an underscore: `_some_non_shared_list` +data-view.mo:61.7-61.15: warning [M0194], unused identifier: `override` help: if this is intentional, prefix it with an underscore: `_override` -data-view.mo:40.7-40.17: warning [M0194], unused identifier: `motoko_xxx` +data-view.mo:64.7-64.17: warning [M0194], unused identifier: `motoko_xxx` help: if this is intentional, prefix it with an underscore: `_motoko_xxx` From 908c3d3254f9f8863aeedaff9b246d4e52b8e87a Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Tue, 7 Apr 2026 22:30:28 +0100 Subject: [PATCH 49/52] add example of nested stable type, approximated --- test/run-drun/data-view-nested.mo | 85 +++++++++++++++++++++ test/run-drun/ok/data-view-nested.tc.ok | 1 + test/run-drun/ok/data-view-nested.tc.ret.ok | 1 + 3 files changed, 87 insertions(+) create mode 100644 test/run-drun/data-view-nested.mo create mode 100644 test/run-drun/ok/data-view-nested.tc.ok create mode 100644 test/run-drun/ok/data-view-nested.tc.ret.ok diff --git a/test/run-drun/data-view-nested.mo b/test/run-drun/data-view-nested.mo new file mode 100644 index 00000000000..e5a2292eca4 --- /dev/null +++ b/test/run-drun/data-view-nested.mo @@ -0,0 +1,85 @@ +//MOC-FLAG --generate-view-queries --idl --package core $MOTOKO_CORE +import Map "mo:core/Map"; +import Set "mo:core/Set"; +import Nat "mo:core/Nat"; +import Text "mo:core/Text"; +import Principal "mo:core/Principal"; +import Debug "mo:core/Debug"; + +// mixin providing views for Core data structures +import Views "data-view-sample/views"; + +/* +produces .did + +type Set = record {}; // approximated +service : { + __customers: (ko: opt text, count: opt nat) -> + (vec + record { + text; + record { + city: text; + companyName: text; + contactName: text; + contactTitle: text; + countries: Set; + }; + }) query; + go: () -> (); +} +*/ + +persistent actor Self { + + include Views(); + + // ── Customers ────────────────────────────────────────────────── + + let customers : Map.Map; + }> = Map.empty(); + + customers.add("ALFKI", {companyName = "Alfreds Futterkiste"; contactName = "Maria Anders"; contactTitle = "Sales Representative"; city = "Berlin"; countries = Set.singleton "Germany"}); + customers.add("ANATR", {companyName = "Ana Trujillo Emparedados y helados"; contactName = "Ana Trujillo"; contactTitle = "Owner"; city = "Mexico D.F."; countries = Set.singleton "Mexico"}); + customers.add("ANTON", {companyName = "Antonio Moreno Taqueria"; contactName = "Antonio Moreno"; contactTitle = "Owner"; city = "Mexico D.F."; countries = Set.singleton "Mexico"}); + customers.add("AROUT", {companyName = "Around the Horn"; contactName = "Thomas Hardy"; contactTitle = "Sales Representative"; city = "London"; countries = Set.singleton "UK"}); + customers.add("BERGS", {companyName = "Berglunds snabbkop"; contactName = "Christina Berglund"; contactTitle = "Order Administrator"; city = "Lulea"; countries = Set.singleton "Sweden"}); + customers.add("BLAUS", {companyName = "Blauer See Delikatessen"; contactName = "Hanna Moos"; contactTitle = "Sales Representative"; city = "Mannheim"; countries = Set.singleton "Germany"}); + customers.add("BLONP", {companyName = "Blondesddsl pere et fils"; contactName = "Frederique Citeaux"; contactTitle = "Marketing Manager"; city = "Strasbourg"; countries = Set.singleton "France"}); + customers.add("BOLID", {companyName = "Bolido Comidas preparadas"; contactName = "Martin Sommer"; contactTitle = "Owner"; city = "Madrid"; countries = Set.singleton "Spain"}); + customers.add("BONAP", {companyName = "Bon app'"; contactName = "Laurence Lebihan"; contactTitle = "Owner"; city = "Marseille"; countries = Set.singleton "France"}); + customers.add("BOTTM", {companyName = "Bottom-Dollar Markets"; contactName = "Elizabeth Lincoln"; contactTitle = "Accounting Manager"; city = "Tsawassen"; countries = Set.singleton "Canada"}); + customers.add("BSBEV", {companyName = "B's Beverages"; contactName = "Victoria Ashworth"; contactTitle = "Sales Representative"; city = "London"; countries = Set.singleton "UK"}); + customers.add("CACTU", {companyName = "Cactus Comidas para llevar"; contactName = "Patricio Simpson"; contactTitle = "Sales Agent"; city = "Buenos Aires"; countries = Set.singleton "Argentina"}); + customers.add("CENTC", {companyName = "Centro comercial Moctezuma"; contactName = "Francisco Chang"; contactTitle = "Marketing Manager"; city = "Mexico D.F."; countries = Set.singleton "Mexico"}); + customers.add("CHOPS", {companyName = "Chop-suey Chinese"; contactName = "Yang Wang"; contactTitle = "Owner"; city = "Bern"; countries = Set.singleton "Switzerland"}); + customers.add("COMMI", {companyName = "Comercio Mineiro"; contactName = "Pedro Afonso"; contactTitle = "Sales Associate"; city = "Sao Paulo"; countries = Set.singleton "Brazil"}); + + public func go() : async () { + let views = actor (debug_show (Principal.fromActor(Self))) : + actor { + __customers: query (ko: ?Text, count: ?Nat) -> + async [(Text, + { city: Text; + companyName: Text; + contactName: Text; + contactTitle: Text; + countries: {} })] + }; + Debug.print(debug_show (await views.__customers(null, null))); // show all customers from first key + Debug.print(debug_show (await views.__customers(?"BOTTM", ?4))); // show at most 4 customers from key "" + } + +} +//SKIP run +//SKIP run-ir +//SKIP run-low +//CALL ingress go "DIDL\x00\x00" + + + diff --git a/test/run-drun/ok/data-view-nested.tc.ok b/test/run-drun/ok/data-view-nested.tc.ok new file mode 100644 index 00000000000..23e6e00665a --- /dev/null +++ b/test/run-drun/ok/data-view-nested.tc.ok @@ -0,0 +1 @@ +moc: multiple execution modes specified diff --git a/test/run-drun/ok/data-view-nested.tc.ret.ok b/test/run-drun/ok/data-view-nested.tc.ret.ok new file mode 100644 index 00000000000..69becfa16f9 --- /dev/null +++ b/test/run-drun/ok/data-view-nested.tc.ret.ok @@ -0,0 +1 @@ +Return code 1 From c1e726bac0e6e943aa88e2781070ea875940afb4 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Tue, 7 Apr 2026 23:01:49 +0100 Subject: [PATCH 50/52] remove unused viewer bindings --- src/lowering/desugar.ml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 2a65fc41c03..d07413ffc4a 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -894,7 +894,7 @@ and stabilize stab_opt d = match s, d.it with | (S.Flexible, _) -> ([], fun _ -> d) - | (S.Stable viewer, I.VarD(i, t, e)) -> + | (S.Stable _, I.VarD(i, t, e)) -> ([(i, T.Mut t)], fun get_state -> let v = fresh_var i t in @@ -903,8 +903,8 @@ and stabilize stab_opt d = e (varP v) (varE v) t)) - | (S.Stable viewer, I.RefD _) -> assert false (* RefD cannot come from user code *) - | (S.Stable viewer, I.LetD({it = I.VarP i; _} as p, e)) -> + | (S.Stable _, I.RefD _) -> assert false (* RefD cannot come from user code *) + | (S.Stable _, I.LetD({it = I.VarP i; _} as p, e)) -> let t = p.note in ([(i, t)], fun get_state -> @@ -914,7 +914,7 @@ and stabilize stab_opt d = e (varP v) (varE v) t)) - | (S.Stable viewer, I.LetD _) -> + | (S.Stable _, I.LetD _) -> assert false and view stab_opt = From f36837d444ec2ea3f733da2c4cbb489478c45232 Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Tue, 7 Apr 2026 23:12:58 +0100 Subject: [PATCH 51/52] consider identifiers in successfully generated queries _used_ --- src/mo_frontend/typing.ml | 12 +++++++++--- test/run-drun/ok/data-view-arity.tc.ok | 16 ---------------- test/run-drun/ok/data-view-core.tc.ok | 19 +------------------ test/run-drun/ok/data-view.tc.ok | 9 +-------- 4 files changed, 11 insertions(+), 45 deletions(-) delete mode 100644 test/run-drun/ok/data-view-arity.tc.ok diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 2eadb46b271..1b9877e6158 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4162,13 +4162,15 @@ and infer_viewer env scope mut id viewer = assert (!viewer = None); let lab = "__" ^ id.it in if T.Env.mem lab scope.Scope.val_env || String.starts_with ~prefix:"__motoko" lab - then () (* avoid any clash with local or reserved `__motoko_XXX` members by omitting viewer *) + then () (* avoid any clash with local or reserved `__motokoXXX` members by omitting viewer *) else let viewer_field args ret = T.{ lab; typ = Func (Shared Query, Promises, [scope_bind], args, ret); src = empty_src } in let infer_dot_view = Diag.with_message_store (recover_opt (fun msgs -> - let env = {env with msgs; used_identifiers = ref T.Env.empty } in (* don't record errors in outer env *) + (* checkpoint env.used_identifiers *) + let saved_used_identifiers = !(env.used_identifiers) in + let env = {env with msgs} in (* don't record errors in outer env *) let env = adjoin env scope in let varE = VarE {it = id.it; at; note = (mut, None)} @? at in let dot_exp = DotE(varE, "view" @@ at, ref None) @? at in @@ -4181,7 +4183,10 @@ and infer_viewer env scope mut id viewer = when List.for_all T.shared ts1 && List.for_all T.shared ts2 -> { viewer_body = DotViewV exp; viewer_field = viewer_field ts1 ts2 } - | _ -> raise Recover)) + | _ -> + (* restore env.used_identifiers *) + env.used_identifiers := saved_used_identifiers; + raise Recover)) in match infer_dot_view with | Ok (exp_typ, _) -> @@ -4197,6 +4202,7 @@ and infer_viewer env scope mut id viewer = with note = { note_typ = typ; note_eff = T.Triv} } in + use_identifier env id.it; viewer := Some { viewer_body = DefaultV(varE); viewer_field = viewer_field [] [typ] } diff --git a/test/run-drun/ok/data-view-arity.tc.ok b/test/run-drun/ok/data-view-arity.tc.ok deleted file mode 100644 index 57cf9d25532..00000000000 --- a/test/run-drun/ok/data-view-arity.tc.ok +++ /dev/null @@ -1,16 +0,0 @@ -data-view-arity.mo:6.10-6.15: warning [M0194], unused identifier: `View0` -help: if this is intentional, prefix it with an underscore: `_View0` -data-view-arity.mo:12.7-12.9: warning [M0194], unused identifier: `v0` -help: if this is intentional, prefix it with an underscore: `_v0` -data-view-arity.mo:15.10-15.15: warning [M0194], unused identifier: `View1` -help: if this is intentional, prefix it with an underscore: `_View1` -data-view-arity.mo:21.7-21.9: warning [M0194], unused identifier: `v1` -help: if this is intentional, prefix it with an underscore: `_v1` -data-view-arity.mo:25.10-25.15: warning [M0194], unused identifier: `View2` -help: if this is intentional, prefix it with an underscore: `_View2` -data-view-arity.mo:31.7-31.9: warning [M0194], unused identifier: `v2` -help: if this is intentional, prefix it with an underscore: `_v2` -data-view-arity.mo:34.10-34.15: warning [M0194], unused identifier: `View3` -help: if this is intentional, prefix it with an underscore: `_View3` -data-view-arity.mo:40.7-40.9: warning [M0194], unused identifier: `v3` -help: if this is intentional, prefix it with an underscore: `_v3` diff --git a/test/run-drun/ok/data-view-core.tc.ok b/test/run-drun/ok/data-view-core.tc.ok index 22bcc9d04f2..53c6b45476d 100644 --- a/test/run-drun/ok/data-view-core.tc.ok +++ b/test/run-drun/ok/data-view-core.tc.ok @@ -1,20 +1,3 @@ -data-view-core.mo:11.10-11.17: warning [M0194], unused identifier: `MapView` -help: if this is intentional, prefix it with an underscore: `_MapView` -data-view-core.mo:27.7-27.10: warning [M0194], unused identifier: `map` -help: if this is intentional, prefix it with an underscore: `_map` -data-view-core.mo:35.10-35.17: warning [M0194], unused identifier: `SetView` -help: if this is intentional, prefix it with an underscore: `_SetView` -data-view-core.mo:55.7-55.10: warning [M0194], unused identifier: `set` -help: if this is intentional, prefix it with an underscore: `_set` -data-view-core.mo:63.10-63.19: warning [M0194], unused identifier: `ArrayView` -help: if this is intentional, prefix it with an underscore: `_ArrayView` -data-view-core.mo:81.7-81.12: warning [M0194], unused identifier: `array` -help: if this is intentional, prefix it with an underscore: `_array` data-view-core.mo:91.7-91.22: warning [M0194], unused identifier: `invisible_array` help: if this is intentional, prefix it with an underscore: `_invisible_array` -data-view-core.mo:94.7-94.19: warning [M0194], unused identifier: `some_variant` -help: if this is intentional, prefix it with an underscore: `_some_variant` -data-view-core.mo:95.7-95.18: warning [M0194], unused identifier: `some_record` -help: if this is intentional, prefix it with an underscore: `_some_record` -data-view-core.mo:98.7-98.26: warning [M0194], unused identifier: `some_mutable_record` -help: if this is intentional, prefix it with an underscore: `_some_mutable_record` +data-view-core.mo:94.7-94.19: warning [M0244], variable some_variant is never reassigned, consider using `let` diff --git a/test/run-drun/ok/data-view.tc.ok b/test/run-drun/ok/data-view.tc.ok index 60ba415f67d..bc1be42bfda 100644 --- a/test/run-drun/ok/data-view.tc.ok +++ b/test/run-drun/ok/data-view.tc.ok @@ -1,11 +1,4 @@ -data-view.mo:23.7-23.22: warning [M0194], unused identifier: `invisible_array` -help: if this is intentional, prefix it with an underscore: `_invisible_array` -data-view.mo:27.7-27.19: warning [M0194], unused identifier: `some_variant` -help: if this is intentional, prefix it with an underscore: `_some_variant` -data-view.mo:28.7-28.18: warning [M0194], unused identifier: `some_record` -help: if this is intentional, prefix it with an underscore: `_some_record` -data-view.mo:31.7-31.26: warning [M0194], unused identifier: `some_mutable_record` -help: if this is intentional, prefix it with an underscore: `_some_mutable_record` +data-view.mo:27.7-27.19: warning [M0244], variable some_variant is never reassigned, consider using `let` data-view.mo:35.7-35.15: warning [M0194], unused identifier: `override` help: if this is intentional, prefix it with an underscore: `_override` data-view.mo:38.7-38.17: warning [M0194], unused identifier: `motoko_xxx` From 7657c270eb1ebbffb9cc779efd6e60530b117c2c Mon Sep 17 00:00:00 2001 From: Claudio Russo Date: Tue, 7 Apr 2026 23:15:15 +0100 Subject: [PATCH 52/52] remove commented debug code --- src/mo_frontend/typing.ml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 1b9877e6158..467a93a4185 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -4190,10 +4190,8 @@ and infer_viewer env scope mut id viewer = in match infer_dot_view with | Ok (exp_typ, _) -> - (* info env at "viewer found for %s" id.it; *) viewer := Some exp_typ | Error _ -> - (* info env id.at "viewer not found for %s" id.it; *) let (typ, _, _) = T.Env.find id.it scope.Scope.val_env in let typ = T.as_immut typ in if T.shared typ then