Skip source occurrences under [@merlin.hide] (#1456) - #1465
Conversation
…1456) Honor [@merlin.hide] attribute to prevent collecting occurences from generated code with invalid non ghost locations (see ocaml#1456). The [@merlin.hide] attribute is used by Merlin to hide generated code. It is automatically added by ppx_deriving, and it hides a whole subtree (as opposed to a single node like ghost locations), making it a more reliable source of truth to detect generated code.
7280d7e to
5f86e2b
Compare
|
Thanks for the PR! I wonder why we need both I'll review the implementation soon. |
The current way of marking and then detecting ghost generated code in the OCaml pipeline is not very satisfying indeed, this patch does not really try to fix the situation, it just attempts to mimic Merlin's behaviour and follow ppxlib's check semantics to properly interprete the current output of derivers. Both markings have a different scope: marking a node as ghost only marks the node itself, whereas marking a node as In theory, only ghost should suffice, if ppx properly marked all generated code as ghost and produced consistent locations. Because it is more precise, ghost is also mode powerful, for instance you could have a ppx turning In practice some derivers generate code that is marked non ghost. They also reuse code locations from various sub-nodes of the input and don't follow proper nesting. Both are not on purpose and should be considered violations of the ppxlib good practices [1]. However, deriving will wrap the code they generate in a [1]: see the manual for ppxlib section on good practices |
panglesd
left a comment
There was a problem hiding this comment.
Looks good to me. (And thanks for the explanation!)
(If you want to improve the code, maybe you could split the "iterator that does not iterate on merlin.hide" part, and the "iterator that calls the Analysis module" part:
let rec restricted_iterator next_iterator =
let module_declaration iterator ({ Typedtree.md_attributes; _ } as md) =
if not_hidden md_attributes then next_iterator.module_declaration iterator md
in
...
let our_iterator = <the iterator that was defined before>
let final_iterator = restricted_iterator our_iteratorNot sure it is worth it, just a remark!)
Right, I'll look into this. Ideally, this code would eventually be shared with Merlin somehow, so splitting may help. |
Fixes #1456.
In rendered source pages, ppx-generated code can produce spurious source links, even on tokens that aren't identifiers at all. For instance on Core.Char the
typekeyword,tandcharare clickable and jump to unrelated definitions.The cause is that derivers sometimes emit code that borrows the source location of the type declaration it's derived from (or parts of it). Those locations are not marked ghost, so odoc collects occurrences for them, and emits links to their definitions. The result on the example above is even more confusing because these links are nested, triggering the auto-closing semantics of links by the browser.
In this PR, we honour the
[@merlin.hide]attribute: ppx_deriving wraps its output ininclude struct ... end [@@merlin.hide], so we prune those subtrees when collecting occurrences. AFAIU, this is the same signal Merlin and ppxlib use to skip generated code. We do this in addition to ghost locations (who mark single nodes, not subtrees), not as a replacement.Comes with a regression test for the raw mechanism, not the end-to-end behaviour using ppx deriver to limit dependencies. I initially wrote such an end-to-end test, but it was difficult to make it compatible with both plain OCaml and OxCaml CI environments so I dropped it. I did check manually that the patch works on both an OCaml switch (5.4) with ppx_deriving and an OxCaml switch (5.2.0) with ppx_jane.
Disclosure: this PR was made with AI assistance. The investigation and the chosen solution are mine; the regression test was written by Claude and the fix itself is a mix. I'm also new to this codebase, so there may well be a more obvious or idiomatic fix I've missed, happy to be pointed at one.
I don't think this is urgent, I did this as an exercise to dive into the codebase, but since source display is enabled on
ocaml.org, it's probably worth doing if my fix does not look too stupid.