529 lines
13 KiB
Elixir
529 lines
13 KiB
Elixir
Code.require_file("../test_helper.exs", __DIR__)
|
|
|
|
defmodule Kernel.Overridable do
|
|
def sample do
|
|
1
|
|
end
|
|
|
|
def with_super do
|
|
1
|
|
end
|
|
|
|
def without_super do
|
|
1
|
|
end
|
|
|
|
def super_with_multiple_args(x, y) do
|
|
x + y
|
|
end
|
|
|
|
def capture_super(x) do
|
|
x
|
|
end
|
|
|
|
defmacro capture_super_macro(x) do
|
|
x
|
|
end
|
|
|
|
def many_clauses(0) do
|
|
11
|
|
end
|
|
|
|
def many_clauses(1) do
|
|
13
|
|
end
|
|
|
|
def locals do
|
|
undefined_function()
|
|
end
|
|
|
|
def multiple_overrides do
|
|
[1]
|
|
end
|
|
|
|
def public_to_private do
|
|
:public
|
|
end
|
|
|
|
defoverridable sample: 0,
|
|
with_super: 0,
|
|
without_super: 0,
|
|
super_with_multiple_args: 2,
|
|
capture_super: 1,
|
|
capture_super_macro: 1,
|
|
many_clauses: 1,
|
|
locals: 0,
|
|
multiple_overrides: 0,
|
|
public_to_private: 0
|
|
|
|
true = Module.overridable?(__MODULE__, {:without_super, 0})
|
|
true = Module.overridable?(__MODULE__, {:with_super, 0})
|
|
|
|
def without_super do
|
|
:without_super
|
|
end
|
|
|
|
def with_super do
|
|
super() + 2
|
|
end
|
|
|
|
def super_with_multiple_args(x, y) do
|
|
super(x, y * 2)
|
|
end
|
|
|
|
def capture_super(x) do
|
|
Enum.map(1..x, &super(&1)) ++ Enum.map(1..x, &super/1)
|
|
end
|
|
|
|
defmacro capture_super_macro(x) do
|
|
Enum.map(1..x, &super(&1)) ++ Enum.map(1..x, &super/1)
|
|
end
|
|
|
|
def many_clauses(2) do
|
|
17
|
|
end
|
|
|
|
def many_clauses(3) do
|
|
super(0) + super(1)
|
|
end
|
|
|
|
def many_clauses(x) do
|
|
super(x)
|
|
end
|
|
|
|
def locals do
|
|
:ok
|
|
end
|
|
|
|
def multiple_overrides do
|
|
[2 | super()]
|
|
end
|
|
|
|
defp public_to_private do
|
|
:private
|
|
end
|
|
|
|
def test_public_to_private do
|
|
public_to_private()
|
|
end
|
|
|
|
defoverridable multiple_overrides: 0
|
|
|
|
def multiple_overrides do
|
|
[3 | super()]
|
|
end
|
|
|
|
## Macros
|
|
|
|
defmacro overridable_macro(x) do
|
|
quote do
|
|
unquote(x) + 100
|
|
end
|
|
end
|
|
|
|
defoverridable overridable_macro: 1
|
|
|
|
defmacro overridable_macro(x) do
|
|
quote do
|
|
unquote(super(x)) + 1000
|
|
end
|
|
end
|
|
|
|
defmacrop private_macro(x \\ raise("never called"))
|
|
|
|
defmacrop private_macro(x) do
|
|
quote do
|
|
unquote(x) + 100
|
|
end
|
|
end
|
|
|
|
defoverridable private_macro: 1
|
|
|
|
defmacrop private_macro(x) do
|
|
quote do
|
|
unquote(super(x)) + 1000
|
|
end
|
|
end
|
|
|
|
def private_macro_call(val \\ 11) do
|
|
private_macro(val)
|
|
end
|
|
end
|
|
|
|
defmodule Kernel.OverridableExampleBehaviour do
|
|
@callback required_callback :: any
|
|
@callback optional_callback :: any
|
|
@macrocallback required_macro_callback(arg :: any) :: Macro.t()
|
|
@macrocallback optional_macro_callback(arg :: any, arg2 :: any) :: Macro.t()
|
|
@optional_callbacks optional_callback: 0, optional_macro_callback: 2
|
|
end
|
|
|
|
defmodule Kernel.OverridableTest do
|
|
require Kernel.Overridable, as: Overridable
|
|
use ExUnit.Case
|
|
|
|
defp purge(module) do
|
|
:code.purge(module)
|
|
:code.delete(module)
|
|
end
|
|
|
|
test "overridable keeps function ordering" do
|
|
defmodule OverridableOrder do
|
|
def not_private(str) do
|
|
process_url(str)
|
|
end
|
|
|
|
def process_url(_str) do
|
|
:first
|
|
end
|
|
|
|
# There was a bug where the order in which we removed
|
|
# overridable expressions lead to errors. This module
|
|
# aims to guarantee removing process_url/1 before we
|
|
# remove the function that depends on it does not cause
|
|
# errors. If it compiles, it works!
|
|
defoverridable process_url: 1, not_private: 1
|
|
|
|
def process_url(_str) do
|
|
:second
|
|
end
|
|
end
|
|
end
|
|
|
|
test "overridable works with defaults" do
|
|
defmodule OverridableDefault do
|
|
def fun(value, opt \\ :from_parent) do
|
|
{value, opt}
|
|
end
|
|
|
|
defmacro macro(value, opt \\ :from_parent) do
|
|
{{value, opt}, Macro.escape(__CALLER__)}
|
|
end
|
|
|
|
# There was a bug where the default function would
|
|
# attempt to call its overridable name instead of
|
|
# func/1. If it compiles, it works!
|
|
defoverridable fun: 1, fun: 2, macro: 1, macro: 2
|
|
|
|
def fun(value) do
|
|
{value, super(value)}
|
|
end
|
|
|
|
defmacro macro(value) do
|
|
{{value, super(value)}, Macro.escape(__CALLER__)}
|
|
end
|
|
end
|
|
|
|
defmodule OverridableCall do
|
|
require OverridableDefault
|
|
OverridableDefault.fun(:foo)
|
|
OverridableDefault.macro(:bar)
|
|
end
|
|
end
|
|
|
|
test "overridable is made concrete if no other is defined" do
|
|
assert Overridable.sample() == 1
|
|
end
|
|
|
|
test "overridable overridden with super" do
|
|
assert Overridable.with_super() == 3
|
|
end
|
|
|
|
test "overridable overridden without super" do
|
|
assert Overridable.without_super() == :without_super
|
|
end
|
|
|
|
test "public overridable overridden as private function" do
|
|
assert Overridable.test_public_to_private() == :private
|
|
refute {:public_to_private, 0} in Overridable.module_info(:exports)
|
|
end
|
|
|
|
test "overridable locals are ignored without super" do
|
|
assert Overridable.locals() == :ok
|
|
end
|
|
|
|
test "calling super with multiple args" do
|
|
assert Overridable.super_with_multiple_args(1, 2) == 5
|
|
end
|
|
|
|
test "calling super using function captures" do
|
|
assert Overridable.capture_super(5) == [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
|
|
end
|
|
|
|
test "calling super of an overridable macro using function captures" do
|
|
assert Overridable.capture_super_macro(5) == [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
|
|
end
|
|
|
|
test "super as a variable" do
|
|
super = :ok
|
|
assert super == :ok
|
|
end
|
|
|
|
test "overridable with many clauses" do
|
|
assert Overridable.many_clauses(0) == 11
|
|
assert Overridable.many_clauses(1) == 13
|
|
assert Overridable.many_clauses(2) == 17
|
|
assert Overridable.many_clauses(3) == 24
|
|
end
|
|
|
|
test "overridable definitions are private" do
|
|
refute {:"with_super (overridable 0)", 0} in Overridable.module_info(:exports)
|
|
refute {:"with_super (overridable 1)", 0} in Overridable.module_info(:exports)
|
|
end
|
|
|
|
test "multiple overrides" do
|
|
assert Overridable.multiple_overrides() == [3, 2, 1]
|
|
end
|
|
|
|
test "overridable macros" do
|
|
a = 11
|
|
assert Overridable.overridable_macro(a) == 1111
|
|
assert Overridable.private_macro_call() == 1111
|
|
end
|
|
|
|
test "invalid super call" do
|
|
message =
|
|
"nofile:4: no super defined for foo/0 in module Kernel.OverridableOrder.Forwarding. " <>
|
|
"Overridable functions available are: bar/0"
|
|
|
|
assert_raise CompileError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableOrder.Forwarding do
|
|
def bar(), do: 1
|
|
defoverridable bar: 0
|
|
def foo(), do: super()
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableOrder.Forwarding)
|
|
end
|
|
|
|
test "invalid super call with different arity" do
|
|
message =
|
|
"nofile:4: super must be called with the same number of arguments as the current definition"
|
|
|
|
assert_raise CompileError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableSuper.DifferentArities do
|
|
def bar(a), do: a
|
|
defoverridable bar: 1
|
|
def bar(_), do: super()
|
|
end
|
|
""")
|
|
end
|
|
end
|
|
|
|
test "invalid super capture with different arity" do
|
|
message =
|
|
"nofile:4: super must be called with the same number of arguments as the current definition"
|
|
|
|
assert_raise CompileError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableSuperCapture.DifferentArities do
|
|
def bar(a), do: a
|
|
defoverridable bar: 1
|
|
def bar(_), do: (&super/0).()
|
|
end
|
|
""")
|
|
end
|
|
end
|
|
|
|
test "does not allow to override a macro as a function" do
|
|
message =
|
|
"nofile:4: cannot override macro (defmacro, defmacrop) foo/0 in module " <>
|
|
"Kernel.OverridableMacro.FunctionOverride as a function (def, defp)"
|
|
|
|
assert_raise CompileError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableMacro.FunctionOverride do
|
|
defmacro foo(), do: :ok
|
|
defoverridable foo: 0
|
|
def foo(), do: :invalid
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableMacro.FunctionOverride)
|
|
|
|
assert_raise CompileError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableMacro.FunctionOverride do
|
|
defmacro foo(), do: :ok
|
|
defoverridable foo: 0
|
|
def foo(), do: :invalid
|
|
defoverridable foo: 0
|
|
def foo(), do: :invalid
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableMacro.FunctionOverride)
|
|
|
|
assert_raise CompileError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableMacro.FunctionOverride do
|
|
defmacro foo(), do: :ok
|
|
defoverridable foo: 0
|
|
def foo(), do: super()
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableMacro.FunctionOverride)
|
|
end
|
|
|
|
test "does not allow to override a function as a macro" do
|
|
message =
|
|
"nofile:4: cannot override function (def, defp) foo/0 in module " <>
|
|
"Kernel.OverridableFunction.MacroOverride as a macro (defmacro, defmacrop)"
|
|
|
|
assert_raise CompileError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableFunction.MacroOverride do
|
|
def foo(), do: :ok
|
|
defoverridable foo: 0
|
|
defmacro foo(), do: :invalid
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableFunction.MacroOverride)
|
|
|
|
assert_raise CompileError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableFunction.MacroOverride do
|
|
def foo(), do: :ok
|
|
defoverridable foo: 0
|
|
defmacro foo(), do: :invalid
|
|
defoverridable foo: 0
|
|
defmacro foo(), do: :invalid
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableFunction.MacroOverride)
|
|
|
|
assert_raise CompileError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableFunction.MacroOverride do
|
|
def foo(), do: :ok
|
|
defoverridable foo: 0
|
|
defmacro foo(), do: super()
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableFunction.MacroOverride)
|
|
end
|
|
|
|
test "undefined functions can't be marked as overridable" do
|
|
message = "cannot make function foo/2 overridable because it was not defined"
|
|
|
|
assert_raise ArgumentError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableOrder.Foo do
|
|
defoverridable foo: 2
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableOrder.Foo)
|
|
end
|
|
|
|
test "overrides with behaviour" do
|
|
defmodule OverridableWithBehaviour do
|
|
@behaviour Elixir.Kernel.OverridableExampleBehaviour
|
|
|
|
def required_callback(), do: "original"
|
|
|
|
def optional_callback(), do: "original"
|
|
|
|
def not_a_behaviour_callback(), do: "original"
|
|
|
|
defmacro required_macro_callback(boolean) do
|
|
quote do
|
|
if unquote(boolean) do
|
|
"original"
|
|
end
|
|
end
|
|
end
|
|
|
|
defoverridable Elixir.Kernel.OverridableExampleBehaviour
|
|
|
|
defmacro optional_macro_callback(arg1, arg2), do: {arg1, arg2}
|
|
|
|
assert Module.overridable?(__MODULE__, {:required_callback, 0})
|
|
assert Module.overridable?(__MODULE__, {:optional_callback, 0})
|
|
assert Module.overridable?(__MODULE__, {:required_macro_callback, 1})
|
|
refute Module.overridable?(__MODULE__, {:optional_macro_callback, 1})
|
|
refute Module.overridable?(__MODULE__, {:not_a_behaviour_callback, 1})
|
|
end
|
|
end
|
|
|
|
test "undefined module can't be passed as argument to defoverridable" do
|
|
message =
|
|
"cannot pass module Kernel.OverridableTest.Bar as argument to defoverridable/1 because it was not defined"
|
|
|
|
assert_raise ArgumentError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableTest.Foo do
|
|
defoverridable Kernel.OverridableTest.Bar
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableTest.Foo)
|
|
end
|
|
|
|
test "module without @behaviour can't be passed as argument to defoverridable" do
|
|
message =
|
|
"cannot pass module Kernel.OverridableExampleBehaviour as argument to defoverridable/1" <>
|
|
" because its corresponding behaviour is missing. Did you forget to add " <>
|
|
"@behaviour Kernel.OverridableExampleBehaviour?"
|
|
|
|
assert_raise ArgumentError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableTest.Foo do
|
|
defoverridable Kernel.OverridableExampleBehaviour
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableTest.Foo)
|
|
end
|
|
|
|
test "module with no callbacks can't be passed as argument to defoverridable" do
|
|
message =
|
|
"cannot pass module Kernel.OverridableTest.Bar as argument to defoverridable/1 because it does not define any callbacks"
|
|
|
|
assert_raise ArgumentError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableTest.Bar do
|
|
end
|
|
defmodule Kernel.OverridableTest.Foo do
|
|
@behaviour Kernel.OverridableTest.Bar
|
|
defoverridable Kernel.OverridableTest.Bar
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableTest.Bar)
|
|
purge(Kernel.OverridableTest.Foo)
|
|
end
|
|
|
|
test "atom which is not a module can't be passed as argument to defoverridable" do
|
|
message = "cannot pass module :abc as argument to defoverridable/1 because it was not defined"
|
|
|
|
assert_raise ArgumentError, message, fn ->
|
|
Code.eval_string("""
|
|
defmodule Kernel.OverridableTest.Foo do
|
|
defoverridable :abc
|
|
end
|
|
""")
|
|
end
|
|
|
|
purge(Kernel.OverridableTest.Foo)
|
|
end
|
|
end
|