From b4e270354bfbbf230bb2da3633e05f3a2e73b65e Mon Sep 17 00:00:00 2001 From: Eksperimental Date: Mon, 12 Apr 2021 07:21:58 +0000 Subject: [PATCH] Fix optional callback blame hint (#10893) Previously it was wrongly hinting that the behaviour Behaviour expected a private or undefined function to be present, when the callback was optional. --- lib/elixir/lib/exception.ex | 4 +- lib/elixir/test/elixir/exception_test.exs | 45 +++++++++++++++++------ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/elixir/lib/exception.ex b/lib/elixir/lib/exception.ex index 9ba4a4eab3..0a71fb8a5b 100644 --- a/lib/elixir/lib/exception.ex +++ b/lib/elixir/lib/exception.ex @@ -1064,7 +1064,9 @@ defmodule UndefinedFunctionError do end defp expects_callback?(behaviour, function, arity) do - callbacks = behaviour.behaviour_info(:callbacks) + callbacks = + behaviour.behaviour_info(:callbacks) -- behaviour.behaviour_info(:optional_callbacks) + Enum.member?(callbacks, {function, arity}) end diff --git a/lib/elixir/test/elixir/exception_test.exs b/lib/elixir/test/elixir/exception_test.exs index d05082c230..67643f031a 100644 --- a/lib/elixir/test/elixir/exception_test.exs +++ b/lib/elixir/test/elixir/exception_test.exs @@ -3,6 +3,10 @@ Code.require_file("test_helper.exs", __DIR__) defmodule ExceptionTest do use ExUnit.Case, async: true + defp capture_err(fun) do + ExUnit.CaptureIO.capture_io(:stderr, fun) + end + doctest Exception test "raising preserves the stacktrace" do @@ -15,7 +19,7 @@ defmodule ExceptionTest do file = __ENV__.file |> Path.relative_to_cwd() |> String.to_charlist() - assert {__MODULE__, :"test raising preserves the stacktrace", _, [file: ^file, line: 11]} = + assert {__MODULE__, :"test raising preserves the stacktrace", _, [file: ^file, line: 15]} = stacktrace end @@ -485,19 +489,38 @@ defmodule ExceptionTest do "you intend to invoke this macro" end - test "annotates undefined function clause error with callback hints hints" do - defmodule Behaviour do - @callback callback() :: :ok - @optional_callbacks callback: 0 - end + test "annotates undefined function clause error with callback hints" do + capture_err(fn -> + Code.eval_string(""" + defmodule Behaviour do + @callback callback() :: :ok + end - defmodule Implementation do - @behaviour Behaviour - end + defmodule Implementation do + @behaviour Behaviour + end + """) + end) assert blame_message(Implementation, & &1.callback()) == - "function ExceptionTest.Implementation.callback/0 is undefined or private" <> - ", but the behaviour ExceptionTest.Behaviour expects it to be present" + "function Implementation.callback/0 is undefined or private" <> + ", but the behaviour Behaviour expects it to be present" + end + + test "does not annotate undefined function clause error with callback hints when callback is optional" do + defmodule BehaviourWithOptional do + @callback callback() :: :ok + @callback optional() :: :ok + @optional_callbacks callback: 0, optional: 0 + end + + defmodule ImplementationWithOptional do + @behaviour BehaviourWithOptional + def callback(), do: :ok + end + + assert blame_message(ImplementationWithOptional, & &1.optional()) == + "function ExceptionTest.ImplementationWithOptional.optional/0 is undefined or private" end if :erlang.system_info(:otp_release) >= '23' do