Weird errors in non-matching clause in anonymous function

If you have the following code:

defmodule Fn do
  def f1(g, x) do
    fun = fn
      # We'll mutate the function patterns
      value when is_integer(value) -> g.(value)
    end

    fun.(x)
  end

  def f2(x) do
    fun = fn
      # We'll mutate the function patterns
      value when is_integer(value) -> value
    end

    fun.(x)
  end
end

then you would have:

iex(49)> Fn.f1(fn x -> x end, :invalid) 
** (CaseClauseError) no case clause matching: {:invalid}
    (darwin 0.1.0) lib/darwin.ex:28: Fn.f1/2
iex(49)> Fn.f2(:invalid)               
** (FunctionClauseError) no function clause matching in Fn.f2/1

    The following arguments were given to Fn.f2/1:

        # 1
        :invalid

    Attempted function clauses (showing 1 out of 1):

        def f2(x)

    (darwin 0.1.0) lib/darwin.ex:37: Fn.f2/1

In one case you get a FunctionClauseError and in another case you get a CaseClauseError. Is this supposed behaviour? I came up with this when testing my mutation testing framework, so it’s definitely a weird corner case, but I wonder if the Elixir core team would like to look into it.

2 Likes

Which Elixir version do you use?
I tried to run the code using 1.12 and in both cases I have the error that is similar to Fn.f2(:invalid)

1 Like
$ elixir --version

Erlang/OTP 24 [erts-12.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
Elixir 1.12.0 (compiled with Erlang/OTP 24)

I think this is because the fun inside f1 is immediately invoked with arguments that are in scope so it is transformed into a case expression by the compiler. I know such optimization exists but I cannot find the article or changelog where it is described.

See Immediately-called funs optimization (looking for docs) .

Well, I tested with OTP 23 for the first time. Seems like this was introduced in OTP 24

1 Like

@fuelen it seems like it has never worked
Test: Weird errors in non-matching clause in anonymous function · eksperimental/debug_me@2ce3e3b · GitHub?

Sorry, i misread your comment @fuelen
here are the tests that verify it is indeed only in OTP 24
Test exceptions · eksperimental/debug_me@422239a · GitHub

1 Like

@tmbb I think you could report your findings to OTP.

Here’s a similar issue that has been reported a few days ago
Inconsistent error messages when errors are optimized away by the compiler · Issue #5440 · erlang/otp · GitHub

The Erlang code:

-module(foo).
-export([f1/2, f2/1]).

f1(G, X) ->
    Fun =
        fun(Value) when is_integer(Value) ->
               G(Value)
        end,
    Fun(X).

f2(X) ->
    Fun =
        fun(Value) when is_integer(Value) ->
               Value
        end,
    Fun(X).

The console outputs:

Erlang/OTP 24 [erts-12.1.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Eshell V12.1.2  (abort with ^G)
1> c('foo').                        
{ok,foo}

2> foo:f1(invalid, fun(X) -> X end).
** exception error: no case clause matching {#Fun<erl_eval.44.65746770>}
     in function  foo:f1/2 (foo.erl, line 31)

3> foo:f2(invalid).                 
** exception error: no function clause matching foo:f2(invalid) (foo.erl, line 38)


Erlang/OTP 23 [erts-11.2.2.8] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
1> c('foo').

2> foo:f1(invalid, fun(X) -> X end).
** exception error: no function clause matching foo:'-f1/2-fun-0-'(#Fun<erl_eval.44.79398840>) (foo.erl, line 31)

3> foo:f2(invalid).                 
** exception error: no function clause matching foo:'-f2/1-fun-0-'(invalid) (foo.erl, line 38)

Sorry to ask you here @lud but I cannot see to reply to the linked post. May I know what is decompiler you say you use?

It has less features than the popular one as it only decompiles to erlang module. But it writes to stdout instead of creating files :slight_smile:

1 Like

Thanks, I’ll translate it into Erlang and report it

1 Like

I already translated it

1 Like

Hi @tmbb
Did you get the chance to report it?
If not I can do it.