Preserve evaluation order when rewriting (#15389)

This was done by moving the rewriting which change evaluation order to the Erlang pass,
otherwise would be hard to rewrite back in the type checker reports.

Closes #15310.
This commit is contained in:
José Valim
2026-05-14 21:16:55 +02:00
committed by GitHub
parent 63708d313c
commit 87582af546
12 changed files with 311 additions and 77 deletions
+2 -2
View File
@@ -339,7 +339,7 @@ defmodule Kernel.ParallelCompiler do
defp spawn_workers(schedulers, checker, files, output, options) do
threshold = Keyword.get(options, :long_compilation_threshold, 10) * 1000
timer_ref = Process.send_after(self(), :threshold_check, threshold)
timer_ref = :erlang.send_after(threshold, self(), :threshold_check)
purge_compiler_modules =
if Keyword.get(options, :purge_compiler_modules, false) do
@@ -840,7 +840,7 @@ defmodule Kernel.ParallelCompiler do
end
end
timer_ref = Process.send_after(self(), :threshold_check, state.long_compilation_threshold)
timer_ref = :erlang.send_after(state.long_compilation_threshold, self(), :threshold_check)
state = %{state | timer_ref: timer_ref}
spawn_workers(queue, spawned, waiting, files, result, warnings, errors, state)
+1 -1
View File
@@ -654,7 +654,7 @@ defmodule Module.ParallelChecker do
defp run_checkers(%{modules: [{module, pid, ref} | modules]} = state) do
send(pid, {ref, :check})
timer = Process.send_after(self(), {__MODULE__, :timeout, module, pid}, state.threshold)
timer = :erlang.send_after(state.threshold, self(), {__MODULE__, :timeout, module, pid})
spawned = Map.put(state.spawned, module, timer)
run_checkers(%{state | modules: modules, spawned: spawned})
end
+198 -18
View File
@@ -236,24 +236,39 @@ defmodule Module.Types.Apply do
{:erlang, :tl, [{[non_empty_list(term(), term())], dynamic()}]},
{:erlang, :tuple_to_list, [{[open_tuple([])], dynamic(list(term()))}]},
## Kernel
{Kernel, :elem, [{[open_tuple([]), integer()], dynamic()}]},
{Kernel, :is_map_key, [{[open_map(), term()], boolean()}]},
{Kernel, :put_elem, [{[open_tuple([]), integer(), term()], dynamic(open_tuple([]))}]},
## Lists
{:lists, :member, [{[term(), list(term())], boolean()}]},
## Map
{Map, :delete, [{[open_map(), term()], open_map()}]},
{Map, :fetch,
[{[open_map(), term()], tuple([atom([:ok]), term()]) |> union(atom([:error]))}]},
{Map, :fetch!, [{[open_map(), term()], term()}]},
{Map, :from_struct, [{[open_map()], open_map(__struct__: not_set())}]},
{Map, :get, [{[open_map(), term()], term()}]},
{Map, :get, [{[open_map(), term(), term()], term()}]},
{Map, :get_lazy, [{[open_map(), term(), fun(0)], term()}]},
{Map, :has_key?, [{[open_map(), term()], boolean()}]},
{Map, :pop, [{[open_map(), term()], tuple([term(), open_map()])}]},
{Map, :pop, [{[open_map(), term(), term()], tuple([term(), open_map()])}]},
{Map, :pop!, [{[open_map(), term()], tuple([term(), open_map()])}]},
{Map, :pop_lazy, [{[open_map(), term(), fun(0)], tuple([term(), open_map()])}]},
{Map, :put, [{[open_map(), term(), term()], open_map()}]},
{Map, :put_new, [{[open_map(), term(), term()], open_map()}]},
{Map, :put_new_lazy, [{[open_map(), term(), fun(0)], open_map()}]},
{Map, :replace, [{[open_map(), term(), term()], open_map()}]},
{Map, :replace!, [{[open_map(), term(), term()], open_map()}]},
{Map, :replace_lazy, [{[open_map(), term(), fun(1)], open_map()}]},
{Map, :update, [{[open_map(), term(), term(), fun(1)], open_map()}]},
{Map, :update!, [{[open_map(), term(), fun(1)], open_map()}]},
{Tuple, :delete_at, [{[open_tuple([]), integer()], dynamic(open_tuple([]))}]},
{Tuple, :duplicate, [{[term(), integer()], tuple()}]},
{Tuple, :insert_at, [{[open_tuple([]), integer(), term()], dynamic(open_tuple([]))}]},
{:maps, :from_keys, [{[list(term()), term()], open_map()}]},
{:maps, :find,
[{[term(), open_map()], tuple([atom([:ok]), term()]) |> union(atom([:error]))}]},
@@ -270,8 +285,6 @@ defmodule Module.Types.Apply do
] do
[arity] = Enum.map(clauses, fn {args, _return} -> length(args) end) |> Enum.uniq()
true = Code.ensure_loaded?(mod)
domain_clauses =
case clauses do
[_] ->
@@ -487,13 +500,6 @@ defmodule Module.Types.Apply do
end
end
defp do_remote(:erlang, :setelement, [index, tuple, elem], _, expr, stack, context, of_fun)
when is_integer(index) and index < 1 do
{_, context} = of_fun.(tuple, open_tuple([]), expr, stack, context)
{_, context} = of_fun.(elem, term(), expr, stack, context)
remote_error({:negindex, index - 1}, :erlang, :setelement, 3, expr, stack, context)
end
defp do_remote(:erlang, :setelement, [index, tuple, elem], _, expr, stack, context, of_fun)
when is_integer(index) do
tuple_type = open_tuple(List.duplicate(term(), max(index, 1)))
@@ -501,17 +507,21 @@ defmodule Module.Types.Apply do
{tuple_type, context} = of_fun.(tuple, tuple_type, expr, stack, context)
{elem_type, context} = of_fun.(elem, term(), expr, stack, context)
case tuple_replace_at(tuple_type, index - 1, elem_type) do
value_type when is_descr(value_type) ->
{return(value_type, [tuple_type, elem_type], stack), context}
if index < 1 do
remote_error({:negindex, index - 1}, :erlang, :setelement, 3, expr, stack, context)
else
case tuple_replace_at(tuple_type, index - 1, elem_type) do
value_type when is_descr(value_type) ->
{return(value_type, [tuple_type, elem_type], stack), context}
:badtuple ->
args_types = [integer(), tuple_type, elem_type]
remote_error(:erlang, :setelement, args_types, expr, stack, context)
:badtuple ->
args_types = [integer(), tuple_type, elem_type]
remote_error(:erlang, :setelement, args_types, expr, stack, context)
:badindex ->
error = {:badindex, index, tuple_type}
remote_error(error, :erlang, :setelement, 3, expr, stack, context)
:badindex ->
error = {:badindex, index, tuple_type}
remote_error(error, :erlang, :setelement, 3, expr, stack, context)
end
end
end
@@ -537,6 +547,105 @@ defmodule Module.Types.Apply do
end
end
defp do_remote(Kernel, :elem, [tuple, index], expected, expr, stack, context, of_fun)
when is_integer(index) do
tuple_type = open_tuple(List.duplicate(term(), max(index, 0)) ++ [expected])
{tuple_type, context} = of_fun.(tuple, tuple_type, expr, stack, context)
if index < 0 do
remote_error({:negindex, index}, Kernel, :elem, 2, expr, stack, context)
else
case tuple_fetch(tuple_type, index) do
{_optional?, value_type} ->
{return(value_type, [tuple_type], stack), context}
:badtuple ->
remote_error(Kernel, :elem, [tuple_type, integer()], expr, stack, context)
:badindex ->
remote_error({:badindex, index + 1, tuple_type}, Kernel, :elem, 2, expr, stack, context)
end
end
end
defp do_remote(Kernel, :put_elem, [tuple, index, elem], _, expr, stack, context, of_fun)
when is_integer(index) do
tuple_type = open_tuple(List.duplicate(term(), max(index + 1, 1)))
{tuple_type, context} = of_fun.(tuple, tuple_type, expr, stack, context)
{elem_type, context} = of_fun.(elem, term(), expr, stack, context)
if index < 0 do
remote_error({:negindex, index}, Kernel, :put_elem, 3, expr, stack, context)
else
case tuple_replace_at(tuple_type, index, elem_type) do
value_type when is_descr(value_type) ->
{return(value_type, [tuple_type, elem_type], stack), context}
:badtuple ->
args_types = [tuple_type, integer(), elem_type]
remote_error(Kernel, :put_elem, args_types, expr, stack, context)
:badindex ->
error = {:badindex, index + 1, tuple_type}
remote_error(error, Kernel, :put_elem, 3, expr, stack, context)
end
end
end
defp do_remote(Tuple, :duplicate, [elem, size], _, expr, stack, context, of_fun)
when is_integer(size) and size >= 0 do
{elem_type, context} = of_fun.(elem, term(), expr, stack, context)
{return(tuple(List.duplicate(elem_type, size)), [elem_type], stack), context}
end
defp do_remote(Tuple, :insert_at, [tuple, index, elem], _, expr, stack, context, of_fun)
when is_integer(index) do
tuple_type = open_tuple(List.duplicate(term(), max(index, 0)))
{tuple_type, context} = of_fun.(tuple, tuple_type, expr, stack, context)
{elem_type, context} = of_fun.(elem, term(), expr, stack, context)
if index < 0 do
remote_error({:negindex, index}, Tuple, :insert_at, 3, expr, stack, context)
else
case tuple_insert_at(tuple_type, index, elem_type) do
value_type when is_descr(value_type) ->
{return(value_type, [tuple_type, elem_type], stack), context}
:badtuple ->
args_types = [tuple_type, integer(), elem_type]
remote_error(Tuple, :insert_at, args_types, expr, stack, context)
:badindex ->
error = {:badindex, index, tuple_type}
remote_error(error, Tuple, :insert_at, 3, expr, stack, context)
end
end
end
defp do_remote(Tuple, :delete_at, [tuple, index], _, expr, stack, context, of_fun)
when is_integer(index) do
tuple_type = open_tuple(List.duplicate(term(), max(index + 1, 1)))
{tuple_type, context} = of_fun.(tuple, tuple_type, expr, stack, context)
if index < 0 do
remote_error({:negindex, index}, Tuple, :delete_at, 2, expr, stack, context)
else
case tuple_delete_at(tuple_type, index) do
value_type when is_descr(value_type) ->
{return(value_type, [tuple_type], stack), context}
:badtuple ->
remote_error(Tuple, :delete_at, [tuple_type, integer()], expr, stack, context)
:badindex ->
error = {:badindex, index + 1, tuple_type}
remote_error(error, Tuple, :delete_at, 2, expr, stack, context)
end
end
end
defp do_remote(:lists, :member, [arg, list] = args, expected, expr, stack, context, of_fun)
when is_list(list) do
if list == [] or Enum.any?(list, &match?({:|, _, [_, _]}, &1)) do
@@ -877,17 +986,40 @@ defmodule Module.Types.Apply do
{info, filter_domain(info, expected, 2), context}
end
def remote_domain(Kernel, :is_map_key, [_map, key], expected, _meta, _stack, context)
when is_atom(key) do
info =
{:strong, [open_map(), term()],
[
{[open_map([{key, term()}]), term()], atom([true])},
{[open_map([{key, not_set()}]), term()], atom([false])}
]}
{info, filter_domain(info, expected, 2), context}
end
def remote_domain(:erlang, :map_get, [key, _], expected, _meta, _stack, context)
when is_atom(key) do
domain = [term(), open_map([{key, expected}])]
{{:strong, nil, [{domain, term()}]}, domain, context}
end
def remote_domain(Map, :fetch!, [_, key], expected, _meta, _stack, context) when is_atom(key) do
domain = [open_map([{key, expected}]), term()]
{{:strong, nil, [{domain, term()}]}, domain, context}
end
def remote_domain(:maps, :get, [key, _], expected, _meta, _stack, context) when is_atom(key) do
domain = [term(), open_map([{key, expected}])]
{{:strong, nil, [{domain, term()}]}, domain, context}
end
def remote_domain(Map, :replace!, [_, key, _], _expected, _meta, _stack, context)
when is_atom(key) do
domain = [open_map([{key, term()}]), term(), term()]
{{:strong, nil, [{domain, open_map()}]}, domain, context}
end
def remote_domain(:maps, :update, [key, _, _], _expected, _meta, _stack, context)
when is_atom(key) do
domain = [term(), term(), open_map([{key, term()}])]
@@ -967,6 +1099,36 @@ defmodule Module.Types.Apply do
end
end
defp remote_apply(Map, :delete, _info, [map, key] = args_types, stack) do
case map_update(map, key, not_set(), false, true) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
:badmap -> {:error, badremote(Map, :delete, args_types)}
{:error, _errors} -> {:ok, map}
end
end
defp remote_apply(Map, :fetch, _info, [map, key] = args_types, stack) do
case map_get(map, key) do
{_, value} ->
result = tuple([atom([:ok]), value]) |> union(atom([:error]))
{:ok, return(result, args_types, stack)}
:badmap ->
{:error, badremote(Map, :fetch, args_types)}
:error ->
{:error, {:badkeydomain, map, key, atom([:error])}}
end
end
defp remote_apply(Map, :fetch!, _info, [map, key] = args_types, stack) do
case map_get(map, key) do
{:ok, value} -> {:ok, return(value, args_types, stack)}
:badmap -> {:error, badremote(Map, :fetch!, args_types)}
:error -> {:error, {:badkeydomain, map, key, "raise"}}
end
end
defp remote_apply(Map, :get, _info, [map, key] = args_types, stack) do
case map_get(map, key) do
{:ok, value} -> {:ok, return(union(value, @nil_atom), args_types, stack)}
@@ -1048,6 +1210,14 @@ defmodule Module.Types.Apply do
end
end
defp remote_apply(Map, :put, _info, [map, key, value] = args_types, stack) do
case map_update(map, key, value, false, true) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
:badmap -> {:error, badremote(Map, :put, args_types)}
{:error, _errors} -> {:ok, map}
end
end
defp remote_apply(Map, :pop!, _info, [map, key] = args_types, stack) do
case map_update(map, key, not_set(), true, false) do
{value, descr, _errors} -> {:ok, return(tuple([value, descr]), args_types, stack)}
@@ -1066,6 +1236,16 @@ defmodule Module.Types.Apply do
end
end
defp remote_apply(Map, :replace!, _info, [map, key, value] = args_types, stack) do
fun = fn optional?, _type -> if optional?, do: if_set(value), else: value end
case map_update_fun(map, key, fun, false, false) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
:badmap -> {:error, badremote(Map, :replace!, args_types)}
{:error, _errors} -> {:error, {:badkeydomain, map, key, "raise"}}
end
end
defp remote_apply(Map, :replace_lazy, _info, args_types, stack) do
map_update_or_replace_lazy(:replace_lazy, args_types, stack, "do nothing")
end
-2
View File
@@ -387,8 +387,6 @@ defmodule Process do
automatically canceled when `dest` is an atom (as the atom resolution is done
on delivery).
Inlined by the compiler.
## Options
* `:abs` - (boolean) when `false`, `time` is treated as relative to the
+42 -4
View File
@@ -230,7 +230,7 @@ translate({Name, Meta, Args}, _Ann, S) when is_atom(Name), is_list(Meta), is_lis
translate({{'.', _, [Left, Right]}, Meta, []}, _Ann, #elixir_erl{context=guard} = S)
when is_tuple(Left), is_atom(Right), is_list(Meta) ->
Ann = ?ann(Meta),
{TLeft, SL} = translate(Left, Ann, S),
{TLeft, SL} = translate(Left, Ann, S),
TRight = {atom, Ann, Right},
{?remote(Ann, erlang, map_get, [TRight, TLeft]), SL};
@@ -642,12 +642,18 @@ translate_remote(Left, Right, Meta, Args, S) ->
[TOne] -> {{op, Ann, Right, TOne}, SA};
[TOne, TTwo] -> {{op, Ann, Right, TOne, TTwo}, SA}
end;
{reorder, ErlLeft, ErlRight, FunArgs} ->
evaluate_first(Args, Ann, S, fun(ErlArgs) ->
TLeft = {atom, Ann, ErlLeft},
TRight = {atom, Ann, ErlRight},
{call, Ann, {remote, Ann, TLeft, TRight}, FunArgs(Ann, ErlArgs)}
end);
{inline_pure, Result} ->
Generated = erl_anno:set_generated(true, Ann),
translate(Result, Generated, S);
{inline_args, NewArgs} ->
{TLeft, SL} = translate(Left, Ann, S),
{TArgs, SA} = translate_args(NewArgs, Ann, SL),
{TArgs, SA} = translate_args(NewArgs, Ann, S),
TLeft = {atom, Ann, Left},
TRight = {atom, Ann, Right},
{{call, Ann, {remote, Ann, TLeft, TRight}, TArgs}, SA};
none ->
@@ -698,9 +704,41 @@ rewrite_strategy(Left, Right, Args) ->
none
end;
false ->
none
reorder_strategy(Left, Right, length(Args))
end.
-define(
reorder(ExMod, ExFun, ExArity, ExArgs, ErlMod, ErlFun, ErlArgs),
reorder_strategy(ExMod, ExFun, ExArity) -> {reorder, ErlMod, ErlFun, fun(Ann, ExArgs) -> _ = Ann, ErlArgs end}
).
?reorder('Elixir.Kernel', elem, 2, [Tuple, Index], erlang, element, [increment(Ann, Index), Tuple]);
?reorder('Elixir.Kernel', put_elem, 2, [Tuple, Index, Term], erlang, element, [increment(Ann, Index), Tuple, Term]);
?reorder('Elixir.Kernel', is_map_key, 2, [Map, Key], erlang, is_map_key, [Key, Map]);
?reorder('Elixir.Map', delete, 2, [Map, Key], maps, remove, [Key, Map]);
?reorder('Elixir.Map', fetch, 2, [Map, Key], maps, find, [Key, Map]);
?reorder('Elixir.Map', 'fetch!', 2, [Map, Key], maps, get, [Key, Map]);
?reorder('Elixir.Map', 'has_key?', 2, [Map, Key], maps, is_key, [Key, Map]);
?reorder('Elixir.Map', put, 3, [Map, Key, Value], maps, put, [Key, Value, Map]);
?reorder('Elixir.Map', 'replace!', 3, [Map, Key, Value], maps, update, [Key, Value, Map]);
?reorder('Elixir.Process', group_leader, 2, [Pid, Leader], erlang, group_leader, [Leader, Pid]);
?reorder('Elixir.Tuple', delete_at, 2, [Tuple, Index], erlang, delete_element, [increment(Ann, Index), Tuple]);
?reorder('Elixir.Tuple', duplicate, 2, [Data, Size], erlang, make_tuple, [Size, Data]);
?reorder('Elixir.Tuple', insert_at, 3, [Tuple, Index, Term], erlang, insert_element, [increment(Ann, Index), Tuple, Term]);
reorder_strategy(_, _, _) -> none.
increment(_Ann, {integer, Ann, Number}) when is_number(Number) -> {integer, Ann, Number + 1};
increment(Ann, Other) -> {op, Ann, '+', Other, {integer, Ann, 1}}.
evaluate_first([{Var, _Meta, Ctx} | _Tail] = Args, Ann, S, Fun) when is_atom(Var), is_atom(Ctx) ->
{TArgs, ST} = translate_args(Args, Ann, S),
{Fun(TArgs), ST};
evaluate_first(Args, Ann, S, Fun) ->
{VarName, SV} = elixir_erl_var:build('_', S),
{[Head | Tail], ST} = translate_args(Args, Ann, SV),
Var = {var, Ann, VarName},
{{block, Ann, [{match, Ann, Var, Head}, Fun([Var | Tail])]}, ST}.
inline_pure_function('Elixir.Duration', 'new!') -> true;
inline_pure_function('Elixir.MapSet', new) -> true;
inline_pure_function('Elixir.String', length) -> true;
+13 -36
View File
@@ -238,8 +238,8 @@ inner_inline(_, _, _, _) -> false.
%%
%% Rewrite rules are more complex than regular inlining code
%% as they may change the number of arguments. However, they
%% don't add new code (such as case expressions), at best they
%% perform dead code removal.
%% cannot change tha argument order, as that can change the
%% code semantics.
rewrite(?string_chars, DotMeta, to_string, Meta, [Arg]) ->
case is_always_string(Arg) of
true -> Arg;
@@ -253,48 +253,21 @@ rewrite(Receiver, DotMeta, Right, Meta, Args) ->
?rewrite(?float, to_charlist, [Arg], erlang, float_to_list, [Arg, [short]]);
?rewrite(?float, to_string, [Arg], erlang, float_to_binary, [Arg, [short]]);
?rewrite(?kernel, is_map_key, [Map, Key], erlang, is_map_key, [Key, Map]);
?rewrite(?map, delete, [Map, Key], maps, remove, [Key, Map]);
?rewrite(?map, fetch, [Map, Key], maps, find, [Key, Map]);
?rewrite(?map, 'fetch!', [Map, Key], maps, get, [Key, Map]);
?rewrite(?map, 'has_key?', [Map, Key], maps, is_key, [Key, Map]);
?rewrite(?map, put, [Map, Key, Value], maps, put, [Key, Value, Map]);
?rewrite(?map, 'replace!', [Map, Key, Value], maps, update, [Key, Value, Map]);
?rewrite(?port, monitor, [Arg], erlang, monitor, [port, Arg]);
?rewrite(?process, group_leader, [Pid, Leader], erlang, group_leader, [Leader, Pid]);
?rewrite(?process, monitor, [Arg], erlang, monitor, [process, Arg]);
?rewrite(?process, monitor, [Arg, Opts], erlang, monitor, [process, Arg, Opts]);
?rewrite(?process, send_after, [Dest, Msg, Time], erlang, send_after, [Time, Dest, Msg]);
?rewrite(?process, send_after, [Dest, Msg, Time, Opts], erlang, send_after, [Time, Dest, Msg, Opts]);
?rewrite(?tuple, duplicate, [Data, Size], erlang, make_tuple, [Size, Data]);
inner_rewrite(ex_to_erl, Meta, ?tuple, delete_at, [Tuple, Index]) ->
{erlang, delete_element, [increment(Meta, Index), Tuple]};
inner_rewrite(ex_to_erl, Meta, ?tuple, insert_at, [Tuple, Index, Term]) ->
{erlang, insert_element, [increment(Meta, Index), Tuple, Term]};
inner_rewrite(ex_to_erl, Meta, ?kernel, elem, [Tuple, Index]) ->
{erlang, element, [increment(Meta, Index), Tuple]};
inner_rewrite(ex_to_erl, Meta, ?kernel, put_elem, [Tuple, Index, Value]) ->
{erlang, setelement, [increment(Meta, Index), Tuple, Value]};
inner_rewrite(erl_to_ex, _Meta, erlang, delete_element, [Index, Tuple]) when is_number(Index) ->
{?tuple, delete_at, [Tuple, Index - 1], fun([Index, Tuple]) -> [Tuple, Index] end};
inner_rewrite(erl_to_ex, _Meta, erlang, insert_element, [Index, Tuple, Term]) when is_number(Index) ->
{?tuple, insert_at, [Tuple, Index - 1, Term], fun([Index, Tuple, Term]) -> [Tuple, Index, Term] end};
%% These are guard exclusive rewrites
inner_rewrite(erl_to_ex, _Meta, erlang, is_map_key, [Key, Map]) ->
{?kernel, is_map_key, [Map, Key], fun([Key, Map]) -> [Map, Key] end};
inner_rewrite(erl_to_ex, _Meta, erlang, element, [Index, Tuple]) when is_number(Index) ->
{?kernel, elem, [Tuple, Index - 1], fun([Index, Tuple]) -> [Tuple, Index] end};
inner_rewrite(erl_to_ex, _Meta, erlang, setelement, [Index, Tuple, Term]) when is_number(Index) ->
{?kernel, put_elem, [Tuple, Index - 1, Term], fun([Index, Tuple, Term]) -> [Tuple, Index, Term] end};
inner_rewrite(erl_to_ex, _Meta, erlang, delete_element, [{{'.', _, [erlang, '+']}, _, [Index, 1]}, Tuple]) ->
{?tuple, delete_at, [Tuple, Index], fun([Index, Tuple]) -> [Tuple, Index] end};
inner_rewrite(erl_to_ex, _Meta, erlang, insert_element, [{{'.', _, [erlang, '+']}, _, [Index, 1]}, Tuple, Term]) ->
{?tuple, insert_at, [Tuple, Index, Term], fun([Index, Tuple, Term]) -> [Tuple, Index, Term] end};
inner_rewrite(erl_to_ex, _Meta, erlang, element, [{{'.', _, [erlang, '+']}, _, [Index, 1]}, Tuple]) ->
{?kernel, elem, [Tuple, Index], fun([Index, Tuple]) -> [Tuple, Index] end};
inner_rewrite(erl_to_ex, _Meta, erlang, setelement, [{{'.', _, [erlang, '+']}, _, [Index, 1]}, Tuple, Term]) ->
{?kernel, put_elem, [Tuple, Index, Term], fun([Index, Tuple, Term]) -> [Tuple, Index, Term] end};
inner_rewrite(erl_to_ex, _Meta, erlang, 'orelse', [_, _] = Args) ->
{?kernel, 'or', Args, fun identity/1};
inner_rewrite(erl_to_ex, _Meta, erlang, 'andalso', [_, _] = Args) ->
@@ -305,10 +278,8 @@ inner_rewrite(erl_to_ex, _Meta, Mod, Fun, Args) -> {Mod, Fun, Args, fun identity
identity(Arg) -> Arg.
increment(_Meta, Number) when is_number(Number) ->
Number + 1;
increment(Meta, Other) ->
{{'.', Meta, [erlang, '+']}, Meta, [Other, 1]}.
increment(_Meta, Number) when is_number(Number) -> Number + 1;
increment(Meta, Other) -> {{'.', Meta, [erlang, '+']}, Meta, [Other, 1]}.
%% Match rewrite
%%
@@ -337,6 +308,12 @@ static_append(_, _, _) -> throw(impossible).
%% Guard rewrite is similar to regular rewrite, except
%% it also verifies the resulting function is supported in
%% guard context - only certain BIFs and operators are.
guard(?kernel, DotMeta, is_map_key, Meta, [Map, Key], _S) ->
{ok, {{'.', DotMeta, [erlang, is_map_key]}, Meta, [Key, Map]}};
guard(?kernel, DotMeta, elem, Meta, [Tuple, Index], _S) ->
{ok, {{'.', DotMeta, [erlang, element]}, Meta, [increment(Meta, Index), Tuple]}};
guard(?kernel, DotMeta, put_elem, Meta, [Tuple, Index, Value], _S) ->
{ok, {{'.', DotMeta, [erlang, setelement]}, Meta, [increment(Meta, Index), Tuple, Value]}};
guard(Receiver, DotMeta, Right, Meta, Args, S) ->
case inner_rewrite(ex_to_erl, DotMeta, Receiver, Right, Args) of
{erlang, RRight, RArgs} ->
+3 -13
View File
@@ -169,19 +169,9 @@ defmodule MapTest do
end
end
test "put/3 optimized by the compiler" do
map = %{a: 1, b: 2}
assert Map.put(map, :a, 2) == %{a: 2, b: 2}
assert Map.put(map, :c, 3) == %{a: 1, b: 2, c: 3}
assert Map.put(%{map | a: 2}, :a, 3) == %{a: 3, b: 2}
assert Map.put(%{map | a: 2}, :b, 3) == %{a: 2, b: 3}
assert Map.put(map, :a, 2) |> Map.put(:a, 3) == %{a: 3, b: 2}
assert Map.put(map, :a, 2) |> Map.put(:c, 3) == %{a: 2, b: 2, c: 3}
assert Map.put(map, :c, 3) |> Map.put(:a, 2) == %{a: 2, b: 2, c: 3}
assert Map.put(map, :c, 3) |> Map.put(:c, 4) == %{a: 1, b: 2, c: 4}
test "put/3 evaluation order" do
assert Map.put(send(self(), %{}), send(self(), :key), send(self(), :value)) == %{key: :value}
assert Process.info(self(), :messages) == {:messages, [%{}, :key, :value]}
end
test "merge/2 with map literals optimized by the compiler" do
@@ -779,6 +779,8 @@ defmodule Module.Types.ExprTest do
test "elem/2" do
assert typecheck!(elem({:ok, 123}, 0)) == atom([:ok])
assert typecheck!(elem({:ok, 123}, 1)) == integer()
assert typecheck!(:erlang.element(1, {:ok, 123})) == atom([:ok])
assert typecheck!(:erlang.element(2, {:ok, 123})) == integer()
assert typecheck!([x], elem({:ok, x}, 0)) == dynamic(atom([:ok]))
assert typecheck!([x], elem({:ok, x}, 1)) == dynamic(term())
@@ -835,6 +837,9 @@ defmodule Module.Types.ExprTest do
assert typecheck!(Tuple.insert_at({:ok, 123}, 1, "foo")) ==
tuple([atom([:ok]), binary(), integer()])
assert typecheck!(:erlang.insert_element(2, {:ok, 123}, "foo")) ==
tuple([atom([:ok]), binary(), integer()])
assert typecheck!(Tuple.insert_at({:ok, 123}, 2, "foo")) ==
tuple([atom([:ok]), integer(), binary()])
@@ -895,6 +900,7 @@ defmodule Module.Types.ExprTest do
test "Tuple.delete_at/2" do
assert typecheck!(Tuple.delete_at({:ok, 123}, 0)) == tuple([integer()])
assert typecheck!(Tuple.delete_at({:ok, 123}, 1)) == tuple([atom([:ok])])
assert typecheck!(:erlang.delete_element(2, {:ok, 123})) == tuple([atom([:ok])])
assert typecheck!([x], Tuple.delete_at({:ok, x}, 0)) == dynamic(tuple([term()]))
assert typecheck!([x], Tuple.delete_at({:ok, x}, 1)) == dynamic(tuple([atom([:ok])]))
@@ -945,6 +951,10 @@ defmodule Module.Types.ExprTest do
test "put_elem/3" do
assert typecheck!(put_elem({:ok, 123}, 0, "foo")) == tuple([binary(), integer()])
assert typecheck!(put_elem({:ok, 123}, 1, "foo")) == tuple([atom([:ok]), binary()])
assert typecheck!(:erlang.setelement(1, {:ok, 123}, "foo")) == tuple([binary(), integer()])
assert typecheck!(:erlang.setelement(2, {:ok, 123}, "foo")) ==
tuple([atom([:ok]), binary()])
assert typecheck!([x], put_elem({:ok, x}, 0, "foo")) ==
dynamic(tuple([binary(), term()]))
@@ -1017,6 +1027,7 @@ defmodule Module.Types.ExprTest do
assert typecheck!(Tuple.duplicate(123, 0)) == tuple([])
assert typecheck!(Tuple.duplicate(123, 1)) == tuple([integer()])
assert typecheck!(Tuple.duplicate(123, 2)) == tuple([integer(), integer()])
assert typecheck!(:erlang.make_tuple(2, 123)) == tuple([integer(), integer()])
assert typecheck!([x], Tuple.duplicate(x, 2)) == dynamic(tuple([term(), term()]))
end
end
@@ -18,7 +18,6 @@ defmodule Module.Types.HelpersTest do
assert expr_to_string(quote(do: :erlang.orelse(a, b))) == "a or b"
assert expr_to_string(quote(do: :erlang."=:="(a, b))) == "a === b"
assert expr_to_string(quote(do: :erlang.list_to_atom(a))) == "List.to_atom(a)"
assert expr_to_string(quote(do: :maps.remove(a, b))) == "Map.delete(b, a)"
assert expr_to_string(quote(do: :erlang.element(1, a))) == "elem(a, 0)"
assert expr_to_string(quote(do: :erlang.element(:erlang.+(a, 1), b))) == "elem(b, a)"
end
@@ -94,6 +94,9 @@ defmodule Module.Types.MapTest do
assert typecheck!(Map.delete(%{key: 123}, :key)) ==
empty_map()
assert typecheck!(:maps.remove(:key, %{key: 123})) ==
empty_map()
assert typecheck!([x], Map.delete(x, :key)) ==
dynamic(open_map(key: not_set()))
@@ -139,6 +142,9 @@ defmodule Module.Types.MapTest do
assert typecheck!(Map.fetch(%{key: 123}, :key)) ==
tuple([atom([:ok]), integer()]) |> union(atom([:error]))
assert typecheck!(:maps.find(:key, %{key: 123})) ==
tuple([atom([:ok]), integer()]) |> union(atom([:error]))
assert typecheck!([x], Map.fetch(x, :key)) ==
dynamic(tuple([atom([:ok]), term()])) |> union(atom([:error]))
@@ -186,6 +192,8 @@ defmodule Module.Types.MapTest do
test "checking" do
assert typecheck!(Map.fetch!(%{key: 123}, :key)) == integer()
assert typecheck!(:maps.get(:key, %{key: 123})) == integer()
assert typecheck!([x], Map.fetch!(x, :key)) == dynamic()
# If one of them succeeds, we are still fine!
@@ -244,6 +252,18 @@ defmodule Module.Types.MapTest do
end
end
describe "Map.has_key?/2" do
test "checking" do
assert typecheck!(Map.has_key?(%{key: 123}, :key)) == boolean()
assert typecheck!(:maps.is_key(:key, %{key: 123})) == boolean()
end
test "errors" do
assert typeerror!([x = []], Map.has_key?(x, :key)) =~
"incompatible types given to Map.has_key?/2"
end
end
describe "Map.from_keys/2" do
test "checking" do
assert typecheck!([], Map.from_keys([], :value)) ==
@@ -823,6 +843,9 @@ defmodule Module.Types.MapTest do
assert typecheck!(Map.put(%{}, :key, :value)) ==
closed_map(key: atom([:value]))
assert typecheck!(:maps.put(:key, :value, %{})) ==
closed_map(key: atom([:value]))
assert typecheck!(Map.put(%{key: 123}, :key, :value)) ==
closed_map(key: atom([:value]))
@@ -1126,6 +1149,9 @@ defmodule Module.Types.MapTest do
assert typecheck!(Map.replace!(%{key: 123}, :key, :value)) ==
closed_map(key: atom([:value]))
assert typecheck!(:maps.update(:key, :value, %{key: 123})) ==
closed_map(key: atom([:value]))
assert typecheck!([x], Map.replace!(x, :key, :value)) ==
dynamic(open_map(key: atom([:value])))
@@ -692,18 +692,28 @@ defmodule Module.Types.PatternTest do
test "is_map_key/2" do
assert typecheck!([x], is_map_key(x, :foo), x) == dynamic(open_map(foo: term()))
assert typecheck!([x], :erlang.is_map_key(:foo, x), x) == dynamic(open_map(foo: term()))
assert typecheck!([x], not is_map_key(x, :foo), x) == dynamic(open_map(foo: not_set()))
assert typecheck!([x], not :erlang.is_map_key(:foo, x), x) ==
dynamic(open_map(foo: not_set()))
end
test "elem" do
assert typecheck!([x], elem(x, 1), x) ==
dynamic(open_tuple([term(), atom([true])]))
assert typecheck!([x], :erlang.element(2, x), x) ==
dynamic(open_tuple([term(), atom([true])]))
assert typecheck!([x], not elem(x, 1), x) ==
dynamic(open_tuple([term(), atom([false])]))
assert typecheck!([x], is_integer(elem(x, 1)), x) ==
dynamic(open_tuple([term(), integer()]))
assert typecheck!([x], is_integer(:erlang.element(2, x)), x) ==
dynamic(open_tuple([term(), integer()]))
end
test "map.field" do
+5
View File
@@ -19,6 +19,11 @@ defmodule TupleTest do
assert put_elem({:a, :b, :c}, 1, :d) == {:a, :d, :c}
end
test "evaluation order" do
assert elem(send(self(), {:a, :b, :c}), send(self(), 1)) == :b
assert Process.info(self(), :messages) == {:messages, [{:a, :b, :c}, 1]}
end
test "keyword syntax is supported in tuple literals" do
assert {1, 2, three: :four} == {1, 2, [three: :four]}
end