effekt-lang / effekt

A language with lexical effect handlers and lightweight effect polymorphism
https://effekt-lang.org
MIT License
321 stars 23 forks source link

LSP code actions for pattern-matching #472

Open jiribenes opened 4 months ago

jiribenes commented 4 months ago

Follow-up to #463 & #470 It would be nice to offer code actions for pattern-matching, specifically:

1. a quick-fix for a missing case in a pattern match

After #470, the compiler suggests: Non-exhaustive pattern matching, missing case Fork(Fork(_, _, Fork(_, _, _)), _, _)), which really means that the user might want to add <indent>case Fork(Fork(_, _, Fork(_, _, _)), _, _)) => <>.

What we'd really want here is a quick-fix like in Scala 3: https://github.com/scala/scala3/blob/e0c030ccd44089e70629b59d76962c1dfc8dbb16/compiler/src/dotty/tools/dotc/reporting/messages.scala#L871-L891 which automatically inserts the missing case(s).

In order to do that, we need to pair a Message (a LSP Diagnostic) with a corresponding TreeAction (a LSP CodeAction). As far as I can tell, we would need to dynamically register CodeActions based on the Diagnostics we currently have, as it's the job of the "CodeActionContext" to store the relevant diagnostics. This would probably require some changes in Kiama.

I don't know whether we should also name the patterns (we definitely can, since we force the user to name the fields) or whether we should just leave ignore patterns (_) everywhere. 🤷‍♂️

2. exhaustive autocompletion

Building up on 1., if we know the type of a variable, then we should also be able to get all of the constructors for that type. It would be nice to somehow -- either autocomplete on x.match like in IntelliJ, or perhaps by just right-clicking on an identifier -- be able to generate the whole exhaustive match.

x.match // [Autocomplete: Exhaustive pattern-matching]

// would get transformed to
x match {
  case Cons(head, tail) => <>
  case Nil() => <>
}

3. case-of-case split on a pattern

Building up on 2., it would be absolutely amazing to be able to split by right-clicking on a variable pattern, similarly to Agda/Idris, creating a nested pattern match. This might be quite difficult to get right as it's a little bit more complicated to perform the editor changes (replace identifier, make a hole, copy whole line k-times, fill all holes with all k possible constructors).


x match {
  case Cons(head, tail) => head
  //              ^^^^ right click here and select "case split"
  case Nil() => 42
}

// would get transformed to
x match {
  case Cons(head, Cons(head2, tail2)) => head // !
  case Cons(head, Nil()) => head              // !
  case Nil() => 42
}
jiribenes commented 1 month ago

Prior art:

jiribenes commented 1 month ago

Related: It would be also nice to have a similar code action for:

  1. handlers: on try { ... } with MyInterface { }, have a code action to insert the individual operations; similalrly if there are some missing
  2. objects: on new MyInterface { }, same thing: an action to insert the (missing) methods