-
Notifications
You must be signed in to change notification settings - Fork 90
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
Combine metadata for completion items instead of choosing arbitrarily. #1940
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.
Sorry, I proposed this solution, but I just realize it's not correct, because you don't really know if you should actually conflate all foo
s or if they are logically different.
For example:
let C = { foo | Number | doc "Some documentation" } in
{
foo | String,
bar = {
f<CURSOR>
} | C
}
In this example, the LSP incorrectly suggests that the contracts for foo
are String, Number
. But the foo
from the enclosing scope and the one coming from C
shouldn't actually be merged.
Could we have a way of computing if the external foo
is actually shadowed by the inner foo
(as in the example above), in which case the correct behavior is to show only the inner one? I guess this was the original behavior, right? The difficult part is to know when they aren't actually shadowing each other but are "at the same level" and are going to be merged.
I thought that another dumber solution could be to just special case the pattern of the original issue (which is common, a bit like "declaring a function argument"), but even that is incorrect:
let C = { foo | Number | doc "Some documentation" } in
{
bar = {
foo,
f<CURSOR>
}
} | C
This solution would suggest that foo
must be Number
, while once, again, the two foo
s are different and the inner one is unconstrained.
Hm, I think the issue isn't just the scope, but the syntactic position of the thing we're completing, right? I mean, if you're trying to complete the value instead of the field name like this: let C = { foo | Number | doc "Some documentation", .. } in
{
foo | String,
bar = {
baz = f<CURSOR>
} | C
} then you want the string one, right? The current implementation does take scoping into account (for example, in I think the mechanism for this should be somewhat similar to #1932, where we need to do filtering of completion candidates for field names. Also, the current behavior just takes the metadata from an arbitrary completion candidate. I think that combining all valid completion candidates is the right behavior; we just need some more work to filter out invalid ones. As an example where you'd want to merge multiple candidates:
(Ok, so in this example we actually want the docs from |
Ah, this is a good point, but I think still rather a separate one. At any location in a source file, there is a local environment where many In your last example we should indeed merge them - the issue with merging documentation is unfortunate, but kinda beside the point. In fact we decided to just arbitrarily get rid of one docstring in Then, it turns out we only want to keep one suggestion, because of shadowing. So it's true that in practice this is equivalent to decide "should I get rid of this other suggestion or merge it with the current, most local one?". In the end we're probably saying the same thing in a different way 🙂 About the fact that you might want a different rationale for completion when defining a field or using a field, I think it boils down to the official Nickel scoping versus NLS completion "scoping": stuff brought into "scope" by contracts and types aren't considered to be into scope by Nickel (yet, but some people would actually expect it, see #1915 (comment) and the whole discussion). So maybe just filtering out stuff coming from types/contracts for completion in the right-hand side of ab At a distance it looks like the more involved part is deciding what to merge and what to drop. I imagine you'd need some sort of "level" or similar metadata attached to bindings in the environment that help you decide which declaration takes precedence, and which declarations are at the same level and should be merged. Then, when doing |
@yannham I think this is ready for another look. I basically just separated out an extra case, so now we have three cases: completing record paths like There's one (pre-existing) bug that I know of, in the that incomplete parsing heuristics misclassify (edit: this will also fix #1932) |
} | ||
grouped | ||
.entry(item.label.clone()) | ||
.and_modify(|old| *old = Combine::combine(old.clone(), item.clone())) |
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.
Could be nice to write documentation for this function explaining that it combines the metadata of identical items, because I fear it's not obvious from the function's name anymore
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 refactored it a little to make it more clear (and also added documentation)
@jneem Oh and I forgot: could we also have the test case that I included in my comment above: let C = { foo | Number | doc "Some documentation" } in
{
foo | String,
bar = {
f<CURSOR>
} | C
} I'm not entirely convinced the issue is solved, because I don't know where |
Co-authored-by: Yann Hamdaoui <yann.hamdaoui@tweag.io>
The
|
Fixes #1933