452 lines
11 KiB
Elixir
452 lines
11 KiB
Elixir
Code.require_file "../test_helper.exs", __DIR__
|
|
|
|
defmodule Kernel.QuoteTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
test "list" do
|
|
assert quote(do: [1, 2, 3]) == [1, 2, 3]
|
|
end
|
|
|
|
test "tuple" do
|
|
assert quote(do: {:a, 1}) == {:a, 1}
|
|
end
|
|
|
|
test "keep line" do
|
|
# DO NOT MOVE THIS LINE
|
|
assert quote(location: :keep, do: bar(1, 2, 3)) ==
|
|
{:bar, [file: Path.relative_to_cwd(__ENV__.file), keep: 16], [1, 2, 3]}
|
|
end
|
|
|
|
test "fixed line" do
|
|
assert quote(line: 3, do: bar(1, 2, 3)) == {:bar, [line: 3], [1, 2, 3]}
|
|
end
|
|
|
|
test "quote line var" do
|
|
# DO NOT MOVE THIS LINE
|
|
line = __ENV__.line
|
|
assert quote(line: line, do: bar(1, 2, 3)) == {:bar, [line: 26], [1, 2, 3]}
|
|
end
|
|
|
|
test "generated" do
|
|
assert quote(generated: true, do: bar(1)) == {:bar, [generated: true], [1]}
|
|
end
|
|
|
|
test "unquote call" do
|
|
assert quote(do: foo(bar)[unquote(:baz)]) == quote(do: foo(bar)[:baz])
|
|
assert quote(do: unquote(:bar)()) == quote(do: bar())
|
|
assert quote(do: unquote(:bar)(1) do 2 + 3 end) == quote(do: bar(1) do 2 + 3 end)
|
|
assert quote(do: foo.unquote(:bar)) == quote(do: foo.bar)
|
|
assert quote(do: foo.unquote(:bar)(1)) == quote(do: foo.bar(1))
|
|
assert quote(do: foo.unquote(:bar)(1) do 2 + 3 end) == quote(do: foo.bar(1) do 2 + 3 end)
|
|
assert quote(do: foo.unquote({:bar, [], nil})) == quote(do: foo.bar)
|
|
assert quote(do: foo.unquote({:bar, [], [1, 2]})) == quote(do: foo.bar(1, 2))
|
|
|
|
assert Code.eval_quoted(quote(do: Foo.unquote(Bar))) == {Elixir.Foo.Bar, []}
|
|
assert Code.eval_quoted(quote(do: Foo.unquote(quote do: Bar))) == {Elixir.Foo.Bar, []}
|
|
|
|
assert_raise ArgumentError, fn ->
|
|
quote(do: foo.unquote(1))
|
|
end
|
|
end
|
|
|
|
test "nested quote" do
|
|
assert {:quote, _, [[do: {:unquote, _, _}]]} = quote(do: quote(do: unquote(x)))
|
|
end
|
|
|
|
defmacrop nested_quote_in_macro do
|
|
x = 1
|
|
quote do
|
|
x = unquote(x)
|
|
quote do
|
|
unquote(x)
|
|
end
|
|
end
|
|
end
|
|
|
|
test "nested quote in macro" do
|
|
assert nested_quote_in_macro() == 1
|
|
end
|
|
|
|
Enum.each [foo: 1, bar: 2, baz: 3], fn {k, v} ->
|
|
def unquote(k)(arg) do
|
|
unquote(v) + arg
|
|
end
|
|
end
|
|
|
|
test "dynamic definition with unquote" do
|
|
assert foo(1) == 2
|
|
assert bar(2) == 4
|
|
assert baz(3) == 6
|
|
end
|
|
|
|
test "splice on root" do
|
|
contents = [1, 2, 3]
|
|
assert quote(do: (unquote_splicing(contents))) == quote do: (1; 2; 3)
|
|
end
|
|
|
|
test "splice with tail" do
|
|
contents = [1, 2, 3]
|
|
assert quote(do: [unquote_splicing(contents) | [1, 2, 3]]) ==
|
|
[1, 2, 3, 1, 2, 3]
|
|
|
|
assert quote(do: [unquote_splicing(contents) | val]) ==
|
|
quote(do: [1, 2, 3 | val])
|
|
|
|
assert quote(do: [unquote_splicing(contents) | unquote([4])]) ==
|
|
quote(do: [1, 2, 3, 4])
|
|
end
|
|
|
|
test "splice on stab" do
|
|
{fun, []} =
|
|
Code.eval_quoted(quote(do: fn(unquote_splicing([1, 2, 3])) -> :ok end), [])
|
|
assert fun.(1, 2, 3) == :ok
|
|
|
|
{fun, []} =
|
|
Code.eval_quoted(quote(do: fn(1, unquote_splicing([2, 3])) -> :ok end), [])
|
|
assert fun.(1, 2, 3) == :ok
|
|
end
|
|
|
|
test "splice on definition" do
|
|
defmodule Hello do
|
|
def world([unquote_splicing(["foo", "bar"]) | rest]) do
|
|
rest
|
|
end
|
|
end
|
|
|
|
assert Hello.world(["foo", "bar", "baz"]) == ["baz"]
|
|
end
|
|
|
|
test "splice on map" do
|
|
assert %{unquote_splicing([foo: :bar])} == %{foo: :bar}
|
|
assert %{unquote_splicing([foo: :bar]), baz: :bat} == %{foo: :bar, baz: :bat}
|
|
assert %{unquote_splicing([foo: :bar]), :baz => :bat} == %{foo: :bar, baz: :bat}
|
|
assert %{:baz => :bat, unquote_splicing([foo: :bar])} == %{foo: :bar, baz: :bat}
|
|
|
|
map = %{foo: :default}
|
|
assert %{map | unquote_splicing([foo: :bar])} == %{foo: :bar}
|
|
end
|
|
|
|
test "when" do
|
|
assert [{:->, _, [[{:when, _, [1, 2, 3, 4]}], 5]}] = quote(do: (1, 2, 3 when 4 -> 5))
|
|
assert [{:->, _, [[{:when, _, [1, 2, 3, 4]}], 5]}] = quote(do: ((1, 2, 3) when 4 -> 5))
|
|
|
|
assert [{:->, _, [[{:when, _, [1, 2, 3, {:when, _, [4, 5]}]}], 6]}] =
|
|
quote(do: ((1, 2, 3) when 4 when 5 -> 6))
|
|
end
|
|
|
|
test "stab" do
|
|
assert [{:->, _, [[], 1]}] = (quote do -> 1 end)
|
|
assert [{:->, _, [[], 1]}] = (quote do: (-> 1))
|
|
end
|
|
|
|
test "bind quoted" do
|
|
assert quote(bind_quoted: [foo: 1 + 2], do: foo) == {:__block__, [], [
|
|
{:=, [], [{:foo, [], Kernel.QuoteTest}, 3]},
|
|
{:foo, [], Kernel.QuoteTest}
|
|
]}
|
|
end
|
|
|
|
test "literals" do
|
|
assert (quote do: []) == []
|
|
assert (quote do: nil) == nil
|
|
assert (quote do [] end) == []
|
|
assert (quote do nil end) == nil
|
|
end
|
|
|
|
defmacrop dynamic_opts do
|
|
[line: 3]
|
|
end
|
|
|
|
test "with dynamic opts" do
|
|
assert quote(dynamic_opts(), do: bar(1, 2, 3)) == {:bar, [line: 3], [1, 2, 3]}
|
|
end
|
|
|
|
test "unary with integer precedence" do
|
|
assert quote(do: +1.foo) == quote(do: (+1).foo)
|
|
assert quote(do: @1.foo) == quote(do: (@1).foo)
|
|
assert quote(do: &1.foo) == quote(do: (&1).foo)
|
|
end
|
|
|
|
test "operators slash arity" do
|
|
assert {:/, _, [{:+, _, _}, 2]} = quote do: +/2
|
|
assert {:/, _, [{:&&, _, _}, 3]} = quote do: &&/3
|
|
end
|
|
|
|
test "pipe precedence" do
|
|
assert {:|>, _, [{:|>, _, [{:foo, _, _}, {:bar, _, _}]}, {:baz, _, _}]} =
|
|
quote do: (foo |> bar |> baz)
|
|
|
|
assert {:|>, _, [{:|>, _, [{:foo, _, _}, {:bar, _, _}]}, {:baz, _, _}]} =
|
|
quote do: (foo do end |> bar |> baz)
|
|
|
|
assert {:|>, _, [{:|>, _, [{:foo, _, _}, {:bar, _, _}]}, {:baz, _, _}]} =
|
|
quote do: (foo |> bar do end |> baz)
|
|
|
|
assert {:|>, _, [{:|>, _, [{:foo, _, _}, {:bar, _, _}]}, {:baz, _, _}]} =
|
|
quote do: (foo |> bar |> baz do end)
|
|
|
|
assert {:|>, _, [{:|>, _, [{:foo, _, _}, {:bar, _, _}]}, {:baz, _, _}]} =
|
|
quote do: (foo do end |> bar |> baz do end)
|
|
|
|
assert {:|>, _, [{:|>, _, [{:foo, _, _}, {:bar, _, _}]}, {:baz, _, _}]} =
|
|
quote do: (foo do end |> bar do end |> baz do end)
|
|
end
|
|
end
|
|
|
|
# DO NOT MOVE THIS LINE
|
|
defmodule Kernel.QuoteTest.Errors do
|
|
defmacro defadd do
|
|
quote location: :keep do
|
|
def add(a, b), do: a + b
|
|
end
|
|
end
|
|
|
|
defmacro will_raise do
|
|
quote location: :keep, do: raise "omg"
|
|
end
|
|
end
|
|
|
|
defmodule Kernel.QuoteTest.ErrorsTest do
|
|
use ExUnit.Case, async: true
|
|
import Kernel.QuoteTest.Errors
|
|
|
|
# Defines the add function
|
|
defadd()
|
|
|
|
test "inside function error" do
|
|
assert_raise ArithmeticError, fn ->
|
|
add(:a, :b)
|
|
end
|
|
|
|
mod = Kernel.QuoteTest.ErrorsTest
|
|
file = __ENV__.file |> Path.relative_to_cwd |> String.to_charlist
|
|
assert [{^mod, :add, 2, [file: ^file, line: 200]} | _] = System.stacktrace
|
|
end
|
|
|
|
test "outside function error" do
|
|
assert_raise RuntimeError, fn ->
|
|
will_raise()
|
|
end
|
|
|
|
mod = Kernel.QuoteTest.ErrorsTest
|
|
file = __ENV__.file |> Path.relative_to_cwd |> String.to_charlist
|
|
assert [{^mod, _, _, [file: ^file, line: 228]} | _] = System.stacktrace
|
|
end
|
|
end
|
|
|
|
defmodule Kernel.QuoteTest.VarHygiene do
|
|
defmacro no_interference do
|
|
quote do: a = 1
|
|
end
|
|
|
|
defmacro write_interference do
|
|
quote do: var!(a) = 1
|
|
end
|
|
|
|
defmacro read_interference do
|
|
quote do: 10 = var!(a)
|
|
end
|
|
|
|
defmacro cross_module_interference do
|
|
quote do: var!(a, Kernel.QuoteTest.VarHygieneTest) = 1
|
|
end
|
|
end
|
|
|
|
defmodule Kernel.QuoteTest.VarHygieneTest do
|
|
use ExUnit.Case, async: true
|
|
import Kernel.QuoteTest.VarHygiene
|
|
|
|
defmacrop cross_module_no_interference do
|
|
quote do: a = 10
|
|
end
|
|
|
|
defmacrop read_cross_module do
|
|
quote do: var!(a, __MODULE__)
|
|
end
|
|
|
|
defmacrop nested(var, do: block) do
|
|
quote do
|
|
var = unquote(var)
|
|
unquote(block)
|
|
var
|
|
end
|
|
end
|
|
|
|
defmacrop hat do
|
|
quote do
|
|
var = 1
|
|
^var = 1
|
|
var
|
|
end
|
|
end
|
|
|
|
test "no interference" do
|
|
a = 10
|
|
no_interference()
|
|
assert a == 10
|
|
end
|
|
|
|
test "cross module interference" do
|
|
cross_module_no_interference()
|
|
cross_module_interference()
|
|
assert read_cross_module() == 1
|
|
end
|
|
|
|
test "write interference" do
|
|
write_interference()
|
|
assert a == 1
|
|
end
|
|
|
|
test "read interference" do
|
|
a = 10
|
|
read_interference()
|
|
end
|
|
|
|
test "nested" do
|
|
assert (nested 1 do
|
|
nested 2 do
|
|
_ = :ok
|
|
end
|
|
end) == 1
|
|
end
|
|
|
|
test "hat" do
|
|
assert hat() == 1
|
|
end
|
|
end
|
|
|
|
defmodule Kernel.QuoteTest.AliasHygiene do
|
|
alias Dict, as: SuperDict
|
|
|
|
defmacro dict do
|
|
quote do: Dict.Bar
|
|
end
|
|
|
|
defmacro super_dict do
|
|
quote do: SuperDict.Bar
|
|
end
|
|
end
|
|
|
|
defmodule Kernel.QuoteTest.AliasHygieneTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Dict, as: SuperDict
|
|
|
|
test "annotate aliases" do
|
|
assert {:__aliases__, [alias: false], [:Foo, :Bar]} =
|
|
quote(do: Foo.Bar)
|
|
assert {:__aliases__, [alias: false], [:Dict, :Bar]} =
|
|
quote(do: Dict.Bar)
|
|
assert {:__aliases__, [alias: Dict.Bar], [:SuperDict, :Bar]} =
|
|
quote(do: SuperDict.Bar)
|
|
end
|
|
|
|
test "expand aliases" do
|
|
assert Code.eval_quoted(quote do: SuperDict.Bar) == {Elixir.Dict.Bar, []}
|
|
assert Code.eval_quoted(quote do: alias!(SuperDict.Bar)) == {Elixir.SuperDict.Bar, []}
|
|
end
|
|
|
|
test "expand aliases without macro" do
|
|
alias HashDict, as: SuperDict
|
|
assert SuperDict.Bar == Elixir.HashDict.Bar
|
|
end
|
|
|
|
test "expand aliases with macro does not expand source alias" do
|
|
alias HashDict, as: Dict, warn: false
|
|
require Kernel.QuoteTest.AliasHygiene
|
|
assert Kernel.QuoteTest.AliasHygiene.dict == Elixir.Dict.Bar
|
|
end
|
|
|
|
test "expand aliases with macro has higher preference" do
|
|
alias HashDict, as: SuperDict, warn: false
|
|
require Kernel.QuoteTest.AliasHygiene
|
|
assert Kernel.QuoteTest.AliasHygiene.super_dict == Elixir.Dict.Bar
|
|
end
|
|
end
|
|
|
|
defmodule Kernel.QuoteTest.ImportsHygieneTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
# We are redefining |> and using it inside the quote
|
|
# and only inside the quote. This code should still compile.
|
|
defmacro x |> f do
|
|
quote do
|
|
unquote(x) |> unquote(f)
|
|
end
|
|
end
|
|
|
|
defmacrop get_list_length do
|
|
quote do
|
|
length('hello')
|
|
end
|
|
end
|
|
|
|
defmacrop get_list_length_with_partial do
|
|
quote do
|
|
(&length(&1)).('hello')
|
|
end
|
|
end
|
|
|
|
defmacrop get_list_length_with_function do
|
|
quote do
|
|
(&length/1).('hello')
|
|
end
|
|
end
|
|
|
|
test "expand imports" do
|
|
import Kernel, except: [length: 1]
|
|
assert get_list_length() == 5
|
|
assert get_list_length_with_partial() == 5
|
|
assert get_list_length_with_function() == 5
|
|
end
|
|
|
|
defmacrop get_string_length do
|
|
import Kernel, except: [length: 1]
|
|
|
|
quote do
|
|
length("hello")
|
|
end
|
|
end
|
|
|
|
test "lazy expand imports" do
|
|
import Kernel, except: [length: 1]
|
|
import String, only: [length: 1]
|
|
assert get_string_length() == 5
|
|
end
|
|
|
|
test "lazy expand imports no conflicts" do
|
|
import Kernel, except: [length: 1]
|
|
import String, only: [length: 1]
|
|
|
|
assert get_list_length() == 5
|
|
assert get_list_length_with_partial() == 5
|
|
assert get_list_length_with_function() == 5
|
|
end
|
|
|
|
defmacrop with_length do
|
|
quote do
|
|
import Kernel, except: [length: 1]
|
|
import String, only: [length: 1]
|
|
length('hello')
|
|
end
|
|
end
|
|
|
|
test "explicitly overridden imports" do
|
|
assert with_length() == 5
|
|
end
|
|
|
|
defmodule BinaryUtils do
|
|
defmacro int32 do
|
|
quote do
|
|
integer-size(32)
|
|
end
|
|
end
|
|
end
|
|
|
|
test "checks the context also for variables to zero-arity functions" do
|
|
import BinaryUtils
|
|
{:int32, meta, __MODULE__} = quote do: int32
|
|
assert meta[:import] == BinaryUtils
|
|
end
|
|
end
|