Compare commits

...

11 Commits

Author SHA1 Message Date
dependabot[bot] a809c12b95 Bump pygments from 2.14.0 to 2.15.0 in /.github/workflows
Bumps [pygments](https://github.com/pygments/pygments) from 2.14.0 to 2.15.0.
- [Release notes](https://github.com/pygments/pygments/releases)
- [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES)
- [Commits](https://github.com/pygments/pygments/compare/2.14.0...2.15.0)

---
updated-dependencies:
- dependency-name: pygments
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-20 11:28:03 +00:00
Gaith Hallak 86d7b36ba7 Unify typesigs when implementing interface funs (#469) 2023-07-17 13:32:11 +03:00
Gaith Hallak 43c8328615 Prepare v7.2.1 release (#466) 2023-06-29 15:46:23 +04:00
Gaith Hallak c15d411660 Fix bugs caused by the addition of debugging symbols (#464)
* Fix get_catchalls bug

* Fix for event datatype
2023-06-28 18:43:41 +04:00
Gaith Hallak b902226c26 Prepare v7.2.0 release (#462) 2023-06-19 13:21:44 +03:00
Hans Svensson c1e8195fd8 Document Chain.spend and sort Chain functions (#460)
* Document Chain.spend and sort Chain functions

* Too little coffee, re-adding gas-limit
2023-06-19 11:49:03 +02:00
Hans Svensson d5ff9d4a2f fix AENS.update stdlib doc (#459) 2023-06-15 22:45:39 +02:00
Gaith Hallak c395849684 Introduce debugging symbols (#424)
* Add fann type and to_fann fun

* Add fann() to funcall

* Add fann() to closure

* Add fann() to set_state

* Add fann() to remote_u

* Add fann() to remote

* Add fann() to proj

* Add fann() to set_proj

* Add fann() to def and def_u

* Add fann() to op

* Add fann() to let

* Add fann() to lam

* Add fann() to builtin_u

* Add missing functions specs

* Dead code removal

* Fix the spec for compute_state_layout

* Add fann() to var

* Add fann() to switch

* Add fann() to lit and get_state

* Add fann() to builtin

* Add fann() to con

* Add fann() to tuple

* Add fann() to nil

* Fix missing fann() in tuple fexpr()

* Add dbgloc instruction to fate

* Add instructions lines to the debugging result

* Fix compiler tests

* Fix calldata tests

* Rname Ann to FAnn when the type is fann()

* Add line to fann()

* Change attributes for DBGLOC instruction

* Add file to fann()

* Add file to aeso_syntax:ann()

* Fix dialyzer warning

* Remove fann() from fsplit_pat() and fpat()

* Fill out empty fann() when possible

* Save debug locations for child contracts

* Include DBGLOC instructions in the compiler output

* Return an empty string instead of no_file atom

* Wrap args of DBGLOC in immediate tuple

* Upgrade aebytecode ref in rebar.config

* Add DBG_DEF and DBG_UNDEF

* Do not DBG_DEF vars with % prefix

* Do not use DBG_DEF and DBG_UNDEF on args

* Fix dbg_undef for args

* Rename DBGLOC to DBG_LOC

* Remove column from DBG_LOC

* Add missing dbg_loc in to_scode1

* Keep a single DBG_LOC instruction per line

* Remove col from fann

* Add DBG_LOC op to step at function sig

* Remove the variable-register map from debug output

* Use get_value/3 to handle default

* Use lookup instead of lookup_all

* List only needed attributes

* Make debug ops impure

* Split complicated code and add comment

* Fix annotations

* Fix indenting

* Remove dbg_loc before closure

* Add dbg_loc in to_scode

* Add DBG_CALL and DBG_RETURN

* Separate the split at CALL_T and loop

* Revert "Separate the split at CALL_T and loop"

This reverts commit 4ea823a7ca798c756b20cee32f928f41092c4959.

* Revert "Add DBG_CALL and DBG_RETURN"

This reverts commit c406c6feb09b6a5bb859c38d634f08208c901e5a.

* Disable tail call optimization for better debug call stack

* Rename env.debug to env.debug_info

* Upgrade aebytecode: Add DBG_CONTRACT

* Add DBG_CONTRACT instruction

* Check if a var name is fresh in separate function

* Add DBG_CONTRACT and DBG_LOC before DBG_DEF

* Save fresh names of pattern variables

* Implement fsplit_pat_vars for assign

* Set fann for switches

* Revert "Save fresh names of pattern variables"

This reverts commit d2473f982996336131477df2b2115c04a55a62cb.

* Add DBG_DEF for switch pattern vars

* Fix the inability to pattern match constructors

* Upgrade aebytecode dep

* Upgrade aebytecode dep

* Update the lock file

* Add annotations to fexpr var

* Fix issues with pretty-printing of fexprs

* Use FAnn instead of get_fann(Body)

* Upgrade aebytecode version

* Fix pp_fpat

* Fix pattern matching on fpat

* Update rename when a new rename comes up

* Upgrade aebytecode

* Remove the getopt dep

* Fix calldata tests

* Remove file committed by mistake

* Remove location anns from contract call type
2023-06-13 14:36:48 +03:00
Hans Svensson 7bac15949c Introduce encode/decode_value to compiler (#457) 2023-06-01 13:23:21 +02:00
Gaith Hallak 7b6eba5319 Introduce contract-level compile-time constants (#432)
* Allow compile-time constants as toplevel declarations

* Remove the test that fails on toplevel consts

* Warn when shadowing a constant

* Allow records to be used as compile time constants

* Allow data constructors in compile-time constants

* Disable some warnings for toplevel constants

Since variables and functions cannot be used in the definition of
a compile time constants, the following warnings are not going to be
reported:

* Used/Unused variable
* Used/Unused function

* Do not reverse constants declarations

* Add tests for all valid expressions

* Add test for accessing const from namespace

* Revert "Do not reverse constants declarations"

This reverts commit c4647fadacd134866e4be9c2ab4b0d54870a35fd.

* Add test for assigining constant to a constant

* Show empty map or record error when assigning to const

* Report all invalid constant expressions before fail

* Allow accessing records fields in toplevel consts

* Undo a mistake

* Add test for warning on const shadowing

* Show error message when using pattern matching for consts

* Remove unused error

* Ban toplevel constants in contract interfaces

* Varibles rename

* Change the error message for invalid_const_id

* Make constants public in namespaces and private in contracts

* Add a warning about unused constants in contracts

* Use ban_when_const for function applications

* Test for qualified access of constants in functions

* Add failing tests

* Add test for the unused const warning

* Update CHANGELOG

* Update all_syntax test file

* Treat expr and type inside bound as bound

* Allow typed ids to be used for constants

* List valid exprs in the error message for invalid exprs

* Fix tests

* Update the docs about constants

* Update syntax docs

* Check validity of const exprs in a separate functions

* Call both resolve_const and resolve_fun from resolve_var
2023-04-12 14:20:41 +03:00
Gaith Hallak 99bb3fe1fb Mark only included files as potentially unused (#442)
* Mark only included files as potentially unused

* Update CHANGELOG

* Add test
2023-03-21 13:55:18 +03:00
26 changed files with 1571 additions and 760 deletions
+1 -1
View File
@@ -2,4 +2,4 @@ mkdocs==1.4.2
mkdocs-simple-hooks==0.1.5 mkdocs-simple-hooks==0.1.5
mkdocs-material==9.0.9 mkdocs-material==9.0.9
mike==1.1.2 mike==1.1.2
pygments==2.14.0 pygments==2.15.0
+23 -1
View File
@@ -9,6 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
### Removed ### Removed
### Fixed ### Fixed
- Fixed a bug with polymorphism that allowed functions with the same name but different type to be considered as implementations for their corresponding interface function.
## [7.2.1]
### Fixed
- Fixed bugs with the newly added debugging symbols
## [7.2.0]
### Added
- Toplevel compile-time constants
```
namespace N =
let nc = 1
contract C =
let cc = 2
```
- API functions for encoding/decoding Sophia values to/from FATE.
### Removed
- Remove the mapping from variables to FATE registers from the compilation output.
### Fixed
- Warning about unused include when there is no include.
## [7.1.0] ## [7.1.0]
### Added ### Added
@@ -380,7 +400,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply - 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.1.0...HEAD [Unreleased]: https://github.com/aeternity/aesophia/compare/v7.2.1...HEAD
[7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.0...v7.2.1
[7.2.0]: https://github.com/aeternity/aesophia/compare/v7.1.0...v7.2.0
[7.1.0]: https://github.com/aeternity/aesophia/compare/v7.0.1...v7.1.0 [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
-2
View File
@@ -53,8 +53,6 @@ The **pp_** options all print to standard output the following:
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 `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: #### Options to control which compiler optimizations should run:
By default all optimizations are turned on, to disable an optimization, it should be By default all optimizations are turned on, to disable an optimization, it should be
+26
View File
@@ -573,6 +573,32 @@ contract C =
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. 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.
## Constants
Constants in Sophia are contract-level bindings that can be used in either contracts or namespaces. The value of a constant can be a literal, another constant, or arithmetic operations applied to other constants. Lists, tuples, maps, and records can also be used to define a constant as long as their elements are also constants.
The following visibility rules apply to constants:
* Constants defined inside a contract are private in that contract. Thus, cannot be accessed through instances of their defining contract.
* Constants defined inside a namespace are public. Thus, can be used in other contracts or namespaces.
* Constants cannot be defined inside a contract interface.
When a constant is shadowed, it can be accessed using its qualified name:
```
contract C =
let c = 1
entrypoint f() =
let c = 2
c + C.c // the result is 3
```
The name of the constant must be an id; therefore, no pattern matching is allowed when defining a constant:
```
contract C
let x::y::_ = [1,2,3] // this will result in an error
```
## 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
+46 -34
View File
@@ -190,7 +190,7 @@ using the private key of the `owner` account for signing.
##### update ##### update
``` ```
AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int), AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int),
new_ptrs : map(string, AENS.pointee), <signature : signature>) : unit new_ptrs : option(map(string, AENS.pointee)), <signature : signature>) : unit
``` ```
Updates the name. If the optional parameters are set to `None` that parameter Updates the name. If the optional parameters are set to `None` that parameter
@@ -470,38 +470,6 @@ Chain.block_height : int"
The height of the current block (i.e. the block in which the current call will be included). The height of the current block (i.e. the block in which the current call will be included).
##### coinbase
```
Chain.coinbase : address
```
The address of the account that mined the current block.
##### timestamp
```
Chain.timestamp : int
```
The timestamp of the current block (unix time, milliseconds).
##### difficulty
```
Chain.difficulty : int
```
The difficulty of the current block.
##### gas
```
Chain.gas_limit : int
```
The gas limit of the current block.
##### bytecode_hash ##### bytecode_hash
``` ```
Chain.bytecode_hash : 'c => option(hash) Chain.bytecode_hash : 'c => option(hash)
@@ -565,6 +533,7 @@ main contract Market =
The typechecker must be certain about the created contract's type, so it is The typechecker must be certain about the created contract's type, so it is
worth writing it explicitly as shown in the example. worth writing it explicitly as shown in the example.
##### clone ##### clone
``` ```
Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ... Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ...
@@ -623,11 +592,54 @@ implementation of the `init` function does not actually return `state`, but
calls `put` instead. Moreover, FATE prevents even handcrafted calls to `init`. calls `put` instead. Moreover, FATE prevents even handcrafted calls to `init`.
##### coinbase
```
Chain.coinbase : address
```
The address of the account that mined the current block.
##### difficulty
```
Chain.difficulty : int
```
The difficulty of the current block.
##### event ##### event
``` ```
Chain.event(e : event) : unit Chain.event(e : event) : unit
``` ```
Emits the event. To use this function one needs to define the `event` type as a `datatype` in the contract.
Emits the event. To use this function one needs to define the `event` type as a
`datatype` in the contract.
##### gas\_limit
```
Chain.gas_limit : int
```
The gas limit of the current block.
##### spend
```
Chain.spend(to : address, amount : int) : unit
```
Spend `amount` tokens to `to`. Will fail (and abort the contract) if contract
doesn't have `amount` tokens to transfer, or, if `to` is not `payable`.
##### timestamp
```
Chain.timestamp : int
```
The timestamp of the current block (unix time, milliseconds).
### Char ### Char
+1
View File
@@ -104,6 +104,7 @@ Implement ::= ':' Sep1(Con, ',')
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
| 'record' Id ['(' TVar* ')'] '=' RecordType | 'record' Id ['(' TVar* ')'] '=' RecordType
| 'datatype' Id ['(' TVar* ')'] '=' DataType | 'datatype' Id ['(' TVar* ')'] '=' DataType
| 'let' Id [':' Type] '=' Expr
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl) | (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
| Using | Using
+2 -3
View File
@@ -2,8 +2,7 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.2.0"}}} {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.3.0"}}}
, {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 +13,7 @@
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}. ]}.
{relx, [{release, {aesophia, "7.1.0"}, {relx, [{release, {aesophia, "7.2.1"},
[aesophia, aebytecode, getopt]}, [aesophia, aebytecode, getopt]},
{dev_mode, true}, {dev_mode, true},
+3 -3
View File
@@ -1,11 +1,11 @@
{"1.2.0", {"1.2.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"2a0a397afad6b45da52572170f718194018bf33c"}}, {ref,"b38349274fc2bed98d7fe86877e6e1a2df302109"}},
0}, 0},
{<<"aeserialization">>, {<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git", {git,"https://github.com/aeternity/aeserialization.git",
{ref,"eb68fe331bd476910394966b7f5ede7a74d37e35"}}, {ref,"177bf604b2a05e940f92cf00e96e6e269e708245"}},
1}, 1},
{<<"base58">>, {<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git", {git,"https://github.com/aeternity/erl-base58.git",
@@ -16,7 +16,7 @@
{git,"https://github.com/aeternity/enacl.git", {git,"https://github.com/aeternity/enacl.git",
{ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}}, {ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}},
2}, 2},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}, {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},1},
{<<"jsx">>, {<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git", {git,"https://github.com/talentdeficit/jsx.git",
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}}, {ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
+257 -40
View File
@@ -124,15 +124,18 @@
-type variance() :: invariant | covariant | contravariant | bivariant. -type variance() :: invariant | covariant | contravariant | bivariant.
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}. -type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
-type type_info() :: {aeso_syntax:ann(), typedef()}. -type type_info() :: {aeso_syntax:ann(), typedef()}.
-type var_info() :: {aeso_syntax:ann(), utype()}. -type const_info() :: {aeso_syntax:ann(), type()}.
-type var_info() :: {aeso_syntax:ann(), utype()}.
-type fun_env() :: [{name(), fun_info()}]. -type fun_env() :: [{name(), fun_info()}].
-type type_env() :: [{name(), type_info()}]. -type type_env() :: [{name(), type_info()}].
-type const_env() :: [{name(), const_info()}].
-record(scope, { funs = [] :: fun_env() -record(scope, { funs = [] :: fun_env()
, types = [] :: type_env() , types = [] :: type_env()
, consts = [] :: const_env()
, access = public :: access() , access = public :: access()
, kind = namespace :: namespace | contract , kind = namespace :: namespace | contract
, ann = [{origin, system}] :: aeso_syntax:ann() , ann = [{origin, system}] :: aeso_syntax:ann()
@@ -152,6 +155,7 @@
, in_guard = false :: boolean() , in_guard = false :: boolean()
, stateful = false :: boolean() , stateful = false :: boolean()
, unify_throws = true :: boolean() , unify_throws = true :: boolean()
, current_const = none :: none | aeso_syntax:id()
, current_function = none :: none | aeso_syntax:id() , current_function = none :: none | aeso_syntax:id()
, what = top :: top | namespace | contract | contract_interface , what = top :: top | namespace | contract | contract_interface
}). }).
@@ -183,9 +187,13 @@ pop_scope(Env) ->
get_scope(#env{ scopes = Scopes }, Name) -> get_scope(#env{ scopes = Scopes }, Name) ->
maps:get(Name, Scopes, false). maps:get(Name, Scopes, false).
-spec get_current_scope(env()) -> scope().
get_current_scope(#env{ namespace = NS, scopes = Scopes }) ->
maps:get(NS, Scopes).
-spec on_current_scope(env(), fun((scope()) -> scope())) -> env(). -spec on_current_scope(env(), fun((scope()) -> scope())) -> env().
on_current_scope(Env = #env{ namespace = NS, scopes = Scopes }, Fun) -> on_current_scope(Env = #env{ namespace = NS, scopes = Scopes }, Fun) ->
Scope = maps:get(NS, Scopes), Scope = get_current_scope(Env),
Env#env{ scopes = Scopes#{ NS => Fun(Scope) } }. Env#env{ scopes = Scopes#{ NS => Fun(Scope) } }.
-spec on_scopes(env(), fun((scope()) -> scope())) -> env(). -spec on_scopes(env(), fun((scope()) -> scope())) -> env().
@@ -193,8 +201,8 @@ on_scopes(Env = #env{ scopes = Scopes }, Fun) ->
Env#env{ scopes = maps:map(fun(_, Scope) -> Fun(Scope) end, Scopes) }. Env#env{ scopes = maps:map(fun(_, Scope) -> Fun(Scope) end, Scopes) }.
-spec bind_var(aeso_syntax:id(), utype(), env()) -> env(). -spec bind_var(aeso_syntax:id(), utype(), env()) -> env().
bind_var({id, Ann, X}, T, Env = #env{ vars = Vars }) -> bind_var({id, Ann, X}, T, Env) ->
when_warning(warn_shadowing, fun() -> warn_potential_shadowing(Ann, X, Vars) end), when_warning(warn_shadowing, fun() -> warn_potential_shadowing(Env, Ann, X) end),
Env#env{ vars = [{X, {Ann, T}} | Env#env.vars] }. Env#env{ vars = [{X, {Ann, T}} | Env#env.vars] }.
-spec bind_vars([{aeso_syntax:id(), utype()}], env()) -> env(). -spec bind_vars([{aeso_syntax:id(), utype()}], env()) -> env().
@@ -247,6 +255,37 @@ bind_type(X, Ann, Def, Env) ->
Scope#scope{ types = [{X, {Ann, Def}} | Types] } Scope#scope{ types = [{X, {Ann, Def}} | Types] }
end). end).
-spec bind_const(name(), aeso_syntax:ann(), type(), env()) -> env().
bind_const(X, Ann, Type, Env) ->
case lookup_env(Env, term, Ann, [X]) of
false ->
on_current_scope(Env, fun(Scope = #scope{ consts = Consts }) ->
Scope#scope{ consts = [{X, {Ann, Type}} | Consts] }
end);
_ ->
type_error({duplicate_definition, X, [Ann, aeso_syntax:get_ann(Type)]}),
Env
end.
-spec bind_consts(env(), #{ name() => aeso_syntax:decl() }, [{acyclic, name()} | {cyclic, [name()]}], [aeso_syntax:decl()]) ->
{env(), [aeso_syntax:decl()]}.
bind_consts(Env, _Consts, [], Acc) ->
{Env, lists:reverse(Acc)};
bind_consts(Env, Consts, [{cyclic, Xs} | _SCCs], _Acc) ->
ConstDecls = [ maps:get(X, Consts) || X <- Xs ],
type_error({mutually_recursive_constants, lists:reverse(ConstDecls)}),
{Env, []};
bind_consts(Env, Consts, [{acyclic, X} | SCCs], Acc) ->
case maps:get(X, Consts, undefined) of
Const = {letval, Ann, Id, _} ->
NewConst = {letval, _, {typed, _, _, Type}, _} = infer_const(Env, Const),
NewEnv = bind_const(name(Id), Ann, Type, Env),
bind_consts(NewEnv, Consts, SCCs, [NewConst | Acc]);
undefined ->
%% When a used id is not a letval, a type error will be thrown
bind_consts(Env, Consts, SCCs, Acc)
end.
%% Bind state primitives %% Bind state primitives
-spec bind_state(env()) -> env(). -spec bind_state(env()) -> env().
bind_state(Env) -> bind_state(Env) ->
@@ -312,11 +351,11 @@ bind_contract(Typing, {Contract, Ann, Id, _Impls, Contents}, Env)
Sys = [{origin, system}], Sys = [{origin, system}],
TypeOrFresh = fun({typed, _, _, Type}) -> Type; (_) -> fresh_uvar(Sys) end, TypeOrFresh = fun({typed, _, _, Type}) -> Type; (_) -> fresh_uvar(Sys) end,
Fields = Fields =
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)} [ {field_t, AnnF, Entrypoint, contract_call_type(aeso_syntax:set_ann(Sys, Type))}
|| {fun_decl, AnnF, Entrypoint, Type = {fun_t, _, _, _, _}} <- 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, [], [TypeOrFresh(Arg) || Arg <- Args], TypeOrFresh(Ret)}) {fun_t, Sys, [], [TypeOrFresh(Arg) || Arg <- Args], TypeOrFresh(Ret)})
} }
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], Ret}]} <- Contents, || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], Ret}]} <- Contents,
Name =/= "init" Name =/= "init"
@@ -431,21 +470,30 @@ lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes
%% Get the scope %% Get the scope
case maps:get(Qual, Scopes, false) of case maps:get(Qual, Scopes, false) of
false -> false; %% TODO: return reason for not in scope false -> false; %% TODO: return reason for not in scope
#scope{ funs = Funs, types = Types } -> #scope{ funs = Funs, types = Types, consts = Consts, kind = ScopeKind } ->
Defs = case Kind of Defs = case Kind of
type -> Types; type -> Types;
term -> Funs term -> Funs
end, end,
%% Look up the unqualified name %% Look up the unqualified name
case proplists:get_value(Name, Defs, false) of case proplists:get_value(Name, Defs, false) of
false -> false; false ->
case proplists:get_value(Name, Consts, false) of
false ->
false;
Const when AllowPrivate; ScopeKind == namespace ->
{QName, Const};
Const ->
type_error({contract_treated_as_namespace_constant, Ann, QName}),
{QName, Const}
end;
{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 -> {contract_fun, Ann1, Type} when AllowPrivate orelse QNameIsEvent ->
{QName, {Ann1, Type}}; {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_entrypoint, Ann, QName}),
{QName, {Ann1, Type}}; {QName, {Ann1, Type}};
{Ann1, _} = E -> {Ann1, _} = E ->
%% Check that it's not private (or we can see private funs) %% Check that it's not private (or we can see private funs)
@@ -486,8 +534,11 @@ qname({qid, _, Xs}) -> Xs;
qname({con, _, X}) -> [X]; qname({con, _, X}) -> [X];
qname({qcon, _, Xs}) -> Xs. qname({qcon, _, Xs}) -> Xs.
-spec name(aeso_syntax:id() | aeso_syntax:con()) -> name(). -spec name(Named | {typed, _, Named, _}) -> name() when
name({_, _, X}) -> X. Named :: aeso_syntax:id() | aeso_syntax:con().
name({typed, _, X, _}) -> name(X);
name({id, _, X}) -> X;
name({con, _, X}) -> X.
-spec qid(aeso_syntax:ann(), qname()) -> aeso_syntax:id() | aeso_syntax:qid(). -spec qid(aeso_syntax:ann(), qname()) -> aeso_syntax:id() | aeso_syntax:qid().
qid(Ann, [X]) -> {id, Ann, X}; qid(Ann, [X]) -> {id, Ann, X};
@@ -1054,6 +1105,7 @@ infer_contract(Env0, What, Defs0, Options) ->
({fun_clauses, _, _, _, _}) -> function; ({fun_clauses, _, _, _, _}) -> function;
({fun_decl, _, _, _}) -> prototype; ({fun_decl, _, _, _}) -> prototype;
({using, _, _, _, _}) -> using; ({using, _, _, _, _}) -> using;
({letval, _, _, _}) -> constant;
(_) -> unexpected (_) -> unexpected
end, end,
Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end, Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end,
@@ -1069,11 +1121,12 @@ infer_contract(Env0, What, Defs0, Options) ->
contract_interface -> Env1; contract_interface -> Env1;
contract -> bind_state(Env1) %% bind state and put contract -> bind_state(Env1) %% bind state and put
end, end,
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype, Defs) ]), {Env2C, Consts} = check_constants(Env2, Get(constant, Defs)),
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env2C, Decl) || Decl <- Get(prototype, Defs) ]),
[ type_error({missing_definition, Id}) || {fun_decl, _, Id, _} <- Decls, [ type_error({missing_definition, Id}) || {fun_decl, _, Id, _} <- Decls,
What =:= contract, What =:= contract,
get_option(no_code, false) =:= false ], get_option(no_code, false) =:= false ],
Env3 = bind_funs(ProtoSigs, Env2), Env3 = bind_funs(ProtoSigs, Env2C),
Functions = Get(function, Defs), Functions = Get(function, Defs),
%% Check for duplicates in Functions (we turn it into a map below) %% Check for duplicates in Functions (we turn it into a map below)
FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}};
@@ -1093,7 +1146,7 @@ infer_contract(Env0, What, Defs0, Options) ->
check_entrypoints(Defs1), check_entrypoints(Defs1),
destroy_and_report_type_errors(Env4), destroy_and_report_type_errors(Env4),
%% Add inferred types of definitions %% Add inferred types of definitions
{Env5, TypeDefs ++ Decls ++ Defs1}. {Env5, TypeDefs ++ Decls ++ Consts ++ Defs1}.
%% Restructure blocks into multi-clause fundefs (`fun_clauses`). %% Restructure blocks into multi-clause fundefs (`fun_clauses`).
-spec process_blocks([aeso_syntax:decl()]) -> [aeso_syntax:decl()]. -spec process_blocks([aeso_syntax:decl()]) -> [aeso_syntax:decl()].
@@ -1243,6 +1296,21 @@ opposite_variance(covariant) -> contravariant;
opposite_variance(contravariant) -> covariant; opposite_variance(contravariant) -> covariant;
opposite_variance(bivariant) -> bivariant. opposite_variance(bivariant) -> bivariant.
-spec check_constants(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
check_constants(Env = #env{ what = What }, Consts) ->
HasValidId = fun({letval, _, {id, _, _}, _}) -> true;
({letval, _, {typed, _, {id, _, _}, _}, _}) -> true;
(_) -> false
end,
{Valid, Invalid} = lists:partition(HasValidId, Consts),
[ type_error({invalid_const_id, aeso_syntax:get_ann(Pat)}) || {letval, _, Pat, _} <- Invalid ],
[ type_error({illegal_const_in_interface, Ann}) || {letval, Ann, _, _} <- Valid, What == contract_interface ],
when_warning(warn_unused_constants, fun() -> potential_unused_constants(Env, Valid) end),
ConstMap = maps:from_list([ {name(Id), Const} || Const = {letval, _, Id, _} <- Valid ]),
DepGraph = maps:map(fun(_, Const) -> aeso_syntax_utils:used_ids(Const) end, ConstMap),
SCCs = aeso_utils:scc(DepGraph),
bind_consts(Env, ConstMap, SCCs, []).
check_usings(Env, []) -> check_usings(Env, []) ->
Env; Env;
check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con, Alias, Parts} | Rest]) -> check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con, Alias, Parts} | Rest]) ->
@@ -1514,7 +1582,7 @@ check_reserved_entrypoints(Funs) ->
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),
TypeSig = {type_sig, Ann, none, Named, Args, Ret}, TypeSig = {type_sig, Ann, none, Named, Args, Ret},
register_implementation(Id, TypeSig), register_implementation(Env, Id, TypeSig),
{{Name, TypeSig}, {fun_decl, Ann, Id, Type1}}; {{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}),
@@ -1522,13 +1590,16 @@ check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) ->
%% Register the function FunId as implemented by deleting it from the functions %% 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. %% to be implemented table if it is included there, or return true otherwise.
-spec register_implementation(FunId, FunSig) -> true | no_return() when -spec register_implementation(env(), FunId, FunSig) -> true | no_return() when
FunId :: aeso_syntax:id(), FunId :: aeso_syntax:id(),
FunSig :: typesig(). FunSig :: typesig().
register_implementation(Id, Sig) -> register_implementation(Env, Id, Sig) ->
Name = name(Id), Name = name(Id),
case ets_lookup(functions_to_implement, Name) of case ets_lookup(functions_to_implement, Name) of
[{Name, Interface, Decl = {fun_decl, _, DeclId, _}}] -> [{Name, Interface, Decl = {fun_decl, _, DeclId, FunT}}] ->
When = {implement_interface_fun, aeso_syntax:get_ann(Sig), Name, name(Interface)},
unify(Env, typesig_to_fun_t(Sig), FunT, When),
DeclStateful = aeso_syntax:get_ann(stateful, Decl, false), DeclStateful = aeso_syntax:get_ann(stateful, Decl, false),
DeclPayable = aeso_syntax:get_ann(payable, Decl, false), DeclPayable = aeso_syntax:get_ann(payable, Decl, false),
@@ -1556,7 +1627,7 @@ infer_nonrec(Env, LetFun) ->
create_constraints(), create_constraints(),
NewLetFun = {{_, Sig}, _} = 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), register_implementation(Env, 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),
@@ -1586,7 +1657,7 @@ infer_letrec(Env, Defs) ->
Inferred = Inferred =
[ begin [ begin
Res = {{Name, TypeSig}, LetFun} = infer_letfun(ExtendEnv, LF), Res = {{Name, TypeSig}, LetFun} = infer_letfun(ExtendEnv, LF),
register_implementation(get_letfun_id(LetFun), TypeSig), register_implementation(Env, 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}),
@@ -1687,9 +1758,19 @@ lookup_name(Env = #env{ namespace = NS, current_function = CurFn }, As, Id, Opti
type_error({unbound_variable, Id}), type_error({unbound_variable, Id}),
{Id, fresh_uvar(As)}; {Id, fresh_uvar(As)};
{QId, {_, Ty}} -> {QId, {_, Ty}} ->
when_warning(warn_unused_variables, fun() -> used_variable(NS, name(CurFn), QId) end), %% Variables and functions cannot be used when CurFn is `none`.
when_warning(warn_unused_functions, %% i.e. they cannot be used in toplevel constants
fun() -> register_function_call(NS ++ qname(CurFn), QId) end), [ begin
when_warning(
warn_unused_variables,
fun() -> used_variable(NS, name(CurFn), QId) end),
when_warning(
warn_unused_functions,
fun() -> register_function_call(NS ++ qname(CurFn), QId) end)
end || CurFn =/= none ],
when_warning(warn_unused_constants, fun() -> used_constant(NS, QId) end),
Freshen = proplists:get_value(freshen, Options, false), Freshen = proplists:get_value(freshen, Options, false),
check_stateful(Env, Id, Ty), check_stateful(Env, Id, Ty),
Ty1 = case Ty of Ty1 = case Ty of
@@ -2054,6 +2135,81 @@ infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) ->
type_error({missing_body_for_let, Attrs}), type_error({missing_body_for_let, Attrs}),
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}).
check_valid_const_expr({bool, _, _}) ->
true;
check_valid_const_expr({int, _, _}) ->
true;
check_valid_const_expr({char, _, _}) ->
true;
check_valid_const_expr({string, _, _}) ->
true;
check_valid_const_expr({bytes, _, _}) ->
true;
check_valid_const_expr({account_pubkey, _, _}) ->
true;
check_valid_const_expr({oracle_pubkey, _, _}) ->
true;
check_valid_const_expr({oracle_query_id, _, _}) ->
true;
check_valid_const_expr({contract_pubkey, _, _}) ->
true;
check_valid_const_expr({id, _, "_"}) ->
true;
check_valid_const_expr({Tag, _, _}) when Tag == id; Tag == qid; Tag == con; Tag == qcon ->
true;
check_valid_const_expr({tuple, _, Cpts}) ->
lists:all(fun(X) -> X end, [ check_valid_const_expr(C) || C <- Cpts ]);
check_valid_const_expr({list, _, Elems}) ->
lists:all(fun(X) -> X end, [ check_valid_const_expr(Elem) || Elem <- Elems ]);
check_valid_const_expr({list_comp, _, _, _}) ->
false;
check_valid_const_expr({typed, _, Body, _}) ->
check_valid_const_expr(Body);
check_valid_const_expr({app, Ann, Fun, Args0}) ->
{_, Args} = split_args(Args0),
case aeso_syntax:get_ann(format, Ann) of
infix ->
lists:all(fun(X) -> X end, [ check_valid_const_expr(Arg) || Arg <- Args ]);
prefix ->
lists:all(fun(X) -> X end, [ check_valid_const_expr(Arg) || Arg <- Args ]);
_ ->
%% Applications of data constructors are allowed in constants
lists:member(element(1, Fun), [con, qcon])
end;
check_valid_const_expr({'if', _, _, _, _}) ->
false;
check_valid_const_expr({switch, _, _, _}) ->
false;
check_valid_const_expr({record, _, Fields}) ->
lists:all(fun(X) -> X end, [ check_valid_const_expr(Expr) || {field, _, _, Expr} <- Fields ]);
check_valid_const_expr({record, _, _, _}) ->
false;
check_valid_const_expr({proj, _, Record, _}) ->
check_valid_const_expr(Record);
% Maps
check_valid_const_expr({map_get, _, _, _}) -> %% map lookup
false;
check_valid_const_expr({map_get, _, _, _, _}) -> %% map lookup with default
false;
check_valid_const_expr({map, _, KVs}) -> %% map construction
lists:all(fun(X) -> X end, [ check_valid_const_expr(K) andalso check_valid_const_expr(V) || {K, V} <- KVs ]);
check_valid_const_expr({map, _, _, _}) -> %% map update
false;
check_valid_const_expr({block, _, _}) ->
false;
check_valid_const_expr({record_or_map_error, _, Fields}) ->
lists:all(fun(X) -> X end, [ check_valid_const_expr(Expr) || {field, _, _, Expr} <- Fields ]);
check_valid_const_expr({record_or_map_error, _, _, _}) ->
false;
check_valid_const_expr({lam, _, _, _}) ->
false;
check_valid_const_expr({letpat, _, _, _}) ->
false;
check_valid_const_expr({letval, _, _, _}) ->
false;
check_valid_const_expr({letfun, _, _, _, _, _}) ->
false.
infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) ->
FunType = FunType =
case Fun of case Fun of
@@ -2178,9 +2334,14 @@ infer_pattern(Env, Pattern) ->
NewPattern = infer_expr(NewEnv, Pattern), NewPattern = infer_expr(NewEnv, Pattern),
{NewEnv#env{ in_pattern = Env#env.in_pattern }, NewPattern}. {NewEnv#env{ in_pattern = Env#env.in_pattern }, NewPattern}.
infer_case(Env = #env{ namespace = NS, current_function = {id, _, Fun} }, Attrs, Pattern, ExprType, GuardedBranches, SwitchType) -> infer_case(Env = #env{ namespace = NS, current_function = FunId }, Attrs, Pattern, ExprType, GuardedBranches, SwitchType) ->
{NewEnv, NewPattern = {typed, _, _, PatType}} = infer_pattern(Env, Pattern), {NewEnv, NewPattern = {typed, _, _, PatType}} = infer_pattern(Env, Pattern),
when_warning(warn_unused_variables, fun() -> potential_unused_variables(NS, Fun, free_vars(Pattern)) end),
%% Make sure we are inside a function before warning about potentially unused var
[ when_warning(warn_unused_variables,
fun() -> potential_unused_variables(NS, Fun, free_vars(Pattern)) end)
|| {id, _, Fun} <- [FunId] ],
InferGuardedBranches = fun({guarded, Ann, Guards, Branch}) -> InferGuardedBranches = fun({guarded, Ann, Guards, Branch}) ->
NewGuards = lists:map(fun(Guard) -> NewGuards = lists:map(fun(Guard) ->
check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"}) check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"})
@@ -2214,6 +2375,19 @@ infer_block(Env, Attrs, [E|Rest], BlockType) ->
when_warning(warn_unused_return_value, fun() -> potential_unused_return_value(NewE) end), when_warning(warn_unused_return_value, fun() -> potential_unused_return_value(NewE) end),
[NewE|infer_block(Env, Attrs, Rest, BlockType)]. [NewE|infer_block(Env, Attrs, Rest, BlockType)].
infer_const(Env, {letval, Ann, TypedId = {typed, _, Id = {id, _, _}, Type}, Expr}) ->
check_valid_const_expr(Expr) orelse type_error({invalid_const_expr, Id}),
NewExpr = check_expr(Env#env{ current_const = Id }, Expr, Type),
{letval, Ann, TypedId, NewExpr};
infer_const(Env, {letval, Ann, Id = {id, AnnId, _}, Expr}) ->
check_valid_const_expr(Expr) orelse type_error({invalid_const_expr, Id}),
create_constraints(),
NewExpr = {typed, _, _, Type} = infer_expr(Env#env{ current_const = Id }, Expr),
solve_then_destroy_and_report_unsolved_constraints(Env),
IdType = setelement(2, Type, AnnId),
NewId = {typed, aeso_syntax:get_ann(Id), Id, IdType},
instantiate({letval, Ann, NewId, NewExpr}).
infer_infix({BoolOp, As}) infer_infix({BoolOp, As})
when BoolOp =:= '&&'; BoolOp =:= '||' -> when BoolOp =:= '&&'; BoolOp =:= '||' ->
Bool = {id, As, "bool"}, Bool = {id, As, "bool"},
@@ -3177,6 +3351,7 @@ all_warnings() ->
[ warn_unused_includes [ warn_unused_includes
, warn_unused_stateful , warn_unused_stateful
, warn_unused_variables , warn_unused_variables
, warn_unused_constants
, warn_unused_typedefs , warn_unused_typedefs
, warn_unused_return_value , warn_unused_return_value
, warn_unused_functions , warn_unused_functions
@@ -3207,9 +3382,14 @@ when_warning(Warn, Do) ->
%% Warnings (Unused includes) %% Warnings (Unused includes)
potential_unused_include(Ann, SrcFile) -> potential_unused_include(Ann, SrcFile) ->
case aeso_syntax:get_ann(file, Ann, no_file) of IsIncluded = aeso_syntax:get_ann(include_type, Ann, none) =/= none,
no_file -> ok; case IsIncluded of
File -> ets_insert(warnings, {unused_include, File, SrcFile}) false -> ok;
true ->
case aeso_syntax:get_ann(file, Ann, no_file) of
no_file -> ok;
File -> ets_insert(warnings, {unused_include, File, SrcFile})
end
end. end.
used_include(Ann) -> used_include(Ann) ->
@@ -3249,6 +3429,17 @@ used_variable(Namespace, Fun, [VarName]) ->
ets_match_delete(warnings, {unused_variable, '_', Namespace, Fun, VarName}); ets_match_delete(warnings, {unused_variable, '_', Namespace, Fun, VarName});
used_variable(_, _, _) -> ok. used_variable(_, _, _) -> ok.
%% Warnings (Unused constants)
potential_unused_constants(#env{ what = namespace }, _Consts) ->
[];
potential_unused_constants(#env{ namespace = Namespace }, Consts) ->
[ ets_insert(warnings, {unused_constant, Ann, Namespace, Name}) || {letval, _, {id, Ann, Name}, _} <- Consts ].
used_constant(Namespace = [Contract], [Contract, ConstName]) ->
ets_match_delete(warnings, {unused_constant, '_', Namespace, ConstName});
used_constant(_, _) -> ok.
%% Warnings (Unused return value) %% Warnings (Unused return value)
potential_unused_return_value({typed, Ann, {app, _, {typed, _, _, {fun_t, _, _, _, {id, _, Type}}}, _}, _}) when Type /= "unit" -> potential_unused_return_value({typed, Ann, {app, _, {typed, _, _, {fun_t, _, _, _, {id, _, Type}}}, _}, _}) when Type /= "unit" ->
@@ -3294,9 +3485,11 @@ destroy_and_report_unused_functions() ->
%% Warnings (Shadowing) %% Warnings (Shadowing)
warn_potential_shadowing(_, "_", _) -> ok; warn_potential_shadowing(_, _, "_") -> ok;
warn_potential_shadowing(Ann, Name, Vars) -> warn_potential_shadowing(Env = #env{ vars = Vars }, Ann, Name) ->
case proplists:get_value(Name, Vars, false) of CurrentScope = get_current_scope(Env),
Consts = CurrentScope#scope.consts,
case proplists:get_value(Name, Vars ++ Consts, false) of
false -> ok; false -> ok;
{AnnOld, _} -> ets_insert(warnings, {shadowing, Ann, Name, AnnOld}) {AnnOld, _} -> ets_insert(warnings, {shadowing, Ann, Name, AnnOld})
end. end.
@@ -3538,10 +3731,6 @@ mk_error({type_decl, _, {id, Pos, Name}, _}) ->
Msg = io_lib:format("Empty type declarations are not supported. Type `~s` lacks a definition", Msg = io_lib:format("Empty type declarations are not supported. Type `~s` lacks a definition",
[Name]), [Name]),
mk_t_err(pos(Pos), Msg); mk_t_err(pos(Pos), Msg);
mk_error({letval, _Pos, {id, Pos, Name}, _Def}) ->
Msg = io_lib:format("Toplevel \"let\" definitions are not supported. Value `~s` could be replaced by 0-argument function.",
[Name]),
mk_t_err(pos(Pos), Msg);
mk_error({stateful_not_allowed, Id, Fun}) -> mk_error({stateful_not_allowed, Id, Fun}) ->
Msg = io_lib:format("Cannot reference stateful function `~s` in the definition of non-stateful function `~s`.", Msg = io_lib:format("Cannot reference stateful function `~s` in the definition of non-stateful function `~s`.",
[pp(Id), pp(Fun)]), [pp(Id), pp(Fun)]),
@@ -3625,10 +3814,14 @@ mk_error({cannot_call_init_function, Ann}) ->
Msg = "The 'init' function is called exclusively by the create contract transaction " Msg = "The 'init' function is called exclusively by the create contract transaction "
"and cannot be called from the contract code.", "and cannot be called from the contract code.",
mk_t_err(pos(Ann), Msg); mk_t_err(pos(Ann), Msg);
mk_error({contract_treated_as_namespace, Ann, [Con, Fun] = QName}) -> mk_error({contract_treated_as_namespace_entrypoint, Ann, [Con, Fun] = QName}) ->
Msg = io_lib:format("Invalid call to contract entrypoint `~s`.", [string:join(QName, ".")]), Msg = io_lib:format("Invalid call to contract entrypoint `~s`.", [string:join(QName, ".")]),
Cxt = io_lib:format("It must be called as `c.~s` for some `c : ~s`.", [Fun, Con]), Cxt = io_lib:format("It must be called as `c.~s` for some `c : ~s`.", [Fun, Con]),
mk_t_err(pos(Ann), Msg, Cxt); mk_t_err(pos(Ann), Msg, Cxt);
mk_error({contract_treated_as_namespace_constant, Ann, QName}) ->
Msg = io_lib:format("Invalid use of the contract constant `~s`.", [string:join(QName, ".")]),
Cxt = "Toplevel contract constants can only be used in the contracts where they are defined.",
mk_t_err(pos(Ann), Msg, Cxt);
mk_error({bad_top_level_decl, Decl}) -> mk_error({bad_top_level_decl, Decl}) ->
What = case element(1, Decl) of What = case element(1, Decl) of
letval -> "function or entrypoint"; letval -> "function or entrypoint";
@@ -3789,6 +3982,23 @@ mk_error({unpreserved_payablity, Kind, ContractCon, InterfaceCon}) ->
Msg = io_lib:format("Non-payable ~s `~s` cannot implement payable interface `~s`", Msg = io_lib:format("Non-payable ~s `~s` cannot implement payable interface `~s`",
[KindStr, name(ContractCon), name(InterfaceCon)]), [KindStr, name(ContractCon), name(InterfaceCon)]),
mk_t_err(pos(ContractCon), Msg); mk_t_err(pos(ContractCon), Msg);
mk_error({mutually_recursive_constants, Consts}) ->
Msg = [ "Mutual recursion detected between the constants",
[ io_lib:format("\n - `~s` at ~s", [name(Id), pp_loc(Ann)])
|| {letval, Ann, Id, _} <- Consts ] ],
[{letval, Ann, _, _} | _] = Consts,
mk_t_err(pos(Ann), Msg);
mk_error({invalid_const_id, Ann}) ->
Msg = "The name of the compile-time constant cannot have pattern matching",
mk_t_err(pos(Ann), Msg);
mk_error({invalid_const_expr, ConstId}) ->
Msg = io_lib:format("Invalid expression in the definition of the constant `~s`", [name(ConstId)]),
Cxt = "You can only use the following expressions as constants: "
"literals, lists, tuples, maps, and other constants",
mk_t_err(pos(aeso_syntax:get_ann(ConstId)), Msg, Cxt);
mk_error({illegal_const_in_interface, Ann}) ->
Msg = "Cannot define toplevel constants inside a contract interface",
mk_t_err(pos(Ann), 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).
@@ -3802,6 +4012,9 @@ mk_warning({unused_stateful, Ann, FunName}) ->
mk_warning({unused_variable, Ann, _Namespace, _Fun, VarName}) -> mk_warning({unused_variable, Ann, _Namespace, _Fun, VarName}) ->
Msg = io_lib:format("The variable `~s` is defined but never used.", [VarName]), Msg = io_lib:format("The variable `~s` is defined but never used.", [VarName]),
aeso_warnings:new(pos(Ann), Msg); aeso_warnings:new(pos(Ann), Msg);
mk_warning({unused_constant, Ann, _Namespace, ConstName}) ->
Msg = io_lib:format("The constant `~s` is defined but never used.", [ConstName]),
aeso_warnings:new(pos(Ann), Msg);
mk_warning({unused_typedef, Ann, QName, _Arity}) -> mk_warning({unused_typedef, Ann, QName, _Arity}) ->
Msg = io_lib:format("The type `~s` is defined but never used.", [lists:last(QName)]), Msg = io_lib:format("The type `~s` is defined but never used.", [lists:last(QName)]),
aeso_warnings:new(pos(Ann), Msg); aeso_warnings:new(pos(Ann), Msg);
@@ -3955,6 +4168,10 @@ pp_when({var_args, Ann, Fun}) ->
{pos(Ann) {pos(Ann)
, io_lib:format("when resolving arguments of variadic function `~s`", [pp_expr(Fun)]) , io_lib:format("when resolving arguments of variadic function `~s`", [pp_expr(Fun)])
}; };
pp_when({implement_interface_fun, Ann, Entrypoint, Interface}) ->
{ pos(Ann)
, io_lib:format("when implementing the entrypoint `~s` from the interface `~s`", [Entrypoint, Interface])
};
pp_when(unknown) -> {pos(0,0), ""}. pp_when(unknown) -> {pos(0,0), ""}.
-spec pp_why_record(why_record()) -> {pos(), iolist()}. -spec pp_why_record(why_record()) -> {pos(), iolist()}.
+712 -507
View File
File diff suppressed because it is too large Load Diff
+74 -33
View File
@@ -12,6 +12,8 @@
, file/2 , file/2
, from_string/2 , from_string/2
, check_call/4 , check_call/4
, decode_value/4
, encode_value/4
, create_calldata/3 , create_calldata/3
, create_calldata/4 , create_calldata/4
, version/0 , version/0
@@ -117,7 +119,7 @@ from_string1(ContractString, Options) ->
, warnings := Warnings } = string_to_code(ContractString, Options), , warnings := Warnings } = string_to_code(ContractString, Options),
#{ child_con_env := ChildContracts } = FCodeEnv, #{ child_con_env := ChildContracts } = FCodeEnv,
SavedFreshNames = maps:get(saved_fresh_names, FCodeEnv, #{}), SavedFreshNames = maps:get(saved_fresh_names, FCodeEnv, #{}),
{FateCode, VarsRegs} = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options), FateCode = 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(),
@@ -130,13 +132,7 @@ from_string1(ContractString, Options) ->
payable => maps:get(payable, FCode), payable => maps:get(payable, FCode),
warnings => Warnings warnings => Warnings
}, },
ResDbg = Res#{variables_registers => VarsRegs}, {ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
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
@@ -188,30 +184,55 @@ check_call(Source, FunName, Args, Options) ->
check_call1(Source, FunName, Args, Options). check_call1(Source, FunName, Args, Options).
check_call1(ContractString0, FunName, Args, Options) -> check_call1(ContractString0, FunName, Args, Options) ->
case add_extra_call(ContractString0, {call, FunName, Args}, Options) of
{ok, CallName, Code} ->
{def, _, _, FcodeArgs} = get_call_body(CallName, Code),
{ok, FunName, [ aeso_fcode_to_fate:term_to_fate(A) || A <- FcodeArgs ]};
Err = {error, _} ->
Err
end.
add_extra_call(Contract0, Call, Options) ->
try try
%% First check the contract without the __call function %% First check the contract without the __call function
#{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(Contract0, 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,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)), lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args), Contract = insert_call_function(Ast, Contract0, CallName, Call),
#{fcode := Fcode} = string_to_code(ContractString, Options), {ok, CallName, string_to_code(Contract, Options)}
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
catch catch
throw:{error, Errors} -> {error, Errors} throw:{error, Errors} -> {error, Errors}
end. end.
arguments_of_body(CallName, _FunName, Fcode) -> get_call_body(CallName, #{fcode := Fcode}) ->
#{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)), #{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)),
{def, _FName, Args} = Body, Body.
%% FName is either {entrypoint, list_to_binary(FunName)} or 'init'
[ aeso_fcode_to_fate:term_to_fate(A) || A <- Args ]. encode_value(Contract0, Type, Value, Options) ->
case add_extra_call(Contract0, {value, Type, Value}, Options) of
{ok, CallName, Code} ->
Body = get_call_body(CallName, Code),
{ok, aeb_fate_encoding:serialize(aeso_fcode_to_fate:term_to_fate(Body))};
Err = {error, _} ->
Err
end.
decode_value(Contract0, Type, FateValue, Options) ->
case add_extra_call(Contract0, {type, Type}, Options) of
{ok, CallName, Code} ->
#{ unfolded_typed_ast := TypedAst
, type_env := TypeEnv} = Code,
{ok, _, Type0} = get_decode_type(CallName, TypedAst),
Type1 = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
fate_data_to_sophia_value(Type0, Type1, FateValue);
Err = {error, _} ->
Err
end.
first_none_match(_CallName, _Hashes, []) -> first_none_match(_CallName, _Hashes, []) ->
error(unable_to_find_unique_call_name); error(unable_to_find_unique_call_name);
@@ -224,14 +245,31 @@ first_none_match(CallName, Hashes, [Char|Chars]) ->
end. end.
%% Add the __call function to a contract. %% Add the __call function to a contract.
-spec insert_call_function(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string(). -spec insert_call_function(aeso_syntax:ast(), string(), string(),
insert_call_function(Ast, Code, Call, FunName, Args) -> {call, string(), [string()]} | {value, string(), string()} | {type, string()}) -> string().
insert_call_function(Ast, Code, Call, {call, FunName, Args}) ->
Ind = last_contract_indent(Ast), Ind = last_contract_indent(Ast),
lists:flatten( lists:flatten(
[ Code, [ Code,
"\n\n", "\n\n",
lists:duplicate(Ind, " "), lists:duplicate(Ind, " "),
"stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n" "stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n"
]);
insert_call_function(Ast, Code, Call, {value, Type, Value}) ->
Ind = last_contract_indent(Ast),
lists:flatten(
[ Code,
"\n\n",
lists:duplicate(Ind, " "),
"entrypoint ", Call, "() : ", Type, " = ", Value, "\n"
]);
insert_call_function(Ast, Code, Call, {type, Type}) ->
Ind = last_contract_indent(Ast),
lists:flatten(
[ Code,
"\n\n",
lists:duplicate(Ind, " "),
"entrypoint ", Call, "(val : ", Type, ") = val\n"
]). ]).
-spec insert_init_function(string(), options()) -> string(). -spec insert_init_function(string(), options()) -> string().
@@ -274,22 +312,25 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
{ok, _, Type0} = get_decode_type(FunName, TypedAst), {ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
try fate_data_to_sophia_value(Type0, Type, Data)
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s",
[aeb_fate_encoding:deserialize(Data), Type1]),
{error, [aeso_errors:new(data_error, Msg)]};
_:_ ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]),
{error, [aeso_errors:new(data_error, Msg)]}
end
catch catch
throw:{error, Errors} -> {error, Errors} throw:{error, Errors} -> {error, Errors}
end. end.
fate_data_to_sophia_value(Type, UnfoldedType, FateData) ->
try
{ok, aeso_vm_decode:from_fate(UnfoldedType, aeb_fate_encoding:deserialize(FateData))}
catch throw:cannot_translate_to_sophia ->
Type1 = prettypr:format(aeso_pretty:type(Type)),
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s",
[aeb_fate_encoding:deserialize(FateData), Type1]),
{error, [aeso_errors:new(data_error, Msg)]};
_:_ ->
Type1 = prettypr:format(aeso_pretty:type(Type)),
Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]),
{error, [aeso_errors:new(data_error, Msg)]}
end.
-spec create_calldata(string(), string(), [string()]) -> -spec create_calldata(string(), string(), [string()]) ->
{ok, binary()} | {error, [aeso_errors:error()]}. {ok, binary()} | {error, [aeso_errors:error()]}.
create_calldata(Code, Fun, Args) -> create_calldata(Code, Fun, Args) ->
+193 -117
View File
@@ -52,7 +52,8 @@
tailpos = true, tailpos = true,
child_contracts = #{}, child_contracts = #{},
saved_fresh_names = #{}, saved_fresh_names = #{},
options = [] }). options = [],
debug_info = false }).
%% -- Debugging -------------------------------------------------------------- %% -- Debugging --------------------------------------------------------------
@@ -81,24 +82,16 @@ code_error(Err) ->
compile(FCode, SavedFreshNames, Options) -> compile(FCode, SavedFreshNames, Options) ->
compile(#{}, FCode, SavedFreshNames, Options). compile(#{}, FCode, SavedFreshNames, Options).
compile(ChildContracts, FCode, SavedFreshNames, Options) -> compile(ChildContracts, FCode, SavedFreshNames, Options) ->
try
compile1(ChildContracts, FCode, SavedFreshNames, Options)
after
put(variables_registers, undefined)
end.
compile1(ChildContracts, FCode, SavedFreshNames, Options) ->
#{ contract_name := ContractName, #{ contract_name := ContractName,
functions := Functions } = FCode, functions := Functions } = FCode,
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, 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)]),
FateCode1 = case proplists:get_value(include_child_contract_symbols, Options, false) of case proplists:get_value(include_child_contract_symbols, Options, false) of
false -> FateCode; false -> FateCode;
true -> add_child_symbols(ChildContracts, FateCode) true -> add_child_symbols(ChildContracts, FateCode)
end, 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)).
@@ -123,31 +116,15 @@ functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Opt
function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, SavedFreshNames, 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 = [ A || A <- Attrs0, A == private orelse A == payable ],
Env = init_env(ChildContracts, ContractName, Functions, Name, Args, SavedFreshNames, Options), Env = init_env(ChildContracts, ContractName, Functions, Name, Args, SavedFreshNames, Options),
[ add_variables_register(Env, Arg, Register) || ArgsNames = [ X || {X, _} <- lists:reverse(Env#env.vars) ],
proplists:get_value(debug_info, Options, false),
{Arg, Register} <- Env#env.vars ], %% DBG_LOC is added before the function body to make it possible to break
%% at the function signature
SCode = to_scode(Env, Body), SCode = to_scode(Env, Body),
{Attrs, {ArgTypes, ResType1}, SCode}. DbgSCode = dbg_contract(Env) ++ dbg_loc(Env, Attrs0) ++ dbg_scoped_vars(Env, ArgsNames, SCode),
{Attrs, {ArgTypes, ResType1}, DbgSCode}.
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').
@@ -194,20 +171,20 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
%% -- Environment functions -- %% -- Environment functions --
init_env(ChildContracts, ContractName, FunNames, Name, Args, SavedFreshNames, 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 }. saved_fresh_names = SavedFreshNames,
debug_info = proplists:get_value(debug_info, Options, false) }.
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) ->
@@ -234,7 +211,7 @@ serialize_contract_code(Env, C) ->
Options = Env#env.options, Options = Env#env.options,
SavedFreshNames = Env#env.saved_fresh_names, SavedFreshNames = Env#env.saved_fresh_names,
FCode = maps:get(C, Env#env.child_contracts), FCode = maps:get(C, Env#env.child_contracts),
{FateCode, _} = compile1(Env#env.child_contracts, FCode, SavedFreshNames, Options), FateCode = compile(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, ""),
@@ -268,44 +245,44 @@ lit_to_fate(Env, L) ->
term_to_fate(E) -> term_to_fate(#env{}, #{}, E). term_to_fate(E) -> term_to_fate(#env{}, #{}, E).
term_to_fate(GlobEnv, E) -> term_to_fate(GlobEnv, #{}, E). term_to_fate(GlobEnv, E) -> term_to_fate(GlobEnv, #{}, E).
term_to_fate(GlobEnv, _Env, {lit, L}) -> term_to_fate(GlobEnv, _Env, {lit, _, L}) ->
lit_to_fate(GlobEnv, L); lit_to_fate(GlobEnv, L);
%% negative literals are parsed as 0 - N %% negative literals are parsed as 0 - N
term_to_fate(_GlobEnv, _Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) -> term_to_fate(_GlobEnv, _Env, {op, _, '-', [{lit, _, {int, 0}}, {lit, _, {int, N}}]}) ->
aeb_fate_data:make_integer(-N); aeb_fate_data:make_integer(-N);
term_to_fate(_GlobEnv, _Env, nil) -> term_to_fate(_GlobEnv, _Env, {nil, _}) ->
aeb_fate_data:make_list([]); aeb_fate_data:make_list([]);
term_to_fate(GlobEnv, Env, {op, '::', [Hd, Tl]}) -> term_to_fate(GlobEnv, Env, {op, _, '::', [Hd, Tl]}) ->
%% The Tl will translate into a list, because FATE lists are just lists %% The Tl will translate into a list, because FATE lists are just lists
[term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)]; [term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)];
term_to_fate(GlobEnv, Env, {tuple, As}) -> term_to_fate(GlobEnv, Env, {tuple, _, As}) ->
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As])); aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As]));
term_to_fate(GlobEnv, Env, {con, Ar, I, As}) -> term_to_fate(GlobEnv, Env, {con, _, Ar, I, As}) ->
FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ], FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ],
aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs)); aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs));
term_to_fate(_GlobEnv, _Env, {builtin, bits_all, []}) -> term_to_fate(_GlobEnv, _Env, {builtin, _, bits_all, []}) ->
aeb_fate_data:make_bits(-1); aeb_fate_data:make_bits(-1);
term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) -> term_to_fate(_GlobEnv, _Env, {builtin, _, bits_none, []}) ->
aeb_fate_data:make_bits(0); aeb_fate_data:make_bits(0);
term_to_fate(GlobEnv, _Env, {op, bits_set, [B, I]}) -> term_to_fate(GlobEnv, _Env, {op, _, bits_set, [B, I]}) ->
{bits, N} = term_to_fate(GlobEnv, B), {bits, N} = term_to_fate(GlobEnv, B),
J = term_to_fate(GlobEnv, I), J = term_to_fate(GlobEnv, I),
{bits, N bor (1 bsl J)}; {bits, N bor (1 bsl J)};
term_to_fate(GlobEnv, _Env, {op, bits_clear, [B, I]}) -> term_to_fate(GlobEnv, _Env, {op, _, bits_clear, [B, I]}) ->
{bits, N} = term_to_fate(GlobEnv, B), {bits, N} = term_to_fate(GlobEnv, B),
J = term_to_fate(GlobEnv, I), J = term_to_fate(GlobEnv, I),
{bits, N band bnot (1 bsl J)}; {bits, N band bnot (1 bsl J)};
term_to_fate(GlobEnv, Env, {'let', X, E, Body}) -> term_to_fate(GlobEnv, Env, {'let', _, X, E, Body}) ->
Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) }, Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) },
term_to_fate(GlobEnv, Env1, Body); term_to_fate(GlobEnv, Env1, Body);
term_to_fate(_GlobEnv, Env, {var, X}) -> term_to_fate(_GlobEnv, Env, {var, _, X}) ->
case maps:get(X, Env, undefined) of case maps:get(X, Env, undefined) of
undefined -> throw(not_a_fate_value); undefined -> throw(not_a_fate_value);
V -> V V -> V
end; end;
term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) -> term_to_fate(_GlobEnv, _Env, {builtin, _, map_empty, []}) ->
aeb_fate_data:make_map(#{}); aeb_fate_data:make_map(#{});
term_to_fate(GlobEnv, Env, {op, map_set, [M, K, V]}) -> term_to_fate(GlobEnv, Env, {op, _, map_set, [M, K, V]}) ->
Map = term_to_fate(GlobEnv, Env, M), Map = term_to_fate(GlobEnv, Env, M),
Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)}; Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)};
term_to_fate(_GlobEnv, _Env, _) -> term_to_fate(_GlobEnv, _Env, _) ->
@@ -313,52 +290,59 @@ term_to_fate(_GlobEnv, _Env, _) ->
to_scode(Env, T) -> to_scode(Env, T) ->
try term_to_fate(Env, T) of try term_to_fate(Env, T) of
V -> [push(?i(V))] V ->
FAnn = element(2, T),
[dbg_loc(Env, FAnn), push(?i(V))]
catch throw:not_a_fate_value -> catch throw:not_a_fate_value ->
to_scode1(Env, T) to_scode1(Env, T)
end. end.
to_scode1(Env, {lit, L}) -> to_scode1(Env, {lit, Ann, L}) ->
[push(?i(lit_to_fate(Env, L)))]; [ dbg_loc(Env, Ann), push(?i(lit_to_fate(Env, L))) ];
to_scode1(_Env, nil) -> to_scode1(Env, {nil, Ann}) ->
[aeb_fate_ops:nil(?a)]; [ dbg_loc(Env, Ann), aeb_fate_ops:nil(?a) ];
to_scode1(Env, {var, X}) -> to_scode1(Env, {var, Ann, X}) ->
[push(lookup_var(Env, X))]; [ dbg_loc(Env, Ann), push(lookup_var(Env, X)) ];
to_scode1(Env, {con, Ar, I, As}) -> to_scode1(Env, {con, Ann, Ar, I, As}) ->
N = length(As), N = length(As),
[[to_scode(notail(Env), A) || A <- As], [ dbg_loc(Env, Ann),
aeb_fate_ops:variant(?a, ?i(Ar), ?i(I), ?i(N))]; [to_scode(notail(Env), A) || A <- As],
aeb_fate_ops:variant(?a, ?i(Ar), ?i(I), ?i(N)) ];
to_scode1(Env, {tuple, As}) -> to_scode1(Env, {tuple, Ann, As}) ->
N = length(As), N = length(As),
[[ to_scode(notail(Env), A) || A <- As ], [ dbg_loc(Env, Ann),
tuple(N)]; [ to_scode(notail(Env), A) || A <- As ],
tuple(N) ];
to_scode1(Env, {proj, E, I}) -> to_scode1(Env, {proj, Ann, E, I}) ->
[to_scode(notail(Env), E), [ dbg_loc(Env, Ann),
aeb_fate_ops:element_op(?a, ?i(I), ?a)]; to_scode(notail(Env), E),
aeb_fate_ops:element_op(?a, ?i(I), ?a) ];
to_scode1(Env, {set_proj, R, I, E}) -> to_scode1(Env, {set_proj, Ann, R, I, E}) ->
[to_scode(notail(Env), E), [ dbg_loc(Env, Ann),
to_scode(notail(Env), R), to_scode(notail(Env), E),
aeb_fate_ops:setelement(?a, ?i(I), ?a, ?a)]; to_scode(notail(Env), R),
aeb_fate_ops:setelement(?a, ?i(I), ?a, ?a) ];
to_scode1(Env, {op, Op, Args}) -> to_scode1(Env, {op, Ann, Op, Args}) ->
call_to_scode(Env, op_to_scode(Op), Args); [ dbg_loc(Env, Ann) | call_to_scode(Env, op_to_scode(Op), Args) ];
to_scode1(Env, {'let', X, {var, Y}, Body}) -> to_scode1(Env, {'let', Ann, X, {var, _, Y}, Body}) ->
Env1 = bind_var(X, lookup_var(Env, Y), Env), Env1 = bind_var(X, lookup_var(Env, Y), Env),
to_scode(Env1, Body); [ dbg_loc(Env, Ann) | dbg_scoped_vars(Env1, [X], to_scode(Env1, Body)) ];
to_scode1(Env, {'let', X, Expr, Body}) -> to_scode1(Env, {'let', Ann, X, Expr, Body}) ->
{I, Env1} = bind_local(X, Env), {I, Env1} = bind_local(X, Env),
[ to_scode(notail(Env), Expr), SCode = [ to_scode(notail(Env), Expr),
aeb_fate_ops:store({var, I}, {stack, 0}), aeb_fate_ops:store({var, I}, {stack, 0}),
to_scode(Env1, Body) ]; to_scode(Env1, Body) ],
[ dbg_loc(Env, Ann) | dbg_scoped_vars(Env1, [X], SCode) ];
to_scode1(Env = #env{ current_function = Fun, tailpos = true }, {def, Fun, Args}) -> to_scode1(Env = #env{ current_function = Fun, tailpos = true, debug_info = false }, {def, Ann, Fun, Args}) ->
%% Tail-call to current function, f(e0..en). Compile to %% Tail-call to current function, f(e0..en). Compile to
%% [ let xi = ei ] %% [ let xi = ei ]
%% [ STORE argi xi ] %% [ STORE argi xi ]
@@ -371,61 +355,62 @@ to_scode1(Env = #env{ current_function = Fun, tailpos = true }, {def, Fun, Args}
aeb_fate_ops:store({var, I}, ?a)], aeb_fate_ops:store({var, I}, ?a)],
{[I | Is], Acc1, Env2} {[I | Is], Acc1, Env2}
end, {[], [], Env}, Args), end, {[], [], Env}, Args),
[ Code, [ dbg_loc(Env, Ann),
Code,
[ aeb_fate_ops:store({arg, I}, {var, J}) [ aeb_fate_ops:store({arg, I}, {var, J})
|| {I, J} <- lists:zip(lists:seq(0, length(Vars) - 1), || {I, J} <- lists:zip(lists:seq(0, length(Vars) - 1),
lists:reverse(Vars)) ], lists:reverse(Vars)) ],
loop ]; loop ];
to_scode1(Env, {def, Fun, Args}) -> to_scode1(Env, {def, Ann, Fun, Args}) ->
FName = make_function_id(Fun), FName = make_function_id(Fun),
Lbl = aeb_fate_data:make_string(FName), Lbl = aeb_fate_data:make_string(FName),
call_to_scode(Env, local_call(Env, ?i(Lbl)), Args); [ dbg_loc(Env, Ann) | call_to_scode(Env, local_call(Env, ?i(Lbl)), Args) ];
to_scode1(Env, {funcall, Fun, Args}) -> to_scode1(Env, {funcall, Ann, Fun, Args}) ->
call_to_scode(Env, [to_scode(Env, Fun), local_call(Env, ?a)], Args); [ dbg_loc(Env, Ann) | call_to_scode(Env, [to_scode(Env, Fun), local_call(Env, ?a)], Args) ];
to_scode1(Env, {builtin, B, Args}) -> to_scode1(Env, {builtin, Ann, B, Args}) ->
builtin_to_scode(Env, B, Args); [ dbg_loc(Env, Ann) | builtin_to_scode(Env, B, Args) ];
to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) -> to_scode1(Env, {remote, Ann, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) ->
Lbl = make_function_id(Fun), Lbl = make_function_id(Fun),
{ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT), {ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT),
ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})), ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})),
RetType = ?i(aeb_fate_data:make_typerep(RetType0)), RetType = ?i(aeb_fate_data:make_typerep(RetType0)),
case Protected of SCode = case Protected of
{lit, {bool, false}} -> {lit, _, {bool, false}} ->
case Gas of case Gas of
{builtin, call_gas_left, _} -> {builtin, _, call_gas_left, _} ->
Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a), Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a),
call_to_scode(Env, Call, [Ct, Value | Args]); call_to_scode(Env, Call, [Ct, Value | Args]);
_ -> _ ->
Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a), Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a),
call_to_scode(Env, Call, [Ct, Value, Gas | Args]) call_to_scode(Env, Call, [Ct, Value, Gas | Args])
end; end;
{lit, {bool, true}} -> {lit, _, {bool, true}} ->
Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?i(true)), Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?i(true)),
call_to_scode(Env, Call, [Ct, Value, Gas | Args]); call_to_scode(Env, Call, [Ct, Value, Gas | Args]);
_ -> _ ->
Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?a), Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?a),
call_to_scode(Env, Call, [Ct, Value, Gas, Protected | Args]) call_to_scode(Env, Call, [Ct, Value, Gas, Protected | Args])
end; end,
[ dbg_loc(Env, Ann) | SCode ];
to_scode1(_Env, {get_state, Reg}) -> to_scode1(Env, {get_state, Ann, Reg}) ->
[push(?s(Reg))]; [ dbg_loc(Env, Ann), push(?s(Reg)) ];
to_scode1(Env, {set_state, Reg, Val}) -> to_scode1(Env, {set_state, Ann, Reg, Val}) ->
call_to_scode(Env, [{'STORE', ?s(Reg), ?a}, [ dbg_loc(Env, Ann) | call_to_scode(Env, [{'STORE', ?s(Reg), ?a}, tuple(0)], [Val]) ];
tuple(0)], [Val]);
to_scode1(Env, {closure, Fun, FVs}) -> to_scode1(Env, {closure, Ann, Fun, FVs}) ->
to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]}); [ to_scode(Env, {tuple, Ann, [{lit, Ann, {string, make_function_id(Fun)}}, FVs]}) ];
to_scode1(Env, {switch, Case}) -> to_scode1(Env, {switch, Ann, Case}) ->
split_to_scode(Env, Case). [ dbg_loc(Env, Ann) | split_to_scode(Env, Case) ].
local_call( Env, Fun) when Env#env.tailpos -> aeb_fate_ops:call_t(Fun); local_call( Env = #env{debug_info = false}, Fun) when Env#env.tailpos -> aeb_fate_ops:call_t(Fun);
local_call(_Env, Fun) -> aeb_fate_ops:call(Fun). local_call(_Env, Fun) -> aeb_fate_ops:call(Fun).
split_to_scode(Env, {nosplit, Expr}) -> split_to_scode(Env, {nosplit, Renames, Expr}) ->
[switch_body, to_scode(Env, Expr)]; [switch_body, dbg_scoped_vars(Env, Renames, to_scode(Env, Expr))];
split_to_scode(Env, {split, {tuple, _}, X, Alts}) -> split_to_scode(Env, {split, {tuple, _}, X, Alts}) ->
{Def, Alts1} = catchall_to_scode(Env, X, Alts), {Def, Alts1} = catchall_to_scode(Env, X, Alts),
Arg = lookup_var(Env, X), Arg = lookup_var(Env, X),
@@ -649,7 +634,7 @@ builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) ->
builtin_to_scode(Env, chain_clone, builtin_to_scode(Env, chain_clone,
[InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) -> [InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) ->
case GasCap of case GasCap of
{builtin, call_gas_left, _} -> {builtin, _, call_gas_left, _} ->
call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a),
[Contract, InitArgsT, Value, Prot | InitArgs] [Contract, InitArgsT, Value, Prot | InitArgs]
); );
@@ -751,6 +736,77 @@ push(A) -> {'STORE', ?a, A}.
tuple(0) -> push(?i({tuple, {}})); tuple(0) -> push(?i({tuple, {}}));
tuple(N) -> aeb_fate_ops:tuple(?a, N). tuple(N) -> aeb_fate_ops:tuple(?a, N).
%% -- Debug info functions --
dbg_contract(#env{debug_info = false}) ->
[];
dbg_contract(#env{contract = Contract}) ->
[{'DBG_CONTRACT', {immediate, Contract}}].
dbg_loc(#env{debug_info = false}, _) ->
[];
dbg_loc(_Env, Ann) ->
File = case proplists:get_value(file, Ann, no_file) of
no_file -> "";
F -> F
end,
Line = proplists:get_value(line, Ann, undefined),
case Line of
undefined -> [];
_ -> [{'DBG_LOC', {immediate, File}, {immediate, Line}}]
end.
dbg_scoped_vars(#env{debug_info = false}, _, SCode) ->
SCode;
dbg_scoped_vars(_Env, [], SCode) ->
SCode;
dbg_scoped_vars(Env, [{SavedVarName, Var} | Rest], SCode) ->
dbg_scoped_vars(Env, Rest, dbg_scoped_var(Env, SavedVarName, Var, SCode));
dbg_scoped_vars(Env = #env{saved_fresh_names = SavedFreshNames}, [Var | Rest], SCode) ->
SavedVarName = maps:get(Var, SavedFreshNames, Var),
dbg_scoped_vars(Env, Rest, dbg_scoped_var(Env, SavedVarName, Var, SCode)).
dbg_scoped_var(Env, SavedVarName, Var, SCode) ->
case SavedVarName == "_" orelse is_fresh_name(SavedVarName) of
true ->
SCode;
false ->
Register = lookup_var(Env, Var),
Def = [{'DBG_DEF', {immediate, SavedVarName}, Register}],
Undef = [{'DBG_UNDEF', {immediate, SavedVarName}, Register}],
Def ++ dbg_undef(Undef, SCode)
end.
is_fresh_name([$% | _]) ->
true;
is_fresh_name(_) ->
false.
dbg_undef(_Undef, missing) ->
missing;
dbg_undef(Undef, loop) ->
[Undef, loop];
dbg_undef(Undef, switch_body) ->
[switch_body, Undef];
dbg_undef(Undef, {switch, Arg, Type, Alts, Catch}) ->
NewAlts = [ dbg_undef(Undef, Alt) || Alt <- Alts ],
NewCatch = dbg_undef(Undef, Catch),
NewSwitch = {switch, Arg, Type, NewAlts, NewCatch},
NewSwitch;
dbg_undef(Undef, SCode) when is_list(SCode) ->
lists:droplast(SCode) ++ [dbg_undef(Undef, lists:last(SCode))];
dbg_undef(Undef, SCode) when is_tuple(SCode); is_atom(SCode) ->
[Mnemonic | _] =
case is_tuple(SCode) of
true -> tuple_to_list(SCode);
false -> [SCode]
end,
Op = aeb_fate_opcodes:m_to_op(Mnemonic),
case aeb_fate_opcodes:end_bb(Op) of
true -> [Undef, SCode];
false -> [SCode, Undef]
end.
%% -- Phase II --------------------------------------------------------------- %% -- Phase II ---------------------------------------------------------------
%% Optimize %% Optimize
@@ -886,6 +942,10 @@ attributes(I) ->
loop -> Impure(pc, []); loop -> Impure(pc, []);
switch_body -> Pure(none, []); switch_body -> Pure(none, []);
'RETURN' -> Impure(pc, []); 'RETURN' -> Impure(pc, []);
{'DBG_LOC', _, _} -> Impure(none, []);
{'DBG_DEF', _, _} -> Impure(none, []);
{'DBG_UNDEF', _, _} -> Impure(none, []);
{'DBG_CONTRACT', _} -> Impure(none, []);
{'RETURNR', A} -> Impure(pc, A); {'RETURNR', A} -> Impure(pc, A);
{'CALL', A} -> Impure(?a, [A]); {'CALL', A} -> Impure(?a, [A]);
{'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]); {'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
@@ -1605,7 +1665,23 @@ bb(_Name, Code) ->
Blocks = lists:flatmap(fun split_calls/1, Blocks1), Blocks = lists:flatmap(fun split_calls/1, Blocks1),
Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(Blocks) ]), Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(Blocks) ]),
BBs = [ set_labels(Labels, B) || B <- Blocks ], BBs = [ set_labels(Labels, B) || B <- Blocks ],
maps:from_list(BBs). maps:from_list(dbg_loc_filter(BBs)).
%% Filter DBG_LOC instructions to keep one instruction per line
dbg_loc_filter(BBs) ->
dbg_loc_filter(BBs, [], [], sets:new()).
dbg_loc_filter([], _, AllBlocks, _) ->
lists:reverse(AllBlocks);
dbg_loc_filter([{I, []} | Rest], AllOps, AllBlocks, DbgLocs) ->
dbg_loc_filter(Rest, [], [{I, lists:reverse(AllOps)} | AllBlocks], DbgLocs);
dbg_loc_filter([{I, [Op = {'DBG_LOC', _, _} | Ops]} | Rest], AllOps, AllBlocks, DbgLocs) ->
case sets:is_element(Op, DbgLocs) of
true -> dbg_loc_filter([{I, Ops} | Rest], AllOps, AllBlocks, DbgLocs);
false -> dbg_loc_filter([{I, Ops} | Rest], [Op | AllOps], AllBlocks, sets:add_element(Op, DbgLocs))
end;
dbg_loc_filter([{I, [Op | Ops]} | Rest], AllOps, AllBlocks, DbgLocs) ->
dbg_loc_filter([{I, Ops} | Rest], [Op | AllOps], AllBlocks, DbgLocs).
%% -- Break up scode into basic blocks -- %% -- Break up scode into basic blocks --
+3 -2
View File
@@ -10,7 +10,7 @@
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]). -export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]).
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). -export_type([ann_file/0, 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, fundecl/0]). -export_type([decl/0, letbind/0, typedef/0, pragma/0, fundecl/0]).
@@ -24,8 +24,9 @@
-type ann_col() :: integer(). -type ann_col() :: integer().
-type ann_origin() :: system | user. -type ann_origin() :: system | user.
-type ann_format() :: '?:' | hex | infix | prefix | elif. -type ann_format() :: '?:' | hex | infix | prefix | elif.
-type ann_file() :: string() | no_file.
-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} -type ann() :: [ {file, ann_file()} | {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
| stateful | private | payable | main | interface | entrypoint]. | stateful | private | payable | main | interface | entrypoint].
-type name() :: string(). -type name() :: string().
+4 -3
View File
@@ -31,11 +31,13 @@
| aeso_syntax:field(aeso_syntax:expr()) | aeso_syntax:field(aeso_syntax:expr())
| aeso_syntax:stmt(). | aeso_syntax:stmt().
fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
ExprKind = if K == bind_expr -> bind_expr; true -> expr end,
TypeKind = if K == bind_type -> bind_type; true -> type end,
Sum = fun(Xs) -> lists:foldl(Plus, Zero, Xs) end, Sum = fun(Xs) -> lists:foldl(Plus, Zero, Xs) end,
Same = fun(A) -> fold(Alg, Fun, K, A) end, Same = fun(A) -> fold(Alg, Fun, K, A) end,
Decl = fun(D) -> fold(Alg, Fun, decl, D) end, Decl = fun(D) -> fold(Alg, Fun, decl, D) end,
Type = fun(T) -> fold(Alg, Fun, type, T) end, Type = fun(T) -> fold(Alg, Fun, TypeKind, T) end,
Expr = fun(E) -> fold(Alg, Fun, expr, E) end, Expr = fun(E) -> fold(Alg, Fun, ExprKind, E) end,
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end, BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end, BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X), Top = Fun(K, X),
@@ -155,4 +157,3 @@ used(D) ->
(_, _) -> #{} (_, _) -> #{}
end, decl, D)), end, decl, D)),
lists:filter(NotBound, Xs). lists:filter(NotBound, Xs).
+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.1.0"}, {vsn, "7.2.1"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
+84 -10
View File
@@ -69,6 +69,7 @@ simple_compile_test_() ->
[ {"Testing warning messages", [ {"Testing warning messages",
fun() -> fun() ->
#{ warnings := Warnings } = compile("warnings", [warn_all]), #{ warnings := Warnings } = compile("warnings", [warn_all]),
#{ warnings := [] } = compile("warning_unused_include_no_include", [warn_all]),
check_warnings(warnings(), Warnings) check_warnings(warnings(), Warnings)
end} ] ++ end} ] ++
[]. [].
@@ -221,6 +222,7 @@ compilable_contracts() ->
"unapplied_contract_call", "unapplied_contract_call",
"unapplied_named_arg_builtin", "unapplied_named_arg_builtin",
"resolve_field_constraint_by_arity", "resolve_field_constraint_by_arity",
"toplevel_constants",
"test" % Custom general-purpose test file. Keep it last on the list. "test" % Custom general-purpose test file. Keep it last on the list.
]. ].
@@ -285,7 +287,11 @@ warnings() ->
<<?PosW(48, 5) <<?PosW(48, 5)
"Unused return value.">>, "Unused return value.">>,
<<?PosW(60, 5) <<?PosW(60, 5)
"The function `dec` is defined but never used.">> "The function `dec` is defined but never used.">>,
<<?PosW(73, 9)
"The definition of `const` shadows an older definition at line 70, column 3.">>,
<<?PosW(84, 7)
"The constant `c` is defined but never used.">>
]). ]).
failing_contracts() -> failing_contracts() ->
@@ -657,10 +663,6 @@ failing_contracts() ->
[<<?Pos(5, 28) [<<?Pos(5, 28)
"Invalid call to contract entrypoint `Foo.foo`.\n" "Invalid call to contract entrypoint `Foo.foo`.\n"
"It must be called as `c.foo` for some `c : Foo`.">>]) "It must be called as `c.foo` for some `c : Foo`.">>])
, ?TYPE_ERROR(toplevel_let,
[<<?Pos(2, 7)
"Toplevel \"let\" definitions are not supported. "
"Value `this_is_illegal` could be replaced by 0-argument function.">>])
, ?TYPE_ERROR(empty_typedecl, , ?TYPE_ERROR(empty_typedecl,
[<<?Pos(2, 8) [<<?Pos(2, 8)
"Empty type declarations are not supported. " "Empty type declarations are not supported. "
@@ -858,17 +860,21 @@ failing_contracts() ->
<<?Pos(48, 5) <<?Pos(48, 5)
"Unused return value.">>, "Unused return value.">>,
<<?Pos(60, 5) <<?Pos(60, 5)
"The function `dec` is defined but never used.">> "The function `dec` is defined but never used.">>,
<<?Pos(73, 9)
"The definition of `const` shadows an older definition at line 70, column 3.">>,
<<?Pos(84, 7)
"The constant `c` is defined but never used.">>
]) ])
, ?TYPE_ERROR(polymorphism_contract_interface_recursive, , ?TYPE_ERROR(polymorphism_contract_interface_recursive,
[<<?Pos(1,24) [<<?Pos(1,24)
"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(9,5) [<<?Pos(5,5)
"Duplicate definitions of `f` at\n" "Cannot unify `char` and `int`\n"
" - line 8, column 5\n" "when implementing the entrypoint `f` from the interface `I1`">>
" - line 9, column 5">>]) ])
, ?TYPE_ERROR(polymorphism_contract_missing_implementation, , ?TYPE_ERROR(polymorphism_contract_missing_implementation,
[<<?Pos(4,20) [<<?Pos(4,20)
"Unimplemented entrypoint `f` from the interface `I1` in the contract `I2`">> "Unimplemented entrypoint `f` from the interface `I1` in the contract `I2`">>
@@ -1192,6 +1198,74 @@ failing_contracts() ->
<<?Pos(13,20) <<?Pos(13,20)
"Found a hole of type `'a`">> "Found a hole of type `'a`">>
]) ])
, ?TYPE_ERROR(toplevel_constants_contract_as_namespace,
[<<?Pos(5,13)
"Invalid use of the contract constant `G.const`.\n"
"Toplevel contract constants can only be used in the contracts where they are defined.">>,
<<?Pos(10,11)
"Record type `G` does not have field `const`">>,
<<?Pos(10,11)
"Unbound field const">>,
<<?Pos(11,11)
"Record type `G` does not have field `const`">>,
<<?Pos(11,11)
"Unbound field const">>
])
, ?TYPE_ERROR(toplevel_constants_cycles,
[<<?Pos(2,21)
"Unbound variable `selfcycle`">>,
<<?Pos(4,5)
"Mutual recursion detected between the constants\n"
" - `cycle1` at line 4, column 5\n"
" - `cycle2` at line 5, column 5\n"
" - `cycle3` at line 6, column 5">>
])
, ?TYPE_ERROR(toplevel_constants_in_interface,
[<<?Pos(2,10)
"The name of the compile-time constant cannot have pattern matching">>,
<<?Pos(3,5)
"Cannot define toplevel constants inside a contract interface">>,
<<?Pos(4,5)
"Cannot define toplevel constants inside a contract interface">>
])
, ?TYPE_ERROR(toplevel_constants_invalid_expr,
[<<?Pos(10,9)
"Invalid expression in the definition of the constant `c01`\n"
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
<<?Pos(11,9)
"Invalid expression in the definition of the constant `c02`\n"
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
<<?Pos(12,9)
"Invalid expression in the definition of the constant `c03`\n"
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
<<?Pos(13,9)
"Invalid expression in the definition of the constant `c04`\n"
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
<<?Pos(14,9)
"Invalid expression in the definition of the constant `c05`\n"
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
<<?Pos(17,9)
"Invalid expression in the definition of the constant `c07`\n"
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
<<?Pos(18,9)
"Invalid expression in the definition of the constant `c08`\n"
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
<<?Pos(19,9)
"Invalid expression in the definition of the constant `c09`\n"
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
<<?Pos(20,9)
"Invalid expression in the definition of the constant `c10`\n"
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
<<?Pos(21,9)
"Invalid expression in the definition of the constant `c11`\n"
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>
])
, ?TYPE_ERROR(toplevel_constants_invalid_id,
[<<?Pos(2,9)
"The name of the compile-time constant cannot have pattern matching">>,
<<?Pos(3,9)
"The name of the compile-time constant cannot have pattern matching">>
])
]. ].
validation_test_() -> validation_test_() ->
+3
View File
@@ -6,6 +6,7 @@
namespace Ns = namespace Ns =
datatype d('a) = D | S(int) | M('a, list('a), int) datatype d('a) = D | S(int) | M('a, list('a), int)
private function fff() = 123 private function fff() = 123
let const = 1
stateful entrypoint stateful entrypoint
f (1, x) = (_) => x f (1, x) = (_) => x
@@ -33,6 +34,8 @@ contract AllSyntax =
type state = shakespeare(int) type state = shakespeare(int)
let cc = "str"
entrypoint init() = { entrypoint init() = {
johann = 1000, johann = 1000,
wolfgang = -10, wolfgang = -10,
+64
View File
@@ -0,0 +1,64 @@
namespace N0 =
let nsconst = 1
namespace N =
let nsconst = N0.nsconst
contract C =
datatype event = EventX(int, string)
record account = { name : string,
balance : int }
let c01 = 2425
let c02 = -5
let c03 = ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
let c04 = true
let c05 = Bits.none
let c06 = #fedcba9876543210
let c07 = "str"
let c08 = [1, 2, 3]
let c09 = [(true, 24), (false, 19), (false, -42)]
let c10 = (42, "Foo", true)
let c11 = { name = "str", balance = 100000000 }
let c12 = {["foo"] = 19, ["bar"] = 42}
let c13 = Some(42)
let c14 = 11 : int
let c15 = EventX(0, "Hello")
let c16 = #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
let c17 = #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
let c18 = RelativeTTL(50)
let c19 = ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
let c20 = oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
let c21 = ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ : C
let c22 = N.nsconst
let c23 = c01
let c24 = c11.name
let c25 : int = 1
entrypoint f01() = c01
entrypoint f02() = c02
entrypoint f03() = c03
entrypoint f04() = c04
entrypoint f05() = c05
entrypoint f06() = c06
entrypoint f07() = c07
entrypoint f08() = c08
entrypoint f09() = c09
entrypoint f10() = c10
entrypoint f11() = c11
entrypoint f12() = c12
entrypoint f13() = c13
entrypoint f14() = c14
entrypoint f15() = c15
entrypoint f16() = c16
entrypoint f17() = c17
entrypoint f18() = c18
entrypoint f19() = c19
entrypoint f20() = c20
entrypoint f21() = c21
entrypoint f22() = c22
entrypoint f23() = c23
entrypoint f24() = c24
entrypoint f25() = c25
entrypoint fqual() = C.c01
@@ -0,0 +1,11 @@
contract G =
let const = 1
main contract C =
let c = G.const
stateful entrypoint f() =
let g = Chain.create() : G
g.const
g.const()
@@ -0,0 +1,6 @@
contract C =
let selfcycle = selfcycle
let cycle1 = cycle2
let cycle2 = cycle3
let cycle3 = cycle1
@@ -0,0 +1,7 @@
contract interface I =
let (x::y::_) = [1,2,3]
let c = 10
let d = 10
contract C =
entrypoint init() = ()
@@ -0,0 +1,21 @@
main contract C =
record account = { name : string,
balance : int }
let one = 1
let opt = Some(5)
let acc = { name = "str", balance = 100000 }
let mpp = {["foo"] = 19, ["bar"] = 42}
let c01 = [x | x <- [1,2,3,4,5]]
let c02 = [x + k | x <- [1,2,3,4,5], let k = x*x]
let c03 = [x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]
let c04 = if (one > 2) 3 else 4
let c05 = switch (opt)
Some(x) => x
None => 2
let c07 = acc{ balance = one }
let c08 = mpp["foo"]
let c09 = mpp["non" = 10]
let c10 = mpp{["foo"] = 20}
let c11 = (x) => x + 1
@@ -0,0 +1,3 @@
contract C =
let x::_ = [1,2,3,4]
let y::(p = z::_) = [1,2,3,4]
-3
View File
@@ -1,3 +0,0 @@
contract C =
let this_is_illegal = 2/0
entrypoint this_is_legal() = 2/0
@@ -0,0 +1,5 @@
namespace N =
function nconst() = 1
main contract C =
entrypoint f() = N.nconst()
+21
View File
@@ -65,3 +65,24 @@ contract Remote =
contract C = contract C =
payable stateful entrypoint payable stateful entrypoint
call_missing_con() : int = (ct_1111111111111111111111111111112JF6Dz72 : Remote).id(value = 1, 0) call_missing_con() : int = (ct_1111111111111111111111111111112JF6Dz72 : Remote).id(value = 1, 0)
namespace ShadowingConst =
let const = 1
function f() =
let const = 2
const
namespace UnusedConstNamespace =
// No warnings should be shown even though const is not used
let const = 1
contract UnusedConstContract =
// Only `c` should show a warning because it is never used in the contract
let a = 1
let b = 2
let c = 3
entrypoint f() =
// Both normal access and qualified access should prevent the unused const warning
a + UnusedConstContract.b