diff --git a/Changelog.md b/Changelog.md index ee14329bb33..456d7428c92 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,9 +13,20 @@ **Limitations**: recursive and mutually recursive definitions are considered used, even if never referenced outside the recursive definition. - + * Remove `__get_candid_interface_tmp_hack` endpoint. Candid interface is already stored as canister metadata, this temporary endpoint is redundant, thus removed. (#4386) + * Improved capability system, introducing a synchronous (`system`) capability (#4406). + + `actor` initialisation body, `pre`/`postupgrade` hooks, `async` function bodies (and + blocks) possess this capability. Functions (and classes) can demand it by prepending `system` + to the type argument list. The capability can be forwarded in calls by mentioning `` + in the instantiation parameter list. + + BREAKING CHANGE (Minor): A few built-in functions have been marked with demand + for the `system` capability. In order to call these, the full call hierarchy needs to be + adapted to pass the `system` capability. + ## 0.10.4 (2024-01-10) * motoko (`moc`) diff --git a/doc/md/base/List.md b/doc/md/base/List.md index a31770bf1b3..c783d956119 100644 --- a/doc/md/base/List.md +++ b/doc/md/base/List.md @@ -162,9 +162,7 @@ sum // => 3 Runtime: O(size) -Space: O(size) - -*Runtime and space assumes that `f` runs in O(1) time and space. +Space: O(1) ## Function `map` ``` motoko no-repl @@ -221,8 +219,6 @@ Runtime: O(size) Space: O(size) -*Runtime and space assumes that `f` runs in O(1) time and space. - ## Function `mapFilter` ``` motoko no-repl func mapFilter(l : List, f : T -> ?U) : List @@ -249,8 +245,6 @@ Runtime: O(size) Space: O(size) -*Runtime and space assumes that `f` runs in O(1) time and space. - ## Function `mapResult` ``` motoko no-repl func mapResult(xs : List, f : T -> Result.Result) : Result.Result, E> @@ -660,7 +654,7 @@ Runtime: O(min(size(xs), size(ys))) Space: O(min(size(xs), size(ys))) -*Runtime and space assumes that `f` runs in O(1) time and space. +*Runtime and space assumes that `zip` runs in O(1) time and space. ## Function `split` ``` motoko no-repl @@ -681,6 +675,8 @@ Runtime: O(n) Space: O(n) +*Runtime and space assumes that `zip` runs in O(1) time and space. + ## Function `chunks` ``` motoko no-repl func chunks(n : Nat, xs : List) : List> @@ -707,6 +703,8 @@ Runtime: O(size) Space: O(size) +*Runtime and space assumes that `zip` runs in O(1) time and space. + ## Function `fromArray` ``` motoko no-repl func fromArray(xs : [T]) : List diff --git a/doc/md/base/Option.md b/doc/md/base/Option.md index 8bf84923aa8..7acf1548052 100644 --- a/doc/md/base/Option.md +++ b/doc/md/base/Option.md @@ -124,13 +124,6 @@ func isNull(x : ?Any) : Bool Returns true if the argument is `null`, otherwise returns false. -## Function `equal` -``` motoko no-repl -func equal(x : ?A, y : ?A, eq : (A, A) -> Bool) : Bool -``` - -Returns true if the optional arguments are equal according to the equality function provided, otherwise returns false. - ## Function `assertSome` ``` motoko no-repl func assertSome(x : ?Any) diff --git a/doc/md/base/Trie.md b/doc/md/base/Trie.md index 0cd6748652b..dd84a19dcfa 100644 --- a/doc/md/base/Trie.md +++ b/doc/md/base/Trie.md @@ -546,7 +546,7 @@ Build sequence of two sub-builds ### Function `prod` ``` motoko no-repl -func prod(tl : Trie, tr : Trie, op : (K1, V1, K2, V2) -> ?(K3, V3), _k3_eq : (K3, K3) -> Bool) : Build +func prod(tl : Trie, tr : Trie, op : (K1, V1, K2, V2) -> ?(K3, V3), k3_eq : (K3, K3) -> Bool) : Build ``` Like [`prod`](#prod), except do not actually do the put calls, just @@ -920,7 +920,7 @@ new trie, and the prior value, if any. ## Function `mergeDisjoint2D` ``` motoko no-repl -func mergeDisjoint2D(t : Trie2D, _k1_eq : (K1, K1) -> Bool, k2_eq : (K2, K2) -> Bool) : Trie +func mergeDisjoint2D(t : Trie2D, k1_eq : (K1, K1) -> Bool, k2_eq : (K2, K2) -> Bool) : Trie ``` Like [`mergeDisjoint`](#mergedisjoint), except instead of merging a diff --git a/doc/md/examples/Alice.mo b/doc/md/examples/Alice.mo index bee3ff12e6b..43c0833308f 100644 --- a/doc/md/examples/Alice.mo +++ b/doc/md/examples/Alice.mo @@ -5,12 +5,12 @@ actor Alice { public func test() : async () { - Cycles.add(10_000_000_000_000); + Cycles.add(10_000_000_000_000); let porky = await Lib.PiggyBank(Alice.credit, 1_000_000_000); assert (0 == (await porky.getSavings())); - Cycles.add(1_000_000); + Cycles.add(1_000_000); await porky.deposit(); assert (1_000_000 == (await porky.getSavings())); @@ -20,7 +20,7 @@ actor Alice { await porky.withdraw(500_000); assert (0 == (await porky.getSavings())); - Cycles.add(2_000_000_000); + Cycles.add(2_000_000_000); await porky.deposit(); let refund = Cycles.refunded(); assert (1_000_000_000 == refund); @@ -31,7 +31,7 @@ actor Alice { // Callback for accepting cycles from PiggyBank public func credit() : async () { let available = Cycles.available(); - let accepted = Cycles.accept(available); + let accepted = Cycles.accept(available); assert (accepted == available); } diff --git a/doc/md/examples/PiggyBank.mo b/doc/md/examples/PiggyBank.mo index 316114e660e..4fb662c952c 100644 --- a/doc/md/examples/PiggyBank.mo +++ b/doc/md/examples/PiggyBank.mo @@ -20,7 +20,7 @@ shared(msg) actor class PiggyBank( let acceptable = if (amount <= limit) amount else limit; - let accepted = Cycles.accept(acceptable); + let accepted = Cycles.accept(acceptable); assert (accepted == acceptable); savings += acceptable; }; @@ -29,7 +29,7 @@ shared(msg) actor class PiggyBank( : async () { assert (msg.caller == owner); assert (amount <= savings); - Cycles.add(amount); + Cycles.add(amount); await benefit(); let refund = Cycles.refunded(); savings -= amount - refund; diff --git a/doc/md/examples/Reminder.mo b/doc/md/examples/Reminder.mo index 9fc8aa8abba..c1c2c5c5a1d 100644 --- a/doc/md/examples/Reminder.mo +++ b/doc/md/examples/Reminder.mo @@ -11,9 +11,9 @@ actor Reminder { print("Happy New Year!"); }; - ignore setTimer(#seconds (solarYearSeconds - abs(now() / 1_000_000_000) % solarYearSeconds), + ignore setTimer(#seconds (solarYearSeconds - abs(now() / 1_000_000_000) % solarYearSeconds), func () : async () { - ignore recurringTimer(#seconds solarYearSeconds, remind); + ignore recurringTimer(#seconds solarYearSeconds, remind); await remind(); }); } diff --git a/doc/md/examples/grammar.txt b/doc/md/examples/grammar.txt index c31fb626972..9b30a88bff1 100644 --- a/doc/md/examples/grammar.txt +++ b/doc/md/examples/grammar.txt @@ -52,7 +52,7 @@ ::= - ('<' , ',')> '>')? '->' + '->' ::= @@ -66,10 +66,19 @@ ::= '<' , ',')> '>' + ::= + + '<' , ',')> '>' + '<' 'system' (',' )* '>' + + ::= + ('<' , ',')> '>')? + '<' 'system' (',' )* '>' + ::= 'type' ('<' , ',')> '>')? '=' 'var'? ':' - ('<' , ',')> '>')? ':' + ':' ::= '#' (':' )? @@ -165,7 +174,7 @@ '[' ']' '.' '.' - ('<' , ',')> '>')? + '!' '(' 'system' '.' ')' @@ -293,8 +302,8 @@ 'let' '=' 'type' ('<' , ',')> '>')? '=' ? (':' )? '='? - 'func' ? ('<' , ',')> '>')? (':' )? - ? 'class' ? ('<' , ',')> '>')? (':' )? + 'func' ? (':' )? + ? 'class' ? (':' )? ::= diff --git a/doc/md/language-manual.md b/doc/md/language-manual.md index a0ec1014614..58e9203ffb2 100644 --- a/doc/md/language-manual.md +++ b/doc/md/language-manual.md @@ -398,7 +398,7 @@ The syntax of a *declaration* is as follows: var (: )? = mutable ? (: )? =? object ? func ? ? (: )? =? function - type ? = type + type ? = type ? ? class class ? ? (: )? @@ -556,7 +556,7 @@ Type expressions are used to specify the types of arguments, constraints (a.k.a ``` bnf ::= type expressions - ? constructor + ? constructor ? { ;* } object { ;* } variant { # } empty variant @@ -758,7 +758,7 @@ See [Stable Regions](stable-regions.md) and library [Region](./base/Region.md) f ### Constructed types -` ?` is the application of a type identifier or path, either built-in (i.e. `Int`) or user defined, to zero or more type **arguments**. The type arguments must satisfy the bounds, if any, expected by the type constructor’s type parameters (see [Well-formed types](#well-formed-types)). +` ?` is the application of a type identifier or path, either built-in (i.e. `Int`) or user defined, to zero or more type **arguments**. The type arguments must satisfy the bounds, if any, expected by the type constructor’s type parameters (see [Well-formed types](#well-formed-types)). Though typically a type identifier, more generally, `` may be a `.`-separated sequence of actor, object or module identifiers ending in an identifier accessing a type component of a value (for example, `Acme.Collections.List`). @@ -867,7 +867,7 @@ In all other positions, `( )` has the same meaning as ``. : immutable value var : mutable value ? : function value (short-hand) - type ? = type component + type ? = type component ``` A type field specifies the name and type of a value field of an object, or the name and definition of a type component of an object. The value field names within a single object type must be distinct and have non-colliding hashes. The type component names within a single object type must also be distinct and have non-colliding hashes. Value fields and type components reside in separate name spaces and thus may have names in common. @@ -876,7 +876,7 @@ A type field specifies the name and type of a value field of an object, or the n `var : ` specifies a *mutable* field, named `` of type ``. -`type ? = ` specifies a *type* component, with field name ``, abbreviating (parameterized) type ``. +`type ? = ` specifies a *type* component, with field name ``, abbreviating (parameterized) type ``. Unlike type declarations, a type component is not, in itself, recursive (though it may abbreviate an existing recursive type). In particular, the name `` is not bound in `` nor in any other fields of the enclosing object type. The name `` only determines the label to use when accessing the definition through a record of this type (using the dot notation). @@ -910,7 +910,24 @@ When enclosed by a non-`actor` object type, ` ? : ` unconstrained type parameter ``` -A type constructors, function value or function type may be parameterised by a vector of comma-separated, optionally constrained, type parameters. +``` bnf + ::= type parameters to type constructors + < typ-param,* > + + ::= function type parameters + < typ-param,* > type parameters + < system (, *)) > system capability prefixed type parameters + + + <: constrained type parameter + unconstrained type parameter + +``` + +A type constructor may be parameterised by a vector of comma-separated, optionally constrained, type parameters. + +A function, class constructor or function type may be parameterised by a vector of comma-separated, optionally constrained, type parameters. +The first of these may be the special, pseudo type parameter `system`. ` <: ` declares a type parameter with constraint ``. Any instantiation of `` must subtype `` (at that same instantiation). @@ -920,11 +937,20 @@ The names of type parameters in a vector must be distinct. All type parameters declared in a vector are in scope within its bounds. +The `system` pseudo-type parameter on function types indicates that a value of that type +requires `system` capability in order to be called and may itself call functions requiring `system` capability during its execution. + ### Type arguments ``` bnf - ::= type arguments + ::= type arguments to type constructors < ,* > + + + ::= type arguments to functions + < ,* > plain type arguments + < system (, *) > system capability prefixed type arguments + ``` Type constructors and functions may take type arguments. @@ -935,6 +961,20 @@ For a function, the number of type arguments, when provided, must agree with the Given a vector of type arguments instantiating a vector of type parameters, each type argument must satisfy the instantiated bounds of the corresponding type parameter. +In function calls, supplying the `system` pseudo type argument grants system capability to the function that requires it. + +System capability is available only in the following syntactic contexts: + +- in the body of an actor expression or actor class; +- in the body of a (non-`query`) `shared` function, asynchronous function, `async` expression or `async*` expression; +- in the body of a function or class that is declared with `system` pseudo type parameter; +- in system functions `preupgrade` and `postupgrade`. + +No other context provides `system` capabilities, including `query` and `composite query` methods. + +The `` type parameters of shared and asynchronous functions need not be declared. + + ### Well-formed types A type `T` is well-formed only if (recursively) its constituent types are well-formed, and: @@ -1265,8 +1305,8 @@ The declaration `` of a `system` field must be a manifest `func` declaratio | `heartbeat` | `() -> async ()` | heartbeat action | | `timer` | `(Nat64 -> ()) -> async ()` | timer action | | `inspect` | `{ caller : Principal; msg : ; arg : Blob } -> Bool` | message predicate | -| `preupgrade` | `() -> ()` | pre upgrade action | -| `postupgrade` | `() -> ()` | post upgrade action | +| `preupgrade` | `() -> ()` | pre upgrade action | +| `postupgrade` | `() -> ()` | post upgrade action | - `heartbeat`, when declared, is called on every Internet Computer subnet **heartbeat**, scheduling an asynchronous call to the `heartbeat` function. Due to its `async` return type, a heartbeat function may send messages and await results. The result of a heartbeat call, including any trap or thrown error, is ignored. The implicit context switch means that the time the heartbeat body is executed may be later than the time the heartbeat was issued by the subnet. @@ -1275,8 +1315,9 @@ The declaration `` of a `system` field must be a manifest `func` declaratio - `inspect`, when declared, is called as a predicate on every Internet Computer ingress message (with the exception of HTTP query calls). The return value, a `Bool`, indicates whether to accept or decline the given message. The argument type depends on the interface of the enclosing actor (see [Inspect](#inspect)). - `preupgrade`, when declared, is called during an upgrade, immediately *before* the (current) values of the (retired) actor’s stable variables are transferred to the replacement actor. + Its `` type parameter is implicitly assumed and need not be declared. -- `postupgrade`, when declared, is called during an upgrade, immediately *after* the (replacement) actor body has initialized its fields (inheriting values of the retired actors' stable variables), and before its first message is processed. +- `postupgrade`, when declared, is called during an upgrade, immediately *after* the (replacement) actor body has initialized its fields (inheriting values of the retired actors' stable variables), and before its first message is processed. Its `` type parameter is implicitly assumed and need not be declared. These `preupgrade` and `postupgrade` system methods provide the opportunity to save and restore in-flight data structures (e.g. caches) that are better represented using non-stable types. @@ -1481,7 +1522,7 @@ Evaluation of `var (: )? = ` proceeds by evaluating `` to a ### Type declaration -The declaration `type ? = ` declares a new type constructor ``, with optional type parameters `` and definition ``. +The declaration `type ? = ` declares a new type constructor ``, with optional type parameters `` and definition ``. The declaration `type C< X0 <: T0, …​, Xn <: Tn > = U` is well-formed provided: @@ -1698,7 +1739,7 @@ The *class* declaration `? ? class ? ? ( ``` bnf ? ? class ? (: )? := - type = { ;* }; + type ? = { ;* }; ? func ? : async? = async? ? ``` @@ -1707,7 +1748,7 @@ where: - `?`, when present, requires `` == `actor`, and provides access to the `caller` of an `actor` constructor, and -- `?` is the sequence of type identifiers bound by `?` (if any), and +- `?` and `?` is the sequence of type identifiers bound by `?` (if any), and - `;*` is the set of public field types inferred from `;*`. diff --git a/nix/sources.json b/nix/sources.json index d95985d7957..ed56e48914a 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -75,10 +75,10 @@ "homepage": null, "owner": "dfinity", "repo": "motoko-base", - "rev": "712d0587b0c8d2958adf450bd2e1554f2b0ff258", - "sha256": "17ghrfgzwlz7hbapy4sq9wbckxqy4xmcj8jpqv68bng9s83zvvvn", + "rev": "c7774995a5e271339dff11e16474ece959cc4b60", + "sha256": "10zfybm940bdbx1a1z614127r8bnjrxyq3qm6d3zn27l32y248k4", "type": "tarball", - "url": "https://github.com/dfinity/motoko-base/archive/712d0587b0c8d2958adf450bd2e1554f2b0ff258.tar.gz", + "url": "https://github.com/dfinity/motoko-base/archive/c7774995a5e271339dff11e16474ece959cc4b60.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "motoko-matchers": { diff --git a/src/ir_def/check_ir.ml b/src/ir_def/check_ir.ml index 6f92399002b..e9c0e0d84a9 100644 --- a/src/ir_def/check_ir.ml +++ b/src/ir_def/check_ir.ml @@ -78,8 +78,8 @@ let initial_env flavor : env = labs = T.Env.empty; rets = None; async = Async_cap.(match initial_cap() with - | (NullCap | ErrorCap) -> None - | (QueryCap c | AwaitCap c | AsyncCap c | CompositeCap c | CompositeAwaitCap c) -> Some c); + | NullCap | ErrorCap -> None + | QueryCap c | AwaitCap c | AsyncCap c | CompositeCap c | CompositeAwaitCap c | SystemCap c -> Some c); seen = ref T.ConSet.empty; check_run; } diff --git a/src/lang_utils/error_codes.ml b/src/lang_utils/error_codes.ml index c2a680afcfa..c5550755229 100644 --- a/src/lang_utils/error_codes.ml +++ b/src/lang_utils/error_codes.ml @@ -198,4 +198,7 @@ let error_codes : (string * string option) list = "M0192", None; (* Object/Actor/Module body type mismatch *) "M0193", None; (* Can't declare actor class to have `async*` result *) "M0194", Some([%blob "lang_utils/error_codes/M0194.md"]); (* Unused identifier warning *) + "M0195", Some([%blob "lang_utils/error_codes/M0195.md"]); (* warn that `system` capability is implicitly supplied *) + "M0196", None; (* `system` capability supplied but not required *) + "M0197", Some([%blob "lang_utils/error_codes/M0197.md"]); (* `system` capability required *) ] diff --git a/src/lang_utils/error_codes/M0195.md b/src/lang_utils/error_codes/M0195.md new file mode 100644 index 00000000000..d48f7124d59 --- /dev/null +++ b/src/lang_utils/error_codes/M0195.md @@ -0,0 +1,5 @@ +# M0195 + +This warning means that you called a function that demands elevated (`system`) capabilities, +without manifestly passing the capability. + diff --git a/src/lang_utils/error_codes/M0197.md b/src/lang_utils/error_codes/M0197.md new file mode 100644 index 00000000000..9bc9f95ee15 --- /dev/null +++ b/src/lang_utils/error_codes/M0197.md @@ -0,0 +1,8 @@ +# M0197 + +This error means that you tried to call a function that requires (`system`) capabilities, +in a context that does not provide them. + +Only actor bodies, async expressions, non-query async function bodies and +local functions with a leading `system` type parameter have system capabilities. + diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 9eadd3839b2..f3ffa3cacd5 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -412,8 +412,11 @@ and call_system_func_opt name es obj_typ = (unitE ()) (primE (Ir.OtherPrim "trap") [textE "canister_inspect_message explicitly refused message"])) - | _name -> - callE (varE (var id.it note)) [] (tupE [])) + | name -> + let inst = match name with + | "preupgrade" | "postupgrade" -> [T.scope_bound] + | _ -> [] in + callE (varE (var id.it note)) inst (tupE [])) | _ -> None) es and build_candid ts obj_typ = let open Idllib in diff --git a/src/mo_def/arrange.ml b/src/mo_def/arrange.ml index 3b75c896f92..22da9f70a56 100644 --- a/src/mo_def/arrange.ml +++ b/src/mo_def/arrange.ml @@ -132,7 +132,8 @@ module Make (Cfg : Config) = struct and inst inst = match inst.it with | None -> [] - | Some ts -> List.map typ ts + | Some (false, ts) -> List.map typ ts + | Some (true, ts) -> Atom "system" :: List.map typ ts and pat p = source p.at (annot_typ p.note (match p.it with | WildP -> Atom "WildP" @@ -144,7 +145,7 @@ module Make (Cfg : Config) = struct | SignP (uo, l) -> "SignP" $$ [Arrange_ops.unop uo ; lit !l] | OptP p -> "OptP" $$ [pat p] | TagP (i, p) -> "TagP" $$ [tag i; pat p] - | AltP (p1, p2) -> "AltP" $$ [pat p1; pat p2] + | AltP (p1, p2) -> "AltP" $$ [pat p1; pat p2] | ParP p -> "ParP" $$ [pat p])) and lit = function diff --git a/src/mo_def/syntax.ml b/src/mo_def/syntax.ml index 8060759b4b9..a23421ea6ed 100644 --- a/src/mo_def/syntax.ml +++ b/src/mo_def/syntax.ml @@ -133,8 +133,7 @@ and stab' = Stable | Flexible type op_typ = Type.typ ref (* For overloaded resolution; initially Type.Pre. *) - -type inst = (typ list option, Type.typ list) Source.annotated_phrase (* For implicit scope instantiation *) +type inst = ((bool * typ list) option, Type.typ list) Source.annotated_phrase (* For implicit scope instantiation *) type sort_pat = (Type.shared_sort * pat) Type.shared Source.phrase diff --git a/src/mo_frontend/parser.mly b/src/mo_frontend/parser.mly index 57d542dfa72..5ceca1e9e6e 100644 --- a/src/mo_frontend/parser.mly +++ b/src/mo_frontend/parser.mly @@ -136,7 +136,6 @@ let desugar_func_body sp x t_opt (is_sugar, e) = true, ignore_asyncE (scope_bind x.it e.at) e | _, _ -> (true, e) - let share_typ t = match t.it with | FuncT ({it = Type.Local; _} as s, tbs, t1, t2) -> @@ -173,21 +172,28 @@ let share_stab stab_opt dec = | _ -> None) | _ -> stab_opt +let ensure_system_cap (df : dec_field) = + match df.it.dec.it with + | LetD ({ it = VarP { it = "preupgrade" | "postupgrade"; _}; _} as pat, ({ it = FuncE (x, sp, tbs, p, t_opt, s, e); _ } as value), other) -> + let it = LetD (pat, { value with it = FuncE (x, sp, ensure_scope_bind "" tbs, p, t_opt, s, e) }, other) in + { df with it = { df.it with dec = { df.it.dec with it } } } + | _ -> df + let share_dec_field (df : dec_field) = match df.it.vis.it with | Public _ -> {df with it = {df.it with dec = share_dec df.it.dec; stab = share_stab df.it.stab df.it.dec}} - | _ -> - if is_sugared_func_or_module (df.it.dec) then - {df with it = - {df.it with stab = + | System -> ensure_system_cap df + | _ when is_sugared_func_or_module (df.it.dec) -> + {df with it = + {df.it with stab = match df.it.stab with | None -> Some (Flexible @@ df.it.dec.at) | some -> some} - } - else df + } + | _ -> df and objblock s ty dec_fields = List.iter (fun df -> @@ -453,15 +459,20 @@ inst : | (* empty *) { { it = None; at = no_region; note = [] } } | LT ts=seplist(typ, COMMA) GT - { { it = Some ts; at = at $sloc; note = [] } } + { { it = Some (false, ts); at = at $sloc; note = [] } } + | LT SYSTEM ts=preceded(COMMA, typ)* GT + { { it = Some (true, ts); at = at $sloc; note = [] } } - -%inline typ_params_opt : +%inline type_typ_params_opt : | (* empty *) { [] } | LT ts=seplist(typ_bind, COMMA) GT { ts } +%inline typ_params_opt : + | ts=type_typ_params_opt { ts } + | LT SYSTEM ts=preceded(COMMA, typ_bind)* GT { ensure_scope_bind "" ts } + typ_field : - | TYPE c=typ_id tps=typ_params_opt EQ t=typ + | TYPE c=typ_id tps=type_typ_params_opt EQ t=typ { TypF (c, tps, t) @@ at $sloc } | mut=var_opt x=id COLON t=typ { ValF (x, t, mut) @@ at $sloc } @@ -848,7 +859,7 @@ dec_nonvar : | LET p=pat EQ e=exp(ob) { let p', e' = normalize_let p e in LetD (p', e', None) @? at $sloc } - | TYPE x=typ_id tps=typ_params_opt EQ t=typ + | TYPE x=typ_id tps=type_typ_params_opt EQ t=typ { TypD(x, tps, t) @? at $sloc } | s=obj_sort xf=id_opt t=annot_opt EQ? efs=obj_body { let sort = Type.(match s.it with @@ -942,7 +953,7 @@ parse_module_header : | start import_list EOF {} typ_dec : - | TYPE x=typ_id tps=typ_params_opt EQ t=typ + | TYPE x=typ_id tps=type_typ_params_opt EQ t=typ { TypD(x, tps, t) @? at $sloc } stab_field : diff --git a/src/mo_frontend/printers.ml b/src/mo_frontend/printers.ml index 3e114afed0d..c0b9e5c0a3a 100644 --- a/src/mo_frontend/printers.ml +++ b/src/mo_frontend/printers.ml @@ -206,7 +206,9 @@ let string_of_symbol = function | X (N N_seplist_pat_bin_COMMA_) -> "seplist(,,)" | X (N N_seplist_pat_field_semicolon_) -> "seplist(,)" | X (N N_seplist_typ_COMMA_) -> "seplist(,,)" + | X (N N_list_preceded_COMMA_typ__) -> "(, )*" | X (N N_seplist_typ_bind_COMMA_) -> "seplist(,,)" + | X (N N_list_preceded_COMMA_typ_bind__) -> "(, )*" | X (N N_seplist_typ_field_semicolon_) -> "seplist(,)" | X (N N_seplist_stab_field_semicolon_) -> "seplist(,)" | X (N N_seplist_typ_item_COMMA_) -> "seplist(,,)" diff --git a/src/mo_frontend/typing.ml b/src/mo_frontend/typing.ml index 9e5ca45e264..f3696eaefb7 100644 --- a/src/mo_frontend/typing.ml +++ b/src/mo_frontend/typing.ml @@ -294,17 +294,17 @@ let system_funcs tfs = [ ("heartbeat", heartbeat_type); ("timer", timer_type); - ("preupgrade", T.Func (T.Local, T.Returns, [], [], [])); - ("postupgrade", T.Func (T.Local, T.Returns, [], [], [])); + T.("preupgrade", Func (Local, Returns, [scope_bind], [], [])); + T.("postupgrade", Func (Local, Returns, [scope_bind], [], [])); ("inspect", (let msg_typ = T.decode_msg_typ tfs in let record_typ = - T.Obj (T.Object, List.sort T.compare_field - [{T.lab = "caller"; T.typ = T.principal; T.src = T.empty_src}; - {T.lab = "arg"; T.typ = T.blob; T.src = T.empty_src}; - {T.lab = "msg"; T.typ = msg_typ; T.src = T.empty_src}]) + T.(Obj (Object, List.sort compare_field + [{lab = "caller"; typ = principal; src = empty_src}; + {lab = "arg"; typ = blob; src = empty_src}; + {lab = "msg"; typ = msg_typ; src = empty_src}])) in - T.Func (T.Local, T.Returns, [], [record_typ], [T.bool]))) + T.(Func (Local, Returns, [], [record_typ], [bool])))) ] @@ -478,21 +478,30 @@ let scope_info env typ at = (T.string_of_typ_expand typ) (string_of_region at); | None -> () -let rec infer_async_cap env sort cs tbs at = +let rec infer_async_cap env sort cs tbs body_opt at = + let open T in match sort, cs, tbs with - | (T.Shared T.Write | T.Local) , c::_, { T.sort = T.Scope; _ }::_ -> - { env with typs = T.Env.add T.default_scope_var c env.typs; - scopes = T.ConEnv.add c at env.scopes; + | Shared Write, c::_, { T.sort = Scope; _ }::_ -> + { env with typs = Env.add default_scope_var c env.typs; + scopes = ConEnv.add c at env.scopes; async = C.AsyncCap c } - | (T.Shared T.Query) , c::_, { T.sort = T.Scope; _ }::_ -> - { env with typs = T.Env.add T.default_scope_var c env.typs; - scopes = T.ConEnv.add c at env.scopes; + | Shared Query, c::_, { sort = Scope; _ }::_ -> + { env with typs = Env.add default_scope_var c env.typs; + scopes = ConEnv.add c at env.scopes; async = C.QueryCap c } - | (T.Shared T.Composite) , c::_, { T.sort = T.Scope; _ }::_ -> - { env with typs = T.Env.add T.default_scope_var c env.typs; - scopes = T.ConEnv.add c at env.scopes; + | Shared Composite, c::_, { sort = Scope; _ }::_ -> + { env with typs = Env.add default_scope_var c env.typs; + scopes = ConEnv.add c at env.scopes; async = C.CompositeCap c } - | T.Shared _, _, _ -> assert false (* impossible given sugaring *) + | Shared _, _, _ -> assert false (* impossible given sugaring *) + | Local, c::_, { sort = Scope; _ }::_ -> + let async = match body_opt with + | Some exp when not (is_asyncE exp) -> C.SystemCap c + | _ -> C.AsyncCap c + in + { env with typs = Env.add default_scope_var c env.typs; + scopes = ConEnv.add c at env.scopes; + async } | _ -> { env with async = C.NullCap } and check_AsyncCap env s at : T.typ * (T.con -> C.async_cap) = @@ -504,7 +513,7 @@ and check_AsyncCap env s at : T.typ * (T.con -> C.async_cap) = | C.ErrorCap -> local_error env at "M0037" "misplaced %s; a query cannot contain an %s" s s; T.Con(C.bogus_cap,[]), fun c -> C.NullCap - | C.NullCap -> + | C.(NullCap | SystemCap _) -> local_error env at "M0037" "misplaced %s; try enclosing in an async function" s; T.Con(C.bogus_cap,[]), fun c -> C.NullCap | C.CompositeAwaitCap _ -> @@ -513,16 +522,15 @@ and check_AsyncCap env s at : T.typ * (T.con -> C.async_cap) = and check_AwaitCap env s at = match env.async with - | C.AwaitCap c -> T.Con(c, []) - | C.CompositeAwaitCap c -> T.Con(c, []) + | C.(AwaitCap c + | CompositeAwaitCap c) -> T.Con(c, []) | C.AsyncCap _ | C.QueryCap _ | C.CompositeCap _ -> local_error env at "M0038" "misplaced %s; try enclosing in an async expression" s; T.Con(C.bogus_cap,[]) - | C.ErrorCap - | C.NullCap -> + | C.(ErrorCap | NullCap | SystemCap _) -> local_error env at "M0038" "misplaced %s" s; T.Con(C.bogus_cap,[]) @@ -535,19 +543,18 @@ and check_ErrorCap env s at = | C.QueryCap _ | C.CompositeCap _ -> local_error env at "M0039" "misplaced %s; try enclosing in an async expression or query function" s - | C.NullCap -> + | C.(NullCap | SystemCap _) -> local_error env at "M0039" "misplaced %s" s and scope_of_env env = - match env.async with - | C.AsyncCap c - | C.QueryCap c - | C.CompositeCap c - | C.CompositeAwaitCap c - | C.AwaitCap c -> - Some (T.Con(c,[])) - | C.ErrorCap -> None - | C.NullCap -> None + C.(match env.async with + | AsyncCap c + | QueryCap c + | CompositeCap c + | CompositeAwaitCap c + | AwaitCap c + | SystemCap c -> Some (T.Con(c, [])) + | ErrorCap | NullCap -> None) (* Types *) @@ -579,7 +586,7 @@ and check_typ' env typ : T.typ = T.Tup (List.map (fun (_, t) -> check_typ env t) typs) | FuncT (sort, binds, typ1, typ2) -> let cs, tbs, te, ce = check_typ_binds env binds in - let env' = infer_async_cap (adjoin_typs env te ce) sort.it cs tbs typ.at in + let env' = infer_async_cap (adjoin_typs env te ce) sort.it cs tbs None typ.at in let typs1 = as_domT typ1 in let c, typs2 = as_codomT sort.it typ2 in let ts1 = List.map (check_typ env') typs1 in @@ -795,24 +802,34 @@ and check_con_env env at ce = error env at "M0156" "block contains expansive type definitions%s" msg end; -and infer_inst env sort tbs typs at = +and infer_inst env sort tbs typs t_ret at = let ts = List.map (check_typ env) typs in let ats = List.map (fun typ -> typ.at) typs in - match tbs,typs with + match tbs, typs with | {T.bound; sort = T.Scope; _}::tbs', typs' -> assert (List.for_all (fun tb -> tb.T.sort = T.Type) tbs'); (match env.async with - | (C.AwaitCap c | C.AsyncCap c) when sort = T.Shared T.Query || sort = T.Shared T.Write || sort = T.Local -> - (T.Con(c,[])::ts, at::ats) - | (C.AwaitCap c | C.AsyncCap c) when sort = T.Shared T.Composite -> + | cap when sort = T.Local && not (T.is_async t_ret) -> + begin + match cap with + | C.(SystemCap c | AwaitCap c | AsyncCap c) -> + (T.Con(c, [])::ts, at::ats) + | _ -> + local_error env at "M0197" + "`system` capability required, but not available\n (need an enclosing async expression or function body or explicit `system` type parameter)"; + (T.Con(C.bogus_cap, [])::ts, at::ats) + end + | C.(AwaitCap c | AsyncCap c) when T.(sort = Shared Query || sort = Shared Write || sort = Local) -> + (T.Con(c, [])::ts, at::ats) + | C.(AwaitCap c | AsyncCap c) when sort = T.(Shared Composite) -> error env at "M0186" "composite send capability required, but not available\n (cannot call a `composite query` function from a non-`composite query` function)" - | (C.CompositeAwaitCap c | C.CompositeCap c) -> + | C.(CompositeAwaitCap c | CompositeCap c) -> begin match sort with | T.(Shared (Composite | Query)) -> - (T.Con(c,[])::ts, at::ats) - | T.((Shared Write) | Local) -> + (T.Con(c, [])::ts, at::ats) + | T.(Shared Write | Local) -> error env at "M0187" "send capability required, but not available\n (cannot call a `shared` function from a `composite query` function; only calls to `query` and `composite query` functions are allowed)" end @@ -829,8 +846,8 @@ and infer_inst env sort tbs typs at = assert (List.for_all (fun tb -> tb.T.sort = T.Type) tbs'); ts, ats -and check_inst_bounds env sort tbs inst at = - let ts, ats = infer_inst env sort tbs inst at in +and check_inst_bounds env sort tbs inst t_ret at = + let ts, ats = infer_inst env sort tbs inst t_ret at in check_typ_bounds env tbs ts ats at; ts @@ -1216,7 +1233,9 @@ and infer_exp'' env exp : T.typ = end; let env' = if obj_sort.it = T.Actor then - { env with async = C.NullCap; in_actor = true } + { env with + in_actor = true; + async = C.SystemCap C.top_cap } else env in let t = infer_obj env' obj_sort.it dec_fields exp.at in @@ -1376,7 +1395,7 @@ and infer_exp'' env exp : T.typ = let cs, tbs, te, ce = check_typ_binds env typ_binds in let c, ts2 = as_codomT sort typ in check_shared_return env typ.at sort c ts2; - let env' = infer_async_cap (adjoin_typs env te ce) sort cs tbs exp.at in + let env' = infer_async_cap (adjoin_typs env te ce) sort cs tbs (Some exp1) exp.at in let t1, ve1 = infer_pat_exhaustive (if T.is_shared_sort sort then local_error else warn) env' pat in let ve2 = T.Env.adjoin ve ve1 in let ts2 = List.map (check_typ env') ts2 in @@ -1846,7 +1865,7 @@ and check_exp_field env (ef : exp_field) fts = ignore (infer_exp env exp) and infer_call env exp1 inst exp2 at t_expect_opt = - let n = match inst.it with None -> 0 | Some typs -> List.length typs in + let n = match inst.it with None -> 0 | Some (_, typs) -> List.length typs in let t1 = infer_exp_promote env exp1 in let sort, tbs, t_arg, t_ret = try T.as_func_sub T.Local n t1 @@ -1861,12 +1880,12 @@ and infer_call env exp1 inst exp2 at t_expect_opt = in let ts, t_arg', t_ret' = match tbs, inst.it with - | [], (None | Some []) (* no inference required *) - | [{T.sort = T.Scope;_}], _ (* special case to allow t_arg driven overload resolution *) + | [], (None | Some (_, [])) (* no inference required *) + | [T.{sort = Scope;_}], _ (* special case to allow t_arg driven overload resolution *) | _, Some _ -> (* explicit instantiation, check argument against instantiated domain *) - let typs = match inst.it with None -> [] | Some typs -> typs in - let ts = check_inst_bounds env sort tbs typs at in + let typs = match inst.it with None -> [] | Some (_, typs) -> typs in + let ts = check_inst_bounds env sort tbs typs t_ret at in let t_arg' = T.open_ ts t_arg in let t_ret' = T.open_ ts t_ret in if not env.pre then check_exp_strong env t_arg' exp2; @@ -1914,7 +1933,13 @@ and infer_call env exp1 inst exp2 at t_expect_opt = error env exp2.at "M0100" "shared function call result contains abstract type%a" display_typ_expand t_ret'; - end + end; + match T.(is_shared_sort sort || is_async t_ret'), inst.it, tbs with + | false, Some (true, _), ([] | T.{ sort = Type; _ } :: _) -> + local_error env inst.at "M0196" "unexpected `system` capability (try deleting it)" + | false, (None | Some (false, _)), T.{ sort = Scope; _ } :: _ -> + warn env at "M0195" "this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning)" (if List.length tbs = 1 then "" else ", ...") + | _ -> () end; (* note t_ret' <: t checked by caller if necessary *) t_ret' @@ -2332,7 +2357,7 @@ and infer_obj env s dec_fields at : T.typ = in_actor = true; labs = T.Env.empty; rets = None; - async = C.NullCap; } + } in let decs = List.map (fun (df : dec_field) -> df.it.dec) dec_fields in let initial_usage = enter_scope env in @@ -2501,24 +2526,33 @@ and infer_dec env dec : T.typ = if not env.pre then begin let c = T.Env.find id.it env.typs in let ve0 = check_class_shared_pat env shared_pat obj_sort in - let cs, _tbs, te, ce = check_typ_binds env typ_binds in + let cs, tbs, te, ce = check_typ_binds env typ_binds in let env' = adjoin_typs env te ce in + let in_actor = obj_sort.it = T.Actor in let t_pat, ve = - infer_pat_exhaustive (if obj_sort.it = T.Actor then error else warn) env' pat + infer_pat_exhaustive (if in_actor then error else warn) env' pat in - if obj_sort.it = T.Actor && not (T.shared t_pat) then + if in_actor && not (T.shared t_pat) then error_shared env t_pat pat.at "M0034" "shared constructor has non-shared parameter type%a" display_typ_expand t_pat; let env'' = adjoin_vals (adjoin_vals env' ve0) ve in - let cs' = if obj_sort.it = T.Actor then List.tl cs else cs in + let sys_cap = match tbs with + | T.{sort = Scope; _} :: _ -> true + | _ -> false in + if in_actor then assert sys_cap; + let cs' = if sys_cap then List.tl cs else cs in let self_typ = T.Con (c, List.map (fun c -> T.Con (c, [])) cs') in let env''' = { (add_val env'' self_id.it self_typ self_id.at) with labs = T.Env.empty; rets = None; - async = C.NullCap; - in_actor = obj_sort.it = T.Actor; + async = + if sys_cap then + C.SystemCap (if in_actor then C.top_cap else List.hd cs) + else + C.NullCap; + in_actor; } in let initial_usage = enter_scope env''' in diff --git a/src/mo_types/async_cap.ml b/src/mo_types/async_cap.ml index aa7c03498bf..397cd4dc12f 100644 --- a/src/mo_types/async_cap.ml +++ b/src/mo_types/async_cap.ml @@ -6,6 +6,7 @@ type async_cap = | ErrorCap (* can try, catch (i.e. in the async body of shared query func) *) | AsyncCap of T.con (* can async, send (i.e. in a func of async type or shared func) *) | AwaitCap of T.con (* can async, send, try, catch, await (i.e. in an async expression *) + | SystemCap of T.con (* can call protected system functions (e,g, addCycles(...), setTimer(...) *) | NullCap (* none of the above *) | CompositeCap of T.con (* can (query) async (i.e. in a shared composite query func) *) | CompositeAwaitCap of T.con (* can send a (composite or vanilla) query, try, catch, await (i.e. in a composite query func) *) diff --git a/src/mo_types/type.ml b/src/mo_types/type.ml index 75ee7e132da..4fe364fbe69 100644 --- a/src/mo_types/type.ml +++ b/src/mo_types/type.ml @@ -1509,7 +1509,7 @@ and can_omit n t = | Obj (_, fs) | Variant fs -> List.for_all (fun f -> go i f.typ) fs | Func (s, c, tbs, ts1, ts2) -> let i' = i+List.length tbs in - List.for_all (fun tb -> (go i' tb.bound)) tbs && + List.for_all (fun tb -> go i' tb.bound) tbs && List.for_all (go i') ts1 && List.for_all (go i') ts2 | Typ c -> true (* assumes type defs are closed *) @@ -1602,14 +1602,16 @@ and pp_typ_nobin vs ppf t = if sugar then List.tl vs', List.tl tbs else - vs', tbs + match tbs with + | { sort = Scope; _ } :: _ -> ("system", List.hd vs' |> snd) :: List.tl vs', tbs + | _ -> vs', tbs in let vs'vs = vs' @ vs in fprintf ppf "@[<2>%s%a%a ->@ %a@]" (string_of_func_sort s) - (pp_binds (vs'vs) vs'') tbs' - (sequence (pp_typ_un (vs'vs))) ts1 - (pp_control_cod sugar c (vs'vs)) ts2 + (pp_binds vs'vs vs'') tbs' + (sequence (pp_typ_un vs'vs)) ts1 + (pp_control_cod sugar c vs'vs) ts2 | t -> pp_typ_pre vs ppf t @@ -1665,7 +1667,7 @@ and vars_of_binds vs bs = and name_of_var vs v = match vs with | [] -> v - | v'::vs' -> name_of_var vs' (if (fst v) = (fst v') then (fst v, snd v + 1) else v) + | v'::vs' -> name_of_var vs' (if fst v = fst v' then (fst v, snd v + 1) else v) and pp_bind vs ppf (v, {bound; _}) = if bound = Any then diff --git a/src/prelude/internals.mo b/src/prelude/internals.mo index e74977d5ca8..3cba8906b82 100644 --- a/src/prelude/internals.mo +++ b/src/prelude/internals.mo @@ -13,11 +13,11 @@ var @cycles : Nat = 0; // Function called by backend to add funds to call. // DO NOT RENAME without modifying compilation. -func @add_cycles() { +func @add_cycles() { let cycles = @cycles; @reset_cycles(); if (cycles != 0) { - (prim "cyclesAdd" : (Nat) -> ()) (cycles); + (prim "cyclesAdd" : Nat -> ()) (cycles); } }; @@ -416,7 +416,7 @@ func @install_actor_helper( switch install_arg { case (#new settings) { let available = (prim "cyclesAvailable" : () -> Nat) (); - let accepted = (prim "cyclesAccept" : Nat -> Nat) (available); + let accepted = (prim "cyclesAccept" : Nat -> Nat) (available); let sender_canister_version = ?(prim "canister_version" : () -> Nat64)(); @cycles += accepted; let { canister_id } = @@ -449,7 +449,7 @@ func @install_actor_helper( // that Prim.createActor was mentioned on the forum and might be in use. (#3420) func @create_actor_helper(wasm_module : Blob, arg : Blob) : async Principal = async { let available = (prim "cyclesAvailable" : () -> Nat) (); - let accepted = (prim "cyclesAccept" : Nat -> Nat) (available); + let accepted = (prim "cyclesAccept" : Nat -> Nat) (available); let sender_canister_version = ?(prim "canister_version" : () -> Nat64)(); @cycles += accepted; let { canister_id } = @@ -581,7 +581,7 @@ func @timer_helper() : async () { var @lastTimerId = 0; -func @setTimer(delayNanos : Nat64, recurring : Bool, job : () -> async ()) : (id : Nat) { +func @setTimer(delayNanos : Nat64, recurring : Bool, job : () -> async ()) : (id : Nat) { @lastTimerId += 1; let id = @lastTimerId; let now = (prim "time" : () -> Nat64) (); diff --git a/src/prelude/prim.mo b/src/prelude/prim.mo index c1a8125e920..27e2efa3783 100644 --- a/src/prelude/prim.mo +++ b/src/prelude/prim.mo @@ -334,11 +334,11 @@ func cyclesRefunded() : Nat { @refund; }; -func cyclesAccept(amount : Nat) : Nat { - (prim "cyclesAccept" : Nat -> Nat)(amount); +func cyclesAccept(amount : Nat) : Nat { + (prim "cyclesAccept" : Nat -> Nat)(amount); }; -func cyclesAdd(amount : Nat) : () { +func cyclesAdd(amount : Nat) : () { if (amount == 0) return; @cycles += amount; // trap if @cycles would exceed 2^128 diff --git a/test/fail/bad-call-raw.mo b/test/fail/bad-call-raw.mo index 33d3ea48427..fcfc43e1764 100644 --- a/test/fail/bad-call-raw.mo +++ b/test/fail/bad-call-raw.mo @@ -1,5 +1,5 @@ import P "mo:⛔"; actor self { - let a = P.call_raw("","foo",""); // reject, send capability required + let a = P.call_raw(P.principalOfBlob(""),"foo",""); // reject, send capability required }; diff --git a/test/fail/no-system-cap.mo b/test/fail/no-system-cap.mo new file mode 100644 index 00000000000..22d4eccd25f --- /dev/null +++ b/test/fail/no-system-cap.mo @@ -0,0 +1,15 @@ +import { setTimer; cyclesAdd } = "mo:⛔"; + +actor { + + public query func q() : async () { + ignore setTimer(1_000_000, false, func () : async () { }); + cyclesAdd(1_000_000); + }; + + public composite query func cq() : async() { + ignore setTimer(1_000_000, false, func () : async () { }); + cyclesAdd(1_000_000); + }; + +} diff --git a/test/fail/ok/M0127.tc.ok b/test/fail/ok/M0127.tc.ok index 0e1f6740d69..29b4943a89c 100644 --- a/test/fail/ok/M0127.tc.ok +++ b/test/fail/ok/M0127.tc.ok @@ -1,5 +1,5 @@ M0127.mo:2.3-2.40: type error [M0127], system function preupgrade is declared with type - Bool -> () + Bool -> () instead of expected type - () -> () + () -> () M0127.mo:2.26-2.29: warning [M0194], unused identifier foo (delete or rename to wildcard `_` or `_foo`) diff --git a/test/fail/ok/bad-call-raw.tc.ok b/test/fail/ok/bad-call-raw.tc.ok index 7ab0b072fc8..ad7be8f3cdf 100644 --- a/test/fail/ok/bad-call-raw.tc.ok +++ b/test/fail/ok/bad-call-raw.tc.ok @@ -1,2 +1,2 @@ -bad-call-raw.mo:4.11-4.34: type error [M0047], send capability required, but not available +bad-call-raw.mo:4.11-4.53: type error [M0047], send capability required, but not available (need an enclosing async expression or function body) diff --git a/test/fail/ok/illegal-await.tc.ok b/test/fail/ok/illegal-await.tc.ok index 6c1ca3d8d23..8a162af30d7 100644 --- a/test/fail/ok/illegal-await.tc.ok +++ b/test/fail/ok/illegal-await.tc.ok @@ -24,14 +24,14 @@ illegal-await.mo:24.11: info, start of scope $@anon-async-24.11 mentioned in err illegal-await.mo:26.5: info, end of scope $@anon-async-24.11 mentioned in error at illegal-await.mo:25.7-25.14 illegal-await.mo:22.10: info, start of scope $@anon-async-22.10 mentioned in error at illegal-await.mo:25.7-25.14 illegal-await.mo:27.3: info, end of scope $@anon-async-22.10 mentioned in error at illegal-await.mo:25.7-25.14 -illegal-await.mo:35.11-35.12: type error [M0087], ill-scoped await: expected async type from current scope $Rec, found async type from other scope $__13 +illegal-await.mo:35.11-35.12: type error [M0087], ill-scoped await: expected async type from current scope $Rec, found async type from other scope $__18 scope $Rec is illegal-await.mo:33.44-40.2 - scope $__13 is illegal-await.mo:33.1-40.2 + scope $__18 is illegal-await.mo:33.1-40.2 illegal-await.mo:33.44: info, start of scope $Rec mentioned in error at illegal-await.mo:35.5-35.12 illegal-await.mo:40.1: info, end of scope $Rec mentioned in error at illegal-await.mo:35.5-35.12 -illegal-await.mo:33.1: info, start of scope $__13 mentioned in error at illegal-await.mo:35.5-35.12 -illegal-await.mo:40.1: info, end of scope $__13 mentioned in error at illegal-await.mo:35.5-35.12 +illegal-await.mo:33.1: info, start of scope $__18 mentioned in error at illegal-await.mo:35.5-35.12 +illegal-await.mo:40.1: info, end of scope $__18 mentioned in error at illegal-await.mo:35.5-35.12 illegal-await.mo:38.20-38.21: type error [M0096], expression of type - async<$__13> () + async<$__18> () cannot produce expected type async<$Rec> () diff --git a/test/fail/ok/no-system-cap.tc.ok b/test/fail/ok/no-system-cap.tc.ok new file mode 100644 index 00000000000..0a69219048e --- /dev/null +++ b/test/fail/ok/no-system-cap.tc.ok @@ -0,0 +1,8 @@ +no-system-cap.mo:6.12-6.70: type error [M0197], `system` capability required, but not available + (need an enclosing async expression or function body or explicit `system` type parameter) +no-system-cap.mo:7.5-7.33: type error [M0197], `system` capability required, but not available + (need an enclosing async expression or function body or explicit `system` type parameter) +no-system-cap.mo:11.13-11.71: type error [M0197], `system` capability required, but not available + (need an enclosing async expression or function body or explicit `system` type parameter) +no-system-cap.mo:12.6-12.34: type error [M0197], `system` capability required, but not available + (need an enclosing async expression or function body or explicit `system` type parameter) diff --git a/test/fail/ok/no-system-cap.tc.ret.ok b/test/fail/ok/no-system-cap.tc.ret.ok new file mode 100644 index 00000000000..69becfa16f9 --- /dev/null +++ b/test/fail/ok/no-system-cap.tc.ret.ok @@ -0,0 +1 @@ +Return code 1 diff --git a/test/fail/ok/no-timer-canc.tc.ok b/test/fail/ok/no-timer-canc.tc.ok index 2cf6ec61c3e..92026795a3e 100644 --- a/test/fail/ok/no-timer-canc.tc.ok +++ b/test/fail/ok/no-timer-canc.tc.ok @@ -83,8 +83,8 @@ no-timer-canc.mo:3.10-3.21: type error [M0119], object field cancelTimer is not ctzNat32 : Nat32 -> Nat32; ctzNat64 : Nat64 -> Nat64; ctzNat8 : Nat8 -> Nat8; - cyclesAccept : Nat -> Nat; - cyclesAdd : Nat -> (); + cyclesAccept : Nat -> Nat; + cyclesAdd : Nat -> (); cyclesAvailable : () -> Nat; cyclesBalance : () -> Nat; cyclesRefunded : () -> Nat; diff --git a/test/fail/ok/no-timer-set.tc.ok b/test/fail/ok/no-timer-set.tc.ok index 7c46ff57e95..42632af93d9 100644 --- a/test/fail/ok/no-timer-set.tc.ok +++ b/test/fail/ok/no-timer-set.tc.ok @@ -83,8 +83,8 @@ no-timer-set.mo:3.10-3.18: type error [M0119], object field setTimer is not cont ctzNat32 : Nat32 -> Nat32; ctzNat64 : Nat64 -> Nat64; ctzNat8 : Nat8 -> Nat8; - cyclesAccept : Nat -> Nat; - cyclesAdd : Nat -> (); + cyclesAccept : Nat -> Nat; + cyclesAdd : Nat -> (); cyclesAvailable : () -> Nat; cyclesBalance : () -> Nat; cyclesRefunded : () -> Nat; diff --git a/test/fail/ok/timer-no-send.tc.ok b/test/fail/ok/timer-no-send.tc.ok new file mode 100644 index 00000000000..c53a3ccc090 --- /dev/null +++ b/test/fail/ok/timer-no-send.tc.ok @@ -0,0 +1,6 @@ +timer-no-send.mo:5.12-5.62: type error [M0197], `system` capability required, but not available + (need an enclosing async expression or function body or explicit `system` type parameter) +timer-no-send.mo:5.12-5.62: warning [M0195], this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning) +timer-no-send.mo:10.12-10.62: warning [M0195], this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning) +timer-no-send.mo:12.15-12.23: type error [M0196], unexpected `system` capability (try deleting it) +timer-no-send.mo:14.12-14.20: type error [M0037], misplaced async expression; try enclosing in an async function diff --git a/test/fail/ok/timer-no-send.tc.ret.ok b/test/fail/ok/timer-no-send.tc.ret.ok new file mode 100644 index 00000000000..69becfa16f9 --- /dev/null +++ b/test/fail/ok/timer-no-send.tc.ret.ok @@ -0,0 +1 @@ +Return code 1 diff --git a/test/fail/timer-no-send.mo b/test/fail/timer-no-send.mo new file mode 100644 index 00000000000..ca038cd0eca --- /dev/null +++ b/test/fail/timer-no-send.mo @@ -0,0 +1,15 @@ +import { debugPrint; setTimer } = "mo:⛔"; + +// no system capability: must not call `setTimer` +func _bowm() { + ignore setTimer(1_000_000, false, func () : async () { }); +}; + +// transferred system capability: may call `setTimer` +func _gawd() { + ignore setTimer(1_000_000, false, func () : async () { }); + + debugPrint("caveat"); // misplaced `` + + ignore async 42 // not allowed +}; diff --git a/test/run-drun/actor-class-cycles.mo b/test/run-drun/actor-class-cycles.mo index da65eca7c80..06c85db266c 100644 --- a/test/run-drun/actor-class-cycles.mo +++ b/test/run-drun/actor-class-cycles.mo @@ -19,7 +19,7 @@ actor a { Prim.debugPrint(debug_show({ iteration = i })); Prim.debugPrint(debug_show({ balance = round(Cycles.balance()) })); let c = await { - Cycles.add((i + 1) * 10_000_000_000_000); + Cycles.add((i + 1) * 10_000_000_000_000); Lib.C(); }; let {current = cur; initial = init} = await c.balance(); diff --git a/test/run-drun/actor-class-mgmt.mo b/test/run-drun/actor-class-mgmt.mo index b229bcfc608..8f11a38d773 100644 --- a/test/run-drun/actor-class-mgmt.mo +++ b/test/run-drun/actor-class-mgmt.mo @@ -70,24 +70,24 @@ actor a { await Cycles.provisional_top_up_actor(a, 100_000_000_000_000); do { - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let c0 = await Cs.C (0, ?(Prim.principalOfActor a)); assert ({args = 0; upgrades = 0} == (await c0.observe())); - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let c1 = await (system Cs.C)(#new default_settings)(1, null); assert ({args = 1; upgrades = 0} == (await c1.observe())); assert (c1 != c0); - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let c2 = await (system Cs.C)(#new settings)(2, null); assert ({args = 2; upgrades = 0} == (await c2.observe())); assert (c2 != c1); - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let {canister_id = p} = await ic00.create_canister default_settings; // no need to add cycles diff --git a/test/run-drun/basic-cycles.mo b/test/run-drun/basic-cycles.mo index 14f25eca5fe..42b3b159282 100644 --- a/test/run-drun/basic-cycles.mo +++ b/test/run-drun/basic-cycles.mo @@ -6,8 +6,8 @@ actor a { let balance : () -> Nat = Prim.cyclesBalance; let available : () -> Nat = Prim.cyclesAvailable; - let accept : Nat-> Nat = Prim.cyclesAccept; - let add : Nat -> () = Prim.cyclesAdd; + let accept : Nat -> Nat = Prim.cyclesAccept; + let add : Nat -> () = Prim.cyclesAdd; let refunded : () -> Nat = Prim.cyclesRefunded; @@ -34,23 +34,23 @@ actor a { // detect immediate overflow of add try (await async { - add(0x1_00000000_00000000_00000000_00000000); + add(0x1_00000000_00000000_00000000_00000000); assert false; }) catch (e) {Prim.debugPrint(Prim.errorMessage(e))}; // detect incremental overflow of add try (await async { - add(0xFFFFFFFF_FFFFFFFF_FFFFFFFFF_FFFFFFF); + add(0xFFFFFFFF_FFFFFFFF_FFFFFFFFF_FFFFFFF); Prim.debugPrint("ok"); - add(0x1); + add(0x1); assert false; }) catch (e) { Prim.debugPrint(Prim.errorMessage(e)) }; // detect accept overflow try (await async { - let _ = accept(0x1_00000000_00000000_00000000_00000000); + let _ = accept(0x1_00000000_00000000_00000000_00000000); assert false; }) catch (e) { Prim.debugPrint(Prim.errorMessage(e)) }; @@ -78,7 +78,7 @@ actor a { Prim.debugPrint("can't top up more"); return; // give up on test }; - add(amount); + add(amount); Prim.debugPrint(debug_show({added = amount})); try { await test(amount); diff --git a/test/run-drun/class-import.mo b/test/run-drun/class-import.mo index cad8c448449..7ce67345fb8 100644 --- a/test/run-drun/class-import.mo +++ b/test/run-drun/class-import.mo @@ -12,23 +12,23 @@ actor a { await Cycles.provisional_top_up_actor(a, 100_000_000_000_000); // test no arg class - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let empty : M0.Empty = await M0.Empty(); await empty.test(); // test single arg class - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let one : M1.One = await M1.One("one"); await one.test(); // test two arg class - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let two : M2.Two = await M2.Two("one","two"); await two.test(); // test non-trapping install try { - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let trap : M3.Trap = await M3.Trap(false); } catch _ { @@ -37,7 +37,7 @@ actor a { // test trapping install try { - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let trap : M3.Trap = await M3.Trap(true); assert false; } diff --git a/test/run-drun/clone.mo b/test/run-drun/clone.mo index 554ee29486c..6a6df77ed03 100644 --- a/test/run-drun/clone.mo +++ b/test/run-drun/clone.mo @@ -7,8 +7,8 @@ actor Cloner { // Calls Lib.Cloneable to construct a new Clonable, // passing itself as first argument, using available funds public shared func makeCloneable(init : Nat): async Lib.Cloneable { - let accepted = Cycles.accept(Cycles.available()); - Cycles.add(accepted); + let accepted = Cycles.accept(Cycles.available()); + Cycles.add(accepted); await Lib.Cloneable(makeCloneable, init); }; @@ -18,7 +18,7 @@ actor Cloner { await Cycles.provisional_top_up_actor(Cloner, 100_000_000_000_000); // create the original Cloneable object - Cycles.add(10_000_000_000_000); + Cycles.add(10_000_000_000_000); let c0 : Lib.Cloneable = await makeCloneable(0); await c0.someMethod(); // prints 1 Prim.debugPrint(debug_show(Prim.principalOfActor c0)); diff --git a/test/run-drun/clone/cloneable.mo b/test/run-drun/clone/cloneable.mo index acf99f1f705..8dded284eaa 100644 --- a/test/run-drun/clone/cloneable.mo +++ b/test/run-drun/clone/cloneable.mo @@ -20,7 +20,7 @@ actor class Cloneable( // our clone methods, indirecting through makeCloneable public func clone(init : Nat) : async Cloneable { - Cycles.add(Cycles.balance() / 2); + Cycles.add(Cycles.balance() / 2); await makeCloneable(init : Nat); } } diff --git a/test/run-drun/cycles/cycles.mo b/test/run-drun/cycles/cycles.mo index dff9d0907c2..bb1f845ab98 100644 --- a/test/run-drun/cycles/cycles.mo +++ b/test/run-drun/cycles/cycles.mo @@ -6,9 +6,9 @@ module { public let available : () -> Nat = Prim.cyclesAvailable; - public let accept : Nat -> Nat = Prim.cyclesAccept; + public let accept : Nat -> Nat = Prim.cyclesAccept; - public let add : Nat -> () = Prim.cyclesAdd; + public let add : Nat -> () = Prim.cyclesAdd; public let refunded : () -> Nat = Prim.cyclesRefunded; diff --git a/test/run-drun/cycles/wallet.mo b/test/run-drun/cycles/wallet.mo index eb218613626..7c91e1861b0 100644 --- a/test/run-drun/cycles/wallet.mo +++ b/test/run-drun/cycles/wallet.mo @@ -20,7 +20,7 @@ shared(msg) actor class Wallet() { public func credit() : async () { let bu = Cycles.balance(); let du = Cycles.available(); - ignore Cycles.accept(du); + ignore Cycles.accept(du); assert Cycles.balance() == bu + du; }; @@ -29,14 +29,14 @@ shared(msg) actor class Wallet() { credit : shared () -> async ()) : async () { if (msg.caller != owner) assert false; - Cycles.add(amount); + Cycles.add(amount); await credit(); }; public shared func refund( amount : Nat) : async () { - ignore Cycles.accept(Cycles.available() - amount); + ignore Cycles.accept(Cycles.available() - amount); print("refunding: " # debug_show(amount)); }; diff --git a/test/run-drun/distributor.mo b/test/run-drun/distributor.mo index 9289128ff00..3498438f70f 100644 --- a/test/run-drun/distributor.mo +++ b/test/run-drun/distributor.mo @@ -29,7 +29,7 @@ actor a { let i = k % n; let node = switch (nodes[i]) { case null { - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let n = await Lib.Node(i); // dynamically install a new Node nodes[i] := ?n; n; diff --git a/test/run-drun/install-empty-actor.mo b/test/run-drun/install-empty-actor.mo index dbb9e0efe7f..b1833cad947 100644 --- a/test/run-drun/install-empty-actor.mo +++ b/test/run-drun/install-empty-actor.mo @@ -12,7 +12,7 @@ actor a { try { Prim.debugPrint("Installing actor:"); - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let principal = await Prim.createActor(wasm_mod, empty_arg); let id = debug_show principal; Prim.debugPrint(id); diff --git a/test/run-drun/ok/timer-default.tc.ok b/test/run-drun/ok/timer-default.tc.ok new file mode 100644 index 00000000000..d07568fd4ab --- /dev/null +++ b/test/run-drun/ok/timer-default.tc.ok @@ -0,0 +1,8 @@ +timer-default.mo:27.13-27.55: warning [M0195], this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning) +timer-default.mo:28.13-28.57: warning [M0195], this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning) +timer-default.mo:33.7-33.25: warning [M0195], this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning) +timer-default.mo:38.7-38.25: warning [M0195], this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning) +timer-default.mo:43.7-43.25: warning [M0195], this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning) +timer-default.mo:48.7-48.25: warning [M0195], this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning) +timer-default.mo:53.14-53.64: warning [M0195], this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning) +timer-default.mo:59.14-59.64: warning [M0195], this function call implicitly requires `system` capability and may perform undesired actions (please review the call and provide a type instantiation `` to suppress this warning) diff --git a/test/run-drun/piggy-bank/ExperimentalCycles.mo b/test/run-drun/piggy-bank/ExperimentalCycles.mo index 2f643d2c312..02c4f3a886e 100644 --- a/test/run-drun/piggy-bank/ExperimentalCycles.mo +++ b/test/run-drun/piggy-bank/ExperimentalCycles.mo @@ -1,60 +1,151 @@ -/// Managing cycles +/// Managing cycles within actors on the Internet Computer (IC). /// -/// Usage of the Internet Computer is measured, and paid for, in _cycles_. -/// This library provides imperative operations for observing cycles, transferring cycles and +/// The usage of the Internet Computer is measured, and paid for, in _cycles_. +/// This library provides imperative operations for observing cycles, transferring cycles, and /// observing refunds of cycles. /// /// **WARNING:** This low-level API is **experimental** and likely to change or even disappear. /// Dedicated syntactic support for manipulating cycles may be added to the language in future, obsoleting this library. /// -/// **NOTE:** Since cycles measure computational resources, the value of -/// `balance()` can change from one call to the next. - +/// **NOTE:** Since cycles measure computational resources, the value of `balance()` can change from one call to the next. +/// +/// Example for use on IC: +/// ```motoko no-repl +/// import Cycles "mo:base/ExperimentalCycles"; +/// import Debug "mo:base/Debug"; +/// +/// actor { +/// public func main() : async() { +/// Debug.print("Main balance: " # debug_show(Cycles.balance())); +/// Cycles.add(15_000_000); +/// await operation(); // accepts 10_000_000 cycles +/// Debug.print("Main refunded: " # debug_show(Cycles.refunded())); // 5_000_000 +/// Debug.print("Main balance: " # debug_show(Cycles.balance())); // decreased by around 10_000_000 +/// }; +/// +/// func operation() : async() { +/// Debug.print("Operation balance: " # debug_show(Cycles.balance())); +/// Debug.print("Operation available: " # debug_show(Cycles.available())); +/// let obtained = Cycles.accept(10_000_000); +/// Debug.print("Operation obtained: " # debug_show(obtained)); // => 10_000_000 +/// Debug.print("Operation balance: " # debug_show(Cycles.balance())); // increased by 10_000_000 +/// Debug.print("Operation available: " # debug_show(Cycles.available())); // decreased by 10_000_000 +/// } +/// } +/// ``` import Prim "mo:⛔"; module { /// Returns the actor's current balance of cycles as `amount`. - public func balance() : (amount : Nat) { - Prim.cyclesBalance() - }; + /// + /// Example for use on the IC: + /// ```motoko no-repl + /// import Cycles "mo:base/ExperimentalCycles"; + /// import Debug "mo:base/Debug"; + /// + /// actor { + /// public func main() : async() { + /// let balance = Cycles.balance(); + /// Debug.print("Balance: " # debug_show(balance)); + /// } + /// } + /// ``` + public let balance : () -> (amount : Nat) = Prim.cyclesBalance; /// Returns the currently available `amount` of cycles. /// The amount available is the amount received in the current call, /// minus the cumulative amount `accept`ed by this call. - /// On exit from the current shared function or async expression via `return` or `throw` - /// any remaining available amount is automatically - /// refunded to the caller/context. - public func available() : (amount : Nat) { - Prim.cyclesAvailable() - }; + /// On exit from the current shared function or async expression via `return` or `throw`, + /// any remaining available amount is automatically refunded to the caller/context. + /// + /// Example for use on the IC: + /// ```motoko no-repl + /// import Cycles "mo:base/ExperimentalCycles"; + /// import Debug "mo:base/Debug"; + /// + /// actor { + /// public func main() : async() { + /// let available = Cycles.available(); + /// Debug.print("Available: " # debug_show(available)); + /// } + /// } + /// ``` + public let available : () -> (amount : Nat) = Prim.cyclesAvailable; /// Transfers up to `amount` from `available()` to `balance()`. /// Returns the amount actually transferred, which may be less than /// requested, for example, if less is available, or if canister balance limits are reached. - public func accept(amount : Nat) : (accepted : Nat) { - Prim.cyclesAccept(amount) - }; + /// + /// Example for use on the IC (for simplicity, only transferring cycles to itself): + /// ```motoko no-repl + /// import Cycles "mo:base/ExperimentalCycles"; + /// import Debug "mo:base/Debug"; + /// + /// actor { + /// public func main() : async() { + /// Cycles.add(15_000_000); + /// await operation(); // accepts 10_000_000 cycles + /// }; + /// + /// func operation() : async() { + /// let obtained = Cycles.accept(10_000_000); + /// Debug.print("Obtained: " # debug_show(obtained)); // => 10_000_000 + /// } + /// } + /// ``` + public let accept : (amount : Nat) -> (accepted : Nat) = Prim.cyclesAccept; /// Indicates additional `amount` of cycles to be transferred in /// the next call, that is, evaluation of a shared function call or /// async expression. + /// Traps if the current total would exceed `2 ** 128` cycles. /// Upon the call, but not before, the total amount of cycles ``add``ed since /// the last call is deducted from `balance()`. /// If this total exceeds `balance()`, the caller traps, aborting the call. /// - /// **Note**: the implicit register of added amounts is reset to zero on entry to + /// **Note**: The implicit register of added amounts is reset to zero on entry to /// a shared function and after each shared function call or resume from an await. - public func add(amount : Nat) : () { - Prim.cyclesAdd(amount) - }; + /// + /// Example for use on the IC (for simplicity, only transferring cycles to itself): + /// ```motoko no-repl + /// import Cycles "mo:base/ExperimentalCycles"; + /// + /// actor { + /// func operation() : async() { + /// ignore Cycles.accept(10_000_000); + /// }; + /// + /// public func main() : async() { + /// Cycles.add(15_000_000); + /// await operation(); + /// } + /// } + /// ``` + public let add : (amount : Nat) -> () = Prim.cyclesAdd; /// Reports `amount` of cycles refunded in the last `await` of the current /// context, or zero if no await has occurred yet. /// Calling `refunded()` is solely informational and does not affect `balance()`. /// Instead, refunds are automatically added to the current balance, /// whether or not `refunded` is used to observe them. - public func refunded() : (amount : Nat) { - Prim.cyclesRefunded() - }; + /// + /// Example for use on the IC (for simplicity, only transferring cycles to itself): + /// ```motoko no-repl + /// import Cycles "mo:base/ExperimentalCycles"; + /// import Debug "mo:base/Debug"; + /// + /// actor { + /// func operation() : async() { + /// ignore Cycles.accept(10_000_000); + /// }; + /// + /// public func main() : async() { + /// Cycles.add(15_000_000); + /// await operation(); // accepts 10_000_000 cycles + /// Debug.print("Refunded: " # debug_show(Cycles.refunded())); // 5_000_000 + /// } + /// } + /// ``` + public let refunded : () -> (amount : Nat) = Prim.cyclesRefunded; } diff --git a/test/run-drun/piggy-bank/PiggyBank.mo b/test/run-drun/piggy-bank/PiggyBank.mo index c9f21fdd8d6..04087e19bb8 100644 --- a/test/run-drun/piggy-bank/PiggyBank.mo +++ b/test/run-drun/piggy-bank/PiggyBank.mo @@ -20,7 +20,7 @@ shared(msg) actor class PiggyBank( let acceptable = if (amount <= limit) amount else limit; - let accepted = Cycles.accept(acceptable); + let accepted = Cycles.accept(acceptable); assert (accepted == acceptable); savings += acceptable; }; @@ -28,7 +28,7 @@ shared(msg) actor class PiggyBank( public shared(msg) func withdraw(amount : Nat) : async () { assert (msg.caller == owner); assert (amount <= savings); - Cycles.add(amount); + Cycles.add(amount); await benefit(); let refund = Cycles.refunded(); savings -= amount - refund; diff --git a/test/run-drun/system-capability-class.mo b/test/run-drun/system-capability-class.mo new file mode 100644 index 00000000000..3fa4c663421 --- /dev/null +++ b/test/run-drun/system-capability-class.mo @@ -0,0 +1,11 @@ +import { setTimer } = "mo:⛔"; + +class _Sys() : {} { + ignore setTimer(0, false, func() : async () {}) +} + +//SKIP run +//SKIP run-ir +//SKIP run-low +//SKIP drun-run +//SKIP wasm-run diff --git a/test/run-drun/system-capability.mo b/test/run-drun/system-capability.mo new file mode 100644 index 00000000000..e168d1f3a87 --- /dev/null +++ b/test/run-drun/system-capability.mo @@ -0,0 +1,23 @@ +// check whether the system capability demand can be satisfied from specific +// `actor` callsites + +import { setTimer } = "mo:⛔"; + +actor { + + system let preupgrade = func () { + ignore setTimer(0, false, func() : async () {}); + }; + + system func postupgrade() { + ignore setTimer(0, false, func() : async () {}); + }; + + ignore setTimer(0, false, func() : async () {}); // from init too +} + +//SKIP run +//SKIP run-ir +//SKIP run-low +//SKIP drun-run +//SKIP wasm-run diff --git a/test/run-drun/test-cycles-state.mo b/test/run-drun/test-cycles-state.mo index 09c1eb5f5e1..8518b42ec62 100644 --- a/test/run-drun/test-cycles-state.mo +++ b/test/run-drun/test-cycles-state.mo @@ -14,7 +14,7 @@ actor a { if (Cycles.balance() == 0) await Cycles.provisional_top_up_actor(a, 3_000_000_000_000); - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let wallet : WalletLib.Wallet = await WalletLib.Wallet(); await wallet.show(); print ("setting cycles"); @@ -30,7 +30,7 @@ actor a { // print(debug_show(Cycles.balance())); do { // check cycles available - Cycles.add(1000_000); + Cycles.add(1000_000); let cs = await wallet.available(); assert (cs == 1000_000); assert (Cycles.refunded() == 1000_000); @@ -44,25 +44,25 @@ actor a { do { // check cycles additive to zero on send - Cycles.add(1000_000); - Cycles.add(2000_000); + Cycles.add(1000_000); + Cycles.add(2000_000); let cs = await wallet.available(); assert (cs == 3000_000); assert (Cycles.refunded() == 3000_000); }; // check cycles reset on context switch - Cycles.add(1000_000); + Cycles.add(1000_000); await async { assert(Cycles.available() == 1000_000); // check cycles received - Cycles.add(5000); + Cycles.add(5000); let cs = await wallet.available(); assert (cs == 5000); assert (Cycles.refunded() == 5000); // add some unconsumed cycles - Cycles.add(200); + Cycles.add(200); }; // check refund from await async ... assert (Cycles.refunded() == 1000_000); @@ -76,7 +76,7 @@ actor a { public func credit() : async () { let b = Cycles.balance(); let a = Cycles.available(); - let acc = Cycles.accept(a); + let acc = Cycles.accept(a); // print("balance before " # debug_show(b)); // print("available " # debug_show(a)); // print("accepted " # debug_show(acc)); diff --git a/test/run-drun/test-cycles.mo b/test/run-drun/test-cycles.mo index 46016ac376e..e8c9007ea25 100644 --- a/test/run-drun/test-cycles.mo +++ b/test/run-drun/test-cycles.mo @@ -15,9 +15,9 @@ actor client { print("available: " # debug_show(Cycles.available())); - print("accept(0): " # debug_show(Cycles.accept(0))); + print("accept(0): " # debug_show(Cycles.accept(0))); - Cycles.add(2_000_000_000_000); + Cycles.add(2_000_000_000_000); let wallet : WalletLib.Wallet = await WalletLib.Wallet(); await wallet.show(); print ("setting cycles"); @@ -37,7 +37,7 @@ actor client { print("# credit-1"); // transfer half the amount back to the wallet // print(debug_show(await wallet.balance())); - Cycles.add(amount/4); + Cycles.add(amount/4); await wallet.credit(); print("refunded: " # debug_show(Cycles.refunded())); // print(debug_show(await wallet.balance())); @@ -46,7 +46,7 @@ actor client { print("# credit-2"); // transfer half the amount back to the wallet // print(debug_show(await wallet.balance())); - Cycles.add(amount/4); + Cycles.add(amount/4); await wallet.credit(); print("refunded: " # debug_show(Cycles.refunded())); // print(debug_show(await wallet.balance())); @@ -55,7 +55,7 @@ actor client { print("# refund"); // transfer half the amount back to the wallet // print(debug_show(await wallet.balance())); - Cycles.add(amount/2); + Cycles.add(amount/2); await wallet.refund(amount/4); print("refunded: " # debug_show(Cycles.refunded())); // print(debug_show(await wallet.balance())); @@ -64,7 +64,7 @@ actor client { // issue a bunch of refund requests, await them in reverse and check the refunds are as expected. func testRefunds(n : Nat) : async () { if (n == 0) return; - Cycles.add(n); + Cycles.add(n); print("refund(" # debug_show(n) # ")"); let a = wallet.refund(n); await testRefunds( n - 1); @@ -77,7 +77,7 @@ actor client { // try to accept cycles that aren't available // this should trap - print(debug_show(Cycles.accept(1))); + print(debug_show(Cycles.accept(1))); }; @@ -86,7 +86,7 @@ actor client { // print("credit: balance " # debug_show(Cycles.balance())); let b = Cycles.balance(); let a = Cycles.available(); - ignore Cycles.accept(a); + ignore Cycles.accept(a); // print("credit:balance " # debug_show(Cycles.balance())); // assert (Cycles.balance() == b + a); }; diff --git a/test/run-drun/test-piggy-bank.mo b/test/run-drun/test-piggy-bank.mo index 33cb629ff86..7bbe2d6956e 100644 --- a/test/run-drun/test-piggy-bank.mo +++ b/test/run-drun/test-piggy-bank.mo @@ -6,12 +6,12 @@ actor Alice { public func test() : async () { - Cycles.add(10_000_000_000_000); + Cycles.add(10_000_000_000_000); let porky = await Lib.PiggyBank(Alice.credit, 1_000_000_000); assert (0 == (await porky.getSavings())); - Cycles.add(1_000_000); + Cycles.add(1_000_000); await porky.deposit(); assert (1_000_000 == (await porky.getSavings())); @@ -21,7 +21,7 @@ actor Alice { await porky.withdraw(500_000); assert (0 == (await porky.getSavings())); - Cycles.add(2_000_000_000); + Cycles.add(2_000_000_000); await porky.deposit(); let refund = Cycles.refunded(); assert (1_000_000_000 == refund); @@ -32,7 +32,7 @@ actor Alice { // Callback for accepting cycles from PiggyBank public func credit() : async () { let available = Cycles.available(); - let accepted = Cycles.accept(available); + let accepted = Cycles.accept(available); assert (accepted == available); } diff --git a/test/run-drun/timer-cancel.mo b/test/run-drun/timer-cancel.mo index 4f8c5f0ccdc..0e32c75ac84 100644 --- a/test/run-drun/timer-cancel.mo +++ b/test/run-drun/timer-cancel.mo @@ -22,9 +22,9 @@ actor { let raw_rand = (actor "aaaaa-aa" : actor { raw_rand : () -> async Blob }).raw_rand; public shared func go() : async () { - ignore setTimer(2 * second, false, + ignore setTimer(2 * second, false, func () : async () { - t := setTimer(1 * second, true, remind); + t := setTimer(1 * second, true, remind); await remind(); }); diff --git a/test/run-drun/timer-default.mo b/test/run-drun/timer-default.mo index 41114156409..2087071eceb 100644 --- a/test/run-drun/timer-default.mo +++ b/test/run-drun/timer-default.mo @@ -12,7 +12,7 @@ actor { public shared func go() : async () { var attempts = 0; - ignore setTimer(1_000_000_000, false, func () : async () { count += 1; debugPrint "YEP!" }); + ignore setTimer(1_000_000_000, false, func () : async () { count += 1; debugPrint "YEP!" }); while (count < max) { ignore await raw_rand(); // yield to scheduler @@ -22,6 +22,45 @@ actor { }; debugPrint(debug_show {count}); }; + + func this_should_warn() { + ignore setTimer(1, false, func () : async () { }); + ignore setTimer<>(1, false, func () : async () { }); + }; + + func _warn1() : async () { + this_should_warn(); // OK: this line is fine + this_should_warn(); // call should warn + }; + + func _warn2() : async* () { + this_should_warn(); // OK: this line is fine + this_should_warn(); // call should warn + }; + + func _warn3() : async () = async { + this_should_warn(); // OK: this line is fine + this_should_warn(); // call should warn + }; + + func _warn4() : async* () = async* { + this_should_warn(); // OK: this line is fine + this_should_warn(); // call should warn + }; + + // these are allowed to contain sends + func _eeek() : async () { + ignore setTimer(1_000_000, false, func () : async () { }); + + ignore await async 42 + }; + + func _gwerr() : async Int = async { + ignore setTimer(1_000_000, false, func () : async () { }); + + await async 42 + }; + }; //SKIP run diff --git a/test/run-drun/timer-trap.mo b/test/run-drun/timer-trap.mo index fe24398ab02..1976a17eed2 100644 --- a/test/run-drun/timer-trap.mo +++ b/test/run-drun/timer-trap.mo @@ -15,7 +15,7 @@ actor { public shared func go() : async () { var attempts = 0; - periodicTimer := setTimer(1 * second, true, + periodicTimer := setTimer(1 * second, true, func () : async () { count += 1; debugPrint ("YEP!"); @@ -25,10 +25,10 @@ actor { cancelTimer periodicTimer; } }); - ignore setTimer(1 * second, false, - func () : async () { count += 1; debugPrint "EEK!"; assert false }); - ignore setTimer(1 * second, false, - func () : async () { count += 1; debugPrint "BEAM!"; throw error("beam me up Scotty!") }); + ignore setTimer(1 * second, false, + func () : async () { count += 1; debugPrint "EEK!"; assert false }); + ignore setTimer(1 * second, false, + func () : async () { count += 1; debugPrint "BEAM!"; throw error("beam me up Scotty!") }); while (count < max) { ignore await raw_rand(); // yield to scheduler diff --git a/test/run-drun/timer-zero-recur.mo b/test/run-drun/timer-zero-recur.mo index 47a063316b9..e12eb84da70 100644 --- a/test/run-drun/timer-zero-recur.mo +++ b/test/run-drun/timer-zero-recur.mo @@ -13,7 +13,7 @@ actor { var attempts = 0; // when duration is 0 all "future" recurrent expirations happen at once - ignore setTimer(0, true, func () : async () { count += 1; debugPrint "YEP!" }); + ignore setTimer(0, true, func () : async () { count += 1; debugPrint "YEP!" }); while (count < max) { ignore await raw_rand(); // yield to scheduler diff --git a/test/run-drun/timer.mo b/test/run-drun/timer.mo index 7fbe874edb4..07cb269435f 100644 --- a/test/run-drun/timer.mo +++ b/test/run-drun/timer.mo @@ -28,12 +28,12 @@ actor { var attempts = 0; var last = 0; - let id1 = setTimer(1 * second, false, func () : async () { count += 1; debugPrint "YEP!" }); - let id2 = setTimer(2 * second, false, func () : async () { count += 1; debugPrint "DIM!" }); - let id3 = setTimer(3 * second, false, func () : async () { + let id1 = setTimer(1 * second, false, func () : async () { count += 1; debugPrint "YEP!" }); + let id2 = setTimer(2 * second, false, func () : async () { count += 1; debugPrint "DIM!" }); + let id3 = setTimer(3 * second, false, func () : async () { count += 1; debugPrint "ROOK!"; - last := setTimer(2 * second, true, func () : async () { + last := setTimer(2 * second, true, func () : async () { count += 1; debugPrint "BATT!"; if (count == max) { cancelTimer last; } });