Skip to content

feat: type-checked merge inputs - #10

Merged
psyinf merged 1 commit into
mainfrom
feature/typed-merge-inputs
Sep 17, 2026
Merged

psyinf merged 1 commit into
mainfrom
feature/typed-merge-inputs

Conversation

@psyinf

@psyinf psyinf commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Stack (bottom → top)

  1. feat: add GraphContext, a graph-scoped side channel for stages #9GraphContext
  2. feat: type-checked merge inputs #10 — type-checked merge inputs (this PR)
  3. feat: add finish(), the end-of-stream lifecycle hook #11finish() lifecycle hook

Summary

A merge stage may now declare the type of each of its slots, so the DSL checks the edges of its fan-in group when the graph is built instead of failing with a std::bad_any_cast on the first message. This closes the only edges the library did not check: GraphPlan checks every single-input edge, but a merge only declared MergeInputs = std::vector<std::any>, so (msg, valid) and (valid, msg) both validated and ran — silently producing a different result when the slots share a type.

What's new

  • MergeStage / MergeSlotTypes (new MergeStage.hpp) — how a merge declares its slot types. MergeInputs moved here from MergeFilter.hpp, which still provides it.
  • AnyMessageFilter::mergeInputTypes() — exposes them to the DSL. The adapter picks them up from any stage implementing MergeStage, so MessageFilter itself is unchanged.
  • TypedMergeFilter<Out, Ins...> — fixed arity, one type per slot. merge() receives the slots as std::optionals (std::nullopt = a hole left by a dropped path), so user code needs no any_cast.
  • UniformMergeFilter<In, Out> — any number of slots, all of one type: N variants of the same computation.
  • registerTypedMergeFilter<Out, Ins...>(name, fn) — the same from a lambda, the typed counterpart of registerMergeFilter.

New diagnostics, in the usual line:column style:

3:20: slot 1 of 'Report' expects 'class std::basic_string<char,...>' but edge 'length' carries 'unsigned __int64'
5:9: stage 'MergeLabel' takes 2 inputs but the group has 3

Compatibility

Additive. MergeFilter / registerMergeFilter declare no slot types, so their edges stay unchecked and existing graphs behave exactly as before.

Notes

  • A hand-written merge stage (MessageFilter<MergeInputs, Out> registered via FilterRegistrar) can also implement MergeStage to be checked.
  • registerMergeFilter's combiner is copied into every instance, so captured state is shared between instances. That is now documented at the registration function; per-instance state is what the class-based merges above are for.

Testing

New MergeTests (9 cases): typed slots and holes, the lambda registration, uniform merges of 2 and 3 slots, a mis-wired group and a wrong slot count reported at build time, an untyped merge still accepted unchecked, per-instance state, and direct use outside a graph. The sample app gains a typed merge (Report) whose real output and diagnostics are what EXAMPLE.md shows. Full suite: 77/77 locally (MSVC debug, ASan).

@psyinf
psyinf added this pull request to stack #12 September 17, 2026 17:44
@psyinf
psyinf force-pushed the feature/typed-merge-inputs branch from 1aa2a3b to 0354a55 Compare September 17, 2026 18:13
Base automatically changed from feature/graph-context to main September 17, 2026 18:22
A merge stage may now declare the type of each of its slots, so the DSL
checks the edges of its fan-in group when the graph is built instead of
failing with a std::bad_any_cast on the first message.

- MergeStage / MergeSlotTypes (new MergeStage.hpp): how a merge declares
  its slot types; MergeInputs moved here from MergeFilter.hpp
- AnyMessageFilter::mergeInputTypes() exposes them to the DSL; the
  adapter picks them up from any stage implementing MergeStage
- TypedMergeFilter<Out, Ins...>: fixed arity, one type per slot, passed
  to merge() as std::optionals (empty = hole), no any_cast in user code
- UniformMergeFilter<In, Out>: any number of slots of one type
- registerTypedMergeFilter<Out, Ins...>: the same from a lambda
- GraphPlan reports "slot N of 'X' expects ... but edge 'e' carries ..."
  and "stage 'X' takes N inputs but the group has M"
- MergeFilter / registerMergeFilter declare no slot types and keep their
  unchecked behaviour

The sample app gains a typed merge (Report), and README, EXAMPLE.md and
the changelog document it.
@psyinf
psyinf force-pushed the feature/typed-merge-inputs branch from 0354a55 to 54d07ec Compare September 17, 2026 18:22
@psyinf
psyinf merged commit 6ee338e into main Sep 17, 2026
3 checks passed
@psyinf
psyinf deleted the feature/typed-merge-inputs branch September 17, 2026 18:26
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