Skip to content
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

OverloadedRecordDot support - Type Awareness / Completion #2732

Open
Dessix opened this issue Feb 22, 2022 · 10 comments
Open

OverloadedRecordDot support - Type Awareness / Completion #2732

Dessix opened this issue Feb 22, 2022 · 10 comments
Labels
type: enhancement New feature or request

Comments

@Dessix
Copy link

Dessix commented Feb 22, 2022

Given the following (GHC 9.2+) snippet:

{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NoFieldSelectors #-}

data MyRecord = MyRecord
  { a :: String
  , b :: Integer
  } deriving (Eq, Show)

x :: MyRecord
x = MyRecord { a = "Hello", b = 12 }

y = x.a

z = x.b

HLS now understands (without record-dot-preprocessor!) that y is a String and z is an Integer, seen in both above-line hints and on hover.

Unfortunately, it has no type information available when hovering over the a in x.a or the b in x.b. Additionally, it lacks the ability to suggest- when entering a dot after a record type- the fields accessible on that type.

Describe the solution you'd like

The ability to "dot into" types is both a small form of IDE-driven API exploration and a way to easily manage larger applications without needing a Haddock window on the side at all times.

Pressing '.' after a type (or manually opening auto-completion with the cursor directly following a dot following a type) should summon auto-completion with the context narrowed to the prior type's fields.

For a step further, but one worth considering when designing a solution, a useful addition would be allowing automatic import of the type information required if necessary, similar to when accepting a completion of an unimported trait function in rust-analyzer. Documentation on the HasField requirements is available here (HasField constraints) and here (OverloadedRecordDot). NoFieldSelectors may complicate this functionality.

Describe alternatives you've considered

I've built my glue-code in Rust instead, so far, to avoid having to manage large numbers of one-off record types in Haskell, but I'd love to unify my codebase, and Record Dot Syntax has been the selling point that would get me there, assuming the presence of IDE support. It appears that 9.2.1 support is now functioning, but dot-accesses appear opaque to the language server.

@michaelpj
Copy link
Collaborator

Worth noting that we can probably do this for records easily enough, but I'm not sure how easy it would be to handle arbitrary "fields" created by user-provided HasField instances.

@ocharles
Copy link
Contributor

@michaelpj Maybe it's possible to find all instances of HasField in scope for a particular type? That would handle both record fields and user defined fields, I believe.

@michaelpj
Copy link
Collaborator

(Just to clarify, I'm talking about completions, hovers should be fine in all cases.)

Well, typeclass instances can apply non-obviously, can depend on what's in scope from local type signatures, etc. In the limit, that amounts to "run the typechecker in some special way", I think.

So in increasing order of difficulty:

  1. Actual record fields.
  2. "Simple" instances of HasField with literal Symbols and no superclass constraints
  3. Arbitrary HasField instances.

@Dessix
Copy link
Author

Dessix commented Feb 23, 2022

That implementation order would cover the largest portion of use cases with just part 1, while covering niche cases like Haskell-Methods in part 3. I think "superrecord"-style types and such fall in part 2?

Regardless- part 1 would bring support to the most obvious case of record dot syntax, and also covers a lot of scenarios like the ReaderIO / RIO pattern. I'd suggest splitting this into three implementation/feature parts, if it helps close on part 1 more easily.

@pepeiborra pepeiborra pinned this issue Feb 26, 2022
@Dessix Dessix mentioned this issue May 28, 2022
32 tasks
@michaelpj
Copy link
Collaborator

This is going to be tackled as part of GSoC by @coltenwebb.

@coltenwebb
Copy link
Contributor

coltenwebb commented Jul 4, 2022

I'll be addressing these features in separate PRs, and track them here.

@coltenwebb
Copy link
Contributor

coltenwebb commented Jul 21, 2022

I've started brainstorming how to do the record field completions, so I'll write my thoughts here:

For the record field completions, it looks like I might be able to extend this to get the name of the record that is dotted. From there, I should be able to calculate the type by searching the HieAST for the text that's dotted. My question then is how to get a fresh HieAST while calculating the completions, and whether computing the types on the fly will be fast enough. Perhaps there is a way to pass the HieAST to this, or to access it with LspM. Thoughts/feedback on this would be appreciated.

Edit: It turns out that types get inferred top-down, so using HieAst directly will cause completions not to show for some edge cases. For example in y = "hello" ++ (x.c), the x.c will have [Char] type no matter what c actually should be, so completions for x.c. will break. A standalone record will always have the correct type it seems, so x. completions will still work.

If that works, I need a way to get the fields for the record. I don't think HieDB provides a way to read the record fields. One workaround could be querying with hiedb for functions of type MyRecord -> * but this doesn't seem robust, especially since NoFieldSelectors would ruin it. Thoughts would be appreciated here too.

@guibou
Copy link
Collaborator

guibou commented Aug 27, 2024

A few note about the status of hover and error message recording OverloadedRecordDot:

  • Since Record Dot Hover Types #3016 we got support for hover on record dot, but:

    • unfortunately, not for hover on record dot accessor. Said otherwise x.foo, we can hover on foo and get informations. However map (.foo) someX, hovering on foo does not give any information
    • The lsp is able to send some informations about identical identifiers (in my editor, when I have my cursor over a name, it highlights all the usage of that binding). There is nothing alike for the field accessor.

See how when I have my point on x, all the other reference to x are highlighted:

image

  • (Maybe requiring another issue), when I use a selector for a type for which the constructors are not imported, the error from GHC is something like: Could not deduce ‘HasField "patients" MyType and most of the time the solution is as simple as importing the constructors for this type (e.g. adding MyType(..) to the import list). However there is no code action for that, which is painful. Could it be as simple as matching this kind of error and adding a suggestion to add the import? Is this something which should be upstreamed to GHC so they add a suggestion we can use?

@michaelpj
Copy link
Collaborator

Is this something which should be upstreamed to GHC so they add a suggestion we can use?

I think that would be good. We very much rely on GHC to drive our import suggestions. Since HasField is a magic typeclass, it would be very helpful if GHC could suggest imports that could be added to make that constraint solvable!

@guibou
Copy link
Collaborator

guibou commented Sep 1, 2024

There are already an issue opened on GHC side related to suggesting import or using another name in case of typo: https://gitlab.haskell.org/ghc/ghc/-/issues/18776

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants