Add compiler warnings #837
@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
- Compiler warnings for the follwing: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
|
||||||
### Changed
|
### Changed
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
|
|||||||
do_contract_interface(Type, ContractString, Options) ->
|
do_contract_interface(Type, ContractString, Options) ->
|
||||||
try
|
try
|
||||||
Ast = aeso_compiler:parse(ContractString, Options),
|
Ast = aeso_compiler:parse(ContractString, Options),
|
||||||
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
{TypedAst, _, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
||||||
from_typed_ast(Type, TypedAst)
|
from_typed_ast(Type, TypedAst)
|
||||||
catch
|
catch
|
||||||
throw:{error, Errors} -> {error, Errors}
|
throw:{error, Errors} -> {error, Errors}
|
||||||
|
@ -171,7 +171,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) ->
|
bind_var({id, Ann, X}, T, Env = #env{ vars = Vars }) ->
|
||||||
|
when_warning(warn_shadowing, fun() -> warn_potential_shadowing(Ann, X, Vars) 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().
|
||||||
@ -368,7 +369,9 @@ lookup_env(Env, Kind, Ann, Name) ->
|
|||||||
Names = [ Qual ++ [lists:last(Name)] || Qual <- possible_scopes(Env, Name) ],
|
Names = [ Qual ++ [lists:last(Name)] || Qual <- possible_scopes(Env, Name) ],
|
||||||
case [ Res || QName <- Names, Res <- [lookup_env1(Env, Kind, Ann, QName)], Res /= false] of
|
case [ Res || QName <- Names, Res <- [lookup_env1(Env, Kind, Ann, QName)], Res /= false] of
|
||||||
[] -> false;
|
[] -> false;
|
||||||
[Res] -> Res;
|
[Res = {_, {AnnR, _}}] ->
|
||||||
|
when_warning(warn_unused_includes, fun() -> used_include(AnnR) end),
|
||||||
|
Res;
|
||||||
Many ->
|
Many ->
|
||||||
type_error({ambiguous_name, [{qid, A, Q} || {Q, {A, _}} <- Many]}),
|
type_error({ambiguous_name, [{qid, A, Q} || {Q, {A, _}} <- Many]}),
|
||||||
false
|
false
|
||||||
@ -775,7 +778,7 @@ global_env() ->
|
|||||||
option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}.
|
option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}.
|
||||||
map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
|
map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
|
||||||
|
|
||||||
-spec infer(aeso_syntax:ast()) -> {aeso_syntax:ast(), aeso_syntax:ast()} | {env(), aeso_syntax:ast(), aeso_syntax:ast()}.
|
-spec infer(aeso_syntax:ast()) -> {aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]} | {env(), aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]}.
|
||||||
infer(Contracts) ->
|
infer(Contracts) ->
|
||||||
infer(Contracts, []).
|
infer(Contracts, []).
|
||||||
|
|
||||||
@ -785,7 +788,7 @@ infer(Contracts) ->
|
|||||||
init_env(_Options) -> global_env().
|
init_env(_Options) -> global_env().
|
||||||
|
|
||||||
-spec infer(aeso_syntax:ast(), list(option())) ->
|
-spec infer(aeso_syntax:ast(), list(option())) ->
|
||||||
{aeso_syntax:ast(), aeso_syntax:ast()} | {env(), aeso_syntax:ast(), aeso_syntax:ast()}.
|
{aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]} | {env(), aeso_syntax:ast(), aeso_syntax:ast(), [aeso_warnings:warning()]}.
|
||||||
infer([], Options) ->
|
infer([], Options) ->
|
||||||
create_type_errors(),
|
create_type_errors(),
|
||||||
type_error({no_decls, proplists:get_value(src_file, Options, no_file)}),
|
type_error({no_decls, proplists:get_value(src_file, Options, no_file)}),
|
||||||
@ -797,11 +800,16 @@ infer(Contracts, Options) ->
|
|||||||
create_options(Options),
|
create_options(Options),
|
||||||
ets_new(defined_contracts, [bag]),
|
ets_new(defined_contracts, [bag]),
|
||||||
ets_new(type_vars, [set]),
|
ets_new(type_vars, [set]),
|
||||||
|
ets_new(warnings, [bag]),
|
||||||
|
when_warning(warn_unused_functions, fun() -> create_unused_functions() end),
|
||||||
check_modifiers(Env, Contracts),
|
check_modifiers(Env, Contracts),
|
||||||
create_type_errors(),
|
create_type_errors(),
|
||||||
Contracts1 = identify_main_contract(Contracts, Options),
|
Contracts1 = identify_main_contract(Contracts, Options),
|
||||||
destroy_and_report_type_errors(Env),
|
destroy_and_report_type_errors(Env),
|
||||||
{Env1, Decls} = infer1(Env, Contracts1, [], Options),
|
{Env1, Decls} = infer1(Env, Contracts1, [], Options),
|
||||||
|
when_warning(warn_unused_functions, fun() -> destroy_and_report_unused_functions() end),
|
||||||
|
when_option(warn_error, fun() -> destroy_and_report_warnings_as_type_errors() end),
|
||||||
|
Warnings = lists:map(fun mk_warning/1, ets_tab2list(warnings)),
|
||||||
{Env2, DeclsFolded, DeclsUnfolded} =
|
{Env2, DeclsFolded, DeclsUnfolded} =
|
||||||
case proplists:get_value(dont_unfold, Options, false) of
|
case proplists:get_value(dont_unfold, Options, false) of
|
||||||
true -> {Env1, Decls, Decls};
|
true -> {Env1, Decls, Decls};
|
||||||
@ -809,8 +817,8 @@ infer(Contracts, Options) ->
|
|||||||
{E, Decls, unfold_record_types(E, Decls)}
|
{E, Decls, unfold_record_types(E, Decls)}
|
||||||
end,
|
end,
|
||||||
case proplists:get_value(return_env, Options, false) of
|
case proplists:get_value(return_env, Options, false) of
|
||||||
false -> {DeclsFolded, DeclsUnfolded};
|
false -> {DeclsFolded, DeclsUnfolded, Warnings};
|
||||||
true -> {Env2, DeclsFolded, DeclsUnfolded}
|
true -> {Env2, DeclsFolded, DeclsUnfolded, Warnings}
|
||||||
end
|
end
|
||||||
after
|
after
|
||||||
clean_up_ets()
|
clean_up_ets()
|
||||||
@ -838,6 +846,7 @@ infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options)
|
|||||||
Env3 = bind_contract(Contract1, Env2),
|
Env3 = bind_contract(Contract1, Env2),
|
||||||
infer1(Env3, Rest, [Contract1 | Acc], Options);
|
infer1(Env3, Rest, [Contract1 | Acc], Options);
|
||||||
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) ->
|
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) ->
|
||||||
|
when_warning(warn_unused_includes, fun() -> potential_unused_include(Ann, proplists:get_value(src_file, Options, no_file)) end),
|
||||||
check_scope_name_clash(Env, namespace, Name),
|
check_scope_name_clash(Env, namespace, Name),
|
||||||
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options),
|
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options),
|
||||||
Namespace1 = {namespace, Ann, Name, Code1},
|
Namespace1 = {namespace, Ann, Name, Code1},
|
||||||
@ -907,6 +916,7 @@ infer_contract(Env0, What, Defs0, Options) ->
|
|||||||
OldUsedNamespaces = Env#env.used_namespaces,
|
OldUsedNamespaces = Env#env.used_namespaces,
|
||||||
Env01 = check_usings(Env, Get(using, Defs)),
|
Env01 = check_usings(Env, Get(using, Defs)),
|
||||||
{Env1, TypeDefs} = check_typedefs(Env01, Get(type, Defs)),
|
{Env1, TypeDefs} = check_typedefs(Env01, Get(type, Defs)),
|
||||||
|
when_warning(warn_unused_typedefs, fun() -> potential_unused_typedefs(Env#env.namespace, TypeDefs) end),
|
||||||
create_type_errors(),
|
create_type_errors(),
|
||||||
check_unexpected(Get(unexpected, Defs)),
|
check_unexpected(Get(unexpected, Defs)),
|
||||||
Env2 =
|
Env2 =
|
||||||
@ -1345,7 +1355,10 @@ infer_letrec(Env, Defs) ->
|
|||||||
[print_typesig(S) || S <- TypeSigs],
|
[print_typesig(S) || S <- TypeSigs],
|
||||||
{TypeSigs, NewDefs}.
|
{TypeSigs, NewDefs}.
|
||||||
|
|
||||||
infer_letfun(Env, {fun_clauses, Ann, Fun = {id, _, Name}, Type, Clauses}) ->
|
infer_letfun(Env = #env{ namespace = Namespace }, {fun_clauses, Ann, Fun = {id, _, Name}, Type, Clauses}) ->
|
||||||
|
when_warning(warn_unused_stateful, fun() -> potential_unused_stateful(Ann, Fun) end),
|
||||||
|
when_warning(warn_unused_functions,
|
||||||
|
fun() -> potential_unused_function(Env, Ann, Namespace ++ qname(Fun), Fun) end),
|
||||||
Type1 = check_type(Env, Type),
|
Type1 = check_type(Env, Type),
|
||||||
{NameSigs, Clauses1} = lists:unzip([ infer_letfun1(Env, Clause) || Clause <- Clauses ]),
|
{NameSigs, Clauses1} = lists:unzip([ infer_letfun1(Env, Clause) || Clause <- Clauses ]),
|
||||||
{_, Sigs = [Sig | _]} = lists:unzip(NameSigs),
|
{_, Sigs = [Sig | _]} = lists:unzip(NameSigs),
|
||||||
@ -1354,13 +1367,17 @@ infer_letfun(Env, {fun_clauses, Ann, Fun = {id, _, Name}, Type, Clauses}) ->
|
|||||||
unify(Env, ClauseT, Type1, {check_typesig, Name, ClauseT, Type1})
|
unify(Env, ClauseT, Type1, {check_typesig, Name, ClauseT, Type1})
|
||||||
end || ClauseSig <- Sigs ],
|
end || ClauseSig <- Sigs ],
|
||||||
{{Name, Sig}, desugar_clauses(Ann, Fun, Sig, Clauses1)};
|
{{Name, Sig}, desugar_clauses(Ann, Fun, Sig, Clauses1)};
|
||||||
infer_letfun(Env, LetFun = {letfun, Ann, Fun, _, _, _}) ->
|
infer_letfun(Env = #env{ namespace = Namespace }, LetFun = {letfun, Ann, Fun, _, _, _}) ->
|
||||||
|
when_warning(warn_unused_stateful, fun() -> potential_unused_stateful(Ann, Fun) end),
|
||||||
|
when_warning(warn_unused_functions, fun() -> potential_unused_function(Env, Ann, Namespace ++ qname(Fun), Fun) end),
|
||||||
{{Name, Sig}, Clause} = infer_letfun1(Env, LetFun),
|
{{Name, Sig}, Clause} = infer_letfun1(Env, LetFun),
|
||||||
{{Name, Sig}, desugar_clauses(Ann, Fun, Sig, [Clause])}.
|
{{Name, Sig}, desugar_clauses(Ann, Fun, Sig, [Clause])}.
|
||||||
infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, GuardedBodies}) ->
|
|
||||||
|
infer_letfun1(Env0 = #env{ namespace = NS }, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, GuardedBodies}) ->
|
||||||
Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false),
|
Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false),
|
||||||
current_function = Fun },
|
current_function = Fun },
|
||||||
{NewEnv, {typed, _, {tuple, _, TypedArgs}, {tuple_t, _, ArgTypes}}} = infer_pattern(Env, {tuple, [{origin, system} | NameAttrib], Args}),
|
{NewEnv, {typed, _, {tuple, _, TypedArgs}, {tuple_t, _, ArgTypes}}} = infer_pattern(Env, {tuple, [{origin, system} | NameAttrib], Args}),
|
||||||
|
when_warning(warn_unused_variables, fun() -> potential_unused_variables(NS, Name, free_vars(Args)) end),
|
||||||
ExpectedType = check_type(Env, arg_type(NameAttrib, What)),
|
ExpectedType = check_type(Env, arg_type(NameAttrib, What)),
|
||||||
InferGuardedBodies = fun({guarded, Ann, Guards, Body}) ->
|
InferGuardedBodies = fun({guarded, Ann, Guards, Body}) ->
|
||||||
NewGuards = lists:map(fun(Guard) ->
|
NewGuards = lists:map(fun(Guard) ->
|
||||||
@ -1415,12 +1432,13 @@ app_t(Ann, Name, Args) -> {app_t, Ann, Name, Args}.
|
|||||||
lookup_name(Env, As, Name) ->
|
lookup_name(Env, As, Name) ->
|
||||||
lookup_name(Env, As, Name, []).
|
lookup_name(Env, As, Name, []).
|
||||||
|
|
||||||
lookup_name(Env, As, Id, Options) ->
|
lookup_name(Env = #env{ namespace = NS, current_function = {id, _, Fun} }, As, Id, Options) ->
|
||||||
case lookup_env(Env, term, As, qname(Id)) of
|
case lookup_env(Env, term, As, qname(Id)) of
|
||||||
false ->
|
false ->
|
||||||
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, Fun, 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
|
||||||
@ -1443,7 +1461,9 @@ check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {typ
|
|||||||
true ->
|
true ->
|
||||||
type_error({stateful_not_allowed, Id, Fun})
|
type_error({stateful_not_allowed, Id, Fun})
|
||||||
end;
|
end;
|
||||||
check_stateful(_Env, _Id, _Type) -> ok.
|
check_stateful(#env { current_function = Fun }, _Id, _Type) ->
|
||||||
|
when_warning(warn_unused_stateful, fun() -> used_stateful(Fun) end),
|
||||||
|
ok.
|
||||||
|
|
||||||
%% Hack: don't allow passing the 'value' named arg if not stateful. This only
|
%% Hack: don't allow passing the 'value' named arg if not stateful. This only
|
||||||
%% works since the user can't create functions with named arguments.
|
%% works since the user can't create functions with named arguments.
|
||||||
@ -1601,16 +1621,21 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
|||||||
prefix ->
|
prefix ->
|
||||||
infer_op(Env, Ann, Fun, Args, fun infer_prefix/1);
|
infer_op(Env, Ann, Fun, Args, fun infer_prefix/1);
|
||||||
_ ->
|
_ ->
|
||||||
|
CurrentFun = Env#env.current_function,
|
||||||
|
Namespace = Env#env.namespace,
|
||||||
NamedArgsVar = fresh_uvar(Ann),
|
NamedArgsVar = fresh_uvar(Ann),
|
||||||
NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ],
|
NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ],
|
||||||
NewFun0 = infer_expr(Env, Fun),
|
NewFun0 = infer_expr(Env, Fun),
|
||||||
NewArgs = [infer_expr(Env, A) || A <- Args],
|
NewArgs = [infer_expr(Env, A) || A <- Args],
|
||||||
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
|
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
|
||||||
NewFun1 = {typed, _, _, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
|
NewFun1 = {typed, _, Name, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
|
||||||
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
|
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
|
||||||
GeneralResultType = fresh_uvar(Ann),
|
GeneralResultType = fresh_uvar(Ann),
|
||||||
ResultType = fresh_uvar(Ann),
|
ResultType = fresh_uvar(Ann),
|
||||||
|
when_warning(warn_unused_functions,
|
||||||
|
fun() -> register_function_call(Namespace ++ qname(CurrentFun), Name) end),
|
||||||
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
|
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
|
||||||
|
when_warning(warn_negative_spend, fun() -> warn_potential_negative_spend(Ann, NewFun1, NewArgs) end),
|
||||||
add_named_argument_constraint(
|
add_named_argument_constraint(
|
||||||
#dependent_type_constraint{ named_args_t = NamedArgsVar,
|
#dependent_type_constraint{ named_args_t = NamedArgsVar,
|
||||||
named_args = NamedArgs1,
|
named_args = NamedArgs1,
|
||||||
@ -1835,6 +1860,7 @@ infer_op(Env, As, Op, Args, InferOp) ->
|
|||||||
ArgTypes = [T || {typed, _, _, T} <- TypedArgs],
|
ArgTypes = [T || {typed, _, _, T} <- TypedArgs],
|
||||||
Inferred = {fun_t, _, _, OperandTypes, ResultType} = InferOp(Op),
|
Inferred = {fun_t, _, _, OperandTypes, ResultType} = InferOp(Op),
|
||||||
unify(Env, ArgTypes, OperandTypes, {infer_app, Op, [], Args, Inferred, ArgTypes}),
|
unify(Env, ArgTypes, OperandTypes, {infer_app, Op, [], Args, Inferred, ArgTypes}),
|
||||||
|
when_warning(warn_division_by_zero, fun() -> warn_potential_division_by_zero(As, Op, Args) end),
|
||||||
{typed, As, {app, As, Op, TypedArgs}, ResultType}.
|
{typed, As, {app, As, Op, TypedArgs}, ResultType}.
|
||||||
|
|
||||||
infer_pattern(Env, Pattern) ->
|
infer_pattern(Env, Pattern) ->
|
||||||
@ -1848,8 +1874,9 @@ 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, Attrs, Pattern, ExprType, GuardedBranches, SwitchType) ->
|
infer_case(Env = #env{ namespace = NS, current_function = {id, _, Fun} }, 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),
|
||||||
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"})
|
||||||
@ -1879,7 +1906,9 @@ infer_block(Env, _, [{letval, Attrs, Pattern, E}|Rest], BlockType) ->
|
|||||||
infer_block(Env, Attrs, [Using = {using, _, _, _, _} | Rest], BlockType) ->
|
infer_block(Env, Attrs, [Using = {using, _, _, _, _} | Rest], BlockType) ->
|
||||||
infer_block(check_usings(Env, Using), Attrs, Rest, BlockType);
|
infer_block(check_usings(Env, Using), Attrs, Rest, BlockType);
|
||||||
infer_block(Env, Attrs, [E|Rest], BlockType) ->
|
infer_block(Env, Attrs, [E|Rest], BlockType) ->
|
||||||
[infer_expr(Env, E)|infer_block(Env, Attrs, Rest, BlockType)].
|
NewE = infer_expr(Env, E),
|
||||||
|
when_warning(warn_unused_return_value, fun() -> potential_unused_return_value(NewE) end),
|
||||||
|
[NewE|infer_block(Env, Attrs, Rest, BlockType)].
|
||||||
|
|
||||||
infer_infix({BoolOp, As})
|
infer_infix({BoolOp, As})
|
||||||
when BoolOp =:= '&&'; BoolOp =:= '||' ->
|
when BoolOp =:= '&&'; BoolOp =:= '||' ->
|
||||||
@ -1959,7 +1988,8 @@ next_count() ->
|
|||||||
|
|
||||||
ets_tables() ->
|
ets_tables() ->
|
||||||
[options, type_vars, type_defs, record_fields, named_argument_constraints,
|
[options, type_vars, type_defs, record_fields, named_argument_constraints,
|
||||||
field_constraints, freshen_tvars, type_errors, defined_contracts].
|
field_constraints, freshen_tvars, type_errors, defined_contracts,
|
||||||
|
warnings, function_calls, all_functions].
|
||||||
|
|
||||||
clean_up_ets() ->
|
clean_up_ets() ->
|
||||||
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
|
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
|
||||||
@ -1971,6 +2001,13 @@ clean_up_ets() ->
|
|||||||
ets_init() ->
|
ets_init() ->
|
||||||
put(aeso_ast_infer_types, #{}).
|
put(aeso_ast_infer_types, #{}).
|
||||||
|
|
||||||
|
ets_tab_exists(Name) ->
|
||||||
|
Tabs = get(aeso_ast_infer_types),
|
||||||
|
case maps:find(Name, Tabs) of
|
||||||
|
{ok, _} -> true;
|
||||||
|
error -> false
|
||||||
|
end.
|
||||||
|
|
||||||
ets_tabid(Name) ->
|
ets_tabid(Name) ->
|
||||||
#{Name := TabId} = get(aeso_ast_infer_types),
|
#{Name := TabId} = get(aeso_ast_infer_types),
|
||||||
TabId.
|
TabId.
|
||||||
@ -1996,6 +2033,10 @@ ets_lookup(Name, Key) ->
|
|||||||
TabId = ets_tabid(Name),
|
TabId = ets_tabid(Name),
|
||||||
ets:lookup(TabId, Key).
|
ets:lookup(TabId, Key).
|
||||||
|
|
||||||
|
ets_match_delete(Name, Pattern) ->
|
||||||
|
TabId = ets_tabid(Name),
|
||||||
|
ets:match_delete(TabId, Pattern).
|
||||||
|
|
||||||
ets_tab2list(Name) ->
|
ets_tab2list(Name) ->
|
||||||
TabId = ets_tabid(Name),
|
TabId = ets_tabid(Name),
|
||||||
ets:tab2list(TabId).
|
ets:tab2list(TabId).
|
||||||
@ -2461,6 +2502,7 @@ unfold_types_in_type(Env, {app_t, Ann, Id = {id, _, "map"}, Args = [KeyType0, _]
|
|||||||
[ type_error({map_in_map_key, Ann1, KeyType0}) || has_maps(KeyType) ],
|
[ type_error({map_in_map_key, Ann1, KeyType0}) || has_maps(KeyType) ],
|
||||||
{app_t, Ann, Id, Args1};
|
{app_t, Ann, Id, Args1};
|
||||||
unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) ->
|
unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) ->
|
||||||
|
when_warning(warn_unused_typedefs, fun() -> used_typedef(Id, length(Args)) end),
|
||||||
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
|
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
|
||||||
UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false),
|
UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false),
|
||||||
case lookup_type(Env, Id) of
|
case lookup_type(Env, Id) of
|
||||||
@ -2481,6 +2523,7 @@ unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id)
|
|||||||
end;
|
end;
|
||||||
unfold_types_in_type(Env, Id, Options) when ?is_type_id(Id) ->
|
unfold_types_in_type(Env, Id, Options) when ?is_type_id(Id) ->
|
||||||
%% Like the case above, but for types without parameters.
|
%% Like the case above, but for types without parameters.
|
||||||
|
when_warning(warn_unused_typedefs, fun() -> used_typedef(Id, 0) end),
|
||||||
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
|
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
|
||||||
UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false),
|
UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false),
|
||||||
case lookup_type(Env, Id) of
|
case lookup_type(Env, Id) of
|
||||||
@ -2733,6 +2776,154 @@ integer_to_tvar(X) when X < 26 ->
|
|||||||
integer_to_tvar(X) ->
|
integer_to_tvar(X) ->
|
||||||
[integer_to_tvar(X div 26)] ++ [$a + (X rem 26)].
|
[integer_to_tvar(X div 26)] ++ [$a + (X rem 26)].
|
||||||
|
|
||||||
|
%% Warnings
|
||||||
|
|
||||||
|
all_warnings() ->
|
||||||
|
[ warn_unused_includes
|
||||||
|
, warn_unused_stateful
|
||||||
|
, warn_unused_variables
|
||||||
|
, warn_unused_typedefs
|
||||||
|
, warn_unused_return_value
|
||||||
|
, warn_unused_functions
|
||||||
|
, warn_shadowing
|
||||||
|
, warn_division_by_zero
|
||||||
|
, warn_negative_spend ].
|
||||||
|
|
||||||
|
when_warning(Warn, Do) ->
|
||||||
|
case lists:member(Warn, all_warnings()) of
|
||||||
|
false ->
|
||||||
|
create_type_errors(),
|
||||||
|
type_error({unknown_warning, Warn}),
|
||||||
|
destroy_and_report_type_errors(global_env());
|
||||||
|
true ->
|
||||||
|
case ets_tab_exists(warnings) of
|
||||||
|
true ->
|
||||||
|
IsEnabled = get_option(Warn, false),
|
||||||
|
IsAll = get_option(warn_all, false) andalso lists:member(Warn, all_warnings()),
|
||||||
|
if
|
||||||
|
IsEnabled orelse IsAll -> Do();
|
||||||
|
true -> ok
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Warnings (Unused includes)
|
||||||
|
|
||||||
|
potential_unused_include(Ann, SrcFile) ->
|
||||||
|
case aeso_syntax:get_ann(file, Ann, no_file) of
|
||||||
|
no_file -> ok;
|
||||||
|
File -> ets_insert(warnings, {unused_include, File, SrcFile})
|
||||||
|
end.
|
||||||
|
|
||||||
|
used_include(Ann) ->
|
||||||
|
case aeso_syntax:get_ann(file, Ann, no_file) of
|
||||||
|
no_file -> ok;
|
||||||
|
File -> ets_match_delete(warnings, {unused_include, File, '_'})
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Warnings (Unused stateful)
|
||||||
|
|
||||||
|
potential_unused_stateful(Ann, Fun) ->
|
||||||
|
case aeso_syntax:get_ann(stateful, Ann, false) of
|
||||||
|
false -> ok;
|
||||||
|
true -> ets_insert(warnings, {unused_stateful, Ann, Fun})
|
||||||
|
end.
|
||||||
|
|
||||||
|
used_stateful(Fun) ->
|
||||||
|
ets_match_delete(warnings, {unused_stateful, '_', Fun}).
|
||||||
|
|
||||||
|
%% Warnings (Unused type defs)
|
||||||
|
|
||||||
|
potential_unused_typedefs(Namespace, TypeDefs) ->
|
||||||
|
lists:map(fun({type_def, Ann, Id, Args, _}) ->
|
||||||
|
ets_insert(warnings, {unused_typedef, Ann, Namespace ++ qname(Id), length(Args)}) end, TypeDefs).
|
||||||
|
|
||||||
|
used_typedef(TypeAliasId, Arity) ->
|
||||||
|
ets_match_delete(warnings, {unused_typedef, '_', qname(TypeAliasId), Arity}).
|
||||||
|
|
||||||
|
%% Warnings (Unused variables)
|
||||||
|
|
||||||
|
potential_unused_variables(Namespace, Fun, Vars0) ->
|
||||||
|
Vars = [ Var || Var = {id, _, VarName} <- Vars0, VarName /= "_" ],
|
||||||
|
lists:map(fun({id, Ann, VarName}) ->
|
||||||
|
ets_insert(warnings, {unused_variable, Ann, Namespace, Fun, VarName}) end, Vars).
|
||||||
|
|
||||||
|
used_variable(Namespace, Fun, [VarName]) ->
|
||||||
|
ets_match_delete(warnings, {unused_variable, '_', Namespace, Fun, VarName});
|
||||||
|
used_variable(_, _, _) -> ok.
|
||||||
|
|
||||||
|
%% Warnings (Unused return value)
|
||||||
|
|
||||||
|
potential_unused_return_value({typed, Ann, {app, _, {typed, _, _, {fun_t, _, _, _, {id, _, Type}}}, _}, _}) when Type /= "unit" ->
|
||||||
|
ets_insert(warnings, {unused_return_value, Ann});
|
||||||
|
potential_unused_return_value(_) -> ok.
|
||||||
|
|
||||||
|
%% Warnings (Unused functions)
|
||||||
|
|
||||||
|
create_unused_functions() ->
|
||||||
|
ets_new(function_calls, [bag]),
|
||||||
|
ets_new(all_functions, [set]).
|
||||||
|
|
||||||
|
register_function_call(_Caller, {proj, _, _, _}) -> ok;
|
||||||
|
register_function_call(Caller, Callee) ->
|
||||||
|
ets_insert(function_calls, {Caller, qname(Callee)}).
|
||||||
|
|
||||||
|
potential_unused_function(#env{ what = namespace }, Ann, FunQName, FunId) ->
|
||||||
|
ets_insert(all_functions, {Ann, FunQName, FunId, not aeso_syntax:get_ann(private, Ann, false)});
|
||||||
|
potential_unused_function(_Env, Ann, FunQName, FunId) ->
|
||||||
|
ets_insert(all_functions, {Ann, FunQName, FunId, aeso_syntax:get_ann(entrypoint, Ann, false)}).
|
||||||
|
|
||||||
|
remove_used_funs(All) ->
|
||||||
|
{Used, Unused} = lists:partition(fun({_, _, _, IsUsed}) -> IsUsed end, All),
|
||||||
|
CallsByUsed = lists:flatmap(fun({_, F, _, _}) -> ets_lookup(function_calls, F) end, Used),
|
||||||
|
CalledFuns = sets:from_list(lists:map(fun({_, Callee}) -> Callee end, CallsByUsed)),
|
||||||
|
MarkUsedFun = fun(Fun, Acc) ->
|
||||||
|
case lists:keyfind(Fun, 2, Acc) of
|
||||||
|
false -> Acc;
|
||||||
|
T -> lists:keyreplace(Fun, 2, Acc, setelement(4, T, true))
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
NewUnused = sets:fold(MarkUsedFun, Unused, CalledFuns),
|
||||||
|
case lists:keyfind(true, 4, NewUnused) of
|
||||||
|
false -> NewUnused;
|
||||||
|
_ -> remove_used_funs(NewUnused)
|
||||||
|
end.
|
||||||
|
|
||||||
|
destroy_and_report_unused_functions() ->
|
||||||
|
AllFuns = ets_tab2list(all_functions),
|
||||||
|
lists:map(fun({Ann, _, FunId, _}) -> ets_insert(warnings, {unused_function, Ann, name(FunId)}) end,
|
||||||
|
remove_used_funs(AllFuns)),
|
||||||
|
ets_delete(all_functions),
|
||||||
|
ets_delete(function_calls).
|
||||||
|
|
||||||
|
%% Warnings (Shadowing)
|
||||||
|
|
||||||
|
warn_potential_shadowing(_, "_", _) -> ok;
|
||||||
|
warn_potential_shadowing(Ann, Name, Vars) ->
|
||||||
|
case proplists:get_value(Name, Vars, false) of
|
||||||
|
false -> ok;
|
||||||
|
{AnnOld, _} -> ets_insert(warnings, {shadowing, Ann, Name, AnnOld})
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Warnings (Division by zero)
|
||||||
|
|
||||||
|
warn_potential_division_by_zero(Ann, Op, Args) ->
|
||||||
|
case {Op, Args} of
|
||||||
|
{{'/', _}, [_, {int, _, 0}]} -> ets_insert(warnings, {division_by_zero, Ann});
|
||||||
|
_ -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Warnings (Negative spends)
|
||||||
|
|
||||||
|
warn_potential_negative_spend(Ann, Fun, Args) ->
|
||||||
|
case {Fun, Args} of
|
||||||
|
{ {typed, _, {qid, _, ["Chain", "spend"]}, _}
|
||||||
|
, [_, {typed, _, {app, _, {'-', _}, [{typed, _, {int, _, X}, _}]}, _}]} when X > 0 ->
|
||||||
|
ets_insert(warnings, {negative_spend, Ann});
|
||||||
|
_ -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
%% Save unification failures for error messages.
|
%% Save unification failures for error messages.
|
||||||
|
|
||||||
@ -2752,6 +2943,11 @@ destroy_and_report_type_errors(Env) ->
|
|||||||
Errors = [ mk_error(unqualify(Env, Err)) || Err <- Errors0 ],
|
Errors = [ mk_error(unqualify(Env, Err)) || Err <- Errors0 ],
|
||||||
aeso_errors:throw(Errors). %% No-op if Errors == []
|
aeso_errors:throw(Errors). %% No-op if Errors == []
|
||||||
|
|
||||||
|
destroy_and_report_warnings_as_type_errors() ->
|
||||||
|
Warnings = [ mk_warning(Warn) || Warn <- ets_tab2list(warnings) ],
|
||||||
|
Errors = lists:map(fun mk_t_err_from_warn/1, Warnings),
|
||||||
|
aeso_errors:throw(Errors). %% No-op if Warnings == []
|
||||||
|
|
||||||
%% Strip current namespace from error message for nicer printing.
|
%% Strip current namespace from error message for nicer printing.
|
||||||
unqualify(#env{ namespace = NS }, {qid, Ann, Xs}) ->
|
unqualify(#env{ namespace = NS }, {qid, Ann, Xs}) ->
|
||||||
qid(Ann, unqualify1(NS, Xs));
|
qid(Ann, unqualify1(NS, Xs));
|
||||||
@ -2774,6 +2970,9 @@ mk_t_err(Pos, Msg) ->
|
|||||||
mk_t_err(Pos, Msg, Ctxt) ->
|
mk_t_err(Pos, Msg, Ctxt) ->
|
||||||
aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)).
|
aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)).
|
||||||
|
|
||||||
|
mk_t_err_from_warn(Warn) ->
|
||||||
|
aeso_warnings:warn_to_err(type_error, Warn).
|
||||||
|
|
||||||
mk_error({no_decls, File}) ->
|
mk_error({no_decls, File}) ->
|
||||||
Pos = aeso_errors:pos(File, 0, 0),
|
Pos = aeso_errors:pos(File, 0, 0),
|
||||||
mk_t_err(Pos, "Empty contract\n");
|
mk_t_err(Pos, "Empty contract\n");
|
||||||
@ -3112,10 +3311,44 @@ mk_error({using_undefined_namespace_parts, Ann, Namespace, Parts}) ->
|
|||||||
PartsStr = lists:concat(lists:join(", ", Parts)),
|
PartsStr = lists:concat(lists:join(", ", Parts)),
|
||||||
Msg = io_lib:format("The namespace ~s does not define the following names: ~s", [Namespace, PartsStr]),
|
Msg = io_lib:format("The namespace ~s does not define the following names: ~s", [Namespace, PartsStr]),
|
||||||
mk_t_err(pos(Ann), Msg);
|
mk_t_err(pos(Ann), Msg);
|
||||||
|
mk_error({unknown_warning, Warning}) ->
|
||||||
|
Msg = io_lib:format("Trying to report unknown warning: ~p", [Warning]),
|
||||||
|
mk_t_err(pos(0, 0), Msg);
|
||||||
mk_error(Err) ->
|
mk_error(Err) ->
|
||||||
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
||||||
mk_t_err(pos(0, 0), Msg).
|
mk_t_err(pos(0, 0), Msg).
|
||||||
|
|
||||||
|
mk_warning({unused_include, FileName, SrcFile}) ->
|
||||||
|
Msg = io_lib:format("The file ~s is included but not used", [FileName]),
|
||||||
|
aeso_warnings:new(aeso_errors:pos(SrcFile, 0, 0), Msg);
|
||||||
|
mk_warning({unused_stateful, Ann, FunName}) ->
|
||||||
|
Msg = io_lib:format("The function ~s is unnecessarily marked as stateful at ~s", [name(FunName), pp_loc(Ann)]),
|
||||||
|
aeso_warnings:new(pos(Ann), Msg);
|
||||||
|
mk_warning({unused_variable, Ann, _Namespace, _Fun, VarName}) ->
|
||||||
|
Msg = io_lib:format("The variable ~s is defined at ~s but never used", [VarName, pp_loc(Ann)]),
|
||||||
|
aeso_warnings:new(pos(Ann), Msg);
|
||||||
|
mk_warning({unused_typedef, Ann, QName, _Arity}) ->
|
||||||
|
Msg = io_lib:format("The type ~s is defined at ~s but never used", [lists:last(QName), pp_loc(Ann)]),
|
||||||
|
aeso_warnings:new(pos(Ann), Msg);
|
||||||
|
mk_warning({unused_return_value, Ann}) ->
|
||||||
|
Msg = io_lib:format("Unused return value at ~s", [pp_loc(Ann)]),
|
||||||
|
aeso_warnings:new(pos(Ann), Msg);
|
||||||
|
mk_warning({unused_function, Ann, FunName}) ->
|
||||||
|
Msg = io_lib:format("The function ~s is defined at ~s but never used", [FunName, pp_loc(Ann)]),
|
||||||
|
aeso_warnings:new(pos(Ann), Msg);
|
||||||
|
mk_warning({shadowing, Ann, VarName, AnnOld}) ->
|
||||||
|
Msg = io_lib:format("The definition of ~s at ~s shadows an older definition at ~s", [VarName, pp_loc(Ann), pp_loc(AnnOld)]),
|
||||||
|
aeso_warnings:new(pos(Ann), Msg);
|
||||||
|
mk_warning({division_by_zero, Ann}) ->
|
||||||
|
Msg = io_lib:format("Division by zero at ~s", [pp_loc(Ann)]),
|
||||||
|
aeso_warnings:new(pos(Ann), Msg);
|
||||||
|
mk_warning({negative_spend, Ann}) ->
|
||||||
|
Msg = io_lib:format("Negative spend at ~s", [pp_loc(Ann)]),
|
||||||
|
aeso_warnings:new(pos(Ann), Msg);
|
||||||
|
mk_warning(Warn) ->
|
||||||
|
Msg = io_lib:format("Unknown warning: ~p\n", [Warn]),
|
||||||
|
aeso_warnings:new(Msg).
|
||||||
|
|
||||||
mk_entrypoint(Decl) ->
|
mk_entrypoint(Decl) ->
|
||||||
Ann = [entrypoint | lists:keydelete(public, 1,
|
Ann = [entrypoint | lists:keydelete(public, 1,
|
||||||
lists:keydelete(private, 1,
|
lists:keydelete(private, 1,
|
||||||
|
@ -120,7 +120,8 @@ from_string(Backend, ContractString, Options) ->
|
|||||||
|
|
||||||
from_string1(aevm, ContractString, Options) ->
|
from_string1(aevm, ContractString, Options) ->
|
||||||
#{ icode := Icode
|
#{ icode := Icode
|
||||||
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
|
, folded_typed_ast := FoldedTypedAst
|
||||||
|
, warnings := Warnings } = string_to_code(ContractString, Options),
|
||||||
TypeInfo = extract_type_info(Icode),
|
TypeInfo = extract_type_info(Icode),
|
||||||
Assembler = assemble(Icode, Options),
|
Assembler = assemble(Icode, Options),
|
||||||
pp_assembler(aevm, Assembler, Options),
|
pp_assembler(aevm, Assembler, Options),
|
||||||
@ -133,13 +134,15 @@ from_string1(aevm, ContractString, Options) ->
|
|||||||
contract_source => ContractString,
|
contract_source => ContractString,
|
||||||
type_info => TypeInfo,
|
type_info => TypeInfo,
|
||||||
abi_version => aeb_aevm_abi:abi_version(),
|
abi_version => aeb_aevm_abi:abi_version(),
|
||||||
payable => maps:get(payable, Icode)
|
payable => maps:get(payable, Icode),
|
||||||
|
warnings => Warnings
|
||||||
},
|
},
|
||||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
|
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
|
||||||
from_string1(fate, ContractString, Options) ->
|
from_string1(fate, ContractString, Options) ->
|
||||||
#{ fcode := FCode
|
#{ fcode := FCode
|
||||||
, fcode_env := #{child_con_env := ChildContracts}
|
, fcode_env := #{child_con_env := ChildContracts}
|
||||||
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
|
, folded_typed_ast := FoldedTypedAst
|
||||||
|
, warnings := Warnings } = string_to_code(ContractString, Options),
|
||||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
|
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
|
||||||
pp_assembler(fate, FateCode, Options),
|
pp_assembler(fate, FateCode, Options),
|
||||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||||
@ -150,7 +153,8 @@ from_string1(fate, ContractString, Options) ->
|
|||||||
type_info => [],
|
type_info => [],
|
||||||
fate_code => FateCode,
|
fate_code => FateCode,
|
||||||
abi_version => aeb_fate_abi:abi_version(),
|
abi_version => aeb_fate_abi:abi_version(),
|
||||||
payable => maps:get(payable, FCode)
|
payable => maps:get(payable, FCode),
|
||||||
|
warnings => Warnings
|
||||||
},
|
},
|
||||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
|
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
|
||||||
|
|
||||||
@ -168,7 +172,7 @@ string_to_code(ContractString, Options) ->
|
|||||||
Ast = parse(ContractString, Options),
|
Ast = parse(ContractString, Options),
|
||||||
pp_sophia_code(Ast, Options),
|
pp_sophia_code(Ast, Options),
|
||||||
pp_ast(Ast, Options),
|
pp_ast(Ast, Options),
|
||||||
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
|
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst, Warnings} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
|
||||||
pp_typed_ast(UnfoldedTypedAst, Options),
|
pp_typed_ast(UnfoldedTypedAst, Options),
|
||||||
case proplists:get_value(backend, Options, aevm) of
|
case proplists:get_value(backend, Options, aevm) of
|
||||||
aevm ->
|
aevm ->
|
||||||
@ -178,7 +182,8 @@ string_to_code(ContractString, Options) ->
|
|||||||
, unfolded_typed_ast => UnfoldedTypedAst
|
, unfolded_typed_ast => UnfoldedTypedAst
|
||||||
, folded_typed_ast => FoldedTypedAst
|
, folded_typed_ast => FoldedTypedAst
|
||||||
, type_env => TypeEnv
|
, type_env => TypeEnv
|
||||||
, ast => Ast };
|
, ast => Ast
|
||||||
|
, warnings => Warnings};
|
||||||
fate ->
|
fate ->
|
||||||
{Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]),
|
{Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]),
|
||||||
#{ fcode => Fcode
|
#{ fcode => Fcode
|
||||||
@ -186,7 +191,8 @@ string_to_code(ContractString, Options) ->
|
|||||||
, unfolded_typed_ast => UnfoldedTypedAst
|
, unfolded_typed_ast => UnfoldedTypedAst
|
||||||
, folded_typed_ast => FoldedTypedAst
|
, folded_typed_ast => FoldedTypedAst
|
||||||
, type_env => TypeEnv
|
, type_env => TypeEnv
|
||||||
, ast => Ast }
|
, ast => Ast
|
||||||
|
, warnings => Warnings }
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-define(CALL_NAME, "__call").
|
-define(CALL_NAME, "__call").
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
, pos/2
|
, pos/2
|
||||||
, pos/3
|
, pos/3
|
||||||
, pp/1
|
, pp/1
|
||||||
|
, pp_pos/1
|
||||||
, to_json/1
|
, to_json/1
|
||||||
, throw/1
|
, throw/1
|
||||||
, type/1
|
, type/1
|
||||||
|
@ -15,7 +15,8 @@
|
|||||||
many/1, many1/1, sep/2, sep1/2,
|
many/1, many1/1, sep/2, sep1/2,
|
||||||
infixl/2, infixr/2]).
|
infixl/2, infixr/2]).
|
||||||
|
|
||||||
-export([current_file/0, set_current_file/1]).
|
-export([current_file/0, set_current_file/1,
|
||||||
|
current_include_type/0, set_current_include_type/1]).
|
||||||
|
|
||||||
%% -- Types ------------------------------------------------------------------
|
%% -- Types ------------------------------------------------------------------
|
||||||
|
|
||||||
@ -465,6 +466,13 @@ merge_with(Fun, Map1, Map2) ->
|
|||||||
end, Map2, maps:to_list(Map1))
|
end, Map2, maps:to_list(Map1))
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% Current include type
|
||||||
|
current_include_type() ->
|
||||||
|
get('$current_include_type').
|
||||||
|
|
||||||
|
set_current_include_type(IncludeType) ->
|
||||||
|
put('$current_include_type', IncludeType).
|
||||||
|
|
||||||
%% Current source file
|
%% Current source file
|
||||||
current_file() ->
|
current_file() ->
|
||||||
get('$current_file').
|
get('$current_file').
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
run_parser/3]).
|
run_parser/3]).
|
||||||
|
|
||||||
-include("aeso_parse_lib.hrl").
|
-include("aeso_parse_lib.hrl").
|
||||||
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
|
-import(aeso_parse_lib, [current_file/0, set_current_file/1,
|
||||||
|
current_include_type/0, set_current_include_type/1]).
|
||||||
|
|
||||||
-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none().
|
-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none().
|
||||||
|
|
||||||
@ -57,6 +58,7 @@ run_parser(P, Inp, Opts) ->
|
|||||||
|
|
||||||
parse_and_scan(P, S, Opts) ->
|
parse_and_scan(P, S, Opts) ->
|
||||||
set_current_file(proplists:get_value(src_file, Opts, no_file)),
|
set_current_file(proplists:get_value(src_file, Opts, no_file)),
|
||||||
|
set_current_include_type(proplists:get_value(include_type, Opts, none)),
|
||||||
case aeso_scan:scan(S) of
|
case aeso_scan:scan(S) of
|
||||||
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
||||||
{error, {{Input, Pos}, _}} ->
|
{error, {{Input, Pos}, _}} ->
|
||||||
@ -523,7 +525,11 @@ bracket_list(P) -> brackets(comma_sep(P)).
|
|||||||
-type ann_col() :: aeso_syntax:ann_col().
|
-type ann_col() :: aeso_syntax:ann_col().
|
||||||
|
|
||||||
-spec pos_ann(ann_line(), ann_col()) -> ann().
|
-spec pos_ann(ann_line(), ann_col()) -> ann().
|
||||||
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
|
pos_ann(Line, Col) ->
|
||||||
|
[ {file, current_file()}
|
||||||
|
, {include_type, current_include_type()}
|
||||||
|
, {line, Line}
|
||||||
|
, {col, Col} ].
|
||||||
|
|
||||||
ann_pos(Ann) ->
|
ann_pos(Ann) ->
|
||||||
{proplists:get_value(file, Ann),
|
{proplists:get_value(file, Ann),
|
||||||
@ -665,9 +671,14 @@ expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Op
|
|||||||
Hashed = hash_include(File, Code),
|
Hashed = hash_include(File, Code),
|
||||||
case sets:is_element(Hashed, Included) of
|
case sets:is_element(Hashed, Included) of
|
||||||
false ->
|
false ->
|
||||||
|
IncludeType = case proplists:get_value(file, Ann) of
|
||||||
|
no_file -> direct;
|
||||||
|
_ -> indirect
|
||||||
|
end,
|
||||||
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
|
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
|
||||||
|
Opts2 = lists:keystore(include_type, 1, Opts1, {include_type, IncludeType}),
|
||||||
Included1 = sets:add_element(Hashed, Included),
|
Included1 = sets:add_element(Hashed, Included),
|
||||||
case parse_and_scan(file(), Code, Opts1) of
|
case parse_and_scan(file(), Code, Opts2) of
|
||||||
{ok, AST1} ->
|
{ok, AST1} ->
|
||||||
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
|
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
|
||||||
Err = {error, _} ->
|
Err = {error, _} ->
|
||||||
|
27
src/aeso_warnings.erl
Normal file
27
src/aeso_warnings.erl
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-module(aeso_warnings).
|
||||||
|
|
||||||
|
-record(warn, { pos :: aeso_errors:pos()
|
||||||
|
, message :: iolist()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-opaque warning() :: #warn{}.
|
||||||
|
|
||||||
|
-export_type([warning/0]).
|
||||||
|
|
||||||
|
-export([ new/1
|
||||||
|
, new/2
|
||||||
|
, warn_to_err/2
|
||||||
|
, pp/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
new(Msg) ->
|
||||||
|
new(aeso_errors:pos(0, 0), Msg).
|
||||||
|
|
||||||
|
new(Pos, Msg) ->
|
||||||
|
#warn{ pos = Pos, message = Msg }.
|
||||||
|
|
||||||
|
warn_to_err(Kind, #warn{ pos = Pos, message = Msg }) ->
|
||||||
|
aeso_errors:new(Kind, Pos, lists:flatten(Msg)).
|
||||||
|
|
||||||
|
pp(#warn{ pos = Pos, message = Msg }) ->
|
||||||
|
lists:flatten(io_lib:format("Warning~s:\n~s", [aeso_errors:pp_pos(Pos), Msg])).
|
@ -46,7 +46,7 @@ simple_compile_test_() ->
|
|||||||
end} ] ++
|
end} ] ++
|
||||||
[ {"Testing error messages of " ++ ContractName,
|
[ {"Testing error messages of " ++ ContractName,
|
||||||
fun() ->
|
fun() ->
|
||||||
Errors = compile(aevm, ContractName),
|
Errors = compile(aevm, ContractName, [warn_all, warn_error]),
|
||||||
check_errors(ExpectedErrors, Errors)
|
check_errors(ExpectedErrors, Errors)
|
||||||
end} ||
|
end} ||
|
||||||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
|
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
|
||||||
@ -88,6 +88,11 @@ simple_compile_test_() ->
|
|||||||
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}),
|
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}),
|
||||||
ok
|
ok
|
||||||
end} || Backend <- [aevm, fate] ] ++
|
end} || Backend <- [aevm, fate] ] ++
|
||||||
|
[ {"Testing warning messages",
|
||||||
|
fun() ->
|
||||||
|
#{ warnings := Warnings } = compile(Backend, "warnings", [warn_all]),
|
||||||
|
check_warnings(warnings(), Warnings)
|
||||||
|
end} || Backend <- [aevm, fate] ] ++
|
||||||
[].
|
[].
|
||||||
|
|
||||||
%% Check if all modules in the standard library compile
|
%% Check if all modules in the standard library compile
|
||||||
@ -119,6 +124,15 @@ check_errors(Expect0, Actual0) ->
|
|||||||
{Missing, Extra} -> ?assertEqual(Missing, Extra)
|
{Missing, Extra} -> ?assertEqual(Missing, Extra)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
check_warnings(Expect0, Actual0) ->
|
||||||
|
Expect = lists:sort(Expect0),
|
||||||
|
Actual = [ list_to_binary(string:trim(aeso_warnings:pp(Warn))) || Warn <- Actual0 ],
|
||||||
|
case {Expect -- Actual, Actual -- Expect} of
|
||||||
|
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
|
||||||
|
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
|
||||||
|
{Missing, Extra} -> ?assertEqual(Missing, Extra)
|
||||||
|
end.
|
||||||
|
|
||||||
compile(Backend, Name) ->
|
compile(Backend, Name) ->
|
||||||
compile(Backend, Name,
|
compile(Backend, Name,
|
||||||
[{include, {file_system, [aeso_test_utils:contract_path()]}}]).
|
[{include, {file_system, [aeso_test_utils:contract_path()]}}]).
|
||||||
@ -228,6 +242,49 @@ debug_mode_contracts() ->
|
|||||||
-define(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)).
|
-define(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)).
|
||||||
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
|
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
|
||||||
|
|
||||||
|
-define(PosW(Kind, File, Line, Col), (list_to_binary(Kind))/binary, " in '",
|
||||||
|
(list_to_binary(File))/binary, ".aes' at line " ??Line ", col " ??Col ":\n").
|
||||||
|
-define(PosW(Line, Col), ?PosW(__Kind, __File, Line, Col)).
|
||||||
|
|
||||||
|
-define(WARNING(Name, Warns),
|
||||||
|
(fun() ->
|
||||||
|
__Kind = "Warning",
|
||||||
|
__File = ??Name,
|
||||||
|
Warns
|
||||||
|
end)()).
|
||||||
|
|
||||||
|
warnings() ->
|
||||||
|
?WARNING(warnings,
|
||||||
|
[<<?PosW(0, 0)
|
||||||
|
"The file Triple.aes is included but not used">>,
|
||||||
|
<<?PosW(13, 3)
|
||||||
|
"The function h is defined at line 13, column 3 but never used">>,
|
||||||
|
<<?PosW(19, 3)
|
||||||
|
"The type unused_type is defined at line 19, column 3 but never used">>,
|
||||||
|
<<?PosW(23, 54)
|
||||||
|
"Negative spend at line 23, column 54">>,
|
||||||
|
<<?PosW(27, 9)
|
||||||
|
"The definition of x at line 27, column 9 shadows an older definition at line 26, column 9">>,
|
||||||
|
<<?PosW(30, 36)
|
||||||
|
"Division by zero at line 30, column 36">>,
|
||||||
|
<<?PosW(32, 3)
|
||||||
|
"The function unused_stateful is unnecessarily marked as stateful at line 32, column 3">>,
|
||||||
|
<<?PosW(35, 31)
|
||||||
|
"The variable unused_arg is defined at line 35, column 31 but never used">>,
|
||||||
|
<<?PosW(36, 9)
|
||||||
|
"The variable unused_var is defined at line 36, column 9 but never used">>,
|
||||||
|
<<?PosW(41, 3)
|
||||||
|
"The function unused_function is defined at line 41, column 3 but never used">>,
|
||||||
|
<<?PosW(42, 3)
|
||||||
|
"The function recursive_unused_function is defined at line 42, column 3 but never used">>,
|
||||||
|
<<?PosW(43, 3)
|
||||||
|
"The function called_unused_function1 is defined at line 43, column 3 but never used">>,
|
||||||
|
<<?PosW(44, 3)
|
||||||
|
"The function called_unused_function2 is defined at line 44, column 3 but never used">>,
|
||||||
|
<<?PosW(48, 5)
|
||||||
|
"Unused return value at line 48, column 5">>
|
||||||
|
]).
|
||||||
|
|
||||||
failing_contracts() ->
|
failing_contracts() ->
|
||||||
{ok, V} = aeso_compiler:numeric_version(),
|
{ok, V} = aeso_compiler:numeric_version(),
|
||||||
Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
|
Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
|
||||||
@ -817,6 +874,36 @@ failing_contracts() ->
|
|||||||
[<<?Pos(4,24)
|
[<<?Pos(4,24)
|
||||||
"Cannot unify string\n and bool\nwhen checking the type of the expression at line 4, column 24\n \"y\" : string\nagainst the expected type\n bool">>
|
"Cannot unify string\n and bool\nwhen checking the type of the expression at line 4, column 24\n \"y\" : string\nagainst the expected type\n bool">>
|
||||||
])
|
])
|
||||||
|
, ?TYPE_ERROR(warnings,
|
||||||
|
[<<?Pos(0, 0)
|
||||||
|
"The file Triple.aes is included but not used">>,
|
||||||
|
<<?Pos(13, 3)
|
||||||
|
"The function h is defined at line 13, column 3 but never used">>,
|
||||||
|
<<?Pos(19, 3)
|
||||||
|
"The type unused_type is defined at line 19, column 3 but never used">>,
|
||||||
|
<<?Pos(23, 54)
|
||||||
|
"Negative spend at line 23, column 54">>,
|
||||||
|
<<?Pos(27, 9)
|
||||||
|
"The definition of x at line 27, column 9 shadows an older definition at line 26, column 9">>,
|
||||||
|
<<?Pos(30, 36)
|
||||||
|
"Division by zero at line 30, column 36">>,
|
||||||
|
<<?Pos(32, 3)
|
||||||
|
"The function unused_stateful is unnecessarily marked as stateful at line 32, column 3">>,
|
||||||
|
<<?Pos(35, 31)
|
||||||
|
"The variable unused_arg is defined at line 35, column 31 but never used">>,
|
||||||
|
<<?Pos(36, 9)
|
||||||
|
"The variable unused_var is defined at line 36, column 9 but never used">>,
|
||||||
|
<<?Pos(41, 3)
|
||||||
|
"The function unused_function is defined at line 41, column 3 but never used">>,
|
||||||
|
<<?Pos(42, 3)
|
||||||
|
"The function recursive_unused_function is defined at line 42, column 3 but never used">>,
|
||||||
|
<<?Pos(43, 3)
|
||||||
|
"The function called_unused_function1 is defined at line 43, column 3 but never used">>,
|
||||||
|
<<?Pos(44, 3)
|
||||||
|
"The function called_unused_function2 is defined at line 44, column 3 but never used">>,
|
||||||
|
<<?Pos(48, 5)
|
||||||
|
"Unused return value at line 48, column 5">>
|
||||||
|
])
|
||||||
].
|
].
|
||||||
|
|
||||||
-define(Path(File), "code_errors/" ??File).
|
-define(Path(File), "code_errors/" ??File).
|
||||||
|
49
test/contracts/warnings.aes
Normal file
49
test/contracts/warnings.aes
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Used include
|
||||||
|
include "Pair.aes"
|
||||||
|
// Unused include
|
||||||
|
include "Triple.aes"
|
||||||
|
|
||||||
|
namespace UnusedNamespace =
|
||||||
|
function f() = 1 + g()
|
||||||
|
|
||||||
|
// Used in f
|
||||||
|
private function g() = 2
|
||||||
|
|
||||||
|
// Unused
|
||||||
|
private function h() = 3
|
||||||
|
|
||||||
|
contract Warnings =
|
||||||
|
|
||||||
|
type state = int
|
||||||
|
|
||||||
|
type unused_type = bool
|
||||||
|
|
||||||
|
entrypoint init(p) = Pair.fst(p) + Pair.snd(p)
|
||||||
|
|
||||||
|
stateful entrypoint negative_spend(to : address) = Chain.spend(to, -1)
|
||||||
|
|
||||||
|
entrypoint shadowing() =
|
||||||
|
let x = 1
|
||||||
|
let x = 2
|
||||||
|
x
|
||||||
|
|
||||||
|
entrypoint division_by_zero(x) = x / 0
|
||||||
|
|
||||||
|
stateful entrypoint unused_stateful() = 1
|
||||||
|
stateful entrypoint used_stateful(x : int) = put(x)
|
||||||
|
|
||||||
|
entrypoint unused_variables(unused_arg : int) =
|
||||||
|
let unused_var = 10
|
||||||
|
let z = 20
|
||||||
|
z
|
||||||
|
|
||||||
|
// Unused functions
|
||||||
|
function unused_function() = ()
|
||||||
|
function recursive_unused_function() = recursive_unused_function()
|
||||||
|
function called_unused_function1() = called_unused_function2()
|
||||||
|
function called_unused_function2() = called_unused_function1()
|
||||||
|
|
||||||
|
function rv() = 1
|
||||||
|
entrypoint unused_return_value() =
|
||||||
|
rv()
|
||||||
|
2
|
Loading…
x
Reference in New Issue
Block a user