diff --git a/rebar.config b/rebar.config index bc761b8..2d24e73 100644 --- a/rebar.config +++ b/rebar.config @@ -23,7 +23,7 @@ {dialyzer, [ - {warnings, [error_handling, race_conditions, unmatched_returns, unknown]}, + {warnings, [error_handling, unmatched_returns, unknown]}, {plt_extra_apps, [compiler]} ]}. diff --git a/src/logi_location.erl b/src/logi_location.erl index bb30d28..ed53cb5 100644 --- a/src/logi_location.erl +++ b/src/logi_location.erl @@ -47,7 +47,7 @@ -export_type([location/0]). -export_type([map_form/0]). --export_type([application/0, line/0]). +-export_type([application/0, line/0, line_or_anno/0]). %%---------------------------------------------------------------------------------------------------------------------- %% Macros & Records & Types @@ -56,11 +56,11 @@ -record(?LOCATION, { - process :: pid(), - application :: application(), - module :: module(), - function :: atom(), - line :: line() + process :: pid(), + application :: application(), + module :: module(), + function :: atom(), + line_or_anno :: line_or_anno() }). -opaque location() :: #?LOCATION{}. @@ -84,34 +84,41 @@ %% %% `0' means "Unknown Line" +-type line_or_anno() :: line() | erl_anno:anno(). +%% A line number or an erl_anno:anno() +%% +%% Starting from OTP 23, the abstract format uses annos instead of line numbers. +%% This type absorbs the change. +%% See: https://www.erlang.org/docs/23/apps/erts/absform.html + %%---------------------------------------------------------------------------------------------------------------------- %% Exported Functions %%---------------------------------------------------------------------------------------------------------------------- %% @equiv new(self(), guess_application(Module), Module, Function, Line) --spec new(module(), atom(), line()) -> location(). -new(Module, Function, Line) -> - new(self(), guess_application(Module), Module, Function, Line). +-spec new(module(), atom(), line_or_anno()) -> location(). +new(Module, Function, LineOrAnno) -> + new(self(), guess_application(Module), Module, Function, LineOrAnno). %% @doc Creates a new location object --spec new(pid(), application(), module(), atom(), line()) -> location(). -new(Pid, Application, Module, Function, Line) -> - Args = [Pid, Application, Module, Function, Line], +-spec new(pid(), application(), module(), atom(), line_or_anno()) -> location(). +new(Pid, Application, Module, Function, LineOrAnno) -> + Args = [Pid, Application, Module, Function, LineOrAnno], _ = is_pid(Pid) orelse error(badarg, Args), _ = is_atom(Application) orelse error(badarg, Args), _ = is_atom(Module) orelse error(badarg, Args), _ = is_atom(Function) orelse error(badarg, Args), - _ = (is_integer(Line) andalso Line >= 0) orelse error(badarg, Args), - unsafe_new(Pid, Application, Module, Function, Line). + _ = (is_integer(LineOrAnno) andalso LineOrAnno >= 0) orelse erl_anno:is_anno(LineOrAnno) orelse error(badarg, Args), + unsafe_new(Pid, Application, Module, Function, LineOrAnno). %% @doc Equivalent to {@link new/5} except omission of the arguments validation --spec unsafe_new(pid(), application(), module(), atom(), line()) -> location(). -unsafe_new(Pid, Application, Module, Function, Line) -> +-spec unsafe_new(pid(), application(), module(), atom(), line_or_anno()) -> location(). +unsafe_new(Pid, Application, Module, Function, LineOrAnno) -> #?LOCATION{ - process = Pid, - application = Application, - module = Module, - function = Function, - line = Line + process = Pid, + application = Application, + module = Module, + function = Function, + line_or_anno = LineOrAnno }. %% @doc Returns `true' if `X' is a location object, `false' otherwise. @@ -145,11 +152,11 @@ from_map(Map) -> -spec to_map(Location :: location()) -> map_form(). to_map(L) -> #{ - process => L#?LOCATION.process, - application => L#?LOCATION.application, - module => L#?LOCATION.module, - function => L#?LOCATION.function, - line => L#?LOCATION.line + process => get_process(L), + application => get_application(L), + module => get_module(L), + function => get_function(L), + line => get_line(L) }. %% @doc Guesses the location where the function is called (parse transformation fallback) @@ -218,4 +225,5 @@ get_function(#?LOCATION{function = Function}) -> Function. %% @doc Gets the line of `Location' -spec get_line(Location :: location()) -> line(). -get_line(#?LOCATION{line = Line}) -> Line. +get_line(#?LOCATION{line_or_anno = LineOrAnno}) when is_integer(LineOrAnno) -> LineOrAnno; +get_line(#?LOCATION{line_or_anno = LineOrAnno}) -> erl_anno:line(LineOrAnno). diff --git a/src/logi_transform.erl b/src/logi_transform.erl index 378b255..5e141de 100644 --- a/src/logi_transform.erl +++ b/src/logi_transform.erl @@ -23,16 +23,16 @@ %% Exported API %%---------------------------------------------------------------------------------------------------------------------- -export([parse_transform/2]). --export_type([form/0, line/0, expr/0, expr_call_remote/0, expr_var/0]). +-export_type([form/0, line_or_anno/0, expr/0, expr_call_remote/0, expr_var/0]). %%---------------------------------------------------------------------------------------------------------------------- %% Types & Records %%---------------------------------------------------------------------------------------------------------------------- --type form() :: {attribute, line(), atom(), term()} - | {function, line(), atom(), non_neg_integer(), [clause()]} +-type form() :: {attribute, line_or_anno(), atom(), term()} + | {function, line_or_anno(), atom(), non_neg_integer(), [clause()]} | erl_parse:abstract_form(). --type clause() :: {clause, line(), [term()], [term()], [expr()]} +-type clause() :: {clause, line_or_anno(), [term()], [term()], [expr()]} | erl_parse:abstract_clause(). -type expr() :: expr_call_remote() @@ -40,17 +40,17 @@ | erl_parse:abstract_expr() | term(). --type expr_call_remote() :: {call, line(), {remote, line(), expr(), expr()}, [expr()]}. --type expr_var() :: {var, line(), atom()}. +-type expr_call_remote() :: {call, line_or_anno(), {remote, line_or_anno(), expr(), expr()}, [expr()]}. +-type expr_var() :: {var, line_or_anno(), atom()}. --type line() :: non_neg_integer(). +-type line_or_anno() :: non_neg_integer() | erl_anno:anno(). -record(location, { - application :: atom(), - module :: module(), - function :: atom(), - line :: line() + application :: atom(), + module :: module(), + function :: atom(), + line_or_anno :: line_or_anno() }). %%---------------------------------------------------------------------------------------------------------------------- @@ -60,9 +60,9 @@ -spec parse_transform([form()], [compile:option()]) -> [form()]. parse_transform(AbstractForms, Options) -> Loc = #location{ - application = logi_transform_utils:guess_application(AbstractForms, Options), - module = logi_transform_utils:get_module(AbstractForms), - line = 0 + application = logi_transform_utils:guess_application(AbstractForms, Options), + module = logi_transform_utils:get_module(AbstractForms), + line_or_anno = 0 }, walk_forms(AbstractForms, Loc). @@ -79,14 +79,14 @@ walk_forms(Forms, Loc) -> -spec walk_clauses([clause()], #location{}) -> [clause()]. walk_clauses(Clauses, Loc) -> [case Clause of - {clause, Line, Args, Guards, Body} -> {clause, Line, Args, Guards, [walk_expr(E, Loc) || E <- Body]}; - _ -> Clause + {clause, LineOrAnno, Args, Guards, Body} -> {clause, LineOrAnno, Args, Guards, [walk_expr(E, Loc) || E <- Body]}; + _ -> Clause end || Clause <- Clauses]. -spec walk_expr(expr(), #location{}) -> expr(). -walk_expr({call, Line, {remote, _, {atom, _, M}, {atom, _, F}}, _} = C0, Loc) -> +walk_expr({call, LineOrAnno, {remote, _, {atom, _, M}, {atom, _, F}}, _} = C0, Loc) -> C1 = list_to_tuple(walk_expr_parts(tuple_to_list(C0), Loc)), - transform_call(M, F, C1, Loc#location{line = Line}); + transform_call(M, F, C1, Loc#location{line_or_anno = LineOrAnno}); walk_expr(Expr, Loc) when is_tuple(Expr) -> list_to_tuple(walk_expr_parts(tuple_to_list(Expr), Loc)); walk_expr(Expr, Loc) when is_list(Expr) -> @@ -101,7 +101,7 @@ walk_expr_parts(Parts, Loc) -> -spec transform_call(module(), atom(), expr_call_remote(), #location{}) -> expr(). transform_call(logi_location, guess_location, _, Loc) -> logi_location_expr(Loc); -transform_call(logi, Severity0, {_, _, _, Args} = Call, Loc = #location{line = Line}) -> +transform_call(logi, Severity0, {_, _, _, Args} = Call, Loc = #location{line_or_anno = LineOrAnno}) -> Severity = normalize_severity(Severity0), case logi:is_severity(Severity) of false -> Call; @@ -109,17 +109,17 @@ transform_call(logi, Severity0, {_, _, _, Args} = Call, Loc = #location{line = L case Args of %% For maintaining compatibility with v0.0.12 [Logger, {string, _, _} = Fmt] -> - Opts = {cons, Line, {tuple, Line, [{atom, Line, logger}, Logger]}, {nil, Line}}, - logi_call_expr(Severity, Fmt, {nil, Line}, Opts, Loc); - [Logger, {string, _, _} = Fmt, {nil, Line} = Data] -> - Opts = {cons, Line, {tuple, Line, [{atom, Line, logger}, Logger]}, {nil, Line}}, + Opts = {cons, LineOrAnno, {tuple, LineOrAnno, [{atom, LineOrAnno, logger}, Logger]}, {nil, LineOrAnno}}, + logi_call_expr(Severity, Fmt, {nil, LineOrAnno}, Opts, Loc); + [Logger, {string, _, _} = Fmt, {nil, LineOrAnno} = Data] -> + Opts = {cons, LineOrAnno, {tuple, LineOrAnno, [{atom, LineOrAnno, logger}, Logger]}, {nil, LineOrAnno}}, logi_call_expr(Severity, Fmt, Data, Opts, Loc); [Logger, {string, _, _} = Fmt, {cons, _, _, _} = Data] -> - Opts = {cons, Line, {tuple, Line, [{atom, Line, logger}, Logger]}, {nil, Line}}, + Opts = {cons, LineOrAnno, {tuple, LineOrAnno, [{atom, LineOrAnno, logger}, Logger]}, {nil, LineOrAnno}}, logi_call_expr(Severity, Fmt, Data, Opts, Loc); - [Fmt] -> logi_call_expr(Severity, Fmt, {nil, Line}, {nil, Line}, Loc); - [Fmt, Data] -> logi_call_expr(Severity, Fmt, Data, {nil, Line}, Loc); + [Fmt] -> logi_call_expr(Severity, Fmt, {nil, LineOrAnno}, {nil, LineOrAnno}, Loc); + [Fmt, Data] -> logi_call_expr(Severity, Fmt, Data, {nil, LineOrAnno}, Loc); [Fmt, Data, Opts] -> logi_call_expr(Severity, Fmt, Data, Opts, Loc); _ -> Call end @@ -128,33 +128,33 @@ transform_call(_, _, Call, _Loc) -> Call. -spec logi_location_expr(#location{}) -> expr(). -logi_location_expr(Loc = #location{line = Line}) -> +logi_location_expr(Loc = #location{line_or_anno = LineOrAnno}) -> logi_transform_utils:make_call_remote( - Line, logi_location, unsafe_new, + LineOrAnno, logi_location, unsafe_new, [ - {call, Line, {atom, Line, self}, []}, - {atom, Line, Loc#location.application}, - {atom, Line, Loc#location.module}, - {atom, Line, Loc#location.function}, - {integer, Line, Line} + {call, LineOrAnno, {atom, LineOrAnno, self}, []}, + {atom, LineOrAnno, Loc#location.application}, + {atom, LineOrAnno, Loc#location.module}, + {atom, LineOrAnno, Loc#location.function}, + {integer, LineOrAnno, LineOrAnno} ]). -spec logi_call_expr(logi:severity(), expr(), expr(), expr(), #location{}) -> expr(). -logi_call_expr(Severity, FormatExpr, DataExpr, OptionsExpr, Loc = #location{line = Line}) -> +logi_call_expr(Severity, FormatExpr, DataExpr, OptionsExpr, Loc = #location{line_or_anno = LineOrAnno}) -> LocationExpr = logi_location_expr(Loc), - LoggerVar = logi_transform_utils:make_var(Line, "__Logger"), - ResultVar = logi_transform_utils:make_var(Line, "__Result"), + LoggerVar = logi_transform_utils:make_var(LineOrAnno, "__Logger"), + ResultVar = logi_transform_utils:make_var(LineOrAnno, "__Result"), LogiReadyCall = logi_transform_utils:make_call_remote( - Line, logi, '_ready', [{atom, Line, Severity}, LocationExpr, OptionsExpr]), - {'case', Line, LogiReadyCall, + LineOrAnno, logi, '_ready', [{atom, LineOrAnno, Severity}, LocationExpr, OptionsExpr]), + {'case', LineOrAnno, LogiReadyCall, [ %% {Logger, []} -> Logger - {clause, Line, [{tuple, Line, [LoggerVar, {nil, Line}]}], [], + {clause, LineOrAnno, [{tuple, LineOrAnno, [LoggerVar, {nil, LineOrAnno}]}], [], [LoggerVar]}, %% {Logger, Result} -> logi:'_write'(Result, Format, Data), Logger - {clause, Line, [{tuple, Line, [LoggerVar, ResultVar]}], [], - [logi_transform_utils:make_call_remote(Line, logi, '_write', [ResultVar, FormatExpr, DataExpr]), + {clause, LineOrAnno, [{tuple, LineOrAnno, [LoggerVar, ResultVar]}], [], + [logi_transform_utils:make_call_remote(LineOrAnno, logi, '_write', [ResultVar, FormatExpr, DataExpr]), LoggerVar]} ]}. diff --git a/src/logi_transform_utils.erl b/src/logi_transform_utils.erl index 65c24f3..db60316 100644 --- a/src/logi_transform_utils.erl +++ b/src/logi_transform_utils.erl @@ -32,20 +32,20 @@ guess_application(Forms, Options) -> find_app_file([Dir || Dir <- [OutDir, SrcDir], Dir =/= undefined]). %% @doc Makes a abstract term for variable --spec make_var(logi_transform:line(), string()) -> logi_transform:expr_var(). -make_var(Line, Prefix) -> +-spec make_var(logi_transform:line_or_anno(), string()) -> logi_transform:expr_var(). +make_var(LineOrAnno, Prefix) -> Seq = case get({?MODULE, seq}) of undefined -> 0; Seq0 -> Seq0 end, _ = put({?MODULE, seq}, Seq + 1), - Name = list_to_atom(Prefix ++ "_line" ++ integer_to_list(Line) ++ "_" ++ integer_to_list(Seq)), - {var, Line, Name}. + Name = list_to_atom(Prefix ++ "_line" ++ line_or_anno_to_string(LineOrAnno) ++ "_" ++ integer_to_list(Seq)), + {var, LineOrAnno, Name}. %% @doc Makes a abstract term for external function call --spec make_call_remote(logi_transform:line(), module(), atom(), [logi_transform:expr()]) -> logi_transform:expr_call_remote(). -make_call_remote(Line, Module, Function, ArgsExpr) -> - {call, Line, {remote, Line, {atom, Line, Module}, {atom, Line, Function}}, ArgsExpr}. +-spec make_call_remote(logi_transform:line_or_anno(), module(), atom(), [logi_transform:expr()]) -> logi_transform:expr_call_remote(). +make_call_remote(LineOrAnno, Module, Function, ArgsExpr) -> + {call, LineOrAnno, {remote, LineOrAnno, {atom, LineOrAnno, Module}, {atom, LineOrAnno, Function}}, ArgsExpr}. %%---------------------------------------------------------------------------------------------------------------------- %% Internal Functions @@ -61,3 +61,9 @@ find_app_file([Dir | Dirs]) -> end; _ -> find_app_file(Dirs) end. + +-spec line_or_anno_to_string(logi_transform:line_or_anno()) -> string(). +line_or_anno_to_string(LineOrAnno) when is_integer(LineOrAnno) + -> integer_to_list(LineOrAnno); +line_or_anno_to_string(LineOrAnno) + -> integer_to_list(erl_anno:line(LineOrAnno)). diff --git a/test/logi_location_tests.erl b/test/logi_location_tests.erl index bf973d3..a26a841 100644 --- a/test/logi_location_tests.erl +++ b/test/logi_location_tests.erl @@ -59,3 +59,18 @@ guess_test_() -> ?assertEqual(undefined, logi_location:guess_application('UNDEFINED_MODULE')) end} ]}. + +anno_test_() -> + [ + {"Creates a new location object", + fun () -> + L = logi_location:new(lists, map, erl_anno:new({12, 10})), + ?assert(logi_location:is_location(L)), + ?assertEqual(12, logi_location:get_line(L)) + end}, + {"Converts to a map without anno", + fun () -> + L = logi_location:new(lists, map, erl_anno:new({12, 10})), + ?assertMatch(#{line := 12}, logi_location:to_map(L)) + end} + ]. diff --git a/test/logi_transform_tests.erl b/test/logi_transform_tests.erl index 5179dfc..82fea46 100644 --- a/test/logi_transform_tests.erl +++ b/test/logi_transform_tests.erl @@ -54,7 +54,10 @@ log_test_() -> %% [NO ERROR] transformed call Logger = logi:info("hello world"), - ?assertLog("hello world", [], fun (C) -> ?assertEqual(info, logi_context:get_severity(C)) end), + ?assertLog("hello world", [], fun (C) -> + ?assertEqual(info, logi_context:get_severity(C)), + ?assertEqual(56, logi_location:get_line(logi_context:get_location(C))) + end), ?assert(logi:is_logger(Logger)) end}, {"`Data` arugment will not be evaluated if it is unnecessary",