
* 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
160 lines
6.7 KiB
Erlang
160 lines
6.7 KiB
Erlang
%%%-------------------------------------------------------------------
|
|
%%% @copyright (C) 2018, Aeternity Anstalt
|
|
%%% @doc
|
|
%%% Sophia syntax utilities.
|
|
%%% @end
|
|
%%%-------------------------------------------------------------------
|
|
-module(aeso_syntax_utils).
|
|
|
|
-export([used_ids/1, used_types/2, used/1]).
|
|
|
|
-record(alg, {zero, plus, scoped}).
|
|
|
|
-type alg(A) :: #alg{ zero :: A
|
|
, plus :: fun((A, A) -> A)
|
|
, scoped :: fun((A, A) -> A) }.
|
|
|
|
-type kind() :: decl | type | bind_type | expr | bind_expr.
|
|
|
|
-spec fold(alg(A), fun((kind(), _) -> A), kind(), E | [E]) -> A
|
|
when E :: aeso_syntax:decl()
|
|
| aeso_syntax:typedef()
|
|
| aeso_syntax:field_t()
|
|
| aeso_syntax:constructor_t()
|
|
| aeso_syntax:type()
|
|
| aeso_syntax:expr()
|
|
| aeso_syntax:pat()
|
|
| aeso_syntax:arg()
|
|
| aeso_syntax:alt()
|
|
| aeso_syntax:elim()
|
|
| aeso_syntax:arg_expr()
|
|
| 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, 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),
|
|
Rec = case X of
|
|
%% lists (bound things in head scope over tail)
|
|
[A | As] -> Scoped(Same(A), Same(As));
|
|
%% decl()
|
|
{contract, _, _, Ds} -> Decl(Ds);
|
|
{namespace, _, _, Ds} -> Decl(Ds);
|
|
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
|
|
{fun_decl, _, _, T} -> Type(T);
|
|
{letval, _, P, E} -> Scoped(BindExpr(P), Expr(E));
|
|
{letfun, _, F, Xs, T, GEs} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ GEs)]);
|
|
{fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]);
|
|
%% typedef()
|
|
{alias_t, T} -> Type(T);
|
|
{record_t, Fs} -> Type(Fs);
|
|
{variant_t, Cs} -> Type(Cs);
|
|
%% field_t() and constructor_t()
|
|
{field_t, _, _, T} -> Type(T);
|
|
{constr_t, _, _, Ts} -> Type(Ts);
|
|
%% type()
|
|
{fun_t, _, Named, Args, Ret} -> Type([Named, Args, Ret]);
|
|
{app_t, _, T, Ts} -> Type([T | Ts]);
|
|
{tuple_t, _, Ts} -> Type(Ts);
|
|
%% named_arg_t()
|
|
{named_arg_t, _, _, T, E} -> Plus(Type(T), Expr(E));
|
|
%% expr()
|
|
{lam, _, Args, E} -> Scoped(BindExpr(Args), Expr(E));
|
|
{'if', _, A, B, C} -> Expr([A, B, C]);
|
|
{switch, _, E, Alts} -> Expr([E, Alts]);
|
|
{app, _, A, As} -> Expr([A | As]);
|
|
{proj, _, E, _} -> Expr(E);
|
|
{tuple, _, As} -> Expr(As);
|
|
{list, _, As} -> Expr(As);
|
|
{list_comp, _, Y, []} -> Expr(Y);
|
|
{list_comp, A, Y, [{comprehension_bind, I, E}|R]} ->
|
|
Plus(Expr(E), Scoped(BindExpr(I), Expr({list_comp, A, Y, R})));
|
|
{list_comp, A, Y, [{comprehension_if, _, E}|R]} ->
|
|
Plus(Expr(E), Expr({list_comp, A, Y, R}));
|
|
{list_comp, A, Y, [D = {letval, _, Pat, _} | R]} ->
|
|
Plus(Decl(D), Scoped(BindExpr(Pat), Expr({list_comp, A, Y, R})));
|
|
{list_comp, A, Y, [D = {letfun, _, F, _, _, _} | R]} ->
|
|
Plus(Decl(D), Scoped(BindExpr(F), Expr({list_comp, A, Y, R})));
|
|
{typed, _, E, T} -> Plus(Expr(E), Type(T));
|
|
{record, _, Fs} -> Expr(Fs);
|
|
{record, _, E, Fs} -> Expr([E | Fs]);
|
|
{map, _, E, Fs} -> Expr([E | Fs]);
|
|
{map, _, KVs} -> Sum([Expr([Key, Val]) || {Key, Val} <- KVs]);
|
|
{map_get, _, A, B} -> Expr([A, B]);
|
|
{map_get, _, A, B, C} -> Expr([A, B, C]);
|
|
{block, _, Ss} -> Expr(Ss);
|
|
{letpat, _, X, P} -> Plus(BindExpr(X), Expr(P));
|
|
{guarded, _, Gs, E} -> Expr([E | Gs]);
|
|
%% field()
|
|
{field, _, LV, E} -> Expr([LV, E]);
|
|
{field, _, LV, _, E} -> Expr([LV, E]);
|
|
%% arg()
|
|
{arg, _, Y, T} -> Plus(BindExpr(Y), Type(T));
|
|
%% alt()
|
|
{'case', _, P, GEs} -> Scoped(BindExpr(P), Expr(GEs));
|
|
%% elim()
|
|
{proj, _, _} -> Zero;
|
|
{map_get, _, E} -> Expr(E);
|
|
%% arg_expr()
|
|
{named_arg, _, _, E} -> Expr(E);
|
|
_ -> Alg#alg.zero
|
|
end,
|
|
(Alg#alg.plus)(Top, Rec).
|
|
|
|
%% Name dependencies
|
|
|
|
used_ids(E) ->
|
|
[ X || {{term, [X]}, _} <- used(E) ].
|
|
|
|
used_types([Top] = _CurrentNS, T) ->
|
|
F = fun({{type, [X]}, _}) -> [X];
|
|
({{type, [Top1, X]}, _}) when Top1 == Top -> [X];
|
|
(_) -> []
|
|
end,
|
|
lists:flatmap(F, used(T)).
|
|
|
|
-type entity() :: {term, [string()]}
|
|
| {type, [string()]}
|
|
| {namespace, [string()]}.
|
|
|
|
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}).
|
|
entity_alg() ->
|
|
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
|
|
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
|
|
Remove = fun(Keys, Map) -> maps:without(Keys, Map) end,
|
|
Scoped = fun(Xs, Ys) ->
|
|
Bound = [E || E <- maps:keys(Xs), IsBound(E)],
|
|
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
|
|
Others = Remove(Bound1, Ys),
|
|
maps:merge(Remove(Bound, Xs), Others)
|
|
end,
|
|
#alg{ zero = #{}
|
|
, plus = fun maps:merge/2
|
|
, scoped = Scoped }.
|
|
|
|
-spec used(_) -> [{entity(), aeso_syntax:ann()}].
|
|
used(D) ->
|
|
Kind = fun(expr) -> term;
|
|
(bind_expr) -> bound_term;
|
|
(type) -> type;
|
|
(bind_type) -> bound_type
|
|
end,
|
|
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
|
|
NotBound = fun({{Tag, _}, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
|
|
Xs =
|
|
maps:to_list(fold(entity_alg(),
|
|
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann};
|
|
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
|
|
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann};
|
|
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
|
|
(_, _) -> #{}
|
|
end, decl, D)),
|
|
lists:filter(NotBound, Xs).
|