Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

This tutorial is a simple, example-first path into CGP.

What to expect:

  • Start with CGP from the beginning.
  • Build concrete things quickly.
  • Learn by wiring real examples.
  • Keep explanations short and practical.

In CGP, your main job is usually:

  1. define a capability,
  2. implement one or more providers for that capability,
  3. wire a context to the provider you want.

That is the core loop. Everything else builds on top of it.

Context, Component, Provider

This chapter shows the core CGP relationship in code:

  • a context is your concrete app type,
  • a component is a capability slot,
  • a provider is the implementation plugged into that slot.

If you remember one sentence, use this: the context chooses a provider for each component.

You can say the same idea in a few equivalent ways:

  • A context gets a component’s behavior by wiring that component to a provider.
  • A context implements a component’s consumer trait by delegating to a provider.
  • A component defines the capability, a provider defines the implementation, and the context chooses the provider.
  • A context has a capability when its component key is mapped to a valid provider.
  • In CGP, behavior comes from provider selection per component.

Complete example

use cgp::prelude::*;

// 1) Define a capability (consumer trait)
#[cgp_component(Greeter)]
pub trait CanGreet {
    fn greet(&self);
}

// 2) Define a dependency used by the provider
#[cgp_auto_getter]
pub trait HasName {
    fn name(&self) -> &str;
}

// 3) Define a provider for that capability
#[cgp_impl(new GreetHello)]
impl Greeter
where
    Self: HasName,
{
    fn greet(&self) {
        println!("Hello, {}!", self.name());
    }
}

// 4) Define the concrete context
#[derive(HasField)]
pub struct Person {
    pub name: String,
}

// 5) Wire context -> component -> provider
delegate_components! {
    Person {
        GreeterComponent: GreetHello,
    }
}

fn main() {
    let person = Person {
        name: "Ada".into(),
    };

    person.greet();
}

What each term means in this code

  • Context: Person.
  • Component: GreeterComponent (the key used in wiring).
  • Provider: GreetHello (the implementation wired for that component).

Person gets CanGreet by wiring GreeterComponent to GreetHello.

Vocabulary: capability, consumer, dependency

  • Capability: what a context can do at call-site, like person.greet().
  • Consumer trait: the trait that exposes that capability to callers, like CanGreet.
  • Component: the full CGP unit around that capability (consumer trait + provider trait + component key). In wiring code, you usually see the component key type (GreeterComponent).
  • Capability slot: another way to describe the component key in a context’s wiring table.
  • Provider: the implementation plugged into that slot, like GreetHello.
  • Dependency: another trait requirement needed by a provider implementation, like Self: HasName.

When step (1) says “define a capability,” it means: define the consumer trait (CanGreet) that represents that capability. When step (2) says “define a dependency,” it means: define required supporting traits/data the provider needs (HasName, and later Person { name: … }).

In short:

  • consumer = the interface callers use (CanGreet)
  • provider = the implementation of that interface logic (GreetHello via impl Greeter)
  • context = the concrete type that picks providers (Person)
  • component key = the selector used in wiring (GreeterComponent)

How the relationship works

  1. #[cgp_component(Greeter)] defines a CGP capability from CanGreet.
  2. #[cgp_impl(new GreetHello)] impl Greeter says GreetHello implements that capability’s provider trait.
  3. delegate_components! on Person selects GreetHello for the GreeterComponent slot.
  4. Calling person.greet() uses the provider chosen by that wiring.

That is the fundamental context-component-provider loop.

Another way to read the same flow:

  1. Define what callers can ask for (CanGreet).
  2. Define how that behavior is implemented (GreetHello).
  3. Define who receives the behavior (Person).
  4. Wire which implementation that context should use (GreeterComponent: GreetHello).

Identifier guide (to avoid confusion)

Some names are generated by macros, so here is the direct map:

  • CanGreet is the consumer trait you wrote.
  • Greeter is the provider trait name you passed to #[cgp_component(Greeter)].
  • GreeterComponent is the generated component key type used by delegate_components!.
  • GreetHello is the provider type name you chose in #[cgp_impl(new GreetHello)].

Rules to remember:

  • impl Greeter must match the provider-trait name from #[cgp_component(Greeter)].
  • fn greet in the provider impl must match fn greet in CanGreet.
  • GreeterComponent is used in wiring because it is the component key type.
  • Names like Person, HasName, and GreetHello are arbitrary; consistency matters, not specific prefixes.

Optional compile-time check

check_components! {
    #[check_providers(GreetHello)]
    CanUsePerson for Person {
        GreeterComponent,
    }
}

This confirms the wiring for Person and also confirms GreetHello is valid for that component.

Auto Getter and HasField

This chapter explains two pieces that often appear together:

  • #[derive(HasField)]
  • #[cgp_auto_getter]

Short version:

  • HasField gives a context a generic way to read fields by field name.
  • cgp_auto_getter uses HasField to auto-implement simple getter traits.

If HasField is the raw field access engine, cgp_auto_getter is the user-friendly wrapper.

Why this exists

You usually want provider code to depend on traits like HasName, not on concrete struct fields.

That keeps providers reusable:

  • any context with a matching getter trait can use the provider,
  • the provider does not care about the concrete context type.

HasField + cgp_auto_getter is the easiest way to create those getter traits.

Step 1: #[derive(HasField)] on a context

use cgp::prelude::*;

#[derive(HasField)]
pub struct Person {
    pub name: String,
    pub age: u32,
}

After this derive, Person can expose its fields through CGP’s generic field-access trait machinery.

In plain terms: the derive teaches CGP that Person has a name field and an age field, and what their types are.

Step 2: define getter traits with #[cgp_auto_getter]

use cgp::prelude::*;

#[cgp_auto_getter]
pub trait HasName {
    fn name(&self) -> &str;
}

#[cgp_auto_getter]
pub trait HasAge {
    fn age(&self) -> &u32;
}

#[cgp_auto_getter] auto-generates blanket getter implementations using HasField.

In plain terms:

  • if a context has a name field compatible with the name() return type, it gets HasName automatically.
  • if a context has an age field compatible with the age() return type, it gets HasAge automatically.

Full working example

use cgp::prelude::*;

#[cgp_auto_getter]
pub trait HasName {
    fn name(&self) -> &str;
}

#[cgp_auto_getter]
pub trait HasAge {
    fn age(&self) -> &u32;
}

#[derive(HasField)]
pub struct Person {
    pub name: String,
    pub age: u32,
}

