ghcide: type of selected expression via haskell/hoverRange custom method (#709) - #4999
Open
HCHogan wants to merge 2 commits into
Open
ghcide: type of selected expression via haskell/hoverRange custom method (#709)#4999HCHogan wants to merge 2 commits into
HCHogan wants to merge 2 commits into
Conversation
…haskell#709) Adds a custom LSP request `haskell/hoverRange`, modelled after rust-analyzer's "Hover Range" extension: given a text document and a range (typically the current selection), it returns a standard `Hover | null` describing the smallest expression that fully contains the range, so clients can show the type of an arbitrary selected expression. Implementation notes: * The range lookup generalises the existing hover machinery: `pointCommand` becomes a special case of the new `rangeCommand` (`selectSmallestContaining` with a non-zero-width span), and `atPoint`/`getAtPoint` gain range-aware variants. Position-based hover behaviour is unchanged. * The HieAST deliberately omits the types of intermediate expression nodes such as applications (see `skipDesugaring` in GHC.Iface.Ext.Ast), so a selection usually lands on a node with no hover information. In that case we recover the type from the typechecked source instead: `exprTypeAtSpan` finds the smallest `LHsExpr GhcTc` containing the span (SYB over `tcg_binds`, pruning subtrees whose spans do not contain the target) and computes its type with `lhsExprType`. This is per-request, proportional to a single expression, and follows the same `tcg_binds` traversal pattern already used by the type lenses and explicit-record-fields plugins. The fallback only applies to files of interest; if GHC ever records these types in the HieAST it can be removed without changing the interface. * A custom method is used because `lsp-types` cannot parse a `Range` in the `position` field of `textDocument/hover`, so rust-analyzer's wire format is not implementable as-is (microsoft/language-server-protocol#377 is still open). Clients should treat `MethodNotFound` as "not supported".
Restructures HoverRangeTests into a table-driven suite covering: * basic: sub-expression selection, misaligned selection snapping to the enclosing expression (with the reported range checked), literals, selections exactly covering an identifier (rich hover), operators, multi-line selections, if-branches (typechecked-source fallback with exact range), string literals, empty range behaving like positional hover, and null responses for selections spanning declarations or outside any expression. * GADTs/DataKinds: partially applied GADT constructors report their instantiated type (including promoted type-level indices), nested constructor applications, and existentials hiding the index.
HCHogan
marked this pull request as ready for review
July 4, 2026 05:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #709 (type information for arbitrary selected expressions), one of the two features called out in #3715.
What
A custom LSP request
haskell/hoverRange, with the same semantics as rust-analyzer's "Hover Range" extension:The response is a standard
Hover | nullfor the smallest expression that fully contains the range (the returnedHover.rangeis that expression's span, so clients can highlight it). An empty range behaves exactly liketextDocument/hover; selections that align with a single identifier get the existing rich hover (docs, definition site, evidence).A custom method rather than rust-analyzer's wire format (a
RangeinHoverParams.position), becauselsp-typesparses hover params strictly — aRangethere is rejected before reaching HLS. Upstream LSP support (microsoft/language-server-protocol#377) is still open. Clients should treatMethodNotFoundas "not supported"; advertisingcapabilities.experimentallike rust-analyzer does would need a small hook inlspfirst (possible follow-up).How
Two layers:
HieAST:
pointCommandis generalised torangeCommand—selectSmallestContainingwith a non-zero-width span. Position hover is the degenerate case; its behaviour is unchanged (all existing hover tests pass).Typechecked-source fallback: the HieAST deliberately omits types of intermediate expression nodes (
skipDesugaringinGHC.Iface.Ext.AstskipsHsApp/OpApp/if/case/...), e.g. forcombined = negate 3 + 7thenegate 3node hastypes=[]. So when the selected node yields nothing,exprTypeAtSpanfinds the smallestLHsExpr GhcTccontaining the span (SYB overtcg_binds, pruning subtrees whose spans don't contain the target) and computes its type withlhsExprType. Sametcg_bindstraversal pattern as TypeLenses / explicit-record-fields; cost is per-request and proportional to one expression.Trade-offs (stated upfront)
TypeCheckresult, i.e. works for files of interest only; forHieFromDiskyou get whatever the AST has. If GHC ever records these types in the HieAST, the fallback can be deleted with no interface change.Q Exp). Splice interiors report the expansion's type.Testing
ghcide-test/exe/HoverRangeTests.hs: exact selection, multi-line selection, misaligned selection, empty range behaving like hover, null for spans without an enclosing expression / spanning declarations.docs/features.md.