Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/elixir/lib/module/types/pattern.ex
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,18 @@ defmodule Module.Types.Pattern do
end
end

# map.field
def of_guard({{:., meta1, [map, field]}, meta2, []}, stack, context) do
of_guard({{:., meta1, [:erlang, :map_get]}, meta2, [field, map]}, stack, context)
end

# var
def of_guard(var, _stack, context) when is_var(var) do
type = Map.fetch!(context.vars, var_name(var))
{:ok, type, context}
end

# other literals
def of_guard(expr, stack, context) do
# Fall back to of_pattern/3 for literals
of_pattern(expr, stack, context)
Expand Down
12 changes: 9 additions & 3 deletions lib/elixir/src/elixir_erl_pass.erl
Original file line number Diff line number Diff line change
Expand Up @@ -211,15 +211,21 @@ translate({Name, Meta, Args}, S) when is_atom(Name), is_list(Meta), is_list(Args

%% Remote calls

translate({{'.', _, [Left, Right]}, Meta, []}, S)
translate({{'.', _, [Left, Right]}, Meta, []}, #elixir_erl{context=guard} = S)
when is_tuple(Left), is_atom(Right), is_list(Meta) ->
{TLeft, SL} = translate(Left, S),
{Var, SV} = elixir_erl_var:build('_', SL),
Ann = ?ann(Meta),
TRight = {atom, Ann, Right},
{?remote(Ann, erlang, map_get, [TRight, TLeft]), SL};

translate({{'.', _, [Left, Right]}, Meta, []}, S) when is_tuple(Left), is_atom(Right), is_list(Meta) ->
{TLeft, SL} = translate(Left, S),
Ann = ?ann(Meta),
Generated = erl_anno:set_generated(true, Ann),
TRight = {atom, Ann, Right},

{Var, SV} = elixir_erl_var:build('_', SL),
TVar = {var, Ann, Var},
Generated = erl_anno:set_generated(true, Ann),
TError = {tuple, Ann, [{atom, Ann, badkey}, TRight, TVar]},

{{'case', Generated, TLeft, [
Expand Down
34 changes: 24 additions & 10 deletions lib/elixir/src/elixir_expand.erl
Original file line number Diff line number Diff line change
Expand Up @@ -810,19 +810,29 @@ expand_local(Meta, Name, Args, #{module := Module, function := Function} = E) ->

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

is_atom(Receiver) andalso
elixir_env:trace({remote_function, DotMeta, Receiver, Right, length(Args)}, E),
case {Context, lists:keyfind(no_parens, 1, Meta)} of
{guard, {no_parens, true}} when is_tuple(Receiver) ->
{{{'.', DotMeta, [Receiver, Right]}, Meta, []}, EL};

{EArgs, {EA, _}} = lists:mapfoldl(fun expand_arg/2, {EL, E}, Args),
{guard, _} when is_tuple(Receiver) ->
form_error(Meta, E, ?MODULE, {parens_map_lookup_guard, Receiver, Right});

case rewrite(Context, Receiver, AttachedDotMeta, Right, Meta, EArgs) of
{ok, Rewritten} ->
maybe_warn_comparison(Rewritten, Args, E),
{Rewritten, elixir_env:close_write(EA, E)};
{error, Error} ->
form_error(Meta, E, elixir_rewrite, Error)
_ ->
AttachedDotMeta = attach_context_module(Receiver, DotMeta, E),

is_atom(Receiver) andalso
elixir_env:trace({remote_function, DotMeta, Receiver, Right, length(Args)}, E),

{EArgs, {EA, _}} = lists:mapfoldl(fun expand_arg/2, {EL, E}, Args),

case rewrite(Context, Receiver, AttachedDotMeta, Right, Meta, EArgs) of
{ok, Rewritten} ->
maybe_warn_comparison(Rewritten, Args, E),
{Rewritten, elixir_env:close_write(EA, E)};
{error, Error} ->
form_error(Meta, E, elixir_rewrite, Error)
end
end;
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, _) ->
Call = {{'.', DotMeta, [Receiver, Right]}, Meta, Args},
Expand Down Expand Up @@ -1287,6 +1297,10 @@ format_error({no_parens_nullary_remote, Remote, Fun}) ->
io_lib:format("missing parenthesis on call to ~ts.~ts/0. "
"parenthesis are always required on function calls without arguments",
[elixir_aliases:inspect(Remote), Fun]);
format_error({parens_map_lookup_guard, Map, Field}) ->
io_lib:format("cannot invoke remote function in guard. "
"If you want to do a map lookup instead, please remove parens from ~ts.~ts()",
['Elixir.Macro':to_string(Map), Field]);
format_error({super_in_genserver, {Name, Arity}}) ->
io_lib:format("calling super for GenServer callback ~ts/~B is deprecated", [Name, Arity]);
format_error({parallel_bitstring_match, Expr}) ->
Expand Down
28 changes: 28 additions & 0 deletions lib/elixir/test/elixir/kernel_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,34 @@ defmodule KernelTest do
assert case_in(-3, -1..-3) == true
end

def map_dot(map) when map.field, do: true
def map_dot(_other), do: false

test "map dot guard" do
refute map_dot(:foo)
refute map_dot(%{})
refute map_dot(%{field: false})
assert map_dot(%{field: true})

message =
"cannot invoke remote function in guard. " <>
"If you want to do a map lookup instead, please remove parens from map.field()"

assert_raise CompileError, Regex.compile!(message), fn ->
defmodule MapDot do
def map_dot(map) when map.field(), do: true
end
end

message = ~r"cannot invoke remote function Module.fun/0 inside guards"

assert_raise CompileError, message, fn ->
defmodule MapDot do
def map_dot(map) when Module.fun(), do: true
end
end
end

test "performs all side-effects" do
assert 1 in [1, send(self(), 2)]
assert_received 2
Expand Down