fn print_profile<C: HasName>(person: &C) {
    println!("name={}", person.name());
}

fn print_age<C: HasAge>(person: &C) {
    println!("age={}", person.age());
}

fn main() {
    let person = Person {
        name: "Ada".into(),
        age: 36,
    };

    print_profile(&person);
    print_age(&person);
}

No manual impl blocks were needed for HasName or HasAge.

What exactly gets matched

For #[cgp_auto_getter], the getter method name is important:

  • fn name(&self) -> … expects a name field.
  • fn age(&self) -> … expects an age field.

And the return type must be compatible with the field type.

Example:

  • name: String can satisfy fn name(&self) -> &str.
  • age: u32 satisfies fn age(&self) -> &u32.

How this helps providers

Providers can depend on getter traits, not concrete structs:

use cgp::prelude::*;

#[cgp_component(Greeter)]
pub trait CanGreet {
    fn greet(&self);
}

#[cgp_auto_getter]
pub trait HasName {
    fn name(&self) -> &str;
}

#[cgp_impl(new GreetHello)]
impl Greeter
where
    Self: HasName,
{
    fn greet(&self) {
        println!("Hello, {}!", self.name());
    }
}

This provider works with any context that has HasName, whether that context is Person, AdminUser, or something else.

Setting a field with HasFieldMut

#[derive(HasField)] also enables mutable field access via HasFieldMut.

That lets you update a field in generic code without naming the concrete context type.

use core::marker::PhantomData;
use cgp::prelude::*;

#[derive(HasField)]
pub struct Person {
    pub name: String,
    pub age: u32,
}

fn rename<C>(context: &mut C, new_name: String)
where
    C: HasFieldMut<Symbol!("name"), Value = String>,
{
    *context.get_field_mut(PhantomData) = new_name;
}

fn main() {
    let mut person = Person {
        name: "Ada".into(),
        age: 36,
    };

    rename(&mut person, "Grace".into());
    assert_eq!(person.name, "Grace");
}

How to read this:

  • HasFieldMut<Symbol!(“name”)> means “this context has a mutable name field”.
  • get_field_mut(PhantomData) returns &mut String for that field.
  • We assign through that mutable reference.

So a practical rule is:

  • use #[cgp_auto_getter] for read-focused traits,
  • use HasFieldMut when you need generic field updates.

Mental model

  • HasField = low-level field lookup capability generated from your struct fields.
  • cgp_auto_getter = easy getter traits built on top of HasField.
  • Provider constraints (like Self: HasName) = reusable requirements that many contexts can satisfy.

This is one of the main CGP patterns: use small getter traits as reusable dependencies.

Provider-Side Dependencies

This chapter covers a practical cleanup pattern for provider where clauses.

Important framing:

  • the cleanup technique is plain Rust (not CGP-specific),
  • it is especially useful in CGP because provider bounds can grow quickly.

If you remember one sentence, use this:

  • factor out detailed trait bounds into a helper trait with a blanket impl, then depend on that helper in providers.

Step 1: direct provider bounds (works, but can get noisy)

Start with a straightforward provider that depends directly on getter traits.

use cgp::prelude::*;

#[cgp_auto_getter]
pub trait HasFirstName {
    fn first_name(&self) -> &str;
}

#[cgp_auto_getter]
pub trait HasLastName {
    fn last_name(&self) -> &str;
}

#[cgp_auto_getter]
pub trait HasTitle {
    fn title(&self) -> &str;
}

#[cgp_component(Introducer)]
pub trait CanIntroduce {
    fn introduce(&self) -> String;
}

#[cgp_impl(new FormalIntroduce)]
impl Introducer
where
    Self: HasFirstName + HasLastName + HasTitle,
{
    fn introduce(&self) -> String {
        format!("Hello, I am {} {} {}.", self.title(), self.first_name(), self.last_name())
    }
}

#[derive(HasField)]
pub struct Person {
    pub title: String,
    pub first_name: String,
    pub last_name: String,
}

delegate_components! {
    Person {
        IntroducerComponent: FormalIntroduce,
    }
}

fn main() {
    let person = Person {
        title: "Dr.".into(),
        first_name: "Ada".into(),
        last_name: "Lovelace".into(),
    };

    assert_eq!(person.introduce(), "Hello, I am Dr. Ada Lovelace.");
}

This is valid and often fine for small cases.

The issue appears when many providers repeat the same long bound list.

Step 2: factor out dependencies with plain Rust

Now we use a standard Rust move:

  1. create a helper trait,
  2. add a blanket impl with detailed bounds,
  3. depend on the helper trait in the provider.
use cgp::prelude::*;

#[cgp_auto_getter]
pub trait HasFirstName {
    fn first_name(&self) -> &str;
}

#[cgp_auto_getter]
pub trait HasLastName {
    fn last_name(&self) -> &str;
}

#[cgp_auto_getter]
pub trait HasTitle {
    fn title(&self) -> &str;
}

// Plain Rust helper trait
pub trait CanFormatDisplayName {
    fn display_name(&self) -> String;
}

// Plain Rust blanket impl with detailed dependencies
impl<Context> CanFormatDisplayName for Context
where
    Context: HasFirstName + HasLastName + HasTitle,
{
    fn display_name(&self) -> String {
        format!("{} {} {}", self.title(), self.first_name(), self.last_name())
    }
}

#[cgp_component(Introducer)]
pub trait CanIntroduce {
    fn introduce(&self) -> String;
}

#[cgp_impl(new FormalIntroduce)]
impl Introducer
where
    Self: CanFormatDisplayName,
{
    fn introduce(&self) -> String {
        format!("Hello, I am {}.", self.display_name())
    }
}

#[derive(HasField)]
pub struct Person {
    pub title: String,
    pub first_name: String,
    pub last_name: String,
}

delegate_components! {
    Person {
        IntroducerComponent: FormalIntroduce,
    }
}

fn main() {
    let person = Person {
        title: "Dr.".into(),
        first_name: "Ada".into(),
        last_name: "Lovelace".into(),
    };

    assert_eq!(person.introduce(), "Hello, I am Dr. Ada Lovelace.");
}

What changed (same behavior, cleaner provider)

  • Before: provider directly listed HasFirstName + HasLastName + HasTitle.
  • After: provider lists Self: CanFormatDisplayName.
  • The detailed bounds still exist, now localized in one blanket impl.

Equivalent ways to say this:

  • we factor out trait dependencies,
  • we hide low-level bounds behind a helper capability,
  • we compose constraints once, then reuse them everywhere.

Why CanFormatDisplayName is not a #[cgp_component(…)]

  • CanIntroduce is consumer-facing and needs component wiring (IntroducerComponent).
  • CanFormatDisplayName is an internal helper dependency trait.

So we intentionally use:

  • #[cgp_component(…)] for capabilities that need provider selection in delegate_components!,
  • plain traits + blanket impls for helper dependency factoring.

If you later want swappable formatting strategies, you can promote formatting to its own component.

Plain helper trait vs component trait

Use this quick contrast when deciding:

Plain Rust helper trait (CanFormatDisplayName)CGP component + consumer trait
Reuse internal helper logic across providersExpose a capability as part of context API
No independent provider wiring neededNeeds provider selection via delegate_components!
Behavior is derived from existing capabilities/boundsBehavior should be swappable/configurable per context
Acts as implementation compositionActs as a capability slot
Answers: “what helper logic do I need?”Answers: “which provider should this context use?”
Usually plain trait + blanket implUsually #[cgp_component(…)] + provider impl(s)

Memory shortcut:

  • plain trait = compose helpers,
  • component trait = choose providers.

Where provider-side dependencies live

Look in two places:

  • helper blanket impl: detailed trait requirements,
  • provider impl: compact high-level dependency.

This is the provider-side dependency pattern in one line:

  • detailed bounds in helper impl, short bounds in provider impl.

Practical checklist

  1. Start with a working provider, even if bounds are long.
  2. Spot repeated or noisy bound groups.
  3. Extract a helper trait for that group.
  4. Add a blanket impl with detailed bounds.
  5. Replace long provider bounds with the helper trait.

Practical rule: when provider bounds repeat or become hard to scan, factor them out with a plain Rust helper trait + blanket impl.

Extensible Builder with BuildField

This chapter shows a simple CGP builder pattern using BuildField.

Goal:

  • build a bigger struct from smaller structs,
  • keep each piece independent,
  • finish with one strongly typed final value.

What BuildField gives you

For a target struct, deriving BuildField gives you a typed builder flow:

  • Target::builder() starts an empty partial builder,
  • .build_field(…) sets one field,
  • .build_from(…) merges fields from another struct,
  • .finalize_build() produces the final target when all required fields are present.

Think of it as: start empty -> add pieces -> finalize.

HasField vs HasFields (important)

The names are very similar, and that is easy to mix up:

  • HasField (singular) is about one field at a time by tag (for example name or employee_id).
  • HasFields (plural) is about the whole set of fields of a type.

In this chapter, build_from(…) takes a whole source struct and merges its field set into the target builder. So source types used with build_from(…) need HasFields (plural), even if they only have one field like EmployeeId.

By contrast, build_field(…) does not take a source struct. It takes a field tag (Symbol!(“…”)) and a field value, then sets that one target field.

(In many CGP codebases you also derive HasField for individual field access, but this specific build_from + build_field example does not require it on Person or EmployeeId.)

Quick memory trick:

  • Field = one slot.
  • Fields = a bundle of slots.

Complete example

use core::marker::PhantomData;

use cgp::core::field::impls::CanBuildFrom;
use cgp::prelude::*;

#[derive(Debug, Eq, PartialEq, HasFields, BuildField)]
pub struct Employee {
    pub employee_id: u64,
    pub first_name: String,
    pub last_name: String,
}

#[derive(Debug, Eq, PartialEq, HasFields, BuildField)]
pub struct Person {
    pub first_name: String,
    pub last_name: String,
}

#[derive(Debug, Eq, PartialEq, HasFields, BuildField)]
pub struct EmployeeId {
    pub employee_id: u64,
}

fn main() {
    let person = Person {
        first_name: "Ada".to_owned(),
        last_name: "Lovelace".to_owned(),
    };

    let employee_id = EmployeeId { employee_id: 7 };

    let employee: Employee = Employee::builder()
        .build_from(person)
        .build_from(employee_id)
        .finalize_build();

    assert_eq!(
        employee,
        Employee {
            employee_id: 7,
            first_name: "Ada".to_owned(),
            last_name: "Lovelace".to_owned(),
        }
    );

    let employee2: Employee = Employee::builder()
        .build_from(Person {
            first_name: "Grace".to_owned(),
            last_name: "Hopper".to_owned(),
        })
        .build_field(PhantomData::<Symbol!("employee_id")>, 99)
        .finalize_build();

    assert_eq!(employee2.employee_id, 99);
}

What each step means

  • Employee::builder() starts a partial Employee builder with no fields filled.
  • .build_from(person) copies matching fields from Person (first_name, last_name).
  • .build_from(employee_id) copies employee_id from EmployeeId.
  • .build_field(PhantomData::<Symbol!(“employee_id”)>, 99) sets one field directly by field tag.
  • .finalize_build() returns Employee.

This is why it is called extensible: different modules can contribute different field bundles.

Where extensibility comes from

  • Person does not need to know about Employee.
  • EmployeeId does not need to know about Employee.
  • The composition happens at the build site by chaining build_from calls.

So you can keep field producers small and reusable, then assemble bigger targets as needed.

Name and type checklist

  • The Symbol!(“employee_id”) tag must match a real target field name.
  • Types must match (employee_id is u64, so value must be u64).
  • finalize_build() works only after all required fields are present.

Practical rule:

  • use build_from for multi-field pieces,
  • use build_field for one-off values.

Enum Upcasting and Downcasting

This chapter shows the safe enum casting pattern in CGP.

Goal:

  • move from a smaller enum to a bigger enum (upcast),
  • move from a bigger enum to a smaller enum (downcast),
  • safely handle what does not fit.

What to derive

For these casts, derive CgpData on your enums.

CgpData gives the enum traits needed by the cast helpers (upcast, downcast, downcast_fields).

Complete example

use core::marker::PhantomData;

use cgp::core::field::impls::{CanDowncast, CanDowncastFields, CanUpcast};
use cgp::prelude::*;

#[derive(Debug, PartialEq, CgpData)]
pub enum Shape {
    Circle(Circle),
    Rectangle(Rectangle),
}

#[derive(Debug, PartialEq, CgpData)]
pub enum ShapePlus {
    Triangle(Triangle),
    Rectangle(Rectangle),
    Circle(Circle),
}

#[derive(Debug, PartialEq, CgpData)]
pub enum TriangleOnly {
    Triangle(Triangle),
}

#[derive(Debug, PartialEq, CgpData)]
pub enum CircleOnly {
    Circle(Circle),
}

#[derive(Debug, PartialEq)]
pub struct Circle {
    pub radius: f64,
}

#[derive(Debug, PartialEq)]
pub struct Rectangle {
    pub width: f64,
    pub height: f64,
}

#[derive(Debug, PartialEq)]
pub struct Triangle {
    pub base: f64,
    pub height: f64,
}

fn main() {
    // Upcast: smaller enum -> bigger enum
    let shape = Shape::Circle(Circle { radius: 5.0 });
    let shape_plus = shape.upcast(PhantomData::<ShapePlus>);
    assert_eq!(shape_plus, ShapePlus::Circle(Circle { radius: 5.0 }));

    // Downcast success: bigger enum -> smaller enum
    let ok = ShapePlus::Rectangle(Rectangle {
        width: 3.0,
        height: 4.0,
    })
    .downcast(PhantomData::<Shape>)
    .ok();
    assert_eq!(ok, Some(Shape::Rectangle(Rectangle { width: 3.0, height: 4.0 })));

    // Downcast failure: variant not in Shape
    let remainder = ShapePlus::Triangle(Triangle {
        base: 3.0,
        height: 4.0,
    })
    .downcast(PhantomData::<Shape>)
    .unwrap_err();

    // Handle the remainder by downcasting fields into another enum
    let triangle_only_result: Result<TriangleOnly, _> =
        remainder.downcast_fields(PhantomData::<TriangleOnly>);

    let triangle_only = triangle_only_result.ok();
    assert_eq!(
        triangle_only,
        Some(TriangleOnly::Triangle(Triangle {
            base: 3.0,
            height: 4.0,
        }))
    );

    // downcast_fields can also fail, depending on what the remainder still contains.
    let remainder2 = ShapePlus::Rectangle(Rectangle {
        width: 8.0,
        height: 5.0,
    })
    .downcast(PhantomData::<CircleOnly>)
    .unwrap_err();

    let not_triangle: Result<TriangleOnly, _> =
        remainder2.downcast_fields(PhantomData::<TriangleOnly>);

    assert!(not_triangle.is_err());
}

What each cast means

  • upcast(PhantomData::) says: “convert this enum into a compatible larger target enum.”
  • downcast(PhantomData::) says: “try converting into this smaller target enum.”
  • downcast returns Result<Target, Remainder>:
    • Ok(target) when the variant exists in target,
    • Err(remainder) when it does not.
  • downcast_fields(PhantomData::) tries another safe conversion step on an extracted remainder source.

What is Remainder?

Remainder is not usually the original source enum type.

It is an internal “what is left” extraction type produced by CGP when a downcast fails. You can think of it as a strongly typed carrier for the still-unhandled variants.

That is why this works:

  • first downcast gives Err(remainder),
  • then downcast_fields continues from that remainder without losing type safety.

Practical takeaway: treat Remainder as “unhandled cases so far,” not as “the original enum again.”

How downcast_fields works

downcast_fields serves the same purpose as downcast, but for the remainder type.

  • Use downcast on a full enum value.
  • If that returns Err(remainder), use downcast_fields on that remainder.

Its return shape is also the same pattern: Result<Target, NextRemainder>.

  • Ok(target) means the remainder fits the new target enum.
  • Err(next_remainder) means it still does not fit, and you can continue routing.

Why this is useful

  • Layered APIs: convert from domain-specific enums into broader app enums with upcast.
  • Selective handling: try downcast into the subset you care about, then pass remainder onward.
  • Exhaustive workflows: repeatedly downcast remainder into other subsets until every case is handled.

Mental model

  • Upcast = “embed into a bigger enum.”
  • Downcast = “attempt to recover a smaller enum.”
  • Remainder = “the part that did not fit yet.”

This gives you safe, composable enum routing without unsafe and without ad-hoc conversion glue.

Compute Components: Computer and ComputerRef

This chapter explains how CGP models computation as a component.

Goal:

  • use Computer for by-value compute,
  • use ComputerRef for by-reference compute,
  • wire both on one context.

One sentence mental model

  • Computer means: consume input and compute output.
  • ComputerRef means: borrow input and compute output.

Same idea, different ownership style.

Vocabulary first: Eval and Code

You will see this shape a lot:

  • compute(code, input)
  • compute_ref(code, input_ref)

The code argument is a type-level selector (a marker type), usually a zero-sized struct.

Example marker:

  • pub struct Eval;

What that means:

  • Eval is not runtime data.
  • It is a type tag used to name “which computation mode” you are invoking.
  • In simple examples, we pass it but do not branch on it yet.

So Code in Computer<Code, Input> is the generic type parameter for that tag.

Example 1: one computation mode, generic provider

Start with one mode (Eval) and one generic provider impl.

use cgp::extra::handler::{CanCompute, Computer};
use cgp::prelude::*;

pub struct Eval;

#[derive(Clone, Copy)]
pub struct Pair {
    pub left: u64,
    pub right: u64,
}

#[cgp_impl(new SumPair)]
impl<Context, Code> Computer<Code, Pair> for Context {
    type Output = u64;

    fn compute(_context: &Context, _code: PhantomData<Code>, input: Pair) -> Self::Output {
        input.left + input.right
    }
}

pub struct App;

delegate_components! {
    App {
        ComputerComponent: SumPair,
    }
}

fn main() {
    let app = App;
    let eval = PhantomData::<Eval>;

    let out = app.compute(eval, Pair { left: 2, right: 3 });
    assert_eq!(out, 5);
}

What this shows:

  • SumPair is generic over Code, so one impl can serve many mode tags.
  • We only use one mode (Eval) at call-site.
  • This is a good default starting point.

Example 2: two computation modes, separate impl behavior

Now we keep the same input type, but give different behavior per mode.

use cgp::extra::handler::{CanCompute, Computer};
use cgp::prelude::*;

pub struct Eval;
pub struct Explain;

#[derive(Clone, Copy)]
pub struct Pair {
    pub left: u64,
    pub right: u64,
}

#[cgp_impl(new SumValue)]
impl<Context> Computer<Eval, Pair> for Context {
    type Output = u64;

    fn compute(_context: &Context, _code: PhantomData<Eval>, input: Pair) -> Self::Output {
        input.left + input.right
    }
}

#[cgp_impl(new SumExplain)]
impl<Context> Computer<Explain, Pair> for Context {
    type Output = String;

    fn compute(_context: &Context, _code: PhantomData<Explain>, input: Pair) -> Self::Output {
        format!("{} + {}", input.left, input.right)
    }
}

pub struct App;

delegate_components! {
    App {
        ComputerComponent: UseDelegate<
            new ModeHandlers {
                Eval: SumValue,
                Explain: SumExplain,
            }
        >,
    }
}

fn main() {
    let app = App;

    let value = app.compute(PhantomData::<Eval>, Pair { left: 2, right: 3 });
    let text = app.compute(PhantomData::<Explain>, Pair { left: 2, right: 3 });

    assert_eq!(value, 5);
    assert_eq!(text, "2 + 3");
}

What changed from Example 1:

  • provider impls are now mode-specific (Eval vs Explain),
  • wiring uses UseDelegate to dispatch by Code,
  • output type can differ by mode (u64 vs String).

This is the key role of Code: same input type, different compute behavior.

UseDelegate and new ModeHandlers explained

In Example 2, this wiring is the important line:

ComputerComponent: UseDelegate<
    new ModeHandlers {
        Eval: SumValue,
        Explain: SumExplain,
    }
>,

Read it as a compile-time lookup table:

  • key = Code type (Eval, Explain),
  • value = provider (SumValue, SumExplain).

ModeHandlers is just a name for that table type.

  • It is not a reserved CGP keyword.
  • You can rename it (for example ComputeByMode, PairHandlers, CodeTable).

What must match:

  • Eval and Explain in this table must match the code tags you call with at runtime (PhantomData::, PhantomData::).
  • SumValue and SumExplain must implement the corresponding Computer<Code, Input> cases.

So the full phrase UseDelegate<new ModeHandlers { … }> means:

  • “for this component, choose provider by code tag using this table.”

Where ComputerRef fits

Everything above used Computer (by-value).

ComputerRef is the borrowed-input counterpart:

  • Computer<Code, Input> takes input: Input
  • ComputerRef<Code, Input> takes input: &Input

Use ComputerRef when you want to avoid moving/cloning large values, or when recursive data should stay borrowed.

Naming map for this chapter

  • Eval, Explain = mode tags (Code values at type level).
  • Pair = input type.
  • ComputerComponent = compute capability slot on the context.
  • SumPair / SumValue / SumExplain = providers.
  • App = context choosing providers.

Practical rules

  • Start with one mode tag (Eval) and a generic Computer<Code, Input> impl.
  • Split into mode-specific impls only when behavior diverges.
  • Use UseDelegate<…> when dispatch should happen by Code.
  • Prefer ComputerRef for borrow-first recursive workflows.

Input-Based Dispatch with UseInputDelegate

This chapter shows how one compute component can route to different providers based on input type.

Goal:

  • define small handlers per expression node type,
  • wire them with UseInputDelegate,
  • dispatch enum variants with MatchWithValueHandlersRef.

If you remember one sentence, use this:

  • UseInputDelegate chooses a provider by input type.

Vocabulary first

  • Input type: the type being computed (for example Number, Plus, or MathExpr).
  • Input dispatch table: the new … { Type: Provider } block inside UseInputDelegate.
  • Dispatcher provider: a provider that routes enum variants (here: DispatchEval).

You can say the same idea in different ways:

  • input type in -> matching provider out,
  • one component, many input-specific handlers,
  • the context picks provider by input shape.

Complete example

use cgp::extra::dispatch::MatchWithValueHandlersRef;
use cgp::extra::handler::{CanComputeRef, ComputerRef, ComputerRefComponent, UseInputDelegate};
use cgp::prelude::*;

pub struct Eval;

#[derive(Debug, PartialEq)]
pub struct Number(pub u64);

#[derive(Debug, PartialEq)]
pub struct Plus<Expr> {
    pub left: Box<Expr>,
    pub right: Box<Expr>,
}

#[derive(Debug, PartialEq, CgpData)]
pub enum MathExpr {
    Number(Number),
    Plus(Plus<MathExpr>),
}

#[cgp_impl(new EvalNumber)]
impl<Context, Code> ComputerRef<Code, Number> for Context {
    type Output = u64;

    fn compute_ref(_context: &Context, _code: PhantomData<Code>, Number(value): &Number) -> Self::Output {
        *value
    }
}

#[cgp_impl(new EvalPlus)]
impl<Context, Code, Expr> ComputerRef<Code, Plus<Expr>> for Context
where
    Context: CanComputeRef<Code, Expr, Output = u64>,
{
    type Output = u64;

    fn compute_ref(context: &Context, code: PhantomData<Code>, Plus { left, right }: &Plus<Expr>) -> Self::Output {
        context.compute_ref(code, left) + context.compute_ref(code, right)
    }
}

pub struct Interpreter;

delegate_components! {
    Interpreter {
        ComputerRefComponent:
            UseInputDelegate<
                new EvalComponents {
                    MathExpr: DispatchEval,
                    Number: EvalNumber,
                    Plus<MathExpr>: EvalPlus,
                }
            >,
    }
}

#[cgp_impl(new DispatchEval)]
impl<Code> ComputerRef<Code, MathExpr> for Interpreter {
    type Output = u64;

    fn compute_ref(context: &Interpreter, code: PhantomData<Code>, expr: &MathExpr) -> Self::Output {
        <MatchWithValueHandlersRef as ComputerRef<Interpreter, Code, MathExpr>>::compute_ref(
            context, code, expr,
        )
    }
}

fn main() {
    let interpreter = Interpreter;
    let code = PhantomData::<Eval>;

    let expr = MathExpr::Plus(Plus {
        left: MathExpr::Number(Number(2)).into(),
        right: MathExpr::Number(Number(3)).into(),
    });

    assert_eq!(interpreter.compute_ref(code, &expr), 5);
}

What each part means

  • ComputerRef<Code, Input> is the compute capability for borrowed input.
  • EvalNumber handles Number input.
  • EvalPlus handles Plus input and recursively computes children.
  • DispatchEval handles top-level MathExpr by delegating variant routing.
  • UseInputDelegate<new EvalComponents { … }> is the input dispatch table on Interpreter.

Why DispatchEval is on Interpreter, but others use generic Context

You might notice:

  • EvalNumber and EvalPlus are implemented for generic Context.
  • DispatchEval is implemented for concrete Interpreter.

That split is intentional.

  • EvalNumber and EvalPlus are reusable node handlers. They only need capability bounds like Context: CanComputeRef<…>, so they can work in many contexts.
  • DispatchEval is the top-level entry-point router for this specific interpreter wiring. It relies on the context’s configured input-dispatch table for MathExpr, so keeping it on Interpreter makes the assembly point explicit.

