232 lines
8.8 KiB
Erlang
232 lines
8.8 KiB
Erlang
-module(aeso_tc_warnings).
|
|
|
|
-export([ warn_potential_shadowing/3
|
|
, used_include/1
|
|
, create_unused_functions/0
|
|
, destroy_and_report_unused_functions/0
|
|
, destroy_and_report_warnings_as_type_errors/0
|
|
, potential_unused_include/2
|
|
, potential_unused_typedefs/2
|
|
, potential_unused_constants/2
|
|
, potential_unused_stateful/2
|
|
, potential_unused_variables/3
|
|
, potential_unused_function/4
|
|
, mk_warning/1
|
|
, used_variable/3
|
|
, register_function_call/2
|
|
, used_constant/2
|
|
, used_stateful/1
|
|
, warn_potential_negative_spend/3
|
|
, warn_potential_division_by_zero/3
|
|
, potential_unused_return_value/1
|
|
, used_typedef/2
|
|
, all_warnings/0
|
|
]).
|
|
|
|
%% -- Moved functions --------------------------------------------------------
|
|
|
|
name(A) -> aeso_tc_name_manip:name(A).
|
|
qname(A) -> aeso_tc_name_manip:qname(A).
|
|
|
|
%% -------
|
|
|
|
pos(A) -> aeso_tc_ann_manip:pos(A).
|
|
|
|
%% -------
|
|
|
|
pp_loc(A) -> aeso_tc_pp:pp_loc(A).
|
|
|
|
%% ---------------------------------------------------------------------------
|
|
|
|
all_warnings() ->
|
|
[ warn_unused_includes
|
|
, warn_unused_stateful
|
|
, warn_unused_variables
|
|
, warn_unused_constants
|
|
, warn_unused_typedefs
|
|
, warn_unused_return_value
|
|
, warn_unused_functions
|
|
, warn_shadowing
|
|
, warn_division_by_zero
|
|
, warn_negative_spend ].
|
|
|
|
%% Warnings (Unused includes)
|
|
|
|
potential_unused_include(Ann, SrcFile) ->
|
|
IsIncluded = aeso_syntax:get_ann(include_type, Ann, none) =/= none,
|
|
case IsIncluded of
|
|
false -> ok;
|
|
true ->
|
|
case aeso_syntax:get_ann(file, Ann, no_file) of
|
|
no_file -> ok;
|
|
File -> aeso_tc_ets_manager:ets_insert(warnings, {unused_include, File, SrcFile})
|
|
end
|
|
end.
|
|
|
|
used_include(Ann) ->
|
|
case aeso_syntax:get_ann(file, Ann, no_file) of
|
|
no_file -> ok;
|
|
File -> aeso_tc_ets_manager: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 -> aeso_tc_ets_manager:ets_insert(warnings, {unused_stateful, Ann, Fun})
|
|
end.
|
|
|
|
used_stateful(Fun) ->
|
|
aeso_tc_ets_manager:ets_match_delete(warnings, {unused_stateful, '_', Fun}).
|
|
|
|
%% Warnings (Unused type defs)
|
|
|
|
potential_unused_typedefs(Namespace, TypeDefs) ->
|
|
lists:map(fun({type_def, Ann, Id, Args, _}) ->
|
|
aeso_tc_ets_manager:ets_insert(warnings, {unused_typedef, Ann, Namespace ++ qname(Id), length(Args)}) end, TypeDefs).
|
|
|
|
used_typedef(TypeAliasId, Arity) ->
|
|
aeso_tc_ets_manager: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}) ->
|
|
aeso_tc_ets_manager:ets_insert(warnings, {unused_variable, Ann, Namespace, Fun, VarName}) end, Vars).
|
|
|
|
used_variable(Namespace, Fun, [VarName]) ->
|
|
aeso_tc_ets_manager:ets_match_delete(warnings, {unused_variable, '_', Namespace, Fun, VarName});
|
|
used_variable(_, _, _) -> ok.
|
|
|
|
%% Warnings (Unused constants)
|
|
|
|
potential_unused_constants(Env, Consts) ->
|
|
case aeso_tc_env:what(Env) of
|
|
namespace -> [];
|
|
_ ->
|
|
[ aeso_tc_ets_manager:ets_insert(warnings, {unused_constant, Ann, aeso_tc_env:namespace(Env), Name}) || {letval, _, {id, Ann, Name}, _} <- Consts ]
|
|
end.
|
|
|
|
used_constant(Namespace = [Contract], [Contract, ConstName]) ->
|
|
aeso_tc_ets_manager:ets_match_delete(warnings, {unused_constant, '_', Namespace, ConstName});
|
|
used_constant(_, _) -> ok.
|
|
|
|
%% Warnings (Unused return value)
|
|
|
|
potential_unused_return_value({typed, Ann, {app, _, {typed, _, _, {fun_t, _, _, _, {id, _, Type}}}, _}, _}) when Type /= "unit" ->
|
|
aeso_tc_ets_manager:ets_insert(warnings, {unused_return_value, Ann});
|
|
potential_unused_return_value(_) -> ok.
|
|
|
|
%% Warnings (Unused functions)
|
|
|
|
create_unused_functions() ->
|
|
aeso_tc_ets_manager:ets_new(function_calls, [bag]),
|
|
aeso_tc_ets_manager:ets_new(all_functions, [set]).
|
|
|
|
register_function_call(Caller, Callee) ->
|
|
aeso_tc_ets_manager:ets_insert(function_calls, {Caller, Callee}).
|
|
|
|
potential_unused_function(Env, Ann, FunQName, FunId) ->
|
|
case aeso_tc_env:what(Env) of
|
|
namespace ->
|
|
aeso_tc_ets_manager:ets_insert(all_functions, {Ann, FunQName, FunId, not aeso_syntax:get_ann(private, Ann, false)});
|
|
_ ->
|
|
aeso_tc_ets_manager:ets_insert(all_functions, {Ann, FunQName, FunId, aeso_syntax:get_ann(entrypoint, Ann, false)})
|
|
end.
|
|
|
|
remove_used_funs(All) ->
|
|
{Used, Unused} = lists:partition(fun({_, _, _, IsUsed}) -> IsUsed end, All),
|
|
CallsByUsed = lists:flatmap(fun({_, F, _, _}) -> aeso_tc_ets_manager: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 = aeso_tc_ets_manager:ets_tab2list(all_functions),
|
|
lists:map(fun({Ann, _, FunId, _}) -> aeso_tc_ets_manager:ets_insert(warnings, {unused_function, Ann, name(FunId)}) end,
|
|
remove_used_funs(AllFuns)),
|
|
aeso_tc_ets_manager:ets_delete(all_functions),
|
|
aeso_tc_ets_manager:ets_delete(function_calls).
|
|
|
|
%% Warnings (Shadowing)
|
|
|
|
warn_potential_shadowing(_, _, "_") -> ok;
|
|
warn_potential_shadowing(Env, Ann, Name) ->
|
|
Vars = aeso_tc_env:vars(Env),
|
|
Consts = aeso_tc_env:scope_consts(aeso_tc_env:get_current_scope(Env)),
|
|
case proplists:get_value(Name, Vars ++ Consts, false) of
|
|
false -> ok;
|
|
{AnnOld, _} -> aeso_tc_ets_manager: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}]} -> aeso_tc_ets_manager: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 ->
|
|
aeso_tc_ets_manager:ets_insert(warnings, {negative_spend, Ann});
|
|
_ -> ok
|
|
end.
|
|
|
|
destroy_and_report_warnings_as_type_errors() ->
|
|
Warnings = [ mk_warning(Warn) || Warn <- aeso_tc_ets_manager:ets_tab2list(warnings) ],
|
|
Errors = lists:map(fun mk_t_err_from_warn/1, Warnings),
|
|
aeso_errors:throw(Errors). %% No-op if Warnings == []
|
|
|
|
mk_t_err_from_warn(Warn) ->
|
|
aeso_warnings:warn_to_err(type_error, Warn).
|
|
|
|
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.", [name(FunName)]),
|
|
aeso_warnings:new(pos(Ann), Msg);
|
|
mk_warning({unused_variable, Ann, _Namespace, _Fun, VarName}) ->
|
|
Msg = io_lib:format("The variable `~s` is defined but never used.", [VarName]),
|
|
aeso_warnings:new(pos(Ann), Msg);
|
|
mk_warning({unused_constant, Ann, _Namespace, ConstName}) ->
|
|
Msg = io_lib:format("The constant `~s` is defined but never used.", [ConstName]),
|
|
aeso_warnings:new(pos(Ann), Msg);
|
|
mk_warning({unused_typedef, Ann, QName, _Arity}) ->
|
|
Msg = io_lib:format("The type `~s` is defined but never used.", [lists:last(QName)]),
|
|
aeso_warnings:new(pos(Ann), Msg);
|
|
mk_warning({unused_return_value, Ann}) ->
|
|
Msg = io_lib:format("Unused return value.", []),
|
|
aeso_warnings:new(pos(Ann), Msg);
|
|
mk_warning({unused_function, Ann, FunName}) ->
|
|
Msg = io_lib:format("The function `~s` is defined but never used.", [FunName]),
|
|
aeso_warnings:new(pos(Ann), Msg);
|
|
mk_warning({shadowing, Ann, VarName, AnnOld}) ->
|
|
Msg = io_lib:format("The definition of `~s` shadows an older definition at ~s.", [VarName, pp_loc(AnnOld)]),
|
|
aeso_warnings:new(pos(Ann), Msg);
|
|
mk_warning({division_by_zero, Ann}) ->
|
|
Msg = io_lib:format("Division by zero.", []),
|
|
aeso_warnings:new(pos(Ann), Msg);
|
|
mk_warning({negative_spend, Ann}) ->
|
|
Msg = io_lib:format("Negative spend.", []),
|
|
aeso_warnings:new(pos(Ann), Msg);
|
|
mk_warning(Warn) ->
|
|
Msg = io_lib:format("Unknown warning: ~p", [Warn]),
|
|
aeso_warnings:new(Msg).
|