Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash with Uncaught error: function_clause #530

Open
ilya-klyuchnikov opened this issue Apr 27, 2023 · 6 comments
Open

Crash with Uncaught error: function_clause #530

ilya-klyuchnikov opened this issue Apr 27, 2023 · 6 comments
Labels

Comments

@ilya-klyuchnikov
Copy link
Collaborator

Trying to run it on our code base:

===> Uncaught error: function_clause
===> Stack trace to the error location:
[{gradualizer_lib,pick_value,
  [{type,0,term,[]},
   {env,
     ..., omitting environment
    false}],
  [{file,
    "gradualizer/src/gradualizer_lib.erl"},
   {line,162}]},
 {gradualizer_lib,'-pick_values/2-lc$^0/1-0-',2,
  [{file,
    "gradualizer/src/gradualizer_lib.erl"},
   {line,156}]},
 {typechecker,check_clauses_intersection_throw_if_seen,5,
  [{file,
    "gradualizer/src/typechecker.erl"},
   {line,3892}]},
 {typechecker,check_clauses_intersection,5,
  [{file,
    "gradualizer/src/typechecker.erl"},
   {line,3830}]},
 {typechecker,check_clauses_fun,3,
  [{file,
    "gradualizer/src/typechecker.erl"},
   {line,3744}]},
 {typechecker,type_check_function,2,
  [{file,
    "gradualizer/src/typechecker.erl"},
   {line,4647}]},
 {typechecker,type_check_form,5,
  [{file,
    "gradualizer/src/typechecker.erl"},
   {line,5687}]},
 {typechecker,'-type_check_form_with_timeout/5-fun-0-',6,
  [{file,
    "gradualizer/src/typechecker.erl"},
   {line,5645}]}]

Looking into gradualizer_lib:pick_value - it doesn't expect term() type.

@erszcz
Copy link
Collaborator

erszcz commented Apr 27, 2023

Thanks, @ilya-klyuchnikov!

Can you share a (possibly minimised) snippet on which this happens? Generally, a denormalised type like term() should not reach pick_value because of these checks, specifically all_refinable:

case exhaustiveness_checking(Env) andalso
all_refinable(ArgTys, Env) andalso
no_clause_has_guards(Clauses) andalso
some_type_not_none(RefinedArgTys)

But, apparently, something's falling through the cracks 🤔

@erszcz erszcz added the bug label Apr 27, 2023
@erszcz
Copy link
Collaborator

erszcz commented Apr 27, 2023

For more context, simple examples like these don't trigger this:

$ cat test.erl
-module(test).

-spec f(term()) -> ok.
f(T) ->
    case T of
        1 -> ok
    end.

-spec g({a | b, term()}) -> ok.
g(T) ->
    case T of
        {a, 1} -> ok
    end.

And while a trivial patch like this should fix the problem, I'd be hesitant to merge it without understanding what expression leads to the crash in the first place.

@ilya-klyuchnikov
Copy link
Collaborator Author

I am trying to run gradualizer on a fairly large project. How do I find out what it analyzes when it fails?

@erszcz
Copy link
Collaborator

erszcz commented Apr 28, 2023

Please add

{gradualizer_opts, [
    verbose
]}.

to rebar.config or, if using the escript, --verbose to the list of command line arguments. Verbose logging should result in a log like this (but with lots of output for a large codebase):

Checking module rebar3_example
Spawning async task...
Checking function c/1
Pushing {clauses_controls,true}
6:1: Checking clause :: boolean()
7:5: Checking that X :: boolean()
Exhaustiveness checking: true
Popping clauses controls
Task returned from function c/1 with []

which should make it possible to pinpoint where the problem is. It might help to run Gradualizer app by app in a big umbrella repo, to limit the scope.

This use case is certainly not polished for large code bases yet, but with the available resources we have to pick our battles.

@ilya-klyuchnikov
Copy link
Collaborator Author

A minimized repro:

-module(gradualizer_repro).

-compile([export_all, nowarn_export_all]).

-spec to_i(term()) -> integer().
to_i(_) -> error(example).

-spec to_b(term()) -> boolean().
to_b(_) -> error(example).

-spec to_x
    (term(), to_i | i, Default) -> integer() | Default;
    (term(), to_b | b, Default) -> boolean() | Default.
to_x(Value, _, Default) when Value =:= Default ->
    Default;
to_x(Value, to_i, _) -> to_i(Value);
to_x(Value, i, _) -> to_i(Value);
to_x(Value, to_b, _) -> to_b(Value);
to_x(Value, b, _) -> to_b(Value).

@erszcz
Copy link
Collaborator

erszcz commented May 8, 2023

Thanks for the example, @ilya-klyuchnikov! It's most likely the case that spurious types reach gradualizer:pick_value/2 due to changes introduced in #461, which introduces another call to pick_value than the one that's guarded with exhaustiveness checks. I'll have to investigate further how to prevent this from happening.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants