Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 311bf49505 | |||
| 0e3bcba07d | |||
| 699d1f7ab8 | |||
| 1a40a93157 | |||
| c078119bc4 | |||
| 31fd8fe24f | |||
| 9ad8e26e88 | |||
| 5adeb6c93e | |||
| 256df25af4 | |||
| 83abfae32b | |||
| 4ca90feea0 | |||
| 09638daa90 | |||
| d59023a9f4 | |||
| 34b52739fd | |||
| 1c83287d45 | |||
| da92ddbd5d | |||
| c1c169273c | |||
| ad4c341a4a | |||
| f964fa89a1 | |||
| 8d8d9c6b83 | |||
| c98ea25e8b |
@@ -1,5 +1,5 @@
|
||||
mkdocs==1.2.4
|
||||
mkdocs==1.4.2
|
||||
mkdocs-simple-hooks==0.1.5
|
||||
mkdocs-material==7.3.6
|
||||
mkdocs-material==9.0.9
|
||||
mike==1.1.2
|
||||
pygments==2.12.0
|
||||
pygments==2.14.0
|
||||
+31
-1
@@ -8,6 +8,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
### Changed
|
||||
### Removed
|
||||
### Fixed
|
||||
|
||||
## [7.1.0]
|
||||
### Added
|
||||
- Options to enable/disable certain optimizations.
|
||||
- The ability to call a different instance of the current contract
|
||||
```
|
||||
contract Main =
|
||||
entrypoint spend(x : int) : int = x
|
||||
entrypoint f(c : Main) : int = c.spend(10)
|
||||
```
|
||||
- Return a mapping from variables to FATE registers in the compilation output.
|
||||
- Hole expression.
|
||||
### Changed
|
||||
- Type definitions serialised to ACI as `typedefs` field instead of `type_defs` to increase compatibility.
|
||||
- Check contracts and entrypoints modifiers when implementing interfaces.
|
||||
- Contracts can no longer be used as namespaces.
|
||||
- Do not show unused stateful warning for functions that call other contracts with a non-zero value argument.
|
||||
### Fixed
|
||||
- Typechecker crashes if Chain.create or Chain.clone are used without arguments.
|
||||
|
||||
## [7.0.1]
|
||||
### Added
|
||||
- Add CONTRIBUTING.md file.
|
||||
### Changed
|
||||
- Update Sophia syntax docs to include missing information about existing syntax.
|
||||
### Fixed
|
||||
- [404](https://github.com/aeternity/aesophia/issues/404) Contract polymorphism crashes on non-obvious child contract typing.
|
||||
|
||||
## [7.0.0]
|
||||
### Added
|
||||
@@ -352,7 +380,9 @@ 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
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.1.0...HEAD
|
||||
[7.1.0]: https://github.com/aeternity/aesophia/compare/v7.0.1...v7.1.0
|
||||
[7.0.1]: https://github.com/aeternity/aesophia/compare/v7.0.0...v7.0.1
|
||||
[7.0.0]: https://github.com/aeternity/aesophia/compare/v6.1.0...v7.0.0
|
||||
[6.1.0]: https://github.com/aeternity/aesophia/compare/v6.0.2...v6.1.0
|
||||
[6.0.2]: https://github.com/aeternity/aesophia/compare/v6.0.1...v6.0.2
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Contributing to Sophia
|
||||
|
||||
## Checklist For Creating New Pull Requests
|
||||
|
||||
The following points should be considered before creating a new PR to the Sophia compiler.
|
||||
|
||||
### Documentation
|
||||
|
||||
- The [Changelog](CHANGELOG.md) file should be updated for all PRs.
|
||||
- If a PR introduces a new feature that is relevant to the users of the language, the [Sophia Features Documentation](docs/sophia_features.md) should be updated to describe the new feature.
|
||||
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the [Sophia Syntax Documentation](docs/sophia_syntax.md) should be updated to include the new syntax.
|
||||
- If a PR introduces a new library, the public interface of the new library should be fully documented in the [Sophia Standard Library Documentation](docs/sophia_stdlib.md).
|
||||
- If a PR introduces a new compiler option, the new option should be documented in the file
|
||||
[aeso_compiler.md](docs/aeso_compiler.md).
|
||||
|
||||
### Tests
|
||||
|
||||
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the contract [all_syntax.aes](test/contracts/all_syntax.aes) should be updated to include the new syntax.
|
||||
- If a PR fixes a bug, the code that replicates the bug should be added as a new passing test contract.
|
||||
- If a PR introduces a new feature, add tests for both successful and failing usage of that feature. In order to run the entire compilation pipeline and to avoid erroring during intermediate steps, failing tests should not be mixed with the successful ones.
|
||||
|
||||
### Source Code
|
||||
|
||||
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the following code should be updated to handle the new syntax:
|
||||
- The function `aeso_syntax_utils:fold/4` in the file [aeso_syntax_utils.erl](src/aeso_syntax_utils.erl).
|
||||
- Any related pretty printing function in the file [aeso_pretty.erl](src/aeso_pretty.erl), depending on the type of the newly added syntax.
|
||||
|
||||
## Checklist For Creating a Release
|
||||
|
||||
- Update the version in the file [aesophia.app.src](src/aesophia.app.src).
|
||||
- Update the version in the file [rebar.config](rebar.config).
|
||||
- In the [Changelog](CHANGELOG.md):
|
||||
- Update the `Unreleased` changes to be under the new version.
|
||||
- Update the version at the bottom of the file.
|
||||
- Commit and the changes and create a new PR (check the commit of [v6.1.0](https://github.com/aeternity/aesophia/commit/5ad5270e381f6e810d7b8b5cdc168d283e7a90bb) for reference).
|
||||
- Create a release after merging the new PR to `master` branch.
|
||||
- After releasing `aesophia`, refer to each of the following repositories and create new releases as well, using the new `aesophia` release:
|
||||
- [aesophia_cli](https://github.com/aeternity/aesophia_cli)
|
||||
- [aesophia_http](https://github.com/aeternity/aesophia_http)
|
||||
- [aerepl](https://github.com/aeternity/aerepl)
|
||||
@@ -14,6 +14,7 @@ The compiler is currently being used three places
|
||||
* [Features](docs/sophia_features.md)
|
||||
* [Standard library](docs/sophia_stdlib.md)
|
||||
* [Contract examples](docs/sophia_examples.md)
|
||||
* [Contributing](CONTRIBUTING.md)
|
||||
|
||||
Additionally you can check out the [contracts section](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) of the æternity blockchain specification.
|
||||
|
||||
|
||||
+2
-2
@@ -67,7 +67,7 @@ generates the following JSON structure representing the contract interface:
|
||||
}
|
||||
]
|
||||
},
|
||||
"type_defs": [
|
||||
"typedefs": [
|
||||
{
|
||||
"name": "answers",
|
||||
"typedef": {
|
||||
@@ -138,7 +138,7 @@ be included inside another contract.
|
||||
state =>
|
||||
#{record =>
|
||||
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
|
||||
type_defs =>
|
||||
typedefs =>
|
||||
[#{name => <<"answers">>,
|
||||
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
|
||||
vars => []}]}}]}
|
||||
|
||||
@@ -51,6 +51,36 @@ The **pp_** options all print to standard output the following:
|
||||
|
||||
`pp_assembler` - print the generated assembler code
|
||||
|
||||
The option `include_child_contract_symbols` includes the symbols of child contracts functions in the generated fate code. It is turned off by default to avoid making contracts bigger on chain.
|
||||
|
||||
The option `debug_info` includes information related to debugging in the compiler output. Currently this option only includes the mapping from variables to registers.
|
||||
|
||||
#### Options to control which compiler optimizations should run:
|
||||
|
||||
By default all optimizations are turned on, to disable an optimization, it should be
|
||||
explicitly set to false and passed as a compiler option.
|
||||
|
||||
List of optimizations:
|
||||
|
||||
- optimize_inliner
|
||||
- optimize_inline_local_functions
|
||||
- optimize_bind_subexpressions
|
||||
- optimize_let_floating
|
||||
- optimize_simplifier
|
||||
- optimize_drop_unused_lets
|
||||
- optimize_push_consume
|
||||
- optimize_one_shot_var
|
||||
- optimize_write_to_dead_var
|
||||
- optimize_inline_switch_target
|
||||
- optimize_swap_push
|
||||
- optimize_swap_pop
|
||||
- optimize_swap_write
|
||||
- optimize_constant_propagation
|
||||
- optimize_prune_impossible_branches
|
||||
- optimize_single_successful_branch
|
||||
- optimize_inline_store
|
||||
- optimize_float_switch_bod
|
||||
|
||||
#### check_call(ContractString, Options) -> CheckRet
|
||||
|
||||
Types
|
||||
|
||||
+28
-4
@@ -191,6 +191,17 @@ contract interface X : Z =
|
||||
entrypoint z() = 1
|
||||
```
|
||||
|
||||
#### Adding or removing modifiers
|
||||
|
||||
When a `contract` or a `contract interface` implements another `contract interface`, the `payable` and `stateful` modifiers can be kept or changed, both in the contract and in the entrypoints, according to the following rules:
|
||||
|
||||
1. A `payable` contract or interface can implement a `payable` interface or a non-`payable` interface.
|
||||
2. A non-`payable` contract or interface can only implement a non-`payable` interface, and cannot implement a `payable` interface.
|
||||
3. A `payable` entrypoint can implement a `payable` entrypoint or a non-`payable` entrypoint.
|
||||
4. A non-`payable` entrypoint can only implement a non-`payable` entrypoint, and cannot implement a `payable` entrypoint.
|
||||
5. A non-`stateful` entrypoint can implement a `stateful` entrypoint or a non-`stateful` entrypoint.
|
||||
6. A `stateful` entrypoint can only implement a `stateful` entrypoint, and cannot implement a non-`stateful` entrypoint.
|
||||
|
||||
#### 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)),
|
||||
@@ -245,10 +256,10 @@ 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
|
||||
- `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:
|
||||
|
||||
@@ -549,6 +560,19 @@ Sophia has the following types:
|
||||
| oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` |
|
||||
| contract | `ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ` |
|
||||
|
||||
## Hole expression
|
||||
|
||||
Hole expressions, written as `???`, are expressions that are used as a placeholder. During compilation, the compiler will generate a type error indication the type of the hole expression.
|
||||
|
||||
```
|
||||
include "List.aes"
|
||||
contract C =
|
||||
entrypoint f() =
|
||||
List.sum(List.map(???, [1,2,3]))
|
||||
```
|
||||
|
||||
A hole expression found in the example above will generate the error `` Found a hole of type `(int) => int` ``. This says that the compiler expects a function from `int` to `int` in place of the `???` placeholder.
|
||||
|
||||
## Arithmetic
|
||||
|
||||
Sophia integers (`int`) are represented by arbitrary-sized signed words and support the following
|
||||
|
||||
@@ -483,7 +483,7 @@ The address of the account that mined the current block.
|
||||
Chain.timestamp : int
|
||||
```
|
||||
|
||||
The timestamp of the current block.
|
||||
The timestamp of the current block (unix time, milliseconds).
|
||||
|
||||
|
||||
##### difficulty
|
||||
|
||||
+31
-11
@@ -10,8 +10,9 @@ and `*/` and can be nested.
|
||||
### Keywords
|
||||
|
||||
```
|
||||
contract elif else entrypoint false function if import include let mod namespace
|
||||
private payable stateful switch true type record datatype main interface
|
||||
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
|
||||
```
|
||||
|
||||
### Tokens
|
||||
@@ -91,18 +92,30 @@ A Sophia file consists of a sequence of *declarations* in a layout block.
|
||||
```c
|
||||
File ::= Block(TopDecl)
|
||||
|
||||
TopDecl ::= ['payable'] 'contract' Con '=' Block(Decl)
|
||||
| 'namespace' Con '=' Block(Decl)
|
||||
| '@compiler' PragmaOp Version
|
||||
| 'include' String
|
||||
TopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl)
|
||||
| 'contract' 'interface' Con [Implement] '=' Block(Decl)
|
||||
| 'namespace' Con '=' Block(Decl)
|
||||
| '@compiler' PragmaOp Version
|
||||
| 'include' String
|
||||
| Using
|
||||
|
||||
Implement ::= ':' Sep1(Con, ',')
|
||||
|
||||
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
|
||||
| 'record' Id ['(' TVar* ')'] '=' RecordType
|
||||
| 'datatype' Id ['(' TVar* ')'] '=' DataType
|
||||
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
|
||||
| Using
|
||||
|
||||
FunDecl ::= Id ':' Type // Type signature
|
||||
| Id Args [':' Type] '=' Block(Stmt) // Definition
|
||||
| Id Args [':' Type] Block(GuardedDef) // Guarded definitions
|
||||
|
||||
GuardedDef ::= '|' Sep1(Expr, ',') '=' Block(Stmt)
|
||||
|
||||
Using ::= 'using' Con ['as' Con] [UsingParts]
|
||||
UsingParts ::= 'for' '[' Sep1(Id, ',') ']'
|
||||
| 'hiding' '[' Sep1(Id, ',') ']'
|
||||
|
||||
PragmaOp ::= '<' | '=<' | '==' | '>=' | '>'
|
||||
Version ::= Sep1(Int, '.')
|
||||
@@ -172,12 +185,17 @@ Stmt ::= 'switch' '(' Expr ')' Block(Case)
|
||||
| 'elif' '(' Expr ')' Block(Stmt)
|
||||
| 'else' Block(Stmt)
|
||||
| 'let' LetDef
|
||||
| Using
|
||||
| Expr
|
||||
|
||||
LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition
|
||||
| Pattern '=' Block(Stmt) // Value definition
|
||||
|
||||
Case ::= Pattern '=>' Block(Stmt)
|
||||
| Pattern Block(GuardedCase)
|
||||
|
||||
GuardedCase ::= '|' Sep1(Expr, ',') '=>' Block(Stmt)
|
||||
|
||||
Pattern ::= Expr
|
||||
```
|
||||
|
||||
@@ -215,10 +233,12 @@ Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x +
|
||||
| '[' Expr '..' Expr ']' // List range [1..n]
|
||||
| '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val}
|
||||
| '(' Expr ')' // Parens (1 + 2) * 3
|
||||
| '(' Expr '=' Expr ')' // Assign pattern (y = x::_)
|
||||
| Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token
|
||||
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
|
||||
| AccountAddress | ContractAddress // Chain identifiers
|
||||
| OracleAddress | OracleQueryId // Chain identifiers
|
||||
| '???' // Hole expression 1 + ???
|
||||
|
||||
Generator ::= Pattern '<-' Expr // Generator
|
||||
| 'if' '(' Expr ')' // Guard
|
||||
@@ -244,12 +264,12 @@ UnOp ::= '-' | '!'
|
||||
| Operators | Type
|
||||
| --- | ---
|
||||
| `-` `+` `*` `/` `mod` `^` | arithmetic operators
|
||||
| `!` `&&` `\|\|` | logical operators
|
||||
| `!` `&&` `||` | logical operators
|
||||
| `==` `!=` `<` `>` `=<` `>=` | comparison operators
|
||||
| `::` `++` | list operators
|
||||
| `\|>` | functional operators
|
||||
| `|>` | functional operators
|
||||
|
||||
## Operator precendences
|
||||
## Operator precedence
|
||||
|
||||
In order of highest to lowest precedence.
|
||||
|
||||
@@ -263,5 +283,5 @@ In order of highest to lowest precedence.
|
||||
| `::` `++` | right
|
||||
| `<` `>` `=<` `>=` `==` `!=` | none
|
||||
| `&&` | right
|
||||
| `\|\|` | right
|
||||
| `\|>` | left
|
||||
| `||` | right
|
||||
| `|>` | left
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
{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", {tag, "v3.2.0"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
, {eblake2, "1.0.0"}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
|
||||
@@ -14,7 +14,7 @@
|
||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||
]}.
|
||||
|
||||
{relx, [{release, {aesophia, "7.0.0"},
|
||||
{relx, [{release, {aesophia, "7.1.0"},
|
||||
[aesophia, aebytecode, getopt]},
|
||||
|
||||
{dev_mode, true},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{"1.2.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"8269dbd71e9011921c60141636f1baa270a0e784"}},
|
||||
{ref,"2a0a397afad6b45da52572170f718194018bf33c"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
|
||||
+4
-4
@@ -91,7 +91,7 @@ encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HE
|
||||
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0),
|
||||
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1),
|
||||
|
||||
C1 = C0#{type_defs => Tdefs},
|
||||
C1 = C0#{typedefs => Tdefs},
|
||||
|
||||
C2 = case Es of
|
||||
[] -> C1;
|
||||
@@ -111,7 +111,7 @@ encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HE
|
||||
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
|
||||
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
|
||||
#{namespace => #{name => encode_name(Name),
|
||||
type_defs => Tdefs}}.
|
||||
typedefs => Tdefs}}.
|
||||
|
||||
%% Encode a function definition. Currently we are only interested in
|
||||
%% the interface and type.
|
||||
@@ -234,7 +234,7 @@ do_render_aci_json(Json) ->
|
||||
decode_contract(#{contract := #{name := Name,
|
||||
kind := Kind,
|
||||
payable := Payable,
|
||||
type_defs := Ts0,
|
||||
typedefs := Ts0,
|
||||
functions := Fs} = C}) ->
|
||||
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
|
||||
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
|
||||
@@ -246,7 +246,7 @@ decode_contract(#{contract := #{name := Name,
|
||||
end,
|
||||
io_lib:format("~s", [Name])," =\n",
|
||||
decode_tdefs(Ts), decode_funcs(Fs)];
|
||||
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
|
||||
decode_contract(#{namespace := #{name := Name, typedefs := Ts}}) when Ts /= [] ->
|
||||
["namespace ", io_lib:format("~s", [Name])," =\n",
|
||||
decode_tdefs(Ts)];
|
||||
decode_contract(_) -> [].
|
||||
|
||||
+221
-75
@@ -229,7 +229,7 @@ force_bind_fun(X, Type, Env = #env{ what = What }) ->
|
||||
NoCode = get_option(no_code, false),
|
||||
Entry = if X == "init", What == contract, not NoCode ->
|
||||
{reserved_init, Ann, Type};
|
||||
What == contract_interface -> {contract_fun, Ann, Type};
|
||||
What == contract; What == contract_interface -> {contract_fun, Ann, Type};
|
||||
true -> {Ann, Type}
|
||||
end,
|
||||
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
|
||||
@@ -270,15 +270,24 @@ bind_state(Env) ->
|
||||
false -> Env1
|
||||
end.
|
||||
|
||||
-spec bind_field(name(), field_info(), env()) -> env().
|
||||
bind_field(X, Info, Env = #env{ fields = Fields }) ->
|
||||
-spec bind_field_append(name(), field_info(), env()) -> env().
|
||||
bind_field_append(X, Info, Env = #env{ fields = Fields }) ->
|
||||
Fields1 = maps:update_with(X, fun(Infos) -> [Info | Infos] end, [Info], Fields),
|
||||
Env#env{ fields = Fields1 }.
|
||||
|
||||
-spec bind_fields([{name(), field_info()}], env()) -> env().
|
||||
bind_fields([], Env) -> Env;
|
||||
bind_fields([{Id, Info} | Rest], Env) ->
|
||||
bind_fields(Rest, bind_field(Id, Info, Env)).
|
||||
-spec bind_field_update(name(), field_info(), env()) -> env().
|
||||
bind_field_update(X, Info, Env = #env{ fields = Fields }) ->
|
||||
Fields1 = maps:update_with(X, fun([_ | Infos]) -> [Info | Infos]; ([]) -> [Info] end, [Info], Fields),
|
||||
Env#env{ fields = Fields1 }.
|
||||
|
||||
-spec bind_fields([{name(), field_info()}], typed | untyped, env()) -> env().
|
||||
bind_fields([], _Typing, Env) -> Env;
|
||||
bind_fields([{Id, Info} | Rest], Typing, Env) ->
|
||||
NewEnv = case Typing of
|
||||
untyped -> bind_field_append(Id, Info, Env);
|
||||
typed -> bind_field_update(Id, Info, Env)
|
||||
end,
|
||||
bind_fields(Rest, Typing, NewEnv).
|
||||
|
||||
%% Contract entrypoints take three named arguments
|
||||
%% gas : int = Call.gas_left()
|
||||
@@ -296,26 +305,27 @@ contract_call_type({fun_t, Ann, [], Args, Ret}) ->
|
||||
Named("protected", Typed({bool, Ann, false}, Id("bool")))],
|
||||
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)
|
||||
-spec bind_contract(typed | untyped, aeso_syntax:decl(), env()) -> env().
|
||||
bind_contract(Typing, {Contract, Ann, Id, _Impls, Contents}, Env)
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
Key = name(Id),
|
||||
Sys = [{origin, system}],
|
||||
Fields =
|
||||
Key = name(Id),
|
||||
Sys = [{origin, system}],
|
||||
TypeOrFresh = fun({typed, _, _, Type}) -> Type; (_) -> fresh_uvar(Sys) end,
|
||||
Fields =
|
||||
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|
||||
|| {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++
|
||||
|| {fun_decl, AnnF, Entrypoint, Type = {fun_t, _, _, _, _}} <- Contents ] ++
|
||||
[ {field_t, AnnF, Entrypoint,
|
||||
contract_call_type(
|
||||
{fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT})
|
||||
{fun_t, AnnF, [], [TypeOrFresh(Arg) || Arg <- Args], TypeOrFresh(Ret)})
|
||||
}
|
||||
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], {typed, _, _, RetT}}]} <- Contents,
|
||||
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], Ret}]} <- Contents,
|
||||
Name =/= "init"
|
||||
] ++
|
||||
%% Predefined fields
|
||||
[ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++
|
||||
[ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME},
|
||||
contract_call_type(
|
||||
case [ [ArgT || {typed, _, _, ArgT} <- Args]
|
||||
case [ [TypeOrFresh(Arg) || Arg <- Args]
|
||||
|| {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents,
|
||||
aeso_syntax:get_ann(entrypoint, AnnF, false)]
|
||||
++ [ Args
|
||||
@@ -337,7 +347,7 @@ bind_contract({Contract, Ann, Id, _Impls, Contents}, Env)
|
||||
record_t = Id }}
|
||||
|| {field_t, _, {id, FieldAnn, Entrypoint}, Type} <- Fields ],
|
||||
bind_type(Key, Ann, {[], {contract_t, Fields}},
|
||||
bind_fields(FieldInfo, Env)).
|
||||
bind_fields(FieldInfo, Typing, Env)).
|
||||
|
||||
%% What scopes could a given name come from?
|
||||
-spec possible_scopes(env(), qname()) -> [qname()].
|
||||
@@ -416,6 +426,7 @@ lookup_env(Env, Kind, Ann, Name) ->
|
||||
lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
Qual = lists:droplast(QName),
|
||||
Name = lists:last(QName),
|
||||
QNameIsEvent = lists:suffix(["Chain", "event"], QName),
|
||||
AllowPrivate = lists:prefix(Qual, Current),
|
||||
%% Get the scope
|
||||
case maps:get(Qual, Scopes, false) of
|
||||
@@ -431,6 +442,8 @@ lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes
|
||||
{reserved_init, Ann1, Type} ->
|
||||
type_error({cannot_call_init_function, Ann}),
|
||||
{QName, {Ann1, Type}}; %% Return the type to avoid an extra not-in-scope error
|
||||
{contract_fun, Ann1, Type} when AllowPrivate orelse QNameIsEvent ->
|
||||
{QName, {Ann1, Type}};
|
||||
{contract_fun, Ann1, Type} ->
|
||||
type_error({contract_treated_as_namespace, Ann, QName}),
|
||||
{QName, {Ann1, Type}};
|
||||
@@ -447,6 +460,9 @@ lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes
|
||||
end
|
||||
end.
|
||||
|
||||
fun_arity({fun_t, _, _, Args, _}) -> length(Args);
|
||||
fun_arity(_) -> none.
|
||||
|
||||
-spec lookup_record_field(env(), name()) -> [field_info()].
|
||||
lookup_record_field(Env, FieldName) ->
|
||||
maps:get(FieldName, Env#env.fields, []).
|
||||
@@ -457,6 +473,11 @@ lookup_record_field(Env, FieldName, Kind) ->
|
||||
[ Fld || Fld = #field_info{ kind = K } <- lookup_record_field(Env, FieldName),
|
||||
Kind == project orelse K /= contract ].
|
||||
|
||||
lookup_record_field_arity(Env, FieldName, Arity, Kind) ->
|
||||
Fields = lookup_record_field(Env, FieldName, Kind),
|
||||
[ Fld || Fld = #field_info{ field_t = FldType } <- Fields,
|
||||
fun_arity(dereference_deep(FldType)) == Arity ].
|
||||
|
||||
%% -- Name manipulation ------------------------------------------------------
|
||||
|
||||
-spec qname(type_id()) -> qname().
|
||||
@@ -837,6 +858,7 @@ infer(Contracts, Options) ->
|
||||
ets_new(type_vars, [set]),
|
||||
ets_new(warnings, [bag]),
|
||||
ets_new(type_vars_variance, [set]),
|
||||
ets_new(functions_to_implement, [set]),
|
||||
%% Set the variance for builtin types
|
||||
ets_insert(type_vars_variance, {"list", [covariant]}),
|
||||
ets_insert(type_vars_variance, {"option", [covariant]}),
|
||||
@@ -871,7 +893,7 @@ 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(Env0, [Contract0 = {Contract, Ann, ConName, Impls, 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),
|
||||
@@ -887,12 +909,16 @@ infer1(Env0, [{Contract, Ann, ConName, Impls, Code} | Rest], Acc, Options)
|
||||
contract -> ets_insert(defined_contracts, {qname(ConName)});
|
||||
contract_interface -> ok
|
||||
end,
|
||||
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options),
|
||||
check_contract_preserved_payability(Env, ConName, Ann, Impls, Acc, What),
|
||||
populate_functions_to_implement(Env, ConName, Impls, Acc),
|
||||
Env1 = bind_contract(untyped, Contract0, Env),
|
||||
{Env2, Code1} = infer_contract_top(push_scope(contract, ConName, Env1), What, Code, Options),
|
||||
report_unimplemented_functions(Env1, ConName),
|
||||
Contract1 = {Contract, Ann, ConName, Impls, Code1},
|
||||
check_implemented_interfaces(Env1, Contract1, Acc),
|
||||
Env2 = pop_scope(Env1),
|
||||
Env3 = bind_contract(Contract1, Env2),
|
||||
infer1(Env3, Rest, [Contract1 | Acc], Options);
|
||||
Env3 = pop_scope(Env2),
|
||||
%% Rebinding because the qualifications of types are added during type inference. Could we do better?
|
||||
Env4 = bind_contract(typed, Contract1, Env3),
|
||||
infer1(Env4, Rest, [Contract1 | Acc], Options);
|
||||
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) ->
|
||||
when_warning(warn_unused_includes,
|
||||
fun() ->
|
||||
@@ -909,54 +935,71 @@ 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) ->
|
||||
-spec check_contract_preserved_payability(env(), Con, Ann, Impls, Contracts, Kind) -> ok | no_return() when
|
||||
Con :: aeso_syntax:con(),
|
||||
Ann :: aeso_syntax:ann(),
|
||||
Impls :: [Con],
|
||||
Contracts :: [aeso_syntax:decl()],
|
||||
Kind :: contract | contract_interface.
|
||||
check_contract_preserved_payability(Env, ContractName, ContractAnn, Impls, DefinedContracts, Kind) ->
|
||||
Payable = proplists:get_value(payable, ContractAnn, false),
|
||||
ImplsNames = [ name(I) || I <- Impls ],
|
||||
Interfaces = [ Con || I = {contract_interface, _, Con, _, _} <- DefinedContracts,
|
||||
lists:member(name(Con), ImplsNames),
|
||||
aeso_syntax:get_ann(payable, I, false) ],
|
||||
|
||||
create_type_errors(),
|
||||
AllInterfaces = [{name(IName), I} || I = {contract_interface, _, IName, _, _} <- DefinedContracts],
|
||||
ImplsNames = lists:map(fun name/1, Impls),
|
||||
[ type_error({unpreserved_payablity, Kind, ContractName, I}) || I <- Interfaces, Payable == false ],
|
||||
destroy_and_report_type_errors(Env),
|
||||
|
||||
%% 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),
|
||||
ok.
|
||||
|
||||
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),
|
||||
%% Report all functions that were not implemented by the contract ContractName.
|
||||
-spec report_unimplemented_functions(env(), ContractName) -> ok | no_return() when
|
||||
ContractName :: aeso_syntax:con().
|
||||
report_unimplemented_functions(Env, ContractName) ->
|
||||
create_type_errors(),
|
||||
[ type_error({unimplemented_interface_function, ContractName, name(I), FunName})
|
||||
|| {FunName, I, _} <- ets_tab2list(functions_to_implement) ],
|
||||
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).
|
||||
%% Return a list of all function declarations to be implemented, given the list
|
||||
%% of interfaces to be implemented Impls and all the previously defined
|
||||
%% contracts DefinedContracts>
|
||||
-spec functions_to_implement(Impls, DefinedContracts) -> [{InterfaceCon, FunDecl}] when
|
||||
Impls :: [aeso_syntax:con()],
|
||||
DefinedContracts :: [aeso_syntax:decl()],
|
||||
InterfaceCon :: aeso_syntax:con(),
|
||||
FunDecl :: aeso_syntax:fundecl().
|
||||
functions_to_implement(Impls, DefinedContracts) ->
|
||||
ImplsNames = [ name(I) || I <- Impls ],
|
||||
Interfaces = [ I || I = {contract_interface, _, Con, _, _} <- DefinedContracts,
|
||||
lists:member(name(Con), ImplsNames) ],
|
||||
|
||||
%% 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).
|
||||
%% All implemented intrefaces should already be defined
|
||||
InterfacesNames = [name(element(3, I)) || I <- Interfaces],
|
||||
[ begin
|
||||
Found = lists:member(name(Impl), InterfacesNames),
|
||||
Found orelse type_error({referencing_undefined_interface, Impl})
|
||||
end || Impl <- Impls
|
||||
],
|
||||
|
||||
lists:flatten([ [ {Con, Decl} || Decl <- Decls] || {contract_interface, _, Con, _, Decls} <- Interfaces ]).
|
||||
|
||||
%% Fill the ets table functions_to_implement with functions from the implemented
|
||||
%% interfaces Impls.
|
||||
-spec populate_functions_to_implement(env(), ContractName, Impls, DefinedContracts) -> ok | no_return() when
|
||||
ContractName :: aeso_syntax:con(),
|
||||
Impls :: [aeso_syntax:con()],
|
||||
DefinedContracts :: [aeso_syntax:decl()].
|
||||
populate_functions_to_implement(Env, ContractName, Impls, DefinedContracts) ->
|
||||
create_type_errors(),
|
||||
[ begin
|
||||
Inserted = ets_insert_new(functions_to_implement, {name(Id), I, Decl}),
|
||||
[{_, I2, _}] = ets_lookup(functions_to_implement, name(Id)),
|
||||
Inserted orelse type_error({interface_implementation_conflict, ContractName, I, I2, Id})
|
||||
end || {I, Decl = {fun_decl, _, Id, _}} <- functions_to_implement(Impls, DefinedContracts) ],
|
||||
destroy_and_report_type_errors(Env).
|
||||
|
||||
%% Asserts that the main contract is somehow defined.
|
||||
identify_main_contract(Contracts, Options) ->
|
||||
@@ -1214,6 +1257,10 @@ check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con,
|
||||
create_type_errors(),
|
||||
type_error({using_undefined_namespace, Ann, qname(Con)}),
|
||||
destroy_and_report_type_errors(Env);
|
||||
#scope{kind = contract} ->
|
||||
create_type_errors(),
|
||||
type_error({using_undefined_namespace, Ann, qname(Con)}),
|
||||
destroy_and_report_type_errors(Env);
|
||||
Scope ->
|
||||
Nsp = case Parts of
|
||||
none ->
|
||||
@@ -1367,7 +1414,7 @@ check_named_arg(Env, {named_arg_t, Ann, Id, Type, Default}) ->
|
||||
-spec check_fields(env(), #{ name() => aeso_syntax:decl() }, type(), [aeso_syntax:field_t()]) -> env().
|
||||
check_fields(Env, _TypeMap, _, []) -> Env;
|
||||
check_fields(Env, TypeMap, RecTy, [{field_t, Ann, Id, Type} | Fields]) ->
|
||||
Env1 = bind_field(name(Id), #field_info{ ann = Ann, kind = record, field_t = Type, record_t = RecTy }, Env),
|
||||
Env1 = bind_field_append(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"}, [_ | _]) ->
|
||||
@@ -1466,15 +1513,50 @@ check_reserved_entrypoints(Funs) ->
|
||||
-spec check_fundecl(env(), aeso_syntax:decl()) -> {{name(), typesig()}, aeso_syntax:decl()}.
|
||||
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) ->
|
||||
Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type),
|
||||
{{Name, {type_sig, Ann, none, Named, Args, Ret}}, {fun_decl, Ann, Id, Type1}};
|
||||
TypeSig = {type_sig, Ann, none, Named, Args, Ret},
|
||||
register_implementation(Id, TypeSig),
|
||||
{{Name, TypeSig}, {fun_decl, Ann, Id, Type1}};
|
||||
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) ->
|
||||
type_error({fundecl_must_have_funtype, Ann, Id, Type}),
|
||||
{{Name, {type_sig, Ann, none, [], [], Type}}, check_type(Env, Type)}.
|
||||
|
||||
%% Register the function FunId as implemented by deleting it from the functions
|
||||
%% to be implemented table if it is included there, or return true otherwise.
|
||||
-spec register_implementation(FunId, FunSig) -> true | no_return() when
|
||||
FunId :: aeso_syntax:id(),
|
||||
FunSig :: typesig().
|
||||
register_implementation(Id, Sig) ->
|
||||
Name = name(Id),
|
||||
case ets_lookup(functions_to_implement, Name) of
|
||||
[{Name, Interface, Decl = {fun_decl, _, DeclId, _}}] ->
|
||||
DeclStateful = aeso_syntax:get_ann(stateful, Decl, false),
|
||||
DeclPayable = aeso_syntax:get_ann(payable, Decl, false),
|
||||
|
||||
SigEntrypoint = aeso_syntax:get_ann(entrypoint, Sig, false),
|
||||
SigStateful = aeso_syntax:get_ann(stateful, Sig, false),
|
||||
SigPayable = aeso_syntax:get_ann(payable, Sig, false),
|
||||
|
||||
[ type_error({function_should_be_entrypoint, Id, DeclId, Interface})
|
||||
|| not SigEntrypoint ],
|
||||
|
||||
[ type_error({entrypoint_cannot_be_stateful, Id, DeclId, Interface})
|
||||
|| SigStateful andalso not DeclStateful ],
|
||||
|
||||
[ type_error({entrypoint_must_be_payable, Id, DeclId, Interface})
|
||||
|| not SigPayable andalso DeclPayable ],
|
||||
|
||||
ets_delete(functions_to_implement, Name);
|
||||
[] ->
|
||||
true;
|
||||
_ ->
|
||||
error("Ets set has multiple keys")
|
||||
end.
|
||||
|
||||
infer_nonrec(Env, LetFun) ->
|
||||
create_constraints(),
|
||||
NewLetFun = infer_letfun(Env, LetFun),
|
||||
NewLetFun = {{_, Sig}, _} = infer_letfun(Env, LetFun),
|
||||
check_special_funs(Env, NewLetFun),
|
||||
register_implementation(get_letfun_id(LetFun), Sig),
|
||||
solve_then_destroy_and_report_unsolved_constraints(Env),
|
||||
Result = {TypeSig, _} = instantiate(NewLetFun),
|
||||
print_typesig(TypeSig),
|
||||
@@ -1503,7 +1585,8 @@ infer_letrec(Env, Defs) ->
|
||||
ExtendEnv = bind_funs(Funs, Env),
|
||||
Inferred =
|
||||
[ begin
|
||||
Res = {{Name, TypeSig}, _} = infer_letfun(ExtendEnv, LF),
|
||||
Res = {{Name, TypeSig}, LetFun} = infer_letfun(ExtendEnv, LF),
|
||||
register_implementation(get_letfun_id(LetFun), TypeSig),
|
||||
Got = proplists:get_value(Name, Funs),
|
||||
Expect = typesig_to_fun_t(TypeSig),
|
||||
unify(Env, Got, Expect, {check_typesig, Name, Got, Expect}),
|
||||
@@ -1555,6 +1638,9 @@ infer_letfun1(Env0 = #env{ namespace = NS }, {letfun, Attrib, Fun = {id, NameAtt
|
||||
{{Name, TypeSig},
|
||||
{letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewGuardedBodies}}.
|
||||
|
||||
get_letfun_id({fun_clauses, _, Id, _, _}) -> Id;
|
||||
get_letfun_id({letfun, _, Id, _, _, _}) -> Id.
|
||||
|
||||
desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) ->
|
||||
NeedDesugar =
|
||||
case Clauses of
|
||||
@@ -1632,10 +1718,14 @@ check_stateful(#env { current_function = Fun }, _Id, _Type) ->
|
||||
|
||||
%% Hack: don't allow passing the 'value' named arg if not stateful. This only
|
||||
%% works since the user can't create functions with named arguments.
|
||||
check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id, _, "value"}, Default) ->
|
||||
check_stateful_named_arg(#env{ stateful = Stateful, current_function = Fun }, {id, _, "value"}, Default) ->
|
||||
case Default of
|
||||
{int, _, 0} -> ok;
|
||||
_ -> type_error({value_arg_not_allowed, Default, Fun})
|
||||
_ ->
|
||||
case Stateful of
|
||||
true -> when_warning(warn_unused_stateful, fun() -> used_stateful(Fun) end);
|
||||
false -> type_error({value_arg_not_allowed, Default, Fun})
|
||||
end
|
||||
end;
|
||||
check_stateful_named_arg(_, _, _) -> ok.
|
||||
|
||||
@@ -1753,6 +1843,10 @@ infer_expr(_Env, Body={contract_pubkey, As, _}) ->
|
||||
{typed, As, Body, Con};
|
||||
infer_expr(_Env, Body={id, As, "_"}) ->
|
||||
{typed, As, Body, fresh_uvar(As)};
|
||||
infer_expr(_Env, Body={id, As, "???"}) ->
|
||||
T = fresh_uvar(As),
|
||||
type_error({hole_found, As, T}),
|
||||
{typed, As, Body, T};
|
||||
infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid ->
|
||||
{QName, Type} = lookup_name(Env, As, Id),
|
||||
{typed, As, QName, Type};
|
||||
@@ -2204,7 +2298,7 @@ next_count() ->
|
||||
ets_tables() ->
|
||||
[options, type_vars, constraints, freshen_tvars, type_errors,
|
||||
defined_contracts, warnings, function_calls, all_functions,
|
||||
type_vars_variance].
|
||||
type_vars_variance, functions_to_implement].
|
||||
|
||||
clean_up_ets() ->
|
||||
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
|
||||
@@ -2240,10 +2334,18 @@ ets_delete(Name) ->
|
||||
put(aeso_ast_infer_types, maps:remove(Name, Tabs)),
|
||||
ets:delete(TabId).
|
||||
|
||||
ets_delete(Name, Key) ->
|
||||
TabId = ets_tabid(Name),
|
||||
ets:delete(TabId, Key).
|
||||
|
||||
ets_insert(Name, Object) ->
|
||||
TabId = ets_tabid(Name),
|
||||
ets:insert(TabId, Object).
|
||||
|
||||
ets_insert_new(Name, Object) ->
|
||||
TabId = ets_tabid(Name),
|
||||
ets:insert_new(TabId, Object).
|
||||
|
||||
ets_lookup(Name, Key) ->
|
||||
TabId = ets_tabid(Name),
|
||||
ets:lookup(TabId, Key).
|
||||
@@ -2310,7 +2412,12 @@ solve_constraints(Env) ->
|
||||
field_t = FieldType,
|
||||
kind = Kind,
|
||||
context = When }) ->
|
||||
case lookup_record_field(Env, FieldName, Kind) of
|
||||
Arity = fun_arity(dereference_deep(FieldType)),
|
||||
FieldInfos = case Arity of
|
||||
none -> lookup_record_field(Env, FieldName, Kind);
|
||||
_ -> lookup_record_field_arity(Env, FieldName, Arity, Kind)
|
||||
end,
|
||||
case FieldInfos of
|
||||
[] ->
|
||||
type_error({undefined_field, Field}),
|
||||
false;
|
||||
@@ -2805,6 +2912,8 @@ unify0(Env, A, B, Variance, When) ->
|
||||
|
||||
unify1(_Env, {uvar, _, R}, {uvar, _, R}, _Variance, _When) ->
|
||||
true;
|
||||
unify1(_Env, {uvar, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) ->
|
||||
type_error({unify_varargs, When});
|
||||
unify1(Env, {uvar, A, R}, T, _Variance, When) ->
|
||||
case occurs_check(R, T) of
|
||||
true ->
|
||||
@@ -2828,6 +2937,12 @@ 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) ->
|
||||
true;
|
||||
unify1(_Env, _A, {id, _, "void"}, Variance, _When)
|
||||
when Variance == covariant orelse Variance == bivariant ->
|
||||
true;
|
||||
unify1(_Env, {id, _, "void"}, _B, Variance, _When)
|
||||
when Variance == contravariant orelse Variance == bivariant ->
|
||||
true;
|
||||
unify1(_Env, {id, _, Name}, {id, _, Name}, _Variance, _When) ->
|
||||
true;
|
||||
unify1(Env, A = {con, _, NameA}, B = {con, _, NameB}, Variance, When) ->
|
||||
@@ -3285,6 +3400,9 @@ mk_error({cannot_unify, A, B, Cxt, When}) ->
|
||||
[pp(instantiate(A)), pp(instantiate(B))]),
|
||||
{Pos, Ctxt} = pp_when(When),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({hole_found, Ann, Type}) ->
|
||||
Msg = io_lib:format("Found a hole of type `~s`", [pp(instantiate(Type))]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({unbound_variable, Id}) ->
|
||||
Msg = io_lib:format("Unbound variable `~s`", [pp(Id)]),
|
||||
case Id of
|
||||
@@ -3568,7 +3686,7 @@ mk_error({multiple_main_contracts, Ann}) ->
|
||||
Msg = "Only one main contract can be defined.",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({unify_varargs, When}) ->
|
||||
Msg = "Cannot unify variable argument list.",
|
||||
Msg = "Cannot infer types for variable argument list.",
|
||||
{Pos, Ctxt} = pp_when(When),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({clone_no_contract, Ann}) ->
|
||||
@@ -3600,7 +3718,7 @@ 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)]),
|
||||
Msg = io_lib:format("Unimplemented entrypoint `~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)]),
|
||||
@@ -3643,6 +3761,34 @@ mk_error({invalid_oracle_type, Why, What, Ann, Type}) ->
|
||||
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({interface_implementation_conflict, Contract, I1, I2, Fun}) ->
|
||||
Msg = io_lib:format("Both interfaces `~s` and `~s` implemented by "
|
||||
"the contract `~s` have a function called `~s`",
|
||||
[name(I1), name(I2), name(Contract), name(Fun)]),
|
||||
mk_t_err(pos(Contract), Msg);
|
||||
mk_error({function_should_be_entrypoint, Impl, Base, Interface}) ->
|
||||
Msg = io_lib:format("`~s` must be declared as an entrypoint instead of a function "
|
||||
"in order to implement the entrypoint `~s` from the interface `~s`",
|
||||
[name(Impl), name(Base), name(Interface)]),
|
||||
mk_t_err(pos(Impl), Msg);
|
||||
mk_error({entrypoint_cannot_be_stateful, Impl, Base, Interface}) ->
|
||||
Msg = io_lib:format("`~s` cannot be stateful because the entrypoint `~s` in the "
|
||||
"interface `~s` is not stateful",
|
||||
[name(Impl), name(Base), name(Interface)]),
|
||||
mk_t_err(pos(Impl), Msg);
|
||||
mk_error({entrypoint_must_be_payable, Impl, Base, Interface}) ->
|
||||
Msg = io_lib:format("`~s` must be payable because the entrypoint `~s` in the "
|
||||
"interface `~s` is payable",
|
||||
[name(Impl), name(Base), name(Interface)]),
|
||||
mk_t_err(pos(Impl), Msg);
|
||||
mk_error({unpreserved_payablity, Kind, ContractCon, InterfaceCon}) ->
|
||||
KindStr = case Kind of
|
||||
contract -> "contract";
|
||||
contract_interface -> "interface"
|
||||
end,
|
||||
Msg = io_lib:format("Non-payable ~s `~s` cannot implement payable interface `~s`",
|
||||
[KindStr, name(ContractCon), name(InterfaceCon)]),
|
||||
mk_t_err(pos(ContractCon), Msg);
|
||||
mk_error(Err) ->
|
||||
Msg = io_lib:format("Unknown error: ~p", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg).
|
||||
|
||||
+62
-31
@@ -149,17 +149,18 @@
|
||||
|
||||
-type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}.
|
||||
|
||||
-type env() :: #{ type_env := type_env(),
|
||||
fun_env := fun_env(),
|
||||
con_env := con_env(),
|
||||
child_con_env := child_con_env(),
|
||||
event_type => aeso_syntax:typedef(),
|
||||
builtins := builtins(),
|
||||
options := [option()],
|
||||
state_layout => state_layout(),
|
||||
context => context(),
|
||||
vars => [var_name()],
|
||||
functions := #{ fun_name() => fun_def() }
|
||||
-type env() :: #{ type_env := type_env(),
|
||||
fun_env := fun_env(),
|
||||
con_env := con_env(),
|
||||
child_con_env := child_con_env(),
|
||||
event_type => aeso_syntax:typedef(),
|
||||
builtins := builtins(),
|
||||
options := [option()],
|
||||
state_layout => state_layout(),
|
||||
context => context(),
|
||||
vars => [var_name()],
|
||||
functions := #{ fun_name() => fun_def() },
|
||||
saved_fresh_names => #{ var_name() => var_name() }
|
||||
}.
|
||||
|
||||
-define(HASH_BYTES, 32).
|
||||
@@ -170,7 +171,7 @@
|
||||
%% and produces Fate intermediate code.
|
||||
-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> {env(), fcode()}.
|
||||
ast_to_fcode(Code, Options) ->
|
||||
init_fresh_names(),
|
||||
init_fresh_names(Options),
|
||||
{Env1, FCode1} = to_fcode(init_env(Options), Code),
|
||||
FCode2 = optimize(FCode1, Options),
|
||||
Env2 = Env1#{ child_con_env :=
|
||||
@@ -178,13 +179,18 @@ ast_to_fcode(Code, Options) ->
|
||||
fun (_, FC) -> optimize(FC, Options) end,
|
||||
maps:get(child_con_env, Env1)
|
||||
)},
|
||||
clear_fresh_names(),
|
||||
{Env2, FCode2}.
|
||||
Env3 =
|
||||
case proplists:get_value(debug_info, Options, false) of
|
||||
true -> Env2#{ saved_fresh_names => get(saved_fresh_names) };
|
||||
false -> Env2
|
||||
end,
|
||||
clear_fresh_names(Options),
|
||||
{Env3, FCode2}.
|
||||
|
||||
optimize(FCode1, Options) ->
|
||||
Verbose = lists:member(pp_fcode, Options),
|
||||
[io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose],
|
||||
FCode2 = optimize_fcode(FCode1),
|
||||
FCode2 = optimize_fcode(FCode1, Options),
|
||||
[ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ],
|
||||
FCode3 = lambda_lift(FCode2),
|
||||
[ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ],
|
||||
@@ -693,7 +699,7 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
|
||||
end;
|
||||
|
||||
%% Function calls
|
||||
expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) ->
|
||||
expr_to_fcode(Env, _, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, Type}}, Args}) ->
|
||||
Args1 = get_named_args(NamedArgsT, Args),
|
||||
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
|
||||
case expr_to_fcode(Env, Fun) of
|
||||
@@ -1287,20 +1293,28 @@ lambda_lift_exprs(Layout, As) -> [lambda_lift_expr(Layout, A) || A <- As].
|
||||
%% - Constant propagation
|
||||
%% - Inlining
|
||||
|
||||
-spec optimize_fcode(fcode()) -> fcode().
|
||||
optimize_fcode(Code = #{ functions := Funs }) ->
|
||||
Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def) end, Funs) },
|
||||
-spec optimize_fcode(fcode(), [option()]) -> fcode().
|
||||
optimize_fcode(Code = #{ functions := Funs }, Options) ->
|
||||
Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def, Options) end, Funs) },
|
||||
eliminate_dead_code(Code1).
|
||||
|
||||
-spec optimize_fun(fcode(), fun_name(), fun_def()) -> fun_def().
|
||||
optimize_fun(Fcode, Fun, Def = #{ body := Body }) ->
|
||||
%% io:format("Optimizing ~p =\n~s\n", [_Fun, prettypr:format(pp_fexpr(_Body))]),
|
||||
Def#{ body := drop_unused_lets(
|
||||
simplifier(
|
||||
let_floating(
|
||||
bind_subexpressions(
|
||||
inline_local_functions(
|
||||
inliner(Fcode, Fun, Body)))))) }.
|
||||
-spec optimize_fun(fcode(), fun_name(), fun_def(), [option()]) -> fun_def().
|
||||
optimize_fun(Fcode, Fun, Def = #{ body := Body0 }, Options) ->
|
||||
Inliner = proplists:get_value(optimize_inliner, Options, true),
|
||||
InlineLocalFunctions = proplists:get_value(optimize_inline_local_functions, Options, true),
|
||||
BindSubexpressions = proplists:get_value(optimize_bind_subexpressions, Options, true),
|
||||
LetFloating = proplists:get_value(optimize_let_floating, Options, true),
|
||||
Simplifier = proplists:get_value(optimize_simplifier, Options, true),
|
||||
DropUnusedLets = proplists:get_value(optimize_drop_unused_lets, Options, true),
|
||||
|
||||
Body1 = if Inliner -> inliner (Fcode, Fun, Body0); true -> Body0 end,
|
||||
Body2 = if InlineLocalFunctions -> inline_local_functions(Body1); true -> Body1 end,
|
||||
Body3 = if BindSubexpressions -> bind_subexpressions (Body2); true -> Body2 end,
|
||||
Body4 = if LetFloating -> let_floating (Body3); true -> Body3 end,
|
||||
Body5 = if Simplifier -> simplifier (Body4); true -> Body4 end,
|
||||
Body6 = if DropUnusedLets -> drop_unused_lets (Body5); true -> Body5 end,
|
||||
|
||||
Def#{ body := Body6 }.
|
||||
|
||||
%% --- Inlining ---
|
||||
|
||||
@@ -1720,12 +1734,29 @@ resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) ->
|
||||
{{Fun, Ar}, _} -> {def_u, Fun, Ar}
|
||||
end.
|
||||
|
||||
init_fresh_names() ->
|
||||
init_fresh_names(Options) ->
|
||||
proplists:get_value(debug_info, Options, false) andalso init_saved_fresh_names(),
|
||||
put('%fresh', 0).
|
||||
|
||||
clear_fresh_names() ->
|
||||
clear_fresh_names(Options) ->
|
||||
proplists:get_value(debug_info, Options, false) andalso clear_saved_fresh_names(),
|
||||
erase('%fresh').
|
||||
|
||||
init_saved_fresh_names() ->
|
||||
put(saved_fresh_names, #{}).
|
||||
|
||||
clear_saved_fresh_names() ->
|
||||
erase(saved_fresh_names).
|
||||
|
||||
-spec fresh_name_save(string()) -> var_name().
|
||||
fresh_name_save(Name) ->
|
||||
Fresh = fresh_name(),
|
||||
case get(saved_fresh_names) of
|
||||
undefined -> ok;
|
||||
Old -> put(saved_fresh_names, Old#{Fresh => Name})
|
||||
end,
|
||||
Fresh.
|
||||
|
||||
-spec fresh_name() -> var_name().
|
||||
fresh_name() -> fresh_name("%").
|
||||
|
||||
@@ -1854,7 +1885,7 @@ bottom_up(F, Env, Expr) ->
|
||||
(_) -> true end,
|
||||
case ShouldFreshen(X) of
|
||||
true ->
|
||||
Z = fresh_name(),
|
||||
Z = fresh_name_save(X),
|
||||
Env1 = Env#{ Z => E1 },
|
||||
{'let', Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))};
|
||||
false ->
|
||||
|
||||
+12
-4
@@ -112,10 +112,12 @@ from_string(ContractString, Options) ->
|
||||
|
||||
from_string1(ContractString, Options) ->
|
||||
#{ fcode := FCode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, fcode_env := FCodeEnv
|
||||
, folded_typed_ast := FoldedTypedAst
|
||||
, warnings := Warnings } = string_to_code(ContractString, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
|
||||
#{ child_con_env := ChildContracts } = FCodeEnv,
|
||||
SavedFreshNames = maps:get(saved_fresh_names, FCodeEnv, #{}),
|
||||
{FateCode, VarsRegs} = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options),
|
||||
pp_assembler(FateCode, Options),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = version(),
|
||||
@@ -128,7 +130,13 @@ from_string1(ContractString, Options) ->
|
||||
payable => maps:get(payable, FCode),
|
||||
warnings => Warnings
|
||||
},
|
||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
|
||||
ResDbg = Res#{variables_registers => VarsRegs},
|
||||
FinalRes =
|
||||
case proplists:get_value(debug_info, Options, false) of
|
||||
true -> ResDbg;
|
||||
false -> Res
|
||||
end,
|
||||
{ok, maybe_generate_aci(FinalRes, FoldedTypedAst, Options)}.
|
||||
|
||||
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
|
||||
case proplists:get_value(aci, Options) of
|
||||
@@ -185,7 +193,7 @@ check_call1(ContractString0, FunName, Args, Options) ->
|
||||
#{fcode := OrgFcode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, ast := Ast} = string_to_code(ContractString0, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, []),
|
||||
{FateCode, _} = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []),
|
||||
%% collect all hashes and compute the first name without hash collision to
|
||||
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
|
||||
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
||||
|
||||
+123
-75
@@ -9,7 +9,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_fcode_to_fate).
|
||||
|
||||
-export([compile/2, compile/3, term_to_fate/1, term_to_fate/2]).
|
||||
-export([compile/3, compile/4, term_to_fate/1, term_to_fate/2]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-export([optimize_fun/4, to_basic_blocks/1]).
|
||||
@@ -45,7 +45,14 @@
|
||||
-define(s(N), {store, N}).
|
||||
-define(void, {var, 9999}).
|
||||
|
||||
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true, child_contracts = #{}, options = []}).
|
||||
-record(env, { contract,
|
||||
vars = [],
|
||||
locals = [],
|
||||
current_function,
|
||||
tailpos = true,
|
||||
child_contracts = #{},
|
||||
saved_fresh_names = #{},
|
||||
options = [] }).
|
||||
|
||||
%% -- Debugging --------------------------------------------------------------
|
||||
|
||||
@@ -71,16 +78,27 @@ code_error(Err) ->
|
||||
%% -- Main -------------------------------------------------------------------
|
||||
|
||||
%% @doc Main entry point.
|
||||
compile(FCode, Options) ->
|
||||
compile(#{}, FCode, Options).
|
||||
compile(ChildContracts, FCode, Options) ->
|
||||
compile(FCode, SavedFreshNames, Options) ->
|
||||
compile(#{}, FCode, SavedFreshNames, Options).
|
||||
compile(ChildContracts, FCode, SavedFreshNames, Options) ->
|
||||
try
|
||||
compile1(ChildContracts, FCode, SavedFreshNames, Options)
|
||||
after
|
||||
put(variables_registers, undefined)
|
||||
end.
|
||||
|
||||
compile1(ChildContracts, FCode, SavedFreshNames, Options) ->
|
||||
#{ contract_name := ContractName,
|
||||
functions := Functions } = FCode,
|
||||
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, Options),
|
||||
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options),
|
||||
SFuns1 = optimize_scode(SFuns, Options),
|
||||
FateCode = to_basic_blocks(SFuns1),
|
||||
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
|
||||
FateCode.
|
||||
FateCode1 = case proplists:get_value(include_child_contract_symbols, Options, false) of
|
||||
false -> FateCode;
|
||||
true -> add_child_symbols(ChildContracts, FateCode)
|
||||
end,
|
||||
{FateCode1, get_variables_registers()}.
|
||||
|
||||
make_function_id(X) ->
|
||||
aeb_fate_code:symbol_identifier(make_function_name(X)).
|
||||
@@ -89,22 +107,48 @@ make_function_name(event) -> <<"Chain.event">>;
|
||||
make_function_name({entrypoint, Name}) -> Name;
|
||||
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")).
|
||||
|
||||
functions_to_scode(ChildContracts, ContractName, Functions, Options) ->
|
||||
add_child_symbols(ChildContracts, FateCode) ->
|
||||
Funs = lists:flatten([ maps:keys(ChildFuns) || {_, #{functions := ChildFuns}} <- maps:to_list(ChildContracts) ]),
|
||||
Symbols = maps:from_list([ {make_function_id(FName), make_function_name(FName)} || FName <- Funs ]),
|
||||
aeb_fate_code:update_symbols(FateCode, Symbols).
|
||||
|
||||
functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options) ->
|
||||
FunNames = maps:keys(Functions),
|
||||
maps:from_list(
|
||||
[ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)}
|
||||
[ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, SavedFreshNames, Options)}
|
||||
|| {Name, #{args := Args,
|
||||
body := Body,
|
||||
attrs := Attrs,
|
||||
return := Type}} <- maps:to_list(Functions)]).
|
||||
|
||||
function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, Options) ->
|
||||
function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, SavedFreshNames, Options) ->
|
||||
{ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
|
||||
Attrs = Attrs0 -- [stateful], %% Only track private and payable from here.
|
||||
Env = init_env(ChildContracts, ContractName, Functions, Name, Args, Options),
|
||||
Env = init_env(ChildContracts, ContractName, Functions, Name, Args, SavedFreshNames, Options),
|
||||
[ add_variables_register(Env, Arg, Register) ||
|
||||
proplists:get_value(debug_info, Options, false),
|
||||
{Arg, Register} <- Env#env.vars ],
|
||||
SCode = to_scode(Env, Body),
|
||||
{Attrs, {ArgTypes, ResType1}, SCode}.
|
||||
|
||||
get_variables_registers() ->
|
||||
case get(variables_registers) of
|
||||
undefined -> #{};
|
||||
Vs -> Vs
|
||||
end.
|
||||
|
||||
add_variables_register(Env = #env{saved_fresh_names = SavedFreshNames}, Name, Register) ->
|
||||
Olds = get_variables_registers(),
|
||||
RealName = maps:get(Name, SavedFreshNames, Name),
|
||||
FunName =
|
||||
case Env#env.current_function of
|
||||
event -> "Chain.event";
|
||||
{entrypoint, BinName} -> binary_to_list(BinName);
|
||||
{local_fun, QualName} -> lists:last(QualName)
|
||||
end,
|
||||
New = {Env#env.contract, FunName, RealName},
|
||||
put(variables_registers, Olds#{New => Register}).
|
||||
|
||||
-define(tvars, '$tvars').
|
||||
|
||||
typesig_to_scode(Args, Res) ->
|
||||
@@ -149,19 +193,21 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
|
||||
|
||||
%% -- Environment functions --
|
||||
|
||||
init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) ->
|
||||
init_env(ChildContracts, ContractName, FunNames, Name, Args, SavedFreshNames, Options) ->
|
||||
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
|
||||
contract = ContractName,
|
||||
child_contracts = ChildContracts,
|
||||
locals = FunNames,
|
||||
current_function = Name,
|
||||
options = Options,
|
||||
tailpos = true }.
|
||||
tailpos = true,
|
||||
saved_fresh_names = SavedFreshNames }.
|
||||
|
||||
next_var(#env{ vars = Vars }) ->
|
||||
1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]).
|
||||
|
||||
bind_var(Name, Var, Env = #env{ vars = Vars }) ->
|
||||
proplists:get_value(debug_info, Env#env.options, false) andalso add_variables_register(Env, Name, Var),
|
||||
Env#env{ vars = [{Name, Var} | Vars] }.
|
||||
|
||||
bind_local(Name, Env) ->
|
||||
@@ -185,9 +231,10 @@ serialize_contract_code(Env, C) ->
|
||||
end,
|
||||
case maps:get(C, Cache, none) of
|
||||
none ->
|
||||
Options = Env#env.options,
|
||||
FCode = maps:get(C, Env#env.child_contracts),
|
||||
FateCode = compile(Env#env.child_contracts, FCode, Options),
|
||||
Options = Env#env.options,
|
||||
SavedFreshNames = Env#env.saved_fresh_names,
|
||||
FCode = maps:get(C, Env#env.child_contracts),
|
||||
{FateCode, _} = compile1(Env#env.child_contracts, FCode, SavedFreshNames, Options),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = aeso_compiler:version(),
|
||||
OriginalSourceCode = proplists:get_value(original_src, Options, ""),
|
||||
@@ -709,7 +756,7 @@ tuple(N) -> aeb_fate_ops:tuple(?a, N).
|
||||
|
||||
optimize_scode(Funs, Options) ->
|
||||
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
|
||||
Funs).
|
||||
Funs).
|
||||
|
||||
flatten(missing) -> missing;
|
||||
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
|
||||
@@ -1092,7 +1139,8 @@ simpl_top(I, Code, Options) ->
|
||||
simpl_top(0, I, Code, _Options) ->
|
||||
code_error({optimizer_out_of_fuel, I, Code});
|
||||
simpl_top(Fuel, I, Code, Options) ->
|
||||
apply_rules(Fuel, rules(), I, Code, Options).
|
||||
Rules = [R || R = {Rule, _} <- rules(), proplists:get_value(Rule, Options, true)],
|
||||
apply_rules(Fuel, Rules, I, Code, Options).
|
||||
|
||||
apply_rules(Fuel, Rules, I, Code, Options) ->
|
||||
Cons = fun(X, Xs) -> simpl_top(Fuel - 1, X, Xs, Options) end,
|
||||
@@ -1119,29 +1167,29 @@ apply_rules_once([{RName, Rule} | Rules], I, Code) ->
|
||||
-define(RULE(Name), {Name, fun Name/2}).
|
||||
|
||||
merge_rules() ->
|
||||
[?RULE(r_push_consume),
|
||||
?RULE(r_one_shot_var),
|
||||
?RULE(r_write_to_dead_var),
|
||||
?RULE(r_inline_switch_target)
|
||||
[?RULE(optimize_push_consume),
|
||||
?RULE(optimize_one_shot_var),
|
||||
?RULE(optimize_write_to_dead_var),
|
||||
?RULE(optimize_inline_switch_target)
|
||||
].
|
||||
|
||||
rules() ->
|
||||
merge_rules() ++
|
||||
[?RULE(r_swap_push),
|
||||
?RULE(r_swap_pop),
|
||||
?RULE(r_swap_write),
|
||||
?RULE(r_constant_propagation),
|
||||
?RULE(r_prune_impossible_branches),
|
||||
?RULE(r_single_successful_branch),
|
||||
?RULE(r_inline_store),
|
||||
?RULE(r_float_switch_body)
|
||||
[?RULE(optimize_swap_push),
|
||||
?RULE(optimize_swap_pop),
|
||||
?RULE(optimize_swap_write),
|
||||
?RULE(optimize_constant_propagation),
|
||||
?RULE(optimize_prune_impossible_branches),
|
||||
?RULE(optimize_single_successful_branch),
|
||||
?RULE(optimize_inline_store),
|
||||
?RULE(optimize_float_switch_body)
|
||||
].
|
||||
|
||||
%% Removing pushes that are immediately consumed.
|
||||
r_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
|
||||
optimize_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
|
||||
inline_push(Ann1, A, 0, Code, []);
|
||||
%% Writing directly to memory instead of going through the accumulator.
|
||||
r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
||||
optimize_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
||||
IsPush =
|
||||
case op_view(I) of
|
||||
{_, ?a, _} -> true;
|
||||
@@ -1153,7 +1201,7 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
||||
end,
|
||||
if IsPush -> {[{i, merge_ann(Ann1, Ann2), setelement(2, I, R)}], Code};
|
||||
true -> false end;
|
||||
r_push_consume(_, _) -> false.
|
||||
optimize_push_consume(_, _) -> false.
|
||||
|
||||
inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) ->
|
||||
{AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI),
|
||||
@@ -1186,7 +1234,7 @@ split_stack_arg(N, [A | As], Acc) ->
|
||||
split_stack_arg(N1, As, [A | Acc]).
|
||||
|
||||
%% Move PUSHes past non-stack instructions.
|
||||
r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
|
||||
optimize_swap_push(Push = {i, _, PushI}, [I | Code]) ->
|
||||
case op_view(PushI) of
|
||||
{_, ?a, _} ->
|
||||
case independent(Push, I) of
|
||||
@@ -1197,10 +1245,10 @@ r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
|
||||
end;
|
||||
_ -> false
|
||||
end;
|
||||
r_swap_push(_, _) -> false.
|
||||
optimize_swap_push(_, _) -> false.
|
||||
|
||||
%% Move non-stack instruction past POPs.
|
||||
r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
optimize_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
case independent(IA, JA) of
|
||||
true ->
|
||||
case {op_view(I), op_view(J)} of
|
||||
@@ -1208,7 +1256,7 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
{_, false} -> false;
|
||||
{{_, IR, IAs}, {_, RJ, JAs}} ->
|
||||
NonStackI = not lists:member(?a, [IR | IAs]),
|
||||
%% RJ /= ?a to not conflict with r_swap_push
|
||||
%% RJ /= ?a to not conflict with optimize_swap_push
|
||||
PopJ = RJ /= ?a andalso lists:member(?a, JAs),
|
||||
case NonStackI andalso PopJ of
|
||||
false -> false;
|
||||
@@ -1219,22 +1267,22 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
end;
|
||||
false -> false
|
||||
end;
|
||||
r_swap_pop(_, _) -> false.
|
||||
optimize_swap_pop(_, _) -> false.
|
||||
|
||||
%% Match up writes to variables with instructions further down.
|
||||
r_swap_write(I = {i, _, _}, [J | Code]) ->
|
||||
optimize_swap_write(I = {i, _, _}, [J | Code]) ->
|
||||
case {var_writes(I), independent(I, J)} of
|
||||
{[_], true} ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
r_swap_write([J1], I1, Code);
|
||||
optimize_swap_write([J1], I1, Code);
|
||||
_ -> false
|
||||
end;
|
||||
r_swap_write(_, _) -> false.
|
||||
optimize_swap_write(_, _) -> false.
|
||||
|
||||
r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
|
||||
optimize_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
r_swap_write([J1 | Pre], I1, Code);
|
||||
r_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
optimize_swap_write([J1 | Pre], I1, Code);
|
||||
optimize_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
case apply_rules_once(merge_rules(), I, Code0) of
|
||||
{_Rule, New, Rest} ->
|
||||
{lists:reverse(Pre) ++ New, Rest};
|
||||
@@ -1243,27 +1291,27 @@ r_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
false -> false;
|
||||
true ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
r_swap_write([J1 | Pre], I1, Code)
|
||||
optimize_swap_write([J1 | Pre], I1, Code)
|
||||
end
|
||||
end;
|
||||
r_swap_write(_, _, _) -> false.
|
||||
optimize_swap_write(_, _, _) -> false.
|
||||
|
||||
%% Precompute instructions with known values
|
||||
r_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
optimize_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
Store = {i, Ann, {'STORE', S, ?i(false)}},
|
||||
Cons1 = case R of
|
||||
?a -> {i, Ann1, {'CONS', ?void, X, Xs}};
|
||||
_ -> Cons
|
||||
end,
|
||||
{[Cons1, Store], Code};
|
||||
r_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
optimize_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
Store = {i, Ann, {'STORE', S, ?i(true)}},
|
||||
Nil1 = case R of
|
||||
?a -> {i, Ann1, {'NIL', ?void}};
|
||||
_ -> Nil
|
||||
end,
|
||||
{[Nil1, Store], Code};
|
||||
r_constant_propagation({i, Ann, I}, Code) ->
|
||||
optimize_constant_propagation({i, Ann, I}, Code) ->
|
||||
case op_view(I) of
|
||||
false -> false;
|
||||
{Op, R, As} ->
|
||||
@@ -1277,7 +1325,7 @@ r_constant_propagation({i, Ann, I}, Code) ->
|
||||
end
|
||||
end
|
||||
end;
|
||||
r_constant_propagation(_, _) -> false.
|
||||
optimize_constant_propagation(_, _) -> false.
|
||||
|
||||
eval_op('ADD', [X, Y]) when is_integer(X), is_integer(Y) -> X + Y;
|
||||
eval_op('SUB', [X, Y]) when is_integer(X), is_integer(Y) -> X - Y;
|
||||
@@ -1296,12 +1344,12 @@ eval_op('NOT', [false]) -> true;
|
||||
eval_op(_, _) -> no_eval. %% TODO: bits?
|
||||
|
||||
%% Prune impossible branches from switches
|
||||
r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
|
||||
optimize_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
|
||||
case pick_branch(Type, V, Alts) of
|
||||
false -> false;
|
||||
Alt -> {Alt, Code}
|
||||
end;
|
||||
r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
|
||||
optimize_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
|
||||
Alts1 = [if V -> missing; true -> False end,
|
||||
if V -> True; true -> missing end],
|
||||
case Alts == Alts1 of
|
||||
@@ -1312,7 +1360,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def},
|
||||
_ -> {[{switch, ?i(V), boolean, Alts1, Def}], Code}
|
||||
end
|
||||
end;
|
||||
r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
|
||||
optimize_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
|
||||
[{switch, R, Type = {variant, _}, Alts, missing} | Code]) when is_integer(Tag) ->
|
||||
case {R, lists:nth(Tag + 1, Alts)} of
|
||||
{_, missing} ->
|
||||
@@ -1328,7 +1376,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_
|
||||
false -> {Alt, Code}
|
||||
end
|
||||
end;
|
||||
r_prune_impossible_branches(_, _) -> false.
|
||||
optimize_prune_impossible_branches(_, _) -> false.
|
||||
|
||||
pick_branch(boolean, V, [False, True]) when V == true; V == false ->
|
||||
Alt = if V -> True; true -> False end,
|
||||
@@ -1341,7 +1389,7 @@ pick_branch(_Type, _V, _Alts) ->
|
||||
|
||||
%% If there's a single branch that doesn't abort we can push the code for that
|
||||
%% out of the switch.
|
||||
r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
|
||||
optimize_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
|
||||
case push_code_out_of_switch([Def | Alts]) of
|
||||
{_, none} -> false;
|
||||
{_, many} -> false;
|
||||
@@ -1349,7 +1397,7 @@ r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
|
||||
{[Def1 | Alts1], PushedOut} ->
|
||||
{[{switch, R, Type, Alts1, Def1} | PushedOut], Code}
|
||||
end;
|
||||
r_single_successful_branch(_, _) -> false.
|
||||
optimize_single_successful_branch(_, _) -> false.
|
||||
|
||||
push_code_out_of_switch([]) -> {[], none};
|
||||
push_code_out_of_switch([Alt | Alts]) ->
|
||||
@@ -1385,7 +1433,7 @@ does_abort({switch, _, _, Alts, Def}) ->
|
||||
does_abort(_) -> false.
|
||||
|
||||
%% STORE R A, SWITCH R --> SWITCH A
|
||||
r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
|
||||
optimize_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
|
||||
Ann1 =
|
||||
case is_reg(A) of
|
||||
true -> Ann#{ live_out := ordsets:add_element(A, maps:get(live_out, Ann)) };
|
||||
@@ -1404,18 +1452,18 @@ r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def}
|
||||
end;
|
||||
_ -> false %% impossible
|
||||
end;
|
||||
r_inline_switch_target(_, _) -> false.
|
||||
optimize_inline_switch_target(_, _) -> false.
|
||||
|
||||
%% Float switch-body to closest switch
|
||||
r_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
|
||||
optimize_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
{[], [J1, I1 | Code]};
|
||||
r_float_switch_body(_, _) -> false.
|
||||
optimize_float_switch_body(_, _) -> false.
|
||||
|
||||
%% Inline stores
|
||||
r_inline_store({i, _, {'STORE', R, R}}, Code) ->
|
||||
optimize_inline_store({i, _, {'STORE', R, R}}, Code) ->
|
||||
{[], Code};
|
||||
r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
||||
optimize_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
||||
%% Not when A is var unless updating the annotations properly.
|
||||
Inline = case A of
|
||||
{arg, _} -> true;
|
||||
@@ -1423,13 +1471,13 @@ r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
||||
{store, _} -> true;
|
||||
_ -> false
|
||||
end,
|
||||
if Inline -> r_inline_store([I], false, R, A, Code);
|
||||
if Inline -> optimize_inline_store([I], false, R, A, Code);
|
||||
true -> false end;
|
||||
r_inline_store(_, _) -> false.
|
||||
optimize_inline_store(_, _) -> false.
|
||||
|
||||
r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
|
||||
r_inline_store([I | Acc], Progress, R, A, Code);
|
||||
r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||
optimize_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
|
||||
optimize_inline_store([I | Acc], Progress, R, A, Code);
|
||||
optimize_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||
#{ write := W } = attributes(I),
|
||||
Inl = fun(X) when X == R -> A; (X) -> X end,
|
||||
case live_in(R, Ann) of
|
||||
@@ -1449,14 +1497,14 @@ r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||
case lists:member(W, [R, A]) of
|
||||
true when Progress1 -> {lists:reverse(Acc1), Code};
|
||||
true -> false;
|
||||
false -> r_inline_store(Acc1, Progress1, R, A, Code)
|
||||
false -> optimize_inline_store(Acc1, Progress1, R, A, Code)
|
||||
end
|
||||
end;
|
||||
r_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
|
||||
r_inline_store(_, false, _, _, _) -> false.
|
||||
optimize_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
|
||||
optimize_inline_store(_, false, _, _, _) -> false.
|
||||
|
||||
%% Shortcut write followed by final read
|
||||
r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||
optimize_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||
case op_view(I) of
|
||||
{Op, R = {var, _}, As} ->
|
||||
Copy = case J of
|
||||
@@ -1470,11 +1518,11 @@ r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||
end;
|
||||
_ -> false
|
||||
end;
|
||||
r_one_shot_var(_, _) -> false.
|
||||
optimize_one_shot_var(_, _) -> false.
|
||||
|
||||
%% Remove writes to dead variables
|
||||
r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
|
||||
r_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
optimize_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
|
||||
optimize_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
#{ pure := Pure } = attributes(I),
|
||||
case op_view(I) of
|
||||
{_Op, R, As} when R /= ?a, Pure ->
|
||||
@@ -1487,7 +1535,7 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
end;
|
||||
_ -> false
|
||||
end;
|
||||
r_write_to_dead_var(_, _) -> false.
|
||||
optimize_write_to_dead_var(_, _) -> false.
|
||||
|
||||
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
|
||||
op_view({'EXIT', R}) -> {'EXIT', none, [R]};
|
||||
|
||||
@@ -359,9 +359,12 @@ exprAtom() ->
|
||||
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
|
||||
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
|
||||
, letpat()
|
||||
, hole()
|
||||
])
|
||||
end).
|
||||
|
||||
hole() -> ?RULE(token('???'), {id, get_ann(_1), "???"}).
|
||||
|
||||
comprehension_exp() ->
|
||||
?LAZY_P(choice(
|
||||
[ comprehension_bind()
|
||||
|
||||
+6
-1
@@ -261,6 +261,8 @@ type(Type, Options) ->
|
||||
with_options(Options, fun() -> type(Type) end).
|
||||
|
||||
-spec type(aeso_syntax:type()) -> doc().
|
||||
type(F = {fun_t, _, _, var_args, _}) ->
|
||||
type(setelement(4, F, [var_args]));
|
||||
type({fun_t, _, Named, Args, Ret}) ->
|
||||
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret));
|
||||
type({type_sig, _, Named, Args, Ret}) ->
|
||||
@@ -288,7 +290,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(var_args) -> text("var_args").
|
||||
|
||||
-spec args_type([aeso_syntax:type()]) -> doc().
|
||||
args_type(Args) ->
|
||||
@@ -430,6 +434,7 @@ lc_bind(Let) ->
|
||||
bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]'
|
||||
bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]'
|
||||
bin_prec('@') -> { 0, 0, 0}; %% Only in error messages
|
||||
bin_prec('|>') -> {150, 150, 200};
|
||||
bin_prec('||') -> {200, 300, 200};
|
||||
bin_prec('&&') -> {300, 400, 300};
|
||||
bin_prec('<') -> {400, 500, 500};
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@
|
||||
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
|
||||
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
|
||||
-export_type([bin_op/0, un_op/0]).
|
||||
-export_type([decl/0, letbind/0, typedef/0, pragma/0]).
|
||||
-export_type([decl/0, letbind/0, typedef/0, pragma/0, fundecl/0]).
|
||||
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
|
||||
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
|
||||
-export_type([ast/0]).
|
||||
@@ -106,7 +106,7 @@
|
||||
|
||||
-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^'
|
||||
| '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!='
|
||||
| '||' | '&&' | '..'.
|
||||
| '||' | '&&' | '..' | '|>'.
|
||||
-type un_op() :: '-' | '!'.
|
||||
|
||||
-type expr()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{application, aesophia,
|
||||
[{description, "Compiler for Aeternity Sophia language"},
|
||||
{vsn, "7.0.0"},
|
||||
{vsn, "7.1.0"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
|
||||
@@ -21,7 +21,7 @@ test_cases(1) ->
|
||||
" payable stateful entrypoint a(i : int) = i+1\n">>,
|
||||
MapACI = #{contract =>
|
||||
#{name => <<"C">>,
|
||||
type_defs => [],
|
||||
typedefs => [],
|
||||
payable => true,
|
||||
kind => contract_main,
|
||||
functions =>
|
||||
@@ -43,7 +43,7 @@ test_cases(2) ->
|
||||
MapACI = #{contract =>
|
||||
#{name => <<"C">>, payable => false,
|
||||
kind => contract_main,
|
||||
type_defs =>
|
||||
typedefs =>
|
||||
[#{name => <<"allan">>,
|
||||
typedef => <<"int">>,
|
||||
vars => []}],
|
||||
@@ -76,7 +76,7 @@ test_cases(3) ->
|
||||
name => <<"C">>, payable => false, kind => contract_main,
|
||||
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
|
||||
state => <<"unit">>,
|
||||
type_defs =>
|
||||
typedefs =>
|
||||
[#{name => <<"bert">>,
|
||||
typedef =>
|
||||
#{variant =>
|
||||
|
||||
@@ -202,6 +202,12 @@ compilable_contracts() ->
|
||||
"polymorphism_contract_interface_extensions",
|
||||
"polymorphism_contract_interface_same_decl_multi_interface",
|
||||
"polymorphism_contract_interface_same_name_same_type",
|
||||
"polymorphism_variance_switching_chain_create",
|
||||
"polymorphism_variance_switching_void_supertype",
|
||||
"polymorphism_variance_switching_unify_with_interface_decls",
|
||||
"polymorphism_preserve_or_add_payable_contract",
|
||||
"polymorphism_preserve_or_add_payable_entrypoint",
|
||||
"polymorphism_preserve_or_remove_stateful_entrypoint",
|
||||
"missing_init_fun_state_unit",
|
||||
"complex_compare_leq",
|
||||
"complex_compare",
|
||||
@@ -214,6 +220,7 @@ compilable_contracts() ->
|
||||
"polymorphic_map_keys",
|
||||
"unapplied_contract_call",
|
||||
"unapplied_named_arg_builtin",
|
||||
"resolve_field_constraint_by_arity",
|
||||
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||
].
|
||||
|
||||
@@ -730,10 +737,22 @@ failing_contracts() ->
|
||||
"Conflicting updates for field 'foo'">>])
|
||||
, ?TYPE_ERROR(factories_type_errors,
|
||||
[<<?Pos(10,18)
|
||||
"Chain.clone requires `ref` named argument of contract type.">>,
|
||||
"Chain.clone requires `ref` named argument of contract type.">>,
|
||||
<<?Pos(11,18)
|
||||
"Cannot unify `(gas : int, value : int, protected : bool) => if(protected, option(void), void)` and `(gas : int, value : int, protected : bool, int, bool) => 'b`\n"
|
||||
"when checking contract construction of type\n (gas : int, value : int, protected : bool) =>\n if(protected, option(void), void) (at line 11, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool, int, bool) => 'b">>,
|
||||
"Cannot unify `(gas : int, value : int, protected : bool) => if(protected, option(void), void)` and `(gas : int, value : int, protected : bool, int, bool) => if(protected, option(void), void)`\n"
|
||||
"when checking contract construction of type\n"
|
||||
" (gas : int, value : int, protected : bool) =>\n"
|
||||
" if(protected, option(void), void) (at line 11, column 18)\n"
|
||||
"against the expected type\n"
|
||||
" (gas : int, value : int, protected : bool, int, bool) =>\n"
|
||||
" if(protected, option(void), void)">>,
|
||||
<<?Pos(11,18)
|
||||
"Cannot unify `Bakoom` and `Kaboom`\n"
|
||||
"when checking that contract construction of type\n"
|
||||
" Bakoom\n"
|
||||
"arising from resolution of variadic function `Chain.clone`\n"
|
||||
"matches the expected type\n"
|
||||
" Kaboom">>,
|
||||
<<?Pos(12,37)
|
||||
"Cannot unify `int` and `bool`\n"
|
||||
"when checking named argument `gas : int` against inferred type `bool`">>,
|
||||
@@ -846,25 +865,25 @@ failing_contracts() ->
|
||||
"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`">>])
|
||||
[<<?Pos(9,5)
|
||||
"Duplicate definitions of `f` at\n"
|
||||
" - line 8, column 5\n"
|
||||
" - line 9, column 5">>])
|
||||
, ?TYPE_ERROR(polymorphism_contract_missing_implementation,
|
||||
[<<?Pos(4,20)
|
||||
"Unimplemented function `f` from the interface `I1` in the contract `I2`">>
|
||||
"Unimplemented entrypoint `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`">>
|
||||
"Both interfaces `I` and `J` implemented by the contract `C` have a function called `f`">>
|
||||
])
|
||||
, ?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">>
|
||||
[<<?Pos(7,10)
|
||||
"Both interfaces `I` and `J` implemented by the contract `C` have a function called `f`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_contract_interface_undefined_interface,
|
||||
[<<?Pos(1,24)
|
||||
@@ -1025,6 +1044,20 @@ failing_contracts() ->
|
||||
"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(polymorphism_variance_switching_chain_create_fail,
|
||||
[<<?Pos(9,22)
|
||||
"I is not implemented.\n"
|
||||
"when resolving arguments of variadic function `Chain.create`">>,
|
||||
<<?Pos(10,13)
|
||||
"Cannot unify `I` and `C` in a covariant context\n"
|
||||
"when checking the type of the pattern `c2 : C` against the expected type `I`">>,
|
||||
<<?Pos(10,22)
|
||||
"I is not implemented.\n"
|
||||
"when resolving arguments of variadic function `Chain.create`">>,
|
||||
<<?Pos(11,22)
|
||||
"I is not implemented.\n"
|
||||
"when resolving arguments of variadic function `Chain.create`">>
|
||||
])
|
||||
, ?TYPE_ERROR(missing_definition,
|
||||
[<<?Pos(2,14)
|
||||
"Missing definition of function `foo`">>
|
||||
@@ -1109,6 +1142,56 @@ failing_contracts() ->
|
||||
" `oracle(string, (int) => int)`\n"
|
||||
"The response type must not be higher-order (contain function types)">>
|
||||
])
|
||||
, ?TYPE_ERROR(var_args_unify_let,
|
||||
[<<?Pos(3,9)
|
||||
"Cannot infer types for variable argument list.\n"
|
||||
"when checking the type of the pattern `x : 'a` against the expected type `(gas : int, value : int, protected : bool, ref : 'b, var_args) => 'b`">>
|
||||
])
|
||||
, ?TYPE_ERROR(var_args_unify_fun_call,
|
||||
[<<?Pos(6,5)
|
||||
"Cannot infer types for variable argument list.\n"
|
||||
"when checking the application of\n"
|
||||
" `g : (() => 'b) => 'b`\n"
|
||||
"to arguments\n"
|
||||
" `Chain.create : (value : int, var_args) => 'c`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_add_stateful_entrypoint,
|
||||
[<<?Pos(5,25)
|
||||
"`f` cannot be stateful because the entrypoint `f` in the interface `I` is not stateful">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_change_entrypoint_to_function,
|
||||
[<<?Pos(6,14)
|
||||
"`f` must be declared as an entrypoint instead of a function in order to implement the entrypoint `f` from the interface `I`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_non_payable_contract_implement_payable,
|
||||
[<<?Pos(4,10)
|
||||
"Non-payable contract `C` cannot implement payable interface `I`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_non_payable_interface_implement_payable,
|
||||
[<<?Pos(4,20)
|
||||
"Non-payable interface `H` cannot implement payable interface `I`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_remove_payable_entrypoint,
|
||||
[<<?Pos(5,16)
|
||||
"`f` must be payable because the entrypoint `f` in the interface `I` is payable">>
|
||||
])
|
||||
, ?TYPE_ERROR(calling_child_contract_entrypoint,
|
||||
[<<?Pos(5,20)
|
||||
"Invalid call to contract entrypoint `F.g`.\n"
|
||||
"It must be called as `c.g` for some `c : F`.">>])
|
||||
, ?TYPE_ERROR(using_contract_as_namespace,
|
||||
[<<?Pos(5,3)
|
||||
"Cannot use undefined namespace F">>])
|
||||
, ?TYPE_ERROR(hole_expression,
|
||||
[<<?Pos(5,13)
|
||||
"Found a hole of type `bool`">>,
|
||||
<<?Pos(6,17)
|
||||
"Found a hole of type `string`">>,
|
||||
<<?Pos(9,37)
|
||||
"Found a hole of type `(int) => int`">>,
|
||||
<<?Pos(13,20)
|
||||
"Found a hole of type `'a`">>
|
||||
])
|
||||
].
|
||||
|
||||
validation_test_() ->
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract F =
|
||||
entrypoint g() = 1
|
||||
|
||||
main contract C =
|
||||
entrypoint f() = F.g()
|
||||
@@ -1,16 +1,7 @@
|
||||
|
||||
contract interface Remote =
|
||||
entrypoint up_to : (int) => list(int)
|
||||
entrypoint sum : (list(int)) => int
|
||||
entrypoint some_string : () => string
|
||||
entrypoint pair : (int, string) => int * string
|
||||
entrypoint squares : (int) => list(int * int)
|
||||
entrypoint filter_some : (list(option(int))) => list(int)
|
||||
entrypoint all_some : (list(option(int))) => option(list(int))
|
||||
|
||||
contract ComplexTypes =
|
||||
|
||||
record state = { worker : Remote }
|
||||
record state = { worker : ComplexTypes }
|
||||
|
||||
entrypoint init(worker) = {worker = worker}
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
|
||||
// Testing primitives for accessing the block chain environment
|
||||
contract interface Interface =
|
||||
entrypoint contract_address : () => address
|
||||
entrypoint call_origin : () => address
|
||||
entrypoint call_caller : () => address
|
||||
entrypoint call_value : () => int
|
||||
|
||||
contract Environment =
|
||||
|
||||
record state = {remote : Interface}
|
||||
record state = {remote : Environment}
|
||||
|
||||
entrypoint init(remote) = {remote = remote}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
include "List.aes"
|
||||
|
||||
contract C =
|
||||
entrypoint f() =
|
||||
let ??? = true
|
||||
let v = ???
|
||||
let q = v == "str"
|
||||
let xs = [1, 2, 3, 4]
|
||||
switch (List.first(List.map(???, xs)))
|
||||
Some(x) => x + 1
|
||||
None => 0
|
||||
|
||||
function g() = ???
|
||||
@@ -0,0 +1,5 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : I =
|
||||
stateful entrypoint f() = 1
|
||||
@@ -0,0 +1,6 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : I =
|
||||
entrypoint init() = ()
|
||||
function f() = 1
|
||||
@@ -0,0 +1,5 @@
|
||||
payable contract interface I =
|
||||
payable entrypoint f : () => int
|
||||
|
||||
contract C : I =
|
||||
entrypoint f() = 123
|
||||
@@ -0,0 +1,8 @@
|
||||
payable contract interface I =
|
||||
payable entrypoint f : () => int
|
||||
|
||||
contract interface H : I =
|
||||
payable entrypoint f : () => int
|
||||
|
||||
payable contract C : H =
|
||||
entrypoint f() = 123
|
||||
@@ -0,0 +1,14 @@
|
||||
contract interface F =
|
||||
entrypoint f : () => int
|
||||
|
||||
payable contract interface G : F =
|
||||
payable entrypoint f : () => int
|
||||
entrypoint g : () => int
|
||||
|
||||
payable contract interface H =
|
||||
payable entrypoint h : () => int
|
||||
|
||||
payable contract C : G, H =
|
||||
payable entrypoint f() = 1
|
||||
payable entrypoint g() = 2
|
||||
payable entrypoint h() = 3
|
||||
@@ -0,0 +1,7 @@
|
||||
contract interface I =
|
||||
payable entrypoint f : () => int
|
||||
entrypoint g : () => int
|
||||
|
||||
contract C : I =
|
||||
payable entrypoint f() = 1
|
||||
payable entrypoint g() = 2
|
||||
@@ -0,0 +1,7 @@
|
||||
contract interface I =
|
||||
stateful entrypoint f : () => int
|
||||
stateful entrypoint g : () => int
|
||||
|
||||
contract C : I =
|
||||
stateful entrypoint f() = 1
|
||||
entrypoint g() = 2
|
||||
@@ -0,0 +1,5 @@
|
||||
contract interface I =
|
||||
payable entrypoint f : () => int
|
||||
|
||||
contract C : I =
|
||||
entrypoint f() = 1
|
||||
@@ -0,0 +1,29 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C1 : I =
|
||||
entrypoint f() = 123
|
||||
|
||||
contract C2 : I =
|
||||
entrypoint f() = 888
|
||||
|
||||
namespace Make =
|
||||
stateful function new1() : I = Chain.create() : C1
|
||||
stateful function new2() : I = Chain.create() : C2
|
||||
|
||||
stateful function new(c : I) : int = c.f()
|
||||
|
||||
main contract Main =
|
||||
stateful entrypoint test1() =
|
||||
let c = Make.new1()
|
||||
Make.new(c)
|
||||
|
||||
stateful entrypoint test2() =
|
||||
let c = Make.new2()
|
||||
Make.new(c)
|
||||
|
||||
stateful entrypoint test3() =
|
||||
let c1 = Chain.create() : C1 // succeeds
|
||||
let c2 : I = Chain.create() : C1 // succeeds
|
||||
let c3 : C1 = Chain.create() // succeeds
|
||||
()
|
||||
@@ -0,0 +1,12 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : I =
|
||||
entrypoint f() = 123
|
||||
|
||||
main contract Main =
|
||||
stateful entrypoint test() =
|
||||
let c1 : I = Chain.create() // fails
|
||||
let c2 : C = Chain.create() : I // fails
|
||||
let c3 = Chain.create() : I // fails
|
||||
()
|
||||
@@ -0,0 +1,5 @@
|
||||
payable contract interface SalesOffer =
|
||||
entrypoint init : (address, address, int, int) => unit
|
||||
|
||||
payable contract Test : SalesOffer =
|
||||
entrypoint init(_, _, _, _) = ()
|
||||
@@ -0,0 +1,5 @@
|
||||
payable contract interface SalesOffer =
|
||||
entrypoint init : (address, address, int, int) => void
|
||||
|
||||
payable contract Test : SalesOffer =
|
||||
entrypoint init(_ : address, _ : address, _ : int, _ : int) = ()
|
||||
@@ -0,0 +1,9 @@
|
||||
contract First =
|
||||
entrypoint print_num(x) = 1 + x
|
||||
|
||||
contract Second =
|
||||
entrypoint print_num() = 1
|
||||
|
||||
main contract Test =
|
||||
entrypoint f(c) = c.print_num(1)
|
||||
entrypoint g(c) = c.print_num()
|
||||
@@ -1,7 +1,4 @@
|
||||
|
||||
contract interface SpendContract =
|
||||
entrypoint withdraw : (int) => int
|
||||
|
||||
contract SpendTest =
|
||||
|
||||
stateful entrypoint spend(to, amount) =
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
contract F =
|
||||
entrypoint g() = 1
|
||||
|
||||
main contract C =
|
||||
using F for [g]
|
||||
|
||||
entrypoint f() = g()
|
||||
@@ -0,0 +1,7 @@
|
||||
contract C =
|
||||
stateful function g(h) =
|
||||
h()
|
||||
|
||||
stateful entrypoint f() =
|
||||
g(Chain.create)
|
||||
123
|
||||
@@ -0,0 +1,4 @@
|
||||
main contract C =
|
||||
stateful entrypoint f() =
|
||||
let x = Chain.clone
|
||||
123
|
||||
@@ -12,7 +12,7 @@ namespace UnusedNamespace =
|
||||
// Unused
|
||||
private function h() = 3
|
||||
|
||||
contract Warnings =
|
||||
main contract Warnings =
|
||||
|
||||
type state = int
|
||||
|
||||
@@ -58,3 +58,10 @@ namespace FunctionsAsArgs =
|
||||
private function inc(n : int) : int = n + 1
|
||||
// Never used
|
||||
private function dec(n : int) : int = n - 1
|
||||
|
||||
contract Remote =
|
||||
entrypoint id(_) = 0
|
||||
|
||||
contract C =
|
||||
payable stateful entrypoint
|
||||
call_missing_con() : int = (ct_1111111111111111111111111111112JF6Dz72 : Remote).id(value = 1, 0)
|
||||
|
||||
Reference in New Issue
Block a user