Practical pattern:

  • make leaf/sub-expression providers generic and reusable,
  • make the top-level enum dispatcher context-specific.

How the routing actually works

For interpreter.compute_ref(code, &expr) where expr: MathExpr:

  1. Input is MathExpr, so UseInputDelegate picks DispatchEval.
  2. DispatchEval calls MatchWithValueHandlersRef, which matches the enum variant.
  3. If variant is Number, it routes to EvalNumber.
  4. If variant is Plus, it routes to EvalPlus.
  5. EvalPlus recursively calls compute_ref on sub-expressions.

So there are two levels:

  • level 1: input-type dispatch (UseInputDelegate),
  • level 2: enum-variant dispatch (MatchWithValueHandlersRef).

When to use MatchWithValueHandlersRef

Use MatchWithValueHandlersRef at the enum-dispatch boundary.

  • It belongs in the provider that handles the enum input itself (here: DispatchEval for MathExpr).
  • It does not belong in variant-specific providers (EvalNumber, EvalPlus), because those already handle concrete input types.

Rule of thumb:

  • top-level enum provider -> MatchWithValueHandlersRef,
  • variant handlers -> regular provider logic.

And by ownership style:

  • use MatchWithValueHandlers with by-value Computer,
  • use MatchWithValueHandlersRef with by-reference ComputerRef.

new EvalComponents explained

In this wiring:

UseInputDelegate<
    new EvalComponents {
        MathExpr: DispatchEval,
        Number: EvalNumber,
        Plus<MathExpr>: EvalPlus,
    }
>
  • EvalComponents is an arbitrary name for the generated input-dispatch table type.
  • It is not a reserved CGP keyword.
  • You can rename it (for example ExprHandlers, InputTable, EvalByInput).

What must match:

  • each key type (MathExpr, Number, Plus) must match the input type of the provider on the right.

Why the DispatchEval impl uses explicit trait syntax

This line uses fully-qualified syntax on purpose:

<MatchWithValueHandlersRef as ComputerRef<Interpreter, Code, MathExpr>>::compute_ref(...)

Reason: both ComputerRef and CanComputeRef define compute_ref, so explicit syntax avoids method-name ambiguity in current cgp usage.

What is doing the routing?

  • UseInputDelegate selects a provider by input type.
  • DispatchEval handles the top-level enum MathExpr.
  • MatchWithValueHandlersRef picks the matching variant handler (EvalNumber or EvalPlus).

You can read it as: input type in -> matching provider out.

Why this pattern is useful

  • each handler stays tiny and focused,
  • adding a new variant means adding one new handler + one wiring line,
  • no giant manual match function is needed in one place.

Practical rule: when behavior depends on input shape, start with UseInputDelegate and keep handlers small.

Auto Dispatch with #[cgp_auto_dispatch]

This chapter shows how to remove enum dispatch boilerplate with #[cgp_auto_dispatch].

Goal:

  • define a normal trait once,
  • implement it for each variant payload type,
  • get automatic enum-level dispatch for free.

If you remember one sentence, use this:

  • #[cgp_auto_dispatch] generates the “match enum variant, then call trait on payload” glue for you.

When this is useful

You often have:

  • an enum (for example Shape),
  • per-variant structs (Circle, Rectangle),
  • the same trait implemented on each payload type.

Without the macro, you would write manual impl Trait for Enum with a match. With the macro, CGP generates that dispatch impl.

Complete example

use core::f64::consts::PI;

use cgp::prelude::*;

#[derive(CgpData)]
pub enum Shape {
    Circle(Circle),
    Rectangle(Rectangle),
}

pub struct Circle {
    pub radius: f64,
}

pub struct Rectangle {
    pub width: f64,
    pub height: f64,
}

#[cgp_auto_dispatch]
pub trait HasArea {
    fn area(&self) -> f64;
}

impl HasArea for Circle {
    fn area(&self) -> f64 {
        PI * self.radius * self.radius
    }
}

impl HasArea for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

#[cgp_auto_dispatch]
pub trait CanScale {
    fn scale(&mut self, factor: f64);
}

impl CanScale for Circle {
    fn scale(&mut self, factor: f64) {
        self.radius *= factor;
    }
}

impl CanScale for Rectangle {
    fn scale(&mut self, factor: f64) {
        self.width *= factor;
        self.height *= factor;
    }
}

fn main() {
    let mut shape = Shape::Rectangle(Rectangle {
        width: 2.0,
        height: 2.0,
    });

    assert_eq!(shape.area(), 4.0);

    shape.scale(2.0);
    assert_eq!(shape.area(), 16.0);
}

What each part does

  • #[derive(CgpData)] marks enum data metadata needed for auto dispatch.
  • #[cgp_auto_dispatch] on HasArea generates enum dispatch for &self style calls.
  • #[cgp_auto_dispatch] on CanScale generates enum dispatch for &mut self style calls.
  • Your manual impls stay focused on payload types (Circle, Rectangle).

So the enum behaves like it implements the trait, without hand-written match boilerplate.

What this is (and is not)

  • This is a dispatch convenience macro.
  • It is not a component wiring macro.

In other words:

  • #[cgp_auto_dispatch] helps with enum method forwarding.
  • #[cgp_component] + #[cgp_impl] + delegate_components! helps with provider selection by context.

Both are useful. They solve different problems.

Practical checklist

  1. Derive CgpData on the enum.
  2. Add #[cgp_auto_dispatch] on the trait you want forwarded.
  3. Implement that trait on each payload type.
  4. Call methods on enum values directly.

Practical rule: use #[cgp_auto_dispatch] when your enum dispatch is straightforward and you want to avoid manual forwarding code.

Type Components and Context Bounds

This chapter introduces #[cgp_type] in the simplest useful way.

Goal:

  • define a type capability with #[cgp_type],
  • choose the concrete type in context wiring,
  • use trait bounds on Context inside a provider.

If you remember one sentence, use this:

  • a type component lets the context choose a type the provider depends on.

Vocabulary first

  • Type component: a CGP capability that provides an associated type.
  • Context bound: a where constraint on Context/Self in provider impls.
  • Type provider: the concrete type selection in delegate_components! (usually UseType<…>).

Complete example

use cgp::prelude::*;

#[cgp_type]
pub trait HasGreetingType {
    type Greeting;
}

#[cgp_component(Greeter)]
pub trait CanGreet: HasGreetingType {
    fn greet(&self, name: &str) -> Self::Greeting;
}

