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
This commit is contained in:
parent
99bb3fe1fb
commit
7b6eba5319
@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
- Toplevel compile-time constants
|
||||||
|
```
|
||||||
|
namespace N =
|
||||||
|
let nc = 1
|
||||||
|
contract C =
|
||||||
|
let cc = 2
|
||||||
|
```
|
||||||
### Changed
|
### Changed
|
||||||
### Removed
|
### Removed
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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) ->
|
||||||
@ -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]) ->
|
||||||
@ -1687,9 +1755,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 +2132,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 +2331,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 +2372,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 +3348,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
|
||||||
@ -3254,6 +3426,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" ->
|
||||||
@ -3299,9 +3482,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.
|
||||||
@ -3543,10 +3728,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)]),
|
||||||
@ -3630,10 +3811,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";
|
||||||
@ -3794,6 +3979,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).
|
||||||
@ -3807,6 +4009,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);
|
||||||
|
@ -160,6 +160,7 @@
|
|||||||
context => context(),
|
context => context(),
|
||||||
vars => [var_name()],
|
vars => [var_name()],
|
||||||
functions := #{ fun_name() => fun_def() },
|
functions := #{ fun_name() => fun_def() },
|
||||||
|
consts := #{ var_name() => fexpr() },
|
||||||
saved_fresh_names => #{ var_name() => var_name() }
|
saved_fresh_names => #{ var_name() => var_name() }
|
||||||
}.
|
}.
|
||||||
|
|
||||||
@ -240,7 +241,8 @@ init_env(Options) ->
|
|||||||
["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities }
|
["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities }
|
||||||
},
|
},
|
||||||
options => Options,
|
options => Options,
|
||||||
functions => #{}
|
functions => #{},
|
||||||
|
consts => #{}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
-spec builtins() -> builtins().
|
-spec builtins() -> builtins().
|
||||||
@ -395,7 +397,11 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, R
|
|||||||
return => FRet,
|
return => FRet,
|
||||||
body => FBody },
|
body => FBody },
|
||||||
NewFuns = Funs#{ FName => Def },
|
NewFuns = Funs#{ FName => Def },
|
||||||
Env#{ functions := NewFuns }.
|
Env#{ functions := NewFuns };
|
||||||
|
decl_to_fcode(Env = #{ consts := Consts }, {letval, _, {typed, _, {id, _, X}, _}, Val}) ->
|
||||||
|
FVal = expr_to_fcode(Env, Val),
|
||||||
|
NewConsts = Consts#{ qname(Env, X) => FVal },
|
||||||
|
Env#{ consts := NewConsts }.
|
||||||
|
|
||||||
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
|
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
|
||||||
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
|
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
|
||||||
@ -1722,9 +1728,23 @@ bind_var(Env = #{ vars := Vars }, X) -> Env#{ vars := [X | Vars] }.
|
|||||||
resolve_var(#{ vars := Vars } = Env, [X]) ->
|
resolve_var(#{ vars := Vars } = Env, [X]) ->
|
||||||
case lists:member(X, Vars) of
|
case lists:member(X, Vars) of
|
||||||
true -> {var, X};
|
true -> {var, X};
|
||||||
false -> resolve_fun(Env, [X])
|
false ->
|
||||||
|
case resolve_const(Env, [X]) of
|
||||||
|
false -> resolve_fun(Env, [X]);
|
||||||
|
Const -> Const
|
||||||
|
end
|
||||||
end;
|
end;
|
||||||
resolve_var(Env, Q) -> resolve_fun(Env, Q).
|
resolve_var(Env, Q) ->
|
||||||
|
case resolve_const(Env, Q) of
|
||||||
|
false -> resolve_fun(Env, Q);
|
||||||
|
Const -> Const
|
||||||
|
end.
|
||||||
|
|
||||||
|
resolve_const(#{ consts := Consts }, Q) ->
|
||||||
|
case maps:get(Q, Consts, not_found) of
|
||||||
|
not_found -> false;
|
||||||
|
Val -> Val
|
||||||
|
end.
|
||||||
|
|
||||||
resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) ->
|
resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) ->
|
||||||
case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of
|
case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of
|
||||||
|
@ -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).
|
||||||
|
|
||||||
|
@ -222,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.
|
||||||
].
|
].
|
||||||
|
|
||||||
@ -286,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() ->
|
||||||
@ -658,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. "
|
||||||
@ -859,7 +860,11 @@ 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)
|
||||||
@ -1193,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_() ->
|
||||||
|
@ -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
test/contracts/toplevel_constants.aes
Normal file
64
test/contracts/toplevel_constants.aes
Normal 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
|
11
test/contracts/toplevel_constants_contract_as_namespace.aes
Normal file
11
test/contracts/toplevel_constants_contract_as_namespace.aes
Normal file
@ -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()
|
6
test/contracts/toplevel_constants_cycles.aes
Normal file
6
test/contracts/toplevel_constants_cycles.aes
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
contract C =
|
||||||
|
let selfcycle = selfcycle
|
||||||
|
|
||||||
|
let cycle1 = cycle2
|
||||||
|
let cycle2 = cycle3
|
||||||
|
let cycle3 = cycle1
|
7
test/contracts/toplevel_constants_in_interface.aes
Normal file
7
test/contracts/toplevel_constants_in_interface.aes
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
contract interface I =
|
||||||
|
let (x::y::_) = [1,2,3]
|
||||||
|
let c = 10
|
||||||
|
let d = 10
|
||||||
|
|
||||||
|
contract C =
|
||||||
|
entrypoint init() = ()
|
21
test/contracts/toplevel_constants_invalid_expr.aes
Normal file
21
test/contracts/toplevel_constants_invalid_expr.aes
Normal file
@ -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
|
3
test/contracts/toplevel_constants_invalid_id.aes
Normal file
3
test/contracts/toplevel_constants_invalid_id.aes
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
contract C =
|
||||||
|
let x::_ = [1,2,3,4]
|
||||||
|
let y::(p = z::_) = [1,2,3,4]
|
@ -1,3 +0,0 @@
|
|||||||
contract C =
|
|
||||||
let this_is_illegal = 2/0
|
|
||||||
entrypoint this_is_legal() = 2/0
|
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user