erlangelixirtrace

elixir/erlang stacktrace. Correct function call interpretation


How to interpret a function attribute in FunctionClauseError

 %FunctionClauseError{
   module: MyApp.MyModule,
   function: :"-load_data/4-inlined-0-",
   arity: 1,
   kind: nil,
   args: nil,
   clauses: nil
}

The term -load_data/4-inlined-0- points to something inside the load_data/4 function.
How to interpret this part: inlined-0? This part of the environment seems to be undocumented. Can anyone elaborate on this subject?


Solution

  • The -load_data/4-inlined-0- function name occurs when an anonymous function is inlined by the compiler, and the call to the anonymous function fails with a function clause error. It is not the name of an actual function; it was added to fix this issue, titled "Confusing case_clause exception instead of function_clause". I don't think this is documented anywhere.


    This requires a little history lesson. Let's consider this piece of code:

    -module(foo).
    
    -export([foo/0]).
    
    foo() ->
        F = fun(X) when is_atom(X) -> ok end,
        F(0).
    

    That is, we create an anonymous function, and then we call it in a way that causes a function clause error.

    Inlining of anonymous functions was introduced in Erlang/OTP 24. Thus, when running this in Erlang 23, we can see that the function name in the stack trace is the usual name for anonymous functions, with -fun-:

    > catch foo:foo().
    {'EXIT',{function_clause,[{foo,'-foo/0-fun-0-',
                                   [0],
                                   [{file,"foo.erl"},{line,6}]},
                              {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,684}]},
                              {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,437}]},
                              {shell,exprs,7,[{file,"shell.erl"},{line,686}]},
                              {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
                              {shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]}}
    

    In Erlang 24, the function is inlined by the compiler, and gets rewritten to a case expression. Therefore, we see a case_clause exception, and the stack trace suggests that the error occurred in the foo function itself:

    > catch foo:foo().
    {'EXIT',{{case_clause,{0}},
             [{foo,foo,0,[{file,"foo.erl"},{line,6}]},
              {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,689}]},
              {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,434}]},
              {shell,exprs,7,[{file,"shell.erl"},{line,686}]},
              {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
              {shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]}}
    

    Of course, that's confusing for a programmer who goes looking for a case expression to find the cause of the error, and doesn't find any. That's why Erlang 25 makes sure to use an actual function_clause exception, to reflect the fact that the error occurred at a place where there was a function call in the code. It uses -inlined- instead of -fun- since the anonymous function is no longer present in the compiled module:

    > catch foo:foo().
    {'EXIT',{function_clause,[{foo,'-foo/0-inlined-0-',
                                   [0],
                                   [{file,"foo.erl"},{line,6}]},
                              {erl_eval,do_apply,7,[{file,"erl_eval.erl"},{line,748}]},
                              {erl_eval,expr,6,[{file,"erl_eval.erl"},{line,480}]},
                              {shell,exprs,7,[{file,"shell.erl"},{line,691}]},
                              {shell,eval_exprs,7,[{file,"shell.erl"},{line,647}]},
                              {shell,eval_loop,3,[{file,"shell.erl"},{line,632}]}]}}