#[cgp_impl(new BuildGreeting)]
impl Greeter
where
    Self: HasGreetingType,
    String: Into<Self::Greeting>,
{
    fn greet(&self, name: &str) -> Self::Greeting {
        format!("Hello, {}!", name).into()
    }
}

pub struct AppText;

delegate_components! {
    AppText {
        GreetingTypeProviderComponent: UseType<String>,
        GreeterComponent: BuildGreeting,
    }
}

pub struct AppBoxed;

delegate_components! {
    AppBoxed {
        GreetingTypeProviderComponent: UseType<Box<str>>,
        GreeterComponent: BuildGreeting,
    }
}

fn main() {
    let text = AppText.greet("Ada");
    let boxed = AppBoxed.greet("Grace");

    assert_eq!(text, "Hello, Ada!".to_owned());
    assert_eq!(boxed, Box::<str>::from("Hello, Grace!"));
}

What each part means

  • #[cgp_type] on HasGreetingType defines a type capability.
  • CanGreet uses that capability by returning Self::Greeting.
  • BuildGreeting is one provider implementation of Greeter.
  • The where clause in BuildGreeting is the key context bound:
    • Self: HasGreetingType means this provider needs a context that supplies Greeting.
    • String: IntoSelf::Greeting means the provider can build a String and convert it to the context-chosen greeting type.

Where GreetingTypeProviderComponent comes from

#[cgp_type] generates the wiring key used by delegate_components!.

So these names correspond to two different things:

  • HasGreetingType = trait you wrote.
  • GreetingTypeProviderComponent = generated component key used in wiring (generated from the associated type name Greeting as Greeting + TypeProviderComponent).

Why the same provider works in two contexts

BuildGreeting is generic over Self.

  • AppText chooses Greeting = String.
  • AppBoxed chooses Greeting = Box.

Since both satisfy String: IntoSelf::Greeting, both can reuse the same provider.

This is the practical value of context bounds:

  • provider says what it needs,
  • context wiring chooses values that satisfy those needs.

Naming guide

  • Arbitrary names: HasGreetingType, BuildGreeting, AppText, AppBoxed.
  • Must match references:
    • Self::Greeting in CanGreet and provider bounds,
    • GreetingTypeProviderComponent in delegate_components!,
    • GreeterComponent: BuildGreeting wiring.

Practical checklist

  1. Define a type trait with #[cgp_type].
  2. Use that associated type in a consumer trait.
  3. Add provider bounds describing required capabilities/conversions.
  4. Choose concrete type with UseType<…> per context.
  5. Wire the main behavior component to your provider.

Practical rule: use #[cgp_type] when provider logic should be reusable but output/input type choice should stay in context wiring.

Defining Components with #[cgp_component]

This chapter explains how to define CGP components in v0.6.1 style.

Goal:

  • understand the two #[cgp_component] forms,
  • know what names are generated,
  • learn practical naming and usage best practices.

If you remember one sentence, use this:

  • #[cgp_component] turns a consumer trait into a wireable CGP capability.

The two macro forms

#[cgp_component] has two forms.

#[cgp_component(Greeter)]
pub trait CanGreet {
    fn greet(&self);
}

This is the form you should use most of the time.

Form B (explicit key/value form)

#[cgp_component {
    name: GreeterComponent,
    provider: Greeter,
    context: Context,
}]
pub trait CanGreet {
    fn greet(&self);
}

For this example, Form B is equivalent to Form A.

What gets generated

For #[cgp_component(Greeter)]:

  • consumer trait: CanGreet (the trait you wrote),
  • provider trait: Greeter (from macro argument),
  • component key type: GreeterComponent (generated default name).

Default key naming rule:

  • ProviderName + Component

So Greeter becomes GreeterComponent.

Complete example

use cgp::prelude::*;

#[cgp_component(Greeter)]
pub trait CanGreet {
    fn greet(&self) -> String;
}

#[cgp_auto_getter]
pub trait HasName {
    fn name(&self) -> &str;
}

#[cgp_impl(new GreetHello)]
impl Greeter
where
    Self: HasName,
{
    fn greet(&self) -> String {
        format!("Hello, {}!", self.name())
    }
}

#[derive(HasField)]
pub struct Person {
    pub name: String,
}

delegate_components! {
    Person {
        GreeterComponent: GreetHello,
    }
}

fn main() {
    let person = Person { name: "Ada".into() };
    assert_eq!(person.greet(), "Hello, Ada!");
}

How to read this:

  1. CanGreet defines what callers can ask for.
  2. Greeter is the provider trait generated for implementations.
  3. GreeterComponent is the key used in context wiring.
  4. GreetHello is the concrete provider selected for Person.

When to use the explicit form

Use Form B only when you need customization.

Typical reasons:

  • custom component key name (name: …),
  • custom provider trait name (provider: …),
  • custom context generic identifier (context: …),
  • advanced dispatch derivation (derive_delegate: […]).

For basic tutorials and most app code, Form A is clearer.

Best practices (v0.6.1)

  • Prefer Form A: #[cgp_component(ProviderName)].
  • Use a consumer trait name like CanX (CanGreet, CanCompute, etc.).
  • Use a provider trait name that sounds like behavior role (Greeter, Fetcher, Formatter).
  • Let generated …Component names stand unless you have naming conflicts.
  • Keep consumer traits small and focused (one capability area).
  • Use direct delegate_components! on context types (v0.6.1 style).

Quick naming map template

For:

#[cgp_component(FooProvider)]
pub trait CanDoFoo {
    fn do_foo(&self);
}

Expect:

  • consumer trait: CanDoFoo
  • provider trait: FooProvider
  • component key: FooProviderComponent

Use this map whenever names feel confusing.

Type Components and Sub-Enum Upcasting

