diff options
author | Hans Bolinder <hasse@erlang.org> | 2021-02-01 07:17:32 +0100 |
---|---|---|
committer | Hans Bolinder <hasse@erlang.org> | 2021-02-01 07:17:32 +0100 |
commit | ee6a69bcef0dfdafe07906b939052d24e25be9c3 (patch) | |
tree | a778d0648b2b9fbbb3bc1eba374ce0d25a29a1e3 /lib/compiler | |
parent | 2ac9e0744e867f791662a5f279e91f6a7bf67199 (diff) | |
parent | 370a2f1c940d305818eb6e415e874741382c22d9 (diff) | |
download | erlang-ee6a69bcef0dfdafe07906b939052d24e25be9c3.tar.gz |
Merge branch 'richcarl/columns/PR-2664/OTP-16824'
* richcarl/columns/PR-2664/OTP-16824: (56 commits)
Update primary bootstrap
erl_lint: Give a better column position for format warnings
erl_lint: Correct column number for unsized binary not at end
Include column numbers in compiler warnings
dialyzer: Improve column numbers in warnings
stdlib: Improve locations of annotations of abstract code
stdlib: Improve error locations in module erl_lint
stdlib: Improve error locations in module epp
stdlib: Improve error locations in module erl_parse
stdlib: Fix handling of annotations in erl_expand_records
erl_docgen: Correct handling of annotation of abstract code
syntax_tools: Generalize start line to be location
syntax_tools: Correct handling of annotations of abstract code
edoc: Correct handling of annotations of abstract code
tools: Substitute Anno for Line in xref_reader
tools: Substitute Anno for Line in cover
stdlib: Substitute Anno for Line in epp
stdlib: Substitute Anno for Line in erl_eval
stdlib: Substitute Anno for Line in erl_pp
stdlib: Substitute Anno for Line in eval_bits
...
Diffstat (limited to 'lib/compiler')
-rw-r--r-- | lib/compiler/doc/src/compile.xml | 28 | ||||
-rw-r--r-- | lib/compiler/src/beam_kernel_to_ssa.erl | 6 | ||||
-rw-r--r-- | lib/compiler/src/beam_validator.erl | 2 | ||||
-rw-r--r-- | lib/compiler/src/compile.erl | 66 | ||||
-rw-r--r-- | lib/compiler/src/core_pp.erl | 4 | ||||
-rw-r--r-- | lib/compiler/src/sys_core_fold.erl | 102 | ||||
-rw-r--r-- | lib/compiler/src/sys_pre_attributes.erl | 16 | ||||
-rw-r--r-- | lib/compiler/src/v3_core.erl | 11 | ||||
-rw-r--r-- | lib/compiler/src/v3_kernel.erl | 16 | ||||
-rw-r--r-- | lib/compiler/src/v3_kernel_pp.erl | 2 | ||||
-rw-r--r-- | lib/compiler/test/beam_validator_SUITE.erl | 6 | ||||
-rw-r--r-- | lib/compiler/test/compile_SUITE.erl | 21 | ||||
-rw-r--r-- | lib/compiler/test/error_SUITE.erl | 151 | ||||
-rw-r--r-- | lib/compiler/test/warnings_SUITE.erl | 539 |
14 files changed, 655 insertions, 315 deletions
diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml index 8329ed867c..467f01b9d8 100644 --- a/lib/compiler/doc/src/compile.xml +++ b/lib/compiler/doc/src/compile.xml @@ -355,6 +355,20 @@ module.beam: module.erl \ since R13B04.</p> </item> + <tag><c>error_location</c></tag> + <item> + <p>If the value of this flag is <c>line</c>, the location + <seeerl marker="#error_information"><c>ErrorLocation</c></seeerl> + of warnings and errors is a line number. If + the value is <c>column</c>, <c>ErrorLocation</c> includes + both a line number and a column number. + Default is <c>column</c>. This option is supported + since Erlang/OTP 24.0.</p> + <p>If the value of this flag is <c>column</c>, + <seeerl marker="#debug_info">debug information</seeerl> + includes column information.</p> + </item> + <tag><c>return</c></tag> <item> <p>A short form for both <c>return_errors</c> and @@ -783,7 +797,7 @@ module.beam: module.erl \ The filename is included here, as the compiler uses the Erlang pre-processor <c>epp</c>, which allows the code to be included in other files. It is therefore important to know to - <em>which</em> file the line number of an error or a warning refers. + <em>which</em> file the location of an error or a warning refers. </p> </desc> </func> @@ -810,7 +824,7 @@ module.beam: module.erl \ <v>ModuleName = module()</v> <v>BinaryOrCode = binary() | term()</v> <v>ErrRet = error | {error,Errors,Warnings}</v> - <v>Warnings = Errors = [{<seetype marker="kernel:file#filename">file:filename()</seetype>, [{<seetype marker="stdlib:erl_anno#line">erl_anno:line()</seetype> | 'none', module(), term()}]}]</v> + <v>Warnings = Errors = [{<seetype marker="kernel:file#filename">file:filename()</seetype>, [{<seetype marker="stdlib:erl_anno#location">erl_anno:location()</seetype> | 'none', module(), term()}]}]</v> </type> <desc> <p>Analogous to <c>file/1</c>, but takes a list of forms (in @@ -1003,6 +1017,10 @@ pi() -> 3.1416. <p>Parse transformations are used when a programmer wants to use Erlang syntax but with different semantics. The original Erlang code is then transformed into other Erlang code.</p> + + <p>See <seeerl marker="stdlib:erl_id_trans">erl_id_trans(3)</seeerl> + for an example and an explanation of the function + <c>parse_transform_info/0</c>.</p> </section> <section> @@ -1013,10 +1031,10 @@ pi() -> 3.1416. <c>ErrorInfo</c> structure, which is returned from all I/O modules. It has the following format:</p> <code> -{ErrorLine, Module, ErrorDescriptor}</code> +{ErrorLocation, Module, ErrorDescriptor}</code> - <p><c>ErrorLine</c> is the atom <c>none</c> if the error does - not correspond to a specific line, for example, if the source file does + <p><c>ErrorLocation</c> is the atom <c>none</c> if the error does + not correspond to a specific location, for example, if the source file does not exist.</p> <p>A string describing the error is obtained with the following diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl index 7ba2d925e4..0c79f00d1f 100644 --- a/lib/compiler/src/beam_kernel_to_ssa.erl +++ b/lib/compiler/src/beam_kernel_to_ssa.erl @@ -1276,6 +1276,9 @@ new_label(#cg{lcount=Next}=St) -> line_anno([Line,{file,Name}]) when is_integer(Line) -> line_anno_1(Name, Line); +line_anno([{Line,Column},{file,Name}]) when is_integer(Line), + is_integer(Column) -> + line_anno_1(Name, Line); line_anno([_|_]=A) -> {Name,Line} = find_loc(A, no_file, 0), line_anno_1(Name, Line); @@ -1292,6 +1295,9 @@ line_anno_1(Name, Line) -> find_loc([Line|T], File, _) when is_integer(Line) -> find_loc(T, File, Line); +find_loc([{Line, Column}|T], File, _) when is_integer(Line), + is_integer(Column) -> + find_loc(T, File, Line); find_loc([{file,File}|T], _, Line) -> find_loc(T, File, Line); find_loc([_|T], File, Line) -> diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 233167b536..794ba7874e 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -57,7 +57,7 @@ validate({Mod,Exp,Attr,Fs,Lc}, Level) when is_atom(Mod), [] -> ok; Es0 -> - Es = [{?MODULE,E} || E <- Es0], + Es = [{1,?MODULE,E} || E <- Es0], {error,[{atom_to_list(Mod),Es}]} end. diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 5ccaaf1714..4fec417c6e 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -375,7 +375,13 @@ internal({forms,Forms}, Opts0) -> Source = proplists:get_value(source, Opts0, ""), Opts1 = proplists:delete(source, Opts0), Compile = build_compile(Opts1), - internal_comp(Ps, Forms, Source, "", Compile); + NewForms = case with_columns(Opts0) of + true -> + Forms; + false -> + strip_columns(Forms) + end, + internal_comp(Ps, NewForms, Source, "", Compile); internal({file,File}, Opts) -> {Ext,Ps} = passes(file, Opts), Compile = build_compile(Opts), @@ -1007,18 +1013,32 @@ do_parse_module(DefEncoding, #compile{ifile=File,options=Opts,dir=Dir}=St) -> true -> filename:basename(SourceName0); false -> SourceName0 end, + StartLocation = case with_columns(Opts) of + true -> + {1,1}; + false -> + 1 + end, R = epp:parse_file(File, [{includes,[".",Dir|inc_paths(Opts)]}, {source_name, SourceName}, {macros,pre_defs(Opts)}, {default_encoding,DefEncoding}, + {location,StartLocation}, extra]), case R of {ok,Forms,Extra} -> Encoding = proplists:get_value(encoding, Extra), case find_invalid_unicode(Forms, File) of none -> - {ok,Forms,St#compile{encoding=Encoding}}; + Forms1 = + case with_columns(Opts ++ compile_options(Forms)) of + true -> + Forms; + false -> + strip_columns(Forms) + end, + {ok,Forms1,St#compile{encoding=Encoding}}; {invalid_unicode,_,_}=Ret -> case Encoding of none -> @@ -1032,6 +1052,9 @@ do_parse_module(DefEncoding, #compile{ifile=File,options=Opts,dir=Dir}=St) -> {error,St#compile{errors=St#compile.errors ++ Es}} end. +with_columns(Opts) -> + proplists:get_value(error_location, Opts, column) =:= column. + find_invalid_unicode([H|T], File0) -> case H of {attribute,_,file,{File,_}} -> @@ -1135,10 +1158,12 @@ foldl_transform([T|Ts], Code0, St) -> Es = [{St#compile.ifile,[{none,compile, {parse_transform,T,R}}]}], {error,St#compile{errors=St#compile.errors ++ Es}}; - {warning, Forms, Ws} -> + {warning, Forms0, Ws} -> + Forms = maybe_strip_columns(Forms0, T), foldl_transform(Ts, Forms, St#compile{warnings=St#compile.warnings ++ Ws}); - Forms -> + Forms0 -> + Forms = maybe_strip_columns(Forms0, T), foldl_transform(Ts, Forms, St) end; false -> @@ -1148,6 +1173,27 @@ foldl_transform([T|Ts], Code0, St) -> end; foldl_transform([], Code, St) -> {ok,Code,St}. +%%% It is possible--although unlikely--that parse transforms add +%%% columns to the abstract code, why this function is called for +%%% every parse transform. +maybe_strip_columns(Code, T) -> + case erlang:function_exported(T, parse_transform_info, 0) of + true -> + Info = T:parse_transform_info(), + case maps:get(error_location, Info, column) of + line -> + strip_columns(Code); + _ -> + Code + end; + false -> + Code + end. + +strip_columns(Code) -> + F = fun(A) -> erl_anno:set_location(erl_anno:line(A), A) end, + [erl_parse:map_anno(F, Form) || Form <- Code]. + get_core_transforms(Opts) -> [M || {core_transform,M} <- Opts]. core_transforms(Code, St) -> @@ -1729,18 +1775,13 @@ format_message(F, P, [{none,Mod,E}|Es]) -> M = {none,io_lib:format("~ts: ~s~ts\n", [F,P,Mod:format_error(E)])}, [M|format_message(F, P, Es)]; format_message(F, P, [{{Line,Column}=Loc,Mod,E}|Es]) -> - M = {{F,Loc},io_lib:format("~ts:~w:~w ~s~ts\n", + M = {{F,Loc},io_lib:format("~ts:~w:~w: ~s~ts\n", [F,Line,Column,P,Mod:format_error(E)])}, [M|format_message(F, P, Es)]; format_message(F, P, [{Line,Mod,E}|Es]) -> M = {{F,{Line,0}},io_lib:format("~ts:~w: ~s~ts\n", [F,Line,P,Mod:format_error(E)])}, [M|format_message(F, P, Es)]; -format_message(F, P, [{Mod,E}|Es]) -> - %% Not documented and not expected to be used any more, but - %% keep a while just in case. - M = {none,io_lib:format("~ts: ~s~ts\n", [F,P,Mod:format_error(E)])}, - [M|format_message(F, P, Es)]; format_message(_, _, []) -> []. %% list_errors(File, ErrorDescriptors) -> ok @@ -1754,11 +1795,6 @@ list_errors(F, [{{Line,Column},Mod,E}|Es]) -> list_errors(F, [{Line,Mod,E}|Es]) -> io:fwrite("~ts:~w: ~ts\n", [F,Line,Mod:format_error(E)]), list_errors(F, Es); -list_errors(F, [{Mod,E}|Es]) -> - %% Not documented and not expected to be used any more, but - %% keep a while just in case. - io:fwrite("~ts: ~ts\n", [F,Mod:format_error(E)]), - list_errors(F, Es); list_errors(_F, []) -> ok. %% erlfile(Dir, Base) -> ErlFile diff --git a/lib/compiler/src/core_pp.erl b/lib/compiler/src/core_pp.erl index 19fa11235c..ab92f3dfce 100644 --- a/lib/compiler/src/core_pp.erl +++ b/lib/compiler/src/core_pp.erl @@ -101,6 +101,8 @@ format_anno_list([H], Ctxt) -> strip_line([A | As]) when is_integer(A) -> strip_line(As); +strip_line([{A,C} | As]) when is_integer(A), is_integer(C) -> + strip_line(As); strip_line([{file,_File} | As]) -> strip_line(As); strip_line([A | As]) -> @@ -110,6 +112,8 @@ strip_line([]) -> get_line([L | _As]) when is_integer(L) -> L; +get_line([{L, _Column} | _As]) when is_integer(L) -> + L; get_line([_ | As]) -> get_line(As); get_line([]) -> diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index 704bddc2ac..287280c18f 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -97,7 +97,8 @@ -record(sub, {v=[], %Variable substitutions s=sets:new([{version, 2}]) :: sets:set(), %Variables in scope t=#{} :: map(), %Types - in_guard=false}). %In guard or not. + in_guard=false, %In guard or not. + top=true}). %Not inside a term. -spec module(cerl:c_module(), [compile:option()]) -> {'ok', cerl:c_module(), [_]}. @@ -176,7 +177,7 @@ expr(#c_var{}=V, Ctxt, Sub) -> effect -> void(); value -> sub_get_var(V, Sub) end; -expr(#c_literal{val=Val}=L, Ctxt, _Sub) -> +expr(#c_literal{val=Val}=L, Ctxt, Sub) -> case Ctxt of effect -> case Val of @@ -188,35 +189,36 @@ expr(#c_literal{val=Val}=L, Ctxt, _Sub) -> void(); _ -> %% Warn and replace with void(). - add_warning(L, useless_building), + warn_useless_building(L, Sub), void() end; value -> L end; expr(#c_cons{anno=Anno,hd=H0,tl=T0}=Cons, Ctxt, Sub) -> - H1 = expr(H0, Ctxt, Sub), - T1 = expr(T0, Ctxt, Sub), + DeeperSub = descend(Cons, Sub), + H1 = expr(H0, Ctxt, DeeperSub), + T1 = expr(T0, Ctxt, DeeperSub), case Ctxt of effect -> - add_warning(Cons, useless_building), + warn_useless_building(Cons, Sub), make_effect_seq([H1,T1], Sub); value -> ann_c_cons(Anno, H1, T1) end; expr(#c_tuple{anno=Anno,es=Es0}=Tuple, Ctxt, Sub) -> - Es = expr_list(Es0, Ctxt, Sub), + Es = expr_list(Es0, Ctxt, descend(Tuple, Sub)), case Ctxt of effect -> - add_warning(Tuple, useless_building), + warn_useless_building(Tuple, Sub), make_effect_seq(Es, Sub); value -> ann_c_tuple(Anno, Es) end; expr(#c_map{anno=Anno,arg=V0,es=Es0}=Map, Ctxt, Sub) -> - Es = pair_list(Es0, Ctxt, Sub), + Es = pair_list(Es0, Ctxt, descend(Map, Sub)), case Ctxt of effect -> - add_warning(Map, useless_building), + warn_useless_building(Map, Sub), make_effect_seq(Es, Sub); value -> V = expr(V0, Ctxt, Sub), @@ -226,15 +228,15 @@ expr(#c_binary{segments=Ss}=Bin0, Ctxt, Sub) -> %% Warn for useless building, but always build the binary %% anyway to preserve a possible exception. case Ctxt of - effect -> add_warning(Bin0, useless_building); + effect -> warn_useless_building(Bin0, Sub); value -> ok end, Bin1 = Bin0#c_binary{segments=bitstr_list(Ss, Sub)}, Bin = bin_un_utf(Bin1), eval_binary(Bin); -expr(#c_fun{}=Fun, effect, _) -> +expr(#c_fun{}=Fun, effect, Sub) -> %% A fun is created, but not used. Warn, and replace with the void value. - add_warning(Fun, useless_building), + warn_useless_building(Fun, Sub), void(); expr(#c_fun{vars=Vs0,body=B0}=Fun, Ctxt0, Sub0) -> {Vs1,Sub1} = var_list(Vs0, Sub0), @@ -2712,6 +2714,63 @@ copy_type(_, _, Tdb) -> Tdb. void() -> #c_literal{val=ok}. +%%% +%%% Handling of the `useless_building` warning (building a term that +%%% is never used). +%%% +%%% Consider this code fragment: +%%% +%%% [ {ok,Term} ], +%%% ok +%%% +%%% The list that is ignored contains a tuple that is also ignored. +%%% While optimizing this code fragment, two warnings for useless +%%% building will be generated: one for the list and one for the tuple +%%% inside. Before the introduction of column numbers, those two warnings +%%% would be coalesced to one becuase they had the same line number. +%%% +%%% With column numbers, we will need a more sophisticated solution to +%%% avoid emitting annoying duplicate warnings. +%%% +%%% Note that if two separate terms are being built on the same line, we +%%% do expect to get two warnings: +%%% +%%% [ {ok,Term} ], [ {error,BadTerm} ], ok +%%% ^ ^ +%%% +%%% (The carets mark the expected columns for the warnings.) +%%% +%%% To handle those requirements, we will use the #sub{} record to keep +%%% track of whether we are at the top level or have descended into +%%% a sub expression. +%%% + +%% Note in the Sub record that we have are no longer at the top level. +descend(_Core, #sub{top=false}=Sub) -> + Sub; +descend(Core, #sub{top=true}=Sub) -> + case should_suppress_warning(Core) of + true -> + %% In a list comprehension being ignored such as: + %% + %% [{error,Z} || Z <- List], ok + %% + %% the warning for ignoring the cons cell should be + %% suppressed, but there should still be a warning for + %% ignoring the {error,Z} tuple. Therefore, pretend that + %% we are still at the top level. + Sub; + false -> + %% No longer at top level. Warnings for useless building + %% should now be suppressed. + Sub#sub{top=false} + end. + +warn_useless_building(Core, #sub{top=Top}) -> + case Top of + true -> add_warning(Core, useless_building); + false -> ok + end. %%% %%% Handling of warnings. @@ -2719,29 +2778,38 @@ void() -> #c_literal{val=ok}. init_warnings() -> put({?MODULE,warnings}, []). - add_warning(Core, Term) -> case should_suppress_warning(Core) of true -> ok; false -> Anno = cerl:get_ann(Core), - Line = get_line(Anno), + Location = get_location(Anno), File = get_file(Anno), Key = {?MODULE,warnings}, case get(Key) of - [{File,[{Line,?MODULE,Term}]}|_] -> + [{File,[{Location,?MODULE,Term}]}|_] -> ok; %We already have %an identical warning. Ws -> - put(Key, [{File,[{Line,?MODULE,Term}]}|Ws]) + put(Key, [{File,[{Location,?MODULE,Term}]}|Ws]) end end. get_line([Line|_]) when is_integer(Line) -> Line; +get_line([{Line, _Column} | _T]) when is_integer(Line) -> Line; get_line([_|T]) -> get_line(T); get_line([]) -> none. +get_location([Line|_]) when is_integer(Line) -> + Line; +get_location([{Line, Column} | _T]) when is_integer(Line), is_integer(Column) -> + {Line,Column}; +get_location([_|T]) -> + get_location(T); +get_location([]) -> + none. + get_file([{file,File}|_]) -> File; get_file([_|T]) -> get_file(T); get_file([]) -> "no_file". % should not happen diff --git a/lib/compiler/src/sys_pre_attributes.erl b/lib/compiler/src/sys_pre_attributes.erl index 67adae5acf..1b0fcd46af 100644 --- a/lib/compiler/src/sys_pre_attributes.erl +++ b/lib/compiler/src/sys_pre_attributes.erl @@ -114,7 +114,7 @@ pre_transform(S) -> pre_transform([H | T], Acc, S) -> case H of - {attribute, Line, Name, Val} -> + {attribute, Anno, Name, Val} -> case lists:keyfind(Name, 2, S#state.pre_ops) of false -> pre_transform(T, [H | Acc], S); @@ -123,7 +123,7 @@ pre_transform([H | T], Acc, S) -> report_warning("Replace attribute ~p: ~p -> ~p~n", [Name, Val, NewVal], S), - New = {attribute, Line, Name, NewVal}, + New = {attribute, Anno, Name, NewVal}, Pre = lists:keydelete(Name, 2, S#state.pre_ops), Post = lists:keydelete(Name, 2, S#state.post_ops), S2 = S#state{pre_ops = Pre, post_ops = Post}, @@ -158,9 +158,9 @@ post_transform(S) -> post_transform([H | T], Acc, S) -> case H of - {attribute, Line, module, _Val} = Attribute -> + {attribute, Anno, module, _Val} = Attribute -> Acc2 = lists:reverse([Attribute | Acc]), - Forms = Acc2 ++ attrs(S#state.post_ops, Line, S) ++ T, + Forms = Acc2 ++ attrs(S#state.post_ops, Anno, S) ++ T, S#state{forms = Forms, post_ops = []}; _Any -> post_transform(T, [H | Acc], S) @@ -168,12 +168,12 @@ post_transform([H | T], Acc, S) -> post_transform([], Acc, S) -> S#state{forms = lists:reverse(Acc)}. -attrs([{replace, Name, NewVal} | T], Line, S) -> +attrs([{replace, Name, NewVal} | T], Anno, S) -> report_verbose("Insert attribute ~p: ~p~n", [Name, NewVal], S), - [{attribute, Line, Name, NewVal} | attrs(T, Line, S)]; -attrs([{insert, Name, NewVal} | T], Line, S) -> + [{attribute, Anno, Name, NewVal} | attrs(T, Anno, S)]; +attrs([{insert, Name, NewVal} | T], Anno, S) -> report_verbose("Insert attribute ~p: ~p~n", [Name, NewVal], S), - [{attribute, Line, Name, NewVal} | attrs(T, Line, S)]; + [{attribute, Anno, Name, NewVal} | attrs(T, Anno, S)]; attrs([], _, _) -> []. diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index d4ee6053ab..1b9e961514 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -947,20 +947,21 @@ map_build_pairs_1([{Op0,L,K0,V0}|Es], Used0, St0) -> {Pairs,Pre2,Used1,St3} = map_build_pairs_1(Es, Used0, St2), As = lineno_anno(L, St3), Op = map_op(Op0), - {Used2,St4} = maybe_warn_repeated_keys(K, L, Used1, St3), + {Used2,St4} = maybe_warn_repeated_keys(K, K0, Used1, St3), Pair = cerl:ann_c_map_pair(As, Op, K, V), {[Pair|Pairs],Pre0++Pre1++Pre2,Used2,St4}; map_build_pairs_1([], Used, St) -> {[],[],Used,St}. -maybe_warn_repeated_keys(Ck,Line,Used,St) -> +maybe_warn_repeated_keys(Ck, K0, Used, St) -> case cerl:is_literal(Ck) of false -> {Used,St}; true -> K = cerl:concrete(Ck), case sets:is_element(K,Used) of true -> - {Used, add_warning(Line, {map_key_repeated,K}, St)}; + Location = element(2, K0), + {Used, add_warning(Location, {map_key_repeated,K}, St)}; false -> {sets:add_element(K,Used), St} end @@ -3389,10 +3390,10 @@ full_anno(L, #core{wanted=true}=St) -> lineno_anno(L, St). lineno_anno(L, St) -> - Line = erl_anno:line(L), + Location = erl_anno:location(L), Generated = erl_anno:generated(L), CompilerGenerated = [compiler_generated || Generated], - [Line] ++ St#core.file ++ CompilerGenerated. + [Location] ++ St#core.file ++ CompilerGenerated. get_lineno_anno(Ce) -> case get_anno(Ce) of diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl index eb04a2c916..40cf1c276c 100644 --- a/lib/compiler/src/v3_kernel.erl +++ b/lib/compiler/src/v3_kernel.erl @@ -338,7 +338,7 @@ expr(#c_call{anno=A,module=M0,name=F0,args=Cargs}, Sub, St0) -> error -> %% Invalid call (e.g. M:42/3). Issue a warning, and let %% the generated code use the old explict apply. - St = add_warning(get_line(A), bad_call, A, St0), + St = add_warning(get_location(A), bad_call, A, St0), Call = #c_call{anno=A, module=#c_literal{val=erlang}, name=#c_literal{val=apply}, @@ -1066,7 +1066,7 @@ maybe_add_warning(Ke, MatchAnno, St) -> St; false -> Anno = get_kanno(Ke), - Line = get_line(Anno), + Line = get_location(Anno), MatchLine = get_line(MatchAnno), Warn = case MatchLine of none -> nomatch_shadow; @@ -1074,8 +1074,18 @@ maybe_add_warning(Ke, MatchAnno, St) -> end, add_warning(Line, Warn, Anno, St) end. - + +get_location([Line|_]) when is_integer(Line) -> + Line; +get_location([{Line, Column} | _T]) when is_integer(Line), is_integer(Column) -> + {Line,Column}; +get_location([_|T]) -> + get_location(T); +get_location([]) -> + none. + get_line([Line|_]) when is_integer(Line) -> Line; +get_line([{Line, _Column} | _T]) when is_integer(Line) -> Line; get_line([_|T]) -> get_line(T); get_line([]) -> none. diff --git a/lib/compiler/src/v3_kernel_pp.erl b/lib/compiler/src/v3_kernel_pp.erl index f7479e6b15..5a4e6fa788 100644 --- a/lib/compiler/src/v3_kernel_pp.erl +++ b/lib/compiler/src/v3_kernel_pp.erl @@ -57,6 +57,8 @@ format(Node, Ctxt) -> format_1(Node, Ctxt); [L,{file,_}] when is_integer(L) -> format_1(Node, Ctxt); + [{L,C},{file,_}] when is_integer(L), is_integer(C) -> + format_1(Node, Ctxt); List -> format_anno(List, Ctxt, fun (Ctxt1) -> format_1(Node, Ctxt1) diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl index 49731f9137..565a86be98 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -99,7 +99,7 @@ compiler_bug(Config) when is_list(Config) -> %% the beam_validator module. {error, [{"compiler_bug", - [{beam_validator,_}]}], + [{_Pos,beam_validator,_}]}], []} = compile:file(File, [from_asm,return_errors,time]), ok. @@ -892,7 +892,7 @@ do_val(Mod, Config) -> case compile:file(File, [from_asm,no_postopt,return_errors]) of {error,L,[]} -> [{Base,Errors0}] = L, - Errors = [E || {beam_validator,E} <- Errors0], + Errors = [E || {_Pos,beam_validator,E} <- Errors0], _ = [io:put_chars(beam_validator:format_error(E)) || E <- Errors], Errors; @@ -903,7 +903,7 @@ do_val(Mod, Config) -> beam_val(M) -> Name = atom_to_list(element(1, M)), {error,[{Name,Errors0}]} = beam_validator:validate(M, strong), - Errors = [E || {beam_validator,E} <- Errors0], + Errors = [E || {_Pos,beam_validator,E} <- Errors0], _ = [io:put_chars(beam_validator:format_error(E)) || E <- Errors], Errors. diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index 290ed87016..6af2d9b2b5 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -1240,9 +1240,11 @@ do_warnings_1([{Name,Ws}|T], F) -> end; do_warnings_1([], _) -> ok. -do_warnings_2([{Int,_,_}=W|T], Next, F) -> - if - is_integer(Int) -> +do_warnings_2([{Pos,_,_}=W|T], Next, F) -> + case Pos of + Line when is_integer(Line) -> + do_warnings_2(T, Next, F); + {Line,Col} when is_integer(Line), is_integer(Col) -> do_warnings_2(T, Next, F); true -> io:format("~s:\nMissing line number: ~p\n", @@ -1502,14 +1504,17 @@ debug_info_attribute(DataDir, Name, Opts) -> {ok,_,Bin} = compile:file(File, [binary | Opts]), {ok, {_, Attrs}} = beam_lib:chunks(Bin, [debug_info]), - [{debug_info,{debug_info_v1,erl_abstract_code, - {[{attribute,1,file,{_,1}}, - {attribute,1,module,debug_info}, - {attribute,2,compile,[debug_info]}, - {eof,2}], _}}}] = Attrs, + [{debug_info,{debug_info_v1,erl_abstract_code, {Forms, _}}}] = Attrs, + [{attribute,{1,1},file,{_,1}}, + {attribute,{1,2},module,debug_info}, + {attribute,{2,2},compile,[debug_info]}, + {eof,_}] = forms_to_terms(Forms), ok. +forms_to_terms(Forms) -> + [erl_parse:anno_to_term(Form) || Form <- Forms]. + %%% %%% Utilities. %%% diff --git a/lib/compiler/test/error_SUITE.erl b/lib/compiler/test/error_SUITE.erl index 9436ad5d53..388ecce4f9 100644 --- a/lib/compiler/test/error_SUITE.erl +++ b/lib/compiler/test/error_SUITE.erl @@ -23,7 +23,7 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, head_mismatch_line/1,warnings_as_errors/1, bif_clashes/1, - transforms/1,maps_warnings/1,bad_utf8/1]). + transforms/1,maps_warnings/1,bad_utf8/1,bad_decls/1]). %% Used by transforms/1 test case. -export([parse_transform/2]). @@ -36,7 +36,7 @@ all() -> groups() -> [{p,test_lib:parallel(), [head_mismatch_line,warnings_as_errors,bif_clashes, - transforms,maps_warnings,bad_utf8]}]. + transforms,maps_warnings,bad_utf8,bad_decls]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -64,7 +64,7 @@ bif_clashes(Config) when is_list(Config) -> ">>, [return_warnings], {error, - [{4, erl_lint,{call_to_redefined_old_bif,{length,1}}}], []} }], + [{{4,18}, erl_lint,{call_to_redefined_old_bif,{length,1}}}], []} }], [] = run(Config, Ts), Ts1 = [{bif_clashes2, <<" @@ -75,7 +75,7 @@ bif_clashes(Config) when is_list(Config) -> ">>, [return_warnings], {error, - [{3, erl_lint,{redefine_old_bif_import,{length,1}}}], []} }], + [{{3,16}, erl_lint,{redefine_old_bif_import,{length,1}}}], []} }], [] = run(Config, Ts1), Ts00 = [{bif_clashes3, <<" @@ -112,7 +112,7 @@ bif_clashes(Config) when is_list(Config) -> ">>, [return_warnings], {warning, - [{4, erl_lint,{call_to_redefined_bif,{binary_part,3}}}]} }], + [{{4,18}, erl_lint,{call_to_redefined_bif,{binary_part,3}}}]} }], [] = run(Config, Ts000), Ts111 = [{bif_clashes6, <<" @@ -123,7 +123,7 @@ bif_clashes(Config) when is_list(Config) -> ">>, [return_warnings], {warning, - [{3, erl_lint,{redefine_bif_import,{binary_part,3}}}]} }], + [{{3,16}, erl_lint,{redefine_bif_import,{binary_part,3}}}]} }], [] = run(Config, Ts111), Ts2 = [{bif_clashes7, <<" @@ -137,7 +137,7 @@ bif_clashes(Config) when is_list(Config) -> ">>, [], {error, - [{7,erl_lint,{define_import,{length,1}}}], + [{{7,15},erl_lint,{define_import,{length,1}}}], []} }], [] = run2(Config, Ts2), Ts3 = [{bif_clashes8, @@ -151,7 +151,7 @@ bif_clashes(Config) when is_list(Config) -> ">>, [], {error, - [{4,erl_lint,{illegal_guard_local_call,{length,1}}}], + [{{4,25},erl_lint,{illegal_guard_local_call,{length,1}}}], []} }], [] = run2(Config, Ts3), Ts4 = [{bif_clashes9, @@ -164,7 +164,7 @@ bif_clashes(Config) when is_list(Config) -> ">>, [], {error, - [{5,erl_lint,{illegal_guard_local_call,{length,1}}}], + [{{5,25},erl_lint,{illegal_guard_local_call,{length,1}}}], []} }], [] = run2(Config, Ts4), @@ -176,7 +176,7 @@ bif_clashes(Config) when is_list(Config) -> %% Tests that a head mismatch is reported on the correct line (OTP-2125). head_mismatch_line(Config) when is_list(Config) -> [E|_] = get_compilation_errors(Config, "head_mismatch_line"), - {26, Mod, Reason} = E, + {{26,1}, Mod, Reason} = E, Mod:format_error(Reason), ok. @@ -202,7 +202,7 @@ warnings_as_errors(Config) when is_list(Config) -> [warnings_as_errors, export_all, {outdir, OutDir}], {error, [], - [{3,erl_lint,{unused_var,'A'}}]} }], + [{{3,18},erl_lint,{unused_var,'A'}}]} }], [] = run(Ts1, TestFile, write_beam), false = filelib:is_regular(BeamFile), @@ -214,7 +214,7 @@ warnings_as_errors(Config) when is_list(Config) -> ">>, [return_warnings, export_all, {outdir, OutDir}], {warning, - [{3,erl_lint,{unused_var,'A'}}]} }], + [{{3,18},erl_lint,{unused_var,'A'}}]} }], [] = run(Ts2, TestFile, write_beam), true = filelib:is_regular(BeamFile), @@ -270,7 +270,7 @@ maps_warnings(Config) when is_list(Config) -> id(I) -> I. ">>, [return], - {error,[{3,erl_lint,{unbound_var,'K'}}],[]}} + {error,[{{3,15},erl_lint,{unbound_var,'K'}}],[]}} ], [] = run2(Config, Ts1), ok. @@ -285,13 +285,102 @@ bad_utf8(Config) -> t() -> \"",246,"\". ">>, [], - {error,[{2,epp,cannot_parse}, - {2,file_io_server,invalid_unicode}], + {error,[{{2,15},epp,cannot_parse}, + {{2,15},file_io_server,invalid_unicode}], []} }], [] = run2(Config, Ts), ok. +bad_decls(Config) -> + Ts = [{bad_decls_1, + <<"\n-module({l}). + ">>, + [], + {error,[{{2,9},erl_parse,"bad " ++ ["module"] ++ " declaration"}], + []} + }, + {bad_decls_2, + <<"\n-module(l, m). + ">>, + [], + {error,[{{2,12},erl_parse,"bad variable list"}],[]} + }, + {bad_decls_3, + <<"\n-export([a/1], Y). + ">>, + [], + {error,[{{2,16},erl_parse,"bad " ++ ["export"] ++ " declaration"}], + []} + }, + {bad_decls_4, + <<"\n-import([a/1], Y). + ">>, + [], + {error,[{{2,16},erl_parse,"bad " ++ ["import"] ++ " declaration"}], + []} + }, + {bad_decls_5, + <<"\n-ugly({A,B}). + ">>, + [], + {error,[{{2,7},erl_parse,"bad attribute"}],[]} + }, + {bad_decls_6, + <<"\n-ugly(a, b). + ">>, + [], + {error,[{{2,10},erl_parse,"bad attribute"}],[]} + }, + {bad_decls_7, + <<"\n-export([A/1]). + ">>, + [], + {error,[{{2,10},erl_parse,"bad function name"}],[]} + }, + {bad_decls_8, + <<"\n-export([a/a]). + ">>, + [], + {error,[{{2,12},erl_parse,"bad function arity"}],[]} + }, + {bad_decls_9, + <<"\n-export([a/1, {3,4}]). + ">>, + [], + {error,[{{2,15},erl_parse,"bad Name/Arity"}],[]} + }, + {bad_decls_10, + <<"\n-record(A, {{bad,a}}). + ">>, + [], + {error,[{{2,9},erl_parse,"bad " ++ ["record"] ++ " declaration"}], + []} + }, + {bad_decls_11, + <<"\n-record(a, [a,b,c,d]). + ">>, + [], + {error,[{{2,12},erl_parse,"bad record declaration"}],[]} + }, + {bad_decls_12, + <<"\n-record(a). + ">>, + [], + {error,[{{2,9},erl_parse,"bad " ++ ["record"] ++ " declaration"}], + []} + } + ], + [] = run2(Config, Ts), + + {error,{{1,4},erl_parse,"bad term"}} = parse_string("1, 2 + 4."), + {error,{{1,1},erl_parse,"bad term"}} = parse_string("34 + begin 34 end."), + ok. + +parse_string(S) -> + {ok,Ts,_} = erl_scan:string(S, {1, 1}), + erl_parse:parse_term(Ts). + run(Config, Tests) -> File = test_filename(Config), @@ -343,7 +432,7 @@ test_filename(Conf) -> run_test(Test0, File, Warnings, WriteBeam) -> ModName = filename:rootname(filename:basename(File), ".erl"), Mod = list_to_atom(ModName), - Test = ["-module(",ModName,"). ",Test0], + Test = iolist_to_binary(["-module(",ModName,"). ",Test0]), Opts = case WriteBeam of dont_write_beam -> [binary,return_errors|Warnings]; @@ -359,6 +448,7 @@ run_test(Test0, File, Warnings, WriteBeam) -> io:format("~p\n", [Opts]), Res = case compile:file(File, Opts) of {ok,Mod,_,[{_File,Ws}]} -> + print_diagnostics(Ws, Test), {warning,Ws}; {ok,Mod,_,[]} -> []; @@ -367,15 +457,42 @@ run_test(Test0, File, Warnings, WriteBeam) -> {ok,Mod,[]} -> []; {error,[{XFile,Es}],Ws} = _ZZ when is_list(XFile) -> + print_diagnostics(Es, Test), {error,Es,Ws}; {error,[{XFile,Es1},{XFile,Es2}],Ws} = _ZZ when is_list(XFile) -> - {error,Es1++Es2,Ws}; + Es = Es1 ++ Es2, + print_diagnostics(Es, Test), + {error,Es,Ws}; {error,Es,[{_File,Ws}]} = _ZZ-> + print_diagnostics(Es ++ Ws, Test), {error,Es,Ws} end, file:delete(File), Res. +print_diagnostics(Warnings, Source) -> + case binary:match(Source, <<"-file(">>) of + nomatch -> + Lines = binary:split(Source, <<"\n">>, [global]), + Cs = [print_diagnostic(W, Lines) || W <- Warnings], + io:put_chars(Cs); + _ -> + %% There are probably fake line numbers greater than + %% the number of actual lines. + ok + end. + +print_diagnostic({{LineNum,Column},Mod,Data}, Lines) -> + Line0 = lists:nth(LineNum, Lines), + <<Line1:(Column-1)/binary,_/binary>> = Line0, + Spaces = re:replace(Line1, <<"[^\t]">>, <<" ">>, [global]), + CaretLine = [Spaces,"^"], + [io_lib:format("~p:~p: ~ts\n", [LineNum,Column,Mod:format_error(Data)]), + Line0, "\n", + CaretLine, "\n\n"]; +print_diagnostic(_, _) -> + []. + fail() -> ct:fail(failed). diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl index 46672c3a88..877329bd42 100644 --- a/lib/compiler/test/warnings_SUITE.erl +++ b/lib/compiler/test/warnings_SUITE.erl @@ -97,11 +97,11 @@ pattern(Config) when is_list(Config) -> foo(X) -> a = {nisse,b} = X. ">>, - [warn_unused_vars], - {warnings, - [{2,v3_core,nomatch}, - {6,v3_core,nomatch}, - {11,v3_core,nomatch} ] }}], + [warn_unused_vars], + {warnings, + [{{2,15},v3_core,nomatch}, + {{6,20},v3_core,nomatch}, + {{11,20},v3_core,nomatch} ] }}], [] = run(Config, Ts), ok. @@ -111,21 +111,21 @@ pattern2(Config) when is_list(Config) -> %% v3_kernel should generate some of the warnings. Source = <<"f(A) -> ok; f(B) -> error. - t(A, B, C) -> - case {A,B,C} of - {a,B} -> ok; - {_,B} -> ok - end. + t(A, B, C) -> + case {A,B,C} of + {a,B} -> ok; + {_,B} -> ok + end. ">>, %% Test warnings from sys_core_fold. Ts = [{pattern2, Source, [nowarn_unused_vars], - {warnings,[{2,sys_core_fold,{nomatch_shadow,1,{f,1}}}, - {4,sys_core_fold,no_clause_match}, - {5,sys_core_fold,nomatch_clause_type}, - {6,sys_core_fold,nomatch_clause_type}]}}], + {warnings,[{{2,15},sys_core_fold,{nomatch_shadow,1,{f,1}}}, + {{4,15},sys_core_fold,no_clause_match}, + {{5,17},sys_core_fold,nomatch_clause_type}, + {{6,17},sys_core_fold,nomatch_clause_type}]}}], [] = run(Config, Ts), %% Disable Core Erlang optimizations. v3_kernel should produce @@ -134,7 +134,7 @@ pattern2(Config) when is_list(Config) -> Source, [nowarn_unused_vars,no_copt], {warnings, - [{2,v3_kernel,{nomatch_shadow,1}}]}}], + [{{2,15},v3_kernel,{nomatch_shadow,1}}]}}], [] = run(Config, Ts2), ok. @@ -144,13 +144,13 @@ pattern3(Config) when is_list(Config) -> Ts = [{pattern3, <<" - f({A,_}) -> {ok,A}; - f([_|_]=B) -> {ok,B}; - f({urk,nisse}) -> urka_glurka. + f({A,_}) -> {ok,A}; + f([_|_]=B) -> {ok,B}; + f({urk,nisse}) -> urka_glurka. ">>, [nowarn_unused_vars], {warnings, - [{4,v3_kernel,{nomatch_shadow,2}}]}}], + [{{4,13},v3_kernel,{nomatch_shadow,2}}]}}], [] = run(Config, Ts), ok. @@ -205,12 +205,12 @@ pattern4(Config) when is_list(Config) -> ">>, [nowarn_unused_vars], {warnings, - [{9,sys_core_fold,no_clause_match}, - {11,sys_core_fold,nomatch_shadow}, - {15,sys_core_fold,nomatch_shadow}, - {18,sys_core_fold,no_clause_match}, - {23,sys_core_fold,no_clause_match}, - {33,sys_core_fold,no_clause_match} + [{{9,16},sys_core_fold,no_clause_match}, + {{11,18},sys_core_fold,nomatch_shadow}, + {{15,18},sys_core_fold,nomatch_shadow}, + {{18,16},sys_core_fold,no_clause_match}, + {{23,16},sys_core_fold,no_clause_match}, + {{33,16},sys_core_fold,no_clause_match} ]}}], [] = run(Config, Ts), @@ -234,14 +234,14 @@ guard(Config) when is_list(Config) -> ">>, [nowarn_unused_vars], {warnings, - [{2,sys_core_fold,no_clause_match}, - {2,sys_core_fold,nomatch_guard}, - {2,sys_core_fold,{eval_failure,badarg}}, - {4,sys_core_fold,no_clause_match}, - {4,sys_core_fold,nomatch_guard}, - {6,sys_core_fold,no_clause_match}, - {6,sys_core_fold,nomatch_guard}, - {6,sys_core_fold,{eval_failure,badarg}} + [{{2,15},sys_core_fold,no_clause_match}, + {{2,15},sys_core_fold,nomatch_guard}, + {{2,28},sys_core_fold,{eval_failure,badarg}}, + {{4,15},sys_core_fold,no_clause_match}, + {{4,15},sys_core_fold,nomatch_guard}, + {{6,15},sys_core_fold,no_clause_match}, + {{6,15},sys_core_fold,nomatch_guard}, + {{6,26},sys_core_fold,{eval_failure,badarg}} ]}}], [] = run(Config, Ts), @@ -252,14 +252,14 @@ bad_arith(Config) when is_list(Config) -> <<"f() -> if a + 3 > 3 -> ok; - true -> error + true -> error end. g(A) -> if is_integer(A), a + 3 > 3 -> ok; a + 3 > 42, is_integer(A) -> ok; - true -> error + true -> error end. h(A) -> @@ -267,14 +267,14 @@ bad_arith(Config) when is_list(Config) -> ">>, [], {warnings, - [{3,sys_core_fold,nomatch_guard}, - {3,sys_core_fold,{eval_failure,badarith}}, - {9,sys_core_fold,nomatch_guard}, - {9,sys_core_fold,{eval_failure,badarith}}, - {9,sys_core_fold,{no_effect,{erlang,is_integer,1}}}, - {10,sys_core_fold,nomatch_guard}, - {10,sys_core_fold,{eval_failure,badarith}}, - {15,sys_core_fold,{eval_failure,badarith}} + [{{3,19},sys_core_fold,nomatch_guard}, + {{3,21},sys_core_fold,{eval_failure,badarith}}, + {{9,19},sys_core_fold,nomatch_guard}, + {{9,19},sys_core_fold,{no_effect,{erlang,is_integer,1}}}, + {{9,36},sys_core_fold,{eval_failure,badarith}}, + {{10,19},sys_core_fold,nomatch_guard}, + {{10,21},sys_core_fold,{eval_failure,badarith}}, + {{15,19},sys_core_fold,{eval_failure,badarith}} ] }}], [] = run(Config, Ts), ok. @@ -296,18 +296,18 @@ bool_cases(Config) when is_list(Config) -> Other -> {error,not_bool} end. - h(Bool) -> - case not Bool of - maybe -> strange; - false -> ok; - true -> error - end. + h(Bool) -> + case not Bool of + maybe -> strange; + false -> ok; + true -> error + end. ">>, - [nowarn_unused_vars], - {warnings, - [{6,sys_core_fold,nomatch_shadow}, - {13,sys_core_fold,nomatch_shadow}, - {18,sys_core_fold,nomatch_clause_type} ]} }], + [nowarn_unused_vars], + {warnings, + [{{6,18},sys_core_fold,nomatch_shadow}, + {{13,18},sys_core_fold,nomatch_shadow}, + {{18,18},sys_core_fold,nomatch_clause_type} ]} }], [] = run(Config, Ts), ok. @@ -320,13 +320,13 @@ bad_apply(Config) when is_list(Config) -> t(4) -> []:start(); t(5) -> erlang:[](). ">>, - [], - {warnings, - [{2,v3_kernel,bad_call}, - {3,v3_kernel,bad_call}, - {4,v3_kernel,bad_call}, - {5,v3_kernel,bad_call}, - {6,v3_kernel,bad_call}]}}], + [], + {warnings, + [{{2,22},v3_kernel,bad_call}, + {{3,22},v3_kernel,bad_call}, + {{4,22},v3_kernel,bad_call}, + {{5,22},v3_kernel,bad_call}, + {{6,22},v3_kernel,bad_call}]}}], [] = run(Config, Ts), %% Also verify that the generated code generates the correct error. @@ -352,108 +352,63 @@ files(Config) when is_list(Config) -> ">>, [], {warnings, - [{"file1",[{17,sys_core_fold,{eval_failure,badarith}}]}, - {"file2",[{10,sys_core_fold,{eval_failure,badarith}}]}]}}], + [{"file1",[{{17,20},sys_core_fold,{eval_failure,badarith}}]}, + {"file2",[{{10,20},sys_core_fold,{eval_failure,badarith}}]}]}}], [] = run(Config, Ts), ok. %% Test warnings for term construction and BIF calls in effect context. effect(Config) when is_list(Config) -> - Ts = [{effect, - <<" - t(X) -> - case X of - warn_lc -> - [is_integer(Z) || Z <- [1,2,3]]; - warn_lc_2 -> - [{error,Z} || Z <- [1,2,3]]; - warn_lc_3 -> - [{error,abs(Z)} || Z <- [1,2,3]]; - no_warn_lc -> - [put(last_integer, Z) || Z <- [1,2,3]]; %no warning - unused_tuple_literal -> - {a,b,c}; - unused_list_literal -> - [1,2,3,4]; - unused_integer -> - 42; - unused_arith -> - X*X; - nested -> - [{ok,node(),?MODULE:foo(),self(),[time(),date()],time()}, - is_integer(X)]; - unused_bit_syntax -> - <<X:8>>; - unused_fun -> - fun() -> {ok,X} end; - unused_named_fun -> - fun F(0) -> 1; - F(N) -> N*F(N-1) - end; - unused_atom -> - ignore; %no warning - unused_nil -> - []; %no warning - comp_op -> - X =:= 2; - cookie -> - erlang:get_cookie(); - result_ignore -> - _ = list_to_integer(X); - warn_lc_4 -> - %% No warning because of assignment to _. - [_ = abs(Z) || Z <- [1,2,3]] - end, - ok. - - %% No warnings should be generated in the following functions. + Ts = [{no_warnings, + %% No warnings should be generated in the following functions. + <<" m1(X, Sz) -> if - Sz =:= 0 -> X = 0; - true -> ok + Sz =:= 0 -> X = 0; + true -> ok end, ok. m2(X, Sz) -> if - Sz =:= 0 -> X = {a,Sz}; - true -> ok + Sz =:= 0 -> X = {a,Sz}; + true -> ok end, ok. m3(X, Sz) -> if - Sz =:= 0 -> X = [a,Sz]; - true -> ok + Sz =:= 0 -> X = [a,Sz]; + true -> ok end, ok. m4(X, Sz, Var) -> if - Sz =:= 0 -> X = Var; - true -> ok + Sz =:= 0 -> X = Var; + true -> ok end, ok. m5(X, Sz) -> if - Sz =:= 0 -> X = {a,b,c}; - true -> ok + Sz =:= 0 -> X = {a,b,c}; + true -> ok end, ok. m6(X, Sz) -> if - Sz =:= 0 -> X = {a,Sz,[1,2,3]}; - true -> ok + Sz =:= 0 -> X = {a,Sz,[1,2,3]}; + true -> ok end, ok. m7(X, Sz) -> if - Sz =:= 0 -> X = {a,Sz,[1,2,3],abs(Sz)}; - true -> ok + Sz =:= 0 -> X = {a,Sz,[1,2,3],abs(Sz)}; + true -> ok end, ok. @@ -473,34 +428,103 @@ effect(Config) when is_list(Config) -> CurrentConfig = {id(camel_phase3),id(sms)}, case CurrentConfig of {apa, bepa} -> ok; - _ -> ok - end + _ -> ok + end end, ok. id(I) -> I. ">>, - [], - {warnings,[{5,sys_core_fold,{no_effect,{erlang,is_integer,1}}}, - {7,sys_core_fold,useless_building}, - {9,sys_core_fold,result_ignored}, - {9,sys_core_fold,useless_building}, - {13,sys_core_fold,useless_building}, - {15,sys_core_fold,useless_building}, - {17,sys_core_fold,useless_building}, - {19,sys_core_fold,result_ignored}, - {21,sys_core_fold,useless_building}, - {21,sys_core_fold,{no_effect,{erlang,date,0}}}, - {21,sys_core_fold,{no_effect,{erlang,node,0}}}, - {21,sys_core_fold,{no_effect,{erlang,self,0}}}, - {21,sys_core_fold,{no_effect,{erlang,time,0}}}, - {22,sys_core_fold,useless_building}, - {22,sys_core_fold,{no_effect,{erlang,is_integer,1}}}, - {24,sys_core_fold,useless_building}, - {26,sys_core_fold,useless_building}, - {28,sys_core_fold,useless_building}, - {36,sys_core_fold,{no_effect,{erlang,'=:=',2}}}, - {38,sys_core_fold,{no_effect,{erlang,get_cookie,0}}}]}}], + [],[]}, + + {basic, + <<" + t(X) -> + case X of + warn_lc -> + [is_integer(Z) || Z <- [1,2,3]]; + warn_lc_2 -> + [{error,Z} || Z <- [1,2,3]]; + warn_lc_3 -> + [{error,abs(Z)} || Z <- [1,2,3]]; + no_warn_lc -> + [put(last_integer, Z) || Z <- [1,2,3]]; %no warning + unused_tuple_literal -> + {a,b,c}; + unused_list_literal -> + [1,2,3,4]; + unused_integer -> + 42; + unused_arith -> + X*X + end, + ok. + ">>, + [], + {warnings,[{{5,22},sys_core_fold,{no_effect,{erlang,is_integer,1}}}, + {{7,22},sys_core_fold,useless_building}, + {{9,22},sys_core_fold,useless_building}, + {{9,29},sys_core_fold,result_ignored}, + {{13,21},sys_core_fold,useless_building}, + {{15,21},sys_core_fold,useless_building}, + {{17,21},sys_core_fold,useless_building}, + {{19,22},sys_core_fold,result_ignored}]}}, + + {nested, + <<" + t(X) -> + case X of + nested -> + [{ok,node(),module:foo(),self(),[time(),date()],time()}, + is_integer(X)]; + unused_bit_syntax -> + <<X:8>>; + unused_fun -> + fun() -> {ok,X} end; + unused_named_fun -> + fun F(0) -> 1; + F(N) -> N*F(N-1) + end; + unused_atom -> + ignore; %no warning + unused_nil -> + []; %no warning + comp_op -> + X =:= 2; + cookie -> + erlang:get_cookie(); + result_ignore -> + _ = list_to_integer(X); + warn_lc_4 -> + %% No warning because of assignment to _. + [_ = abs(Z) || Z <- [1,2,3]] + end, + ok. + ">>, + [], + {warnings,[{{5,21},sys_core_fold,useless_building}, + {{5,26},sys_core_fold,{no_effect,{erlang,node,0}}}, + {{5,46},sys_core_fold,{no_effect,{erlang,self,0}}}, + {{5,54},sys_core_fold,{no_effect,{erlang,time,0}}}, + {{5,61},sys_core_fold,{no_effect,{erlang,date,0}}}, + {{5,69},sys_core_fold,{no_effect,{erlang,time,0}}}, + {{6,22},sys_core_fold,{no_effect,{erlang,is_integer,1}}}, + {{8,21},sys_core_fold,useless_building}, + {{10,21},sys_core_fold,useless_building}, + {{12,21},sys_core_fold,useless_building}, + {{20,23},sys_core_fold,{no_effect,{erlang,'=:=',2}}}, + {{22,21},sys_core_fold,{no_effect,{erlang,get_cookie,0}}}]}}, + + {seq, + <<" + t(T) -> + [ {a,b,T} ], [ {x,y,T} ], + ok. + ">>, + [], + {warnings,[{{3,16},sys_core_fold,useless_building}, + {{3,30},sys_core_fold,useless_building}]}} + ], [] = run(Config, Ts), ok. @@ -508,9 +532,9 @@ bin_opt_info(Config) when is_list(Config) -> Code = <<" t1(Bin) -> case Bin of - _ when byte_size(Bin) > 20 -> erlang:error(too_long); + _ when byte_size(Bin) > 20 -> erlang:error(too_long); <<_,T/binary>> -> t1(T); - <<>> -> ok + <<>> -> ok end. %% We use a tail in a BIF instruction, remote call, function @@ -533,17 +557,17 @@ bin_opt_info(Config) when is_list(Config) -> %% to run. {warnings, [{5,beam_ssa_bsm,{unsuitable_call, - {{b_local,{b_literal,t1},1}, - {used_before_match, - {b_set,_,_,{bif,byte_size},[_]}}}}}, + {{b_local,{b_literal,t1},1}, + {used_before_match, + {b_set,_,_,{bif,byte_size},[_]}}}}}, {5,beam_ssa_bsm,{binary_created,_,_}}, {11,beam_ssa_bsm,{binary_created,_,_}}, %% A =< B -> T {13,beam_ssa_bsm,context_reused}, %% A > B -> t2(T); {16,beam_ssa_bsm,{binary_created,_,_}}, %% when byte_size(T) < 4 -> {19,beam_ssa_bsm,{remote_call, - {b_remote, - {b_literal,erlang}, - {b_literal,split_binary},2}}}, + {b_remote, + {b_literal,erlang}, + {b_literal,split_binary},2}}}, {19,beam_ssa_bsm,{binary_created,_,_}} %% split_binary(T, 4) ]} = Ws, @@ -563,9 +587,9 @@ bin_construction(Config) when is_list(Config) -> Bin = <<1,2,3,7:4>>, <<Bin/binary>>. ">>, - [], - {warnings,[{4,sys_core_fold,embedded_binary_size}, - {8,sys_core_fold,{embedded_unit,8,28}}]}}], + [], + {warnings,[{{4,18},sys_core_fold,embedded_binary_size}, + {{8,18},sys_core_fold,{embedded_unit,8,28}}]}}], [] = run(Config, Ts), ok. @@ -595,17 +619,17 @@ maps(Config) when is_list(Config) -> end. ">>, [], - {warnings,[{3,sys_core_fold,no_clause_match}, - {9,sys_core_fold,nomatch_clause_type}]}}, + {warnings,[{{3,18},sys_core_fold,no_clause_match}, + {{9,22},sys_core_fold,nomatch_clause_type}]}}, {bad_map_src1, <<" t() -> - M = {a,[]}, - {'EXIT',{badarg,_}} = (catch(M#{ a => 1 })), - ok. + M = {a,[]}, + {'EXIT',{badarg,_}} = (catch(M#{ a => 1 })), + ok. ">>, [], - {warnings,[{4,sys_core_fold,{eval_failure,badmap}}]}}, + {warnings,[{{4,48},sys_core_fold,{eval_failure,badmap}}]}}, {bad_map_src2, <<" t() -> @@ -619,11 +643,11 @@ maps(Config) when is_list(Config) -> {bad_map_src3, <<" t() -> - {'EXIT',{badarg,_}} = (catch <<>>#{ a := 1}), - ok. + {'EXIT',{badarg,_}} = (catch <<>>#{ a := 1}), + ok. ">>, [], - {warnings,[{3,sys_core_fold,{eval_failure,badmap}}]}}, + {warnings,[{{3,51},sys_core_fold,{eval_failure,badmap}}]}}, {ok_map_literal_key, <<" t() -> @@ -672,15 +696,15 @@ maps(Config) when is_list(Config) -> M#{<<\"a\">>=>1, <<\"b\">>=> 2, <<\"a\">>:=3}. ">>, [], - {warnings,[{3,v3_core,{map_key_repeated,a}}, - {8,v3_core,{map_key_repeated,a}}, - {11,v3_core,{map_key_repeated,a}}, - {14,v3_core,{map_key_repeated,"a"}}, - {17,v3_core,{map_key_repeated,"a"}}, - {20,v3_core,{map_key_repeated,"a"}}, - {23,v3_core,{map_key_repeated,"a"}}, - {28,v3_core,{map_key_repeated,"a"}}, - {31,v3_core,{map_key_repeated,<<"a">>}}]}}, + {warnings,[{{3,20},v3_core,{map_key_repeated,a}}, + {{8,21},v3_core,{map_key_repeated,a}}, + {{11,21},v3_core,{map_key_repeated,a}}, + {{14,20},v3_core,{map_key_repeated,"a"}}, + {{17,21},v3_core,{map_key_repeated,"a"}}, + {{20,21},v3_core,{map_key_repeated,"a"}}, + {{23,20},v3_core,{map_key_repeated,"a"}}, + {{28,21},v3_core,{map_key_repeated,"a"}}, + {{31,21},v3_core,{map_key_repeated,<<"a">>}}]}}, {repeated_keys2, <<" foo4(K) -> @@ -733,15 +757,15 @@ maps(Config) when is_list(Config) -> M1#{#{<<\"a\">>=>1}:=3,K=>2}. ">>, [], - {warnings,[{3,v3_core,{map_key_repeated,"a"}}, - {6,v3_core,{map_key_repeated,a}}, - {9,v3_core,{map_key_repeated,<<"a">>}}, - {14,v3_core,{map_key_repeated,{"a",1}}}, - {17,v3_core,{map_key_repeated,{"a",<<"b">>}}}, - {21,v3_core,{map_key_repeated,{<<"a">>,1}}}, - {25,v3_core,{map_key_repeated,#{"a" => 1}}}, - {28,v3_core,{map_key_repeated,#{"a" => <<"b">>}}}, - {32,v3_core,{map_key_repeated,#{<<"a">> => 1}}}]}} + {warnings,[{{3,20},v3_core,{map_key_repeated,"a"}}, + {{6,21},v3_core,{map_key_repeated,a}}, + {{9,21},v3_core,{map_key_repeated,<<"a">>}}, + {{14,20},v3_core,{map_key_repeated,{"a",1}}}, + {{17,21},v3_core,{map_key_repeated,{"a",<<"b">>}}}, + {{21,21},v3_core,{map_key_repeated,{<<"a">>,1}}}, + {{25,20},v3_core,{map_key_repeated,#{"a" => 1}}}, + {{28,21},v3_core,{map_key_repeated,#{"a" => <<"b">>}}}, + {{32,21},v3_core,{map_key_repeated,#{<<"a">> => 1}}}]}} ], run(Config, Ts), ok. @@ -770,7 +794,7 @@ redundant_boolean_clauses(Config) when is_list(Config) -> end. ">>, [], - {warnings,[{5,sys_core_fold,nomatch_shadow}]}}], + {warnings,[{{5,22},sys_core_fold,nomatch_shadow}]}}], run(Config, Ts), ok. @@ -782,13 +806,13 @@ latin1_fallback(Conf) when is_list(Conf) -> %% Test that the compiler fall backs to latin-1 with %% a warning if a file has no encoding and does not %% contain correct UTF-8 sequences. - <<"%% Bj",246,"rn + <<"\n%% Bj",246,"rn t(_) -> \"",246,"\"; t(x) -> ok. ">>, - [], - {warnings,[{1,compile,reparsing_invalid_unicode}, - {3,sys_core_fold,{nomatch_shadow,2,{t,1}}}]}}], + [], + {warnings,[{{2,1},compile,reparsing_invalid_unicode}, + {{4,15},sys_core_fold,{nomatch_shadow,3,{t,1}}}]}}], [] = run(Conf, Ts1), Ts2 = [{latin1_fallback2, @@ -800,7 +824,7 @@ latin1_fallback(Conf) when is_list(Conf) -> -include(\"include_me.hrl\"). ">>, [], - {warnings,[{1,compile,reparsing_invalid_unicode}]} + {warnings,[{{1,1},compile,reparsing_invalid_unicode}]} }], [] = run(Conf, Ts2), @@ -814,38 +838,39 @@ latin1_fallback(Conf) when is_list(Conf) -> -endif. ">>, [], - {warnings,[{2,compile,reparsing_invalid_unicode}]}}], + {warnings,[{{2,24},compile,reparsing_invalid_unicode}]}}], [] = run(Conf, Ts3), ok. underscore(Config) when is_list(Config) -> %% The code template. - S0 = <<"f(A) -> + S0 = <<" + f(A) -> _VAR1 = <<A>>, _VAR2 = {ok,A}, _VAR3 = [A], ok. - g(A) -> + g(A) -> _VAR1 = A/0, _VAR2 = date(), - ok. + ok. h() -> _VAR1 = fun() -> ok end, - ok. + ok. i(A) -> _VAR1 = #{A=>42}, - ok. + ok. ">>, %% Define all possible warnings. - Warnings = [{2,sys_core_fold,useless_building}, - {3,sys_core_fold,useless_building}, - {4,sys_core_fold,useless_building}, - {7,sys_core_fold,result_ignored}, - {8,sys_core_fold,{no_effect,{erlang,date,0}}}, - {11,sys_core_fold,useless_building}, - {14,sys_core_fold,useless_building}], + Warnings = [{{3,23},sys_core_fold,useless_building}, + {{4,23},sys_core_fold,useless_building}, + {{5,23},sys_core_fold,useless_building}, + {{8,24},sys_core_fold,result_ignored}, + {{9,23},sys_core_fold,{no_effect,{erlang,date,0}}}, + {{12,24},sys_core_fold,useless_building}, + {{15,24},sys_core_fold,useless_building}], %% Compile the unmodified template. Assigning to variable that @@ -861,7 +886,7 @@ underscore(Config) when is_list(Config) -> [] = run(Config, Ts1), %% Make sure that we get warnings if we remove "_VAR<digit> = ". - S2 = re:replace(S0, "_VAR\\d+ = ", "", [global]), + S2 = re:replace(S0, "_VAR\\d = ", " ", [global]), io:format("~s\n", [S2]), Ts2 = [{underscore2,S2,[],{warnings,Warnings}}], [] = run(Config, Ts2), @@ -869,7 +894,7 @@ underscore(Config) when is_list(Config) -> %% We should also get warnings if we assign to a variables that don't %% begin with underscore (as well as warnings for unused variables from %% erl_lint). - S3 = re:replace(S0, "_(?=VAR\\d+)", "", [global]), + S3 = re:replace(S0, "_(?=VAR\\d+)", " ", [global]), io:format("~s\n", [S3]), Ts3 = [{underscore2,S3,[],{warnings,Warnings}}], [] = run(Config, Ts3), @@ -913,7 +938,8 @@ no_warnings(Config) when is_list(Config) -> bit_syntax(Config) -> Ts = [{?FUNCTION_NAME, - <<"a(<<-1>>) -> ok; + <<" + a(<<-1>>) -> ok; a(<<1023>>) -> ok; a(<<777/signed>>) -> ok; a(<<a/binary>>) -> ok; @@ -932,25 +958,25 @@ bit_syntax(Config) -> end. ">>, [], - {warnings,[{1,sys_core_fold,no_clause_match}, - {1,sys_core_fold,{nomatch_bit_syntax_unsigned,-1}}, - {2,sys_core_fold,{nomatch_bit_syntax_truncated, - unsigned,1023,8}}, - {3,sys_core_fold,{nomatch_bit_syntax_truncated, - signed,777,8}}, - {4,sys_core_fold,{nomatch_bit_syntax_type,a,binary}}, - {5,sys_core_fold,{nomatch_bit_syntax_type,a,integer}}, - {6,sys_core_fold,{nomatch_bit_syntax_type,a,float}}, - {7,sys_core_fold,{nomatch_bit_syntax_type,a,utf8}}, - {8,sys_core_fold,{nomatch_bit_syntax_type,a,utf16}}, - {9,sys_core_fold,{nomatch_bit_syntax_type,a,utf32}}, - {10,sys_core_fold,{nomatch_bit_syntax_type,a,utf32}}, - {11,sys_core_fold,no_clause_match}, - {11,sys_core_fold,{nomatch_bit_syntax_size,bad}}, - {14,sys_core_fold,{nomatch_bit_syntax_unsigned,-42}}, - {16,sys_core_fold,{nomatch_bit_syntax_type,42,binary}} - ]} - }], + {warnings,[{{2,15},sys_core_fold,no_clause_match}, + {{2,19},sys_core_fold,{nomatch_bit_syntax_unsigned,-1}}, + {{3,19},sys_core_fold,{nomatch_bit_syntax_truncated, + unsigned,1023,8}}, + {{4,19},sys_core_fold,{nomatch_bit_syntax_truncated, + signed,777,8}}, + {{5,19},sys_core_fold,{nomatch_bit_syntax_type,a,binary}}, + {{6,19},sys_core_fold,{nomatch_bit_syntax_type,a,integer}}, + {{7,19},sys_core_fold,{nomatch_bit_syntax_type,a,float}}, + {{8,19},sys_core_fold,{nomatch_bit_syntax_type,a,utf8}}, + {{9,19},sys_core_fold,{nomatch_bit_syntax_type,a,utf16}}, + {{10,19},sys_core_fold,{nomatch_bit_syntax_type,a,utf32}}, + {{11,19},sys_core_fold,{nomatch_bit_syntax_type,a,utf32}}, + {{12,37},sys_core_fold,{nomatch_bit_syntax_size,bad}}, + {{12,45},sys_core_fold,no_clause_match}, + {{15,21},sys_core_fold,{nomatch_bit_syntax_unsigned,-42}}, + {{17,21},sys_core_fold,{nomatch_bit_syntax_type,42,binary}} + ]} + }], run(Config, Ts), ok. @@ -1053,8 +1079,33 @@ recv_opt_info(Config) when is_list(Config) -> %%% End of test cases. %%% -run(Config, Tests) -> +run(Config, Tests0) -> + do_run(Config, Tests0), + + %% Now test without column numbers. + Tests = [lines_only(T) || T <- Tests0], + do_run(Config, Tests). + +lines_only({Name,Test,Opts,{warnings,Result0}}) -> + Result1 = lists:map(fun lines_only_1/1, Result0), + Result = {warnings,lists:usort(Result1)}, + {Name,Test,[{error_location,line}|Opts],Result}; +lines_only(NoWarnings) -> NoWarnings. + +lines_only_1({File,Es0}) when is_list(Es0) -> + Es = [lines_only_1(E) || E <- Es0], + {File,Es}; +lines_only_1({Loc,Mod,Error}) -> + case Loc of + {Line,_Col} -> + {Line,Mod,Error}; + Line when is_integer(Line) -> + {Line,Mod,Error} + end. + +do_run(Config, Tests) -> F = fun({N,P,Ws,E}, BadL) -> + io:format("### ~s\n", [N]), case catch run_test(Config, P, Ws) of E -> BadL; @@ -1069,15 +1120,16 @@ run(Config, Tests) -> %% Compiles a test module and returns the list of errors and warnings. run_test(Conf, Test0, Warnings) -> - Module = "warnings_"++test_lib:uniq(), + Module = "warnings" ++ test_lib:uniq(), Filename = Module ++ ".erl", DataDir = ?privdir, - Test = ["-module(", Module, "). ", Test0], + Test1 = ["-module(", Module, "). -file( \"", Filename, "\", 1). ", Test0], + Test = iolist_to_binary(Test1), File = filename:join(DataDir, Filename), Opts = [binary,export_all,return|Warnings], ok = file:write_file(File, Test), - %% Compile once just to print all warnings. + %% Compile once just to print all warnings (and cover more code). compile:file(File, [binary,export_all,report|Warnings]), %% Test result of compilation. @@ -1091,12 +1143,33 @@ run_test(Conf, Test0, Warnings) -> Mod =/= erl_lint]} || {F,Ws} <- Ws0], case WsL of - [{_File,Ws}] -> {warnings, Ws}; - _ -> list_to_tuple([warnings, WsL]) + [{_File,Ws}] -> + print_warnings(Ws, Test), + {warnings, Ws}; + _ -> + list_to_tuple([warnings, WsL]) end end, file:delete(File), Res. +print_warnings(Warnings, Source) -> + Lines = binary:split(Source, <<"\n">>, [global]), + Cs = [print_warning(W, Lines) || W <- Warnings], + io:put_chars(Cs), + ok. + +print_warning({{LineNum,Column},Mod,Data}, Lines) -> + Line0 = lists:nth(LineNum, Lines), + <<Line1:(Column-1)/binary,_/binary>> = Line0, + Spaces = re:replace(Line1, <<"[^\t]">>, <<" ">>, [global]), + CaretLine = [Spaces,"^"], + [io_lib:format("~p:~p: ~ts\n", + [LineNum,Column,Mod:format_error(Data)]), + Line0, "\n", + CaretLine, "\n\n"]; +print_warning(_, _) -> + []. + fail() -> ct:fail(failed). |