Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dbc9858fb | |||
| 51f9eaa934 | |||
| 0ebcf006e2 | |||
| 381a7c98cd | |||
| 4bec4e5107 | |||
| 4dd247b159 | |||
| d926c4a7e3 |
@@ -3,7 +3,7 @@ version: 2.1
|
|||||||
executors:
|
executors:
|
||||||
aebuilder:
|
aebuilder:
|
||||||
docker:
|
docker:
|
||||||
- image: aeternity/builder:xenial-otp21
|
- image: aeternity/builder:bionic-otp24
|
||||||
user: builder
|
user: builder
|
||||||
working_directory: ~/aesophia
|
working_directory: ~/aesophia
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,6 @@ jobs:
|
|||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
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: 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.email "github-action@users.noreply.github.com"
|
||||||
- run: git config --global user.name "GitHub Action"
|
- run: git config --global user.name "GitHub Action"
|
||||||
|
|||||||
@@ -13,10 +13,6 @@ jobs:
|
|||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
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: 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.email "github-action@users.noreply.github.com"
|
||||||
- run: git config --global user.name "GitHub Action"
|
- run: git config --global user.name "GitHub Action"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
mkdocs==1.2.4
|
mkdocs==1.2.4
|
||||||
mkdocs-simple-hooks==0.1.3
|
mkdocs-simple-hooks==0.1.5
|
||||||
mkdocs-material==7.1.9
|
mkdocs-material==7.3.6
|
||||||
mike==1.0.1
|
mike==1.1.2
|
||||||
pygments==2.11.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]
|
## [Unreleased]
|
||||||
### Added
|
### 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 |>
|
- The pipe operator |>
|
||||||
```
|
```
|
||||||
[1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3]))
|
[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 sum(l : list(int)) : int = foldl((+), 0, l)
|
||||||
function logical_and(x, y) = (&&)(x, y)
|
function logical_and(x, y) = (&&)(x, y)
|
||||||
```
|
```
|
||||||
|
- Contract interfaces polymorphism
|
||||||
### Changed
|
### Changed
|
||||||
- Error messages have been restructured (less newlines) to provide more unified errors. Also `pp_oneline/1` has been added.
|
- 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).
|
- 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
|
- Simplify calldata creation - instead of passing a compiled contract, simply
|
||||||
pass a (stubbed) contract string.
|
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.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.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
|
[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
|
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 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
|
## Mutable state
|
||||||
|
|
||||||
@@ -862,6 +1011,16 @@ function require(b : bool, err : string) =
|
|||||||
if(!b) abort(err)
|
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
|
## Delegation signature
|
||||||
|
|
||||||
Some chain operations (`Oracle.<operation>` and `AENS.<operation>`) have an
|
Some chain operations (`Oracle.<operation>` and `AENS.<operation>`) have an
|
||||||
|
|||||||
+3
-4
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
{erl_opts, [debug_info]}.
|
{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"}
|
, {getopt, "1.0.1"}
|
||||||
, {eblake2, "1.0.0"}
|
, {eblake2, "1.0.0"}
|
||||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
|
||||||
{tag, "2.8.0"}}}
|
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{dialyzer, [
|
{dialyzer, [
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{relx, [{release, {aesophia, "6.1.0"},
|
{relx, [{release, {aesophia, "7.0.0"},
|
||||||
[aesophia, aebytecode, getopt]},
|
[aesophia, aebytecode, getopt]},
|
||||||
|
|
||||||
{dev_mode, true},
|
{dev_mode, true},
|
||||||
|
|||||||
+8
-5
@@ -1,11 +1,11 @@
|
|||||||
{"1.1.0",
|
{"1.2.0",
|
||||||
[{<<"aebytecode">>,
|
[{<<"aebytecode">>,
|
||||||
{git,"https://github.com/aeternity/aebytecode.git",
|
{git,"https://github.com/aeternity/aebytecode.git",
|
||||||
{ref,"0699f35b0398bac6cd4468da654d608375bd853d"}},
|
{ref,"8269dbd71e9011921c60141636f1baa270a0e784"}},
|
||||||
0},
|
0},
|
||||||
{<<"aeserialization">>,
|
{<<"aeserialization">>,
|
||||||
{git,"https://github.com/aeternity/aeserialization.git",
|
{git,"https://github.com/aeternity/aeserialization.git",
|
||||||
{ref,"47aaa8f5434b365c50a35bfd1490340b19241991"}},
|
{ref,"eb68fe331bd476910394966b7f5ede7a74d37e35"}},
|
||||||
1},
|
1},
|
||||||
{<<"base58">>,
|
{<<"base58">>,
|
||||||
{git,"https://github.com/aeternity/erl-base58.git",
|
{git,"https://github.com/aeternity/erl-base58.git",
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
||||||
{<<"enacl">>,
|
{<<"enacl">>,
|
||||||
{git,"https://github.com/aeternity/enacl.git",
|
{git,"https://github.com/aeternity/enacl.git",
|
||||||
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
|
{ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}},
|
||||||
2},
|
2},
|
||||||
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
|
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
|
||||||
{<<"jsx">>,
|
{<<"jsx">>,
|
||||||
@@ -24,5 +24,8 @@
|
|||||||
[
|
[
|
||||||
{pkg_hash,[
|
{pkg_hash,[
|
||||||
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
||||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]},
|
||||||
|
{pkg_hash_ext,[
|
||||||
|
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>},
|
||||||
|
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]}
|
||||||
].
|
].
|
||||||
|
|||||||
+171
-12
@@ -14,8 +14,13 @@
|
|||||||
|
|
||||||
-export([ infer/1
|
-export([ infer/1
|
||||||
, infer/2
|
, infer/2
|
||||||
|
, unfold_types_in_type/2
|
||||||
, unfold_types_in_type/3
|
, unfold_types_in_type/3
|
||||||
|
, switch_scope/2
|
||||||
, pp_type/2
|
, pp_type/2
|
||||||
|
, lookup_env1/4
|
||||||
|
, name/1
|
||||||
|
, qname/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include("aeso_utils.hrl").
|
-include("aeso_utils.hrl").
|
||||||
@@ -87,7 +92,11 @@
|
|||||||
-type byte_constraint() :: {is_bytes, utype()}
|
-type byte_constraint() :: {is_bytes, utype()}
|
||||||
| {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), 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,
|
-record(field_info,
|
||||||
{ ann :: aeso_syntax:ann()
|
{ ann :: aeso_syntax:ann()
|
||||||
@@ -155,6 +164,10 @@
|
|||||||
|
|
||||||
%% -- Environment manipulation -----------------------------------------------
|
%% -- 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().
|
-spec push_scope(namespace | contract, aeso_syntax:con(), env()) -> env().
|
||||||
push_scope(Kind, Con, Env) ->
|
push_scope(Kind, Con, Env) ->
|
||||||
Ann = aeso_syntax:get_ann(Con),
|
Ann = aeso_syntax:get_ann(Con),
|
||||||
@@ -399,7 +412,7 @@ lookup_env(Env, Kind, Ann, Name) ->
|
|||||||
end
|
end
|
||||||
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) ->
|
lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
|
||||||
Qual = lists:droplast(QName),
|
Qual = lists:droplast(QName),
|
||||||
Name = lists:last(QName),
|
Name = lists:last(QName),
|
||||||
@@ -539,8 +552,9 @@ global_env() ->
|
|||||||
%% TTL constructors
|
%% TTL constructors
|
||||||
{"RelativeTTL", Fun1(Int, TTL)},
|
{"RelativeTTL", Fun1(Int, TTL)},
|
||||||
{"FixedTTL", Fun1(Int, TTL)},
|
{"FixedTTL", Fun1(Int, TTL)},
|
||||||
%% Abort
|
%% Abort/exit
|
||||||
{"abort", Fun1(String, A)},
|
{"abort", Fun1(String, A)},
|
||||||
|
{"exit", Fun1(String, A)},
|
||||||
{"require", Fun([Bool, String], Unit)}])
|
{"require", Fun([Bool, String], Unit)}])
|
||||||
, types = MkDefs(
|
, types = MkDefs(
|
||||||
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
|
[{"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
|
contract -> bind_state(Env1) %% bind state and put
|
||||||
end,
|
end,
|
||||||
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype, Defs) ]),
|
{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),
|
Env3 = bind_funs(ProtoSigs, Env2),
|
||||||
Functions = Get(function, Defs),
|
Functions = Get(function, Defs),
|
||||||
%% Check for duplicates in Functions (we turn it into a map below)
|
%% 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, []),
|
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
||||||
%% Remove namespaces used in the current namespace
|
%% Remove namespaces used in the current namespace
|
||||||
Env5 = Env4#env{ used_namespaces = OldUsedNamespaces },
|
Env5 = Env4#env{ used_namespaces = OldUsedNamespaces },
|
||||||
%% Check that `init` doesn't read or write the state
|
%% Check that `init` doesn't read or write the state and that `init` is not missing
|
||||||
check_state_dependencies(Env4, Defs1),
|
check_state(Env4, Defs1),
|
||||||
|
%% Check that entrypoints have first-order arg types and return types
|
||||||
|
check_entrypoints(Defs1),
|
||||||
destroy_and_report_type_errors(Env4),
|
destroy_and_report_type_errors(Env4),
|
||||||
%% Add inferred types of definitions
|
%% Add inferred types of definitions
|
||||||
{Env5, TypeDefs ++ Decls ++ Defs1}.
|
{Env5, TypeDefs ++ Decls ++ Defs1}.
|
||||||
@@ -1095,6 +1114,7 @@ check_typedef_sccs(Env, TypeMap, [{acyclic, Name} | SCCs], Acc) ->
|
|||||||
case maps:get(Name, TypeMap, undefined) of
|
case maps:get(Name, TypeMap, undefined) of
|
||||||
undefined -> check_typedef_sccs(Env, TypeMap, SCCs, Acc); %% Builtin type
|
undefined -> check_typedef_sccs(Env, TypeMap, SCCs, Acc); %% Builtin type
|
||||||
{type_def, Ann, D, Xs, Def0} ->
|
{type_def, Ann, D, Xs, Def0} ->
|
||||||
|
check_parameterizable(D, Xs),
|
||||||
Def = check_event(Env, Name, Ann, check_typedef(bind_tvars(Xs, Env), Def0)),
|
Def = check_event(Env, Name, Ann, check_typedef(bind_tvars(Xs, Env), Def0)),
|
||||||
Acc1 = [{type_def, Ann, D, Xs, Def} | Acc],
|
Acc1 = [{type_def, Ann, D, Xs, Def} | Acc],
|
||||||
Env1 = bind_type(Name, Ann, {Xs, Def}, Env),
|
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),
|
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_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) ->
|
check_event(Env, "event", Ann, Def) ->
|
||||||
case Def of
|
case Def of
|
||||||
{variant_t, Cons} ->
|
{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, 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
|
case lookup_env(Env, term, As, qname(Id)) of
|
||||||
false ->
|
false ->
|
||||||
type_error({unbound_variable, Id}),
|
type_error({unbound_variable, Id}),
|
||||||
{Id, fresh_uvar(As)};
|
{Id, fresh_uvar(As)};
|
||||||
{QId, {_, Ty}} ->
|
{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,
|
when_warning(warn_unused_functions,
|
||||||
fun() -> register_function_call(NS ++ qname(CurFn), QId) end),
|
fun() -> register_function_call(NS ++ qname(CurFn), QId) end),
|
||||||
Freshen = proplists:get_value(freshen, Options, false),
|
Freshen = proplists:get_value(freshen, Options, false),
|
||||||
@@ -1612,8 +1639,52 @@ check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id,
|
|||||||
end;
|
end;
|
||||||
check_stateful_named_arg(_, _, _) -> ok.
|
check_stateful_named_arg(_, _, _) -> ok.
|
||||||
|
|
||||||
%% Check that `init` doesn't read or write the state
|
check_entrypoints(Defs) ->
|
||||||
check_state_dependencies(Env, 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,
|
Top = Env#env.namespace,
|
||||||
GetState = Top ++ ["state"],
|
GetState = Top ++ ["state"],
|
||||||
SetState = Top ++ ["put"],
|
SetState = Top ++ ["put"],
|
||||||
@@ -1622,7 +1693,7 @@ check_state_dependencies(Env, Defs) ->
|
|||||||
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _GuardedBodies} <- Defs ],
|
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _GuardedBodies} <- Defs ],
|
||||||
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
|
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
|
||||||
case maps:get(Init, Deps, false) of
|
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})
|
[ type_error({init_depends_on_state, state, Chain})
|
||||||
|| Chain <- get_call_chains(Deps, Init, GetState) ],
|
|| Chain <- get_call_chains(Deps, Init, GetState) ],
|
||||||
@@ -1764,12 +1835,17 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
|||||||
NewFun0 = infer_expr(Env, Fun),
|
NewFun0 = infer_expr(Env, Fun),
|
||||||
NewArgs = [infer_expr(Env, A) || A <- Args],
|
NewArgs = [infer_expr(Env, A) || A <- Args],
|
||||||
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
|
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},
|
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
|
||||||
GeneralResultType = fresh_uvar(Ann),
|
GeneralResultType = fresh_uvar(Ann),
|
||||||
ResultType = fresh_uvar(Ann),
|
ResultType = fresh_uvar(Ann),
|
||||||
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
|
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
|
||||||
when_warning(warn_negative_spend, fun() -> warn_potential_negative_spend(Ann, NewFun1, NewArgs) end),
|
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(
|
add_constraint(
|
||||||
#dependent_type_constraint{ named_args_t = NamedArgsVar,
|
#dependent_type_constraint{ named_args_t = NamedArgsVar,
|
||||||
named_args = NamedArgs1,
|
named_args = NamedArgs1,
|
||||||
@@ -2290,11 +2366,19 @@ destroy_and_report_unsolved_constraints(Env) ->
|
|||||||
(#named_argument_constraint{}) -> true;
|
(#named_argument_constraint{}) -> true;
|
||||||
(_) -> false
|
(_) -> false
|
||||||
end, OtherCs2),
|
end, OtherCs2),
|
||||||
{BytesCs, []} =
|
{BytesCs, OtherCs4} =
|
||||||
lists:partition(fun({is_bytes, _}) -> true;
|
lists:partition(fun({is_bytes, _}) -> true;
|
||||||
({add_bytes, _, _, _, _, _}) -> true;
|
({add_bytes, _, _, _, _, _}) -> true;
|
||||||
(_) -> false
|
(_) -> false
|
||||||
end, OtherCs3),
|
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 ],
|
Unsolved = [ S || S <- [ solve_constraint(Env, dereference_deep(C)) || C <- NamedArgCs ],
|
||||||
S == unsolved ],
|
S == unsolved ],
|
||||||
@@ -2312,9 +2396,20 @@ destroy_and_report_unsolved_constraints(Env) ->
|
|||||||
check_record_create_constraints(Env, CreateCs),
|
check_record_create_constraints(Env, CreateCs),
|
||||||
check_is_contract_constraints(Env, ContractCs),
|
check_is_contract_constraints(Env, ContractCs),
|
||||||
check_bytes_constraints(Env, BytesCs),
|
check_bytes_constraints(Env, BytesCs),
|
||||||
|
check_aens_resolve_constraints(Env, AensResolveCs),
|
||||||
|
check_oracle_type_constraints(Env, OracleTypeCs),
|
||||||
|
|
||||||
destroy_constraints().
|
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 --
|
%% -- Named argument constraints --
|
||||||
|
|
||||||
%% If false, a type error has been emitted, so it's safe to drop the constraint.
|
%% 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})
|
_ -> type_error({unsolved_bytes_constraint, Ann, Fun, A, B, C})
|
||||||
end.
|
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 --
|
%% -- Field constraints --
|
||||||
|
|
||||||
check_record_create_constraints(_, []) -> ok;
|
check_record_create_constraints(_, []) -> ok;
|
||||||
@@ -3484,6 +3605,44 @@ mk_error({unimplemented_interface_function, ConId, InterfaceName, FunName}) ->
|
|||||||
mk_error({referencing_undefined_interface, InterfaceId}) ->
|
mk_error({referencing_undefined_interface, InterfaceId}) ->
|
||||||
Msg = io_lib:format("Trying to implement or extend an undefined interface `~s`", [pp(InterfaceId)]),
|
Msg = io_lib:format("Trying to implement or extend an undefined interface `~s`", [pp(InterfaceId)]),
|
||||||
mk_t_err(pos(InterfaceId), Msg);
|
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) ->
|
mk_error(Err) ->
|
||||||
Msg = io_lib:format("Unknown error: ~p", [Err]),
|
Msg = io_lib:format("Unknown error: ~p", [Err]),
|
||||||
mk_t_err(pos(0, 0), Msg).
|
mk_t_err(pos(0, 0), Msg).
|
||||||
|
|||||||
+17
-125
@@ -75,7 +75,6 @@
|
|||||||
| {switch, fsplit()}
|
| {switch, fsplit()}
|
||||||
| {set_state, state_reg(), fexpr()}
|
| {set_state, state_reg(), fexpr()}
|
||||||
| {get_state, state_reg()}
|
| {get_state, state_reg()}
|
||||||
| {loop, fexpr(), var_name(), fexpr()} | {continue, fexpr()} | {break, fexpr()}
|
|
||||||
%% The following (unapplied top-level functions/builtins and
|
%% The following (unapplied top-level functions/builtins and
|
||||||
%% lambdas) are generated by the fcode compiler, but translated
|
%% lambdas) are generated by the fcode compiler, but translated
|
||||||
%% to closures by the lambda lifter.
|
%% to closures by the lambda lifter.
|
||||||
@@ -243,7 +242,7 @@ builtins() ->
|
|||||||
MkName = fun(NS, Fun) ->
|
MkName = fun(NS, Fun) ->
|
||||||
list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_")))
|
list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_")))
|
||||||
end,
|
end,
|
||||||
Scopes = [{[], [{"abort", 1}, {"require", 2}]},
|
Scopes = [{[], [{"abort", 1}, {"require", 2}, {"exit", 1}]},
|
||||||
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
|
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
|
||||||
{"timestamp", none}, {"block_height", none}, {"difficulty", none},
|
{"timestamp", none}, {"block_height", none}, {"difficulty", none},
|
||||||
{"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]},
|
{"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]},
|
||||||
@@ -327,7 +326,7 @@ get_option(Opt, Env, Default) ->
|
|||||||
%% -- Compilation ------------------------------------------------------------
|
%% -- Compilation ------------------------------------------------------------
|
||||||
|
|
||||||
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
|
-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) ->
|
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||||
case Contract =:= contract_interface of
|
case Contract =:= contract_interface of
|
||||||
false ->
|
false ->
|
||||||
@@ -350,7 +349,7 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
|
|||||||
event_type => EventType,
|
event_type => EventType,
|
||||||
payable => Payable,
|
payable => Payable,
|
||||||
functions => add_init_function(
|
functions => add_init_function(
|
||||||
Env1, Con, StateType,
|
Env1,
|
||||||
add_event_function(Env1, EventType, Funs)) },
|
add_event_function(Env1, EventType, Funs)) },
|
||||||
case Contract of
|
case Contract of
|
||||||
contract_main -> [] = Rest, {Env1, ConFcode};
|
contract_main -> [] = Rest, {Env1, ConFcode};
|
||||||
@@ -363,8 +362,6 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
|
|||||||
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Name} }, Decls),
|
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Name} }, Decls),
|
||||||
to_fcode(Env1, Rest)
|
to_fcode(Env1, Rest)
|
||||||
end;
|
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]) ->
|
to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
|
||||||
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
|
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
|
||||||
to_fcode(Env1, Code).
|
to_fcode(Env1, Code).
|
||||||
@@ -378,22 +375,15 @@ decls_to_fcode(Env, Decls) ->
|
|||||||
end, Env1, Decls).
|
end, Env1, Decls).
|
||||||
|
|
||||||
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
|
-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, {fun_decl, _, _, _}) -> Env;
|
||||||
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
|
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
|
||||||
typedef_to_fcode(Env, 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),
|
Attrs = get_attributes(Ann),
|
||||||
FName = lookup_fun(Env, qname(Env, Name)),
|
FName = lookup_fun(Env, qname(Env, Name)),
|
||||||
FArgs = args_to_fcode(Env, Args),
|
FArgs = args_to_fcode(Env, Args),
|
||||||
FRet = type_to_fcode(Env, Ret),
|
FRet = type_to_fcode(Env, Ret),
|
||||||
FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body),
|
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,
|
Def = #{ attrs => Attrs,
|
||||||
args => FArgs,
|
args => FArgs,
|
||||||
return => FRet,
|
return => FRet,
|
||||||
@@ -402,8 +392,7 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Ar
|
|||||||
Env#{ functions := NewFuns }.
|
Env#{ functions := NewFuns }.
|
||||||
|
|
||||||
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
|
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
|
||||||
typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
|
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
|
||||||
check_state_and_event_types(Env, Id, Xs),
|
|
||||||
Q = qname(Env, Name),
|
Q = qname(Env, Name),
|
||||||
FDef = fun(Args) when length(Args) == length(Xs) ->
|
FDef = fun(Args) when length(Args) == length(Xs) ->
|
||||||
Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)),
|
Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)),
|
||||||
@@ -467,14 +456,6 @@ compute_state_layout(R, [H | T]) ->
|
|||||||
compute_state_layout(R, _) ->
|
compute_state_layout(R, _) ->
|
||||||
{R + 1, {reg, 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().
|
-spec type_to_fcode(env(), aeso_syntax:type()) -> ftype().
|
||||||
type_to_fcode(Env, Type) ->
|
type_to_fcode(Env, Type) ->
|
||||||
type_to_fcode(Env, #{}, Type).
|
type_to_fcode(Env, #{}, Type).
|
||||||
@@ -492,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, {tuple_t, [], lists:map(FieldType, Fields)});
|
||||||
type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
|
type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
|
||||||
{bytes, N};
|
{bytes, N};
|
||||||
type_to_fcode(_Env, _Sub, {tvar, Ann, "void"}) ->
|
|
||||||
fcode_error({found_void, Ann});
|
|
||||||
type_to_fcode(_Env, Sub, {tvar, _, X}) ->
|
type_to_fcode(_Env, Sub, {tvar, _, X}) ->
|
||||||
maps:get(X, Sub, {tvar, X});
|
maps:get(X, Sub, {tvar, X});
|
||||||
type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) ->
|
type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) ->
|
||||||
@@ -555,7 +534,7 @@ expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}};
|
|||||||
|
|
||||||
%% Variables
|
%% Variables
|
||||||
expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]);
|
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
|
case resolve_var(Env, X) of
|
||||||
{builtin_u, B, Ar} when B =:= oracle_query;
|
{builtin_u, B, Ar} when B =:= oracle_query;
|
||||||
B =:= oracle_get_question;
|
B =:= oracle_get_question;
|
||||||
@@ -566,13 +545,11 @@ expr_to_fcode(Env, Type, {qid, Ann, X}) ->
|
|||||||
B =:= oracle_check_query ->
|
B =:= oracle_check_query ->
|
||||||
OType = get_oracle_type(B, Type),
|
OType = get_oracle_type(B, Type),
|
||||||
{oracle, QType, RType} = type_to_fcode(Env, OType),
|
{oracle, QType, RType} = type_to_fcode(Env, OType),
|
||||||
validate_oracle_type(Ann, OType, QType, RType),
|
|
||||||
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
|
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
|
||||||
{builtin_u, B, Ar, TypeArgs};
|
{builtin_u, B, Ar, TypeArgs};
|
||||||
{builtin_u, B = aens_resolve, Ar} ->
|
{builtin_u, B = aens_resolve, Ar} ->
|
||||||
{fun_t, _, _, _, ResType} = Type,
|
{fun_t, _, _, _, ResType} = Type,
|
||||||
AensType = type_to_fcode(Env, ResType),
|
AensType = type_to_fcode(Env, ResType),
|
||||||
validate_aens_resolve_type(Ann, ResType, AensType),
|
|
||||||
TypeArgs = [{lit, {typerep, AensType}}],
|
TypeArgs = [{lit, {typerep, AensType}}],
|
||||||
{builtin_u, B, Ar, TypeArgs};
|
{builtin_u, B, Ar, TypeArgs};
|
||||||
{builtin_u, B = bytes_split, Ar} ->
|
{builtin_u, B = bytes_split, Ar} ->
|
||||||
@@ -652,22 +629,11 @@ expr_to_fcode(Env, {record_t, FieldTypes}, {record, _Ann, Rec, Fields}) ->
|
|||||||
expr_to_fcode(Env, _Type, {list, _, Es}) ->
|
expr_to_fcode(Env, _Type, {list, _, Es}) ->
|
||||||
lists:foldr(fun(E, L) -> {op, '::', [expr_to_fcode(Env, E), L]} end,
|
lists:foldr(fun(E, L) -> {op, '::', [expr_to_fcode(Env, E), L]} end,
|
||||||
nil, Es);
|
nil, Es);
|
||||||
|
|
||||||
expr_to_fcode(Env, _Type, {app, _, {'..', _}, [A, B]}) ->
|
expr_to_fcode(Env, _Type, {app, _, {'..', _}, [A, B]}) ->
|
||||||
AV = fresh_name(), % var to keep B
|
{def_u, FromTo, _} = resolve_fun(Env, ["ListInternal", "from_to"]),
|
||||||
WithA = fun(X) -> {'let', AV, expr_to_fcode(Env, A), X} end,
|
{def, FromTo, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]};
|
||||||
St = fresh_name(), % loop state
|
|
||||||
ItProj = {proj, {var, St}, 1},
|
|
||||||
AcProj = {proj, {var, St}, 0},
|
|
||||||
Init = {tuple, [nil, expr_to_fcode(Env, B)]},
|
|
||||||
Loop = {loop, Init, St,
|
|
||||||
make_if(
|
|
||||||
{op, '>=', [ItProj, {var, AV}]},
|
|
||||||
{continue, {tuple, [{op, '::', [ItProj, AcProj]},
|
|
||||||
{op, '-', [ItProj, {lit, {int, 1}}]}
|
|
||||||
]}},
|
|
||||||
{break, AcProj}
|
|
||||||
)},
|
|
||||||
WithA(Loop);
|
|
||||||
expr_to_fcode(Env, _Type, {list_comp, _, Yield, []}) ->
|
expr_to_fcode(Env, _Type, {list_comp, _, Yield, []}) ->
|
||||||
{op, '::', [expr_to_fcode(Env, Yield), nil]};
|
{op, '::', [expr_to_fcode(Env, Yield), nil]};
|
||||||
expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {typed, _, _, PatType}, BindExpr}|Rest]}) ->
|
expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {typed, _, _, PatType}, BindExpr}|Rest]}) ->
|
||||||
@@ -836,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_check_query, {fun_t, _, _, [OType | _], _}) -> OType;
|
||||||
get_oracle_type(oracle_respond, {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 --
|
%% -- Pattern matching --
|
||||||
|
|
||||||
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()], aeso_syntax:expr()) -> fsplit().
|
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()], aeso_syntax:expr()) -> fsplit().
|
||||||
@@ -1215,11 +1134,11 @@ builtin_to_fcode(_Layout, Builtin, Args) ->
|
|||||||
|
|
||||||
%% -- Init function --
|
%% -- Init function --
|
||||||
|
|
||||||
add_init_function(Env, Main, StateType, Funs0) ->
|
add_init_function(Env, Funs0) ->
|
||||||
case is_no_code(Env) of
|
case is_no_code(Env) of
|
||||||
true -> Funs0;
|
true -> Funs0;
|
||||||
false ->
|
false ->
|
||||||
Funs = add_default_init_function(Env, Main, StateType, Funs0),
|
Funs = add_default_init_function(Env, Funs0),
|
||||||
InitName = {entrypoint, <<"init">>},
|
InitName = {entrypoint, <<"init">>},
|
||||||
InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
|
InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
|
||||||
Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []},
|
Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []},
|
||||||
@@ -1227,16 +1146,14 @@ add_init_function(Env, Main, StateType, Funs0) ->
|
|||||||
Funs1
|
Funs1
|
||||||
end.
|
end.
|
||||||
|
|
||||||
add_default_init_function(_Env, Main, StateType, Funs) ->
|
add_default_init_function(_Env, Funs) ->
|
||||||
InitName = {entrypoint, <<"init">>},
|
InitName = {entrypoint, <<"init">>},
|
||||||
case maps:get(InitName, Funs, none) of
|
case maps:get(InitName, Funs, none) of
|
||||||
%% Only add default init function if state is unit.
|
none ->
|
||||||
none when StateType == {tuple, []} ->
|
|
||||||
Funs#{ InitName => #{attrs => [],
|
Funs#{ InitName => #{attrs => [],
|
||||||
args => [],
|
args => [],
|
||||||
return => {tuple, []},
|
return => {tuple, []},
|
||||||
body => {tuple, []}} };
|
body => {tuple, []}} };
|
||||||
none -> fcode_error({missing_init_function, Main});
|
|
||||||
_ -> Funs
|
_ -> Funs
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@@ -1350,9 +1267,6 @@ lambda_lift_expr(Layout, Expr) ->
|
|||||||
{proj, A, I} -> {proj, lambda_lift_expr(Layout, A), I};
|
{proj, A, I} -> {proj, lambda_lift_expr(Layout, A), I};
|
||||||
{set_proj, A, I, B} -> {set_proj, lambda_lift_expr(Layout, A), I, lambda_lift_expr(Layout, B)};
|
{set_proj, A, I, B} -> {set_proj, lambda_lift_expr(Layout, A), I, lambda_lift_expr(Layout, B)};
|
||||||
{op, Op, As} -> {op, Op, lambda_lift_exprs(Layout, As)};
|
{op, Op, As} -> {op, Op, lambda_lift_exprs(Layout, As)};
|
||||||
{loop, Init, I, Body} -> {loop, lambda_lift_expr(Layout, Init), I, lambda_lift_expr(Layout, Body)};
|
|
||||||
{break, E} -> {break, lambda_lift_expr(Layout, E)};
|
|
||||||
{continue, E} -> {continue, lambda_lift_expr(Layout, E)};
|
|
||||||
{'let', X, A, B} -> {'let', X, lambda_lift_expr(Layout, A), lambda_lift_expr(Layout, B)};
|
{'let', X, A, B} -> {'let', X, lambda_lift_expr(Layout, A), lambda_lift_expr(Layout, B)};
|
||||||
{funcall, A, Bs} -> {funcall, lambda_lift_expr(Layout, A), lambda_lift_exprs(Layout, Bs)};
|
{funcall, A, Bs} -> {funcall, lambda_lift_expr(Layout, A), lambda_lift_exprs(Layout, Bs)};
|
||||||
{set_state, R, A} -> {set_state, R, lambda_lift_expr(Layout, A)};
|
{set_state, R, A} -> {set_state, R, lambda_lift_expr(Layout, A)};
|
||||||
@@ -1668,9 +1582,6 @@ read_only({switch, Split}) -> read_only(Split);
|
|||||||
read_only({split, _, _, Cases}) -> read_only(Cases);
|
read_only({split, _, _, Cases}) -> read_only(Cases);
|
||||||
read_only({nosplit, E}) -> read_only(E);
|
read_only({nosplit, E}) -> read_only(E);
|
||||||
read_only({'case', _, Split}) -> read_only(Split);
|
read_only({'case', _, Split}) -> read_only(Split);
|
||||||
read_only({loop, Init, _, Body}) -> read_only(Init) andalso read_only(Body);
|
|
||||||
read_only({break, E}) -> read_only(E);
|
|
||||||
read_only({continue, E}) -> read_only(E);
|
|
||||||
read_only({'let', _, A, B}) -> read_only([A, B]);
|
read_only({'let', _, A, B}) -> read_only([A, B]);
|
||||||
read_only({funcall, _, _}) -> false;
|
read_only({funcall, _, _}) -> false;
|
||||||
read_only({closure, _, _}) -> internal_error(no_closures_here);
|
read_only({closure, _, _}) -> internal_error(no_closures_here);
|
||||||
@@ -1868,9 +1779,6 @@ free_vars(Expr) ->
|
|||||||
{proj, A, _} -> free_vars(A);
|
{proj, A, _} -> free_vars(A);
|
||||||
{set_proj, A, _, B} -> free_vars([A, B]);
|
{set_proj, A, _, B} -> free_vars([A, B]);
|
||||||
{op, _, As} -> free_vars(As);
|
{op, _, As} -> free_vars(As);
|
||||||
{loop, Init, Var, Body} -> free_vars(Init) ++ (free_vars(Body) -- [Var]);
|
|
||||||
{break, E} -> free_vars(E);
|
|
||||||
{continue, E} -> free_vars(E);
|
|
||||||
{'let', X, A, B} -> free_vars([A, {lam, [X], B}]);
|
{'let', X, A, B} -> free_vars([A, {lam, [X], B}]);
|
||||||
{funcall, A, Bs} -> free_vars([A | Bs]);
|
{funcall, A, Bs} -> free_vars([A | Bs]);
|
||||||
{set_state, _, A} -> free_vars(A);
|
{set_state, _, A} -> free_vars(A);
|
||||||
@@ -1902,9 +1810,6 @@ used_defs(Expr) ->
|
|||||||
{proj, A, _} -> used_defs(A);
|
{proj, A, _} -> used_defs(A);
|
||||||
{set_proj, A, _, B} -> used_defs([A, B]);
|
{set_proj, A, _, B} -> used_defs([A, B]);
|
||||||
{op, _, As} -> used_defs(As);
|
{op, _, As} -> used_defs(As);
|
||||||
{loop, I, _, B} -> used_defs(I) ++ used_defs(B);
|
|
||||||
{break, E} -> used_defs(E);
|
|
||||||
{continue, E} -> used_defs(E);
|
|
||||||
{'let', _, A, B} -> used_defs([A, B]);
|
{'let', _, A, B} -> used_defs([A, B]);
|
||||||
{funcall, A, Bs} -> used_defs([A | Bs]);
|
{funcall, A, Bs} -> used_defs([A | Bs]);
|
||||||
{set_state, _, A} -> used_defs(A);
|
{set_state, _, A} -> used_defs(A);
|
||||||
@@ -1941,9 +1846,6 @@ bottom_up(F, Env, Expr) ->
|
|||||||
{get_state, _} -> Expr;
|
{get_state, _} -> Expr;
|
||||||
{closure, F, CEnv} -> {closure, F, bottom_up(F, Env, CEnv)};
|
{closure, F, CEnv} -> {closure, F, bottom_up(F, Env, CEnv)};
|
||||||
{switch, Split} -> {switch, bottom_up(F, Env, Split)};
|
{switch, Split} -> {switch, bottom_up(F, Env, Split)};
|
||||||
{loop, Init, Var, Body} -> {loop, bottom_up(F, Env, Init), Var, bottom_up(F, Env, Body)};
|
|
||||||
{break, E} -> {break, bottom_up(F, Env, E)};
|
|
||||||
{continue, E} -> {continue, bottom_up(F, Env, E)};
|
|
||||||
{lam, Xs, B} -> {lam, Xs, bottom_up(F, Env, B)};
|
{lam, Xs, B} -> {lam, Xs, bottom_up(F, Env, B)};
|
||||||
{'let', X, E, Body} ->
|
{'let', X, E, Body} ->
|
||||||
E1 = bottom_up(F, Env, E),
|
E1 = bottom_up(F, Env, E),
|
||||||
@@ -2005,11 +1907,6 @@ rename(Ren, Expr) ->
|
|||||||
{lam, Xs, B} ->
|
{lam, Xs, B} ->
|
||||||
{Zs, Ren1} = rename_bindings(Ren, Xs),
|
{Zs, Ren1} = rename_bindings(Ren, Xs),
|
||||||
{lam, Zs, rename(Ren1, B)};
|
{lam, Zs, rename(Ren1, B)};
|
||||||
{loop, Init, Var, Body} ->
|
|
||||||
{Z, Ren1} = rename_binding(Ren, Var),
|
|
||||||
{loop, rename(Ren, Init), Z, rename(Ren1, Body)};
|
|
||||||
{break, E} -> {break, rename(Ren, E)};
|
|
||||||
{continue, E} -> {continue, rename(Ren, E)};
|
|
||||||
{'let', X, E, Body} ->
|
{'let', X, E, Body} ->
|
||||||
{Z, Ren1} = rename_binding(Ren, X),
|
{Z, Ren1} = rename_binding(Ren, X),
|
||||||
{'let', Z, rename(Ren, E), rename(Ren1, Body)}
|
{'let', Z, rename(Ren, E), rename(Ren1, Body)}
|
||||||
@@ -2122,7 +2019,9 @@ setnth(I, X, Xs) ->
|
|||||||
-dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}).
|
-dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}).
|
||||||
|
|
||||||
fcode_error(Error) ->
|
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) ->
|
internal_error(Error) ->
|
||||||
Msg = lists:flatten(io_lib:format("~p\n", [Error])),
|
Msg = lists:flatten(io_lib:format("~p\n", [Error])),
|
||||||
@@ -2201,13 +2100,6 @@ pp_fexpr({tuple, Es}) ->
|
|||||||
pp_parens(pp_par(pp_punctuate(pp_text(","), [pp_fexpr(E) || E <- Es])));
|
pp_parens(pp_par(pp_punctuate(pp_text(","), [pp_fexpr(E) || E <- Es])));
|
||||||
pp_fexpr({proj, E, I}) ->
|
pp_fexpr({proj, E, I}) ->
|
||||||
pp_beside([pp_fexpr(E), pp_text("."), pp_int(I)]);
|
pp_beside([pp_fexpr(E), pp_text("."), pp_int(I)]);
|
||||||
pp_fexpr({loop, Init, Var, Body}) ->
|
|
||||||
pp_par(
|
|
||||||
[ pp_beside([pp_text("loop"), pp_fexpr(Init), pp_text("as"), pp_text(Var)])
|
|
||||||
, pp_fexpr(Body)
|
|
||||||
]);
|
|
||||||
pp_fexpr({break, E}) -> pp_beside([pp_text("break"), pp_fexpr(E)]);
|
|
||||||
pp_fexpr({continue, E}) -> pp_beside([pp_text("continue"), pp_fexpr(E)]);
|
|
||||||
pp_fexpr({lam, Xs, A}) ->
|
pp_fexpr({lam, Xs, A}) ->
|
||||||
pp_par([pp_fexpr({tuple, [{var, X} || X <- Xs]}), pp_text("=>"),
|
pp_par([pp_fexpr({tuple, [{var, X} || X <- Xs]}), pp_text("=>"),
|
||||||
prettypr:nest(2, pp_fexpr(A))]);
|
prettypr:nest(2, pp_fexpr(A))]);
|
||||||
|
|||||||
@@ -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, [], []}};
|
"init" -> {ok, [], {tuple_t, [], []}};
|
||||||
_ ->
|
_ ->
|
||||||
Msg = io_lib:format("Function '~s' is missing in contract", [FunName]),
|
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))
|
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
, new/2
|
, new/2
|
||||||
, new/3
|
, new/3
|
||||||
, new/4
|
, new/4
|
||||||
|
, pos/1
|
||||||
, pos/2
|
, pos/2
|
||||||
, pos/3
|
, pos/3
|
||||||
, pp/1
|
, pp/1
|
||||||
@@ -53,6 +54,12 @@ new(Type, Pos, Msg) ->
|
|||||||
new(Type, Pos, Msg, Ctxt) ->
|
new(Type, Pos, Msg, Ctxt) ->
|
||||||
#err{ type = Type, pos = Pos, message = Msg, context = 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, Col) ->
|
||||||
#pos{ line = Line, col = Col }.
|
#pos{ line = Line, col = Col }.
|
||||||
|
|
||||||
|
|||||||
+19
-67
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
-type scode() :: [sinstr()].
|
-type scode() :: [sinstr()].
|
||||||
-type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all
|
-type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all
|
||||||
| {loop, scode(), var(), scode(), reference(), reference()}
|
|
||||||
| switch_body
|
| switch_body
|
||||||
| loop
|
| loop
|
||||||
| tuple() | atom(). %% FATE instruction
|
| tuple() | atom(). %% FATE instruction
|
||||||
@@ -46,7 +45,7 @@
|
|||||||
-define(s(N), {store, N}).
|
-define(s(N), {store, N}).
|
||||||
-define(void, {var, 9999}).
|
-define(void, {var, 9999}).
|
||||||
|
|
||||||
-record(env, { contract, vars = [], locals = [], break_ref = none, cont_ref = none, loop_it = none, current_function, tailpos = true, child_contracts = #{}, options = []}).
|
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true, child_contracts = #{}, options = []}).
|
||||||
|
|
||||||
%% -- Debugging --------------------------------------------------------------
|
%% -- Debugging --------------------------------------------------------------
|
||||||
|
|
||||||
@@ -65,7 +64,9 @@ debug(Tag, Options, Fun) ->
|
|||||||
|
|
||||||
-dialyzer({nowarn_function, [code_error/1]}).
|
-dialyzer({nowarn_function, [code_error/1]}).
|
||||||
code_error(Err) ->
|
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 -------------------------------------------------------------------
|
%% -- Main -------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -160,9 +161,6 @@ init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) ->
|
|||||||
next_var(#env{ vars = Vars }) ->
|
next_var(#env{ vars = Vars }) ->
|
||||||
1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]).
|
1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]).
|
||||||
|
|
||||||
bind_loop(ContRef, BreakRef, It, Env) ->
|
|
||||||
Env#env{break_ref = BreakRef, cont_ref = ContRef, loop_it = It}.
|
|
||||||
|
|
||||||
bind_var(Name, Var, Env = #env{ vars = Vars }) ->
|
bind_var(Name, Var, Env = #env{ vars = Vars }) ->
|
||||||
Env#env{ vars = [{Name, Var} | Vars] }.
|
Env#env{ vars = [{Name, Var} | Vars] }.
|
||||||
|
|
||||||
@@ -372,21 +370,7 @@ to_scode1(Env, {set_state, Reg, Val}) ->
|
|||||||
|
|
||||||
to_scode1(Env, {closure, Fun, FVs}) ->
|
to_scode1(Env, {closure, Fun, FVs}) ->
|
||||||
to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]});
|
to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]});
|
||||||
to_scode1(Env, {loop, Init, It, Expr}) ->
|
|
||||||
ContRef = make_ref(),
|
|
||||||
BreakRef = make_ref(),
|
|
||||||
{ItV, Env1} = bind_local(It, Env),
|
|
||||||
InitS = [to_scode(notail(Env), Init),
|
|
||||||
{jump, ContRef}],
|
|
||||||
ExprS = [aeb_fate_ops:store({var, ItV}, {stack, 0}),
|
|
||||||
to_scode(bind_loop(ContRef, BreakRef, ItV, Env1), Expr),
|
|
||||||
{jump, BreakRef}],
|
|
||||||
[{loop, InitS, It, ExprS, ContRef, BreakRef}];
|
|
||||||
to_scode1(Env = #env{cont_ref = ContRef}, {continue, Expr}) ->
|
|
||||||
[to_scode1(notail(Env), Expr),
|
|
||||||
{jump, ContRef}];
|
|
||||||
to_scode1(Env, {break, Expr}) ->
|
|
||||||
to_scode1(Env, Expr);
|
|
||||||
to_scode1(Env, {switch, Case}) ->
|
to_scode1(Env, {switch, Case}) ->
|
||||||
split_to_scode(Env, Case).
|
split_to_scode(Env, Case).
|
||||||
|
|
||||||
@@ -525,6 +509,8 @@ builtin_to_scode(Env, bytes_split, [_, _] = Args) ->
|
|||||||
call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args);
|
call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args);
|
||||||
builtin_to_scode(Env, abort, [_] = Args) ->
|
builtin_to_scode(Env, abort, [_] = Args) ->
|
||||||
call_to_scode(Env, aeb_fate_ops:abort(?a), 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) ->
|
builtin_to_scode(Env, chain_spend, [_, _] = Args) ->
|
||||||
call_to_scode(Env, [aeb_fate_ops:spend(?a, ?a),
|
call_to_scode(Env, [aeb_fate_ops:spend(?a, ?a),
|
||||||
tuple(0)], Args);
|
tuple(0)], Args);
|
||||||
@@ -730,8 +716,6 @@ flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
|
|||||||
|
|
||||||
flatten_s({switch, Arg, Type, Alts, Catch}) ->
|
flatten_s({switch, Arg, Type, Alts, Catch}) ->
|
||||||
{switch, Arg, Type, [flatten(Alt) || Alt <- Alts], flatten(Catch)};
|
{switch, Arg, Type, [flatten(Alt) || Alt <- Alts], flatten(Catch)};
|
||||||
flatten_s({loop, Init, It, Body, BRef, CRef}) ->
|
|
||||||
{loop, flatten(Init), It, flatten(Body), BRef, CRef};
|
|
||||||
flatten_s(I) -> I.
|
flatten_s(I) -> I.
|
||||||
|
|
||||||
-define(MAX_SIMPL_ITERATIONS, 10).
|
-define(MAX_SIMPL_ITERATIONS, 10).
|
||||||
@@ -828,11 +812,6 @@ ann_live1(LiveTop, {switch, Arg, Type, Alts, Def}, LiveOut) ->
|
|||||||
{Def1, LiveDef} = ann_live(LiveTop, Def, LiveOut),
|
{Def1, LiveDef} = ann_live(LiveTop, Def, LiveOut),
|
||||||
LiveIn = ordsets:union([Read, LiveDef | LiveAlts]),
|
LiveIn = ordsets:union([Read, LiveDef | LiveAlts]),
|
||||||
{{switch, Arg, Type, Alts1, Def1}, LiveIn};
|
{{switch, Arg, Type, Alts1, Def1}, LiveIn};
|
||||||
ann_live1(LiveTop, {loop, Init, It, Body, BRef, CRef}, LiveOut) ->
|
|
||||||
{Init1, LiveInit} = ann_live(LiveTop, Init, LiveOut),
|
|
||||||
{Body1, LiveBody} = ann_live(LiveTop, Body, LiveOut),
|
|
||||||
LiveIn = ordsets:union([It, LiveInit, LiveBody]), % TODO not sure about this
|
|
||||||
{{loop, Init1, It, Body1, BRef, CRef}, LiveIn};
|
|
||||||
ann_live1(_LiveTop, I, LiveOut) ->
|
ann_live1(_LiveTop, I, LiveOut) ->
|
||||||
#{ read := Reads0, write := W } = attributes(I),
|
#{ read := Reads0, write := W } = attributes(I),
|
||||||
Reads = lists:filter(fun is_reg/1, Reads0),
|
Reads = lists:filter(fun is_reg/1, Reads0),
|
||||||
@@ -859,7 +838,6 @@ attributes(I) ->
|
|||||||
case I of
|
case I of
|
||||||
loop -> Impure(pc, []);
|
loop -> Impure(pc, []);
|
||||||
switch_body -> Pure(none, []);
|
switch_body -> Pure(none, []);
|
||||||
{jump, _} -> Impure(pc, []);
|
|
||||||
'RETURN' -> Impure(pc, []);
|
'RETURN' -> Impure(pc, []);
|
||||||
{'RETURNR', A} -> Impure(pc, A);
|
{'RETURNR', A} -> Impure(pc, A);
|
||||||
{'CALL', A} -> Impure(?a, [A]);
|
{'CALL', A} -> Impure(?a, [A]);
|
||||||
@@ -1049,7 +1027,6 @@ var_writes(I) ->
|
|||||||
-spec independent(sinstr_a(), sinstr_a()) -> boolean().
|
-spec independent(sinstr_a(), sinstr_a()) -> boolean().
|
||||||
%% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging
|
%% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging
|
||||||
independent(_, {switch, _, _, _, _}) -> false;
|
independent(_, {switch, _, _, _, _}) -> false;
|
||||||
independent(_, {loop, _, _, _, _, _}) -> false;
|
|
||||||
independent({i, _, I}, {i, _, J}) ->
|
independent({i, _, I}, {i, _, J}) ->
|
||||||
#{ write := WI, read := RI, pure := PureI } = attributes(I),
|
#{ write := WI, read := RI, pure := PureI } = attributes(I),
|
||||||
#{ write := WJ, read := RJ, pure := PureJ } = attributes(J),
|
#{ write := WJ, read := RJ, pure := PureJ } = attributes(J),
|
||||||
@@ -1088,8 +1065,6 @@ live_in(R, {i, Ann, _}) -> live_in(R, Ann);
|
|||||||
live_in(R, [I = {i, _, _} | _]) -> live_in(R, I);
|
live_in(R, [I = {i, _, _} | _]) -> live_in(R, I);
|
||||||
live_in(R, [{switch, A, _, Alts, Def} | _]) ->
|
live_in(R, [{switch, A, _, Alts, Def} | _]) ->
|
||||||
R == A orelse lists:any(fun(Code) -> live_in(R, Code) end, [Def | Alts]);
|
R == A orelse lists:any(fun(Code) -> live_in(R, Code) end, [Def | Alts]);
|
||||||
live_in(R, [{loop, Init, Var, Expr, _, _}]) ->
|
|
||||||
live_in(Var, Init) orelse (R /= Var andalso live_in(R, Expr));
|
|
||||||
live_in(_, missing) -> false;
|
live_in(_, missing) -> false;
|
||||||
live_in(_, []) -> false.
|
live_in(_, []) -> false.
|
||||||
|
|
||||||
@@ -1105,8 +1080,6 @@ simplify([I | Code], Options) ->
|
|||||||
|
|
||||||
simpl_s({switch, Arg, Type, Alts, Def}, Options) ->
|
simpl_s({switch, Arg, Type, Alts, Def}, Options) ->
|
||||||
{switch, Arg, Type, [simplify(A, Options) || A <- Alts], simplify(Def, Options)};
|
{switch, Arg, Type, [simplify(A, Options) || A <- Alts], simplify(Def, Options)};
|
||||||
simpl_s({loop, Init, Var, Expr, ContRef, BreakRef}, Options) ->
|
|
||||||
{loop, simplify(Init, Options), Var, simplify(Expr, Options), ContRef, BreakRef};
|
|
||||||
simpl_s(I, _) -> I.
|
simpl_s(I, _) -> I.
|
||||||
|
|
||||||
%% Safe-guard against loops in the rewriting. Shouldn't happen so throw an
|
%% Safe-guard against loops in the rewriting. Shouldn't happen so throw an
|
||||||
@@ -1409,8 +1382,6 @@ does_abort({i, _, {'EXIT', _}}) -> true;
|
|||||||
does_abort(missing) -> true;
|
does_abort(missing) -> true;
|
||||||
does_abort({switch, _, _, Alts, Def}) ->
|
does_abort({switch, _, _, Alts, Def}) ->
|
||||||
lists:all(fun does_abort/1, [Def | Alts]);
|
lists:all(fun does_abort/1, [Def | Alts]);
|
||||||
does_abort({loop, Init, _, Expr, _, _}) ->
|
|
||||||
does_abort(Init) orelse does_abort(Expr);
|
|
||||||
does_abort(_) -> false.
|
does_abort(_) -> false.
|
||||||
|
|
||||||
%% STORE R A, SWITCH R --> SWITCH A
|
%% STORE R A, SWITCH R --> SWITCH A
|
||||||
@@ -1519,6 +1490,7 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
|
|||||||
r_write_to_dead_var(_, _) -> false.
|
r_write_to_dead_var(_, _) -> false.
|
||||||
|
|
||||||
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
|
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
|
||||||
|
op_view({'EXIT', R}) -> {'EXIT', none, [R]};
|
||||||
op_view(T) when is_tuple(T) ->
|
op_view(T) when is_tuple(T) ->
|
||||||
[Op, R | As] = tuple_to_list(T),
|
[Op, R | As] = tuple_to_list(T),
|
||||||
CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end,
|
CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end,
|
||||||
@@ -1538,8 +1510,6 @@ from_op_view(Op, R, As) -> list_to_tuple([Op, R | As]).
|
|||||||
(missing) -> missing.
|
(missing) -> missing.
|
||||||
unannotate({switch, Arg, Type, Alts, Def}) ->
|
unannotate({switch, Arg, Type, Alts, Def}) ->
|
||||||
[{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}];
|
[{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}];
|
||||||
unannotate({loop, Init, It, Body, BRef, CRef}) ->
|
|
||||||
[{loop, unannotate(Init), It, unannotate(Body), BRef, CRef}];
|
|
||||||
unannotate(missing) -> missing;
|
unannotate(missing) -> missing;
|
||||||
unannotate(Code) when is_list(Code) ->
|
unannotate(Code) when is_list(Code) ->
|
||||||
lists:flatmap(fun unannotate/1, Code);
|
lists:flatmap(fun unannotate/1, Code);
|
||||||
@@ -1556,8 +1526,6 @@ desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(desugar_arg(A))];
|
|||||||
desugar({'STORE', R, ?a}) -> [aeb_fate_ops:pop(desugar_arg(R))];
|
desugar({'STORE', R, ?a}) -> [aeb_fate_ops:pop(desugar_arg(R))];
|
||||||
desugar({switch, Arg, Type, Alts, Def}) ->
|
desugar({switch, Arg, Type, Alts, Def}) ->
|
||||||
[{switch, desugar_arg(Arg), Type, [desugar(A) || A <- Alts], desugar(Def)}];
|
[{switch, desugar_arg(Arg), Type, [desugar(A) || A <- Alts], desugar(Def)}];
|
||||||
desugar({loop, Init, Var, Expr, ContRef, BreakRef}) ->
|
|
||||||
[{loop, desugar(Init), Var, desugar(Expr), ContRef, BreakRef}];
|
|
||||||
desugar(missing) -> missing;
|
desugar(missing) -> missing;
|
||||||
desugar(Code) when is_list(Code) ->
|
desugar(Code) when is_list(Code) ->
|
||||||
lists:flatmap(fun desugar/1, Code);
|
lists:flatmap(fun desugar/1, Code);
|
||||||
@@ -1604,7 +1572,7 @@ bb(_Name, Code) ->
|
|||||||
-type bb() :: {bbref(), bcode()}.
|
-type bb() :: {bbref(), bcode()}.
|
||||||
-type bcode() :: [binstr()].
|
-type bcode() :: [binstr()].
|
||||||
-type binstr() :: {jump, bbref()}
|
-type binstr() :: {jump, bbref()}
|
||||||
| {jumpif, term(), bbref()}
|
| {jumpif, bbref()}
|
||||||
| tuple(). %% FATE instruction
|
| tuple(). %% FATE instruction
|
||||||
|
|
||||||
-spec blocks(scode()) -> [bb()].
|
-spec blocks(scode()) -> [bb()].
|
||||||
@@ -1618,29 +1586,25 @@ blocks([], Acc) ->
|
|||||||
blocks([Blk | Blocks], Acc) ->
|
blocks([Blk | Blocks], Acc) ->
|
||||||
block(Blk, [], Blocks, Acc).
|
block(Blk, [], Blocks, Acc).
|
||||||
|
|
||||||
fresh_block(C, Ca) ->
|
|
||||||
R = make_ref(),
|
|
||||||
{R, [#blk{ref = R, code = C, catchall = Ca}]}.
|
|
||||||
|
|
||||||
-spec block(#blk{}, bcode(), [#blk{}], [bb()]) -> [bb()].
|
-spec block(#blk{}, bcode(), [#blk{}], [bb()]) -> [bb()].
|
||||||
block(#blk{ref = Ref, code = []}, CodeAcc, Blocks, BlockAcc) ->
|
block(#blk{ref = Ref, code = []}, CodeAcc, Blocks, BlockAcc) ->
|
||||||
blocks(Blocks, [{Ref, lists:reverse(CodeAcc)} | BlockAcc]);
|
blocks(Blocks, [{Ref, lists:reverse(CodeAcc)} | BlockAcc]);
|
||||||
block(Blk = #blk{code = [{loop, Init, _, Expr, ContRef, BreakRef} | Code], catchall = Catchall}, Acc, Blocks, BlockAcc) ->
|
|
||||||
LoopBlock = #blk{ref = ContRef, code = Expr, catchall = none},
|
|
||||||
BreakBlock = #blk{ref = BreakRef, code = Code, catchall = Catchall},
|
|
||||||
block(Blk#blk{code = Init}, Acc, [LoopBlock, BreakBlock | Blocks], BlockAcc);
|
|
||||||
block(Blk = #blk{code = [switch_body | Code]}, Acc, Blocks, BlockAcc) ->
|
block(Blk = #blk{code = [switch_body | Code]}, Acc, Blocks, BlockAcc) ->
|
||||||
%% Reached the body of a switch. Clear catchall ref.
|
%% Reached the body of a switch. Clear catchall ref.
|
||||||
block(Blk#blk{code = Code, catchall = none}, Acc, Blocks, BlockAcc);
|
block(Blk#blk{code = Code, catchall = none}, Acc, Blocks, BlockAcc);
|
||||||
block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
||||||
catchall = Catchall}, Acc, Blocks, BlockAcc) ->
|
catchall = Catchall}, Acc, Blocks, BlockAcc) ->
|
||||||
{RestRef, RestBlk} = fresh_block(Code, Catchall),
|
FreshBlk = fun(C, Ca) ->
|
||||||
|
R = make_ref(),
|
||||||
|
{R, [#blk{ref = R, code = C, catchall = Ca}]}
|
||||||
|
end,
|
||||||
|
{RestRef, RestBlk} = FreshBlk(Code, Catchall),
|
||||||
{DefRef, DefBlk} =
|
{DefRef, DefBlk} =
|
||||||
case Default of
|
case Default of
|
||||||
missing when Catchall == none ->
|
missing when Catchall == none ->
|
||||||
fresh_block([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none);
|
FreshBlk([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none);
|
||||||
missing -> {Catchall, []};
|
missing -> {Catchall, []};
|
||||||
_ -> fresh_block(Default ++ [{jump, RestRef}], Catchall)
|
_ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall)
|
||||||
%% ^ fall-through to the outer catchall
|
%% ^ fall-through to the outer catchall
|
||||||
end,
|
end,
|
||||||
%% If we don't generate a switch, we need to pop the argument if on the stack.
|
%% If we don't generate a switch, we need to pop the argument if on the stack.
|
||||||
@@ -1652,7 +1616,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
|||||||
{ThenRef, ThenBlk} =
|
{ThenRef, ThenBlk} =
|
||||||
case TrueCode of
|
case TrueCode of
|
||||||
missing -> {DefRef, []};
|
missing -> {DefRef, []};
|
||||||
_ -> fresh_block(TrueCode ++ [{jump, RestRef}], DefRef)
|
_ -> FreshBlk(TrueCode ++ [{jump, RestRef}], DefRef)
|
||||||
end,
|
end,
|
||||||
ElseCode =
|
ElseCode =
|
||||||
case FalseCode of
|
case FalseCode of
|
||||||
@@ -1687,7 +1651,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
|||||||
true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
|
true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
|
||||||
false ->
|
false ->
|
||||||
MkBlk = fun(missing) -> {DefRef, []};
|
MkBlk = fun(missing) -> {DefRef, []};
|
||||||
(ACode) -> fresh_block(ACode ++ [{jump, RestRef}], DefRef)
|
(ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef)
|
||||||
end,
|
end,
|
||||||
{AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)),
|
{AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)),
|
||||||
{Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)}
|
{Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)}
|
||||||
@@ -1703,7 +1667,7 @@ block(Blk = #blk{code = [I | Code]}, Acc, Blocks, BlockAcc) ->
|
|||||||
optimize_blocks(Blocks) ->
|
optimize_blocks(Blocks) ->
|
||||||
%% We need to look at the last instruction a lot, so reverse all blocks.
|
%% We need to look at the last instruction a lot, so reverse all blocks.
|
||||||
Rev = fun(Bs) -> [ {Ref, lists:reverse(Code)} || {Ref, Code} <- Bs ] end,
|
Rev = fun(Bs) -> [ {Ref, lists:reverse(Code)} || {Ref, Code} <- Bs ] end,
|
||||||
RBlocks = [{Ref, crop_jumps(Code)} || {Ref, Code} <- Blocks],
|
RBlocks = Rev(Blocks),
|
||||||
RBlockMap = maps:from_list(RBlocks),
|
RBlockMap = maps:from_list(RBlocks),
|
||||||
RBlocks1 = reorder_blocks(RBlocks, []),
|
RBlocks1 = reorder_blocks(RBlocks, []),
|
||||||
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
|
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
|
||||||
@@ -1785,18 +1749,6 @@ tweak_returns(['RETURN' | Code = [{'EXIT', _} | _]]) -> Code;
|
|||||||
tweak_returns(['RETURN' | Code = [loop | _]]) -> Code;
|
tweak_returns(['RETURN' | Code = [loop | _]]) -> Code;
|
||||||
tweak_returns(Code) -> Code.
|
tweak_returns(Code) -> Code.
|
||||||
|
|
||||||
%% -- Remove instructions that appear after jumps. Returns reversed code.
|
|
||||||
%% This is useful for example when bb emitter adds continuation jumps
|
|
||||||
%% for switch expressions, but some of the branches
|
|
||||||
crop_jumps(Code) ->
|
|
||||||
crop_jumps(Code, []).
|
|
||||||
crop_jumps([], Acc) ->
|
|
||||||
Acc;
|
|
||||||
crop_jumps([I = {jump, _}|_], Acc) ->
|
|
||||||
[I|Acc];
|
|
||||||
crop_jumps([I|Code], Acc) ->
|
|
||||||
crop_jumps(Code, [I|Acc]).
|
|
||||||
|
|
||||||
%% -- Split basic blocks at CALL instructions --
|
%% -- Split basic blocks at CALL instructions --
|
||||||
%% Calls can only return to a new basic block. Also splits at JUMPIF instructions.
|
%% Calls can only return to a new basic block. Also splits at JUMPIF instructions.
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -26,7 +26,7 @@
|
|||||||
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
||||||
|
|
||||||
-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
|
-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 name() :: string().
|
||||||
-type id() :: {id, ann(), name()}.
|
-type id() :: {id, ann(), name()}.
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
| {contract_child, ann(), con(), [con()], [decl()]}
|
| {contract_child, ann(), con(), [con()], [decl()]}
|
||||||
| {contract_interface, ann(), con(), [con()], [decl()]}
|
| {contract_interface, ann(), con(), [con()], [decl()]}
|
||||||
| {namespace, ann(), con(), [decl()]}
|
| {namespace, ann(), con(), [decl()]}
|
||||||
|
| {include, ann(), {string, ann(), string()}}
|
||||||
| {pragma, ann(), pragma()}
|
| {pragma, ann(), pragma()}
|
||||||
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
||||||
| {type_def, ann(), id(), [tvar()], typedef()}
|
| {type_def, ann(), id(), [tvar()], typedef()}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{application, aesophia,
|
{application, aesophia,
|
||||||
[{description, "Compiler for Aeternity Sophia language"},
|
[{description, "Compiler for Aeternity Sophia language"},
|
||||||
{vsn, "6.1.0"},
|
{vsn, "7.0.0"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications,
|
{applications,
|
||||||
[kernel,
|
[kernel,
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ check_stub(Stub, Options) ->
|
|||||||
Ast ->
|
Ast ->
|
||||||
try
|
try
|
||||||
%% io:format("AST: ~120p\n", [Ast]),
|
%% 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} ->
|
catch throw:{type_errors, TE} ->
|
||||||
io:format("Type error:\n~s\n", [TE]),
|
io:format("Type error:\n~s\n", [TE]),
|
||||||
error(TE);
|
error(TE);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ calldata_aci_test_() ->
|
|||||||
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
|
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
|
||||||
fun() ->
|
fun() ->
|
||||||
ContractString = aeso_test_utils:read_contract(ContractName),
|
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),
|
ContractACI = binary_to_list(ContractACIBin),
|
||||||
io:format("ACI:\n~s\n", [ContractACIBin]),
|
io:format("ACI:\n~s\n", [ContractACIBin]),
|
||||||
FateExprs = ast_exprs(ContractACI, Fun, Args),
|
FateExprs = ast_exprs(ContractACI, Fun, Args),
|
||||||
|
|||||||
+105
-72
@@ -45,12 +45,6 @@ simple_compile_test_() ->
|
|||||||
check_errors(ExpectedErrors, Errors)
|
check_errors(ExpectedErrors, Errors)
|
||||||
end} ||
|
end} ||
|
||||||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
|
{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",
|
[ {"Testing include with explicit files",
|
||||||
fun() ->
|
fun() ->
|
||||||
FileSystem = maps:from_list(
|
FileSystem = maps:from_list(
|
||||||
@@ -208,6 +202,18 @@ compilable_contracts() ->
|
|||||||
"polymorphism_contract_interface_extensions",
|
"polymorphism_contract_interface_extensions",
|
||||||
"polymorphism_contract_interface_same_decl_multi_interface",
|
"polymorphism_contract_interface_same_decl_multi_interface",
|
||||||
"polymorphism_contract_interface_same_name_same_type",
|
"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.
|
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||||
].
|
].
|
||||||
|
|
||||||
@@ -289,34 +295,26 @@ failing_contracts() ->
|
|||||||
|
|
||||||
%% Type errors
|
%% Type errors
|
||||||
, ?TYPE_ERROR(name_clash,
|
, ?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"
|
"Duplicate definitions of `abort` at\n"
|
||||||
" - (builtin location)\n"
|
" - (builtin location)\n"
|
||||||
" - line 14, column 3">>,
|
" - line 7, column 3">>,
|
||||||
<<?Pos(15, 3)
|
<<?Pos(8, 3)
|
||||||
"Duplicate definitions of `require` at\n"
|
"Duplicate definitions of `require` at\n"
|
||||||
" - (builtin location)\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">>,
|
" - line 8, column 3">>,
|
||||||
<<?Pos(16, 3)
|
<<?Pos(9, 3)
|
||||||
"Duplicate definitions of `put` at\n"
|
"Duplicate definitions of `put` at\n"
|
||||||
" - (builtin location)\n"
|
" - (builtin location)\n"
|
||||||
" - line 16, column 3">>,
|
" - line 9, column 3">>,
|
||||||
<<?Pos(17, 3)
|
<<?Pos(10, 3)
|
||||||
"Duplicate definitions of `state` at\n"
|
"Duplicate definitions of `state` at\n"
|
||||||
" - (builtin location)\n"
|
" - (builtin location)\n"
|
||||||
" - line 17, column 3">>])
|
" - line 10, column 3">>])
|
||||||
, ?TYPE_ERROR(type_errors,
|
, ?TYPE_ERROR(type_errors,
|
||||||
[<<?Pos(17, 23)
|
[<<?Pos(17, 23)
|
||||||
"Unbound variable `zz`">>,
|
"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)`">>,
|
"when checking the type of the pattern `r10 : rec_inv(Animal)` against the expected type `Main.rec_inv(Cat)`">>,
|
||||||
<<?Pos(41,13)
|
<<?Pos(41,13)
|
||||||
"Cannot unify `Animal` and `Cat` in a invariant context\n"
|
"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,
|
, ?TYPE_ERROR(polymorphism_variance_switching_oracles,
|
||||||
[<<?Pos(15,13)
|
[<<?Pos(15,13)
|
||||||
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
|
"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)`">>,
|
"when checking the type of the pattern `q14 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Animal, Cat)`">>,
|
||||||
<<?Pos(44,13)
|
<<?Pos(44,13)
|
||||||
"Cannot unify `Animal` and `Cat` in a covariant context\n"
|
"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)`">>])
|
"when checking the type of the pattern `q15 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Cat, Animal)`">>
|
||||||
].
|
])
|
||||||
|
, ?TYPE_ERROR(missing_definition,
|
||||||
-define(Path(File), "code_errors/" ??File).
|
[<<?Pos(2,14)
|
||||||
-define(Msg(File, Line, Col, Err), <<?Pos("Code generation", ?Path(File), Line, Col) Err>>).
|
"Missing definition of function `foo`">>
|
||||||
|
])
|
||||||
-define(FATE_ERR(File, Line, Col, Err), {?Path(File), ?Msg(File, Line, Col, Err)}).
|
, ?TYPE_ERROR(child_with_decls,
|
||||||
|
[<<?Pos(2,14)
|
||||||
failing_code_gen_contracts() ->
|
"Missing definition of function `f`">>
|
||||||
[ ?FATE_ERR(missing_definition, 2, 14,
|
])
|
||||||
"Missing definition of function 'foo'.")
|
, ?TYPE_ERROR(parameterised_state,
|
||||||
, ?FATE_ERR(higher_order_entrypoint, 2, 20,
|
[<<?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"
|
"The argument\n"
|
||||||
" f : (int) => int\n"
|
" `f : (int) => int`\n"
|
||||||
"of entrypoint 'apply' has a higher-order (contains function types) type.")
|
"of entrypoint `apply` has a higher-order (contains function types) type">>
|
||||||
, ?FATE_ERR(higher_order_entrypoint_return, 2, 3,
|
])
|
||||||
|
, ?TYPE_ERROR(higher_order_entrypoint_return,
|
||||||
|
[<<?Pos(2,3)
|
||||||
"The return type\n"
|
"The return type\n"
|
||||||
" (int) => int\n"
|
" `(int) => int`\n"
|
||||||
"of entrypoint 'add' is higher-order (contains function types).")
|
"of entrypoint `add` is higher-order (contains function types)">>
|
||||||
, ?FATE_ERR(missing_init_function, 1, 10,
|
])
|
||||||
"Missing init function for the contract 'MissingInitFunction'.\n"
|
, ?TYPE_ERROR(polymorphic_aens_resolve,
|
||||||
"The 'init' function can only be omitted if the state type is 'unit'.")
|
[<<?Pos(4,5)
|
||||||
, ?FATE_ERR(parameterised_state, 3, 8,
|
"Invalid return type of `AENS.resolve`:\n"
|
||||||
"The state type cannot be parameterized.")
|
" `'a`\n"
|
||||||
, ?FATE_ERR(parameterised_event, 3, 12,
|
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
|
||||||
"The event type cannot be parameterized.")
|
])
|
||||||
, ?FATE_ERR(polymorphic_aens_resolve, 4, 5,
|
, ?TYPE_ERROR(bad_aens_resolve,
|
||||||
"Invalid return type of AENS.resolve:\n"
|
[<<?Pos(6,5)
|
||||||
" 'a\n"
|
"Invalid return type of `AENS.resolve`:\n"
|
||||||
"It must be a string or a pubkey type (address, oracle, etc).")
|
" `list(int)`\n"
|
||||||
, ?FATE_ERR(bad_aens_resolve, 6, 5,
|
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
|
||||||
"Invalid return type of AENS.resolve:\n"
|
])
|
||||||
" list(int)\n"
|
, ?TYPE_ERROR(bad_aens_resolve_using,
|
||||||
"It must be a string or a pubkey type (address, oracle, etc).")
|
[<<?Pos(7,5)
|
||||||
, ?FATE_ERR(polymorphic_query_type, 3, 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"
|
"Invalid oracle type\n"
|
||||||
" oracle('a, 'b)\n"
|
" `oracle('a, 'b)`\n"
|
||||||
"The query type must not be polymorphic (contain type variables).")
|
"The query type must not be polymorphic (contain type variables)">>,
|
||||||
, ?FATE_ERR(polymorphic_response_type, 3, 5,
|
<<?Pos(3,5)
|
||||||
"Invalid oracle type\n"
|
"Invalid oracle type\n"
|
||||||
" oracle(string, 'r)\n"
|
" `oracle('a, 'b)`\n"
|
||||||
"The response type must not be polymorphic (contain type variables).")
|
"The response type must not be polymorphic (contain type variables)">>
|
||||||
, ?FATE_ERR(higher_order_query_type, 3, 5,
|
])
|
||||||
|
, ?TYPE_ERROR(polymorphic_response_type,
|
||||||
|
[<<?Pos(3,5)
|
||||||
"Invalid oracle type\n"
|
"Invalid oracle type\n"
|
||||||
" oracle((int) => int, string)\n"
|
" `oracle(string, 'r)`\n"
|
||||||
"The query type must not be higher-order (contain function types).")
|
"The response type must not be polymorphic (contain type variables)">>
|
||||||
, ?FATE_ERR(higher_order_response_type, 3, 5,
|
])
|
||||||
|
, ?TYPE_ERROR(higher_order_query_type,
|
||||||
|
[<<?Pos(3,5)
|
||||||
"Invalid oracle type\n"
|
"Invalid oracle type\n"
|
||||||
" oracle(string, (int) => int)\n"
|
" `oracle((int) => int, string)`\n"
|
||||||
"The response type must not be higher-order (contain function types).")
|
"The query type must not be higher-order (contain function types)">>
|
||||||
, ?FATE_ERR(child_with_decls, 2, 14,
|
])
|
||||||
"Missing definition of function 'f'.")
|
, ?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_() ->
|
validation_test_() ->
|
||||||
|
|||||||
+12
-1
@@ -1,5 +1,7 @@
|
|||||||
|
contract C = entrypoint init() = ()
|
||||||
|
|
||||||
// AENS tests
|
// AENS tests
|
||||||
contract AENSTest =
|
main contract AENSTest =
|
||||||
|
|
||||||
// Name resolution
|
// Name resolution
|
||||||
|
|
||||||
@@ -9,6 +11,15 @@ contract AENSTest =
|
|||||||
stateful entrypoint resolve_string(name : string, key : string) : option(string) =
|
stateful entrypoint resolve_string(name : string, key : string) : option(string) =
|
||||||
AENS.resolve(name, key)
|
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
|
// Transactions
|
||||||
|
|
||||||
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
|
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
|
||||||
|
|||||||
@@ -80,3 +80,4 @@ contract AllSyntax =
|
|||||||
let sh : shakespeare(shakespeare(int)) =
|
let sh : shakespeare(shakespeare(int)) =
|
||||||
{wolfgang = state}
|
{wolfgang = state}
|
||||||
sh{wolfgang.wolfgang = sh.wolfgang} // comment
|
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 =
|
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(x) = x
|
||||||
entrypoint double_def(y) = 0
|
entrypoint double_def(y) = 0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user