Skip to content

Instantly share code, notes, and snippets.

Last active October 12, 2022 22:36
Show Gist options
  • Save OliverMadine/96927f88b6e5e7890e5179559089166c to your computer and use it in GitHub Desktop.
Save OliverMadine/96927f88b6e5e7890e5179559089166c to your computer and use it in GitHub Desktop.
Haskell Language Server - Rename Plugin

Haskell Language Server - Rename Plugin

See the Code!

The main pull request for the project is #2108.

Project Summary

This Google Summer of Code project aimed to implement a symbol renaming plugin (as specified by the Language Server Protocol) for the Haskell Language Server (HLS). The motivation was that renaming is a fundamental feature of an integrated development environment that the HLS did not provide.

Motivating issues:

The deliverables of the plugin outlined in the initial proposal were to:

  • perform workspace-wide renames;
  • work for all identifiers, including types and type constructors;
  • work for qualified identifier;
  • have thorough unit tests.

These were all achieved!


Reference finding

Using HieDb allowed a relatively simple method of finding the correct name reference locations. However, naively replacing identifiers at these locations would be incorrect since Haskell can be written in a layout-sensitive manner. Therefore, a more robust method of rewriting the source code was required.

Rewriting the source code

Throughout the project, many different solutions to this task were considered. Initially, using Retrie was suggested. However, when drafting this solution, several limitations became apparent.

Therefore, replacing the relevant identifier nodes using SYB to transform the abstract syntax tree (AST) was a more suitable solution.

Reprinting the AST

ExactPrint for GHC was used to write the source code and create the workspace edit required to respond to the client.

The HieDb Limitation

The use of HieDb in the HLS currently has limitations since the database is populated when each project is indexed, which only happens when the editor loads a project file. This issue also impacts other features, such as reference finding and definition finding.

The work towards resolving this has been started: #2009, #7498.

Due to this issue, the rename plugin currently has limited support in multi-project workspaces and is disabled by default.

Update: April 2022

While the work on full multi-component support continues, we have enabled the rename plugin for use across files within the same module. We also introduced a configuration option for experimental cross-module renaming.

Update: July 2022

Haskell's "Record Puns" language extension was not considered during the initial solution. This language extension allows a single identifier to refer to both a record field and variable binding. As a result, when renaming an identifier, we must either remove the pun (i.e. rewrite the source code to use an explicit binding) or rename all punned identifiers.

We opt for the latter. The implementation can be found at #3013. To find references to all punned identifiers, we find the transitive closure of the direct references. This is found in a single pass since each direct reference can only pun a maximum of one additional identifier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment