Add compiler warnings (#346)
* Add compiler warnings Add include_type annotation to position Add warning for unused includes Add warning for unused stateful annotation Add warning for unused functions Add warning for shadowed variables Add division by zero warning Add warning for negative spends Add warning for unused variables Add warning for unused parameters Change the ets table type to set for unused vars Add warning for unused type defs Move unused variables warning to the top level Temporarily disable unused functions warnings Add all kinds of warnings to a single ets table Enable warnings separately through options Use when_option instead of enabled_warnings Turn warnings into type errors with warn_error option Enable warning package warn_all Re-enable unused functions warnings Report warnings as type errors in a separate function Make unused_function a recognized warning Report warnings as a result of compilation Fix tests and error for unknown warnings options Fix dialyzer warnings Do not show warning for variables called "_" Move warnings handling into a separate module Do not show warning for unused public functions in namespaces Add src file name to unused include warning Mark public functions in namespaces as used Add tests for added warnings Add warning for unused return value Add test for turning warnings into type errors * Update CHANGELOG
This commit is contained in:
parent
98a4049f03
commit
fe5f5545d3
@ -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