1Code.require_file("../test_helper.exs", __DIR__)
2
3defmodule Kernel.ImplTest do
4  use ExUnit.Case, async: true
5
6  defp capture_err(fun) do
7    ExUnit.CaptureIO.capture_io(:stderr, fun)
8  end
9
10  defp purge(module) do
11    :code.purge(module)
12    :code.delete(module)
13  end
14
15  setup do
16    on_exit(fn -> purge(Kernel.ImplTest.ImplAttributes) end)
17  end
18
19  defprotocol AProtocol do
20    def foo(term)
21    def bar(term)
22  end
23
24  defmodule Behaviour do
25    @callback foo() :: any
26  end
27
28  defmodule BehaviourWithArgument do
29    @callback foo(any) :: any
30  end
31
32  defmodule BehaviourWithThreeArguments do
33    @callback foo(any, any, any) :: any
34  end
35
36  defmodule UseBehaviourWithoutImpl do
37    @callback foo_without_impl() :: any
38    @callback bar_without_impl() :: any
39    @callback baz_without_impl() :: any
40
41    defmacro __using__(_opts) do
42      quote do
43        @behaviour Kernel.ImplTest.UseBehaviourWithoutImpl
44        def foo_without_impl(), do: :auto_generated
45      end
46    end
47  end
48
49  defmodule UseBehaviourWithImpl do
50    @callback foo_with_impl() :: any
51    @callback bar_with_impl() :: any
52    @callback baz_with_impl() :: any
53
54    defmacro __using__(_opts) do
55      quote do
56        @behaviour Kernel.ImplTest.UseBehaviourWithImpl
57        @impl true
58        def foo_with_impl(), do: :auto_generated
59        def bar_with_impl(), do: :auto_generated
60      end
61    end
62  end
63
64  defmodule MacroBehaviour do
65    @macrocallback bar :: any
66  end
67
68  defmodule ManualBehaviour do
69    def behaviour_info(:callbacks), do: [foo: 0]
70    def behaviour_info(:optional_callbacks), do: :undefined
71  end
72
73  test "sets @impl to boolean" do
74    defmodule ImplAttributes do
75      @behaviour Behaviour
76
77      @impl true
78      def foo(), do: :ok
79
80      @impl false
81      def foo(term) do
82        term
83      end
84    end
85  end
86
87  test "sets @impl to nil" do
88    assert_raise ArgumentError, ~r/should be a module or a boolean/, fn ->
89      defmodule ImplAttributes do
90        @behaviour Behaviour
91        @impl nil
92        def foo(), do: :ok
93      end
94    end
95  end
96
97  test "sets @impl to behaviour" do
98    defmodule ImplAttributes do
99      @behaviour Behaviour
100      @impl Behaviour
101      def foo(), do: :ok
102    end
103  end
104
105  test "does not set @impl" do
106    defmodule ImplAttributes do
107      @behaviour Behaviour
108      def foo(), do: :ok
109    end
110  end
111
112  test "sets @impl to boolean on manual behaviour" do
113    defmodule ImplAttributes do
114      @behaviour ManualBehaviour
115
116      @impl true
117      def foo(), do: :ok
118    end
119  end
120
121  test "warns for undefined value" do
122    assert capture_err(fn ->
123             Code.eval_string("""
124             defmodule Kernel.ImplTest.ImplAttributes do
125               @behaviour :abc
126
127               @impl :abc
128               def foo(), do: :ok
129             end
130             """)
131           end) =~
132             "got \"@impl :abc\" for function foo/0 but this behaviour does not specify such callback. There are no known callbacks"
133  end
134
135  test "warns for callbacks without impl and @impl has been set before" do
136    assert capture_err(fn ->
137             Code.eval_string("""
138             defmodule Kernel.ImplTest.ImplAttributes do
139               @behaviour Kernel.ImplTest.Behaviour
140               @behaviour Kernel.ImplTest.MacroBehaviour
141
142               @impl true
143               def foo(), do: :ok
144
145               defmacro bar(), do: :ok
146             end
147             """)
148           end) =~
149             "module attribute @impl was not set for macro bar/0 callback (specified in Kernel.ImplTest.MacroBehaviour)"
150  end
151
152  test "warns for callbacks without impl and @impl has been set after" do
153    assert capture_err(fn ->
154             Code.eval_string("""
155             defmodule Kernel.ImplTest.ImplAttributes do
156               @behaviour Kernel.ImplTest.Behaviour
157               @behaviour Kernel.ImplTest.MacroBehaviour
158
159               defmacro bar(), do: :ok
160
161               @impl true
162               def foo(), do: :ok
163             end
164             """)
165           end) =~
166             "module attribute @impl was not set for macro bar/0 callback (specified in Kernel.ImplTest.MacroBehaviour)"
167  end
168
169  test "warns when @impl is set on private function" do
170    assert capture_err(fn ->
171             Code.eval_string("""
172             defmodule Kernel.ImplTest.ImplAttributes do
173               @behaviour Kernel.ImplTest.Behaviour
174               @impl true
175               defp foo(), do: :ok
176             end
177             """)
178           end) =~
179             "function foo/0 is private, @impl attribute is always discarded for private functions/macros"
180  end
181
182  test "warns when @impl is set and no function" do
183    assert capture_err(fn ->
184             Code.eval_string("""
185             defmodule Kernel.ImplTest.ImplAttributes do
186               @behaviour Kernel.ImplTest.Behaviour
187               @impl true
188             end
189             """)
190           end) =~ "module attribute @impl was set but no definition follows it"
191  end
192
193  test "warns for @impl true and no behaviour" do
194    assert capture_err(fn ->
195             Code.eval_string("""
196             defmodule Kernel.ImplTest.ImplAttributes do
197               @impl true
198               def foo(), do: :ok
199             end
200             """)
201           end) =~ "got \"@impl true\" for function foo/0 but no behaviour was declared"
202  end
203
204  test "warns for @impl true with callback name not in behaviour" do
205    assert capture_err(fn ->
206             Code.eval_string("""
207             defmodule Kernel.ImplTest.ImplAttributes do
208               @behaviour Kernel.ImplTest.Behaviour
209               @impl true
210               def bar(), do: :ok
211             end
212             """)
213           end) =~
214             "got \"@impl true\" for function bar/0 but no behaviour specifies such callback"
215  end
216
217  test "warns for @impl true with macro callback name not in behaviour" do
218    assert capture_err(fn ->
219             Code.eval_string("""
220             defmodule Kernel.ImplTest.ImplAttributes do
221               @behaviour Kernel.ImplTest.MacroBehaviour
222               @impl true
223               defmacro foo(), do: :ok
224             end
225             """)
226           end) =~ "got \"@impl true\" for macro foo/0 but no behaviour specifies such callback"
227  end
228
229  test "warns for @impl true with callback kind not in behaviour" do
230    assert capture_err(fn ->
231             Code.eval_string("""
232             defmodule Kernel.ImplTest.ImplAttributes do
233               @behaviour Kernel.ImplTest.MacroBehaviour
234               @impl true
235               def foo(), do: :ok
236             end
237             """)
238           end) =~
239             "got \"@impl true\" for function foo/0 but no behaviour specifies such callback"
240  end
241
242  test "warns for @impl true with wrong arity" do
243    assert capture_err(fn ->
244             Code.eval_string("""
245             defmodule Kernel.ImplTest.ImplAttributes do
246               @behaviour Kernel.ImplTest.Behaviour
247               @impl true
248               def foo(arg), do: arg
249             end
250             """)
251           end) =~
252             "got \"@impl true\" for function foo/1 but no behaviour specifies such callback"
253  end
254
255  test "warns for @impl false and there are no callbacks" do
256    assert capture_err(fn ->
257             Code.eval_string("""
258             defmodule Kernel.ImplTest.ImplAttributes do
259               @impl false
260               def baz(term), do: term
261             end
262             """)
263           end) =~ "got \"@impl false\" for function baz/1 but no behaviour was declared"
264  end
265
266  test "warns for @impl false and it is a callback" do
267    assert capture_err(fn ->
268             Code.eval_string("""
269             defmodule Kernel.ImplTest.ImplAttributes do
270               @behaviour Kernel.ImplTest.Behaviour
271               @impl false
272               def foo(), do: :ok
273             end
274             """)
275           end) =~
276             "got \"@impl false\" for function foo/0 but it is a callback specified in Kernel.ImplTest.Behaviour"
277  end
278
279  test "warns for @impl module and no behaviour" do
280    assert capture_err(fn ->
281             Code.eval_string("""
282             defmodule Kernel.ImplTest.ImplAttributes do
283               @impl Kernel.ImplTest.Behaviour
284               def foo(), do: :ok
285             end
286             """)
287           end) =~
288             "got \"@impl Kernel.ImplTest.Behaviour\" for function foo/0 but no behaviour was declared"
289  end
290
291  test "warns for @impl module with callback name not in behaviour" do
292    assert capture_err(fn ->
293             Code.eval_string("""
294             defmodule Kernel.ImplTest.ImplAttributes do
295               @behaviour Kernel.ImplTest.Behaviour
296               @impl Kernel.ImplTest.Behaviour
297               def bar(), do: :ok
298             end
299             """)
300           end) =~
301             "got \"@impl Kernel.ImplTest.Behaviour\" for function bar/0 but this behaviour does not specify such callback"
302  end
303
304  test "warns for @impl module with macro callback name not in behaviour" do
305    assert capture_err(fn ->
306             Code.eval_string("""
307             defmodule Kernel.ImplTest.ImplAttributes do
308               @behaviour Kernel.ImplTest.MacroBehaviour
309               @impl Kernel.ImplTest.MacroBehaviour
310               defmacro foo(), do: :ok
311             end
312             """)
313           end) =~
314             "got \"@impl Kernel.ImplTest.MacroBehaviour\" for macro foo/0 but this behaviour does not specify such callback"
315  end
316
317  test "warns for @impl module with macro callback kind not in behaviour" do
318    assert capture_err(fn ->
319             Code.eval_string("""
320             defmodule Kernel.ImplTest.ImplAttributes do
321               @behaviour Kernel.ImplTest.MacroBehaviour
322               @impl Kernel.ImplTest.MacroBehaviour
323               def foo(), do: :ok
324             end
325             """)
326           end) =~
327             "got \"@impl Kernel.ImplTest.MacroBehaviour\" for function foo/0 but this behaviour does not specify such callback"
328  end
329
330  test "warns for @impl module and callback belongs to another known module" do
331    assert capture_err(fn ->
332             Code.eval_string("""
333             defmodule Kernel.ImplTest.ImplAttributes do
334               @behaviour Kernel.ImplTest.Behaviour
335               @behaviour Kernel.ImplTest.MacroBehaviour
336               @impl Kernel.ImplTest.Behaviour
337               def bar(), do: :ok
338             end
339             """)
340           end) =~
341             "got \"@impl Kernel.ImplTest.Behaviour\" for function bar/0 but this behaviour does not specify such callback"
342  end
343
344  test "warns for @impl module and callback belongs to another unknown module" do
345    assert capture_err(fn ->
346             Code.eval_string("""
347             defmodule Kernel.ImplTest.ImplAttributes do
348               @behaviour Kernel.ImplTest.Behaviour
349               @impl Kernel.ImplTest.MacroBehaviour
350               def bar(), do: :ok
351             end
352             """)
353           end) =~
354             "got \"@impl Kernel.ImplTest.MacroBehaviour\" for function bar/0 but this behaviour was not declared with @behaviour"
355  end
356
357  test "does not warn for @impl when the function with default conforms with several typespecs" do
358    Code.eval_string(~S"""
359    defmodule Kernel.ImplTest.ImplAttributes do
360      @behaviour Kernel.ImplTest.Behaviour
361      @behaviour Kernel.ImplTest.BehaviourWithArgument
362
363      @impl true
364      def foo(args \\ []), do: args
365    end
366    """)
367  end
368
369  test "does not warn for @impl when the function conforms to behaviour but has default value for arg" do
370    Code.eval_string(~S"""
371    defmodule Kernel.ImplTest.ImplAttributes do
372      @behaviour Kernel.ImplTest.BehaviourWithArgument
373
374      @impl true
375      def foo(args \\ []), do: args
376    end
377    """)
378  end
379
380  test "does not warn for @impl when the function conforms to behaviour but has additional trailing default args" do
381    Code.eval_string(~S"""
382    defmodule Kernel.ImplTest.ImplAttributes do
383      @behaviour Kernel.ImplTest.BehaviourWithArgument
384
385      @impl true
386      def foo(arg_1, _args \\ []), do: arg_1
387    end
388    """)
389  end
390
391  test "does not warn for @impl when the function conforms to behaviour but has additional leading default args" do
392    Code.eval_string(~S"""
393    defmodule Kernel.ImplTest.ImplAttributes do
394      @behaviour Kernel.ImplTest.BehaviourWithArgument
395
396      @impl true
397      def foo(_defaulted_arg \\ [], args), do: args
398    end
399    """)
400  end
401
402  test "does not warn for @impl when the function has more args than callback, but they're all defaulted" do
403    Code.eval_string(~S"""
404    defmodule Kernel.ImplTest.ImplAttributes do
405      @behaviour Kernel.ImplTest.BehaviourWithArgument
406
407      @impl true
408      def foo(args \\ [], _bar \\ []), do: args
409    end
410    """)
411  end
412
413  test "does not warn for @impl with defaults when the same function is defined multiple times" do
414    Code.eval_string(~S"""
415    defmodule Kernel.ImplTest.ImplAttributes do
416      @behaviour Kernel.ImplTest.BehaviourWithArgument
417      @behaviour Kernel.ImplTest.BehaviourWithThreeArguments
418
419      @impl Kernel.ImplTest.BehaviourWithArgument
420      def foo(_foo \\ [], _bar \\ []), do: :ok
421
422      @impl Kernel.ImplTest.BehaviourWithThreeArguments
423      def foo(_foo, _bar, _baz, _qux \\ []), do: :ok
424    end
425    """)
426  end
427
428  test "does not warn for no @impl when overriding callback" do
429    Code.eval_string(~S"""
430    defmodule Kernel.ImplTest.ImplAttributes do
431      @behaviour Kernel.ImplTest.Behaviour
432
433      def foo(), do: :overridable
434
435      defoverridable Kernel.ImplTest.Behaviour
436
437      def foo(), do: :overridden
438    end
439    """)
440  end
441
442  test "does not warn for overridable function missing @impl" do
443    Code.eval_string(~S"""
444    defmodule Kernel.ImplTest.ImplAttributes do
445      @behaviour Kernel.ImplTest.Behaviour
446
447      def foo(), do: :overridable
448
449      defoverridable Kernel.ImplTest.Behaviour
450
451      @impl Kernel.ImplTest.Behaviour
452      def foo(), do: :overridden
453    end
454    """)
455  end
456
457  test "warns correctly for missing @impl only for end-user implemented function" do
458    assert capture_err(fn ->
459             Code.eval_string("""
460             defmodule Kernel.ImplTest.ImplAttributes do
461               @behaviour Kernel.ImplTest.Behaviour
462               @behaviour Kernel.ImplTest.MacroBehaviour
463
464               def foo(), do: :overridable
465
466               defoverridable Kernel.ImplTest.Behaviour
467
468               def foo(), do: :overridden
469
470               @impl true
471               defmacro bar(), do: :overridden
472             end
473             """)
474           end) =~
475             "module attribute @impl was not set for function foo/0 callback (specified in Kernel.ImplTest.Behaviour)"
476  end
477
478  test "warns correctly for incorrect @impl in overridable callback" do
479    assert capture_err(fn ->
480             Code.eval_string("""
481             defmodule Kernel.ImplTest.ImplAttributes do
482               @behaviour Kernel.ImplTest.Behaviour
483               @behaviour Kernel.ImplTest.MacroBehaviour
484
485               @impl Kernel.ImplTest.MacroBehaviour
486               def foo(), do: :overridable
487
488               defoverridable Kernel.ImplTest.Behaviour
489
490               @impl Kernel.ImplTest.Behaviour
491               def foo(), do: :overridden
492             end
493             """)
494           end) =~
495             "got \"@impl Kernel.ImplTest.MacroBehaviour\" for function foo/0 but this behaviour does not specify such callback"
496  end
497
498  test "warns only for non-generated functions in non-generated @impl" do
499    message =
500      capture_err(fn ->
501        Code.eval_string("""
502        defmodule Kernel.ImplTest.ImplAttributes do
503          @behaviour Kernel.ImplTest.Behaviour
504          use Kernel.ImplTest.UseBehaviourWithoutImpl
505
506          @impl true
507          def bar_without_impl(), do: :overridden
508          def baz_without_impl(), do: :overridden
509
510          defdelegate foo(), to: __MODULE__, as: :baz
511          def baz(), do: :ok
512        end
513        """)
514      end)
515
516    assert message =~
517             "module attribute @impl was not set for function baz_without_impl/0 callback"
518
519    assert message =~
520             "module attribute @impl was not set for function foo/0 callback"
521
522    refute message =~ "foo_without_impl/0"
523  end
524
525  test "warns only for non-generated functions in non-generated @impl in protocols" do
526    message =
527      capture_err(fn ->
528        Code.eval_string("""
529        defimpl  Kernel.ImplTest.AProtocol, for: List do
530          @impl true
531          def foo(_list), do: :ok
532
533          defdelegate bar(list), to: __MODULE__, as: :baz
534          def baz(_list), do: :ok
535        end
536        """)
537      end)
538
539    assert message =~
540             "module attribute @impl was not set for function bar/1 callback"
541  end
542
543  test "warns only for generated functions in generated @impl" do
544    message =
545      capture_err(fn ->
546        Code.eval_string("""
547        defmodule Kernel.ImplTest.ImplAttributes do
548          use Kernel.ImplTest.UseBehaviourWithImpl
549          def baz_with_impl(), do: :overridden
550        end
551        """)
552      end)
553
554    assert message =~ "module attribute @impl was not set for function bar_with_impl/0 callback"
555    refute message =~ "foo_with_impl/0"
556  end
557
558  test "does not warn for overridable callback when using __before_compile__/1 hook" do
559    Code.eval_string(~S"""
560    defmodule BeforeCompile do
561      defmacro __before_compile__(_) do
562        quote do
563          @behaviour Kernel.ImplTest.Behaviour
564
565          def foo(), do: :overridable
566
567          defoverridable Kernel.ImplTest.Behaviour
568        end
569      end
570    end
571
572    defmodule Kernel.ImplTest.ImplAttributes do
573      @before_compile BeforeCompile
574      @behaviour Kernel.ImplTest.MacroBehaviour
575
576      defmacro bar(), do: :overridable
577
578      defoverridable Kernel.ImplTest.MacroBehaviour
579
580      @impl Kernel.ImplTest.MacroBehaviour
581      defmacro bar(), do: :overridden
582    end
583    """)
584  end
585end
586