Skip to content

Commit 9154c8b

Browse files
authored
Include column in warnings emitted by compiler (#11339)
We also extend Mix.Tasks.Compiler.Diagnostic to include {line, column} as possible position. We also explicitly document the behaviour of line=0, which is equivalent to unknown line. Elixir was already setting the line to zero in multiple occasions prior to this patch, so this patch makes it official and we stop returning `nil` for said cases.
1 parent f315500 commit 9154c8b

File tree

14 files changed

+135
-85
lines changed

14 files changed

+135
-85
lines changed

lib/eex/lib/eex/compiler.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ defmodule EEx.Compiler do
7272
message =
7373
"the contents of this expression won't be output unless the EEx block starts with \"<%=\""
7474

75-
:elixir_errors.erl_warn(start_line, state.file, message)
75+
:elixir_errors.erl_warn({start_line, start_column}, state.file, message)
7676
end
7777

7878
{rest, line, contents} =
@@ -117,7 +117,7 @@ defmodule EEx.Compiler do
117117
"unexpected beginning of EEx tag \"<%#{modifier}\" on \"<%#{modifier}#{chars}%>\", " <>
118118
"please remove \"#{modifier}\" accordingly"
119119

120-
:elixir_errors.erl_warn(line, state.file, message)
120+
:elixir_errors.erl_warn({line, column}, state.file, message)
121121
generate_buffer([{:middle_expr, line, column, '', chars} | t], buffer, scope, state)
122122
# TODO: Make this an error on Elixir v2.0 since it accidentally worked previously.
123123
# raise EEx.SyntaxError, message: message, file: state.file, line: line
@@ -155,7 +155,7 @@ defmodule EEx.Compiler do
155155
"unexpected beginning of EEx tag \"<%#{modifier}\" on end of " <>
156156
"expression \"<%#{modifier}#{chars}%>\", please remove \"#{modifier}\" accordingly"
157157

158-
:elixir_errors.erl_warn(line, state.file, message)
158+
:elixir_errors.erl_warn({line, column}, state.file, message)
159159
generate_buffer([{:end_expr, line, column, '', chars} | t], buffer, scope, state)
160160
# TODO: Make this an error on Elixir v2.0 since it accidentally worked previously.
161161
# raise EEx.SyntaxError, message: message, file: state.file, line: line, column: column

lib/eex/lib/eex/tokenizer.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ defmodule EEx.Tokenizer do
7272
{key, expr} =
7373
case :elixir_tokenizer.tokenize(expr, 1, file: "eex", check_terminators: false) do
7474
{:ok, _line, _column, warnings, tokens} ->
75-
Enum.each(Enum.reverse(warnings), fn {line, file, msg} ->
76-
:elixir_errors.erl_warn(line, file, msg)
75+
Enum.each(Enum.reverse(warnings), fn {location, file, msg} ->
76+
:elixir_errors.erl_warn(location, file, msg)
7777
end)
7878

7979
token_key(tokens, expr)

lib/elixir/lib/io.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ defmodule IO do
305305
@spec warn(chardata | String.Chars.t(), Exception.stacktrace()) :: :ok
306306
def warn(message, []) do
307307
message = [to_chardata(message), ?\n]
308-
:elixir_errors.io_warn(0, nil, message, message)
308+
:elixir_errors.log_and_print_warning(0, nil, message, message)
309309
end
310310

311311
def warn(message, [{_, _, _, opts} | _] = stacktrace) do
@@ -314,7 +314,7 @@ defmodule IO do
314314
line = opts[:line]
315315
file = opts[:file]
316316

317-
:elixir_errors.io_warn(
317+
:elixir_errors.log_and_print_warning(
318318
line || 0,
319319
file && List.to_string(file),
320320
message,

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ defmodule Kernel.ParallelCompiler do
33
A module responsible for compiling and requiring files in parallel.
44
"""
55

6+
@typedoc "The line. 0 indicates no line."
7+
@type line() :: non_neg_integer()
8+
@type location() :: line() | {line(), column :: non_neg_integer}
9+
@type warning() :: {file :: Path.t(), location(), message :: String.t()}
10+
@type error() :: {file :: Path.t(), line(), message :: String.t()}
11+
612
@doc """
713
Starts a task for parallel compilation.
814
@@ -91,6 +97,7 @@ defmodule Kernel.ParallelCompiler do
9197
9298
"""
9399
@doc since: "1.6.0"
100+
@spec compile([Path.t()], keyword()) :: {:ok, [atom], [warning]} | {:error, [error], [warning]}
94101
def compile(files, options \\ []) when is_list(options) do
95102
spawn_workers(files, :compile, options)
96103
end
@@ -101,6 +108,8 @@ defmodule Kernel.ParallelCompiler do
101108
See `compile/2` for more information.
102109
"""
103110
@doc since: "1.6.0"
111+
@spec compile_to_path([Path.t()], Path.t(), keyword()) ::
112+
{:ok, [atom], [warning]} | {:error, [error], [warning]}
104113
def compile_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do
105114
spawn_workers(files, {:compile, path}, options)
106115
end
@@ -126,10 +135,21 @@ defmodule Kernel.ParallelCompiler do
126135
127136
"""
128137
@doc since: "1.6.0"
138+
@spec compile_to_path([Path.t()], keyword()) ::
139+
{:ok, [atom], [warning]} | {:error, [error], [warning]}
129140
def require(files, options \\ []) when is_list(options) do
130141
spawn_workers(files, :require, options)
131142
end
132143

144+
@doc """
145+
Prints a warning returned by the compiler.
146+
"""
147+
@doc since: "1.13.0"
148+
@spec print_warning(warning) :: :ok
149+
def print_warning({file, location, warning}) do
150+
:elixir_errors.print_warning(location, file, warning)
151+
end
152+
133153
@doc false
134154
@deprecated "Use Kernel.ParallelCompiler.compile/2 instead"
135155
def files(files, options \\ []) when is_list(options) do
@@ -557,10 +577,10 @@ defmodule Kernel.ParallelCompiler do
557577
state = %{state | timer_ref: timer_ref}
558578
spawn_workers(queue, spawned, waiting, files, result, warnings, state)
559579

560-
{:warning, file, line, message} ->
580+
{:warning, file, location, message} ->
561581
file = file && Path.absname(file)
562582
message = :unicode.characters_to_binary(message)
563-
warning = {file, line, message}
583+
warning = {file, location, message}
564584
wait_for_messages(queue, spawned, waiting, files, result, [warning | warnings], state)
565585

566586
{:file_ok, child_pid, ref, file, lexical} ->
@@ -729,7 +749,7 @@ defmodule Kernel.ParallelCompiler do
729749
line = get_line(file, reason, stack)
730750
file = Path.absname(file)
731751
message = :unicode.characters_to_binary(Kernel.CLI.format_error(kind, reason, stack))
732-
{file, line, message}
752+
{file, line || 0, message}
733753
end
734754

735755
defp get_line(_file, %{line: line}, _stack) when is_integer(line) and line > 0 do

lib/elixir/src/elixir_errors.erl

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,26 @@
44
%% Note that this is also called by the Erlang backend, so we also support
55
%% the line number to be none (as it may happen in some erlang errors).
66
-module(elixir_errors).
7-
-export([compile_error/3, compile_error/4, warning_prefix/0,
8-
form_error/4, form_warn/4, parse_error/4, erl_warn/3, io_warn/4]).
7+
-export([compile_error/3, compile_error/4, form_error/4, parse_error/4]).
8+
-export([warning_prefix/0, erl_warn/3, print_warning/3, log_and_print_warning/4, form_warn/4]).
99
-include("elixir.hrl").
10+
-type location() :: non_neg_integer() | {non_neg_integer(), non_neg_integer()}.
1011

1112
%% Low-level warning, should be used only from Erlang passes.
12-
-spec erl_warn(non_neg_integer() | none, unicode:chardata(), unicode:chardata()) -> ok.
13+
-spec erl_warn(location() | none, unicode:chardata(), unicode:chardata()) -> ok.
1314
erl_warn(none, File, Warning) ->
1415
erl_warn(0, File, Warning);
15-
erl_warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
16-
io_warn(Line, File, Warning, [Warning, "\n ", file_format(Line, File), $\n]).
16+
erl_warn(Location, File, Warning) when is_binary(File) ->
17+
send_warning(Location, File, Warning),
18+
print_warning(Location, File, Warning).
1719

18-
%% Low-level warning, all other warnings are built on top of it.
19-
-spec io_warn(non_neg_integer(), unicode:chardata() | nil, unicode:chardata(), unicode:chardata()) -> ok.
20-
io_warn(Line, File, LogMessage, PrintMessage) when is_integer(Line), is_binary(File) or (File == nil) ->
21-
send_warning(Line, File, LogMessage),
20+
-spec print_warning(location(), unicode:chardata(), unicode:chardata()) -> ok.
21+
print_warning(Location, File, Warning) ->
22+
print_warning([Warning, "\n ", file_format(Location, File), $\n]).
23+
24+
-spec log_and_print_warning(location(), unicode:chardata() | nil, unicode:chardata(), unicode:chardata()) -> ok.
25+
log_and_print_warning(Location, File, LogMessage, PrintMessage) when is_binary(File) or (File == nil) ->
26+
send_warning(Location, File, LogMessage),
2227
print_warning(PrintMessage).
2328

2429
-spec warning_prefix() -> binary().
@@ -59,7 +64,7 @@ do_form_warn(Meta, GivenFile, E, Warning) ->
5964
file_format(Line, File)
6065
end,
6166

62-
io_warn(Line, File, Warning, [Warning, "\n ", Location, $\n]).
67+
log_and_print_warning(Line, File, Warning, [Warning, "\n ", Location, $\n]).
6368

6469
%% Compilation error.
6570

@@ -176,9 +181,12 @@ send_warning(Line, File, Message) ->
176181
end,
177182
ok.
178183

184+
file_format({0, _Column}, File) ->
185+
io_lib:format("~ts", [elixir_utils:relative_to_cwd(File)]);
186+
file_format({Line, Column}, File) ->
187+
io_lib:format("~ts:~w:~w", [elixir_utils:relative_to_cwd(File), Line, Column]);
179188
file_format(0, File) ->
180189
io_lib:format("~ts", [elixir_utils:relative_to_cwd(File)]);
181-
182190
file_format(Line, File) ->
183191
io_lib:format("~ts:~w", [elixir_utils:relative_to_cwd(File), Line]).
184192

lib/elixir/src/elixir_parser.yrl

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ Erlang code.
652652
%% The following directive is needed for (significantly) faster
653653
%% compilation of the generated .erl file by the HiPE compiler
654654
-compile([{hipe, [{regalloc, linear_scan}]}]).
655-
-compile({inline, meta_from_token/1, meta_from_location/1, line_from_location/1, is_eol/1}).
655+
-compile({inline, meta_from_token/1, meta_from_location/1, is_eol/1}).
656656
-import(lists, [reverse/1, reverse/2]).
657657

658658
meta_from_token(Token) ->
@@ -664,8 +664,6 @@ meta_from_location({Line, Column, _}) ->
664664
false -> [{line, Line}]
665665
end.
666666

667-
line_from_location({Line, _Column, _}) -> Line.
668-
669667
do_end_meta(Do, End) ->
670668
case ?token_metadata() of
671669
true ->
@@ -728,13 +726,14 @@ build_op(AST, {_Kind, Location, '//'}, Right) ->
728726
return_error(Location, "the range step operator (//) must immediately follow the range definition operator (..), for example: 1..9//2. If you wanted to define a default argument, use (\\\\) instead. Syntax error before: ", "'//'")
729727
end;
730728

731-
build_op({UOp, _, [Left]}, {_Kind, Location, 'in'}, Right) when ?rearrange_uop(UOp) ->
729+
build_op({UOp, _, [Left]}, {_Kind, {Line, Column, _} = Location, 'in'}, Right) when ?rearrange_uop(UOp) ->
732730
%% TODO: Remove "not left in right" rearrangement on v2.0
733-
elixir_errors:erl_warn(line_from_location(Location), ?file(),
731+
elixir_errors:erl_warn({Line, Column}, ?file(),
734732
"\"not expr1 in expr2\" is deprecated. "
735733
"Instead use \"expr1 not in expr2\" if you require Elixir v1.5+, "
736734
"or \"not(expr1 in expr2)\" if you have to support earlier Elixir versions"),
737-
{UOp, meta_from_location(Location), [{'in', meta_from_location(Location), [Left, Right]}]};
735+
Meta = meta_from_location(Location),
736+
{UOp, Meta, [{'in', Meta, [Left, Right]}]};
738737

739738
build_op(Left, {_Kind, Location, 'not in'}, Right) ->
740739
Meta = meta_from_location(Location),
@@ -1168,16 +1167,16 @@ error_invalid_kw_identifier({_, Location, KW}) ->
11681167
return_error(Location, "syntax error before: ", "'" ++ atom_to_list(KW) ++ ":'").
11691168

11701169
%% TODO: Make this an error on v2.0
1171-
warn_empty_paren({_, {Line, _, _}}) ->
1172-
elixir_errors:erl_warn(Line, ?file(),
1170+
warn_empty_paren({_, {Line, Column, _}}) ->
1171+
elixir_errors:erl_warn({Line, Column}, ?file(),
11731172
"invalid expression (). "
11741173
"If you want to invoke or define a function, make sure there are "
11751174
"no spaces between the function name and its arguments. If you wanted "
11761175
"to pass an empty block or code, pass a value instead, such as a nil or an atom").
11771176

11781177
%% TODO: Make this an error on v2.0
1179-
warn_pipe({arrow_op, {Line, _, _}, Op}, {_, [_ | _], [_ | _]}) ->
1180-
elixir_errors:erl_warn(Line, ?file(),
1178+
warn_pipe({arrow_op, {Line, Column, _}, Op}, {_, [_ | _], [_ | _]}) ->
1179+
elixir_errors:erl_warn({Line, Column}, ?file(),
11811180
io_lib:format(
11821181
"parentheses are required when piping into a function call. For example:\n\n"
11831182
" foo 1 ~ts bar 2 ~ts baz 3\n\n"
@@ -1190,7 +1189,7 @@ warn_pipe({arrow_op, {Line, _, _}, Op}, {_, [_ | _], [_ | _]}) ->
11901189
warn_pipe(_Token, _) ->
11911190
ok.
11921191

1193-
warn_empty_stab_clause({stab_op, {Line, _, _}, '->'}) ->
1194-
elixir_errors:erl_warn(Line, ?file(),
1192+
warn_empty_stab_clause({stab_op, {Line, Column, _}, '->'}) ->
1193+
elixir_errors:erl_warn({Line, Column}, ?file(),
11951194
"an expression is always required on the right side of ->. "
11961195
"Please provide a value after ->").

0 commit comments

Comments
 (0)