From a3405ee0d261cc7bddf4ae28e8b02de41397f1c3 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 30 Jan 2022 15:02:43 +0400 Subject: [PATCH 01/10] Fix stdlib warnings --- priv/stdlib/BLS12_381.aes | 8 ++++---- priv/stdlib/Bitwise.aes | 10 ---------- priv/stdlib/List.aes | 6 +++--- priv/stdlib/ListInternal.aes | 4 ++-- priv/stdlib/String.aes | 32 ++++++++++++++++---------------- 5 files changed, 25 insertions(+), 35 deletions(-) diff --git a/priv/stdlib/BLS12_381.aes b/priv/stdlib/BLS12_381.aes index 4bc27ff..21eee16 100644 --- a/priv/stdlib/BLS12_381.aes +++ b/priv/stdlib/BLS12_381.aes @@ -7,13 +7,13 @@ namespace BLS12_381 = record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp, x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp } - function pairing_check(xs : list(g1), ys : list(g2)) = - switch((xs, ys)) + function pairing_check(us : list(g1), vs : list(g2)) = + switch((us, vs)) ([], []) => true (x :: xs, y :: ys) => pairing_check_(pairing(x, y), xs, ys) - function pairing_check_(acc : gt, xs : list(g1), ys : list(g2)) = - switch((xs, ys)) + function pairing_check_(acc : gt, us : list(g1), vs : list(g2)) = + switch((us, vs)) ([], []) => gt_is_one(acc) (x :: xs, y :: ys) => pairing_check_(gt_mul(acc, pairing(x, y)), xs, ys) diff --git a/priv/stdlib/Bitwise.aes b/priv/stdlib/Bitwise.aes index 63be770..cc273f0 100644 --- a/priv/stdlib/Bitwise.aes +++ b/priv/stdlib/Bitwise.aes @@ -116,16 +116,6 @@ namespace Bitwise = 1 => ubxor__(a / 2, b / 2, val * 2, acc + val) _ => ubxor__(a / 2, b / 2, val * 2, acc) - // Bitwise combined 'and' and 'not' of second argument for positive integers - // x 'bnand' y = x 'band' ('bnot' y) - // The tricky bit is that after negation the second argument has an infinite number of 1's - // use as many as needed! - // - // NOTE: this function is not symmetric! - private function ubnand(a, b) = - require(a >= 0 && b >= 0, "ubxor is only defined for non-negative integers") - ubnand__(a, b, 1, 0) - private function ubnand_(a, b) = ubnand__(a, b, 1, 0) private function diff --git a/priv/stdlib/List.aes b/priv/stdlib/List.aes index cf9670e..e0201d0 100644 --- a/priv/stdlib/List.aes +++ b/priv/stdlib/List.aes @@ -173,7 +173,7 @@ namespace List = if (n == 0) l else switch(l) [] => [] - h::t => drop_(n-1, t) + _::t => drop_(n-1, t) /** Get the longest prefix of a list in which every element * matches predicate `p` @@ -191,7 +191,7 @@ namespace List = /** Splits list into two lists of elements that respectively * match and don't match predicate `p` */ - function partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) = switch(l) + function partition(p : 'a => bool, lst : list('a)) : (list('a) * list('a)) = switch(lst) [] => ([], []) h::t => let (l, r) = partition(p, t) @@ -313,4 +313,4 @@ namespace List = function enumerate(l : list('a)) : list(int * 'a) = enumerate_(l, 0) private function enumerate_(l : list('a), n : int) : list(int * 'a) = switch(l) [] => [] - h::t => (n, h)::enumerate_(t, n + 1) \ No newline at end of file + h::t => (n, h)::enumerate_(t, n + 1) diff --git a/priv/stdlib/ListInternal.aes b/priv/stdlib/ListInternal.aes index 7d6773a..3909283 100644 --- a/priv/stdlib/ListInternal.aes +++ b/priv/stdlib/ListInternal.aes @@ -2,8 +2,8 @@ namespace ListInternal = // -- Flatmap ---------------------------------------------------------------- - function flat_map(f : 'a => list('b), xs : list('a)) : list('b) = - switch(xs) + function flat_map(f : 'a => list('b), lst : list('a)) : list('b) = + switch(lst) [] => [] x :: xs => f(x) ++ flat_map(f, xs) diff --git a/priv/stdlib/String.aes b/priv/stdlib/String.aes index 36c54ba..33f813b 100644 --- a/priv/stdlib/String.aes +++ b/priv/stdlib/String.aes @@ -53,21 +53,21 @@ namespace String = // Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string // into an integer. If the string doesn't contain a valid number `None` is returned. - function to_int(s : string) : option(int) = - let s = StringInternal.to_list(s) - switch(is_prefix(['-'], s)) - None => to_int_pos(s) + function to_int(str : string) : option(int) = + let lst = StringInternal.to_list(str) + switch(is_prefix(['-'], lst)) + None => to_int_pos(lst) Some(s) => switch(to_int_pos(s)) None => None Some(x) => Some(-x) // Private helper functions below - private function to_int_pos(s : list(char)) = - switch(is_prefix(['0', 'x'], s)) + private function to_int_pos(chs : list(char)) = + switch(is_prefix(['0', 'x'], chs)) None => - to_int_(s, ch_to_int_10, 0, 10) - Some(s) => - to_int_(s, ch_to_int_16, 0, 16) + to_int_(chs, ch_to_int_10, 0, 10) + Some(str) => + to_int_(str, ch_to_int_16, 0, 16) private function tokens_(_, [], acc) = [StringInternal.from_list(List.reverse(acc))] @@ -84,8 +84,8 @@ namespace String = contains_(ix, str, substr) = switch(is_prefix(substr, str)) None => - let _ :: str = str - contains_(ix + 1, str, substr) + let _ :: tailstr = str + contains_(ix + 1, tailstr, substr) Some(_) => Some(ix) @@ -101,15 +101,15 @@ namespace String = to_int_(i :: is, value, x, b) = switch(value(i)) None => None - Some(i) => to_int_(is, value, x * b + i, b) + Some(n) => to_int_(is, value, x * b + n, b) - private function ch_to_int_10(c) = - let c = Char.to_int(c) + private function ch_to_int_10(ch) = + let c = Char.to_int(ch) if(c >= 48 && c =< 57) Some(c - 48) else None - private function ch_to_int_16(c) = - let c = Char.to_int(c) + private function ch_to_int_16(ch) = + let c = Char.to_int(ch) if(c >= 48 && c =< 57) Some(c - 48) elif(c >= 65 && c =< 70) Some(c - 55) elif(c >= 97 && c =< 102) Some(c - 87) -- 2.30.2 From c943148d2d7e91478315fece0d0db57da0c093e2 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 30 Jan 2022 17:11:17 +0400 Subject: [PATCH 02/10] Mark unused includes when used from non-included files --- src/aeso_ast_infer_types.erl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index d7fd152..b03834e 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -375,7 +375,13 @@ lookup_env(Env, Kind, Ann, Name) -> case [ Res || QName <- Names, Res <- [lookup_env1(Env, Kind, Ann, QName)], Res /= false] of [] -> false; [Res = {_, {AnnR, _}}] -> - when_warning(warn_unused_includes, fun() -> used_include(AnnR) end), + when_warning(warn_unused_includes, + fun() -> + case proplists:get_value(include_type, Ann, none) of + none -> used_include(AnnR); + _ -> ok + end + end), Res; Many -> type_error({ambiguous_name, qid(Ann, Name), [{qid, A, Q} || {Q, {A, _}} <- Many]}), -- 2.30.2 From 1b8be7a7495d3579a5215aba1456c91923180a09 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 30 Jan 2022 17:48:51 +0400 Subject: [PATCH 03/10] Do not mark indirectly included files as unused --- src/aeso_ast_infer_types.erl | 11 ++++++++++- src/aeso_parser.erl | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index b03834e..4378271 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -857,7 +857,16 @@ infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options) Env3 = bind_contract(Contract1, Env2), infer1(Env3, Rest, [Contract1 | 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), + when_warning(warn_unused_includes, + fun() -> + case proplists:get_value(include_type, Ann, none) of + direct -> + SrcFile = proplists:get_value(src_file, Options, no_file), + potential_unused_include(Ann, SrcFile); + _ -> + ok + end + end), check_scope_name_clash(Env, namespace, Name), {Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options), Namespace1 = {namespace, Ann, Name, Code1}, diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 9175e0f..51b81f6 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -685,8 +685,9 @@ expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Op Hashed = hash_include(File, Code), case sets:is_element(Hashed, Included) of false -> + SrcFile = proplists:get_value(src_file, Opts, no_file), IncludeType = case proplists:get_value(file, Ann) of - no_file -> direct; + SrcFile -> direct; _ -> indirect end, Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}), -- 2.30.2 From e6e6df9d21c3e9f8c54db6ad92d81445e32f8766 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 30 Jan 2022 18:25:04 +0400 Subject: [PATCH 04/10] Show unused include warning only for files that are never used --- src/aeso_ast_infer_types.erl | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 4378271..eff0aa0 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -377,9 +377,15 @@ lookup_env(Env, Kind, Ann, Name) -> [Res = {_, {AnnR, _}}] -> when_warning(warn_unused_includes, fun() -> - case proplists:get_value(include_type, Ann, none) of - none -> used_include(AnnR); - _ -> ok + %% If a file is used from a different file, we + %% can then mark it as used + F1 = proplists:get_value(file, Ann, no_file), + F2 = proplists:get_value(file, AnnR, no_file), + if + F1 /= F2 -> + used_include(AnnR); + true -> + ok end end), Res; @@ -859,13 +865,8 @@ infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options) infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) -> when_warning(warn_unused_includes, fun() -> - case proplists:get_value(include_type, Ann, none) of - direct -> - SrcFile = proplists:get_value(src_file, Options, no_file), - potential_unused_include(Ann, SrcFile); - _ -> - ok - end + SrcFile = proplists:get_value(src_file, Options, no_file), + potential_unused_include(Ann, SrcFile) end), check_scope_name_clash(Env, namespace, Name), {Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options), -- 2.30.2 From 9700bd9efed5bbe51a8a916769cb943d990fb0a7 Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Sun, 30 Jan 2022 18:49:14 +0400 Subject: [PATCH 05/10] Remove unused include from Option.aes --- priv/stdlib/Option.aes | 2 -- 1 file changed, 2 deletions(-) diff --git a/priv/stdlib/Option.aes b/priv/stdlib/Option.aes index 45e6594..651a2d5 100644 --- a/priv/stdlib/Option.aes +++ b/priv/stdlib/Option.aes @@ -1,5 +1,3 @@ -include "List.aes" - namespace Option = function is_none(o : option('a)) : bool = switch(o) -- 2.30.2 From 9153735a8503990f4536e3166f3dd86c69ffda2a Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Tue, 1 Feb 2022 19:04:20 +0400 Subject: [PATCH 06/10] Consider functions passed as args as used --- src/aeso_ast_infer_types.erl | 15 +++++---------- test/aeso_compiler_tests.erl | 8 ++++++-- test/contracts/warnings.aes | 11 +++++++++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index eff0aa0..3ce4836 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1456,13 +1456,15 @@ app_t(Ann, Name, Args) -> {app_t, Ann, Name, Args}. lookup_name(Env, As, Name) -> lookup_name(Env, As, Name, []). -lookup_name(Env = #env{ namespace = NS, current_function = {id, _, Fun} }, As, Id, Options) -> +lookup_name(Env = #env{ namespace = NS, current_function = {id, _, Fun} = CurFn }, As, Id, Options) -> case lookup_env(Env, term, As, qname(Id)) of false -> type_error({unbound_variable, Id}), {Id, fresh_uvar(As)}; {QId, {_, Ty}} -> when_warning(warn_unused_variables, fun() -> used_variable(NS, Fun, QId) end), + when_warning(warn_unused_functions, + fun() -> register_function_call(NS ++ qname(CurFn), QId) end), Freshen = proplists:get_value(freshen, Options, false), check_stateful(Env, Id, Ty), Ty1 = case Ty of @@ -1645,22 +1647,15 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> prefix -> infer_op(Env, Ann, Fun, Args, fun infer_prefix/1); _ -> - CurrentFun = Env#env.current_function, - Namespace = Env#env.namespace, NamedArgsVar = fresh_uvar(Ann), NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ], NewFun0 = infer_expr(Env, Fun), NewArgs = [infer_expr(Env, A) || A <- Args], ArgTypes = [T || {typed, _, _, T} <- NewArgs], - NewFun1 = {typed, _, Name, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes), + NewFun1 = {typed, _, _, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes), When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes}, GeneralResultType = fresh_uvar(Ann), ResultType = fresh_uvar(Ann), - when_warning(warn_unused_functions, - fun() -> if element(1, Name) == lam -> ok; - true -> register_function_call(Namespace ++ qname(CurrentFun), Name) - end - end), unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When), when_warning(warn_negative_spend, fun() -> warn_potential_negative_spend(Ann, NewFun1, NewArgs) end), add_constraint( @@ -2856,7 +2851,7 @@ create_unused_functions() -> register_function_call(_Caller, {proj, _, _, _}) -> ok; register_function_call(Caller, Callee) -> - ets_insert(function_calls, {Caller, qname(Callee)}). + ets_insert(function_calls, {Caller, 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)}); diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 13f07d0..0610e93 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -264,7 +264,9 @@ warnings() -> <>, <> + "Unused return value.">>, + <> ]). failing_contracts() -> @@ -831,7 +833,9 @@ failing_contracts() -> <>, <> + "Unused return value.">>, + <> ]) ]. diff --git a/test/contracts/warnings.aes b/test/contracts/warnings.aes index 51834b5..5aa05ce 100644 --- a/test/contracts/warnings.aes +++ b/test/contracts/warnings.aes @@ -47,3 +47,14 @@ contract Warnings = entrypoint unused_return_value() = rv() 2 + +namespace FunctionsAsArgs = + function f() = g() + + private function g() = h(inc) + private function h(fn : (int => int)) = fn(1) + + // Passed as arg to h in g + private function inc(n : int) : int = n + 1 + // Never used + private function dec(n : int) : int = n - 1 -- 2.30.2 From 0a4c49215e352595dd9cabd9f1a192159405004d Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Tue, 1 Feb 2022 19:37:16 +0400 Subject: [PATCH 07/10] Return warnings as a sorted list --- src/aeso_ast_infer_types.erl | 3 ++- src/aeso_warnings.erl | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 3ce4836..2324547 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -826,7 +826,8 @@ infer(Contracts, 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)), + WarningsUnsorted = lists:map(fun mk_warning/1, ets_tab2list(warnings)), + Warnings = aeso_warnings:sort_warnings(WarningsUnsorted), {Env2, DeclsFolded, DeclsUnfolded} = case proplists:get_value(dont_unfold, Options, false) of true -> {Env1, Decls, Decls}; diff --git a/src/aeso_warnings.erl b/src/aeso_warnings.erl index bf16edf..47daeae 100644 --- a/src/aeso_warnings.erl +++ b/src/aeso_warnings.erl @@ -11,6 +11,7 @@ -export([ new/1 , new/2 , warn_to_err/2 + , sort_warnings/1 , pp/1 ]). @@ -23,5 +24,8 @@ new(Pos, Msg) -> warn_to_err(Kind, #warn{ pos = Pos, message = Msg }) -> aeso_errors:new(Kind, Pos, lists:flatten(Msg)). +sort_warnings(Warnings) -> + lists:sort(fun(W1, W2) -> W1#warn.pos =< W2#warn.pos end, Warnings). + pp(#warn{ pos = Pos, message = Msg }) -> lists:flatten(io_lib:format("Warning~s:\n~s", [aeso_errors:pp_pos(Pos), Msg])). -- 2.30.2 From e8392189719432ed91d6406ba22311fe397345af Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Tue, 1 Feb 2022 19:46:19 +0400 Subject: [PATCH 08/10] Fix failing tests --- test/aeso_compiler_tests.erl | 2 +- test/contracts/manual_stdlib_include.aes | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 0610e93..6fccd21 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -834,7 +834,7 @@ failing_contracts() -> "The function `called_unused_function2` is defined but never used.">>, <>, - <> ]) ]. diff --git a/test/contracts/manual_stdlib_include.aes b/test/contracts/manual_stdlib_include.aes index 76a896e..8f59868 100644 --- a/test/contracts/manual_stdlib_include.aes +++ b/test/contracts/manual_stdlib_include.aes @@ -1,4 +1,4 @@ -// This should include Lists.aes implicitly, since Option.aes does. +include "List.aes" include "Option.aes" contract Test = -- 2.30.2 From e93167c6c43bc3f24b5fb17df1e077c83c1adb2e Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Tue, 1 Feb 2022 19:55:29 +0400 Subject: [PATCH 09/10] Fix dialyzer warning --- src/aeso_ast_infer_types.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 2324547..72c9372 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2850,7 +2850,6 @@ 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, Callee}). -- 2.30.2 From 1e1c2c1dfdf70af0878be21c1bf24cf27b3688fd Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Wed, 2 Feb 2022 12:26:35 +0400 Subject: [PATCH 10/10] Fix warning in Func.aes --- priv/stdlib/Func.aes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priv/stdlib/Func.aes b/priv/stdlib/Func.aes index 42cef77..5c4585e 100644 --- a/priv/stdlib/Func.aes +++ b/priv/stdlib/Func.aes @@ -2,7 +2,7 @@ namespace Func = function id(x : 'a) : 'a = x - function const(x : 'a) : 'b => 'a = (y) => x + function const(x : 'a) : 'b => 'a = (_) => x function flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c = (b, a) => f(a, b) -- 2.30.2