-
Notifications
You must be signed in to change notification settings - Fork 35
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
Recover position information for undef/not_exported type errors #384
Recover position information for undef/not_exported type errors #384
Conversation
This catches undef/not_exported type errors on a level where position information is available from function specs and rethrows with this information supplied in the exception. In order to source this position information from specs it has to be stored in #env.fenv, so it is NOT removed from specs in `create_fenv()` anymore - this has to be done as required after fetching specs from #env.fenv.
@zuiderkwast Do you have any remarks about this one? |
[ {{Name, NArgs}, lists:map(fun typelib:remove_pos/1, | ||
absform:normalize_function_type_list(Types))} | ||
[ {{Name, NArgs}, absform:normalize_function_type_list(Types)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we keep the positions in fenv but we remove them in tenv. Don't we need them in tenv too for the same reasons?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used here:
- https://github.com/erszcz/Gradualizer/blob/d78e28ea5213212f4518908d83ab26b409751d06/src/typechecker.erl#L775
- https://github.com/erszcz/Gradualizer/blob/d78e28ea5213212f4518908d83ab26b409751d06/src/typechecker.erl#L783
And set here:
https://github.com/erszcz/Gradualizer/blob/d78e28ea5213212f4518908d83ab26b409751d06/src/typechecker.erl#L3985
Which means that we use position information from the currently checked function spec when it's missing on a type to be thrown with an error. This means we throw exactly the location where the error occurs. I don't see the need to keep position information in tenv
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, we only check functions, not types. Good point. It makes sense.
I wonder where we normalize records from other modules. I can't find it. I mean normalize_rec({type, P, record, [{atom, _, Name} | Fields]}, ...
where P has a filename in it, meaning it's a record in another module which comes from an expanded remote type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder where we normalize records from other modules.
I think we can only access them through gradualizer_db
and there's code like this there:
Gradualizer/src/gradualizer_db.erl
Lines 506 to 535 in 5be0f94
%% Normalize Type Defs | |
%% ------------------- | |
%% | |
%% Extracts and normalizes type definitions from a list of forms. | |
%% | |
%% Normalise record definitions into types (i.e. typed record definitions). | |
%% That is, if there is no typed definition of a record among the | |
%% forms, create one from the untyped one and normalize so that they | |
%% all have a default value. | |
%% | |
%% Location in field names and types are set to zero to allow comparison using | |
%% equality and pattern matching. This is not done for the default value (which | |
%% is an expression, not a type). | |
-spec extract_record_defs(Forms :: [tuple()]) -> Typedefs :: [{atom(), [type()]}]. | |
extract_record_defs([{attribute, L, record, {Name, _UntypedFields}}, | |
{attribute, L, type, {{record, Name}, Fields, []}} | | |
Rest]) -> | |
%% This representation is only used in OTP < 19 | |
extract_record_defs([{attribute, L, record, {Name, Fields}} | Rest]); | |
extract_record_defs([{attribute, _L, record, {Name, Fields}} | Rest]) -> | |
TypedFields = [gradualizer_lib:remove_pos_typed_record_field( | |
absform:normalize_record_field(Field)) | |
|| Field <- Fields], | |
R = {Name, TypedFields}, | |
[R | extract_record_defs(Rest)]; | |
extract_record_defs([_ | Rest]) -> | |
%% Skip forms that are not record definitions | |
extract_record_defs(Rest); | |
extract_record_defs([]) -> | |
[]. |
I think this is a good approach in general. It's much better than the separate pass. |
get_bounded_fun_type_list(Name, Arity, Env, P) -> | ||
case maps:find({Name, Arity}, Env#env.fenv) of | ||
{ok, Types} -> | ||
typelib:remove_pos(Types); | ||
%% TODO: https://github.com/josefs/Gradualizer/issues/388 | ||
{ok, Types} when is_list(Types) -> | ||
[ typelib:remove_pos(Ty) || Ty <- Types ]; | ||
{ok, Type} -> | ||
typelib:remove_pos(Type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name of this function is unfortunate, since it doesn't always return a list of bounded fun types. Sometimes it just returns the type any()
.
For functions without a spec, any()
is set in create_fenv/2
. We could replace this default type with a type on the form fun((any(), ...., any()) -> any())
of the correct arity. (This is already done in gradualizer_db:make_function_type/1
which could be moved/exported/reused.)
This is another take at #379 which supersedes #380. The implementation is significantly simpler. It also covers the
undef, user_type
cases which were still not handled by #380.Current master:
This PR (some lines reordered for a simpler diff):
Diff: