Compare commits

...

15 Commits

Author SHA1 Message Date
radrow 8032bad915 changelog 2023-09-02 17:41:18 +02:00
radrow 8f950ed5c5 Name lambdas by their locations 2023-09-02 17:31:15 +02:00
Hans Svensson 9b518150c3 Fix typo in CHANGELOG 2023-08-24 13:00:42 +02:00
Hans Svensson 67948513d5 Prepare v7.3.0 (#484) 2023-08-24 10:59:42 +02:00
Hans Svensson 08fa372c24 Improve independence analysis in code optimizer (#483)
* Improve independence analysis

* Changelog updated
2023-08-24 09:43:40 +02:00
Hans Svensson 3b0ca28c8e Improve constraint solving (#480)
* Clean up constraint solving a bit

* Make unify always return true or false

* Remove unused unify_throws field from Env

* Better structure for constraint solving

* Fix formatting of if_branches error

* More cleanup
2023-08-23 09:43:49 +02: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
24 changed files with 1780 additions and 959 deletions
+30 -1
View File
@@ -7,8 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
### Changed
- Names of lifted lambdas now consist of parent function's name and their
position in the source code.
### Removed
### Fixed
- Lifted lambdas get their names assigned deterministically.
## [7.3.0]
### 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.
- Fixed a bug in the byte code optimization that incorrectly reordered dependent instructions.
## [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]
@@ -381,7 +407,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.1.0...HEAD
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.3.0...HEAD
[7.3.0]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0
[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.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
-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 `debug_info` includes information related to debugging in the compiler output. Currently this option only includes the mapping from variables to registers.
#### Options to control which compiler optimizations should run:
By default all optimizations are turned on, to disable an optimization, it should be
+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.
## 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
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
```
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
@@ -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).
##### 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
```
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
worth writing it explicitly as shown in the example.
##### clone
```
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`.
##### 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
```
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
+1
View File
@@ -104,6 +104,7 @@ Implement ::= ':' Sep1(Con, ',')
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
| 'record' Id ['(' TVar* ')'] '=' RecordType
| 'datatype' Id ['(' TVar* ')'] '=' DataType
| 'let' Id [':' Type] '=' Expr
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
| Using
+2 -3
View File
@@ -2,8 +2,7 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.2.0"}}}
, {getopt, "1.0.1"}
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.3.0"}}}
, {eblake2, "1.0.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]}
]}.
{relx, [{release, {aesophia, "7.1.0"},
{relx, [{release, {aesophia, "7.3.0"},
[aesophia, aebytecode, getopt]},
{dev_mode, true},
+3 -3
View File
@@ -1,11 +1,11 @@
{"1.2.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"2a0a397afad6b45da52572170f718194018bf33c"}},
{ref,"b38349274fc2bed98d7fe86877e6e1a2df302109"}},
0},
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
{ref,"eb68fe331bd476910394966b7f5ede7a74d37e35"}},
{ref,"177bf604b2a05e940f92cf00e96e6e269e708245"}},
1},
{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git",
@@ -16,7 +16,7 @@
{git,"https://github.com/aeternity/enacl.git",
{ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}},
2},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},1},
{<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git",
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
+400 -214
View File
@@ -124,15 +124,18 @@
-type variance() :: invariant | covariant | contravariant | bivariant.
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
-type type_info() :: {aeso_syntax:ann(), typedef()}.
-type var_info() :: {aeso_syntax:ann(), utype()}.
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
-type type_info() :: {aeso_syntax:ann(), typedef()}.
-type const_info() :: {aeso_syntax:ann(), type()}.
-type var_info() :: {aeso_syntax:ann(), utype()}.
-type fun_env() :: [{name(), fun_info()}].
-type type_env() :: [{name(), type_info()}].
-type fun_env() :: [{name(), fun_info()}].
-type type_env() :: [{name(), type_info()}].
-type const_env() :: [{name(), const_info()}].
-record(scope, { funs = [] :: fun_env()
, types = [] :: type_env()
, consts = [] :: const_env()
, access = public :: access()
, kind = namespace :: namespace | contract
, ann = [{origin, system}] :: aeso_syntax:ann()
@@ -151,7 +154,7 @@
, in_pattern = false :: boolean()
, in_guard = false :: boolean()
, stateful = false :: boolean()
, unify_throws = true :: boolean()
, current_const = none :: none | aeso_syntax:id()
, current_function = none :: none | aeso_syntax:id()
, what = top :: top | namespace | contract | contract_interface
}).
@@ -183,9 +186,13 @@ pop_scope(Env) ->
get_scope(#env{ scopes = Scopes }, Name) ->
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().
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) } }.
-spec on_scopes(env(), fun((scope()) -> scope())) -> env().
@@ -193,8 +200,8 @@ on_scopes(Env = #env{ scopes = Scopes }, Fun) ->
Env#env{ scopes = maps:map(fun(_, Scope) -> Fun(Scope) end, Scopes) }.
-spec bind_var(aeso_syntax:id(), utype(), env()) -> env().
bind_var({id, Ann, X}, T, Env = #env{ vars = Vars }) ->
when_warning(warn_shadowing, fun() -> warn_potential_shadowing(Ann, X, Vars) end),
bind_var({id, Ann, X}, T, Env) ->
when_warning(warn_shadowing, fun() -> warn_potential_shadowing(Env, Ann, X) end),
Env#env{ vars = [{X, {Ann, T}} | Env#env.vars] }.
-spec bind_vars([{aeso_syntax:id(), utype()}], env()) -> env().
@@ -247,6 +254,37 @@ bind_type(X, Ann, Def, Env) ->
Scope#scope{ types = [{X, {Ann, Def}} | Types] }
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
-spec bind_state(env()) -> env().
bind_state(Env) ->
@@ -312,11 +350,11 @@ bind_contract(Typing, {Contract, Ann, Id, _Impls, Contents}, Env)
Sys = [{origin, system}],
TypeOrFresh = fun({typed, _, _, Type}) -> Type; (_) -> fresh_uvar(Sys) end,
Fields =
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
[ {field_t, AnnF, Entrypoint, contract_call_type(aeso_syntax:set_ann(Sys, Type))}
|| {fun_decl, AnnF, Entrypoint, Type = {fun_t, _, _, _, _}} <- Contents ] ++
[ {field_t, AnnF, Entrypoint,
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,
Name =/= "init"
@@ -431,21 +469,30 @@ lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes
%% Get the scope
case maps:get(Qual, Scopes, false) of
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
type -> Types;
term -> Funs
end,
%% Look up the unqualified name
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} ->
type_error({cannot_call_init_function, Ann}),
{QName, {Ann1, Type}}; %% Return the type to avoid an extra not-in-scope error
{contract_fun, Ann1, Type} when AllowPrivate orelse QNameIsEvent ->
{QName, {Ann1, Type}};
{contract_fun, Ann1, Type} ->
type_error({contract_treated_as_namespace, Ann, QName}),
type_error({contract_treated_as_namespace_entrypoint, Ann, QName}),
{QName, {Ann1, Type}};
{Ann1, _} = E ->
%% Check that it's not private (or we can see private funs)
@@ -486,8 +533,11 @@ qname({qid, _, Xs}) -> Xs;
qname({con, _, X}) -> [X];
qname({qcon, _, Xs}) -> Xs.
-spec name(aeso_syntax:id() | aeso_syntax:con()) -> name().
name({_, _, X}) -> X.
-spec name(Named | {typed, _, Named, _}) -> name() when
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().
qid(Ann, [X]) -> {id, Ann, X};
@@ -1054,6 +1104,7 @@ infer_contract(Env0, What, Defs0, Options) ->
({fun_clauses, _, _, _, _}) -> function;
({fun_decl, _, _, _}) -> prototype;
({using, _, _, _, _}) -> using;
({letval, _, _, _}) -> constant;
(_) -> unexpected
end,
Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end,
@@ -1069,11 +1120,12 @@ infer_contract(Env0, What, Defs0, Options) ->
contract_interface -> Env1;
contract -> bind_state(Env1) %% bind state and put
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,
What =:= contract,
get_option(no_code, false) =:= false ],
Env3 = bind_funs(ProtoSigs, Env2),
Env3 = bind_funs(ProtoSigs, Env2C),
Functions = Get(function, Defs),
%% Check for duplicates in Functions (we turn it into a map below)
FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}};
@@ -1093,7 +1145,7 @@ infer_contract(Env0, What, Defs0, Options) ->
check_entrypoints(Defs1),
destroy_and_report_type_errors(Env4),
%% Add inferred types of definitions
{Env5, TypeDefs ++ Decls ++ Defs1}.
{Env5, TypeDefs ++ Decls ++ Consts ++ Defs1}.
%% Restructure blocks into multi-clause fundefs (`fun_clauses`).
-spec process_blocks([aeso_syntax:decl()]) -> [aeso_syntax:decl()].
@@ -1243,6 +1295,21 @@ opposite_variance(covariant) -> contravariant;
opposite_variance(contravariant) -> covariant;
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, []) ->
Env;
check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con, Alias, Parts} | Rest]) ->
@@ -1514,7 +1581,7 @@ check_reserved_entrypoints(Funs) ->
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) ->
Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type),
TypeSig = {type_sig, Ann, none, Named, Args, Ret},
register_implementation(Id, TypeSig),
register_implementation(Env, Id, TypeSig),
{{Name, TypeSig}, {fun_decl, Ann, Id, Type1}};
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) ->
type_error({fundecl_must_have_funtype, Ann, Id, Type}),
@@ -1522,13 +1589,16 @@ check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) ->
%% Register the function FunId as implemented by deleting it from the functions
%% to be implemented table if it is included there, or return true otherwise.
-spec register_implementation(FunId, FunSig) -> true | no_return() when
-spec register_implementation(env(), FunId, FunSig) -> true | no_return() when
FunId :: aeso_syntax:id(),
FunSig :: typesig().
register_implementation(Id, Sig) ->
register_implementation(Env, Id, Sig) ->
Name = name(Id),
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),
DeclPayable = aeso_syntax:get_ann(payable, Decl, false),
@@ -1556,7 +1626,7 @@ infer_nonrec(Env, LetFun) ->
create_constraints(),
NewLetFun = {{_, Sig}, _} = infer_letfun(Env, LetFun),
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),
Result = {TypeSig, _} = instantiate(NewLetFun),
print_typesig(TypeSig),
@@ -1586,11 +1656,11 @@ infer_letrec(Env, Defs) ->
Inferred =
[ begin
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),
Expect = typesig_to_fun_t(TypeSig),
unify(Env, Got, Expect, {check_typesig, Name, Got, Expect}),
solve_constraints(Env),
solve_all_constraints(Env),
?PRINT_TYPES("Checked ~s : ~s\n",
[Name, pp(dereference_deep(Got))]),
Res
@@ -1687,9 +1757,19 @@ lookup_name(Env = #env{ namespace = NS, current_function = CurFn }, As, Id, Opti
type_error({unbound_variable, Id}),
{Id, fresh_uvar(As)};
{QId, {_, Ty}} ->
when_warning(warn_unused_variables, fun() -> used_variable(NS, name(CurFn), QId) end),
when_warning(warn_unused_functions,
fun() -> register_function_call(NS ++ qname(CurFn), QId) end),
%% Variables and functions cannot be used when CurFn is `none`.
%% i.e. they cannot be used in toplevel constants
[ 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),
check_stateful(Env, Id, Ty),
Ty1 = case Ty of
@@ -2054,6 +2134,81 @@ infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) ->
type_error({missing_body_for_let, Attrs}),
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) ->
FunType =
case Fun of
@@ -2178,9 +2333,14 @@ infer_pattern(Env, Pattern) ->
NewPattern = infer_expr(NewEnv, Pattern),
{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),
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}) ->
NewGuards = lists:map(fun(Guard) ->
check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"})
@@ -2214,6 +2374,19 @@ infer_block(Env, Attrs, [E|Rest], BlockType) ->
when_warning(warn_unused_return_value, fun() -> potential_unused_return_value(NewE) end),
[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})
when BoolOp =:= '&&'; BoolOp =:= '||' ->
Bool = {id, As, "bool"},
@@ -2402,61 +2575,118 @@ get_constraints() ->
destroy_constraints() ->
ets_delete(constraints).
-spec solve_constraints(env()) -> ok.
solve_constraints(Env) ->
%% First look for record fields that appear in only one type definition
IsAmbiguous =
fun(#field_constraint{
record_t = RecordType,
field = Field={id, _Attrs, FieldName},
field_t = FieldType,
kind = Kind,
context = When }) ->
Arity = fun_arity(dereference_deep(FieldType)),
FieldInfos = case Arity of
none -> lookup_record_field(Env, FieldName, Kind);
_ -> lookup_record_field_arity(Env, FieldName, Arity, Kind)
end,
case FieldInfos of
[] ->
type_error({undefined_field, Field}),
false;
[#field_info{field_t = FldType, record_t = RecType}] ->
create_freshen_tvars(),
FreshFldType = freshen(FldType),
FreshRecType = freshen(RecType),
destroy_freshen_tvars(),
unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}),
unify(Env, FreshRecType, RecordType, {record_constraint, FreshRecType, RecordType, When}),
false;
_ ->
%% ambiguity--need cleverer strategy
true
end;
(_) -> true
end,
AmbiguousConstraints = lists:filter(IsAmbiguous, get_constraints()),
%% Solve all constraints by iterating until no-progress
% The two passes on AmbiguousConstraints are needed
solve_ambiguous_constraints(Env, AmbiguousConstraints ++ AmbiguousConstraints).
-spec solve_all_constraints(env()) -> ok.
solve_all_constraints(Env) ->
Constraints = [C || C <- get_constraints(), not one_shot_field_constraint(Env, C) ],
solve_constraints_top(Env, Constraints).
-spec solve_ambiguous_constraints(env(), [constraint()]) -> ok.
solve_ambiguous_constraints(Env, Constraints) ->
Unknown = solve_known_record_types(Env, Constraints),
if Unknown == [] -> ok;
length(Unknown) < length(Constraints) ->
%% progress! Keep trying.
solve_ambiguous_constraints(Env, Unknown);
solve_constraints_top(Env, Constraints) ->
UnsolvedCs = solve_constraints(Env, Constraints),
Progress = solve_unknown_record_constraints(Env, UnsolvedCs),
if length(UnsolvedCs) < length(Constraints) orelse Progress == true ->
solve_constraints_top(Env, UnsolvedCs);
true ->
case solve_unknown_record_types(Env, Unknown) of
true -> %% Progress!
solve_ambiguous_constraints(Env, Unknown);
_ -> ok %% No progress. Report errors later.
end
ok
end.
-spec solve_constraints(env(), [constraint()]) -> [constraint()].
solve_constraints(Env, Constraints) ->
[ C1 || C <- Constraints, C1 <- [dereference_deep(C)], not solve_constraint(Env, C1) ].
solve_unknown_record_constraints(Env, Constraints) ->
FieldCs = lists:filter(fun(#field_constraint{record_t = {uvar, _, _}}) -> true; (_) -> false end, Constraints),
FieldCsUVars = lists:usort([UVar || #field_constraint{record_t = UVar = {uvar, _, _}} <- FieldCs]),
FieldConstraint = fun(#field_constraint{ field = F, kind = K, context = Ctx }) -> {K, Ctx, F} end,
FieldsForUVar = fun(UVar) ->
[ FieldConstraint(FC) || FC = #field_constraint{record_t = U} <- FieldCs, U == UVar ]
end,
Solutions = [ solve_for_uvar(Env, UVar, FieldsForUVar(UVar)) || UVar <- FieldCsUVars ],
case lists:member(true, Solutions) of
true -> true;
false -> Solutions
end.
%% -- Simple constraints --
%% Returns true if solved (unified or type error)
solve_constraint(_Env, #field_constraint{record_t = {uvar, _, _}}) ->
false;
solve_constraint(Env, #field_constraint{record_t = RecordType,
field = Field = {id, _As, FieldName},
field_t = FieldType,
context = When}) ->
RecId = record_type_name(RecordType),
Attrs = aeso_syntax:get_ann(RecId),
case lookup_type(Env, RecId) of
{_, {_Ann, {Formals, {What, Fields}}}} when What =:= record_t; What =:= contract_t ->
FieldTypes = [{Name, Type} || {field_t, _, {id, _, Name}, Type} <- Fields],
case proplists:get_value(FieldName, FieldTypes) of
undefined ->
type_error({missing_field, Field, RecId});
FldType ->
solve_field_constraint(Env, FieldType, FldType, RecordType, app_t(Attrs, RecId, Formals), When)
end;
_ ->
type_error({not_a_record_type, instantiate(RecordType), When})
end,
true;
solve_constraint(Env, C = #dependent_type_constraint{}) ->
check_named_argument_constraint(Env, C);
solve_constraint(Env, C = #named_argument_constraint{}) ->
check_named_argument_constraint(Env, C);
solve_constraint(_Env, {is_bytes, _}) -> false;
solve_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) ->
A = unfold_types_in_type(Env, dereference(A0)),
B = unfold_types_in_type(Env, dereference(B0)),
C = unfold_types_in_type(Env, dereference(C0)),
case {A, B, C} of
{{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann});
{{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann});
{_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann});
_ -> false
end;
solve_constraint(_, _) -> false.
one_shot_field_constraint(Env, #field_constraint{record_t = RecordType,
field = Field = {id, _As, FieldName},
field_t = FieldType,
kind = Kind,
context = When}) ->
Arity = fun_arity(dereference_deep(FieldType)),
FieldInfos = case Arity of
none -> lookup_record_field(Env, FieldName, Kind);
_ -> lookup_record_field_arity(Env, FieldName, Arity, Kind)
end,
case FieldInfos of
[] ->
type_error({undefined_field, Field}),
true;
[#field_info{field_t = FldType, record_t = RecType}] ->
solve_field_constraint(Env, FieldType, FldType, RecordType, RecType, When),
true;
_ ->
false
end;
one_shot_field_constraint(_Env, _Constraint) ->
false.
solve_field_constraint(Env, FieldType, FldType, RecordType, RecType, When) ->
create_freshen_tvars(),
FreshFldType = freshen(FldType),
FreshRecType = freshen(RecType),
destroy_freshen_tvars(),
unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}),
unify(Env, FreshRecType, RecordType, {record_constraint, FreshRecType, RecordType, When}).
solve_then_destroy_and_report_unsolved_constraints(Env) ->
solve_constraints(Env),
solve_all_constraints(Env),
destroy_and_report_unsolved_constraints(Env).
destroy_and_report_unsolved_constraints(Env) ->
@@ -2487,21 +2717,10 @@ destroy_and_report_unsolved_constraints(Env) ->
(_) -> false
end, OtherCs5),
Unsolved = [ S || S <- [ solve_constraint(Env, dereference_deep(C)) || C <- NamedArgCs ],
S == unsolved ],
[ type_error({unsolved_named_argument_constraint, C}) || C <- Unsolved ],
Unknown = solve_known_record_types(Env, FieldCs),
if Unknown == [] -> ok;
true ->
case solve_unknown_record_types(Env, Unknown) of
true -> ok;
Errors -> [ type_error(Err) || Err <- Errors ]
end
end,
check_field_constraints(Env, FieldCs),
check_record_create_constraints(Env, CreateCs),
check_is_contract_constraints(Env, ContractCs),
check_named_args_constraints(Env, NamedArgCs),
check_bytes_constraints(Env, BytesCs),
check_aens_resolve_constraints(Env, AensResolveCs),
check_oracle_type_constraints(Env, OracleTypeCs),
@@ -2519,20 +2738,21 @@ get_oracle_type(_Fun, _Args, _Ret) -> false.
%% -- Named argument constraints --
%% If false, a type error has been emitted, so it's safe to drop the constraint.
-spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false | unsolved.
%% True if solved (unified or type error), false otherwise
-spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false.
check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) ->
unsolved;
false;
check_named_argument_constraint(Env,
C = #named_argument_constraint{ args = Args,
name = Id = {id, _, Name},
type = Type }) ->
case [ T || {named_arg_t, _, {id, _, Name1}, T, _} <- Args, Name1 == Name ] of
[] ->
type_error({bad_named_argument, Args, Id}),
false;
[T] -> unify(Env, T, Type, {check_named_arg_constraint, C}), true
end;
type_error({bad_named_argument, Args, Id});
[T] ->
unify(Env, T, Type, {check_named_arg_constraint, C})
end,
true;
check_named_argument_constraint(Env,
#dependent_type_constraint{ named_args_t = NamedArgsT0,
named_args = NamedArgs,
@@ -2549,10 +2769,11 @@ check_named_argument_constraint(Env,
ArgEnv = maps:from_list([ {Name, GetVal(Name, Default)}
|| {named_arg_t, _, {id, _, Name}, _, Default} <- NamedArgsT ]),
GenType1 = specialize_dependent_type(ArgEnv, GenType),
unify(Env, GenType1, SpecType, {check_expr, App, GenType1, SpecType}),
true;
_ -> unify(Env, GenType, SpecType, {check_expr, App, GenType, SpecType}), true
end.
unify(Env, GenType1, SpecType, {check_expr, App, GenType1, SpecType});
_ ->
unify(Env, GenType, SpecType, {check_expr, App, GenType, SpecType})
end,
true.
specialize_dependent_type(Env, Type) ->
case dereference(Type) of
@@ -2568,53 +2789,16 @@ specialize_dependent_type(Env, Type) ->
_ -> Type %% Currently no deep dependent types
end.
%% -- Bytes constraints --
check_field_constraints(Env, Constraints) ->
UnsolvedFieldCs = solve_constraints(Env, Constraints),
case solve_unknown_record_constraints(Env, UnsolvedFieldCs) of
true -> ok;
Errors -> [ type_error(Err) || Err <- Errors ]
end.
solve_constraint(_Env, #field_constraint{record_t = {uvar, _, _}}) ->
not_solved;
solve_constraint(Env, C = #field_constraint{record_t = RecType,
field = FieldName,
field_t = FieldType,
context = When}) ->
RecId = record_type_name(RecType),
Attrs = aeso_syntax:get_ann(RecId),
case lookup_type(Env, RecId) of
{_, {_Ann, {Formals, {What, Fields}}}} when What =:= record_t; What =:= contract_t ->
FieldTypes = [{Name, Type} || {field_t, _, {id, _, Name}, Type} <- Fields],
{id, _, FieldString} = FieldName,
case proplists:get_value(FieldString, FieldTypes) of
undefined ->
type_error({missing_field, FieldName, RecId}),
not_solved;
FldType ->
create_freshen_tvars(),
FreshFldType = freshen(FldType),
FreshRecType = freshen(app_t(Attrs, RecId, Formals)),
destroy_freshen_tvars(),
unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}),
unify(Env, FreshRecType, RecType, {record_constraint, FreshRecType, RecType, When}),
C
end;
_ ->
type_error({not_a_record_type, instantiate(RecType), When}),
not_solved
end;
solve_constraint(Env, C = #dependent_type_constraint{}) ->
check_named_argument_constraint(Env, C);
solve_constraint(Env, C = #named_argument_constraint{}) ->
check_named_argument_constraint(Env, C);
solve_constraint(_Env, {is_bytes, _}) -> ok;
solve_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) ->
A = unfold_types_in_type(Env, dereference(A0)),
B = unfold_types_in_type(Env, dereference(B0)),
C = unfold_types_in_type(Env, dereference(C0)),
case {A, B, C} of
{{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann});
{{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann});
{_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann});
_ -> ok
end;
solve_constraint(_, _) -> ok.
check_named_args_constraints(Env, Constraints) ->
UnsolvedNamedArgCs = solve_constraints(Env, Constraints),
[ type_error({unsolved_named_argument_constraint, C}) || C <- UnsolvedNamedArgCs ].
check_bytes_constraints(Env, Constraints) ->
InAddConstraint = [ T || {add_bytes, _, _, A, B, C} <- Constraints,
@@ -2711,30 +2895,6 @@ check_is_contract_constraints(Env, [C | Cs]) ->
end,
check_is_contract_constraints(Env, Cs).
-spec solve_unknown_record_types(env(), [field_constraint()]) -> true | [tuple()].
solve_unknown_record_types(Env, Unknown) ->
UVars = lists:usort([UVar || #field_constraint{record_t = UVar = {uvar, _, _}} <- Unknown]),
Solutions = [solve_for_uvar(Env, UVar, [{Kind, When, Field}
|| #field_constraint{record_t = U, field = Field, kind = Kind, context = When} <- Unknown,
U == UVar])
|| UVar <- UVars],
case lists:member(true, Solutions) of
true -> true;
false -> Solutions
end.
%% This will solve all kinds of constraints but will only return the
%% unsolved field constraints
-spec solve_known_record_types(env(), [constraint()]) -> [field_constraint()].
solve_known_record_types(Env, Constraints) ->
DerefConstraints = lists:map(fun(C = #field_constraint{record_t = RecordType}) ->
C#field_constraint{record_t = dereference(RecordType)};
(C) -> dereference_deep(C)
end, Constraints),
SolvedConstraints = lists:map(fun(C) -> solve_constraint(Env, dereference_deep(C)) end, DerefConstraints),
Unsolved = DerefConstraints--SolvedConstraints,
lists:filter(fun(#field_constraint{}) -> true; (_) -> false end, Unsolved).
record_type_name({app_t, _Attrs, RecId, _Args}) when ?is_type_id(RecId) ->
RecId;
record_type_name(RecId) when ?is_type_id(RecId) ->
@@ -2913,16 +3073,12 @@ unify0(Env, A, B, Variance, When) ->
unify1(_Env, {uvar, _, R}, {uvar, _, R}, _Variance, _When) ->
true;
unify1(_Env, {uvar, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) ->
type_error({unify_varargs, When});
unify1(Env, {uvar, A, R}, T, _Variance, When) ->
type_error({unify_varargs, When}),
false;
unify1(_Env, {uvar, A, R}, T, _Variance, When) ->
case occurs_check(R, T) of
true ->
if
Env#env.unify_throws ->
cannot_unify({uvar, A, R}, T, none, When);
true ->
ok
end,
cannot_unify({uvar, A, R}, T, none, When),
false;
false ->
ets_insert(type_vars, {R, T}),
@@ -2949,18 +3105,13 @@ unify1(Env, A = {con, _, NameA}, B = {con, _, NameB}, Variance, When) ->
case is_subtype(Env, NameA, NameB, Variance) of
true -> true;
false ->
if
Env#env.unify_throws ->
IsSubtype = is_subtype(Env, NameA, NameB, contravariant) orelse
is_subtype(Env, NameA, NameB, covariant),
Cxt = case IsSubtype of
true -> Variance;
false -> none
end,
cannot_unify(A, B, Cxt, When);
true ->
ok
end,
IsSubtype = is_subtype(Env, NameA, NameB, contravariant) orelse
is_subtype(Env, NameA, NameB, covariant),
Cxt = case IsSubtype of
true -> Variance;
false -> none
end,
cannot_unify(A, B, Cxt, When),
false
end;
unify1(_Env, {qid, _, Name}, {qid, _, Name}, _Variance, _When) ->
@@ -2974,9 +3125,11 @@ unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2,
unify0(Env, Else1, Else2, Variance, When);
unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) ->
type_error({unify_varargs, When});
type_error({unify_varargs, When}),
false;
unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, _Variance, When) ->
type_error({unify_varargs, When});
type_error({unify_varargs, When}),
false;
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, Variance, When)
when length(Args1) == length(Args2) ->
unify0(Env, Named1, Named2, opposite_variance(Variance), When) andalso
@@ -2998,7 +3151,7 @@ unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, Variance, When)
when length(As) == length(Bs) ->
unify0(Env, As, Bs, Variance, When);
unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, Variance, When) ->
unify1(Env, Id1, Id2, Variance, {arg_name, Id1, Id2, When}),
unify1(Env, Id1, Id2, Variance, {arg_name, Id1, Id2, When}) andalso
unify1(Env, Type1, Type2, Variance, When);
%% The grammar is a bit inconsistent about whether types without
%% arguments are represented as applications to an empty list of
@@ -3007,13 +3160,8 @@ unify1(Env, {app_t, _, T, []}, B, Variance, When) ->
unify0(Env, T, B, Variance, When);
unify1(Env, A, {app_t, _, T, []}, Variance, When) ->
unify0(Env, A, T, Variance, When);
unify1(Env, A, B, _Variance, When) ->
if
Env#env.unify_throws ->
cannot_unify(A, B, none, When);
true ->
ok
end,
unify1(_Env, A, B, _Variance, When) ->
cannot_unify(A, B, none, When),
false.
is_subtype(_Env, NameA, NameB, invariant) ->
@@ -3177,6 +3325,7 @@ all_warnings() ->
[ warn_unused_includes
, warn_unused_stateful
, warn_unused_variables
, warn_unused_constants
, warn_unused_typedefs
, warn_unused_return_value
, warn_unused_functions
@@ -3254,6 +3403,17 @@ used_variable(Namespace, Fun, [VarName]) ->
ets_match_delete(warnings, {unused_variable, '_', Namespace, Fun, VarName});
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)
potential_unused_return_value({typed, Ann, {app, _, {typed, _, _, {fun_t, _, _, _, {id, _, Type}}}, _}, _}) when Type /= "unit" ->
@@ -3299,9 +3459,11 @@ destroy_and_report_unused_functions() ->
%% Warnings (Shadowing)
warn_potential_shadowing(_, "_", _) -> ok;
warn_potential_shadowing(Ann, Name, Vars) ->
case proplists:get_value(Name, Vars, false) of
warn_potential_shadowing(_, _, "_") -> ok;
warn_potential_shadowing(Env = #env{ vars = Vars }, Ann, Name) ->
CurrentScope = get_current_scope(Env),
Consts = CurrentScope#scope.consts,
case proplists:get_value(Name, Vars ++ Consts, false) of
false -> ok;
{AnnOld, _} -> ets_insert(warnings, {shadowing, Ann, Name, AnnOld})
end.
@@ -3543,10 +3705,6 @@ mk_error({type_decl, _, {id, Pos, Name}, _}) ->
Msg = io_lib:format("Empty type declarations are not supported. Type `~s` lacks a definition",
[Name]),
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}) ->
Msg = io_lib:format("Cannot reference stateful function `~s` in the definition of non-stateful function `~s`.",
[pp(Id), pp(Fun)]),
@@ -3630,10 +3788,14 @@ mk_error({cannot_call_init_function, Ann}) ->
Msg = "The 'init' function is called exclusively by the create contract transaction "
"and cannot be called from the contract code.",
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, ".")]),
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_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}) ->
What = case element(1, Decl) of
letval -> "function or entrypoint";
@@ -3794,6 +3956,23 @@ mk_error({unpreserved_payablity, Kind, ContractCon, InterfaceCon}) ->
Msg = io_lib:format("Non-payable ~s `~s` cannot implement payable interface `~s`",
[KindStr, name(ContractCon), name(InterfaceCon)]),
mk_t_err(pos(ContractCon), Msg);
mk_error({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) ->
Msg = io_lib:format("Unknown error: ~p", [Err]),
mk_t_err(pos(0, 0), Msg).
@@ -3807,6 +3986,9 @@ mk_warning({unused_stateful, Ann, FunName}) ->
mk_warning({unused_variable, Ann, _Namespace, _Fun, VarName}) ->
Msg = io_lib:format("The variable `~s` is defined but never used.", [VarName]),
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}) ->
Msg = io_lib:format("The type `~s` is defined but never used.", [lists:last(QName)]),
aeso_warnings:new(pos(Ann), Msg);
@@ -3912,8 +4094,8 @@ pp_when({if_branches, Then, ThenType0, Else, ElseType0}) ->
Branches = [ {Then, ThenType} | [ {B, ElseType} || B <- if_branches(Else) ] ],
{pos(element(1, hd(Branches))),
io_lib:format("when comparing the types of the if-branches\n"
"~s", [ [ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", B, BType), pp_loc(B)])
|| {B, BType} <- Branches ] ])};
"~s", [string:join([ io_lib:format("~s (at ~s)", [pp_typed(" - ", B, BType), pp_loc(B)])
|| {B, BType} <- Branches ], "\n")])};
pp_when({case_pat, Pat, PatType0, ExprType0}) ->
{PatType, ExprType} = instantiate({PatType0, ExprType0}),
{pos(Pat),
@@ -3960,6 +4142,10 @@ pp_when({var_args, Ann, Fun}) ->
{pos(Ann)
, 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), ""}.
-spec pp_why_record(why_record()) -> {pos(), iolist()}.
+765 -527
View File
File diff suppressed because it is too large Load Diff
+74 -33
View File
@@ -12,6 +12,8 @@
, file/2
, from_string/2
, check_call/4
, decode_value/4
, encode_value/4
, create_calldata/3
, create_calldata/4
, version/0
@@ -117,7 +119,7 @@ from_string1(ContractString, Options) ->
, warnings := Warnings } = string_to_code(ContractString, Options),
#{ child_con_env := ChildContracts } = 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),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(),
@@ -130,13 +132,7 @@ from_string1(ContractString, Options) ->
payable => maps:get(payable, FCode),
warnings => Warnings
},
ResDbg = Res#{variables_registers => VarsRegs},
FinalRes =
case proplists:get_value(debug_info, Options, false) of
true -> ResDbg;
false -> Res
end,
{ok, maybe_generate_aci(FinalRes, FoldedTypedAst, Options)}.
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
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(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
%% First check the contract without the __call function
#{fcode := OrgFcode
, fcode_env := #{child_con_env := ChildContracts}
, ast := Ast} = string_to_code(ContractString0, Options),
{FateCode, _} = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []),
, ast := Ast} = string_to_code(Contract0, Options),
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
#{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
Contract = insert_call_function(Ast, Contract0, CallName, Call),
{ok, CallName, string_to_code(Contract, Options)}
catch
throw:{error, Errors} -> {error, Errors}
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)),
{def, _FName, Args} = Body,
%% FName is either {entrypoint, list_to_binary(FunName)} or 'init'
[ aeso_fcode_to_fate:term_to_fate(A) || A <- Args ].
Body.
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, []) ->
error(unable_to_find_unique_call_name);
@@ -224,14 +245,31 @@ first_none_match(CallName, Hashes, [Char|Chars]) ->
end.
%% Add the __call function to a contract.
-spec insert_call_function(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string().
insert_call_function(Ast, Code, Call, FunName, Args) ->
-spec insert_call_function(aeso_syntax:ast(), string(), string(),
{call, string(), [string()]} | {value, string(), string()} | {type, string()}) -> string().
insert_call_function(Ast, Code, Call, {call, FunName, Args}) ->
Ind = last_contract_indent(Ast),
lists:flatten(
[ Code,
"\n\n",
lists:duplicate(Ind, " "),
"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().
@@ -274,22 +312,25 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
try
{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
fate_data_to_sophia_value(Type0, Type, Data)
catch
throw:{error, Errors} -> {error, Errors}
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()]) ->
{ok, binary()} | {error, [aeso_errors:error()]}.
create_calldata(Code, Fun, Args) ->
+203 -122
View File
@@ -52,7 +52,8 @@
tailpos = true,
child_contracts = #{},
saved_fresh_names = #{},
options = [] }).
options = [],
debug_info = false }).
%% -- Debugging --------------------------------------------------------------
@@ -81,24 +82,16 @@ code_error(Err) ->
compile(FCode, SavedFreshNames, Options) ->
compile(#{}, FCode, SavedFreshNames, Options).
compile(ChildContracts, FCode, SavedFreshNames, Options) ->
try
compile1(ChildContracts, FCode, SavedFreshNames, Options)
after
put(variables_registers, undefined)
end.
compile1(ChildContracts, FCode, SavedFreshNames, Options) ->
#{ contract_name := ContractName,
functions := Functions } = FCode,
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options),
SFuns1 = optimize_scode(SFuns, Options),
FateCode = to_basic_blocks(SFuns1),
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
FateCode1 = case proplists:get_value(include_child_contract_symbols, Options, false) of
false -> FateCode;
true -> add_child_symbols(ChildContracts, FateCode)
end,
{FateCode1, get_variables_registers()}.
case proplists:get_value(include_child_contract_symbols, Options, false) of
false -> FateCode;
true -> add_child_symbols(ChildContracts, FateCode)
end.
make_function_id(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) ->
{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),
[ add_variables_register(Env, Arg, Register) ||
proplists:get_value(debug_info, Options, false),
{Arg, Register} <- Env#env.vars ],
ArgsNames = [ X || {X, _} <- lists:reverse(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),
{Attrs, {ArgTypes, ResType1}, SCode}.
get_variables_registers() ->
case get(variables_registers) of
undefined -> #{};
Vs -> Vs
end.
add_variables_register(Env = #env{saved_fresh_names = SavedFreshNames}, Name, Register) ->
Olds = get_variables_registers(),
RealName = maps:get(Name, SavedFreshNames, Name),
FunName =
case Env#env.current_function of
event -> "Chain.event";
{entrypoint, BinName} -> binary_to_list(BinName);
{local_fun, QualName} -> lists:last(QualName)
end,
New = {Env#env.contract, FunName, RealName},
put(variables_registers, Olds#{New => Register}).
DbgSCode = dbg_contract(Env) ++ dbg_loc(Env, Attrs0) ++ dbg_scoped_vars(Env, ArgsNames, SCode),
{Attrs, {ArgTypes, ResType1}, DbgSCode}.
-define(tvars, '$tvars').
@@ -194,20 +171,20 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
%% -- Environment functions --
init_env(ChildContracts, ContractName, FunNames, Name, Args, SavedFreshNames, Options) ->
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
contract = ContractName,
child_contracts = ChildContracts,
locals = FunNames,
current_function = Name,
options = Options,
tailpos = true,
saved_fresh_names = SavedFreshNames }.
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
contract = ContractName,
child_contracts = ChildContracts,
locals = FunNames,
current_function = Name,
options = Options,
tailpos = true,
saved_fresh_names = SavedFreshNames,
debug_info = proplists:get_value(debug_info, Options, false) }.
next_var(#env{ vars = Vars }) ->
1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]).
bind_var(Name, Var, Env = #env{ vars = Vars }) ->
proplists:get_value(debug_info, Env#env.options, false) andalso add_variables_register(Env, Name, Var),
Env#env{ vars = [{Name, Var} | Vars] }.
bind_local(Name, Env) ->
@@ -234,7 +211,7 @@ serialize_contract_code(Env, C) ->
Options = Env#env.options,
SavedFreshNames = Env#env.saved_fresh_names,
FCode = maps:get(C, Env#env.child_contracts),
{FateCode, _} = compile1(Env#env.child_contracts, FCode, SavedFreshNames, Options),
FateCode = compile(Env#env.child_contracts, FCode, SavedFreshNames, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = aeso_compiler:version(),
OriginalSourceCode = proplists:get_value(original_src, Options, ""),
@@ -268,44 +245,44 @@ lit_to_fate(Env, L) ->
term_to_fate(E) -> term_to_fate(#env{}, #{}, 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);
%% 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);
term_to_fate(_GlobEnv, _Env, nil) ->
term_to_fate(_GlobEnv, _Env, {nil, _}) ->
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
[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]));
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 ],
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);
term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) ->
term_to_fate(_GlobEnv, _Env, {builtin, _, bits_none, []}) ->
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),
J = term_to_fate(GlobEnv, I),
{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),
J = term_to_fate(GlobEnv, I),
{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) },
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
undefined -> throw(not_a_fate_value);
V -> V
end;
term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) ->
term_to_fate(_GlobEnv, _Env, {builtin, _, map_empty, []}) ->
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, K) => term_to_fate(GlobEnv, Env, V)};
term_to_fate(_GlobEnv, _Env, _) ->
@@ -313,52 +290,59 @@ term_to_fate(_GlobEnv, _Env, _) ->
to_scode(Env, T) ->
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 ->
to_scode1(Env, T)
end.
to_scode1(Env, {lit, L}) ->
[push(?i(lit_to_fate(Env, L)))];
to_scode1(Env, {lit, Ann, L}) ->
[ dbg_loc(Env, Ann), push(?i(lit_to_fate(Env, L))) ];
to_scode1(_Env, nil) ->
[aeb_fate_ops:nil(?a)];
to_scode1(Env, {nil, Ann}) ->
[ dbg_loc(Env, Ann), aeb_fate_ops:nil(?a) ];
to_scode1(Env, {var, X}) ->
[push(lookup_var(Env, X))];
to_scode1(Env, {var, Ann, 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),
[[to_scode(notail(Env), A) || A <- As],
aeb_fate_ops:variant(?a, ?i(Ar), ?i(I), ?i(N))];
[ dbg_loc(Env, Ann),
[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),
[[ to_scode(notail(Env), A) || A <- As ],
tuple(N)];
[ dbg_loc(Env, Ann),
[ to_scode(notail(Env), A) || A <- As ],
tuple(N) ];
to_scode1(Env, {proj, E, I}) ->
[to_scode(notail(Env), E),
aeb_fate_ops:element_op(?a, ?i(I), ?a)];
to_scode1(Env, {proj, Ann, E, I}) ->
[ dbg_loc(Env, Ann),
to_scode(notail(Env), E),
aeb_fate_ops:element_op(?a, ?i(I), ?a) ];
to_scode1(Env, {set_proj, R, I, E}) ->
[to_scode(notail(Env), E),
to_scode(notail(Env), R),
aeb_fate_ops:setelement(?a, ?i(I), ?a, ?a)];
to_scode1(Env, {set_proj, Ann, R, I, E}) ->
[ dbg_loc(Env, Ann),
to_scode(notail(Env), E),
to_scode(notail(Env), R),
aeb_fate_ops:setelement(?a, ?i(I), ?a, ?a) ];
to_scode1(Env, {op, Op, Args}) ->
call_to_scode(Env, op_to_scode(Op), Args);
to_scode1(Env, {op, Ann, 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),
to_scode(Env1, Body);
to_scode1(Env, {'let', X, Expr, Body}) ->
[ dbg_loc(Env, Ann) | dbg_scoped_vars(Env1, [X], to_scode(Env1, Body)) ];
to_scode1(Env, {'let', Ann, X, Expr, Body}) ->
{I, Env1} = bind_local(X, Env),
[ to_scode(notail(Env), Expr),
aeb_fate_ops:store({var, I}, {stack, 0}),
to_scode(Env1, Body) ];
SCode = [ to_scode(notail(Env), Expr),
aeb_fate_ops:store({var, I}, {stack, 0}),
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
%% [ let xi = ei ]
%% [ 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)],
{[I | Is], Acc1, Env2}
end, {[], [], Env}, Args),
[ Code,
[ dbg_loc(Env, Ann),
Code,
[ aeb_fate_ops:store({arg, I}, {var, J})
|| {I, J} <- lists:zip(lists:seq(0, length(Vars) - 1),
lists:reverse(Vars)) ],
loop ];
to_scode1(Env, {def, Fun, Args}) ->
to_scode1(Env, {def, Ann, Fun, Args}) ->
FName = make_function_id(Fun),
Lbl = aeb_fate_data:make_string(FName),
call_to_scode(Env, local_call(Env, ?i(Lbl)), Args);
to_scode1(Env, {funcall, Fun, Args}) ->
call_to_scode(Env, [to_scode(Env, Fun), local_call(Env, ?a)], Args);
[ dbg_loc(Env, Ann) | call_to_scode(Env, local_call(Env, ?i(Lbl)), Args) ];
to_scode1(Env, {funcall, Ann, Fun, Args}) ->
[ dbg_loc(Env, Ann) | call_to_scode(Env, [to_scode(Env, Fun), local_call(Env, ?a)], Args) ];
to_scode1(Env, {builtin, B, Args}) ->
builtin_to_scode(Env, B, Args);
to_scode1(Env, {builtin, Ann, 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),
{ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT),
ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})),
RetType = ?i(aeb_fate_data:make_typerep(RetType0)),
case Protected of
{lit, {bool, false}} ->
SCode = case Protected of
{lit, _, {bool, false}} ->
case Gas of
{builtin, call_gas_left, _} ->
{builtin, _, call_gas_left, _} ->
Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a),
call_to_scode(Env, Call, [Ct, Value | Args]);
_ ->
Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a),
call_to_scode(Env, Call, [Ct, Value, Gas | Args])
end;
{lit, {bool, true}} ->
{lit, _, {bool, 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 = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?a),
call_to_scode(Env, Call, [Ct, Value, Gas, Protected | Args])
end;
end,
[ dbg_loc(Env, Ann) | SCode ];
to_scode1(_Env, {get_state, Reg}) ->
[push(?s(Reg))];
to_scode1(Env, {set_state, Reg, Val}) ->
call_to_scode(Env, [{'STORE', ?s(Reg), ?a},
tuple(0)], [Val]);
to_scode1(Env, {get_state, Ann, Reg}) ->
[ dbg_loc(Env, Ann), push(?s(Reg)) ];
to_scode1(Env, {set_state, Ann, Reg, Val}) ->
[ dbg_loc(Env, Ann) | call_to_scode(Env, [{'STORE', ?s(Reg), ?a}, tuple(0)], [Val]) ];
to_scode1(Env, {closure, Fun, FVs}) ->
to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]});
to_scode1(Env, {closure, Ann, Fun, FVs}) ->
[ to_scode(Env, {tuple, Ann, [{lit, Ann, {string, make_function_id(Fun)}}, FVs]}) ];
to_scode1(Env, {switch, Case}) ->
split_to_scode(Env, Case).
to_scode1(Env, {switch, Ann, 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, Fun) -> aeb_fate_ops:call(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).
split_to_scode(Env, {nosplit, Expr}) ->
[switch_body, to_scode(Env, Expr)];
split_to_scode(Env, {nosplit, Renames, Expr}) ->
[switch_body, dbg_scoped_vars(Env, Renames, to_scode(Env, Expr))];
split_to_scode(Env, {split, {tuple, _}, X, Alts}) ->
{Def, Alts1} = catchall_to_scode(Env, X, Alts),
Arg = lookup_var(Env, X),
@@ -649,7 +634,7 @@ builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) ->
builtin_to_scode(Env, chain_clone,
[InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) ->
case GasCap of
{builtin, call_gas_left, _} ->
{builtin, _, call_gas_left, _} ->
call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a),
[Contract, InitArgsT, Value, Prot | InitArgs]
);
@@ -751,6 +736,77 @@ push(A) -> {'STORE', ?a, A}.
tuple(0) -> push(?i({tuple, {}}));
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 ---------------------------------------------------------------
%% Optimize
@@ -886,6 +942,10 @@ attributes(I) ->
loop -> Impure(pc, []);
switch_body -> Pure(none, []);
'RETURN' -> Impure(pc, []);
{'DBG_LOC', _, _} -> Impure(none, []);
{'DBG_DEF', _, _} -> Impure(none, []);
{'DBG_UNDEF', _, _} -> Impure(none, []);
{'DBG_CONTRACT', _} -> Impure(none, []);
{'RETURNR', A} -> Impure(pc, A);
{'CALL', A} -> Impure(?a, [A]);
{'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
@@ -1081,11 +1141,16 @@ independent({i, _, I}, {i, _, J}) ->
StackI = lists:member(?a, [WI | RI]),
StackJ = lists:member(?a, [WJ | RJ]),
if WI == pc; WJ == pc -> false; %% no jumps
not (PureI or PureJ) -> false; %% at least one is pure
StackI and StackJ -> false; %% cannot both use the stack
WI == WJ -> false; %% cannot write to the same register
true ->
ReadStoreI = [] /= [ x || {store, _} <- RI ],
ReadStoreJ = [] /= [ x || {store, _} <- RJ ],
if WI == pc; WJ == pc -> false; %% no jumps
not (PureI or PureJ) -> false; %% at least one is pure
StackI and StackJ -> false; %% cannot both use the stack
WI == WJ -> false; %% cannot write to the same register
ReadStoreI and not PureJ -> false; %% can't read store/state if other is impure
ReadStoreJ and not PureI -> false; %% can't read store/state if other is impure
true ->
%% and cannot write to each other's inputs
not lists:member(WI, RJ) andalso
not lists:member(WJ, RI)
@@ -1605,7 +1670,23 @@ bb(_Name, Code) ->
Blocks = lists:flatmap(fun split_calls/1, Blocks1),
Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(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 --
+3 -2
View File
@@ -10,7 +10,7 @@
-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([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0, pragma/0, fundecl/0]).
@@ -24,8 +24,9 @@
-type ann_col() :: integer().
-type ann_origin() :: system | user.
-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].
-type name() :: string().
+4 -3
View File
@@ -31,11 +31,13 @@
| aeso_syntax:field(aeso_syntax:expr())
| aeso_syntax:stmt().
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,
Same = fun(A) -> fold(Alg, Fun, K, A) end,
Decl = fun(D) -> fold(Alg, Fun, decl, D) end,
Type = fun(T) -> fold(Alg, Fun, type, T) end,
Expr = fun(E) -> fold(Alg, Fun, expr, E) end,
Type = fun(T) -> fold(Alg, Fun, TypeKind, T) end,
Expr = fun(E) -> fold(Alg, Fun, ExprKind, E) end,
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X),
@@ -155,4 +157,3 @@ used(D) ->
(_, _) -> #{}
end, decl, D)),
lists:filter(NotBound, Xs).
+1 -1
View File
@@ -1,6 +1,6 @@
{application, aesophia,
[{description, "Compiler for Aeternity Sophia language"},
{vsn, "7.1.0"},
{vsn, "7.3.0"},
{registered, []},
{applications,
[kernel,
+86 -11
View File
@@ -222,6 +222,7 @@ compilable_contracts() ->
"unapplied_contract_call",
"unapplied_named_arg_builtin",
"resolve_field_constraint_by_arity",
"toplevel_constants",
"test" % Custom general-purpose test file. Keep it last on the list.
].
@@ -286,7 +287,11 @@ warnings() ->
<<?PosW(48, 5)
"Unused return value.">>,
<<?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() ->
@@ -658,10 +663,6 @@ failing_contracts() ->
[<<?Pos(5, 28)
"Invalid call to contract entrypoint `Foo.foo`.\n"
"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,
[<<?Pos(2, 8)
"Empty type declarations are not supported. "
@@ -859,17 +860,21 @@ failing_contracts() ->
<<?Pos(48, 5)
"Unused return value.">>,
<<?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,
[<<?Pos(1,24)
"Trying to implement or extend an undefined interface `Z`">>
])
, ?TYPE_ERROR(polymorphism_contract_interface_same_name_different_type,
[<<?Pos(9,5)
"Duplicate definitions of `f` at\n"
" - line 8, column 5\n"
" - line 9, column 5">>])
[<<?Pos(5,5)
"Cannot unify `char` and `int`\n"
"when implementing the entrypoint `f` from the interface `I1`">>
])
, ?TYPE_ERROR(polymorphism_contract_missing_implementation,
[<<?Pos(4,20)
"Unimplemented entrypoint `f` from the interface `I1` in the contract `I2`">>
@@ -1193,6 +1198,74 @@ failing_contracts() ->
<<?Pos(13,20)
"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_() ->
@@ -1240,7 +1313,9 @@ validate(Contract1, Contract2) ->
true -> [debug_mode];
false -> []
end ++
[{include, {file_system, [aeso_test_utils:contract_path()]}}]);
[ {src_file, lists:concat([Contract2, ".aes"])}
, {include, {file_system, [aeso_test_utils:contract_path()]}}
]);
Error -> print_and_throw(Error)
end.
+3
View File
@@ -6,6 +6,7 @@
namespace Ns =
datatype d('a) = D | S(int) | M('a, list('a), int)
private function fff() = 123
let const = 1
stateful entrypoint
f (1, x) = (_) => x
@@ -33,6 +34,8 @@ contract AllSyntax =
type state = shakespeare(int)
let cc = "str"
entrypoint init() = {
johann = 1000,
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
+21
View File
@@ -65,3 +65,24 @@ contract Remote =
contract C =
payable stateful entrypoint
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