Skip to content

Fix locate in let punnings#2066

Open
panglesd wants to merge 8 commits into
ocaml:mainfrom
panglesd:locate-in-punning
Open

Fix locate in let punnings#2066
panglesd wants to merge 8 commits into
ocaml:mainfrom
panglesd:locate-in-punning

Conversation

@panglesd
Copy link
Copy Markdown
Collaborator

This fixes an issue with the locate query reported in #1796.

(Sorry this is quite verbose for such a small PR but it is also here to help me be corrected if I have assumed something wrong)

In a punned let binding, both the pattern and the expression have the same location. None of them is ghost. I think that somehow breaks the assumption that "sibling nodes have disjoint locations".

During a locate query, the first thing is to load the environment at the point of the cursor. The lookup of "nodes at cursor position" will suppose the invariant mentioned above to return a list all nodes the cursor is in (of which we take the first one, the "deepest").

Since two siblings (the pattern and expression) have the same location, the order in which they are considered matter: when the first one is found, we never look at the other siblings, and "enter" it to continue the search.

In the current implementation, patterns are looked first, and we end up with the environment at the pattern to do our "locate". And so, the unhelpful "Already at definition point".

This PR fixes this by folding let operators in a different order, making the expressions side be entered first.

Having the behaviour depend on the order of the fold is slightly brittle, but I think this PR is still fine to solve #1796 without refactoring a huge part to solve a bigger underlying issue ("siblings might have the same location"). But I'm new to the codebase so I might miss something!
(I'm happy to implement a better fix, but then I'd like to discuss with more experienced Merlin disciple first)

panglesd added a commit to panglesd/merlin that referenced this pull request May 11, 2026
Copy link
Copy Markdown
Collaborator

@xvw xvw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat!

voodoos pushed a commit to panglesd/merlin that referenced this pull request May 22, 2026
@voodoos voodoos force-pushed the locate-in-punning branch from 22e7942 to 31b48aa Compare May 22, 2026 13:53
Copy link
Copy Markdown
Collaborator

@voodoos voodoos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed feeling because this makes the behavior of occurrences (and maybe other queries? we should think about it!) less intuitive. I do think locate is the most important of them so this is an improvement, just one that is not entirely satisfying.

Comment thread tests/test-dirs/punning.t Outdated
> x
> EOF

FIXME: Should locate to the `x` in `f x`, not say that we are at the definition
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a fixme anymore, tight ?

Suggested change
FIXME: Should locate to the `x` in `f x`, not say that we are at the definition
Should locate to the `x` in `f x`, not say that we are at the definition

Comment thread tests/test-dirs/punning.t
@voodoos
Copy link
Copy Markdown
Collaborator

voodoos commented May 26, 2026

@panglesd found a much more annoying regression, this also changes the type returned by type-enclosing on the punned value. He his going to try a better fix.

panglesd added a commit to panglesd/merlin that referenced this pull request May 26, 2026
@panglesd panglesd force-pushed the locate-in-punning branch from 31b48aa to 0a38fec Compare May 26, 2026 12:25
@panglesd
Copy link
Copy Markdown
Collaborator Author

Thanks for the review! Yes, this is annoying especially for type-enclosing: We want the type of the pattern to be displayed.

I've changed the PR in the following way:

  • Added a test for type enclosing before any fix
  • Kept the first implementation to show it broke the added test
  • Revert the first implementation
  • Made another implementation, where one can pass a "tie breaker" function to use when two nodes have the same location.

This way, we can select the "expression" node in case of locate query, and the pattern node otherwise. The "oneliner" fix is not anymore a oneliner, but I think it is more correct and hopefully the tie-breaking mechanism can be useful in other contexts.

Copy link
Copy Markdown
Collaborator

@voodoos voodoos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that looks much better !

The next step is to check which other places could benefit from this (aka record labels and labelled arguments).

Comment thread src/kernel/mbrowse.ml Outdated

let compare_locations pos l1 l2 =
module Tie_breaker = struct
type tie_break = [ `Prefer_first | `Prefer_second ]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏓

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we really need polymorphic variant here?
Is that just for avoiding repeating the path Mbrowse.Tie_breaking... ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I don't think we need it, and the path is probably not needed anyway with type-directed disambiguation of constructors. I think I started with it before I defined the type and it stuck.
I will turn it into regular variants

Comment thread src/kernel/mbrowse.mli
module Tie_breaker : sig
(** Some nodes in the typedtree may share the same, non-ghost, location, while
being different. One example of this is let-punning:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For science, can we have more than two overlapped nodes ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I... don't think so, unless you take ppx rewriting into account.

But maybe it can, I don't know. For instance, I think defining an object defines internally: an object, a type and a class type? Whose locations might coincide? (completely outside of my comfort zone).

Anyway, the tie-breaker compares nodes two-by-two, carrying the winner to the next match, until there is no challenger left. That is less expressive than taking all overlapping nodes, but I think it is expressive enough.

Comment thread src/analysis/locate.ml Outdated
Comment on lines +848 to +851
( Context.inspect_browse_tree
~disambiguate:Mbrowse.Tie_breaker.prefer_expression ~cursor:pos lid
[ browse ],
is_label )
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: maybe this would be more readable by defining disambiguate before the match expression ? Or even the complete fst of the pair.

panglesd added 8 commits May 29, 2026 11:33
Include let punnings, named argument punnings and fields punning
Change the order in which we fold the let operators, to give priority to the
expression in the "locate" query
Changing the selected node for a better locate experience, made a worse
experience for other queries such as type-enclosing.
and fix a typo in the docstring
@panglesd panglesd force-pushed the locate-in-punning branch from 27cb84d to 8bbda06 Compare May 29, 2026 09:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants