Skip to content

perf: prefilter walker-rule type queries by declared type - #583

Merged
mattiamanzati merged 1 commit into
mainfrom
feat/walker-rule-effect-prefilter
Aug 9, 2026
Merged

perf: prefilter walker-rule type queries by declared type#583
mattiamanzati merged 1 commit into
mainfrom
feat/walker-rule-effect-prefilter

Conversation

@mattiamanzati

Copy link
Copy Markdown
Contributor

What changed

effectInFailure and promiseInEffectSuccess walk every node of a source file and call GetTypeAtLocation — a flow-analysis query, the most expensive part of Effect rule execution — just to test whether the node's type is a strict Effect. This PR adds a declared-type prefilter that skips that query for reference nodes which conclusively cannot be an Effect:

  • New TypeParser.NodeCouldBeStrictEffect(node) in internal/typeparser/could_be_strict_effect.go: for identifiers and property accesses it resolves the referenced symbol (via the already-cached ReferenceSymbolAtNode) and inspects its declared type with a conservative walk. false is returned only on a conclusive negative: primitives/never, plain object types whose symbol has a different name, and unions of those.
  • Both walker rules gain a one-line gate before their GetTypeAtLocation call. Skipped nodes can never match, so the rules' shouldSkip/matched bookkeeping is unaffected; call expressions are never gated.
  • The internal walk is generalized over a type-name set (couldBeNamed), so prefilters for other wrapper types (Stream, Layer, …) are one-line wrappers if ever needed.

Why it is sound

StrictEffectType only matches types whose symbol is named Effect. Flow analysis can only refine a reference's declared type — select union constituents, narrow any/unknown, or intersect it (and intersections are synthetic, with no Effect symbol). So a declared type conclusively containing no possibly-Effect constituent can never produce a strict-Effect flow type. Every case where this argument gets shaky stays conservative and falls through to the full query: any/unknown, type parameters/conditionals/indexed accesses, symbol-less types (including the object keyword and evolving types), unions nested beyond depth 4, unresolvable symbols, and all other node kinds.

Example

declare const message: string          // declared type conclusively non-Effect
declare const eff: Effect.Effect<number>

message  // flow-analysis query skipped by the prefilter
eff      // full query runs as before

Verification

  • Emitted diagnostics (errors and warnings) are byte-identical to current main across a full tsc -b --diagnostics build of the Effect monorepo.
  • Measured on that build, 3-run medians against current main under matched stock controls: total 26.826s → 24.099s (−10.2%), check 26.721s → 23.778s (−11.0%). Composes with the severity skip from perf: skip rules below the minimum visible severity #581 (these rules are warning-severity, so perf: skip rules below the minimum visible severity #581 does not cover them).
  • New unit tests exercise the predicate against real checker types (Effect v4): conclusive negatives, all conservative fall-throughs (any/unknown, type parameters, object keyword, Effect-containing unions), call expressions never gated, and nil safety.
  • pnpm lint, pnpm check, and the full pnpm test suite pass.

🤖 Generated with Claude Code

The effectInFailure and promiseInEffectSuccess rules walk every node of
a file and query its flow type via GetTypeAtLocation just to test
whether it is a strict Effect type. Flow narrowing can only refine a
reference's declared type, so a declared type that conclusively
contains no possibly-Effect constituent can never produce a
strict-Effect flow type.

The new TypeParser.NodeCouldBeStrictEffect predicate exploits this: for
identifier and property-access nodes it inspects the referenced
symbol's declared type and lets the rules skip the expensive
flow-analysis query on a conclusive negative. It stays conservative
(any/unknown, instantiables, symbol-less types, deep unions and every
other node kind fall through to the full query), and the underlying
walk is generalized over a type-name set so future prefilters for other
wrapper types can reuse it.

Diagnostics are unchanged (verified byte-identical on a full Effect
monorepo build); wall time for that build drops ~10%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mattiamanzati
mattiamanzati merged commit 257af25 into main Aug 9, 2026
6 checks passed
@mattiamanzati
mattiamanzati deleted the feat/walker-rule-effect-prefilter branch August 9, 2026 16:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant