diff --git a/lib/elixir/lib/kernel/parallel_compiler.ex b/lib/elixir/lib/kernel/parallel_compiler.ex index a26b838994..a4f7b2d8d1 100644 --- a/lib/elixir/lib/kernel/parallel_compiler.ex +++ b/lib/elixir/lib/kernel/parallel_compiler.ex @@ -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) diff --git a/lib/elixir/lib/module/parallel_checker.ex b/lib/elixir/lib/module/parallel_checker.ex index 4c5b1bee9e..7ad9c6382a 100644 --- a/lib/elixir/lib/module/parallel_checker.ex +++ b/lib/elixir/lib/module/parallel_checker.ex @@ -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 diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index d0e8d4262b..d49b30cb34 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -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 diff --git a/lib/elixir/lib/process.ex b/lib/elixir/lib/process.ex index 17e7149f2b..57edae463e 100644 --- a/lib/elixir/lib/process.ex +++ b/lib/elixir/lib/process.ex @@ -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 diff --git a/lib/elixir/src/elixir_erl_pass.erl b/lib/elixir/src/elixir_erl_pass.erl index 451019f845..3a42a96c75 100644 --- a/lib/elixir/src/elixir_erl_pass.erl +++ b/lib/elixir/src/elixir_erl_pass.erl @@ -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; diff --git a/lib/elixir/src/elixir_rewrite.erl b/lib/elixir/src/elixir_rewrite.erl index dcfff3b690..e782cdb260 100644 --- a/lib/elixir/src/elixir_rewrite.erl +++ b/lib/elixir/src/elixir_rewrite.erl @@ -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} -> diff --git a/lib/elixir/test/elixir/map_test.exs b/lib/elixir/test/elixir/map_test.exs index d551e78fc3..c47de97b4c 100644 --- a/lib/elixir/test/elixir/map_test.exs +++ b/lib/elixir/test/elixir/map_test.exs @@ -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 diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 2c9c50974b..735c3ea2f8 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -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 diff --git a/lib/elixir/test/elixir/module/types/helpers_test.exs b/lib/elixir/test/elixir/module/types/helpers_test.exs index f7be49f377..144ce090f2 100644 --- a/lib/elixir/test/elixir/module/types/helpers_test.exs +++ b/lib/elixir/test/elixir/module/types/helpers_test.exs @@ -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 diff --git a/lib/elixir/test/elixir/module/types/map_test.exs b/lib/elixir/test/elixir/module/types/map_test.exs index 0c9db45fa0..9f706d9004 100644 --- a/lib/elixir/test/elixir/module/types/map_test.exs +++ b/lib/elixir/test/elixir/module/types/map_test.exs @@ -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]))) diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs index 3b51f75ef4..f38558414c 100644 --- a/lib/elixir/test/elixir/module/types/pattern_test.exs +++ b/lib/elixir/test/elixir/module/types/pattern_test.exs @@ -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 diff --git a/lib/elixir/test/elixir/tuple_test.exs b/lib/elixir/test/elixir/tuple_test.exs index dea4d965f6..e1b4ce7703 100644 --- a/lib/elixir/test/elixir/tuple_test.exs +++ b/lib/elixir/test/elixir/tuple_test.exs @@ -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