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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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} ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user