Skip to content

Stop generation when an interface redeclares isApplicableTo or getPriority - #4

Merged
thomasrepnik merged 1 commit into
masterfrom
fix/duplicate-dispatcher-members
Nov 6, 2026
Merged

thomasrepnik merged 1 commit into
masterfrom
fix/duplicate-dispatcher-members

Conversation

@thomasrepnik

@thomasrepnik thomasrepnik commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Fixes #2

Summary

 analyzeDispatcher(iface)
+  if iface or a direct base declares isApplicableTo / getPriority
+    return fatal reason
   collect the remaining members

 cli
+  if the analysis is fatal
+    print "cannot generate: ..." and exit 1, before anything is written

Dispatchable itself may still declare both methods, and implementation classes still implement them. This differs from the issue's expected result of one generated copy of each; the reasoning is in the comment below.

Evidence

Interface: PaymentHandler extends Dispatchable<PaymentContext> { isApplicableTo(context: PaymentContext): boolean; pay(): string }

  • Before: generation succeeds, and the dispatcher contains isApplicableTo twice, so it fails to compile with TS2393.
    After: generation exits with 1 and prints nothing to stdout:

    cannot generate: PaymentHandler or one of its base types declares isApplicableTo. isApplicableTo and getPriority are created automatically and must not be provided by the interface; remove the declaration (implementation classes still implement them).
    
  • Tests before the implementation: 13 new tests failed (40 passed).
    After: 53 of 53 pass. Typecheck, build, both examples and the package smoke test also pass, and example/generated has no diff.

The tests cover matching and differing signatures, overloads, the property form, generic and type alias bases, and a base listed either side of Dispatchable.

Merge Danger

Door: two-way

The check can be relaxed later without affecting anyone who has already removed their redeclarations.

Blast Radius: consumers

Projects that redeclare these methods now get an error instead of a dispatcher, and the registry and all other dispatchers are not generated until they remove the declaration. For a redeclaration on the interface itself or on a non-generic interface base, the output did not compile before, so nothing working is lost. For a generic or type alias base, the old result was a warning and no dispatcher while the rest of the generation carried on, so those projects go from a warning to a hard stop. This deserves a line in the release notes.

Not covered: H extends Mid, where Mid extends Dispatchable<Ctx>, Base and Base declares the method.

…ority

The dispatcher always creates both methods, so a redeclaration rendered
them twice and the generated file failed with TS2393. Skipping the
redeclaration by name is not enough: its signature can differ from the
rendered one, which breaks compilation or silently drops arguments.

Reject any redeclaration on a Dispatchable interface or its base types,
whatever the signature, with an error and exit code 1.

Fixes #2
@thomasrepnik

Copy link
Copy Markdown
Owner Author

Why generation stops instead of checking signatures

Issue #2 proposes skipping isApplicableTo and getPriority by name, so that one copy of each is rendered. We tried that first. It fixes the TS2393 from the issue, but only when the redeclared signature matches the rendered one, because the dispatcher ignores whatever the interface declared. Other valid redeclarations then go wrong:

Redeclared in H extends Dispatchable<Ctx> Result with skip-by-name
isApplicableTo(c: Ctx, strict?: boolean): boolean compiles, but strict is silently never forwarded
isApplicableTo(): boolean generated file does not compile
isApplicableTo(c: Sub): boolean generated file does not compile
isApplicableTo(c: Ctx): c is Sub generated file does not compile
getPriority(): 1 | 2 generated file does not compile

Why not check signatures? A check would have to compare parameters, the context type, return types and type predicates against what the dispatcher renders, and decide what to do with compatible variants such as the property form. Rendering the user's signature instead would add the same complexity. Either way, that is a lot of logic for a case with no benefit: these methods are already inherited from Dispatchable, so redeclaring them adds nothing.

Why reject every redeclaration? One rule by name covers all signatures, needs no comparison logic, and is easy to explain. The fix for the user is to delete the declaration, which is also the workaround the issue already recommends.

Why an error instead of the usual warning? The aim is that the user sees the cause immediately. A warning is easy to miss in build output, and the first visible symptom would be a missing dispatcher somewhere else.

The cost:

If a real need to redeclare these methods comes up, the check can be relaxed later without breaking anyone.

@thomasrepnik
thomasrepnik merged commit 85f92b5 into master Nov 6, 2026
3 checks passed
@thomasrepnik
thomasrepnik deleted the fix/duplicate-dispatcher-members branch November 6, 2026 16:23
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.

Generation of duplicate isApplicableTo / getPriority methods in dispatcher

1 participant