fix(haskell): retain arrowless IO-action entry points in the #1312 guard (#2934) - #2968
Merged
Merged
Conversation
…ard (#2934) The #1312 point-free guard in `_slice_by_indentation` used "no `->`/`⊸` arrow in the type signature" as its entire test for a point-free VALUE binding, and dropped every match that failed it. That discards the canonical zero-arg Haskell entry point `entry :: IO ()` / `main :: IO a`: an arrowless signature that nonetheless opens an executable block under its own name (docs/func_start_rule_contract.md, #2856), producing zero FunctionNodes for files whose only public surface is an arrowless action. Narrow the guard: an arrowless signature is skipped only when its return-type head is NOT `IO`. `_haskell_arrowless_signature_is_action` strips a leading `forall ... .` quantifier and any `... =>` constraint (mirroring `_count_haskell_type_arrows`'s last-`=>` rule) and matches the outermost type-application head as a whole token, so pure values (`Text`, `Int`, `IORef Int`) still read as CAFs -- `IORef` is never mistaken for `IO`. Broader action monads (`ReaderT ... IO ()`, `m ()` under a `MonadIO` constraint) are deliberately left to a follow-up. Mirror the same relaxation on the ground-truth side (`tree_sitter_accuracy_audit.py::_get_node_name`): a bind whose unwrapped signature type is a bare `IO ...` application is kept alongside arrow chains, so the accuracy audit does not book the newly-retained entry points as extra_functions false positives. Golden crucible (both legs) is unchanged: language-crucible v1.2.0 has no top-level arrowless IO action -- every col-0 `IO` signature there already carries an arrow, and the arrowless col-0 signatures are all pure values. rosetta stays 46/46 for the same reason (its haskell `IORef Int` globals stay values). Behaviour is covered by new slicer-level unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXAjGXaZKr2Ge6jjZshPRD
Contributor
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.
Closes #2934.
Problem
The #1312 point-free guard in
_slice_by_indentation(detector.py) skipped every top-level Haskell signature with no arrow token, treating "no->/⊸" as its whole definition of "point-free value binding". That drops the canonical zero-arg entry pointentry :: IO ()/main :: IO a— an arrowless signature that does open an executable block under its own name (docs/func_start_rule_contract.md, #2856) — emitting zeroFunctionNodes.Fix (single count/slicer-layer change, per #2812 rule 4)
detector.py): an arrowless signature is skipped only when its return-type head is notIO. New helper_haskell_arrowless_signature_is_actionstrips a leadingforall … .and any… =>constraint (mirroring_count_haskell_type_arrows) and whole-token-matches the outermost type-application head, so pure values (Text,Int,IORef Int) still read as CAFs andIORefis never mistaken forIO.tree_sitter_accuracy_audit.py::_get_node_name): mirrored so a bind whose unwrapped signature type is a bareIO …application is kept alongside arrow chains — otherwise the accuracy audit would book the newly-retained entry points asextra_functionsfalse positives.ReaderT … IO (),m ()underMonadIO) are deliberately left to a follow-up; only a bareIOhead is retained.Verification
test_haskell.py:entry :: IO (),main :: IO a,forall a. IO a,Monad m => IO ()all extracted;IORef IntandReaderT Env IO ()stay rejected. Engine and ground-truth mirror agree on all cases.IOsignature already carries an arrow; the arrowless col-0 signatures are all pure values), so the golden masters legitimately do not move.IORef Intglobals correctly stay values.ruff_audit/mypy_audit/dead_key_audit--ci: no new findings. Fullpytest tests/: 8739 passed (the only failures are the pre-existingtest_security_auditor.pyxgboost-env gap, identical onorigin/main).No paired keyword-rosetta corpus rebless is owed — counts do not move on either pinned corpus.
🤖 Generated with Claude Code