Skip to content

Commit 1f98708

Browse files
committed
Add @undefined_impl_description
1 parent 12780b1 commit 1f98708

File tree

3 files changed

+38
-14
lines changed

3 files changed

+38
-14
lines changed

lib/elixir/lib/module.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,10 @@ defmodule Module do
745745
doc:
746746
"If set to `true` generates a default protocol implementation for all types (inside `defprotocol`)."
747747
},
748+
undefined_impl_description: %{
749+
doc:
750+
"A string with additional description to be used on `Protocol.UndefinedError` when looking up the implementation fails."
751+
},
748752
for: %{
749753
doc:
750754
"The current module/type a protocol implementation is being defined for (inside `defimpl`)."

lib/elixir/lib/protocol.ex

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ defmodule Protocol do
66
implementations. A protocol is defined with `Kernel.defprotocol/2`
77
and its implementations with `Kernel.defimpl/3`.
88
9-
## A real case
9+
## Example
1010
1111
In Elixir, we have two nouns for checking how many items there
1212
are in a data structure: `length` and `size`. `length` means the
@@ -102,8 +102,7 @@ defmodule Protocol do
102102
invoking the protocol will raise unless it is configured to
103103
fall back to `Any`. Conveniences for building implementations
104104
on top of existing ones are also available, look at `defstruct/1`
105-
for more information about deriving
106-
protocols.
105+
for more information about deriving protocols.
107106
108107
## Fallback to `Any`
109108
@@ -165,6 +164,17 @@ defmodule Protocol do
165164
The `@spec` above expresses that all types allowed to implement the
166165
given protocol are valid argument types for the given function.
167166
167+
## Configuration
168+
169+
The following module attributes are available to configure a protocol:
170+
171+
* `@fallback_to_any` - when true, enables protocol dispatch to
172+
fallback to any
173+
174+
* `@undefined_impl_description` - a string with additional description
175+
to be used on `Protocol.UndefinedError` when looking up the implementation
176+
fails. This option is only applied if `@fallback_to_any` is not set to true
177+
168178
## Reflection
169179
170180
Any protocol module contains three extra functions:
@@ -828,7 +838,7 @@ defmodule Protocol do
828838
quote bind_quoted: [built_in: __built_in__()] do
829839
any_impl_for =
830840
if @fallback_to_any do
831-
quote do: unquote(__MODULE__.Any)
841+
__MODULE__.Any
832842
else
833843
nil
834844
end
@@ -874,6 +884,9 @@ defmodule Protocol do
874884
unquote(any_impl_for)
875885
end
876886

887+
undefined_impl_description =
888+
Module.get_attribute(__MODULE__, :undefined_impl_description, "")
889+
877890
@doc false
878891
@spec impl_for!(term) :: atom
879892
if any_impl_for do
@@ -882,17 +895,21 @@ defmodule Protocol do
882895
end
883896
else
884897
Kernel.def impl_for!(data) do
885-
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
898+
impl_for(data) ||
899+
raise(Protocol.UndefinedError,
900+
protocol: __MODULE__,
901+
value: data,
902+
description: unquote(undefined_impl_description)
903+
)
886904
end
887905
end
888906

889907
# Internal handler for Structs
890908
Kernel.defp struct_impl_for(struct) do
891-
target =
892-
case Code.ensure_compiled(Module.concat(__MODULE__, struct)) do
893-
{:module, module} -> module
894-
{:error, _} -> unquote(any_impl_for)
895-
end
909+
case Code.ensure_compiled(Module.concat(__MODULE__, struct)) do
910+
{:module, module} -> module
911+
{:error, _} -> unquote(any_impl_for)
912+
end
896913
end
897914

898915
# Inline struct implementation for performance

lib/elixir/test/elixir/protocol_test.exs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ defmodule ProtocolTest do
2626
@with_any_binary with_any_binary
2727

2828
defprotocol Derivable do
29+
@undefined_impl_description "you should try harder"
2930
def ok(a)
3031
end
3132

@@ -283,10 +284,12 @@ defmodule ProtocolTest do
283284
struct = %ImplStruct{a: 1, b: 1}
284285
assert Derivable.ok(struct) == {:ok, struct, %ImplStruct{}, []}
285286

286-
assert_raise Protocol.UndefinedError, fn ->
287-
struct = %NoImplStruct{a: 1, b: 1}
288-
Derivable.ok(struct)
289-
end
287+
assert_raise Protocol.UndefinedError,
288+
~r"protocol ProtocolTest.Derivable not implemented for type ProtocolTest.NoImplStruct \(a struct\), you should try harder",
289+
fn ->
290+
struct = %NoImplStruct{a: 1, b: 1}
291+
Derivable.ok(struct)
292+
end
290293
end
291294

292295
test "derives protocol explicitly with options" do

0 commit comments

Comments
 (0)