-
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
Detect undefined remote types in a separate pass #380
Detect undefined remote types in a separate pass #380
Conversation
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.
Seems you like the idea of a separate pass. :-)
I think it is slightly more complex than this though. What about remote types that expand to other remote types? There may even be circular dependencies between remote types.
E.g. here is a call to gradualizer_lib:get_type_definition
, which expands types and calls remove pos internally.
Instead of a completely separate pass, how about checking for undefined remote types (and undefined user types and records) just after importing a type or spec from gradualizer_lib and just before removing the pos from these types? That code is currently inside gradualizer_lib:get_type_definition but we can split it out again if you prefer.
- fix
typelib:remove_pos
to actually remove pos from all remote_type elements
Doesn't it already? I.e. it removes the line and column, but it keeps the filename annotation in user types and records, which is good because it these are different in different modules.
Well, it was relatively easy to pull off, at the very least to check if it solves the problem :)
Good point!
I was thinking of incorporating this check in the existing pass, but it wasn't clear to me where we could do it. I'll try your suggestion out.
It doesn't remove pos from Lines 119 to 121 in b216594
which do have it according to the trace from #379:
|
Good point! It should, so that two identical remote types will compare as identical. |
ae15ad6
to
d95fd9e
Compare
It seems to me this case is covered by tests in If that's what you had in mind, I think we could live with this solution in a separate pass, as there are plenty of other bugs waiting to be fixed, and this is good enough to make CI green. WDYT? |
Ok, I'm taking that back, it's not tested yet. The one I described above is testing reference to a remote type via a spec, not via remote type expansion. |
5901bec
to
29e59ab
Compare
Ok, with the latest changes we also explicitly handle that 👍 |
@zuiderkwast Any feedback on this one? I think it's ready for merging.
We handle that.
I considered that, but I don't think that anytime before actually calling |
check_undefined_types(Forms) -> | ||
gradualizer_lib:fold_ast(fun check_remote_type/2, [], Forms). |
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.
Correct me if I'm wrong, but I can't see that we handle the following:
- remote types which expand to structures containing user types or records which are undefined
remote types which expand to structures containing more remote typesHandled- user types and records belong to a remote module (i.e. they come from expanding a remote type) which refer to other remote types, user types and records
- cycles of the above
Some of it could be solved if gradualizer_db would perform linting when parsing a module, but I don't think it does (yet). (Btw, that's another topic: Unify the gradualizer_db and typechecker code to parse and extracts stuff from a parse tree.)
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.
What would the difference between:
remote types which expand to structures containing user types or records which are undefined
and
user types and records belong to a remote module (i.e. they come from expanding a remote type) which refer to other remote types, user types and records
be? Could you give an example?
I've added a number of tests - could you confirm if they capture what you had in mind above? Apparently, there was no need to change the check itself.
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.
remote types which expand to structures containing user types or records which are undefined
-module(foo).
-export_type([t/0]).
-type t() :: {undef(), #undef{}}.
user types and records belong to a remote module (i.e. they come from expanding a remote type) which refer to other remote types, user types and records
-module(bar).
-export_type([t/0]).
-type t() :: [w() | #r{}].
-type w() :: {foo:undef()}.
-record(r, {f :: foo:undef()}).
If these are covered by tests, then I suspect the line and column are either zero (because I can't see that they're covered by the new pass, but only by the regular typechecking pass) or if they're non-zero, maybe line and column refer to the location in foo or bar where the error is rather than the module being type checked. We can probably report the line and column where foo:t() or bar:t() is used in the module being checked rather than where the error is in the foo and bar modules.
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.
You were right, this work so far only improved one particular situation when the reference was to a remote type which was not exported. All the other reports were still based on type representation fetched from gradualizer_db
where position information was already lost, so the reported lines were 0.
50007a8
to
6835d36
Compare
Remove position info when fetching types from gradualizer_db not when importing them. This means the caller can decide if they want pos info available or removed. This, in turn, means the check_undefined_types pass can fetch types with this info present, while the main typechecker pass can discard position info for easy type comparison.
96df3b5
to
81da18b
Compare
"Store position info in gradualizer_db" changes where type information is removed in Results from current master:
Results with "Store position info in gradualizer_db":
Diff of the above:
As can be seen above we can still improve on:
and there's 1 test failing, but the direction is good 👍 |
I just added When we check |
I realise that and I think it was a good move. I'm just thinking it might be needed in slightly different places in
Hmmm, so do you think we should provide this position information after we catch the thrown |
Yes, that's what I mean, sort of. It doesn't necessarily have to be a catch. If we pass around the current location, we can throw a tuple with the right location when we get case gradualizer_db:get_record_type(Module, RecName) of
not_found ->
throw({undef, record, Anno, {Module, RecName}});
%% ... where Anno is within the currently checked module If this is done in the separate pass, we probably also need to pass around a set to prevent cycles. Then, we can hopefully remove all the |
Superseded by #384. |
Obviously. I looked if we could pass the spec down from Using the function's own position info would be a bit lame, as it's not the same as that of the spec (it might be completely off, there's no requirement on specs actually being close to the functions they specify, see https://github.com/erlang/otp/blob/60497f1b78d9fc7f02e16b8dba035548abbf4e9f/lib/stdlib/src/otp_internal.hrl#L32-L36 for an example). So the spec is only looked up in |
This tries fixing #379 by introducing a separate pass over the AST.
Remaining tasks:
typelib:remove_pos
to actually remove pos from allremote_type
elements