Skip to content

Commit 27c3a4a

Browse files
committed
Warn if a protocol has no definition, closes #11588
1 parent 808e955 commit 27c3a4a

File tree

3 files changed

+86
-81
lines changed

3 files changed

+86
-81
lines changed

lib/elixir/lib/protocol.ex

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -763,13 +763,20 @@ defmodule Protocol do
763763
IO.warn(message, stacktrace)
764764
end
765765

766-
# TODO: Convert the following warnings into errors in future Elixir versions
767766
def __before_compile__(env) do
768-
# Callbacks
769767
callback_metas = callback_metas(env.module, :callback)
770768
callbacks = :maps.keys(callback_metas)
771769
functions = Module.get_attribute(env.module, :__functions__)
772770

771+
if functions == [] do
772+
warn(
773+
"protocols must define at least one function, but none was defined",
774+
env,
775+
nil
776+
)
777+
end
778+
779+
# TODO: Convert the following warnings into errors in future Elixir versions
773780
:lists.map(
774781
fn {name, arity} = fa ->
775782
warn(
@@ -799,7 +806,7 @@ defmodule Protocol do
799806
# Optional Callbacks
800807
optional_callbacks = Module.get_attribute(env.module, :optional_callbacks)
801808

802-
if length(optional_callbacks) > 0 do
809+
if optional_callbacks != [] do
803810
warn(
804811
"cannot define @optional_callbacks inside protocol, all of the protocol definitions are required",
805812
env,

lib/elixir/test/elixir/kernel/warning_test.exs

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,66 +1224,6 @@ defmodule Kernel.WarningTest do
12241224
purge([Sample1, Sample1.Atom])
12251225
end
12261226

1227-
test "warn when callbacks and friends are defined inside a protocol" do
1228-
message =
1229-
capture_err(fn ->
1230-
Code.eval_string(~S"""
1231-
defprotocol SampleWithCallbacks do
1232-
@spec with_specs(any(), keyword()) :: tuple()
1233-
def with_specs(term, options \\ [])
1234-
1235-
@spec with_specs_and_when(any(), opts) :: tuple() when opts: keyword
1236-
def with_specs_and_when(term, options \\ [])
1237-
1238-
def without_specs(term, options \\ [])
1239-
1240-
@callback foo :: {:ok, term}
1241-
@callback foo(term) :: {:ok, term}
1242-
@callback foo(term, keyword) :: {:ok, term, keyword}
1243-
1244-
@callback foo_when :: {:ok, x} when x: term
1245-
@callback foo_when(x) :: {:ok, x} when x: term
1246-
@callback foo_when(x, opts) :: {:ok, x, opts} when x: term, opts: keyword
1247-
1248-
@macrocallback bar(term) :: {:ok, term}
1249-
@macrocallback bar(term, keyword) :: {:ok, term, keyword}
1250-
1251-
@optional_callbacks [foo: 1, foo: 2]
1252-
@optional_callbacks [without_specs: 2]
1253-
end
1254-
""")
1255-
end)
1256-
1257-
assert message =~
1258-
"cannot define @callback foo/0 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
1259-
1260-
assert message =~
1261-
"cannot define @callback foo/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
1262-
1263-
assert message =~
1264-
"cannot define @callback foo/2 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
1265-
1266-
assert message =~
1267-
"cannot define @callback foo_when/0 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
1268-
1269-
assert message =~
1270-
"cannot define @callback foo_when/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
1271-
1272-
assert message =~
1273-
"cannot define @callback foo_when/2 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
1274-
1275-
assert message =~
1276-
"cannot define @macrocallback bar/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
1277-
1278-
assert message =~
1279-
"cannot define @macrocallback bar/2 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
1280-
1281-
assert message =~
1282-
"cannot define @optional_callbacks inside protocol, all of the protocol definitions are required\n nofile:1"
1283-
after
1284-
purge([SampleWithCallbacks])
1285-
end
1286-
12871227
test "overridden def name" do
12881228
assert capture_err(fn ->
12891229
Code.eval_string("""

lib/elixir/test/elixir/protocol_test.exs

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -267,27 +267,84 @@ defmodule ProtocolTest do
267267
String.to_charlist(__ENV__.file)
268268
end
269269

270-
test "cannot derive without any implementation" do
271-
assert_raise ArgumentError,
272-
~r"could not load module #{inspect(Sample.Any)} due to reason :nofile, cannot derive #{inspect(Sample)}",
273-
fn ->
274-
defmodule NotCompiled do
275-
@derive [Sample]
276-
defstruct hello: :world
277-
end
278-
end
270+
describe "warnings" do
271+
import ExUnit.CaptureIO
272+
273+
test "with no definitions" do
274+
assert capture_io(:stderr, fn ->
275+
defprotocol SampleWithNoDefinitions do
276+
end
277+
end) =~ "protocols must define at least one function, but none was defined"
278+
end
279+
280+
test "when @callbacks and friends are defined inside a protocol" do
281+
message =
282+
capture_io(:stderr, fn ->
283+
defprotocol SampleWithCallbacks do
284+
@spec with_specs(any(), keyword()) :: tuple()
285+
def with_specs(term, options \\ [])
286+
287+
@spec with_specs_and_when(any(), opts) :: tuple() when opts: keyword
288+
def with_specs_and_when(term, options \\ [])
289+
290+
def without_specs(term, options \\ [])
291+
292+
@callback foo :: {:ok, term}
293+
@callback foo(term) :: {:ok, term}
294+
@callback foo(term, keyword) :: {:ok, term, keyword}
295+
296+
@callback foo_when :: {:ok, x} when x: term
297+
@callback foo_when(x) :: {:ok, x} when x: term
298+
@callback foo_when(x, opts) :: {:ok, x, opts} when x: term, opts: keyword
299+
300+
@macrocallback bar(term) :: {:ok, term}
301+
@macrocallback bar(term, keyword) :: {:ok, term, keyword}
302+
303+
@optional_callbacks [foo: 1, foo: 2]
304+
@optional_callbacks [without_specs: 2]
305+
end
306+
end)
307+
308+
assert message =~
309+
"cannot define @callback foo/0 inside protocol, use def/1 to outline your protocol definition"
310+
311+
assert message =~
312+
"cannot define @callback foo/1 inside protocol, use def/1 to outline your protocol definition"
313+
314+
assert message =~
315+
"cannot define @callback foo/2 inside protocol, use def/1 to outline your protocol definition"
316+
317+
assert message =~
318+
"cannot define @callback foo_when/0 inside protocol, use def/1 to outline your protocol definition"
319+
320+
assert message =~
321+
"cannot define @callback foo_when/1 inside protocol, use def/1 to outline your protocol definition"
322+
323+
assert message =~
324+
"cannot define @callback foo_when/2 inside protocol, use def/1 to outline your protocol definition"
325+
326+
assert message =~
327+
"cannot define @macrocallback bar/1 inside protocol, use def/1 to outline your protocol definition"
328+
329+
assert message =~
330+
"cannot define @macrocallback bar/2 inside protocol, use def/1 to outline your protocol definition"
331+
332+
assert message =~
333+
"cannot define @optional_callbacks inside protocol, all of the protocol definitions are required"
334+
end
279335
end
280336

281-
test "malformed @callback raises with CompileError" do
282-
assert_raise CompileError,
283-
"nofile:2: type specification missing return type: foo(term)",
284-
fn ->
285-
Code.eval_string("""
286-
defprotocol WithMalformedCallback do
287-
@callback foo(term)
337+
describe "errors" do
338+
test "cannot derive without any implementation" do
339+
assert_raise ArgumentError,
340+
~r"could not load module #{inspect(Sample.Any)} due to reason :nofile, cannot derive #{inspect(Sample)}",
341+
fn ->
342+
defmodule NotCompiled do
343+
@derive [Sample]
344+
defstruct hello: :world
345+
end
288346
end
289-
""")
290-
end
347+
end
291348
end
292349
end
293350

@@ -299,6 +356,7 @@ defmodule Protocol.DebugInfoTest do
299356

300357
{:module, _, binary, _} =
301358
defprotocol DebugInfoProto do
359+
def example(info)
302360
end
303361

304362
assert {:ok, {DebugInfoProto, [debug_info: debug_info]}} =

0 commit comments

Comments
 (0)