Skip to content

add a demonstration on how to specify effects to be performed#675

Open
rkuhn wants to merge 1 commit intomainfrom
rk/typestate
Open

add a demonstration on how to specify effects to be performed#675
rkuhn wants to merge 1 commit intomainfrom
rk/typestate

Conversation

@rkuhn
Copy link
Contributor

@rkuhn rkuhn commented Feb 14, 2026

based on conversations at the Dagstuhl seminar on behavioural types

I intend to use this approach in a refactoring of the miniprotocols after the relay is done, to get
better type safety with less tests, therefore I find it reasonable to add it now even though it
isn't used yet. I also want to show this to some of the researchers who were at Dagstuhl.

Signed-off-by: Roland Kuhn rk@rkuhn.info

Summary by CodeRabbit

  • New Features
    • Introduced a new typestate module enabling type-safe protocol implementations with structured state management and transitions
    • Expanded the library's public API with traits and utilities for composing protocol states and managing effects
    • Added example implementations demonstrating practical protocol patterns, including a chainsync protocol reference implementation

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 14, 2026

Walkthrough

The PR introduces a new publicly exposed typestate module implementing a comprehensive protocol framework. It includes macro-driven state generation, effect primitives, state transition traits, runtime scaffolding for async operations, and a chainsync example demonstrating practical usage patterns.

Changes

Cohort / File(s) Summary
Module Exposure
crates/pure-stage/src/lib.rs
Re-exports new typestate module as public, expanding the crate's public API surface.
Typestate Protocol Framework
crates/pure-stage/src/typestate.rs
Introduces complete typestate-based protocol system with macro-driven state generation, type-level effect primitives (Send, Receive, Call, Schedule, etc.), state transition traits, Effects runtime integration, and a chainsync protocol example with application code and tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • stevana

Poem

🎮 State machines go brrr with phantom types so clean,
The typestate gods have spoken in this protocol dream,
From Idle to Intersect, effects flow like a scene,
Rust's type-level sorcery makes the abstract seen—
No runtime surprises, just compile-time sheen!

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (6 files):

⚔️ crates/amaru-protocols/src/connection.rs (content)
⚔️ crates/amaru-protocols/src/keepalive/tests.rs (content)
⚔️ crates/amaru-protocols/src/manager.rs (content)
⚔️ crates/amaru-protocols/src/tx_submission/tests/system_test.rs (content)
⚔️ crates/amaru/src/stages/build_stage_graph.rs (content)
⚔️ crates/pure-stage/src/lib.rs (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: introducing a typestate module with demonstrations of specifying effects, matching the PR objectives and file additions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rk/typestate
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch rk/typestate
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

based on conversations at the [Dagstuhl seminar on behavioural types](https://www.dagstuhl.de/26071)

Signed-off-by: Roland Kuhn <rk@rkuhn.info>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@crates/pure-stage/src/typestate.rs`:
- Line 286: Fix the typo in the doc comment inside typestate.rs: change "THe
underlying low-level effects machinery; this could be hidden as well to remove"
to "The underlying low-level effects machinery; this could be hidden as well to
remove" (look for the comment containing "THe underlying low-level effects
machinery" near the Typestate module).
- Around line 273-275: The import currently uses the redundant nested module
path crate::typestate::typestate::Transition inside mod application_code;
flatten the inner module by removing the unnecessary inner module layer in the
typestate module and update the import in application_code to use
crate::typestate::Transition (keep StageRef and super::chainsync::* as-is) so
the code imports Transition from the flattened typestate module.
- Around line 15-16: The file defines an inner `pub mod typestate { ... }`
inside typestate.rs causing double nesting (e.g.
crate::typestate::typestate::Transition); remove the inner `pub mod typestate
{}` wrapper and hoist all its contents to the file root so types like Transition
and functions live at crate::typestate::Transition, remove the `#[macro_use]`
annotation on the inner module and either leave macros as normal items (visible
to sibling modules chainsync and application_code) or add `#[macro_export]` to
specific macro_rules! definitions if they must be exported to downstream crates,
then update any paths that referenced the inner module to the flattened
crate::typestate::* paths.
- Around line 216-226: The async method send on the typestate impl captures self
(which holds crate::Effects<M>) and produces a non-Send future; fix by adding
the appropriate Send bounds to the impl/generic parameters (e.g., require M:
Send, and if needed S: Send) so the returned future is Send; also inspect the
definition of Effects<M> to determine and propagate any additional trait bounds
(for example M must satisfy whatever makes Effects<M> Send) and update the
impl/generics for the send method and its surrounding impl block accordingly.
🧹 Nitpick comments (2)
crates/pure-stage/src/typestate.rs (2)

201-214: Minor nit: redundant S in PhantomData.

S is already stored concretely in next: S, so including it again in PhantomData<(Eff, S)> is carrying extra inventory you don't need. Won't cause any bugs — just a wee bit of noise.

🧹 Optional cleanup
     pub struct Effects<M, Eff, S> {
         effects: crate::Effects<M>,
         next: S,
-        _ph: PhantomData<(Eff, S)>,
+        _ph: PhantomData<Eff>,
     }

(And update the PhantomData construction sites accordingly.)


304-317: Heads up: type_name output isn't stable across Rust versions.

The test asserts on "alloc::string::String" which comes from std::any::type_name. That function's docs explicitly say the output is "best effort" and not guaranteed to be stable. It's grand for a demo, but if this test sticks around long-term, a Rust toolchain update could break it like a save file from an old patch version. Maybe worth a comment noting the fragility, or switching to a contains-based assertion.

Comment on lines +15 to +16
#[macro_use]
pub mod typestate {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CI blocker: Clippy module-inception — inner module has the same name as its parent.

Right, so this is a bit like naming your character the same as the game — Clippy's not having it. The file typestate.rs already is the typestate module, so wrapping everything in another pub mod typestate { ... } creates a typestate::typestate::* path (which you can see biting at line 275 with crate::typestate::typestate::Transition).

The cleanest fix is to flatten the inner module: hoist its contents to file level. The macro_rules! definitions will still be visible to the sibling chainsync and application_code modules declared later in the same file, so #[macro_use] on the inner module becomes unnecessary. External consumers would use crate::typestate::* directly — no double-nesting.

If you want the macros exported for downstream crates too, swap #[macro_use] for #[macro_export] on each macro.

🧰 Tools
🪛 GitHub Actions: Coding Practices

[error] 16-16: Clippy: module-inception. The module name 'typestate' is the same as its containing module in crates/pure-stage/src/typestate.rs:16:1.

🤖 Prompt for AI Agents
In `@crates/pure-stage/src/typestate.rs` around lines 15 - 16, The file defines an
inner `pub mod typestate { ... }` inside typestate.rs causing double nesting
(e.g. crate::typestate::typestate::Transition); remove the inner `pub mod
typestate {}` wrapper and hoist all its contents to the file root so types like
Transition and functions live at crate::typestate::Transition, remove the
`#[macro_use]` annotation on the inner module and either leave macros as normal
items (visible to sibling modules chainsync and application_code) or add
`#[macro_export]` to specific macro_rules! definitions if they must be exported
to downstream crates, then update any paths that referenced the inner module to
the flattened crate::typestate::* paths.

Comment on lines +216 to +226
pub async fn send<T: SendData, E>(self, target: &StageRef<T>, msg: T) -> Effects<M, E, S>
where
Eff: Sequence<Head = Send<T>, Tail = E>,
{
self.effects.send(target, msg).await;
Effects {
effects: self.effects,
next: self.next,
_ph: PhantomData,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CI blocker: Clippy future_not_send — the async send method produces a non-Send future.

This one's like trying to fast-travel with contraband — the #![deny(clippy::future_not_send)] in lib.rs won't let it through. The future captures self (which holds crate::Effects<M>), and there's no Send bound on M anywhere in the impl block at line 207.

Adding a Send bound on M (and potentially on S) should sort it:

🔧 Proposed fix
-    impl<M, Eff, S: State> Effects<M, Eff, S> {
+    impl<M: Send, Eff, S: State + Send> Effects<M, Eff, S> {

If crate::Effects<M> requires more than M: Send to be Send itself, you may need to check its definition and propagate the necessary bounds.

🧰 Tools
🪛 GitHub Actions: Coding Practices

[error] 216-220: Clippy: future_not_send. The async function 'send' returns a future that is not Send. This prevents crossing threads (see -D clippy::future_not_send).

🤖 Prompt for AI Agents
In `@crates/pure-stage/src/typestate.rs` around lines 216 - 226, The async method
send on the typestate impl captures self (which holds crate::Effects<M>) and
produces a non-Send future; fix by adding the appropriate Send bounds to the
impl/generic parameters (e.g., require M: Send, and if needed S: Send) so the
returned future is Send; also inspect the definition of Effects<M> to determine
and propagate any additional trait bounds (for example M must satisfy whatever
makes Effects<M> Send) and update the impl/generics for the send method and its
surrounding impl block accordingly.

Comment on lines +273 to +275
mod application_code {
use super::chainsync::*;
use crate::{StageRef, typestate::typestate::Transition};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The typestate::typestate:: double-nesting is a downstream symptom of the module-inception issue.

Once you flatten the inner module (as suggested above), this import simplifies to use crate::typestate::Transition — much tidier, like finally cleaning up your inventory after a long dungeon crawl.

🤖 Prompt for AI Agents
In `@crates/pure-stage/src/typestate.rs` around lines 273 - 275, The import
currently uses the redundant nested module path
crate::typestate::typestate::Transition inside mod application_code; flatten the
inner module by removing the unnecessary inner module layer in the typestate
module and update the import in application_code to use
crate::typestate::Transition (keep StageRef and super::chainsync::* as-is) so
the code imports Transition from the flattened typestate module.

// Some names (channels) needed to perform the Send effects
mux: &StageRef<String>,
other: &StageRef<u8>,
// THe underlying low-level effects machinery; this could be hidden as well to remove
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo: "THe" → "The".

Wee typo in the doc comment, mate.

✏️ Fix
-        // THe underlying low-level effects machinery; this could be hidden as well to remove
+        // The underlying low-level effects machinery; this could be hidden as well to remove
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// THe underlying low-level effects machinery; this could be hidden as well to remove
// The underlying low-level effects machinery; this could be hidden as well to remove
🤖 Prompt for AI Agents
In `@crates/pure-stage/src/typestate.rs` at line 286, Fix the typo in the doc
comment inside typestate.rs: change "THe underlying low-level effects machinery;
this could be hidden as well to remove" to "The underlying low-level effects
machinery; this could be hidden as well to remove" (look for the comment
containing "THe underlying low-level effects machinery" near the Typestate
module).

@codecov
Copy link

codecov bot commented Feb 14, 2026

Codecov Report

❌ Patch coverage is 31.76471% with 58 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/pure-stage/src/typestate.rs 31.76% 58 Missing ⚠️
Files with missing lines Coverage Δ
crates/pure-stage/src/typestate.rs 31.76% <31.76%> (ø)

... and 11 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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