Compare commits

..

15 Commits

Author SHA1 Message Date
Gaith Hallak 21cc6f2b3e Add constrained_t to fold 2022-06-20 10:28:17 +04:00
Gaith Hallak e8da0a7cfe Update docs/sophia_features.md
Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2022-06-19 21:44:47 +04:00
Gaith Hallak 4562a7166c Fix the tests after changing address to ord 2022-06-19 21:07:41 +04:00
Gaith Hallak fc2875965e Make address comparable by inequality 2022-06-19 20:54:58 +04:00
Gaith Hallak 9d296f04cb Formatting fix 2022-06-19 19:19:15 +04:00
Gaith Hallak ea98fc97bb Add an example of address type to the docs 2022-06-19 19:18:10 +04:00
Gaith Hallak 46ac9bfa82 Update the docs 2022-06-14 22:19:35 +04:00
Gaith Hallak 75f2711148 Update CHANGELOG 2022-06-14 19:04:20 +04:00
Gaith Hallak 8e6c6d81ad Add char type to the docs 2022-06-14 18:29:27 +04:00
Gaith Hallak 2d17ce3ee2 Add test for unknown tvar constraints 2022-06-14 18:29:27 +04:00
Gaith Hallak fc08fe09a5 Add tests for calling constrained functions 2022-06-14 18:29:27 +04:00
Gaith Hallak 6c17e57a7c Fix dialyzer warnings 2022-06-14 18:29:27 +04:00
Gaith Hallak 69713036d0 Add constraints to typechecker, fix old tests, add new ones 2022-06-14 18:29:26 +04:00
Gaith Hallak f56eeb0b2b Use id() for constraints instead of keywords 2022-06-14 18:26:22 +04:00
Gaith Hallak 3f177d363f Add constraints to the syntax and parser 2022-06-14 18:25:21 +04:00
81 changed files with 894 additions and 1452 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ version: 2.1
executors:
aebuilder:
docker:
- image: aeternity/builder:bionic-otp24
- image: aeternity/builder:xenial-otp21
user: builder
working_directory: ~/aesophia
+4
View File
@@ -13,6 +13,10 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: 3.8
- uses: actions/cache@v2
with:
path: ~/.cache/pip3
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
- run: pip3 install -r .github/workflows/requirements.txt -U
- run: git config --global user.email "github-action@users.noreply.github.com"
- run: git config --global user.name "GitHub Action"
+4
View File
@@ -13,6 +13,10 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: 3.8
- uses: actions/cache@v2
with:
path: ~/.cache/pip3
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
- run: pip3 install -r .github/workflows/requirements.txt -U
- run: git config --global user.email "github-action@users.noreply.github.com"
- run: git config --global user.name "GitHub Action"
+4 -4
View File
@@ -1,5 +1,5 @@
mkdocs==1.2.4
mkdocs-simple-hooks==0.1.5
mkdocs-material==7.3.6
mike==1.1.2
pygments==2.12.0
mkdocs-simple-hooks==0.1.3
mkdocs-material==7.1.9
mike==1.0.1
pygments==2.11.2
+10 -10
View File
@@ -6,13 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
### 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.
- 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.
- The pipe operator |>
```
[1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3]))
@@ -22,7 +16,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
function sum(l : list(int)) : int = foldl((+), 0, l)
function logical_and(x, y) = (&&)(x, y)
```
- Contract interfaces polymorphism
- Add comparable typevar constraints (`ord` and `eq`)
```
lt : 'a is ord ; ('a, 'a) => bool
lt(x, y) = x < y
is_eq : 'a is eq ; ('a, 'a) => bool
is_eq(x, y) = x == y
```
### Changed
- Error messages have been restructured (less newlines) to provide more unified errors. Also `pp_oneline/1` has been added.
- Ban empty record definitions (e.g. `record r = {}` would give an error).
@@ -352,8 +353,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.0.0...HEAD
[7.0.0]: https://github.com/aeternity/aesophia/compare/v6.1.0...v7.0.0
[Unreleased]: https://github.com/aeternity/aesophia/compare/v6.1.0...HEAD
[6.1.0]: https://github.com/aeternity/aesophia/compare/v6.0.2...v6.1.0
[6.0.2]: https://github.com/aeternity/aesophia/compare/v6.0.1...v6.0.2
[6.0.1]: https://github.com/aeternity/aesophia/compare/v6.0.0...v6.0.1
+76 -181
View File
@@ -134,155 +134,6 @@ main contract IntHolderFactory =
In case of a presence of child contracts (`IntHolder` in this case), the main
contract must be pointed out with the `main` keyword as shown in the example.
### Contract interfaces and polymorphism
Contracts can implement one or multiple interfaces, the contract has to define
every entrypoint from the implemented interface and the entrypoints in both
the contract and implemented interface should have compatible types.
```
contract interface Animal =
entrypoint sound : () => string
contract Cat : Animal =
entrypoint sound() = "Cat sound"
```
Contract interfaces can extend other interfaces. An extended interface has to
declare all entrypoints from every parent interface. All the declarations in the extended
interface must have types compatible with the declarations from the parent
interface.
```
contract interface II =
entrypoint f : () => unit
contract interface I : II =
entrypoint f : () => unit
entrypoint g : () => unit
contract C : I =
entrypoint f() = ()
entrypoint g() = ()
```
It is only possible to implement (or extend) an interface that has been already
defined earlier in the file (or in an included file). Therefore recursive
interface implementation is not allowed in Sophia.
```
// The following code would show an error
contract interface X : Z =
entrypoint x : () => int
contract interface Y : X =
entrypoint x : () => int
entrypoint y : () => int
contract interface Z : Y =
entrypoint x : () => int
entrypoint y : () => int
entrypoint z : () => int
contract C : Z =
entrypoint x() = 1
entrypoint y() = 1
entrypoint z() = 1
```
#### Subtyping and variance
Subtyping in Sophia follows common rules that take type variance into account. As described by [Wikipedia](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)),
>Variance refers to how subtyping between more complex types relates to subtyping between their components.
This concept plays an important role in complex types such as tuples, `datatype`s and functions. Depending on the context, it can apply to positions in the structure of a type, or type parameters of generic types. There are four kinds of variances:
- covariant
- contravariant
- invariant
- bivariant
A type is said to be on a "covariant" position when it describes output or a result of some computation. Analogously, position is "contravariant" when it is an input, or a parameter. Intuitively, when a part of the type is produced by values of it, it is covariant. When it is consumed, it is contravariant. When a type appears to be simultaneously input and output, it is described as invariant. If a type is neither of those (that is, it's unused) it's bivariant. Furthermore, whenever a complex type appears on a contravariant position, all its covariant components become contravariant and vice versa.
Variance influences how subtyping is applied. Types on covariant positions are subtyped normally, while contravariant the opposite way. Invariant types have to be exactly the same in order for subtyping to work. Bivariant types are always compatible.
A good example of where it matters can be pictured by subtyping of function types. Let us assume there is a contract interface `Animal` and two contracts that implement it: `Dog` and `Cat`.
```sophia
contract interface Animal =
entrypoint age : () => int
contract Dog : Animal =
entrypoint age() = // ...
entrypoint woof() = "woof"
contract Cat : Animal =
entrypoint age() = // ...
entrypoint meow() = "meow"
```
The assumption of this exercise is that cats do not bark (because `Cat` does not define the `woof` entrypoint). If subtyping rules were applied naively, that is if we let `Dog => Dog` be a subtype of `Animal => Animal`, the following code would break:
```sophia
let f : (Dog) => string = d => d.woof()
let g : (Animal) => string = f
let c : Cat = Chain.create()
g(c) // Cat barking!
```
That is because arguments of functions are contravariant, as opposed to return the type which is covariant. Because of that, the assignment of `f` to `g` is invalid - while `Dog` is a subtype of `Animal`, `Dog => string` is **not** a subtype of `Animal => string`. However, `Animal => string` **is** a subtype of `Dog => string`. More than that, `(Dog => Animal) => Dog` is a subtype of `(Animal => Dog) => Animal`.
This has consequences on how user-defined generic types work. A type variable gains its variance from its role in the type definition as shown in the example:
```sophia
datatype co('a) = Co('a) // co is covariant on 'a
datatype ct('a) = Ct('a => unit) // ct is contravariant on 'a
datatype in('a) = In('a => 'a) // in is invariant on 'a
datatype bi('a) = Bi // bi is bivariant on 'a
```
The following facts apply here:
- `co('a)` is a subtype of `co('b) when `'a` is a subtype of `'b`
- `ct('a)` is a subtype of `ct('b) when `'b` is a subtype of `'a`
- `in('a)` is a subtype of `in('b) when `'a` is equal to `'b`
- `bi('a)` is a subtype of `bi('b) always
That altogether induce the following rules of subtyping in Sophia:
- A function type `(Args1) => Ret1` is a subtype of `(Args2) => Ret2` when `Ret1`
is a subtype of `Ret2` and each argument type from `Args2` is a subtype of its
counterpart in `Args1`.
- A list type `list(A)` is a subtype of `list(B)` if `A` is a subtype of `B`.
- An option type `option(A)` is a subtype of `option(B)` if `A` is a subtype of `B`.
- A map type `map(A1, A2)` is a subtype of `map(B1, B2)` if `A1` is a subtype
of `B1`, and `A2` is a subtype of `B2`.
- An oracle type `oracle(A1, A2)` is a subtype of `oracle(B1, B2)` if `B1` is
a subtype of `A1`, and `A2` is a subtype of `B2`.
- An oracle_query type `oracle_query(A1, A2)` is a subtype of `oracle_query(B1, B2)`
if `A1` is a subtype of `B1`, and `A2` is a subtype of `B2`.
- A user-defined datatype `t(Args1)` is a subtype of `t(Args2)`
- When a user-defined type `t('a)` is covariant in `'a`, then `t(A)` is a
subtype of `t(B)` when `A` is a subtype of `B`.
- When a user-defined type `t('a)` is contravariant in `'a`, then `t(A)` is a
subtype of `t(B)` when `B` is a subtype of `A`.
- When a user-defined type `t('a)` is binvariant in `'a`, then `t(A)` is a
subtype of `t(B)` when either `A` is a subtype of `B` or when `B` is a subtype
of `A`.
- When a user-defined type `t('a)` is invariant in `'a`, then `t(A)` can never be
a subtype of `t(B)`.
## Mutable state
@@ -503,28 +354,29 @@ namespace C =
## Types
Sophia has the following types:
| Type | Description | Example |
|----------------------|---------------------------------------------------------------------------------------------|--------------------------------------------------------------|
| int | A 2-complement integer | ```-1``` |
| address | æternity address, 32 bytes | ```Call.origin``` |
| bool | A Boolean | ```true``` |
| bits | A bit field | ```Bits.none``` |
| bytes(n) | A byte array with `n` bytes | ```#fedcba9876543210``` |
| string | An array of bytes | ```"Foo"``` |
| list | A homogeneous immutable singly linked list. | ```[1, 2, 3]``` |
| ('a, 'b) => 'c | A function. Parentheses can be skipped if there is only one argument | ```(x : int, y : int) => x + y``` |
| tuple | An ordered heterogeneous array | ```(42, "Foo", true)``` |
| record | An immutable key value store with fixed key names and typed values | ``` record balance = { owner: address, value: int } ``` |
| map | An immutable key value store with dynamic mapping of keys of one type to values of one type | ```type accounts = map(string, address)``` |
| option('a) | An optional value either None or Some('a) | ```Some(42)``` |
| state | A user defined type holding the contract state | ```record state = { owner: address, magic_key: bytes(4) }``` |
| event | An append only list of blockchain events (or log entries) | ```datatype event = EventX(indexed int, string)``` |
| hash | A 32-byte hash - equivalent to `bytes(32)` | |
| signature | A signature - equivalent to `bytes(64)` | |
| Chain.ttl | Time-to-live (fixed height or relative to current block) | ```FixedTTL(1050)``` ```RelativeTTL(50)``` |
| oracle('a, 'b) | And oracle answering questions of type 'a with answers of type 'b | ```Oracle.register(acct, qfee, ttl)``` |
| oracle_query('a, 'b) | A specific oracle query | ```Oracle.query(o, q, qfee, qttl, rttl)``` |
| contract | A user defined, typed, contract address | ```function call_remote(r : RemoteContract) = r.fun()``` |
| Type | Description | Example |
|----------------------|---------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------|
| int | A 2-complement integer | ```-1``` |
| char | A single character | ```'c'``` |
| address | æternity address, 32 bytes | ```Call.origin``` ```ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt``` |
| bool | A Boolean | ```true``` |
| bits | A bit field | ```Bits.none``` |
| bytes(n) | A byte array with `n` bytes | ```#fedcba9876543210``` |
| string | An array of bytes | ```"Foo"``` |
| list | A homogeneous immutable singly linked list. | ```[1, 2, 3]``` |
| ('a, 'b) => 'c | A function. Parentheses can be skipped if there is only one argument | ```(x : int, y : int) => x + y``` |
| tuple | An ordered heterogeneous array | ```(42, "Foo", true)``` |
| record | An immutable key value store with fixed key names and typed values | ``` record balance = { owner: address, value: int } ``` |
| map | An immutable key value store with dynamic mapping of keys of one type to values of one type | ```type accounts = map(string, address)``` |
| option('a) | An optional value either None or Some('a) | ```Some(42)``` |
| state | A user defined type holding the contract state | ```record state = { owner: address, magic_key: bytes(4) }``` |
| event | An append only list of blockchain events (or log entries) | ```datatype event = EventX(indexed int, string)``` |
| hash | A 32-byte hash - equivalent to `bytes(32)` | |
| signature | A signature - equivalent to `bytes(64)` | |
| Chain.ttl | Time-to-live (fixed height or relative to current block) | ```FixedTTL(1050)``` ```RelativeTTL(50)``` |
| oracle('a, 'b) | And oracle answering questions of type 'a with answers of type 'b | ```Oracle.register(acct, qfee, ttl)``` |
| oracle_query('a, 'b) | A specific oracle query | ```Oracle.query(o, q, qfee, qttl, rttl)``` |
| contract | A user defined, typed, contract address | ```function call_remote(r : RemoteContract) = r.fun()``` |
## Literals
| Type | Constant/Literal example(s) |
@@ -650,6 +502,59 @@ function
Guards cannot be stateful even when used inside a stateful function.
## Comparable types
Only certain types are allowed to be compared by equality (`==`, `!=`) and
inequality (`<`, `>`, `=<`, `>=`). For instance, while it is legal to compare
integers, comparing functions would lead to an error:
```
function f() =
f == f // type error
```
The rules apply as follows:
- All types that are comparable by inequality are also comparable by equality.
- The builtin types `bool`, `int`, `char`, `bits`, `bytes`, `string`, `unit`,
`hash`, `address` and `signature` are comparable by inequality (and thus by
equality).
- The composite types `list`, `option`, and tuples are comparable by
equality/inequality if their type parameters are comparable by
equality/inequality.
- The composite types `map`, `oracle`, and `oracle_query` are comparable by
equality if their type parameters are comparable by equality.
- User-defined records and datatypes are comparable by equality if their type
parameters are comparable by equality.
- Smart contracts are comparable by equality.
- User-declared type variables are comparable according to the [type
constraints](#type-constraints) given in the function signature.
In all other cases the types are not comparable.
### Type constraints
Polymorphic types are not declared as comparable by default. If the user
specifies the type signature for a function, they need to manually declare type
constraints in order to allow the variables to be compared. This can only be
done if the type declaration is separated from the function definition. The
constraints have to be prepended to the type declaration and separated with a
semicolon:
```
function eq(x : 'a, y : 'a) = x == y // Type error, 'a is not comparable
function
eq : 'a is eq ; ('a, 'a) => bool
eq(x, y) = x == y // Compiles
function eq(x, y) = x == y // Compiles as the constraints are inferred
```
Currently only two constraints are allowed: `eq` for equality and `ord` for
inequality. Declaring a type as `ord` automatically implies `eq`.
## Lists
A Sophia list is a dynamically sized, homogenous, immutable, singly
@@ -1011,16 +916,6 @@ function require(b : bool, err : string) =
if(!b) abort(err)
```
Aside from that, there is an almost equivalent function `exit`
```sophia
exit(reason : string) : 'a
```
Just like `abort`, it breaks the execution with the given reason. The difference
however is in the gas consumption — while `abort` returns unused gas, a call to
`exit` burns it all.
## Delegation signature
Some chain operations (`Oracle.<operation>` and `AENS.<operation>`) have an
+2 -2
View File
@@ -812,8 +812,8 @@ Registers new oracle answering questions of type `'a` with answers of type `'b`.
private key of the account, proving you have the private key of the oracle to be. If the
address is the same as the contract `sign` is ignored and can be left out entirely.
* The `qfee` is the minimum query fee to be paid by a user when asking a question of the oracle.
* The `ttl` is the Time To Live for the oracle in key blocks, either relative to the current
key block height (`RelativeTTL(delta)`) or a fixed key block height (`FixedTTL(height)`).
* The `ttl` is the Time To Live for the oracle, either relative to the current
height (`RelativeTTL(delta)`) or a fixed height (`FixedTTL(height)`).
* The type `'a` is the type of the question to ask.
* The type `'b` is the type of the oracle answers.
+5 -3
View File
@@ -29,9 +29,11 @@ namespace List =
[] => abort("drop_last_unsafe: list empty")
function contains(e : 'a, l : list('a)) = switch(l)
[] => false
h::t => h == e || contains(e, t)
function
contains : 'a is eq; ('a, list('a)) => bool
contains(e, l) = switch(l)
[] => false
h::t => h == e || contains(e, t)
/** Finds first element of `l` fulfilling predicate `p` as `Some` or `None`
* if no such element exists.
+3 -1
View File
@@ -30,7 +30,9 @@ namespace Option =
None => abort(err)
Some(x) => x
function contains(e : 'a, o : option('a)) = o == Some(e)
function
contains : 'a is eq; ('a, option('a)) => bool
contains(e, o) = o == Some(e)
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)
+3 -2
View File
@@ -90,6 +90,7 @@ namespace String =
Some(ix)
private function
is_prefix : (list(char), list(char)) => option(list(char))
is_prefix([], ys) = Some(ys)
is_prefix(_, []) = None
is_prefix(x :: xs, y :: ys) =
@@ -98,10 +99,10 @@ namespace String =
private function
to_int_([], _, x, _) = Some(x)
to_int_(i :: is, value, x, b) =
to_int_(i :: ints, value, x, b) =
switch(value(i))
None => None
Some(n) => to_int_(is, value, x * b + n, b)
Some(i) => to_int_(ints, value, x * b + i, b)
private function ch_to_int_10(ch) =
let c = Char.to_int(ch)
+4 -3
View File
@@ -2,10 +2,11 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.1.1"}}}
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"0699f35"}}}
, {getopt, "1.0.1"}
, {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}}
]}.
{dialyzer, [
@@ -14,7 +15,7 @@
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}.
{relx, [{release, {aesophia, "7.0.0"},
{relx, [{release, {aesophia, "6.1.0"},
[aesophia, aebytecode, getopt]},
{dev_mode, true},
+5 -8
View File
@@ -1,11 +1,11 @@
{"1.2.0",
{"1.1.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"8269dbd71e9011921c60141636f1baa270a0e784"}},
{ref,"0699f35b0398bac6cd4468da654d608375bd853d"}},
0},
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
{ref,"eb68fe331bd476910394966b7f5ede7a74d37e35"}},
{ref,"47aaa8f5434b365c50a35bfd1490340b19241991"}},
1},
{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git",
@@ -14,7 +14,7 @@
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
{<<"enacl">>,
{git,"https://github.com/aeternity/enacl.git",
{ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}},
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
2},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>,
@@ -24,8 +24,5 @@
[
{pkg_hash,[
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]},
{pkg_hash_ext,[
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>},
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]}
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
].
BIN
View File
Binary file not shown.
+3 -5
View File
@@ -83,7 +83,7 @@ from_typed_ast(Type, TypedAst) ->
string -> do_render_aci_json(JArray)
end.
encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HEAD(Head) ->
encode_contract(Contract = {Head, _, {con, _, Name}, _}) when ?IS_CONTRACT_HEAD(Head) ->
C0 = #{name => encode_name(Name)},
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
@@ -341,12 +341,10 @@ stateful(false) -> "".
%% #contract{Ann, Con, [Declarations]}.
contract_funcs({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
contract_funcs({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
[ D || D <- Decls, is_fun(D)].
contract_types({namespace, _, _, Decls}) ->
[ D || D <- Decls, is_type(D) ];
contract_types({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
contract_types({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
[ D || D <- Decls, is_type(D) ].
is_fun({letfun, _, _, _, _, _}) -> true;
+193 -403
View File
@@ -14,13 +14,8 @@
-export([ infer/1
, infer/2
, unfold_types_in_type/2
, unfold_types_in_type/3
, switch_scope/2
, pp_type/2
, lookup_env1/4
, name/1
, qname/1
]).
-include("aeso_utils.hrl").
@@ -92,11 +87,10 @@
-type byte_constraint() :: {is_bytes, utype()}
| {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}.
-type aens_resolve_constraint() :: {aens_resolve_type, utype()}.
-type oracle_type_constraint() :: {oracle_type, aeso_syntax:ann(), utype()}.
-type tvar_constraint() :: {is_eq, utype()}
| {is_ord, utype()}.
-type constraint() :: named_argument_constraint() | field_constraint() | byte_constraint()
| aens_resolve_constraint() | oracle_type_constraint().
-type constraint() :: named_argument_constraint() | field_constraint() | byte_constraint() | tvar_constraint().
-record(field_info,
{ ann :: aeso_syntax:ann()
@@ -122,8 +116,6 @@
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract | bytecode_hash.
-type variance() :: invariant | covariant | contravariant | bivariant.
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
-type type_info() :: {aeso_syntax:ann(), typedef()}.
-type var_info() :: {aeso_syntax:ann(), utype()}.
@@ -145,13 +137,11 @@
, vars = [] :: [{name(), var_info()}]
, typevars = unrestricted :: unrestricted | [name()]
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
, contract_parents = #{} :: #{ name() => [name()] }
, namespace = [] :: qname()
, used_namespaces = [] :: used_namespaces()
, in_pattern = false :: boolean()
, in_guard = false :: boolean()
, stateful = false :: boolean()
, unify_throws = true :: boolean()
, current_function = none :: none | aeso_syntax:id()
, what = top :: top | namespace | contract | contract_interface
}).
@@ -164,10 +154,6 @@
%% -- Environment manipulation -----------------------------------------------
-spec switch_scope(qname(), env()) -> env().
switch_scope(Scope, Env) ->
Env#env{namespace = Scope}.
-spec push_scope(namespace | contract, aeso_syntax:con(), env()) -> env().
push_scope(Kind, Con, Env) ->
Ann = aeso_syntax:get_ann(Con),
@@ -297,7 +283,7 @@ contract_call_type({fun_t, Ann, [], Args, Ret}) ->
Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}.
-spec bind_contract(aeso_syntax:decl(), env()) -> env().
bind_contract({Contract, Ann, Id, _Impls, Contents}, Env)
bind_contract({Contract, Ann, Id, Contents}, Env)
when ?IS_CONTRACT_HEAD(Contract) ->
Key = name(Id),
Sys = [{origin, system}],
@@ -412,7 +398,7 @@ lookup_env(Env, Kind, Ann, Name) ->
end
end.
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info() | type_info()}.
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info()}.
lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
Qual = lists:droplast(QName),
Name = lists:last(QName),
@@ -552,9 +538,8 @@ global_env() ->
%% TTL constructors
{"RelativeTTL", Fun1(Int, TTL)},
{"FixedTTL", Fun1(Int, TTL)},
%% Abort/exit
%% Abort
{"abort", Fun1(String, A)},
{"exit", Fun1(String, A)},
{"require", Fun([Bool, String], Unit)}])
, types = MkDefs(
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
@@ -836,13 +821,11 @@ infer(Contracts, Options) ->
ets_new(defined_contracts, [bag]),
ets_new(type_vars, [set]),
ets_new(warnings, [bag]),
ets_new(type_vars_variance, [set]),
%% Set the variance for builtin types
ets_insert(type_vars_variance, {"list", [covariant]}),
ets_insert(type_vars_variance, {"option", [covariant]}),
ets_insert(type_vars_variance, {"map", [covariant, covariant]}),
ets_insert(type_vars_variance, {"oracle", [contravariant, covariant]}),
ets_insert(type_vars_variance, {"oracle_query", [covariant, covariant]}),
%% Set the constraints for the builtin types
ets_new(ord_constraint_types, [set]),
OrdTypes = [ {"int"}, {"bool"}, {"bits"}, {"char"}, {"string"}, {"list"}, {"option"}, {"address"} ],
ets_insert(ord_constraint_types, OrdTypes),
when_warning(warn_unused_functions, fun() -> create_unused_functions() end),
check_modifiers(Env, Contracts),
@@ -871,12 +854,9 @@ infer(Contracts, Options) ->
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}.
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)};
infer1(Env0, [{Contract, Ann, ConName, Impls, Code} | Rest], Acc, Options)
infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options)
when ?IS_CONTRACT_HEAD(Contract) ->
%% do type inference on each contract independently.
Env = Env0#env{ contract_parents = maps:put(name(ConName),
[name(Impl) || Impl <- Impls],
Env0#env.contract_parents) },
check_scope_name_clash(Env, contract, ConName),
What = case Contract of
contract_main -> contract;
@@ -888,8 +868,7 @@ infer1(Env0, [{Contract, Ann, ConName, Impls, Code} | Rest], Acc, Options)
contract_interface -> ok
end,
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options),
Contract1 = {Contract, Ann, ConName, Impls, Code1},
check_implemented_interfaces(Env1, Contract1, Acc),
Contract1 = {Contract, Ann, ConName, Code1},
Env2 = pop_scope(Env1),
Env3 = bind_contract(Contract1, Env2),
infer1(Env3, Rest, [Contract1 | Acc], Options);
@@ -909,66 +888,17 @@ infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
%% Pragmas are checked in check_modifiers
infer1(Env, Rest, Acc, Options).
check_implemented_interfaces(Env, {_Contract, _Ann, ConName, Impls, Code}, DefinedContracts) ->
create_type_errors(),
AllInterfaces = [{name(IName), I} || I = {contract_interface, _, IName, _, _} <- DefinedContracts],
ImplsNames = lists:map(fun name/1, Impls),
%% All implemented intrefaces should already be defined
lists:foreach(fun(Impl) -> case proplists:get_value(name(Impl), AllInterfaces) of
undefined -> type_error({referencing_undefined_interface, Impl});
_ -> ok
end
end, Impls),
ImplementedInterfaces = [I || I <- [proplists:get_value(Name, AllInterfaces) || Name <- ImplsNames],
I /= undefined],
Funs = [ Fun || Fun <- Code,
element(1, Fun) == letfun orelse element(1, Fun) == fun_decl ],
check_implemented_interfaces1(Env, ImplementedInterfaces, ConName, Funs, AllInterfaces),
destroy_and_report_type_errors(Env).
%% Recursively check that all directly and indirectly referenced interfaces are implemented
check_implemented_interfaces1(_, [], _, _, _) ->
ok;
check_implemented_interfaces1(Env, [{contract_interface, _, IName, _, Decls} | Interfaces],
ConId, Impls, AllInterfaces) ->
Unmatched = match_impls(Env, Decls, ConId, name(IName), Impls),
check_implemented_interfaces1(Env, Interfaces, ConId, Unmatched, AllInterfaces).
%% Match the functions of the contract with the interfaces functions, and return unmatched functions
match_impls(_, [], _, _, Impls) ->
Impls;
match_impls(Env, [{fun_decl, _, {id, _, FunName}, FunType = {fun_t, _, _, ArgsTypes, RetDecl}} | Decls], ConId, IName, Impls) ->
Match = fun({letfun, _, {id, _, FName}, Args, RetFun, _}) when FName == FunName ->
length(ArgsTypes) == length(Args) andalso
unify(Env#env{unify_throws = false}, RetDecl, RetFun, unknown) andalso
lists:all(fun({T1, {typed, _, _, T2}}) -> unify(Env#env{unify_throws = false}, T1, T2, unknown) end,
lists:zip(ArgsTypes, Args));
({fun_decl, _, {id, _, FName}, FunT}) when FName == FunName ->
unify(Env#env{unify_throws = false}, FunT, FunType, unknown);
(_) -> false
end,
UnmatchedImpls = case lists:search(Match, Impls) of
{value, V} ->
lists:delete(V, Impls);
false ->
type_error({unimplemented_interface_function, ConId, IName, FunName}),
Impls
end,
match_impls(Env, Decls, ConId, IName, UnmatchedImpls).
%% Asserts that the main contract is somehow defined.
identify_main_contract(Contracts, Options) ->
Children = [C || C = {contract_child, _, _, _, _} <- Contracts],
Mains = [C || C = {contract_main, _, _, _, _} <- Contracts],
Children = [C || C = {contract_child, _, _, _} <- Contracts],
Mains = [C || C = {contract_main, _, _, _} <- Contracts],
case Mains of
[] -> case Children of
[] -> type_error(
{main_contract_undefined,
[{file, File} || {src_file, File} <- Options]});
[{contract_child, Ann, Con, Impls, Body}] ->
(Contracts -- Children) ++ [{contract_main, Ann, Con, Impls, Body}];
[{contract_child, Ann, Con, Body}] ->
(Contracts -- Children) ++ [{contract_main, Ann, Con, Body}];
[H|_] -> type_error({ambiguous_main_contract,
aeso_syntax:get_ann(H)})
end;
@@ -1027,9 +957,6 @@ infer_contract(Env0, What, Defs0, Options) ->
contract -> bind_state(Env1) %% bind state and put
end,
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype, Defs) ]),
[ type_error({missing_definition, Id}) || {fun_decl, _, Id, _} <- Decls,
What =:= contract,
get_option(no_code, false) =:= false ],
Env3 = bind_funs(ProtoSigs, Env2),
Functions = Get(function, Defs),
%% Check for duplicates in Functions (we turn it into a map below)
@@ -1044,10 +971,8 @@ infer_contract(Env0, What, Defs0, Options) ->
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
%% Remove namespaces used in the current namespace
Env5 = Env4#env{ used_namespaces = OldUsedNamespaces },
%% Check that `init` doesn't read or write the state and that `init` is not missing
check_state(Env4, Defs1),
%% Check that entrypoints have first-order arg types and return types
check_entrypoints(Defs1),
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env4, Defs1),
destroy_and_report_type_errors(Env4),
%% Add inferred types of definitions
{Env5, TypeDefs ++ Decls ++ Defs1}.
@@ -1114,7 +1039,6 @@ check_typedef_sccs(Env, TypeMap, [{acyclic, Name} | SCCs], Acc) ->
case maps:get(Name, TypeMap, undefined) of
undefined -> check_typedef_sccs(Env, TypeMap, SCCs, Acc); %% Builtin type
{type_def, Ann, D, Xs, Def0} ->
check_parameterizable(D, Xs),
Def = check_event(Env, Name, Ann, check_typedef(bind_tvars(Xs, Env), Def0)),
Acc1 = [{type_def, Ann, D, Xs, Def} | Acc],
Env1 = bind_type(Name, Ann, {Xs, Def}, Env),
@@ -1124,15 +1048,11 @@ check_typedef_sccs(Env, TypeMap, [{acyclic, Name} | SCCs], Acc) ->
type_error({empty_record_definition, Ann, Name}),
check_typedef_sccs(Env1, TypeMap, SCCs, Acc1);
{record_t, Fields} ->
ets_insert(type_vars_variance, {Env#env.namespace ++ qname(D),
infer_type_vars_variance(Xs, Fields)}),
%% check_type to get qualified name
RecTy = check_type(Env1, app_t(Ann, D, Xs)),
Env2 = check_fields(Env1, TypeMap, RecTy, Fields),
check_typedef_sccs(Env2, TypeMap, SCCs, Acc1);
{variant_t, Cons} ->
ets_insert(type_vars_variance, {Env#env.namespace ++ qname(D),
infer_type_vars_variance(Xs, Cons)}),
Target = check_type(Env1, app_t(Ann, D, Xs)),
ConType = fun([]) -> Target; (Args) -> {type_sig, Ann, none, [], Args, Target} end,
ConTypes = [ begin
@@ -1158,48 +1078,6 @@ check_typedef(Env, {variant_t, Cons}) ->
{variant_t, [ {constr_t, Ann, Con, [ check_type(Env, Arg) || Arg <- Args ]}
|| {constr_t, Ann, Con, Args} <- Cons ]}.
infer_type_vars_variance(TypeParams, Cons) ->
% args from all type constructors
FlatArgs = lists:flatten([Args || {constr_t, _, _, Args} <- Cons]) ++ [Type || {field_t, _, _, Type} <- Cons],
Vs = lists:flatten([infer_type_vars_variance(Arg) || Arg <- FlatArgs]),
lists:map(fun({tvar, _, TVar}) ->
S = sets:from_list([Variance || {TV, Variance} <- Vs, TV == TVar]),
IsCovariant = sets:is_element(covariant, S),
IsContravariant = sets:is_element(contravariant, S),
case {IsCovariant, IsContravariant} of
{true, true} -> invariant;
{true, false} -> covariant;
{false, true} -> contravariant;
{false, false} -> bivariant
end
end, TypeParams).
-spec infer_type_vars_variance(utype()) -> [{name(), variance()}].
infer_type_vars_variance(Types)
when is_list(Types) ->
lists:flatten([infer_type_vars_variance(T) || T <- Types]);
infer_type_vars_variance({app_t, _, Type, Args}) ->
Variances = case ets_lookup(type_vars_variance, qname(Type)) of
[{_, Vs}] -> Vs;
_ -> lists:duplicate(length(Args), covariant)
end,
TypeVarsVariance = [{TVar, Variance}
|| {{tvar, _, TVar}, Variance} <- lists:zip(Args, Variances)],
TypeVarsVariance;
infer_type_vars_variance({tvar, _, TVar}) -> [{TVar, covariant}];
infer_type_vars_variance({fun_t, _, [], Args, Res}) ->
ArgsVariance = infer_type_vars_variance(Args),
ResVariance = infer_type_vars_variance(Res),
FlippedArgsVariance = lists:map(fun({TVar, Variance}) -> {TVar, opposite_variance(Variance)} end, ArgsVariance),
FlippedArgsVariance ++ ResVariance;
infer_type_vars_variance(_) -> [].
opposite_variance(invariant) -> invariant;
opposite_variance(covariant) -> contravariant;
opposite_variance(contravariant) -> covariant;
opposite_variance(bivariant) -> bivariant.
check_usings(Env, []) ->
Env;
check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con, Alias, Parts} | Rest]) ->
@@ -1245,7 +1123,7 @@ check_modifiers(Env, Contracts) ->
check_modifiers_(Env, Contracts),
destroy_and_report_type_errors(Env).
check_modifiers_(Env, [{Contract, _, Con, _Impls, Decls} | Rest])
check_modifiers_(Env, [{Contract, _, Con, Decls} | Rest])
when ?IS_CONTRACT_HEAD(Contract) ->
IsInterface = Contract =:= contract_interface,
check_modifiers1(contract, Decls),
@@ -1308,6 +1186,14 @@ check_modifiers1(What, Decl) when element(1, Decl) == letfun; element(1, Decl) =
ok;
check_modifiers1(_, _) -> ok.
extract_typevars(Type) ->
case Type of
TVar = {tvar, _, _} -> [TVar];
Tup when is_tuple(Tup) -> extract_typevars(tuple_to_list(Tup));
[H | T] -> extract_typevars(H) ++ extract_typevars(T);
_ -> []
end.
-spec check_type(env(), aeso_syntax:type()) -> aeso_syntax:type().
check_type(Env, T) ->
check_type(Env, T, 0).
@@ -1350,6 +1236,15 @@ check_type(Env, Type = {fun_t, Ann, NamedArgs, Args, Ret}, Arity) ->
check_type(_Env, Type = {uvar, _, _}, Arity) ->
ensure_base_type(Type, Arity),
Type;
check_type(Env, {constrained_t, Ann, Constraints, Type}, Arity) ->
when_warning(warn_duplicated_constraints, fun() -> warn_duplicated_constraints(Constraints) end),
TVars = [ Name || {tvar, _, Name} <- extract_typevars(Type) ],
[ type_error({unused_constraint, C}) || C = {constraint, _, {tvar, _, Name}, _} <- Constraints,
not lists:member(Name, TVars) ],
[ type_error({unknown_tvar_constraint, C}) || C = {constraint, _, _, {id, _, Name}} <- Constraints,
not lists:member(Name, ["eq", "ord"]) ],
{constrained_t, Ann, Constraints, check_type(Env, Type, Arity)};
check_type(_Env, {args_t, Ann, Ts}, _) ->
type_error({new_tuple_syntax, Ann, Ts}),
{tuple_t, Ann, Ts}.
@@ -1370,13 +1265,6 @@ check_fields(Env, TypeMap, RecTy, [{field_t, Ann, Id, Type} | Fields]) ->
Env1 = bind_field(name(Id), #field_info{ ann = Ann, kind = record, field_t = Type, record_t = RecTy }, Env),
check_fields(Env1, TypeMap, RecTy, Fields).
check_parameterizable({id, Ann, "event"}, [_ | _]) ->
type_error({parameterized_event, Ann});
check_parameterizable({id, Ann, "state"}, [_ | _]) ->
type_error({parameterized_state, Ann});
check_parameterizable(_Name, _Xs) ->
ok.
check_event(Env, "event", Ann, Def) ->
case Def of
{variant_t, Cons} ->
@@ -1595,13 +1483,13 @@ app_t(Ann, Name, Args) -> {app_t, Ann, Name, Args}.
lookup_name(Env, As, Name) ->
lookup_name(Env, As, Name, []).
lookup_name(Env = #env{ namespace = NS, current_function = CurFn }, As, Id, Options) ->
lookup_name(Env = #env{ namespace = NS, current_function = {id, _, Fun} = CurFn }, As, Id, Options) ->
case lookup_env(Env, term, As, qname(Id)) of
false ->
type_error({unbound_variable, Id}),
{Id, fresh_uvar(As)};
{QId, {_, Ty}} ->
when_warning(warn_unused_variables, fun() -> used_variable(NS, name(CurFn), QId) end),
when_warning(warn_unused_variables, fun() -> used_variable(NS, Fun, QId) end),
when_warning(warn_unused_functions,
fun() -> register_function_call(NS ++ qname(CurFn), QId) end),
Freshen = proplists:get_value(freshen, Options, false),
@@ -1639,52 +1527,8 @@ check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id,
end;
check_stateful_named_arg(_, _, _) -> ok.
check_entrypoints(Defs) ->
[ ensure_first_order_entrypoint(LetFun)
|| LetFun <- Defs,
aeso_syntax:get_ann(entrypoint, LetFun, false),
get_option(allow_higher_order_entrypoints, false) =:= false ].
ensure_first_order_entrypoint({letfun, Ann, Id = {id, _, Name}, Args, Ret, _}) ->
[ ensure_first_order(ArgType, {higher_order_entrypoint, AnnArg, Id, {argument, ArgId, ArgType}})
|| {typed, AnnArg, ArgId, ArgType} <- Args ],
[ ensure_first_order(Ret, {higher_order_entrypoint, Ann, Id, {result, Ret}})
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
%% rather than being returned.
ok.
ensure_first_order(Type, Err) ->
is_first_order(Type) orelse type_error(Err).
is_first_order({fun_t, _, _, _, _}) -> false;
is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
is_first_order(_) -> true.
ensure_monomorphic(Type, Err) ->
is_monomorphic(Type) orelse type_error(Err).
is_monomorphic({tvar, _, _}) -> false;
is_monomorphic(Ts) when is_list(Ts) -> lists:all(fun is_monomorphic/1, Ts);
is_monomorphic(Tup) when is_tuple(Tup) -> is_monomorphic(tuple_to_list(Tup));
is_monomorphic(_) -> true.
check_state_init(Env) ->
Top = Env#env.namespace,
StateType = lookup_type(Env, {id, [{origin, system}], "state"}),
case unfold_types_in_type(Env, StateType) of
false ->
ok;
{_, {_, {_, {alias_t, {tuple_t, _, []}}}}} -> %% type state = ()
ok;
_ ->
#scope{ ann = AnnCon } = get_scope(Env, Top),
type_error({missing_init_function, {con, AnnCon, lists:last(Top)}})
end.
%% Check that `init` doesn't read or write the state and that `init` is defined
%% when the state type is not unit
check_state(Env, Defs) ->
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env, Defs) ->
Top = Env#env.namespace,
GetState = Top ++ ["state"],
SetState = Top ++ ["put"],
@@ -1693,7 +1537,7 @@ check_state(Env, Defs) ->
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _GuardedBodies} <- Defs ],
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
case maps:get(Init, Deps, false) of
false -> get_option(no_code, false) orelse check_state_init(Env);
false -> ok; %% No init, so nothing to check
_ ->
[ type_error({init_depends_on_state, state, Chain})
|| Chain <- get_call_chains(Deps, Init, GetState) ],
@@ -1835,17 +1679,12 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
NewFun0 = infer_expr(Env, Fun),
NewArgs = [infer_expr(Env, A) || A <- Args],
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
NewFun1 = {typed, _, FunName, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
NewFun1 = {typed, _, _, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
GeneralResultType = fresh_uvar(Ann),
ResultType = fresh_uvar(Ann),
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
when_warning(warn_negative_spend, fun() -> warn_potential_negative_spend(Ann, NewFun1, NewArgs) end),
[ add_constraint({aens_resolve_type, GeneralResultType})
|| element(3, FunName) =:= ["AENS", "resolve"] ],
[ add_constraint({oracle_type, Ann, OType})
|| OType <- [get_oracle_type(FunName, ArgTypes, GeneralResultType)],
OType =/= false ],
add_constraint(
#dependent_type_constraint{ named_args_t = NamedArgsVar,
named_args = NamedArgs1,
@@ -2095,7 +1934,7 @@ infer_case(Env = #env{ namespace = NS, current_function = {id, _, Fun} }, Attrs,
{guarded, Ann, NewGuards, NewBranch}
end,
NewGuardedBranches = lists:map(InferGuardedBranches, GuardedBranches),
unify(Env, ExprType, PatType, {case_pat, Pattern, PatType, ExprType}),
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
{'case', Attrs, NewPattern, NewGuardedBranches}.
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
@@ -2130,10 +1969,16 @@ infer_infix({IntOp, As})
Int = {id, As, "int"},
{fun_t, As, [], [Int, Int], Int};
infer_infix({RelOp, As})
when RelOp == '=='; RelOp == '!=';
RelOp == '<'; RelOp == '>';
when RelOp == '=='; RelOp == '!=' ->
T = fresh_uvar(As),
add_constraint({is_eq, T}),
Bool = {id, As, "bool"},
{fun_t, As, [], [T, T], Bool};
infer_infix({RelOp, As})
when RelOp == '<'; RelOp == '>';
RelOp == '<='; RelOp == '=<'; RelOp == '>=' ->
T = fresh_uvar(As), %% allow any type here, check in the backend that we have comparison for it
T = fresh_uvar(As),
add_constraint({is_ord, T}),
Bool = {id, As, "bool"},
{fun_t, As, [], [T, T], Bool};
infer_infix({'..', As}) ->
@@ -2204,7 +2049,7 @@ next_count() ->
ets_tables() ->
[options, type_vars, constraints, freshen_tvars, type_errors,
defined_contracts, warnings, function_calls, all_functions,
type_vars_variance].
ord_constraint_types].
clean_up_ets() ->
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
@@ -2371,14 +2216,11 @@ destroy_and_report_unsolved_constraints(Env) ->
({add_bytes, _, _, _, _, _}) -> true;
(_) -> false
end, OtherCs3),
{AensResolveCs, OtherCs5} =
lists:partition(fun({aens_resolve_type, _}) -> true;
(_) -> false
{TVarsCs, []} =
lists:partition(fun({is_eq, _}) -> true;
({is_ord, _}) -> true;
(_) -> false
end, OtherCs4),
{OracleTypeCs, []} =
lists:partition(fun({oracle_type, _, _}) -> true;
(_) -> false
end, OtherCs5),
Unsolved = [ S || S <- [ solve_constraint(Env, dereference_deep(C)) || C <- NamedArgCs ],
S == unsolved ],
@@ -2396,20 +2238,10 @@ destroy_and_report_unsolved_constraints(Env) ->
check_record_create_constraints(Env, CreateCs),
check_is_contract_constraints(Env, ContractCs),
check_bytes_constraints(Env, BytesCs),
check_aens_resolve_constraints(Env, AensResolveCs),
check_oracle_type_constraints(Env, OracleTypeCs),
check_tvars_constraints(Env, TVarsCs),
destroy_constraints().
get_oracle_type({qid, _, ["Oracle", "register"]}, _ , OType) -> OType;
get_oracle_type({qid, _, ["Oracle", "query"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "get_question"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "get_answer"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "check"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "check_query"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "respond"]}, [OType| _], _ ) -> OType;
get_oracle_type(_Fun, _Args, _Ret) -> false.
%% -- Named argument constraints --
%% If false, a type error has been emitted, so it's safe to drop the constraint.
@@ -2536,31 +2368,32 @@ check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) ->
_ -> type_error({unsolved_bytes_constraint, Ann, Fun, A, B, C})
end.
check_aens_resolve_constraints(_Env, []) ->
ok;
check_aens_resolve_constraints(Env, [{aens_resolve_type, Type} | Rest]) ->
Type1 = unfold_types_in_type(Env, instantiate(Type)),
{app_t, _, {id, _, "option"}, [Type2]} = Type1,
case Type2 of
{id, _, "string"} -> ok;
{id, _, "address"} -> ok;
{con, _, _} -> ok;
{app_t, _, {id, _, "oracle"}, [_, _]} -> ok;
{app_t, _, {id, _, "oracle_query"}, [_, _]} -> ok;
_ -> type_error({invalid_aens_resolve_type, aeso_syntax:get_ann(Type), Type2})
end,
check_aens_resolve_constraints(Env, Rest).
%% -- Typevars constraints --
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).
check_tvars_constraints(Env, Constraints) ->
[ check_tvars_constraint(Env, C) || C <- Constraints ].
check_tvars_constraint(Env, {is_eq, Type = {uvar, Ann, _}}) ->
Type1 = unfold_types_in_type(Env, instantiate(Type)),
type_is_eq(Type1) orelse type_error({type_not_eq, Ann, Type1});
check_tvars_constraint(Env, {is_ord, Type = {uvar, Ann, _}}) ->
Type1 = unfold_types_in_type(Env, instantiate(Type)),
type_is_ord(Type1) orelse type_error({type_not_ord, Ann, Type1}).
type_is_ord({app_t, _, Id, Ts}) -> type_is_ord(Id) andalso lists:all(fun type_is_ord/1, Ts);
type_is_ord({tuple_t, _, Ts}) -> lists:all(fun type_is_ord/1, Ts);
type_is_ord({bytes_t, _, _}) -> true;
type_is_ord({constrained_t, _, Constraints, {tvar, _, _}}) -> lists:keyfind("ord", 3, Constraints) =/= false;
type_is_ord({id, _, Id}) -> ets_lookup(ord_constraint_types, Id) =/= [];
type_is_ord(_) -> false.
type_is_eq({app_t, _, Id, Ts}) -> type_is_eq(Id) andalso lists:all(fun type_is_eq/1, Ts);
type_is_eq({con, _, _}) -> true;
type_is_eq({qcon, _, _}) -> true;
type_is_eq({id, _, _}) -> true;
type_is_eq({qid, _, _}) -> true;
type_is_eq({constrained_t, _, Constraints, {tvar, _, _}}) -> lists:keyfind("eq", 3, Constraints) =/= false;
type_is_eq(T) -> type_is_ord(T).
%% -- Field constraints --
@@ -2789,11 +2622,9 @@ subst_tvars1(_Env, X) ->
%% Unification
unify(Env, A, B, When) -> unify0(Env, A, B, covariant, When).
unify0(_, {id, _, "_"}, _, _Variance, _When) -> true;
unify0(_, _, {id, _, "_"}, _Variance, _When) -> true;
unify0(Env, A, B, Variance, When) ->
unify(_, {id, _, "_"}, _, _When) -> true;
unify(_, _, {id, _, "_"}, _When) -> true;
unify(Env, A, B, When) ->
Options =
case When of %% Improve source location for map_in_map_key errors
{check_expr, E, _, _} -> [{ann, aeso_syntax:get_ann(E)}];
@@ -2801,128 +2632,102 @@ unify0(Env, A, B, Variance, When) ->
end,
A1 = dereference(unfold_types_in_type(Env, A, Options)),
B1 = dereference(unfold_types_in_type(Env, B, Options)),
unify1(Env, A1, B1, Variance, When).
unify1(Env, A1, B1, When).
unify1(_Env, {uvar, _, R}, {uvar, _, R}, _Variance, _When) ->
unify1(_Env, {uvar, _, R}, {uvar, _, R}, _When) ->
true;
unify1(Env, {uvar, A, R}, T, _Variance, When) ->
unify1(_Env, {uvar, A, R}, T, When) ->
case occurs_check(R, T) of
true ->
if
Env#env.unify_throws ->
cannot_unify({uvar, A, R}, T, none, When);
true ->
ok
end,
cannot_unify({uvar, A, R}, T, When),
false;
false ->
ets_insert(type_vars, {R, T}),
true
end;
unify1(Env, T, {uvar, A, R}, Variance, When) ->
unify1(Env, {uvar, A, R}, T, Variance, When);
unify1(_Env, {tvar, _, X}, {tvar, _, X}, _Variance, _When) -> true; %% Rigid type variables
unify1(Env, [A|B], [C|D], [V|Variances], When) ->
unify0(Env, A, C, V, When) andalso unify0(Env, B, D, Variances, When);
unify1(Env, [A|B], [C|D], Variance, When) ->
unify0(Env, A, C, Variance, When) andalso unify0(Env, B, D, Variance, When);
unify1(_Env, X, X, _Variance, _When) ->
unify1(Env, T, {uvar, A, R}, When) ->
unify1(Env, {uvar, A, R}, T, When);
unify1(Env, {constrained_t, _, Cs, UVar = {uvar, _, _}}, Type2, When) ->
[ add_constraint({is_ord, UVar}) || {id, _, "ord"} <- Cs ],
[ add_constraint({is_eq, UVar}) || {id, _, "eq"} <- Cs ],
unify1(Env, UVar, Type2, When);
unify1(Env, Type1, UVar = {constrained_t, _, Cs, {uvar, _, _}}, When) ->
[ add_constraint({is_ord, UVar}) || {id, _, "ord"} <- Cs ],
[ add_constraint({is_eq, UVar}) || {id, _, "eq"} <- Cs ],
unify1(Env, Type1, UVar, When);
unify1(_Env, {tvar, _, X}, {tvar, _, X}, _When) -> true; %% Rigid type variables
unify1(_Env, {constrained_t, _, Cs, {tvar, _, X}}, {constrained_t, _, Cs, {tvar, _, X}}, _When) -> true;
unify1(Env, [A|B], [C|D], When) ->
unify(Env, A, C, When) andalso unify(Env, B, D, When);
unify1(_Env, X, X, _When) ->
true;
unify1(_Env, {id, _, Name}, {id, _, Name}, _Variance, _When) ->
unify1(_Env, {id, _, Name}, {id, _, Name}, _When) ->
true;
unify1(Env, A = {con, _, NameA}, B = {con, _, NameB}, Variance, When) ->
case is_subtype(Env, NameA, NameB, Variance) of
true -> true;
false ->
if
Env#env.unify_throws ->
IsSubtype = is_subtype(Env, NameA, NameB, contravariant) orelse
is_subtype(Env, NameA, NameB, covariant),
Cxt = case IsSubtype of
true -> Variance;
false -> none
end,
cannot_unify(A, B, Cxt, When);
true ->
ok
end,
false
end;
unify1(_Env, {qid, _, Name}, {qid, _, Name}, _Variance, _When) ->
unify1(_Env, {con, _, Name}, {con, _, Name}, _When) ->
true;
unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _Variance, _When) ->
unify1(_Env, {qid, _, Name}, {qid, _, Name}, _When) ->
true;
unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _Variance, _When) ->
unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) ->
true;
unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, Variance, When) ->
unify0(Env, Then1, Then2, Variance, When) andalso
unify0(Env, Else1, Else2, Variance, When);
unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
true;
unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, When) ->
unify(Env, Then1, Then2, When) andalso
unify(Env, Else1, Else2, When);
unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) ->
unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, When) ->
type_error({unify_varargs, When});
unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, _Variance, When) ->
unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, When) ->
type_error({unify_varargs, When});
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, Variance, When)
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When)
when length(Args1) == length(Args2) ->
unify0(Env, Named1, Named2, opposite_variance(Variance), When) andalso
unify0(Env, Args1, Args2, opposite_variance(Variance), When) andalso
unify0(Env, Result1, Result2, Variance, When);
unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, Variance, When)
unify(Env, Named1, Named2, When) andalso
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When)
when length(Args1) == length(Args2), Tag == id orelse Tag == qid ->
Variances = case ets_lookup(type_vars_variance, F) of
[{_, Vs}] ->
case Variance of
contravariant -> lists:map(fun opposite_variance/1, Vs);
invariant -> invariant;
_ -> Vs
end;
_ -> invariant
end,
unify1(Env, Args1, Args2, Variances, When);
unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, Variance, When)
unify(Env, Args1, Args2, When);
unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, When)
when length(As) == length(Bs) ->
unify0(Env, As, Bs, Variance, When);
unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, Variance, When) ->
unify1(Env, Id1, Id2, Variance, {arg_name, Id1, Id2, When}),
unify1(Env, Type1, Type2, Variance, When);
unify(Env, As, Bs, When);
unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, When) ->
unify1(Env, Id1, Id2, {arg_name, Id1, Id2, When}),
unify1(Env, Type1, Type2, When);
unify1(Env, {constrained_t, _, Constraints, Type1 = {fun_t, _, _, _, _}}, Type2, When) ->
unify1(Env, constrain_tvars(Type1, Constraints), Type2, When);
unify1(Env, Type1, {constrained_t, _, Constraints, Type2 = {fun_t, _, _, _, _}}, When) ->
unify1(Env, Type1, constrain_tvars(Type2, Constraints), When);
%% The grammar is a bit inconsistent about whether types without
%% arguments are represented as applications to an empty list of
%% parameters or not. We therefore allow them to unify.
unify1(Env, {app_t, _, T, []}, B, Variance, When) ->
unify0(Env, T, B, Variance, When);
unify1(Env, A, {app_t, _, T, []}, Variance, When) ->
unify0(Env, A, T, Variance, When);
unify1(Env, A, B, _Variance, When) ->
if
Env#env.unify_throws ->
cannot_unify(A, B, none, When);
true ->
ok
end,
unify1(Env, {app_t, _, T, []}, B, When) ->
unify(Env, T, B, When);
unify1(Env, A, {app_t, _, T, []}, When) ->
unify(Env, A, T, When);
unify1(_Env, A, B, When) ->
cannot_unify(A, B, When),
false.
is_subtype(_Env, NameA, NameB, invariant) ->
NameA == NameB;
is_subtype(Env, NameA, NameB, covariant) ->
is_subtype(Env, NameA, NameB);
is_subtype(Env, NameA, NameB, contravariant) ->
is_subtype(Env, NameB, NameA);
is_subtype(Env, NameA, NameB, bivariant) ->
is_subtype(Env, NameA, NameB) orelse is_subtype(Env, NameB, NameA).
is_subtype(Env, Child, Base) ->
Parents = maps:get(Child, Env#env.contract_parents, []),
if
Child == Base ->
true;
Parents == [] ->
false;
true ->
case lists:member(Base, Parents) of
true -> true;
false -> lists:any(fun(Parent) -> is_subtype(Env, Parent, Base) end, Parents)
end
end.
%% Propagate the constraints to their corresponding type vars
-spec constrain_tvars(utype() | [utype()], [constraint()]) -> utype().
constrain_tvars(Types, Constraints)
when is_list(Types) ->
[ constrain_tvars(Type, Constraints) || Type <- Types ];
constrain_tvars({fun_t, Ann, NamedArgs, ArgsT, RetT}, Constraints) ->
ConstrainedArgsT = constrain_tvars(ArgsT, Constraints),
ConstrainedRetT = constrain_tvars(RetT, Constraints),
{fun_t, Ann, NamedArgs, ConstrainedArgsT, ConstrainedRetT};
constrain_tvars({app_t, Ann, AppT, ArgsT}, Constraints) ->
ConstrainedAppT = constrain_tvars(AppT, Constraints),
ConstrainedArgsT = constrain_tvars(ArgsT, Constraints),
{app_t, Ann, ConstrainedAppT, ConstrainedArgsT};
constrain_tvars({tuple_t, Ann, ElemsT}, Constraints) ->
ConstrainedElemsT = constrain_tvars(ElemsT, Constraints),
{tuple_t, Ann, ConstrainedElemsT};
constrain_tvars(TVar = {tvar, Ann, NameT}, Constraints) ->
TVarConstraints = [ C || {constraint, _, {tvar, _, NameC}, C} <- Constraints, NameT == NameC ],
{constrained_t, Ann, TVarConstraints, TVar};
constrain_tvars(Type, _) ->
Type.
dereference(T = {uvar, _, R}) ->
case ets_lookup(type_vars, R) of
@@ -2951,6 +2756,7 @@ occurs_check1(_, {con, _, _}) -> false;
occurs_check1(_, {qid, _, _}) -> false;
occurs_check1(_, {qcon, _, _}) -> false;
occurs_check1(_, {tvar, _, _}) -> false;
occurs_check1(_, {constrained_t, _, _, _}) -> false;
occurs_check1(_, {bytes_t, _, _}) -> false;
occurs_check1(R, {fun_t, _, Named, Args, Res}) ->
occurs_check(R, [Res, Named | Args]);
@@ -3067,7 +2873,8 @@ all_warnings() ->
, warn_unused_functions
, warn_shadowing
, warn_division_by_zero
, warn_negative_spend ].
, warn_negative_spend
, warn_duplicated_constraints ].
when_warning(Warn, Do) ->
case lists:member(Warn, all_warnings()) of
@@ -3204,10 +3011,23 @@ warn_potential_negative_spend(Ann, Fun, Args) ->
_ -> ok
end.
%% Warnings (Duplicated tvar constraints)
warn_duplicated_constraints(Constraints) ->
warn_duplicated_constraints([], Constraints).
warn_duplicated_constraints(_, []) -> ok;
warn_duplicated_constraints(UniqueConstraints, [Constraint = {constraint, _, {tvar, _, Name1}, {id, _, Constr1}}| Rest]) ->
case [ C || C = {constraint, _, {tvar, _, Name2}, {id, _, Constr2}} <- UniqueConstraints,
Name1 == Name2 andalso Constr1 == Constr2 ] of
[] -> warn_duplicated_constraints([Constraint | UniqueConstraints], Rest);
[Unique] -> ets_insert(warnings, {duplicated_constraint, Constraint, Unique})
end.
%% Save unification failures for error messages.
cannot_unify(A, B, Cxt, When) ->
type_error({cannot_unify, A, B, Cxt, When}).
cannot_unify(A, B, When) ->
type_error({cannot_unify, A, B, When}).
type_error(Err) ->
ets_insert(type_errors, Err).
@@ -3276,12 +3096,8 @@ mk_error({fundecl_must_have_funtype, _Ann, Id, Type}) ->
"Entrypoints and functions must have functional types"
, [pp(Id), pp(instantiate(Type))]),
mk_t_err(pos(Id), Msg);
mk_error({cannot_unify, A, B, Cxt, When}) ->
VarianceContext = case Cxt of
none -> "";
_ -> io_lib:format(" in a ~p context", [Cxt])
end,
Msg = io_lib:format("Cannot unify `~s` and `~s`" ++ VarianceContext,
mk_error({cannot_unify, A, B, When}) ->
Msg = io_lib:format("Cannot unify `~s` and `~s`",
[pp(instantiate(A)), pp(instantiate(B))]),
{Pos, Ctxt} = pp_when(When),
mk_t_err(Pos, Msg, Ctxt);
@@ -3412,7 +3228,7 @@ mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
Msg = io_lib:format("Nested namespaces are not allowed. Namespace `~s` is not defined at top level.",
[Name]),
mk_t_err(pos(Pos), Msg);
mk_error({Contract, _Pos, {con, Pos, Name}, _Impls, _Def}) when ?IS_CONTRACT_HEAD(Contract) ->
mk_error({Contract, _Pos, {con, Pos, Name}, _Def}) when ?IS_CONTRACT_HEAD(Contract) ->
Msg = io_lib:format("Nested contracts are not allowed. Contract `~s` is not defined at top level.",
[Name]),
mk_t_err(pos(Pos), Msg);
@@ -3599,50 +3415,18 @@ mk_error({unknown_warning, Warning}) ->
mk_error({empty_record_definition, Ann, Name}) ->
Msg = io_lib:format("Empty record definitions are not allowed. Cannot define the record `~s`", [Name]),
mk_t_err(pos(Ann), Msg);
mk_error({unimplemented_interface_function, ConId, InterfaceName, FunName}) ->
Msg = io_lib:format("Unimplemented function `~s` from the interface `~s` in the contract `~s`", [FunName, InterfaceName, pp(ConId)]),
mk_t_err(pos(ConId), Msg);
mk_error({referencing_undefined_interface, InterfaceId}) ->
Msg = io_lib:format("Trying to implement or extend an undefined interface `~s`", [pp(InterfaceId)]),
mk_t_err(pos(InterfaceId), Msg);
mk_error({missing_definition, Id}) ->
Msg = io_lib:format("Missing definition of function `~s`", [name(Id)]),
mk_t_err(pos(Id), Msg);
mk_error({parameterized_state, Ann}) ->
Msg = "The state type cannot be parameterized",
mk_error({type_not_eq, Ann, Type}) ->
Msg = io_lib:format("Values of type `~s` are not comparable by equality", [pp_type("", Type)]),
mk_t_err(pos(Ann), Msg);
mk_error({parameterized_event, Ann}) ->
Msg = "The event type cannot be parameterized",
mk_error({type_not_ord, Ann, Type}) ->
Msg = io_lib:format("Values of type `~s` are not comparable by inequality", [pp_type("", Type)]),
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_error({unused_constraint, {constraint, Ann, {tvar, _, Name}, _}}) ->
Msg = io_lib:format("The type variable `~s` is constrained but never used", [Name]),
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_error({unknown_tvar_constraint, {constraint, _, {tvar, _, TVar}, {id, Ann, C}}}) ->
Msg = io_lib:format("Unknown constraint `~s` used on the type variable `~s`", [C, TVar]),
mk_t_err(pos(Ann), Msg);
mk_error({invalid_oracle_type, Why, What, Ann, Type}) ->
WhyS = case Why of higher_order -> "higher-order (contain function types)";
polymorphic -> "polymorphic (contain type variables)" end,
Msg = io_lib:format("Invalid oracle type\n~s`", [pp_type(" `", Type)]),
Cxt = io_lib:format("The ~s type must not be ~s", [What, WhyS]),
mk_t_err(pos(Ann), Msg, Cxt);
mk_error(Err) ->
Msg = io_lib:format("Unknown error: ~p", [Err]),
mk_t_err(pos(0, 0), Msg).
@@ -3674,6 +3458,10 @@ mk_warning({division_by_zero, Ann}) ->
mk_warning({negative_spend, Ann}) ->
Msg = io_lib:format("Negative spend.", []),
aeso_warnings:new(pos(Ann), Msg);
mk_warning({duplicated_constraint, {constraint, Ann, {tvar, _, Name}, _}, {constraint, AnnFirst, _, _}}) ->
Msg = io_lib:format("The constraint on the type variable `~s` is a duplication of the constraint at ~s",
[Name, pp_loc(AnnFirst)]),
aeso_warnings:new(pos(Ann), Msg);
mk_warning(Warn) ->
Msg = io_lib:format("Unknown warning: ~p", [Warn]),
aeso_warnings:new(Msg).
@@ -3898,6 +3686,8 @@ pp({uvar, _, Ref}) ->
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
pp({tvar, _, Name}) ->
Name;
pp(T = {constrained_t, _, _, {tvar, _, _}}) ->
prettypr:format(aeso_pretty:type(T));
pp({if_t, _, Id, Then, Else}) ->
["if(", pp([Id, Then, Else]), ")"];
pp({tuple_t, _, []}) ->
+84 -13
View File
@@ -242,7 +242,7 @@ builtins() ->
MkName = fun(NS, Fun) ->
list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_")))
end,
Scopes = [{[], [{"abort", 1}, {"require", 2}, {"exit", 1}]},
Scopes = [{[], [{"abort", 1}, {"require", 2}]},
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
{"timestamp", none}, {"block_height", none}, {"difficulty", none},
{"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]},
@@ -326,7 +326,7 @@ get_option(Opt, Env, Default) ->
%% -- Compilation ------------------------------------------------------------
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
to_fcode(Env, [{Contract, Attrs, {con, _, Name}, _Impls, Decls}|Rest])
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest])
when ?IS_CONTRACT_HEAD(Contract) ->
case Contract =:= contract_interface of
false ->
@@ -349,7 +349,7 @@ to_fcode(Env, [{Contract, Attrs, {con, _, Name}, _Impls, Decls}|Rest])
event_type => EventType,
payable => Payable,
functions => add_init_function(
Env1,
Env1, Con, StateType,
add_event_function(Env1, EventType, Funs)) },
case Contract of
contract_main -> [] = Rest, {Env1, ConFcode};
@@ -362,6 +362,8 @@ to_fcode(Env, [{Contract, Attrs, {con, _, Name}, _Impls, Decls}|Rest])
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Name} }, Decls),
to_fcode(Env1, Rest)
end;
to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= contract_def ->
fcode_error({last_declaration_must_be_main_contract, NotMain});
to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
to_fcode(Env1, Code).
@@ -375,15 +377,22 @@ decls_to_fcode(Env, Decls) ->
end, Env1, Decls).
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
decl_to_fcode(Env = #{context := {contract_def, _}}, {fun_decl, _, Id, _}) ->
case is_no_code(Env) of
false -> fcode_error({missing_definition, Id});
true -> Env
end;
decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env;
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
typedef_to_fcode(Env, Name, Args, Def);
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
Attrs = get_attributes(Ann),
FName = lookup_fun(Env, qname(Env, Name)),
FArgs = args_to_fcode(Env, Args),
FRet = type_to_fcode(Env, Ret),
FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body),
[ ensure_first_order_entrypoint(Ann, Id, Args, Ret, FArgs, FRet)
|| aeso_syntax:get_ann(entrypoint, Ann, false) ],
Def = #{ attrs => Attrs,
args => FArgs,
return => FRet,
@@ -392,7 +401,8 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, R
Env#{ functions := NewFuns }.
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
check_state_and_event_types(Env, Id, Xs),
Q = qname(Env, Name),
FDef = fun(Args) when length(Args) == length(Xs) ->
Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)),
@@ -456,6 +466,14 @@ compute_state_layout(R, [H | T]) ->
compute_state_layout(R, _) ->
{R + 1, {reg, R}}.
check_state_and_event_types(#{ context := {contract_def, _} }, Id, [_ | _]) ->
case Id of
{id, _, "state"} -> fcode_error({parameterized_state, Id});
{id, _, "event"} -> fcode_error({parameterized_event, Id});
_ -> ok
end;
check_state_and_event_types(_, _, _) -> ok.
-spec type_to_fcode(env(), aeso_syntax:type()) -> ftype().
type_to_fcode(Env, Type) ->
type_to_fcode(Env, #{}, Type).
@@ -473,8 +491,12 @@ type_to_fcode(Env, Sub, {record_t, Fields}) ->
type_to_fcode(Env, Sub, {tuple_t, [], lists:map(FieldType, Fields)});
type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
{bytes, N};
type_to_fcode(_Env, _Sub, {tvar, Ann, "void"}) ->
fcode_error({found_void, Ann});
type_to_fcode(_Env, Sub, {tvar, _, X}) ->
maps:get(X, Sub, {tvar, X});
type_to_fcode(Env, Sub, {constrained_t, _, _, TVar = {tvar, _, _}}) ->
type_to_fcode(Env, Sub, TVar);
type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) ->
fcode_error({var_args_not_set, {id, Ann, "a very suspicious function"}});
type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) ->
@@ -534,7 +556,7 @@ expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}};
%% Variables
expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]);
expr_to_fcode(Env, Type, {qid, _, X}) ->
expr_to_fcode(Env, Type, {qid, Ann, X}) ->
case resolve_var(Env, X) of
{builtin_u, B, Ar} when B =:= oracle_query;
B =:= oracle_get_question;
@@ -545,11 +567,13 @@ expr_to_fcode(Env, Type, {qid, _, X}) ->
B =:= oracle_check_query ->
OType = get_oracle_type(B, Type),
{oracle, QType, RType} = type_to_fcode(Env, OType),
validate_oracle_type(Ann, OType, QType, RType),
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
{builtin_u, B, Ar, TypeArgs};
{builtin_u, B = aens_resolve, Ar} ->
{fun_t, _, _, _, ResType} = Type,
AensType = type_to_fcode(Env, ResType),
validate_aens_resolve_type(Ann, ResType, AensType),
TypeArgs = [{lit, {typerep, AensType}}],
{builtin_u, B, Ar, TypeArgs};
{builtin_u, B = bytes_split, Ar} ->
@@ -802,6 +826,53 @@ get_oracle_type(oracle_check, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check_query, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_respond, {fun_t, _, _, [OType | _], _}) -> OType.
validate_oracle_type(Ann, Type, QType, RType) ->
ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
ok.
validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) ->
case FType of
string -> ok;
address -> ok;
contract -> ok;
{oracle, _, _} -> ok;
oracle_query -> ok;
_ -> fcode_error({invalid_aens_resolve_type, Ann, Type})
end.
ensure_first_order_entrypoint(Ann, Id = {id, _, Name}, Args, Ret, FArgs, FRet) ->
[ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Id, {argument, X, T}})
|| {{typed, Ann1, X, T}, {_, FT}} <- lists:zip(Args, FArgs) ],
[ ensure_first_order(FRet, {invalid_entrypoint, higher_order, Ann, Id, {result, Ret}})
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
%% rather than being returned.
ok.
ensure_monomorphic(Type, Err) ->
case is_monomorphic(Type) of
true -> ok;
false -> fcode_error(Err)
end.
ensure_first_order(Type, Err) ->
case is_first_order(Type) of
true -> ok;
false -> fcode_error(Err)
end.
is_monomorphic({tvar, _}) -> false;
is_monomorphic(Ts) when is_list(Ts) -> lists:all(fun is_monomorphic/1, Ts);
is_monomorphic(Tup) when is_tuple(Tup) -> is_monomorphic(tuple_to_list(Tup));
is_monomorphic(_) -> true.
is_first_order({function, _, _}) -> false;
is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
is_first_order(_) -> true.
%% -- Pattern matching --
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()], aeso_syntax:expr()) -> fsplit().
@@ -1134,11 +1205,11 @@ builtin_to_fcode(_Layout, Builtin, Args) ->
%% -- Init function --
add_init_function(Env, Funs0) ->
add_init_function(Env, Main, StateType, Funs0) ->
case is_no_code(Env) of
true -> Funs0;
false ->
Funs = add_default_init_function(Env, Funs0),
Funs = add_default_init_function(Env, Main, StateType, Funs0),
InitName = {entrypoint, <<"init">>},
InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []},
@@ -1146,14 +1217,16 @@ add_init_function(Env, Funs0) ->
Funs1
end.
add_default_init_function(_Env, Funs) ->
add_default_init_function(_Env, Main, StateType, Funs) ->
InitName = {entrypoint, <<"init">>},
case maps:get(InitName, Funs, none) of
none ->
%% Only add default init function if state is unit.
none when StateType == {tuple, []} ->
Funs#{ InitName => #{attrs => [],
args => [],
return => {tuple, []},
body => {tuple, []}} };
none -> fcode_error({missing_init_function, Main});
_ -> Funs
end.
@@ -2019,9 +2092,7 @@ setnth(I, X, Xs) ->
-dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}).
fcode_error(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)).
aeso_errors:throw(aeso_code_errors:format(Error)).
internal_error(Error) ->
Msg = lists:flatten(io_lib:format("~p\n", [Error])),
+94
View File
@@ -0,0 +1,94 @@
%%%-------------------------------------------------------------------
%%% @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)).
+4 -4
View File
@@ -238,8 +238,8 @@ insert_init_function(Code, Options) ->
last_contract_indent(Decls) ->
case lists:last(Decls) of
{_, _, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
_ -> 0
{_, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
_ -> 0
end.
-spec to_sophia_value(string(), string(), ok | error | revert, binary()) ->
@@ -338,7 +338,7 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
end.
-dialyzer({nowarn_function, get_decode_type/2}).
get_decode_type(FunName, [{Contract, Ann, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
get_decode_type(FunName, [{Contract, Ann, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
(_) -> [] end,
@@ -349,7 +349,7 @@ get_decode_type(FunName, [{Contract, Ann, _, _, Defs}]) when ?IS_CONTRACT_HEAD(C
"init" -> {ok, [], {tuple_t, [], []}};
_ ->
Msg = io_lib:format("Function '~s' is missing in contract", [FunName]),
Pos = aeso_errors:pos(Ann),
Pos = aeso_code_errors:pos(Ann),
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
end
end;
-7
View File
@@ -34,7 +34,6 @@
, new/2
, new/3
, new/4
, pos/1
, pos/2
, pos/3
, pp/1
@@ -54,12 +53,6 @@ new(Type, Pos, Msg) ->
new(Type, Pos, Msg, Ctxt) ->
#err{ type = Type, pos = Pos, message = Msg, context = Ctxt }.
pos(Ann) ->
File = aeso_syntax:get_ann(file, Ann, no_file),
Line = aeso_syntax:get_ann(line, Ann, 0),
Col = aeso_syntax:get_ann(col, Ann, 0),
pos(File, Line, Col).
pos(Line, Col) ->
#pos{ line = Line, col = Col }.
+1 -6
View File
@@ -64,9 +64,7 @@ debug(Tag, Options, Fun) ->
-dialyzer({nowarn_function, [code_error/1]}).
code_error(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)).
aeso_errors:throw(aeso_code_errors:format(Err)).
%% -- Main -------------------------------------------------------------------
@@ -509,8 +507,6 @@ builtin_to_scode(Env, bytes_split, [_, _] = Args) ->
call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args);
builtin_to_scode(Env, abort, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:abort(?a), Args);
builtin_to_scode(Env, exit, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:exit(?a), Args);
builtin_to_scode(Env, chain_spend, [_, _] = Args) ->
call_to_scode(Env, [aeb_fate_ops:spend(?a, ?a),
tuple(0)], Args);
@@ -1490,7 +1486,6 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
r_write_to_dead_var(_, _) -> false.
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
op_view({'EXIT', R}) -> {'EXIT', none, [R]};
op_view(T) when is_tuple(T) ->
[Op, R | As] = tuple_to_list(T),
CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end,
+19 -20
View File
@@ -96,29 +96,17 @@ decl() ->
choice(
%% Contract declaration
[ ?RULE(token(main), keyword(contract),
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, [], _5})
, ?RULE(token(main), keyword(contract),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5, _7})
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5})
, ?RULE(keyword(contract),
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, [], _4})
, ?RULE(keyword(contract),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4, _6})
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4})
, ?RULE(keyword(contract), token(interface),
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, [], _5})
, ?RULE(keyword(contract), token(interface),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5, _7})
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5})
, ?RULE(token(payable), token(main), keyword(contract),
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, [], _6}))
, ?RULE(token(payable), token(main), keyword(contract),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6, _8}))
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6}))
, ?RULE(token(payable), keyword(contract),
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, [], _5}))
, ?RULE(token(payable), keyword(contract),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5, _7}))
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5}))
, ?RULE(token(payable), keyword(contract), token(interface),
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, [], _6}))
, ?RULE(token(payable), keyword(contract), token(interface),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6, _8}))
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6}))
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
@@ -146,9 +134,19 @@ fun_block(Mods, Kind, [Decl]) ->
fun_block(Mods, Kind, Decls) ->
{block, get_ann(Kind), [ add_modifiers(Mods, Kind, Decl) || Decl <- Decls ]}.
typevar_constraint() ->
?RULE(tvar(), keyword(is), id(), {constraint, get_ann(_1), _1, _3}).
typevars_constraints() ->
?RULE(comma_sep1(typevar_constraint()), tok(';'), _1).
fundecl() ->
choice([?RULE(id(), tok(':'), typevars_constraints(), type(),
{fun_decl, get_ann(_1), _1, {constrained_t, get_ann(_1), _3, _4}}),
?RULE(id(), tok(':'), type(), {fun_decl, get_ann(_1), _1, _3})]).
fundef_or_decl() ->
choice([?RULE(id(), tok(':'), type(), {fun_decl, get_ann(_1), _1, _3}),
fundef()]).
choice([fundecl(), fundef()]).
using() ->
Alias = {keyword(as), con()},
@@ -539,6 +537,7 @@ parens(P) -> between(tok('('), P, tok(')')).
braces(P) -> between(tok('{'), P, tok('}')).
brackets(P) -> between(tok('['), P, tok(']')).
comma_sep(P) -> sep(P, tok(',')).
comma_sep1(P) -> sep1(P, tok(',')).
paren_list(P) -> parens(comma_sep(P)).
brace_list(P) -> braces(comma_sep(P)).
+5 -7
View File
@@ -151,16 +151,12 @@ decl(D, Options) ->
with_options(Options, fun() -> decl(D) end).
-spec decl(aeso_syntax:decl()) -> doc().
decl({Con, Attrs, C, Is, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
decl({Con, Attrs, C, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
Mod = fun({Mod, true}) when Mod == payable ->
text(atom_to_list(Mod));
(_) -> empty() end,
ImplsList = case Is of
[] -> [empty()];
_ -> [text(":"), par(punctuate(text(","), lists:map(fun name/1, Is)), 0)]
end,
block(follow( hsep(lists:map(Mod, Attrs) ++ [contract_head(Con)])
, hsep([name(C)] ++ ImplsList ++ [text("=")])), decls(Ds));
, hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
decl({pragma, _, Pragma}) -> pragma(Pragma);
@@ -288,7 +284,9 @@ type(T = {id, _, _}) -> name(T);
type(T = {qid, _, _}) -> name(T);
type(T = {con, _, _}) -> name(T);
type(T = {qcon, _, _}) -> name(T);
type(T = {tvar, _, _}) -> name(T).
type(T = {tvar, _, _}) -> name(T);
type({constrained_t, _, Cs, T}) ->
beside([name(T), text(" is "), tuple(lists:map(fun expr/1, Cs))]).
-spec args_type([aeso_syntax:type()]) -> doc().
args_type(Args) ->
+1 -1
View File
@@ -45,7 +45,7 @@ lexer() ->
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace",
"interface", "main", "using", "as", "for", "hiding"
"interface", "main", "using", "as", "for", "hiding", "is"
],
KW = string:join(Keywords, "|"),
+7 -5
View File
@@ -26,7 +26,7 @@
-type ann_format() :: '?:' | hex | infix | prefix | elif.
-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
| stateful | private | payable | main | interface | entrypoint].
| stateful | private | payable | main | interface].
-type name() :: string().
-type id() :: {id, ann(), name()}.
@@ -38,11 +38,10 @@
-type namespace_alias() :: none | con().
-type namespace_parts() :: none | {for, [id()]} | {hiding, [id()]}.
-type decl() :: {contract_main, ann(), con(), [con()], [decl()]}
| {contract_child, ann(), con(), [con()], [decl()]}
| {contract_interface, ann(), con(), [con()], [decl()]}
-type decl() :: {contract_main, ann(), con(), [decl()]}
| {contract_child, ann(), con(), [decl()]}
| {contract_interface, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]}
| {include, ann(), {string, ann(), string()}}
| {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
| {type_def, ann(), id(), [tvar()], typedef()}
@@ -80,11 +79,14 @@
-type constructor_t() :: {constr_t, ann(), con(), [type()]}.
-type tvar_constraint() :: {constraint, ann(), tvar(), id()}.
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
| {app_t, ann(), type(), [type()]}
| {tuple_t, ann(), [type()]}
| {args_t, ann(), [type()]} %% old tuple syntax, old for error messages
| {bytes_t, ann(), integer() | any}
| {constrained_t, ann(), [tvar_constraint()], type()}
| id() | qid()
| con() | qcon() %% contracts
| tvar().
+1
View File
@@ -61,6 +61,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{fun_t, _, Named, Args, Ret} -> Type([Named, Args, Ret]);
{app_t, _, T, Ts} -> Type([T | Ts]);
{tuple_t, _, Ts} -> Type(Ts);
{constrained_t, _, _, T} -> Type(T);
%% named_arg_t()
{named_arg_t, _, _, T, E} -> Plus(Type(T), Expr(E));
%% expr()
+1 -1
View File
@@ -1,6 +1,6 @@
{application, aesophia,
[{description, "Compiler for Aeternity Sophia language"},
{vsn, "7.0.0"},
{vsn, "6.1.0"},
{registered, []},
{applications,
[kernel,
+1 -1
View File
@@ -127,7 +127,7 @@ check_stub(Stub, Options) ->
Ast ->
try
%% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, [no_code])
aeso_ast_infer_types:infer(Ast, [])
catch throw:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]),
error(TE);
+2 -2
View File
@@ -29,7 +29,7 @@ calldata_aci_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString, [no_code]),
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
ContractACI = binary_to_list(ContractACIBin),
io:format("ACI:\n~s\n", [ContractACIBin]),
FateExprs = ast_exprs(ContractACI, Fun, Args),
@@ -39,7 +39,7 @@ calldata_aci_test_() ->
end} || {ContractName, Fun, Args} <- compilable_contracts()].
parse_args(Fun, Args) ->
[{contract_main, _, _, _, [{letfun, _, _, _, _, [{guarded, _, [], {app, _, _, AST}}]}]}] =
[{contract_main, _, _, [{letfun, _, _, _, _, [{guarded, _, [], {app, _, _, AST}}]}]}] =
aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
strip_ann(AST).
+165 -300
View File
@@ -45,6 +45,12 @@ simple_compile_test_() ->
check_errors(ExpectedErrors, Errors)
end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
[ {"Testing code generation error messages of " ++ ContractName,
fun() ->
Errors = compile(ContractName),
check_errors([ExpectedError], Errors)
end} ||
{ContractName, ExpectedError} <- failing_code_gen_contracts()] ++
[ {"Testing include with explicit files",
fun() ->
FileSystem = maps:from_list(
@@ -196,24 +202,6 @@ compilable_contracts() ->
"assign_patterns",
"patterns_guards",
"pipe_operator",
"polymorphism_contract_implements_interface",
"polymorphism_contract_multi_interface",
"polymorphism_contract_interface_extends_interface",
"polymorphism_contract_interface_extensions",
"polymorphism_contract_interface_same_decl_multi_interface",
"polymorphism_contract_interface_same_name_same_type",
"missing_init_fun_state_unit",
"complex_compare_leq",
"complex_compare",
"higher_order_compare",
"higher_order_map_keys",
"higher_order_state",
"polymorphic_compare",
"polymorphic_entrypoint",
"polymorphic_entrypoint_return",
"polymorphic_map_keys",
"unapplied_contract_call",
"unapplied_named_arg_builtin",
"test" % Custom general-purpose test file. Keep it last on the list.
].
@@ -277,7 +265,9 @@ warnings() ->
"The function `called_unused_function2` is defined but never used.">>,
<<?PosW(48, 5)
"Unused return value.">>,
<<?PosW(60, 5)
<<?PosW(53, 44)
"The constraint on the type variable `'a` is a duplication of the constraint at line 53, column 34">>,
<<?PosW(65, 5)
"The function `dec` is defined but never used.">>
]).
@@ -295,26 +285,34 @@ failing_contracts() ->
%% Type errors
, ?TYPE_ERROR(name_clash,
[<<?Pos(4, 3)
"Duplicate definitions of `double_def` at\n"
" - line 3, column 3\n"
" - line 4, column 3">>,
<<?Pos(7, 3)
[<<?Pos(14, 3)
"Duplicate definitions of `abort` at\n"
" - (builtin location)\n"
" - line 7, column 3">>,
<<?Pos(8, 3)
" - line 14, column 3">>,
<<?Pos(15, 3)
"Duplicate definitions of `require` at\n"
" - (builtin location)\n"
" - line 15, column 3">>,
<<?Pos(11, 3)
"Duplicate definitions of `double_def` at\n"
" - line 10, column 3\n"
" - line 11, column 3">>,
<<?Pos(5, 3)
"Duplicate definitions of `double_proto` at\n"
" - line 4, column 3\n"
" - line 5, column 3">>,
<<?Pos(8, 3)
"Duplicate definitions of `proto_and_def` at\n"
" - line 7, column 3\n"
" - line 8, column 3">>,
<<?Pos(9, 3)
<<?Pos(16, 3)
"Duplicate definitions of `put` at\n"
" - (builtin location)\n"
" - line 9, column 3">>,
<<?Pos(10, 3)
" - line 16, column 3">>,
<<?Pos(17, 3)
"Duplicate definitions of `state` at\n"
" - (builtin location)\n"
" - line 10, column 3">>])
" - line 17, column 3">>])
, ?TYPE_ERROR(type_errors,
[<<?Pos(17, 23)
"Unbound variable `zz`">>,
@@ -568,7 +566,7 @@ failing_contracts() ->
])
, ?TYPE_ERROR(list_comp_bad_shadow,
[<<?Pos(2, 53)
"Cannot unify `string` and `int`\n"
"Cannot unify `int` and `string`\n"
"when checking the type of the pattern `x : int` against the expected type `string`">>
])
, ?TYPE_ERROR(map_as_map_key,
@@ -809,6 +807,88 @@ failing_contracts() ->
"to arguments\n"
" `1 : int`">>
])
, ?TYPE_ERROR(comparable_typevar_constraints,
[<<?Pos(21,30)
"Values of type `'a` are not comparable by equality">>,
<<?Pos(25,38)
"The type variable `'b` is constrained but never used">>,
<<?Pos(29,41)
"Unknown constraint `foo` used on the type variable `'a`">>,
<<?Pos(63,58)
"Values of type `Chain.ttl` are not comparable by inequality">>,
<<?Pos(66,45)
"Values of type `A` are not comparable by inequality">>,
<<?Pos(73,47)
"Values of type `(int, char) => bool` are not comparable by inequality">>,
<<?Pos(74,47)
"Values of type `(int, char) => bool` are not comparable by equality">>,
<<?Pos(89,59)
"Values of type `list(A)` are not comparable by inequality">>,
<<?Pos(92,65)
"Values of type `option(A)` are not comparable by inequality">>,
<<?Pos(95,64)
"Values of type `(A * int)` are not comparable by inequality">>,
<<?Pos(96,64)
"Values of type `(A * int)` are not comparable by equality">>,
<<?Pos(100,68)
"Values of type `list((int, char) => bool)` are not comparable by inequality">>,
<<?Pos(101,68)
"Values of type `list((int, char) => bool)` are not comparable by equality">>,
<<?Pos(103,74)
"Values of type `option((int, char) => bool)` are not comparable by inequality">>,
<<?Pos(104,74)
"Values of type `option((int, char) => bool)` are not comparable by equality">>,
<<?Pos(106,73)
"Values of type `((int, char) => bool * int)` are not comparable by inequality">>,
<<?Pos(107,73)
"Values of type `((int, char) => bool * int)` are not comparable by equality">>,
<<?Pos(111,71)
"Values of type `map(int, int)` are not comparable by inequality">>,
<<?Pos(114,80)
"Values of type `oracle(int, int)` are not comparable by inequality">>,
<<?Pos(117,98)
"Values of type `oracle_query(int, int)` are not comparable by inequality">>,
<<?Pos(120,90)
"Values of type `custom_datatype(int)` are not comparable by inequality">>,
<<?Pos(123,84)
"Values of type `custom_record(int)` are not comparable by inequality">>,
<<?Pos(128,62)
"Values of type `map(A, A)` are not comparable by inequality">>,
<<?Pos(131,71)
"Values of type `oracle(A, A)` are not comparable by inequality">>,
<<?Pos(134,89)
"Values of type `oracle_query(A, A)` are not comparable by inequality">>,
<<?Pos(137,85)
"Values of type `custom_datatype(A)` are not comparable by inequality">>,
<<?Pos(140,79)
"Values of type `custom_record(A)` are not comparable by inequality">>,
<<?Pos(145,75)
"Values of type `map((int, char) => bool, (int, char) => bool)` are not comparable by inequality">>,
<<?Pos(146,75)
"Values of type `map((int, char) => bool, (int, char) => bool)` are not comparable by equality">>,
<<?Pos(148,84)
"Values of type `oracle((int, char) => bool, (int, char) => bool)` are not comparable by inequality">>,
<<?Pos(149,84)
"Values of type `oracle((int, char) => bool, (int, char) => bool)` are not comparable by equality">>,
<<?Pos(151,102)
"Values of type `oracle_query((int, char) => bool, (int, char) => bool)` are not comparable by inequality">>,
<<?Pos(152,102)
"Values of type `oracle_query((int, char) => bool, (int, char) => bool)` are not comparable by equality">>,
<<?Pos(154,94)
"Values of type `custom_datatype((int, char) => bool)` are not comparable by inequality">>,
<<?Pos(155,94)
"Values of type `custom_datatype((int, char) => bool)` are not comparable by equality">>,
<<?Pos(157,88)
"Values of type `custom_record((int, char) => bool)` are not comparable by inequality">>,
<<?Pos(158,88)
"Values of type `custom_record((int, char) => bool)` are not comparable by equality">>,
<<?Pos(162,35)
"Values of type `map(int, int)` are not comparable by inequality">>,
<<?Pos(163,35)
"Values of type `('a) => 'a` are not comparable by inequality">>,
<<?Pos(167,34)
"Values of type `('b) => 'b` are not comparable by equality">>
])
, ?TYPE_ERROR(warnings,
[<<?Pos(0, 0)
"The file `Triple.aes` is included but not used.">>,
@@ -838,277 +918,62 @@ failing_contracts() ->
"The function `called_unused_function2` is defined but never used.">>,
<<?Pos(48, 5)
"Unused return value.">>,
<<?Pos(60, 5)
<<?Pos(53, 44)
"The constraint on the type variable `'a` is a duplication of the constraint at line 53, column 34">>,
<<?Pos(65, 5)
"The function `dec` is defined but never used.">>
])
, ?TYPE_ERROR(polymorphism_contract_interface_recursive,
[<<?Pos(1,24)
"Trying to implement or extend an undefined interface `Z`">>
])
, ?TYPE_ERROR(polymorphism_contract_interface_same_name_different_type,
[<<?Pos(4,20)
"Unimplemented function `f` from the interface `I1` in the contract `I2`">>])
, ?TYPE_ERROR(polymorphism_contract_missing_implementation,
[<<?Pos(4,20)
"Unimplemented function `f` from the interface `I1` in the contract `I2`">>
])
, ?TYPE_ERROR(polymorphism_contract_same_decl_multi_interface,
[<<?Pos(7,10)
"Unimplemented function `f` from the interface `J` in the contract `C`">>
])
, ?TYPE_ERROR(polymorphism_contract_undefined_interface,
[<<?Pos(1,14)
"Trying to implement or extend an undefined interface `I`">>
])
, ?TYPE_ERROR(polymorphism_contract_same_name_different_type_multi_interface,
[<<?Pos(9,5)
"Duplicate definitions of `f` at\n"
" - line 8, column 5\n"
" - line 9, column 5">>
])
, ?TYPE_ERROR(polymorphism_contract_interface_undefined_interface,
[<<?Pos(1,24)
"Trying to implement or extend an undefined interface `H`">>
])
, ?TYPE_ERROR(polymorphism_variance_switching,
[<<?Pos(36,49)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the application of\n"
" `g2 : (Cat) => Cat`\n"
"to arguments\n"
" `x : Animal`">>,
<<?Pos(39,43)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the expression `g3(x) : Animal` against the expected type `Cat`">>,
<<?Pos(48,55)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the application of\n"
" `g5 : ((Animal) => Animal) => Cat`\n"
"to arguments\n"
" `x : (Cat) => Cat`">>,
<<?Pos(52,44)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the expression `f6() : option(Animal)` against the expected type `option(Cat)`">>,
<<?Pos(73,43)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the expression `some_animal : Animal` against the expected type `Cat`">>
])
, ?TYPE_ERROR(polymorphism_variance_switching_custom_types,
[<<?Pos(56,39)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the expression `DT_CONTRA(f_c_to_u) : dt_contra(Cat)` against the expected type `dt_contra(Animal)`">>,
<<?Pos(62,35)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the expression `DT_CO(f_u_to_a) : dt_co(Animal)` against the expected type `dt_co(Cat)`">>,
<<?Pos(67,36)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the application of\n `DT_INV : ((Cat) => Cat) => dt_inv(Cat)`\nto arguments\n `f_c_to_a : (Cat) => Animal`">>,
<<?Pos(68,36)
"Cannot unify `Cat` and `Animal` in a invariant context\n"
"when checking the type of the expression `DT_INV(f_c_to_c) : dt_inv(Cat)` against the expected type `dt_inv(Animal)`">>,
<<?Pos(69,36)
"Cannot unify `Animal` and `Cat` in a invariant context\n"
"when checking the type of the expression `DT_INV(f_a_to_a) : dt_inv(Animal)` against the expected type `dt_inv(Cat)`">>,
<<?Pos(70,36)
"Cannot unify `Animal` and `Cat` in a invariant context\n"
"when checking the type of the expression `DT_INV(f_a_to_c) : dt_inv(Animal)` against the expected type `dt_inv(Cat)`">>,
<<?Pos(71,36)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the application of\n `DT_INV : ((Cat) => Cat) => dt_inv(Cat)`\nto arguments\n `f_c_to_a : (Cat) => Animal`">>,
<<?Pos(80,40)
"Cannot unify `Cat` and `Animal` in a invariant context\n"
"when checking the type of the expression `DT_INV_SEP_A(f_c_to_u) : dt_inv_sep(Cat)` against the expected type `dt_inv_sep(Animal)`">>,
<<?Pos(82,40)
"Cannot unify `Cat` and `Animal` in a invariant context\n"
"when checking the type of the expression `DT_INV_SEP_B(f_u_to_c) : dt_inv_sep(Cat)` against the expected type `dt_inv_sep(Animal)`">>,
<<?Pos(83,40)
"Cannot unify `Animal` and `Cat` in a invariant context\n"
"when checking the type of the expression `DT_INV_SEP_A(f_a_to_u) : dt_inv_sep(Animal)` against the expected type `dt_inv_sep(Cat)`">>,
<<?Pos(85,40)
"Cannot unify `Animal` and `Cat` in a invariant context\n"
"when checking the type of the expression `DT_INV_SEP_B(f_u_to_a) : dt_inv_sep(Animal)` against the expected type `dt_inv_sep(Cat)`">>,
<<?Pos(90,42)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the expression `DT_CO_NEST_A(f_dt_contra_a_to_u) : dt_co_nest_a(Animal)` against the expected type `dt_co_nest_a(Cat)`">>,
<<?Pos(94,46)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the expression `DT_CONTRA_NEST_A(f_dt_co_c_to_u) : dt_contra_nest_a(Cat)` against the expected type `dt_contra_nest_a(Animal)`">>,
<<?Pos(99,46)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the expression `DT_CONTRA_NEST_B(f_u_to_dt_contra_c) : dt_contra_nest_b(Cat)` against the expected type `dt_contra_nest_b(Animal)`">>,
<<?Pos(105,42)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the expression `DT_CO_NEST_B(f_u_to_dt_co_a) : dt_co_nest_b(Animal)` against the expected type `dt_co_nest_b(Cat)`">>,
<<?Pos(110,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `vj3 : dt_co_twice(Cat)` against the expected type `dt_co_twice(Animal)`">>,
<<?Pos(114,59)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the expression `DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) : dt_a_contra_b_contra(Animal, Cat)` against the expected type `dt_a_contra_b_contra(Animal, Animal)`">>,
<<?Pos(115,59)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the expression `DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) : dt_a_contra_b_contra(Cat, Animal)` against the expected type `dt_a_contra_b_contra(Animal, Animal)`">>,
<<?Pos(116,59)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the expression `DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) : dt_a_contra_b_contra(Cat, Cat)` against the expected type `dt_a_contra_b_contra(Animal, Animal)`">>,
<<?Pos(119,59)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the expression `DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) : dt_a_contra_b_contra(Cat, Animal)` against the expected type `dt_a_contra_b_contra(Animal, Cat)`">>,
<<?Pos(120,59)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the expression `DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) : dt_a_contra_b_contra(Cat, Cat)` against the expected type `dt_a_contra_b_contra(Animal, Cat)`">>,
<<?Pos(122,59)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the expression `DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) : dt_a_contra_b_contra(Animal, Cat)` against the expected type `dt_a_contra_b_contra(Cat, Animal)`">>,
<<?Pos(124,59)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the expression `DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) : dt_a_contra_b_contra(Cat, Cat)` against the expected type `dt_a_contra_b_contra(Cat, Animal)`">>,
<<?Pos(131,13)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the pattern `vl2 : dt_contra_twice(Animal)` against the expected type `dt_contra_twice(Cat)`">>
])
, ?TYPE_ERROR(polymorphism_variance_switching_records,
[<<?Pos(27,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `r03 : rec_co(Cat)` against the expected type `Main.rec_co(Animal)`">>,
<<?Pos(33,13)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the pattern `r06 : rec_contra(Animal)` against the expected type `Main.rec_contra(Cat)`">>,
<<?Pos(40,13)
"Cannot unify `Cat` and `Animal` in a invariant context\n"
"when checking the type of the pattern `r10 : rec_inv(Animal)` against the expected type `Main.rec_inv(Cat)`">>,
<<?Pos(41,13)
"Cannot unify `Animal` and `Cat` in a invariant context\n"
"when checking the type of the pattern `r11 : rec_inv(Cat)` against the expected type `Main.rec_inv(Animal)`">>
])
, ?TYPE_ERROR(polymorphism_variance_switching_oracles,
[<<?Pos(15,13)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the pattern `o03 : oracle(Animal, Animal)` against the expected type `oracle(Cat, Animal)`">>,
<<?Pos(16,13)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the pattern `o04 : oracle(Animal, Animal)` against the expected type `oracle(Cat, Cat)`">>,
<<?Pos(17,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `o05 : oracle(Animal, Cat)` against the expected type `oracle(Animal, Animal)`">>,
<<?Pos(19,13)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the pattern `o07 : oracle(Animal, Cat)` against the expected type `oracle(Cat, Animal)`">>,
<<?Pos(20,13)
"Cannot unify `Cat` and `Animal` in a contravariant context\n"
"when checking the type of the pattern `o08 : oracle(Animal, Cat)` against the expected type `oracle(Cat, Cat)`">>,
<<?Pos(25,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `o13 : oracle(Cat, Cat)` against the expected type `oracle(Animal, Animal)`">>,
<<?Pos(27,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `o15 : oracle(Cat, Cat)` against the expected type `oracle(Cat, Animal)`">>,
<<?Pos(34,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `q05 : oracle_query(Animal, Cat)` against the expected type `oracle_query(Animal, Animal)`">>,
<<?Pos(36,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `q07 : oracle_query(Animal, Cat)` against the expected type `oracle_query(Cat, Animal)`">>,
<<?Pos(38,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `q09 : oracle_query(Cat, Animal)` against the expected type `oracle_query(Animal, Animal)`">>,
<<?Pos(39,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `q10 : oracle_query(Cat, Animal)` against the expected type `oracle_query(Animal, Cat)`">>,
<<?Pos(42,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `q13 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Animal, Animal)`">>,
<<?Pos(43,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `q14 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Animal, Cat)`">>,
<<?Pos(44,13)
"Cannot unify `Animal` and `Cat` in a covariant context\n"
"when checking the type of the pattern `q15 : oracle_query(Cat, Cat)` against the expected type `oracle_query(Cat, Animal)`">>
])
, ?TYPE_ERROR(missing_definition,
[<<?Pos(2,14)
"Missing definition of function `foo`">>
])
, ?TYPE_ERROR(child_with_decls,
[<<?Pos(2,14)
"Missing definition of function `f`">>
])
, ?TYPE_ERROR(parameterised_state,
[<<?Pos(3,8)
"The state type cannot be parameterized">>
])
, ?TYPE_ERROR(parameterised_event,
[<<?Pos(3,12)
"The event type cannot be parameterized">>
])
, ?TYPE_ERROR(missing_init_fun_alias_to_type,
[<<?Pos(1,10)
"Missing `init` function for the contract `AliasToType`.\n"
"The `init` function can only be omitted if the state type is `unit`">>
])
, ?TYPE_ERROR(missing_init_fun_alias_to_alias_to_type,
[<<?Pos(1,10)
"Missing `init` function for the contract `AliasToAliasToType`.\n"
"The `init` function can only be omitted if the state type is `unit`">>
])
, ?TYPE_ERROR(higher_order_entrypoint,
[<<?Pos(2,20)
"The argument\n"
" `f : (int) => int`\n"
"of entrypoint `apply` has a higher-order (contains function types) type">>
])
, ?TYPE_ERROR(higher_order_entrypoint_return,
[<<?Pos(2,3)
"The return type\n"
" `(int) => int`\n"
"of entrypoint `add` is higher-order (contains function types)">>
])
, ?TYPE_ERROR(polymorphic_aens_resolve,
[<<?Pos(4,5)
"Invalid return type of `AENS.resolve`:\n"
" `'a`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
])
, ?TYPE_ERROR(bad_aens_resolve,
[<<?Pos(6,5)
"Invalid return type of `AENS.resolve`:\n"
" `list(int)`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
])
, ?TYPE_ERROR(bad_aens_resolve_using,
[<<?Pos(7,5)
"Invalid return type of `AENS.resolve`:\n"
" `list(int)`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
])
, ?TYPE_ERROR(polymorphic_query_type,
[<<?Pos(3,5)
"Invalid oracle type\n"
" `oracle('a, 'b)`\n"
"The query type must not be polymorphic (contain type variables)">>,
<<?Pos(3,5)
"Invalid oracle type\n"
" `oracle('a, 'b)`\n"
"The response type must not be polymorphic (contain type variables)">>
])
, ?TYPE_ERROR(polymorphic_response_type,
[<<?Pos(3,5)
"Invalid oracle type\n"
" `oracle(string, 'r)`\n"
"The response type must not be polymorphic (contain type variables)">>
])
, ?TYPE_ERROR(higher_order_query_type,
[<<?Pos(3,5)
"Invalid oracle type\n"
" `oracle((int) => int, string)`\n"
"The query type must not be higher-order (contain function types)">>
])
, ?TYPE_ERROR(higher_order_response_type,
[<<?Pos(3,5)
"Invalid oracle type\n"
" `oracle(string, (int) => int)`\n"
"The response type must not be higher-order (contain function types)">>
])
].
-define(Path(File), "code_errors/" ??File).
-define(Msg(File, Line, Col, Err), <<?Pos("Code generation", ?Path(File), Line, Col) Err>>).
-define(FATE_ERR(File, Line, Col, Err), {?Path(File), ?Msg(File, Line, Col, Err)}).
failing_code_gen_contracts() ->
[ ?FATE_ERR(missing_definition, 2, 14,
"Missing definition of function 'foo'.")
, ?FATE_ERR(higher_order_entrypoint, 2, 20,
"The argument\n"
" f : (int) => int\n"
"of entrypoint 'apply' has a higher-order (contains function types) type.")
, ?FATE_ERR(higher_order_entrypoint_return, 2, 3,
"The return type\n"
" (int) => int\n"
"of entrypoint 'add' is higher-order (contains function types).")
, ?FATE_ERR(missing_init_function, 1, 10,
"Missing init function for the contract 'MissingInitFunction'.\n"
"The 'init' function can only be omitted if the state type is 'unit'.")
, ?FATE_ERR(parameterised_state, 3, 8,
"The state type cannot be parameterized.")
, ?FATE_ERR(parameterised_event, 3, 12,
"The event type cannot be parameterized.")
, ?FATE_ERR(polymorphic_aens_resolve, 4, 5,
"Invalid return type of AENS.resolve:\n"
" 'a\n"
"It must be a string or a pubkey type (address, oracle, etc).")
, ?FATE_ERR(bad_aens_resolve, 6, 5,
"Invalid return type of AENS.resolve:\n"
" list(int)\n"
"It must be a string or a pubkey type (address, oracle, etc).")
, ?FATE_ERR(polymorphic_query_type, 3, 5,
"Invalid oracle type\n"
" oracle('a, 'b)\n"
"The query type must not be polymorphic (contain type variables).")
, ?FATE_ERR(polymorphic_response_type, 3, 5,
"Invalid oracle type\n"
" oracle(string, 'r)\n"
"The response type must not be polymorphic (contain type variables).")
, ?FATE_ERR(higher_order_query_type, 3, 5,
"Invalid oracle type\n"
" oracle((int) => int, string)\n"
"The query type must not be higher-order (contain function types).")
, ?FATE_ERR(higher_order_response_type, 3, 5,
"Invalid oracle type\n"
" oracle(string, (int) => int)\n"
"The response type must not be higher-order (contain function types).")
, ?FATE_ERR(child_with_decls, 2, 14,
"Missing definition of function 'f'.")
].
validation_test_() ->
+1 -1
View File
@@ -15,7 +15,7 @@ simple_contracts_test_() ->
Text = "main contract Identity =\n"
" function id(x) = x\n",
?assertMatch(
[{contract_main, _, {con, _, "Identity"}, _,
[{contract_main, _, {con, _, "Identity"},
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
[{guarded, _, [], {id, _, "x"}}]}]}], parse_string(Text)),
ok
+2 -13
View File
@@ -1,7 +1,5 @@
contract C = entrypoint init() = ()
// AENS tests
main contract AENSTest =
contract AENSTest =
// Name resolution
@@ -11,19 +9,10 @@ main contract AENSTest =
stateful entrypoint resolve_string(name : string, key : string) : option(string) =
AENS.resolve(name, key)
stateful entrypoint resolve_contract(name : string, key : string) : option(C) =
AENS.resolve(name, key)
stateful entrypoint resolve_oracle(name : string, key : string) : option(oracle(int, int)) =
AENS.resolve(name, key)
stateful entrypoint resolve_oracle_query(name : string, key : string) : option(oracle_query(int, int)) =
AENS.resolve(name, key)
// Transactions
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash) : unit = // Commitment hash
chash : hash) : unit = // Commitment hash
AENS.preclaim(addr, chash)
stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
-1
View File
@@ -80,4 +80,3 @@ contract AllSyntax =
let sh : shakespeare(shakespeare(int)) =
{wolfgang = state}
sh{wolfgang.wolfgang = sh.wolfgang} // comment
exit("hope you had fun reading this")
@@ -1,9 +0,0 @@
contract BadAENSresolve =
using AENS
type t('a) = option(list('a))
function fail() : t(int) =
resolve("foo.aet", "whatever")
entrypoint main_fun() = ()
@@ -0,0 +1,3 @@
contract MissingInitFunction =
type state = int * int
@@ -0,0 +1,169 @@
contract A = entrypoint init() = ()
main contract C =
datatype custom_datatype('a) = CD('a)
record custom_record('a) = { f : 'a }
// pass
function
passing_ord : 'a is ord ; ('a, 'a) => bool
passing_ord(x, y) = x >= y
// pass
function
passing_eq : 'a is eq ; ('a, 'a) => bool
passing_eq(x, y) = x == y
// fail because eq is not specified for 'a
function
fail_no_eq : ('a, 'a) => bool
fail_no_eq(x, y) = x == y
// fail because 'b is not used
function
fail_unused_tvar : 'a is eq, 'b is eq ; ('a, 'a) => bool
fail_unused_tvar(x, y) = x == y
function
fail_unknown_constraint : 'a is foo ; ('a) => 'a
fail_unknown_constraint(x) = x
// Ord types
function bool_ord(x : bool, y : bool) = x >= y // pass
function bool_eq (x : bool, y : bool) = x == y // pass
function int_ord(x : int, y : int) = x >= y // pass
function int_eq (x : int, y : int) = x == y // pass
function char_ord(x : char, y : char) = x >= y // pass
function char_eq (x : char, y : char) = x == y // pass
function bits_ord(x : bits, y : bits) = x >= y // pass
function bits_eq (x : bits, y : bits) = x == y // pass
function bytes_ord(x : bytes(16), y : bytes(16)) = x >= y // pass
function bytes_eq (x : bytes(16), y : bytes(16)) = x == y // pass
function string_ord(x : string, y : string) = x >= y // pass
function string_eq (x : string, y : string) = x == y // pass
function hash_ord(x : hash, y : hash) = x >= y // pass
function hash_eq (x : hash, y : hash) = x == y // pass
function signature_ord(x : signature, y : signature) = x >= y // pass
function signature_eq (x : signature, y : signature) = x == y // pass
function address_ord(x : address, y : address) = x >= y // pass
function address_eq (x : address, y : address) = x == y // pass
// Eq types
function event_ord(x : Chain.ttl, y : Chain.ttl) = x >= y // fail
function event_eq (x : Chain.ttl, y : Chain.ttl) = x == y // pass
function contract_ord(x : A, y : A) = x >= y // fail
function contract_eq (x : A, y : A) = x == y // pass
// Noncomparable types
type lam = (int, char) => bool
function lambda_ord(x : lam, y : lam) = x >= y // fail
function lambda_eq (x : lam, y : lam) = x == y // fail
// Ord composite types of ord
function list_of_ord_ord(x : list(int), y : list(int)) = x >= y // pass
function list_of_ord_eq (x : list(int), y : list(int)) = x == y // pass
function option_of_ord_ord(x : option(int), y : option(int)) = x >= y // pass
function option_of_ord_eq (x : option(int), y : option(int)) = x == y // pass
function tuple_of_ord_ord(x : (int * bool), y : (int * bool)) = x >= y // pass
function tuple_of_ord_eq (x : (int * bool), y : (int * bool)) = x == y // pass
// Ord composite types of eq
function list_of_eq_ord(x : list(A), y : list(A)) = x >= y // fail
function list_of_eq_eq (x : list(A), y : list(A)) = x == y // pass
function option_of_eq_ord(x : option(A), y : option(A)) = x >= y // fail
function option_of_eq_eq (x : option(A), y : option(A)) = x == y // pass
function tuple_of_eq_ord(x : (A * int), y : (A * int)) = x >= y // fail
function tuple_of_eq_eq (x : (A * int), y : (A * int)) = x == y // pass
// Ord composite types of nomcomparable
function list_of_noncomp_ord(x : list(lam), y : list(lam)) = x >= y // fail
function list_of_noncomp_eq (x : list(lam), y : list(lam)) = x == y // fail
function option_of_noncomp_ord(x : option(lam), y : option(lam)) = x >= y // fail
function option_of_noncomp_eq (x : option(lam), y : option(lam)) = x == y // fail
function tuple_of_noncomp_ord(x : (lam * int), y : (lam * int)) = x >= y // fail
function tuple_of_noncomp_eq (x : (lam * int), y : (lam * int)) = x == y // fail
// Eq composite types of ord
function map_of_ord_ord(x : map(int, int), y : map(int, int)) = x >= y // fail
function map_of_ord_eq (x : map(int, int), y : map(int, int)) = x == y // pass
function oracle_of_ord_ord(x : oracle(int, int), y : oracle(int, int)) = x >= y // fail
function oracle_of_ord_eq (x : oracle(int, int), y : oracle(int, int)) = x == y // pass
function oracle_query_of_ord_ord(x : oracle_query(int, int), y : oracle_query(int, int)) = x >= y // fail
function oracle_query_of_ord_eq (x : oracle_query(int, int), y : oracle_query(int, int)) = x == y // pass
function datatype_of_ord_ord(x : custom_datatype(int), y : custom_datatype(int)) = x >= y // fail
function datatype_of_ord_eq (x : custom_datatype(int), y : custom_datatype(int)) = x == y // pass
function record_of_ord_ord(x : custom_record(int), y : custom_record(int)) = x >= y // fail
function record_of_ord_eq (x : custom_record(int), y : custom_record(int)) = x == y // pass
// Eq composite types of eq
function map_of_eq_ord(x : map(A, A), y : map(A, A)) = x >= y // fail
function map_of_eq_eq (x : map(A, A), y : map(A, A)) = x == y // pass
function oracle_of_eq_ord(x : oracle(A, A), y : oracle(A, A)) = x >= y // fail
function oracle_of_eq_eq (x : oracle(A, A), y : oracle(A, A)) = x == y // pass
function oracle_query_of_eq_ord(x : oracle_query(A, A), y : oracle_query(A, A)) = x >= y // fail
function oracle_query_of_eq_eq (x : oracle_query(A, A), y : oracle_query(A, A)) = x == y // pass
function datatype_of_eq_ord(x : custom_datatype(A), y : custom_datatype(A)) = x >= y // fail
function datatype_of_eq_eq (x : custom_datatype(A), y : custom_datatype(A)) = x == y // pass
function record_of_eq_ord(x : custom_record(A), y : custom_record(A)) = x >= y // fail
function record_of_eq_eq (x : custom_record(A), y : custom_record(A)) = x == y // pass
// Eq composite types of nomcomparable
function map_of_noncomp_ord(x : map(lam, lam), y : map(lam, lam)) = x >= y // fail
function map_of_noncomp_eq (x : map(lam, lam), y : map(lam, lam)) = x == y // fail
function oracle_of_noncomp_ord(x : oracle(lam, lam), y : oracle(lam, lam)) = x >= y // fail
function oracle_of_noncomp_eq (x : oracle(lam, lam), y : oracle(lam, lam)) = x == y // fail
function oracle_query_of_noncomp_ord(x : oracle_query(lam, lam), y : oracle_query(lam, lam)) = x >= y // fail
function oracle_query_of_noncomp_eq (x : oracle_query(lam, lam), y : oracle_query(lam, lam)) = x == y // fail
function datatype_of_noncomp_ord(x : custom_datatype(lam), y : custom_datatype(lam)) = x >= y // fail
function datatype_of_noncomp_eq (x : custom_datatype(lam), y : custom_datatype(lam)) = x == y // pass
function record_of_nomcomp_ord(x : custom_record(lam), y : custom_record(lam)) = x >= y // fail
function record_of_nomcomp_eq (x : custom_record(lam), y : custom_record(lam)) = x == y // pass
entrypoint init() =
let passing_ord_ord = passing_ord([1], [2]) // pass
let passing_ord_eq = passing_ord({[1] = 2}, {[2] = 3}) // fail
let passing_ord_noncomp = passing_ord((x) => x, (x) => x) // fail
let passing_eq_ord = passing_eq([1], [2]) // pass
let passing_eq_eq = passing_eq({[1] = 2}, {[2] = 3}) // pass
let passing_eq_noncomp = passing_eq((x) => x, (x) => x) // fail
()
@@ -1,3 +0,0 @@
contract AliasToAliasToType =
type alias = int * int
type state = alias
@@ -1,2 +0,0 @@
contract AliasToType =
type state = int * int
@@ -1,9 +0,0 @@
contract AliasToAliasToUnit =
type alias = unit
type state = alias
contract AliasToUnit =
type state = unit
main contract ImplicitState =
type sometype = int
+8 -1
View File
@@ -1,5 +1,12 @@
contract NameClash =
entrypoint double_proto : () => int
entrypoint double_proto : () => int
entrypoint proto_and_def : int => int
entrypoint proto_and_def(n) = n + 1
entrypoint double_def(x) = x
entrypoint double_def(y) = 0
@@ -7,4 +14,4 @@ contract NameClash =
entrypoint abort() : int = 0
entrypoint require(b, err) = if(b) abort(err)
entrypoint put(x) = x
entrypoint state(x, y) = x + y
entrypoint state(x, y) = x + y
@@ -1,5 +0,0 @@
contract interface Strokable =
entrypoint stroke : () => string
contract Cat : Strokable =
entrypoint stroke() = "Cat stroke"
@@ -1,10 +0,0 @@
contract interface II =
entrypoint f : () => unit
contract interface I : II =
entrypoint f : () => unit
entrypoint g : () => unit
contract C : I =
entrypoint f() = ()
entrypoint g() = ()
@@ -1,9 +0,0 @@
contract interface I0 =
entrypoint f : () => int
contract interface I1 : I0 =
entrypoint f : () => int
entrypoint something_else : () => int
main contract C =
entrypoint f(x : I1) = x.f() // Here we should know that x has f
@@ -1,13 +0,0 @@
contract interface X : Z =
entrypoint x : () => int
contract interface Y : X =
entrypoint y : () => int
contract interface Z : Y =
entrypoint z : () => int
contract C : Z =
entrypoint x() = 1
entrypoint y() = 1
entrypoint z() = 1
@@ -1,8 +0,0 @@
contract interface I =
entrypoint f : () => int
contract interface II : I =
entrypoint f : () => int
contract C : II =
entrypoint f() = 1
@@ -1,9 +0,0 @@
contract interface I1 =
entrypoint f : () => int
contract interface I2 : I1 =
entrypoint f : () => char
contract C : I2 =
entrypoint f() = 1
entrypoint f() = 'c'
@@ -1,8 +0,0 @@
contract interface I1 =
entrypoint f : () => int
contract interface I2 : I1 =
entrypoint f : () => int
contract C : I2 =
entrypoint f() = 1
@@ -1,5 +0,0 @@
contract interface I : H =
entrypoint f : () => unit
contract C =
entrypoint g() = ()
@@ -1,8 +0,0 @@
contract interface I1 =
entrypoint f : () => int
contract interface I2 : I1 =
entrypoint g : () => int
contract C : I2 =
entrypoint g() = 1
@@ -1,9 +0,0 @@
contract interface I =
entrypoint f : () => int
contract interface J =
entrypoint g : () => char
contract C : I, J =
entrypoint f() = 1
entrypoint g() = 'c'
@@ -1,8 +0,0 @@
contract interface I =
entrypoint f : () => int
contract interface J =
entrypoint f : () => int
contract C : I, J =
entrypoint f() = 1
@@ -1,9 +0,0 @@
contract interface I =
entrypoint f : () => int
contract interface J =
entrypoint f : () => char
contract C : I, J =
entrypoint f() = 1
entrypoint f() = 'c'
@@ -1,2 +0,0 @@
contract C : I =
entrypoint f() = ()
@@ -1,75 +0,0 @@
contract interface Creature =
entrypoint is_alive : () => bool
contract interface Animal : Creature =
entrypoint is_alive : () => bool
entrypoint sound : () => string
contract Cat : Animal =
entrypoint sound() = "meow"
entrypoint is_alive() = true
main contract Main =
entrypoint init() = ()
stateful function g0(_ : Creature) : Cat = Chain.create()
stateful function f0(x : Cat) : Creature = g0(x)
stateful function h0() =
let a : Animal = (Chain.create() : Cat)
let c : Creature = (Chain.create() : Cat)
let c1 : Creature = a
()
stateful function g1(x : Animal) : Cat = Chain.create()
stateful function f1(x : Cat) : Animal = g1(x)
stateful function g11(x : list(Animal)) : list(Cat) = [Chain.create()]
stateful function f11(x : list(Cat)) : list(Animal) = g11(x)
stateful function g12(x : Animal * Animal) : Cat * Cat = (Chain.create(), Chain.create())
stateful function f12(x : Cat * Cat) : Animal * Animal = g12(x)
stateful function g13() : map(Cat, Cat) = { [Chain.create()] = Chain.create() }
stateful function f13() : map(Animal, Animal) = g13()
stateful function g2(x : Cat) : Cat = Chain.create()
stateful function f2(x : Animal) : Animal = g2(x) // fail
stateful function g3(x : Cat) : Animal = f1(x)
stateful function f3(x : Cat) : Cat = g3(x) // fail
stateful function g4(x : (Cat => Animal)) : Cat = Chain.create()
stateful function f4(x : (Animal => Cat)) : Animal = g4(x)
stateful function g44(x : list(list(Cat) => list(Animal))) : Cat = Chain.create()
stateful function f44(x : list(list(Animal) => list(Cat))) : Animal = g44(x)
stateful function g5(x : (Animal => Animal)) : Cat = Chain.create()
stateful function f5(x : (Cat => Cat)) : Animal = g5(x) // fail
stateful function g6() : option(Cat) = Some(Chain.create())
stateful function f6() : option(Animal) = g6()
stateful function h6() : option(Cat) = f6() // fail
type cat_type = Cat
type animal_type = Animal
type cat_cat_map = map(cat_type, cat_type)
type animal_animal_map = map(animal_type, animal_type)
stateful function g71(x : animal_type) : cat_type = Chain.create()
stateful function f71(x : cat_type) : animal_type = g1(x)
stateful function g72() : cat_cat_map = { [Chain.create()] = Chain.create() }
stateful function f72() : animal_animal_map = g13()
stateful function g73() =
let some_cat : Cat = Chain.create()
let some_animal : Animal = some_cat
let some_cat_cat_map : map(Cat, Cat) = g13()
let some_animal_animal_map : map(Animal, Animal) = some_cat_cat_map
let x : Animal = some_animal_animal_map[some_cat] // success
let y : Cat = some_cat_cat_map[some_animal] // fail
()
@@ -1,135 +0,0 @@
contract interface Animal =
entrypoint sound : () => string
contract Cat : Animal =
entrypoint sound() = "meow"
main contract Main =
datatype dt_contra('a) = DT_CONTRA('a => unit)
datatype dt_co('a) = DT_CO(unit => 'a)
datatype dt_inv('a) = DT_INV('a => 'a)
datatype dt_biv('a) = DT_BIV(unit => unit)
datatype dt_inv_sep('a) = DT_INV_SEP_A('a => unit) | DT_INV_SEP_B(unit => 'a)
datatype dt_co_nest_a('a) = DT_CO_NEST_A(dt_contra('a) => unit)
datatype dt_contra_nest_a('a) = DT_CONTRA_NEST_A(dt_co('a) => unit)
datatype dt_contra_nest_b('a) = DT_CONTRA_NEST_B(unit => dt_contra('a))
datatype dt_co_nest_b('a) = DT_CO_NEST_B(unit => dt_co('a))
datatype dt_co_twice('a) = DT_CO_TWICE(('a => unit) => 'a)
datatype dt_contra_twice('a) = DT_CONTRA_TWICE('a => 'a => unit)
datatype dt_a_contra_b_contra('a, 'b) = DT_A_CONTRA_B_CONTRA('a => 'b => unit)
function f_a_to_a_to_u(_ : Animal) : (Animal => unit) = f_a_to_u
function f_a_to_c_to_u(_ : Animal) : (Cat => unit) = f_c_to_u
function f_c_to_a_to_u(_ : Cat) : (Animal => unit) = f_a_to_u
function f_c_to_c_to_u(_ : Cat) : (Cat => unit) = f_c_to_u
function f_u_to_u(_ : unit) : unit = ()
function f_a_to_u(_ : Animal) : unit = ()
function f_c_to_u(_ : Cat) : unit = ()
function f_dt_contra_a_to_u(_ : dt_contra(Animal)) : unit = ()
function f_dt_contra_c_to_u(_ : dt_contra(Cat)) : unit = ()
function f_dt_co_a_to_u(_ : dt_co(Animal)) : unit = ()
function f_dt_co_c_to_u(_ : dt_co(Cat)) : unit = ()
function f_u_to_dt_contra_a(_ : unit) : dt_contra(Animal) = DT_CONTRA(f_a_to_u)
function f_u_to_dt_contra_c(_ : unit) : dt_contra(Cat) = DT_CONTRA(f_c_to_u)
stateful function f_c() : Cat = Chain.create()
stateful function f_a() : Animal = f_c()
stateful function f_u_to_a(_ : unit) : Animal = f_a()
stateful function f_u_to_c(_ : unit) : Cat = f_c()
stateful function f_a_to_a(_ : Animal) : Animal = f_a()
stateful function f_a_to_c(_ : Animal) : Cat = f_c()
stateful function f_c_to_a(_ : Cat) : Animal = f_a()
stateful function f_c_to_c(_ : Cat) : Cat = f_c()
stateful function f_a_to_u_to_c(_ : (Animal => unit)) : Cat = f_c()
stateful function f_c_to_u_to_a(_ : (Cat => unit)) : Animal = f_a()
stateful function f_c_to_u_to_c(_ : (Cat => unit)) : Cat = f_c()
stateful function f_u_to_dt_co_a(_ : unit) : dt_co(Animal) = DT_CO(f_u_to_a)
stateful function f_u_to_dt_co_c(_ : unit) : dt_co(Cat) = DT_CO(f_u_to_c)
stateful entrypoint init() =
let va1 : dt_contra(Animal) = DT_CONTRA(f_a_to_u) // success
let va2 : dt_contra(Animal) = DT_CONTRA(f_c_to_u) // fail
let va3 : dt_contra(Cat) = DT_CONTRA(f_a_to_u) // success
let va4 : dt_contra(Cat) = DT_CONTRA(f_c_to_u) // success
let vb1 : dt_co(Animal) = DT_CO(f_u_to_a) // success
let vb2 : dt_co(Animal) = DT_CO(f_u_to_c) // success
let vb3 : dt_co(Cat) = DT_CO(f_u_to_a) // fail
let vb4 : dt_co(Cat) = DT_CO(f_u_to_c) // success
let vc1 : dt_inv(Animal) = DT_INV(f_a_to_a) // success
let vc2 : dt_inv(Animal) = DT_INV(f_a_to_c) // success
let vc3 : dt_inv(Animal) = DT_INV(f_c_to_a) // fail
let vc4 : dt_inv(Animal) = DT_INV(f_c_to_c) // fail
let vc5 : dt_inv(Cat) = DT_INV(f_a_to_a) // fail
let vc6 : dt_inv(Cat) = DT_INV(f_a_to_c) // fail
let vc7 : dt_inv(Cat) = DT_INV(f_c_to_a) // fail
let vc8 : dt_inv(Cat) = DT_INV(f_c_to_c) // success
let vd1 : dt_biv(Animal) = DT_BIV(f_u_to_u) : dt_biv(Animal) // success
let vd2 : dt_biv(Animal) = DT_BIV(f_u_to_u) : dt_biv(Cat) // success
let vd3 : dt_biv(Cat) = DT_BIV(f_u_to_u) : dt_biv(Animal) // success
let vd4 : dt_biv(Cat) = DT_BIV(f_u_to_u) : dt_biv(Cat) // success
let ve1 : dt_inv_sep(Animal) = DT_INV_SEP_A(f_a_to_u) // success
let ve2 : dt_inv_sep(Animal) = DT_INV_SEP_A(f_c_to_u) // fail
let ve3 : dt_inv_sep(Animal) = DT_INV_SEP_B(f_u_to_a) // success
let ve4 : dt_inv_sep(Animal) = DT_INV_SEP_B(f_u_to_c) // fail
let ve5 : dt_inv_sep(Cat) = DT_INV_SEP_A(f_a_to_u) // fail
let ve6 : dt_inv_sep(Cat) = DT_INV_SEP_A(f_c_to_u) // success
let ve7 : dt_inv_sep(Cat) = DT_INV_SEP_B(f_u_to_a) // fail
let ve8 : dt_inv_sep(Cat) = DT_INV_SEP_B(f_u_to_c) // success
let vf1 : dt_co_nest_a(Animal) = DT_CO_NEST_A(f_dt_contra_a_to_u) // success
let vf2 : dt_co_nest_a(Animal) = DT_CO_NEST_A(f_dt_contra_c_to_u) // success
let vf3 : dt_co_nest_a(Cat) = DT_CO_NEST_A(f_dt_contra_a_to_u) // fail
let vf4 : dt_co_nest_a(Cat) = DT_CO_NEST_A(f_dt_contra_c_to_u) // success
let vg1 : dt_contra_nest_a(Animal) = DT_CONTRA_NEST_A(f_dt_co_a_to_u) // success
let vg2 : dt_contra_nest_a(Animal) = DT_CONTRA_NEST_A(f_dt_co_c_to_u) // fail
let vg3 : dt_contra_nest_a(Cat) = DT_CONTRA_NEST_A(f_dt_co_a_to_u) // success
let vg4 : dt_contra_nest_a(Cat) = DT_CONTRA_NEST_A(f_dt_co_c_to_u) // success
let vh1 : dt_contra_nest_b(Animal) = DT_CONTRA_NEST_B(f_u_to_dt_contra_a) // success
let vh2 : dt_contra_nest_b(Animal) = DT_CONTRA_NEST_B(f_u_to_dt_contra_c) // fail
let vh3 : dt_contra_nest_b(Cat) = DT_CONTRA_NEST_B(f_u_to_dt_contra_a) // success
let vh4 : dt_contra_nest_b(Cat) = DT_CONTRA_NEST_B(f_u_to_dt_contra_c) // success
let vi1 : dt_co_nest_b(Animal) = DT_CO_NEST_B(f_u_to_dt_co_a) // success
let vi2 : dt_co_nest_b(Animal) = DT_CO_NEST_B(f_u_to_dt_co_c) // success
let vi3 : dt_co_nest_b(Cat) = DT_CO_NEST_B(f_u_to_dt_co_a) // fail
let vi4 : dt_co_nest_b(Cat) = DT_CO_NEST_B(f_u_to_dt_co_c) // success
let vj1 : dt_co_twice(Animal) = DT_CO_TWICE(f_a_to_u_to_c : (Animal => unit) => Animal) : dt_co_twice(Animal) // success
let vj2 : dt_co_twice(Animal) = DT_CO_TWICE(f_c_to_u_to_c : (Cat => unit) => Cat ) : dt_co_twice(Cat) // success
let vj3 : dt_co_twice(Cat) = DT_CO_TWICE(f_c_to_u_to_a : (Animal => unit) => Animal) : dt_co_twice(Animal) // fail
let vj4 : dt_co_twice(Cat) = DT_CO_TWICE(f_c_to_u_to_c : (Cat => unit) => Cat ) : dt_co_twice(Cat) // success
let vk01 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
let vk02 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // fail
let vk03 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // fail
let vk04 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // fail
let vk05 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
let vk06 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // success
let vk07 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // fail
let vk08 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // fail
let vk09 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
let vk10 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // fail
let vk11 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // success
let vk12 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // fail
let vk13 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
let vk14 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // success
let vk15 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // success
let vk16 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // success
let vl1 : dt_contra_twice(Animal) = DT_CONTRA_TWICE(f_a_to_a_to_u : Animal => Animal => unit) : dt_contra_twice(Animal) // success
let vl2 : dt_contra_twice(Animal) = DT_CONTRA_TWICE(f_a_to_c_to_u : Cat => Cat => unit) : dt_contra_twice(Cat) // fail
let vl3 : dt_contra_twice(Cat) = DT_CONTRA_TWICE(f_a_to_a_to_u : Animal => Animal => unit) : dt_contra_twice(Animal) // success
let vl4 : dt_contra_twice(Cat) = DT_CONTRA_TWICE(f_c_to_a_to_u : Cat => Cat => unit) : dt_contra_twice(Cat) // success
()
@@ -1,47 +0,0 @@
contract interface Animal =
entrypoint sound : () => string
contract Cat : Animal =
entrypoint sound() = "meow"
main contract Main =
entrypoint oracle() = ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint query() = oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint init() =
let o01 : oracle(Animal, Animal) = oracle() : oracle(Animal, Animal) // success
let o02 : oracle(Animal, Animal) = oracle() : oracle(Animal, Cat) // success
let o03 : oracle(Animal, Animal) = oracle() : oracle(Cat, Animal) // fail
let o04 : oracle(Animal, Animal) = oracle() : oracle(Cat, Cat) // fail
let o05 : oracle(Animal, Cat) = oracle() : oracle(Animal, Animal) // fail
let o06 : oracle(Animal, Cat) = oracle() : oracle(Animal, Cat) // success
let o07 : oracle(Animal, Cat) = oracle() : oracle(Cat, Animal) // fail
let o08 : oracle(Animal, Cat) = oracle() : oracle(Cat, Cat) // fail
let o09 : oracle(Cat, Animal) = oracle() : oracle(Animal, Animal) // success
let o10 : oracle(Cat, Animal) = oracle() : oracle(Animal, Cat) // success
let o11 : oracle(Cat, Animal) = oracle() : oracle(Cat, Animal) // success
let o12 : oracle(Cat, Animal) = oracle() : oracle(Cat, Cat) // success
let o13 : oracle(Cat, Cat) = oracle() : oracle(Animal, Animal) // fail
let o14 : oracle(Cat, Cat) = oracle() : oracle(Animal, Cat) // success
let o15 : oracle(Cat, Cat) = oracle() : oracle(Cat, Animal) // fail
let o16 : oracle(Cat, Cat) = oracle() : oracle(Cat, Cat) // success
let q01 : oracle_query(Animal, Animal) = query() : oracle_query(Animal, Animal) // success
let q02 : oracle_query(Animal, Animal) = query() : oracle_query(Animal, Cat) // success
let q03 : oracle_query(Animal, Animal) = query() : oracle_query(Cat, Animal) // success
let q04 : oracle_query(Animal, Animal) = query() : oracle_query(Cat, Cat) // success
let q05 : oracle_query(Animal, Cat) = query() : oracle_query(Animal, Animal) // fail
let q06 : oracle_query(Animal, Cat) = query() : oracle_query(Animal, Cat) // success
let q07 : oracle_query(Animal, Cat) = query() : oracle_query(Cat, Animal) // fail
let q08 : oracle_query(Animal, Cat) = query() : oracle_query(Cat, Cat) // success
let q09 : oracle_query(Cat, Animal) = query() : oracle_query(Animal, Animal) // fail
let q10 : oracle_query(Cat, Animal) = query() : oracle_query(Animal, Cat) // fail
let q11 : oracle_query(Cat, Animal) = query() : oracle_query(Cat, Animal) // success
let q12 : oracle_query(Cat, Animal) = query() : oracle_query(Cat, Cat) // success
let q13 : oracle_query(Cat, Cat) = query() : oracle_query(Animal, Animal) // fail
let q14 : oracle_query(Cat, Cat) = query() : oracle_query(Animal, Cat) // fail
let q15 : oracle_query(Cat, Cat) = query() : oracle_query(Cat, Animal) // fail
let q16 : oracle_query(Cat, Cat) = query() : oracle_query(Cat, Cat) // success
()
@@ -1,51 +0,0 @@
contract interface Animal =
entrypoint sound : () => string
contract Cat : Animal =
entrypoint sound() = "meow"
main contract Main =
record rec_co('a) = { x : 'a ,
y : () => 'a }
record rec_contra('a) = { x : 'a => unit }
record rec_inv('a) = { x : 'a => unit,
y : () => 'a }
record rec_biv('a) = { x : int }
stateful entrypoint new_cat() : Cat = Chain.create()
stateful entrypoint new_animal() : Animal = new_cat()
stateful entrypoint animal_to_unit(_ : Animal) : unit = ()
stateful entrypoint cat_to_unit(_ : Cat) : unit = ()
stateful entrypoint unit_to_animal() : Animal = new_animal()
stateful entrypoint unit_to_cat() : Cat = new_cat()
stateful entrypoint init() =
let ra : rec_co(Animal) = { x = new_animal(), y = unit_to_animal }
let rc : rec_co(Cat) = { x = new_cat(), y = unit_to_cat }
let r01 : rec_co(Animal) = ra // success
let r02 : rec_co(Animal) = rc // success
let r03 : rec_co(Cat) = ra // fail
let r04 : rec_co(Cat) = rc // sucess
let ratu : rec_contra(Animal) = { x = animal_to_unit }
let rctu : rec_contra(Cat) = { x = cat_to_unit }
let r05 : rec_contra(Animal) = ratu // success
let r06 : rec_contra(Animal) = rctu // fail
let r07 : rec_contra(Cat) = ratu // success
let r08 : rec_contra(Cat) = rctu // success
let rxaya : rec_inv(Animal) = { x = animal_to_unit, y = unit_to_animal }
let rxcyc : rec_inv(Cat) = { x = cat_to_unit, y = unit_to_cat }
let r09 : rec_inv(Animal) = rxaya // success
let r10 : rec_inv(Animal) = rxcyc // fail
let r11 : rec_inv(Cat) = rxaya // fail
let r12 : rec_inv(Cat) = rxcyc // success
let rba : rec_biv(Animal) = { x = 1 }
let rbc : rec_biv(Cat) = { x = 1 }
let r13 : rec_biv(Animal) = rba // success
let r14 : rec_biv(Animal) = rbc // success
let r15 : rec_biv(Cat) = rba // success
let r16 : rec_biv(Cat) = rbc // success
()
+4 -12
View File
@@ -1,12 +1,4 @@
// This is a custom test file if you need to run a compiler without
// changing aeso_compiler_tests.erl
include "List.aes"
contract IntegerHolder =
type state = int
entrypoint init(x) = x
entrypoint get() = state
main contract Test =
stateful entrypoint f(c) = Chain.clone(ref=c, 123)
contract ShareTwo =
record state = {s1 : int, s2 : int}
entrypoint init() = {s1 = 0, s2 = 0}
stateful entrypoint buy() = ()
+5
View File
@@ -48,6 +48,11 @@ contract Warnings =
rv()
2
// Duplicated constraint on 'a
entrypoint
duplicated_tvar_constraint : 'a is eq, 'a is eq ; ('a, 'a) => bool
duplicated_tvar_constraint (x, y) = x == y
namespace FunctionsAsArgs =
function f() = g()