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