Optimize replaceAllTypeSynonyms with per-node TypeFlags#11
Merged
kozak merged 5 commits intorestaumaticfrom Apr 23, 2026
Merged
Optimize replaceAllTypeSynonyms with per-node TypeFlags#11kozak merged 5 commits intorestaumaticfrom
kozak merged 5 commits intorestaumaticfrom
Conversation
Add a TypeFlags field to every Type constructor that caches structural properties of the subtree. Pattern synonyms auto-compute flags on construction and ignore them on matching, so existing code is unchanged. Three flags are tracked: - tfHasWildcards: subtree contains TypeWildcard nodes - tfHasUnscopedForAlls: subtree contains ForAll without SkolemScope - tfSynonymsFree: subtree has been fully synonym-expanded This enables short-circuiting in three hot traversals: - replaceAllTypeSynonyms skips types already marked synonym-free - replaceTypeWildcards skips types with no wildcards - introduceSkolemScope skips types with no unscoped ForAlls The synonym expansion also uses a custom single-pass traversal that both expands synonyms and marks all output nodes as synonym-free, so subsequent calls on the same type or any subtree return in O(1). Profile results (pr-admin, 1758 modules): - replaceAllTypeSynonyms'.go: 16.9% → 0.1% - introduceSkolemScope: 2.4% → 0.0% - replaceTypeWildcards: 2.2% → 0.0% - Full build wall time: ~72s → ~65s (~10% faster) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These utility functions were defined during development but ended up unused — the custom traversal in Synonyms.hs uses raw constructors directly instead. Weeder flagged them as dead code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When short-circuiting based on tfSynonymsFree, tfHasWildcards, or tfHasUnscopedForAlls, assert that a fresh scan of the type confirms the flag's claim. Catches any bug where a flag is set on a type that actually contains the nodes it claims to exclude. Uses Control.Exception.assert, which is active in --fast builds (no -O) but compiled away in optimized builds, so there is no production cost. All 1340 tests pass with assertions active, confirming the invariants hold across the full test corpus. The key invariant: any construction via pattern synonyms calls combineFlags, which masks to structural flags only (0x03), so tfSynonymsFree is always cleared on reconstructed internal nodes. This means substituteType (which uses everywhereOnTypes and thus pattern synonyms) cannot sneak an unexpanded synonym past the flag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a concrete example showing how substitution can introduce a new synonym application at a parent node even when both children are synonym-free in isolation. This explains why we conservatively clear the flag on every reconstruction instead of propagating it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kozak
commented
Apr 16, 2026
| -- Substitution: u -> TypeConstructor SomeAlias -- standalone, fine | ||
| -- After substitution: TypeApp (TypeConstructor SomeAlias) someArg | ||
| -- -- now a fully-applied synonym that needs expansion! | ||
| -- @ |
Author
There was a problem hiding this comment.
@zyla does this + assertions address your substitution concern ?
Derive the mask from tfHasWildcards and tfHasUnscopedForAlls so that adding a new structural flag automatically includes it in propagation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
Summary
TypeFlagsfield to everyTypeconstructor, cached per-node, tracking structural properties of the subtreereplaceAllTypeSynonyms,replaceTypeWildcards,introduceSkolemScope, andsubstituteTypewhen flags indicate the traversal is unnecessaryFlags tracked
tfHasWildcardsTypeWildcardreplaceTypeWildcardstfHasUnscopedForAllsForAllwithoutSkolemScopeintroduceSkolemScopetfSynonymsFreereplaceAllTypeSynonymstfHasUnknownsTUnknownsubstituteTypeProfile results (pr-admin, 1758 modules)
replaceAllTypeSynonyms'.gointroduceSkolemScopereplaceTypeWildcardseverywhereOnTypes.goTest plan
stack test --fast)Control.Exception.assert) verify flag correctness in--fastbuilds🤖 Generated with Claude Code