rigor-mangrove
Sharpens Mangrove Result / Option
unwrap-family return types from untyped to the concrete
type-argument carried at the call site, and synthesises the
subclasses an Enum’s variants do … end DSL generates at runtime
so they resolve statically. It layers on top of
rigor-sorbet (which ingests Mangrove’s sig /
RBI surface); this plugin adds the two precision steps sig-level
types can’t reach. No runtime dependency.
It ships bundled in rigortype. Activate it under plugins:
(alongside rigor-sorbet, which supplies the carrier types):
plugins: - rigor-sorbet - rigor-mangroveWhat it infers — no diagnostics, no config
Section titled “What it infers — no diagnostics, no config”The plugin emits no diagnostics of its own and has no configuration. It contributes types in two places; the engine’s ordinary method-existence check then catches typos on the now-known types.
Unwrap-family return types. When the receiver is a Mangrove carrier carrying a type argument, the unwrap return narrows to that argument:
# token : Result::Ok[String, StandardError]session.token.unwrap!.uppercaze # error: undefined method `uppercaze' for StringEnum variant synthesis (ADR-36 Slice A). The variants DSL
emits nested subclasses at runtime; the plugin synthesises them
statically — the variant constant resolves, .new dispatches, and
a typed #inner reader returns the declared payload type:
class Shape extend Mangrove::Enum variants do variant Circle, Float endend
Shape::Circle.new(1.5).inner.floor # ok — FloatShape::Circle.new(1.5).inner.no_such_float_method # error: undefined for FloatLimitations
Section titled “Limitations”- No generic inference from constructors.
Result::Ok.new("x")yields a carrier with no type argument, so unwrap there contributes nothing (a conservative no-op, never a false positive). - Downcast narrowing keeps no type args (ADR-36 Slice B,
deferred). Narrowing a parent generic to a variant via
is_a?doesn’t yet carry the type arguments through the inheritance edge. - Non-constant payload shapes degrade. A variant whose payload
is a shape hash (
{ name: String }) falls back toDynamic[Top].
Plugin internals
Section titled “Plugin internals”The carrier-generic instantiation, the NestedClassTemplate
variant synthesis, and the relationship to rigor-sorbet are in
the plugin’s README;
the synthesis tier is ADR-36.
To write a plugin, see examples/
and the rigor-plugin-author skill.
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.