Skip to content

Commit

Permalink
Merge branch 'elixir-lang:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasvegi authored Sep 25, 2023
2 parents 22efadd + c7bd0fe commit 51f0cc3
Show file tree
Hide file tree
Showing 18 changed files with 161 additions and 83 deletions.
8 changes: 3 additions & 5 deletions lib/eex/lib/eex/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,9 @@ defmodule EEx.Compiler do
{:ok, expr, new_line, new_column, rest} ->
{key, expr} =
case :elixir_tokenizer.tokenize(expr, 1, file: "eex", check_terminators: false) do
{:ok, _line, _column, warnings, tokens} ->
Enum.each(Enum.reverse(warnings), fn {location, msg} ->
:elixir_errors.erl_warn(location, state.file, msg)
end)

{:ok, _line, _column, _warnings, tokens} ->
# We ignore warnings because the code will be tokenized
# again later with the right line+column info
token_key(tokens, expr)

{:error, _, _, _, _} ->
Expand Down
6 changes: 0 additions & 6 deletions lib/eex/test/eex/tokenizer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ defmodule EEx.TokenizerTest do

@opts [indentation: 0, trim: false]

test "tokenizer warning" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.tokenize(~c"foo <% :'bar' %>", @opts)
end) =~ "found quoted atom \"bar\" but the quotes are not required"
end

test "simple charlists" do
assert EEx.tokenize(~c"foo", @opts) ==
{:ok, [{:text, ~c"foo", %{column: 1, line: 1}}, {:eof, %{column: 4, line: 1}}]}
Expand Down
72 changes: 41 additions & 31 deletions lib/eex/test/eex_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,19 @@ defmodule EExTest do
end
end

test "when <%!-- is not closed" do
message = """
my_file.eex:1:5: expected closing '--%>' for EEx expression
|
1 | foo <%!-- bar
| ^\
"""

assert_raise EEx.SyntaxError, message, fn ->
EEx.compile_string("foo <%!-- bar", file: "my_file.eex")
end
end

test "when the token is invalid" do
message = """
nofile:1:5: expected closing '%>' for EEx expression
Expand Down Expand Up @@ -476,39 +489,24 @@ defmodule EExTest do
end
end

test "when middle expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<%= else %>false<% end %>")
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on \"<%= else %>\"]
end

test "when end expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<% else %>false<%= end %>")
end) =~
~s[unexpected beginning of EEx tag \"<%=\" on \"<%= end %>\"]
end

test "when trying to use marker '/' without implementation" do
test "when trying to use marker '|' without implementation" do
msg =
~r/unsupported EEx syntax <%\/ %> \(the syntax is valid but not supported by the current EEx engine\)/
~r/unsupported EEx syntax <%| %> \(the syntax is valid but not supported by the current EEx engine\)/

assert_raise EEx.SyntaxError, msg, fn ->
EEx.compile_string("<%/ true %>")
EEx.compile_string("<%| true %>")
end
end

test "when trying to use marker '|' without implementation" do
test "when trying to use marker '/' without implementation" do
msg =
~r/unsupported EEx syntax <%| %> \(the syntax is valid but not supported by the current EEx engine\)/
~r/unsupported EEx syntax <%\/ %> \(the syntax is valid but not supported by the current EEx engine\)/

assert_raise EEx.SyntaxError, msg, fn ->
EEx.compile_string("<%| true %>")
EEx.compile_string("<%/ true %>")
end
end
end

describe "error messages" do
test "honor line numbers" do
assert_raise EEx.SyntaxError,
"nofile:100:6: expected closing '%>' for EEx expression",
Expand All @@ -529,18 +527,30 @@ defmodule EExTest do
EEx.compile_string("foo <%= bar", file: "my_file.eex")
end
end
end

test "when <%!-- is not closed" do
message = """
my_file.eex:1:5: expected closing '--%>' for EEx expression
|
1 | foo <%!-- bar
| ^\
"""
describe "warnings" do
test "when middle expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<%= else %>false<% end %>")
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on \"<%= else %>\"]
end

assert_raise EEx.SyntaxError, message, fn ->
EEx.compile_string("foo <%!-- bar", file: "my_file.eex")
end
test "when end expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<% else %>false<%= end %>")
end) =~
~s[unexpected beginning of EEx tag \"<%=\" on \"<%= end %>\"]
end

test "from tokenizer" do
warning =
ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string(~s'<%= :"foo" %>', file: "tokenizer.ex")
end)

assert warning =~ "found quoted atom \"foo\" but the quotes are not required"
assert warning =~ "tokenizer.ex:1:5"
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/elixir/lib/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ defmodule Config do
the `mix.exs` file and inside custom Mix tasks, which always within the
`Mix.Tasks` namespace.
## config/runtime.exs
## `config/runtime.exs`
For runtime configuration, you can use the `config/runtime.exs` file.
It is executed right before applications start in both Mix and releases
Expand Down
8 changes: 5 additions & 3 deletions lib/elixir/lib/module/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -413,14 +413,14 @@ defmodule Module.Types do

defp format_message_hint(:inferred_dot) do
"""
HINT: "var.field" (without parentheses) implies "var" is a map() while \
#{hint()} "var.field" (without parentheses) implies "var" is a map() while \
"var.fun()" (with parentheses) implies "var" is an atom()
"""
end

defp format_message_hint(:inferred_bitstring_spec) do
"""
HINT: all expressions given to binaries are assumed to be of type \
#{hint()} all expressions given to binaries are assumed to be of type \
integer() unless said otherwise. For example, <<expr>> assumes "expr" \
is an integer. Pass a modifier, such as <<expr::float>> or <<expr::binary>>, \
to change the default behaviour.
Expand All @@ -429,11 +429,13 @@ defmodule Module.Types do

defp format_message_hint({:sized_and_unsize_tuples, {size, var}}) do
"""
HINT: use pattern matching or "is_tuple(#{Macro.to_string(var)}) and \
#{hint()} use pattern matching or "is_tuple(#{Macro.to_string(var)}) and \
tuple_size(#{Macro.to_string(var)}) == #{size}" to guard a sized tuple.
"""
end

defp hint, do: :elixir_errors.prefix(:hint)

defp format_type_hint(type, types, expr, hints) do
case format_type_hint(type, types, expr) do
{message, hint} -> {message, [hint | hints]}
Expand Down
11 changes: 11 additions & 0 deletions lib/elixir/lib/range.ex
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ defmodule Range do
"""
@doc since: "1.14.0"
@spec shift(t, integer) :: t
def shift(first..last//step, steps_to_shift)
when is_integer(steps_to_shift) do
new(first + steps_to_shift * step, last + steps_to_shift * step, step)
Expand Down Expand Up @@ -364,6 +365,7 @@ defmodule Range do
"""
@doc since: "1.15.0"
@spec split(t, integer) :: {t, t}
def split(first..last//step = range, split) when is_integer(split) do
if split >= 0 do
split(first, last, step, split)
Expand Down Expand Up @@ -392,8 +394,17 @@ defmodule Range do

@doc """
Converts a range to a list.
## Examples
iex> Range.to_list(0..5)
[0, 1, 2, 3, 4, 5]
iex> Range.to_list(-3..0)
[-3, -2, -1, 0]
"""
@doc since: "1.15.0"
@spec to_list(t) :: list(integer)
def to_list(first..last//step)
when step > 0 and first <= last
when step < 0 and first >= last do
Expand Down
8 changes: 8 additions & 0 deletions lib/elixir/src/elixir_erl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ elixir_to_erl(Tree, Ann) when is_atom(Tree) ->
{atom, Ann, Tree};
elixir_to_erl(Tree, Ann) when is_integer(Tree) ->
{integer, Ann, Tree};
elixir_to_erl(Tree, Ann) when is_float(Tree), Tree == 0.0 ->
% 0.0 needs to be rewritten as the AST for +0.0 in matches
Op =
case <<Tree/float>> of
<<1:1,_:63>> -> '-';
_ -> '+'
end,
{op, Ann, Op, {float, Ann, 0.0}};
elixir_to_erl(Tree, Ann) when is_float(Tree) ->
{float, Ann, Tree};
elixir_to_erl(Tree, Ann) when is_binary(Tree) ->
Expand Down
18 changes: 11 additions & 7 deletions lib/elixir/src/elixir_errors.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
-export([function_error/4, module_error/4, file_error/4]).
-export([format_snippet/7]).
-export([erl_warn/3, file_warn/4]).
-export([prefix/1]).
-export([print_diagnostic/2, emit_diagnostic/6]).
-export([print_warning/2, print_warning/3]).
-export([print_warning_group/2]).
Expand Down Expand Up @@ -135,14 +136,14 @@ extract_column(_) -> nil.
%% "Snippet" here refers to the source code line where the diagnostic/error occured

format_snippet(_Position, nil, Message, nil, Severity, _Stacktrace, _Span) ->
Formatted = [prefix(Severity), Message],
Formatted = [prefix(Severity), " ", Message],
unicode:characters_to_binary(Formatted);

format_snippet(Position, File, Message, nil, Severity, Stacktrace, _Span) ->
Location = location_format(Position, File, Stacktrace),

Formatted = io_lib:format(
"~ts~ts\n"
"~ts ~ts\n"
"└─ ~ts",
[prefix(Severity), Message, Location]
),
Expand All @@ -167,7 +168,7 @@ format_snippet(Position, File, Message, Snippet, Severity, Stacktrace, Span) ->
end,

Formatted = io_lib:format(
" ~ts~ts~ts\n"
" ~ts~ts ~ts\n"
" ~ts│\n"
" ~ts~p~ts\n"
" ~ts│ ~ts\n"
Expand Down Expand Up @@ -435,18 +436,21 @@ snippet_line(InputString, Location, StartLine) ->

%% Helpers

prefix(warning) -> highlight(<<"warning: ">>, warning);
prefix(error) -> highlight(<<"error: ">>, error).
prefix(warning) -> highlight(<<"warning:">>, warning);
prefix(error) -> highlight(<<"error:">>, error);
prefix(hint) -> highlight(<<"hint:">>, hint).

highlight(Message, Severity) ->
case {Severity, application:get_env(elixir, ansi_enabled, false)} of
{warning, true} -> yellow(Message);
{error, true} -> red(Message);
{hint, true} -> blue(Message);
_ -> Message
end.

yellow(Msg) -> io_lib:format("\e[33m~ts\e[0m", [Msg]).
red(Msg) -> io_lib:format("\e[31m~ts\e[0m", [Msg]).
yellow(Msg) -> ["\e[33m", Msg, "\e[0m"].
blue(Msg) -> ["\e[34m", Msg, "\e[0m"].
red(Msg) -> ["\e[31m", Msg, "\e[0m"].

env_format(Meta, #{file := EnvFile} = E) ->
{File, Position} = meta_location(Meta, EnvFile),
Expand Down
9 changes: 9 additions & 0 deletions lib/elixir/src/elixir_expand.erl
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@ expand(Pid, S, E) when is_pid(Pid) ->
{Pid, E}
end;

expand(Zero, S, #{context := match} = E) when is_float(Zero), Zero == 0.0 ->
elixir_errors:file_warn([], E, ?MODULE, invalid_match_on_zero_float),
{Zero, S, E};

expand(Other, S, E) when is_number(Other); is_atom(Other); is_binary(Other) ->
{Other, S, E};

Expand Down Expand Up @@ -890,6 +894,9 @@ attach_context_module(Receiver, Meta, #{context_modules := ContextModules}) ->
false -> Meta
end.

% Signed numbers can be rewritten no matter the context
rewrite(_, erlang, _, '+', _, [Arg], _S) when is_number(Arg) -> {ok, Arg};
rewrite(_, erlang, _, '-', _, [Arg], _S) when is_number(Arg) -> {ok, -Arg};
rewrite(match, Receiver, DotMeta, Right, Meta, EArgs, _S) ->
elixir_rewrite:match_rewrite(Receiver, DotMeta, Right, Meta, EArgs);
rewrite(guard, Receiver, DotMeta, Right, Meta, EArgs, S) ->
Expand Down Expand Up @@ -1163,6 +1170,8 @@ guard_context(_) -> "guards".
format_error({remote_nullary_no_parens, Expr}) ->
String = 'Elixir.String':replace_suffix('Elixir.Macro':to_string(Expr), <<"()">>, <<>>),
io_lib:format("parentheses are required for function calls with no arguments, got: ~ts", [String]);
format_error(invalid_match_on_zero_float) ->
"pattern matching on 0.0 is equivalent to matching only on +0.0 from Erlang/OTP 27+. Instead you must match on +0.0 or -0.0";
format_error({useless_literal, Term}) ->
io_lib:format("code block contains unused literal ~ts "
"(remove the literal or assign it to _ to avoid warnings)",
Expand Down
2 changes: 0 additions & 2 deletions lib/elixir/src/elixir_rewrite.erl
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,6 @@ increment(Meta, Other) ->
%% The allowed operations are very limited.
%% The Kernel operators are already inlined by now, we only need to
%% care about Erlang ones.
match_rewrite(erlang, _, '+', _, [Arg]) when is_number(Arg) -> {ok, Arg};
match_rewrite(erlang, _, '-', _, [Arg]) when is_number(Arg) -> {ok, -Arg};
match_rewrite(erlang, _, '++', Meta, [Left, Right]) ->
try {ok, static_append(Left, Right, Meta)}
catch impossible -> {error, {invalid_match_append, Left}}
Expand Down
8 changes: 4 additions & 4 deletions lib/elixir/src/elixir_tokenizer.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1434,8 +1434,8 @@ check_terminator({'end', {Line, Column, _}}, [], #elixir_tokenizer{mismatch_hint
Suffix =
case lists:keyfind('end', 1, Hints) of
{'end', HintLine, _Identation} ->
io_lib:format("\n\n HINT: the \"end\" on line ~B may not have a matching \"do\" "
"defined before it (based on indentation)", [HintLine]);
io_lib:format("\n~ts the \"end\" on line ~B may not have a matching \"do\" "
"defined before it (based on indentation)", [elixir_errors:prefix(hint), HintLine]);
false ->
""
end,
Expand All @@ -1455,8 +1455,8 @@ unexpected_token_or_reserved(_) -> "unexpected token: ".
missing_terminator_hint(Start, End, #elixir_tokenizer{mismatch_hints=Hints}) ->
case lists:keyfind(Start, 1, Hints) of
{Start, {HintLine, _, _}, _} ->
io_lib:format("\n\n HINT: it looks like the \"~ts\" on line ~B does not have a matching \"~ts\"",
[Start, HintLine, End]);
io_lib:format("\n~ts it looks like the \"~ts\" on line ~B does not have a matching \"~ts\"",
[elixir_errors:prefix(hint), Start, HintLine, End]);
false ->
""
end.
Expand Down
17 changes: 15 additions & 2 deletions lib/elixir/test/elixir/kernel/expansion_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,16 @@ defmodule Kernel.ExpansionTest do
end
end

describe "floats" do
test "cannot be 0.0 inside match" do
assert capture_io(:stderr, fn -> expand(quote(do: 0.0 = 0.0)) end) =~
"pattern matching on 0.0 is equivalent to matching only on +0.0 from Erlang/OTP 27+"

assert {:=, [], [+0.0, +0.0]} = expand(quote(do: +0.0 = 0.0))
assert {:=, [], [-0.0, +0.0]} = expand(quote(do: -0.0 = 0.0))
end
end

describe "tuples" do
test "expanded as arguments" do
assert expand(quote(do: {after_expansion = 1, a})) == quote(do: {after_expansion = 1, a()})
Expand Down Expand Up @@ -715,8 +725,11 @@ defmodule Kernel.ExpansionTest do
expand(quote(do: [1] ++ 2 ++ [3] = [1, 2, 3]))
end)

assert {:=, _, [-1, {{:., _, [:erlang, :-]}, _, [1]}]} = expand(quote(do: -1 = -1))
assert {:=, _, [1, {{:., _, [:erlang, :+]}, _, [1]}]} = expand(quote(do: +1 = +1))
assert {:=, _, [-1, -1]} =
expand(quote(do: -1 = -1))

assert {:=, _, [1, 1]} =
expand(quote(do: +1 = +1))

assert {:=, _, [[{:|, _, [1, [{:|, _, [2, 3]}]]}], [1, 2, 3]]} =
expand(quote(do: [1] ++ [2] ++ 3 = [1, 2, 3]))
Expand Down
Loading

0 comments on commit 51f0cc3

Please sign in to comment.