This chapter combines two CGP ideas that unlock reusable transformation providers:

  • choose output types through a type component (#[cgp_type] + UseType),
  • construct with a smaller local enum, then upcast to the full enum.

If you remember one sentence, use this:

  • providers stay generic, contexts choose concrete types.

Vocabulary first

  • Type component: a CGP component whose job is to provide an associated type.
  • Type provider: the concrete type chosen in wiring (for example UseType).
  • Sub-enum: a small enum that contains only variants a provider needs to build.
  • Upcast: safe promotion from that sub-enum into a larger target enum.

Why this pattern exists

Suppose you write providers that convert math expressions to Lisp expressions.

Without type components, providers would hard-code LispExpr everywhere. With type components, providers can say:

  • “I need some LispExpr type from context.”

Then each context chooses the concrete enum.

Complete example

use cgp::core::field::impls::CanUpcast;
use cgp::extra::dispatch::MatchWithValueHandlersRef;
use cgp::extra::handler::{CanComputeRef, ComputerRef, ComputerRefComponent, UseInputDelegate};
use cgp::prelude::*;

pub struct ToLisp;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Number(pub u64);

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Plus<Expr> {
    pub left: Box<Expr>,
    pub right: Box<Expr>,
}

#[derive(Debug, PartialEq, Eq, Clone, CgpData)]
pub enum MathExpr {
    Number(Number),
    Plus(Plus<MathExpr>),
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Ident(pub String);

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct List<Expr>(pub Vec<Box<Expr>>);

#[derive(Debug, PartialEq, Eq, Clone, CgpData)]
pub enum LispExpr {
    List(List<LispExpr>),
    Number(Number),
    Ident(Ident),
}

// Local subset enum: only variants these providers need to construct
#[derive(Debug, PartialEq, Eq, Clone, CgpData)]
enum LispSubExpr<Expr> {
    List(List<Expr>),
    Number(Number),
    Ident(Ident),
}

#[cgp_type]
pub trait HasLispExprType {
    type LispExpr;
}

#[cgp_impl(new ToLispNumber)]
impl<Context, Code, LispExprT> ComputerRef<Code, Number> for Context
where
    Context: HasLispExprType<LispExpr = LispExprT>,
    LispSubExpr<LispExprT>: CanUpcast<LispExprT>,
{
    type Output = LispExprT;

    fn compute_ref(_context: &Context, _code: PhantomData<Code>, number: &Number) -> Self::Output {
        LispSubExpr::Number(number.clone()).upcast(PhantomData)
    }
}

#[cgp_impl(new ToLispPlus)]
impl<Context, Code, Expr, LispExprT> ComputerRef<Code, Plus<Expr>> for Context
where
    Context: HasLispExprType<LispExpr = LispExprT> + CanComputeRef<Code, Expr, Output = LispExprT>,
    LispSubExpr<LispExprT>: CanUpcast<LispExprT>,
{
    type Output = LispExprT;

    fn compute_ref(context: &Context, code: PhantomData<Code>, Plus { left, right }: &Plus<Expr>) -> Self::Output {
        let left_expr = context.compute_ref(code, left);
        let right_expr = context.compute_ref(code, right);
        let plus_ident = LispSubExpr::Ident(Ident("+".to_owned())).upcast(PhantomData);

        LispSubExpr::List(List(vec![
            plus_ident.into(),
            left_expr.into(),
            right_expr.into(),
        ]))
        .upcast(PhantomData)
    }
}

pub struct Interpreter;

delegate_components! {
    Interpreter {
        LispExprTypeProviderComponent: UseType<LispExpr>,
        ComputerRefComponent:
            UseInputDelegate<
                new ToLispComponents {
                    MathExpr: DispatchToLisp,
                    Number: ToLispNumber,
                    Plus<MathExpr>: ToLispPlus,
                }
            >,
    }
}

#[cgp_impl(new DispatchToLisp)]
impl<Code> ComputerRef<Code, MathExpr> for Interpreter {
    type Output = LispExpr;

    fn compute_ref(context: &Interpreter, code: PhantomData<Code>, expr: &MathExpr) -> Self::Output {
        <MatchWithValueHandlersRef as ComputerRef<Interpreter, Code, MathExpr>>::compute_ref(
            context, code, expr,
        )
    }
}

fn main() {
    let interpreter = Interpreter;
    let code = PhantomData::<ToLisp>;

    let expr = MathExpr::Plus(Plus {
        left: MathExpr::Number(Number(2)).into(),
        right: MathExpr::Number(Number(3)).into(),
    });

    assert_eq!(
        interpreter.compute_ref(code, &expr),
        LispExpr::List(List(vec![
            LispExpr::Ident(Ident("+".to_owned())).into(),
            LispExpr::Number(Number(2)).into(),
            LispExpr::Number(Number(3)).into(),
        ]))
    );
}

What each term maps to in this code

  • Context: Interpreter.
  • Type component capability: HasLispExprType.
  • Type provider choice: UseType.
  • Sub-enum: LispSubExpr.
  • Upcast boundary: LispSubExpr<…>: CanUpcast<…> and .upcast(PhantomData).

Where LispExprTypeProviderComponent comes from

#[cgp_type] on HasLispExprType generates CGP wiring artifacts, including the component key type used in delegation.

That is why this wiring key exists:

  • LispExprTypeProviderComponent

So in practice:

  • HasLispExprType is the trait you write,
  • LispExprTypeProviderComponent is the generated key used in delegate_components!.

Why LispSubExpr is generic

List contains nested expression values, so the sub-enum needs a type parameter for “what expression type goes inside the list.”

In providers, we instantiate it as LispSubExpr:

  • build local pieces as LispSubExpr,
  • upcast to LispExprT.

This keeps providers generic over the final output type.

Step-by-step flow of one call

For interpreter.compute_ref(PhantomData::, &math_expr):

  1. Input type is MathExpr, so input dispatch picks DispatchToLisp.
  2. DispatchToLisp uses MatchWithValueHandlersRef to route by enum variant.
  3. Number variant goes to ToLispNumber; Plus variant goes to ToLispPlus.
  4. ToLispPlus recursively computes left/right subexpressions.
  5. Provider constructs LispSubExpr values (Ident, List, etc.).
  6. Each local value is upcast into the full context-chosen output type.

Why this is better than hard-coding LispExpr

  • providers can be reused in contexts that choose a different output enum,
  • providers only construct variants they care about,
  • upcast keeps construction safe and type-checked.

Naming guide (what is arbitrary vs fixed)

  • Arbitrary: HasLispExprType, LispSubExpr, ToLispComponents, ToLispPlus, ToLispNumber.
  • Must stay consistent:
    • trait associated type name used in bounds (LispExpr),
    • wiring key generated from the type trait (LispExprTypeProviderComponent),
    • type/provider pairs in UseInputDelegate.

Practical checklist

  1. Define a type component with #[cgp_type].
  2. Choose concrete type in context with UseType<…>.
  3. Build a local sub-enum for only needed variants.
  4. Add CanUpcast bounds from sub-enum to final output type.
  5. Upcast local pieces as you build outputs.

Practical rule: when providers only need part of a large output enum, build with a sub-enum and upcast into the context-chosen final type.