feat(haskell): add module-aware extraction and resolution#1337
Open
michelguillaume wants to merge 11 commits into
Open
feat(haskell): add module-aware extraction and resolution#1337michelguillaume wants to merge 11 commits into
michelguillaume wants to merge 11 commits into
Conversation
Closes three of the gaps flagged after the basic Haskell extractor landed:
- instance C T where ... emits an implements reference from T to C
(local-receiver lookup; orphan instances where T lives in another file
remain a known gap).
- data T ... deriving (C1, C2) and the newtype analogue emit one
implements reference per derived class.
- class (Eq a, Show a) => Ord a where ... emits extends references from
Ord to Eq and Show. Wired through core extractInheritance with a new
Haskell context: case alongside the existing extends_clause /
superclass / base_class_clause arms — no impact on other languages.
- Record-syntax fields (data Foo = Foo { x :: Int }) become field nodes
scoped to the parent enum. Haskell record selectors live at the type
level (x :: Foo -> Int), so scoping at the enum, not the constructor,
is the right model.
Also adds a top-of-file doc block in haskell.ts that enumerates what the
module extracts, what it does NOT extract yet, and how the module is
tested (vitest cases + verify-extraction.mjs on the 4 pinned repos +
agent A/B benchmark).
Verified on 4 pinned repos:
xmonad (v0.18.1) — 799 nodes, 1630 edges, 57 fields, 12 implements
shellcheck (v0.11.0) — 2034 / 4137 / 143 fields
pandoc (3.9.0.2) — 16220 / 35985 / 1119 fields / 67 implements / 3 extends
purescript (c4a35b34) — 8185 / 15757 / 578 fields / 277 implements
5 new vitest cases (now 13 Haskell tests; 828 tests overall pass).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends Haskell support with extraction fixes uncovered during an audit
cycle. All changes are additive — 843 tests pass, 0 fails. Real-repo call
coverage roughly doubles to triples across the four pinned repos.
Higher-order call synthesis
`map area xs` now emits a call edge from the caller to `area`, even
though `area` is passed as data. The HOF_NAMES allowlist covers ~30
function-first Foldable / Traversable / list-utility combinators.
Data-first variants (forM_, for_, forM, for) are deliberately excluded
— their signature is `t a -> (a -> f b) -> …`, so bridging them would
emit the data list as a bogus callee.
Where-clause calls
New `extraBodyFields` hook in LanguageExtractor; used in the core
extractFunction / extractMethod. Haskell sets it to ['binds'] so the
framework also walks the `where`-clause sibling field, not just
`match`. Without this, ~1,900 where-clauses across the four pinned
repos were dropping their call edges. Shellcheck alone went from
1,822 → 7,552 calls (+314%) from this fix.
Top-level `bind` extraction
Tree-sitter parses parameterless top-level bindings (`main = do …`,
`pi = 3.14`, `constVal = 42`) as `bind` nodes, NOT `function`. Without
handling, `main` had no node at all — a huge miss for any Haskell
program. Now emitted as `function` kind.
Operator function definitions
`x === y = …` (infix style) and `(===) x y = …` (prefix style) had no
`name:` field — only an `infix` child. Now extracted with the operator
wrapped in parens (e.g. `function:(===)`).
GADT constructors
`data Term a where IntT :: Int -> Term Int` uses a `gadt_constructors`
wrapper instead of `data_constructors`, and each `gadt_constructor`
carries its `name:` directly (no nested `record`). Both shapes are
now walked.
Bare-variable do-statements
`main = do { hi; bye }` — the bare variable in do-statement position
is the action being run. Tree-sitter wraps it as
`statement → exp → variable` (no `apply` node), so no call edge was
emitted. Now `variable`/`constructor` inside a single-child `exp`
emits a call to the caller.
Scope-stack regression guards
Custom function / method / operator body walks in visitNode now
pushScope before visiting body, so calls inside attribute to the
correct enclosing node. Previously instance-method and operator-
function calls were leaking to the file scope.
Real-repo call edges (before → after these fixes):
xmonad 613 → 1,015 (+66%)
shellcheck 1,822 → 7,552 (+314%)
pandoc 15,693 → 31,242 (+99%)
purescript 4,852 → 14,492 (+199%)
Tests
28 Haskell vitest cases (was 13). 843 total tests pass. Also a 29-case
audit probe suite with positive and negative coverage (lambdas /
composed-fns / sections / data-args don't bridge; constructors,
multi-arg HOFs, case/if/do-block calls, where-bound nested fns,
do-block bare statements, prefix and infix operator defs, GADT
constructors, newtype non-stdlib deriving, instance method
receivers all do).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Builds on the Haskell support introduced by @ichxorya in colbymchenry#395, whose four commits remain intact in this branch history.
michelguillaume
marked this pull request as ready for review
July 18, 2026 17:07
|
hey @michelguillaume thank you for picking up the flag! you may want to keep an eye on merge conflicts and have them resolved proactively. |
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.
Credit and lineage
This PR continues and supersedes #395 by @ichxorya. The branch intentionally preserves the four original commits (
f441a09,c0ff295,1c52378, and4abb846) with their original authorship, then merges currentmainand layers the extraction, resolution, performance, sync-safety, and regression work on top.Thank you to @ichxorya for the grammar integration, initial extractor, HOF/
where/do/operator/GADT work, and original evaluation data.What changed
Extraction
.hsand packages the pinned, MIT-licensed ABI-14tree-sitter-haskellWASM with a reproducible rebuild/health-check recipe.^.,==.,<-., or<.>.do, qualifiedmdo,rec, lambda/case/view patterns, recursivelet/where, list comprehensions, pattern guards, record dot/update syntax, and lexical scopes for same-named helpers.>>=,=<<,>>,<*>,<**>,<|>,*>,<*,<$>,<&>,$>,<$). Continuations remain function references rather than fabricated direct calls.Resolution
ImportQualifiedPost, aliases, explicit lists, operators,Type(..), selected children,hiding, export lists, and restricted/aliased/wildcard re-exports.DuplicateRecordFieldsand propagates ambiguity/restrictions through re-export chains instead of choosing an arbitrary field.OverloadedRecordDotreceivers by parent type and deliberately leaves unknown/ambiguous projections unresolved.Module/Path.hssuffix and importer proximity, while declining ties.Incremental correctness and performance
Validation
npm run buildnpm pack --dry-run: 775 package entries; Haskell JS, WASM, schema migration, and grammar license included.git diff --checkpassed.Deliberate limits
This remains a static syntax/scope graph, not GHC. It models monadic/applicative syntax, lexical binders, constraints, and import/export visibility, but does not run dictionary elaboration, infer concrete monad stacks, or perform compiler-grade typeclass/instance selection. Cabal component graphs, CPP/Template Haskell expansion,
.lhs, and.hscare not modeled yet; genuinely ambiguous static references are intentionally left unresolved.The fork keeps a daily/manual upstream merge workflow that validates build, tests, and package contents before updating this branch while the upstream PR remains open.