Compare commits

..

17 Commits

Author SHA1 Message Date
Hans Svensson 311bf49505 Prepare v7.1.0 release (#438) 2023-02-24 09:40:06 +01:00
Denis Davidyuk 0e3bcba07d Fix markup in sophia_features.md (#437) 2023-02-15 13:27:29 +03:00
Marco Walz 699d1f7ab8 fix: use latest pygments version to generate docs (#435) 2023-02-02 08:32:54 +01:00
Marco Walz 1a40a93157 chore(deps): mkdocs version update (#434) 2023-02-02 10:16:27 +03:00
Gaith Hallak c078119bc4 Add hole expression (#433)
* Add hole expressions

* Fix the issue of unreported holes

* Add tests

* New line in the end of the test file

* Update CHANGELOG

* Add hole expression to the docs

* Do not treat hole as a special type

* Update docs

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2023-01-12 16:23:36 +03:00
Gaith Hallak 31fd8fe24f Hide warning when calling with non-0 value arg (#431)
* Hide warning when calling with non-0 value arg

* Update the tests

* Update CHANGELOG
2022-12-12 11:44:24 +03:00
Nikita Fuchs 9ad8e26e88 Add clarification for Chain.timestamp in the stdlib docs (#429) 2022-12-07 17:34:55 +03:00
Gaith Hallak 5adeb6c93e Ban using contracts as namespaces (#428)
* Ban calling contracts functions as functions namespaces

* Ban using contracts as namespaces

* Add tests

* Update CHANGELOG

* Separate guards with a semicolon
2022-11-23 12:03:24 +03:00
Gaith Hallak 256df25af4 Check contracts and entrypoints modifiers when implementing interfaces (#427)
* Check contracts and entrypoints modifiers when implementing interfaces

* Fix existing tests

* Add passing tests

* Add failing tests

* Update docs

* Update CHANGELOG
2022-11-17 11:40:57 +03:00
Gaith Hallak 83abfae32b Ban the unification of uvars and var_args functions (#423)
* Ban the unification of uvar and var_args function

* Update CHANGELOG

* Fix the tests

* Undo indent

* Change the error message for unify_varargs
2022-11-01 18:10:57 +02:00
Denis Davidyuk 4ca90feea0 Rename type_defs to typedefs in ACI to increase compatibility (#421) 2022-11-01 08:55:00 +02:00
Gaith Hallak 09638daa90 Return mapping from variables to registers in fate compilation (#411)
* Return mapping from variables to registers

* Fix dialyzer issues

* Record real names

* Report saved fresh names as part of fcode env

* Undo whitespace changes

* Fix dialyzer warnings

* Formatting fix

* Use function names as strings

* Manually handle making function names

* Update CHANGELOG

* Make variables registers optional

* Update docs about the new flag

* Remove empty saved_fresh_names map from fcode env
2022-10-25 09:42:02 +03:00
Gaith Hallak d59023a9f4 Allow calling a different instance of the current contract (#379)
Add functions as fields before inferring

Unbound untyped fields before binding typed ones

Fix failing tests

Make complex_types contract non-compatible with aevm

Reduce code duplication

Undo changes to test.aes

Remove special handling of __constructor__ field

Resolve field constraint by arity of contract function

Update CHANGELOG

Update CHANGELOG.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

Split bind_field function

Add a comment about rebinding
2022-10-23 15:01:28 +03:00
Radosław Rowicki 34b52739fd Include all functions in the symbols map (#418)
* Include all functions in the symbols map

* .

* remove improper wording

* Use update_symbols exported from aebytecode

* Extract adding child symbols into a separate fun

* Make child contracts symbols optional

* Document include_child_contract_symbols option

Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>
2022-10-07 15:57:37 +03:00
Gaith Hallak 1c83287d45 Add separate flags for each scode optimization (#410)
* Add separate flags for each scode optimization

* Add a list of available optimizations to docs

* Update CONTRIBUTING.md

* Update docs/aeso_compiler.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

* Prefix rules functions with optimize_ instead of r_

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2022-10-07 12:09:53 +03:00
Gaith Hallak da92ddbd5d Polymorphism fixes (#415)
* Assume that void is a supertype of all types

* Add test for void supertype

* Unify functions with decls from implemented interfaces

* Rename delete_if_implementation

* Match only with function name and without typesig
2022-10-04 12:40:50 +03:00
Gaith Hallak c1c169273c Add options to enable/disable certain optimizations (#409)
* Add flags to enable/disable specific optimizations

* Fix typos

* Enable/disable scode optimization

* Update CHANGELOG.md

* Remove optimize_all option
2022-08-30 10:14:46 +03:00
41 changed files with 726 additions and 239 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
mkdocs==1.2.4 mkdocs==1.4.2
mkdocs-simple-hooks==0.1.5 mkdocs-simple-hooks==0.1.5
mkdocs-material==7.3.6 mkdocs-material==9.0.9
mike==1.1.2 mike==1.1.2
pygments==2.12.0 pygments==2.14.0
+21 -1
View File
@@ -10,6 +10,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed ### Removed
### Fixed ### 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] ## [7.0.1]
### Added ### Added
- Add CONTRIBUTING.md file. - Add CONTRIBUTING.md file.
@@ -361,7 +380,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply - Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string. pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.0.1...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.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 [7.0.0]: https://github.com/aeternity/aesophia/compare/v6.1.0...v7.0.0
[6.1.0]: https://github.com/aeternity/aesophia/compare/v6.0.2...v6.1.0 [6.1.0]: https://github.com/aeternity/aesophia/compare/v6.0.2...v6.1.0
+2
View File
@@ -10,6 +10,8 @@ The following points should be considered before creating a new PR to the Sophia
- 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 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 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 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 ### Tests
+2 -2
View File
@@ -67,7 +67,7 @@ generates the following JSON structure representing the contract interface:
} }
] ]
}, },
"type_defs": [ "typedefs": [
{ {
"name": "answers", "name": "answers",
"typedef": { "typedef": {
@@ -138,7 +138,7 @@ be included inside another contract.
state => state =>
#{record => #{record =>
[#{name => <<"a">>,type => <<"Answers.answers">>}]}, [#{name => <<"a">>,type => <<"Answers.answers">>}]},
type_defs => typedefs =>
[#{name => <<"answers">>, [#{name => <<"answers">>,
typedef => #{<<"map">> => [<<"string">>,<<"int">>]}, typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
vars => []}]}}]} vars => []}]}}]}
+30
View File
@@ -51,6 +51,36 @@ The **pp_** options all print to standard output the following:
`pp_assembler` - print the generated assembler code `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 #### check_call(ContractString, Options) -> CheckRet
Types Types
+28 -4
View File
@@ -191,6 +191,17 @@ contract interface X : Z =
entrypoint z() = 1 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 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)), 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: The following facts apply here:
- `co('a)` is a subtype of `co('b) when `'a` is a subtype of `'b` - `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` - `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` - `in('a)` is a subtype of `in('b)` when `'a` is equal to `'b`
- `bi('a)` is a subtype of `bi('b) always - `bi('a)` is a subtype of `bi('b)` always
That altogether induce the following rules of subtyping in Sophia: 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` | | oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` |
| contract | `ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ` | | 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 ## Arithmetic
Sophia integers (`int`) are represented by arbitrary-sized signed words and support the following Sophia integers (`int`) are represented by arbitrary-sized signed words and support the following
+1 -1
View File
@@ -483,7 +483,7 @@ The address of the account that mined the current block.
Chain.timestamp : int Chain.timestamp : int
``` ```
The timestamp of the current block. The timestamp of the current block (unix time, milliseconds).
##### difficulty ##### difficulty
+1
View File
@@ -238,6 +238,7 @@ Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x +
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%' | Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
| AccountAddress | ContractAddress // Chain identifiers | AccountAddress | ContractAddress // Chain identifiers
| OracleAddress | OracleQueryId // Chain identifiers | OracleAddress | OracleQueryId // Chain identifiers
| '???' // Hole expression 1 + ???
Generator ::= Pattern '<-' Expr // Generator Generator ::= Pattern '<-' Expr // Generator
| 'if' '(' Expr ')' // Guard | 'if' '(' Expr ')' // Guard
+2 -2
View File
@@ -2,7 +2,7 @@
{erl_opts, [debug_info]}. {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"} , {getopt, "1.0.1"}
, {eblake2, "1.0.0"} , {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
@@ -14,7 +14,7 @@
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}. ]}.
{relx, [{release, {aesophia, "7.0.1"}, {relx, [{release, {aesophia, "7.1.0"},
[aesophia, aebytecode, getopt]}, [aesophia, aebytecode, getopt]},
{dev_mode, true}, {dev_mode, true},
+1 -1
View File
@@ -1,7 +1,7 @@
{"1.2.0", {"1.2.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"8269dbd71e9011921c60141636f1baa270a0e784"}}, {ref,"2a0a397afad6b45da52572170f718194018bf33c"}},
0}, 0},
{<<"aeserialization">>, {<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git", {git,"https://github.com/aeternity/aeserialization.git",
+4 -4
View File
@@ -91,7 +91,7 @@ encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HE
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0), {Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0),
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1), {Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1),
C1 = C0#{type_defs => Tdefs}, C1 = C0#{typedefs => Tdefs},
C2 = case Es of C2 = case Es of
[] -> C1; [] -> C1;
@@ -111,7 +111,7 @@ encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HE
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) -> encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ], Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
#{namespace => #{name => encode_name(Name), #{namespace => #{name => encode_name(Name),
type_defs => Tdefs}}. typedefs => Tdefs}}.
%% Encode a function definition. Currently we are only interested in %% Encode a function definition. Currently we are only interested in
%% the interface and type. %% the interface and type.
@@ -234,7 +234,7 @@ do_render_aci_json(Json) ->
decode_contract(#{contract := #{name := Name, decode_contract(#{contract := #{name := Name,
kind := Kind, kind := Kind,
payable := Payable, payable := Payable,
type_defs := Ts0, typedefs := Ts0,
functions := Fs} = C}) -> functions := Fs} = C}) ->
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end, MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++ Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
@@ -246,7 +246,7 @@ decode_contract(#{contract := #{name := Name,
end, end,
io_lib:format("~s", [Name])," =\n", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts), decode_funcs(Fs)]; 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", ["namespace ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts)]; decode_tdefs(Ts)];
decode_contract(_) -> []. decode_contract(_) -> [].
+221 -75
View File
@@ -229,7 +229,7 @@ force_bind_fun(X, Type, Env = #env{ what = What }) ->
NoCode = get_option(no_code, false), NoCode = get_option(no_code, false),
Entry = if X == "init", What == contract, not NoCode -> Entry = if X == "init", What == contract, not NoCode ->
{reserved_init, Ann, Type}; {reserved_init, Ann, Type};
What == contract_interface -> {contract_fun, Ann, Type}; What == contract; What == contract_interface -> {contract_fun, Ann, Type};
true -> {Ann, Type} true -> {Ann, Type}
end, end,
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) -> on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
@@ -270,15 +270,24 @@ bind_state(Env) ->
false -> Env1 false -> Env1
end. end.
-spec bind_field(name(), field_info(), env()) -> env(). -spec bind_field_append(name(), field_info(), env()) -> env().
bind_field(X, Info, Env = #env{ fields = Fields }) -> bind_field_append(X, Info, Env = #env{ fields = Fields }) ->
Fields1 = maps:update_with(X, fun(Infos) -> [Info | Infos] end, [Info], Fields), Fields1 = maps:update_with(X, fun(Infos) -> [Info | Infos] end, [Info], Fields),
Env#env{ fields = Fields1 }. Env#env{ fields = Fields1 }.
-spec bind_fields([{name(), field_info()}], env()) -> env(). -spec bind_field_update(name(), field_info(), env()) -> env().
bind_fields([], Env) -> Env; bind_field_update(X, Info, Env = #env{ fields = Fields }) ->
bind_fields([{Id, Info} | Rest], Env) -> Fields1 = maps:update_with(X, fun([_ | Infos]) -> [Info | Infos]; ([]) -> [Info] end, [Info], Fields),
bind_fields(Rest, bind_field(Id, Info, Env)). 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 %% Contract entrypoints take three named arguments
%% gas : int = Call.gas_left() %% 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")))], Named("protected", Typed({bool, Ann, false}, Id("bool")))],
Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}. Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}.
-spec bind_contract(aeso_syntax:decl(), env()) -> env(). -spec bind_contract(typed | untyped, aeso_syntax:decl(), env()) -> env().
bind_contract({Contract, Ann, Id, _Impls, Contents}, Env) bind_contract(Typing, {Contract, Ann, Id, _Impls, Contents}, Env)
when ?IS_CONTRACT_HEAD(Contract) -> when ?IS_CONTRACT_HEAD(Contract) ->
Key = name(Id), Key = name(Id),
Sys = [{origin, system}], Sys = [{origin, system}],
Fields = TypeOrFresh = fun({typed, _, _, Type}) -> Type; (_) -> fresh_uvar(Sys) end,
Fields =
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)} [ {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, [ {field_t, AnnF, Entrypoint,
contract_call_type( 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" Name =/= "init"
] ++ ] ++
%% Predefined fields %% Predefined fields
[ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++ [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++
[ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME}, [ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME},
contract_call_type( contract_call_type(
case [ [ArgT || {typed, _, _, ArgT} <- Args] case [ [TypeOrFresh(Arg) || Arg <- Args]
|| {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents, || {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents,
aeso_syntax:get_ann(entrypoint, AnnF, false)] aeso_syntax:get_ann(entrypoint, AnnF, false)]
++ [ Args ++ [ Args
@@ -337,7 +347,7 @@ bind_contract({Contract, Ann, Id, _Impls, Contents}, Env)
record_t = Id }} record_t = Id }}
|| {field_t, _, {id, FieldAnn, Entrypoint}, Type} <- Fields ], || {field_t, _, {id, FieldAnn, Entrypoint}, Type} <- Fields ],
bind_type(Key, Ann, {[], {contract_t, 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? %% What scopes could a given name come from?
-spec possible_scopes(env(), qname()) -> [qname()]. -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) -> lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
Qual = lists:droplast(QName), Qual = lists:droplast(QName),
Name = lists:last(QName), Name = lists:last(QName),
QNameIsEvent = lists:suffix(["Chain", "event"], QName),
AllowPrivate = lists:prefix(Qual, Current), AllowPrivate = lists:prefix(Qual, Current),
%% Get the scope %% Get the scope
case maps:get(Qual, Scopes, false) of case maps:get(Qual, Scopes, false) of
@@ -431,6 +442,8 @@ lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes
{reserved_init, Ann1, Type} -> {reserved_init, Ann1, Type} ->
type_error({cannot_call_init_function, Ann}), type_error({cannot_call_init_function, Ann}),
{QName, {Ann1, Type}}; %% Return the type to avoid an extra not-in-scope error {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} -> {contract_fun, Ann1, Type} ->
type_error({contract_treated_as_namespace, Ann, QName}), type_error({contract_treated_as_namespace, Ann, QName}),
{QName, {Ann1, Type}}; {QName, {Ann1, Type}};
@@ -447,6 +460,9 @@ lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes
end end
end. end.
fun_arity({fun_t, _, _, Args, _}) -> length(Args);
fun_arity(_) -> none.
-spec lookup_record_field(env(), name()) -> [field_info()]. -spec lookup_record_field(env(), name()) -> [field_info()].
lookup_record_field(Env, FieldName) -> lookup_record_field(Env, FieldName) ->
maps:get(FieldName, Env#env.fields, []). 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), [ Fld || Fld = #field_info{ kind = K } <- lookup_record_field(Env, FieldName),
Kind == project orelse K /= contract ]. 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 ------------------------------------------------------ %% -- Name manipulation ------------------------------------------------------
-spec qname(type_id()) -> qname(). -spec qname(type_id()) -> qname().
@@ -837,6 +858,7 @@ infer(Contracts, Options) ->
ets_new(type_vars, [set]), ets_new(type_vars, [set]),
ets_new(warnings, [bag]), ets_new(warnings, [bag]),
ets_new(type_vars_variance, [set]), ets_new(type_vars_variance, [set]),
ets_new(functions_to_implement, [set]),
%% Set the variance for builtin types %% Set the variance for builtin types
ets_insert(type_vars_variance, {"list", [covariant]}), ets_insert(type_vars_variance, {"list", [covariant]}),
ets_insert(type_vars_variance, {"option", [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())) -> -spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}. {env(), [aeso_syntax:decl()]}.
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)}; 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) -> when ?IS_CONTRACT_HEAD(Contract) ->
%% do type inference on each contract independently. %% do type inference on each contract independently.
Env = Env0#env{ contract_parents = maps:put(name(ConName), 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 -> ets_insert(defined_contracts, {qname(ConName)});
contract_interface -> ok contract_interface -> ok
end, 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}, Contract1 = {Contract, Ann, ConName, Impls, Code1},
check_implemented_interfaces(Env1, Contract1, Acc), Env3 = pop_scope(Env2),
Env2 = pop_scope(Env1), %% Rebinding because the qualifications of types are added during type inference. Could we do better?
Env3 = bind_contract(Contract1, Env2), Env4 = bind_contract(typed, Contract1, Env3),
infer1(Env3, Rest, [Contract1 | Acc], Options); infer1(Env4, Rest, [Contract1 | Acc], Options);
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) -> infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) ->
when_warning(warn_unused_includes, when_warning(warn_unused_includes,
fun() -> fun() ->
@@ -909,54 +935,71 @@ infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
%% Pragmas are checked in check_modifiers %% Pragmas are checked in check_modifiers
infer1(Env, Rest, Acc, Options). 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(), create_type_errors(),
AllInterfaces = [{name(IName), I} || I = {contract_interface, _, IName, _, _} <- DefinedContracts], [ type_error({unpreserved_payablity, Kind, ContractName, I}) || I <- Interfaces, Payable == false ],
ImplsNames = lists:map(fun name/1, Impls), destroy_and_report_type_errors(Env),
%% All implemented intrefaces should already be defined ok.
lists:foreach(fun(Impl) -> case proplists:get_value(name(Impl), AllInterfaces) of
undefined -> type_error({referencing_undefined_interface, Impl});
_ -> ok
end
end, Impls),
ImplementedInterfaces = [I || I <- [proplists:get_value(Name, AllInterfaces) || Name <- ImplsNames], %% Report all functions that were not implemented by the contract ContractName.
I /= undefined], -spec report_unimplemented_functions(env(), ContractName) -> ok | no_return() when
Funs = [ Fun || Fun <- Code, ContractName :: aeso_syntax:con().
element(1, Fun) == letfun orelse element(1, Fun) == fun_decl ], report_unimplemented_functions(Env, ContractName) ->
check_implemented_interfaces1(Env, ImplementedInterfaces, ConName, Funs, AllInterfaces), 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). destroy_and_report_type_errors(Env).
%% Recursively check that all directly and indirectly referenced interfaces are implemented %% Return a list of all function declarations to be implemented, given the list
check_implemented_interfaces1(_, [], _, _, _) -> %% of interfaces to be implemented Impls and all the previously defined
ok; %% contracts DefinedContracts>
check_implemented_interfaces1(Env, [{contract_interface, _, IName, _, Decls} | Interfaces], -spec functions_to_implement(Impls, DefinedContracts) -> [{InterfaceCon, FunDecl}] when
ConId, Impls, AllInterfaces) -> Impls :: [aeso_syntax:con()],
Unmatched = match_impls(Env, Decls, ConId, name(IName), Impls), DefinedContracts :: [aeso_syntax:decl()],
check_implemented_interfaces1(Env, Interfaces, ConId, Unmatched, AllInterfaces). 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 %% All implemented intrefaces should already be defined
match_impls(_, [], _, _, Impls) -> InterfacesNames = [name(element(3, I)) || I <- Interfaces],
Impls; [ begin
match_impls(Env, [{fun_decl, _, {id, _, FunName}, FunType = {fun_t, _, _, ArgsTypes, RetDecl}} | Decls], ConId, IName, Impls) -> Found = lists:member(name(Impl), InterfacesNames),
Match = fun({letfun, _, {id, _, FName}, Args, RetFun, _}) when FName == FunName -> Found orelse type_error({referencing_undefined_interface, Impl})
length(ArgsTypes) == length(Args) andalso end || Impl <- Impls
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)); lists:flatten([ [ {Con, Decl} || Decl <- Decls] || {contract_interface, _, Con, _, Decls} <- Interfaces ]).
({fun_decl, _, {id, _, FName}, FunT}) when FName == FunName ->
unify(Env#env{unify_throws = false}, FunT, FunType, unknown); %% Fill the ets table functions_to_implement with functions from the implemented
(_) -> false %% interfaces Impls.
end, -spec populate_functions_to_implement(env(), ContractName, Impls, DefinedContracts) -> ok | no_return() when
UnmatchedImpls = case lists:search(Match, Impls) of ContractName :: aeso_syntax:con(),
{value, V} -> Impls :: [aeso_syntax:con()],
lists:delete(V, Impls); DefinedContracts :: [aeso_syntax:decl()].
false -> populate_functions_to_implement(Env, ContractName, Impls, DefinedContracts) ->
type_error({unimplemented_interface_function, ConId, IName, FunName}), create_type_errors(),
Impls [ begin
end, Inserted = ets_insert_new(functions_to_implement, {name(Id), I, Decl}),
match_impls(Env, Decls, ConId, IName, UnmatchedImpls). [{_, 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. %% Asserts that the main contract is somehow defined.
identify_main_contract(Contracts, Options) -> identify_main_contract(Contracts, Options) ->
@@ -1214,6 +1257,10 @@ check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con,
create_type_errors(), create_type_errors(),
type_error({using_undefined_namespace, Ann, qname(Con)}), type_error({using_undefined_namespace, Ann, qname(Con)}),
destroy_and_report_type_errors(Env); 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 -> Scope ->
Nsp = case Parts of Nsp = case Parts of
none -> 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(). -spec check_fields(env(), #{ name() => aeso_syntax:decl() }, type(), [aeso_syntax:field_t()]) -> env().
check_fields(Env, _TypeMap, _, []) -> Env; check_fields(Env, _TypeMap, _, []) -> Env;
check_fields(Env, TypeMap, RecTy, [{field_t, Ann, Id, Type} | Fields]) -> 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_fields(Env1, TypeMap, RecTy, Fields).
check_parameterizable({id, Ann, "event"}, [_ | _]) -> 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()}. -spec check_fundecl(env(), aeso_syntax:decl()) -> {{name(), typesig()}, aeso_syntax:decl()}.
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) -> check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) ->
Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type), 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}) -> check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) ->
type_error({fundecl_must_have_funtype, Ann, Id, Type}), type_error({fundecl_must_have_funtype, Ann, Id, Type}),
{{Name, {type_sig, Ann, none, [], [], Type}}, check_type(Env, 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) -> infer_nonrec(Env, LetFun) ->
create_constraints(), create_constraints(),
NewLetFun = infer_letfun(Env, LetFun), NewLetFun = {{_, Sig}, _} = infer_letfun(Env, LetFun),
check_special_funs(Env, NewLetFun), check_special_funs(Env, NewLetFun),
register_implementation(get_letfun_id(LetFun), Sig),
solve_then_destroy_and_report_unsolved_constraints(Env), solve_then_destroy_and_report_unsolved_constraints(Env),
Result = {TypeSig, _} = instantiate(NewLetFun), Result = {TypeSig, _} = instantiate(NewLetFun),
print_typesig(TypeSig), print_typesig(TypeSig),
@@ -1503,7 +1585,8 @@ infer_letrec(Env, Defs) ->
ExtendEnv = bind_funs(Funs, Env), ExtendEnv = bind_funs(Funs, Env),
Inferred = Inferred =
[ begin [ 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), Got = proplists:get_value(Name, Funs),
Expect = typesig_to_fun_t(TypeSig), Expect = typesig_to_fun_t(TypeSig),
unify(Env, Got, Expect, {check_typesig, Name, Got, Expect}), 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}, {{Name, TypeSig},
{letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewGuardedBodies}}. {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) -> desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) ->
NeedDesugar = NeedDesugar =
case Clauses of 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 %% 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. %% 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 case Default of
{int, _, 0} -> ok; {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; end;
check_stateful_named_arg(_, _, _) -> ok. check_stateful_named_arg(_, _, _) -> ok.
@@ -1753,6 +1843,10 @@ infer_expr(_Env, Body={contract_pubkey, As, _}) ->
{typed, As, Body, Con}; {typed, As, Body, Con};
infer_expr(_Env, Body={id, As, "_"}) -> infer_expr(_Env, Body={id, As, "_"}) ->
{typed, As, Body, fresh_uvar(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 -> infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid ->
{QName, Type} = lookup_name(Env, As, Id), {QName, Type} = lookup_name(Env, As, Id),
{typed, As, QName, Type}; {typed, As, QName, Type};
@@ -2204,7 +2298,7 @@ next_count() ->
ets_tables() -> ets_tables() ->
[options, type_vars, constraints, freshen_tvars, type_errors, [options, type_vars, constraints, freshen_tvars, type_errors,
defined_contracts, warnings, function_calls, all_functions, defined_contracts, warnings, function_calls, all_functions,
type_vars_variance]. type_vars_variance, functions_to_implement].
clean_up_ets() -> clean_up_ets() ->
[ catch ets_delete(Tab) || Tab <- ets_tables() ], [ catch ets_delete(Tab) || Tab <- ets_tables() ],
@@ -2240,10 +2334,18 @@ ets_delete(Name) ->
put(aeso_ast_infer_types, maps:remove(Name, Tabs)), put(aeso_ast_infer_types, maps:remove(Name, Tabs)),
ets:delete(TabId). ets:delete(TabId).
ets_delete(Name, Key) ->
TabId = ets_tabid(Name),
ets:delete(TabId, Key).
ets_insert(Name, Object) -> ets_insert(Name, Object) ->
TabId = ets_tabid(Name), TabId = ets_tabid(Name),
ets:insert(TabId, Object). ets:insert(TabId, Object).
ets_insert_new(Name, Object) ->
TabId = ets_tabid(Name),
ets:insert_new(TabId, Object).
ets_lookup(Name, Key) -> ets_lookup(Name, Key) ->
TabId = ets_tabid(Name), TabId = ets_tabid(Name),
ets:lookup(TabId, Key). ets:lookup(TabId, Key).
@@ -2310,7 +2412,12 @@ solve_constraints(Env) ->
field_t = FieldType, field_t = FieldType,
kind = Kind, kind = Kind,
context = When }) -> 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}), type_error({undefined_field, Field}),
false; false;
@@ -2805,6 +2912,8 @@ unify0(Env, A, B, Variance, When) ->
unify1(_Env, {uvar, _, R}, {uvar, _, R}, _Variance, _When) -> unify1(_Env, {uvar, _, R}, {uvar, _, R}, _Variance, _When) ->
true; true;
unify1(_Env, {uvar, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) ->
type_error({unify_varargs, When});
unify1(Env, {uvar, A, R}, T, _Variance, When) -> unify1(Env, {uvar, A, R}, T, _Variance, When) ->
case occurs_check(R, T) of case occurs_check(R, T) of
true -> 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); unify0(Env, A, C, Variance, When) andalso unify0(Env, B, D, Variance, When);
unify1(_Env, X, X, _Variance, _When) -> unify1(_Env, X, X, _Variance, _When) ->
true; 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) -> unify1(_Env, {id, _, Name}, {id, _, Name}, _Variance, _When) ->
true; true;
unify1(Env, A = {con, _, NameA}, B = {con, _, NameB}, Variance, When) -> 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))]), [pp(instantiate(A)), pp(instantiate(B))]),
{Pos, Ctxt} = pp_when(When), {Pos, Ctxt} = pp_when(When),
mk_t_err(Pos, Msg, Ctxt); 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}) -> mk_error({unbound_variable, Id}) ->
Msg = io_lib:format("Unbound variable `~s`", [pp(Id)]), Msg = io_lib:format("Unbound variable `~s`", [pp(Id)]),
case Id of case Id of
@@ -3568,7 +3686,7 @@ mk_error({multiple_main_contracts, Ann}) ->
Msg = "Only one main contract can be defined.", Msg = "Only one main contract can be defined.",
mk_t_err(pos(Ann), Msg); mk_t_err(pos(Ann), Msg);
mk_error({unify_varargs, When}) -> mk_error({unify_varargs, When}) ->
Msg = "Cannot unify variable argument list.", Msg = "Cannot infer types for variable argument list.",
{Pos, Ctxt} = pp_when(When), {Pos, Ctxt} = pp_when(When),
mk_t_err(Pos, Msg, Ctxt); mk_t_err(Pos, Msg, Ctxt);
mk_error({clone_no_contract, Ann}) -> 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]), Msg = io_lib:format("Empty record definitions are not allowed. Cannot define the record `~s`", [Name]),
mk_t_err(pos(Ann), Msg); mk_t_err(pos(Ann), Msg);
mk_error({unimplemented_interface_function, ConId, InterfaceName, FunName}) -> 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_t_err(pos(ConId), Msg);
mk_error({referencing_undefined_interface, InterfaceId}) -> mk_error({referencing_undefined_interface, InterfaceId}) ->
Msg = io_lib:format("Trying to implement or extend an undefined interface `~s`", [pp(InterfaceId)]), Msg = io_lib:format("Trying to implement or extend an undefined interface `~s`", [pp(InterfaceId)]),
@@ -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)]), 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]), Cxt = io_lib:format("The ~s type must not be ~s", [What, WhyS]),
mk_t_err(pos(Ann), Msg, Cxt); 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) -> mk_error(Err) ->
Msg = io_lib:format("Unknown error: ~p", [Err]), Msg = io_lib:format("Unknown error: ~p", [Err]),
mk_t_err(pos(0, 0), Msg). mk_t_err(pos(0, 0), Msg).
+61 -30
View File
@@ -149,17 +149,18 @@
-type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}. -type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}.
-type env() :: #{ type_env := type_env(), -type env() :: #{ type_env := type_env(),
fun_env := fun_env(), fun_env := fun_env(),
con_env := con_env(), con_env := con_env(),
child_con_env := child_con_env(), child_con_env := child_con_env(),
event_type => aeso_syntax:typedef(), event_type => aeso_syntax:typedef(),
builtins := builtins(), builtins := builtins(),
options := [option()], options := [option()],
state_layout => state_layout(), state_layout => state_layout(),
context => context(), context => context(),
vars => [var_name()], vars => [var_name()],
functions := #{ fun_name() => fun_def() } functions := #{ fun_name() => fun_def() },
saved_fresh_names => #{ var_name() => var_name() }
}. }.
-define(HASH_BYTES, 32). -define(HASH_BYTES, 32).
@@ -170,7 +171,7 @@
%% and produces Fate intermediate code. %% and produces Fate intermediate code.
-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> {env(), fcode()}. -spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> {env(), fcode()}.
ast_to_fcode(Code, Options) -> ast_to_fcode(Code, Options) ->
init_fresh_names(), init_fresh_names(Options),
{Env1, FCode1} = to_fcode(init_env(Options), Code), {Env1, FCode1} = to_fcode(init_env(Options), Code),
FCode2 = optimize(FCode1, Options), FCode2 = optimize(FCode1, Options),
Env2 = Env1#{ child_con_env := Env2 = Env1#{ child_con_env :=
@@ -178,13 +179,18 @@ ast_to_fcode(Code, Options) ->
fun (_, FC) -> optimize(FC, Options) end, fun (_, FC) -> optimize(FC, Options) end,
maps:get(child_con_env, Env1) maps:get(child_con_env, Env1)
)}, )},
clear_fresh_names(), Env3 =
{Env2, FCode2}. 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) -> optimize(FCode1, Options) ->
Verbose = lists:member(pp_fcode, Options), Verbose = lists:member(pp_fcode, Options),
[io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose], [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 ], [ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ],
FCode3 = lambda_lift(FCode2), FCode3 = lambda_lift(FCode2),
[ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ], [ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ],
@@ -1287,20 +1293,28 @@ lambda_lift_exprs(Layout, As) -> [lambda_lift_expr(Layout, A) || A <- As].
%% - Constant propagation %% - Constant propagation
%% - Inlining %% - Inlining
-spec optimize_fcode(fcode()) -> fcode(). -spec optimize_fcode(fcode(), [option()]) -> fcode().
optimize_fcode(Code = #{ functions := Funs }) -> optimize_fcode(Code = #{ functions := Funs }, Options) ->
Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def) end, Funs) }, Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def, Options) end, Funs) },
eliminate_dead_code(Code1). eliminate_dead_code(Code1).
-spec optimize_fun(fcode(), fun_name(), fun_def()) -> fun_def(). -spec optimize_fun(fcode(), fun_name(), fun_def(), [option()]) -> fun_def().
optimize_fun(Fcode, Fun, Def = #{ body := Body }) -> optimize_fun(Fcode, Fun, Def = #{ body := Body0 }, Options) ->
%% io:format("Optimizing ~p =\n~s\n", [_Fun, prettypr:format(pp_fexpr(_Body))]), Inliner = proplists:get_value(optimize_inliner, Options, true),
Def#{ body := drop_unused_lets( InlineLocalFunctions = proplists:get_value(optimize_inline_local_functions, Options, true),
simplifier( BindSubexpressions = proplists:get_value(optimize_bind_subexpressions, Options, true),
let_floating( LetFloating = proplists:get_value(optimize_let_floating, Options, true),
bind_subexpressions( Simplifier = proplists:get_value(optimize_simplifier, Options, true),
inline_local_functions( DropUnusedLets = proplists:get_value(optimize_drop_unused_lets, Options, true),
inliner(Fcode, Fun, Body)))))) }.
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 --- %% --- Inlining ---
@@ -1720,12 +1734,29 @@ resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) ->
{{Fun, Ar}, _} -> {def_u, Fun, Ar} {{Fun, Ar}, _} -> {def_u, Fun, Ar}
end. end.
init_fresh_names() -> init_fresh_names(Options) ->
proplists:get_value(debug_info, Options, false) andalso init_saved_fresh_names(),
put('%fresh', 0). put('%fresh', 0).
clear_fresh_names() -> clear_fresh_names(Options) ->
proplists:get_value(debug_info, Options, false) andalso clear_saved_fresh_names(),
erase('%fresh'). 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(). -spec fresh_name() -> var_name().
fresh_name() -> fresh_name("%"). fresh_name() -> fresh_name("%").
@@ -1854,7 +1885,7 @@ bottom_up(F, Env, Expr) ->
(_) -> true end, (_) -> true end,
case ShouldFreshen(X) of case ShouldFreshen(X) of
true -> true ->
Z = fresh_name(), Z = fresh_name_save(X),
Env1 = Env#{ Z => E1 }, Env1 = Env#{ Z => E1 },
{'let', Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))}; {'let', Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))};
false -> false ->
+12 -4
View File
@@ -112,10 +112,12 @@ from_string(ContractString, Options) ->
from_string1(ContractString, Options) -> from_string1(ContractString, Options) ->
#{ fcode := FCode #{ fcode := FCode
, fcode_env := #{child_con_env := ChildContracts} , fcode_env := FCodeEnv
, folded_typed_ast := FoldedTypedAst , folded_typed_ast := FoldedTypedAst
, warnings := Warnings } = string_to_code(ContractString, Options), , 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), pp_assembler(FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []), ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(), {ok, Version} = version(),
@@ -128,7 +130,13 @@ from_string1(ContractString, Options) ->
payable => maps:get(payable, FCode), payable => maps:get(payable, FCode),
warnings => Warnings 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) -> maybe_generate_aci(Result, FoldedTypedAst, Options) ->
case proplists:get_value(aci, Options) of case proplists:get_value(aci, Options) of
@@ -185,7 +193,7 @@ check_call1(ContractString0, FunName, Args, Options) ->
#{fcode := OrgFcode #{fcode := OrgFcode
, fcode_env := #{child_con_env := ChildContracts} , fcode_env := #{child_con_env := ChildContracts}
, ast := Ast} = string_to_code(ContractString0, Options), , 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 %% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)), SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes, CallName = first_none_match(?CALL_NAME, SymbolHashes,
+123 -75
View File
@@ -9,7 +9,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_fcode_to_fate). -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). -ifdef(TEST).
-export([optimize_fun/4, to_basic_blocks/1]). -export([optimize_fun/4, to_basic_blocks/1]).
@@ -45,7 +45,14 @@
-define(s(N), {store, N}). -define(s(N), {store, N}).
-define(void, {var, 9999}). -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 -------------------------------------------------------------- %% -- Debugging --------------------------------------------------------------
@@ -71,16 +78,27 @@ code_error(Err) ->
%% -- Main ------------------------------------------------------------------- %% -- Main -------------------------------------------------------------------
%% @doc Main entry point. %% @doc Main entry point.
compile(FCode, Options) -> compile(FCode, SavedFreshNames, Options) ->
compile(#{}, FCode, Options). compile(#{}, FCode, SavedFreshNames, Options).
compile(ChildContracts, FCode, 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, #{ contract_name := ContractName,
functions := Functions } = FCode, 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), SFuns1 = optimize_scode(SFuns, Options),
FateCode = to_basic_blocks(SFuns1), FateCode = to_basic_blocks(SFuns1),
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]), ?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) -> make_function_id(X) ->
aeb_fate_code:symbol_identifier(make_function_name(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({entrypoint, Name}) -> Name;
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")). 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), FunNames = maps:keys(Functions),
maps:from_list( 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, || {Name, #{args := Args,
body := Body, body := Body,
attrs := Attrs, attrs := Attrs,
return := Type}} <- maps:to_list(Functions)]). 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), {ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
Attrs = Attrs0 -- [stateful], %% Only track private and payable from here. 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), SCode = to_scode(Env, Body),
{Attrs, {ArgTypes, ResType1}, SCode}. {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'). -define(tvars, '$tvars').
typesig_to_scode(Args, Res) -> typesig_to_scode(Args, Res) ->
@@ -149,19 +193,21 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
%% -- Environment functions -- %% -- 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) ], #env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
contract = ContractName, contract = ContractName,
child_contracts = ChildContracts, child_contracts = ChildContracts,
locals = FunNames, locals = FunNames,
current_function = Name, current_function = Name,
options = Options, options = Options,
tailpos = true }. tailpos = true,
saved_fresh_names = SavedFreshNames }.
next_var(#env{ vars = Vars }) -> next_var(#env{ vars = Vars }) ->
1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]). 1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]).
bind_var(Name, Var, Env = #env{ vars = 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] }. Env#env{ vars = [{Name, Var} | Vars] }.
bind_local(Name, Env) -> bind_local(Name, Env) ->
@@ -185,9 +231,10 @@ serialize_contract_code(Env, C) ->
end, end,
case maps:get(C, Cache, none) of case maps:get(C, Cache, none) of
none -> none ->
Options = Env#env.options, Options = Env#env.options,
FCode = maps:get(C, Env#env.child_contracts), SavedFreshNames = Env#env.saved_fresh_names,
FateCode = compile(Env#env.child_contracts, FCode, Options), FCode = maps:get(C, Env#env.child_contracts),
{FateCode, _} = compile1(Env#env.child_contracts, FCode, SavedFreshNames, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []), ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = aeso_compiler:version(), {ok, Version} = aeso_compiler:version(),
OriginalSourceCode = proplists:get_value(original_src, Options, ""), OriginalSourceCode = proplists:get_value(original_src, Options, ""),
@@ -709,7 +756,7 @@ tuple(N) -> aeb_fate_ops:tuple(?a, N).
optimize_scode(Funs, Options) -> optimize_scode(Funs, Options) ->
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end, maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
Funs). Funs).
flatten(missing) -> missing; flatten(missing) -> missing;
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)). 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) -> simpl_top(0, I, Code, _Options) ->
code_error({optimizer_out_of_fuel, I, Code}); code_error({optimizer_out_of_fuel, I, Code});
simpl_top(Fuel, I, Code, Options) -> 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) -> apply_rules(Fuel, Rules, I, Code, Options) ->
Cons = fun(X, Xs) -> simpl_top(Fuel - 1, X, Xs, Options) end, 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}). -define(RULE(Name), {Name, fun Name/2}).
merge_rules() -> merge_rules() ->
[?RULE(r_push_consume), [?RULE(optimize_push_consume),
?RULE(r_one_shot_var), ?RULE(optimize_one_shot_var),
?RULE(r_write_to_dead_var), ?RULE(optimize_write_to_dead_var),
?RULE(r_inline_switch_target) ?RULE(optimize_inline_switch_target)
]. ].
rules() -> rules() ->
merge_rules() ++ merge_rules() ++
[?RULE(r_swap_push), [?RULE(optimize_swap_push),
?RULE(r_swap_pop), ?RULE(optimize_swap_pop),
?RULE(r_swap_write), ?RULE(optimize_swap_write),
?RULE(r_constant_propagation), ?RULE(optimize_constant_propagation),
?RULE(r_prune_impossible_branches), ?RULE(optimize_prune_impossible_branches),
?RULE(r_single_successful_branch), ?RULE(optimize_single_successful_branch),
?RULE(r_inline_store), ?RULE(optimize_inline_store),
?RULE(r_float_switch_body) ?RULE(optimize_float_switch_body)
]. ].
%% Removing pushes that are immediately consumed. %% 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, []); inline_push(Ann1, A, 0, Code, []);
%% Writing directly to memory instead of going through the accumulator. %% 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 = IsPush =
case op_view(I) of case op_view(I) of
{_, ?a, _} -> true; {_, ?a, _} -> true;
@@ -1153,7 +1201,7 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
end, end,
if IsPush -> {[{i, merge_ann(Ann1, Ann2), setelement(2, I, R)}], Code}; if IsPush -> {[{i, merge_ann(Ann1, Ann2), setelement(2, I, R)}], Code};
true -> false end; true -> false end;
r_push_consume(_, _) -> false. optimize_push_consume(_, _) -> false.
inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) -> inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) ->
{AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI), {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]). split_stack_arg(N1, As, [A | Acc]).
%% Move PUSHes past non-stack instructions. %% 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 case op_view(PushI) of
{_, ?a, _} -> {_, ?a, _} ->
case independent(Push, I) of case independent(Push, I) of
@@ -1197,10 +1245,10 @@ r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
end; end;
_ -> false _ -> false
end; end;
r_swap_push(_, _) -> false. optimize_swap_push(_, _) -> false.
%% Move non-stack instruction past POPs. %% 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 case independent(IA, JA) of
true -> true ->
case {op_view(I), op_view(J)} of case {op_view(I), op_view(J)} of
@@ -1208,7 +1256,7 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
{_, false} -> false; {_, false} -> false;
{{_, IR, IAs}, {_, RJ, JAs}} -> {{_, IR, IAs}, {_, RJ, JAs}} ->
NonStackI = not lists:member(?a, [IR | IAs]), 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), PopJ = RJ /= ?a andalso lists:member(?a, JAs),
case NonStackI andalso PopJ of case NonStackI andalso PopJ of
false -> false; false -> false;
@@ -1219,22 +1267,22 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
end; end;
false -> false false -> false
end; end;
r_swap_pop(_, _) -> false. optimize_swap_pop(_, _) -> false.
%% Match up writes to variables with instructions further down. %% 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 case {var_writes(I), independent(I, J)} of
{[_], true} -> {[_], true} ->
{J1, I1} = swap_instrs(I, J), {J1, I1} = swap_instrs(I, J),
r_swap_write([J1], I1, Code); optimize_swap_write([J1], I1, Code);
_ -> false _ -> false
end; 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), {J1, I1} = swap_instrs(I, J),
r_swap_write([J1 | Pre], I1, Code); optimize_swap_write([J1 | Pre], I1, Code);
r_swap_write(Pre, I, Code0 = [J | Code]) -> optimize_swap_write(Pre, I, Code0 = [J | Code]) ->
case apply_rules_once(merge_rules(), I, Code0) of case apply_rules_once(merge_rules(), I, Code0) of
{_Rule, New, Rest} -> {_Rule, New, Rest} ->
{lists:reverse(Pre) ++ New, Rest}; {lists:reverse(Pre) ++ New, Rest};
@@ -1243,27 +1291,27 @@ r_swap_write(Pre, I, Code0 = [J | Code]) ->
false -> false; false -> false;
true -> true ->
{J1, I1} = swap_instrs(I, J), {J1, I1} = swap_instrs(I, J),
r_swap_write([J1 | Pre], I1, Code) optimize_swap_write([J1 | Pre], I1, Code)
end end
end; end;
r_swap_write(_, _, _) -> false. optimize_swap_write(_, _, _) -> false.
%% Precompute instructions with known values %% 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)}}, Store = {i, Ann, {'STORE', S, ?i(false)}},
Cons1 = case R of Cons1 = case R of
?a -> {i, Ann1, {'CONS', ?void, X, Xs}}; ?a -> {i, Ann1, {'CONS', ?void, X, Xs}};
_ -> Cons _ -> Cons
end, end,
{[Cons1, Store], Code}; {[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)}}, Store = {i, Ann, {'STORE', S, ?i(true)}},
Nil1 = case R of Nil1 = case R of
?a -> {i, Ann1, {'NIL', ?void}}; ?a -> {i, Ann1, {'NIL', ?void}};
_ -> Nil _ -> Nil
end, end,
{[Nil1, Store], Code}; {[Nil1, Store], Code};
r_constant_propagation({i, Ann, I}, Code) -> optimize_constant_propagation({i, Ann, I}, Code) ->
case op_view(I) of case op_view(I) of
false -> false; false -> false;
{Op, R, As} -> {Op, R, As} ->
@@ -1277,7 +1325,7 @@ r_constant_propagation({i, Ann, I}, Code) ->
end end
end 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('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; 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? eval_op(_, _) -> no_eval. %% TODO: bits?
%% Prune impossible branches from switches %% 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 case pick_branch(Type, V, Alts) of
false -> false; false -> false;
Alt -> {Alt, Code} Alt -> {Alt, Code}
end; 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, Alts1 = [if V -> missing; true -> False end,
if V -> True; true -> missing end], if V -> True; true -> missing end],
case Alts == Alts1 of 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} _ -> {[{switch, ?i(V), boolean, Alts1, Def}], Code}
end end
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) -> [{switch, R, Type = {variant, _}, Alts, missing} | Code]) when is_integer(Tag) ->
case {R, lists:nth(Tag + 1, Alts)} of case {R, lists:nth(Tag + 1, Alts)} of
{_, missing} -> {_, missing} ->
@@ -1328,7 +1376,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_
false -> {Alt, Code} false -> {Alt, Code}
end end
end; end;
r_prune_impossible_branches(_, _) -> false. optimize_prune_impossible_branches(_, _) -> false.
pick_branch(boolean, V, [False, True]) when V == true; V == false -> pick_branch(boolean, V, [False, True]) when V == true; V == false ->
Alt = if V -> True; true -> False end, 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 %% If there's a single branch that doesn't abort we can push the code for that
%% out of the switch. %% 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 case push_code_out_of_switch([Def | Alts]) of
{_, none} -> false; {_, none} -> false;
{_, many} -> false; {_, many} -> false;
@@ -1349,7 +1397,7 @@ r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
{[Def1 | Alts1], PushedOut} -> {[Def1 | Alts1], PushedOut} ->
{[{switch, R, Type, Alts1, Def1} | PushedOut], Code} {[{switch, R, Type, Alts1, Def1} | PushedOut], Code}
end; end;
r_single_successful_branch(_, _) -> false. optimize_single_successful_branch(_, _) -> false.
push_code_out_of_switch([]) -> {[], none}; push_code_out_of_switch([]) -> {[], none};
push_code_out_of_switch([Alt | Alts]) -> push_code_out_of_switch([Alt | Alts]) ->
@@ -1385,7 +1433,7 @@ does_abort({switch, _, _, Alts, Def}) ->
does_abort(_) -> false. does_abort(_) -> false.
%% STORE R A, SWITCH R --> SWITCH A %% 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 = Ann1 =
case is_reg(A) of case is_reg(A) of
true -> Ann#{ live_out := ordsets:add_element(A, maps:get(live_out, Ann)) }; 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; end;
_ -> false %% impossible _ -> false %% impossible
end; end;
r_inline_switch_target(_, _) -> false. optimize_inline_switch_target(_, _) -> false.
%% Float switch-body to closest switch %% 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} = swap_instrs(I, J),
{[], [J1, I1 | Code]}; {[], [J1, I1 | Code]};
r_float_switch_body(_, _) -> false. optimize_float_switch_body(_, _) -> false.
%% Inline stores %% Inline stores
r_inline_store({i, _, {'STORE', R, R}}, Code) -> optimize_inline_store({i, _, {'STORE', R, R}}, Code) ->
{[], 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. %% Not when A is var unless updating the annotations properly.
Inline = case A of Inline = case A of
{arg, _} -> true; {arg, _} -> true;
@@ -1423,13 +1471,13 @@ r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
{store, _} -> true; {store, _} -> true;
_ -> false _ -> false
end, end,
if Inline -> r_inline_store([I], false, R, A, Code); if Inline -> optimize_inline_store([I], false, R, A, Code);
true -> false end; true -> false end;
r_inline_store(_, _) -> false. optimize_inline_store(_, _) -> false.
r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) -> optimize_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
r_inline_store([I | Acc], Progress, R, A, Code); optimize_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, Ann, I} | Code]) ->
#{ write := W } = attributes(I), #{ write := W } = attributes(I),
Inl = fun(X) when X == R -> A; (X) -> X end, Inl = fun(X) when X == R -> A; (X) -> X end,
case live_in(R, Ann) of 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 case lists:member(W, [R, A]) of
true when Progress1 -> {lists:reverse(Acc1), Code}; true when Progress1 -> {lists:reverse(Acc1), Code};
true -> false; true -> false;
false -> r_inline_store(Acc1, Progress1, R, A, Code) false -> optimize_inline_store(Acc1, Progress1, R, A, Code)
end end
end; end;
r_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code}; optimize_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
r_inline_store(_, false, _, _, _) -> false. optimize_inline_store(_, false, _, _, _) -> false.
%% Shortcut write followed by final read %% 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 case op_view(I) of
{Op, R = {var, _}, As} -> {Op, R = {var, _}, As} ->
Copy = case J of Copy = case J of
@@ -1470,11 +1518,11 @@ r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
end; end;
_ -> false _ -> false
end; end;
r_one_shot_var(_, _) -> false. optimize_one_shot_var(_, _) -> false.
%% Remove writes to dead variables %% Remove writes to dead variables
r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping optimize_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, Ann, I}, Code) ->
#{ pure := Pure } = attributes(I), #{ pure := Pure } = attributes(I),
case op_view(I) of case op_view(I) of
{_Op, R, As} when R /= ?a, Pure -> {_Op, R, As} when R /= ?a, Pure ->
@@ -1487,7 +1535,7 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
end; end;
_ -> false _ -> false
end; end;
r_write_to_dead_var(_, _) -> false. optimize_write_to_dead_var(_, _) -> false.
op_view({'ABORT', R}) -> {'ABORT', none, [R]}; op_view({'ABORT', R}) -> {'ABORT', none, [R]};
op_view({'EXIT', R}) -> {'EXIT', none, [R]}; op_view({'EXIT', R}) -> {'EXIT', none, [R]};
+3
View File
@@ -359,9 +359,12 @@ exprAtom() ->
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4)) , ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2)) , ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
, letpat() , letpat()
, hole()
]) ])
end). end).
hole() -> ?RULE(token('???'), {id, get_ann(_1), "???"}).
comprehension_exp() -> comprehension_exp() ->
?LAZY_P(choice( ?LAZY_P(choice(
[ comprehension_bind() [ comprehension_bind()
+5 -1
View File
@@ -261,6 +261,8 @@ type(Type, Options) ->
with_options(Options, fun() -> type(Type) end). with_options(Options, fun() -> type(Type) end).
-spec type(aeso_syntax:type()) -> doc(). -spec type(aeso_syntax:type()) -> doc().
type(F = {fun_t, _, _, var_args, _}) ->
type(setelement(4, F, [var_args]));
type({fun_t, _, Named, Args, Ret}) -> type({fun_t, _, Named, Args, Ret}) ->
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret)); follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret));
type({type_sig, _, Named, Args, Ret}) -> type({type_sig, _, Named, Args, Ret}) ->
@@ -288,7 +290,9 @@ type(T = {id, _, _}) -> name(T);
type(T = {qid, _, _}) -> name(T); type(T = {qid, _, _}) -> name(T);
type(T = {con, _, _}) -> name(T); type(T = {con, _, _}) -> name(T);
type(T = {qcon, _, _}) -> 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(). -spec args_type([aeso_syntax:type()]) -> doc().
args_type(Args) -> args_type(Args) ->
+1 -1
View File
@@ -13,7 +13,7 @@
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). -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([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_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([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([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]). -export_type([ast/0]).
+1 -1
View File
@@ -1,6 +1,6 @@
{application, aesophia, {application, aesophia,
[{description, "Compiler for Aeternity Sophia language"}, [{description, "Compiler for Aeternity Sophia language"},
{vsn, "7.0.1"}, {vsn, "7.1.0"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
+3 -3
View File
@@ -21,7 +21,7 @@ test_cases(1) ->
" payable stateful entrypoint a(i : int) = i+1\n">>, " payable stateful entrypoint a(i : int) = i+1\n">>,
MapACI = #{contract => MapACI = #{contract =>
#{name => <<"C">>, #{name => <<"C">>,
type_defs => [], typedefs => [],
payable => true, payable => true,
kind => contract_main, kind => contract_main,
functions => functions =>
@@ -43,7 +43,7 @@ test_cases(2) ->
MapACI = #{contract => MapACI = #{contract =>
#{name => <<"C">>, payable => false, #{name => <<"C">>, payable => false,
kind => contract_main, kind => contract_main,
type_defs => typedefs =>
[#{name => <<"allan">>, [#{name => <<"allan">>,
typedef => <<"int">>, typedef => <<"int">>,
vars => []}], vars => []}],
@@ -76,7 +76,7 @@ test_cases(3) ->
name => <<"C">>, payable => false, kind => contract_main, name => <<"C">>, payable => false, kind => contract_main,
event => #{variant => [#{<<"SingleEventDefined">> => []}]}, event => #{variant => [#{<<"SingleEventDefined">> => []}]},
state => <<"unit">>, state => <<"unit">>,
type_defs => typedefs =>
[#{name => <<"bert">>, [#{name => <<"bert">>,
typedef => typedef =>
#{variant => #{variant =>
+79 -11
View File
@@ -203,6 +203,11 @@ compilable_contracts() ->
"polymorphism_contract_interface_same_decl_multi_interface", "polymorphism_contract_interface_same_decl_multi_interface",
"polymorphism_contract_interface_same_name_same_type", "polymorphism_contract_interface_same_name_same_type",
"polymorphism_variance_switching_chain_create", "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", "missing_init_fun_state_unit",
"complex_compare_leq", "complex_compare_leq",
"complex_compare", "complex_compare",
@@ -215,6 +220,7 @@ compilable_contracts() ->
"polymorphic_map_keys", "polymorphic_map_keys",
"unapplied_contract_call", "unapplied_contract_call",
"unapplied_named_arg_builtin", "unapplied_named_arg_builtin",
"resolve_field_constraint_by_arity",
"test" % Custom general-purpose test file. Keep it last on the list. "test" % Custom general-purpose test file. Keep it last on the list.
]. ].
@@ -731,10 +737,22 @@ failing_contracts() ->
"Conflicting updates for field 'foo'">>]) "Conflicting updates for field 'foo'">>])
, ?TYPE_ERROR(factories_type_errors, , ?TYPE_ERROR(factories_type_errors,
[<<?Pos(10,18) [<<?Pos(10,18)
"Chain.clone requires `ref` named argument of contract type.">>, "Chain.clone requires `ref` named argument of contract type.">>,
<<?Pos(11,18) <<?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" "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)\nagainst the expected type\n (gas : int, value : int, protected : bool, int, bool) => 'b">>, "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) <<?Pos(12,37)
"Cannot unify `int` and `bool`\n" "Cannot unify `int` and `bool`\n"
"when checking named argument `gas : int` against inferred type `bool`">>, "when checking named argument `gas : int` against inferred type `bool`">>,
@@ -847,25 +865,25 @@ failing_contracts() ->
"Trying to implement or extend an undefined interface `Z`">> "Trying to implement or extend an undefined interface `Z`">>
]) ])
, ?TYPE_ERROR(polymorphism_contract_interface_same_name_different_type, , ?TYPE_ERROR(polymorphism_contract_interface_same_name_different_type,
[<<?Pos(4,20) [<<?Pos(9,5)
"Unimplemented function `f` from the interface `I1` in the contract `I2`">>]) "Duplicate definitions of `f` at\n"
" - line 8, column 5\n"
" - line 9, column 5">>])
, ?TYPE_ERROR(polymorphism_contract_missing_implementation, , ?TYPE_ERROR(polymorphism_contract_missing_implementation,
[<<?Pos(4,20) [<<?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, , ?TYPE_ERROR(polymorphism_contract_same_decl_multi_interface,
[<<?Pos(7,10) [<<?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, , ?TYPE_ERROR(polymorphism_contract_undefined_interface,
[<<?Pos(1,14) [<<?Pos(1,14)
"Trying to implement or extend an undefined interface `I`">> "Trying to implement or extend an undefined interface `I`">>
]) ])
, ?TYPE_ERROR(polymorphism_contract_same_name_different_type_multi_interface, , ?TYPE_ERROR(polymorphism_contract_same_name_different_type_multi_interface,
[<<?Pos(9,5) [<<?Pos(7,10)
"Duplicate definitions of `f` at\n" "Both interfaces `I` and `J` implemented by the contract `C` have a function called `f`">>
" - line 8, column 5\n"
" - line 9, column 5">>
]) ])
, ?TYPE_ERROR(polymorphism_contract_interface_undefined_interface, , ?TYPE_ERROR(polymorphism_contract_interface_undefined_interface,
[<<?Pos(1,24) [<<?Pos(1,24)
@@ -1124,6 +1142,56 @@ failing_contracts() ->
" `oracle(string, (int) => int)`\n" " `oracle(string, (int) => int)`\n"
"The response type must not be higher-order (contain function types)">> "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_() -> validation_test_() ->
@@ -0,0 +1,5 @@
contract F =
entrypoint g() = 1
main contract C =
entrypoint f() = F.g()
+1 -10
View File
@@ -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 = contract ComplexTypes =
record state = { worker : Remote } record state = { worker : ComplexTypes }
entrypoint init(worker) = {worker = worker} entrypoint init(worker) = {worker = worker}
+1 -6
View File
@@ -1,14 +1,9 @@
// Testing primitives for accessing the block chain environment // 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 = contract Environment =
record state = {remote : Interface} record state = {remote : Environment}
entrypoint init(remote) = {remote = remote} entrypoint init(remote) = {remote = remote}
+13
View File
@@ -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,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()
-3
View File
@@ -1,7 +1,4 @@
contract interface SpendContract =
entrypoint withdraw : (int) => int
contract SpendTest = contract SpendTest =
stateful entrypoint spend(to, amount) = 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
+4
View File
@@ -0,0 +1,4 @@
main contract C =
stateful entrypoint f() =
let x = Chain.clone
123
+8 -1
View File
@@ -12,7 +12,7 @@ namespace UnusedNamespace =
// Unused // Unused
private function h() = 3 private function h() = 3
contract Warnings = main contract Warnings =
type state = int type state = int
@@ -58,3 +58,10 @@ namespace FunctionsAsArgs =
private function inc(n : int) : int = n + 1 private function inc(n : int) : int = n + 1
// Never used // Never used
private function dec(n : int) : int = n - 1 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)