Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dbc9858fb | |||
| 51f9eaa934 | |||
| 0ebcf006e2 | |||
| 381a7c98cd | |||
| 4bec4e5107 | |||
| 4dd247b159 | |||
| d926c4a7e3 |
@@ -3,7 +3,7 @@ version: 2.1
|
||||
executors:
|
||||
aebuilder:
|
||||
docker:
|
||||
- image: aeternity/builder:xenial-otp21
|
||||
- image: aeternity/builder:bionic-otp24
|
||||
user: builder
|
||||
working_directory: ~/aesophia
|
||||
|
||||
|
||||
@@ -13,10 +13,6 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip3
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
|
||||
- run: pip3 install -r .github/workflows/requirements.txt -U
|
||||
- run: git config --global user.email "github-action@users.noreply.github.com"
|
||||
- run: git config --global user.name "GitHub Action"
|
||||
|
||||
@@ -13,10 +13,6 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip3
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
|
||||
- run: pip3 install -r .github/workflows/requirements.txt -U
|
||||
- run: git config --global user.email "github-action@users.noreply.github.com"
|
||||
- run: git config --global user.name "GitHub Action"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mkdocs==1.2.4
|
||||
mkdocs-simple-hooks==0.1.3
|
||||
mkdocs-material==7.1.9
|
||||
mike==1.0.1
|
||||
pygments==2.11.2
|
||||
mkdocs-simple-hooks==0.1.5
|
||||
mkdocs-material==7.3.6
|
||||
mike==1.1.2
|
||||
pygments==2.12.0
|
||||
+10
-2
@@ -6,7 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Compiler warnings for the follwing: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
|
||||
### Changed
|
||||
### Removed
|
||||
|
||||
## [7.0.0]
|
||||
### Added
|
||||
- Added support for `EXIT` opcode via `exit : (string) => 'a` function (behaves same as `ABORT`, but consumes all gas).
|
||||
- Compiler warnings for the following: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
|
||||
- The pipe operator |>
|
||||
```
|
||||
[1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3]))
|
||||
@@ -16,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
function sum(l : list(int)) : int = foldl((+), 0, l)
|
||||
function logical_and(x, y) = (&&)(x, y)
|
||||
```
|
||||
- Contract interfaces polymorphism
|
||||
### Changed
|
||||
- Error messages have been restructured (less newlines) to provide more unified errors. Also `pp_oneline/1` has been added.
|
||||
- Ban empty record definitions (e.g. `record r = {}` would give an error).
|
||||
@@ -345,7 +352,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Simplify calldata creation - instead of passing a compiled contract, simply
|
||||
pass a (stubbed) contract string.
|
||||
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v6.1.0...HEAD
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.0.0...HEAD
|
||||
[7.0.0]: https://github.com/aeternity/aesophia/compare/v6.1.0...v7.0.0
|
||||
[6.1.0]: https://github.com/aeternity/aesophia/compare/v6.0.2...v6.1.0
|
||||
[6.0.2]: https://github.com/aeternity/aesophia/compare/v6.0.1...v6.0.2
|
||||
[6.0.1]: https://github.com/aeternity/aesophia/compare/v6.0.0...v6.0.1
|
||||
|
||||
@@ -134,6 +134,155 @@ main contract IntHolderFactory =
|
||||
In case of a presence of child contracts (`IntHolder` in this case), the main
|
||||
contract must be pointed out with the `main` keyword as shown in the example.
|
||||
|
||||
### Contract interfaces and polymorphism
|
||||
|
||||
Contracts can implement one or multiple interfaces, the contract has to define
|
||||
every entrypoint from the implemented interface and the entrypoints in both
|
||||
the contract and implemented interface should have compatible types.
|
||||
|
||||
```
|
||||
contract interface Animal =
|
||||
entrypoint sound : () => string
|
||||
|
||||
contract Cat : Animal =
|
||||
entrypoint sound() = "Cat sound"
|
||||
```
|
||||
|
||||
Contract interfaces can extend other interfaces. An extended interface has to
|
||||
declare all entrypoints from every parent interface. All the declarations in the extended
|
||||
interface must have types compatible with the declarations from the parent
|
||||
interface.
|
||||
|
||||
```
|
||||
contract interface II =
|
||||
entrypoint f : () => unit
|
||||
|
||||
contract interface I : II =
|
||||
entrypoint f : () => unit
|
||||
entrypoint g : () => unit
|
||||
|
||||
contract C : I =
|
||||
entrypoint f() = ()
|
||||
entrypoint g() = ()
|
||||
```
|
||||
|
||||
It is only possible to implement (or extend) an interface that has been already
|
||||
defined earlier in the file (or in an included file). Therefore recursive
|
||||
interface implementation is not allowed in Sophia.
|
||||
|
||||
```
|
||||
// The following code would show an error
|
||||
|
||||
contract interface X : Z =
|
||||
entrypoint x : () => int
|
||||
|
||||
contract interface Y : X =
|
||||
entrypoint x : () => int
|
||||
entrypoint y : () => int
|
||||
|
||||
contract interface Z : Y =
|
||||
entrypoint x : () => int
|
||||
entrypoint y : () => int
|
||||
entrypoint z : () => int
|
||||
|
||||
contract C : Z =
|
||||
entrypoint x() = 1
|
||||
entrypoint y() = 1
|
||||
entrypoint z() = 1
|
||||
```
|
||||
|
||||
#### Subtyping and variance
|
||||
|
||||
Subtyping in Sophia follows common rules that take type variance into account. As described by [Wikipedia](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)),
|
||||
|
||||
>Variance refers to how subtyping between more complex types relates to subtyping between their components.
|
||||
|
||||
This concept plays an important role in complex types such as tuples, `datatype`s and functions. Depending on the context, it can apply to positions in the structure of a type, or type parameters of generic types. There are four kinds of variances:
|
||||
|
||||
- covariant
|
||||
- contravariant
|
||||
- invariant
|
||||
- bivariant
|
||||
|
||||
A type is said to be on a "covariant" position when it describes output or a result of some computation. Analogously, position is "contravariant" when it is an input, or a parameter. Intuitively, when a part of the type is produced by values of it, it is covariant. When it is consumed, it is contravariant. When a type appears to be simultaneously input and output, it is described as invariant. If a type is neither of those (that is, it's unused) it's bivariant. Furthermore, whenever a complex type appears on a contravariant position, all its covariant components become contravariant and vice versa.
|
||||
|
||||
Variance influences how subtyping is applied. Types on covariant positions are subtyped normally, while contravariant the opposite way. Invariant types have to be exactly the same in order for subtyping to work. Bivariant types are always compatible.
|
||||
|
||||
A good example of where it matters can be pictured by subtyping of function types. Let us assume there is a contract interface `Animal` and two contracts that implement it: `Dog` and `Cat`.
|
||||
|
||||
```sophia
|
||||
contract interface Animal =
|
||||
entrypoint age : () => int
|
||||
|
||||
contract Dog : Animal =
|
||||
entrypoint age() = // ...
|
||||
entrypoint woof() = "woof"
|
||||
|
||||
contract Cat : Animal =
|
||||
entrypoint age() = // ...
|
||||
entrypoint meow() = "meow"
|
||||
```
|
||||
|
||||
The assumption of this exercise is that cats do not bark (because `Cat` does not define the `woof` entrypoint). If subtyping rules were applied naively, that is if we let `Dog => Dog` be a subtype of `Animal => Animal`, the following code would break:
|
||||
|
||||
```sophia
|
||||
let f : (Dog) => string = d => d.woof()
|
||||
let g : (Animal) => string = f
|
||||
let c : Cat = Chain.create()
|
||||
g(c) // Cat barking!
|
||||
```
|
||||
|
||||
That is because arguments of functions are contravariant, as opposed to return the type which is covariant. Because of that, the assignment of `f` to `g` is invalid - while `Dog` is a subtype of `Animal`, `Dog => string` is **not** a subtype of `Animal => string`. However, `Animal => string` **is** a subtype of `Dog => string`. More than that, `(Dog => Animal) => Dog` is a subtype of `(Animal => Dog) => Animal`.
|
||||
|
||||
This has consequences on how user-defined generic types work. A type variable gains its variance from its role in the type definition as shown in the example:
|
||||
|
||||
```sophia
|
||||
datatype co('a) = Co('a) // co is covariant on 'a
|
||||
datatype ct('a) = Ct('a => unit) // ct is contravariant on 'a
|
||||
datatype in('a) = In('a => 'a) // in is invariant on 'a
|
||||
datatype bi('a) = Bi // bi is bivariant on 'a
|
||||
```
|
||||
|
||||
The following facts apply here:
|
||||
|
||||
- `co('a)` is a subtype of `co('b) when `'a` is a subtype of `'b`
|
||||
- `ct('a)` is a subtype of `ct('b) when `'b` is a subtype of `'a`
|
||||
- `in('a)` is a subtype of `in('b) when `'a` is equal to `'b`
|
||||
- `bi('a)` is a subtype of `bi('b) always
|
||||
|
||||
That altogether induce the following rules of subtyping in Sophia:
|
||||
|
||||
- A function type `(Args1) => Ret1` is a subtype of `(Args2) => Ret2` when `Ret1`
|
||||
is a subtype of `Ret2` and each argument type from `Args2` is a subtype of its
|
||||
counterpart in `Args1`.
|
||||
|
||||
- A list type `list(A)` is a subtype of `list(B)` if `A` is a subtype of `B`.
|
||||
|
||||
- An option type `option(A)` is a subtype of `option(B)` if `A` is a subtype of `B`.
|
||||
|
||||
- A map type `map(A1, A2)` is a subtype of `map(B1, B2)` if `A1` is a subtype
|
||||
of `B1`, and `A2` is a subtype of `B2`.
|
||||
|
||||
- An oracle type `oracle(A1, A2)` is a subtype of `oracle(B1, B2)` if `B1` is
|
||||
a subtype of `A1`, and `A2` is a subtype of `B2`.
|
||||
|
||||
- An oracle_query type `oracle_query(A1, A2)` is a subtype of `oracle_query(B1, B2)`
|
||||
if `A1` is a subtype of `B1`, and `A2` is a subtype of `B2`.
|
||||
|
||||
- A user-defined datatype `t(Args1)` is a subtype of `t(Args2)`
|
||||
|
||||
- When a user-defined type `t('a)` is covariant in `'a`, then `t(A)` is a
|
||||
subtype of `t(B)` when `A` is a subtype of `B`.
|
||||
|
||||
- When a user-defined type `t('a)` is contravariant in `'a`, then `t(A)` is a
|
||||
subtype of `t(B)` when `B` is a subtype of `A`.
|
||||
|
||||
- When a user-defined type `t('a)` is binvariant in `'a`, then `t(A)` is a
|
||||
subtype of `t(B)` when either `A` is a subtype of `B` or when `B` is a subtype
|
||||
of `A`.
|
||||
|
||||
- When a user-defined type `t('a)` is invariant in `'a`, then `t(A)` can never be
|
||||
a subtype of `t(B)`.
|
||||
|
||||
## Mutable state
|
||||
|
||||
@@ -862,6 +1011,16 @@ function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
```
|
||||
|
||||
Aside from that, there is an almost equivalent function `exit`
|
||||
|
||||
```sophia
|
||||
exit(reason : string) : 'a
|
||||
```
|
||||
|
||||
Just like `abort`, it breaks the execution with the given reason. The difference
|
||||
however is in the gas consumption — while `abort` returns unused gas, a call to
|
||||
`exit` burns it all.
|
||||
|
||||
## Delegation signature
|
||||
|
||||
Some chain operations (`Oracle.<operation>` and `AENS.<operation>`) have an
|
||||
|
||||
+3
-4
@@ -2,11 +2,10 @@
|
||||
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"0699f35"}}}
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.1.1"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
, {eblake2, "1.0.0"}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||
{tag, "2.8.0"}}}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
|
||||
]}.
|
||||
|
||||
{dialyzer, [
|
||||
@@ -15,7 +14,7 @@
|
||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||
]}.
|
||||
|
||||
{relx, [{release, {aesophia, "6.1.0"},
|
||||
{relx, [{release, {aesophia, "7.0.0"},
|
||||
[aesophia, aebytecode, getopt]},
|
||||
|
||||
{dev_mode, true},
|
||||
|
||||
+8
-5
@@ -1,11 +1,11 @@
|
||||
{"1.1.0",
|
||||
{"1.2.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"0699f35b0398bac6cd4468da654d608375bd853d"}},
|
||||
{ref,"8269dbd71e9011921c60141636f1baa270a0e784"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
{ref,"47aaa8f5434b365c50a35bfd1490340b19241991"}},
|
||||
{ref,"eb68fe331bd476910394966b7f5ede7a74d37e35"}},
|
||||
1},
|
||||
{<<"base58">>,
|
||||
{git,"https://github.com/aeternity/erl-base58.git",
|
||||
@@ -14,7 +14,7 @@
|
||||
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
||||
{<<"enacl">>,
|
||||
{git,"https://github.com/aeternity/enacl.git",
|
||||
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
|
||||
{ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}},
|
||||
2},
|
||||
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
|
||||
{<<"jsx">>,
|
||||
@@ -24,5 +24,8 @@
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>},
|
||||
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]}
|
||||
].
|
||||
|
||||
+171
-12
@@ -14,8 +14,13 @@
|
||||
|
||||
-export([ infer/1
|
||||
, infer/2
|
||||
, unfold_types_in_type/2
|
||||
, unfold_types_in_type/3
|
||||
, switch_scope/2
|
||||
, pp_type/2
|
||||
, lookup_env1/4
|
||||
, name/1
|
||||
, qname/1
|
||||
]).
|
||||
|
||||
-include("aeso_utils.hrl").
|
||||
@@ -87,7 +92,11 @@
|
||||
-type byte_constraint() :: {is_bytes, utype()}
|
||||
| {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}.
|
||||
|
||||
-type constraint() :: named_argument_constraint() | field_constraint() | byte_constraint().
|
||||
-type aens_resolve_constraint() :: {aens_resolve_type, utype()}.
|
||||
-type oracle_type_constraint() :: {oracle_type, aeso_syntax:ann(), utype()}.
|
||||
|
||||
-type constraint() :: named_argument_constraint() | field_constraint() | byte_constraint()
|
||||
| aens_resolve_constraint() | oracle_type_constraint().
|
||||
|
||||
-record(field_info,
|
||||
{ ann :: aeso_syntax:ann()
|
||||
@@ -155,6 +164,10 @@
|
||||
|
||||
%% -- Environment manipulation -----------------------------------------------
|
||||
|
||||
-spec switch_scope(qname(), env()) -> env().
|
||||
switch_scope(Scope, Env) ->
|
||||
Env#env{namespace = Scope}.
|
||||
|
||||
-spec push_scope(namespace | contract, aeso_syntax:con(), env()) -> env().
|
||||
push_scope(Kind, Con, Env) ->
|
||||
Ann = aeso_syntax:get_ann(Con),
|
||||
@@ -399,7 +412,7 @@ lookup_env(Env, Kind, Ann, Name) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info()}.
|
||||
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info() | type_info()}.
|
||||
lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
Qual = lists:droplast(QName),
|
||||
Name = lists:last(QName),
|
||||
@@ -539,8 +552,9 @@ global_env() ->
|
||||
%% TTL constructors
|
||||
{"RelativeTTL", Fun1(Int, TTL)},
|
||||
{"FixedTTL", Fun1(Int, TTL)},
|
||||
%% Abort
|
||||
%% Abort/exit
|
||||
{"abort", Fun1(String, A)},
|
||||
{"exit", Fun1(String, A)},
|
||||
{"require", Fun([Bool, String], Unit)}])
|
||||
, types = MkDefs(
|
||||
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
|
||||
@@ -1013,6 +1027,9 @@ infer_contract(Env0, What, Defs0, Options) ->
|
||||
contract -> bind_state(Env1) %% bind state and put
|
||||
end,
|
||||
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype, Defs) ]),
|
||||
[ type_error({missing_definition, Id}) || {fun_decl, _, Id, _} <- Decls,
|
||||
What =:= contract,
|
||||
get_option(no_code, false) =:= false ],
|
||||
Env3 = bind_funs(ProtoSigs, Env2),
|
||||
Functions = Get(function, Defs),
|
||||
%% Check for duplicates in Functions (we turn it into a map below)
|
||||
@@ -1027,8 +1044,10 @@ infer_contract(Env0, What, Defs0, Options) ->
|
||||
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
||||
%% Remove namespaces used in the current namespace
|
||||
Env5 = Env4#env{ used_namespaces = OldUsedNamespaces },
|
||||
%% Check that `init` doesn't read or write the state
|
||||
check_state_dependencies(Env4, Defs1),
|
||||
%% Check that `init` doesn't read or write the state and that `init` is not missing
|
||||
check_state(Env4, Defs1),
|
||||
%% Check that entrypoints have first-order arg types and return types
|
||||
check_entrypoints(Defs1),
|
||||
destroy_and_report_type_errors(Env4),
|
||||
%% Add inferred types of definitions
|
||||
{Env5, TypeDefs ++ Decls ++ Defs1}.
|
||||
@@ -1095,6 +1114,7 @@ check_typedef_sccs(Env, TypeMap, [{acyclic, Name} | SCCs], Acc) ->
|
||||
case maps:get(Name, TypeMap, undefined) of
|
||||
undefined -> check_typedef_sccs(Env, TypeMap, SCCs, Acc); %% Builtin type
|
||||
{type_def, Ann, D, Xs, Def0} ->
|
||||
check_parameterizable(D, Xs),
|
||||
Def = check_event(Env, Name, Ann, check_typedef(bind_tvars(Xs, Env), Def0)),
|
||||
Acc1 = [{type_def, Ann, D, Xs, Def} | Acc],
|
||||
Env1 = bind_type(Name, Ann, {Xs, Def}, Env),
|
||||
@@ -1350,6 +1370,13 @@ check_fields(Env, TypeMap, RecTy, [{field_t, Ann, Id, Type} | Fields]) ->
|
||||
Env1 = bind_field(name(Id), #field_info{ ann = Ann, kind = record, field_t = Type, record_t = RecTy }, Env),
|
||||
check_fields(Env1, TypeMap, RecTy, Fields).
|
||||
|
||||
check_parameterizable({id, Ann, "event"}, [_ | _]) ->
|
||||
type_error({parameterized_event, Ann});
|
||||
check_parameterizable({id, Ann, "state"}, [_ | _]) ->
|
||||
type_error({parameterized_state, Ann});
|
||||
check_parameterizable(_Name, _Xs) ->
|
||||
ok.
|
||||
|
||||
check_event(Env, "event", Ann, Def) ->
|
||||
case Def of
|
||||
{variant_t, Cons} ->
|
||||
@@ -1568,13 +1595,13 @@ app_t(Ann, Name, Args) -> {app_t, Ann, Name, Args}.
|
||||
lookup_name(Env, As, Name) ->
|
||||
lookup_name(Env, As, Name, []).
|
||||
|
||||
lookup_name(Env = #env{ namespace = NS, current_function = {id, _, Fun} = CurFn }, As, Id, Options) ->
|
||||
lookup_name(Env = #env{ namespace = NS, current_function = CurFn }, As, Id, Options) ->
|
||||
case lookup_env(Env, term, As, qname(Id)) of
|
||||
false ->
|
||||
type_error({unbound_variable, Id}),
|
||||
{Id, fresh_uvar(As)};
|
||||
{QId, {_, Ty}} ->
|
||||
when_warning(warn_unused_variables, fun() -> used_variable(NS, Fun, QId) end),
|
||||
when_warning(warn_unused_variables, fun() -> used_variable(NS, name(CurFn), QId) end),
|
||||
when_warning(warn_unused_functions,
|
||||
fun() -> register_function_call(NS ++ qname(CurFn), QId) end),
|
||||
Freshen = proplists:get_value(freshen, Options, false),
|
||||
@@ -1612,8 +1639,52 @@ check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id,
|
||||
end;
|
||||
check_stateful_named_arg(_, _, _) -> ok.
|
||||
|
||||
%% Check that `init` doesn't read or write the state
|
||||
check_state_dependencies(Env, Defs) ->
|
||||
check_entrypoints(Defs) ->
|
||||
[ ensure_first_order_entrypoint(LetFun)
|
||||
|| LetFun <- Defs,
|
||||
aeso_syntax:get_ann(entrypoint, LetFun, false),
|
||||
get_option(allow_higher_order_entrypoints, false) =:= false ].
|
||||
|
||||
ensure_first_order_entrypoint({letfun, Ann, Id = {id, _, Name}, Args, Ret, _}) ->
|
||||
[ ensure_first_order(ArgType, {higher_order_entrypoint, AnnArg, Id, {argument, ArgId, ArgType}})
|
||||
|| {typed, AnnArg, ArgId, ArgType} <- Args ],
|
||||
[ ensure_first_order(Ret, {higher_order_entrypoint, Ann, Id, {result, Ret}})
|
||||
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
|
||||
%% rather than being returned.
|
||||
ok.
|
||||
|
||||
ensure_first_order(Type, Err) ->
|
||||
is_first_order(Type) orelse type_error(Err).
|
||||
|
||||
is_first_order({fun_t, _, _, _, _}) -> false;
|
||||
is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
|
||||
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
|
||||
is_first_order(_) -> true.
|
||||
|
||||
ensure_monomorphic(Type, Err) ->
|
||||
is_monomorphic(Type) orelse type_error(Err).
|
||||
|
||||
is_monomorphic({tvar, _, _}) -> false;
|
||||
is_monomorphic(Ts) when is_list(Ts) -> lists:all(fun is_monomorphic/1, Ts);
|
||||
is_monomorphic(Tup) when is_tuple(Tup) -> is_monomorphic(tuple_to_list(Tup));
|
||||
is_monomorphic(_) -> true.
|
||||
|
||||
check_state_init(Env) ->
|
||||
Top = Env#env.namespace,
|
||||
StateType = lookup_type(Env, {id, [{origin, system}], "state"}),
|
||||
case unfold_types_in_type(Env, StateType) of
|
||||
false ->
|
||||
ok;
|
||||
{_, {_, {_, {alias_t, {tuple_t, _, []}}}}} -> %% type state = ()
|
||||
ok;
|
||||
_ ->
|
||||
#scope{ ann = AnnCon } = get_scope(Env, Top),
|
||||
type_error({missing_init_function, {con, AnnCon, lists:last(Top)}})
|
||||
end.
|
||||
|
||||
%% Check that `init` doesn't read or write the state and that `init` is defined
|
||||
%% when the state type is not unit
|
||||
check_state(Env, Defs) ->
|
||||
Top = Env#env.namespace,
|
||||
GetState = Top ++ ["state"],
|
||||
SetState = Top ++ ["put"],
|
||||
@@ -1622,7 +1693,7 @@ check_state_dependencies(Env, Defs) ->
|
||||
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _GuardedBodies} <- Defs ],
|
||||
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
|
||||
case maps:get(Init, Deps, false) of
|
||||
false -> ok; %% No init, so nothing to check
|
||||
false -> get_option(no_code, false) orelse check_state_init(Env);
|
||||
_ ->
|
||||
[ type_error({init_depends_on_state, state, Chain})
|
||||
|| Chain <- get_call_chains(Deps, Init, GetState) ],
|
||||
@@ -1764,12 +1835,17 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
||||
NewFun0 = infer_expr(Env, Fun),
|
||||
NewArgs = [infer_expr(Env, A) || A <- Args],
|
||||
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
|
||||
NewFun1 = {typed, _, _, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
|
||||
NewFun1 = {typed, _, FunName, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
|
||||
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
|
||||
GeneralResultType = fresh_uvar(Ann),
|
||||
ResultType = fresh_uvar(Ann),
|
||||
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
|
||||
when_warning(warn_negative_spend, fun() -> warn_potential_negative_spend(Ann, NewFun1, NewArgs) end),
|
||||
[ add_constraint({aens_resolve_type, GeneralResultType})
|
||||
|| element(3, FunName) =:= ["AENS", "resolve"] ],
|
||||
[ add_constraint({oracle_type, Ann, OType})
|
||||
|| OType <- [get_oracle_type(FunName, ArgTypes, GeneralResultType)],
|
||||
OType =/= false ],
|
||||
add_constraint(
|
||||
#dependent_type_constraint{ named_args_t = NamedArgsVar,
|
||||
named_args = NamedArgs1,
|
||||
@@ -2290,11 +2366,19 @@ destroy_and_report_unsolved_constraints(Env) ->
|
||||
(#named_argument_constraint{}) -> true;
|
||||
(_) -> false
|
||||
end, OtherCs2),
|
||||
{BytesCs, []} =
|
||||
{BytesCs, OtherCs4} =
|
||||
lists:partition(fun({is_bytes, _}) -> true;
|
||||
({add_bytes, _, _, _, _, _}) -> true;
|
||||
(_) -> false
|
||||
end, OtherCs3),
|
||||
{AensResolveCs, OtherCs5} =
|
||||
lists:partition(fun({aens_resolve_type, _}) -> true;
|
||||
(_) -> false
|
||||
end, OtherCs4),
|
||||
{OracleTypeCs, []} =
|
||||
lists:partition(fun({oracle_type, _, _}) -> true;
|
||||
(_) -> false
|
||||
end, OtherCs5),
|
||||
|
||||
Unsolved = [ S || S <- [ solve_constraint(Env, dereference_deep(C)) || C <- NamedArgCs ],
|
||||
S == unsolved ],
|
||||
@@ -2312,9 +2396,20 @@ destroy_and_report_unsolved_constraints(Env) ->
|
||||
check_record_create_constraints(Env, CreateCs),
|
||||
check_is_contract_constraints(Env, ContractCs),
|
||||
check_bytes_constraints(Env, BytesCs),
|
||||
check_aens_resolve_constraints(Env, AensResolveCs),
|
||||
check_oracle_type_constraints(Env, OracleTypeCs),
|
||||
|
||||
destroy_constraints().
|
||||
|
||||
get_oracle_type({qid, _, ["Oracle", "register"]}, _ , OType) -> OType;
|
||||
get_oracle_type({qid, _, ["Oracle", "query"]}, [OType| _], _ ) -> OType;
|
||||
get_oracle_type({qid, _, ["Oracle", "get_question"]}, [OType| _], _ ) -> OType;
|
||||
get_oracle_type({qid, _, ["Oracle", "get_answer"]}, [OType| _], _ ) -> OType;
|
||||
get_oracle_type({qid, _, ["Oracle", "check"]}, [OType| _], _ ) -> OType;
|
||||
get_oracle_type({qid, _, ["Oracle", "check_query"]}, [OType| _], _ ) -> OType;
|
||||
get_oracle_type({qid, _, ["Oracle", "respond"]}, [OType| _], _ ) -> OType;
|
||||
get_oracle_type(_Fun, _Args, _Ret) -> false.
|
||||
|
||||
%% -- Named argument constraints --
|
||||
|
||||
%% If false, a type error has been emitted, so it's safe to drop the constraint.
|
||||
@@ -2441,6 +2536,32 @@ check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) ->
|
||||
_ -> type_error({unsolved_bytes_constraint, Ann, Fun, A, B, C})
|
||||
end.
|
||||
|
||||
check_aens_resolve_constraints(_Env, []) ->
|
||||
ok;
|
||||
check_aens_resolve_constraints(Env, [{aens_resolve_type, Type} | Rest]) ->
|
||||
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||
{app_t, _, {id, _, "option"}, [Type2]} = Type1,
|
||||
case Type2 of
|
||||
{id, _, "string"} -> ok;
|
||||
{id, _, "address"} -> ok;
|
||||
{con, _, _} -> ok;
|
||||
{app_t, _, {id, _, "oracle"}, [_, _]} -> ok;
|
||||
{app_t, _, {id, _, "oracle_query"}, [_, _]} -> ok;
|
||||
_ -> type_error({invalid_aens_resolve_type, aeso_syntax:get_ann(Type), Type2})
|
||||
end,
|
||||
check_aens_resolve_constraints(Env, Rest).
|
||||
|
||||
check_oracle_type_constraints(_Env, []) ->
|
||||
ok;
|
||||
check_oracle_type_constraints(Env, [{oracle_type, Ann, OType} | Rest]) ->
|
||||
Type = unfold_types_in_type(Env, instantiate(OType)),
|
||||
{app_t, _, {id, _, "oracle"}, [QType, RType]} = Type,
|
||||
ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
|
||||
ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
|
||||
ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
|
||||
ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
|
||||
check_oracle_type_constraints(Env, Rest).
|
||||
|
||||
%% -- Field constraints --
|
||||
|
||||
check_record_create_constraints(_, []) -> ok;
|
||||
@@ -3484,6 +3605,44 @@ mk_error({unimplemented_interface_function, ConId, InterfaceName, FunName}) ->
|
||||
mk_error({referencing_undefined_interface, InterfaceId}) ->
|
||||
Msg = io_lib:format("Trying to implement or extend an undefined interface `~s`", [pp(InterfaceId)]),
|
||||
mk_t_err(pos(InterfaceId), Msg);
|
||||
mk_error({missing_definition, Id}) ->
|
||||
Msg = io_lib:format("Missing definition of function `~s`", [name(Id)]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({parameterized_state, Ann}) ->
|
||||
Msg = "The state type cannot be parameterized",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({parameterized_event, Ann}) ->
|
||||
Msg = "The event type cannot be parameterized",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({missing_init_function, Con}) ->
|
||||
Msg = io_lib:format("Missing `init` function for the contract `~s`.", [name(Con)]),
|
||||
Cxt = "The `init` function can only be omitted if the state type is `unit`",
|
||||
mk_t_err(pos(Con), Msg, Cxt);
|
||||
mk_error({higher_order_entrypoint, Ann, {id, _, Name}, Thing}) ->
|
||||
What = "higher-order (contains function types)",
|
||||
ThingS = case Thing of
|
||||
{argument, X, T} -> io_lib:format("argument\n~s`\n", [pp_typed(" `", X, T)]);
|
||||
{result, T} -> io_lib:format("return type\n~s`\n", [pp_type(" `", T)])
|
||||
end,
|
||||
Bad = case Thing of
|
||||
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
|
||||
{result, _} -> io_lib:format("is ~s", [What])
|
||||
end,
|
||||
Msg = io_lib:format("The ~sof entrypoint `~s` ~s",
|
||||
[ThingS, Name, Bad]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({invalid_aens_resolve_type, Ann, T}) ->
|
||||
Msg = io_lib:format("Invalid return type of `AENS.resolve`:\n"
|
||||
"~s`\n"
|
||||
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)",
|
||||
[pp_type(" `", T)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({invalid_oracle_type, Why, What, Ann, Type}) ->
|
||||
WhyS = case Why of higher_order -> "higher-order (contain function types)";
|
||||
polymorphic -> "polymorphic (contain type variables)" end,
|
||||
Msg = io_lib:format("Invalid oracle type\n~s`", [pp_type(" `", Type)]),
|
||||
Cxt = io_lib:format("The ~s type must not be ~s", [What, WhyS]),
|
||||
mk_t_err(pos(Ann), Msg, Cxt);
|
||||
mk_error(Err) ->
|
||||
Msg = io_lib:format("Unknown error: ~p", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg).
|
||||
|
||||
+13
-82
@@ -242,7 +242,7 @@ builtins() ->
|
||||
MkName = fun(NS, Fun) ->
|
||||
list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_")))
|
||||
end,
|
||||
Scopes = [{[], [{"abort", 1}, {"require", 2}]},
|
||||
Scopes = [{[], [{"abort", 1}, {"require", 2}, {"exit", 1}]},
|
||||
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
|
||||
{"timestamp", none}, {"block_height", none}, {"difficulty", none},
|
||||
{"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]},
|
||||
@@ -326,7 +326,7 @@ get_option(Opt, Env, Default) ->
|
||||
%% -- Compilation ------------------------------------------------------------
|
||||
|
||||
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
|
||||
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
|
||||
to_fcode(Env, [{Contract, Attrs, {con, _, Name}, _Impls, Decls}|Rest])
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
case Contract =:= contract_interface of
|
||||
false ->
|
||||
@@ -349,7 +349,7 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
|
||||
event_type => EventType,
|
||||
payable => Payable,
|
||||
functions => add_init_function(
|
||||
Env1, Con, StateType,
|
||||
Env1,
|
||||
add_event_function(Env1, EventType, Funs)) },
|
||||
case Contract of
|
||||
contract_main -> [] = Rest, {Env1, ConFcode};
|
||||
@@ -362,8 +362,6 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
|
||||
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Name} }, Decls),
|
||||
to_fcode(Env1, Rest)
|
||||
end;
|
||||
to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= contract_def ->
|
||||
fcode_error({last_declaration_must_be_main_contract, NotMain});
|
||||
to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
|
||||
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
|
||||
to_fcode(Env1, Code).
|
||||
@@ -377,22 +375,15 @@ decls_to_fcode(Env, Decls) ->
|
||||
end, Env1, Decls).
|
||||
|
||||
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
|
||||
decl_to_fcode(Env = #{context := {contract_def, _}}, {fun_decl, _, Id, _}) ->
|
||||
case is_no_code(Env) of
|
||||
false -> fcode_error({missing_definition, Id});
|
||||
true -> Env
|
||||
end;
|
||||
decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env;
|
||||
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
|
||||
typedef_to_fcode(Env, Name, Args, Def);
|
||||
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
|
||||
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
|
||||
Attrs = get_attributes(Ann),
|
||||
FName = lookup_fun(Env, qname(Env, Name)),
|
||||
FArgs = args_to_fcode(Env, Args),
|
||||
FRet = type_to_fcode(Env, Ret),
|
||||
FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body),
|
||||
[ ensure_first_order_entrypoint(Ann, Id, Args, Ret, FArgs, FRet)
|
||||
|| aeso_syntax:get_ann(entrypoint, Ann, false) ],
|
||||
Def = #{ attrs => Attrs,
|
||||
args => FArgs,
|
||||
return => FRet,
|
||||
@@ -401,8 +392,7 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Ar
|
||||
Env#{ functions := NewFuns }.
|
||||
|
||||
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
|
||||
typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
|
||||
check_state_and_event_types(Env, Id, Xs),
|
||||
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
|
||||
Q = qname(Env, Name),
|
||||
FDef = fun(Args) when length(Args) == length(Xs) ->
|
||||
Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)),
|
||||
@@ -466,14 +456,6 @@ compute_state_layout(R, [H | T]) ->
|
||||
compute_state_layout(R, _) ->
|
||||
{R + 1, {reg, R}}.
|
||||
|
||||
check_state_and_event_types(#{ context := {contract_def, _} }, Id, [_ | _]) ->
|
||||
case Id of
|
||||
{id, _, "state"} -> fcode_error({parameterized_state, Id});
|
||||
{id, _, "event"} -> fcode_error({parameterized_event, Id});
|
||||
_ -> ok
|
||||
end;
|
||||
check_state_and_event_types(_, _, _) -> ok.
|
||||
|
||||
-spec type_to_fcode(env(), aeso_syntax:type()) -> ftype().
|
||||
type_to_fcode(Env, Type) ->
|
||||
type_to_fcode(Env, #{}, Type).
|
||||
@@ -491,8 +473,6 @@ type_to_fcode(Env, Sub, {record_t, Fields}) ->
|
||||
type_to_fcode(Env, Sub, {tuple_t, [], lists:map(FieldType, Fields)});
|
||||
type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
|
||||
{bytes, N};
|
||||
type_to_fcode(_Env, _Sub, {tvar, Ann, "void"}) ->
|
||||
fcode_error({found_void, Ann});
|
||||
type_to_fcode(_Env, Sub, {tvar, _, X}) ->
|
||||
maps:get(X, Sub, {tvar, X});
|
||||
type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) ->
|
||||
@@ -554,7 +534,7 @@ expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}};
|
||||
|
||||
%% Variables
|
||||
expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]);
|
||||
expr_to_fcode(Env, Type, {qid, Ann, X}) ->
|
||||
expr_to_fcode(Env, Type, {qid, _, X}) ->
|
||||
case resolve_var(Env, X) of
|
||||
{builtin_u, B, Ar} when B =:= oracle_query;
|
||||
B =:= oracle_get_question;
|
||||
@@ -565,13 +545,11 @@ expr_to_fcode(Env, Type, {qid, Ann, X}) ->
|
||||
B =:= oracle_check_query ->
|
||||
OType = get_oracle_type(B, Type),
|
||||
{oracle, QType, RType} = type_to_fcode(Env, OType),
|
||||
validate_oracle_type(Ann, OType, QType, RType),
|
||||
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
|
||||
{builtin_u, B, Ar, TypeArgs};
|
||||
{builtin_u, B = aens_resolve, Ar} ->
|
||||
{fun_t, _, _, _, ResType} = Type,
|
||||
AensType = type_to_fcode(Env, ResType),
|
||||
validate_aens_resolve_type(Ann, ResType, AensType),
|
||||
TypeArgs = [{lit, {typerep, AensType}}],
|
||||
{builtin_u, B, Ar, TypeArgs};
|
||||
{builtin_u, B = bytes_split, Ar} ->
|
||||
@@ -824,53 +802,6 @@ get_oracle_type(oracle_check, {fun_t, _, _, [OType | _], _}) -> OType;
|
||||
get_oracle_type(oracle_check_query, {fun_t, _, _, [OType | _], _}) -> OType;
|
||||
get_oracle_type(oracle_respond, {fun_t, _, _, [OType | _], _}) -> OType.
|
||||
|
||||
validate_oracle_type(Ann, Type, QType, RType) ->
|
||||
ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
|
||||
ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
|
||||
ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
|
||||
ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
|
||||
ok.
|
||||
|
||||
validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) ->
|
||||
case FType of
|
||||
string -> ok;
|
||||
address -> ok;
|
||||
contract -> ok;
|
||||
{oracle, _, _} -> ok;
|
||||
oracle_query -> ok;
|
||||
_ -> fcode_error({invalid_aens_resolve_type, Ann, Type})
|
||||
end.
|
||||
|
||||
ensure_first_order_entrypoint(Ann, Id = {id, _, Name}, Args, Ret, FArgs, FRet) ->
|
||||
[ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Id, {argument, X, T}})
|
||||
|| {{typed, Ann1, X, T}, {_, FT}} <- lists:zip(Args, FArgs) ],
|
||||
[ ensure_first_order(FRet, {invalid_entrypoint, higher_order, Ann, Id, {result, Ret}})
|
||||
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
|
||||
%% rather than being returned.
|
||||
ok.
|
||||
|
||||
ensure_monomorphic(Type, Err) ->
|
||||
case is_monomorphic(Type) of
|
||||
true -> ok;
|
||||
false -> fcode_error(Err)
|
||||
end.
|
||||
|
||||
ensure_first_order(Type, Err) ->
|
||||
case is_first_order(Type) of
|
||||
true -> ok;
|
||||
false -> fcode_error(Err)
|
||||
end.
|
||||
|
||||
is_monomorphic({tvar, _}) -> false;
|
||||
is_monomorphic(Ts) when is_list(Ts) -> lists:all(fun is_monomorphic/1, Ts);
|
||||
is_monomorphic(Tup) when is_tuple(Tup) -> is_monomorphic(tuple_to_list(Tup));
|
||||
is_monomorphic(_) -> true.
|
||||
|
||||
is_first_order({function, _, _}) -> false;
|
||||
is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
|
||||
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
|
||||
is_first_order(_) -> true.
|
||||
|
||||
%% -- Pattern matching --
|
||||
|
||||
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()], aeso_syntax:expr()) -> fsplit().
|
||||
@@ -1203,11 +1134,11 @@ builtin_to_fcode(_Layout, Builtin, Args) ->
|
||||
|
||||
%% -- Init function --
|
||||
|
||||
add_init_function(Env, Main, StateType, Funs0) ->
|
||||
add_init_function(Env, Funs0) ->
|
||||
case is_no_code(Env) of
|
||||
true -> Funs0;
|
||||
false ->
|
||||
Funs = add_default_init_function(Env, Main, StateType, Funs0),
|
||||
Funs = add_default_init_function(Env, Funs0),
|
||||
InitName = {entrypoint, <<"init">>},
|
||||
InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
|
||||
Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []},
|
||||
@@ -1215,16 +1146,14 @@ add_init_function(Env, Main, StateType, Funs0) ->
|
||||
Funs1
|
||||
end.
|
||||
|
||||
add_default_init_function(_Env, Main, StateType, Funs) ->
|
||||
add_default_init_function(_Env, Funs) ->
|
||||
InitName = {entrypoint, <<"init">>},
|
||||
case maps:get(InitName, Funs, none) of
|
||||
%% Only add default init function if state is unit.
|
||||
none when StateType == {tuple, []} ->
|
||||
none ->
|
||||
Funs#{ InitName => #{attrs => [],
|
||||
args => [],
|
||||
return => {tuple, []},
|
||||
body => {tuple, []}} };
|
||||
none -> fcode_error({missing_init_function, Main});
|
||||
_ -> Funs
|
||||
end.
|
||||
|
||||
@@ -2090,7 +2019,9 @@ setnth(I, X, Xs) ->
|
||||
-dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}).
|
||||
|
||||
fcode_error(Error) ->
|
||||
aeso_errors:throw(aeso_code_errors:format(Error)).
|
||||
Pos = aeso_errors:pos(0, 0),
|
||||
Msg = lists:flatten(io_lib:format("Unknown error: ~p\n", [Error])),
|
||||
aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg)).
|
||||
|
||||
internal_error(Error) ->
|
||||
Msg = lists:flatten(io_lib:format("~p\n", [Error])),
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Ulf Norell
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Formatting of code generation errors.
|
||||
%%% @end
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_code_errors).
|
||||
|
||||
-export([format/1, pos/1]).
|
||||
|
||||
format({last_declaration_must_be_main_contract, Decl = {Kind, _, {con, _, C}, _}}) ->
|
||||
Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'",
|
||||
[Kind, C]),
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({missing_init_function, Con}) ->
|
||||
Msg = io_lib:format("Missing init function for the contract '~s'.", [pp_expr(Con)]),
|
||||
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.",
|
||||
mk_err(pos(Con), Msg, Cxt);
|
||||
format({missing_definition, Id}) ->
|
||||
Msg = io_lib:format("Missing definition of function '~s'.", [pp_expr(Id)]),
|
||||
mk_err(pos(Id), Msg);
|
||||
format({parameterized_state, Decl}) ->
|
||||
Msg = "The state type cannot be parameterized.",
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({parameterized_event, Decl}) ->
|
||||
Msg = "The event type cannot be parameterized.",
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
|
||||
What = case Why of higher_order -> "higher-order (contains function types)";
|
||||
polymorphic -> "polymorphic (contains type variables)" end,
|
||||
ThingS = case Thing of
|
||||
{argument, X, T} -> io_lib:format("argument\n~s\n", [pp_typed(X, T)]);
|
||||
{result, T} -> io_lib:format("return type\n~s\n", [pp_type(2, T)])
|
||||
end,
|
||||
Bad = case Thing of
|
||||
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
|
||||
{result, _} -> io_lib:format("is ~s", [What])
|
||||
end,
|
||||
Msg = io_lib:format("The ~sof entrypoint '~s' ~s.",
|
||||
[ThingS, Name, Bad]),
|
||||
case Why of
|
||||
higher_order -> mk_err(pos(Ann), Msg)
|
||||
end;
|
||||
format({invalid_aens_resolve_type, Ann, T}) ->
|
||||
Msg = io_lib:format("Invalid return type of AENS.resolve:\n"
|
||||
"~s\n"
|
||||
"It must be a string or a pubkey type (address, oracle, etc).",
|
||||
[pp_type(2, T)]),
|
||||
mk_err(pos(Ann), Msg);
|
||||
format({invalid_oracle_type, Why, What, Ann, Type}) ->
|
||||
WhyS = case Why of higher_order -> "higher-order (contain function types)";
|
||||
polymorphic -> "polymorphic (contain type variables)" end,
|
||||
Msg = io_lib:format("Invalid oracle type\n~s", [pp_type(2, Type)]),
|
||||
Cxt = io_lib:format("The ~s type must not be ~s.", [What, WhyS]),
|
||||
mk_err(pos(Ann), Msg, Cxt);
|
||||
format({var_args_not_set, Expr}) ->
|
||||
mk_err( pos(Expr), "Could not deduce type of variable arguments list"
|
||||
, "When compiling " ++ pp_expr(Expr)
|
||||
);
|
||||
format({found_void, Ann}) ->
|
||||
mk_err(pos(Ann), "Found a void-typed value.", "`void` is a restricted, uninhabited type. Did you mean `unit`?");
|
||||
|
||||
format(Err) ->
|
||||
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
|
||||
|
||||
pos(Ann) ->
|
||||
File = aeso_syntax:get_ann(file, Ann, no_file),
|
||||
Line = aeso_syntax:get_ann(line, Ann, 0),
|
||||
Col = aeso_syntax:get_ann(col, Ann, 0),
|
||||
aeso_errors:pos(File, Line, Col).
|
||||
|
||||
pp_typed(E, T) ->
|
||||
prettypr:format(prettypr:nest(2,
|
||||
lists:foldr(fun prettypr:beside/2, prettypr:empty(),
|
||||
[aeso_pretty:expr(E), prettypr:text(" : "),
|
||||
aeso_pretty:type(T)]))).
|
||||
|
||||
pp_expr(E) ->
|
||||
pp_expr(0, E).
|
||||
|
||||
pp_expr(N, E) ->
|
||||
prettypr:format(prettypr:nest(N, aeso_pretty:expr(E))).
|
||||
|
||||
pp_type(N, T) ->
|
||||
prettypr:format(prettypr:nest(N, aeso_pretty:type(T))).
|
||||
|
||||
mk_err(Pos, Msg) ->
|
||||
aeso_errors:new(code_error, Pos, lists:flatten(Msg)).
|
||||
|
||||
mk_err(Pos, Msg, Cxt) ->
|
||||
aeso_errors:new(code_error, Pos, lists:flatten(Msg), lists:flatten(Cxt)).
|
||||
|
||||
@@ -349,7 +349,7 @@ get_decode_type(FunName, [{Contract, Ann, _, _, Defs}]) when ?IS_CONTRACT_HEAD(C
|
||||
"init" -> {ok, [], {tuple_t, [], []}};
|
||||
_ ->
|
||||
Msg = io_lib:format("Function '~s' is missing in contract", [FunName]),
|
||||
Pos = aeso_code_errors:pos(Ann),
|
||||
Pos = aeso_errors:pos(Ann),
|
||||
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
|
||||
end
|
||||
end;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
, new/2
|
||||
, new/3
|
||||
, new/4
|
||||
, pos/1
|
||||
, pos/2
|
||||
, pos/3
|
||||
, pp/1
|
||||
@@ -53,6 +54,12 @@ new(Type, Pos, Msg) ->
|
||||
new(Type, Pos, Msg, Ctxt) ->
|
||||
#err{ type = Type, pos = Pos, message = Msg, context = Ctxt }.
|
||||
|
||||
pos(Ann) ->
|
||||
File = aeso_syntax:get_ann(file, Ann, no_file),
|
||||
Line = aeso_syntax:get_ann(line, Ann, 0),
|
||||
Col = aeso_syntax:get_ann(col, Ann, 0),
|
||||
pos(File, Line, Col).
|
||||
|
||||
pos(Line, Col) ->
|
||||
#pos{ line = Line, col = Col }.
|
||||
|
||||
|
||||
@@ -64,7 +64,9 @@ debug(Tag, Options, Fun) ->
|
||||
|
||||
-dialyzer({nowarn_function, [code_error/1]}).
|
||||
code_error(Err) ->
|
||||
aeso_errors:throw(aeso_code_errors:format(Err)).
|
||||
Pos = aeso_errors:pos(0, 0),
|
||||
Msg = lists:flatten(io_lib:format("Unknown error: ~p\n", [Err])),
|
||||
aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg)).
|
||||
|
||||
%% -- Main -------------------------------------------------------------------
|
||||
|
||||
@@ -507,6 +509,8 @@ builtin_to_scode(Env, bytes_split, [_, _] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args);
|
||||
builtin_to_scode(Env, abort, [_] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:abort(?a), Args);
|
||||
builtin_to_scode(Env, exit, [_] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:exit(?a), Args);
|
||||
builtin_to_scode(Env, chain_spend, [_, _] = Args) ->
|
||||
call_to_scode(Env, [aeb_fate_ops:spend(?a, ?a),
|
||||
tuple(0)], Args);
|
||||
@@ -1486,6 +1490,7 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
r_write_to_dead_var(_, _) -> false.
|
||||
|
||||
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
|
||||
op_view({'EXIT', R}) -> {'EXIT', none, [R]};
|
||||
op_view(T) when is_tuple(T) ->
|
||||
[Op, R | As] = tuple_to_list(T),
|
||||
CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end,
|
||||
|
||||
+2
-1
@@ -26,7 +26,7 @@
|
||||
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
||||
|
||||
-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
|
||||
| stateful | private | payable | main | interface].
|
||||
| stateful | private | payable | main | interface | entrypoint].
|
||||
|
||||
-type name() :: string().
|
||||
-type id() :: {id, ann(), name()}.
|
||||
@@ -42,6 +42,7 @@
|
||||
| {contract_child, ann(), con(), [con()], [decl()]}
|
||||
| {contract_interface, ann(), con(), [con()], [decl()]}
|
||||
| {namespace, ann(), con(), [decl()]}
|
||||
| {include, ann(), {string, ann(), string()}}
|
||||
| {pragma, ann(), pragma()}
|
||||
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
||||
| {type_def, ann(), id(), [tvar()], typedef()}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{application, aesophia,
|
||||
[{description, "Compiler for Aeternity Sophia language"},
|
||||
{vsn, "6.1.0"},
|
||||
{vsn, "7.0.0"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
|
||||
@@ -127,7 +127,7 @@ check_stub(Stub, Options) ->
|
||||
Ast ->
|
||||
try
|
||||
%% io:format("AST: ~120p\n", [Ast]),
|
||||
aeso_ast_infer_types:infer(Ast, [])
|
||||
aeso_ast_infer_types:infer(Ast, [no_code])
|
||||
catch throw:{type_errors, TE} ->
|
||||
io:format("Type error:\n~s\n", [TE]),
|
||||
error(TE);
|
||||
|
||||
@@ -29,7 +29,7 @@ calldata_aci_test_() ->
|
||||
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
|
||||
fun() ->
|
||||
ContractString = aeso_test_utils:read_contract(ContractName),
|
||||
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
|
||||
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString, [no_code]),
|
||||
ContractACI = binary_to_list(ContractACIBin),
|
||||
io:format("ACI:\n~s\n", [ContractACIBin]),
|
||||
FateExprs = ast_exprs(ContractACI, Fun, Args),
|
||||
|
||||
+111
-78
@@ -45,12 +45,6 @@ simple_compile_test_() ->
|
||||
check_errors(ExpectedErrors, Errors)
|
||||
end} ||
|
||||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
|
||||
[ {"Testing code generation error messages of " ++ ContractName,
|
||||
fun() ->
|
||||
Errors = compile(ContractName),
|
||||
check_errors([ExpectedError], Errors)
|
||||
end} ||
|
||||
{ContractName, ExpectedError} <- failing_code_gen_contracts()] ++
|
||||
[ {"Testing include with explicit files",
|
||||
fun() ->
|
||||
FileSystem = maps:from_list(
|
||||
@@ -208,6 +202,18 @@ compilable_contracts() ->
|
||||
"polymorphism_contract_interface_extensions",
|
||||
"polymorphism_contract_interface_same_decl_multi_interface",
|
||||
"polymorphism_contract_interface_same_name_same_type",
|
||||
"missing_init_fun_state_unit",
|
||||
"complex_compare_leq",
|
||||
"complex_compare",
|
||||
"higher_order_compare",
|
||||
"higher_order_map_keys",
|
||||
"higher_order_state",
|
||||
"polymorphic_compare",
|
||||
"polymorphic_entrypoint",
|
||||
"polymorphic_entrypoint_return",
|
||||
"polymorphic_map_keys",
|
||||
"unapplied_contract_call",
|
||||
"unapplied_named_arg_builtin",
|
||||
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||
].
|
||||
|
||||
@@ -289,34 +295,26 @@ failing_contracts() ->
|
||||
|
||||
%% Type errors
|
||||
, ?TYPE_ERROR(name_clash,
|
||||
[<<?Pos(14, 3)
|
||||
[<<?Pos(4, 3)
|
||||
"Duplicate definitions of `double_def` at\n"
|
||||
" - line 3, column 3\n"
|
||||
" - line 4, column 3">>,
|
||||
<<?Pos(7, 3)
|
||||
"Duplicate definitions of `abort` at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 14, column 3">>,
|
||||
<<?Pos(15, 3)
|
||||
" - line 7, column 3">>,
|
||||
<<?Pos(8, 3)
|
||||
"Duplicate definitions of `require` at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 15, column 3">>,
|
||||
<<?Pos(11, 3)
|
||||
"Duplicate definitions of `double_def` at\n"
|
||||
" - line 10, column 3\n"
|
||||
" - line 11, column 3">>,
|
||||
<<?Pos(5, 3)
|
||||
"Duplicate definitions of `double_proto` at\n"
|
||||
" - line 4, column 3\n"
|
||||
" - line 5, column 3">>,
|
||||
<<?Pos(8, 3)
|
||||
"Duplicate definitions of `proto_and_def` at\n"
|
||||
" - line 7, column 3\n"
|
||||
" - line 8, column 3">>,
|
||||
<<?Pos(16, 3)
|
||||
<<?Pos(9, 3)
|
||||
"Duplicate definitions of `put` at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 16, column 3">>,
|
||||
<<?Pos(17, 3)
|
||||
" - line 9, column 3">>,
|
||||
<<?Pos(10, 3)
|
||||
"Duplicate definitions of `state` at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 17, column 3">>])
|
||||
" - line 10, column 3">>])
|
||||
, ?TYPE_ERROR(type_errors,
|
||||
[<<?Pos(17, 23)
|
||||
"Unbound variable `zz`">>,
|
||||
@@ -981,7 +979,8 @@ failing_contracts() ->
|
||||
"when checking the type of the pattern `r10 : rec_inv(Animal)` against the expected type `Main.rec_inv(Cat)`">>,
|
||||
<<?Pos(41,13)
|
||||
"Cannot unify `Animal` and `Cat` in a invariant context\n"
|
||||
"when checking the type of the pattern `r11 : rec_inv(Cat)` against the expected type `Main.rec_inv(Animal)`">>])
|
||||
"when checking the type of the pattern `r11 : rec_inv(Cat)` against the expected type `Main.rec_inv(Animal)`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_variance_switching_oracles,
|
||||
[<<?Pos(15,13)
|
||||
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
|
||||
@@ -1024,58 +1023,92 @@ failing_contracts() ->
|
||||
"when checking the type of the pattern `q14 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Animal, Cat)`">>,
|
||||
<<?Pos(44,13)
|
||||
"Cannot unify `Animal` and `Cat` in a covariant context\n"
|
||||
"when checking the type of the pattern `q15 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Cat, Animal)`">>])
|
||||
].
|
||||
|
||||
-define(Path(File), "code_errors/" ??File).
|
||||
-define(Msg(File, Line, Col, Err), <<?Pos("Code generation", ?Path(File), Line, Col) Err>>).
|
||||
|
||||
-define(FATE_ERR(File, Line, Col, Err), {?Path(File), ?Msg(File, Line, Col, Err)}).
|
||||
|
||||
failing_code_gen_contracts() ->
|
||||
[ ?FATE_ERR(missing_definition, 2, 14,
|
||||
"Missing definition of function 'foo'.")
|
||||
, ?FATE_ERR(higher_order_entrypoint, 2, 20,
|
||||
"The argument\n"
|
||||
" f : (int) => int\n"
|
||||
"of entrypoint 'apply' has a higher-order (contains function types) type.")
|
||||
, ?FATE_ERR(higher_order_entrypoint_return, 2, 3,
|
||||
"The return type\n"
|
||||
" (int) => int\n"
|
||||
"of entrypoint 'add' is higher-order (contains function types).")
|
||||
, ?FATE_ERR(missing_init_function, 1, 10,
|
||||
"Missing init function for the contract 'MissingInitFunction'.\n"
|
||||
"The 'init' function can only be omitted if the state type is 'unit'.")
|
||||
, ?FATE_ERR(parameterised_state, 3, 8,
|
||||
"The state type cannot be parameterized.")
|
||||
, ?FATE_ERR(parameterised_event, 3, 12,
|
||||
"The event type cannot be parameterized.")
|
||||
, ?FATE_ERR(polymorphic_aens_resolve, 4, 5,
|
||||
"Invalid return type of AENS.resolve:\n"
|
||||
" 'a\n"
|
||||
"It must be a string or a pubkey type (address, oracle, etc).")
|
||||
, ?FATE_ERR(bad_aens_resolve, 6, 5,
|
||||
"Invalid return type of AENS.resolve:\n"
|
||||
" list(int)\n"
|
||||
"It must be a string or a pubkey type (address, oracle, etc).")
|
||||
, ?FATE_ERR(polymorphic_query_type, 3, 5,
|
||||
"Invalid oracle type\n"
|
||||
" oracle('a, 'b)\n"
|
||||
"The query type must not be polymorphic (contain type variables).")
|
||||
, ?FATE_ERR(polymorphic_response_type, 3, 5,
|
||||
"Invalid oracle type\n"
|
||||
" oracle(string, 'r)\n"
|
||||
"The response type must not be polymorphic (contain type variables).")
|
||||
, ?FATE_ERR(higher_order_query_type, 3, 5,
|
||||
"Invalid oracle type\n"
|
||||
" oracle((int) => int, string)\n"
|
||||
"The query type must not be higher-order (contain function types).")
|
||||
, ?FATE_ERR(higher_order_response_type, 3, 5,
|
||||
"Invalid oracle type\n"
|
||||
" oracle(string, (int) => int)\n"
|
||||
"The response type must not be higher-order (contain function types).")
|
||||
, ?FATE_ERR(child_with_decls, 2, 14,
|
||||
"Missing definition of function 'f'.")
|
||||
"when checking the type of the pattern `q15 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Cat, Animal)`">>
|
||||
])
|
||||
, ?TYPE_ERROR(missing_definition,
|
||||
[<<?Pos(2,14)
|
||||
"Missing definition of function `foo`">>
|
||||
])
|
||||
, ?TYPE_ERROR(child_with_decls,
|
||||
[<<?Pos(2,14)
|
||||
"Missing definition of function `f`">>
|
||||
])
|
||||
, ?TYPE_ERROR(parameterised_state,
|
||||
[<<?Pos(3,8)
|
||||
"The state type cannot be parameterized">>
|
||||
])
|
||||
, ?TYPE_ERROR(parameterised_event,
|
||||
[<<?Pos(3,12)
|
||||
"The event type cannot be parameterized">>
|
||||
])
|
||||
, ?TYPE_ERROR(missing_init_fun_alias_to_type,
|
||||
[<<?Pos(1,10)
|
||||
"Missing `init` function for the contract `AliasToType`.\n"
|
||||
"The `init` function can only be omitted if the state type is `unit`">>
|
||||
])
|
||||
, ?TYPE_ERROR(missing_init_fun_alias_to_alias_to_type,
|
||||
[<<?Pos(1,10)
|
||||
"Missing `init` function for the contract `AliasToAliasToType`.\n"
|
||||
"The `init` function can only be omitted if the state type is `unit`">>
|
||||
])
|
||||
, ?TYPE_ERROR(higher_order_entrypoint,
|
||||
[<<?Pos(2,20)
|
||||
"The argument\n"
|
||||
" `f : (int) => int`\n"
|
||||
"of entrypoint `apply` has a higher-order (contains function types) type">>
|
||||
])
|
||||
, ?TYPE_ERROR(higher_order_entrypoint_return,
|
||||
[<<?Pos(2,3)
|
||||
"The return type\n"
|
||||
" `(int) => int`\n"
|
||||
"of entrypoint `add` is higher-order (contains function types)">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphic_aens_resolve,
|
||||
[<<?Pos(4,5)
|
||||
"Invalid return type of `AENS.resolve`:\n"
|
||||
" `'a`\n"
|
||||
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
|
||||
])
|
||||
, ?TYPE_ERROR(bad_aens_resolve,
|
||||
[<<?Pos(6,5)
|
||||
"Invalid return type of `AENS.resolve`:\n"
|
||||
" `list(int)`\n"
|
||||
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
|
||||
])
|
||||
, ?TYPE_ERROR(bad_aens_resolve_using,
|
||||
[<<?Pos(7,5)
|
||||
"Invalid return type of `AENS.resolve`:\n"
|
||||
" `list(int)`\n"
|
||||
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphic_query_type,
|
||||
[<<?Pos(3,5)
|
||||
"Invalid oracle type\n"
|
||||
" `oracle('a, 'b)`\n"
|
||||
"The query type must not be polymorphic (contain type variables)">>,
|
||||
<<?Pos(3,5)
|
||||
"Invalid oracle type\n"
|
||||
" `oracle('a, 'b)`\n"
|
||||
"The response type must not be polymorphic (contain type variables)">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphic_response_type,
|
||||
[<<?Pos(3,5)
|
||||
"Invalid oracle type\n"
|
||||
" `oracle(string, 'r)`\n"
|
||||
"The response type must not be polymorphic (contain type variables)">>
|
||||
])
|
||||
, ?TYPE_ERROR(higher_order_query_type,
|
||||
[<<?Pos(3,5)
|
||||
"Invalid oracle type\n"
|
||||
" `oracle((int) => int, string)`\n"
|
||||
"The query type must not be higher-order (contain function types)">>
|
||||
])
|
||||
, ?TYPE_ERROR(higher_order_response_type,
|
||||
[<<?Pos(3,5)
|
||||
"Invalid oracle type\n"
|
||||
" `oracle(string, (int) => int)`\n"
|
||||
"The response type must not be higher-order (contain function types)">>
|
||||
])
|
||||
].
|
||||
|
||||
validation_test_() ->
|
||||
|
||||
+13
-2
@@ -1,5 +1,7 @@
|
||||
contract C = entrypoint init() = ()
|
||||
|
||||
// AENS tests
|
||||
contract AENSTest =
|
||||
main contract AENSTest =
|
||||
|
||||
// Name resolution
|
||||
|
||||
@@ -9,10 +11,19 @@ contract AENSTest =
|
||||
stateful entrypoint resolve_string(name : string, key : string) : option(string) =
|
||||
AENS.resolve(name, key)
|
||||
|
||||
stateful entrypoint resolve_contract(name : string, key : string) : option(C) =
|
||||
AENS.resolve(name, key)
|
||||
|
||||
stateful entrypoint resolve_oracle(name : string, key : string) : option(oracle(int, int)) =
|
||||
AENS.resolve(name, key)
|
||||
|
||||
stateful entrypoint resolve_oracle_query(name : string, key : string) : option(oracle_query(int, int)) =
|
||||
AENS.resolve(name, key)
|
||||
|
||||
// Transactions
|
||||
|
||||
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
|
||||
chash : hash) : unit = // Commitment hash
|
||||
chash : hash) : unit = // Commitment hash
|
||||
AENS.preclaim(addr, chash)
|
||||
|
||||
stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
|
||||
|
||||
@@ -80,3 +80,4 @@ contract AllSyntax =
|
||||
let sh : shakespeare(shakespeare(int)) =
|
||||
{wolfgang = state}
|
||||
sh{wolfgang.wolfgang = sh.wolfgang} // comment
|
||||
exit("hope you had fun reading this")
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
contract BadAENSresolve =
|
||||
using AENS
|
||||
|
||||
type t('a) = option(list('a))
|
||||
|
||||
function fail() : t(int) =
|
||||
resolve("foo.aet", "whatever")
|
||||
|
||||
entrypoint main_fun() = ()
|
||||
@@ -1,3 +0,0 @@
|
||||
contract MissingInitFunction =
|
||||
type state = int * int
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
contract AliasToAliasToType =
|
||||
type alias = int * int
|
||||
type state = alias
|
||||
@@ -0,0 +1,2 @@
|
||||
contract AliasToType =
|
||||
type state = int * int
|
||||
@@ -0,0 +1,9 @@
|
||||
contract AliasToAliasToUnit =
|
||||
type alias = unit
|
||||
type state = alias
|
||||
|
||||
contract AliasToUnit =
|
||||
type state = unit
|
||||
|
||||
main contract ImplicitState =
|
||||
type sometype = int
|
||||
@@ -1,12 +1,5 @@
|
||||
|
||||
contract NameClash =
|
||||
|
||||
entrypoint double_proto : () => int
|
||||
entrypoint double_proto : () => int
|
||||
|
||||
entrypoint proto_and_def : int => int
|
||||
entrypoint proto_and_def(n) = n + 1
|
||||
|
||||
entrypoint double_def(x) = x
|
||||
entrypoint double_def(y) = 0
|
||||
|
||||
@@ -14,4 +7,4 @@ contract NameClash =
|
||||
entrypoint abort() : int = 0
|
||||
entrypoint require(b, err) = if(b) abort(err)
|
||||
entrypoint put(x) = x
|
||||
entrypoint state(x, y) = x + y
|
||||
entrypoint state(x, y) = x + y
|
||||
Reference in New Issue
Block a user