Skip to content

Commit b878f37

Browse files
committed
Improve error message for protocols with no implementation, closes #14364
1 parent 9619116 commit b878f37

File tree

2 files changed

+60
-6
lines changed

2 files changed

+60
-6
lines changed

lib/elixir/lib/module/types/apply.ex

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,11 +1031,21 @@ defmodule Module.Types.Apply do
10311031

10321032
Code.ensure_loaded?(mod) and
10331033
Keyword.has_key?(mod.module_info(:attributes), :__protocol__) ->
1034-
# Protocol errors can be very verbose, so we collapse structs
1035-
"""
1036-
but expected a type that implements the #{inspect(mod)} protocol, it must be one of:
1037-
#{clauses_args_to_quoted_string(clauses, converter, collapse_structs: true)}
1038-
"""
1034+
if function_exported?(mod, :__protocol__, 1) and
1035+
mod.__protocol__(:impls) == {:consolidated, []} do
1036+
"""
1037+
but the protocol was not yet implemented for any type and therefore will always fail. \
1038+
This error typically happens within libraries that define protocols and will disappear as \
1039+
soon as there is one implementation. If you expect the protocol to be implemented later on, \
1040+
you can define an implementation specific for development/test.
1041+
"""
1042+
else
1043+
# Protocol errors can be very verbose, so we collapse structs
1044+
"""
1045+
but expected a type that implements the #{inspect(mod)} protocol, it must be one of:
1046+
#{clauses_args_to_quoted_string(clauses, converter, collapse_structs: true)}
1047+
"""
1048+
end
10391049

10401050
true ->
10411051
"""

lib/elixir/test/elixir/module/types/integration_test.exs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ defmodule Module.Types.IntegrationTest do
384384
assert_no_warnings(files)
385385
end
386386

387-
test "mismatched impl" do
387+
test "mismatched implementation" do
388388
files = %{
389389
"a.ex" => """
390390
defprotocol Itself do
@@ -420,6 +420,50 @@ defmodule Module.Types.IntegrationTest do
420420
assert_warnings(files, warnings)
421421
end
422422

423+
@tag :require_ast
424+
test "no implementation" do
425+
files = %{
426+
"a.ex" => """
427+
defprotocol NoImplProtocol do
428+
def callback(data)
429+
end
430+
""",
431+
"b.ex" => """
432+
defmodule NoImplProtocol.Caller do
433+
def run do
434+
NoImplProtocol.callback(:hello)
435+
end
436+
end
437+
"""
438+
}
439+
440+
warnings = [
441+
"""
442+
warning: incompatible types given to NoImplProtocol.callback/1:
443+
444+
NoImplProtocol.callback(:hello)
445+
446+
given types:
447+
448+
-:hello-
449+
450+
but the protocol was not yet implemented for any type and therefore will always fail. \
451+
This error typically happens within libraries that define protocols and will disappear as \
452+
soon as there is one implementation. If you expect the protocol to be implemented later on, \
453+
you can define an implementation specific for development/test.
454+
455+
typing violation found at:
456+
457+
3 │ NoImplProtocol.callback(:hello)
458+
│ ~
459+
460+
└─ b.ex:3:20: NoImplProtocol.Caller.run/0
461+
"""
462+
]
463+
464+
assert_warnings(files, warnings, consolidate_protocols: true)
465+
end
466+
423467
@tag :require_ast
424468
test "String.Chars protocol dispatch" do
425469
files = %{

0 commit comments

Comments
 (0)