- Project: Qualified Imports and Alias Resolution in Liquid Haskell
- Contributor: Xavier Góngora
- Mentor: Facundo Domínguez @ Tweag
- Organization: Haskell.org
The project aimed to address a long-standing issue in the way Liquid Haskell (LH) resolves type and predicate aliases. Prior to this work, alias resolution relied on a flat namespace, which caused conflicts when multiple dependencies defined the same alias. Specifically, aliases from transitive dependencies were imported automatically, with no mechanism to prevent clashes. This forced users into awkward workarounds when writing module specifications.
The main goals were:
- Integrate Haskell’s existing qualification mechanisms (e.g., import directives) into LH alias resolution.
- Refactor relevant parts of the codebase to make alias resolution more maintainable and consistent.
- Provide clear, documented guidelines on alias import and export.
I undertook a systematic review of LH’s alias resolution code paths, drawing on both LH’s internals and the underlying GHC API. Fully grasping these details proved valuable: the solution aligned with existing mechanisms, achieving the feature mostly through reuse rather than additional machinery.
The main contributions of this project are:
- Restructuring alias resolution. I rewrote the logic by which LH collects and applies aliases. Previously, aliases were indexed only by unqualified names. When identical names appeared across transitive dependencies, resolution followed an accidental collection order. Though duplicate-name checks eventually short-circuited with an error, the process was opaque and confusing. I updated the representation of refinement type aliases to use a structured type from a previous refactoring, applying it within existing name resolution environments. This enabled qualified identifiers, making conflicts resolvable.
- Establishing scope and visibility rules. I examined the fact that module imports in LH pull in all aliases from transitive dependencies, since no export/import mechanism for aliases exists. Implementing explicit export lists was out of scope, so I explored trade-offs. On the importing side, Haskell’s qualification directives could handle ambiguity. On the exporting side, I proposed a set of default behaviors and guidelines to document them clearly.
- Delivering the implementation. The final implementation added about 210 lines of code, supported by extensive refactoring, testing, and documentation. This required studying ~2,150 lines of relevant code within a 25,000-line codebase. The core of the work is in two merged pull requests (#2550 and #2566), both including detailed source documentation and tests.
- Integrating predicate aliases with logical resolution.
Predicate aliases were difficult to reason about because they were conflated
with Haskell entities lifted through
inlineanddefineannotations. I restructured the implementation to keep the uniform expansion mechanism for lifted definitions in logical expressions, while separating predicate aliases into existing logic name resolution environments. This clarified their role and improved consistency. - Communicating results through a blog post. A post on Tweag's Engineering Blog introduces type and predicate alias annotations and their use cases, walks through key design decisions, and outlines the implementation approach.
At the end of GSoC, alias resolution in LH is more predictable and transparent. Ambiguous occurrences now produce clear error messages, pointing to the conflicting symbol and prompting the user to disambiguate. This is a significant improvement over the prior behavior, where resolution failed with a multiple-definitions error—even if the conflicting alias was unused.
The changes have been merged into LH through the two main pull requests. Documentation and tests now support both maintainers and future contributors in understanding the design and usage of alias resolution.
Open questions and potential future work include:
- Export/import mechanism for aliases. A complete solution would introduce explicit export lists for aliases, similar to Haskell’s treatment of functions, data types, and type classes. This would give module authors precise control. Implementing such a mechanism exceeded the scope of this project.
- Improvement of other annotations. Inline and define annotations from dependencies are still conflated during resolution, and the latter are redundantly collected twice. Separating the names associated with these annotations would make the implementation more consistent.
- User-facing ergonomics. While qualification directives now resolve ambiguities, further refinements could improve usability—for example, tailored suggestions or automated fixes.
- Maintenance.
As LH continues to evolve, further adjustments may be needed to keep alias resolution consistent with future changes in the codebase.
The following contributions were merged into the Liquid Haskell repository:
- Qualified type alias support: Introduced a dedicated resolution environment for type aliases along with improved error reporting. Includes documentation, tests, and import/export guidelines.
- Qualified predicate alias support: Extended logic name resolution environments to include predicate aliases, ensuring consistency. Includes documentation, tests, and import/export guidelines.
Together, these PRs constitute the core of the implementation. Additional PRs submitted during the GSoC coding period include #2561 and #2570.
Working on Liquid Haskell exposed me to complex abstractions and subtle design challenges, providing lessons that go beyond the code itself.
One of the first hurdles was understanding the codebase. As a 25,000-line GHC plugin, LH required careful study of its relevant components, cross-referencing with the GHC API, and patient exploration to build a working mental model of the system.
Another challenge arose from the ambiguity of predicate aliases. Their
conflation with lifted entities through inline and define annotations
obscured their behavior. Resolving this required adjusting the expansion
mechanism in incremental steps—addressing both alias flavors in turn—while
separating predicate aliases into the logic environments,
clarifying their role and revealing the intricacy of overlapping abstractions.
Finally, balancing scope and feasibility was crucial. Within the GSoC timeframe, I prioritized pragmatic solutions: while explicit export lists for aliases would have been ideal, I implemented an approach that maintained effectiveness without overcomplicating the project.
On a personal level, I gained valuable experience with the GHC API and the language extensions used throughout LH. It also reinforced the value of reusing existing mechanisms rather than introducing new ones, whenever doing so benefits the clarity and conciseness of the code.
I want to acknowledge my mentor, Facundo Domínguez, for his generous and insightful guidance, which kept me in a creative flow throughout the project. Working on Liquid Haskell was both challenging and rewarding, and I leave with knowledge that I will continue to apply in contributions to the Haskell ecosystem.
- Qualified type alias support: Pull Request #2550
- Qualified predicate alias support: Pull Request #2566
- Refinement type alias import/export guidelines
- Refactoring of a Haskell Codebase by Facundo Domínguez
- Liquid Haskell repository
- Liquid Haskell documentation