Skip to content

Commit

Permalink
Warn on nullary remote calls, related to #9510
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Aug 15, 2023
1 parent 0c2b763 commit e0d04e1
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 18 deletions.
2 changes: 1 addition & 1 deletion lib/elixir/pages/references/syntax-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ end
### Optional parentheses
Elixir provides optional parentheses:
Elixir provides optional parentheses on local and remote calls with one or more arguments:
```elixir
quote do
Expand Down
21 changes: 14 additions & 7 deletions lib/elixir/src/elixir_expand.erl
Original file line number Diff line number Diff line change
Expand Up @@ -396,12 +396,16 @@ expand({Atom, Meta, Args}, S, E) when is_atom(Atom), is_list(Meta), is_list(Args

%% Remote calls

expand({{'.', DotMeta, [Left, Right]}, Meta, Args}, S, E)
expand({{'.', DotMeta, [Left, Right]}, Meta, Args} = Expr, S, E)
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
{ELeft, SL, EL} = expand(Left, elixir_env:prepare_write(S), E),
NoParens = lists:keyfind(no_parens, 1, Meta),

(is_atom(ELeft) and (Args =:= []) and (NoParens =:= {no_parens, true})) andalso
elixir_errors:file_warn(Meta, E, ?MODULE, {remote_nullary_no_parens, Expr}),

elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, S, EL, fun(AR, AF, AA) ->
expand_remote(AR, DotMeta, AF, Meta, AA, S, SL, EL)
expand_remote(AR, DotMeta, AF, Meta, NoParens, AA, S, SL, EL)
end);

%% Anonymous calls
Expand Down Expand Up @@ -844,17 +848,17 @@ expand_local(Meta, Name, Args, _S, #{function := nil} = E) ->

%% Remote

expand_remote(Receiver, DotMeta, Right, Meta, Args, S, SL, #{context := Context} = E) when is_atom(Receiver) or is_tuple(Receiver) ->
expand_remote(Receiver, DotMeta, Right, Meta, NoParens, Args, S, SL, #{context := Context} = E) when is_atom(Receiver) or is_tuple(Receiver) ->
assert_no_clauses(Right, Meta, Args, E),

case {Context, lists:keyfind(no_parens, 1, Meta)} of
{guard, NoParens} when is_tuple(Receiver) ->
if
Context =:= guard, is_tuple(Receiver) ->
(NoParens /= {no_parens, true}) andalso
function_error(Meta, E, ?MODULE, {parens_map_lookup, Receiver, Right, guard_context(S)}),

{{{'.', DotMeta, [Receiver, Right]}, Meta, []}, SL, E};

_ ->
true ->
AttachedDotMeta = attach_context_module(Receiver, DotMeta, E),

is_atom(Receiver) andalso
Expand All @@ -871,7 +875,7 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, S, SL, #{context := Context}
file_error(Meta, E, elixir_rewrite, Error)
end
end;
expand_remote(Receiver, DotMeta, Right, Meta, Args, _, _, E) ->
expand_remote(Receiver, DotMeta, Right, Meta, _NoParens, Args, _, _, E) ->
Call = {{'.', DotMeta, [Receiver, Right]}, Meta, Args},
file_error(Meta, E, ?MODULE, {invalid_call, Call}).

Expand Down Expand Up @@ -1159,6 +1163,9 @@ assert_no_underscore_clause_in_cond(_Other, _E) ->
guard_context(#elixir_ex{prematch={_, _, {bitsize, _}}}) -> "bitstring size specifier";
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({useless_literal, Term}) ->
io_lib:format("code block contains unused literal ~ts "
"(remove the literal or assign it to _ to avoid warnings)",
Expand Down
6 changes: 4 additions & 2 deletions lib/elixir/test/elixir/kernel/cli_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ defmodule Kernel.CLITest do
{output, 0} = System.cmd(elixir_executable(), ["--eval", "IO.puts :hello_world123"])
assert output =~ "hello_world123"

{output, 0} = System.cmd(iex_executable(), ["--eval", "IO.puts :hello_world123; System.halt"])
{output, 0} =
System.cmd(iex_executable(), ["--eval", "IO.puts :hello_world123; System.halt()"])

assert output =~ "hello_world123"

{output, 0} = System.cmd(elixir_executable(), ["-e", "IO.puts :hello_world123"])
assert output =~ "hello_world123"

{output, 0} = System.cmd(iex_executable(), ["-e", "IO.puts :hello_world123; System.halt"])
{output, 0} = System.cmd(iex_executable(), ["-e", "IO.puts :hello_world123; System.halt()"])
assert output =~ "hello_world123"
end

Expand Down
12 changes: 12 additions & 0 deletions lib/elixir/test/elixir/kernel/warning_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,18 @@ defmodule Kernel.WarningTest do
purge(Sample)
end

test "parens on nullary remote call" do
assert_warn_eval(
[
"nofile:1:8",
"parentheses are required for function calls with no arguments, got: System.version"
],
"System.version"
)
after
purge(Sample)
end

test "parens with module attribute" do
assert_warn_eval(
[
Expand Down
8 changes: 0 additions & 8 deletions lib/elixir/test/elixir/module/types/expr_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,6 @@ defmodule Module.Types.ExprTest do
assert quoted_expr({:a, 123}) == {:ok, {:tuple, 2, [{:atom, :a}, :integer]}}
end

# Use module attribute to avoid formatter adding parentheses
@mix_module Mix

test "module call" do
assert quoted_expr(@mix_module.shell) == {:ok, :dynamic}
assert quoted_expr(@mix_module.shell.info) == {:ok, {:var, 0}}
end

describe "binary" do
test "literal" do
assert quoted_expr(<<"foo"::binary>>) == {:ok, :binary}
Expand Down

0 comments on commit e0d04e1

Please sign in to comment.