diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..bc780d74 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,87 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Understanding CGP first + +This repository **is** the implementation of Context-Generic Programming (CGP). Before reading or +writing any CGP code here, **always invoke the `/cgp` skill** to load the fundamentals (consumer vs. +provider traits, `#[cgp_component]`/`#[cgp_impl]`/`#[cgp_fn]`, `delegate_components!`, `HasField`, +`UseDelegate`, check traits, etc.). Re-invoke it whenever you navigate into unfamiliar parts of the +codebase — the macros and core traits here are the ground truth that the skill describes, so the two +should always be read together. + +The canonical export surface for users is `cgp::prelude` — see +[crates/main/cgp/src/prelude.rs](crates/main/cgp/src/prelude.rs), which re-exports +`cgp_core::prelude` + `cgp_extra::prelude`. When unsure what a name resolves to, start from the +prelude re-exports in [crates/main/cgp-core/src/prelude.rs](crates/main/cgp-core/src/prelude.rs). + +## Commands + +This is a Cargo workspace (edition 2024, resolver 3). Toolchain is pinned to **1.93** via +[rust-toolchain.toml](rust-toolchain.toml). Nearly every crate is `#![no_std]` — keep new code +`no_std`-compatible (use `core`/`alloc`, gate `std`/`alloc` usage behind features as existing crates +do). + +- **Format** (requires nightly — `.rustfmt.toml` uses unstable `group_imports`/`imports_granularity`): + `cargo +nightly fmt --all` (check: `cargo +nightly fmt --all -- --check`) +- **Lint:** `cargo clippy --all-features --all-targets -- -D warnings` + and `cargo clippy --no-default-features --all-targets -- -D warnings` +- **Test** (uses `cargo-nextest`): `cargo nextest run --all-features --no-fail-fast --workspace` +- **Single test crate / test:** `cargo nextest run -p cgp-tests` or target one file with the + standard test harness, e.g. `cargo test -p cgp-tests --test component` +- Many "tests" are **compile-time wiring checks** (`check_components!` / + `delegate_and_check_components!`) and **macro-expansion snapshots** — for these, a successful + `cargo build`/`cargo test` compilation *is* the passing test. A wiring mistake surfaces as a + compile error, not a runtime failure. + +## Architecture: layered micro-crates + +Crates are organized so that low-level primitives have no knowledge of the high-level facade. Work +inward (core/macros) when changing fundamentals, outward (main) only to adjust the public surface. + +- **`crates/macros/`** — the proc-macro pipeline. `cgp-macro` is a thin `#[proc_macro]` entrypoint + that forwards to `cgp-macro-lib` (one module per macro), which in turn builds on + **`cgp-macro-core`** — this is where the real parsing, AST types, and codegen live (see + `cgp-macro-core/src/{types,functions,visitors,macros}/`). When a macro misbehaves, the logic to + fix is almost always in `cgp-macro-core`, not the entrypoint crate. `cgp-async-macro` provides + `#[async_trait]`; `cgp-extra-macro{,-lib}` host the extra-feature macros. + +- **`crates/core/`** — the foundational runtime traits the macros expand into: + - `cgp-component` — the wiring machinery: `DelegateComponent`, `IsProviderFor`, + `CanUseComponent`, `UseContext`, `UseDelegate`, `UseField`, `WithProvider`, etc. + - `cgp-type` — abstract types: `HasType`, `TypeProvider`, `UseType`. + - `cgp-field` — `HasField` and extensible data: `Cons`/`Nil`, `Symbol`, `Index`, `Field`, + builders/extractors for records and variants. + - `cgp-error` — `HasErrorType`, `CanRaiseError`, `CanWrapError`. + - `cgp-base-types` — the lowest-level type-level primitives (`Symbol`/`Chars`/`Cons`/`Nil`/path). + +- **`crates/extra/`** — higher-level building blocks layered on core: `cgp-handler`, + `cgp-dispatch`, `cgp-monad`, `cgp-run`, `cgp-runtime`, `cgp-field-extra`, `cgp-error-extra`. + +- **`crates/main/`** — facade crates that only re-export. `cgp` is the crate users depend on + (`cgp = core + extra`, exposing `cgp::prelude`). `cgp-core`/`cgp-extra`/`cgp-base`/ + `cgp-base-extra` are intermediate bundles. Changes here are almost always just re-export plumbing. + +- **`crates/standalone/error/`** — pluggable error backends implementing the `cgp-error` traits: + `cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`. These are opt-in and not part of the + default `cgp` facade. + +- **`crates/tests/`** — `cgp-tests` exercises real wiring and the user-facing macros end-to-end; + `cgp-macro-tests` covers parser corner cases plus **expansion snapshots** via the `snapshot_*` + proc macros in `cgp-macro-test-util{,-lib}` (which pretty-print generated code with + `prettyplease`). When you change macro codegen, expect snapshot output to change — update and + review the expanded code, since it is the contract users see. + +## Conventions specific to this repo + +- All versions are kept in lockstep at the workspace level (currently **0.7.0**); inter-crate + dependencies are declared once in the root [Cargo.toml](Cargo.toml) `[workspace.dependencies]` + and referenced with `{ workspace = true }`. Add new crates to the `members` list and the + workspace dependency table together. +- The crate split is deliberate (coherence-friendly micro-crates). When adding functionality, + place it in the lowest layer that makes sense and re-export upward through the facade crates, + rather than adding cross-layer dependencies that skip the hierarchy. +- See [CHANGELOG.md](CHANGELOG.md) for the evolution of macro syntax — it is the most reliable + record of which macro forms are current vs. removed (e.g. `#[cgp_context]` was removed, + `ProvideType` → `TypeProvider`). diff --git a/crates/macros/cgp-macro-core/CLAUDE.md b/crates/macros/cgp-macro-core/CLAUDE.md new file mode 100644 index 00000000..fe2d3921 --- /dev/null +++ b/crates/macros/cgp-macro-core/CLAUDE.md @@ -0,0 +1,80 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +`cgp-macro-core` is the pure-logic core of the CGP proc-macro suite. **Always invoke the `/cgp` +skill before working here** — every type in this crate exists to generate one of the CGP constructs +the skill describes (`#[cgp_component]`, `#[cgp_impl]`, `#[cgp_fn]`, `delegate_components!`, +`check_components!`, `#[derive(HasField)]`, …). You cannot correctly change the *output* of a macro +without knowing the *expansion* it is supposed to produce, and that expansion is documented by +`/cgp`. The workspace-level [../../../CLAUDE.md](../../../CLAUDE.md) covers build/test/lint commands +and the crate hierarchy. + +## What this crate is (and isn't) + +- It contains **no `#[proc_macro]` entrypoints** and does **not depend on `proc-macro`** — only + `syn` (with `full`, `extra-traits`, `visit`, `visit-mut`), `quote`, `proc-macro2`, `itertools`. + Everything operates on `proc_macro2::TokenStream` and `syn` AST. +- The proc-macro pipeline is **`cgp-macro` (entrypoints) → `cgp-macro-lib` (per-macro glue) → + `cgp-macro-core` (this crate, all the logic)**. A `cgp-macro-lib` function is thin: `syn::parse2` + the attr/item, build a `cgp_macro_core::types::::Item*`, run its transform pipeline, and + wrap the result in `quote!`. See `cgp-macro-lib/src/cgp_component.rs` for the canonical shape. +- Because it is `proc-macro`-free, its parsers and codegen are **unit-testable as plain functions**. + Tests live in the sibling `crates/tests/cgp-macro-tests` crate (parser corner cases like + `IdentWithTypeArgs`, plus expansion **snapshots** via the `snapshot_*` macros). Prefer adding + coverage there over ad-hoc verification. + +## Module map + +- **[src/types/](src/types/)** — the bulk of the crate. One submodule per user-facing macro + (`cgp_component`, `cgp_impl`, `cgp_provider`, `cgp_fn`, `cgp_getter`, `cgp_auto_getter`, + `cgp_type`, `cgp_data`, `delegate_component`, `check_components`, + `delegate_and_check_components`, `namespace`, `product`, `sum`), plus shared building-block types: + `attributes/` (parsing of `#[uses]`, `#[use_type]`, `#[use_provider]`, `#[derive_delegate]`, + `#[default_impl]`), `generics/`, `field/`, `getter/`, `implicits/`, `ident/`, `path/`, `keyword`. +- **[src/functions/](src/functions/)** — free helper functions: identifier case conversion + (`camel_case`/`snake_case`), `parse_internal`, generics merging, delegated-impl synthesis, + field/getter/implicit-argument parsing, `strip`. +- **[src/visitors/](src/visitors/)** — `syn` `VisitMut` passes that rewrite ASTs. `replace_self` + (receiver/type/value) is the heart of `#[cgp_impl]`: it rewrites `self`/`Self` into the explicit + `context`/`Context`. Also `replace_provider`, `remove_self_path`, `self_assoc_type`, and + `substitute_abstract_type` (the `#[use_type]` machinery). **Do AST rewriting with these visitors, + never with string manipulation.** +- **[src/exports.rs](src/exports.rs)** — see "hygiene" below. +- **[src/traits/](src/traits/)** — small internal traits (`ToType`, `IsKeyword`, bound helpers). +- **[src/macros/](src/macros/)** — internal `macro_rules!`: `parse_internal!`, `export_construct(s)!`, + `define_keyword!`. +- **[src/vendor.rs](src/vendor.rs)** — re-exports `quote::quote` so exported `macro_rules!` can use + it from downstream crates. + +## Conventions you must follow + +**1. Construct = a type implementing `Parse` and/or `ToTokens`.** Input is parsed by implementing +`syn::parse::Parse`; code is emitted by implementing `quote::ToTokens`. Add new syntax by writing a +type with these impls rather than hand-rolling token munging. + +**2. Build AST nodes with `parse_internal!`, not by hand.** `parse_internal!(#some_tokens ...)` +(in [src/functions/parse_internal.rs](src/functions/parse_internal.rs)) quasi-quotes tokens and +parses them into the target `syn` type, attaching a descriptive error (including the offending +tokens with the `::cgp::macro_prelude::` prefix stripped) on failure. It expands to a `?` +expression, so call it inside a `syn::Result`-returning function. + +**3. Reference CGP items through `exports.rs`, never by hardcoded path.** Each name in +[src/exports.rs](src/exports.rs) (`IsProviderFor`, `DelegateComponent`, `UseField`, `HasField`, …) +is a zero-sized marker struct whose `ToTokens` emits the fully-qualified path +`::cgp::macro_prelude::`. Generated code interpolates these markers (e.g. +`use crate::exports::IsProviderFor;` then `#is_provider_for`) so the expansion is hygienic and the +user only needs `cgp` in scope. **To emit a new CGP item, add it to `export_constructs!` in +`exports.rs` and interpolate the marker** — don't write `::cgp::...` path literals inline. + +**4. Codegen is a staged transform pipeline.** Each macro type moves through explicit stages rather +than emitting in one pass. The reference shape is `cgp_component`: +`ItemCgpComponent { args, item_trait }` → `.preprocess()` → `PreprocessedCgpComponent` → +`.eval()` → `Evaluated…` → `.to_items()` → `Vec` (driven by +`cgp-macro-lib/src/cgp_component.rs` as `item.preprocess()?.eval()?.to_items()?`). Other macros use +their own stage names — look for `item.rs`, `preprocessed.rs`/`lowered.rs`, `evaluated/`, and +`to_*` methods within each `types//` directory. When extending a macro, slot your change +into the existing stage rather than collapsing the pipeline. + +**5. Custom keywords go through `define_keyword!` + `IsKeyword`** (see +[src/macros/keyword.rs](src/macros/keyword.rs) and `types/keyword*.rs`). diff --git a/crates/main/cgp-core/src/prelude.rs b/crates/main/cgp-core/src/prelude.rs index 3f779db3..8fce4b45 100644 --- a/crates/main/cgp-core/src/prelude.rs +++ b/crates/main/cgp-core/src/prelude.rs @@ -17,7 +17,7 @@ pub use cgp_field::traits::{ pub use cgp_field::types::{Chars, Cons, Either, Field, Index, Life, Nil, Symbol, Void}; pub use cgp_macro::{ BuildField, CgpData, CgpRecord, CgpVariant, ExtractField, FromVariant, HasField, HasFields, - Product, Sum, Symbol, cgp_auto_getter, cgp_component, cgp_fn, cgp_getter, cgp_impl, + Path, Product, Sum, Symbol, cgp_auto_getter, cgp_component, cgp_fn, cgp_getter, cgp_impl, cgp_namespace, cgp_new_provider, cgp_provider, cgp_type, check_components, delegate_and_check_components, delegate_components, product, }; diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md new file mode 100644 index 00000000..714573c7 --- /dev/null +++ b/docs/CLAUDE.md @@ -0,0 +1,93 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +This directory is the CGP knowledge base — agent-maintained documentation whose job is to record the full semantics of every CGP construct. Read [README.md](README.md) for the background and motivation behind it. The rules below govern how to keep it correct. + +## The synchronization rule + +**Documentation here must stay in sync with the code, and keeping it in sync is part of the change, not a follow-up.** Whenever you modify a CGP construct — its accepted syntax, the code it expands to, its defaults, its error behavior, or its relationships to other constructs — you must update the matching reference document in the same change. A reference document that describes behavior the code no longer has is actively harmful: the next agent will trust it and be misled. Treat a stale document as a bug in the change that made it stale. + +This rule applies in both directions. If you add a new construct, add its reference document and register it in the [reference index](reference/README.md). If you remove a construct, remove or supersede its document and update the index. If you change a construct's expansion, revise the "Expansion" section of its document so the desugaring shown still matches what the macro emits. + +The implementation in [crates/macros/cgp-macro-core](../crates/macros/cgp-macro-core), the expansion snapshots in [crates/tests/cgp-macro-tests](../crates/tests/cgp-macro-tests), the reference documents here, and the agent skill under [skills/](skills/README.md) are four views of the same truth. When they disagree, that disagreement is a defect. The snapshots are the most mechanical check on whether a document's "Expansion" section is honest — when you doubt what a macro emits, generate or read the snapshot rather than guessing. The skill is the most distilled view, so a change to a construct's behavior must propagate all the way out to it; see [the skills directory](#the-skills-directory) for how to keep it in sync. + +## Authoring conventions + +Invoke the `/cgp` skill before writing or revising any reference document. The skill is the authoritative source for CGP semantics and terminology; the reference documents must use the same vocabulary (consumer trait, provider trait, provider, wiring, impl-side dependency, and so on) so that a reader moving between the skill and the documents never has to reconcile two dialects. + +Write every reference document in the dual-reader prose style (the `/dual-reader-prose` skill). Each document is read both by agents scanning for one specific fact and by agents reading start-to-finish for complete understanding, so every section opens with a self-contained topic sentence that states its point, followed by elaboration. Avoid orphaned bullet lists; frame any list with a sentence before and after. Use code blocks freely — showing the exact expansion is the whole point — but the prose around them must carry the meaning on its own. + +Prefer code snippets that match the worked examples in [examples/](examples/). When a reference document's Examples section (or any illustrative snippet) needs running code, draw on the contexts, components, and providers used in an example document rather than inventing a fresh scenario, so the same vocabulary and the same running use cases recur across the whole knowledge base. When a review turns up a snippet built on an ad-hoc scenario that an example already covers better, replace it with the example-aligned version as part of that review. The examples directory is described in [examples/README.md](examples/README.md), and the rules for maintaining it are below. + +Verify against the source before writing, not from memory. Read the construct's implementation in `cgp-macro-core` (its `types//` module and the `cgp-macro-lib` entry that drives it) and any tests that exercise it. The "Expansion" section is a claim about generated code; it must reflect what the macro actually produces today, including the real default identifiers (for example, `#[cgp_component]` defaults the context type to `__Context__` and the component name to `{Provider}Component`), not idealized names used for teaching. + +Document the present, not the history. A reference document describes what a construct does now, written as though it had always behaved that way. Do not record how the code reached its current state: no changelog entries, no version numbers attached to behavior, and no "renamed from", "previously", "used to", "no longer", or notes about a past mismatch between the code and the skill or an earlier draft of the document. These historical artifacts accumulate into noise that ages badly — a reader cannot tell which "recently" is which, and a comparison against a former state describes something that no longer exists. When you correct a discrepancy, rewrite the prose to state the current behavior and delete the old wording entirely, rather than leaving a trace of what it used to say. The one place a document describes a deviation is the Known issues section, and even there the deviation is a current one, not a past one. + +## The examples directory + +The [examples/](examples/) directory holds self-contained worked examples — one document per use case, each developing a realistic scenario from its contexts and components through to the wiring that connects them. Examples exist for two reasons: they are the canonical source of code snippets the reference documents reuse (see the note in Authoring conventions above), and they are the raw material an agent draws on when writing expanded documentation such as a tutorial or guide. An example is judged by whether it is quotable and correct, not by whether it covers every detail. + +An example is not a second copy of the reference. A reference document explains one construct completely — its syntax, exact expansion, and corner cases — while an example shows several constructs cooperating to solve a problem and deliberately leaves the mechanics to the reference. Keep the prose in an example light: enough to make the code legible, a short note on which CGP concept each step demonstrates, and a link to the reference document that owns that concept. Do not re-explain a construct an example uses; link to it instead. Examples are still subject to the synchronization rule — code that no longer reflects current CGP is a bug — so verify every snippet against the source the same way you would a reference document's Expansion section. + +To add a new example from a source — example code, an article, a tutorial, or any external write-up — treat the source as a *reference for the scenario and the patterns*, then write a fresh, self-contained example document that stands on its own. Do not cite, name, link, or otherwise point back to the original source; the example must read as native knowledge-base material with no dependency on where the idea came from. Re-derive the code in current CGP vocabulary and verify it against the implementation rather than copying the source's code verbatim, since the source may use older syntax or a different dialect. Give the example its own coherent narrative arc — usually a progression from the simplest form of the use case to the fully wired and composed version — rather than mirroring the source's structure. + +Follow the shape of the existing examples. Open with a level-one heading naming the use case and a one-sentence summary of what it demonstrates and why it is a useful template. Follow that with a short framed list of the concepts the example demonstrates, each linking to its reference document, and a note of any shared assumption such as `use cgp::prelude::*;`. Then develop the use case in titled sections, each introducing one step of the progression with a sentence of context, the code, and a brief concept note pointing into the reference. Register the new document in the catalog in [examples/README.md](examples/README.md) in the same change. + +When an example would demonstrate a concept the reference does not yet cover, document the concept where it belongs rather than explaining it inside the example. Add the missing detail to the relevant reference document, or — when the concept is a cross-cutting idea that ties several constructs together — add a new page under [concepts/](concepts/) and register it in the [concepts index](concepts/README.md). The example then links to that documentation like any other, keeping the example itself focused on the use case rather than on teaching a new construct. + +## The skills directory + +The [skills/](skills/README.md) directory holds the agent skills built from this knowledge base — currently the `cgp` skill, the authoritative orientation an agent loads before reading or writing CGP code. The skill is a distilled, self-contained synthesis of the reference, concepts, and examples: a top-level `SKILL.md` carrying the mental model and a router, plus a `references/` set of sub-skills, one per topic area, each giving enough detail and worked examples to make an agent proficient in that area without further lookup. It is deliberately lighter than the reference documents — it teaches how to read and write CGP, not every corner case — and it is the skill that `/cgp` resolves to, replacing any earlier version. + +The skill is bound by the synchronization rule like everything else here, and because it is the most distilled view it is also the easiest to leave stale. When you change a construct's syntax, expansion, defaults, or recommended form, update the matching sub-skill in the same change: revise the affected sub-skill in `skills/cgp/references/`, the router and reading cheat-sheet in `skills/cgp/SKILL.md` if the change touches a core construct, and the triggering `description` if you add or rename a construct an agent should recognize. The skill must never teach syntax the code no longer has. When a form becomes legacy — as `UseDelegate`/`#[derive_delegate]` did when the `open` statement became the preferred dispatch — the skill leads with the current form and keeps the legacy one only as a clearly-labeled note for *reading* existing code, mirroring how the reference documents treat it. + +Two constraints are unique to the skill because it is deployed on its own, copied out of this repository. First, it must be self-contained: no link may point outside the skill directory — not to `../reference/`, not to any repo path — because those targets will not exist where the skill runs. Cross-links between sub-skills are plain relative filenames (`[wiring](wiring.md)`); for exhaustive detail that the skill deliberately omits, link to the online knowledge base at `https://github.com/contextgeneric/cgp/tree/main/docs` (a file as `…/blob/main/docs/`, a directory as `…/tree/main/docs/`) and ask the agent to fetch it when needed, rather than assuming a local copy. Second, the skill must use the same vocabulary as the reference — consumer trait, provider trait, provider, wiring, impl-side dependency, component — so an agent moving between the skill and the documents never reconciles two dialects. + +Verify the skill against the source the same way you verify a reference document: every snippet must reflect current v0.7.0 syntax, and when you doubt a form, compile a representative snippet against the crates (a scratch test under [crates/tests/cgp-tests](../crates/tests/cgp-tests) is the quickest check) rather than trusting memory or an older draft. The skill ships with an eval set under `skills/cgp/evals/` and a workspace of benchmark runs; when a change alters what good CGP code looks like, re-run or extend the evals so the skill's quality stays measured rather than assumed. + +## Document structure + +Each reference document follows the same shape so readers can navigate any of them by habit. Open with a level-one heading naming the construct and a one-sentence summary of what it is. Then proceed through these sections, using the same headings: + +- **Purpose** — the problem the construct solves and why it exists, in prose. +- **Syntax** — the accepted forms of the construct, with the meaning of each argument and option. +- **Syntax Grammar** — present only for a macro that has custom syntax (a bespoke attribute argument or macro-body grammar that is not just a plain Rust item); a formal grammar of that syntax in the Rust Reference's notation. Omit the heading entirely for a macro whose invocation is a plain Rust item with no arguments. See the grammar conventions below for the notation and what counts as custom syntax. +- **Expansion** — the exact code the construct desugars to, shown with before/after code blocks. This is the heart of the document and the part most likely to drift; keep it faithful to the current macro output. +- **Examples** — at least one realistic, self-contained example showing the construct in use. +- **Related constructs** — links to the reference documents for constructs commonly used with this one, with a phrase explaining each relationship. +- **Known issues** — optional; present only when the construct has corner cases, surprising behavior, or open bugs worth warning a reader about. Omit the heading entirely when there is nothing to record. See the review workflow below for what belongs here. +- **Source** — pointers to the implementing modules in `cgp-macro-core` and the relevant tests, so a reader can drop from prose into code. + +Place each document in the subdirectory that matches what the construct is. The reference is organized into `macros/` (procedural macros a programmer invokes, including the type-level construction macros), `derives/` (the `#[derive(...)]` family), `attributes/` (modifier attributes consumed by a host macro), `components/` (the built-in CGP components — consumer/provider trait pairs defined with `#[cgp_component]`/`#[cgp_type]`/`#[cgp_getter]`), `providers/` (the zero-sized provider structs a context delegates to), `traits/` (capability and mechanism traits that are *not* themselves components), and `types/` (the type-level building-block types). A trait belongs in `components/` rather than `traits/` precisely when it is a CGP component with a generated provider trait and `…Component` marker. The high-level conceptual overviews that tie several constructs together are not per-construct reference documents; they live in the sibling top-level [concepts/](concepts/) directory and are cataloged in the [concepts index](concepts/README.md). The [reference index](reference/README.md) describes the reference layout in full and is the catalog you register a new reference document in. Because documents live in different subdirectories, a cross-link between two of them is a relative path — a sibling in the same directory is `name.md`, and a document in another directory is `../that-dir/name.md`. + +Cross-link generously. When a document mentions another construct, link to its reference document so a reader can follow the thread. A mention of a construct that is not yet documented is a useful signal of what to write next; record it as a gap in the [reference index](reference/README.md) — under the `traits/` or `types/` pending sections, for example — rather than leaving a dangling link with no home. + +## Syntax grammar conventions + +Every macro with custom syntax carries a **Syntax Grammar** section that formalizes what its Syntax section describes in prose, and the two must agree. "Custom syntax" means the macro's invocation accepts a bespoke grammar — an attribute argument with its own structure (`#[cgp_component]`, `#[cgp_impl]`), or a function-like macro body with a table, list, or path grammar (`delegate_components!`, `cgp_namespace!`, `Symbol!`, `Path!`). A macro whose invocation is just a plain Rust item with no arguments — `#[async_trait]`, `#[cgp_auto_dispatch]`, `#[cgp_auto_getter]` — has no custom syntax and gets no grammar section; an attribute that takes only a single optional identifier still does, because that identifier is the macro's own grammar. The grammar describes the *tokens the macro itself parses*: the attribute-argument tokens for an attribute macro, or the body tokens for a function-like macro — not the surrounding `#[...]` or `name!{...}` delimiters, and not the plain Rust item the attribute is applied to. + +Write the grammar in the notation of the [Rust Reference](https://doc.rust-lang.org/nightly/reference/notation.html), using the production-rule flavor of its [grammar dev-guide](https://rust-lang.github.io/reference/dev-guide/grammar.html). The rules are: + +- A production is written `Name -> Expression`, one rule per line, placed inside a fenced ` ```ebnf ` code block. +- A **nonterminal** is a CamelCase name referring to another production. A CamelCase name that the section does not define itself — `Type`, `Expression`, `Generics`, `GenericArgs`, `WhereClause`, `TypePath` — is a production of the Rust grammar, reused rather than re-specified. An `ALL_CAPS` name — `IDENTIFIER`, `STRING_LITERAL` — is a lexer token of the Rust grammar. +- A **terminal** is the exact characters to match, written in backticks: `` `new` ``, `` `:` ``, `` `=>` ``, `` `@` ``. +- `x?` is optional (zero or one); `x*` is zero or more; `x+` is one or more; `A | B` is alternation; `( … )` groups for precedence; juxtaposition is an ordered sequence. The less-common forms — character ranges `` [`a`-`z`] ``, exclusions `` ~[ … ] ``, negative lookahead `!x`, and `// line comments` — follow the Rust Reference and are used only when a construct genuinely needs them. + +Keep each grammar honest the same way the Expansion section is kept honest: it is a claim about what the macro's parser accepts, so verify it against the argument and body parsers in `cgp-macro-core` (the `args.rs`, `table.rs`, `key/`, `value/`, and `statement/` modules under `types//`) rather than transcribing the prose. When a macro shares a sub-grammar with another — `delegate_and_check_components!` reuses `delegate_components!`'s mappings and keys, and `cgp_namespace!` reuses the same key and path forms — define the shared production once in the macro that owns it and reference it by name from the others, exactly as the cross-linking rule requires, so the two grammars cannot drift apart. Follow the dual-reader prose style here too: open the section with a sentence naming what the grammar covers, then the code block, then a short paragraph explaining any nonterminal or constraint a reader could not infer from the rules alone (which keys are required, which options are mutually exclusive, what default fills an omitted value). + +## Reviewing and updating a document + +Reviewing a document means checking it against the code and then improving it, not just reading it. Approach a review in two passes: first confirm that every claim is still true, then improve how the document reads. The two passes are separate concerns — correctness is non-negotiable, while readability is a judgment call — and conflating them leads to polished prose that is subtly wrong. + +The source code is the single source of truth, above the skill and above the documents themselves. When the document, the `/cgp` skill, and the implementation disagree, the implementation wins every time: correct the document to match the code, and do not let the skill's teaching examples or the document's prior wording override what the macro actually does. The skill is a teaching aid whose simplified examples can lag the parser, so confirm any contested detail — an accepted syntax form, a default name, the shape of an expansion — directly against `cgp-macro-core`. Treat such a conflict as a defect in whichever artifact disagrees with the code, and prefer fixing the document you are reviewing. + +Verify specific behavior against the tests and the macro snapshots, then state the behavior in your own words without citing the test. The behavioral tests in [crates/tests/cgp-tests](../crates/tests/cgp-tests) and the expansion snapshots in [crates/tests/cgp-macro-tests](../crates/tests/cgp-macro-tests) are the most reliable evidence of what a macro does in a corner case — read them to confirm how a construct behaves, what it generates, and where it errors. The document, however, explains the behavior itself, not the test: write "a tuple struct keys its fields by `Index`," never "the test `tuple_struct.rs` shows that…". A reader of the reference should learn the semantics directly; the test is your evidence, not theirs, and pointers into test files belong only in the Source section. + +Record corner cases and confirmed bugs under the document's **Known issues** section. When a review uncovers behavior that is surprising, a sharp edge a user could trip on, or an outright bug in the construct, add a short prose note there describing what happens and, for a bug, what the correct behavior would be. Add the heading if the document does not yet have one, and place it just before Source. A known issue is documentation of reality, so describe the behavior as it currently is even when it is wrong — and when the bug is later fixed in the code, remove the note in the same change, per the synchronization rule. + +Improve flow and readability when a review finds the document hard to follow, especially after it has accumulated many small edits. A document that has been patched repeatedly tends to drift from the dual-reader prose style — topic sentences stop matching their paragraphs, the same concept gets explained in two places, and sections lose their order. When you see this, rewrite or reorganize the affected sections rather than adding yet another patch. A clean rewrite that preserves every verified fact is better than a document that is technically correct but no longer reads as a single coherent explanation. + +Remove stale content and deduplicate as part of every review. Delete claims the code no longer supports, drop examples that no longer compile, and collapse repeated explanations of the same concept into one place — ideally the document that owns that concept — replacing the duplicate with a cross-link. Two documents explaining the same mechanism in slightly different words will eventually disagree, so a single explanation plus a link is both shorter and safer. + +Re-check the document's place in the whole reference, not just its contents. Ask whether the change you are making implies a construct that deserves its own document — if a document keeps explaining another construct at length, that construct probably needs its own file and a cross-link. Ask whether the file is still in the right subdirectory and whether the set of files still divides cleanly; a subdirectory that has grown unwieldy or a document that has outgrown its category is a signal to reorganize. When you add, move, or split a document, update the [reference index](reference/README.md) and fix the affected cross-links in the same change, exactly as the synchronization rule requires. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..305d7153 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,23 @@ +# CGP Knowledge Base + +This directory is a knowledge base about Context-Generic Programming (CGP), written by and for AI coding agents. Its purpose is to give an agent everything it needs to understand the *full semantics* of CGP — what each construct means, what code it expands into, and how the pieces fit together — without having to re-derive that understanding from the macro implementation every time. The `/cgp` skill gives a fast orientation; this knowledge base is the durable, version-controlled record that goes deeper and stays in sync with the code. + +## Why this exists + +CGP is implemented almost entirely as procedural macros, and proc-macro source code is a poor place to learn semantics from. An agent reading [crates/macros/cgp-macro-core](../crates/macros/cgp-macro-core) sees token-stream manipulation and AST transforms, not the meaning those transforms produce. The meaning — "`#[cgp_component]` generates a consumer trait, a provider trait, and two blanket impls that connect them" — has to be reconstructed by mentally running the macro. That reconstruction is slow, error-prone, and gets repeated on every visit. This knowledge base captures the reconstruction once, in prose, so the next agent can read the conclusion instead of re-deriving it. + +The knowledge base also serves as a contract. When an agent changes how a macro expands, the corresponding reference document is the place where the intended new behavior is stated in plain language. A reviewer can compare the prose against the code and against the expansion snapshots in [crates/tests/cgp-macro-tests](../crates/tests/cgp-macro-tests) to confirm that all three agree. Documentation that drifts out of sync with the code is worse than no documentation, so keeping these documents accurate is a hard requirement of any change — see [CLAUDE.md](CLAUDE.md) for the maintenance rules. + +## How it is organized + +The knowledge base is divided into three top-level sections, and will grow to contain more as the need arises. + +The [reference/](reference/README.md) directory holds one document per CGP construct — one for `cgp_component`, one for `cgp_impl`, one for `delegate_components`, and so on. Each document is self-contained and explains a single construct completely: its purpose, its accepted syntax, the exact code it desugars to, worked examples, and links to the constructs it relates to. The [reference index](reference/README.md) lists every construct and tracks which ones are documented. + +The [concepts/](concepts/README.md) directory holds the cross-cutting conceptual overviews that span multiple constructs — the consumer/provider duality, dependency injection, namespaces, the handler family, and so on — each explaining one idea and linking down into the reference documents for the mechanics. Where the reference explains the individual trees, the concepts explain the shape of the forest. + +The [examples/](examples/README.md) directory holds self-contained worked examples, one realistic use case developed end to end per document, from its contexts and components through to the wiring that connects them. The examples are the canonical source of code snippets the reference and concept documents reuse, so the same running scenarios recur across the whole knowledge base. + +## How to use it + +An agent working on CGP should read the relevant reference document before changing a construct, and should consult the `/cgp` skill for the conceptual framing that ties constructs together. The reference documents assume familiarity with the vocabulary the `/cgp` skill establishes — consumer traits, provider traits, providers, wiring, and so on — and focus on precise per-construct semantics rather than re-teaching the paradigm. Read the two together: the skill for the shape of the forest, the reference for the individual trees. diff --git a/docs/concepts/README.md b/docs/concepts/README.md new file mode 100644 index 00000000..b107c418 --- /dev/null +++ b/docs/concepts/README.md @@ -0,0 +1,29 @@ +# CGP Concepts + +This directory holds the high-level conceptual overviews that tie the CGP constructs together — the consumer/provider duality, dependency injection, namespaces, handlers, and so on. Each document explains one cross-cutting idea and points down into the [reference documents](../reference/README.md) for the per-construct mechanics, so a reader can grasp the shape of an idea here and follow the links for the precise semantics. + +## How concepts differ from reference documents and examples + +A concept document explains an *idea that spans several constructs*, whereas a [reference document](../reference/README.md) explains a *single construct completely* and an [example](../examples/README.md) develops a *single use case end to end*. The three are complementary. The reference for `delegate_components!` states its syntax and exact expansion; the [coherence](coherence.md) concept explains *why* CGP wires components through such a table at all, and the [modular serialization](../examples/modular-serialization.md) example shows the pattern solving a real problem. A concept document carries only enough mechanism to make the idea legible and links to the reference for the rest, so a reader who wants the detail follows the link while a reader who wants the framing stays here. + +## The catalog + +The authoring rules for concept documents, including when a cross-cutting idea earns its own page, live in [../CLAUDE.md](../CLAUDE.md). These documents explain the ideas that connect the constructs, each linking down to the per-construct references for the detail. + +- [Bypassing coherence](coherence.md) — what Rust's coherence rules forbid, and the incoherent-impl-plus-local-wiring strategy CGP uses to work around them. +- [Modularity hierarchy](modularity-hierarchy.md) — the ladder from a single blanket impl to per-type-per-provider wiring, and how to pick the lowest rung a use case needs. +- [Consumer and provider traits](consumer-and-provider-traits.md) — the trait duality at the heart of CGP and how it sidesteps coherence. +- [Impl-side dependencies](impl-side-dependencies.md) — dependency injection through the `where` clause of blanket impls. +- [Implicit arguments](implicit-arguments.md) — writing providers as ordinary functions whose arguments come from context fields. +- [Higher-order providers](higher-order-providers.md) — providers parameterized by other providers. +- [Check traits](check-traits.md) — why wiring is lazy and how to verify it at compile time. +- [Abstract types](abstract-types.md) — abstract associated types shared and swapped across contexts. +- [Modular error handling](modular-error-handling.md) — an abstract error type plus raising and wrapping capabilities, with the error type and construction strategy chosen by wiring. +- [Namespaces](namespaces.md) — reusable, inheritable wiring tables and preset-style configuration. +- [Handlers](handlers.md) — the Computer/Producer/Handler family of computation components and their sync/async/fallible/by-reference variants. +- [Extensible records](extensible-records.md) — building and reading a struct by its named fields, and the extensible builder pattern. +- [Extensible variants](extensible-variants.md) — constructing and deconstructing an enum by its named variants, and the extensible visitor pattern. +- [Dispatching](dispatching.md) — routing extensible-data inputs to per-field and per-variant handlers. +- [Monadic handlers](monadic-handlers.md) — composing handlers through the identity/ok/err monads. +- [Type-level DSLs](type-level-dsls.md) — encoding a small language as types and interpreting it at compile time through CGP wiring. +- [Recovering `Send` bounds](send-bounds.md) — restoring the `Send` guarantee an async trait method drops, as a stand-in for the Return Type Notation stable Rust lacks. diff --git a/docs/concepts/abstract-types.md b/docs/concepts/abstract-types.md new file mode 100644 index 00000000..57eec597 --- /dev/null +++ b/docs/concepts/abstract-types.md @@ -0,0 +1,84 @@ +# Abstract types + +An abstract type is a trait carrying a single associated type that generic code refers to as `Self::Foo` without committing to any concrete type, so that the concrete choice is supplied by wiring and can differ from one context to another. + +## The idea + +An abstract type lets generic code name a type it does not fix. Instead of hard-coding `f64` or `String`, a CGP trait declares an associated type — `trait HasScalarType { type Scalar; }` — and code written against it refers to `Self::Scalar`, leaving the actual type open. The trait is the abstraction; the associated type is the slot a context fills in. This is the type-level analogue of dependency injection: just as a getter trait lets a context supply a *value* the provider needs, an abstract-type trait lets a context supply a *type* the provider builds on. + +The reason this matters is the same reason behavior is made swappable in CGP. A provider written in terms of `Self::Scalar` works unchanged whether a context chooses `f32`, `f64`, or a fixed-point type, and two contexts can make different choices from the same generic code. An abstract type is, in the end, nothing more than an ordinary Rust trait with one associated type — generic functions constrain `Context: HasScalarType` and use `Context::Scalar` exactly as they would any associated type. CGP adds machinery to make declaring and wiring these traits cheap, but the underlying construct is plain Rust, and a context can always implement the trait directly with `impl HasScalarType for App { type Scalar = f64; }`. + +## Making a type swappable with `#[cgp_type]` + +The [`#[cgp_type]`](../reference/macros/cgp_type.md) macro turns an abstract-type trait into a full CGP component, so the concrete type can be chosen through wiring rather than a hand-written impl. Applied to a trait with exactly one associated type, it produces everything [`#[cgp_component]`](../reference/macros/cgp_component.md) would — the consumer trait, the provider trait, the blanket impls, the component marker — but specialized to forward an associated type rather than a method: + +```rust +#[cgp_type] +pub trait HasScalarType { + type Scalar: Copy; +} +``` + +The default provider name is keyed off the *associated type* name, not the trait name, so `Scalar` yields `ScalarTypeProvider` and the component `ScalarTypeProviderComponent`. A bound on the associated type, such as `Copy` above, is carried everywhere the type appears in the expansion and enforced on whatever concrete type a context chooses. The decisive extra construct `#[cgp_type]` generates is a blanket impl for `UseType`, described next, which is what lets a context pick a concrete type without writing a provider of its own. + +## Wiring a concrete type with `UseType` + +A context binds an abstract type to a concrete one by wiring its provider component to [`UseType`](../reference/providers/use_type.md). Because every abstract-type provider has the same trivial shape — "the associated type *is* this concrete type" — `#[cgp_type]` generates that shape once as a blanket impl of the provider trait for `UseType`, setting the associated type to the generic parameter. A context then names the concrete type directly in its delegation table: + +```rust +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar: Copy; +} + +pub struct App; + +delegate_components! { + App { + ScalarTypeProviderComponent: UseType, + } +} +``` + +Wiring `ScalarTypeProviderComponent` to `UseType` makes `App` implement `HasScalarType` with `Scalar = f64`, with no bespoke provider, and the `Copy` bound is checked against `f64` at the wiring site. This is the type-level mirror of how `UseField` supplies a value-level getter. The `UseType` *provider struct* should not be confused with the [`#[use_type]`](../reference/attributes/use_type.md) *attribute*: the provider, covered here, wires a concrete type into a context; the attribute imports an abstract type into a definition and rewrites bare mentions of it into fully-qualified form. They are complementary but different things. + +## Sharing a type across contexts + +The point of an abstract type compounds when several pieces of generic code share one. Because the type lives on a trait the context implements, every provider and trait that needs a `Scalar` refers to the *same* `Self::Scalar`, so a context fixes the choice once and all of them agree. This is most valuable when the main target of a trait is a generic parameter rather than the context itself — for example a `CanCalculateAreaOfShape` trait implemented by a common `Context` for many `Shape` types. The shapes need not each declare a scalar type or coordinate on a common one; the shared context supplies a single `Scalar`, and every shape interoperates through it: + +```rust +#[cgp_type] +pub trait HasScalarType { + type Scalar: Copy; +} + +#[cgp_component(AreaOfShapeCalculator)] +pub trait CanCalculateAreaOfShape: HasScalarType { + fn area(&self, shape: &Shape) -> Self::Scalar; +} +``` + +Here `Rectangle` and `Circle` carry no scalar type of their own; the context that calculates their areas does, via its `HasScalarType` wiring, and switching the context's `UseType` to `UseType` changes the scalar for every shape at once. The same arrangement is how CGP shares an error type across an entire application, which is the canonical example. + +## The canonical example: `HasErrorType` + +CGP's most-used abstract type is [`HasErrorType`](../reference/components/has_error_type.md), which supplies one `Error` type that an entire context's code agrees on. It is defined with `#[cgp_type]` exactly as above: + +```rust +#[cgp_type] +pub trait HasErrorType { + type Error: Debug; +} +``` + +A context wires `ErrorTypeProviderComponent` to `UseType` (or any concrete error), and from then on every fallible provider in that context refers to the same `Self::Error`, so errors compose without each component declaring its own error type or converting between several. The `Debug` bound lets that abstract error be unwrapped and logged. `HasErrorType` shows the pattern at its most useful: a single associated type, supplied once by the context through `UseType`, shared by arbitrarily much generic code — and built on by the further error machinery (`CanRaiseError`, `CanWrapError`) that constrains `Self: HasErrorType` and works against the abstract `Error` throughout. + +## Related constructs + +Abstract types are defined with [`#[cgp_type]`](../reference/macros/cgp_type.md), the abstract-type specialization of [`#[cgp_component]`](../reference/macros/cgp_component.md). They are built on CGP's foundational [`HasType`/`TypeProvider`](../reference/components/has_type.md) component, the built-in abstract-type machinery that `#[cgp_type]` adapts. A context binds a concrete type through the [`UseType` provider](../reference/providers/use_type.md), and other definitions import an abstract type and rewrite bare mentions of it with the [`#[use_type]` attribute](../reference/attributes/use_type.md) — a different construct from the provider despite the shared name. [`HasErrorType`](../reference/components/has_error_type.md) is the canonical abstract type, supplying a shared `Error` type across a context's code. Abstract-type components are wired with [`delegate_components!`](../reference/macros/delegate_components.md) and verified with [`check_components!`](../reference/macros/check_components.md) like any other component. + +## Source + +The runtime `HasType`, `TypeProvider`, and `UseType` definitions are in [crates/core/cgp-type/src/](../../crates/core/cgp-type/src/); `HasErrorType` is in [crates/core/cgp-error/src/traits/has_error_type.rs](../../crates/core/cgp-error/src/traits/has_error_type.rs); the `#[cgp_type]` codegen is in [crates/macros/cgp-macro-core/src/types/cgp_type/](../../crates/macros/cgp-macro-core/src/types/cgp_type/). diff --git a/docs/concepts/check-traits.md b/docs/concepts/check-traits.md new file mode 100644 index 00000000..61835757 --- /dev/null +++ b/docs/concepts/check-traits.md @@ -0,0 +1,56 @@ +# Check traits + +Check traits are compile-time-only assertions that a context's CGP wiring is complete, written to turn the vague errors that lazy wiring produces into readable ones that name the actual missing dependency. + +## Why CGP wiring is lazy + +CGP wiring is lazy: recording that a context delegates a component to a provider does not verify that the provider can actually satisfy that component. When a context's delegation table maps `AreaCalculatorComponent` to some provider, the type system accepts the [`DelegateComponent`](../reference/traits/delegate_component.md) entry on its own terms — it stores "this key points to this provider" as an associated type and asks no further questions. Whether that provider's own `where` bounds hold for this particular context is simply not checked at the point of delegation. The check is deferred until something downstream tries to *use* the component, which is the first moment the compiler is forced to evaluate the provider's bounds against the concrete context. + +This laziness is what makes CGP composable, but it has a cost: a context can look fully wired and still be broken. Every entry compiles, the struct compiles, the whole module compiles — and then the first call to a consumer trait method fails, often far from the wiring that caused it. A missing `name` field, a missing abstract type, an unsatisfied transitive bound three providers deep: none of these surface where the mistake was made. + +## Why the resulting errors are poor + +When a lazily-wired context is finally used and a dependency is missing, the compiler reports the outermost unmet conclusion and hides the reasoning behind it. Asking the plain question "does this context implement the consumer trait?" makes Rust answer with the last link in the chain — typically that the provider does not implement the provider trait for this context — without explaining *why* the provider's bounds were not met. The root cause, often a single missing getter or type, is buried beneath a conclusion that points at the provider rather than at the gap. Worse, the relevant type names are expanded into their full type-level forms (the verbose `Symbol<…, Chars<…>>` spelling of a field name, for instance), so even the surface error is hard to read. + +The fix is to force the compiler to evaluate the provider's bounds *and report them in detail*, at a location the author controls — the wiring site — rather than wherever the component happens to be used first. + +## How check traits force readable errors + +A check trait is a dummy trait whose supertrait is the requirement being asserted; implementing it for a context with an empty body compiles only if that requirement holds. The hand-written form is plain Rust: + +```rust +trait CanUsePerson: CanGreet {} +impl CanUsePerson for Person {} +``` + +The `impl` block has nothing to prove on its own, so it succeeds exactly when `Person: CanGreet` holds and fails to compile otherwise. Placed next to the wiring, it converts a latent gap into an immediate compile error at a known line. But asserting the consumer trait directly is not enough, because it reproduces the same vague error described above — it tells you the provider trait is not implemented without saying why. The remedy is to route the assertion through [`CanUseComponent`](../reference/traits/can_use_component.md) instead of the consumer trait. + +`CanUseComponent` is satisfied only when the context both delegates the component and the delegated provider satisfies [`IsProviderFor`](../reference/traits/is_provider_for.md) for that context. The crucial property is that `IsProviderFor` is generated to carry the provider's *real* `where` bounds — the same bounds the provider needs to implement its provider trait. Routing a check through `CanUseComponent` therefore forces the compiler to evaluate those bounds and, because they are stated explicitly on the marker, to report the specific one that failed. A missing `name` field surfaces as an unsatisfied `HasField` bound pointing at the context, not as a bare "provider not implemented." `IsProviderFor` is otherwise a near-invisible marker; its whole purpose is to make this error-surfacing work, and check traits are where it earns its keep. + +## Writing checks with the macros + +Rather than spell out check traits by hand, [`check_components!`](../reference/macros/check_components.md) generates them from a short table of components to verify. Given a context and a list of components, it emits a marker trait aliasing `CanUseComponent` and one empty impl per component: + +```rust +check_components! { + Person { + GreeterComponent, + } +} +``` + +The generated impl compiles only if `Person: CanUseComponent`, which in turn drags in the delegated provider's `IsProviderFor` bounds and reports the first that fails. A successful build *is* the passing assertion — these checks have no runtime existence. For components with generic parameters, the parameters to test are listed after a colon, since the check must name them explicitly to have anything concrete to verify. + +Because keeping a standalone check block in sync with the wiring is manual bookkeeping, [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md) fuses the two: it wires each entry exactly as [`delegate_components!`](../reference/macros/delegate_components.md) would and derives a check for each delegated key, so every wiring is proven the moment it is written. This is the recommended form for a main context. Its check trait is named `__CanUse{Context}` while `check_components!` names its trait `__Check{Context}`, deliberately distinct so both can appear once in the same module. Plain `delegate_components!` remains right for intermediary provider tables that are not contexts in their own right. + +## Checking higher-order providers + +For higher-order providers, the `#[check_providers(...)]` form of [`check_components!`](../reference/macros/check_components.md) checks each provider layer independently rather than checking the context as a whole. Instead of asserting `CanUseComponent` on the context, it asserts [`IsProviderFor`](../reference/traits/is_provider_for.md) directly on each named provider — so `RectangleAreaCalculator` and `ScaledAreaCalculator` are each verified on their own impl. This localizes failures: a dependency missing from the inner `RectangleAreaCalculator` errors on both lines, while one missing only from the outer wrapper errors on the wrapper alone, which narrows down where in a nested stack the gap lives. Provider-level checks are the practical tool for debugging the composition described in [higher-order providers](higher-order-providers.md). + +## Related constructs + +Check traits verify the wiring produced by [`delegate_components!`](../reference/macros/delegate_components.md), and the two macros that write them are [`check_components!`](../reference/macros/check_components.md) for a standalone check and [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md) for wiring-and-checking in one step. The assertion they generate is built on [`CanUseComponent`](../reference/traits/can_use_component.md), which combines [`DelegateComponent`](../reference/traits/delegate_component.md) (the lazy table entry whose acceptance is the source of the problem) with [`IsProviderFor`](../reference/traits/is_provider_for.md) (the marker that carries a provider's real bounds so the compiler reports them). The `#[check_providers]` variant of `check_components!` asserts `IsProviderFor` directly on providers, which is the right tool for the [higher-order providers](higher-order-providers.md) it most often checks. + +## Source + +The runtime traits live in [crates/core/cgp-component/src/traits/](../../crates/core/cgp-component/src/traits/) (`CanUseComponent` in `can_use_component.rs`, `IsProviderFor` in `is_provider.rs`, `DelegateComponent` in `delegate_component.rs`); the check-table macros are in [crates/macros/cgp-macro-core/src/types/check_components/](../../crates/macros/cgp-macro-core/src/types/check_components/) and [delegate_and_check_components/](../../crates/macros/cgp-macro-core/src/types/delegate_and_check_components/), with expansion snapshots in [crates/tests/cgp-tests/src/tests/check_components.rs](../../crates/tests/cgp-tests/src/tests/check_components.rs). diff --git a/docs/concepts/coherence.md b/docs/concepts/coherence.md new file mode 100644 index 00000000..360a2be8 --- /dev/null +++ b/docs/concepts/coherence.md @@ -0,0 +1,129 @@ +# Bypassing coherence + +CGP works around Rust's coherence restrictions by splitting every trait into a pair, so that implementations are written incoherently against a unique provider type and coherence is then restored locally, one context at a time, by an explicit wiring step. + +## What coherence guarantees + +Coherence is the property that every trait lookup resolves to one globally unique implementation, no matter where in the program it is performed. Rust depends on this because its trait system doubles as a dependency-injection mechanism: a generic `impl` can require `where T: Display` without the caller ever naming that bound, and the compiler resolves the dependency — and every transitive dependency beneath it — by global lookup. For that lookup to be sound, it must always find the same implementation, so the answer cannot depend on which crate is asking or what else happens to be in scope. + +Two rules enforce this uniqueness. The **overlap rule** forbids two implementations that could both apply to the same type, since the compiler would have no principled way to choose between them. The **orphan rule** forbids implementing a trait for a type unless the current crate owns either the trait or the type, since otherwise two unrelated crates could each define their own implementation and a program depending on both would face an irreconcilable conflict. Together they are what let dependency injection work transitively without ambiguity. + +## The cost + +These rules are necessary for soundness but they forbid a great deal of code that would be perfectly useful. The canonical casualty is the blanket implementation: one cannot implement `serde::Serialize` for *every* type that implements `Display`, or for every type that implements `AsRef<[u8]>`, because a type might implement several of those bounds at once and the implementations would overlap — and Rust permits at most one such blanket impl, with no way to say which should win. + +Spelled out in code, the second of two overlapping blanket impls is rejected by the compiler, because some type — `String`, say, is both `Display` and `AsRef<[u8]>` — could match both: + +```rust +use serde::{Serialize, Serializer}; + +// Legal on its own: serialize anything printable as a string. +impl Serialize for T { + fn serialize(&self, s: S) -> Result { + s.serialize_str(&self.to_string()) + } +} + +// error[E0119]: conflicting implementation — overlaps the impl above +// on every `T: Display + AsRef<[u8]>`. +impl> Serialize for T { + fn serialize(&self, s: S) -> Result { + s.serialize_bytes(self.as_ref()) + } +} +``` + +The orphan rule bites just as often, and independently of any overlap: a crate that wants to serialize a `Person` type it did not define, using a `Serialize` trait it also did not define, simply cannot, because it owns neither side: + +```rust +use serde::{Serialize, Serializer}; +use other_crate::Person; // defined in a crate we do not own + +// error[E0117]: only traits defined in the current crate can be +// implemented for types defined outside of the crate. +impl Serialize for Person { + fn serialize(&self, s: S) -> Result { + /* ... */ + } +} +``` + +The programmer is left writing the same boilerplate by hand for each type, or pressuring upstream crates to add derives they may not want. + +## Moving `Self` to a parameter + +CGP's first move is to take the type that coherence ranges over — the `Self` of the implementation — and turn it into something the implementing crate always owns. A CGP component is declared once but compiled into two traits: a consumer trait that callers invoke with the ordinary `Self` receiver, and a **provider trait** whose `Self` is a dedicated, zero-sized provider struct while the original receiver becomes an explicit `Context` type parameter. Because the provider implements the provider trait *for its own struct* — `ValueSerializer` for `SerializeBytes`, never `Serialize` for some foreign type — neither coherence rule applies: the `Self` type is local and unique, so the implementation is neither an orphan nor an overlap, however general its `Context` and other parameters are. The full mechanics of this split, and the blanket impls that make a context's `context.serialize(...)` resolve to a chosen provider, are the subject of [consumer and provider traits](consumer-and-provider-traits.md). + +This is what makes incoherent implementations expressible. A crate can define `UseSerde` for any `Value: Serialize`, `SerializeBytes` for any `Value: AsRef<[u8]>`, and `SerializeWithDisplay` for any `Value: Display`, all at once — three blanket implementations of the same capability that overlap freely, since a type like `String` satisfies all three bounds at once, each a distinct provider struct the crate owns. The three impls read almost exactly like the rejected `Serialize` impls above, differing only in the provider name given to [`#[cgp_impl]`](../reference/macros/cgp_impl.md), which becomes the unique `Self` type: + +```rust +#[cgp_impl(UseSerde)] +impl ValueSerializer +where + Value: serde::Serialize, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where S: serde::Serializer { value.serialize(serializer) } +} + +#[cgp_impl(SerializeBytes)] +impl ValueSerializer +where + Value: AsRef<[u8]>, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where S: serde::Serializer { serializer.serialize_bytes(value.as_ref()) } +} + +#[cgp_impl(new SerializeWithDisplay)] +#[uses(CanSerializeValue)] +impl ValueSerializer +where + Value: core::fmt::Display, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where S: serde::Serializer { self.serialize(&value.to_string(), serializer) } +} +``` + +Because each impl targets its own `Self` — `UseSerde`, `SerializeBytes`, `SerializeWithDisplay` — all three compile together and overlap on `String` without complaint, where any two of the vanilla `Serialize` impls were rejected. A downstream crate can add yet more for types it does not own, since the orphan rule never enters: the `Self` type is always a struct that crate defines. + +## Restoring coherence locally + +Removing coherence from implementations would be useless if it also removed coherence from *use* — a caller still needs `context.serialize(value, s)` to mean one definite thing. CGP's second move is therefore to reintroduce coherence at a smaller scale: instead of one global choice of implementation per trait, each context makes its own coherent choice, valid within that context alone. The insight is that incoherence is wanted only when writing implementations; when using them, what is wanted is many independent local scopes, each internally consistent. + +The wiring step is what creates those scopes. A concrete context selects exactly one provider per component by becoming a type-level lookup table — written with [`delegate_components!`](../reference/macros/delegate_components.md) — whose entry for a component names the provider to use. Within that one context the choice is unambiguous, so `context.serialize(...)` resolves coherently; a different context can wire the same component to a different provider and resolve differently, with no conflict between them because the resolution is keyed on the context type. + +Two applications can thus serialize `Vec` as hexadecimal and as base64 respectively, each coherent in its own scope, where global coherence would have forced a single answer on both. The two scopes are simply two wiring tables, each opening the serialization component and choosing a provider for `Vec`: + +```rust +delegate_components! { + AppA { + open {ValueSerializerComponent}; + @ValueSerializerComponent.Vec: SerializeHex, + } +} + +delegate_components! { + AppB { + open {ValueSerializerComponent}; + @ValueSerializerComponent.Vec: SerializeBase64, + } +} +``` + +`AppA` serializes a `Vec` as hexadecimal and `AppB` as base64; the overlapping `SerializeHex` and `SerializeBase64` providers coexist globally, while each context's choice stays unambiguous within itself. The `open` statement and its `@`-path entries are the [namespace](namespaces.md)-based wiring form that keys this dispatch on the value type. Because the table is consulted only at the leaves of dependency injection and never appears in a generic bound, the soundness problem that motivated the overlap rule — a blanket impl silently chosen inside generic code and later contradicted by a specialized one — cannot arise: there is no global blanket impl to be silently chosen, only a per-context entry. + +## Composing and selecting incoherent implementations + +Two further patterns let a context draw on many incoherent implementations at once rather than picking a single one. When the choice of provider should depend on a type argument — serialize *this* value type one way and *that* one another — the [`open` statement](../reference/macros/delegate_components.md) wires a provider per value of that argument directly into the context's table, so one component fans out to a type-specific provider per case; this is the [dispatching](dispatching.md) idea applied to a generic parameter, resolved through the [`RedirectLookup`](../reference/providers/redirect_lookup.md) impl each component carries. (The legacy [`UseDelegate`](../reference/providers/use_delegate.md) provider performs the same second lookup through a separate nested table.) When one implementation should be expressed in terms of another — wrap, scale, or recurse over a base case — a [higher-order provider](higher-order-providers.md) takes the inner provider as a parameter and composes with it. Both compose entirely in types, so a context can wire a deep tree of overlapping providers and pay nothing at runtime. + +Dependency injection is what threads the chosen implementations through without explicit plumbing. A provider states the capabilities it needs as bounds in its `where` clause — that the context can serialize a `String`, that it can fetch an arena allocator — and the wiring satisfies them by resolving each through the same context, exactly the [impl-side dependency](impl-side-dependencies.md) mechanism ordinary CGP code uses. The context therefore acts as the implicit carrier of every implementation a computation needs, which is how CGP emulates capability-passing — supplying values and behaviors to deeply nested code through the context rather than as explicit arguments — in stable Rust. + +## Related constructs + +The trait split that makes incoherent implementations expressible is documented in full in [consumer and provider traits](consumer-and-provider-traits.md), generated by [`#[cgp_component]`](../reference/macros/cgp_component.md) and implemented through [`#[cgp_impl]`](../reference/macros/cgp_impl.md). The local-coherence wiring step is [`delegate_components!`](../reference/macros/delegate_components.md), which builds the per-context [`DelegateComponent`](../reference/traits/delegate_component.md) table; [`check_components!`](../reference/macros/check_components.md) verifies that a context's chosen providers actually satisfy their dependencies, the subject of [check traits](check-traits.md). The two composition patterns are per-value dispatch through the [`open` statement](../reference/macros/delegate_components.md) and its [`RedirectLookup`](../reference/providers/redirect_lookup.md) resolution (see [namespaces](namespaces.md) and [dispatching](dispatching.md); the legacy [`UseDelegate`](../reference/providers/use_delegate.md) is the older equivalent) and [higher-order providers](higher-order-providers.md), and the dependency-threading that ties them to a context is [impl-side dependencies](impl-side-dependencies.md). The [modularity hierarchy](modularity-hierarchy.md) concept places this strategy on a ladder, showing how the trait split and per-context wiring described here are the upper rungs above vanilla Rust's one-implementation-per-type coherence. The [modular serialization](../examples/modular-serialization.md) example works the whole strategy through end to end on Serde's `Serialize` and `Deserialize`. + +## Source + +The blanket impls that restore coherent use from the provider traits, and the [`DelegateComponent`](../reference/traits/delegate_component.md)/[`IsProviderFor`](../reference/traits/is_provider_for.md)/[`CanUseComponent`](../reference/traits/can_use_component.md) machinery they rely on, live in [crates/core/cgp-component/src/](../../crates/core/cgp-component/src/); the macros that generate them are in [crates/macros/cgp-macro-core/src/types/cgp_component/](../../crates/macros/cgp-macro-core/src/types/cgp_component/). diff --git a/docs/concepts/consumer-and-provider-traits.md b/docs/concepts/consumer-and-provider-traits.md new file mode 100644 index 00000000..2916b9ff --- /dev/null +++ b/docs/concepts/consumer-and-provider-traits.md @@ -0,0 +1,73 @@ +# Consumer and provider traits + +The defining idea of CGP is that one trait definition becomes two traits — a consumer trait that callers use and a provider trait that implementers write — so that many independent implementations of the same capability can coexist without violating Rust's coherence rules. + +## The problem + +Rust conflates using a capability with implementing it, and its coherence rules then permit only one implementation per type. A plain `trait CanCalculateArea` is implemented by the same type that callers invoke `.area()` on, so a crate can supply at most one `area` behavior for a given context, and an implementation written in a downstream crate runs into the orphan rule. This is exactly the wall a programmer hits when they want two interchangeable strategies for the same operation, or want to define a behavior for a type they do not own. + +CGP's answer is to split the single trait into a pair. The capability is still declared once, but the declaration is compiled into two related traits whose roles are kept apart: one is what code *calls*, the other is what code *implements*. Pulling these two roles into separate traits is what lets an unlimited number of implementations live side by side, because the type that carries each implementation is no longer the context. + +## The duality + +A consumer trait is the caller's view: it keeps the original `Self` receiver, so a caller writes `context.area()` exactly as with an ordinary trait. A provider trait is the implementer's view: the original `Self` is moved out into an explicit leading `Context` type parameter, and every `self`/`Self` becomes `context`/`Context`. The provider trait is then implemented not for the context but for a dedicated, zero-sized **provider** struct — a type-level-only name like `RectangleArea` that is never instantiated and carries no runtime value. + +Moving `Self` to a parameter is what sidesteps coherence. Because a provider implements `AreaCalculator` for *its own* struct over a generic `Context`, rather than implementing a trait for the context, the orphan and overlap rules do not bite: a crate can define `RectangleArea`, `CircleArea`, and any number of further providers for the same component, each a distinct `Self` type, all valid at once. The cost is that the provider-trait shape reads inside-out, which is why providers are normally written through the sugar described below rather than by hand. + +These two traits are produced from one definition by [`#[cgp_component]`](../reference/macros/cgp_component.md), the macro that turns an ordinary trait into a full CGP **component** — the consumer trait, the provider trait, a zero-sized component-name struct used as a key, and the blanket impls that connect them. + +## How the two traits connect + +Two generated blanket impls bridge the consumer and provider sides, and together they make `context.area()` resolve to a chosen provider without the caller naming it. The **consumer blanket impl** says that any context which implements the provider trait *for itself* automatically gets the consumer trait, forwarding `context.area()` to `Context::area(self)`. The **provider blanket impl** says that any provider which delegates the component inherits the provider trait from whatever it delegates to, looked up through [`DelegateComponent`](../reference/traits/delegate_component.md). + +Wiring is what supplies that delegation. A concrete context picks one provider per component by becoming a type-level table whose entry for `AreaCalculatorComponent` names the chosen provider; [`delegate_components!`](../reference/macros/delegate_components.md) writes that table. The two blanket impls then chain through it: `context.area()` resolves through the consumer impl to the context implementing the provider trait for itself, which resolves through the provider impl to the table entry, landing on the selected provider's `area`. Selecting a different provider in the table is the only change needed to swap the behavior — no caller is touched. + +The marker [`IsProviderFor`](../reference/traits/is_provider_for.md) rides along on the provider trait as a supertrait, capturing each provider's dependencies so that a missing one surfaces as a readable compiler error rather than a bare "trait not implemented". It is wiring's bookkeeping, not something a provider author writes; it is generated for them. + +## In practice + +Most code never sees the raw provider-trait shape, because the sugar restores the familiar look of an ordinary trait impl. A provider is written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), which lets the author keep `self`, `Self`, and the consumer-trait signatures while the macro mechanically rewrites the block into the provider-trait form and emits the `IsProviderFor` impl. The lower-level [`#[cgp_provider]`](../reference/macros/cgp_provider.md) is the same machinery without the `self`/`Self` rewrite, for when the inside-out shape is wanted explicitly. + +A complete component ties the pieces together: define the trait, write a provider, wire it, call it. + +```rust +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_impl(new RectangleArea)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +delegate_components! { + Rectangle { + AreaCalculatorComponent: RectangleArea, + } +} + +fn print_area(rect: &Rectangle) { + println!("area = {}", rect.area()); // CanCalculateArea, via RectangleArea +} +``` + +A context can also implement a consumer trait directly, exactly as it would a vanilla Rust trait, when code reuse is not the goal. The consumer/provider split is a superset of ordinary traits, not a replacement: the provider machinery is what you opt into when a capability needs more than one implementation, and skipping it costs nothing for the simple case. + +A second provider mirrors the consumer relationship in reverse. [`UseContext`](../reference/providers/use_context.md) is a built-in provider that implements the provider trait *by routing back through the context's own consumer-trait implementation* — the dual of the consumer blanket impl, and the hook that lets a higher-order provider fall back to whatever the context already has wired. + +## Related constructs + +[`#[cgp_component]`](../reference/macros/cgp_component.md) is the macro that generates the whole component — both traits, the component-name struct, and the blanket impls — and is the doc to read for the exact expansion. Providers are written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) (consumer-trait-style sugar) or its lower layer [`#[cgp_provider]`](../reference/macros/cgp_provider.md); [`#[cgp_fn]`](../reference/macros/cgp_fn.md) is the lighter alternative when only a single implementation is ever needed and no wiring is wanted. + +The connective tissue is documented in the traits and provider sections. [`DelegateComponent`](../reference/traits/delegate_component.md) is the type-level table the provider blanket impl reads, written by [`delegate_components!`](../reference/macros/delegate_components.md); [`IsProviderFor`](../reference/traits/is_provider_for.md) is the dependency-tracking marker on the provider trait; and [`UseContext`](../reference/providers/use_context.md) is the provider that closes the loop back to the consumer side. For the dependency-injection idea that providers express in their `where` clauses, see [impl-side dependencies](impl-side-dependencies.md); for why the trait split is needed in the first place and how local wiring restores coherent use, see [bypassing coherence](coherence.md). diff --git a/docs/concepts/dispatching.md b/docs/concepts/dispatching.md new file mode 100644 index 00000000..0bd055ac --- /dev/null +++ b/docs/concepts/dispatching.md @@ -0,0 +1,39 @@ +# Dispatching + +Dispatching is the CGP pattern for handling an extensible-data value generically by routing it to per-field or per-variant handlers: given an enum, match its current variant to the handler for that variant; given a record, route each field to the handler that produces it. + +## Purpose + +Dispatching solves the problem of writing a single piece of logic that works over a record or enum whose exact shape is not known where the logic is written. A concrete `match` on an enum names every variant in one place and calls a fixed function for each, and a struct literal supplies every field at once — both bake the data's shape into the code. Dispatching keeps the same per-variant and per-field structure but lets the shape and the handlers be chosen by type, so the same matcher can serve many enums and the same builder can serve many records, with each variant's or field's behavior wired separately like any other CGP component. + +The pattern exists because the extensible-data machinery already represents records and enums as type-level lists, and dispatching is what turns those representations into useful work. The extractor family in [`extract_field`](../reference/traits/extract_field.md) can take an enum apart one variant at a time, and the builder family in [`has_builder`](../reference/traits/has_builder.md) can assemble a record one field at a time, but neither decides *what to do* with each variant or field. Dispatching supplies that decision: it pairs each variant with a handler and runs them as a matcher, or pairs each field with a handler and runs them as a builder, expressing both as ordinary [`Computer`](../reference/components/computer.md)/[`Handler`](../reference/components/handler.md) providers so they compose with the rest of CGP. The concrete providers that implement the pattern are documented in [`dispatch_combinators`](../reference/providers/dispatch_combinators.md); this page explains the idea they share. + +## Matching a variant to a handler + +Matching an enum is a loop that tries each variant in turn and stops at the first one that is actually present. The matcher starts by converting the input value into its *extractor* — the partial enum from [`HasExtractor`](../reference/traits/extract_field.md) in which every variant is still possible — and then runs a list of per-variant handlers over it. Each handler attempts one variant: if the runtime value is that variant, the handler pulls out the payload, runs the variant's own handler on it, and reports success; if not, it reports failure and hands back a *remainder*, the extractor with that one variant ruled out at the type level. The next handler in the list receives that shrunken remainder, so each miss narrows the set of variants still in play. + +The narrowing is what makes the match provably exhaustive without a wildcard arm. Because each failed attempt rules out one more variant in the type, the remainder after the last handler has every variant ruled out and is therefore an uninhabited type — a value that cannot exist. The matcher discharges it with `finalize_extract`, which is sound precisely because there is no value to handle, and the compiler accepts the discharge with no fallback case. Adding a variant to the enum without adding a handler for it makes the final remainder inhabited again, so the code fails to compile until the new variant is handled. This is the same exhaustiveness guarantee a concrete `match` gives, recovered for a generic matcher. + +The loop itself is a monadic pipeline. Each handler returns `Result` — `Ok` for a match, `Err` for a miss carrying the remainder — and the matcher runs the list under a monad that short-circuits on `Ok` and threads the `Err` forward, so the list executes handler by handler until one matches. The per-variant handler list is assembled from small adapters: an extract adapter that tries one variant and forwards its payload, and an unwrap adapter that strips the variant name off the payload before handing the bare value to the variant's computer. A matcher can also build this list automatically from the enum's own variant list, so that wiring an enum to a matcher requires naming the matcher alone rather than spelling out a handler per variant. + +## Routing fields of a record to handlers + +Building a record is the mirror image of matching a variant: instead of taking a value apart and stopping at one branch, it starts from nothing and runs a handler for every field. The builder begins with an empty *partial record* from [`HasBuilder`](../reference/traits/has_builder.md), in which every field is marked absent, and pipes that partial record through a list of per-field handlers. Each handler computes one field's value — it may read the fields already set on the partial record, since it sees the builder by reference — and sets that field, advancing the partial record so one more field is now present. After the whole list has run, the partial record has every field present, and the builder finalizes it into the concrete struct. + +Finalizing is where the per-field tracking becomes load-bearing. The operation that turns a partial record back into the concrete struct is available only when every field is present, so a builder that omits a handler for some field cannot finalize and fails to compile, rather than producing a half-built value at runtime. Just as a matcher proves it covered every variant, a builder proves it filled every field. The field handlers are again small adapters: one that computes and sets a single named field, and one that computes a whole record's worth of fields and merges all the shared ones in at once, which is convenient for extending an existing record with a few extra fields. + +The matcher and builder directions are two uses of the same underlying idea — a type-level list of per-element handlers run as a CGP provider — applied to the two halves of the extensible-data system. Matching consumes a sum type and the list runs until one element succeeds; building produces a product type and the list runs over every element. Both are expressed as handler providers, so a dispatcher can appear anywhere a `Computer` or `Handler` is expected: wired into a context, nested inside another dispatcher to handle a group of variants with a sub-matcher, or chained with other handler combinators. + +## How the pieces fit together + +Dispatching is the top layer of a stack, and each layer below it supplies one capability the layer above relies on. At the base, the extensible-data derives give an enum the extractor traits and a record the builder traits, and expose the type-level variant or field list through [`has_fields`](../reference/traits/has_fields.md). The [`extract_field`](../reference/traits/extract_field.md) family turns those into the per-variant extraction-and-remainder operations, and the [`has_builder`](../reference/traits/has_builder.md) family into the per-field set-and-finalize operations. On top of those, the [handler components](../reference/components/handler.md) — [`Computer`](../reference/components/computer.md), `TryComputer`, and `Handler` — provide the uniform "context, code, input → output" interface that every per-element handler and every dispatcher speaks. The matcher loop is then a monadic pipeline ([`PipeMonadic`](../reference/providers/monad_providers.md) under an `Ok`-threading monad) and the builder a handler pipeline, both producing ordinary providers. + +The concrete combinators that occupy the top layer are documented in [`dispatch_combinators`](../reference/providers/dispatch_combinators.md). On the matching side they are the `MatchWithHandlers` family (owned, by-reference, and by-mutable-reference), the `MatchFirstWithHandlers` family for handlers that take extra arguments, and the `MatchWithValueHandlers`/`MatchWithFieldHandlers` conveniences that build the handler list from the enum's variants; the per-variant adapters are `ExtractFieldAndHandle`, `HandleFieldValue`, and the grouped-variant `DowncastAndHandle`. On the building side they are `BuildWithHandlers`, which drives the whole assembly, and the per-field adapters `BuildAndSetField` and `BuildAndMerge`. When the goal is simply to turn a Rust trait with one impl per type into a variant-dispatching impl over an enum, the attribute macro [`#[cgp_auto_dispatch]`](../reference/macros/cgp_auto_dispatch.md) generates the matcher wiring automatically, so the pattern can be used without naming any of these providers directly. + +## Related constructs + +The providers that implement this pattern are catalogued in [`dispatch_combinators`](../reference/providers/dispatch_combinators.md). The matching half rests on [`extract_field`](../reference/traits/extract_field.md) and the building half on [`has_builder`](../reference/traits/has_builder.md), with the variant or field list coming from [`has_fields`](../reference/traits/has_fields.md). Every dispatcher and per-element handler is a member of the handler families documented in [`handler`](../reference/components/handler.md) and [`computer`](../reference/components/computer.md), and the matcher loop is the monadic pipeline from [`monad_providers`](../reference/providers/monad_providers.md). The macro that automates the whole pattern for a per-type trait is [`#[cgp_auto_dispatch]`](../reference/macros/cgp_auto_dispatch.md), and the broader idea of running providers as composable handlers is covered in [`handlers`](handlers.md). The data-type view behind the two directions — what a record and an enum are as extensible structures — is in [extensible records](extensible-records.md) and [extensible variants](extensible-variants.md), worked through end to end in the [application builder](../examples/application-builder.md) example for records and the [extensible shapes](../examples/extensible-shapes.md) and [expression interpreter](../examples/expression-interpreter.md) examples for variants. + +## Source + +The dispatching pattern is implemented entirely by the providers in [crates/extra/cgp-dispatch/src/providers/](../../crates/extra/cgp-dispatch/src/providers/), which build on the extractor and builder traits in [crates/core/cgp-field/src/traits/](../../crates/core/cgp-field/src/traits/) and the handler components in [crates/extra/cgp-handler/src/](../../crates/extra/cgp-handler/src/). The matcher loop reuses the monadic pipeline from [crates/extra/cgp-monad/src/](../../crates/extra/cgp-monad/src/). Tests covering both directions are in [crates/tests/cgp-tests/tests/extensible_data_tests/](../../crates/tests/cgp-tests/tests/extensible_data_tests/), and the macro that generates dispatch wiring is exercised in [crates/tests/cgp-tests/tests/dispatcher_macro_tests/](../../crates/tests/cgp-tests/tests/dispatcher_macro_tests/). diff --git a/docs/concepts/extensible-records.md b/docs/concepts/extensible-records.md new file mode 100644 index 00000000..10843bc6 --- /dev/null +++ b/docs/concepts/extensible-records.md @@ -0,0 +1,75 @@ +# Extensible records + +Extensible records let code build and read a struct through the type-level names and types of its fields, without naming the concrete struct, so that independent components can each contribute part of a record and have their contributions merged into a whole. + +## Purpose + +Extensible records solve the problem that a plain Rust struct is closed against incremental, decoupled construction. A struct literal requires every field to be supplied at one place that names the concrete type, and a hand-written constructor that grows with each new subsystem becomes a single point that every change must edit. Extensible records break that coupling by treating a struct as a *product of named fields*: code can ask for "any context with a `db_path` field" or assemble "whatever struct these field contributions add up to" without ever spelling out the full type. This brings the row-polymorphism of languages like PureScript and the structural typing of records to Rust, expressed entirely through traits and resolved at compile time. + +The payoff is the **extensible builder pattern**, where the construction of one struct is split across several independent providers — one per subsystem — that need not know the final type or each other. Each provider builds a small piece, and a dispatcher merges the pieces into the target struct. Because the pieces are decoupled, the same provider can feed many different target structs, and a target struct can be assembled from a different mix of providers per context, with the compiler verifying that every required field is supplied. The [application builder](../examples/application-builder.md) example develops this pattern end to end. + +## A record as a product of named fields + +The foundation is the view of a struct as a list of named fields, exposed by [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md). The output struct that a builder produces in the [application builder](../examples/application-builder.md) example derives the three record traits together: + +```rust +#[derive(HasField, HasFields, BuildField)] +pub struct SqliteClient { + pub sqlite_pool: SqlitePool, +} +``` + +Deriving `HasFields` gives the struct a `Fields` associated type that is a [`Product!`](../reference/macros/product.md) of [`Field`](../reference/types/field.md) entries — the type-level spelling of its shape, one entry per field tagged by name: + +```rust +type Fields = Product![Field]; +``` + +Reading a single field by name is the older and simpler capability provided by [`#[derive(HasField)]`](../reference/derives/derive_has_field.md), which generates one [`HasField`](../reference/traits/has_field.md) impl per field keyed on a [`Symbol!`](../reference/macros/symbol.md) tag, and is what getter traits written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) resolve against. Reading is only half the story; constructing a struct field by field is what extensible records add on top. + +## Partial records and the builder traits + +Incremental construction runs through a *partial record* — a companion type generated by [`#[derive(BuildField)]`](../reference/derives/derive_build_field.md) that carries one [`MapType`](../reference/traits/map_type.md) marker per field recording whether that field is present yet. The marker `IsPresent` stores the field's real value and `IsNothing` stores `()`, so a partial record with every marker `IsNothing` is an empty builder and one with every marker `IsPresent` is a fully populated value. The [builder trait family](../reference/traits/has_builder.md) walks between these states: `HasBuilder::builder()` produces the empty partial record, each `build_field` call flips one marker from `IsNothing` to `IsPresent`, and `FinalizeBuild::finalize_build` turns the partial record back into the concrete struct. + +The tracking is what makes the pattern safe. Because `finalize_build` is implemented only for the all-present configuration of the partial record, finalizing a value with any field still absent is a compile error rather than a runtime panic, and the order in which fields are built does not matter. In the [application builder](../examples/application-builder.md) example the lifecycle assembles the application context — `builder()` produces the empty partial `App`, each step fills more fields, and `finalize_build` closes it out: + +```rust +let app: App = App::builder() // partial App: every field IsNothing + .build_from(sqlite_client) // the SqliteClient's field is now IsPresent + .build_from(http_client) // ... + .build_from(open_ai_client) // every remaining field now IsPresent + .finalize_build(); // FinalizeBuild exists only at the all-present configuration +``` + +Moving `finalize_build` earlier, before every field is set, would not type-check. The reverse direction is equally useful: `IntoBuilder` turns a complete struct into an all-present partial record, and `TakeField` removes one field at a time, which is how one record's fields are moved into another's builder. + +## Merging one record into another + +Moving the shared fields of a source struct into a target builder in a single step is the job of `CanBuildFrom`, documented with the other [structural casts](../reference/traits/cast.md). Its `build_from` method recurses over the source's field list, using `TakeField` to pull each field out of the source and `BuildField` to write it into the target, so a builder can absorb fields from several smaller structs in sequence before being finalized. This is what lets a small struct like a database client be merged into a larger application struct without either type knowing about the other — they share only the field names, and the names are matched at the type level. + +## The extensible builder pattern + +The builder dispatcher turns a list of independent builder providers into a single provider that constructs a whole struct. Each subsystem is built by a [`Handler`](../reference/components/handler.md) (or [`Computer`](../reference/components/computer.md)) provider that produces a small output struct, and `BuildAndMergeOutputs` — one of the [dispatch combinators](../reference/providers/dispatch_combinators.md) — runs every provider, merges each output into the target's builder via `build_from`, and finalizes the result. Wiring it for a context is a single entry naming the target struct and the provider list: + +```rust +delegate_components! { + FullAppBuilder { + HandlerComponent: + BuildAndMergeOutputs, + } +} +``` + +Because the dispatcher is generic over the target and the provider list, swapping a subsystem (a different database, a different model) means changing one entry in the list, and selecting among several target structs is a matter of [code-based dispatch](../reference/providers/use_delegate.md) on the builder's `Code` parameter. The general routing mechanism this rests on is described in [dispatching](dispatching.md). + +## Related constructs + +Extensible records are the product half of CGP's extensible data; the sum half is covered in [extensible variants](extensible-variants.md), and the two are deliberately dual. The shape of a record comes from [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md) (or the all-in-one [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md)), individual field reads from [`HasField`](../reference/traits/has_field.md), and incremental construction from the [builder family](../reference/traits/has_builder.md) generated by [`#[derive(BuildField)]`](../reference/derives/derive_build_field.md). The presence markers are [`MapType`](../reference/traits/map_type.md) implementations, merging is `CanBuildFrom` from [casting](../reference/traits/cast.md), and the builder dispatcher is among the [dispatch combinators](../reference/providers/dispatch_combinators.md), built on the [handler families](handlers.md). The worked example is the [application builder](../examples/application-builder.md). + +## Source + +The record traits live in [crates/core/cgp-field/src/traits/](../../crates/core/cgp-field/src/traits/) (`has_builder.rs`, `build_field.rs`, `update_field.rs`, `take_field.rs`) with the merge logic in [crates/core/cgp-field/src/impls/build_from.rs](../../crates/core/cgp-field/src/impls/build_from.rs) and the `MapType` markers in [crates/core/cgp-field/src/impls/map_type.rs](../../crates/core/cgp-field/src/impls/map_type.rs). The builder dispatchers are in [crates/extra/cgp-dispatch/src/providers/](../../crates/extra/cgp-dispatch/src/providers/). End-to-end tests are under [crates/tests/cgp-tests/tests/extensible_data_tests/records/](../../crates/tests/cgp-tests/tests/extensible_data_tests/records/). diff --git a/docs/concepts/extensible-variants.md b/docs/concepts/extensible-variants.md new file mode 100644 index 00000000..059db6af --- /dev/null +++ b/docs/concepts/extensible-variants.md @@ -0,0 +1,88 @@ +# Extensible variants + +Extensible variants let code construct and deconstruct an enum through the type-level names and types of its variants, without naming the concrete enum, so that independent components can each handle one variant and operate on any enum that contains it. + +## Purpose + +Extensible variants are the dual of [extensible records](extensible-records.md): where a record is a product of named fields, an enum is a *sum of named variants*, and the same name-driven, decoupled treatment applies. They address the [expression problem](https://en.wikipedia.org/wiki/Expression_problem) — the difficulty of adding new variants to a data type and new operations over it without editing existing code. A concrete `enum` plus a `match` bakes the full set of variants into every function that consumes it, so adding a variant forces every `match` to change, and a variant defined in an upstream crate cannot be handled without forking. Extensible variants decouple each variant's handling from the enum definition, bringing polymorphic variants in the style of OCaml and structural sum types to Rust through traits resolved at compile time. + +The payoff is the **extensible visitor pattern**, where the logic for each variant lives in its own provider and a dispatcher routes a value to the provider for its current variant. New variants can be added without touching the handlers for the others, new operations can be added without touching the enum, and the same handler set can serve several enums that share variants. The [expression interpreter](../examples/expression-interpreter.md) example develops this pattern for a recursive arithmetic language, solving the expression problem, and the [extensible shapes](../examples/extensible-shapes.md) example applies it to a non-recursive enum of geometric shapes. + +## An enum as a sum of named variants + +The shape of an enum is exposed by [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md), whose `Fields` associated type for an enum is a [`Sum!`](../reference/macros/sum.md) of [`Field`](../reference/types/field.md) entries built on the [`Either`/`Void`](../reference/types/either.md) spine, mirroring the `Product!`/`Cons` spine that a record uses. The expression enum in the [expression interpreter](../examples/expression-interpreter.md) example derives the three variant traits together: + +```rust +#[derive(HasFields, FromVariant, ExtractField)] +pub enum MathExpr { + Plus(Plus), + Times(Times), + Literal(Literal), +} +``` + +Its `HasFields` shape is a sum of one `Field` per variant, each tagged by the variant name: + +```rust +type Fields = Sum![ + Field>, + Field>, + Field>, +]; +``` + +Constructing an enum from one of its variants generically is [`FromVariant`](../reference/traits/from_variant.md), generated by [`#[derive(FromVariant)]`](../reference/derives/derive_from_variant.md), and deconstructing it one variant at a time is the extractor family generated by [`#[derive(ExtractField)]`](../reference/derives/derive_extract_field.md). One restriction shapes all of this: a derivable enum must follow the sum-of-products form, where each variant holds exactly one unnamed field — wrap richer payloads in a dedicated struct so the variant's `Value` type stays a single, nameable type. The standalone `Plus`, `Times`, and `Literal` types each variant wraps are exactly such payload structs. + +## Partial variants and the extractor traits + +Deconstruction runs through a *partial variant* — the enum companion that the extractor derive generates, carrying one [`MapType`](../reference/traits/map_type.md) marker per variant, just as a partial record carries one per field. The crucial difference is the marker used for absence: a record uses `IsNothing` (storing `()`), while a variant uses `IsVoid`, which maps every variant to the uninhabited [`Void`](../reference/types/either.md) type. The [extractor family](../reference/traits/extract_field.md) drives the deconstruction: `HasExtractor::to_extractor` converts the enum into a partial variant with every variant still `IsPresent`, and `ExtractField::extract_field` tries to pull out one variant, returning `Result` — `Ok` with the payload if the value is that variant, `Err` with the *remainder* (the partial variant with that variant marked `IsVoid`) otherwise. Deconstructing an enum by hand reads as a chain of `extract_field` calls, each handling one variant and passing the remainder to the next: + +```rust +match expr.to_extractor() // partial MathExpr: every variant still possible + .extract_field(PhantomData::) +{ + Ok(Literal(value)) => value, // it was the Literal variant + Err(remainder) => { + // remainder's type now has Literal ruled out (IsVoid); try the next variant + ... + } +} +``` + +Marking extracted variants as `Void` is what gives compile-time exhaustiveness without a wildcard arm. Each failed `extract_field` rules out one more variant in the type, so after every variant has been tried the remainder has every marker `IsVoid` and is therefore uninhabited — a value that cannot exist. `FinalizeExtract::finalize_extract` discharges such a remainder with an empty `match`, which is sound precisely because no value can reach it, and the compiler accepts the absence of a fallback case. Add a variant to the enum without handling it and the final remainder becomes inhabited again, so the code fails to compile until the new variant is covered — the same guarantee a concrete `match` gives, recovered for generic code. + +## Upcasting and downcasting + +Because variants are matched by name, two enums that share a subset of variants can interconvert with no hand-written conversion, through the [casting traits](../reference/traits/cast.md). `CanUpcast` lifts a value of a narrow enum into a wider one whose variants are a superset — it always succeeds, since every source variant has a home in the target. `CanDowncast` goes the other way, narrowing a wide enum into a smaller one and succeeding only if the value's current variant exists in the target, otherwise handing back a remainder that can be downcast again against another candidate. Both reuse the same extractor recursion that powers deconstruction, differing only in whether the variant list comes from the source or the target. Upcasting is also how a provider can construct a value using only the subset of variants it cares about: it builds a small local enum and upcasts it into the full type, the variant-side analog of reading a field through a getter. In the [expression interpreter](../examples/expression-interpreter.md) example a to-Lisp provider does exactly this, constructing a two-variant local enum and lifting each value into the complete `LispExpr`: + +```rust +let ident = LispSubExpr::Ident(Ident("+".to_owned())).upcast(PhantomData::); +``` + +## The extensible visitor pattern + +The visitor dispatcher turns a per-variant handler set into a single provider over the whole enum. `MatchWithValueHandlers` and its by-reference counterpart `MatchWithValueHandlersRef` — among the [dispatch combinators](../reference/providers/dispatch_combinators.md) — take the enum's variant list, derive one extract-and-handle step per variant, and run them as a monadic pipeline that short-circuits on the first matching variant and threads the remainder forward otherwise. Each variant is handled by a [`Computer`](../reference/components/computer.md) or [`ComputerRef`](../reference/components/computer.md) provider, so the operation composes with the rest of CGP, and the dispatcher's optional provider parameter defaults to [`UseContext`](../reference/providers/use_context.md) so a context can override the handling of individual variants in its wiring. Wiring an enum to a matcher names one provider per variant in a table and routes the whole enum through the dispatcher: + +```rust +delegate_components! { + Interpreter { + ComputerComponent: + UseInputDelegate: EvalAdd, // one provider per variant + Times: EvalMultiply, + Literal: EvalLiteral, + }>, + } +} +``` + +The `MathExpr` entry routes to `DispatchEval`, a thin context-specific provider that defers to `MatchWithValueHandlers`; that wrapper exists to break the trait-resolution cycle between the matcher and the per-variant providers it dispatches to. The routing mechanism is described in full in [dispatching](dispatching.md), and the monadic pipeline that sequences the per-variant steps in [monadic handlers](monadic-handlers.md). + +## Related constructs + +Extensible variants are the sum half of CGP's extensible data; the product half is [extensible records](extensible-records.md), and the two share their `MapType` markers, their casting traits, and their dispatch machinery. An enum's shape comes from [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md) (or the all-in-one [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md)), construction from [`FromVariant`](../reference/traits/from_variant.md) via [`#[derive(FromVariant)]`](../reference/derives/derive_from_variant.md), and deconstruction from the [extractor family](../reference/traits/extract_field.md) via [`#[derive(ExtractField)]`](../reference/derives/derive_extract_field.md). The presence markers are [`MapType`](../reference/traits/map_type.md) implementations, conversions are `CanUpcast`/`CanDowncast` from [casting](../reference/traits/cast.md), and the visitor dispatchers are among the [dispatch combinators](../reference/providers/dispatch_combinators.md), built on the [handler families](handlers.md) and [monadic handlers](monadic-handlers.md). The worked examples are the [expression interpreter](../examples/expression-interpreter.md), which applies the pattern to a recursive language, and [extensible shapes](../examples/extensible-shapes.md), which applies it to a non-recursive enum. + +## Source + +The variant traits live in [crates/core/cgp-field/src/traits/](../../crates/core/cgp-field/src/traits/) (`extract_field.rs`, `from_variant.rs`) with `HasExtractor`, `FinalizeExtract`, and the casting recursion in [crates/core/cgp-field/src/impls/cast.rs](../../crates/core/cgp-field/src/impls/cast.rs), and the `IsVoid` marker in [crates/core/cgp-field/src/impls/map_type.rs](../../crates/core/cgp-field/src/impls/map_type.rs). The visitor dispatchers and their monadic pipeline are in [crates/extra/cgp-dispatch/src/providers/](../../crates/extra/cgp-dispatch/src/providers/) and [crates/extra/cgp-monad/src/](../../crates/extra/cgp-monad/src/). End-to-end tests are under [crates/tests/cgp-tests/tests/extensible_data_tests/](../../crates/tests/cgp-tests/tests/extensible_data_tests/). diff --git a/docs/concepts/handlers.md b/docs/concepts/handlers.md new file mode 100644 index 00000000..219f57b0 --- /dev/null +++ b/docs/concepts/handlers.md @@ -0,0 +1,43 @@ +# Handlers + +The handler family is a group of CGP components that all model the same shape — turning an `Input` value into an `Output` value under a phantom `Code` tag — while varying along the axes of synchrony, fallibility, and how the input is passed. + +## The idea + +A handler is a provider that transforms an `Input` into an `Output`, with the kind of computation it performs selected by a phantom `Code` tag. Every component in this family follows the same `Code`/`Input`/`Output` model: the consumer method takes a `PhantomData` to say *which* computation is wanted, takes the `Input` value to compute over, and produces an associated `Output` type. The `Code` tag carries no data — it exists purely so that one context can host many different handlers, each keyed by a distinct tag type, and so that wiring can dispatch on it. This is why the methods thread a `PhantomData` argument rather than a real value. + +The family exists because real computations differ along a few independent dimensions, and CGP gives each combination its own component so that a provider declares exactly the capabilities it has. A pure function that adds two numbers needs nothing from the context; a function that reads a file may fail and must work with the context's error type; a function that talks to the network must be async. Rather than forcing every computation into the most general signature, the family offers a spectrum of components from the simplest (`Computer`) to the most general (`Handler`), and provides combinators that promote a provider from a simpler variant into a more capable one when the wiring needs it. A provider author writes against the weakest variant that fits, and the combinators bridge the gap. + +The unifying type signature is worth holding in mind before the variations: a handler maps `(context, Code, Input) -> Output`, where `Output` is an associated type chosen by the provider, not a generic parameter the caller fixes. Because `Output` is associated, the provider decides what it returns given a `Code` and `Input`, and downstream wiring reads that decision back out. The fallible and async variants refine this signature — wrapping `Output` in a `Result`, or returning it from a future — without changing the underlying `Code`/`Input`/`Output` correspondence. + +## The three axes of variation + +The family varies along three independent axes, and a component's name encodes its position on each. Understanding the axes makes the otherwise large set of components systematic rather than arbitrary. + +The first axis is **synchronous versus asynchronous**. A synchronous component computes and returns immediately; an asynchronous one returns a future, and its method is declared `async`. The async variants carry an `Async` in their name — `AsyncComputer` is the async counterpart of `Computer`. The most general component, `Handler`, is async by definition and so does not need the `Async` qualifier. + +The second axis is **infallible versus fallible**. An infallible component always produces its `Output`; a fallible one returns `Result` against the context's abstract error type, and so supertraits [`HasErrorType`](../reference/components/has_error_type.md). The fallible synchronous component is `TryComputer`, named with the `Try` prefix that marks the whole fallible-but-synchronous corner of the family. The fully general `Handler` is fallible as well as async. + +The third axis is **owned input versus by-reference input**. By default a handler consumes its `Input` by value, taking ownership; the `*Ref` variants instead borrow the input as `&Input`, which suits computations that only need to read their argument. Every base component has a `*Ref` sibling: `Computer` has `ComputerRef`, `AsyncComputer` has `AsyncComputerRef`, `TryComputer` has `TryComputerRef`, and `Handler` has `HandlerRef`. The `Ref` variants share the same `Code`/`Output` model and differ only in taking `&Input` where the owned variant takes `Input`. + +Combining these axes yields the components documented separately. The pure-computation corner — synchronous, infallible, in owned and by-reference forms, plus their async counterparts — is [`Computer`](../reference/components/computer.md). The fallible-but-synchronous corner is [`TryComputer`](../reference/components/try_computer.md). The fully general async-and-fallible corner is [`Handler`](../reference/components/handler.md). A fifth component, [`Producer`](../reference/components/producer.md), sits slightly apart: it is a handler with *no* input at all, producing a value from the context and a `Code` tag alone. + +## Wiring like any component + +Each handler component is an ordinary CGP component defined with `#[cgp_component]`, so it is consumed, implemented, and wired exactly like every other component. A context calls the consumer trait — `CanCompute`, `CanTryCompute`, `CanHandle`, `CanProduce`, and the rest — and a provider implements the matching provider trait — `Computer`, `TryComputer`, `Handler`, `Producer`. The context selects which provider answers a given component by delegating the component's marker (`ComputerComponent`, `HandlerComponent`, and so on) to a provider in its [`delegate_components!`](../reference/macros/delegate_components.md) table. Nothing about handlers requires special wiring machinery. + +Because the consumer methods carry a `Code` and an `Input` as type parameters, the handler components are defined to support dispatch on both. Each is declared with `#[derive_delegate(UseDelegate)]` and `#[derive_delegate(UseInputDelegate)]`, so a context can route to different providers based on the `Code` tag or on the `Input` type using an inner type-level table, in the manner described in [dispatching](dispatching.md). `UseDelegate` keys the lookup on `Code`; the family's own `UseInputDelegate` keys it on `Input`. This is what lets a single context host many independent handlers — one per `Code` — and still wire each to a different provider. + +## Promoting providers between variants + +A provider written for a simpler variant can be lifted into a more capable one automatically, which is why an author only ever implements the weakest variant that fits. The lifting follows the natural inclusions between the axes: an infallible computation is trivially a fallible one that never returns an error, a synchronous computation is trivially an async one that never awaits, and an owned-input computation can serve a by-reference caller by dereferencing the borrow. CGP encodes each of these inclusions as a combinator provider — `Promote`, `PromoteAsync`, `TryPromote`, `PromoteRef`, and the bundled `Promote*` tables — that wraps a provider of one variant and re-exposes it as another. + +The practical consequence is that wiring one provider can satisfy several handler components at once. The bundled promotion tables ([`PromoteComputer`](../reference/providers/handler_combinators.md), `PromoteProducer`, `PromoteTryComputer`, and the rest) delegate every other handler component to the appropriate single-step promoter, so delegating a context's whole handler surface to one of them makes a single underlying provider answer `CanCompute`, `CanTryCompute`, `CanComputeAsync`, `CanHandle`, and their `Ref` forms together. The full catalogue of promotion combinators, along with `ReturnInput`, `ComposeHandlers`, and `PipeHandlers`, is documented in [handler combinators](../reference/providers/handler_combinators.md). The convenience macros [`#[cgp_computer]`](../reference/macros/cgp_computer.md) and [`#[cgp_producer]`](../reference/macros/cgp_producer.md) lean on these tables: they turn a plain function into a provider for the narrowest fitting component and then wire the promotion table so the same function answers the entire family. + +## Related constructs + +The handler family builds directly on a few core constructs. The fallible variants — `TryComputer`, `Handler`, and their `Ref` forms — supertrait [`HasErrorType`](../reference/components/has_error_type.md), so their `Result` return type names the context's abstract error. The per-component documents cover each corner in detail: [`Computer`](../reference/components/computer.md) for pure synchronous and async computation, [`TryComputer`](../reference/components/try_computer.md) for fallible synchronous computation, [`Handler`](../reference/components/handler.md) for the general async-and-fallible case, and [`Producer`](../reference/components/producer.md) for the no-input case. The combinators that move providers between these components are described in [handler combinators](../reference/providers/handler_combinators.md), and the macros that generate handler providers from functions are [`#[cgp_computer]`](../reference/macros/cgp_computer.md) and [`#[cgp_producer]`](../reference/macros/cgp_producer.md). Dispatching a handler on its `Code` or `Input` uses the mechanism in [dispatching](dispatching.md), and chaining handlers into pipelines is the subject of [monadic handlers](monadic-handlers.md). + +## Source + +The handler components are defined in [crates/extra/cgp-handler/src/components/](../../crates/extra/cgp-handler/src/), with one module per corner: `computer.rs`, `async_computer.rs`, `try_compute.rs`, `handler.rs`, and `produce.rs`. The promotion combinators and the `UseInputDelegate` dispatch type live in [crates/extra/cgp-handler/src/providers/](../../crates/extra/cgp-handler/src/) and `types.rs`. The family is re-exported through `cgp::extra::handler`. Behavioral tests covering the full promotion surface are in [crates/tests/cgp-tests/tests/handler_tests/](../../crates/tests/cgp-tests/tests/handler_tests/). diff --git a/docs/concepts/higher-order-providers.md b/docs/concepts/higher-order-providers.md new file mode 100644 index 00000000..0e7bb59e --- /dev/null +++ b/docs/concepts/higher-order-providers.md @@ -0,0 +1,82 @@ +# Higher-order providers + +A higher-order provider is a provider parameterized by another provider, so that part of its behavior is supplied by an inner provider it delegates to rather than fixed in its own code. + +## The idea + +A higher-order provider takes another provider as a generic parameter and builds its result on top of whatever that inner provider computes. The motivating shape is a wrapper that transforms the output of an existing implementation: a `ScaledArea` provider that multiplies whatever area an `InnerCalculator` produces, an `IterSumArea` provider that sums the areas an inner calculator returns for each element of a collection, a `GloballyScaledArea` provider that applies a context-wide factor on top of a base calculation. In each case the outer provider knows the *transformation* but not the *base case* — the base case is a parameter. + +This is the type-level counterpart to passing a function to a function. Ordinary CGP providers are already swappable behaviors selected at wiring time; a higher-order provider lets one behavior be expressed in terms of another, so the two compose. Wiring `AreaCalculatorComponent` to `ScaledArea` reads as "compute the rectangle area, then scale it," and `ScaledArea` reuses the same scaling logic over a different base. The composition happens entirely in types, with no runtime indirection, because providers carry no runtime value — they are type-level markers naming an implementation, so nesting them costs nothing. + +A higher-order provider is written like any other provider with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), with the inner provider declared as a generic parameter in the provider's `Self` position and bound to a provider trait in the `where` clause. The body invokes the inner provider as an associated function on the context. The skeleton, before the ergonomic attributes below, is a provider struct generic over an inner provider whose `where` clause requires that inner provider to implement the component's provider trait for the same context. + +## The stray `` on the inner bound + +The detail that makes higher-order providers look stranger than they are is the extra `` argument on the inner provider's bound. A provider trait, as generated by [`#[cgp_component]`](../reference/macros/cgp_component.md), moves the original `Self` into an explicit leading `Context` type parameter — `AreaCalculator` rather than the consumer trait's `CanCalculateArea`. So when a higher-order provider depends on its inner provider, the bound must name that context argument explicitly: + +```rust +#[cgp_impl(new ScaledArea)] +impl AreaCalculator +where + InnerCalculator: AreaCalculator, +{ + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + InnerCalculator::area(self) * scale_factor * scale_factor + } +} +``` + +The `` in `InnerCalculator: AreaCalculator` is exactly the leading context slot, filled with the context the outer provider is implementing for. It has no analogue in the consumer trait `CanCalculateArea`, so a reader who thinks of providers as "just the consumer trait implemented elsewhere" is caught off guard by it. The same asymmetry appears at the call site: the inner provider is invoked as the associated function `InnerCalculator::area(self)`, not as a method `self.area()`, because the provider trait's method takes the context as an explicit first argument. + +## Hiding the friction with `#[use_provider]` + +The [`#[use_provider]`](../reference/attributes/use_provider.md) attribute exists to erase the bound surprise, and is the idiomatic way to write higher-order providers. Written alongside `#[cgp_impl]`, it takes the inner bound without the context argument and fills the `` back in for you: + +```rust +#[cgp_impl(new ScaledArea)] +#[use_provider(InnerCalculator: AreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + let base_area = InnerCalculator::area(self); + base_area * scale_factor * scale_factor + } +} +``` + +The author writes `InnerCalculator: AreaCalculator` and the macro emits `InnerCalculator: AreaCalculator` into the `where` clause, so the bound reads as if the provider trait had the same shape as the consumer trait. The call-site asymmetry remains, by contrast: `#[use_provider]` completes the bound only and does not rewrite the body, so the inner provider is still invoked as the associated function `InnerCalculator::area(self)`, passing the context explicitly. That spelled-out call is the one piece of source that still reflects the provider trait's leading context argument. See [`#[use_provider]`](../reference/attributes/use_provider.md) for the exact rules. + +## `UseContext` as a default inner provider + +A higher-order provider can default its inner provider to [`UseContext`](../reference/providers/use_context.md) so that, absent an explicit choice, it falls back to whatever the context itself is already wired to. This requires giving the provider an explicit struct definition with a default generic parameter: + +```rust +pub struct IterSumArea(pub PhantomData); +``` + +`UseContext` is CGP's provider that implements a provider trait by deferring to the context's own consumer-trait implementation — the dual of the consumer blanket impl. With `IterSumArea` as the default, an unparameterized `IterSumArea` computes each inner element through the context's existing area wiring, so a higher-order provider can be dropped in without restating the base case. Overriding the parameter — `IterSumArea` — instead binds the inner behavior statically, bypassing the context's wiring, which is useful for overriding or short-circuiting what the context would otherwise resolve. The default only exists when the provider has an explicit struct with the default parameter; a provider defined purely through `#[cgp_impl(new ...)]` has no default and must always have its inner provider named. + +## Not every generic provider is higher-order + +A provider that merely has generic parameters is not a higher-order provider; it becomes one only when a parameter is constrained to implement a provider trait. The distinction matters because many providers are generic for unrelated reasons. The `UseField`-style getter provider is generic over a field tag: + +```rust +#[cgp_impl(new GetName)] +impl NameGetter +where + Self: HasField, +{ + fn name(&self) -> &str { + self.get_field(PhantomData) + } +} +``` + +Here `Tag` is a type-level field name, used only as a `HasField` key, with no provider-trait bound — so `GetName` is an ordinary parameterized provider, not a higher-order one. The defining trait of a higher-order provider is that a generic parameter appears in a provider-trait bound and is invoked to do part of the work, as `InnerCalculator: AreaCalculator` does. When that bound is absent, the `#[use_provider]` attribute has nothing to fill in, and none of the higher-order machinery applies. + +## Related constructs + +Higher-order providers are written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) (or `#[cgp_fn]`), giving the inner provider as a generic parameter in the provider's `Self` position. The [`#[use_provider]`](../reference/attributes/use_provider.md) attribute is the idiomatic tool for them: it supplies the hidden `` on the inner bound, leaving the body to invoke the inner provider as the associated-function call the provider trait actually requires. [`UseContext`](../reference/providers/use_context.md) serves as the default inner provider when the higher-order provider is given an explicit struct with a defaulted parameter, letting it fall back to the context's own wiring. For dispatching to different inner providers based on a generic type rather than naming one statically, [`UseDelegate`](../reference/providers/use_delegate.md) pairs naturally with higher-order providers — a nested delegation table can map each shape to a different `ScaledArea<...>`. Each layer of a nested provider can be verified independently with the `#[check_providers]` form of [`check_components!`](../reference/macros/check_components.md), which is what makes higher-order wiring debuggable. + +## Source + +The higher-order pattern is exercised throughout [crates/tests/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs](../../crates/tests/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs) and [shape.rs](../../crates/tests/cgp-tests/tests/component_tests/cgp_impl/shape.rs); `UseContext` lives in [crates/core/cgp-component/src/providers/use_context.rs](../../crates/core/cgp-component/src/providers/use_context.rs). diff --git a/docs/concepts/impl-side-dependencies.md b/docs/concepts/impl-side-dependencies.md new file mode 100644 index 00000000..5b326012 --- /dev/null +++ b/docs/concepts/impl-side-dependencies.md @@ -0,0 +1,50 @@ +# Impl-side dependencies + +The core idea of CGP is dependency injection through the `where` clause of a blanket impl: a clean trait interface hides the constraints its implementation needs, so a context that satisfies those constraints gains the capability without ever naming them. + +## The problem + +Generic code needs dependencies, and the usual way of declaring them — a `where` clause on a generic function — leaks those dependencies to every caller. A function `fn greet(ctx: &Context) where Context: HasName` forces its constraint onto its signature, so any generic caller of `greet` must repeat `Context: HasName`, and that caller's callers must repeat it again. The bounds propagate outward through the whole call graph, and a function deep in a library can end up demanding that its distant callers spell out requirements they have no direct interest in. + +CGP removes this leak by moving the dependency off the interface and onto an implementation. The capability is exposed as a trait whose declaration mentions none of its requirements; the requirements live only in the `where` clause of the impl that satisfies the trait. A caller bounds on the clean trait alone, and the requirements stay hidden one level down — which is precisely what stops them from cascading through transitive callers. + +## How CGP expresses it + +The mechanism is a blanket impl whose `where` clause carries the hidden constraints. A trait `CanGreet` exposes only `greet`; its blanket impl over a generic context requires `Context: HasName`, so any context implementing `HasName` automatically implements `CanGreet`, while a caller bounding on `CanGreet` never sees `HasName`. + +```rust +use cgp::prelude::*; + +pub trait CanGreet { + fn greet(&self); +} + +impl CanGreet for Context +where + Context: HasName, +{ + fn greet(&self) { + println!("Hello, {}!", self.name()); + } +} +``` + +This constraint-hiding is why a blanket impl is preferred over a generic function for the same logic: the dependency `HasName` is injected at the impl, not demanded at the interface, so transitive callers carry nothing. The same `where` clause that would bloat a function's signature becomes invisible plumbing on the impl. Writing this impl by hand is repetitive — the supertrait bounds are stated twice and each default body is copied — which is the boilerplate [`#[blanket_trait]`](../reference/macros/blanket_trait.md) removes: you declare the trait with its supertraits and default bodies, and the macro generates the matching blanket impl. + +## How it scales up + +The same `where`-clause injection appears at every level of CGP, from the simplest extension trait to full components. A [`#[blanket_trait]`](../reference/macros/blanket_trait.md) hides trait dependencies declared as supertraits; a [`#[cgp_component]`](../reference/macros/cgp_component.md) provider hides them in the `where` clause of its provider impl, where they are also captured by [`IsProviderFor`](../reference/traits/is_provider_for.md) so a missing one is reported clearly. In both cases the dependency is a constraint on the implementation that the public interface does not mention. The difference is only that a component admits many alternative providers selected by wiring, while a blanket trait admits exactly one. + +Stating those dependencies is made to read like importing them rather than constraining `Self`. The [`#[uses(...)]`](../reference/attributes/uses.md) attribute turns `#[uses(HasName)]` into a `Self: HasName` predicate on the generated impl, so a provider declares the capabilities its body calls in a line that reads like a `use` statement — the dependency lands on the impl, hidden from anyone who uses the trait. This is the same injection as a hand-written `where Self: HasName`, dressed to match a programmer's existing intuition. + +## Value dependencies, not just trait dependencies + +Impl-side dependencies inject values as readily as they inject capabilities, through the same `where`-clause mechanism keyed on fields instead of traits. A provider that needs a `name` value declares `Context: HasField` in its `where` clause and reads it with `get_field`; any context that derives [`HasField`](../reference/derives/derive_has_field.md) and happens to have a matching field satisfies the bound, with the field never appearing in any interface. The field requirement is injected at the impl exactly as a trait requirement is. + +Because the raw `HasField` bound is verbose, the surface syntax hides it the same way [`#[uses(...)]`](../reference/attributes/uses.md) hides a trait bound. A getter method written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) generates the `HasField` bound from the method name, and an [`#[implicit]`](../reference/attributes/implicit.md) function argument generates it from the argument name — both producing the same injected constraint from familiar-looking code. That value-injection model, where providers look like functions taking arguments sourced from context fields, is the subject of [implicit arguments](implicit-arguments.md). + +## Related constructs + +[`#[blanket_trait]`](../reference/macros/blanket_trait.md) is impl-side dependencies in their purest form: a trait with supertraits and default bodies, plus the generated blanket impl that hides those supertraits. [`#[cgp_component]`](../reference/macros/cgp_component.md) extends the pattern to providers, where the hidden constraints live in each provider's `where` clause and many providers can coexist — see [consumer and provider traits](consumer-and-provider-traits.md) for that duality, and [`IsProviderFor`](../reference/traits/is_provider_for.md) for how the hidden dependencies are surfaced in error messages. + +The injection is declared idiomatically through [`#[uses(...)]`](../reference/attributes/uses.md) for trait dependencies and through [`HasField`](../reference/traits/has_field.md) for value dependencies. Value injection in particular is the foundation of [implicit arguments](implicit-arguments.md), which dresses `HasField` reads as ordinary function parameters. diff --git a/docs/concepts/implicit-arguments.md b/docs/concepts/implicit-arguments.md new file mode 100644 index 00000000..35b56aa6 --- /dev/null +++ b/docs/concepts/implicit-arguments.md @@ -0,0 +1,44 @@ +# Implicit arguments + +Implicit arguments are a programming model in which a provider is written like an ordinary function taking arguments, where arguments marked `#[implicit]` are sourced not from the caller but from fields of the context. + +## The idea + +A provider almost always needs values from its context, and CGP makes asking for them look like asking for function parameters. Instead of declaring a `HasField` bound and calling `get_field` inside the body, the author writes a normal-looking parameter — `#[implicit] width: f64` — that reads as "this function needs a `width` of type `f64`." The macro removes that parameter from the public signature, adds the matching field bound, and binds the value at the top of the body, so the code keeps the shape of a plain function while behaving like a provider that pulls its inputs from the context. + +This framing is the recommended on-ramp to CGP precisely because it requires no new vocabulary. A programmer who understands functions and arguments can write a complete provider without first meeting `HasField`, type-level symbols, or `PhantomData` tags; those mechanics stay hidden behind a parameter that looks ordinary. The model defers the underlying machinery until the author has a reason to care about it. + +## How CGP expresses it + +An implicit argument is a function parameter carrying the [`#[implicit]`](../reference/attributes/implicit.md) attribute, whose name doubles as the context field name it reads from. The attribute is meaningful inside the two macros that rewrite function bodies into providers: [`#[cgp_fn]`](../reference/macros/cgp_fn.md), which turns a free function into a single-implementation capability, and [`#[cgp_impl]`](../reference/macros/cgp_impl.md), which writes a provider for an existing component. In both, the marked argument disappears from the signature and is fetched from the context instead. + +```rust +use cgp::prelude::*; + +#[cgp_fn] +pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} +``` + +The desugaring is the impl-side dependency pattern applied to values: each implicit argument becomes a [`HasField`](../reference/traits/has_field.md) bound on the generated impl and a `let` binding that reads the field at the top of the body. The function above generates a `RectangleArea` trait whose method takes no arguments, and a blanket impl requiring `HasField` and the matching `height` bound, with `width` and `height` read from the context before the body runs. The same rewrite happens identically inside a [`#[cgp_impl]`](../reference/macros/cgp_impl.md) method, where the bounds join the provider impl's `where` clause. The field bounds are satisfied by any context that derives [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) and carries fields of the matching names — no wiring is involved for `#[cgp_fn]`, since its blanket impl applies to every context that has the fields. + +Because the argument name is the field name, the values are addressed by name through the type-level symbol [`HasField`](../reference/traits/has_field.md) uses internally, and the whole `Symbol!`/`get_field`/`PhantomData` apparatus stays out of sight. What the author sees is a function with arguments; what the compiler sees is a provider with injected dependencies. + +## Access rules + +The argument type controls how the field is read, following a small set of rules so that the body receives a value of exactly the declared type. The default rule is that an owned argument type, such as `f64` or `String`, reads the field by reference and appends `.clone()`, so the body gets an owned value while the context keeps its field. The one special case worth memorizing is `&str`: an argument typed `&str` is backed by a `String` field and read with `.as_str()` rather than `.clone()`, letting the body borrow without forcing the context to store a `&str`. + +These same rules govern getter access in [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md), so an author who learns them once applies them everywhere field values are read. The shared rule of thumb is that the declared type is what the body works with, and the macro inserts whatever conversion — a clone for owned values, an `.as_str()` for `&str` — bridges it to the stored field. A few further forms exist for references, options, and slices, all documented with [`#[implicit]`](../reference/attributes/implicit.md). + +## When to use which + +Implicit arguments and getter traits both read context fields through `HasField`, and the choice between them is about where the value is needed. An implicit argument is the right tool when a value is consumed once, at the start of a method, as if it were a parameter — it keeps the field access local to the one function that uses it. A getter trait written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is better when the same field is read across many methods, or part-way through a body rather than up front, because it exposes the field as a reusable `self.name()` accessor instead of re-declaring an implicit argument in every function. + +The two are complementary rather than competing, and a provider commonly uses both. Reach for an implicit argument to inject the inputs a single computation consumes, and for a getter trait to expose a field that several computations share; the underlying `HasField` machinery and access rules are identical, so mixing them carries no conceptual overhead. + +## Related constructs + +The [`#[implicit]`](../reference/attributes/implicit.md) attribute is the construct itself, usable inside [`#[cgp_fn]`](../reference/macros/cgp_fn.md) and [`#[cgp_impl]`](../reference/macros/cgp_impl.md) — the former producing a single-implementation capability with no wiring, the latter a provider for a [`#[cgp_component]`](../reference/macros/cgp_component.md). All of them desugar implicit arguments into [`HasField`](../reference/traits/has_field.md) bounds, which a context supplies by deriving [`#[derive(HasField)]`](../reference/derives/derive_has_field.md). + +For repeated or mid-body field access, [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is the getter-trait counterpart that shares the same `.clone()`/`.as_str()` access rules. The whole model is value-level [impl-side dependencies](impl-side-dependencies.md): an implicit argument is a context dependency injected through a `where`-clause `HasField` bound and dressed as a function parameter. diff --git a/docs/concepts/modular-error-handling.md b/docs/concepts/modular-error-handling.md new file mode 100644 index 00000000..9f7d3988 --- /dev/null +++ b/docs/concepts/modular-error-handling.md @@ -0,0 +1,109 @@ +# Modular error handling + +CGP handles errors modularly by splitting error handling into three independent decisions — what the abstract error type is, how a foreign error is turned into it, and how detail is attached to it — each made by wiring rather than baked into the code that fails. + +## The problem with a fixed error type + +Generic code must be able to fail without committing to a concrete error type. A provider deep in a call graph encounters a parse error, an I/O error, or a domain rule violation, and it has to return *some* error — but it is generic over the context and cannot decide whether the application wants `anyhow::Error`, `std::io::Error`, a boxed trait object, or a hand-rolled enum. Hard-coding any of these into the provider would tie every caller to that choice, and converting between them by hand at each boundary is the boilerplate that error-handling crates exist to remove. + +CGP turns the choice of error type, and the choice of how errors are constructed, into wiring decisions that live in one place per application. The code that fails refers only to an abstract error and to the capabilities of raising and wrapping; a concrete context supplies the error type and the construction strategy when it is assembled. Swapping `anyhow` for `eyre`, or routing one source error through `From` while formatting another into a string, changes a few wiring lines and touches no provider. This is the same [coherence](coherence.md)-bypassing move CGP applies everywhere — keep the implementations open and decide locally per context — applied specifically to error handling. + +## The abstract error type + +The anchor is [`HasErrorType`](../reference/components/has_error_type.md), a one-line abstract-type component that gives a context a single shared `Error` type. Every fallible operation returns `Result` (or the alias `ErrorOf`), so generic code names the error without knowing its concrete identity: + +```rust +#[cgp_type] +pub trait HasErrorType { + type Error: Debug; +} +``` + +Centralizing the error on one trait is what lets errors compose across components. If each fallible component declared its own associated `Error`, a context bounded by several of them would face several unrelated error types with no way to unify them; by having every fallible trait supertrait `HasErrorType`, they all refer to the *same* `Self::Error`. The `Debug` bound is the only requirement the abstract error carries, enough for `.unwrap()` and logging, and it propagates to whatever concrete type a context eventually wires in. A context fixes that type either directly — `impl HasErrorType for App { type Error = anyhow::Error; }` — or, more commonly, by wiring its error-type component to a provider such as `UseType`, since `#[cgp_type]` makes `HasErrorType` an abstract-type component built on [`HasType`/`TypeProvider`](../reference/components/has_type.md). + +## Raising and wrapping as capabilities + +Two further components give the abstract error its behavior, each parameterized so a context can handle many error shapes. [`CanRaiseError`](../reference/components/can_raise_error.md) converts a concrete source error into the context's abstract error, and its companion `CanWrapError` attaches a piece of detail to an existing one. Both supertrait `HasErrorType`, so the error they produce and enrich is the context's shared `Self::Error`: + +```rust +#[cgp_component(ErrorRaiser)] +#[derive_delegate(UseDelegate)] +pub trait CanRaiseError: HasErrorType { + fn raise_error(error: SourceError) -> Self::Error; +} + +#[cgp_component(ErrorWrapper)] +#[derive_delegate(UseDelegate)] +pub trait CanWrapError: HasErrorType { + fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; +} +``` + +These two capabilities cover the everyday error-handling motions: raise a foreign error into the abstract one, and wrap context onto it as it propagates. A provider that fails writes `Context::raise_error(source)` and `Context::wrap_error(err, detail)` — both associated functions, called on the context *type*, because constructing an error is a property of the context rather than of any value in scope. Crucially, the provider states which sources it raises and which details it wraps as [impl-side dependencies](impl-side-dependencies.md) in its `where` clause, so those requirements never leak into the consumer trait a caller bounds on. A loader, for example, needs only `Context: CanRaiseError + CanWrapError` to produce and enrich an error it knows nothing concrete about. + +## Strategies as interchangeable providers + +Because raising and wrapping are components, the *strategy* for each becomes a provider a context selects, and CGP ships a family of them in `cgp-error-extra` that stay generic over the context's error type. Each captures one cross-cutting way to handle an error, independent of any particular error library, so an application composes its error handling from these parts rather than writing conversion glue. The most common are a small set worth knowing by name: + +- `RaiseFrom` raises a source error by converting it through the standard `From` trait, the default whenever the abstract error already absorbs the source. +- `ReturnError` is the identity raiser for when the source already *is* the abstract error. +- `RaiseInfallible` absorbs `core::convert::Infallible`, letting code generic over a fallible step wire uniformly even when that step cannot fail. +- `DebugError` and `DisplayError` format any `Debug` or `Display` source into a `String` and forward to the context's *own* `CanRaiseError`, reducing an open-ended set of error types to the one string case the context handles. +- `DiscardDetail` wraps by dropping the detail, and `PanicOnError` aborts instead of producing an error value. + +The full list and the exact bound each provider places on the context live in the [error providers reference](../reference/providers/error_providers.md). What matters at the concept level is that these are plain providers wired like any other, so the choice of provider is also a statement about which source errors a context accepts and how it treats them. + +## Pluggable concrete backends + +Sitting alongside the generic strategy providers are the standalone backends, each pinning the abstract error to one concrete library type and supplying the raisers and wrappers that go with it. The `cgp-error-anyhow`, `cgp-error-eyre`, and `cgp-error-std` crates each export a type-setting provider — `UseAnyhowError` sets `Self::Error` to `anyhow::Error`, for instance — together with providers like `RaiseAnyhowError` and `DebugAnyhowError` that raise and wrap into that concrete type. Wiring a backend is what makes the abstract error concrete: + +```rust +use cgp_error_anyhow::{RaiseAnyhowError, UseAnyhowError}; + +delegate_components! { + App { + ErrorTypeProviderComponent: UseAnyhowError, + ErrorRaiserComponent: RaiseAnyhowError, + } +} +``` + +The backend is opt-in and confined to these wiring lines, which is the whole point of keeping it separate. Choosing `eyre` instead is a matter of swapping `UseAnyhowError`/`RaiseAnyhowError` for their `eyre` counterparts, and no provider that calls `raise_error` changes, because none of them named the concrete type in the first place. A typical application wires a mix: a backend for the concrete error type, plus the generic strategy providers for the cross-cutting cases the backend does not cover. + +## Dispatching per source error type + +The reason `CanRaiseError` and `CanWrapError` are parameterized rather than monomorphic is that a single context usually raises several unrelated source errors, each best handled differently — and the per-type dispatch each component derives is what lets one component fan out to a provider per source. Because both carry `#[derive_delegate(UseDelegate)]`, a context can open the component and assign a provider to each source error type directly in its own table, the same per-type wiring used for [dispatching](dispatching.md) any generic parameter: + +```rust +delegate_components! { + App { + open {ErrorRaiserComponent}; + + @ErrorRaiserComponent.String: RaiseFrom, + @ErrorRaiserComponent.ParseIntError: DebugError, + } +} +``` + +Here a raised `String` is converted straight into the abstract error by `RaiseFrom`, while a `ParseIntError` is formatted with `Debug` and then forwarded back through the `String` entry — which `RaiseFrom` handles — so two different sources collapse into one coherent error type. The `open` statement and its `@`-path keys are the [namespace](namespaces.md)-based wiring form; the older [`UseDelegate`](../reference/providers/use_delegate.md) provider expresses the same dispatch through a separate nested table. Either way, the context decides the routing, and the formatting providers like `DebugError` compose with the converting ones rather than replacing them. + +## Application-specific error components + +Nothing about modular error handling is confined to the built-in components; an application defines its own error-raising components the same way when its errors carry domain structure. A web service, for instance, wants every raised error to carry an HTTP status code, so it declares a component that takes both a status marker and a detail, and dispatches on the pair: + +```rust +#[cgp_component(HttpErrorRaiser)] +pub trait CanRaiseHttpError: HasErrorType { + fn raise_http_error(_code: Code, detail: Detail) -> Self::Error; +} +``` + +The markers `ErrUnauthorized`, `ErrNotFound`, and the like are empty structs that map to status codes, and a provider such as `DisplayHttpError` builds the concrete application error from the code and a `Display` detail. The context then wires this custom component exactly as it wires the built-in ones — opening it and routing each detail type to a provider — so a handler deep in the request pipeline writes `Self::raise_http_error(ErrUnauthorized, "you must first login".into())` without knowing the concrete error type or how status codes are attached. This is the modular error-handling pattern carried up to the application's own vocabulary: the same split between an abstract error, a raising capability, and a wiring choice, applied to errors that are richer than a plain message. + +## Related constructs + +Modular error handling is built entirely from ordinary CGP constructs. The abstract error is [`HasErrorType`](../reference/components/has_error_type.md), an [abstract-type](abstract-types.md) component defined with [`#[cgp_type]`](../reference/macros/cgp_type.md); the raising and wrapping capabilities are [`CanRaiseError` / `CanWrapError`](../reference/components/can_raise_error.md), and the strategies that satisfy them are the [error providers](../reference/providers/error_providers.md) plus the standalone backends. A provider declares which errors it raises through [impl-side dependencies](impl-side-dependencies.md) in its `where` clause, and a context selects a strategy per source error type through the [dispatching](dispatching.md) machinery and the [`open` statement](../reference/macros/delegate_components.md) over [namespaces](namespaces.md). The whole approach is the [coherence](coherence.md)-bypassing strategy specialized to errors: keep the raising implementations overlapping and open, and let each context restore a single coherent error type by wiring. The [modular serialization](../examples/modular-serialization.md) example wires an `anyhow` backend to make its context-supplied deserializer fallible, and the [money-transfer API](../examples/money-transfer-api.md) example raises status-coded HTTP errors through an application-specific error component of exactly this shape. + +## Source + +The abstract error type and the raising and wrapping traits are defined in [crates/core/cgp-error/src/](../../crates/core/cgp-error/src/); the generic strategy providers are in [crates/extra/cgp-error-extra/src/](../../crates/extra/cgp-error-extra/src/), and the pluggable concrete backends in [crates/standalone/error/](../../crates/standalone/error/). diff --git a/docs/concepts/modularity-hierarchy.md b/docs/concepts/modularity-hierarchy.md new file mode 100644 index 00000000..61b3ce08 --- /dev/null +++ b/docs/concepts/modularity-hierarchy.md @@ -0,0 +1,156 @@ +# Modularity hierarchy + +CGP and vanilla Rust together form a ladder of modularity: each rung allows strictly more independent implementations of one interface than the rung below, by loosening a coherence constraint at a matching cost in syntax or coupling, so the right rung is the lowest one that still expresses what a use case needs. + +## A ladder, not a switch + +Adopting CGP is not an all-or-nothing jump from ordinary Rust traits to fully context-generic code. The same capability — say, serializing a value — can be expressed at several levels of modularity, and the levels form a gradient from "exactly one implementation, no wiring" to "any number of implementations, wired per type per provider." Each step up admits more overlapping or orphan implementations that vanilla Rust's [coherence](coherence.md) rules would reject, and each step costs something: more boilerplate, a changed interface, or tighter coupling between providers. Reading the rungs in order shows what each technique buys and what it asks for, so a use case can settle at the lowest rung that still works rather than reaching for the most powerful tool by default. + +The ladder is illustrated throughout with one running capability — serializing a value, mirroring the [modular serialization](../examples/modular-serialization.md) example — so the only thing that changes between rungs is the modularity technique, not the problem. All snippets assume `use cgp::prelude::*;`. + +## Rung 1 — one implementation per interface + +The least modular rung is a generic function or a blanket trait impl, which allows exactly one implementation of the interface it defines. A blanket impl over a generic type captures a single piece of logic that applies everywhere the bound holds, and there can be only one such impl: + +```rust +pub trait CanSerializeBytes { + fn serialize_bytes(&self, serializer: S) -> Result; +} + +impl> CanSerializeBytes for Value { + fn serialize_bytes(&self, serializer: S) -> Result { + serializer.serialize_bytes(self.as_ref()) + } +} +``` + +A blanket trait is preferred over a bare generic function because it hides the `AsRef<[u8]>` bound behind a clean interface rather than leaking it to every transitive caller — the [impl-side dependency](impl-side-dependencies.md) idea in its simplest form. The limitation is absolute, though: this is the *only* way `CanSerializeBytes` is ever implemented. There is no room for a second strategy, so this rung fits a capability that genuinely has one implementation for all types, and nothing more. + +## Rung 2 — one implementation per type + +Vanilla Rust traits climb one rung by allowing a different implementation for each type, while coherence still permits at most one implementation per type. This is the everyday Rust trait, where `Vec` and `&[u8]` can each implement `Serialize` their own way: + +```rust +impl Serialize for Vec { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_bytes(self.as_ref()) + } +} + +impl<'a> Serialize for &'a [u8] { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_bytes(self) + } +} +``` + +The gain over rung 1 is per-type variation; the cost is that each type needs its own explicit impl even when several share logic, and the [overlap rule](coherence.md) forbids any blanket impl that would collide. Reusable building blocks can still be factored out — both bodies above could call the rung-1 `serialize_bytes` — but the trait itself admits no alternatives: once `Serialize for Vec` is chosen, that choice is global and final. This is where Rust's coherence guarantee delivers its value and also where it starts to bind: a type gets exactly one implementation of a trait, no matter what a particular application would prefer. + +## Rung 3 — many implementations, one wiring per type + +The first CGP rung keeps the type in the `Self` position but splits the trait into a consumer/provider pair, so many overlapping implementations can coexist as named providers while each type still commits to one of them globally. Applying [`#[cgp_component]`](../reference/macros/cgp_component.md) to the trait and writing providers with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) lets `SerializeBytes` and a `Serialize`-deferring `UseSerde` both exist, overlapping freely on any type that is both `AsRef<[u8]>` and `Serialize`: + +```rust +#[cgp_component(ValueSerializer)] +pub trait CanSerialize { + fn serialize(&self, serializer: S) -> Result; +} + +#[cgp_impl(new SerializeBytes)] +impl ValueSerializer +where + Self: AsRef<[u8]>, +{ + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_bytes(self.as_ref()) + } +} +``` + +A type then picks one provider with a [`delegate_components!`](../reference/macros/delegate_components.md) entry — `Vec` becomes its own context, wiring its serializer component to `SerializeBytes`: + +```rust +delegate_components! { + Vec { + ValueSerializerComponent: SerializeBytes, + } +} +``` + +This rung's advantage is backward compatibility: the original trait is extended without changing its interface, a type can still implement it directly, and many reusable providers replace the hand-copied logic of rung 2. Its limitation is that coherence is only partly lifted. The wiring still keys on the type in the `Self` position, so `Vec` commits to one provider globally — there can be no separate wiring for a generic `Vec` that would overlap it, and the [orphan rule](coherence.md) still applies, since `delegate_components!` for `Vec` must live in a crate that owns either the trait or `Vec`. This rung suits retrofitting modular providers onto an existing trait when one global choice per type is acceptable. + +## Rung 4 — many implementations, one wiring per type per context + +The decisive CGP rung moves the type being implemented out of `Self` and into an explicit parameter, so the `Self` position names a context that owns the wiring — which lifts the orphan rule and lets each context choose providers per type independently. The trait gains a `Value` parameter, leaving `Self` free to be any application context: + +```rust +#[cgp_component(ValueSerializer)] +pub trait CanSerializeValue { + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer; +} +``` + +Now two application contexts can serialize the *same* type differently, each coherent within itself, by opening the component and keying on the value type — `AppA` encoding `Vec` as hexadecimal where `AppB` uses base64: + +```rust +delegate_components! { + AppA { + open {ValueSerializerComponent}; + @ValueSerializerComponent.Vec: SerializeHex, + } +} + +delegate_components! { + AppB { + open {ValueSerializerComponent}; + @ValueSerializerComponent.Vec: SerializeBase64, + } +} +``` + +This rung nearly eliminates the coherence restrictions. Because the wiring keys on the context rather than on `Vec`, a crate that owns neither the trait nor `Vec` can still wire a serializer for `Vec` as long as it owns the context, so the orphan rule no longer bites and overlapping providers coexist without any global commitment. The cost is that the trait must be designed with the extra context parameter from the start — it cannot be retrofitted onto an existing trait like `serde::Serialize` without a breaking change — and wiring must be spelled out for every value type a context uses. This is the rung most idiomatic CGP code lives on, and the one the [modular serialization](../examples/modular-serialization.md) and [money-transfer API](../examples/money-transfer-api.md) examples build on; the per-type dispatch it relies on is the subject of [dispatching](dispatching.md), wired through the [`open` statement](../reference/macros/delegate_components.md) over [namespaces](namespaces.md). + +## Rung 5 — many implementations, wiring per type per provider + +The top rung lets one provider override the wiring of a nested type locally, without routing that choice back through the context, by taking the inner provider as a parameter — a [higher-order provider](higher-order-providers.md). A recursive serializer for a collection ordinarily asks the context how to serialize each element; a higher-order variant instead accepts an explicit element serializer, defaulting to the context only when none is given: + +```rust +pub struct SerializeIteratorWith(pub PhantomData); + +#[cgp_impl(SerializeIteratorWith)] +impl ValueSerializer +where + for<'a> &'a Value: IntoIterator, + Provider: for<'a> ValueSerializer::Item>, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer, + { /* serialize each item through `Provider` */ } +} +``` + +With this in hand a context can fix the element encoding for one collection while leaving others to the context's general wiring — serializing a `Vec>` whose inner byte vectors are hexadecimal even though plain `Vec` elsewhere is encoded as raw bytes: + +```rust +delegate_components! { + AppA { + open {ValueSerializerComponent}; + @ValueSerializerComponent.Vec: SerializeBytes, + @ValueSerializerComponent.Vec>: SerializeIteratorWith, + @ValueSerializerComponent.Vec: SerializeIteratorWith, + } +} +``` + +This rung adds fine-grained, per-provider control on top of rung 4's per-context control: the `Vec` entry omits the parameter and so still routes its elements through the context, while the `Vec>` entry pins its inner encoding to `SerializeHex` regardless of how the context serializes `Vec` on its own. The `UseContext` default is what makes both forms read the same; the [`UseContext` provider](../reference/providers/use_context.md) routes back to the context's wiring when no override is supplied. The cost is the extra coupling and the higher-order machinery, which is why this rung is reserved for the cases where local override genuinely matters rather than used by default. + +## Choosing a rung + +The guiding rule is to settle at the lowest rung that expresses the use case, because each step up trades simplicity for modularity that may not be needed. A capability with one universal implementation belongs on rung 1; one that varies by type but never by application belongs on rung 2; a trait that should gain alternative providers without changing its interface belongs on rung 3; a capability where different applications must encode the same type differently belongs on rung 4, the home of most CGP code; and only a provider that must override a nested type's wiring locally needs rung 5. Climbing higher than necessary adds context parameters, wiring, and coupling that buy nothing, while stopping too low forces the hand-written impls and global commitments the higher rungs exist to avoid. + +## Related constructs + +The mechanism that makes rungs 3 through 5 possible — splitting a trait into a [consumer and provider trait](consumer-and-provider-traits.md) so overlapping and orphan implementations become legal — is the subject of [bypassing coherence](coherence.md), and the dependency threading every rung relies on is [impl-side dependencies](impl-side-dependencies.md). The constructs the rungs introduce are [`#[cgp_component]`](../reference/macros/cgp_component.md) and [`#[cgp_impl]`](../reference/macros/cgp_impl.md) for the trait split, [`delegate_components!`](../reference/macros/delegate_components.md) for the wiring, the [`open` statement](../reference/macros/delegate_components.md) over [namespaces](namespaces.md) and the [dispatching](dispatching.md) idea for per-type selection, and [higher-order providers](higher-order-providers.md) with the [`UseContext` provider](../reference/providers/use_context.md) for the top rung. The whole progression is worked through on a real capability in the [modular serialization](../examples/modular-serialization.md) example. diff --git a/docs/concepts/monadic-handlers.md b/docs/concepts/monadic-handlers.md new file mode 100644 index 00000000..b2ef2f8b --- /dev/null +++ b/docs/concepts/monadic-handlers.md @@ -0,0 +1,47 @@ +# Monadic handlers + +Monadic handlers are a way to chain CGP handlers into a pipeline that automatically short-circuits when an intermediate step produces a result the chain should stop on, threading a single `Output` value through the whole sequence. + +## Purpose + +The monad layer solves the problem of sequencing handlers when some of those handlers produce a result that should end the pipeline early. Plain handler composition with [`ComposeHandlers`](../reference/providers/handler_combinators.md) and `PipeHandlers` feeds the output of each provider straight into the next, which is the right behavior only when every step always wants to continue. The moment a step can yield a value that means "stop here, this is the final answer," straight composition is wrong: the later steps would run on a value they were never meant to see. The classic case is a `Result`, where an `Err` should abandon the rest of the pipeline and become the output directly, but the same shape appears whenever a step's output type carries two possibilities — one to keep going with, one to return immediately. + +A monad captures exactly that branching. It describes, for a given output type, which case threads forward into the next handler and which case short-circuits out as the final result, plus how to lift a plain value back into that output type. With the monad chosen, a list of handlers can be composed so that each handler runs only on the "continue" branch of the previous one, and any "stop" value flows untouched to the end. The handler that the monad layer builds is itself an ordinary provider for the [`Computer`](../reference/components/computer.md) family, so a monadic pipeline plugs into the same wiring as any other handler. + +CGP ships three monads. The *identity* monad threads every value forward and never short-circuits, recovering plain composition. The *ok* monad short-circuits on `Ok` and continues on `Err`. The *err* monad short-circuits on `Err` and continues on `Ok` — the familiar `?`-style early return where the first error wins. These can also be stacked, so that a pipeline over a nested `Result, F>` can short-circuit on the outer error while threading the inner result. + +## Behavior + +A monadic pipeline runs a list of handlers left to right, where each handler is invoked only on the branch of the previous handler's output that the monad designates as "continue." The first handler receives the pipeline's input. Its output is split by the monad: the continue case is fed as the input to the second handler, and the short-circuit case is lifted directly into the pipeline's final output type, skipping every remaining handler. This repeats down the list, so the pipeline's result is either the short-circuit value of whichever handler first produced one, or the output of the final handler if none short-circuited. + +The err monad makes this concrete. Composing three handlers that each return `Result` under the err monad runs the first handler on the input; if it returns `Ok`, the contained value is passed to the second handler, and so on; if any handler returns `Err`, that error becomes the pipeline's output immediately and the remaining handlers do not run. This is the type-level equivalent of writing each step with a `?` operator. The ok monad is the mirror image: it continues on `Err` and stops on `Ok`, which is useful for pipelines that retry or fall through until something succeeds. + +The whole construction lives in types. The monad is a zero-sized marker, the handler list is a [type-level list](../reference/types/cons.md), and the pipeline provider that results carries no runtime value — it is assembled by the trait machinery at compile time, so chaining handlers monadically costs nothing at runtime beyond the branches the logic actually requires. + +## Examples + +The entry point is the [`PipeMonadic`](../reference/providers/monad_providers.md) provider, which takes a monad marker and a `Product!` list of handler providers. Given an `Increment` computer that returns `Result` — incrementing on success and reporting `"overflow"` as the error — composing three copies under the err monad runs them in sequence and stops at the first overflow: + +```rust +PipeMonadic::::compute(&context, code, 253) +// 253 -> Ok(254) -> Ok(255) -> Err("overflow") +``` + +Each `Increment` runs on the `Ok` value of the one before it; the third increment overflows, so its `Err("overflow")` becomes the pipeline's output and no further step runs. Wiring the same list under [`IdentMonadic`](../reference/providers/monad_providers.md) instead would thread the `Result` value forward unchanged, never short-circuiting — which is plain composition and is rarely what a `Result`-producing chain wants. + +Stacking monads handles nested results. A pipeline of handlers returning `Result, &str>` can short-circuit on the outer `&str` error while threading the inner `Result<(), u8>` by composing under `OkMonadicTrans` — the err monad applied as the base, transformed by the ok monad on top: + +```rust +PipeMonadic::, Product![ReturnOkErr, ReturnOkOk, ReturnOkErr]> + ::compute(&context, code, 1) +``` + +Here an outer `Err` ends the pipeline immediately, while an outer `Ok` unwraps to the inner `Result` that the next handler continues from. + +## Related constructs + +Monadic handlers build directly on the handler family: the pipelines they produce are providers for [`Computer`](../reference/components/computer.md) and its async and fallible relatives, so the same providers that a monadic pipeline composes are the providers any handler wiring uses. [`PipeMonadic`](../reference/providers/monad_providers.md) is the user-facing provider, and the monad markers `IdentMonadic`, `OkMonadic`, and `ErrMonadic` — together with their transformer forms and the per-step `BindOk` / `BindErr` providers — are documented there. The trait layer that defines what a monad *is* (which branch continues, which short-circuits, and how to lift a value) lives in [the monad traits](../reference/traits/monad.md): `MonadicTrans`, `MonadicBind`, `LiftValue`, and `ContainsValue`. The non-monadic composition that monadic pipelines generalize — straight `ComposeHandlers` and `PipeHandlers`, plus the `TryPromote` bridge between fallible and infallible handlers — is covered in [handler combinators](../reference/providers/handler_combinators.md). For dispatching to different handlers by a type-level key rather than running them in sequence, see [dispatch combinators](../reference/providers/dispatch_combinators.md). + +## Source + +The monad layer lives in [crates/extra/cgp-monad/src/](../../crates/extra/cgp-monad/src/), re-exported as `cgp::extra::monad`. The pipeline provider is in `providers/pipe_monadic.rs`, the monad markers in `monadic/{ident,ok,err}.rs`, and the trait layer in `traits/`. The behavior described here is exercised by the tests in [crates/tests/cgp-tests/src/tests/monad/](../../crates/tests/cgp-tests/src/tests/monad/) (`ok.rs`, `err.rs`, `ok_err_trans.rs`). diff --git a/docs/concepts/namespaces.md b/docs/concepts/namespaces.md new file mode 100644 index 00000000..837ecf90 --- /dev/null +++ b/docs/concepts/namespaces.md @@ -0,0 +1,89 @@ +# Namespaces + +A namespace is a reusable, named lookup table of component wirings that a context can inherit wholesale and then selectively override, giving CGP its preset-style configuration without any separate preset construct. + +## The idea + +A namespace lifts a delegation table out of any one context, gives it a name, and lets other contexts inherit it. With [`delegate_components!`](../reference/macros/delegate_components.md) alone, every context spells out its own wiring entry by entry, and two contexts that should share the same set of providers must repeat it. A namespace captures "this exact set of wirings" as a thing contexts can refer to: a context says "use everything in this namespace" and gets the whole group at once. It is the answer to the question "how do I reuse a block of wiring across many contexts," and it is defined with [`cgp_namespace!`](../reference/macros/cgp_namespace.md). + +The defining feature is inheritance with override. A context that joins a namespace inherits its entries as defaults, then adds its own entries that win over the inherited ones — because a directly-wired entry on the context resolves before the namespace fallback is consulted. So a namespace behaves like a base configuration that contexts specialize: most of the wiring comes for free, and each context tweaks the handful of entries it cares about. Namespaces can also inherit from one another, so a base namespace can be extended into a richer one that every context downstream picks up. + +Crucially, a namespace is *not* a context. It is a trait — named after the namespace — that carries a `Delegate` associated type and is implemented once per key. A context opts in and forwards its lookups through that trait, so the namespace supplies defaults without ever being instantiated or holding any wiring of its own at the context level. + +## Path-based redirection + +What lets one namespace inherit from another, and lets a context shadow a single inherited entry without disturbing the rest, is that the forwarding is keyed by a *path* rather than a bare component name. A path is a type-level list of symbols and component names — written with the `@` sigil as a dotted sequence like `@MyFooComponent`, `@app.ErrorRaiserComponent`, or `@cgp.core.error` — and each namespace entry redirects a key along such a path instead of naming a provider outright. The redirection is carried by the [`RedirectLookup`](../reference/providers/redirect_lookup.md) provider, which resolves a key by walking the given path inside whatever table it is handed: + +```rust +cgp_namespace! { + new MyNamespace { + FooProviderComponent => + @MyFooComponent, + } +} +``` + +This says that when `MyNamespace` is asked for `FooProviderComponent`, it should look up the path `MyFooComponent` rather than resolve to a fixed provider — the actual provider is decided wherever the path eventually lands. Because lookups are paths rather than flat keys, a parent namespace's entire subtree can be rerouted at once (`@cgp.core.error => @app` redirects everything under that prefix), and a child context can introduce a more specific path that takes precedence over an inherited one. Under the hood every `@` path desugars into a [`PathCons`](../reference/types/path_cons.md) type-level list built by the [`Path!`](../reference/macros/path.md) macro, with lowercase dotted segments becoming `Symbol` string literals and capitalized segments becoming named types; the `=>` entries become `RedirectLookup` impls while plain `:` entries map a key straight to a provider as in `delegate_components!`. + +## Per-component dispatch with `open` + +The most common place a path appears is the `open` statement, which uses path-based redirection to dispatch a single component on its generic parameter — inline, in a context's own table. Writing `open { ValueSerializerComponent };` at the head of a [`delegate_components!`](../reference/macros/delegate_components.md) block redirects that component's lookup along a path rooted at the component name into the context's own table; the per-value entries that follow are then ordinary `@`-path keys pointing into that route: + +```rust +delegate_components! { + AppA { + open {ValueSerializerComponent}; + + @ValueSerializerComponent.Vec: + SerializeHex, + @ValueSerializerComponent.DateTime: + SerializeRfc3339Date, + } +} +``` + +Each `@ValueSerializerComponent.Vec: SerializeHex` entry maps one value of the component's dispatch parameter — here the `Vec` serialized type — to the provider that handles it, and the [`RedirectLookup`](../reference/providers/redirect_lookup.md) impl every component carries appends that parameter onto the redirect path to find the entry. The effect is the per-type dispatch that the legacy `UseDelegate` nested table also provides, but with the entries living on the context rather than in a separate table type, so no `UseDelegate` wrapper or `#[derive_delegate]` attribute is involved. This is the form the [modular serialization](../examples/modular-serialization.md) example uses throughout. + +`open` is a lightweight form of the full namespace machinery, suited to small applications and self-contained examples where a single context wires its own components directly. It roots a component's route at the bare component name and adds the per-value entries to that context's own table, without joining any shared namespace. The full namespace feature is what scales to a large code base: a library registers each component into a shared namespace under a path prefix with the `#[prefix(...)]` attribute, and many contexts join that namespace with a `namespace …;` header to inherit the whole bundle of wirings at once — the inherit-and-override pattern the rest of this page describes. + +The two forms do not combine for the same component. When a context joins a namespace in which a component carries a prefix, that component's lookups are already routed under the prefix path, so `open`-ing it — which would root the route at the bare component name — no longer reaches those entries. The per-value entries must instead be written with the full prefixed path, `@prefix.SomeComponent.Key: Provider`, exactly as the override entries shown later do. So `open` is the convenience for a component wired directly on a context; once a component lives behind a namespace prefix, the full path is what reaches it. + +## Attaching components and joining namespaces + +A namespace is consumed from two sides: components register themselves into it, and contexts join it. A component attaches to a namespace through the `#[prefix(...)]` attribute on its trait, which emits one extra impl registering the component into the named namespace under a path prefix. CGP's own [`HasErrorType`](../reference/components/has_error_type.md), for instance, carries `#[prefix(@cgp.core.error in DefaultNamespace)]`, placing it into the built-in `DefaultNamespace` under the `cgp.core.error` prefix so any context joining that namespace inherits the standard error wiring. A context joins a namespace inside [`delegate_components!`](../reference/macros/delegate_components.md) with a `namespace` header line, after which every lookup it cannot resolve directly forwards through the namespace: + +```rust +delegate_components! { + AppA { + namespace DefaultNamespace; + + @test.ShowImplComponent.u64: + ShowWithDisplay, // a direct entry overriding the namespace default + } +} +``` + +The `namespace DefaultNamespace;` line makes `AppA` fall back to `DefaultNamespace`'s entries, while the direct line on the same context shadows just the `u64` entry — the override-by-precedence rule in action. Joining through [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md) instead does the same while verifying the merged wiring. + +## Preset-style configuration + +Namespaces are how CGP expresses presets, and there is no separate `cgp_preset!` macro — a preset *is* a namespace. The pattern a preset library would offer elsewhere, "a curated bundle of defaults you adopt and then customize," is exactly the inherit-wholesale-and-override behavior namespaces provide. A library publishes a namespace of sensible default wirings, possibly extended from a base namespace; an application joins it, inherits the bundle, and overrides the few entries specific to its needs: + +```rust +cgp_namespace! { + new ExtendedNamespace: DefaultNamespace { + @cgp.core.error => + @app, + } +} +``` + +`ExtendedNamespace` inherits everything `DefaultNamespace` resolves and additionally reroutes the `@cgp.core.error` subtree to `@app`, so any context joining `ExtendedNamespace` gets the merged result. Because this is all expressed through trait resolution and type-level paths, the configuration has no runtime cost: the inheritance, the overrides, and the redirections are resolved entirely at compile time, and a preset is just one more namespace in the chain. + +## Related constructs + +Namespaces are defined with [`cgp_namespace!`](../reference/macros/cgp_namespace.md), whose `#[prefix(...)]` attribute (on a [`#[cgp_component]`](../reference/macros/cgp_component.md) trait) registers a component into a namespace and whose entries are resolved through the [`RedirectLookup`](../reference/providers/redirect_lookup.md) provider. Inheritance and per-type default lookups go through the [`DefaultNamespace` / `DefaultImpls` traits](../reference/traits/default_namespace.md) in `cgp-component`. Every `@` path desugars into a [`PathCons`](../reference/types/path_cons.md) type-level list built by the [`Path!`](../reference/macros/path.md) macro. A context joins a namespace inside [`delegate_components!`](../reference/macros/delegate_components.md) via its `namespace` header, or [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md) to join and verify at once; the underlying per-key table that `RedirectLookup` walks is [`DelegateComponent`](../reference/traits/delegate_component.md). There is no `cgp_preset!` macro — presets are expressed entirely through namespaces. + +## Source + +The macro is in [crates/macros/cgp-macro-core/src/types/namespace/](../../crates/macros/cgp-macro-core/src/types/namespace/), with the `#[prefix(...)]` attribute in [attributes/prefix.rs](../../crates/macros/cgp-macro-core/src/types/attributes/prefix.rs). The runtime `DefaultNamespace`/`DefaultImpls1`/`DefaultImpls2` traits are in [crates/core/cgp-component/src/namespaces.rs](../../crates/core/cgp-component/src/namespaces.rs) and `RedirectLookup` in [crates/core/cgp-component/src/providers/redirect_lookup.rs](../../crates/core/cgp-component/src/providers/redirect_lookup.rs); expansion snapshots are in [crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/](../../crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/). diff --git a/docs/concepts/send-bounds.md b/docs/concepts/send-bounds.md new file mode 100644 index 00000000..a68bebcc --- /dev/null +++ b/docs/concepts/send-bounds.md @@ -0,0 +1,122 @@ +# Recovering `Send` bounds + +Recovering a `Send` bound is the pattern of restoring the "the returned future is `Send`" guarantee that an async trait method drops, by re-declaring the method on a concrete context where the compiler can prove the bound itself — a hand-written stand-in for the Return Type Notation that stable Rust does not yet offer. + +## Why async trait methods lose their `Send` bound + +An async CGP method advertises a future whose auto-traits the caller cannot name. The standard way to declare an asynchronous component is to write an `async fn` in the consumer trait and let [`#[async_trait]`](../reference/macros/async_trait.md) rewrite it into a return-position `impl Future`: + +```rust +#[cgp_component(ApiHandler)] +#[async_trait] +#[derive_delegate(UseDelegate)] +pub trait CanHandleApi: HasErrorType { + type Request; + type Response; + + async fn handle_api( + &self, + _api: PhantomData, + request: Self::Request, + ) -> Result; +} +``` + +That rewrite is faithful and zero-cost, but it drops every auto-trait bound. `handle_api` becomes `fn handle_api(..) -> impl Future>`, with no boxing and no implicit bounds — and no `Send`. The future is `Send` only when the concrete future the body produces happens to be, and a caller working through the trait has no way to *say* "I need it to be." The opacity that makes return-position `impl Trait` zero-cost is exactly what hides the future's auto-traits behind the trait boundary. + +## Why spawning needs `Send`, and why you cannot ask for it + +The bound becomes load-bearing the moment the future is spawned onto a multi-threaded executor. A work-stealing runtime — the default Tokio runtime that an Axum server runs on, for instance — may migrate a task between threads while it is suspended, so every future it drives must be `Send`. When a generic handler routes a request through `handle_api`, the surrounding task captures and awaits that future, and the task is `Send` only if the future is. For a *concrete* context the compiler can check this directly; for a context left generic it cannot, because the future's `Send`-ness is precisely the fact the trait refuses to expose. + +The bound you want to write does not exist on stable Rust. Conceptually the requirement is "for whatever arguments `handle_api` is called with, its future is `Send`", and Rust has a notation for exactly that — Return Type Notation (RTN), which lets a bound name a method's return type: + +```rust +// Not available on stable Rust: +fn spawn_handler(app: App) +where + App: CanHandleApi + Send + 'static, +{ + tokio::spawn(async move { /* ... app.handle_api(...).await ... */ }); +} +``` + +The `handle_api(..): Send` clause says the future returned by the method is `Send` no matter the arguments, which is what a work-stealing spawn demands. RTN is not stabilized, however, so this bound cannot be written in production code today, and a generic caller is left unable to express the one requirement the executor imposes. + +## Recovering the bound with a concrete `Send` trait + +The workaround is to declare a second, ordinary trait whose method states the `Send` bound directly in its return type, sidestepping RTN entirely: + +```rust +pub trait CanHandleApiSend: + CanHandleApi + Send + Sync +{ + fn handle_api_send( + &self, + _api: PhantomData, + request: Self::Request, + ) -> impl Future> + Send; +} +``` + +`CanHandleApiSend` is a plain trait, not a [component](../reference/macros/cgp_component.md) — it adds nothing to the wiring and exists only to carry stronger bounds. It inherits the full capability from `CanHandleApi` as a supertrait, additionally requiring the request and response to be `Send` and the context itself to be `Send + Sync`, and it spells out `+ Send` on the future explicitly rather than through a clause on `handle_api`. A caller that holds `App: CanHandleApiSend` therefore knows the future is `Send` from the signature alone, with no RTN in sight. This is the bound a spawning handler can finally name: + +```rust +where + App: CanHandleApiSend, +``` + +## Why the implementation must be concrete + +The recovered trait cannot be implemented once, generically — that would require the very bound RTN is missing. The natural impl to reach for is a blanket one over every context that already handles the API: + +```rust +// Does not compile on stable Rust: +impl CanHandleApiSend for App +where + App: CanHandleApi + Send + Sync, +{ + async fn handle_api_send( + &self, + api: PhantomData, + request: Self::Request, + ) -> Result { + self.handle_api(api, request).await + } +} +``` + +The body wraps `self.handle_api(..)` in an `async` block, and that block is `Send` only if the future it awaits is `Send`. For a generic `App` and `Api` the awaited future is an opaque `impl Future` whose auto-traits are unknown — the same gap restated — so the impl fails to prove its own `+ Send` return type. A generic blanket impl is just RTN wearing a disguise, and it is blocked for the same reason. + +Dropping to a concrete context and a concrete API closes the gap. When `Self` is a fixed type and `Api` is a fixed marker, `self.handle_api(api, request)` resolves through the wiring to a concrete provider stack producing a concrete future, and the compiler computes that future's auto-traits and finds it `Send` — no annotation required, because `Send` is inferred structurally for a known type. The impl is therefore written per concrete `(context, API)` pair: + +```rust +impl CanHandleApiSend for MockApp { + async fn handle_api_send( + &self, + api: PhantomData, + request: Self::Request, + ) -> Result { + self.handle_api(api, request).await + } +} + +impl CanHandleApiSend for MockApp { + async fn handle_api_send( + &self, + api: PhantomData, + request: Self::Request, + ) -> Result { + self.handle_api(api, request).await + } +} +``` + +Each impl is mechanical — it forwards to `handle_api` and awaits — yet each is also a proof, accepted only because at this concrete instantiation the future really is `Send`. The repetition is the cost of the missing notation: one concrete impl per API per context replaces the single generic impl RTN would have allowed. The [money-transfer API example](../examples/money-transfer-api.md) shows the pattern in its place, with `MockApp` recovering the bound so its handlers can be served by Axum. + +## Related constructs + +The dropped bound originates with [`#[async_trait]`](../reference/macros/async_trait.md), whose Known issues section records the same `Send`-less future from the macro's side. The recovered trait sits atop a [component](../reference/macros/cgp_component.md) defined the usual way and consumed through the [consumer/provider duality](consumer-and-provider-traits.md); the [`Handler`](../reference/components/handler.md) family is the built-in async component most likely to need this treatment when its futures are spawned. The concrete impls forward through whatever provider stack the context wires with [`delegate_components!`](../reference/macros/delegate_components.md), and the [higher-order providers](higher-order-providers.md) in that stack are part of what makes the resolved future a concrete, checkable type. + +## Source + +The behavior this pattern compensates for lives in the `#[async_trait]` rewrite at [crates/macros/cgp-async-macro/src/impl_async.rs](../../crates/macros/cgp-async-macro/src/impl_async.rs), which produces a bare `-> impl Future` with no auto-trait bounds. The recovery itself is application-level rather than a CGP construct: it is an ordinary trait whose method declares `+ Send` on its return type and whose impls are written for concrete contexts, so there is no macro or core trait that implements it — only the language feature, Return Type Notation, that would make it unnecessary. diff --git a/docs/concepts/type-level-dsls.md b/docs/concepts/type-level-dsls.md new file mode 100644 index 00000000..70ff95ab --- /dev/null +++ b/docs/concepts/type-level-dsls.md @@ -0,0 +1,95 @@ +# Type-level DSLs + +A type-level DSL encodes a small language as Rust *types* and interprets those types at compile time through CGP wiring, so that a program's syntax is decoupled from its semantics and each can vary independently. + +## The idea + +The pattern is to represent a program as a type rather than a value, and to make interpreting that program a matter of resolving a CGP component for it. A program fragment becomes a phantom struct that carries no data — it names an operation — and the meaning of that operation is supplied by whichever provider a context wires for it. Because the program is a type, the Rust trait system *is* the interpreter: type checking the call that runs the program is what selects and composes the per-fragment implementations. There is no runtime parser, no abstract-syntax-tree walk, and no dispatch loop; the whole interpretation collapses into trait resolution that the compiler performs once. + +What makes this worth doing in CGP specifically is the same property that motivates CGP everywhere else: the separation of an interface from its implementations, lifted here to the separation of *syntax* from *semantics*. A fragment of syntax like a "run this command" marker says nothing about how the command runs — on which async runtime, against which error type, producing which output type. All of that is decided by the provider the context binds to that fragment, so a single program runs differently under different contexts, and a context can replace one fragment's interpreter without disturbing any program that uses it. This is the type-level analogue of a tagless-final or finally-encoded interpreter, but with CGP's extra `Context` parameter threading dependency injection through every fragment. + +The pattern composes from constructs documented individually elsewhere; the [shell-scripting DSL example](../examples/shell-scripting-dsl.md) develops a complete instance end to end, and this document explains the shape those pieces form. The running illustrations here use that example's vocabulary — `SimpleExec`, `Pipe`, the `Handler` component — so that the concepts and the worked code share one set of names. + +## Abstract syntax as phantom types + +Each piece of the language's grammar is a zero-data struct whose type parameters carry the program's structure. A "run a command" fragment is nothing more than its phantom marker: + +```rust +pub struct SimpleExec(pub PhantomData<(Path, Args)>); +``` + +The struct has no methods and no interpreter attached — it exists only to be named in a type and matched on later. Its parameters are themselves type-level data: a command path, an argument list, a request method. Composite fragments nest the same way, so a pipeline of stages is a single type built from a [`Product!`](../reference/macros/product.md) type-level list of stage types, and a literal string inside a program becomes a [`Symbol!`](../reference/macros/symbol.md) type-level string because a `&str` value cannot appear where only types are allowed. A whole program is therefore one large type assembled from these markers — the language's *abstract syntax*, deliberately holding no information about how any fragment behaves. + +## A computation component as the interpreter interface + +The interpreter is a single CGP component whose `Code` type parameter is the program fragment being run. The [handler family](handlers.md) is built for exactly this: each component threads a phantom `Code` tag alongside an `Input`, and produces an associated `Output`. The general member, [`Handler`](../reference/components/handler.md), is async and fallible: + +```rust +#[async_trait] +#[cgp_component(Handler)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanHandle: HasErrorType { + type Output; + + async fn handle( + &self, + _tag: PhantomData, + input: Input, + ) -> Result; +} +``` + +Running a program is then one call to the consumer method, passing the program type as `PhantomData`. The `Code` tag carries no value precisely so that one context can host an interpreter for every fragment in the language, each keyed by a distinct `Code` type, and so that the wiring can dispatch on it. A DSL whose fragments never fail or never await can use a simpler member of the family — [`Computer`](../reference/components/computer.md) for pure synchronous fragments — and rely on the family's promotion combinators to lift it where a more capable interpreter is required; the choice of component is the choice of what capabilities the language's fragments are allowed to have. + +## Providers as interpreters + +A provider interprets one fragment by pattern-matching on the `Code` parameter through its generic arguments. Written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), an interpreter reads like an ordinary method body while the context stays generic, and its `where` clause states — as [impl-side dependencies](impl-side-dependencies.md) — everything the fragment needs from the context: + +```rust +#[cgp_impl(new HandleStreamChecksum)] +impl Handler, Input> for Context +where + Context: CanRaiseError, + Input: Unpin + TryStream, + Hasher: Digest, + Input::Ok: AsRef<[u8]>, +{ + type Output = GenericArray; + + async fn handle(/* ... */) -> Result { + /* fold the stream into a digest */ + } +} +``` + +Matching `Handler, …>` for any `Hasher` lets the one provider cover an entire family of fragments, and because the provider never names a concrete error or runtime type it interprets the same fragment under any context that can satisfy its bounds. This is where syntax and semantics finally meet: the fragment `Checksum` says *what*, the provider says *how*, and the binding between them is made only at wiring time. Swapping the provider re-interprets every program that uses the fragment, without editing a single program. + +## Assembling an interpreter by dispatching on the syntax + +A whole-language interpreter is assembled by routing each `Code` to its own provider, which is what [`UseDelegate`](../reference/providers/use_delegate.md) does. Because the handler components are declared `#[derive_delegate(UseDelegate)]`, a context can resolve the handler component through an inner type-level table keyed on the `Code` type, in the manner described in [dispatching](dispatching.md). Each entry maps a fragment type — its generic parameters bound by a leading `<…>` so the key matches every use — to the provider that interprets it: + +```rust +@HandlerComponent. SimpleExec: + HandleSimpleExec, +@HandlerComponent. SimpleHttpRequest: + HandleSimpleHttpRequest, +``` + +Resolving the interpreter for a program then walks one extra step through this table: the context dispatches the handler component on the fragment type and finds its provider. Because the keys are types rather than values, a single entry can capture generic structure that a value-level lookup table never could, and the per-fragment providers stay completely independent of one another — adding a fragment is adding a row. + +## Composing and extending the language + +Two further constructs turn a set of fragment interpreters into a real, extensible language. Composition of fragments is itself interpreted by a provider: a pipeline fragment is wired to a combinator like [`PipeHandlers`](../reference/providers/handler_combinators.md), which threads the output of one stage's interpreter into the input of the next, so that a compound program is interpreted by composing the interpreters of its parts. Extensibility comes from [namespaces](namespaces.md): the table of fragment-to-provider wirings is captured once with [`cgp_namespace!`](../reference/macros/cgp_namespace.md), and a context joins it through [`delegate_components!`](../reference/macros/delegate_components.md) to inherit the whole language at once. A language *extension* is then a new namespace that inherits the base and adds rows for new fragments and their interpreters — an independent crate can extend the DSL without patching, forking, or even depending on the parts of the core it does not use. This is the type-level counterpart of an interpreter that anyone can add cases to from outside. + +## A surface-syntax layer + +A procedural macro can sit on top of the abstract syntax to give the language a readable surface, but it is a strictly optional convenience. Such a macro performs only shallow token rewriting — turning an infix pipe operator into a nested pipeline type, a bracketed list into a `Product!`, a string literal into a `Symbol!` — and emits the same plain type a programmer could write by hand. Because the surface macro produces ordinary types and never touches interpretation, it carries no semantics of its own: the language remains fully usable, and fully extensible, without it. Keeping the surface layer this thin is what lets the *grammar* evolve independently of both the macro and the interpreters. + +## Tradeoffs + +Interpreting at compile time is the pattern's strength and its limit at once. The payoff is that a program runs at native speed with no interpreter overhead, that the Rust type system checks a program's well-formedness before it ever runs, and that the same program retargets to a different context for free. The cost is that a program must be known at compile time, so the technique does not suit languages loaded dynamically at runtime — configuration read from a file, user-supplied scripts, plugins — without also shipping a compiler. The other practical cost is diagnostics: a type error in one fragment surfaces as a trait-resolution failure that can be verbose and indirect, which the [check traits](check-traits.md) help localize during development but do not entirely tame. The pattern fits best where the language is fixed at build time and the value of zero-cost, type-checked, retargetable programs outweighs the friction of compile-time error messages. + +## Related constructs + +A type-level DSL is an assembly of constructs documented on their own. The interpreter interface is a member of the [handler family](handlers.md) — usually [`Handler`](../reference/components/handler.md) or [`Computer`](../reference/components/computer.md) — which rests on the [consumer/provider trait duality](consumer-and-provider-traits.md). Fragment interpreters are providers written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) that draw their needs from the context as [impl-side dependencies](impl-side-dependencies.md), and a fragment parameterized by an abstract type uses [abstract types](abstract-types.md) to stay decoupled from concrete ones. The abstract syntax is built from type-level data — [`Product!`](../reference/macros/product.md) lists and [`Symbol!`](../reference/macros/symbol.md) strings — and dispatched on with [`UseDelegate`](../reference/providers/use_delegate.md) per [dispatching](dispatching.md). Composition uses the [handler combinators](../reference/providers/handler_combinators.md), and the language is bundled and extended through [namespaces](namespaces.md) defined with [`cgp_namespace!`](../reference/macros/cgp_namespace.md) and joined with [`delegate_components!`](../reference/macros/delegate_components.md). The [shell-scripting DSL example](../examples/shell-scripting-dsl.md) is a complete worked instance of the whole pattern. diff --git a/docs/examples/README.md b/docs/examples/README.md new file mode 100644 index 00000000..84018d39 --- /dev/null +++ b/docs/examples/README.md @@ -0,0 +1,23 @@ +# CGP Examples + +This directory holds self-contained worked examples of CGP in use. Each document develops one realistic use case end to end — the contexts, the components and providers, and the wiring that connects them — so that the example reads as a coherent program rather than a list of isolated snippets. The examples are the preferred source of code snippets for the rest of the knowledge base: when a [reference document](../reference/README.md) needs a worked illustration, it should draw on the patterns shown here so that the same vocabulary and the same running example appear everywhere a reader looks. + +## How examples differ from reference documents + +An example demonstrates a *use case and the design patterns that solve it*, whereas a reference document explains a *single construct completely*. The two are complementary. A reference document for `#[cgp_fn]` states its syntax, its exact expansion, and its corner cases; an example shows `#[cgp_fn]` working alongside `delegate_components!` and higher-order providers to actually compute something. Because the constructs are documented in full in the reference, an example does not re-explain them. It carries only enough prose to make the code legible, leaves a short note on which CGP concept each step demonstrates, and links to the reference document that owns that concept. A reader who wants the mechanics follows the link; a reader who wants the shape of a solution stays in the example. + +The audience for these examples is an agent writing expanded documentation — a tutorial, a guide, a how-to. An example is the raw material such an agent draws on: a verified, idiomatic progression it can quote, adapt, and elaborate, confident that the code reflects current CGP. The examples therefore optimize for being *quotable and correct* rather than exhaustive. + +## The catalog + +The authoring rules for examples, including how to add a new one, live in [../CLAUDE.md](../CLAUDE.md). + +- [Area calculation](area-calculation.md) — computing the area of several shapes, progressing from field-driven functions to a wireable area component with composable higher-order providers. +- [Shell-scripting DSL](shell-scripting-dsl.md) — a type-level DSL whose programs are types interpreted at compile time, progressing from a fixed CLI program through the handler component and its namespace wiring to a custom context and a language extension. +- [Profile picture lookup](profile-picture.md) — fetching a user's profile picture across a database query and an object-storage download, progressing from field-driven async functions through impl-only generics that vary the database engine to a storage component wired per context. +- [Money-transfer API](money-transfer-api.md) — the backend for a balance-query and funds-transfer web service, progressing from a per-endpoint-dispatched async handler component through reusable handler wrappers to a context wired to all of it and served over HTTP with a recovered `Send` bound. +- [Application builder](application-builder.md) — assembling an application context from independent per-subsystem builder providers, progressing from a hand-written constructor through merged builder outputs to swapping subsystems and producing several application variants from one builder via the [extensible builder pattern](../concepts/extensible-records.md). +- [Expression interpreter](expression-interpreter.md) — a modular interpreter for an arithmetic language that solves the expression problem, progressing from a closed enum through per-variant evaluation providers and a second to-Lisp operation to code-based dispatch and an extended language, using the [extensible visitor pattern](../concepts/extensible-variants.md). +- [Extensible shapes](extensible-shapes.md) — operations over geometric shapes modeled as enum variants, progressing from an auto-dispatched per-variant operation through mutating and argument-taking methods and enum upcasting/downcasting to context-wired dispatch with the [visitor combinators](../reference/providers/dispatch_combinators.md); the non-recursive companion to the expression interpreter. +- [Social media app](social-media-app.md) — the CRUD backend for a users-and-posts service, progressing from coarse per-domain manager traits through fine-grained per-operation traits and a higher-order input filter to provider bundles and namespace-grouped wiring that keeps the top-level configuration short as the component count grows. +- [Modular serialization](modular-serialization.md) — Serde's `Serialize` and `Deserialize` rebuilt as CGP components, progressing from the two serialization components through a family of overlapping per-type providers to two contexts that encode the same data into different JSON formats by a few wiring lines, and a context-supplied arena capability for deserialization, demonstrating the [coherence](../concepts/coherence.md) bypass on a real-world trait. diff --git a/docs/examples/application-builder.md b/docs/examples/application-builder.md new file mode 100644 index 00000000..bcd09502 --- /dev/null +++ b/docs/examples/application-builder.md @@ -0,0 +1,340 @@ +# Application builder + +This example assembles an application context — a struct holding a database pool, an HTTP client, and an AI agent — from independent builder providers that each construct one subsystem and know nothing of the final struct or of each other. It progresses from a hand-written constructor that grows unmanageably, through a builder provider per subsystem, to a builder context that merges them all, and finally to swapping subsystems and producing several application variants from one builder. It is a template for any use case where a context is configured from independently-evolving parts that should compose without a central constructor. + +The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: + +- assembling a struct from independent contributions — [extensible records](../concepts/extensible-records.md) and the [extensible builder pattern](../concepts/dispatching.md) +- a struct that can be built field by field and merged — [`#[derive(BuildField)]`](../reference/derives/derive_build_field.md) with [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md), and `build_from` from [casting](../reference/traits/cast.md) +- each subsystem builder is a handler — [`Handler` / `CanHandle`](../reference/components/handler.md) in the [handler family](../concepts/handlers.md) +- writing a builder provider — [`#[cgp_impl]`](../reference/macros/cgp_impl.md) reading config through [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) +- the abstract error and raising into it — [`HasErrorType`](../reference/components/has_error_type.md) and [`CanRaiseError`](../reference/components/can_raise_error.md) +- merging builder outputs into the target — [`BuildAndMergeOutputs`](../reference/providers/dispatch_combinators.md) +- wiring and checking a context — [`delegate_components!`](../reference/macros/delegate_components.md) and [`check_components!`](../reference/macros/check_components.md) +- choosing among build targets at the type level — [`UseDelegate`](../reference/providers/use_delegate.md) + +All snippets assume `use cgp::prelude::*;`, the handler items come from `cgp::extra::handler`, and the builder dispatcher from `cgp::extra::dispatch`. + +## The constructor that grows + +The application context is an ordinary struct, and the direct way to build it is a constructor that initializes every field at once: + +```rust +#[derive(HasField, HasFields, BuildField)] +pub struct App { + pub sqlite_pool: SqlitePool, + pub http_client: Client, + pub open_ai_client: openai::Client, + pub open_ai_agent: Agent, +} + +impl App { + pub async fn new( + db_options: &str, + db_journal_mode: &str, + http_user_agent: &str, + open_ai_key: &str, + open_ai_model: &str, + llm_preamble: &str, + ) -> Result { + let journal_mode = SqliteJournalMode::from_str(db_journal_mode)?; + let db_options = SqliteConnectOptions::from_str(db_options)?.journal_mode(journal_mode); + let sqlite_pool = SqlitePool::connect_with(db_options).await?; + + let http_client = Client::builder() + .user_agent(http_user_agent) + .connect_timeout(Duration::from_secs(5)) + .build()?; + + let open_ai_client = openai::Client::new(open_ai_key); + let open_ai_agent = open_ai_client.agent(open_ai_model).preamble(llm_preamble).build(); + + Ok(Self { sqlite_pool, http_client, open_ai_client, open_ai_agent }) + } +} +``` + +Every subsystem's setup lives in one function, so each new field widens the parameter list and every team touches the same constructor. The deriving line is the first move away from that: `App` derives [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md) and [`#[derive(BuildField)]`](../reference/derives/derive_build_field.md), which expose it as a [product of named fields](../concepts/extensible-records.md) and generate a partial-record builder, so the struct can be filled in field by field instead of all at once. + +## A builder provider for one subsystem + +Each subsystem becomes its own provider that constructs a small output struct. The SQLite builder reads its configuration from the context and produces a `SqliteClient`: + +```rust +#[cgp_auto_getter] +pub trait HasSqliteOptions { + fn db_options(&self) -> &str; + fn db_journal_mode(&self) -> &str; +} + +#[derive(HasField, HasFields, BuildField)] +pub struct SqliteClient { + pub sqlite_pool: SqlitePool, +} + +#[cgp_impl(new BuildSqliteClient)] +impl Handler +where + Self: HasSqliteOptions + CanRaiseError, +{ + type Output = SqliteClient; + + async fn handle( + &self, + _code: PhantomData, + _input: Input, + ) -> Result { + let journal_mode = + SqliteJournalMode::from_str(self.db_journal_mode()).map_err(Self::raise_error)?; + let db_options = SqliteConnectOptions::from_str(self.db_options()) + .map_err(Self::raise_error)? + .journal_mode(journal_mode); + let sqlite_pool = SqlitePool::connect_with(db_options) + .await + .map_err(Self::raise_error)?; + + Ok(SqliteClient { sqlite_pool }) + } +} +``` + +`BuildSqliteClient` is a [`Handler`](../reference/components/handler.md) provider written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), so it reads like a method on the builder context while staying generic over it. It does not know the final `App` type — only that its context can supply SQLite options (through the [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) getter `HasSqliteOptions`, satisfied by any context with the matching fields) and can raise an `sqlx::Error` into its own abstract error via [`CanRaiseError`](../reference/components/can_raise_error.md). Its output `SqliteClient` derives the same record traits as `App`, which is what lets its single field be merged into the larger struct later. + +The other subsystems follow the same shape. The HTTP and OpenAI builders each read their own config and produce their own output struct: + +```rust +#[cgp_auto_getter] +pub trait HasHttpClientConfig { + fn http_user_agent(&self) -> &str; +} + +#[derive(HasField, HasFields, BuildField)] +pub struct HttpClient { + pub http_client: Client, +} + +#[cgp_impl(new BuildHttpClient)] +impl Handler +where + Self: HasHttpClientConfig + CanRaiseError, +{ + type Output = HttpClient; + + async fn handle(&self, _code: PhantomData, _input: Input) -> Result { + let http_client = Client::builder() + .user_agent(self.http_user_agent()) + .connect_timeout(Duration::from_secs(5)) + .build() + .map_err(Self::raise_error)?; + + Ok(HttpClient { http_client }) + } +} +``` + +```rust +#[cgp_auto_getter] +pub trait HasOpenAiConfig { + fn open_ai_key(&self) -> &str; + fn open_ai_model(&self) -> &str; + fn llm_preamble(&self) -> &str; +} + +#[derive(HasField, HasFields, BuildField)] +pub struct OpenAiClient { + pub open_ai_client: openai::Client, + pub open_ai_agent: Agent, +} + +#[cgp_impl(new BuildOpenAiClient)] +impl Handler +where + Self: HasOpenAiConfig + HasErrorType, +{ + type Output = OpenAiClient; + + async fn handle(&self, _code: PhantomData, _input: Input) -> Result { + let open_ai_client = openai::Client::new(self.open_ai_key()); + let open_ai_agent = open_ai_client + .agent(self.open_ai_model()) + .preamble(self.llm_preamble()) + .build(); + + Ok(OpenAiClient { open_ai_client, open_ai_agent }) + } +} +``` + +Each provider states exactly what it needs from the context in its `where` clause as an [impl-side dependency](../concepts/impl-side-dependencies.md), and nothing more. A builder whose construction cannot fail — like `BuildOpenAiClient` — only requires [`HasErrorType`](../reference/components/has_error_type.md) so its output type aligns with the others, while a fallible one adds the `CanRaiseError` it needs. + +## Merging the outputs into the application + +The builder context names the configuration fields and wires the providers together. It is a plain struct that derives [`HasField`](../reference/derives/derive_has_field.md) — which makes the getters above resolve against its fields — and wires the `HandlerComponent` to [`BuildAndMergeOutputs`](../reference/providers/dispatch_combinators.md): + +```rust +#[derive(HasField, Deserialize)] +pub struct FullAppBuilder { + pub db_options: String, + pub db_journal_mode: String, + pub http_user_agent: String, + pub open_ai_key: String, + pub open_ai_model: String, + pub llm_preamble: String, +} + +delegate_components! { + FullAppBuilder { + ErrorTypeProviderComponent: + UseAnyhowError, + ErrorRaiserComponent: + RaiseAnyhowError, + HandlerComponent: + BuildAndMergeOutputs< + App, + Product![ + BuildSqliteClient, + BuildHttpClient, + BuildOpenAiClient, + ]>, + } +} + +check_components! { + FullAppBuilder { + HandlerComponent: ((), ()), + } +} +``` + +`BuildAndMergeOutputs` is the heart of the [extensible builder pattern](../concepts/extensible-records.md): it starts an empty `App` builder, runs each provider in the list, merges each output struct into the builder with `build_from` from [casting](../reference/traits/cast.md), and finalizes the complete `App`. The merge is name-driven, so `SqliteClient`'s `sqlite_pool` field lands in `App`'s `sqlite_pool` field with no conversion written. The error wiring picks `anyhow::Error` as the abstract error through `UseAnyhowError` and lets the source errors raise into it through `RaiseAnyhowError`, satisfying the `CanRaiseError` bounds the providers declared. The [`check_components!`](../reference/macros/check_components.md) block asserts at compile time that the handler is wired for the unit `Code` and `Input` the build is invoked with — if any provider's required field were missing from `FullAppBuilder`, this would fail to compile rather than at runtime. + +Building the `App` is then one call. The builder is constructed from its config fields — or deserialized from a file, since it derives `Deserialize` — and `handle` runs the whole pipeline: + +```rust +pub async fn main() -> Result<(), Error> { + let builder = FullAppBuilder { + db_options: "file:./db.sqlite".to_owned(), + db_journal_mode: "WAL".to_owned(), + http_user_agent: "SUPER_AI_AGENT".to_owned(), + open_ai_key: "1234567890".to_owned(), + open_ai_model: "gpt-4o".to_owned(), + llm_preamble: "You are a helpful assistant".to_owned(), + }; + + let _app = builder.handle(PhantomData::<()>, ()).await?; + + Ok(()) +} +``` + +The `PhantomData::<()>` is the `Code` and the `()` the `Input`; neither is constrained here, so the unit type stands in for both. + +## Swapping a subsystem + +Because each builder is decoupled from the target struct, replacing one subsystem reuses everything else. An enterprise variant that uses Postgres instead of SQLite needs a new output struct and builder, plus a target struct that holds a `PgPool`: + +```rust +#[cgp_auto_getter] +pub trait HasPostgresUrl { + fn postgres_url(&self) -> &str; +} + +#[derive(HasField, HasFields, BuildField)] +pub struct PostgresClient { + pub postgres_pool: PgPool, +} + +#[cgp_impl(new BuildPostgresClient)] +impl Handler +where + Self: HasPostgresUrl + CanRaiseError, +{ + type Output = PostgresClient; + + async fn handle(&self, _code: PhantomData, _input: Input) -> Result { + let postgres_pool = PgPool::connect(self.postgres_url()).await.map_err(Self::raise_error)?; + Ok(PostgresClient { postgres_pool }) + } +} +``` + +The new builder context swaps `BuildPostgresClient` in for `BuildSqliteClient` and reuses the HTTP and OpenAI builders unchanged: + +```rust +#[derive(HasField, HasFields, BuildField)] +pub struct App { + pub postgres_pool: PgPool, + pub http_client: Client, + pub open_ai_client: openai::Client, + pub open_ai_agent: Agent, +} + +#[derive(HasField, Deserialize)] +pub struct AppBuilder { + pub postgres_url: String, + pub http_user_agent: String, + pub open_ai_key: String, + pub open_ai_model: String, + pub llm_preamble: String, +} + +delegate_components! { + AppBuilder { + ErrorTypeProviderComponent: UseAnyhowError, + ErrorRaiserComponent: RaiseAnyhowError, + HandlerComponent: + BuildAndMergeOutputs< + App, + Product![ + BuildPostgresClient, + BuildHttpClient, + BuildOpenAiClient, + ]>, + } +} +``` + +Unlike feature flags, which force an either/or split at compile time, the SQLite and Postgres variants can coexist in the same codebase and even compile together, which keeps both paths tested. + +## Many applications from one builder + +A single builder can produce several application variants by dispatching on its `Code` parameter. Given a builder whose fields cover the needs of every variant, marker types name the build targets and [`UseDelegate`](../reference/providers/use_delegate.md) routes each one to its own provider list: + +```rust +pub struct BuildChatGptApp; +pub struct BuildAnthropicApp; +pub struct BuildAnthropicAndChatGptApp; + +delegate_components! { + AnthropicAndChatGptAppBuilder { + ErrorTypeProviderComponent: UseAnyhowError, + ErrorRaiserComponent: RaiseAnyhowError, + HandlerComponent: + UseDelegate, + BuildAnthropicApp: + BuildAndMergeOutputs, + BuildAnthropicAndChatGptApp: + BuildAndMergeOutputs, + }>, + } +} +``` + +Each marker selects a different target struct and provider list, and the `llm_preamble` field is shared by both the Anthropic and OpenAI builders with no coordination — a value-level dependency injected once and read by every provider that needs it. Choosing a variant is then a matter of which `Code` is passed to `handle`: + +```rust +let chat_gpt_app: App = builder.handle(PhantomData::, ()).await?; +let anthropic_app: AnthropicApp = builder.handle(PhantomData::, ()).await?; +let combined_app: AnthropicAndChatGptApp = + builder.handle(PhantomData::, ()).await?; +``` + +The same builder, the same config, three different application contexts — selected by type, dispatched at compile time, with the builder pipeline for each one assembled from the same decoupled providers. diff --git a/docs/examples/area-calculation.md b/docs/examples/area-calculation.md new file mode 100644 index 00000000..fc3ecd39 --- /dev/null +++ b/docs/examples/area-calculation.md @@ -0,0 +1,176 @@ +# Area calculation + +This example computes the area of several shapes, progressing from a single field-driven function to a unified, wireable area-calculation component whose implementations compose through higher-order providers. It is a template for any use case where one operation has several interchangeable implementations chosen per context — the shapes here stand in for whatever set of variants an application needs to treat uniformly. + +The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: + +- context-generic functions — [`#[cgp_fn]`](../reference/macros/cgp_fn.md) with [implicit arguments](../concepts/implicit-arguments.md) +- importing capabilities — [`#[uses]`](../reference/attributes/uses.md) +- field access on contexts — [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) +- components and named providers — [`#[cgp_component]`](../reference/macros/cgp_component.md), [`#[cgp_impl]`](../reference/macros/cgp_impl.md), and the [consumer/provider trait duality](../concepts/consumer-and-provider-traits.md) +- wiring a context to providers — [`delegate_components!`](../reference/macros/delegate_components.md) +- composing providers — [higher-order providers](../concepts/higher-order-providers.md) with [`#[use_provider]`](../reference/attributes/use_provider.md) + +All snippets assume `use cgp::prelude::*;`. + +## A field-driven function + +The smallest unit of reuse is a function that reads its inputs from the context's fields instead of from explicit arguments. Marking `rectangle_area` with [`#[cgp_fn]`](../reference/macros/cgp_fn.md) and tagging its parameters [`#[implicit]`](../reference/attributes/implicit.md) turns it into a method available on any context that carries a `width` and a `height` field: + +```rust +#[cgp_fn] +pub fn rectangle_area( + &self, + #[implicit] width: f64, + #[implicit] height: f64, +) -> f64 { + width * height +} +``` + +A context becomes eligible by deriving [`HasField`](../reference/derives/derive_has_field.md), which generates the field accessors the implicit arguments resolve through. No per-context implementation is needed: + +```rust +#[derive(HasField)] +pub struct PlainRectangle { + pub width: f64, + pub height: f64, +} + +let area = PlainRectangle { width: 2.0, height: 3.0 }.rectangle_area(); +assert_eq!(area, 6.0); +``` + +## Building on another function + +A context-generic function can call another by importing it with [`#[uses]`](../reference/attributes/uses.md), which adds the imported capability as a hidden bound on the context rather than a visible parameter. Here `scaled_rectangle_area` reuses `rectangle_area` while contributing only its own `scale_factor` field: + +```rust +#[cgp_fn] +#[uses(RectangleArea)] +pub fn scaled_rectangle_area( + &self, + #[implicit] scale_factor: f64, +) -> f64 { + self.rectangle_area() * scale_factor * scale_factor +} +``` + +The imported name `RectangleArea` is the trait `#[cgp_fn]` derives from the `rectangle_area` function — a function `foo` generates a trait `Foo`. A context that adds the extra field can use both functions, while the original `PlainRectangle` keeps working with `rectangle_area` alone: + +```rust +#[derive(HasField)] +pub struct ScaledRectangle { + pub width: f64, + pub height: f64, + pub scale_factor: f64, +} + +let r = ScaledRectangle { width: 3.0, height: 4.0, scale_factor: 2.0 }; +assert_eq!(r.rectangle_area(), 12.0); +assert_eq!(r.scaled_rectangle_area(), 48.0); +``` + +## A unified interface across shapes + +A single `#[cgp_fn]` defines exactly one implementation, so it cannot serve as a common interface that different shapes implement differently. For that, define a [component](../concepts/consumer-and-provider-traits.md) with [`#[cgp_component]`](../reference/macros/cgp_component.md). The annotated `CanCalculateArea` trait is the *consumer trait* callers use; the `AreaCalculator` argument names the generated *provider trait* that implementations target: + +```rust +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} +``` + +Each implementation is a *named provider* written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md). Unlike a blanket `impl`, named providers may overlap freely — a rectangle calculator and a circle calculator can both exist even though a context could in principle have the fields for either. Implicit arguments work here just as in `#[cgp_fn]`, so the field-reading logic can be inlined directly: + +```rust +#[cgp_impl(new RectangleAreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} + +#[cgp_impl(new CircleAreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] radius: f64) -> f64 { + core::f64::consts::PI * radius * radius + } +} +``` + +## Wiring contexts to providers + +Defining a provider does not attach it to any context; a context chooses its provider by wiring with [`delegate_components!`](../reference/macros/delegate_components.md). Each entry maps the component — keyed by its generated `…Component` name — to the provider that should implement it for that context: + +```rust +#[derive(HasField)] +pub struct PlainCircle { + pub radius: f64, +} + +delegate_components! { + PlainRectangle { + AreaCalculatorComponent: RectangleAreaCalculator, + } +} + +delegate_components! { + PlainCircle { + AreaCalculatorComponent: CircleAreaCalculator, + } +} +``` + +With the wiring in place, the consumer-trait method is available on each context, dispatched to its chosen provider at compile time with no runtime indirection: + +```rust +assert_eq!(PlainRectangle { width: 2.0, height: 3.0 }.area(), 6.0); +assert_eq!(PlainCircle { radius: 4.0 }.area(), 16.0 * core::f64::consts::PI); +``` + +## Composing providers + +Scaling applies to every shape the same way, so writing a separate scaled provider per shape would duplicate the same logic. A [higher-order provider](../concepts/higher-order-providers.md) captures the transformation once and takes the base calculation as a provider parameter — `InnerCalculator`, declared as an impl generic. The [`#[use_provider]`](../reference/attributes/use_provider.md) attribute supplies that inner provider's bound, filling in the leading context argument a provider trait carries, while the body invokes it as an associated function: + +```rust +#[cgp_impl(new ScaledAreaCalculator)] +#[use_provider(InnerCalculator: AreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + InnerCalculator::area(self) * scale_factor * scale_factor + } +} +``` + +A context now selects its base calculator and its scaling in one wiring entry by nesting the providers. The same `ScaledAreaCalculator` composes over any inner calculator, so a scaled circle reuses exactly the scaling logic a scaled rectangle uses: + +```rust +#[derive(HasField)] +pub struct ScaledCircle { + pub radius: f64, + pub scale_factor: f64, +} + +delegate_components! { + ScaledRectangle { + AreaCalculatorComponent: ScaledAreaCalculator, + } +} + +delegate_components! { + ScaledCircle { + AreaCalculatorComponent: ScaledAreaCalculator, + } +} + +let r = ScaledRectangle { width: 3.0, height: 4.0, scale_factor: 2.0 }; +assert_eq!(r.area(), 48.0); + +let c = ScaledCircle { radius: 3.0, scale_factor: 2.0 }; +assert_eq!(c.area(), 36.0 * core::f64::consts::PI); +``` + +Because providers are plain type-level markers, a long composition can be given a shorter name with an ordinary type alias — `pub type ScaledRectangleAreaCalculator = ScaledAreaCalculator;` — and used anywhere the expanded form would be. + diff --git a/docs/examples/expression-interpreter.md b/docs/examples/expression-interpreter.md new file mode 100644 index 00000000..3eff91f1 --- /dev/null +++ b/docs/examples/expression-interpreter.md @@ -0,0 +1,328 @@ +# Expression interpreter + +This example builds a modular interpreter for a small arithmetic language, where each operator is its own type and each operation over the language — evaluation, conversion to Lisp — is a separate provider, so variants and operations can both be added without editing existing code. It progresses from the closed enum-and-`match` form, through per-variant evaluation providers wired by input dispatch, to a second operation, a generalized operator provider, code-based dispatch between operations, and finally an extended language with new variants. It is a template for any recursive data type — expression trees, JSON values, syntax trees — that must stay open to new cases and new traversals at once, the classic [expression problem](https://en.wikipedia.org/wiki/Expression_problem). + +The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: + +- handling each variant of an enum independently — [extensible variants](../concepts/extensible-variants.md) and the [extensible visitor pattern](../concepts/dispatching.md) +- exposing an enum as a sum of named variants — [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md), [`#[derive(FromVariant)]`](../reference/derives/derive_from_variant.md), [`#[derive(ExtractField)]`](../reference/derives/derive_extract_field.md) +- the computation components — [`Computer` / `CanCompute`](../reference/components/computer.md) and its by-reference variant `ComputerRef` +- writing a per-variant provider — [`#[cgp_impl]`](../reference/macros/cgp_impl.md) +- routing on the input variant and on the operation — [`UseDelegate`](../reference/providers/use_delegate.md) and [dispatching](../concepts/dispatching.md) +- the variant dispatcher — [`MatchWithValueHandlers`](../reference/providers/dispatch_combinators.md) +- constructing part of a target enum — [`CanUpcast`](../reference/traits/cast.md) +- abstract output types per context — [`#[cgp_type]`](../reference/macros/cgp_type.md) and [`UseType`](../reference/providers/use_type.md) + +All snippets assume `use cgp::prelude::*;`, with the computation items from `cgp::extra::handler`, the dispatchers from `cgp::extra::dispatch`, and `CanUpcast` from `cgp::core::field::impls`. + +## The closed interpreter + +The conventional way to model the language is one enum with a `match`-based function per operation: + +```rust +pub enum Expr { + Plus(Box, Box), + Times(Box, Box), + Literal(u64), +} + +pub fn eval(expr: Expr) -> u64 { + match expr { + Expr::Plus(a, b) => eval(*a) + eval(*b), + Expr::Times(a, b) => eval(*a) * eval(*b), + Expr::Literal(value) => value, + } +} +``` + +This is concise but closed in two directions. Adding a variant forces every function that matches on `Expr` to change, and the recursive structure means even a helper like `eval_plus` must still mention `Expr`. A real expression type such as `syn::Expr` has dozens of variants and many operations, so this coupling becomes the central obstacle the rest of the example removes. + +## Variants as standalone types + +The first move is to give each operator its own type, generic over the expression it nests, rather than burying its shape in the enum: + +```rust +#[derive(Debug, Eq, PartialEq, HasField)] +pub struct Plus { + pub left: Box, + pub right: Box, +} + +#[derive(Debug, Eq, PartialEq, HasField)] +pub struct Times { + pub left: Box, + pub right: Box, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct Literal(pub T); +``` + +Each operator is now a reusable building block parameterized by the broader expression type, which is what lets the same `Plus` appear in several languages. `Plus` and `Times` derive [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) so their `left` and `right` fields can be read generically later. + +## Evaluating one variant + +Evaluation is a [`Computer`](../reference/components/computer.md) — CGP's component for a synchronous, pure computation whose consumer trait is `CanCompute`. One provider handles addition, recursing into the operands through the context's own evaluation: + +```rust +#[cgp_impl(new EvalAdd)] +impl Computer> +where + Self: CanCompute, + Output: Add, +{ + type Output = Output; + + fn compute(&self, code: PhantomData, Plus { left, right }: Plus) -> Self::Output { + let output_a = self.compute(code, *left); + let output_b = self.compute(code, *right); + output_a + output_b + } +} +``` + +`EvalAdd` is written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) and is completely decoupled: it knows nothing of the concrete expression enum, only that the context can evaluate a nested `MathExpr` to some `Output` that supports `Add`. Multiplication is identical with `Mul`, and the literal case is the base of the recursion — it just returns its inner value, needing nothing from the context: + +```rust +#[cgp_impl(new EvalLiteral)] +impl Computer> { + type Output = T; + + fn compute(&self, _code: PhantomData, Literal(value): Literal) -> T { + value + } +} +``` + +Each provider lives on its own and could be defined in a separate crate; nothing ties `EvalAdd`, `EvalMultiply`, and `EvalLiteral` together until a context composes them. + +## Assembling the evaluator + +The concrete enum wraps the standalone operator types and derives the [extensible-variant](../concepts/extensible-variants.md) machinery so it can be taken apart generically: + +```rust +pub type Value = u64; + +#[derive(Debug, HasFields, FromVariant, ExtractField)] +pub enum MathExpr { + Plus(Plus), + Times(Times), + Literal(Literal), +} +``` + +The context is an empty struct whose only job is to wire each input type to its provider. [`UseInputDelegate`](../reference/providers/use_delegate.md) keys the `Computer` lookup on the *input* type, so `Plus` routes to `EvalAdd`, `Literal` to `EvalLiteral`, and the whole enum to a dispatcher: + +```rust +pub struct Interpreter; + +delegate_components! { + Interpreter { + ComputerComponent: + UseInputDelegate: EvalAdd, + Times: EvalMultiply, + Literal: EvalLiteral, + }>, + } +} + +#[cgp_impl(new DispatchEval)] +impl Computer for Interpreter { + type Output = Value; + + fn compute(context: &Interpreter, code: PhantomData, expr: MathExpr) -> Self::Output { + ::compute(context, code, expr) + } +} +``` + +The whole `MathExpr` enum is handled by `DispatchEval`, a context-specific provider that defers to [`MatchWithValueHandlers`](../reference/providers/dispatch_combinators.md) — the variant dispatcher that derives one handler per variant from the enum's own variant list and runs them as a match, described in [dispatching](../concepts/dispatching.md). The thin `DispatchEval` wrapper is needed to break a trait-resolution cycle: wiring `MatchWithValueHandlers` directly for `MathExpr` would require the compiler to resolve the per-variant providers, which themselves route back through the dispatcher. Marking the trait implemented in the wrapper's body breaks the cycle. + +## A second operation over the same language + +Converting an expression to a Lisp [S-expression](https://en.wikipedia.org/wiki/S-expression) is a second operation, and adding it must not touch the evaluator. It uses `ComputerRef`, the by-reference variant of `Computer`, so the expression can still be used afterward, and it targets a separate `LispExpr` enum — making this a "double" expression problem, decoupled from both the source and the target type. The target type stays abstract through a [`#[cgp_type]`](../reference/macros/cgp_type.md) component: + +```rust +#[cgp_type] +pub trait HasLispExprType { + type LispExpr; +} + +#[cgp_impl(new PlusToLisp)] +impl ComputerRef> +where + Self: HasLispExprType + CanComputeRef, + LispSubExpr: CanUpcast, +{ + type Output = LispExpr; + + fn compute_ref(&self, code: PhantomData, Plus { left, right }: &Plus) -> Self::Output { + let expr_a = self.compute_ref(code, left); + let expr_b = self.compute_ref(code, right); + let ident = LispSubExpr::Ident(Ident("+".to_owned())).upcast(PhantomData); + + LispSubExpr::List(List(vec![ident.into(), expr_a.into(), expr_b.into()])).upcast(PhantomData) + } +} +``` + +`PlusToLisp` only needs to build two kinds of `LispExpr` — a list and an identifier — so rather than depend on the full target enum it defines a small local enum with just those variants and [upcasts](../reference/traits/cast.md) into the full `LispExpr`: + +```rust +#[derive(HasFields, ExtractField, FromVariant)] +enum LispSubExpr { + List(List), + Ident(Ident), +} +``` + +This is the variant-side analog of reading only the fields you need from a struct: `CanUpcast` constructs the parts of an enum a provider cares about without binding it to the entire definition. Wiring the new operation adds a `ComputerRefComponent` table and a `DispatchToLisp` wrapper alongside the existing evaluator, and binds the abstract `LispExpr` type to the concrete enum with [`UseType`](../reference/providers/use_type.md): + +```rust +delegate_components! { + Interpreter { + MathExprTypeProviderComponent: UseType, + LispExprTypeProviderComponent: UseType, + ComputerComponent: + UseInputDelegate: EvalAdd, + Times: EvalMultiply, + Literal: EvalLiteral, + }>, + ComputerRefComponent: + UseInputDelegate: LiteralToLisp, + Plus: PlusToLisp, + Times: TimesToLisp, + }>, + } +} +``` + +The evaluator wiring is untouched; the conversion is added purely by extension. + +## One provider for every binary operator + +`PlusToLisp` and `TimesToLisp` differ only in the operator symbol, so they collapse into a single provider parameterized by the operator. The operator is a type-level string, and the operand fields are read through a getter that any binary struct satisfies: + +```rust +#[cgp_auto_getter] +pub trait BinarySubExpression { + fn left(&self) -> &Box; + fn right(&self) -> &Box; +} + +#[cgp_impl(new BinaryOpToLisp)] +impl ComputerRef +where + Self: HasMathExprType + + HasLispExprType + + CanComputeRef, + MathSubExpr: BinarySubExpression, + Operator: Default + Display, + LispSubExpr: CanUpcast, +{ + type Output = LispExpr; + + fn compute_ref(&self, code: PhantomData, expr: &MathSubExpr) -> Self::Output { + let expr_a = self.compute_ref(code, expr.left()); + let expr_b = self.compute_ref(code, expr.right()); + let ident = LispSubExpr::Ident(Ident(Operator::default().to_string())).upcast(PhantomData); + + LispSubExpr::List(List(vec![ident.into(), expr_a.into(), expr_b.into()])).upcast(PhantomData) + } +} +``` + +`BinaryOpToLisp` works for any `MathSubExpr` whose `left` and `right` fields the [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) trait `BinarySubExpression` can read — which is why `Plus` and `Times` derived `HasField` earlier. Both operators now wire to the same provider with a different [`Symbol!`](../reference/macros/symbol.md) operator string: + +```rust +ComputerRefComponent: + UseInputDelegate: LiteralToLisp, + Plus: BinaryOpToLisp, + Times: BinaryOpToLisp, + }>, +``` + +## Dispatching on the operation as well as the input + +Evaluation and conversion can share one component by adding a second layer of dispatch keyed on the *operation*. Marker types name the operations, and a per-variant provider routes on them with [`UseDelegate`](../reference/providers/use_delegate.md): + +```rust +pub struct Eval; +pub struct ToLisp; + +delegate_components! { + new HandlePlus { + ComputerRefComponent: UseDelegate, + }> + } +} +``` + +`HandlePlus` interprets a `Plus` as either evaluation or conversion depending on the `Code`, and `HandleTimes`, `HandleLiteral`, and `HandleMathExpr` follow the same shape. The context then dispatches first on the input type and second on the operation — a two-layer table whose order is a free choice that costs nothing at runtime, since all of it resolves through trait selection at compile time. Defining the operation routing through `delegate_components!` rather than separate `impl` blocks is what keeps the `Eval` and `ToLisp` logic free to live in different crates. + +## Extending the language + +A new language lives alongside the old one rather than replacing it, which is the whole point of keeping variants standalone. Subtraction and negation get their own types and evaluation providers, written exactly like the originals: + +```rust +pub struct Minus { pub left: Box, pub right: Box } +pub struct Negate(pub Box); + +#[cgp_impl(new EvalSubtract)] +impl ComputerRef> +where + Self: CanComputeRef, + Output: Sub, +{ + type Output = Output; + + fn compute_ref(&self, code: PhantomData, Minus { left, right }: &Minus) -> Self::Output { + let output_a = self.compute_ref(code, left); + let output_b = self.compute_ref(code, right); + output_a - output_b + } +} +``` + +The extended enum reuses the original operator providers, now instantiated at a signed `Value` so negation has something to return: + +```rust +pub type Value = i64; + +#[derive(Debug, HasFields, FromVariant, ExtractField)] +pub enum MathPlusExpr { + Plus(Plus), + Times(Times), + Literal(Literal), + Negate(Negate), + Minus(Minus), +} + +delegate_components! { + InterpreterPlus { + ComputerRefComponent: + UseDelegate: EvalAdd, + Times: EvalMultiply, + Literal: EvalLiteral, + Minus: EvalSubtract, + Negate: EvalNegate, + }>, + }> + } +} +``` + +`EvalAdd` and `EvalMultiply` work unchanged because `i64` implements `Add` and `Mul` just as `u64` did — the providers never named a concrete numeric type. Equally telling is what is *absent*: `InterpreterPlus` wires only evaluation and simply omits a to-Lisp handler for `Minus` and `Negate`. Because CGP wiring is lazy and checked only where it is used, the evaluator compiles and runs without those handlers, so a new variant can be prototyped against one operation before the others catch up — the kind of partial extension a closed `enum` with exhaustive `match`es cannot express. diff --git a/docs/examples/extensible-shapes.md b/docs/examples/extensible-shapes.md new file mode 100644 index 00000000..4e1f1853 --- /dev/null +++ b/docs/examples/extensible-shapes.md @@ -0,0 +1,226 @@ +# Extensible shapes + +This example computes properties of geometric shapes — area, scaling — modeled as the variants of an enum, dispatching each operation to a per-variant implementation without a hand-written `match`. It progresses from a per-type operation that is lifted onto an enum automatically, through mutating and argument-taking operations, to converting between related shape enums and finally wiring the dispatch into a context. It is a template for any use case where one operation has a separate implementation per case of a sum type, and the set of cases should stay open to extension — the *extensible visitor* counterpart to the per-context dispatch in [area calculation](area-calculation.md). + +The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: + +- shapes as the variants of an enum — [extensible variants](../concepts/extensible-variants.md) via [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md) +- dispatching an operation to per-variant implementations — [`#[cgp_auto_dispatch]`](../reference/macros/cgp_auto_dispatch.md) +- per-variant handlers as computers — [`#[cgp_computer]`](../reference/macros/cgp_computer.md) producing [`Computer`](../reference/components/computer.md) providers +- widening and narrowing the variant set — [upcasting and downcasting](../reference/traits/cast.md) +- the dispatch machinery underneath — the [dispatch combinators](../reference/providers/dispatch_combinators.md) and [dispatching](../concepts/dispatching.md) +- wiring dispatch into a context — [`delegate_components!`](../reference/macros/delegate_components.md) with [`UseInputDelegate`](../reference/providers/use_delegate.md) and a [`check_components!`](../reference/macros/check_components.md) assertion + +All snippets assume `use cgp::prelude::*;`; the dispatch combinators come from `cgp::extra::dispatch`, `UseInputDelegate` from `cgp::extra::handler`, and the cast helpers from `cgp::core::field::impls`. Each shape is its own payload struct: + +```rust +#[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, +} +``` + +## A per-type operation, lifted onto enums + +The smallest unit is an ordinary trait with one implementation per shape type. Marking the trait with [`#[cgp_auto_dispatch]`](../reference/macros/cgp_auto_dispatch.md) is what later lets any *enum* of these shapes gain the operation for free, dispatched to the matching variant — but the per-type impls themselves are plain Rust: + +```rust +#[cgp_auto_dispatch] +pub trait HasArea { + fn area(self) -> f64; +} + +impl HasArea for Circle { + fn area(self) -> f64 { + core::f64::consts::PI * self.radius * self.radius + } +} + +impl HasArea for Rectangle { + fn area(self) -> f64 { + self.width * self.height + } +} + +impl HasArea for Triangle { + fn area(self) -> f64 { + self.base * self.height / 2.0 + } +} +``` + +`#[cgp_auto_dispatch]` leaves the trait and these impls untouched and generates, behind the scenes, the per-variant handler and the enum-level implementation that runs it. The operation reads like a normal method; the dispatch is what the macro supplies. + +## An enum of shapes + +A concrete shape is one enum over the payload types, made [extensible](../concepts/extensible-variants.md) with [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md) so it can be taken apart by the variant name generically. Each variant holds exactly one payload — the single-field tuple form the derive requires: + +```rust +#[derive(Debug, PartialEq, CgpData)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} +``` + +Because `HasArea` carries `#[cgp_auto_dispatch]` and `Shape` is extensible, `Shape` now implements `HasArea` too, matching its current variant and delegating to that payload's `area`: + +```rust +let shape = Shape::Circle(Circle { radius: 5.0 }); +assert_eq!(shape.area(), 25.0 * core::f64::consts::PI); +``` + +The dispatch is resolved at compile time, and forgetting an `impl HasArea` for one variant's payload is a compile error at the point `Shape::area` is used, not a runtime fallthrough. + +## Operations that mutate or take arguments + +The same dispatch covers every method shape, not just a by-value reader. A `&mut self` method that also takes an argument — scaling a shape by a factor — dispatches identically; `#[cgp_auto_dispatch]` selects the matcher form that matches the receiver and threads the extra argument through to each variant's implementation: + +```rust +#[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; + } +} + +impl CanScale for Triangle { + fn scale(&mut self, factor: f64) { + self.base *= factor; + self.height *= factor; + } +} + +let mut shape = Shape::Rectangle(Rectangle { width: 2.0, height: 2.0 }); +shape.scale(2.0); +assert_eq!(shape.area(), 16.0); +``` + +A trait may mix reading and mutating methods, by-value and by-reference receivers, extra arguments, and `async` methods, all dispatched the same way; the [`#[cgp_auto_dispatch]`](../reference/macros/cgp_auto_dispatch.md) reference covers which matcher each shape selects. + +## Widening and narrowing the shape set + +Two enums that share variants interconvert with no hand-written conversion, through the [casting traits](../reference/traits/cast.md). A wider `ShapePlus` adds a `Triangle` case alongside the two `Shape` already has: + +```rust +#[derive(Debug, PartialEq, CgpData)] +pub enum ShapePlus { + Triangle(Triangle), + Rectangle(Rectangle), + Circle(Circle), +} +``` + +Upcasting a `Shape` into `ShapePlus` always succeeds, since every `Shape` variant has a home in the wider enum; downcasting back may fail, returning the value untouched when its variant has no home in the narrower target: + +```rust +let shape = Shape::Circle(Circle { radius: 5.0 }); +let wider: ShapePlus = shape.upcast(PhantomData::); +assert_eq!(wider, ShapePlus::Circle(Circle { radius: 5.0 })); + +let narrowed = ShapePlus::Circle(Circle { radius: 5.0 }).downcast(PhantomData::); +assert_eq!(narrowed.ok(), Some(Shape::Circle(Circle { radius: 5.0 }))); +``` + +A `ShapePlus::Triangle` cannot narrow to `Shape`, so its `downcast` returns `Err(remainder)`. The remainder is the partial enum with the already-tried variants ruled out, and it can be narrowed again against another candidate with `downcast_fields` — narrowing a leftover `Triangle` into a `TriangleOnly` enum, which here cannot fail: + +```rust +#[derive(Debug, PartialEq, CgpData)] +pub enum TriangleOnly { + Triangle(Triangle), +} +``` + +## Under the hood: the dispatch combinators + +What `#[cgp_auto_dispatch]` writes for `Shape::area` is a [dispatch combinator](../reference/providers/dispatch_combinators.md), and the same dispatch can be assembled by hand. The macro turns each method into a per-variant [`Computer`](../reference/components/computer.md) provider with [`#[cgp_computer]`](../reference/macros/cgp_computer.md) — `area` yields a provider named `ComputeArea` — and then runs a *matcher* over the enum's variants: + +```rust +use cgp::extra::dispatch::{ExtractFieldAndHandle, HandleFieldValue, MatchWithHandlers}; + +let circle = Shape::Circle(Circle { radius: 5.0 }); + +let area = MatchWithHandlers::>, + ExtractFieldAndHandle>, +]>::compute(&(), PhantomData::<()>, circle); +``` + +Each `ExtractFieldAndHandle` tries one variant by its [`Symbol!`](../reference/macros/symbol.md) name and, on a match, hands the payload to `ComputeArea`; the list runs first-match-wins, and once every variant has been tried the remainder is uninhabited, so the match is provably exhaustive with no wildcard arm. The `ComputeArea` provider is what [`#[cgp_computer]`](../reference/macros/cgp_computer.md) produces from a function that forwards to the trait method — equivalent to `fn compute_area(s: S) -> f64 { s.area() }` — and it is exactly the per-variant handler `#[cgp_auto_dispatch]` generates. + +Spelling out one adapter per variant is mechanical, so `MatchWithValueHandlers` is the shorthand that derives the same list from the enum's own variant list; naming the per-variant provider once dispatches the whole enum: + +```rust +let circle = Shape::Circle(Circle { radius: 5.0 }); +let _area = MatchWithValueHandlers::::compute(&(), PhantomData::<()>, circle); +``` + +The matcher is a `Computer` provider invoked with a unit context `&()`, because the per-variant logic depends only on the payload — and this `MatchWithValueHandlers::` call is the very body `#[cgp_auto_dispatch]` writes into the enum's generated `area` method. + +## Wiring dispatch into a context + +Rather than implement the operation directly on the enum, the dispatch can be wired into a context's [`Computer`](../reference/components/computer.md) component, keyed on the *input* type with [`UseInputDelegate`](../reference/providers/use_delegate.md). A bare payload type routes straight to its computer, while an enum routes to the matcher, which dispatches each extracted payload back through the context's own wiring: + +```rust +use cgp::extra::handler::UseInputDelegate; + +pub struct App; + +delegate_components! { + App { + ComputerComponent: UseInputDelegate, + } +} +``` + +A `Circle` input resolves to `ComputeArea` directly. A `Shape` or `ShapePlus` input resolves to `MatchWithValueHandlers` — written here with no provider argument, so it defaults to [`UseContext`](../reference/providers/use_context.md) and dispatches each extracted payload *back through* `App`'s own `ComputerComponent` rather than to a fixed handler. That is why the payload types share the table: the matcher routes a `Circle` payload to whatever `App` wires for `Circle`, which here is `ComputeArea`. + +Routing through the context is what makes individual variants overridable. Swapping the handler for one shape — wiring `Circle` to an optimized provider, say — is a one-line change to the table that leaves the matcher and every other variant untouched, precisely because the matcher never names `ComputeArea` itself. Pinning the matcher to a concrete provider as `MatchWithValueHandlers` is the other option, used when the dispatch should bypass the context entirely, as in the unit-context calls earlier. + +Because shape handlers are leaves — `ComputeArea` never calls back into the dispatcher — the enums can wire `MatchWithValueHandlers` directly. A *recursive* visitor cannot: the [expression interpreter](expression-interpreter.md) routes its enum through a thin wrapper provider instead, to break the trait-resolution cycle that its self-recursive handlers would otherwise create. Because CGP wiring is [checked lazily](../concepts/check-traits.md), a [`check_components!`](../reference/macros/check_components.md) block asserts at compile time that both enums are fully dispatchable, listing each as a `(Code, Input)` pair for the generic `Computer` component: + +```rust +check_components! { + App { + ComputerComponent: [ + ((), Shape), + ((), ShapePlus), + ], + } +} +``` diff --git a/docs/examples/modular-serialization.md b/docs/examples/modular-serialization.md new file mode 100644 index 00000000..f0cba9ec --- /dev/null +++ b/docs/examples/modular-serialization.md @@ -0,0 +1,403 @@ +# Modular serialization + +This example rebuilds Serde's `Serialize` and `Deserialize` as CGP components, so that how each value type is encoded becomes a per-context wiring choice rather than a single fixed implementation baked into the type. It progresses from the two serialization components, through a family of overlapping providers that vanilla Rust would reject, to two application contexts that serialize the same nested data into different JSON formats by changing only a handful of wiring lines, and finally to a context-dependent deserializer that allocates into an arena. It is the template for any capability where the same type needs several interchangeable implementations selected per application, and where the orphan rule would otherwise force a library to derive the trait on every data type itself. + +The concepts each step demonstrates are documented in full in the reference; this example notes which one is in play and links to it: + +- splitting a trait so overlapping and orphan implementations are legal — [consumer and provider traits](../concepts/consumer-and-provider-traits.md) and the [coherence](../concepts/coherence.md) strategy behind it +- defining the components — [`#[cgp_component]`](../reference/macros/cgp_component.md) +- writing the providers — [`#[cgp_impl]`](../reference/macros/cgp_impl.md), with [`#[uses]`](../reference/attributes/uses.md) for the capabilities they look up through the context +- serializing a struct with no serialization-specific derive — [extensible records](../concepts/extensible-records.md) via [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md) +- selecting a provider per value type, inline in the context's own table — the `open` statement of [`delegate_components!`](../reference/macros/delegate_components.md) with [`@`-path keys](../concepts/namespaces.md) +- verifying a context's wiring — [`check_components!`](../reference/macros/check_components.md) +- pulling a capability from the context during deserialization — [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) over [`HasField`](../reference/traits/has_field.md), with the [`HasErrorType`](../reference/components/has_error_type.md) and [`CanRaiseError`](../reference/components/can_raise_error.md) error components wired through [modular error handling](../concepts/modular-error-handling.md) + +All snippets assume `use cgp::prelude::*;` and use Serde's `Serializer`/`Deserializer` traits directly. The providers shown here serialize and deserialize, but the example builds up the serialization side first and then mirrors it for deserialization. + +## The two serialization components + +The starting point is a context-generic restatement of Serde's two traits. Each moves the type being serialized out of the `Self` position — where Serde keeps it — and into an explicit `Value` parameter, leaving `Self` to name a **context** that carries the wiring: + +```rust +#[cgp_component(ValueSerializer)] +pub trait CanSerializeValue { + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer; +} + +#[cgp_component(ValueDeserializer)] +pub trait CanDeserializeValue<'de, Value> { + fn deserialize(&self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>; +} +``` + +`CanSerializeValue` and `CanDeserializeValue` are the [consumer traits](../concepts/consumer-and-provider-traits.md) callers use as `context.serialize(value, s)`; `ValueSerializer` and `ValueDeserializer` are the provider traits implementations are written against. The extra `&self` is the whole point — it gives every implementation access to the context, both to look up how to serialize nested values and, for deserialization, to pull runtime dependencies out of it. Because `Value` is a generic parameter rather than the `Self` type, a context can later wire a different provider for each concrete value type, which is the per-type dispatch set up when the contexts are wired below. + +## Overlapping providers + +With the type moved off `Self`, several implementations of the same component can coexist even though they overlap — each is written for its own zero-sized provider struct, which the defining crate owns, so the [coherence](../concepts/coherence.md) rules never apply. The simplest provider stays compatible with the existing Serde ecosystem by deferring to Serde's own `Serialize`: + +```rust +pub struct UseSerde; + +#[cgp_impl(UseSerde)] +impl ValueSerializer +where + Value: serde::Serialize, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer, + { + value.serialize(serializer) + } +} +``` + +`#[cgp_impl(UseSerde)]` reads like a blanket impl of the consumer trait, but the provider name in the attribute is what becomes the actual `Self` type, so this works for *any* `Context` and *any* `Value: Serialize` without overlapping anything. A second provider serializes any byte container directly as bytes, overlapping `UseSerde` on every type that is both `Serialize` and `AsRef<[u8]>` — `Vec` among them: + +```rust +pub struct SerializeBytes; + +#[cgp_impl(SerializeBytes)] +impl ValueSerializer +where + Value: AsRef<[u8]>, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(value.as_ref()) + } +} +``` + +In vanilla Rust these two blanket implementations could not both exist; as named providers they are simply two entries a context may choose between. Which one a given context uses for `Vec` is decided entirely by its wiring, shown further below. + +## Looking serialization up through the context + +A provider needs more than its own logic when it serializes by delegating to another encoding — and it gets that by asking the context. `SerializeWithDisplay` formats any `Display` value to a string and then serializes *that string through the context*, so the eventual byte-level representation of the string is itself a wiring choice rather than fixed here. The capability it depends on is declared with [`#[uses]`](../reference/attributes/uses.md), which adds the bound `Self: CanSerializeValue` as an [impl-side dependency](../concepts/impl-side-dependencies.md): + +```rust +#[cgp_impl(new SerializeWithDisplay)] +#[uses(CanSerializeValue)] +impl ValueSerializer +where + Value: core::fmt::Display, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer, + { + let str_value = value.to_string(); + self.serialize(&str_value, serializer) + } +} +``` + +The `new` keyword defines the `SerializeWithDisplay` struct in passing. The same shape encodes a byte container as a hexadecimal string — converting to a `String` and handing it back to the context — which is one of the two formats the demo needs: + +```rust +pub struct SerializeHex; + +#[cgp_impl(SerializeHex)] +#[uses(CanSerializeValue)] +impl ValueSerializer +where + Value: hex::ToHex, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer, + { + let str_value = value.encode_hex::(); + self.serialize(&str_value, serializer) + } +} +``` + +A base64 provider is identical but for the encoding call, and a date provider serializes a `DateTime` by formatting it to an RFC 3339 string and serializing that through the context, while an alternative serializes the same `DateTime` as a Unix timestamp `i64`. Each is a separate provider overlapping the others on its value type, and the context picks one. + +## Serializing collections and structs + +Two recursive providers handle composite values by serializing each part through the context, so customization reaches arbitrarily deep without any provider knowing the concrete shape. `SerializeIterator` serializes any iterable as a sequence, asking the context how to serialize each item: + +```rust +#[cgp_impl(new SerializeIterator)] +impl ValueSerializer +where + for<'a> &'a Value: IntoIterator, + Self: for<'a> CanSerializeValue<<&'a Value as IntoIterator>::Item>, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(None)?; + for item in value.into_iter() { + seq.serialize_element(&SerializeWithContext::new(self, &item))?; + } + seq.end() + } +} +``` + +The item bound is a higher-ranked `Self: for<'a> CanSerializeValue<...>` rather than a `#[uses]` line, because a higher-ranked bound is beyond what the simplified `#[uses]` syntax expresses. `SerializeWithContext` is the adapter that pairs a value with a context and implements Serde's own `Serialize`, which is how a nested value re-enters the context's wiring — it appears again at the top level below. The companion `SerializeFields` serializes any struct as a map by walking its fields, available because the struct derives [`CgpData`](../reference/derives/derive_cgp_data.md) and so exposes its fields through [`HasFields`](../reference/traits/has_fields.md): + +```rust +#[cgp_impl(new SerializeFields)] +impl ValueSerializer +where + Value: HasFields, + Value::Fields: FieldsSerializer, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer, + { + let map = serializer.serialize_map(None)?; + Value::Fields::serialize_fields(self, value, map) + } +} +``` + +This is the payoff for the orphan rule: a data type needs no serialization-specific derive and no dependency on this crate at all. Deriving the general-purpose `CgpData` is enough for `SerializeFields` to serialize it generically — see [extensible records](../concepts/extensible-records.md) for what that derive exposes — so a library never has to implement a serialization trait on types it owns just because a downstream application wants to encode them. + +## The data types + +The demo serializes a small tree of encrypted-messaging types, each carrying a byte field or a `DateTime` whose encoding the applications will want to control. Their only derive is `CgpData`: + +```rust +use chrono::{DateTime, Utc}; + +#[derive(CgpData)] +pub struct EncryptedMessage { + pub message_id: u64, + pub author_id: u64, + pub date: DateTime, + pub encrypted_data: Vec, +} + +#[derive(CgpData)] +pub struct MessagesByTopic { + pub encrypted_topic: Vec, + pub messages: Vec, +} + +#[derive(CgpData)] +pub struct MessagesArchive { + pub decryption_key: Vec, + pub messages_by_topics: Vec, +} +``` + +## Wiring an application context + +A context turns this pile of overlapping providers into one coherent scheme by choosing, per value type, which provider runs. The `open` statement in [`delegate_components!`](../reference/macros/delegate_components.md) opens the serialization component for per-type wiring directly in the context's own table; after it, an `@ValueSerializerComponent.: ` entry assigns a provider to each value type the archive touches, the type written as a [`@`-path key](../concepts/namespaces.md). `open` is the lightweight wiring form that suits a self-contained application like this one; a large code base with many components instead shares wiring through named [namespaces](../concepts/namespaces.md) that contexts join and selectively override: + +```rust +pub struct AppA; + +delegate_components! { + AppA { + open {ValueSerializerComponent}; + + @ValueSerializerComponent.<'a, T> &'a T: + SerializeDeref, + @ValueSerializerComponent.[ + u64, + String, + ]: + UseSerde, + @ValueSerializerComponent.Vec: + SerializeHex, + @ValueSerializerComponent.DateTime: + SerializeRfc3339Date, + @ValueSerializerComponent.[ + Vec, + Vec, + ]: + SerializeIterator, + @ValueSerializerComponent.[ + MessagesArchive, + MessagesByTopic, + EncryptedMessage, + ]: + SerializeFields, + } +} +``` + +Reading the table top to bottom: a borrowed value routes to `SerializeDeref` (which forwards to the value behind the reference, encountered while serializing nested items), the scalar types fall back to plain Serde, `Vec` is encoded as hexadecimal, `DateTime` as an RFC 3339 string, the collections as sequences, and the structs as maps. Because the byte and date entries are the only ones that fix a *format*, a second application differs in only a few lines — base64 instead of hex, Unix timestamps instead of RFC 3339, plus an `i64` entry for the timestamps: + +```rust +pub struct AppB; + +delegate_components! { + AppB { + open {ValueSerializerComponent}; + + @ValueSerializerComponent.<'a, T> &'a T: + SerializeDeref, + @ValueSerializerComponent.[ + i64, + u64, + String, + ]: + UseSerde, + @ValueSerializerComponent.Vec: + SerializeBase64, + @ValueSerializerComponent.DateTime: + SerializeTimestamp, + @ValueSerializerComponent.[ + Vec, + Vec, + ]: + SerializeIterator, + @ValueSerializerComponent.[ + MessagesArchive, + MessagesByTopic, + EncryptedMessage, + ]: + SerializeFields, + } +} +``` + +The two contexts resolve `Vec` to overlapping providers — `SerializeHex` and `SerializeBase64` — with no conflict, because each choice is coherent only within its own context. CGP wiring is [checked lazily](../concepts/check-traits.md), so a [`check_components!`](../reference/macros/check_components.md) block asserts at compile time that each context can actually serialize every value type, listing them as the `Value` parameters of `ValueSerializerComponent`: + +```rust +check_components! { + AppA { + ValueSerializerComponent: [ + u64, + String, + Vec, + DateTime, + EncryptedMessage, + MessagesByTopic, + MessagesArchive, + ], + } +} +``` + +## Producing JSON + +Because the providers ultimately defer to a real `serde::Serializer`, the existing JSON ecosystem still does the writing. The bridge is `SerializeWithContext`, which wraps a context and a value into a type that implements Serde's `Serialize` by calling the context's `CanSerializeValue`: + +```rust +let archive = MessagesArchive { /* ... */ }; + +let json_a = serde_json::to_string_pretty(&SerializeWithContext::new(&AppA, &archive)).unwrap(); +let json_b = serde_json::to_string_pretty(&SerializeWithContext::new(&AppB, &archive)).unwrap(); +``` + +`json_a` encodes every byte field as hexadecimal and every date as an RFC 3339 string; `json_b` encodes the same archive with base64 and Unix timestamps. Nothing in the data types or the providers changed between the two — only which context wraps the value. + +## Deserializing with a context-supplied capability + +Deserialization mirrors serialization, and the extra `&self` becomes load-bearing in a way Serde cannot match: the context can supply runtime *capabilities* a provider pulls in by dependency injection. The motivating case is an [arena allocator](https://en.wikipedia.org/wiki/Region-based_memory_management) — deserializing many borrowed `&'a T` values into one arena instead of heap-allocating each. The context exposes the arena through a getter trait, written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) so any context with a matching field implements it automatically: + +```rust +use typed_arena::Arena; + +#[cgp_auto_getter] +pub trait HasArena<'a, T: 'a> { + fn arena(&self) -> &&'a Arena; +} +``` + +The provider for the borrowed type then deserializes the owned value through the context and moves it into the arena fetched from the context. Both capabilities it needs — the arena and the ability to deserialize the owned `Value` — are declared with [`#[uses]`](../reference/attributes/uses.md): + +```rust +#[cgp_impl(new DeserializeAndAllocate)] +#[uses(HasArena<'a, Value>, CanDeserializeValue<'de, Value>)] +impl<'de, 'a, Value> ValueDeserializer<'de, &'a Value> { + fn deserialize(&self, deserializer: D) -> Result<&'a Value, D::Error> + where + D: serde::Deserializer<'de>, + { + let value = self.deserialize(deserializer)?; + let value = self.arena().alloc(value); + Ok(value) + } +} +``` + +The data and the context complete the picture. The structs derive `CgpData` for generic field-by-field deserialization, and the context carries the arena as an ordinary field, deriving [`HasField`](../reference/traits/has_field.md) so `HasArena` is satisfied: + +```rust +#[derive(CgpData)] +pub struct Coord { + pub x: u64, + pub y: u64, + pub z: u64, +} + +#[derive(CgpData)] +pub struct Cluster<'a> { + pub id: u64, + pub coords: Vec<&'a Coord>, +} + +#[derive(HasField)] +pub struct App<'a> { + pub arena: &'a Arena, +} +``` + +The wiring opens the deserialization component and keys on the value type as before, routing the bare `Coord` and `Cluster` to a record-field deserializer, the borrowed `&'a Coord` to the arena allocator, and the `Vec<&'a Coord>` to a sequence deserializer. Deserialization can fail, so the context also wires the [`HasErrorType`](../reference/components/has_error_type.md) and [`CanRaiseError`](../reference/components/can_raise_error.md) error components to an `anyhow`-backed backend — those are ordinary component entries, written without `open` because they are not dispatched per value type: + +```rust +use cgp::core::error::{ErrorRaiserComponent, ErrorTypeProviderComponent}; +use cgp_error_anyhow::{RaiseAnyhowError, UseAnyhowError}; + +delegate_components! { + <'s> App<'s> { + open {ValueDeserializerComponent}; + + @ValueDeserializerComponent.u64: + UseSerde, + @ValueDeserializerComponent.[ + Coord, + <'a> Cluster<'a>, + ]: + DeserializeRecordFields, + @ValueDeserializerComponent.<'a> &'a Coord: + DeserializeAndAllocate, + @ValueDeserializerComponent.<'a> Vec<&'a Coord>: + DeserializeExtend, + + ErrorTypeProviderComponent: + UseAnyhowError, + ErrorRaiserComponent: + RaiseAnyhowError, + } +} +``` + +With the context built around an arena, deserializing a JSON cluster allocates its coordinates into that arena, and the returned `Cluster` borrows from it: + +```rust +let serialized = r#" + { + "id": 8, + "coords": [ + { "x": 1, "y": 2, "z": 3 }, + { "x": 4, "y": 5, "z": 6 } + ] + } +"#; + +let arena = Arena::new(); +let app = App { arena: &arena }; + +let cluster: Cluster<'_> = app.deserialize_json_string(serialized).unwrap(); +``` + +The arena was never an argument to a deserialize function — Serde's `from_str` has no slot for one. It reached `DeserializeAndAllocate` through the context, which is how CGP supplies a capability to code nested arbitrarily deep without threading it explicitly, the [dependency-injection](../concepts/impl-side-dependencies.md) idea applied to deserialization. diff --git a/docs/examples/money-transfer-api.md b/docs/examples/money-transfer-api.md new file mode 100644 index 00000000..64991cde --- /dev/null +++ b/docs/examples/money-transfer-api.md @@ -0,0 +1,340 @@ +# Money-transfer API + +This example builds the backend for a small money-transfer web service — querying a user's balance and moving funds between accounts — as a set of composable API handlers that an HTTP server drives. It progresses from a single dispatched handler component, through reusable handler wrappers that add request decoding and authentication, to an in-memory context wired to all of it and finally served over HTTP. It is a template for any request/response service whose endpoints share cross-cutting concerns and whose backend should be swappable behind abstract types. + +The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: + +- abstract domain types — [`#[cgp_type]`](../reference/macros/cgp_type.md) and the [abstract-types concept](../concepts/abstract-types.md) +- an async, per-endpoint-dispatched component — [`#[cgp_component]`](../reference/macros/cgp_component.md), [`#[async_trait]`](../reference/macros/async_trait.md), [`#[derive_delegate]`](../reference/attributes/derive_delegate.md) +- handlers that wrap other handlers — [higher-order providers](../concepts/higher-order-providers.md) written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) +- backend providers reading context fields — [implicit field access](../concepts/implicit-arguments.md) via [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) +- raising status-coded errors through an application-specific error component — [modular error handling](../concepts/modular-error-handling.md) over [`HasErrorType`](../reference/components/has_error_type.md) +- per-endpoint wiring — [`delegate_components!`](../reference/macros/delegate_components.md) with [`UseDelegate`](../reference/providers/use_delegate.md) and a [`check_components!`](../reference/macros/check_components.md) assertion +- restoring a `Send` bound for the HTTP server — the [recovering `Send` bounds concept](../concepts/send-bounds.md) + +All snippets assume `use cgp::prelude::*;`. The service speaks in terms of a handful of domain types that are kept abstract so the same handlers work whatever concrete types a deployment chooses. + +## Abstract domain types + +The service never names a concrete user id, currency, or amount; it names abstract types a context supplies. Each is a one-line [abstract-type component](../concepts/abstract-types.md) defined with [`#[cgp_type]`](../reference/macros/cgp_type.md), carrying only the bound the rest of the code needs — here, that every domain value can be displayed in an error message: + +```rust +#[cgp_type] +pub trait HasUserIdType { + type UserId: Display; +} + +#[cgp_type] +pub trait HasQuantityType { + type Quantity: Display; +} + +#[cgp_type] +pub trait HasCurrencyType { + type Currency: Display; +} +``` + +Keeping these abstract is what lets one balance-query handler serve a context whose currency is a rich enum and another whose currency is a bare string, without rewriting the handler. A context binds each type once during wiring, shown later. + +## The dispatched handler component + +Every endpoint is one case of a single component that dispatches on a marker type naming the API. The consumer trait `CanHandleApi` takes the endpoint marker as a generic parameter and, for that endpoint, fixes a `Request` and `Response` type and an async method that turns one into the other: + +```rust +#[cgp_component(ApiHandler)] +#[async_trait] +#[derive_delegate(UseDelegate)] +pub trait CanHandleApi: HasErrorType { + type Request; + type Response; + + async fn handle_api( + &self, + _api: PhantomData, + request: Self::Request, + ) -> Result; +} + +pub struct TransferApi; +pub struct QueryBalanceApi; +``` + +The three attributes each pull their weight. [`#[cgp_component]`](../reference/macros/cgp_component.md) makes `ApiHandler` a wireable component so each endpoint can bind a different provider; [`#[async_trait]`](../reference/macros/async_trait.md) keeps the async method's declaration lint-clean; and [`#[derive_delegate(UseDelegate)]`](../reference/attributes/derive_delegate.md) generates a [`UseDelegate`](../reference/providers/use_delegate.md) provider that routes each call to a per-`Api` entry in an inner table. The markers `TransferApi` and `QueryBalanceApi` are empty structs used only as keys; `PhantomData` carries the choice at the type level so the right provider is selected with no runtime branch. + +## Endpoint handlers + +Each endpoint is a provider for `ApiHandler` that depends on business capabilities rather than on any concrete backend. The transfer endpoint, for instance, reads the logged-in sender and the transfer details from its request, then calls the `CanTransferMoney` capability — itself an abstract async component the context implements however it likes: + +```rust +#[cgp_component(MoneyTransferrer)] +#[async_trait] +pub trait CanTransferMoney: + HasUserIdType + HasCurrencyType + HasQuantityType + HasErrorType +{ + async fn transfer_money( + &self, + sender: &Self::UserId, + recipient: &Self::UserId, + currency: &Self::Currency, + quantity: &Self::Quantity, + ) -> Result<(), Self::Error>; +} + +#[cgp_impl(new HandleTransfer)] +impl ApiHandler +where + Self: CanTransferMoney + CanRaiseHttpError, + Request: HasLoggedInUser + HasTransferMoneyFields, +{ + type Request = Request; + type Response = (); + + async fn handle_api( + &self, + _api: PhantomData, + request: Request, + ) -> Result<(), Self::Error> { + let sender = request.logged_in_user().as_ref().ok_or_else(|| { + Self::raise_http_error(ErrUnauthorized, "you must first login".into()) + })?; + + self.transfer_money( + sender, + request.recipient(), + request.currency(), + request.quantity(), + ) + .await?; + + Ok(()) + } +} +``` + +The endpoint is generic over its request shape. `HandleTransfer` works for any `Request` type that exposes a logged-in user and the transfer fields through the [getter traits](../reference/macros/cgp_auto_getter.md) named in its `where` clause, so the same handler logic serves whatever request struct a deployment decodes from the wire. The `Self: ...` bounds are [impl-side dependencies](../concepts/impl-side-dependencies.md): they hold the context to providing money-transfer and HTTP-error capabilities without those leaking into the consumer trait. + +## Reusable handler wrappers + +Cross-cutting concerns are handlers that wrap another handler, which makes them [higher-order providers](../concepts/higher-order-providers.md). Each takes an inner handler as a type parameter, implements `ApiHandler` itself, and threads the call through — transforming the request or response on the way. Three small wrappers cover decoding, authentication, and JSON encoding. + +`HandleFromRequest` adapts the request type, letting an endpoint that wants a clean domain request sit behind a handler whose request is the raw type the HTTP layer produces: + +```rust +#[cgp_impl(new HandleFromRequest)] +impl ApiHandler +where + Self: HasErrorType, + InHandler: ApiHandler, + Request: Into, +{ + type Request = Request; + type Response = InHandler::Response; + + async fn handle_api( + &self, + api: PhantomData, + request: Self::Request, + ) -> Result { + InHandler::handle_api(self, api, request.into()).await + } +} +``` + +`UseBasicAuth` performs authentication before delegating, resolving a basic-auth header into a logged-in user and mutating the request in place: + +```rust +#[cgp_impl(new UseBasicAuth)] +impl ApiHandler +where + Self: CanQueryUserHashedPassword + CanCheckPassword, + InHandler: ApiHandler, + InHandler::Request: HasLoggedInUserMut + HasBasicAuthHeader, + Self::UserId: Clone, +{ + type Request = InHandler::Request; + type Response = InHandler::Response; + + async fn handle_api( + &self, + api: PhantomData, + mut request: Self::Request, + ) -> Result { + if request.logged_in_user().is_none() + && let Some((user_id, password)) = request.basic_auth_header() + { + if let Some(hashed) = self.query_user_hashed_password(user_id).await? + && Self::check_password(password, &hashed) + { + *request.logged_in_user() = Some(user_id.clone()); + } + } + + InHandler::handle_api(self, api, request).await + } +} +``` + +`ResponseToJson` adapts in the other direction, wrapping whatever the inner handler returns in an Axum `Json` envelope: + +```rust +#[cgp_impl(ResponseToJson)] +impl ApiHandler +where + Self: HasErrorType, + InHandler: ApiHandler, +{ + type Request = InHandler::Request; + type Response = Json; + + async fn handle_api( + &self, + api: PhantomData, + request: Self::Request, + ) -> Result { + let response = InHandler::handle_api(self, api, request).await?; + Ok(Json(response)) + } +} +``` + +Because each wrapper is itself an `ApiHandler`, they nest into a pipeline. Writing `HandleFromRequest>>>` reads outside-in as the stages a request passes through — decode the raw request, authenticate, run the endpoint, JSON-encode the response — with each layer adding exactly one concern and the endpoint at the center oblivious to all of them. + +## The backend behind the capabilities + +The business capabilities are satisfied by a provider that reads its data from context fields. `UseMockedApp` is an in-memory backend that implements `MoneyTransferrer` and the other capabilities by reaching into maps stored on the context, retrieved through [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) traits: + +```rust +#[cgp_auto_getter] +pub trait HasMockedUserBalances: HasUserIdType + HasCurrencyType + HasQuantityType { + fn user_balances( + &self, + ) -> &Arc>>; +} + +#[cgp_impl(UseMockedApp)] +impl MoneyTransferrer +where + Self: HasMockedUserBalances + + CanRaiseHttpError + + CanRaiseHttpError, + Self::Quantity: CheckedAdd + CheckedSub, + Self::UserId: Ord + Clone, + Self::Currency: Ord + Clone, +{ + async fn transfer_money( + &self, + sender: &Self::UserId, + recipient: &Self::UserId, + currency: &Self::Currency, + quantity: &Self::Quantity, + ) -> Result<(), Self::Error> { + let mut balances = self.user_balances().lock().await; + /* debit the sender, credit the recipient, raising on overflow or missing accounts */ + Ok(()) + } +} +``` + +A real deployment would swap this one provider for a database-backed one. Since `UseMockedApp` is selected per context in the wiring, replacing it with a `UsePostgres` provider that implements the same capabilities changes which backend runs without touching a single endpoint or wrapper. + +## Wiring a context + +A concrete context becomes the running application by binding every abstract type and component in one place. `MockApp` holds the in-memory state and wires it all together: the domain types resolve to concrete types via [`UseType`](../reference/providers/use_type.md), the business capabilities resolve to `UseMockedApp`, and each endpoint of `ApiHandler` resolves to its own wrapper pipeline through an inner [`UseDelegate`](../reference/providers/use_delegate.md) table keyed by the API marker: + +```rust +#[derive(HasField, Default)] +pub struct MockApp { + pub user_balances: Arc>>, + pub user_passwords: BTreeMap, +} + +delegate_components! { + MockApp { + ErrorTypeProviderComponent: + UseType, + [ + UserIdTypeProviderComponent, + PasswordTypeProviderComponent, + HashedPasswordTypeProviderComponent, + ]: + UseType, + QuantityTypeProviderComponent: + UseType, + CurrencyTypeProviderComponent: + UseType, + [ + PasswordCheckerComponent, + UserHashedPasswordQuerierComponent, + UserBalanceQuerierComponent, + MoneyTransferrerComponent, + ]: + UseMockedApp, + ApiHandlerComponent: + UseDelegate>>>, + TransferApi: + HandleFromRequest>>, + }>, + } +} +``` + +The two endpoints assemble different pipelines from the same parts. Both decode an Axum request and authenticate, but the balance query also JSON-encodes its response while the transfer returns nothing, so only the query wraps in `ResponseToJson`. The nested `UseDelegate` builds the per-`Api` lookup table inline, so a call to `handle_api` with the `TransferApi` marker resolves to the transfer pipeline and one with `QueryBalanceApi` to the balance pipeline. + +Because CGP wiring is [checked lazily](../concepts/check-traits.md), a companion [`check_components!`](../reference/macros/check_components.md) block proves at compile time that every endpoint is fully satisfied, listing the API markers to verify for the generic `ApiHandler` component: + +```rust +check_components! { + MockApp { + MoneyTransferrerComponent, + UserBalanceQuerierComponent, + ApiHandlerComponent: [ + QueryBalanceApi, + TransferApi, + ], + } +} +``` + +## Serving over HTTP + +Handing the handlers to an HTTP server needs one bound the component cannot provide: that each handler's future is `Send`. Axum runs on a multi-threaded, work-stealing runtime that may move a task between threads while it is suspended, so the futures it drives must be `Send` — but the `async fn` in `CanHandleApi` desugars to a bare `impl Future` with no such bound, and stable Rust has no way to require it generically. The fix is a plain trait whose method declares `+ Send` directly and which is implemented for the concrete context, where the compiler can verify the bound itself: + +```rust +pub trait CanHandleApiSend: + CanHandleApi + Send + Sync +{ + fn handle_api_send( + &self, + _api: PhantomData, + request: Self::Request, + ) -> impl Future> + Send; +} + +impl CanHandleApiSend for MockApp { + async fn handle_api_send( + &self, + api: PhantomData, + request: Self::Request, + ) -> Result { + self.handle_api(api, request).await + } +} + +impl CanHandleApiSend for MockApp { + async fn handle_api_send( + &self, + api: PhantomData, + request: Self::Request, + ) -> Result { + self.handle_api(api, request).await + } +} +``` + +Each impl just forwards to `handle_api`, but at a concrete context and API the awaited future is a concrete type whose `Send`-ness the compiler can confirm — which is exactly why the impls cannot be folded into one generic blanket impl. The full reasoning, and why this is a stand-in for the Return Type Notation stable Rust lacks, is in [recovering `Send` bounds](../concepts/send-bounds.md). With `CanHandleApiSend` in hand, an Axum route handler can bound `App: CanHandleApiSend` and spawn the handler safely, completing the path from a request on the wire to one of the wired endpoint pipelines. diff --git a/docs/examples/profile-picture.md b/docs/examples/profile-picture.md new file mode 100644 index 00000000..93484ba1 --- /dev/null +++ b/docs/examples/profile-picture.md @@ -0,0 +1,267 @@ +# Profile picture lookup + +This example fetches a user's profile picture — a real-world operation that queries a database for a user record and, if one is set, downloads and decodes the image from object storage. It progresses from two field-driven async functions to a fully wired application that swaps its database engine and its storage backend per context without touching the orchestration logic. It is a template for any use case where one business operation composes several infrastructure steps, each of which may have more than one implementation. + +The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: + +- context-generic functions — [`#[cgp_fn]`](../reference/macros/cgp_fn.md) with [implicit arguments](../concepts/implicit-arguments.md) +- async methods in traits — [`#[async_trait]`](../reference/macros/async_trait.md) +- composing capabilities — [`#[uses]`](../reference/attributes/uses.md) +- field access on contexts — [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) +- impl-only generic parameters — [`#[impl_generics]`](../reference/macros/cgp_fn.md) +- components and named providers — [`#[cgp_component]`](../reference/macros/cgp_component.md), [`#[cgp_impl]`](../reference/macros/cgp_impl.md), and the [consumer/provider trait duality](../concepts/consumer-and-provider-traits.md) +- wiring a context to providers — [`delegate_components!`](../reference/macros/delegate_components.md) + +All snippets assume `use cgp::prelude::*;`. The operation works over two domain types — a `UserId` newtype and a `User` row that may carry the storage key of a profile picture: + +```rust +pub struct UserId(pub u64); + +#[derive(sqlx::FromRow)] +pub struct User { + pub name: String, + pub email: String, + pub profile_picture_object_id: Option, +} +``` + +## Field-driven steps + +The two infrastructure steps are each a function that reads its connection from the context's fields rather than from explicit arguments. Marking a function with [`#[cgp_fn]`](../reference/macros/cgp_fn.md) and tagging a parameter [`#[implicit]`](../reference/attributes/implicit.md) turns it into a method on any context that carries a matching field; the remaining parameters stay as ordinary arguments the caller supplies. Both functions are async, so each also carries [`#[async_trait]`](../reference/macros/async_trait.md), which keeps the generated trait's async method lint-clean: + +```rust +#[cgp_fn] +#[async_trait] +pub async fn get_user( + &self, + #[implicit] database: &PgPool, + user_id: &UserId, +) -> anyhow::Result { + let user = + sqlx::query_as("SELECT name, email, profile_picture_object_id FROM users WHERE id = $1") + .bind(user_id.0 as i64) + .fetch_one(database) + .await?; + + Ok(user) +} + +#[cgp_fn] +#[async_trait] +pub async fn fetch_storage_object( + &self, + #[implicit] storage_client: &Client, + #[implicit] bucket_id: &str, + object_id: &str, +) -> anyhow::Result> { + let output = storage_client + .get_object() + .bucket(bucket_id) + .key(object_id) + .send() + .await?; + + let data = output.body.collect().await?.into_bytes().to_vec(); + Ok(data) +} +``` + +`get_user` reads one implicit field, `database`; `fetch_storage_object` reads two, `storage_client` and `bucket_id`, in addition to its explicit `object_id`. A context becomes eligible for each function purely by deriving [`HasField`](../reference/derives/derive_has_field.md) for fields whose names and types match the implicit arguments — no per-context implementation is written: + +```rust +#[derive(HasField)] +pub struct App { + pub database: PgPool, + pub storage_client: Client, + pub bucket_id: String, +} + +#[derive(HasField)] +pub struct MinimalApp { + pub database: PgPool, +} +``` + +`App` has all three fields, so it can call both functions; `MinimalApp` has only `database`, so it can call `get_user` but the compiler refuses any call to `fetch_storage_object` on it. + +## Composing the steps + +The orchestration is itself a `#[cgp_fn]` that calls the two steps as methods on `self`. Because those calls are CGP capabilities rather than inherent methods, the function declares them with [`#[uses]`](../reference/attributes/uses.md), which adds each as a hidden bound on the context instead of a visible parameter: + +```rust +#[cgp_fn] +#[async_trait] +#[uses(GetUser, FetchStorageObject)] +pub async fn get_user_profile_picture( + &self, + user_id: &UserId, +) -> anyhow::Result> { + let user = self.get_user(user_id).await?; + + if let Some(object_id) = user.profile_picture_object_id { + let data = self.fetch_storage_object(&object_id).await?; + let image = image::load_from_memory(&data)?.to_rgb8(); + + Ok(Some(image)) + } else { + Ok(None) + } +} +``` + +The names in `#[uses(GetUser, FetchStorageObject)]` are the traits `#[cgp_fn]` derives from the two step functions — a function `foo_bar` generates a trait `FooBar`. The body reads as plain method calls; `#[uses]` threads the trait bounds behind the scenes so that only a context carrying every required field gains `get_user_profile_picture`. `App` does; `MinimalApp` does not, and the gap is a compile error rather than a runtime failure. + +## Varying the database engine + +`get_user` above hardcodes `&PgPool`, which ties it to PostgreSQL. To let one implementation serve several database engines, introduce a type parameter that lives on the impl alone — never on the generated trait or its callers — with [`#[impl_generics]`](../reference/macros/cgp_fn.md). The implicit `database` field becomes `&Pool`, and the `where` clause carries the engine-specific bounds: + +```rust +#[cgp_fn] +#[async_trait] +#[impl_generics(Db: Database)] +pub async fn get_user( + &self, + #[implicit] database: &Pool, + user_id: &UserId, +) -> anyhow::Result +where + i64: sqlx::Type, + for<'a> User: sqlx::FromRow<'a, Db::Row>, + for<'a> i64: sqlx::Encode<'a, Db>, + for<'a> ::Arguments<'a>: sqlx::IntoArguments<'a, Db>, + for<'a> &'a mut ::Connection: sqlx::Executor<'a, Database = Db>, +{ + let user = + sqlx::query_as("SELECT name, email, profile_picture_object_id FROM users WHERE id = $1") + .bind(user_id.0 as i64) + .fetch_one(database) + .await?; + + Ok(user) +} +``` + +Each context supplies `Db` implicitly through the type of its `database` field. An `App` carrying a `PgPool` resolves `Db = Postgres`, while an embedded context carrying a `SqlitePool` resolves `Db = Sqlite` — `SqlitePool` is `Pool` under the hood — and both satisfy the sqlx bounds independently: + +```rust +#[derive(HasField)] +pub struct EmbeddedApp { + pub database: SqlitePool, + pub storage_client: Client, + pub bucket_id: String, +} +``` + +`get_user_profile_picture` is unchanged: it depends on the `GetUser` capability, not on which engine satisfies it, so the same orchestration now runs on PostgreSQL and SQLite alike. + +## Varying the storage backend + +A single `#[cgp_fn]` defines exactly one implementation, so it cannot offer the storage fetch in more than one flavor. When a step needs interchangeable implementations — say Amazon S3 in one deployment and Google Cloud Storage in another — promote it to a [component](../concepts/consumer-and-provider-traits.md) with [`#[cgp_component]`](../reference/macros/cgp_component.md). The annotated `CanFetchStorageObject` trait is the *consumer trait* callers use; the `StorageObjectFetcher` argument names the generated *provider trait* that implementations target: + +```rust +#[async_trait] +#[cgp_component(StorageObjectFetcher)] +pub trait CanFetchStorageObject { + async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; +} +``` + +Each backend is a *named provider* written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md). Unlike a blanket `impl`, named providers may overlap freely, and `#[implicit]` works inside them exactly as in `#[cgp_fn]`, so each provider reads whatever connection field its backend needs: + +```rust +#[cgp_impl(new FetchS3Object)] +impl StorageObjectFetcher { + async fn fetch_storage_object( + &self, + #[implicit] storage_client: &Client, + #[implicit] bucket_id: &str, + object_id: &str, + ) -> anyhow::Result> { + let output = storage_client + .get_object() + .bucket(bucket_id) + .key(object_id) + .send() + .await?; + + let data = output.body.collect().await?.into_bytes().to_vec(); + Ok(data) + } +} + +#[cgp_impl(new FetchGCloudObject)] +impl StorageObjectFetcher { + async fn fetch_storage_object( + &self, + #[implicit] storage_client: &Storage, + #[implicit] bucket_id: &str, + object_id: &str, + ) -> anyhow::Result> { + let mut reader = storage_client + .read_object(bucket_id, object_id) + .send() + .await?; + + let mut contents = Vec::new(); + while let Some(chunk) = reader.next().await.transpose()? { + contents.extend_from_slice(&chunk); + } + + Ok(contents) + } +} +``` + +`FetchS3Object` reads an `aws_sdk_s3::Client` from its `storage_client` field; `FetchGCloudObject` reads a `google_cloud_storage::client::Storage` from the same field name. The `new` keyword in each attribute defines the provider struct in place. + +The orchestration now imports the storage step by its *consumer* trait name, so it depends on the capability rather than on any one backend: + +```rust +#[cgp_fn] +#[async_trait] +#[uses(GetUser, CanFetchStorageObject)] +pub async fn get_user_profile_picture( + &self, + user_id: &UserId, +) -> anyhow::Result> { + let user = self.get_user(user_id).await?; + + if let Some(object_id) = user.profile_picture_object_id { + let data = self.fetch_storage_object(&object_id).await?; + let image = image::load_from_memory(&data)?.to_rgb8(); + + Ok(Some(image)) + } else { + Ok(None) + } +} +``` + +## Wiring contexts to backends + +Defining a provider does not attach it to any context; a context chooses its provider by wiring with [`delegate_components!`](../reference/macros/delegate_components.md). Each entry maps the component — keyed by its generated `…Component` name — to the provider that implements it for that context. An `App` carrying an S3 client wires to `FetchS3Object`, while a `GCloudApp` carrying a GCloud client wires to `FetchGCloudObject`: + +```rust +#[derive(HasField)] +pub struct GCloudApp { + pub database: PgPool, + pub storage_client: Storage, + pub bucket_id: String, +} + +delegate_components! { + App { + StorageObjectFetcherComponent: FetchS3Object, + } +} + +delegate_components! { + GCloudApp { + StorageObjectFetcherComponent: FetchGCloudObject, + } +} +``` + +Both contexts remain plain data structs with `#[derive(HasField)]`; all backend selection happens in the wiring block, resolved at compile time with no runtime dispatch. The S3 binary contains only the S3 code path and the GCloud binary only the GCloud one. Adding a third backend — Azure Blob Storage, say — is a new `#[cgp_impl(new FetchAzureObject)]` provider and one more `delegate_components!` entry; `get_user`, `get_user_profile_picture`, and every existing context stay untouched. + + diff --git a/docs/examples/shell-scripting-dsl.md b/docs/examples/shell-scripting-dsl.md new file mode 100644 index 00000000..c87d7efa --- /dev/null +++ b/docs/examples/shell-scripting-dsl.md @@ -0,0 +1,259 @@ +# Shell-scripting DSL + +This example builds a type-level shell-scripting DSL whose programs are ordinary Rust *types*, interpreted at compile time by whichever context runs them. It progresses from a fixed CLI program, through the component that interprets a program and the namespace that wires the interpreters, to a custom context that supplies runtime values and a language extension that adds new syntax — and is a template for any embedded DSL where the program's *syntax* should be decoupled from its *semantics* so that each can vary independently. The general pattern it instantiates is described in [type-level DSLs](../concepts/type-level-dsls.md). + +The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: + +- the computation component the DSL is built on — [`Handler` / `CanHandle`](../reference/components/handler.md) in the [handler family](../concepts/handlers.md) +- writing a provider that interprets one syntax — [`#[cgp_impl]`](../reference/macros/cgp_impl.md) with a pattern-matched `Code` parameter +- raising source errors into the context's abstract error — [`CanRaiseError`](../reference/components/can_raise_error.md) +- dispatching on the program type — [`UseDelegate`](../reference/providers/use_delegate.md) and [dispatching](../concepts/dispatching.md) +- bundling and inheriting wiring — [namespaces](../concepts/namespaces.md) with [`cgp_namespace!`](../reference/macros/cgp_namespace.md) +- joining a namespace from a context — [`delegate_components!`](../reference/macros/delegate_components.md) and [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) +- composing handlers into a pipeline provider — [`PipeHandlers`](../reference/providers/handler_combinators.md) + +All snippets assume `use cgp::prelude::*;`, and the handler items come from `cgp::extra::handler`. + +## A program is a type + +The smallest DSL program is a type, not a value. The `hypershell!` macro provides shell-like surface syntax — a pipe operator and bracketed argument lists — that desugars into a chain of phantom-typed syntax markers. This program runs `echo hello world!` and streams the result to standard output: + +```rust +pub type Program = hypershell! { + SimpleExec< + StaticArg<"echo">, + WithStaticArgs["hello", "world!"], + > + | StreamToStdout +}; +``` + +The macro is sugar only: the `|` desugars into a `Pipe` of handler syntaxes, the bracketed `[...]` shorthand into a `` type-level list, and each string literal into a `Symbol!` type-level string, since a program lives entirely at the type level where a `&str` value cannot appear. The program above expands to exactly this plain type: + +```rust +pub type Program = Pipe, + WithStaticArgs, + >, + StreamToStdout, +]>; +``` + +The macro is entirely optional — writing this type by hand is equivalent. The program carries no data; it is a description of *what* to do, with the *how* supplied separately by the context that runs it. + +A context runs a program by calling `handle`, passing the program as a `PhantomData` type argument and an input value. `HypershellCli` is a predefined empty context for CLI-only programs, so it can be constructed directly: + +```rust +#[tokio::main] +async fn main() -> Result<(), Error> { + HypershellCli + .handle(PhantomData::, Vec::new()) + .await?; + + Ok(()) +} +``` + +The `Vec::new()` is the program's standard input, which `echo` ignores. Running it prints `hello world!`. + +## The component that interprets a program + +Every step of a program is interpreted by one component: the [`Handler`](../reference/components/handler.md) component, the async, fallible corner of the [handler family](../concepts/handlers.md). Its consumer trait `CanHandle` is what `handle` above resolves to: + +```rust +#[async_trait] +#[cgp_component(Handler)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanHandle: HasErrorType { + type Output; + + async fn handle( + &self, + _tag: PhantomData, + input: Input, + ) -> Result; +} +``` + +The `Code` parameter is the program fragment being interpreted, `Input` is the data flowing in (a process's standard input, an HTTP body), and the associated `Output` is what the fragment produces. The phantom `PhantomData` is how a *type* is passed where a value is expected: one context hosts many handlers, each keyed by a distinct `Code` tag, and the wiring dispatches on that tag. Because the program is just a `Code` type, interpreting it is a matter of resolving `CanHandle` for that type. + +## Abstract syntax, decoupled from its meaning + +A syntax marker like `SimpleExec` is nothing but a phantom struct — it has no interpreter attached: + +```rust +pub struct SimpleExec(pub PhantomData<(Path, Args)>); +``` + +This is the whole point of the design: how a program is *written* is completely decoupled from how it is *interpreted*. `SimpleExec` names a piece of syntax; what it *does* is decided entirely by which provider a context wires for the `Code = SimpleExec<…>` case. Swapping that provider — to run commands on a different async runtime, say — changes the program's meaning without touching a single program that uses `SimpleExec`. The syntax is the DSL's abstract grammar; the providers are its interpreters. + +## Interpreting one syntax with a provider + +An interpreter is a `Handler` provider that pattern-matches on a single syntax type through its `Code` parameter. Written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), it reads like an ordinary method implementation while `Context` stays generic. This provider interprets a `Checksum` syntax by consuming a stream of bytes and producing their digest: + +```rust +pub struct Checksum(pub PhantomData); + +#[cgp_impl(new HandleStreamChecksum)] +impl Handler, Input> for Context +where + Context: CanRaiseError, + Input: Unpin + TryStream, + Hasher: Digest, + Input::Ok: AsRef<[u8]>, +{ + type Output = GenericArray; + + async fn handle( + _context: &Context, + _tag: PhantomData>, + mut input: Input, + ) -> Result { + let mut hasher = Hasher::new(); + + while let Some(bytes) = input.try_next().await.map_err(Context::raise_error)? { + hasher.update(bytes); + } + + Ok(hasher.finalize()) + } +} +``` + +Two things make this provider reusable across contexts. It matches `Handler, …>` for *any* `Hasher` that implements `Digest`, so the one provider covers every hash algorithm. And it never names a concrete error type: the `CanRaiseError` bound lets it convert a stream error into the context's own abstract error via [`CanRaiseError`](../reference/components/can_raise_error.md), so a context using `anyhow`, `eyre`, or a bespoke error type all reuse the same code. The `where` clause states everything the provider needs from the context as an [impl-side dependency](../concepts/impl-side-dependencies.md); a context that cannot meet it simply cannot wire this provider. + +## Dispatching on the program + +A single provider only interprets one syntax, so something must route each `Code` to its interpreter. That is the job of [`UseDelegate`](../reference/providers/use_delegate.md): because the `Handler` component is declared `#[derive_delegate(UseDelegate)]`, the `HandlerComponent` lookup can be keyed on the `Code` type through an inner type-level table, as described in [dispatching](../concepts/dispatching.md). A wiring entry therefore reaches *past* the component name to the `Code` it handles — written as a dotted path under `HandlerComponent`, with the syntax's generic parameters bound by a leading `<…>`: + +```rust +@HandlerComponent. SimpleExec: + HandleSimpleExec, +@HandlerComponent. StreamingExec: + HandleStreamingExec, +``` + +The keys are *types*, not values, which is what lets a lookup capture generic parameters like `Path` and `Args` and still match every use of `SimpleExec`. Resolving `CanHandle, …>` for a context then walks one extra step through the `Code` table: `HandlerComponent` dispatches on `SimpleExec<…>` to `HandleSimpleExec`. + +## Bundling the wiring into a namespace + +A real DSL has many syntaxes wired to many providers, and several contexts that should share that wiring. A [namespace](../concepts/namespaces.md) captures the whole table once and lets contexts inherit it. `HypershellNamespace` is defined with [`cgp_namespace!`](../reference/macros/cgp_namespace.md), inheriting CGP's built-in `DefaultNamespace` and adding entries that map groups of syntaxes to their interpreters under the `HandlerComponent` path: + +```rust +cgp_namespace! { + new HypershellNamespace: DefaultNamespace { + @cgp.core.error.ErrorTypeProviderComponent: + UseAnyhowError, + + @cgp.extra.handler.HandlerComponent.[ + SimpleExec, + StreamingExec, + StreamToStdout, + ]: + HypershellTokioProvider, + + @cgp.extra.handler.HandlerComponent.[ + SimpleHttpRequest, + StreamingHttpRequest, + ]: + HypershellReqwestProvider, + } +} +``` + +Each entry is keyed by a *path* — a dotted sequence written with the `@` sigil, like `@cgp.extra.handler.HandlerComponent` — rather than a bare component name, which is what lets a namespace inherit another's entries and lets a context override a single one without disturbing the rest. The array form groups several `Code` keys that share one provider. Because the wiring is expressed through trait resolution, all of this dispatch is resolved at compile time with no runtime indirection. + +## Running on a custom context + +A program that reads runtime values — a URL, a name — needs a context that carries them. Deriving [`HasField`](../reference/derives/derive_has_field.md) exposes a struct's fields so that syntaxes like `FieldArg<"name">` can read them, and joining `HypershellNamespace` inside [`delegate_components!`](../reference/macros/delegate_components.md) inherits every interpreter at once: + +```rust +pub type Program = hypershell! { + SimpleExec< + StaticArg<"echo">, + WithArgs[ + StaticArg<"Hello,">, + FieldArg<"name">, + ], + > + | StreamToStdout +}; + +#[derive(HasField)] +pub struct MyApp { + pub name: String, +} + +delegate_components! { + MyApp { + namespace HypershellNamespace; + } +} +``` + +The `namespace HypershellNamespace;` header makes every lookup `MyApp` cannot resolve directly fall back to the namespace's entries, so `MyApp` interprets the full DSL without restating any wiring. `WithArgs` mixes static arguments with `FieldArg<"name">`, which resolves through `HasField` to the context's `name` field. Constructing `MyApp { name: "Alice".into() }` and calling `handle` prints `Hello, Alice`. The predefined `HypershellCli` is wired the same way with an empty struct — it joins `HypershellNamespace` and adds nothing. + +## Extending the DSL + +A language extension adds new syntax and its interpreters without forking the core. Define the new syntax markers, write providers for them, and publish a namespace that inherits `HypershellNamespace` and layers the new entries on top. Here a `Checksum` syntax (interpreted by `HandleStreamChecksum` from earlier) and a `BytesToHex` syntax join the language: + +```rust +pub struct BytesToHex; + +#[cgp_impl(new HandleBytesToHex)] +impl Handler for Context +where + Context: HasErrorType, + Input: AsRef<[u8]>, +{ + type Output = String; + + async fn handle( + _context: &Context, + _tag: PhantomData, + input: Input, + ) -> Result { + Ok(hex::encode(input)) + } +} + +cgp_namespace! { + new HypershellChecksumNamespace: HypershellNamespace { + @cgp.extra.handler.HandlerComponent. Checksum: + PipeHandlers, + + @cgp.extra.handler.HandlerComponent.BytesToHex: + HandleBytesToHex, + } +} +``` + +`HandleBytesToHex` interprets *any* `Code` whose `Input` is byte-like — it does not inspect the tag — showing that a provider can be as generic as its logic allows. The `Checksum` entry wires not a single provider but a [`PipeHandlers`](../reference/providers/handler_combinators.md) composition: `HandleToFuturesStream` adapts the incoming stream into the `TryStream` that `HandleStreamChecksum` expects, and the two run as one handler. `HypershellChecksumNamespace` inherits everything `HypershellNamespace` resolves and adds the two new keys, so a context joining it speaks the extended language: + +```rust +pub type Program = hypershell! { + StreamingHttpRequest, WithHeaders[]> + | Checksum + | BytesToHex + | StreamToStdout +}; + +#[derive(HasField)] +pub struct MyApp { + pub http_client: Client, + pub url: String, +} + +delegate_components! { + MyApp { + namespace HypershellChecksumNamespace; + } +} +``` + +The program fetches a URL, hashes the streamed response natively, hex-encodes the digest, and prints it — and the only change from joining the core namespace is the namespace name. Because a namespace is just one more entry in an inheritance chain, an extension composes onto the language the same way a context composes onto a namespace, with the whole resolution settled at compile time. diff --git a/docs/examples/social-media-app.md b/docs/examples/social-media-app.md new file mode 100644 index 00000000..e69777a2 --- /dev/null +++ b/docs/examples/social-media-app.md @@ -0,0 +1,388 @@ +# Social media app + +This example builds the CRUD backend for a small social media service — managing users and posts — and follows it as the wiring grows from a handful of components into something a real application would have. It progresses from one coarse manager trait per domain, through fine-grained per-operation traits and a higher-order provider that adds input filtering, to provider bundles and finally namespace-grouped wiring that keeps the top-level configuration short even as the component count climbs. It is a template for any application whose component count grows past the point where a flat delegation table stays readable. + +The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: + +- consumer/provider trait pairs — [`#[cgp_component]`](../reference/macros/cgp_component.md) and [consumer and provider traits](../concepts/consumer-and-provider-traits.md) +- providers that read context fields as method arguments — [`#[cgp_impl]`](../reference/macros/cgp_impl.md) with [`#[implicit]`](../reference/attributes/implicit.md) arguments backed by [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) +- importing a capability a provider depends on — [`#[uses]`](../reference/attributes/uses.md), an [impl-side dependency](../concepts/impl-side-dependencies.md) +- a provider that wraps another provider — [higher-order providers](../concepts/higher-order-providers.md) and [`#[use_provider]`](../reference/attributes/use_provider.md) +- wiring a context and bundling providers into reusable groups — [`delegate_components!`](../reference/macros/delegate_components.md) +- grouping component keys so a context inherits a whole bundle at once — [namespaces](../concepts/namespaces.md), the [`#[prefix(...)]`](../reference/macros/cgp_component.md) attribute, and [`cgp_namespace!`](../reference/macros/cgp_namespace.md) +- checking that a wiring is complete — [`check_components!`](../reference/macros/check_components.md) + +All snippets assume `use cgp::prelude::*;` and share a small set of domain types — the entities the service manipulates and the database handle the providers read: + +```rust +pub struct Email(pub String); + +pub struct User; +pub struct UserData; +pub struct UserId; + +pub struct Post; +pub struct PostId; + +pub enum Error { + InvalidUsername, + InvalidMessage, +} + +pub struct PostgresDb; + +#[derive(PartialOrd, PartialEq)] +pub struct Probability(pub f64); + +impl Probability { + pub const fn new(value: f64) -> Self { + Self(value) + } +} +``` + +## One manager per domain + +The first cut gives each domain a single manager trait that exposes all of its operations. A `UserManager` covers the user lifecycle and a `PostManager` the post lifecycle: + +```rust +#[cgp_component(UserManager)] +pub trait CanManageUser { + fn create_user(&self, username: &str, email: &Email) -> Result; + + fn get_user(&self, user_id: &UserId) -> Result; + + fn update_user_data(&self, user_id: &UserId, user_data: &UserData) -> Result<(), Error>; +} + +#[cgp_component(PostManager)] +pub trait CanManagePost { + fn create_post(&self, title: &str, content: &str) -> Result; + + fn get_post(&self, post_id: &PostId) -> Result; + + fn update_post(&self, post_id: &PostId, content: &str) -> Result<(), Error>; + + fn delete_post(&self, post_id: &PostId) -> Result<(), Error>; +} +``` + +Each trait is an ordinary trait turned into a CGP component by [`#[cgp_component]`](../reference/macros/cgp_component.md), which names the provider trait (`UserManager`, `PostManager`) that implementers write against. The service also needs two content-safety checks — rejecting banned usernames and spam posts — so those become components of their own, each returning a `Probability` the managers can threshold: + +```rust +#[cgp_component(UsernameCensor)] +pub trait CanCensorUsername { + fn username_is_censored(&self, username: &str) -> Probability; +} + +#[cgp_component(SpamMessageDetector)] +pub trait CanDetectSpamMessage { + fn message_is_spam(&self, message: &str) -> Probability; +} +``` + +The production manager talks to PostgreSQL. Its provider reads the database handle straight out of the context as an [`#[implicit]`](../reference/attributes/implicit.md) argument — CGP extracts the `database` field from the context rather than threading it through every call site — and pulls in `CanCensorUsername` with [`#[uses]`](../reference/attributes/uses.md) so `create_user` can reject censored names before writing: + +```rust +#[cgp_impl(new PostgresUserManager)] +#[uses(CanCensorUsername)] +impl UserManager { + fn create_user( + &self, + #[implicit] database: &PostgresDb, + username: &str, + email: &Email, + ) -> Result { + if self.username_is_censored(username) > Probability::new(0.8) { + return Err(Error::InvalidUsername); + } + + // ... insert the user with `database` + } + + fn get_user(&self, #[implicit] database: &PostgresDb, user_id: &UserId) -> Result { + // ... + } + + fn update_user_data( + &self, + #[implicit] database: &PostgresDb, + user_id: &UserId, + user_data: &UserData, + ) -> Result<(), Error> { + // ... + } +} +``` + +`PostgresUserManager` is a provider written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), so its body reads like methods on the context while staying generic over any context that supplies a `database` field and a `CanCensorUsername` implementation. The `PostManager` provider follows the same shape, thresholding `CanDetectSpamMessage` inside `create_post`. The content checks themselves get cheap stand-in providers for now — `DummyUserCensor` and `DummySpamMessageDetector` — that always pass. + +A concrete context ties it together. `ProductionApp` holds the database handle and names a provider for every component in one [`delegate_components!`](../reference/macros/delegate_components.md) table: + +```rust +#[derive(HasField)] +pub struct ProductionApp { + pub database: PostgresDb, +} + +delegate_components! { + ProductionApp { + UserManagerComponent: PostgresUserManager, + PostManagerComponent: PostgresPostManager, + UsernameCensorComponent: DummyUserCensor, + SpamMessageDetectorComponent: DummySpamMessageDetector, + } +} +``` + +The [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) is what makes the `#[implicit] database` arguments resolve, by exposing the `database` field for [`HasField`](../reference/traits/has_field.md) lookup. This wiring is compact, but it hides a strain that grows with the application: `PostgresUserManager` depends on `CanCensorUsername` even though only `create_user` uses it, so `get_user` and `update_user_data` carry a dependency they never touch. As managers accumulate methods, these spurious dependencies pile up, and there is no way to grant `create_user` more capabilities without granting them to the whole manager. + +## One trait per operation + +Breaking each manager into a trait per operation lets every provider declare exactly the dependencies its one method needs. The user manager becomes three fine-grained components: + +```rust +#[cgp_component(UserCreator)] +pub trait CanCreateUser { + fn create_user(&self, username: &str, email: &Email) -> Result; +} + +#[cgp_component(UserGetter)] +pub trait CanGetUser { + fn get_user(&self, user_id: &UserId) -> Result; +} + +#[cgp_component(UserUpdater)] +pub trait CanUpdateUser { + fn update_user_data(&self, user_id: &UserId, user_data: &UserData) -> Result<(), Error>; +} +``` + +The post manager splits the same way into `PostCreator`, `PostGetter`, `PostUpdater`, and `PostDeleter`. Each operation now has its own provider, and the providers that do not need a content check no longer mention one. `GetUserWithPostgres` reads only the database; `CreateUserWithPostgres` adds nothing else either, because — as the next step shows — the censoring will move out of it entirely: + +```rust +#[cgp_impl(new CreateUserWithPostgres)] +impl UserCreator { + fn create_user( + &self, + #[implicit] database: &PostgresDb, + username: &str, + email: &Email, + ) -> Result { + // ... + } +} + +#[cgp_impl(new GetUserWithPostgres)] +impl UserGetter { + fn get_user(&self, #[implicit] database: &PostgresDb, user_id: &UserId) -> Result { + // ... + } +} +``` + +Splitting the traits also makes capability isolation possible: because deleting a post is its own `PostDeleter` component, code that should only read and write posts can be given the getter and updater without ever receiving the destructive `delete_post`. The price is that there are now seven CRUD components plus two content-safety ones to wire, where before there were four. + +## Lifting the filter into a higher-order provider + +With creation isolated in its own trait, the username check no longer belongs inside the database provider — it can become a separate provider that wraps any user creator. `FilterCensoredUsername` is a [higher-order provider](../concepts/higher-order-providers.md): it takes an inner `UserCreator` as a type parameter, runs the censor check, and forwards to the inner provider only if the name is allowed: + +```rust +#[cgp_impl(new FilterCensoredUsername)] +#[uses(CanCensorUsername)] +#[use_provider(InnerCreator: UserCreator)] +impl UserCreator { + fn create_user(&self, username: &str, email: &Email) -> Result { + if self.username_is_censored(username) > Probability::new(0.8) { + return Err(Error::InvalidUsername); + } + + InnerCreator::create_user(self, username, email) + } +} +``` + +The [`#[use_provider]`](../reference/attributes/use_provider.md) attribute is what keeps the inner type readable: `InnerCreator: UserCreator` declares that `InnerCreator` must itself be a user-creation provider for this context, and `InnerCreator::create_user(self, …)` dispatches through it. `FilterCensoredUsername` knows nothing about databases, and `CreateUserWithPostgres` now knows nothing about censoring — the two compose only when wired together as `FilterCensoredUsername`. Because the wrapper is generic, the same filter applies to any creator: a SQLite-backed `CreateUserWithSqlite`, were one added, would gain censoring through `FilterCensoredUsername` with no new code. The post side gets the matching `FilterSpamMessage` wrapper around any post creator. + +## Grouping providers into bundles + +A [`delegate_components!`](../reference/macros/delegate_components.md) table with `new` defines a standalone provider — a bundle — whose only job is to hold a sub-table other contexts can reuse as a unit. Grouping the user providers, the post providers, and the AI-backed content filters each into their own bundle keeps related wiring together: + +```rust +delegate_components! { + new PostgresUserComponents { + UserCreatorComponent: + FilterCensoredUsername, + UserGetterComponent: + GetUserWithPostgres, + UserUpdaterComponent: + UpdateUserWithPostgres, + } +} + +delegate_components! { + new PostgresPostComponents { + PostCreatorComponent: + FilterSpamMessage, + PostGetterComponent: + GetPostWithPostgres, + PostUpdaterComponent: + UpdatePostWithPostgres, + PostDeleterComponent: + DeletePostWithPostgres, + } +} + +delegate_components! { + new AiContentFilterComponents { + UsernameCensorComponent: + AiUserCensor, + SpamMessageDetectorComponent: + AiSpamMessageDetector, + } +} +``` + +The top-level context then forwards each component to the bundle that owns it. Using the array-key form of `delegate_components!`, several keys can share one bundle in a single entry: + +```rust +delegate_components! { + ProductionApp { + [ + UserCreatorComponent, + UserGetterComponent, + UserUpdaterComponent, + ]: + PostgresUserComponents, + [ + PostCreatorComponent, + PostGetterComponent, + PostUpdaterComponent, + PostDeleterComponent, + ]: + PostgresPostComponents, + [ + UsernameCensorComponent, + SpamMessageDetectorComponent, + ]: + AiContentFilterComponents, + } +} +``` + +The bundles read cleanly on their own, but the top-level table still has to spell out every component name to route it to a bundle. The grouping exists in the bundle definitions, yet the context cannot refer to "all the user components" as one thing — it must list them. + +## Grouping component keys with namespaces + +A [namespace](../concepts/namespaces.md) gives a group of components a shared key, so a context can route the whole group with one entry instead of listing each name. A component joins a namespace under a dotted path with the [`#[prefix(...)]`](../reference/macros/cgp_component.md) attribute on its trait; here the three user components all register under `@app.core.user` in the built-in `DefaultNamespace`: + +```rust +#[cgp_component(UserCreator)] +#[prefix(@app.core.user in DefaultNamespace)] +pub trait CanCreateUser { + fn create_user(&self, username: &str, email: &Email) -> Result; +} + +#[cgp_component(UserGetter)] +#[prefix(@app.core.user in DefaultNamespace)] +pub trait CanGetUser { + fn get_user(&self, user_id: &UserId) -> Result; +} + +#[cgp_component(UserUpdater)] +#[prefix(@app.core.user in DefaultNamespace)] +pub trait CanUpdateUser { + fn update_user_data(&self, user_id: &UserId, user_data: &UserData) -> Result<(), Error>; +} +``` + +The post components register under `@app.core.post` the same way, and the two content-safety components under `@app.extra.content_filter`. A context opts into the namespace with a `namespace` header line in its delegation table, after which a path key stands in for every component registered under it: + +```rust +delegate_components! { + ProductionApp { + namespace DefaultNamespace; + + @app.core.user: PostgresUserComponents, + @app.core.post: PostgresPostComponents, + @app.extra.content_filter: AiContentFilterComponents, + } +} +``` + +The `namespace DefaultNamespace;` line tells `ProductionApp` to resolve lookups through the namespace, and `@app.core.user: PostgresUserComponents` forwards every component sitting under that path to the bundle in one entry. The top-level table now has three lines regardless of how many operations the user and post domains grow to, because adding a method means adding a `#[prefix]`-tagged component to an existing path, not a new line at the top level. Listing the bundled components in a [`check_components!`](../reference/macros/check_components.md) block confirms the namespace wiring actually resolves each one: + +```rust +check_components! { + ProductionApp { + UserCreatorComponent, + UserGetterComponent, + UserUpdaterComponent, + PostCreatorComponent, + PostGetterComponent, + PostUpdaterComponent, + PostDeleterComponent, + UsernameCensorComponent, + SpamMessageDetectorComponent, + } +} +``` + +## Hierarchical grouping and context variants + +Because the prefixes form a hierarchy, a parent path can group its children, so the wiring can be collapsed another level. Both `@app.core.user` and `@app.core.post` sit under `@app.core`, which lets an intermediary bundle gather all the core CRUD wiring behind a single path, with the extras gathered the same way: + +```rust +delegate_components! { + new PostgresCoreComponents { + namespace DefaultNamespace; + + @app.core.user: PostgresUserComponents, + @app.core.post: PostgresPostComponents, + } +} + +delegate_components! { + new ProductionExtraComponents { + namespace DefaultNamespace; + + @app.extra.content_filter: AiContentFilterComponents, + } +} +``` + +The top-level context drops to two entries — its essential core behavior and its swappable extras: + +```rust +delegate_components! { + ProductionApp { + namespace DefaultNamespace; + + @app.core: PostgresCoreComponents, + @app.extra: ProductionExtraComponents, + } +} +``` + +The real payoff is that contexts now differ at a glance. A test context reuses the same PostgreSQL-backed core but swaps the AI content filters for dummy ones, so its full definition makes the single difference obvious: + +```rust +delegate_components! { + new DummyExtraComponents { + namespace DefaultNamespace; + + @app.extra.content_filter: DummyContentFilterComponents, + } +} + +delegate_components! { + TestApp { + namespace DefaultNamespace; + + @app.core: PostgresCoreComponents, + @app.extra: DummyExtraComponents, + } +} +``` + +`ProductionApp` and `TestApp` share `@app.core` and differ only in `@app.extra`, so a reader sees immediately that the two run identical core logic and diverge only in their content filtering — a comparison that, with the flat per-component table, would mean scanning a dozen entries that may not even appear in the same order. A local-first variant would follow the same shape, keeping the production extras but pointing `@app.core` at an SQLite core bundle, and the one swapped line would again be the whole story. diff --git a/docs/reference/README.md b/docs/reference/README.md new file mode 100644 index 00000000..53b466fe --- /dev/null +++ b/docs/reference/README.md @@ -0,0 +1,143 @@ +# CGP Construct Reference + +This directory documents every CGP construct — one self-contained document per construct, each explaining its purpose, syntax or definition, expansion or behavior, examples, related constructs, and source. The documents are written for agents who need precise per-construct semantics. The high-level conceptual framing that connects the constructs lives in the sibling [concepts/](../concepts/README.md) directory; the `/cgp` skill remains a complementary teaching aid. The authoring rules, document template, and the requirement to keep these documents in sync with the code live in [../CLAUDE.md](../CLAUDE.md). + +## Directory layout + +The documents are grouped into subdirectories by the *kind* of construct, so a reader looking for "the macro I invoke", "the trait the macro generates", or "the provider I wire" each has an obvious place to start. A new document goes in the subdirectory that matches what the construct is; when you add one, place it accordingly and register it in the matching section below. The high-level conceptual overviews that tie multiple constructs together — the consumer/provider duality, dependency injection, namespaces, handlers, and so on — live in the sibling [concepts/](../concepts/README.md) directory rather than here, each pointing into these per-construct documents for the mechanics. + +The [macros/](macros/) directory holds the procedural macros a programmer invokes directly: the attribute macros that define components and providers, the function-like macros that wire and check them, and the type-level construction macros (`Symbol!`, `Product!`, `Sum!`, `Path!`). The [derives/](derives/) directory holds the `#[derive(...)]` macros, a distinct family large enough to warrant its own space. The [attributes/](attributes/) directory holds the modifier attributes that refine what the definition macros generate — they are not standalone macros but options consumed by a host macro such as `#[cgp_fn]` or `#[cgp_impl]`. + +The remaining directories hold the runtime library constructs the macros expand into. The [components/](components/) directory documents the built-in CGP components CGP ships with — full consumer/provider trait pairs such as `HasType`, `HasErrorType`, and the handler family — that an application consumes and wires like any component it defines itself. The [providers/](providers/) directory documents the zero-sized provider structs that appear in wiring — `UseField`, `UseType`, `UseDelegate`, `UseContext`, and the rest — the values a context delegates a component to. The [traits/](traits/) directory documents the capability and mechanism traits that are *not* themselves components: the wiring traits (`DelegateComponent`, `IsProviderFor`, `CanUseComponent`), the field and type capabilities (`HasField`, `HasFields`), the extensible-data builder and extractor families, and the type-level operations. The [types/](types/) directory documents the type-level building-block types the rest of CGP is constructed from (`Field`, `Index`, the `Cons`/`Nil` product spine, the `Either`/`Void` sum spine, and the `Chars`/`PathCons` lists). + +The distinction between [components/](components/) and [traits/](traits/) is whether the trait is a CGP component: a document belongs in `components/` when its trait is defined with `#[cgp_component]`, `#[cgp_type]`, or `#[cgp_getter]` and therefore has a generated provider trait and `…Component` marker that contexts wire; it belongs in `traits/` when it is an ordinary capability or mechanism trait that the machinery uses but no one delegates. + +This index is the catalog of constructs. When you add, remove, or rename a construct, update both its document and this index in the same change. Because documents live in different subdirectories, a cross-link between two of them is a relative path — a sibling in the same directory is `name.md`, and a document in another directory is `../that-dir/name.md`. + +## Component definition macros — [macros/](macros/) + +These macros define CGP components and the providers that implement them — the core act of writing CGP code. + +- [`#[cgp_component]`](macros/cgp_component.md) — turn a trait into a component (consumer trait, provider trait, blanket impls). +- [`#[cgp_impl]`](macros/cgp_impl.md) — write a provider for a component using consumer-trait-style syntax. +- [`#[cgp_provider]`](macros/cgp_provider.md) — write a provider by implementing the provider trait directly. +- [`#[cgp_new_provider]`](macros/cgp_new_provider.md) — `#[cgp_provider]` that also defines the provider struct. +- [`#[cgp_fn]`](macros/cgp_fn.md) — define a single-implementation capability as a blanket-impl trait from a function. +- [`#[async_trait]`](macros/async_trait.md) — rewrite a trait's `async fn` declarations to `-> impl Future`, the lint-clean way to declare async CGP methods. +- [`#[cgp_type]`](macros/cgp_type.md) — define an abstract-type component. +- [`#[cgp_getter]`](macros/cgp_getter.md) — define a getter component wired through CGP. +- [`#[cgp_auto_getter]`](macros/cgp_auto_getter.md) — define a getter as a blanket impl over `HasField`. +- [`#[blanket_trait]`](macros/blanket_trait.md) — generate a blanket impl from a trait with default methods. +- [`#[cgp_computer]`](macros/cgp_computer.md) — define a `Computer` provider from a function. +- [`#[cgp_producer]`](macros/cgp_producer.md) — define a `Producer` provider from a function. +- [`#[cgp_auto_dispatch]`](macros/cgp_auto_dispatch.md) — generate a handler that dispatches over an extensible-data input. + +## Wiring and checking macros — [macros/](macros/) + +These macros connect components to providers on a concrete context and verify the wiring at compile time. + +- [`delegate_components!`](macros/delegate_components.md) — build a context's type-level table mapping components to providers. +- [`check_components!`](macros/check_components.md) — assert at compile time that a context's wiring is complete. +- [`delegate_and_check_components!`](macros/delegate_and_check_components.md) — delegate and check in one macro. +- [`#[cgp_namespace]`](macros/cgp_namespace.md) — group components under a namespace for presets and inheritance. + +## Type-level construction macros — [macros/](macros/) + +These macros construct the type-level vocabulary — strings, lists, sums, and paths — that the rest of CGP is built on. + +- [`Symbol!`](macros/symbol.md) — type-level string, used for field names. +- [`Product!` / `product!`](macros/product.md) — type-level list type and value. +- [`Sum!`](macros/sum.md) — type-level sum (variant) type. +- [`Path!`](macros/path.md) — type-level path, used by namespaces and redirected lookups. + +## Attribute modifiers — [attributes/](attributes/) + +These attributes refine what the definition macros generate and are used inside `#[cgp_impl]`, `#[cgp_fn]`, and `#[cgp_component]`. + +- [`#[implicit]`](attributes/implicit.md) — extract a function argument from a context field automatically. +- [`#[uses]`](attributes/uses.md) — import other CGP capabilities as `Self` bounds. +- [`#[use_type]`](attributes/use_type.md) — import an abstract associated type with fully-qualified rewriting. +- [`#[use_provider]`](attributes/use_provider.md) — complete an inner provider's bound in higher-order providers. +- [`#[extend]`](attributes/extend.md) — add supertrait bounds to a generated trait. +- [`#[extend_where]`](attributes/extend_where.md) — add `where` clauses to a generated trait definition. +- [`#[derive_delegate]`](attributes/derive_delegate.md) — generate `UseDelegate` providers that dispatch on a generic parameter. + +## Data derives — [derives/](derives/) + +These derive macros generate the field-access and extensible-data machinery for structs and enums. + +- [`#[derive(HasField)]`](derives/derive_has_field.md) — per-field accessors keyed by `Symbol!`/`Index`. +- [`#[derive(HasFields)]`](derives/derive_has_fields.md) — whole-struct/enum field-list view. +- [`#[derive(CgpData)]`](derives/derive_cgp_data.md) — full extensible-data derivation. +- [`#[derive(CgpRecord)]`](derives/derive_cgp_record.md) — extensible record (struct) derivation. +- [`#[derive(CgpVariant)]`](derives/derive_cgp_variant.md) — extensible variant (enum) derivation. +- [`#[derive(BuildField)]`](derives/derive_build_field.md) — builder support for records. +- [`#[derive(ExtractField)]`](derives/derive_extract_field.md) — extractor support for variants. +- [`#[derive(FromVariant)]`](derives/derive_from_variant.md) — variant-construction support. + +## Built-in components — [components/](components/) + +These are the full CGP components CGP ships with — each a consumer trait, provider trait, and `…Component` marker — that an application wires through `delegate_components!` like any component it defines itself. + +- [`HasType` / `TypeProvider`](components/has_type.md) — CGP's built-in abstract-type component. +- [`HasErrorType`](components/has_error_type.md) — the abstract error type component. +- [`CanRaiseError` / `CanWrapError`](components/can_raise_error.md) — raising and wrapping source errors into the abstract error type. +- [`Computer` / `CanCompute`](components/computer.md) — the synchronous computation component and its by-reference and async variants. +- [`TryComputer` / `CanTryCompute`](components/try_computer.md) — the fallible computation component. +- [`Handler` / `CanHandle`](components/handler.md) — the general async, fallible, error-aware computation component. +- [`Producer` / `CanProduce`](components/producer.md) — the input-free production component. +- [`CanRun` / `CanSendRun`](components/runner.md) — the task-running components. +- [`HasRuntime` / `HasRuntimeType`](components/has_runtime.md) — the abstract runtime type and accessor components. + +## Providers — [providers/](providers/) + +These are the zero-sized provider structs a context delegates components to. They carry no runtime value and exist only at the type level. + +- [`UseContext`](providers/use_context.md) — satisfy a provider trait by routing back through the context's own consumer-trait impl. +- [`UseDelegate`](providers/use_delegate.md) — dispatch on a generic parameter through an inner type-level table. +- [`UseDelegatedType`](providers/use_delegated_type.md) — resolve an abstract type through an inner table. +- [`UseField`](providers/use_field.md) — implement a getter by reading a named context field. +- [`UseFieldRef`](providers/use_field_ref.md) — implement a getter by reading a field through `AsRef`/`AsMut`. +- [`UseFields`](providers/use_fields.md) — getter provider keyed by the method name. +- [`UseType`](providers/use_type.md) — supply a concrete type for an abstract-type component. +- [`UseDefault`](providers/use_default.md) — marker provider selecting default implementations. +- [`WithProvider`](providers/with_provider.md) — adapt a foundational provider into a component (and the `WithContext`/`WithType`/`WithField` aliases). +- [`RedirectLookup`](providers/redirect_lookup.md) — re-route a lookup along a type-level path; the namespace mechanism. +- [`ChainGetters`](providers/chain_getters.md) — chain field getters to reach into nested contexts. +- [Handler combinators](providers/handler_combinators.md) — `ComposeHandlers`, `PipeHandlers`, `ReturnInput`, and the `Promote*` adapters that build and lift handlers. +- [Dispatch combinators](providers/dispatch_combinators.md) — `MatchWithHandlers`, `MatchWithValueHandlers`, `ExtractFieldAndHandle`, and the rest of the cgp-dispatch routing providers. +- [Monad providers](providers/monad_providers.md) — `PipeMonadic`, `BindOk`, `BindErr`, and the identity/ok/err monad markers. +- [Error providers](providers/error_providers.md) — `DebugError`, `DisplayError`, `RaiseFrom`, `ReturnError`, and the other backends for the error components. + +## Runtime traits — [traits/](traits/) + +These are the capability and mechanism traits the macros expand into — the traits a programmer rarely writes by hand but must understand to read generated code. + +- [`DelegateComponent`](traits/delegate_component.md) — the per-context type-level table mapping a component key to a provider. +- [`IsProviderFor`](traits/is_provider_for.md) — the marker supertrait that surfaces missing-dependency errors. +- [`CanUseComponent`](traits/can_use_component.md) — the consumer-side check that a context can use a component. +- [`HasField`](traits/has_field.md) — tag-keyed field access (with `HasFieldMut` and the provider-side `FieldGetter`). +- [`HasFields`](traits/has_fields.md) — the whole-shape field representation and its conversions. +- [`HasBuilder`](traits/has_builder.md) — the incremental-builder trait family (`BuildField`, `UpdateField`, `FinalizeBuild`, …). +- [`ExtractField`](traits/extract_field.md) — the incremental-extractor trait family (`HasExtractor`, `FinalizeExtract`, …). +- [`FromVariant`](traits/from_variant.md) — generic construction of an enum from a named variant. +- [`MapType`](traits/map_type.md) — the present/absent/void type-mapping markers (`IsPresent`, `IsNothing`, …) and transforms. +- [`AppendProduct`](traits/product_ops.md) — type-level product operations (`AppendProduct`, `ConcatProduct`, `MapFields`). +- [`CanUpcast`](traits/cast.md) — structural casts between records and variants (`CanUpcast`, `CanDowncast`, `CanBuildFrom`). +- [`DefaultNamespace`](traits/default_namespace.md) — the namespace/preset default-resolution traits. +- [`StaticFormat`](traits/static_format.md) — runtime formatting of type-level strings and path concatenation. +- [Monad traits](traits/monad.md) — `MonadicTrans`, `MonadicBind`, `LiftValue`, and `ContainsValue`, the trait layer behind monadic handler composition. +- [Optional fields](traits/optional_fields.md) — the cgp-field-extra builder/extractor traits for optional and defaulted fields. + +## Type-level types — [types/](types/) + +These are the type-level building-block types the macros and traits operate on. + +- [`Field`](types/field.md) — a value paired with its type-level name tag. +- [`Index`](types/index.md) — a type-level natural number, used to tag tuple-struct fields. +- [`Cons` / `Nil`](types/cons.md) — the product (record) list spine. +- [`Either` / `Void`](types/either.md) — the sum (variant) list spine. +- [`Chars`](types/chars.md) — the type-level character list behind `Symbol`. +- [`PathCons`](types/path_cons.md) — the type-level path list behind `Path!`. +- [`Life`](types/life.md) — a lifetime lifted into a type. +- [`MRef`](types/mref.md) — an owned-or-borrowed value. diff --git a/docs/reference/attributes/derive_delegate.md b/docs/reference/attributes/derive_delegate.md new file mode 100644 index 00000000..34ddbb20 --- /dev/null +++ b/docs/reference/attributes/derive_delegate.md @@ -0,0 +1,150 @@ +# `#[derive_delegate]` + +`#[derive_delegate]` is an attribute on a `#[cgp_component]` trait that generates a `UseDelegate`-style provider, which dispatches the component's implementation on one of the trait's generic type parameters using an inner type-level table. + +> **Legacy:** `#[derive_delegate]` and the [`UseDelegate`](../providers/use_delegate.md) provider it generates are a legacy dispatch mechanism. A component no longer needs this attribute to be dispatched on a generic parameter: the `open` statement of [`delegate_components!`](../macros/delegate_components.md) achieves the same per-type dispatch through the [`RedirectLookup`](../providers/redirect_lookup.md) impl that every [`#[cgp_component]`](../macros/cgp_component.md) already generates, wiring the per-value entries directly into the context's own table with better ergonomics and no separate inner table. Prefer `open` for new code, and add `#[derive_delegate]` only when the legacy `UseDelegate` nested-table wiring is specifically wanted. The attribute is retained for compatibility and is expected to be deprecated, and eventually removed, once the namespace-based form is shown to cover every dispatch case. + +## Purpose + +`#[derive_delegate]` solves the problem of choosing a different provider per value of a generic parameter. A component with a generic parameter, such as `CanCalculateArea`, often wants `Rectangle` to be handled by one provider and `Circle` by another. Without help, the author would have to write a dispatcher provider by hand — an impl of the provider trait that looks up the right delegate based on the `Shape` type and forwards every method to it. That impl is mechanical and identical in shape across every component, differing only in which generic parameter is the dispatch key. + +The attribute generates that dispatcher for you. Adding `#[derive_delegate(UseDelegate)]` to the component emits an implementation of the provider trait for [`UseDelegate`](../providers/use_delegate.md) that treats its inner `Components` type as a type-level table keyed on `Shape`, looks up the delegate for each concrete `Shape`, and forwards the call. A context then wires the component to `UseDelegate` and fills `SomeTable` with one provider per shape, getting per-type dispatch without writing the dispatcher. + +A component may need to dispatch on more than one parameter, and `#[derive_delegate]` supports this by accepting several attributes, each naming its own dispatcher type and key. The `UseDelegate` type CGP provides is the default dispatcher, but the same machinery works for any wrapper type, so a component can dispatch on one parameter through `UseDelegate` and on another through a custom dispatcher such as `UseInputDelegate`. Only the parameter named in the dispatcher's angle brackets is used as the lookup key; the others flow through unchanged. + +## Syntax + +`#[derive_delegate]` is applied as an outer attribute on a `#[cgp_component]` trait and takes a wrapper type parameterized by the generic parameter to dispatch on. The single form names one dispatcher and one key: + +```rust +#[cgp_component(AreaCalculator)] +#[derive_delegate(UseDelegate)] +pub trait CanCalculateArea { + fn area(&self, shape: &Shape) -> f64; +} +``` + +`UseDelegate` is the wrapper type that will carry the lookup table, and `Shape` is the trait generic parameter used as the dispatch key. The key may also be a parenthesized tuple of parameters, `UseDelegate<(A, B)>`, when the table should be keyed on more than one parameter at once. + +To dispatch on several parameters independently, repeat the attribute, one per dispatcher. Each names a distinct wrapper type and the single parameter it keys on: + +```rust +use core::marker::PhantomData; + +#[cgp_component(Computer)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanCompute { + type Output; + + fn compute(&self, _code: PhantomData, input: Input) -> Self::Output; +} +``` + +Here the default `UseDelegate` dispatches on `Code`, while the custom `UseInputDelegate` dispatches on `Input`. The custom dispatcher is an ordinary struct the user defines — `pub struct UseInputDelegate(pub PhantomData);` — with the same single-type-parameter shape as `UseDelegate`. + +## Expansion + +Each `#[derive_delegate]` attribute emits one additional provider impl alongside everything else `#[cgp_component]` generates. The impl is for the named wrapper applied to a fresh `Components` table type, and it follows the same forwarding shape as a normal provider blanket impl, except that the lookup key is the dispatch parameter rather than the component name. Starting from the single-form example: + +```rust +#[cgp_component(AreaCalculator)] +#[derive_delegate(UseDelegate)] +pub trait CanCalculateArea { + fn area(&self, shape: &Shape) -> f64; +} +``` + +the attribute generates, in addition to the consumer trait, provider trait, and blanket impls, the following dispatcher impl: + +```rust +impl AreaCalculator + for UseDelegate +where + Components: DelegateComponent<(Shape), Delegate = Delegate>, + Delegate: AreaCalculator, +{ + fn area(context: &Context, shape: &Shape) -> f64 { + Delegate::area(context, shape) + } +} +``` + +The dispatch key is wrapped in a tuple, `DelegateComponent<(Shape), ...>`, so that a multi-parameter key composes uniformly. The `Components` type is the inner table: when a context looks up the entry for a concrete `Shape`, `DelegateComponent` yields the `Delegate` provider, and the impl forwards `area` to it. The provider trait's other parameters — here just `Context` — pass through to the delegate unchanged. The generic added for the table is named `__Components__` and the looked-up delegate `__Delegate__` in the real output, alongside the provider trait's own context generic `__Context__`; the shorter names are used here for readability. + +When the component carries supertraits, they ride along into the dispatcher. The provider trait records each supertrait as a `Context: Supertrait` predicate in its `where` clause, and because the dispatcher impl reuses the provider trait's generics, that predicate appears on the generated `UseDelegate` impl as well — so a `#[derive_delegate]` on a trait like `CanRaiseError: HasErrorType` produces a dispatcher whose `where` clause also requires `Context: HasErrorType`. + +When several `#[derive_delegate]` attributes are present, one such impl is generated per attribute, each keyed on its own parameter. The `CanCompute` component above produces one impl for `UseDelegate` keyed on `(Code)` and a second for `UseInputDelegate` keyed on `(Input)`, both forwarding `compute` to the looked-up delegate. The two dispatchers are independent, so a context can pick which parameter to dispatch on, or compose them by nesting one table inside another. + +The inner table is built with [`delegate_components!`](../macros/delegate_components.md), and its nested-table syntax pairs naturally with this provider. A context wires the component to `UseDelegate` and defines `SomeTable`'s entries in the same breath: + +```rust +delegate_components! { + MyApp { + AreaCalculatorComponent: + UseDelegate, + } +} +``` + +The keys in the inner table — `Rectangle`, `Circle` — are the concrete `Shape` types the generated impl looks up, and the values are the providers each one dispatches to. + +## Examples + +A complete dispatching component connects the attribute to a working wiring. The component declares the dispatcher, and two providers implement it for different shapes: + +```rust +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +#[derive_delegate(UseDelegate)] +pub trait CanCalculateArea { + fn area(&self, shape: &Shape) -> f64; +} + +pub struct Rectangle { pub width: f64, pub height: f64 } +pub struct Circle { pub radius: f64 } + +#[cgp_new_provider] +impl AreaCalculator for RectangleArea { + fn area(_context: &Context, shape: &Rectangle) -> f64 { + shape.width * shape.height + } +} + +#[cgp_new_provider] +impl AreaCalculator for CircleArea { + fn area(_context: &Context, shape: &Circle) -> f64 { + core::f64::consts::PI * shape.radius * shape.radius + } +} +``` + +A context wires the component to `UseDelegate` with an inner table mapping each shape to its provider: + +```rust +pub struct MyApp; + +delegate_components! { + MyApp { + AreaCalculatorComponent: + UseDelegate, + } +} +``` + +Now `MyApp` implements `CanCalculateArea` through `RectangleArea` and `CanCalculateArea` through `CircleArea`. The generated `UseDelegate` impl performs the lookup: for a `Rectangle` it reads the `Rectangle` entry from `AreaCalculatorComponents`, finds `RectangleArea`, and forwards `area` to it. + +## Related constructs + +`#[derive_delegate]` is an attribute on [`#[cgp_component]`](../macros/cgp_component.md) and only makes sense for components that carry generic parameters. It generates an impl for the [`UseDelegate`](../providers/use_delegate.md) provider (or a user-defined dispatcher of the same shape), whose role and behavior that document covers in full. The inner lookup table it dispatches through is populated with [`delegate_components!`](../macros/delegate_components.md), whose nested-table syntax is the idiomatic way to define `UseDelegate<...>` wirings in place. + +## Source + +The attribute is parsed by `DeriveDelegateAttribute::parse` in [crates/macros/cgp-macro-core/src/types/attributes/derive_delegate/attribute.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/derive_delegate/attribute.rs), which reads the wrapper identifier and the angle-bracketed key (a single identifier or a parenthesized tuple). The dispatcher impl is built by the same file's `to_provider_impl`, which appends `__Components__` and `__Delegate__` generics, emits the `DelegateComponent<(params), Delegate = __Delegate__>` and `__Delegate__: ProviderTrait` bounds, and forwards each method through `trait_items_to_delegated_impl_items`. The attribute is collected in `types/attributes/cgp_component_attributes.rs` and emitted by `to_use_delegate_impls` in [crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/item.rs). The `UseDelegate` provider and a worked single-key expansion are documented in [crates/core/cgp-component/src/providers/use_delegate.rs](../../../crates/core/cgp-component/src/providers/use_delegate.rs); the multi-attribute form with a custom `UseInputDelegate` is used in [crates/extra/cgp-handler/src/components/computer.rs](../../../crates/extra/cgp-handler/src/components/computer.rs). diff --git a/docs/reference/attributes/extend.md b/docs/reference/attributes/extend.md new file mode 100644 index 00000000..9e380858 --- /dev/null +++ b/docs/reference/attributes/extend.md @@ -0,0 +1,124 @@ +# `#[extend(...)]` + +`#[extend(...)]` adds the given trait bounds as supertraits of the generated trait, making them a public part of the trait's interface rather than a hidden impl-side dependency. + +## Purpose + +`#[extend(...)]` exists to add supertraits to a CGP trait through an import-like attribute, and in [`#[cgp_fn]`](../macros/cgp_fn.md) it is the only way to do so. A supertrait is a bound that every implementor of a trait must also satisfy, and that every user of the trait may rely on. In `#[cgp_fn]`, the `where` clauses written in the function body are treated as impl-side dependencies and deliberately kept out of the generated trait definition — so there is no place to write a supertrait by hand. `#[extend(...)]` fills that gap: the bounds it lists are promoted onto the trait itself. + +The distinction from [`#[uses]`](uses.md) is the whole point. Both attributes accept the same simplified trait-path syntax and both feel like imports, but they import into different places. `#[uses(...)]` adds a hidden `Self` bound to the impl only — a private dependency that callers never see. `#[extend(...)]` adds a supertrait to the trait — a public requirement that becomes part of the contract. The natural way to describe the pair is that `#[extend(...)]` is the `pub use` equivalent of `#[uses(...)]`: where `#[uses(...)]` imports a capability for the implementation's own use, `#[extend(...)]` re-exports it as part of what the trait guarantees. + +This framing also explains when to reach for it. `#[extend(...)]` exists to make supertraits approachable for programmers who are not yet comfortable with Rust's supertrait syntax, by presenting them as imports. Authors who are comfortable writing `pub trait Foo: Bar` directly can do so in [`#[cgp_component]`](../macros/cgp_component.md); the choice there is stylistic. In `#[cgp_fn]`, where direct supertrait syntax is unavailable, `#[extend(...)]` is the mechanism. + +## Syntax + +`#[extend(...)]` takes a comma-separated list of trait bounds in the simplified form `TraitIdent`: + +```rust +#[extend(HasScalarType)] +``` + +Each entry names a trait that becomes a supertrait of the generated trait, optionally with generic type arguments. A bare `HasScalarType` becomes a `: HasScalarType` supertrait; a parameterized form carries its arguments through. Multiple bounds may be listed in one attribute or spread across several `#[extend(...)]` attributes, and they accumulate. + +`#[extend(...)]` is accepted in [`#[cgp_fn]`](../macros/cgp_fn.md) and in [`#[cgp_component]`](../macros/cgp_component.md). It is not available in [`#[cgp_impl]`](../macros/cgp_impl.md), because a provider impl has no trait definition of its own to attach supertraits to — the supertraits belong to the component's trait, defined by `#[cgp_component]`. + +## Expansion + +`#[extend(...)]` adds each listed bound as a supertrait of the generated trait, and the same bound also appears in the impl's `where` clause so the implementation may rely on it. Starting from a `#[cgp_fn]` definition that depends on an abstract `Scalar` type: + +```rust +pub trait HasScalarType { + type Scalar: Clone + Mul; +} + +#[cgp_fn] +#[extend(HasScalarType)] +fn rectangle_area( + &self, + #[implicit] width: Self::Scalar, + #[implicit] height: Self::Scalar, +) -> Self::Scalar { + width * height +} +``` + +the macro emits a trait carrying `HasScalarType` as a supertrait, and an impl that carries both `Self: HasScalarType` and the `HasField` bounds from the implicit arguments: + +```rust +pub trait RectangleArea: HasScalarType { + fn rectangle_area(&self) -> Self::Scalar; +} + +impl RectangleArea for Context +where + Self: HasScalarType, + Self: HasField + + HasField, +{ + fn rectangle_area(&self) -> Self::Scalar { + let width: Self::Scalar = + self.get_field(PhantomData::).clone(); + let height: Self::Scalar = + self.get_field(PhantomData::).clone(); + + width * height + } +} +``` + +The bound appears in two places for a reason. On the trait it is a supertrait, so `Self::Scalar` resolves and callers know any `RectangleArea` type is also a `HasScalarType`. In the impl `where` clause it lets the implementation actually use the associated type. This double placement is the difference from [`#[uses]`](uses.md), which adds the bound to the impl only. The generated context type is named `__Context__` in the real output; `Context` is used here for readability. + +In [`#[cgp_component]`](../macros/cgp_component.md) the effect is purely on the consumer trait, and it is exactly equivalent to writing the supertrait directly. The definition + +```rust +#[cgp_component(AreaCalculator)] +#[extend(HasScalarType)] +pub trait CanCalculateArea { + fn area(&self) -> Self::Scalar; +} +``` + +is the same as `pub trait CanCalculateArea: HasScalarType`. Here `#[extend(...)]` buys nothing the language does not already offer — it is available only so that the `use`/`pub use` pairing with `#[uses(...)]` reads consistently across both macros. + +## Examples + +`#[extend(...)]` shines when a `#[cgp_fn]` capability depends on an abstract type that the context must provide. The function below works for any context that defines a `Scalar` type and supplies `width` and `height` fields of that type: + +```rust +use cgp::prelude::*; +use core::ops::Mul; + +pub trait HasScalarType { + type Scalar: Clone + Mul; +} + +#[cgp_fn] +#[extend(HasScalarType)] +pub fn rectangle_area( + &self, + #[implicit] width: Self::Scalar, + #[implicit] height: Self::Scalar, +) -> Self::Scalar { + width * height +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +impl HasScalarType for Rectangle { + type Scalar = f64; +} +``` + +Because `HasScalarType` is a supertrait of `RectangleArea`, the abstract `Self::Scalar` is usable in the signature and body, and `Rectangle` — which implements `HasScalarType` with `Scalar = f64` and derives `HasField` for its two fields — satisfies every bound, gaining `rectangle_area`. + +## Related constructs + +`#[extend(...)]` is the `pub use` counterpart to [`#[uses]`](uses.md): the two share syntax but differ in placement, with `#[extend(...)]` adding public supertraits and `#[uses(...)]` adding hidden impl-side bounds. It is used in [`#[cgp_fn]`](../macros/cgp_fn.md), where it is the only way to declare supertraits, and in [`#[cgp_component]`](../macros/cgp_component.md), where it duplicates the native supertrait syntax for stylistic consistency. When the supertrait is an abstract-type trait whose associated type is referenced throughout the signature, prefer [`#[use_type]`](use_type.md), which adds the supertrait *and* rewrites bare type names into fully-qualified form. To add `where` clauses (not supertraits) to a `#[cgp_fn]` trait definition, use [`#[extend_where]`](extend_where.md). + +## Source + +`#[extend(...)]` is parsed in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) (the `extend` field of `FunctionAttributes`). For `#[cgp_fn]`, the bounds are added to the trait's supertraits and to the impl `where` clause in [crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs). For `#[cgp_component]`, the attribute is parsed by `CgpComponentAttributes::parse` and its bounds appended to the consumer trait's supertraits in [crates/macros/cgp-macro-core/src/types/attributes/cgp_component_attributes.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_component_attributes.rs). Expansion snapshots are in [crates/tests/cgp-tests/tests/cgp_fn_tests/extend.rs](../../../crates/tests/cgp-tests/tests/cgp_fn_tests/extend.rs) and the component abstract-type tests in [crates/tests/cgp-tests/tests/component_tests/abstract_types/](../../../crates/tests/cgp-tests/tests/component_tests/abstract_types/). diff --git a/docs/reference/attributes/extend_where.md b/docs/reference/attributes/extend_where.md new file mode 100644 index 00000000..17be008a --- /dev/null +++ b/docs/reference/attributes/extend_where.md @@ -0,0 +1,65 @@ +# `#[extend_where(...)]` + +`#[extend_where(...)]` adds `where` clauses to the generated trait definition in [`#[cgp_fn]`](../macros/cgp_fn.md), so a bound becomes part of the trait's interface rather than only its implementation. + +## Purpose + +`#[extend_where(...)]` exists to put a `where` clause on the *trait* a `#[cgp_fn]` generates, not just on its impl. By default `#[cgp_fn]` treats every `where` clause written in the function body as an impl-side dependency: the bound goes onto the generated impl and is hidden from the trait definition. That is usually what you want, but sometimes a bound must be visible on the trait itself — for example, a constraint on a generic type parameter that callers of the trait need to know about. `#[extend_where(...)]` is how you promote such a bound onto the trait. + +It complements [`#[extend]`](extend.md). Where `#[extend(...)]` adds *supertrait* bounds (`pub trait Foo: Bar`) to the generated trait, `#[extend_where(...)]` adds *`where`-clause* bounds (`pub trait Foo where T: Bar`) to it. Both make a requirement part of the trait's public interface; they differ only in which syntactic position the bound occupies. + +## Syntax + +`#[extend_where(...)]` takes a comma-separated list of full `where`-clause predicates: + +```rust +#[extend_where(Scalar: Clone)] +``` + +Unlike [`#[uses]`](uses.md) and [`#[extend]`](extend.md), which accept only the simplified trait-path form, `#[extend_where(...)]` accepts arbitrary predicates — the same things a Rust `where` clause allows, including associated-type-equality bounds. Each predicate is added verbatim to the generated trait's `where` clause. + +`#[extend_where(...)]` is supported only in [`#[cgp_fn]`](../macros/cgp_fn.md). It has no meaning in [`#[cgp_impl]`](../macros/cgp_impl.md) or [`#[cgp_component]`](../macros/cgp_component.md), because in those macros the `where` clause you write is already part of the trait definition — there is nothing to promote, so write the bound as a normal `where` clause directly. + +## Expansion + +`#[extend_where(...)]` adds its predicates to the `where` clause of the generated trait, and the same predicates also remain on the impl. Starting from a generic `#[cgp_fn]` definition: + +```rust +#[cgp_fn] +#[extend_where(Scalar: Clone)] +fn rectangle_area( + &self, + #[implicit] width: Scalar, + #[implicit] height: Scalar, +) -> Scalar +where + Scalar: Mul, +{ + width * height +} +``` + +the macro emits a trait whose definition carries the `Scalar: Clone` bound in its own `where` clause: + +```rust +pub trait RectangleArea +where + Scalar: Clone, +{ + fn rectangle_area(&self) -> Scalar; +} +``` + +The `Scalar: Mul` bound, written in the function body, stays as an impl-side dependency on the generated impl and does not appear on the trait. The `Scalar: Clone` bound from `#[extend_where(...)]` is what gets promoted onto the trait definition. + +## Examples + +`#[extend_where(...)]` is the right tool when a generic parameter of a `#[cgp_fn]` trait needs a publicly visible bound. The example above already shows the realistic shape: a `Scalar`-generic area function whose trait advertises `Scalar: Clone` while keeping the multiplication bound private to the impl. The promoted bound means any code naming `RectangleArea` can rely on `Scalar: Clone` without restating it. + +## Related constructs + +`#[extend_where(...)]` is the `where`-clause sibling of [`#[extend]`](extend.md): both promote a requirement onto the generated trait, with `#[extend]` adding supertraits and `#[extend_where(...)]` adding `where` predicates. It is specific to [`#[cgp_fn]`](../macros/cgp_fn.md), since only there are body `where` clauses hidden from the trait. To add hidden impl-side bounds instead of trait-visible ones, use [`#[uses]`](uses.md). + +## Source + +`#[extend_where(...)]` is parsed in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) (the `extend_where` field of `FunctionAttributes`), and its predicates are added to both the trait and impl `where` clauses in [crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs). An expansion snapshot that exercises `#[extend_where(...)]` alongside `#[use_type]` lives in [crates/tests/cgp-tests/tests/cgp_fn_tests/nested_foreign_type.rs](../../../crates/tests/cgp-tests/tests/cgp_fn_tests/nested_foreign_type.rs), among the other `#[cgp_fn]` tests in [crates/tests/cgp-tests/tests/cgp_fn_tests/](../../../crates/tests/cgp-tests/tests/cgp_fn_tests/). diff --git a/docs/reference/attributes/implicit.md b/docs/reference/attributes/implicit.md new file mode 100644 index 00000000..7c89d7ed --- /dev/null +++ b/docs/reference/attributes/implicit.md @@ -0,0 +1,118 @@ +# `#[implicit]` + +`#[implicit]` marks a function argument as an implicit dependency: instead of being passed by the caller, the value is read from a same-named field on the context, and the argument disappears from the public signature. + +## Purpose + +`#[implicit]` exists to make field-based dependency injection look like an ordinary function parameter. In plain CGP, a provider that needs a `width` value from its context declares a `HasField` bound in its `where` clause and calls `self.get_field(PhantomData)` inside the body. That works, but it forces the author to understand `HasField`, type-level symbols, and `PhantomData` tags before writing even the simplest provider. `#[implicit]` hides all of that behind a normal-looking parameter. + +The argument named `width: f64` with `#[implicit]` reads as "this function needs a `width` of type `f64`," which is exactly the intuition a Rust programmer already has. The macro then does the mechanical work: it removes the argument from the signature, adds the matching `HasField` bound, and binds a local variable to the field value at the top of the body. The result is code that looks like a function taking arguments but behaves like a provider injecting dependencies from its context. + +This is why `#[implicit]` is the recommended starting point for basic CGP. It lets a newcomer write providers in [`#[cgp_fn]`](../macros/cgp_fn.md) and [`#[cgp_impl]`](../macros/cgp_impl.md) using only familiar function syntax, deferring the `HasField` machinery until they actually need to understand it. + +## Syntax + +`#[implicit]` is written as a bare attribute on a typed function argument, and the argument must have a plain identifier name: + +```rust +fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} +``` + +The argument name doubles as the field name. Here `width` and `height` name both the local variables used in the body and the context fields the values are read from, via `Symbol!("width")` and `Symbol!("height")`. The argument type is the type the body sees, and it determines how the field is accessed (described under Expansion). + +Three rules constrain where `#[implicit]` may appear. The function must take `self` as its first argument, because the field is read from `self`; a function with implicit arguments but no receiver is rejected. The argument pattern must be a bare identifier, not a destructuring or `mut` pattern — to get a mutable local, clone the injected value explicitly inside the body. And when the receiver is `&mut self`, at most one implicit argument is allowed, since each one borrows from the same context. + +`#[implicit]` is usable wherever CGP rewrites function bodies into providers: inside [`#[cgp_fn]`](../macros/cgp_fn.md) and inside the methods of a [`#[cgp_impl]`](../macros/cgp_impl.md) block. It is not a standalone macro — it is only meaningful as an argument attribute consumed by those macros. + +## Expansion + +`#[implicit]` rewrites each marked argument into a `HasField` bound plus a `let` binding, leaving the rest of the function untouched. Starting from a `#[cgp_fn]` definition: + +```rust +#[cgp_fn] +fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} +``` + +the macro produces a trait whose method takes no extra arguments, and an impl whose `where` clause carries one `HasField` bound per implicit argument: + +```rust +pub trait RectangleArea { + fn rectangle_area(&self) -> f64; +} + +impl RectangleArea for Context +where + Self: HasField + + HasField, +{ + fn rectangle_area(&self) -> f64 { + let width: f64 = self.get_field(PhantomData::).clone(); + let height: f64 = self.get_field(PhantomData::).clone(); + + width * height + } +} +``` + +The two `let` bindings are inserted at the top of the body in argument order, before any of the original statements, so the names are in scope for the rest of the function. The generated context type parameter is literally named `__Context__` in the emitted code; the examples here use `Context` for readability. + +The access expression depends on the argument type, following the same rules as [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md). For an owned type such as `f64` or `String`, the macro reads the field by reference and appends `.clone()`, so the body receives an owned value. The one special case worth knowing is `&str`: an argument typed `&str` is backed by a `String` field, and the access uses `.as_str()` rather than `.clone()`. Concretely: + +```rust +#[cgp_fn] +fn greet(&self, #[implicit] name: &str) { + println!("Hello, {}!", name); +} +``` + +expands so that the bound is `HasField` and the binding is `let name: &str = self.get_field(PhantomData::).as_str();`. The field is a `String`, but the argument the body works with is a borrowed `&str`. + +Inside a [`#[cgp_impl]`](../macros/cgp_impl.md) block the rewrite is identical — the same `HasField` bounds are added to the impl's `where` clause and the same `let` bindings are prepended to the method body. For example: + +```rust +#[cgp_impl(new RectangleArea)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} +``` + +gains `Self: HasField + HasField` on the impl, with `width` and `height` bound from the context at the top of `area`. + +## Examples + +A complete `#[cgp_fn]` capability with implicit arguments needs only a context that derives [`HasField`](../derives/derive_has_field.md) and contains the named fields: + +```rust +use cgp::prelude::*; + +#[cgp_fn] +pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +fn print_area(rect: &Rectangle) { + println!("area = {}", rect.rectangle_area()); +} +``` + +`Rectangle` derives `HasField` for `width` and `height`, which satisfies the two bounds the macro added, so `RectangleArea` is implemented for `Rectangle` through the generated blanket impl. The call `rect.rectangle_area()` reads both fields from `rect` and multiplies them — no arguments are passed, because both were declared implicit and are sourced from the context. + +## Related constructs + +`#[implicit]` is most often used inside [`#[cgp_fn]`](../macros/cgp_fn.md), which turns a function into a single-implementation capability, and inside [`#[cgp_impl]`](../macros/cgp_impl.md), which writes a provider for an existing component. It relies on [`#[derive(HasField)]`](../derives/derive_has_field.md) on the context to supply the field accessors that the generated bounds require. Its access rules — `.clone()` for owned values, `.as_str()` for `&str` — are shared with [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md), which is the better tool when the same fields are accessed across many methods or in the middle of a body. To bring in other CGP capabilities alongside implicit arguments, combine `#[implicit]` with [`#[uses]`](uses.md). + +## Source + +Implicit-argument parsing lives in [crates/macros/cgp-macro-core/src/functions/implicits/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/implicits/parse.rs), which extracts `#[implicit]`-marked arguments and validates the `self`/`mut` rules. The per-argument model is in [crates/macros/cgp-macro-core/src/types/implicits/](../../../crates/macros/cgp-macro-core/src/types/implicits/): `arg_field.rs` builds the `HasField` bound and the `let` binding, and `arg_fields.rs` adds the bounds to the impl generics and prepends the bindings to the body. The field-type-to-access-mode mapping (`.clone()`, `.as_str()`, and the reference/option/slice cases) is in [crates/macros/cgp-macro-core/src/functions/field/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/field/parse.rs) and [crates/macros/cgp-macro-core/src/types/getter/get_field_with_mode_expr.rs](../../../crates/macros/cgp-macro-core/src/types/getter/get_field_with_mode_expr.rs). Expansion snapshots are in [crates/tests/cgp-tests/tests/cgp_fn_tests/basic.rs](../../../crates/tests/cgp-tests/tests/cgp_fn_tests/basic.rs) and [crates/tests/cgp-tests/tests/component_tests/cgp_impl/implicit_args/](../../../crates/tests/cgp-tests/tests/component_tests/cgp_impl/implicit_args/). diff --git a/docs/reference/attributes/use_provider.md b/docs/reference/attributes/use_provider.md new file mode 100644 index 00000000..46b404ee --- /dev/null +++ b/docs/reference/attributes/use_provider.md @@ -0,0 +1,130 @@ +# `#[use_provider]` + +`#[use_provider]` improves the ergonomics of higher-order providers by writing the inner provider's bound for you, hiding the extra `Self` generic that a provider trait inserts at its first position. + +## Purpose + +`#[use_provider]` exists to keep higher-order providers looking like ordinary providers. A higher-order provider is one that takes another provider as a generic parameter and delegates part of its work to it — for example a `ScaledArea` provider that multiplies whatever an `InnerCalculator` computes. The catch is that provider traits move the original `Self` into an explicit leading `Context` parameter, so the inner provider must be bound as `InnerCalculator: AreaCalculator`, not `InnerCalculator: AreaCalculator`. That stray `` is exactly the detail a reader does not expect, because the consumer trait it mirrors has no such parameter. + +`#[use_provider]` lets the author write the bound without the ``. Annotating an impl with `#[use_provider(InnerCalculator: AreaCalculator)]` adds the `Self` argument back automatically and inserts the completed bound into the impl's `where` clause, so the source reads `InnerCalculator: AreaCalculator` while the generated code carries `InnerCalculator: AreaCalculator`. This preserves the illusion that a provider trait looks the same as the consumer trait it came from, which is why it is the idiomatic way to declare the inner dependency of a higher-order provider. + +The body of such a provider still calls the inner provider as an associated function — `InnerCalculator::area(self)` rather than `self.area()` — because the inner provider is named explicitly rather than routed through the context's own wiring. `#[use_provider]` removes the surprise from the bound; the associated-function call at the use site is written out directly. + +## Syntax + +`#[use_provider]` is an attribute on a `#[cgp_impl]` or `#[cgp_fn]` definition, taking a provider type followed by a colon and the provider trait bounds it should satisfy. The shape is a provider, a colon, and one or more trait bounds joined by `+`: + +```rust +#[use_provider(InnerCalculator: AreaCalculator)] +``` + +`InnerCalculator` is the provider type — usually a generic parameter of the impl — and `AreaCalculator` is the provider trait whose `Self`/context argument the macro fills in. The trait may carry its own further generic arguments after the context slot, and these are preserved in order behind the inserted `Self`. Several `#[use_provider]` bounds may be supplied — separated by commas inside one attribute or split across stacked attributes — to bind more than one inner provider. + +## Expansion + +`#[use_provider]` rewrites nothing in the body; it only completes and inserts the `where`-clause bound. Take this higher-order provider, where `ScaledArea` scales the area produced by an inner calculator: + +```rust +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_impl(new ScaledArea)] +#[use_provider(InnerCalculator: AreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + InnerCalculator::area(self) * scale_factor * scale_factor + } +} +``` + +The attribute takes the bound `InnerCalculator: AreaCalculator`, inserts the context type as the leading generic argument, and pushes the result onto the impl's `where` clause. After this step the impl is equivalent to writing the `` argument by hand: + +```rust +#[cgp_impl(new ScaledArea)] +impl AreaCalculator +where + InnerCalculator: AreaCalculator, +{ + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + InnerCalculator::area(self) * scale_factor * scale_factor + } +} +``` + +The same applies to `#[cgp_fn]`. Here the inner provider is bound and then called as an associated function: + +```rust +#[cgp_fn] +#[use_provider(RectangleAreaCalculator: AreaCalculator)] +fn rectangle_area(&self) -> f64 { + RectangleAreaCalculator::area(self) +} +``` + +This desugars to the blanket impl with the completed bound; note the `` the macro supplied: + +```rust +trait RectangleArea { + fn rectangle_area(&self) -> f64; +} + +impl RectangleArea for Context +where + RectangleAreaCalculator: AreaCalculator, +{ + fn rectangle_area(&self) -> f64 { + RectangleAreaCalculator::area(self) + } +} +``` + +In both cases the body is left untouched, so it must invoke the inner provider directly as an associated function — `RectangleAreaCalculator::area(self)` — passing `self` as the explicit context argument. `#[use_provider]` supplies only the bound; it does not rewrite the call expression. Calling the inner provider as a method (`self.area()`) would instead route through whatever provider the context itself has wired for `AreaCalculator`, which is a different dispatch and usually not what a higher-order provider wants. + +## Examples + +A complete higher-order provider shows the outer form pulling its weight. The base component and a concrete provider come first: + +```rust +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_impl(new RectangleArea)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} +``` + +The higher-order `ScaledArea` then wraps any inner calculator and scales its result, declaring the inner dependency with `#[use_provider]`: + +```rust +#[cgp_impl(new ScaledArea)] +#[use_provider(InnerCalculator: AreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + let base_area = InnerCalculator::area(self); + base_area * scale_factor * scale_factor + } +} +``` + +A context can now wire `AreaCalculatorComponent` to `ScaledArea`, and `ScaledArea` will compute the rectangle area through `RectangleArea` and then scale it. The author never wrote `InnerCalculator: AreaCalculator`; `#[use_provider]` supplied the ``. + +## Related constructs + +`#[use_provider]` is written almost exclusively inside [`#[cgp_impl]`](../macros/cgp_impl.md) and [`#[cgp_fn]`](../macros/cgp_fn.md) implementations of components defined with [`#[cgp_component]`](../macros/cgp_component.md), and is the idiomatic tool for the higher-order provider pattern those macros support. It is the provider-bound counterpart to [`#[uses]`](uses.md), which imports consumer-trait dependencies on `Self`; where `#[uses]` adds a bound on the context, `#[use_provider]` adds a bound on a separate provider type and fills in that type's context argument. For dispatching to different providers based on a generic type rather than naming one statically, see [`UseDelegate`](../providers/use_delegate.md) and [`#[derive_delegate]`](derive_delegate.md). + +## Known issues + +`#[use_provider]` only completes and inserts a `where`-clause bound; there is no call-site form that rewrites a method call into a provider dispatch. The attribute's parser requires the `Provider: Trait` shape — a provider, a colon, and the trait bounds — so a bare `#[use_provider(InnerCalculator)]` applied to an expression is not accepted, and no pass rewrites `receiver.method(args)` into `Provider::method(receiver, args)`. A body that delegates to a named inner provider must therefore spell the associated-function call out itself, as `InnerCalculator::area(self)`. + +## Source + +The outer form is parsed by `UseProviderAttribute` in [crates/macros/cgp-macro-core/src/types/attributes/use_provider/attribute.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/use_provider/attribute.rs); its `to_type_param_bounds` inserts the context type at index 0 of the trait's generic arguments, and `to_provider_bounds` builds the `where` predicate. The bounds are appended to the impl by `add_type_param_bounds` in `attributes.rs`. The attribute is collected for `#[cgp_impl]` in `types/attributes/cgp_impl_attributes.rs` and for `#[cgp_fn]` in `types/attributes/function.rs`, and applied in `types/cgp_impl/item.rs` and `types/cgp_fn/preprocessed.rs`. The expansion snapshot for the `#[cgp_fn]` outer form is in [crates/tests/cgp-tests/tests/cgp_fn_tests/use_provider.rs](../../../crates/tests/cgp-tests/tests/cgp_fn_tests/use_provider.rs); `#[cgp_impl]` usage is exercised in [crates/tests/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs](../../../crates/tests/cgp-tests/tests/component_tests/cgp_impl/use_provider.rs) and `shape.rs`. diff --git a/docs/reference/attributes/use_type.md b/docs/reference/attributes/use_type.md new file mode 100644 index 00000000..18c52c5e --- /dev/null +++ b/docs/reference/attributes/use_type.md @@ -0,0 +1,179 @@ +# `#[use_type]` + +`#[use_type]` imports an abstract associated type into a `#[cgp_fn]`, `#[cgp_impl]`, or `#[cgp_component]` definition and rewrites every bare mention of that type into the fully-qualified `::AssocType` form, adding the trait as a supertrait or bound at the same time. + +## Purpose + +`#[use_type]` removes the boilerplate of referring to an abstract type that lives on another CGP trait. A CGP trait often needs a type that is defined elsewhere — a `Scalar` from `HasScalarType`, an `Error` from `HasErrorType` — and Rust requires every reference to that type to be written in fully-qualified form, `::Scalar`, because a bare `Scalar` is not a type the compiler knows about. Writing that prefix on every occurrence, in the return type, in each implicit argument, and in the body, is verbose and easy to get wrong. + +The attribute lets you write the bare identifier `Scalar` everywhere and have the macro expand it for you. You declare the type once in the attribute — `#[use_type(HasScalarType::Scalar)]` — and the macro replaces each standalone `Scalar` type with `::Scalar`, while also adding `HasScalarType` as a supertrait of the generated trait (for `#[cgp_component]`) or as a `where`-clause bound on the impl (for `#[cgp_impl]` and `#[cgp_fn]`). The bare identifier reads like a normal generic, but resolves to the qualified associated type. + +Beyond saving keystrokes, the fully-qualified rewrite removes ambiguity that the bare form cannot express. Because the macro always emits the `::Type` path, nested associated types compose without the author ever spelling out the path, foreign abstract types can be pulled from a type parameter rather than `Self`, and type-equality constraints between two imported types can be stated declaratively. These capabilities are why the `/cgp` skill recommends `#[use_type]` as the default way to import abstract types in all three macros. + +## Syntax + +`#[use_type]` is applied as an outer attribute alongside the `#[cgp_fn]`, `#[cgp_impl]`, or `#[cgp_component]` attribute, and its argument names a trait and one or more of its associated types. The simplest form imports a single type from a trait by path: + +```rust +#[use_type(HasScalarType::Scalar)] +``` + +The path before the final segment is the trait, and the final segment is the associated type to import. The rewrite target — the type the bare identifier expands into — defaults to `Self`, so the example above rewrites `Scalar` to `::Scalar`. + +A leading `@` changes the rewrite target from `Self` to a named type, which is how foreign abstract types are imported. The form `#[use_type(@Types::HasScalarType::Scalar)]` treats the first segment, `Types`, as the context type and rewrites `Scalar` to `::Scalar`. `Types` is typically a generic parameter of the function or impl rather than `Self`, which lets a trait pull an abstract type from a parameter instead of from the implementing context. + +Several types from the same trait can be imported in one attribute using a braced list, and each entry may be renamed with `as` or constrained with `=`. The braced form `#[use_type(HasFooType::{Foo, Bar as Baz})]` imports `Foo` under its own name and `Bar` under the local alias `Baz`. The equality form `#[use_type(HasScalarType::{Scalar = f64})]` imports `Scalar` and additionally constrains it, emitting `Self: HasScalarType` in the `where` clause. Multiple `#[use_type]` attributes may also be stacked, and several trait paths may be separated by commas inside one attribute, as in `#[use_type(HasBarType::{Bar as Baz = Foo}, HasFooType::Foo)]`. + +One restriction applies to `#[cgp_component]` specifically: the `= ...` type-equality form is rejected there, because a trait definition cannot carry the impl-side equality constraint that the equality form produces. Equality constraints belong on `#[cgp_fn]` and `#[cgp_impl]`, where they become `where` bounds. + +## Expansion + +`#[use_type]` runs before the rest of the macro and does two things: it substitutes every matching bare type identifier with the qualified associated type, and it adds the trait as a supertrait or bound. Consider this `#[cgp_fn]` using the single-import form: + +```rust +pub trait HasScalarType { + type Scalar: Clone + Mul; +} + +#[cgp_fn] +#[use_type(HasScalarType::Scalar)] +fn rectangle_area( + &self, + #[implicit] width: Scalar, + #[implicit] height: Scalar, +) -> Scalar { + width * height +} +``` + +The macro first rewrites every standalone `Scalar` to `::Scalar` and appends `HasScalarType` to the bounds, then desugars the resulting `#[cgp_fn]` as usual. The effective expansion is: + +```rust +pub trait RectangleArea: HasScalarType { + fn rectangle_area(&self) -> ::Scalar; +} + +impl RectangleArea for Context +where + Self: HasField::Scalar> + + HasField::Scalar>, + Self: HasScalarType, +{ + fn rectangle_area(&self) -> ::Scalar { + let width: ::Scalar = + self.get_field(PhantomData::).clone(); + let height: ::Scalar = + self.get_field(PhantomData::).clone(); + width * height + } +} +``` + +The substitution is purely textual at the type level: it matches single-segment type paths with no arguments whose identifier equals the imported name (or its alias), and replaces them with `::Scalar`. A bare `Scalar` anywhere — return type, implicit-argument annotation, or a `let` binding inside the body — is rewritten the same way, which is what makes nested uses work without the author writing any path. + +For `#[cgp_component]`, the trait is added as a supertrait rather than a `where` bound, and the rewrite touches the trait's own signatures. Starting from: + +```rust +#[cgp_component(AreaCalculator)] +#[use_type(HasScalarType::Scalar)] +pub trait CanCalculateArea { + fn area(&self) -> Scalar; +} +``` + +the `#[use_type]` phase rewrites the trait into the following before `#[cgp_component]` proceeds: + +```rust +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea: HasScalarType { + fn area(&self) -> ::Scalar; +} +``` + +The supertrait is added only when the rewrite target is `Self`. With the foreign-type `@` form the target is a named type, so no supertrait is added; instead the bound lands in the impl's `where` clause. This `#[cgp_fn]` imports `Scalar` from a generic parameter `Types`: + +```rust +#[cgp_fn] +#[use_type(@Types::HasScalarType::Scalar)] +pub fn rectangle_area( + &self, + #[implicit] width: Scalar, + #[implicit] height: Scalar, +) -> Scalar +where + Scalar: Mul + Copy, +{ + let res: Scalar = width * height; + res +} +``` + +Every `Scalar`, including the ones in the explicit `where` clause, expands to `::Scalar`, and the bound `Types: HasScalarType` is added to the impl's `where` clause rather than as a supertrait: + +```rust +pub trait RectangleArea { + fn rectangle_area(&self) -> ::Scalar; +} + +impl RectangleArea for Context +where + ::Scalar: + Mul::Scalar> + Copy, + Self: HasField::Scalar> + + HasField::Scalar>, + Types: HasScalarType, +{ + fn rectangle_area(&self) -> ::Scalar { + let width: ::Scalar = + self.get_field(PhantomData::).clone(); + let height: ::Scalar = + self.get_field(PhantomData::).clone(); + let res: ::Scalar = width * height; + res + } +} +``` + +The type-equality form adds a constrained bound on top of the substitution. Writing `#[use_type(HasScalarType::{Scalar = f64})]` substitutes `Scalar` to `::Scalar` exactly as before, but emits `Self: HasScalarType` in the `where` clause in place of the plain `Self: HasScalarType`, pinning the abstract type to `f64`. When one import's equality target names another import's alias — as in `#[use_type(HasBarType::{Bar as Baz = Foo}, HasFooType::Foo)]` — the macro resolves the target across specs and emits `Self: HasBarType::Foo>`, tying the two abstract types together. Two imports may not share the same identifier or alias; doing so is a compile error. + +## Examples + +A realistic use threads one abstract `Scalar` type through a component and a provider, with neither writing `Self::` by hand. The component and the type trait come first: + +```rust +use cgp::prelude::*; +use core::ops::Mul; + +#[cgp_type] +pub trait HasScalarType { + type Scalar: Clone + Mul; +} + +#[cgp_component(AreaCalculator)] +#[use_type(HasScalarType::Scalar)] +pub trait CanCalculateArea { + fn area(&self) -> Scalar; +} +``` + +`CanCalculateArea` ends up with `HasScalarType` as a supertrait and `area` returning `::Scalar`. A provider for it imports the same type and writes its body in terms of the bare name: + +```rust +#[cgp_impl(new RectangleArea)] +#[use_type(HasScalarType::Scalar)] +impl AreaCalculator { + fn area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar { + width * height + } +} +``` + +The provider's `#[use_type]` adds `Self: HasScalarType` to its `where` clause and rewrites each `Scalar` to the qualified path, so the implicit `width` and `height` fields and the return value all agree on the context's chosen scalar type. A concrete context then wires `HasScalarType` to a concrete type with `UseType` and supplies the fields, and `area()` works without any reference to associated-type syntax in the user's own code. + +## Related constructs + +`#[use_type]` is most often paired with [`#[cgp_type]`](../macros/cgp_type.md), which defines the abstract type trait it imports, and with the [`UseType` provider](../providers/use_type.md), which a context uses to bind that abstract type to a concrete one. It applies to all three implementation macros — [`#[cgp_fn]`](../macros/cgp_fn.md), [`#[cgp_impl]`](../macros/cgp_impl.md), and [`#[cgp_component]`](../macros/cgp_component.md) — adjusting whether it emits a supertrait or a `where` bound based on which it annotates. It overlaps in role with [`#[extend]`](extend.md), which adds a supertrait bound without rewriting type identifiers; `#[use_type]` is preferred when the imported type is actually mentioned in signatures, since it also performs the substitution. The abstract type itself is read through [`HasType`](../components/has_type.md) at the provider level. + +## Source + +The attribute is parsed by `UseTypeAttribute` in [crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs), with per-type entries (`as` alias and `=` equality) in `ident.rs`. The two-phase transform — substitute then add bounds — lives in `attributes.rs` as `transform_item_trait` (supertrait for `#[cgp_component]`) and `transform_item_impl` (`where` bound for impls), and the type-equality and foreign-context resolution are in `type_predicates.rs`. The identifier substitution itself is the `SubstituteAbstractType` `VisitMut` pass in [crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs](../../../crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs), which matches single-segment, argument-free type paths. The `= ...` rejection for component traits is enforced in `types/attributes/cgp_component_attributes.rs`. Expansion snapshots covering the single, foreign (`@`), alias, equality, and cross-spec equality forms are in [crates/tests/cgp-tests/tests/cgp_fn_tests/type_equality.rs](../../../crates/tests/cgp-tests/tests/cgp_fn_tests/type_equality.rs) and [foreign_type.rs](../../../crates/tests/cgp-tests/tests/cgp_fn_tests/foreign_type.rs). diff --git a/docs/reference/attributes/uses.md b/docs/reference/attributes/uses.md new file mode 100644 index 00000000..bfec8283 --- /dev/null +++ b/docs/reference/attributes/uses.md @@ -0,0 +1,108 @@ +# `#[uses(...)]` + +`#[uses(...)]` adds simple `Self: Trait<...>` bounds to a provider's `where` clause, written to read like a `use` import of the CGP capabilities the body depends on. + +## Purpose + +`#[uses(...)]` exists to make impl-side dependencies look like imports rather than trait bounds. A provider often calls capabilities defined elsewhere — another [`#[cgp_fn]`](../macros/cgp_fn.md) trait, or a [`#[cgp_component]`](../macros/cgp_component.md) consumer trait — and to do so it must require the context to implement them. Expressed in raw Rust, that is a `where Self: SomeTrait` clause, which is unfamiliar territory for many programmers: writing a bound on `Self` is uncommon in everyday Rust, and it reads as machinery rather than intent. + +`#[uses(RectangleArea)]` instead reads as "this function uses the `RectangleArea` capability," which mirrors the mental model of a `use` statement bringing a name into scope. The attribute lists the capabilities the body relies on; the macro turns each into the corresponding `Self` bound on the generated impl. The body can then call those methods directly on `self`, exactly as if they had been imported. + +This framing is the reason `#[uses(...)]` is recommended over hand-written `Self` bounds in basic CGP. It keeps the dependency declaration close in spirit to the `use` statements a reader already understands, and it keeps the code focused on *what* the provider needs rather than *how* the bound is spelled. + +## Syntax + +`#[uses(...)]` takes a comma-separated list of trait references, each in the simplified form `TraitIdent`: + +```rust +#[uses(RectangleArea, CanCalculateArea)] +``` + +Each entry is the name of a capability, optionally with generic type arguments. A bare `RectangleArea` becomes `Self: RectangleArea`; a parameterized `CanCompute` becomes `Self: CanCompute`. The entries may be split across multiple `#[uses(...)]` attributes on the same item, and they accumulate. + +The syntax deliberately supports only this simplified trait-path form. Because the attribute is meant to read like an import, it does not accept the more complex bound forms that Rust allows in a `where` clause — in particular, associated-type-equality bounds such as `Iterator` are not expressible here. When a dependency genuinely needs such a bound, write it as an explicit `where` clause in the function body instead; `#[uses(...)]` is for the common, import-shaped case. + +`#[uses(...)]` is accepted in both [`#[cgp_fn]`](../macros/cgp_fn.md) and [`#[cgp_impl]`](../macros/cgp_impl.md). In either case it imports capabilities into the provider being defined, and those capabilities may themselves be defined with either [`#[cgp_fn]`](../macros/cgp_fn.md) or [`#[cgp_component]`](../macros/cgp_component.md) — the attribute does not care how the imported capability was produced, only that it is a trait the context can implement. + +## Expansion + +`#[uses(...)]` adds one `Self`-anchored predicate to the generated impl's `where` clause for the listed bounds, and changes nothing about the trait definition. Starting from two `#[cgp_fn]` capabilities where the second depends on the first: + +```rust +#[cgp_fn] +fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} + +#[cgp_fn] +#[uses(RectangleArea)] +fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { + self.rectangle_area() * scale_factor * scale_factor +} +``` + +the second definition expands so that the trait is unchanged but the impl gains a `Self: RectangleArea` predicate, sitting alongside the `HasField` bound that the implicit `scale_factor` argument introduces: + +```rust +pub trait ScaledRectangleArea { + fn scaled_rectangle_area(&self) -> f64; +} + +impl ScaledRectangleArea for Context +where + Self: RectangleArea, + Self: HasField, +{ + fn scaled_rectangle_area(&self) -> f64 { + let scale_factor: f64 = + self.get_field(PhantomData::).clone(); + + self.rectangle_area() * scale_factor * scale_factor + } +} +``` + +The imported bound lands on the impl only, never on the trait — it is an impl-side dependency, hidden from anyone who merely uses `ScaledRectangleArea`. Writing `#[uses(RectangleArea)]` is therefore exactly equivalent to writing `where Self: RectangleArea` in the function body; the two desugar identically. The generated context type is named `__Context__` in the real output, shown as `Context` here for readability. + +Inside [`#[cgp_impl]`](../macros/cgp_impl.md) the behavior is the same. The listed bounds are appended to the impl's `where` clause as `Self` predicates, so a provider can import a `#[cgp_fn]` capability and call it directly: + +```rust +#[cgp_impl(new RectangleAreaCalculator)] +#[uses(RectangleArea)] +impl AreaCalculator { + fn area(&self) -> f64 { + self.rectangle_area() + } +} +``` + +This adds `Self: RectangleArea` to the `RectangleAreaCalculator` impl, letting `area` call `self.rectangle_area()`. The component remains usable by any context that implements `RectangleArea`, regardless of which provider supplies it. + +## Examples + +`#[uses(...)]` is the natural way to build a capability on top of a CGP component. Given an `AreaCalculator` component, a scaled-area function can import it and use it without knowing which provider is wired: + +```rust +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_fn] +#[uses(CanCalculateArea)] +pub fn scaled_area(&self, #[implicit] scale_factor: f64) -> f64 { + self.area() * scale_factor * scale_factor +} +``` + +The `#[uses(CanCalculateArea)]` attribute adds `Self: CanCalculateArea` to the generated `ScaledArea` impl, so the body may call `self.area()`. Any context that implements `CanCalculateArea` — through whatever `AreaCalculator` provider it has wired — automatically gains `scaled_area`, because the dependency is on the consumer trait rather than on a specific provider. + +## Related constructs + +`#[uses(...)]` is the impl-side counterpart to [`#[extend]`](extend.md): both add trait bounds to a generated construct, but `#[uses(...)]` adds hidden impl-side `where` bounds while `#[extend]` adds public supertraits — the `use` versus `pub use` distinction. It is used inside [`#[cgp_fn]`](../macros/cgp_fn.md) and [`#[cgp_impl]`](../macros/cgp_impl.md), and the capabilities it imports are typically defined with [`#[cgp_fn]`](../macros/cgp_fn.md) or [`#[cgp_component]`](../macros/cgp_component.md). To import an abstract associated type rather than a method capability, use [`#[use_type]`](use_type.md); to bring in a context field as a function argument, use [`#[implicit]`](implicit.md). For bounds too complex for the simplified syntax — anything with associated-type equality — fall back to an explicit `where` clause in the body. + +## Source + +`#[uses(...)]` parsing lives in [crates/macros/cgp-macro-core/src/types/attributes/uses.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/uses.rs) (the `UsesAttributes` type and its `to_type_param_bounds`), with the attribute dispatch in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) for `#[cgp_fn]` and [crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs) for `#[cgp_impl]`. The bounds are added to the impl `where` clause in [crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs). Expansion snapshots are in [crates/tests/cgp-tests/tests/cgp_fn_tests/uses.rs](../../../crates/tests/cgp-tests/tests/cgp_fn_tests/uses.rs) and [crates/tests/cgp-tests/tests/component_tests/cgp_impl/implicit_args/import.rs](../../../crates/tests/cgp-tests/tests/component_tests/cgp_impl/implicit_args/import.rs). diff --git a/docs/reference/components/can_raise_error.md b/docs/reference/components/can_raise_error.md new file mode 100644 index 00000000..0debe3a6 --- /dev/null +++ b/docs/reference/components/can_raise_error.md @@ -0,0 +1,80 @@ +# `CanRaiseError` + +`CanRaiseError` is the consumer trait for turning a concrete source error into a context's abstract `Self::Error`, with the companion `CanWrapError` adding detail to an existing abstract error; both build on [`HasErrorType`](has_error_type.md). + +## Purpose + +`CanRaiseError` exists so that generic CGP code can produce its context's abstract error from any concrete error it encounters. A provider that calls a fallible operation gets back a specific error type — a parse error, an I/O error, a string message — but it must return the context's abstract `Self::Error`, whose concrete identity it does not know. `CanRaiseError` bridges the gap: it converts a value of the concrete `SourceError` into `Self::Error`, so generic code can write `Context::raise_error(source)` and let the context decide how that source maps into its chosen error type. Because the trait is parameterized by `SourceError`, a single context can know how to raise many different source errors into the one abstract error. + +`CanWrapError` solves the complementary problem of enriching an error as it propagates. Rather than converting a foreign error in, it takes an error the context already holds and attaches a piece of `Detail` to it — a context message, a span, a path — producing an enriched `Self::Error`. The two together cover the common error-handling motions in CGP: raise a foreign error into the abstract one, and wrap context onto it as it bubbles up. + +## Definition + +Both traits supertrait [`HasErrorType`](has_error_type.md), so `Self::Error` refers to the context's shared abstract error type. Each is a `#[cgp_component]`, making it a full component with a generated provider trait. `CanRaiseError` converts a source error into the abstract error: + +```rust +#[cgp_component(ErrorRaiser)] +#[prefix(@cgp.core.error in DefaultNamespace)] +#[derive_delegate(UseDelegate)] +pub trait CanRaiseError: HasErrorType { + fn raise_error(error: SourceError) -> Self::Error; +} +``` + +The `SourceError` parameter is the concrete error being raised, and `raise_error` is an associated function — it takes the source error by value and returns `Self::Error`, without needing a `self` receiver, because raising an error is a property of the context type rather than of any particular value. The `#[cgp_component(ErrorRaiser)]` attribute names the provider trait `ErrorRaiser`, and [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) wires `UseDelegate` so the raise behavior can be dispatched per source-error type through a delegation table — a context can handle each `SourceError` with a different provider. + +`CanWrapError` has the same shape but takes an existing error plus a detail: + +```rust +#[cgp_component(ErrorWrapper)] +#[prefix(@cgp.core.error in DefaultNamespace)] +#[derive_delegate(UseDelegate)] +pub trait CanWrapError: HasErrorType { + fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; +} +``` + +Here `wrap_error` takes the context's current `Self::Error` and a `Detail` value and returns a new `Self::Error` with the detail folded in. Its provider trait is `ErrorWrapper`, and it delegates per `Detail` type, so wrapping a string message and wrapping a structured detail can be handled by different providers. + +## Behavior + +A context gains these capabilities by wiring `ErrorRaiserComponent` and `ErrorWrapperComponent` to providers, exactly as for any other component. Because both traits delegate through `UseDelegate` and `UseDelegate`, the natural wiring is a delegation table that maps each concrete source-error or detail type to a provider that knows how to handle it; a context can therefore raise a handful of unrelated error types into one abstract error, each through its own provider. The pluggable error backends (`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`) supply providers that implement these traits for common cases, so an application usually wires a backend rather than writing the raise and wrap logic itself. + +Both traits being associated-function components means `raise_error` and `wrap_error` are called on the context *type* — `Context::raise_error(source)` — and produce the abstract error without borrowing the context value. This matches how errors are typically constructed deep inside generic code where only the type parameter is in scope. + +## Examples + +A provider raises a concrete error into the abstract one and wraps a message onto it as it propagates: + +```rust +use cgp::prelude::*; + +#[cgp_component(Loader)] +pub trait CanLoad: HasErrorType { + fn load(&self, path: &str) -> Result; +} + +#[cgp_impl(new LoadOrFail)] +impl Loader for Context +where + Context: CanRaiseError + CanWrapError, +{ + fn load(&self, path: &str) -> Result { + if path.is_empty() { + let err = Context::raise_error("empty path".to_owned()); + return Err(Context::wrap_error(err, format!("while loading {path}"))); + } + Ok(format!("contents of {path}")) + } +} +``` + +The provider names neither the context nor its concrete error type. It requires `CanRaiseError` to turn a `String` message into the abstract error and `CanWrapError` to attach further context, and any wired context that satisfies those bounds — typically by plugging in an error backend — makes `load` produce errors in that context's chosen error type. + +## Related constructs + +`CanRaiseError` and `CanWrapError` both supertrait [`HasErrorType`](has_error_type.md), whose abstract `Self::Error` they produce and enrich. Their delegation is configured by [`#[derive_delegate(UseDelegate<...>)]`](../attributes/derive_delegate.md), so a context dispatches per source-error or detail type through a delegation table. Both are ordinary `#[cgp_component]` components, wired with `delegate_components!` and checked with `check_components!`. The [modular error handling](../../concepts/modular-error-handling.md) concept frames how these capabilities, the abstract error type, and the strategy providers combine into wiring-time error-handling decisions. + +## Source + +`CanRaiseError` is defined in [crates/core/cgp-error/src/traits/can_raise_error.rs](../../../crates/core/cgp-error/src/traits/can_raise_error.rs) and `CanWrapError` in [crates/core/cgp-error/src/traits/can_wrap_error.rs](../../../crates/core/cgp-error/src/traits/can_wrap_error.rs). Both build on `HasErrorType` from [crates/core/cgp-error/src/traits/has_error_type.rs](../../../crates/core/cgp-error/src/traits/has_error_type.rs). The pluggable providers that implement them live in [crates/standalone/error/](../../../crates/standalone/error/). Behavioral tests are in [crates/tests/cgp-tests](../../../crates/tests/cgp-tests). diff --git a/docs/reference/components/computer.md b/docs/reference/components/computer.md new file mode 100644 index 00000000..826372e9 --- /dev/null +++ b/docs/reference/components/computer.md @@ -0,0 +1,109 @@ +# `Computer` + +`Computer` and its siblings are the pure-computation corner of the [handler family](../../concepts/handlers.md): infallible components that transform an `Input` into an `Output` under a phantom `Code` tag, in synchronous and async forms, taking the input either by value or by reference. + +## Purpose + +The computer components exist for computations that always succeed and need no error type. A computation that adds two numbers, formats a value, or projects a field never fails and never needs the context's abstract error, so forcing it to return a `Result` would be noise. `Computer` captures exactly this case: a provider names an `Output` type and produces it from the context, a `Code` tag, and an `Input`, with no failure path. It is the simplest member of the handler family and the one a provider author reaches for first, because a pure computation can always be promoted into the fallible or general variants when the wiring needs them — but a fallible provider cannot be demoted back. + +Within the pure-computation corner, the components vary along two of the family's axes: synchronous versus async, and owned input versus by-reference input. `Computer` is synchronous and takes its input by value. `AsyncComputer` is its async counterpart, declaring an `async` method for computations that must await — reading a socket, awaiting a timer — while still never failing. The `ComputerRef` and `AsyncComputerRef` variants borrow the input as `&Input` instead of consuming it, which suits a computation that only reads its argument and should not take ownership of it. All four share the same `Code`/`Output` model and differ only on these two axes. + +## Definition + +Each computer component is a CGP component defined with `#[cgp_component]`, pairing a consumer trait that a context calls with a provider trait that a provider implements. The synchronous, owned-input component is `Computer`, whose consumer trait is `CanCompute`: + +```rust +#[cgp_component(Computer)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanCompute { + type Output; + + fn compute(&self, _code: PhantomData, input: Input) -> Self::Output; +} +``` + +The consumer trait `CanCompute` is what a context implements and callers invoke. Its `compute` method takes `&self` (the context), a `PhantomData` that names which computation is wanted, and the `Input` value by ownership, returning the associated `Output`. The component is wired through the generated `ComputerComponent` marker, and the provider trait generated by the macro is `Computer` — the same method with the context moved into an explicit first parameter. The two `#[derive_delegate(...)]` attributes generate dispatching providers that key on the `Code` type and on the `Input` type respectively, as described under [dispatching](../../concepts/dispatching.md). + +The by-reference sibling `ComputerRef` is identical except that it borrows the input. Its consumer trait `CanComputeRef` declares `fn compute_ref(&self, _code: PhantomData, input: &Input) -> Self::Output`, taking `&Input` where `CanCompute` takes `Input`. + +The async variants mirror the synchronous ones with an `async` method, declared under `#[async_trait]`: + +```rust +#[async_trait] +#[cgp_component(AsyncComputer)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanComputeAsync { + type Output; + + async fn compute_async(&self, _code: PhantomData, input: Input) -> Self::Output; +} +``` + +`CanComputeAsync` is the async counterpart of `CanCompute`, with `compute_async` declared `async`; `#[async_trait]` rewrites the `async fn` into a method returning `impl Future`, so there is no boxing. Its provider trait is `AsyncComputer` and its marker `AsyncComputerComponent`. The by-reference async variant `AsyncComputerRef` declares `async fn compute_async_ref(&self, _code: PhantomData, input: &Input) -> Self::Output`, borrowing the input. None of the four computer components supertrait `HasErrorType`, because none of them can fail. + +## Implementations + +A computer provider is an ordinary CGP provider: a zero-sized struct that implements the provider trait for a generic context. Because the `Output` is an associated type, the provider chooses what it returns given a `Code` and `Input`. The crate ships one built-in provider for the owned-input computers, `UseField`, which reads a field from the context and delegates the computation to the value stored there: + +```rust +impl Computer for UseField +where + Context: HasField, + Context::Value: CanCompute, +{ + type Output = Output; + + fn compute(context: &Context, code: PhantomData, input: Input) -> Output { + context.get_field(PhantomData).compute(code, input) + } +} +``` + +This makes a context compute by forwarding to one of its fields: wiring `ComputerComponent` to `UseField` makes the context's `CanCompute` call the `CanCompute` of the value held in the field named `Tag`. `AsyncComputer` has the analogous `UseField` provider that forwards to the field's `compute_async`. Beyond `UseField`, the [handler combinators](../providers/handler_combinators.md) supply providers that build computers from other handlers — `ReturnInput` returns its input unchanged as the output, `ComposeHandlers` feeds one computer's output into the next, and the `Promote*` family lifts a `Computer` into the async, fallible, and by-reference variants. + +A provider that implements only `Computer` can serve all the other handler components through promotion. Lifting a synchronous computer to `AsyncComputer` wraps it in a future that never awaits; lifting it to `TryComputer` or `Handler` wraps its output in `Ok`; lifting it to a `*Ref` variant supplies an owned input by dereferencing the borrow. These promotions are provided by the combinators rather than by `Computer` itself, so an author implements the single synchronous provider and lets the wiring promote it. + +## Examples + +A self-contained computer provider that doubles its input, wired into a context and invoked through the consumer trait, looks as follows: + +```rust +use core::marker::PhantomData; +use cgp::prelude::*; +use cgp::extra::handler::{CanCompute, Computer, ComputerComponent}; + +#[cgp_new_provider] +impl Computer for Double { + type Output = u64; + + fn compute(_context: &Context, _code: PhantomData, input: u64) -> u64 { + input * 2 + } +} + +#[derive(HasField)] +pub struct App; + +delegate_components! { + App { + ComputerComponent: Double, + } +} + +// `App` now implements `CanCompute<(), u64, Output = u64>`: +fn run(app: &App) -> u64 { + app.compute(PhantomData::<()>, 21) // returns 42 +} +``` + +Here `Double` implements the `Computer` provider trait for any context and any `Code`, fixing `Input` and `Output` to `u64`. The context `App` delegates `ComputerComponent` to `Double`, which gives it `CanCompute<(), u64>`, and the call passes `PhantomData::<()>` as the `Code` tag. In practice a provider like this is rarely written by hand — the [`#[cgp_computer]`](../macros/cgp_computer.md) macro turns a plain function such as `fn double(input: u64) -> u64` into exactly this provider and additionally wires the promotion table so the same function answers `CanComputeAsync`, `CanTryCompute`, `CanHandle`, and the `Ref` variants too. + +## Related constructs + +The computer components are the infallible corner of the [handler family](../../concepts/handlers.md), whose overview explains the three axes that distinguish them from the fallible and general variants. Their fallible counterpart is [`TryComputer`](try_computer.md), which adds a `Result` return and supertraits [`HasErrorType`](has_error_type.md); the fully general async-and-fallible component is [`Handler`](handler.md); and the no-input case is [`Producer`](producer.md). The built-in `UseField` provider is the computer-specific use of [`UseField`](../providers/use_field.md). Promoting a computer into the other variants, composing computers, and the `ReturnInput` provider are covered in [handler combinators](../providers/handler_combinators.md), and the function-to-provider macro is [`#[cgp_computer]`](../macros/cgp_computer.md). Dispatching a computer on its `Code` or `Input` uses the [dispatching](../../concepts/dispatching.md) mechanism via `UseDelegate` and `UseInputDelegate`. + +## Source + +The synchronous computers `Computer`/`ComputerRef` are defined in [crates/extra/cgp-handler/src/components/computer.rs](../../../crates/extra/cgp-handler/src/components/computer.rs), and the async computers `AsyncComputer`/`AsyncComputerRef` in [crates/extra/cgp-handler/src/components/async_computer.rs](../../../crates/extra/cgp-handler/src/components/async_computer.rs). The `UseInputDelegate` dispatch type is in [crates/extra/cgp-handler/src/types.rs](../../../crates/extra/cgp-handler/src/types.rs). The components are re-exported through `cgp::extra::handler`. Behavioral tests exercising computers and their promotions are in [crates/tests/cgp-tests/tests/handler_tests/computer_macro.rs](../../../crates/tests/cgp-tests/tests/handler_tests/computer_macro.rs). diff --git a/docs/reference/components/handler.md b/docs/reference/components/handler.md new file mode 100644 index 00000000..2adc0f6e --- /dev/null +++ b/docs/reference/components/handler.md @@ -0,0 +1,86 @@ +# `Handler` + +`Handler` and `HandlerRef` are the most general members of the [handler family](../../concepts/handlers.md): asynchronous, fallible components that transform an `Input` into an `Output` under a phantom `Code` tag, returning a `Result` against the context's abstract error type. + +## Purpose + +`Handler` exists for the computations that need everything the family offers — to run asynchronously *and* to be able to fail. A handler that calls a remote service must await the response and must report failures, so it occupies the corner of the family that is both async and fallible. Every other handler component is a special case obtained by dropping one of these capabilities: drop the failure path and a `Handler` becomes an [`AsyncComputer`](computer.md), drop the asynchrony and it becomes a [`TryComputer`](try_computer.md), drop both and it becomes a [`Computer`](computer.md). `Handler` is therefore the component a generic consumer bounds against when it wants to accept *any* computation regardless of which capabilities the underlying provider actually uses, because every simpler provider can be promoted up to a `Handler`. + +This generality is why the family promotes toward `Handler` rather than away from it. A pure synchronous computer can serve as a handler that neither awaits nor errors; a fallible computer can serve as a handler that awaits trivially; an async computer can serve as a handler that never errors. Each of these is a safe widening, and the combinators perform them automatically, so a provider author writes against the weakest variant that fits and the wiring lifts it to `Handler` wherever a handler is required. The reverse — using a `Handler` where only a `Computer` is wanted — is not possible, since a general computation cannot be assumed pure or synchronous. + +## Definition + +`Handler` is a CGP component defined with `#[cgp_component]` under `#[async_trait]`, and its consumer trait `CanHandle` supertraits `HasErrorType` so its method can return the context's abstract error: + +```rust +#[async_trait] +#[cgp_component(Handler)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanHandle: HasErrorType { + type Output; + + async fn handle( + &self, + _tag: PhantomData, + input: Input, + ) -> Result; +} +``` + +The consumer trait `CanHandle` combines the async and fallible refinements of the base signature. Its `handle` method is declared `async` and returns `Result`, so it is the async counterpart of `CanTryCompute` and the fallible counterpart of `CanComputeAsync`. The `#[async_trait]` attribute rewrites the `async fn` into a method returning `impl Future>`, avoiding any boxed future. The component is wired through the generated `HandlerComponent` marker, its provider trait is `Handler` with the context moved into an explicit first parameter, and the two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. The `HasErrorType` supertrait supplies the `Self::Error` named in the result. + +The by-reference sibling `HandlerRef` is identical except that it borrows its input. Its consumer trait `CanHandleRef` also supertraits `HasErrorType` and declares `async fn handle_ref(&self, _tag: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanHandle` takes `Input`. + +## Implementations + +A `Handler` provider is a zero-sized struct implementing the provider trait for a generic context with an error type, returning a future that resolves to `Result`. The crate's `ReturnInput` provider shows the minimal async-and-fallible shape — it awaits nothing and succeeds unconditionally: + +```rust +impl Handler for ReturnInput +where + Context: HasErrorType, +{ + type Output = Input; + + async fn handle( + _context: &Context, + _code: PhantomData, + input: Input, + ) -> Result { + Ok(input) + } +} +``` + +Because `Handler` is the top of the promotion lattice, most `Handler` implementations are produced by promoting a simpler provider rather than written directly. A [`Computer`](computer.md) is promoted to `Handler` by wrapping its output in a non-awaiting future that returns `Ok` (`Promote`); a [`TryComputer`](try_computer.md) is promoted by wrapping its result in a non-awaiting future (`PromoteAsync`); an [`AsyncComputer`](computer.md) is promoted by wrapping its awaited output in `Ok` (`Promote`); and a `Computer` whose output is already a `Result` is promoted by unwrapping that result inside a future (`TryPromote`). The `PromoteRef` combinator additionally bridges between `Handler` and `HandlerRef` by dereferencing or re-borrowing the input. These promotions are the subject of [handler combinators](../providers/handler_combinators.md); a provider author rarely implements `Handler` by hand and instead lets the wiring lift the narrowest fitting variant. + +## Examples + +A generic consumer that bounds its context by `CanHandle` accepts any wired computation, whatever its underlying capabilities: + +```rust +use core::marker::PhantomData; +use cgp::prelude::*; +use cgp::extra::handler::CanHandle; + +async fn run_with( + context: &Context, + input: String, +) -> Result +where + Context: CanHandle, +{ + context.handle(PhantomData::, input).await +} +``` + +The function `run_with` works for any context that wires a handler for the given `Code` and `String` input, whether the wired provider is a pure `Computer`, a fallible `TryComputer`, an `AsyncComputer`, or a genuine `Handler` — the promotion combinators make each of them satisfy `CanHandle`. This is why generic pipeline code targets `Handler`: it is the one bound every member of the family can meet. In practice the [`#[cgp_computer]`](../macros/cgp_computer.md) and [`#[cgp_producer]`](../macros/cgp_producer.md) macros wire the promotion table so that a function written as a simple computer or producer answers `CanHandle` automatically, which is what lets such a consumer call it. + +## Related constructs + +`Handler` is the general corner of the [handler family](../../concepts/handlers.md), generalizing [`Computer`](computer.md) (drop fallibility and asynchrony), [`AsyncComputer`](computer.md) (drop fallibility), and [`TryComputer`](try_computer.md) (drop asynchrony). It supertraits [`HasErrorType`](has_error_type.md), which supplies the `Self::Error` it returns. The combinators that promote the simpler variants up to `Handler`, and that bridge `Handler` with `HandlerRef`, are documented in [handler combinators](../providers/handler_combinators.md), and chaining handlers into pipelines is covered in [monadic handlers](../../concepts/monadic-handlers.md). The no-input member of the family is [`Producer`](producer.md). Dispatching a handler on its `Code` or `Input` uses [`UseDelegate`](../providers/use_delegate.md) and the family's `UseInputDelegate`, per [dispatching](../../concepts/dispatching.md). + +## Source + +`Handler` and `HandlerRef` are defined in [crates/extra/cgp-handler/src/components/handler.rs](../../../crates/extra/cgp-handler/src/components/handler.rs). The `ReturnInput` provider is in [crates/extra/cgp-handler/src/providers/return_input.rs](../../../crates/extra/cgp-handler/src/providers/return_input.rs), and the promotion combinators that lift simpler providers into `Handler` are in [crates/extra/cgp-handler/src/providers/](../../../crates/extra/cgp-handler/src/). The components are re-exported through `cgp::extra::handler`. Behavioral tests exercising handlers and their promotions are in [crates/tests/cgp-tests/tests/handler_tests/handler_macro.rs](../../../crates/tests/cgp-tests/tests/handler_tests/handler_macro.rs). diff --git a/docs/reference/components/has_error_type.md b/docs/reference/components/has_error_type.md new file mode 100644 index 00000000..cc65dbcb --- /dev/null +++ b/docs/reference/components/has_error_type.md @@ -0,0 +1,72 @@ +# `HasErrorType` + +`HasErrorType` is the abstract-type component that gives a context a single, shared abstract `Error` type, decoupling CGP code from any concrete error implementation, with `ErrorOf` as the alias for the resolved type. + +## Purpose + +`HasErrorType` exists so that generic CGP code can fail without naming a concrete error type. A provider that may error needs to produce *some* error, but it is generic over the context and cannot commit to `anyhow::Error`, `std::io::Error`, or any other concrete choice; that choice belongs to the application assembling the context. `HasErrorType` resolves this by giving the context one abstract `Self::Error` type that every fallible operation refers to. Generic code returns `Result` (or `ErrorOf`), and the concrete error type is decided once, at wiring time, by whichever error backend the context plugs in. + +Centralizing the error type on one trait also avoids ambiguity. If each context trait declared its own associated `Error`, a context bounded by several of them would face multiple distinct `Self::Error` types with no way to unify them. By having context traits supertrait `HasErrorType`, every fallible component refers to the *same* `Self::Error`, so errors compose across components. This single shared abstract error is the anchor that [`CanRaiseError`](can_raise_error.md) and [`CanWrapError`](can_raise_error.md) build on. + +## Definition + +`HasErrorType` is an abstract-type component: a trait with one associated type, defined with [`#[cgp_type]`](../macros/cgp_type.md). Its source is: + +```rust +#[cgp_type] +#[prefix(@cgp.core.error in DefaultNamespace)] +pub trait HasErrorType { + type Error: Debug; +} + +pub type ErrorOf = ::Error; +``` + +The associated `Error` type is the context's abstract error, and its `Debug` bound is required so that `Self::Error` can be used in `.unwrap()` calls and in straightforward error logging without a separate constraint. The `ErrorOf` alias is the convenient spelling of `::Error`, used wherever writing the full associated-type path would be noise. + +Because the trait is declared with `#[cgp_type]`, it is a full abstract-type component rather than a plain trait: the macro generates a provider trait (`ErrorTypeProvider`), the component marker, the consumer and provider blanket impls, and a [`UseType`](../providers/use_type.md) blanket impl. The `Debug` bound on `Error` is carried through every generated construct, so any concrete error wired in must implement `Debug`. The `#[prefix(...)]` attribute places the generated names in the error namespace. + +## Behavior + +A context obtains its abstract error type either by implementing `HasErrorType` directly — `impl HasErrorType for App { type Error = anyhow::Error; }` — or, more commonly, by wiring its error-type component to a provider. Because `#[cgp_type]` generates the `UseType` impl, a context can name the concrete error type directly in its wiring with `UseType`, and the standalone error backends (the `cgp-error-anyhow`, `cgp-error-eyre`, and `cgp-error-std` crates) provide ready-made providers that set `Error` to their respective error types. Once the context has `HasErrorType`, every component bounded by it shares that one `Self::Error`. + +`HasErrorType` carries no methods of its own — it only declares the type. The behavior of producing errors lives in the traits that supertrait it: [`CanRaiseError`](can_raise_error.md) converts a source error into `Self::Error`, and [`CanWrapError`](can_raise_error.md) adds detail to an existing `Self::Error`. This separation keeps the type declaration independent of any particular way of constructing the error. + +## Examples + +A context declares its abstract error and generic code returns it without naming a concrete type: + +```rust +use cgp::prelude::*; + +#[cgp_component(Validator)] +pub trait CanValidate: HasErrorType { + fn validate(&self) -> Result<(), Self::Error>; +} + +pub struct App; + +delegate_components! { + App { + ErrorTypeProviderComponent: UseType, + } +} +``` + +Here `CanValidate` supertraits `HasErrorType`, so its `Result<(), Self::Error>` refers to the context's shared abstract error. `App` wires its error-type component to `UseType`, fixing `Self::Error` to `String` (which satisfies the `Debug` bound). The same abstract error can equally be implemented directly: + +```rust +impl HasErrorType for App { + type Error = String; +} +``` + +This direct form makes plain that `HasErrorType` is an ordinary trait with a `Debug`-bounded associated type. + +## Related constructs + +`HasErrorType` is defined with [`#[cgp_type]`](../macros/cgp_type.md), which makes it an abstract-type component and generates its `UseType` provider; it is therefore a concrete instance of the [`HasType`/`TypeProvider`](has_type.md) machinery that `#[cgp_type]` builds every abstract type on. The traits that give the abstract error its behavior are [`CanRaiseError`](can_raise_error.md), which raises a source error into `Self::Error`, and [`CanWrapError`](can_raise_error.md), which wraps additional detail onto it — both supertrait `HasErrorType`. The [modular error handling](../../concepts/modular-error-handling.md) concept ties this trait together with those capabilities and the providers that satisfy them into the wider error-handling strategy. + +## Source + +The trait and the `ErrorOf` alias are defined in [crates/core/cgp-error/src/traits/has_error_type.rs](../../../crates/core/cgp-error/src/traits/has_error_type.rs). The `#[cgp_type]` machinery it relies on lives in [crates/macros/cgp-macro-core/src/types/cgp_type/](../../../crates/macros/cgp-macro-core/src/types/cgp_type/), and the underlying `HasType`/`TypeProvider`/`UseType` definitions are in [crates/core/cgp-type/src/](../../../crates/core/cgp-type/src/). The pluggable concrete error backends are in [crates/standalone/error/](../../../crates/standalone/error/). Behavioral tests are in [crates/tests/cgp-tests](../../../crates/tests/cgp-tests). diff --git a/docs/reference/components/has_runtime.md b/docs/reference/components/has_runtime.md new file mode 100644 index 00000000..cffceca1 --- /dev/null +++ b/docs/reference/components/has_runtime.md @@ -0,0 +1,91 @@ +# `HasRuntime` + +`HasRuntime` is the runtime abstraction in CGP: a pair of components that lets a context declare an abstract runtime *type* through `HasRuntimeType` and hand out a borrow of the runtime *value* through `HasRuntime`, with `RuntimeOf` as the alias for the resolved runtime type. + +## Purpose + +`HasRuntime` exists so that context-generic code can run asynchronous and effectful operations against a runtime without committing to a concrete one. A runtime in this sense is whatever object provides the capabilities an application needs at execution time — spawning tasks, sleeping, opening sockets, reading the clock — and different deployments want different runtimes (Tokio in production, a mock in tests, a single-threaded executor in a benchmark). Rather than thread a concrete runtime type through every signature, CGP lets the context name an abstract `Runtime` type and store one runtime value, and lets providers reach it generically through these two traits. + +The abstraction is split deliberately into a type component and a getter component because the two questions are independent. `HasRuntimeType` answers *what* the runtime type is — an abstract associated type chosen per context — while `HasRuntime` answers *how to obtain the runtime value* of that type from a borrow of the context. Some code is generic only over the runtime type (it never touches a runtime value, only names types the runtime exposes); that code needs `HasRuntimeType` alone. Code that actually performs effects needs `HasRuntime`, which supertraits `HasRuntimeType` so the value's type is always in scope. Keeping them separate means a context can declare its runtime type in one place and supply the value in another, and a bound asks for exactly the capability it uses. + +This pair is the substrate beneath CGP's task-running components. The [`Runner`](runner.md) family expresses "run this task," and the providers that implement it typically reach the runtime through `HasRuntime` to spawn or await the work. `HasRuntime` is the seam where context-generic logic meets the concrete async machinery, which is why it underpins asynchronous execution across a CGP application. + +## Definition + +The two components are declared in `cgp-runtime`. `HasRuntimeType` is an abstract-type component defined with [`#[cgp_type]`](../macros/cgp_type.md): + +```rust +#[cgp_type] +pub trait HasRuntimeType { + type Runtime; +} + +pub type RuntimeOf = ::Runtime; +``` + +Because the trait carries no provider-name argument, `#[cgp_type]` derives the provider name from the associated type: `Runtime` yields the provider trait `RuntimeTypeProvider` and the component marker `RuntimeTypeProviderComponent`. The `Runtime` associated type carries no bound, so any concrete type may be plugged in. The `RuntimeOf` alias is the convenient spelling of the resolved runtime type, used wherever writing `::Runtime` in full would be noise. + +`HasRuntime` is a getter component defined with [`#[cgp_getter]`](../macros/cgp_getter.md), and it supertraits `HasRuntimeType` so the runtime type is available as the getter's return type: + +```rust +#[cgp_getter] +pub trait HasRuntime: HasRuntimeType { + fn runtime(&self) -> &Self::Runtime; +} +``` + +`#[cgp_getter]` derives the provider name from the trait name by stripping the `Has` prefix and appending `Getter`, so `HasRuntime` yields the provider trait `RuntimeGetter` and the component marker `RuntimeGetterComponent`. The `runtime` method borrows the runtime value out of a borrow of the context, returning `&Self::Runtime` — the abstract type supplied by `HasRuntimeType`. + +## Behavior + +`HasRuntimeType` behaves like any `#[cgp_type]` abstract-type component, so a context supplies its runtime *type* either by implementing `HasRuntimeType` directly or — far more commonly — by wiring `RuntimeTypeProviderComponent` to [`UseType`](../providers/use_type.md) in `delegate_components!`. Wiring `RuntimeTypeProviderComponent: UseType` makes the context resolve `HasRuntimeType` with `Runtime = TokioRuntime`, and from then on `RuntimeOf` is `TokioRuntime`. The full set of generated constructs — the consumer and provider blanket impls, the `UseContext` and `RedirectLookup` provider impls, and the `UseType`/`WithProvider` impls that make `UseType` resolve the type — is exactly the `#[cgp_type]` expansion described in [`#[cgp_type]`](../macros/cgp_type.md). + +`HasRuntime` behaves like any `#[cgp_getter]` getter component, so a context supplies its runtime *value* either by implementing `HasRuntime` directly or by wiring `RuntimeGetterComponent` to a [`UseField`](../providers/use_field.md) provider that names the field holding the runtime. Because `#[cgp_getter]` generates a `UseField` provider impl, a context that stores its runtime in a `runtime` field wires `RuntimeGetterComponent: UseField`, and the getter reads that field. The supertrait bound on `HasRuntimeType` means a context cannot satisfy `HasRuntime` without also having declared its runtime type, which keeps `&Self::Runtime` well-defined. + +Together the two components let a context fully describe its runtime: one wiring entry fixes the abstract type, another fixes where the value lives. Context-generic providers then write `where Self: HasRuntime` and call `self.runtime()` to obtain a `&RuntimeOf`, never naming the concrete runtime. This is what makes the same task-running and effect-performing code reusable across a Tokio context, a mock context, and a test context with no changes beyond the wiring. + +## Examples + +A context declares its runtime type and the field holding the runtime value, and a provider reaches the runtime generically: + +```rust +use cgp::prelude::*; +use cgp::extra::runtime::{HasRuntime, HasRuntimeType, RuntimeOf}; + +pub struct TokioRuntime { /* handle, clock, etc. */ } + +#[derive(HasField)] +pub struct App { + pub runtime: TokioRuntime, +} + +delegate_components! { + App { + RuntimeTypeProviderComponent: + UseType, + RuntimeGetterComponent: + UseField, + } +} +``` + +Here `App` resolves `HasRuntimeType` with `Runtime = TokioRuntime` through `UseType`, so `RuntimeOf` is `TokioRuntime`, and it resolves `HasRuntime` by reading its `runtime` field through `UseField`. A context-generic function can now demand only the capability it needs: + +```rust +fn runtime_of(context: &Context) -> &RuntimeOf +where + Context: HasRuntime, +{ + context.runtime() +} +``` + +The function names neither `TokioRuntime` nor any field; it works for any context that wires a runtime type and a runtime getter. Swapping `UseType` for `UseType` in a test context retargets every such function at the mock with no change to their bodies. + +## Related constructs + +`HasRuntimeType` is defined with [`#[cgp_type]`](../macros/cgp_type.md), which generates the abstract-type machinery and the [`UseType`](../providers/use_type.md) impl a context uses to fix its runtime type; it builds on the foundational [`HasType`/`TypeProvider`](has_type.md) substrate. `HasRuntime` is defined with [`#[cgp_getter]`](../macros/cgp_getter.md) and backed by a [`UseField`](../providers/use_field.md) provider that reads the runtime value from a named field. The [`Runner`](runner.md) family — `CanRun` and `CanSendRun` — is the primary consumer of this abstraction: task-running providers reach the runtime through `HasRuntime` to execute work asynchronously, which connects `HasRuntime` to the [handler](handler.md) family of effectful components. Both components are wired on a context with [`delegate_components!`](../macros/delegate_components.md). + +## Source + +`HasRuntimeType`, the `RuntimeTypeProvider` provider trait, and the `RuntimeOf` alias are defined in [crates/extra/cgp-runtime/src/traits/has_runtime_type.rs](../../../crates/extra/cgp-runtime/src/traits/has_runtime_type.rs). `HasRuntime` and its `RuntimeGetter` provider trait are in [crates/extra/cgp-runtime/src/traits/has_runtime.rs](../../../crates/extra/cgp-runtime/src/traits/has_runtime.rs), re-exported through [crates/extra/cgp-runtime/src/lib.rs](../../../crates/extra/cgp-runtime/src/lib.rs) and reached from the facade as `cgp::extra::runtime`. The `#[cgp_type]` and `#[cgp_getter]` expansions these rely on live under [crates/macros/cgp-macro-core/src/types/](../../../crates/macros/cgp-macro-core/src/types/). diff --git a/docs/reference/components/has_type.md b/docs/reference/components/has_type.md new file mode 100644 index 00000000..c49f9101 --- /dev/null +++ b/docs/reference/components/has_type.md @@ -0,0 +1,75 @@ +# `HasType` + +`HasType` is CGP's single built-in abstract-type component: a consumer trait that gives a context an abstract type named by a tag, with `TypeProvider` as its provider trait and `TypeOf` as the alias for the resolved type. + +## Purpose + +`HasType` exists to let generic code refer to a type that is chosen per context without committing to a concrete one. An abstract type in CGP is a trait with a single associated type, and `HasType` is the foundational, tag-indexed instance of that pattern: a context can carry many distinct abstract types — one per `Tag` — and resolve each to a concrete type through wiring. Generic code names `Self::Type` (or `TypeOf`), the concrete type stays hidden behind the tag, and any context that wires the tag to a type satisfies the bound. See [abstract types](../../concepts/abstract-types.md) for how this generalizes across a codebase. + +What makes `HasType` special is that it is the *only* abstract-type component built into CGP, and it is the machinery that [`#[cgp_type]`](../macros/cgp_type.md) defers to. Every named abstract-type component a user defines with `#[cgp_type]` is wired on top of this one through a generated `WithProvider` impl, so the same [`UseType`](../providers/use_type.md) marker that resolves `HasType` also resolves any `#[cgp_type]` component. `HasType` is the common substrate; `#[cgp_type]` is the ergonomic layer that gives each abstract type its own named trait. + +## Definition + +`HasType` is declared with `#[cgp_component]`, which makes it a full component — a consumer trait paired with a generated provider trait — rather than a plain trait. Its source is the entire definition: + +```rust +#[cgp_component(TypeProvider)] +#[derive_delegate(UseDelegate)] +pub trait HasType { + type Type; +} + +pub type TypeOf = >::Type; +``` + +The `Tag` parameter is the type-level name that distinguishes one abstract type from another within the same context, and `Type` is the associated type it resolves to. The `#[cgp_component(TypeProvider)]` attribute names the provider trait `TypeProvider`, so the provider-side mirror of `HasType` is `TypeProvider` with a `type Type`. The [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute wires `UseDelegate` so a type lookup can be dispatched per tag through a delegation table. The `TypeOf` alias is the convenient spelling of the resolved type, used wherever writing `>::Type` in full would be noise. + +## Behavior + +Because `HasType` is a `#[cgp_component]`, it carries the standard component machinery: a consumer blanket impl that forwards `HasType` to whatever provider the context wires for `TypeProviderComponent`, the generated `TypeProvider` provider trait, and the usual `UseContext` and `RedirectLookup` provider impls. A context obtains an abstract type either by implementing `HasType` directly or, more commonly, by wiring `TypeProviderComponent` to a provider in `delegate_components!`. + +The provider that makes wiring ergonomic is [`UseType`](../providers/use_type.md), a zero-sized marker `UseType(PhantomData)` that carries no runtime value. It implements `TypeProvider` for any context and tag by setting the abstract type to its own parameter: + +```rust +impl TypeProvider for UseType { + type Type = Type; +} +``` + +This single impl is what lets a context name a concrete type in its wiring rather than write a bespoke provider. Wiring `TypeProviderComponent: UseType` makes the context resolve `HasType` to `Type = String` for that tag. The same `UseType` is reused by every `#[cgp_type]` component: `#[cgp_type]` generates a `WithProvider` impl whose `where` clause requires the wired provider to be a `TypeProvider`, so `UseType` — being a `TypeProvider` — satisfies both the built-in `HasType` and any user-defined abstract-type component at once. The [`#[use_type]`](../attributes/use_type.md) attribute is the related but distinct construct that rewrites bare type names and adds `HasType` bounds inside `#[cgp_fn]`/`#[cgp_impl]` definitions; it is an attribute, not this provider. + +## Examples + +A direct use defines no new component and resolves an abstract type by tag through `UseType`: + +```rust +use cgp::prelude::*; + +pub struct ScalarTag; + +pub struct App; + +delegate_components! { + App { + TypeProviderComponent: UseType, + } +} + +fn zero() -> TypeOf +where + Context: HasType, + TypeOf: Default, +{ + Default::default() +} +``` + +`App` wires `TypeProviderComponent` to `UseType`, so the `UseType` impl makes `App` implement `HasType` with `Type = f64`. In most code you would not use `HasType` directly; you would define a named abstract type with [`#[cgp_type]`](../macros/cgp_type.md) — `#[cgp_type] trait HasScalarType { type Scalar; }` — which gives a readable `Self::Scalar` and its own provider, all resolving down to this `HasType` substrate. + +## Related constructs + +`HasType` is the foundation that [`#[cgp_type]`](../macros/cgp_type.md) builds every named abstract-type component on, via a generated `WithProvider` impl that adapts a `TypeProvider`. Its ergonomic provider is [`UseType`](../providers/use_type.md), the zero-sized marker that supplies a concrete type to the abstract one — not to be confused with the [`#[use_type]`](../attributes/use_type.md) attribute, which rewrites type names in definitions. The general idea of context-chosen types is covered in [abstract types](../../concepts/abstract-types.md). [`HasErrorType`](has_error_type.md) is a concrete abstract-type component defined with `#[cgp_type]` on top of this machinery. + +## Source + +The trait, the `TypeProvider` provider trait, and the `TypeOf` alias are defined in [crates/core/cgp-type/src/traits/has_type.rs](../../../crates/core/cgp-type/src/traits/has_type.rs). The `UseType` provider and its `TypeProvider` impl are in [crates/core/cgp-type/src/impls/use_type.rs](../../../crates/core/cgp-type/src/impls/use_type.rs). The `#[cgp_type]` macro that builds named components on this substrate lives in [crates/macros/cgp-macro-core/src/types/cgp_type/](../../../crates/macros/cgp-macro-core/src/types/cgp_type/). Behavioral and expansion-snapshot tests are in [crates/tests/cgp-tests/tests/component_tests/abstract_types/](../../../crates/tests/cgp-tests/tests/component_tests/abstract_types/). diff --git a/docs/reference/components/producer.md b/docs/reference/components/producer.md new file mode 100644 index 00000000..4b39aca8 --- /dev/null +++ b/docs/reference/components/producer.md @@ -0,0 +1,83 @@ +# `Producer` + +`Producer` is the no-input member of the [handler family](../../concepts/handlers.md): a synchronous, infallible component that produces an `Output` from the context and a phantom `Code` tag alone, with no `Input`. + +## Purpose + +`Producer` exists for computations that take nothing to compute and simply yield a value. A handler that supplies a default configuration, a constant, or a value read entirely from the context has no `Input` to transform — it only needs the context and a `Code` tag to know which value to produce. The rest of the handler family threads an `Input` through every method; `Producer` is the degenerate case where that input is absent. It is the simplest component in the family: synchronous, infallible, and inputless, producing an `Output` directly. + +Being inputless does not isolate `Producer` from the family — it makes it the natural source of values that flow into the other handlers. A producer can be promoted into any computer or handler by ignoring whatever input that variant supplies and returning the produced value regardless. This is exactly how a constant or a context-derived value enters a computation pipeline: it is produced once, independent of the input, and the promotion machinery adapts it to the input-taking shape the pipeline expects. + +## Definition + +`Producer` is a CGP component defined with `#[cgp_component]`, and its consumer trait `CanProduce` differs from the rest of the family only in carrying no `Input` parameter: + +```rust +#[cgp_component(Producer)] +#[derive_delegate(UseDelegate)] +pub trait CanProduce { + type Output; + + fn produce(&self, _code: PhantomData) -> Self::Output; +} +``` + +The consumer trait `CanProduce` takes only a `Code` tag, not an `Input`. Its `produce` method takes `&self` (the context) and a `PhantomData` naming which value to produce, returning the associated `Output`. The component is wired through the generated `ProducerComponent` marker, and the macro generates the provider trait `Producer` with the context moved into an explicit first parameter. Because there is no input to dispatch on, `Producer` carries only the single `#[derive_delegate(UseDelegate)]` attribute — dispatch is on the `Code` tag alone, with no `UseInputDelegate` counterpart. `Producer` does not supertrait `HasErrorType`: like `Computer`, it is infallible and synchronous, returning its `Output` directly rather than a `Result`. + +## Implementations + +A `Producer` provider is a zero-sized struct implementing the provider trait for a generic context, choosing the `Output` it yields. A producer that ignores the context and returns a constant has the minimal shape: + +```rust +#[cgp_new_provider] +impl Producer for MagicNumber { + type Output = u64; + + fn produce(_context: &Context, _code: PhantomData) -> u64 { + 42 + } +} +``` + +A producer is promoted into every other handler component by discarding the input that the target variant supplies and returning the produced value unchanged. The `Promote` combinator lifts a `Producer` into a [`Computer`](computer.md): the resulting computer takes an input, ignores it, and returns `Producer::produce(context, code)`. From there the rest of the promotion lattice carries the value up to the fallible, async, and by-reference variants. The bundled `PromoteProducer` table wires every other handler component to the appropriate promoter, so delegating a context's whole handler surface to `PromoteProducer` makes one producer answer `CanCompute`, `CanTryCompute`, `CanComputeAsync`, `CanHandle`, and their `Ref` forms. These combinators are documented in [handler combinators](../providers/handler_combinators.md). + +## Examples + +A producer wired into a context, then invoked both directly and as an input-ignoring computer, illustrates how it joins the rest of the family: + +```rust +use core::marker::PhantomData; +use cgp::prelude::*; +use cgp::extra::handler::{CanProduce, Producer, ProducerComponent}; + +#[cgp_new_provider] +impl Producer for MagicNumber { + type Output = u64; + + fn produce(_context: &Context, _code: PhantomData) -> u64 { + 42 + } +} + +pub struct App; + +delegate_components! { + App { + ProducerComponent: MagicNumber, + } +} + +fn run(app: &App) -> u64 { + app.produce(PhantomData::<()>) // returns 42 +} +``` + +Here `MagicNumber` produces `42` from the `Code` tag alone, and `App` delegates `ProducerComponent` to it, giving `App` the `CanProduce<(), Output = u64>` capability. The [`#[cgp_producer]`](../macros/cgp_producer.md) macro turns a plain zero-argument function such as `fn magic_number() -> u64 { 42 }` into exactly this provider, and additionally wires `PromoteProducer` so the same function also answers the input-taking components — `MagicNumber::compute(&app, code, &input)` then returns `42` while ignoring `input`. + +## Related constructs + +`Producer` is the no-input member of the [handler family](../../concepts/handlers.md), where the broader `Code`/`Input`/`Output` model and its axes are explained. Its input-taking counterparts are [`Computer`](computer.md) for the synchronous infallible case, [`TryComputer`](try_computer.md) for the fallible case, and [`Handler`](handler.md) for the general async-and-fallible case; a producer promotes into any of them by ignoring the supplied input. The combinators that perform those promotions, including the `Promote` provider and the `PromoteProducer` table, are documented in [handler combinators](../providers/handler_combinators.md). The macro that generates a producer from a zero-argument function is [`#[cgp_producer]`](../macros/cgp_producer.md), and dispatching a producer on its `Code` tag uses [`UseDelegate`](../providers/use_delegate.md), per [dispatching](../../concepts/dispatching.md). + +## Source + +`Producer` is defined in [crates/extra/cgp-handler/src/components/produce.rs](../../../crates/extra/cgp-handler/src/components/produce.rs). The `Promote` combinator that lifts it into a `Computer` is in [crates/extra/cgp-handler/src/providers/promote.rs](../../../crates/extra/cgp-handler/src/providers/promote.rs), and the `PromoteProducer` table in [crates/extra/cgp-handler/src/providers/promote_all.rs](../../../crates/extra/cgp-handler/src/providers/promote_all.rs). The component is re-exported through `cgp::extra::handler`. Behavioral tests exercising a producer and its promotions are in [crates/tests/cgp-tests/tests/handler_tests/producer_macro.rs](../../../crates/tests/cgp-tests/tests/handler_tests/producer_macro.rs). diff --git a/docs/reference/components/runner.md b/docs/reference/components/runner.md new file mode 100644 index 00000000..2c2d27a2 --- /dev/null +++ b/docs/reference/components/runner.md @@ -0,0 +1,103 @@ +# `Runner` (`CanRun` / `CanSendRun`) + +The `Runner` family is CGP's pair of task-running components: `CanRun` runs a named task asynchronously, and `CanSendRun` is the variant whose returned future is `Send`, for when the work must cross a thread boundary. + +## Purpose + +The `Runner` family exists to give a context a uniform way to *execute a unit of work* selected at the type level, with the concrete behavior chosen through wiring. The unit of work is identified by a `Code` type parameter — a phantom tag, not a value — so a single context can host many distinct tasks, one per `Code`, and dispatch each to its own provider. "Running" a task here means invoking the provider wired for that `Code` and awaiting an asynchronous `Result<(), Error>`: the task either completes or produces the context's abstract error. The components carry no input or output beyond success-or-error; they model a fire-and-complete action rather than a transformation, which is what separates them from the [handler](handler.md) family that maps an `Input` to an `Output`. + +`CanRun` is the base form, and `CanSendRun` exists to solve a specific Rust limitation around `Send` futures. When a runner needs to hand its future to a spawner like `tokio::spawn`, that future must be `Send`, but a generic `async fn` over abstract context types cannot promise `Send` without annotating `Send` bounds on every abstract type in scope — bounds that pollute every interface. `CanSendRun` sidesteps this by returning an explicit `impl Future + Send`, so the `Send` requirement lives on this one trait. A context implements `CanSendRun` as a thin proxy over its `CanRun` implementation, and because the proxy is written against the *concrete* context, the compiler can confirm the concrete future is `Send` without abstract-type annotations. This is a workaround that remains necessary until Rust stabilizes Return Type Notation; once that lands, the `Send`-bound future could be expressed without the separate trait. + +The family is the execution layer that ties together the rest of a CGP application: a runner provider typically reaches the context's runtime through [`HasRuntime`](has_runtime.md) to spawn or await work, and dispatches to other components — fetching data, performing effects — to do the actual job. `CanRun` is what application code calls to set everything in motion. + +## Definition + +Both components are declared in `cgp-run`, each with [`#[cgp_component]`](../macros/cgp_component.md), `#[async_trait]`, and a [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute, and each supertraiting [`HasErrorType`](has_error_type.md) so the task can fail with the context's abstract error: + +```rust +#[cgp_component(Runner)] +#[async_trait] +#[derive_delegate(UseDelegate)] +pub trait CanRun: HasErrorType { + async fn run(&self, _code: PhantomData) -> Result<(), Self::Error>; +} + +#[cgp_component(SendRunner)] +#[async_trait] +#[derive_delegate(UseDelegate)] +pub trait CanSendRun: HasErrorType { + fn send_run( + &self, + _code: PhantomData, + ) -> impl Future> + Send; +} +``` + +The `Code` parameter is the type-level name of the task to run; it is passed only as `PhantomData`, so it carries no data and exists purely to select an implementation. `#[cgp_component(Runner)]` names the provider trait `Runner` and the component marker `RunnerComponent`; `#[cgp_component(SendRunner)]` names them `SendRunner` and `SendRunnerComponent`. The [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute generates a `UseDelegate` provider that dispatches on `Code`, so a context can route different `Code` tags to different runner providers through an inner delegation table. + +The two traits differ only in how they shape the asynchronous return. `CanRun::run` is an ordinary `async fn` returning `Result<(), Self::Error>`, with no `Send` requirement on the future. `CanSendRun::send_run` instead returns an explicit `impl Future> + Send`, so callers may move the future across threads. Both produce `()` on success — the task's effect is observable elsewhere, not returned. + +## Behavior + +Because both are `#[cgp_component]` traits, each generates the standard machinery: a consumer blanket impl forwarding to the provider wired for the component marker, the `Runner` / `SendRunner` provider trait, and the usual `UseContext`, `RedirectLookup`, and — from `#[derive_delegate]` — `UseDelegate` provider impls. A context calls `context.run(PhantomData::)` and the consumer impl routes to whatever provider it wired for `RunnerComponent`, with `MyTask` as the `Code`. + +The `UseDelegate` dispatch is the idiomatic way a context hosts multiple tasks. Wiring `RunnerComponent` to `UseDelegate` makes the context look each `Code` up in `Table` and run the provider found there, so distinct tasks get distinct implementations on the same context: + +```rust +delegate_components! { + App { + RunnerComponent: + UseDelegate, + }>, + } +} +``` + +With this wiring, `app.run(PhantomData::)` runs the `RunWithFooBar` provider while `app.run(PhantomData::)` runs `SpawnAndRun`. A runner provider is a normal CGP provider written for the `Runner` provider trait; it receives `&Context` and the `PhantomData` tag and may freely call other components on the context — fetching values, performing effects, then completing. + +The `Send` variant is wired as a proxy on the concrete context. The pattern is to implement `SendRunner` for `App` itself, forwarding `send_run` to the context's own `run`: + +```rust +#[cgp_provider] +impl SendRunner for App { + async fn send_run(context: &App, code: PhantomData) -> Result<(), Infallible> { + context.run(code).await + } +} +``` + +Because this impl names the concrete `App` and `ActionA`, the future produced by `context.run(code)` has a fully known type, so the compiler can verify it is `Send` and satisfy the `+ Send` bound on `send_run` — something the generic `CanRun` definition deliberately does not assert. A spawning runner provider can then require `Context: CanSendRun`, clone the context into a `Send` future, and hand it to a spawner, all without `Send` bounds leaking into the abstract interfaces. + +## Examples + +A complete flow defines tasks, wires runners, proxies the `Send` variant, and spawns one task from inside another. A spawning provider requires the context to be `CanSendRun` and hands a `Send` future to a spawner: + +```rust +#[cgp_new_provider(RunnerComponent)] +impl Runner for SpawnAndRun +where + Context: 'static + Send + Clone + CanSendRun, +{ + async fn run(context: &Context, _code: PhantomData) -> Result<(), Context::Error> { + let context = context.clone(); + + spawn(async move { + let _ = context.send_run(PhantomData).await; + }); + + Ok(()) + } +} +``` + +`SpawnAndRun` runs the task `Code` by cloning the context and spawning the *inner* task `InCode` on a background thread. The spawner requires a `Send + 'static` future, which is exactly what `context.send_run(PhantomData::)` returns — so the constraint `Context: CanSendRun` is what makes the spawn type-check. The context wires `RunnerComponent` to a `UseDelegate` table mapping `ActionB` to `SpawnAndRun`, and supplies a `SendRunner` proxy for `ActionA` as shown above; calling `app.run(PhantomData::)` then spawns `ActionA` to run asynchronously. None of the abstract task or error types carry `Send` bounds — the bound is discharged only at the concrete `SendRunner` proxy. + +## Related constructs + +The `Runner` family is most often paired with [`HasRuntime`](has_runtime.md): a runner provider reaches the context's runtime through `HasRuntime` to spawn tasks or await timers, and names its runtime type through `HasRuntimeType`. It shares its shape with the [handler](handler.md) family — both are `#[cgp_component]` async traits dispatching on a `Code` tag via [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) — but a runner only runs to a `Result<(), Error>`, whereas a handler transforms an `Input` into an `Output`. Both runner traits supertrait [`HasErrorType`](has_error_type.md) for their failure type, are defined with [`#[cgp_component]`](../macros/cgp_component.md), and are wired on a context with [`delegate_components!`](../macros/delegate_components.md), commonly through a `UseDelegate` table keyed by `Code`. + +## Source + +`CanRun` / `Runner` and `CanSendRun` / `SendRunner` are defined together in [crates/extra/cgp-run/src/lib.rs](../../../crates/extra/cgp-run/src/lib.rs), reached from the facade as `cgp::extra::run`. The `#[cgp_component]` and `#[derive_delegate]` expansions they rely on live under [crates/macros/cgp-macro-core/src/](../../../crates/macros/cgp-macro-core/src/). The end-to-end usage — a `UseDelegate` runner table, a `SpawnAndRun` provider, and a concrete `SendRunner` proxy that spawns a `Send` future — is exercised in [crates/tests/cgp-tests/src/tests/async/spawn.rs](../../../crates/tests/cgp-tests/src/tests/async/spawn.rs). diff --git a/docs/reference/components/try_computer.md b/docs/reference/components/try_computer.md new file mode 100644 index 00000000..a1f91fb8 --- /dev/null +++ b/docs/reference/components/try_computer.md @@ -0,0 +1,97 @@ +# `TryComputer` + +`TryComputer` and `TryComputerRef` are the fallible synchronous corner of the [handler family](../../concepts/handlers.md): components that transform an `Input` into an `Output` under a phantom `Code` tag, returning a `Result` against the context's abstract error type. + +## Purpose + +`TryComputer` exists for synchronous computations that can fail. A computation that parses a string, looks up a key, or checks an invariant may not be able to produce its output, and it needs a way to report the failure. `TryComputer` gives it one: instead of returning `Output` directly like [`Computer`](computer.md), its method returns `Result`, where the error is the context's shared abstract error type. This places it one step up from `Computer` on the fallibility axis of the family — still synchronous, but now able to fail — and one step below [`Handler`](handler.md), which adds asynchrony on top of fallibility. + +Returning the *context's* abstract error rather than a concrete one is what keeps a `TryComputer` provider generic. A provider does not commit to `anyhow::Error` or `std::io::Error`; it returns `Self::Error`, and the concrete error type is decided once, at wiring time, by whichever error backend the context plugs in. This is why both fallible computer components supertrait [`HasErrorType`](has_error_type.md): the supertrait is what gives the trait a `Self::Error` to name in its `Result`, and it is what ties every fallible component in a context to the same error type so their results compose. + +## Definition + +`TryComputer` is a CGP component defined with `#[cgp_component]`, and its consumer trait `CanTryCompute` supertraits `HasErrorType` so that its method can return the context's abstract error: + +```rust +#[cgp_component(TryComputer)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanTryCompute: HasErrorType { + type Output; + + fn try_compute( + &self, + _code: PhantomData, + input: Input, + ) -> Result; +} +``` + +The consumer trait `CanTryCompute` mirrors `CanCompute` but for the fallible case. Its `try_compute` method takes `&self`, a `PhantomData` naming the computation, and the `Input` by value, returning `Result` — the associated `Output` on success and the context's abstract `Error` (supplied by the `HasErrorType` supertrait) on failure. The component is wired through the generated `TryComputerComponent` marker, and the macro generates the provider trait `TryComputer` with the context moved into an explicit first parameter. The two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. + +The by-reference sibling `TryComputerRef` is identical except that it borrows its input. Its consumer trait `CanTryComputeRef` also supertraits `HasErrorType` and declares `fn try_compute_ref(&self, _code: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanTryCompute` takes `Input`. Both components are synchronous; their async-and-fallible counterpart is `Handler`. + +## Implementations + +A `TryComputer` provider is a zero-sized struct implementing the provider trait for a generic context that has an error type. Because the result names `Context::Error`, every fallible provider carries a `Context: HasErrorType` bound in its `where` clause. The crate's `ReturnInput` provider illustrates the minimal shape — it succeeds unconditionally, returning its input as the output: + +```rust +impl TryComputer for ReturnInput +where + Context: HasErrorType, +{ + type Output = Input; + + fn try_compute( + _context: &Context, + _code: PhantomData, + input: Input, + ) -> Result { + Ok(input) + } +} +``` + +A `TryComputer` sits in the middle of the promotion lattice, so it both receives providers promoted from below and is promoted upward in turn. A plain [`Computer`](computer.md) becomes a `TryComputer` by wrapping its output in `Ok` — this is the `Promote` combinator — so an infallible provider satisfies `CanTryCompute` for free. In the other direction, a `Computer` whose output is *already* a `Result` becomes a `TryComputer` that unwraps that result, which is the `TryPromote` combinator; and a `TryComputer` is itself promoted to the async [`Handler`](handler.md) by wrapping it in a future. These promotions live in the [handler combinators](../providers/handler_combinators.md), so a provider author implements whichever single variant fits and lets the wiring bridge to `TryComputer`. + +## Examples + +A `TryComputer` provider that parses a string into a number, raising the context's error on failure, wires into a context like any other component: + +```rust +use core::marker::PhantomData; +use cgp::prelude::*; +use cgp::extra::handler::{CanTryCompute, TryComputer, TryComputerComponent}; + +#[cgp_new_provider] +impl TryComputer for ParseU64 +where + Context: CanRaiseError, +{ + type Output = u64; + + fn try_compute( + context: &Context, + _code: PhantomData, + input: String, + ) -> Result { + input.parse().map_err(|e| Context::raise_error(e)) + } +} + +delegate_components! { + App { + TryComputerComponent: ParseU64, + } +} +``` + +The provider `ParseU64` returns `Result`, converting the concrete `ParseIntError` into the context's abstract error with `CanRaiseError`. Because the consumer trait supertraits `HasErrorType`, the context must have an error type wired before it can call `try_compute` — `App` would delegate its `ErrorTypeProviderComponent` (and an error raiser) as well as `TryComputerComponent`. In everyday use the [`#[cgp_computer]`](../macros/cgp_computer.md) macro generates this kind of provider from a function returning `Result`, and wires the promotion table so the same function also answers `CanCompute` (returning the `Result` as its output), `CanHandle`, and the `Ref` variants. + +## Related constructs + +`TryComputer` is the fallible synchronous corner of the [handler family](../../concepts/handlers.md); its infallible counterpart is [`Computer`](computer.md), and its async-and-fallible generalization is [`Handler`](handler.md). It supertraits [`HasErrorType`](has_error_type.md), which supplies the `Self::Error` it returns, and a fallible provider typically uses [`CanRaiseError`](can_raise_error.md) to convert a concrete source error into that abstract error. The combinators that promote a `Computer` into a `TryComputer` (`Promote`, `TryPromote`) and a `TryComputer` into a `Handler` are documented in [handler combinators](../providers/handler_combinators.md). The macro that builds a `TryComputer` provider from a fallible function is [`#[cgp_computer]`](../macros/cgp_computer.md), and dispatching on `Code` or `Input` uses [`UseDelegate`](../providers/use_delegate.md) and the family's `UseInputDelegate`, per [dispatching](../../concepts/dispatching.md). + +## Source + +`TryComputer` and `TryComputerRef` are defined in [crates/extra/cgp-handler/src/components/try_compute.rs](../../../crates/extra/cgp-handler/src/components/try_compute.rs). The `ReturnInput` provider is in [crates/extra/cgp-handler/src/providers/return_input.rs](../../../crates/extra/cgp-handler/src/providers/return_input.rs), and the promotion combinators in [crates/extra/cgp-handler/src/providers/](../../../crates/extra/cgp-handler/src/). The components are re-exported through `cgp::extra::handler`. Behavioral tests exercising the fallible computers and their promotions are in [crates/tests/cgp-tests/tests/handler_tests/computer_macro.rs](../../../crates/tests/cgp-tests/tests/handler_tests/computer_macro.rs). diff --git a/docs/reference/derives/derive_build_field.md b/docs/reference/derives/derive_build_field.md new file mode 100644 index 00000000..b44db841 --- /dev/null +++ b/docs/reference/derives/derive_build_field.md @@ -0,0 +1,132 @@ +# `#[derive(BuildField)]` + +`#[derive(BuildField)]` derives just the incremental-builder machinery for a struct: a partial companion type plus the `HasBuilder`, `IntoBuilder`, `PartialData`, `FinalizeBuild`, and `UpdateField` impls that let the struct be assembled one field at a time. + +## Purpose + +`#[derive(BuildField)]` gives a struct a type-checked, field-by-field builder without the rest of the extensible-data machinery. It is the building block that supplies the *construction* half of a record: starting from an empty value, setting fields individually or copying them in bulk from other records, and finalizing only once every field is present. It exists as a standalone derive for cases where you want the builder but not the field representation traits — though in practice most code reaches for [`#[derive(CgpRecord)]`](derive_cgp_record.md) or [`#[derive(CgpData)]`](derive_cgp_data.md), which include this output. + +The builder's distinguishing property is that field presence is tracked in the type, not at runtime. The derive generates a *partial* companion struct whose type parameters record, per field, whether that field is present, absent, or void. Generic builder providers advance those parameters as fields are added, and the finalize step is implemented only for the fully-present configuration. A premature finalize is therefore a compile error rather than a runtime failure. + +The capability is exposed through `BuildField`, which is not generated per type but defined once in the field crate as a blanket impl over the generated `UpdateField` impls. `build_field` sets one field on a partial value; the surrounding `HasBuilder`/`IntoBuilder`/`FinalizeBuild` traits start and end the build. + +## Syntax + +The derive is applied to a struct and takes no arguments: + +```rust +use cgp::prelude::*; + +#[derive(BuildField)] +pub struct Person { + pub first_name: String, + pub last_name: String, +} +``` + +Each named field becomes a type-level string `Symbol!` used as the field's `Tag`, and its declared type becomes its value type. A tuple struct works equally well: an unnamed field at position `N` is keyed by [`Index`](../types/index.md) instead of a `Symbol!`. Generic parameters on the struct are carried onto the generated impls. The derive emits the same builder impls that the record path of [`#[derive(CgpData)]`](derive_cgp_data.md) emits — it is that slice in isolation, with no `HasField` getters or `HasFields` representation traits. + +## Expansion + +`#[derive(BuildField)]` expands into a partial companion struct and the traits that drive it. The symbols below are abbreviated as `Symbol!("name")` in place of the full `Symbol>` form. Starting from: + +```rust +#[derive(BuildField)] +pub struct Person { + pub first_name: String, + pub last_name: String, +} +``` + +it first emits the partial struct `__PartialPerson`, with one `MapType` parameter per field. The marker decides how each field is stored: `IsPresent` maps `T` to `T`, `IsNothing` maps `T` to `()`, and `IsVoid` maps `T` to the empty `Void` type: + +```rust +pub struct __PartialPerson<__F0__: MapType, __F1__: MapType> { + pub first_name: <__F0__ as MapType>::Map, + pub last_name: <__F1__ as MapType>::Map, +} +``` + +It then emits the entry and exit points. `HasBuilder` starts an empty builder (`IsNothing, IsNothing`); `IntoBuilder` turns an existing value into a fully-present builder; `PartialData` records that any configuration of the partial type targets `Person`; and `FinalizeBuild` reconstructs the struct, implemented only for the all-`IsPresent` configuration: + +```rust +impl HasBuilder for Person { + type Builder = __PartialPerson; + fn builder() -> Self::Builder { __PartialPerson { first_name: (), last_name: () } } +} + +impl IntoBuilder for Person { + type Builder = __PartialPerson; + fn into_builder(self) -> Self::Builder { __PartialPerson { first_name: self.first_name, last_name: self.last_name } } +} + +impl<__F0__: MapType, __F1__: MapType> PartialData for __PartialPerson<__F0__, __F1__> { + type Target = Person; +} + +impl FinalizeBuild for __PartialPerson { + fn finalize_build(self) -> Self::Target { Person { first_name: self.first_name, last_name: self.last_name } } +} +``` + +Next it emits, per field, an `UpdateField` impl. `UpdateField` changes one field's marker from its current state to `M`, returning the old value alongside the rebuilt partial struct. This is the primitive that everything else builds on: + +```rust +impl<__M1__: MapType, __M2__: MapType, __F1__: MapType> + UpdateField for __PartialPerson<__M1__, __F1__> +{ + type Value = String; + type Mapper = __M1__; // the field's prior marker + type Output = __PartialPerson<__M2__, __F1__>; // first_name now in state __M2__ + fn update_field(self, _: PhantomData, value: __M2__::Map) + -> (__M1__::Map, Self::Output) + { (self.first_name, __PartialPerson { first_name: value, last_name: self.last_name }) } +} +// plus the analogous UpdateField for "last_name" +``` + +Finally it emits, per field, a [`HasField`](../traits/has_field.md) impl on the partial type that is in scope only when that field's marker is `IsPresent`, so an already-set field can be read back out of a partially-built value: + +```rust +impl<__F1__: MapType> HasField for __PartialPerson { + type Value = String; + fn get_field(&self, _: PhantomData) -> &String { &self.first_name } +} +// plus the analogous HasField for "last_name" +``` + +The `BuildField` trait itself is not emitted by the derive. It is defined once in the field crate as a blanket impl: any type implementing `UpdateField` — that is, setting a currently-absent field to present — automatically implements `BuildField`. So `build_field` is sugar over the generated `update_field` that specifically transitions a field from `IsNothing` to `IsPresent`. `FinalizeBuild` is likewise defined in the field crate as a subtrait of `PartialData`; the derive only provides the all-present impl. + +The key takeaway is that `builder()` yields `__PartialPerson`, each `build_field` flips one marker to `IsPresent`, and `finalize_build` exists only at `__PartialPerson` — so an incomplete build cannot be finalized. + +## Examples + +The builder is driven through `builder()`, `build_field`, and `finalize_build`, with `build_from` (from the field crate's `CanBuildFrom`) copying every shared field from another record at once: + +```rust +use cgp::prelude::*; +use cgp::core::field::impls::CanBuildFrom; + +#[derive(BuildField)] +pub struct FooBar { pub foo: u64, pub bar: String } + +#[derive(BuildField)] +pub struct FooBarBaz { pub foo: u64, pub bar: String, pub baz: bool } + +fn extend(foo_bar: FooBar) -> FooBarBaz { + FooBarBaz::builder() + .build_from(foo_bar) // sets foo + bar + .build_field(PhantomData::, true) // sets baz + .finalize_build() +} +``` + +Each step changes the partial type, and only after the last field is set does the all-present `FinalizeBuild` impl apply. + +## Related constructs + +`#[derive(BuildField)]` is one slice of the record output of [`#[derive(CgpData)]`](derive_cgp_data.md) and [`#[derive(CgpRecord)]`](derive_cgp_record.md); those derives include it alongside the [`#[derive(HasField)]`](derive_has_field.md) getters and [`#[derive(HasFields)]`](derive_has_fields.md) representation traits. Its enum analogues are [`#[derive(ExtractField)]`](derive_extract_field.md) for incremental matching and [`#[derive(FromVariant)]`](derive_from_variant.md) for variant construction. The entry and exit points it generates target the [`HasBuilder`](../traits/has_builder.md) family of traits. The generated code reads back fields through [`HasField`](../traits/has_field.md), stores them in the [`product`](../macros/product.md)-shaped partial struct, and switches on the [`MapType`](../traits/map_type.md) markers `IsPresent`/`IsNothing`/`IsVoid`. + +## Source + +The derive entry point is `derive_build_field` in [crates/macros/cgp-macro-lib/src/derive_build_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_build_field.rs), which builds an `ItemCgpRecord` and calls `to_build_field_items()`. That method, in [crates/macros/cgp-macro-core/src/types/cgp_data/record.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/record.rs), composes the helpers in the [`derive_builder/`](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/) submodule (`builder_struct.rs`, `has_builder_impl.rs`, `into_builder_impl.rs`, `partial_data.rs`, `finalize_build_impl.rs`, `update_field_impls.rs`, `has_field_impls.rs`). The `BuildField` and `FinalizeBuild` traits are in [crates/core/cgp-field/src/traits/build_field.rs](../../../crates/core/cgp-field/src/traits/build_field.rs), `UpdateField` in `update_field.rs`, `HasBuilder`/`IntoBuilder` in `has_builder.rs`, `PartialData` in `partial_data.rs`, and the `MapType` markers in `crates/core/cgp-field/src/impls/map_type.rs`. Tests are in [crates/tests/cgp-tests/tests/extensible_data_tests/records/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/records/). diff --git a/docs/reference/derives/derive_cgp_data.md b/docs/reference/derives/derive_cgp_data.md new file mode 100644 index 00000000..5d3fa903 --- /dev/null +++ b/docs/reference/derives/derive_cgp_data.md @@ -0,0 +1,200 @@ +# `#[derive(CgpData)]` + +`#[derive(CgpData)]` is the umbrella derive for extensible data: applied to a struct or an enum, it generates every CGP trait impl that lets the type be taken apart, rebuilt, and converted to and from a generic field representation. + +## Purpose + +`#[derive(CgpData)]` turns an ordinary struct or enum into *extensible data* — a type whose fields can be enumerated, accessed, built up incrementally, and matched generically at the type level, rather than only through hand-written field-by-field code. The motivation is the same one that drives the rest of CGP: a concrete `struct Person { first_name, last_name }` is opaque to generic code, because nothing about it can be referred to by a type parameter. `#[derive(CgpData)]` exposes the type's shape as type-level data so that generic providers can operate over any data type uniformly. + +The derive does this by generating two complementary views of the type. The first is a *representation* view: the type's fields become a type-level product (for structs) or sum (for enums) of named [`Field`](../types/field.md) entries, reachable through [`HasFields`](../traits/has_fields.md) and convertible with `FromFields`/`ToFields`. The second is an *incremental* view: a generated partial type that holds each field in a present-or-absent state, so that values can be assembled one field at a time (for structs) or peeled apart one variant at a time (for enums). Together these are what make data "extensible" — generic code can add a field to a partial struct, or remove a variant from a partial enum, without naming the concrete type. + +The practical payoff is that data becomes the subject of CGP wiring, not just behavior. Builders that merge several source structs into a target struct, dispatchers that match an enum variant and route it to a handler, and casts that widen or narrow between related enums all work generically because `#[derive(CgpData)]` supplied the field-level machinery they consume. Behavior-only components never need this derive; it is for the data types that flow through them. + +`#[derive(CgpData)]` is the high-level entry point. It dispatches on whether the annotated item is a struct or an enum and emits exactly what [`#[derive(CgpRecord)]`](derive_cgp_record.md) or [`#[derive(CgpVariant)]`](derive_cgp_variant.md) would emit for that shape — so the two shape-specific derives are simply `CgpData` restricted to one kind of type. + +## Syntax + +The derive is applied like any other derive macro, on a `struct` or an `enum`, and takes no arguments: + +```rust +use cgp::prelude::*; + +#[derive(CgpData)] +pub struct Person { + pub first_name: String, + pub last_name: String, +} + +#[derive(CgpData)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} +``` + +What the derive generates depends entirely on the kind of item. For a struct it produces the record machinery (field getters, the field representation, and the incremental builder); for an enum it produces the variant machinery (the field representation, variant constructors, and the incremental extractor). The two paths share the `HasFields`/`FromFields`/`ToFields` representation traits but otherwise emit different impls, so the sections below treat them separately. + +Field tags drive everything. A named struct field or an enum variant becomes a type-level string [`Symbol!`](../macros/symbol.md), and an unnamed field of a tuple struct becomes a positional [`Index`](../types/index.md); that tag is what addresses the field in the generated impls. Struct field types and single-field variant payload types become the field values. Generic parameters on the type are carried through onto the generated impls. + +## Expansion + +`#[derive(CgpData)]` expands into a fixed set of impls determined by whether the input is a struct or an enum. The exact generated code is verbose because every field name is spelled out as its full `Symbol>` form; the blocks below abbreviate those symbols as `Symbol!("name")` for readability, matching how the expansion snapshots read once collapsed. + +For a struct, the derive emits the record expansion. Given: + +```rust +#[derive(CgpData)] +pub struct Person { + pub first_name: String, + pub last_name: String, +} +``` + +it generates per-field [`HasField`](../traits/has_field.md)/`HasFieldMut` getters, the representation traits, and the builder machinery. The representation traits expose the struct as a product of named fields: + +```rust +impl HasFields for Person { + type Fields = Cons< + Field, + Cons, Nil>, + >; +} + +impl FromFields for Person { + fn from_fields(Cons(first_name, Cons(last_name, Nil)): Self::Fields) -> Self { + Self { first_name: first_name.value, last_name: last_name.value } + } +} + +impl ToFields for Person { /* self -> Cons(...) */ } +// plus HasFieldsRef and ToFieldsRef for borrowed access +``` + +The builder half generates a partial companion struct `__PartialPerson`, whose each field is wrapped in a `MapType` marker so the field can be present (`IsPresent`), absent (`IsNothing`), or void (`IsVoid`): + +```rust +pub struct __PartialPerson<__F0__: MapType, __F1__: MapType> { + pub first_name: <__F0__ as MapType>::Map, + pub last_name: <__F1__ as MapType>::Map, +} + +impl HasBuilder for Person { + type Builder = __PartialPerson; // all absent + fn builder() -> Self::Builder { __PartialPerson { first_name: (), last_name: () } } +} + +impl FinalizeBuild for __PartialPerson { /* -> Person */ } +``` + +This builder expansion — `__Partial{Name}`, `HasBuilder`, `IntoBuilder`, `PartialData`, `FinalizeBuild`, the per-field `UpdateField` impls (which `BuildField` is built on), and the per-field `HasField` impls on the partial type — is exactly what [`#[derive(BuildField)]`](derive_build_field.md) produces on its own. See that document for the full builder breakdown. + +For an enum, the derive emits the variant expansion instead. Given: + +```rust +#[derive(CgpData)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} +``` + +it generates the representation traits (now over a sum), the [`FromVariant`](../traits/from_variant.md) constructors, and the extractor machinery. The representation is a sum of named fields terminated by [`Void`](../types/either.md): + +```rust +impl HasFields for Shape { + type Fields = Either< + Field, + Either, Void>, + >; +} + +impl FromVariant for Shape { + type Value = Circle; + fn from_variant(_tag: PhantomData, value: Circle) -> Self { + Self::Circle(value) + } +} +// plus FromVariant for Rectangle, and FromFields/ToFields/ToFieldsRef +``` + +The extractor half generates partial enums `__PartialShape` and `__PartialRefShape`, plus `HasExtractor`/`HasExtractorRef`/`HasExtractorMut`, `FinalizeExtract`, and per-variant [`ExtractField`](../traits/extract_field.md) impls that peel one variant off and return the remaining variants: + +```rust +pub enum __PartialShape<__F0__: MapType, __F1__: MapType> { + Circle(<__F0__ as MapType>::Map), + Rectangle(<__F1__ as MapType>::Map), +} + +impl HasExtractor for Shape { + type Extractor = __PartialShape; + /* to_extractor / from_extractor */ +} +``` + +This extractor expansion is exactly what [`#[derive(ExtractField)]`](derive_extract_field.md) produces, and the `FromVariant` impls are what [`#[derive(FromVariant)]`](derive_from_variant.md) produces. `#[derive(CgpData)]` on an enum is the union of those two building-block derives plus the shared representation traits. + +Two details of the expansion are worth holding onto. The partial type is named `__Partial{Name}` (and `__PartialRef{Name}` for the borrowed extractor), with reserved double-underscore names so they do not collide with user types. And the `Tag` that keys each field follows the same rule as [`#[derive(HasField)]`](derive_has_field.md): a named field or enum variant is keyed by the [`Symbol!`](../macros/symbol.md) of its identifier, while an unnamed field of a tuple struct is keyed by its positional [`Index`](../types/index.md). A tuple-struct record therefore exposes `Field, _>` entries in its `Fields` product and `UpdateField, _>` impls in its builder, not symbol tags. + +## Examples + +A struct example shows the builder view end to end. Because `Person` derives `CgpData`, it gains a builder that can be filled field by field and from other structs that share field names: + +```rust +use cgp::prelude::*; +use cgp::core::field::impls::CanBuildFrom; + +#[derive(CgpData)] +pub struct Person { + pub first_name: String, + pub last_name: String, +} + +#[derive(CgpData)] +pub struct Employee { + pub employee_id: u64, + pub first_name: String, + pub last_name: String, +} + +fn make_employee(person: Person) -> Employee { + Employee::builder() // all fields absent + .build_from(person) // fills first_name + last_name + .build_field(PhantomData::, 1) + .finalize_build() // all present -> Employee +} +``` + +An enum example shows the extractor view. Because `Shape` derives `CgpData`, a value can be converted to its extractor and have one variant pulled out, with the remainder carrying the still-unmatched variants: + +```rust +use cgp::prelude::*; +use cgp::core::field::traits::FinalizeExtractResult; + +#[derive(CgpData)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} + +fn area(shape: Shape) -> f64 { + match shape.to_extractor().extract_field(PhantomData::) { + Ok(circle) => core::f64::consts::PI * circle.radius * circle.radius, + Err(remainder) => { + let rect = remainder + .extract_field(PhantomData::) + .finalize_extract_result(); + rect.width * rect.height + } + } +} +``` + +These two views are what higher-level constructs build on: builders and field-dispatch handlers consume the struct machinery, while variant dispatch and the `upcast`/`downcast` casts consume the enum machinery. + +## Related constructs + +`#[derive(CgpData)]` is the umbrella over a family of shape- and capability-specific derives. [`#[derive(CgpRecord)]`](derive_cgp_record.md) and [`#[derive(CgpVariant)]`](derive_cgp_variant.md) are `CgpData` restricted to structs and enums respectively, useful when you want to document intent or when only one shape is valid. The building-block derives generate slices of the same output: [`#[derive(BuildField)]`](derive_build_field.md) emits just the struct builder, [`#[derive(ExtractField)]`](derive_extract_field.md) just the enum extractor, and [`#[derive(FromVariant)]`](derive_from_variant.md) just the variant constructors. [`#[derive(HasFields)]`](derive_has_fields.md) emits just the representation traits, and [`#[derive(HasField)]`](derive_has_field.md) just the plain field getters. The generated types reference [`Field`](../types/field.md), the [`product`](../macros/product.md) (`Cons`/`Nil`) and [`sum`](../macros/sum.md) (`Either`/`Void`) type-level lists, and the `MapType` markers `IsPresent`/`IsNothing`/`IsVoid`. + +## Source + +The derive entry point is `derive_cgp_data` in [crates/macros/cgp-macro-lib/src/cgp_data.rs](../../../crates/macros/cgp-macro-lib/src/cgp_data.rs), which parses the input into `ItemCgpData` and calls `to_items()`. The dispatch on struct vs. enum is in [crates/macros/cgp-macro-core/src/types/cgp_data/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/item.rs); the record path is in `record.rs` and the variant path in `variant.rs` in the same directory, which delegate to the `derive_has_fields/`, `derive_builder/`, `derive_extractor/`, and `derive_from_variant.rs` submodules. The runtime traits are in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/) and the `MapType` markers in `crates/core/cgp-field/src/impls/map_type.rs`. Expansion snapshots and behavioral tests live in [crates/tests/cgp-tests/tests/extensible_data_tests/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/), notably `records/` and `variants/`. diff --git a/docs/reference/derives/derive_cgp_record.md b/docs/reference/derives/derive_cgp_record.md new file mode 100644 index 00000000..96cc306b --- /dev/null +++ b/docs/reference/derives/derive_cgp_record.md @@ -0,0 +1,137 @@ +# `#[derive(CgpRecord)]` + +`#[derive(CgpRecord)]` derives the extensible-data machinery for a struct: field getters, the type-level field representation, and an incremental builder, so the struct can be enumerated, accessed, and assembled field by field. + +## Purpose + +`#[derive(CgpRecord)]` makes a struct into an *extensible record* — a product of named fields that generic CGP code can address, convert, and build up one field at a time. A plain Rust struct is opaque to generic code: there is no way to refer to "the `first_name` field" or "a struct with these fields" through a type parameter. This derive exposes the struct's fields as type-level data so that generic providers — builders, field mergers, field-dispatch handlers — can operate over any record uniformly. + +The derive is the struct-specific face of [`#[derive(CgpData)]`](derive_cgp_data.md). When `CgpData` is applied to a struct it emits exactly what `CgpRecord` emits; the two are the same code path. Use `CgpRecord` when the type is always a struct and you want the name to say so, or when you prefer a derive whose meaning is unambiguous at the use site. Using `CgpRecord` on an enum is a type error, since it parses its input as a struct. + +The defining capability a record gains is incremental construction. Beyond plain field access, the derive generates a *partial* companion type that tracks, in its type parameters, which fields are present and which are still absent. Generic code can start from an empty builder, set fields individually or copy them in bulk from other records that share field names, and finalize only once every field is present. This present/absent tracking happens entirely at the type level, so a missing field is a compile error, not a runtime panic. + +## Syntax + +The derive is applied to a struct and takes no arguments: + +```rust +use cgp::prelude::*; + +#[derive(CgpRecord)] +pub struct Person { + pub first_name: String, + pub last_name: String, +} +``` + +Each named field becomes a type-level string [`Symbol!`](../macros/symbol.md) used as the field's `Tag`, while an unnamed field of a tuple struct becomes a positional [`Index`](../types/index.md) — the same tagging rule as [`#[derive(HasField)]`](derive_has_field.md). The field's declared type becomes its value type, and generic parameters on the struct are carried onto the generated impls. The derive accepts the same structs that [`#[derive(CgpData)]`](derive_cgp_data.md) accepts for the record path; the only difference is that `CgpRecord` refuses non-struct inputs outright. + +## Expansion + +`#[derive(CgpRecord)]` expands into three groups of impls: field getters, the representation traits, and the builder machinery. The symbols below are abbreviated as `Symbol!("name")` in place of the full `Symbol>` spelling the macro actually emits. Starting from: + +```rust +#[derive(CgpRecord)] +pub struct Person { + pub first_name: String, + pub last_name: String, +} +``` + +the derive first emits a [`HasField`](../traits/has_field.md) and a `HasFieldMut` impl per field, keyed by the field-name symbol — the same getters [`#[derive(HasField)]`](derive_has_field.md) would produce: + +```rust +impl HasField for Person { + type Value = String; + fn get_field(&self, _: PhantomData) -> &String { &self.first_name } +} +// HasFieldMut, and the same pair for "last_name" +``` + +Second, it emits the representation traits, exposing the struct as a type-level product of named [`Field`](../types/field.md) entries and providing whole-record conversions — the [`#[derive(HasFields)]`](derive_has_fields.md) output: + +```rust +impl HasFields for Person { + type Fields = Cons< + Field, + Cons, Nil>, + >; +} + +impl FromFields for Person { + fn from_fields(Cons(first_name, Cons(last_name, Nil)): Self::Fields) -> Self { + Self { first_name: first_name.value, last_name: last_name.value } + } +} +// plus ToFields, HasFieldsRef, ToFieldsRef +``` + +Third, it emits the builder machinery — the [`#[derive(BuildField)]`](derive_build_field.md) output. This centers on a partial companion struct `__PartialPerson` whose each field is wrapped in a `MapType` marker, so a field can be present (`IsPresent`, the value itself), absent (`IsNothing`, a unit `()`), or void (`IsVoid`): + +```rust +pub struct __PartialPerson<__F0__: MapType, __F1__: MapType> { + pub first_name: <__F0__ as MapType>::Map, + pub last_name: <__F1__ as MapType>::Map, +} + +impl HasBuilder for Person { + type Builder = __PartialPerson; // empty builder + fn builder() -> Self::Builder { __PartialPerson { first_name: (), last_name: () } } +} + +impl IntoBuilder for Person { + type Builder = __PartialPerson; // fully populated + fn into_builder(self) -> Self::Builder { /* move each field in */ } +} + +impl<__F0__: MapType, __F1__: MapType> PartialData for __PartialPerson<__F0__, __F1__> { + type Target = Person; +} + +impl FinalizeBuild for __PartialPerson { + fn finalize_build(self) -> Person { Person { first_name: self.first_name, last_name: self.last_name } } +} +``` + +It then emits, per field, an `UpdateField` impl that flips that one field's marker from one state to another (this is what `BuildField` is implemented in terms of), and a `HasField` impl on the partial type that is available only when that field's marker is `IsPresent`. The full per-field detail is documented in [`#[derive(BuildField)]`](derive_build_field.md). + +The single most important fact about the expansion is that *presence is encoded in the type*. `builder()` returns `__PartialPerson`, `finalize_build` is implemented only for `__PartialPerson`, and each `build_field`/`build_from` call advances the markers. A premature `finalize_build` therefore fails to compile because the all-present impl does not apply. The partial type name is the reserved `__Partial{Name}`. + +## Examples + +A record is most useful when assembling one struct from field-compatible parts. Because both structs derive the record machinery, a builder can copy shared fields in bulk with `build_from` and fill the rest individually: + +```rust +use cgp::prelude::*; +use cgp::core::field::impls::CanBuildFrom; + +#[derive(CgpRecord)] +pub struct Person { + pub first_name: String, + pub last_name: String, +} + +#[derive(CgpRecord)] +pub struct Employee { + pub employee_id: u64, + pub first_name: String, + pub last_name: String, +} + +fn promote(person: Person, id: u64) -> Employee { + Employee::builder() + .build_from(person) // first_name + last_name + .build_field(PhantomData::, id) + .finalize_build() +} +``` + +If a field were left unset before `finalize_build`, the call would not compile — the all-present `FinalizeBuild` impl simply would not be in scope for the partial type. + +## Related constructs + +`#[derive(CgpRecord)]` is the struct restriction of [`#[derive(CgpData)]`](derive_cgp_data.md), which dispatches to this same path; [`#[derive(CgpVariant)]`](derive_cgp_variant.md) is the enum counterpart. Its output decomposes into [`#[derive(HasField)]`](derive_has_field.md) (per-field getters), [`#[derive(HasFields)]`](derive_has_fields.md) (the representation traits), and [`#[derive(BuildField)]`](derive_build_field.md) (the incremental builder) — derive those individually when you need only one slice. The generated types reference [`Field`](../types/field.md), the [`product`](../macros/product.md) type-level list (`Cons`/`Nil`), and the `MapType` markers `IsPresent`/`IsNothing`/`IsVoid`. + +## Source + +The derive entry point is `derive_cgp_record` in [crates/macros/cgp-macro-lib/src/cgp_record.rs](../../../crates/macros/cgp-macro-lib/src/cgp_record.rs), which parses an `ItemCgpRecord` and calls `to_items()`. The record codegen is in [crates/macros/cgp-macro-core/src/types/cgp_data/record.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/record.rs), which composes `derive_has_field_impls_from_struct`, `derive_has_fields_impls_from_struct`, and the builder helpers in the `derive_builder/` submodule. The runtime traits (`HasBuilder`, `IntoBuilder`, `PartialData`, `FinalizeBuild`, `UpdateField`, `BuildField`, `HasFields`, `FromFields`, `ToFields`) live in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/). Expansion snapshots and behavioral tests are in [crates/tests/cgp-tests/tests/extensible_data_tests/records/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/records/). diff --git a/docs/reference/derives/derive_cgp_variant.md b/docs/reference/derives/derive_cgp_variant.md new file mode 100644 index 00000000..3efa0d8b --- /dev/null +++ b/docs/reference/derives/derive_cgp_variant.md @@ -0,0 +1,123 @@ +# `#[derive(CgpVariant)]` + +`#[derive(CgpVariant)]` derives the extensible-data machinery for an enum: the type-level field representation, variant constructors, and an incremental extractor, so the enum can be enumerated, built from a single variant, and matched one variant at a time. + +## Purpose + +`#[derive(CgpVariant)]` makes an enum into an *extensible variant* — a sum of named variants that generic CGP code can address, construct, and take apart one variant at a time. A plain Rust enum is opaque to generic code: there is no way to refer to "the `Circle` variant" or "an enum with these variants" through a type parameter. This derive exposes the enum's variants as type-level data so that generic providers — variant dispatchers, the `upcast`/`downcast` casts, match handlers — can operate over any enum uniformly. + +The derive is the enum-specific face of [`#[derive(CgpData)]`](derive_cgp_data.md). When `CgpData` is applied to an enum it emits exactly what `CgpVariant` emits; the two are the same code path. Use `CgpVariant` when the type is always an enum and you want the name to say so. Using `CgpVariant` on a struct is a type error, since it parses its input as an enum. + +The defining capability a variant gains is incremental extraction. Beyond constructing the enum from any single variant, the derive generates a *partial* companion enum that tracks, in its type parameters, which variants are still possible. Generic code can convert a value to its extractor and pull one variant out; on a match the value is returned, and on a miss the *remainder* — a partial enum with that variant marked impossible — is returned for the next attempt. When every variant has been ruled out the remainder becomes an empty type that can be discharged, so an exhaustive match is proven at the type level. + +## Syntax + +The derive is applied to an enum and takes no arguments: + +```rust +use cgp::prelude::*; + +#[derive(CgpVariant)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} +``` + +Each variant is expected to carry a single payload (a newtype variant); its name becomes a type-level string `Symbol!` used as the variant's `Tag`, and its payload type becomes the variant's value type. Generic parameters on the enum are carried onto the generated impls. The derive accepts the same enums that [`#[derive(CgpData)]`](derive_cgp_data.md) accepts for the variant path; the only difference is that `CgpVariant` refuses non-enum inputs outright. + +## Expansion + +`#[derive(CgpVariant)]` expands into three groups of impls: the representation traits, the variant constructors, and the extractor machinery. The symbols below are abbreviated as `Symbol!("Name")` in place of the full `Symbol>` spelling the macro actually emits. Starting from: + +```rust +#[derive(CgpVariant)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} +``` + +the derive first emits the representation traits, exposing the enum as a type-level sum of named [`Field`](../types/field.md) entries terminated by [`Void`](../types/either.md), with whole-value conversions — the [`#[derive(HasFields)]`](derive_has_fields.md) output for enums: + +```rust +impl HasFields for Shape { + type Fields = Either< + Field, + Either, Void>, + >; +} + +impl FromFields for Shape { /* match each Either arm back to a variant */ } +// plus ToFields, HasFieldsRef, ToFieldsRef +``` + +Second, it emits one [`FromVariant`](../traits/from_variant.md) impl per variant, so the enum can be constructed generically from any single variant by name: + +```rust +impl FromVariant for Shape { + type Value = Circle; + fn from_variant(_: PhantomData, value: Circle) -> Self { Self::Circle(value) } +} +// plus FromVariant +``` + +Third, it emits the extractor machinery — the [`#[derive(ExtractField)]`](derive_extract_field.md) output. This centers on two partial companion enums: `__PartialShape` for owned extraction and `__PartialRefShape` for borrowed extraction. Each arm's payload is wrapped in a `MapType` marker, so a variant can be present (`IsPresent`) or ruled out (`IsVoid`): + +```rust +pub enum __PartialShape<__F0__: MapType, __F1__: MapType> { + Circle(<__F0__ as MapType>::Map), + Rectangle(<__F1__ as MapType>::Map), +} + +impl HasExtractor for Shape { + type Extractor = __PartialShape; // all variants still possible + fn to_extractor(self) -> Self::Extractor { /* map each variant across */ } + fn from_extractor(extractor: Self::Extractor) -> Self { /* map back */ } +} + +impl FinalizeExtract for __PartialShape { // no variants left -> empty + fn finalize_extract<__T__>(self) -> __T__ { match self {} } +} +``` + +It then emits, per variant, an `ExtractField` impl available only when that variant's marker is `IsPresent`; calling it returns `Ok(value)` if the runtime value is that variant, or `Err(remainder)` where the remainder has that variant's marker flipped to `IsVoid`. The borrowed `__PartialRefShape` enum carries an extra `MapTypeRef` parameter and backs `HasExtractorRef`/`HasExtractorMut`. The full per-variant detail is documented in [`#[derive(ExtractField)]`](derive_extract_field.md). + +The single most important fact about the expansion is that *possibility is encoded in the type*. `to_extractor` yields `__PartialShape`; each failed `extract_field` returns a remainder with one more `IsVoid`; and once every marker is `IsVoid` the value inhabits `FinalizeExtract`, which can produce any type because the enum is uninhabited. This is how a chain of `extract_field` calls becomes a provably exhaustive match. The partial type names are the reserved `__Partial{Name}` and `__PartialRef{Name}`. + +## Examples + +A variant is most useful when matching generically with a guaranteed-exhaustive fallthrough. Because `Shape` derives the variant machinery, the match can be expressed as a chain of `extract_field` calls, with `finalize_extract_result` discharging the impossible remainder: + +```rust +use cgp::prelude::*; +use cgp::core::field::traits::FinalizeExtractResult; + +#[derive(CgpVariant)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} + +fn area(shape: Shape) -> f64 { + match shape.to_extractor().extract_field(PhantomData::) { + Ok(circle) => core::f64::consts::PI * circle.radius * circle.radius, + Err(remainder) => { + let rect = remainder + .extract_field(PhantomData::) + .finalize_extract_result(); // remainder is now empty; this cannot fail + rect.width * rect.height + } + } +} +``` + +Because each `extract_field` narrows the remainder type, the compiler knows after the second extraction that no variants remain, so `finalize_extract_result` is accepted without a wildcard arm. + +## Related constructs + +`#[derive(CgpVariant)]` is the enum restriction of [`#[derive(CgpData)]`](derive_cgp_data.md), which dispatches to this same path; [`#[derive(CgpRecord)]`](derive_cgp_record.md) is the struct counterpart. Its output decomposes into [`#[derive(HasFields)]`](derive_has_fields.md) (the representation traits), [`#[derive(FromVariant)]`](derive_from_variant.md) (the variant constructors), and [`#[derive(ExtractField)]`](derive_extract_field.md) (the incremental extractor) — derive those individually when you need only one slice. The generated types reference [`Field`](../types/field.md), the [`sum`](../macros/sum.md) type-level list (`Either`/`Void`), and the `MapType` markers `IsPresent`/`IsVoid`. + +## Source + +The derive entry point is `derive_cgp_variant` in [crates/macros/cgp-macro-lib/src/cgp_variant.rs](../../../crates/macros/cgp-macro-lib/src/cgp_variant.rs), which parses an `ItemCgpVariant` and calls `to_items()`. The variant codegen is in [crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs), which composes `derive_has_fields_impls_from_enum`, `derive_from_variant_from_enum`, and the extractor helpers in the `derive_extractor/` submodule. The runtime traits (`HasExtractor`, `HasExtractorRef`, `HasExtractorMut`, `ExtractField`, `FinalizeExtract`, `FromVariant`, `HasFields`, `FromFields`, `ToFields`) live in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/). Expansion snapshots and behavioral tests are in [crates/tests/cgp-tests/tests/extensible_data_tests/variants/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/variants/). diff --git a/docs/reference/derives/derive_extract_field.md b/docs/reference/derives/derive_extract_field.md new file mode 100644 index 00000000..66667637 --- /dev/null +++ b/docs/reference/derives/derive_extract_field.md @@ -0,0 +1,149 @@ +# `#[derive(ExtractField)]` + +`#[derive(ExtractField)]` derives just the incremental-extractor machinery for an enum: partial companion enums plus the `HasExtractor`, `HasExtractorRef`, `HasExtractorMut`, `PartialData`, `FinalizeExtract`, and `ExtractField` impls that let the enum be matched one variant at a time. + +## Purpose + +`#[derive(ExtractField)]` gives an enum a type-checked, variant-by-variant extractor without the rest of the extensible-data machinery. It is the building block that supplies the *deconstruction* half of a variant: converting a value to an extractor, pulling one variant out at a time, and carrying the still-unmatched variants forward in a *remainder* whose type shrinks with each attempt. It exists as a standalone derive for cases where you want the extractor alone — though most code reaches for [`#[derive(CgpVariant)]`](derive_cgp_variant.md) or [`#[derive(CgpData)]`](derive_cgp_data.md), which include this output. + +The extractor's distinguishing property is that remaining possibilities are tracked in the type. The derive generates *partial* companion enums whose type parameters record, per variant, whether that variant is still possible or has been ruled out. Each failed extraction returns a remainder with one more variant marked impossible; once all are impossible, the remainder is an empty type that can be discharged unconditionally. This is how a chain of extractions becomes a provably exhaustive match without a wildcard arm. + +The capability is exposed through `ExtractField`, generated per variant, together with `HasExtractor` (and its borrowed and mutable forms) to obtain an extractor, and `FinalizeExtract` to discharge the empty remainder. + +## Syntax + +The derive is applied to an enum and takes no arguments: + +```rust +use cgp::prelude::*; + +#[derive(ExtractField)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} +``` + +Each variant's name becomes a type-level string `Symbol!` used as the variant's `Tag`, and its payload type becomes its value type. Every variant must carry exactly one unnamed payload — a single-field tuple variant such as `Circle(Circle)`; a fieldless, multi-field, or struct-style variant is a compile error. Generic parameters on the enum are carried onto the generated impls. The derive emits the same extractor impls that the variant path of [`#[derive(CgpData)]`](derive_cgp_data.md) emits — it is that slice in isolation, with no `HasFields` representation traits and no [`FromVariant`](../traits/from_variant.md) constructors. + +## Expansion + +`#[derive(ExtractField)]` expands into two partial companion enums and the traits that drive them. The symbols below are abbreviated as `Symbol!("Name")` in place of the full `Symbol>` form. Starting from: + +```rust +#[derive(ExtractField)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} +``` + +it first emits the owned partial enum `__PartialShape` and the borrowed partial enum `__PartialRefShape`. Each variant's payload is wrapped in a `MapType` marker, where `IsPresent` keeps the payload and `IsVoid` maps it to the empty [`Void`](../types/either.md) type; the borrowed form adds a `MapTypeRef` parameter that selects shared or mutable references: + +```rust +pub enum __PartialShape<__F0__: MapType, __F1__: MapType> { + Circle(<__F0__ as MapType>::Map), + Rectangle(<__F1__ as MapType>::Map), +} + +pub enum __PartialRefShape<'__a__, __R__: MapTypeRef, __F0__: MapType, __F1__: MapType> { + Circle(<__F0__ as MapType>::Map<<__R__ as MapTypeRef>::Map<'__a__, Circle>>), + Rectangle(<__F1__ as MapType>::Map<<__R__ as MapTypeRef>::Map<'__a__, Rectangle>>), +} +``` + +It then emits `PartialData` (both partial enums target `Shape`) and the extractor accessors. `HasExtractor` yields an owned extractor with every variant present; `HasExtractorRef` and `HasExtractorMut` yield borrowed extractors over `__PartialRefShape` with `IsRef`/`IsMut`: + +```rust +impl HasExtractor for Shape { + type Extractor = __PartialShape; + fn to_extractor(self) -> Self::Extractor { + match self { + Self::Circle(value) => __PartialShape::Circle(value), + Self::Rectangle(value) => __PartialShape::Rectangle(value), + } + } + fn from_extractor(extractor: Self::Extractor) -> Self { + match extractor { + __PartialShape::Circle(value) => Self::Circle(value), + __PartialShape::Rectangle(value) => Self::Rectangle(value), + } + } +} + +impl HasExtractorRef for Shape { + type ExtractorRef<'a> = __PartialRefShape<'a, IsRef, IsPresent, IsPresent> where Self: 'a; + fn extractor_ref(&self) -> Self::ExtractorRef<'_> { /* ... */ } +} +// plus HasExtractorMut over __PartialRefShape<'a, IsMut, IsPresent, IsPresent> +``` + +It emits a `FinalizeExtract` impl for the all-`IsVoid` configuration of each partial enum. Because that configuration is uninhabited, `finalize_extract` can return any type by matching on the empty value: + +```rust +impl FinalizeExtract for __PartialShape { + fn finalize_extract<__T__>(self) -> __T__ { match self {} } +} +// plus the borrowed __PartialRefShape<'a, __R__, IsVoid, IsVoid> +``` + +Finally it emits, per variant, an `ExtractField` impl available only when that variant's marker is `IsPresent`. Calling it returns `Ok(value)` if the runtime value is that variant, or `Err(remainder)` where the remainder has that variant flipped to `IsVoid`: + +```rust +impl<__F1__: MapType> ExtractField for __PartialShape { + type Value = Circle; + type Remainder = __PartialShape; + fn extract_field(self, _: PhantomData) -> Result { + match self { + __PartialShape::Circle(value) => Ok(value), + __PartialShape::Rectangle(value) => Err(__PartialShape::Rectangle(value)), + } + } +} +// plus ExtractField for "Rectangle", and the borrowed variants over __PartialRefShape +``` + +The `FinalizeExtract` trait itself is defined in the field crate (with blanket impls for `Void` and `Infallible`); the derive supplies the all-void impl on the partial enums. The companion `FinalizeExtractResult` trait, also in the field crate, is what `finalize_extract_result` calls to collapse an `Ok`/empty-`Err` result into the value. + +The key takeaway is that `to_extractor` yields `__PartialShape`, each failed `extract_field` returns a remainder with one more `IsVoid`, and at `__PartialShape` the value is uninhabited — so once every variant has been tried, the compiler knows the match is exhaustive. + +## Examples + +The extractor is driven through `to_extractor`, a chain of `extract_field` calls, and `finalize_extract_result` to discharge the impossible remainder: + +```rust +use cgp::prelude::*; +use cgp::core::field::traits::FinalizeExtractResult; + +#[derive(ExtractField)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} + +fn area(shape: Shape) -> f64 { + match shape.to_extractor().extract_field(PhantomData::) { + Ok(circle) => core::f64::consts::PI * circle.radius * circle.radius, + Err(remainder) => { + let rect = remainder + .extract_field(PhantomData::) + .finalize_extract_result(); // remainder is now empty; cannot fail + rect.width * rect.height + } + } +} +``` + +After the second extraction the remainder type has both markers `IsVoid`, so `finalize_extract_result` is accepted with no wildcard arm. + +## Related constructs + +`#[derive(ExtractField)]` is one slice of the variant output of [`#[derive(CgpData)]`](derive_cgp_data.md) and [`#[derive(CgpVariant)]`](derive_cgp_variant.md); those derives include it alongside the [`#[derive(FromVariant)]`](derive_from_variant.md) constructors and [`#[derive(HasFields)]`](derive_has_fields.md) representation traits. Its struct analogue is [`#[derive(BuildField)]`](derive_build_field.md), the incremental builder. The capability it generates is the [`ExtractField`](../traits/extract_field.md) trait. The generated code stores variants in [`sum`](../macros/sum.md)-shaped partial enums ([`Either`/`Void`](../types/either.md)) and switches on the [`MapType`](../traits/map_type.md) markers `IsPresent`/`IsVoid`. + +## Known issues + +The derive only accepts enums whose every variant is a single-field tuple variant. A fieldless variant like `Empty`, a multi-field variant like `Pair(A, B)`, or a struct-style variant like `Named { x: A }` causes the macro to fail with the error "Expected variant to contain exactly one unnamed field." There is no way to opt a variant out of the requirement, so an enum that mixes shapes cannot derive the extractor at all. + +## Source + +The derive entry point is `derive_extract_field` in [crates/macros/cgp-macro-lib/src/derive_extract_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_extract_field.rs), which builds an `ItemCgpVariant` and calls `to_extract_field_items()`. That method, in [crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs), composes the helpers in the [`derive_extractor/`](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/) submodule (`extractor_enum.rs`, `has_extractor_impl.rs`, `partial_data.rs`, `finalize_extract_impl.rs`, `extract_field_impls.rs`). The `ExtractField`, `HasExtractor`/`HasExtractorRef`/`HasExtractorMut`, `FinalizeExtract`, and `FinalizeExtractResult` traits are in [crates/core/cgp-field/src/traits/extract_field.rs](../../../crates/core/cgp-field/src/traits/extract_field.rs), `PartialData` in `partial_data.rs`, and the `MapType`/`MapTypeRef` markers in `crates/core/cgp-field/src/impls/`. Tests are in [crates/tests/cgp-tests/tests/extensible_data_tests/variants/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/variants/). diff --git a/docs/reference/derives/derive_from_variant.md b/docs/reference/derives/derive_from_variant.md new file mode 100644 index 00000000..26c5a477 --- /dev/null +++ b/docs/reference/derives/derive_from_variant.md @@ -0,0 +1,89 @@ +# `#[derive(FromVariant)]` + +`#[derive(FromVariant)]` derives just the variant-construction machinery for an enum: one `FromVariant` impl per variant, so the enum can be built generically from any single variant addressed by name. + +## Purpose + +`#[derive(FromVariant)]` lets generic code construct an enum from one of its variants without naming the concrete variant constructor. It is the smallest building block of the extensible-variant family — the *construction* counterpart to the extractor's deconstruction. Where a plain `Shape::Circle(circle)` hard-codes both the enum and the variant, `FromVariant` lets a generic provider write `Shape::from_variant(tag, value)` with the variant chosen by a type-level `Symbol!` tag, so the same code can target any variant of any enum that derives it. + +The derive exists as a standalone because variant construction is sometimes wanted on its own — for example, by the casts and dispatchers that build a value into a target enum after matching. Most code, however, reaches for [`#[derive(CgpVariant)]`](derive_cgp_variant.md) or [`#[derive(CgpData)]`](derive_cgp_data.md), which include these impls alongside the extractor and the field representation. This is the simplest derive in the family: it generates no companion types and no presence tracking, only a direct constructor per variant. + +## Syntax + +The derive is applied to an enum and takes no arguments: + +```rust +use cgp::prelude::*; + +#[derive(FromVariant)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} +``` + +Each variant's name becomes a type-level string `Symbol!` used as the variant's `Tag`, and its payload type becomes the constructor's value type. Every variant must carry exactly one unnamed payload — a single-field tuple variant such as `Circle(Circle)`; a fieldless, multi-field, or struct-style variant is a compile error. Generic parameters on the enum are carried onto the generated impls. The derive emits exactly the `FromVariant` impls that the variant path of [`#[derive(CgpData)]`](derive_cgp_data.md) emits — that slice in isolation. + +## Expansion + +`#[derive(FromVariant)]` expands into one [`FromVariant`](../traits/from_variant.md) impl per variant and nothing else. The symbols below are abbreviated as `Symbol!("Name")` in place of the full `Symbol>` form. Starting from: + +```rust +#[derive(FromVariant)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} +``` + +the derive emits a constructor keyed by each variant's name symbol, with the payload type as the associated `Value`: + +```rust +impl FromVariant for Shape { + type Value = Circle; + fn from_variant(_tag: PhantomData, value: Self::Value) -> Self { + Self::Circle(value) + } +} + +impl FromVariant for Shape { + type Value = Rectangle; + fn from_variant(_tag: PhantomData, value: Self::Value) -> Self { + Self::Rectangle(value) + } +} +``` + +The `FromVariant` trait is defined in the field crate; the derive only supplies the per-variant impls. There is no partial type, no `MapType` marker, and no state tracking — each impl simply wraps the value in its variant. The `Tag` is the variant name's type-level string, and the `PhantomData` argument exists solely to let the caller pick which variant to build when several `FromVariant` impls are in scope. + +## Examples + +`FromVariant` lets a value be lifted into an enum generically by naming the variant with a tag: + +```rust +use cgp::prelude::*; + +#[derive(FromVariant)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} + +fn wrap_circle(circle: Circle) -> Shape { + Shape::from_variant(PhantomData::, circle) +} +``` + +The call is equivalent to `Shape::Circle(circle)`, but the variant is selected by the type-level tag, so generic code that is parameterized over the tag can construct whichever variant it was asked for. + +## Related constructs + +`#[derive(FromVariant)]` is one slice of the variant output of [`#[derive(CgpData)]`](derive_cgp_data.md) and [`#[derive(CgpVariant)]`](derive_cgp_variant.md); those derives include it alongside the [`#[derive(ExtractField)]`](derive_extract_field.md) extractor and the [`#[derive(HasFields)]`](derive_has_fields.md) representation traits. Its closest relative is [`ExtractField`](../traits/extract_field.md), the reverse operation that takes a variant out rather than putting one in. For structs, the analogous field-setting building block is [`#[derive(BuildField)]`](derive_build_field.md). The generated constructors correspond to the arms of the enum's [`sum`](../macros/sum.md) representation ([`Either`/`Void`](../types/either.md)). + +## Known issues + +The derive only accepts enums whose every variant is a single-field tuple variant. A fieldless variant like `Empty`, a multi-field variant like `Pair(A, B)`, or a struct-style variant like `Named { x: A }` causes the macro to fail with the error "Expected variant to contain exactly one unnamed field." There is no way to opt a variant out of the requirement, so an enum that mixes shapes cannot derive the constructor at all. + +## Source + +The derive entry point is `derive_from_variant` in [crates/macros/cgp-macro-lib/src/derive_from_variant.rs](../../../crates/macros/cgp-macro-lib/src/derive_from_variant.rs), which builds an `ItemCgpVariant` and calls `to_from_variant_impls()`. That method, in [crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs), delegates to `derive_from_variant_from_enum` in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs). The `FromVariant` trait is in [crates/core/cgp-field/src/traits/from_variant.rs](../../../crates/core/cgp-field/src/traits/from_variant.rs). Tests are in [crates/tests/cgp-tests/tests/extensible_data_tests/variants/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/variants/). diff --git a/docs/reference/derives/derive_has_field.md b/docs/reference/derives/derive_has_field.md new file mode 100644 index 00000000..80302756 --- /dev/null +++ b/docs/reference/derives/derive_has_field.md @@ -0,0 +1,176 @@ +# `#[derive(HasField)]` + +`#[derive(HasField)]` is the derive macro that gives a struct field-level getters: for every field it generates a `HasField` (and `HasFieldMut`) implementation keyed by a type-level name, so that providers can read fields out of a context by name without the field ever appearing in a trait interface. + +## Purpose + +`#[derive(HasField)]` exists to turn the concrete fields of a struct into type-level entries that CGP's dependency-injection machinery can look up. The recurring problem in CGP is that a provider needs a value from the context — a `name`, a `width`, a configuration handle — but the provider is generic over the context type and cannot name the concrete struct. `HasField` solves this by indexing each field with a *tag type* that stands in for the field's name, so a provider can demand `Context: HasField` and receive the field without knowing what the context actually is. + +The reason this matters is that it makes field access an impl-side dependency rather than part of any public interface. A provider expresses "I need a `String` field called `name`" purely in its `where` clause; any context that derives `HasField` and happens to have such a field satisfies it automatically. The derive is the bridge between an ordinary Rust struct and that constraint-based access — without it, the struct's fields are invisible to the trait system, and every getter would have to be hand-written. + +The trait being implemented is small. It carries the field's type as an associated `Value` and returns a reference to the field, with a `PhantomData` parameter that exists only to tell the compiler which field is meant when several `HasField` impls are in scope: + +```rust +pub trait HasField { + type Value; + + fn get_field(&self, _tag: PhantomData) -> &Self::Value; +} +``` + +Higher-level constructs are built directly on these generated impls. [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md) and [`#[cgp_getter]`](../macros/cgp_getter.md) (through the [`UseField`](../providers/use_field.md) provider) generate blanket impls whose `where` clauses are `HasField` bounds, and the [`#[implicit]`](../attributes/implicit.md) argument form desugars function parameters into `get_field` calls. All of them assume the context has derived `HasField`; this derive is what makes them work. + +## Syntax + +The macro is applied as a derive on a struct definition and takes no arguments: + +```rust +#[derive(HasField)] +pub struct Person { + pub name: String, + pub age: u8, +} +``` + +It applies to structs with named fields and to structs with unnamed (tuple) fields. The two cases differ only in how each field's tag is computed: a named field is keyed by [`Symbol!("field_name")`](../macros/symbol.md), the type-level string of its identifier, while a tuple field is keyed by [`Index`](../types/index.md), the type-level natural number of its position. Unit structs produce no impls because they have no fields. + +The derive concerns itself only with the *field-level* view. To obtain the whole-struct view as a single type-level [`Product`](../macros/product.md), derive [`HasFields`](derive_has_fields.md) instead; the two are complementary and frequently derived together. + +## Expansion + +`#[derive(HasField)]` emits one `HasField` impl and one `HasFieldMut` impl per field, leaving the struct definition itself untouched. Starting from a named-field struct: + +```rust +#[derive(HasField)] +pub struct Person { + pub name: String, + pub age: u8, +} +``` + +the macro generates a pair of impls for each field, with the field's identifier turned into a `Symbol!` tag and the field's type used as `Value`: + +```rust +impl HasField for Person { + type Value = String; + + fn get_field(&self, key: PhantomData) -> &Self::Value { + &self.name + } +} + +impl HasFieldMut for Person { + fn get_field_mut(&mut self, key: PhantomData) -> &mut Self::Value { + &mut self.name + } +} + +impl HasField for Person { + type Value = u8; + + fn get_field(&self, key: PhantomData) -> &Self::Value { + &self.age + } +} + +impl HasFieldMut for Person { + fn get_field_mut(&mut self, key: PhantomData) -> &mut Self::Value { + &mut self.age + } +} +``` + +The `HasFieldMut` impls come from the same derive and provide mutable access; `HasFieldMut` is a supertrait extension of `HasField` that adds a `get_field_mut` method returning `&mut Self::Value`. Most CGP code only reads through `HasField`, but the mutable counterpart is always generated alongside it. + +A tuple struct expands the same way, except that each field's tag is its positional `Index` rather than a `Symbol!`. Starting from: + +```rust +#[derive(HasField)] +pub struct Rectangle(pub f64, pub f64); +``` + +the macro generates: + +```rust +impl HasField> for Rectangle { + type Value = f64; + + fn get_field(&self, key: PhantomData>) -> &Self::Value { + &self.0 + } +} + +impl HasFieldMut> for Rectangle { + fn get_field_mut(&mut self, key: PhantomData>) -> &mut Self::Value { + &mut self.0 + } +} + +impl HasField> for Rectangle { + type Value = f64; + + fn get_field(&self, key: PhantomData>) -> &Self::Value { + &self.1 + } +} + +impl HasFieldMut> for Rectangle { + fn get_field_mut(&mut self, key: PhantomData>) -> &mut Self::Value { + &mut self.1 + } +} +``` + +When the struct has generic parameters, the impls carry them through faithfully: the macro splits the struct's generics into impl-generics, type-generics, and `where` clause, so `struct Wrapper { pub value: T }` yields `impl HasField for Wrapper` with `Value = T`. + +Field access also threads through smart pointers without an explicit derive. `HasField` and `HasFieldMut` have blanket impls for any type whose `Deref`/`DerefMut` target implements them, so a `Box` or a newtype that dereferences to `Person` resolves `get_field` to the inner struct's field. These blanket impls carry a `#[diagnostic::do_not_recommend]` attribute so the compiler does not suggest them in error messages, keeping the missing-field diagnostic pointed at the underlying struct. + +## Examples + +A provider that needs a value from its context expresses the need as a `HasField` bound, and the derive is what lets a concrete context satisfy it. First a provider for a greeting component, asking for a `String` field named `name`: + +```rust +use cgp::prelude::*; + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasField, +{ + fn greet(&self) { + println!("Hello, {}!", self.get_field(PhantomData)); + } +} +``` + +Then a context that derives `HasField` and wires the component to that provider: + +```rust +#[derive(HasField)] +pub struct Person { + pub name: String, +} + +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} +``` + +Because `Person` derives `HasField`, it implements `HasField`, which is exactly the bound `GreetHello` requires; the wiring therefore type-checks and `person.greet()` prints the person's name. + +In practice the explicit `HasField` bound is rarely written by hand. The same `name` access is more idiomatically expressed with [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md) (`fn name(&self) -> &str`) or with an [`#[implicit]`](../attributes/implicit.md) argument (`fn greet(&self, #[implicit] name: String)`), both of which generate the `HasField` bound for you. The derive is the foundation those forms stand on. + +## Related constructs + +`#[derive(HasField)]` underpins most value-level dependency injection in CGP. [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md) turns a getter-trait method into a blanket impl backed by a `HasField` bound, and [`#[cgp_getter]`](../macros/cgp_getter.md) does the same through the [`UseField`](../providers/use_field.md) provider, which reads an arbitrary field by tag. The [`#[implicit]`](../attributes/implicit.md) argument form desugars context parameters into `get_field` calls against these impls. The tags it generates are documented in [`Symbol!`](../macros/symbol.md) (named fields) and [`Index`](../types/index.md) (tuple fields). For the aggregate view of all fields at once, see [`#[derive(HasFields)]`](derive_has_fields.md), which is commonly derived alongside this one and is itself the basis for [`#[derive(CgpData)]`](derive_cgp_data.md). + +## Source + +The derive entry point is `derive_has_field` in [crates/macros/cgp-macro-lib/src/derive_has_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_has_field.rs), registered as the `HasField` proc-macro derive in [crates/macros/cgp-macro/src/lib.rs](../../../crates/macros/cgp-macro/src/lib.rs). It parses the input as a `syn::ItemStruct`, wraps it in an `ItemCgpRecord` ([crates/macros/cgp-macro-core/src/types/cgp_data/record.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/record.rs)), and calls `to_has_field_impls`, whose codegen lives in `derive_has_field_impls_from_struct` in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_field.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_field.rs) — this is where named fields are mapped to `Symbol` tags and unnamed fields to `Index` tags, and where both the `HasField` and `HasFieldMut` impls are emitted. The traits themselves are defined in [crates/core/cgp-field/src/traits/has_field.rs](../../../crates/core/cgp-field/src/traits/has_field.rs) and [crates/core/cgp-field/src/traits/has_field_mut.rs](../../../crates/core/cgp-field/src/traits/has_field_mut.rs). Expansion snapshots are in [crates/tests/cgp-macro-tests](../../../crates/tests/cgp-macro-tests). diff --git a/docs/reference/derives/derive_has_fields.md b/docs/reference/derives/derive_has_fields.md new file mode 100644 index 00000000..606f699e --- /dev/null +++ b/docs/reference/derives/derive_has_fields.md @@ -0,0 +1,152 @@ +# `#[derive(HasFields)]` + +`#[derive(HasFields)]` is the derive macro that gives a type a whole-shape view: it implements `HasFields` (and `HasFieldsRef`, `ToFields`, `FromFields`, `ToFieldsRef`) so that the entire struct or enum is described by a single type-level [`Product`](../macros/product.md) of named fields, or for an enum a type-level [`Sum`](../macros/sum.md) of named variants. + +## Purpose + +`#[derive(HasFields)]` exists to expose a type's complete field structure as one type, rather than one entry per field. Where [`#[derive(HasField)]`](derive_has_field.md) answers "give me *this* field by name," `HasFields` answers "describe *all* the fields at once" — it produces a single associated `Fields` type that is the type-level list of every field, each tagged with its name. This aggregate view is what lets generic code fold over a context's fields uniformly: serialization, builders, conversions, and the extensible-record machinery all operate on the `Fields` type rather than reaching for fields one at a time. + +The distinction from `HasField` is the whole point. `HasField` is indexed access — many small impls, each keyed by a tag, used for dependency injection where a provider wants one specific value. `HasFields` is the structural mirror — a single impl whose `Fields` type enumerates the entire shape, used when an algorithm must process the type as a record (for a struct) or as a tagged union (for an enum). The two are complementary, and a context that wants both indexed access and structural processing derives both. + +Alongside the structural type, the derive also generates the conversions that move values in and out of that representation. `ToFields` turns an owned value into its `Fields` product, `FromFields` rebuilds the value from a `Fields` product, and `ToFieldsRef` borrows the value as a product of references. Together these make the generic `Fields` view a two-way door: code can decompose a concrete type into its anonymous structural form, transform it generically, and reconstruct the concrete type. + +## Syntax + +The macro is applied as a derive and takes no arguments, but unlike `HasField` it accepts both structs and enums: + +```rust +#[derive(HasFields)] +pub struct Person { + pub name: String, + pub age: u8, +} + +#[derive(HasFields)] +pub enum Shape { + Circle { radius: f64 }, + Rectangle { width: f64, height: f64 }, +} +``` + +For a struct the generated `Fields` type is a product; for an enum it is a sum. Named fields within either are tagged by [`Symbol!`](../macros/symbol.md), and unnamed (tuple) fields by [`Index`](../types/index.md), exactly as in `HasField`. A single-field tuple struct (a newtype) is treated specially: its `Fields` is the inner type directly, not wrapped in a one-element product. Applying the derive to anything other than a struct or enum is a compile error. + +## Expansion + +`#[derive(HasFields)]` emits five impls — `HasFields`, `HasFieldsRef`, `ToFields`, `FromFields`, and `ToFieldsRef` — and leaves the type definition untouched. The shape of the `Fields` type is the load-bearing part. Starting from a named-field struct: + +```rust +#[derive(HasFields)] +pub struct Person { + pub name: String, + pub age: u8, +} +``` + +each field becomes a [`Field`](../types/field.md) entry — the `Value` wrapped together with its type-level name tag — and the entries are chained into a [`Product!`](../macros/product.md). The `HasFields` impl names that product, and `HasFieldsRef` names the same product with each value borrowed: + +```rust +impl HasFields for Person { + type Fields = Product![ + Field, + Field, + ]; +} + +impl HasFieldsRef for Person { + type FieldsRef<'a> = Product![ + Field, + Field, + ] + where + Self: 'a; +} +``` + +The accompanying conversions move values between `Person` and that product. `to_fields` builds the product from the struct's fields, `from_fields` destructures the product back into the struct, and `to_fields_ref` builds the borrowed product: + +```rust +impl ToFields for Person { + fn to_fields(self) -> Self::Fields { + Cons(self.name.into(), Cons(self.age.into(), Nil)) + } +} + +impl FromFields for Person { + fn from_fields(Cons(name, Cons(age, Nil)): Self::Fields) -> Self { + Self { name: name.value, age: age.value } + } +} + +impl ToFieldsRef for Person { + fn to_fields_ref<'a>(&'a self) -> Self::FieldsRef<'a> + where + Self: 'a, + { + Cons((&self.name).into(), Cons((&self.age).into(), Nil)) + } +} +``` + +An enum expands to a [`Sum`](../macros/sum.md) instead of a product. Each variant becomes an [`Either`](../types/either.md) arm tagged by the variant name with [`Symbol!`](../macros/symbol.md), carrying that variant's own fields as a nested product, and the chain is terminated by `Void`. Starting from: + +```rust +#[derive(HasFields)] +pub enum Shape { + Circle { radius: f64 }, + Rectangle { width: f64, height: f64 }, +} +``` + +the `HasFields` impl names the sum of variants: + +```rust +impl HasFields for Shape { + type Fields = Either< + Field]>, + Either< + Field, + Field, + ]>, + Void, + >, + >; +} +``` + +The enum derive likewise emits `HasFieldsRef` (the same sum with borrowed values), `ToFields`, `FromFields`, and `ToFieldsRef`, with each conversion matching on the concrete variant and mapping it to the corresponding `Either` arm. + +The associated trait definitions these impls satisfy are minimal: `HasFields` carries `type Fields`, `HasFieldsRef` carries `type FieldsRef<'a>`, and the three conversion traits each supertrait one of those and add a single method (`to_fields`, `from_fields`, `to_fields_ref`). + +## Examples + +Deriving `HasFields` lets generic code treat any context as a record without naming its concrete type. A common pairing is to derive it alongside [`HasField`](derive_has_field.md) so the same struct supports both indexed and structural access: + +```rust +use cgp::prelude::*; + +#[derive(HasField, HasFields)] +pub struct Config { + pub host: String, + pub port: u16, +} +``` + +With `HasFields` in place, `Config::Fields` is `Product![Field, Field]`, and a value can be round-tripped through that product: + +```rust +let config = Config { host: "localhost".into(), port: 8080 }; + +let fields = config.to_fields(); // ToFields → the Product +let config_again = Config::from_fields(fields); // FromFields → back to Config +``` + +Generic algorithms key off the `Fields` type rather than the concrete struct. This is how higher-level constructs such as the extensible-builder and data-manipulation machinery in [`#[derive(CgpData)]`](derive_cgp_data.md) operate over arbitrary contexts: they bound `Context: HasFields` (or `FromFields`/`ToFields`) and process `Context::Fields` structurally. + +## Related constructs + +`#[derive(HasFields)]` is the structural counterpart to [`#[derive(HasField)]`](derive_has_field.md): `HasField` gives indexed, single-field access for dependency injection, while `HasFields` gives the aggregate view of the whole type. The `Fields` type it produces is built from [`Product`](../macros/product.md) for structs and [`Sum`](../macros/sum.md) for enums, with named entries tagged by [`Symbol!`](../macros/symbol.md). [`#[derive(CgpData)]`](derive_cgp_data.md) builds on this derive, generating `HasFields` together with the additional builder, partial-record, and field-update machinery needed for extensible data. + +## Source + +The derive entry point is `derive_has_fields` in [crates/macros/cgp-macro-lib/src/derive_has_fields.rs](../../../crates/macros/cgp-macro-lib/src/derive_has_fields.rs), registered as the `HasFields` proc-macro derive in [crates/macros/cgp-macro/src/lib.rs](../../../crates/macros/cgp-macro/src/lib.rs). It dispatches on the parsed item: structs go through `ItemCgpRecord::to_has_fields_impls` → `derive_has_fields_impls_from_struct`, and enums through `derive_has_fields_impls_from_enum`, both in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/). Within that module, `product.rs` (`item_fields_to_product_type`) builds the struct `Fields` product, `sum.rs` (`variants_to_sum_type`) builds the enum `Fields` sum, and `from_fields_*`, `to_fields_*`, and `to_fields_ref_*` build the conversion impls. The traits are defined in [crates/core/cgp-field/src/traits/has_fields.rs](../../../crates/core/cgp-field/src/traits/has_fields.rs), [crates/core/cgp-field/src/traits/to_fields.rs](../../../crates/core/cgp-field/src/traits/to_fields.rs), and [crates/core/cgp-field/src/traits/from_fields.rs](../../../crates/core/cgp-field/src/traits/from_fields.rs); the `Field`, `Either`, and `Void` building blocks live under [crates/core/cgp-field/src/types/](../../../crates/core/cgp-field/src/types/). Expansion snapshots are in [crates/tests/cgp-macro-tests](../../../crates/tests/cgp-macro-tests). diff --git a/docs/reference/macros/async_trait.md b/docs/reference/macros/async_trait.md new file mode 100644 index 00000000..69fc09b2 --- /dev/null +++ b/docs/reference/macros/async_trait.md @@ -0,0 +1,139 @@ +# `#[async_trait]` + +`#[async_trait]` rewrites each `async fn` declared in a trait into an ordinary method that returns `impl Future`, so a trait can advertise async methods without tripping the `async_fn_in_trait` lint. + +## Purpose + +`#[async_trait]` exists to make async methods in traits ergonomic to declare. Writing a bare `async fn` directly inside a trait definition compiles, but the compiler emits the `async_fn_in_trait` lint, because a bare `async fn` in a trait leaves the returned future's auto-traits unnameable by callers and is easy to misuse. The hand-written alternative — declaring the method as `fn name(&self) -> impl Future` — silences the lint but is verbose and obscures the intent. `#[async_trait]` lets the author write the natural `async fn` form and performs that rewrite mechanically, so the trait reads as async code while the generated declaration is the lint-clean `impl Future` form. + +The rewrite is a zero-cost desugaring to return-position `impl Trait` in traits: no boxing, no allocation, and no implicit `Send` bound are introduced. The returned future is exactly the one the method body produces. This is why the macro is used pervasively alongside [`#[cgp_component]`](cgp_component.md) and [`#[cgp_fn]`](cgp_fn.md) whenever a CGP capability is asynchronous — it is the standard way to spell an async method in a CGP trait. + +## Syntax + +`#[async_trait]` is an attribute macro applied to a trait definition, taking no arguments. Any tokens placed in the attribute's argument position are ignored, so it is always written bare: + +```rust +#[async_trait] +pub trait CanFetchStorageObject { + async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; +} +``` + +Only the methods declared `async` are affected; non-async methods, associated types, and associated constants in the same trait pass through untouched. When `#[async_trait]` is stacked with a host macro, the ordering follows what that macro needs. With [`#[cgp_component]`](cgp_component.md), place `#[async_trait]` outermost (first) so it rewrites the trait before the component macro reads it: + +```rust +#[async_trait] +#[cgp_component(StorageObjectFetcher)] +pub trait CanFetchStorageObject { + async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; +} +``` + +With [`#[cgp_fn]`](cgp_fn.md), which generates the trait from a function, `#[async_trait]` is written *below* `#[cgp_fn]` on the `async fn`; `#[cgp_fn]` copies it onto the trait and impl it generates: + +```rust +#[cgp_fn] +#[async_trait] +pub async fn fetch_storage_object( + &self, + #[implicit] storage_client: &Client, + object_id: &str, +) -> anyhow::Result> { + /* ... */ +} +``` + +## Expansion + +For each method in the trait whose signature is `async`, `#[async_trait]` removes the `async` keyword and replaces the return type `T` with `impl ::core::future::Future`. A method written without an explicit return type is treated as returning `()`. The trait above expands to: + +```rust +pub trait CanFetchStorageObject { + fn fetch_storage_object( + &self, + object_id: &str, + ) -> impl ::core::future::Future>>; +} +``` + +A method with no return arrow, such as `async fn run(&self);`, expands with `()` as the future's output: + +```rust +// async fn run(&self); +fn run(&self) -> impl ::core::future::Future; +``` + +The macro rewrites only trait definitions. When it is applied to any other item — most importantly an `impl` block — it leaves the tokens completely unchanged. This passthrough is what makes the macro compose so cleanly: an `async fn` is already legal in an inherent or trait `impl` on stable Rust, and only a trait *declaration* triggers the `async_fn_in_trait` lint. So a provider implementation can keep the natural `async fn` body while the matching trait declaration carries the rewritten `-> impl Future` signature, and the two still agree because an `async fn` desugars to exactly such a future-returning method. + +This composition is visible in how [`#[cgp_fn]`](cgp_fn.md) uses it. Given an async `#[cgp_fn]` carrying `#[async_trait]`, `#[cgp_fn]` first produces a trait and a blanket impl, attaching `#[async_trait]` to both: + +```rust +#[async_trait] +pub trait FetchStorageObject { + async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; +} + +#[async_trait] +impl<__Context__> FetchStorageObject for __Context__ +where + Self: HasField, +{ + async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result> { + let storage_client: &Client = + self.get_field(PhantomData::); + /* ... */ + } +} +``` + +`#[async_trait]` then runs on each. On the trait it rewrites the declaration to `fn fetch_storage_object(&self, object_id: &str) -> impl ::core::future::Future>>`. On the impl it is a no-op, so the `async fn` body remains as written — and that `async fn` satisfies the trait's `impl Future` method. + +## Examples + +The most common use is declaring an asynchronous CGP component. The consumer trait carries `#[async_trait]` so its method is a clean `impl Future` declaration, and each provider implements it with a natural `async fn` body: + +```rust +use cgp::prelude::*; + +#[async_trait] +#[cgp_component(StorageObjectFetcher)] +pub trait CanFetchStorageObject { + async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; +} + +#[cgp_impl(new FetchS3Object)] +impl StorageObjectFetcher { + async fn fetch_storage_object( + &self, + #[implicit] storage_client: &Client, + #[implicit] bucket_id: &str, + object_id: &str, + ) -> anyhow::Result> { + let output = storage_client + .get_object() + .bucket(bucket_id) + .key(object_id) + .send() + .await?; + + Ok(output.body.collect().await?.into_bytes().to_vec()) + } +} +``` + +Defining the same capability as a single implementation with [`#[cgp_fn]`](cgp_fn.md) needs `#[async_trait]` directly below it, as shown in the Syntax section. In both forms the author writes only `async fn`, and the lint-clean `impl Future` declaration is generated. + +## Related constructs + +`#[async_trait]` is most often stacked with [`#[cgp_component]`](cgp_component.md), which builds the consumer and provider traits for an async capability, and with [`#[cgp_fn]`](cgp_fn.md), which generates an async trait and blanket impl from a function. Providers for an async component are written with [`#[cgp_impl]`](cgp_impl.md) using ordinary `async fn` bodies, since the macro's passthrough on impl blocks leaves those untouched. It is independent of CGP's wiring layer — [`delegate_components!`](delegate_components.md) and [`check_components!`](check_components.md) treat an async component exactly like a synchronous one. + +## Known issues + +`#[async_trait]` rewrites only the method signature, never the method body, so an async trait method that carries a *default body* is mishandled. The macro strips `async` from such a method's signature and changes its return type to `impl Future`, but the body is left verbatim — it is not wrapped in an `async { ... }` block. The result is a non-async method whose body still returns a plain value and may use `.await`, which fails to compile. In practice this is rarely hit, because async methods in a trait are almost always declarations without bodies, and provided async behavior is supplied by a provider impl instead; but a default-bodied `async fn` inside a `#[async_trait]` trait is not supported. + +The generated future carries no `Send` bound. Because the rewrite produces a bare `impl Future`, the returned future is `Send` only when the concrete future happens to be, and the trait does not require it. Code that must spawn the future onto a work-stealing, multi-threaded executor — which demands `Send` futures — cannot express that requirement through this macro and must add the bound by other means, as described in [recovering `Send` bounds](../../concepts/send-bounds.md). + +## Source + +The macro entry point is `async_trait` in [crates/macros/cgp-async-macro/src/lib.rs](../../../crates/macros/cgp-async-macro/src/lib.rs), a `#[proc_macro_attribute]` that discards its attribute arguments and forwards the annotated item to `impl_async`. The rewrite lives in [crates/macros/cgp-async-macro/src/impl_async.rs](../../../crates/macros/cgp-async-macro/src/impl_async.rs): it parses the item as a `syn::ItemTrait`, and on success walks the trait's methods, replacing each `async` signature's output with `-> impl ::core::future::Future` and clearing the `async` keyword; if the item does not parse as a trait, it is returned unchanged. The macro is re-exported into the prelude from [crates/main/cgp-core/src/prelude.rs](../../../crates/main/cgp-core/src/prelude.rs), so `use cgp::prelude::*;` brings it into scope. Its interaction with `#[cgp_fn]` is exercised by the expansion snapshot in [crates/tests/cgp-tests/tests/cgp_fn_tests/async.rs](../../../crates/tests/cgp-tests/tests/cgp_fn_tests/async.rs), and async providers spawned onto a runtime appear in [crates/tests/cgp-tests/src/tests/async/spawn.rs](../../../crates/tests/cgp-tests/src/tests/async/spawn.rs). + diff --git a/docs/reference/macros/blanket_trait.md b/docs/reference/macros/blanket_trait.md new file mode 100644 index 00000000..6c6811bc --- /dev/null +++ b/docs/reference/macros/blanket_trait.md @@ -0,0 +1,170 @@ +# `#[blanket_trait]` + +`#[blanket_trait]` generates a blanket implementation for a trait whose methods, associated types, and constants carry default definitions, turning the trait into an extension trait that hides its supertrait constraints behind a clean interface. + +## Purpose + +`#[blanket_trait]` exists to remove the boilerplate of writing the blanket impl that makes an extension trait work. The blanket-trait pattern — the foundation CGP itself is built on — uses the `where` clause of a blanket impl to hide the constraints a piece of generic code needs from the interface that callers see. Written by hand, the pattern is repetitive: you state the supertrait bounds twice, once on the trait and once on the impl, and you copy each default method body from the trait into the impl. `#[blanket_trait]` lets you write the trait once, with its default bodies, and generates the matching impl for you. + +The payoff is the impl-side-dependency, or dependency-injection, pattern in its purest form. A trait declared `pub trait FooBar: Foo + Bar` with a default `foo_bar` method exposes only `foo_bar` to its callers; the `Foo + Bar` requirements are carried by the generated blanket impl's `where` clause, so any context satisfying `Foo` and `Bar` automatically gains `FooBar` without naming those dependencies at the call site. This is the same constraint-hiding that the `/cgp` skill introduces as the core idea behind blanket traits, and it is preferred over a free generic function because transitive callers never have to repeat the `Foo + Bar` bounds. + +`#[blanket_trait]` is not a CGP component. It produces an ordinary Rust trait and an ordinary blanket impl, with no consumer/provider split, no component name, and no wiring. It is the tool to reach for when a capability has exactly one definition and you want the extension-trait ergonomics without committing to the full CGP component machinery. When a capability later needs multiple alternative implementations, the trait can be promoted to a [`#[cgp_component]`](cgp_component.md). + +## Syntax + +`#[blanket_trait]` is applied as an attribute on a trait definition. Every method and constant in the trait must have a default body, and every associated type may declare bounds; these defaults are what the generated impl forwards. The supertraits of the trait become the dependencies that the blanket impl requires. + +```rust +#[blanket_trait] +pub trait FooBar: Foo + Bar { + fn foo_bar(&self) { + self.foo(); + self.bar(); + } +} +``` + +The macro accepts an optional argument naming the generic context type used in the generated impl. When omitted, it defaults to `__Context__` — the same reserved identifier used across the CGP macros, chosen to avoid colliding with the user's own type parameters. Passing an identifier overrides this name, which is occasionally useful for readability in expanded output. + +```rust +#[blanket_trait(Ctx)] +pub trait FooBar: Foo + Bar { /* ... */ } +``` + +The trait may carry generic parameters and associated types. Generic parameters on the trait are copied onto the impl. Associated types are turned into fresh generic parameters on the impl and bound through the supertrait's associated-type equality, which is how the pattern lifts an associated type out of a supertrait — covered in the expansion below. + +## Syntax Grammar + +The attribute argument of `#[blanket_trait]` is a single optional context name: + +```ebnf +BlanketTraitArgs -> ContextName? + +ContextName -> IDENTIFIER +``` + +When the argument is omitted, the generic context type in the generated impl defaults to the reserved identifier `__Context__`. A given `IDENTIFIER` overrides that name. + +## Expansion + +`#[blanket_trait]` emits two items: the trait, unchanged from its definition, and a blanket impl for a generic context. The impl forwards each default method body, requires the trait's supertraits in its `where` clause, and strips the defaults from the trait so the trait declaration stays a pure interface. Starting from the method example: + +```rust +#[blanket_trait] +pub trait FooBar: Foo + Bar { + fn foo_bar(&self) { + self.foo(); + self.bar(); + } +} +``` + +the macro produces the trait and a blanket impl whose `where` clause carries the `Foo + Bar` supertraits as the hidden dependency: + +```rust +pub trait FooBar: Foo + Bar { + fn foo_bar(&self); +} + +impl<__Context__> FooBar for __Context__ +where + __Context__: Foo + Bar, +{ + fn foo_bar(&self) { + self.foo(); + self.bar(); + } +} +``` + +The simplest case, a trait with no methods, generates an empty impl — a trait alias in everything but name: + +```rust +#[blanket_trait] +pub trait FooBar: Foo + Bar {} +``` + +expands to: + +```rust +pub trait FooBar: Foo + Bar {} + +impl<__Context__> FooBar for __Context__ +where + __Context__: Foo + Bar, +{} +``` + +Associated types receive special handling, because lifting an associated type out of a supertrait is one of the pattern's main uses. Each associated type in the trait becomes a new generic parameter on the impl, the supertrait's associated-type equality is rewritten to reference that parameter, and the impl assigns the parameter back to the associated type. Given: + +```rust +#[blanket_trait] +pub trait HasFooTypeAtBar: HasFooTypeAt { + type FooBar; +} +``` + +the macro emits: + +```rust +pub trait HasFooTypeAtBar: HasFooTypeAt { + type FooBar; +} + +impl<__Context__, FooBar> HasFooTypeAtBar for __Context__ +where + __Context__: HasFooTypeAt, +{ + type FooBar = FooBar; +} +``` + +When the associated type declares bounds, those bounds are moved into the impl's `where` clause as predicates on the introduced parameter. Declaring `type FooBar: Clone` adds `FooBar: Clone` to the generated `where` clause alongside the supertrait requirement. Associated constants are forwarded in the same way as methods: their default expressions become the impl's constant definitions, and the trait keeps only the declaration. A method or constant without a usable default is an error, since the macro has no body to forward; associated types need no default, because the macro supplies the assignment (`type FooBar = FooBar;`) itself from the introduced parameter. + +## Examples + +A self-contained extension trait that hides two dependencies behind one method illustrates the everyday use. The `FooBar` capability is defined once, with its body, and applies to any context implementing both `Foo` and `Bar`: + +```rust +use cgp::prelude::*; + +pub trait Foo { + fn foo(&self); +} + +pub trait Bar { + fn bar(&self); +} + +#[blanket_trait] +pub trait FooBar: Foo + Bar { + fn foo_bar(&self) { + self.foo(); + self.bar(); + } +} + +pub struct Context; + +impl Foo for Context { + fn foo(&self) {} +} + +impl Bar for Context { + fn bar(&self) {} +} + +fn run(ctx: &Context) { + ctx.foo_bar(); // available because Context: Foo + Bar +} +``` + +`Context` never mentions `FooBar` directly; it implements only `Foo` and `Bar`, and the generated blanket impl supplies `foo_bar` automatically. A caller of `foo_bar` sees a single-method interface and is shielded from the `Foo + Bar` requirement entirely. + +## Related constructs + +`#[blanket_trait]` and [`#[cgp_fn]`](cgp_fn.md) both produce a blanket impl over a generic context, and both are single-implementation tools that need no wiring; the difference is the input. `#[cgp_fn]` derives the trait and its body from a plain function with `#[implicit]` field arguments, while `#[blanket_trait]` takes a trait with default bodies and supertrait constraints directly, making it the better fit when the dependencies are themselves traits rather than context fields. Both contrast with [`#[cgp_component]`](cgp_component.md), which produces a consumer/provider pair supporting multiple implementations selected through [`delegate_components!`](delegate_components.md); a `#[blanket_trait]` can be promoted to a `#[cgp_component]` when more than one implementation becomes necessary. The constraint-hiding it performs is the same impl-side-dependency mechanism that [`#[cgp_auto_getter]`](cgp_auto_getter.md) and [`HasField`](../traits/has_field.md) rely on for value injection. + +## Source + +The macro entry point is `blanket_trait` in [crates/macros/cgp-macro-lib/src/blanket_trait.rs](../../../crates/macros/cgp-macro-lib/src/blanket_trait.rs), which parses the optional context identifier (defaulting to `__Context__` when the attribute argument is empty) and the trait, then runs `item.to_items()?`. The logic lives in [crates/macros/cgp-macro-core/src/types/blanket_trait.rs](../../../crates/macros/cgp-macro-core/src/types/blanket_trait.rs): `to_item_impl` walks the trait items, forwards each default method/const body and associated-type assignment into the impl, lifts associated types into impl generics, moves associated-type bounds into the `where` clause, and appends the trait's supertraits as the `__Context__: ...` predicate. The `Self`-to-parameter rewriting for associated types is done by `RemoveSelfPathVisitor` in [crates/macros/cgp-macro-core/src/visitors/](../../../crates/macros/cgp-macro-core/src/visitors/). Behavioral and expansion-snapshot tests are in [crates/tests/cgp-tests/src/tests/blanket_trait.rs](../../../crates/tests/cgp-tests/src/tests/blanket_trait.rs), covering the empty, method, and associated-type-with-and-without-bounds cases. diff --git a/docs/reference/macros/cgp_auto_dispatch.md b/docs/reference/macros/cgp_auto_dispatch.md new file mode 100644 index 00000000..8a23a7ae --- /dev/null +++ b/docs/reference/macros/cgp_auto_dispatch.md @@ -0,0 +1,138 @@ +# `#[cgp_auto_dispatch]` + +`#[cgp_auto_dispatch]` is an attribute macro that, given a trait implemented separately for each payload type, generates a blanket implementation of that trait for an enum by matching the enum's current variant and delegating to the implementation for that variant's payload. + +## Purpose + +`#[cgp_auto_dispatch]` solves the common case of "I have a trait with one impl per type, and I want it to work on an enum of those types too." Without it, a programmer would write a `match` arm per variant by hand, or wire up the [dispatch combinators](../providers/dispatch_combinators.md) — a matcher, a per-variant computer, and the field-handler machinery — manually. The macro removes that boilerplate: it inspects the trait's methods and emits both the per-variant handler each method needs and the enum-level blanket impl that runs the matcher, so the only code the programmer writes is the trait, its per-payload impls, and the derive that makes the enum extensible. + +The macro is the highest-level entry point to the [dispatching](../../concepts/dispatching.md) pattern. It is meant for the frequent situation where the per-variant behavior is exactly "call the same trait method on the payload," and it generates the same matcher wiring described in [`dispatch_combinators`](../providers/dispatch_combinators.md) so that the generated impl behaves identically to a hand-written one. When the per-variant behavior is more elaborate, or the dispatch needs to be wired into a context's components rather than implemented directly on the enum, the underlying combinators are used directly instead. + +## Syntax + +`#[cgp_auto_dispatch]` is written above a trait definition and takes no arguments. The trait may have generic parameters and supertraits, and each method may take `self` by value, by shared reference, or by mutable reference, may take additional value or reference arguments, and may be `async`: + +```rust +#[cgp_auto_dispatch] +pub trait HasArea { + fn area(&self) -> f64; +} +``` + +Two restrictions apply, both enforced at expansion time. A trait method may not have non-lifetime generic parameters, because Rust lacks the quantified trait bounds the generated impl would need; lifetime parameters on a method are allowed. Every trait item must be a method — associated types and constants are rejected. Each method must have a `self` receiver, since the receiver is the enum value being matched. + +## Expansion + +The macro keeps the original trait unchanged and appends two kinds of generated code: one blanket impl of the trait for a fresh enum type parameter named `__Variants__`, and, for each method, one free function turned into a per-variant computer by [`#[cgp_computer]`](cgp_computer.md). Taking the `HasArea` trait above, the macro emits a per-variant computer for the `area` method: + +```rust +#[cgp_computer(ComputeArea)] +fn area<'__a__, __Variants__: HasArea>(__Variants__: &'__a__ __Variants__) -> f64 { + __Variants__.area() +} +``` + +The computer's name is `Compute` followed by the method name in PascalCase, so `area` yields `ComputeArea`. Its body simply calls the trait method on the payload, which means the per-variant handler is "invoke `HasArea::area` on whatever payload type this variant holds." The function is bound by `__Variants__: HasArea` so it applies to every payload type that implements the trait, and it borrows the payload by a fresh lifetime `'__a__` to mirror the `&self` receiver. + +The macro then emits the enum-level blanket impl, which wires a matcher over `__Variants__`: + +```rust +impl<__Variants__> HasArea for __Variants__ +where + MatchWithValueHandlersRef: + for<'__a__> Computer<(), (), &'__a__ __Variants__, Output = f64>, + __Variants__: HasExtractor, +{ + fn area(&self) -> f64 { + MatchWithValueHandlersRef::::compute( + &(), + ::core::marker::PhantomData::<()>, + self, + ) + } +} +``` + +The matcher struct the impl picks depends on the method's receiver and arguments, and every choice is from the value-handler matcher family so that the per-variant computer receives the bare payload. A method whose receiver and argument list determine the selection as follows: a `&self` method with no extra arguments uses `MatchWithValueHandlersRef`, a `&mut self` method with no extra arguments uses `MatchWithValueHandlersMut`, and a by-value `self` method with no extra arguments uses `MatchWithValueHandlers`. When the method takes additional arguments, the matcher switches to the first-argument family — `MatchFirstWithValueHandlersRef`, `MatchFirstWithValueHandlersMut`, or `MatchFirstWithValueHandlers` respectively — and the arguments are bundled into the matcher input as a tuple. The matcher is invoked with a unit context `&()` and unit code `PhantomData::<()>`, since the per-variant logic depends only on the payload, not on any surrounding context. + +A method that takes arguments shows the first-argument form. For a `contains(&self, x: f64, y: f64) -> bool` method, the generated impl bundles the receiver and the arguments into the input tuple and selects `MatchFirstWithValueHandlersRef`: + +```rust +// where MatchFirstWithValueHandlersRef: +// for<'__a__> Computer<(), (), (&'__a__ __Variants__, (f64, f64)), Output = bool> +fn contains(&self, arg_0: f64, arg_1: f64) -> bool { + MatchFirstWithValueHandlersRef::::compute( + &(), + ::core::marker::PhantomData::<()>, + (self, (arg_0, arg_1)), + ) +} +``` + +For an `async` method the macro selects the `AsyncComputer` form of the bound and the matcher call instead of `Computer`, appends the method's lifetime handling for any reference arguments or reference return type by introducing the `'__a__` lifetime and a `for<'__a__>` quantifier where needed, and awaits the matcher result. The enum-level impl always carries the `__Variants__: HasExtractor` bound, since matching requires the enum to be extensible. + +## Examples + +The macro turns a per-type trait into one that also works on an enum of those types, with no hand-written matching. The enum derives `CgpData` so it is extensible, the trait carries `#[cgp_auto_dispatch]`, and each payload type implements the trait on its own: + +```rust +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 { core::f64::consts::PI * self.radius * self.radius } +} + +impl HasArea for Rectangle { + fn area(&self) -> f64 { self.width * self.height } +} + +// HasArea is now also implemented for Shape, dispatching to the variant's impl: +let shape = Shape::Rectangle(Rectangle { width: 2.0, height: 2.0 }); +assert_eq!(shape.area(), 4.0); +``` + +Because the generated impl is bound by `MatchWithValueHandlersRef: …`, it requires every variant's payload to implement `HasArea`; forgetting an impl for one variant is a compile error at the point the enum's `area` is used. A `&mut self` method dispatches the same way through the `Mut` matcher, so a single `#[cgp_auto_dispatch]` trait can mix reading and mutating methods: + +```rust +#[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; } +} + +let mut shape = Shape::Rectangle(Rectangle { width: 2.0, height: 2.0 }); +shape.scale(2.0); // dispatches to Rectangle::scale through MatchFirstWithValueHandlersMut +``` + +## Known issues + +The macro rejects trait methods with non-lifetime generic parameters, so a dispatch trait cannot have a generic method even though an ordinary trait can. This is a deliberate limitation rather than an oversight: the generated blanket impl would need a quantified trait bound over the method's type parameter to guarantee every variant's payload satisfies the bound for all instantiations, and Rust has no such bound. A method that needs to be generic must be handled with the dispatch combinators directly instead of through this macro. + +## Related constructs + +`#[cgp_auto_dispatch]` is the automated front end to the [dispatching](../../concepts/dispatching.md) pattern, and the providers it wires together are documented in [`dispatch_combinators`](../providers/dispatch_combinators.md) — specifically the value-handler matcher family (`MatchWithValueHandlers` and its `Ref`/`Mut` and `First` variants). It emits each per-variant handler with [`#[cgp_computer]`](cgp_computer.md), producing a [`Computer`](../components/computer.md) provider. The enum it dispatches over must be made extensible with a `CgpData`-style derive that supplies [`HasExtractor`](../traits/extract_field.md) and [`HasFields`](../traits/has_fields.md). For per-variant behavior that is not a direct method call, or for dispatch wired into a context's components rather than implemented on the enum, the combinators in [`dispatch_combinators`](../providers/dispatch_combinators.md) are used directly. The [extensible shapes](../../examples/extensible-shapes.md) example develops this macro end to end — dispatching an `area` reader, a mutating `scale`, and argument-taking methods over an enum of shapes — and shows how the generated wiring relates to using the combinators directly. + +## Source + +The macro is the `cgp_auto_dispatch` entry point in [crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs), forwarded from the proc-macro shim in [crates/macros/cgp-extra-macro/src/lib.rs](../../../crates/macros/cgp-extra-macro/src/lib.rs) and re-exported through [crates/main/cgp-extra/src/prelude.rs](../../../crates/main/cgp-extra/src/prelude.rs). The matchers it generates live in [crates/extra/cgp-dispatch/src/providers/matchers/](../../../crates/extra/cgp-dispatch/src/providers/matchers/). Tests are in [crates/tests/cgp-tests/tests/dispatcher_macro_tests/](../../../crates/tests/cgp-tests/tests/dispatcher_macro_tests/), including the `shape.rs` cases covering by-reference, by-mutable-reference, by-value, multi-argument, and async methods. diff --git a/docs/reference/macros/cgp_auto_getter.md b/docs/reference/macros/cgp_auto_getter.md new file mode 100644 index 00000000..7ee950cb --- /dev/null +++ b/docs/reference/macros/cgp_auto_getter.md @@ -0,0 +1,159 @@ +# `#[cgp_auto_getter]` + +`#[cgp_auto_getter]` turns a getter trait into a single blanket implementation that satisfies each getter method by reading a context field through [`HasField`](../traits/has_field.md), keyed by the method's own name as a [`Symbol!`](symbol.md). + +## Purpose + +`#[cgp_auto_getter]` exists to remove the boilerplate of writing field accessors by hand. Reading a value out of a context is the most basic form of dependency injection in CGP, and the underlying mechanism — `HasField` — is precise but verbose and unfamiliar to most Rust developers. The macro lets you state the intent in ordinary trait-method syntax (`fn name(&self) -> &str;`) and generates the `HasField`-based plumbing for you. + +The defining trait-off of `#[cgp_auto_getter]` is that it produces exactly one implementation and no CGP wiring. Unlike a full component, there is no provider trait, no component name, and no delegation table; the macro emits a blanket impl over a generic context, and any context that derives `HasField` with a matching field automatically satisfies the trait. This makes it the lightest-weight getter construct — ideal when the field name in the context always matches the method name and you never need an alternative implementation. + +The cost of that simplicity is rigidity. Because the field tag is derived directly from the method name, the context *must* expose a field of exactly that name. When you need the field name to differ from the method name, or you want the getter to be swappable through wiring, reach for [`#[cgp_getter]`](cgp_getter.md) instead, which builds the same convenience on top of a real CGP component. + +## Syntax + +The macro is applied as a bare attribute on a trait definition and takes no arguments. The trait body consists of getter methods, each taking `&self` (or `&mut self`) and returning a reference: + +```rust +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} +``` + +A getter trait may declare several methods, and each maps independently to its own field. Each method name becomes the field tag, so the following injects two separate fields: + +```rust +#[cgp_auto_getter] +pub trait HasDimensions { + fn width(&self) -> &f64; + fn height(&self) -> &f64; +} +``` + +The return type controls how the field value is read, and several shorthand forms are recognized so the method signature stays ergonomic. A plain reference `&T` reads a field of type `T` directly. The form `&str` is treated specially: it reads a `String` field and calls `.as_str()` on it, so you can return `&str` while the context stores a `String`. Other recognized forms include `Option<&T>` (an `Option` field returned via `.as_ref()`), `&[T]` (a field whose value implements `AsRef<[T]>`), and an owned `T` (a `Copy` field returned by value). A `&mut self` receiver with a `&mut T` return reads the field mutably through `get_field_mut`. + +A getter trait may also declare a single associated type and use it as the method's return type, which lets the abstract type be inferred from the field. This is covered under Expansion below. When an associated type is present, the trait must contain exactly one getter method, and that method's return type must be `&Self::AssocType`. + +## Expansion + +`#[cgp_auto_getter]` re-emits the trait unchanged and adds one blanket impl over a generic context. Starting from this input: + +```rust +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} +``` + +the macro produces the trait verbatim plus the following blanket impl. The context type parameter is the reserved name `__Context__`, the field tag is the method name rendered as a `Symbol!`, and because the return type is `&str`, the `Value` is `String` and the method appends `.as_str()`: + +```rust +pub trait HasName { + fn name(&self) -> &str; +} + +impl<__Context__> HasName for __Context__ +where + __Context__: HasField, +{ + fn name(&self) -> &str { + self.get_field(PhantomData::).as_str() + } +} +``` + +A trait with multiple getter methods produces one `where` predicate and one method body per field, all within the same blanket impl. The `&f64` returns below are plain references, so each `Value` matches the return type and no conversion is appended: + +```rust +impl<__Context__> HasDimensions for __Context__ +where + __Context__: HasField, + __Context__: HasField, +{ + fn width(&self) -> &f64 { + self.get_field(PhantomData::) + } + + fn height(&self) -> &f64 { + self.get_field(PhantomData::) + } +} +``` + +When the trait declares an associated type used as the return type, the macro lifts that type into an extra generic parameter on the impl and binds it through the `HasField` `Value`. This is what allows the abstract type to be inferred from the concrete field. Given: + +```rust +#[cgp_auto_getter] +pub trait HasName { + type Name: Display; + + fn name(&self) -> &Self::Name; +} +``` + +the blanket impl carries `Name` as a generic parameter, copies the associated type's bounds into the `where` clause, and sets the associated type to that parameter: + +```rust +impl<__Context__, Name> HasName for __Context__ +where + Name: Display, + __Context__: HasField, +{ + type Name = Name; + + fn name(&self) -> &Self::Name { + self.get_field(PhantomData::) + } +} +``` + +These desugarings are the exact shape the macro emits today; the only cosmetic difference from the snapshots is that `Symbol!("name")` is shown here in its sugared form rather than the expanded `Symbol<4, Chars<'n', ...>>`. + +## Examples + +A complete use pairs the getter trait with a context that derives `HasField`, after which the method is available with no further wiring: + +```rust +use cgp::prelude::*; + +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[derive(HasField)] +pub struct Person { + pub name: String, +} + +fn greet(person: &Person) { + println!("Hello, {}!", person.name()); // HasName, via the blanket impl +} +``` + +`Person` derives `HasField`, which is precisely the bound the blanket impl requires, so `Person` implements `HasName` automatically and `person.name()` returns the `name` field as a `&str`. + +A getter trait can always be implemented explicitly instead, which is useful when the context does not derive `HasField` or does not store the value under the matching field name. Because `#[cgp_auto_getter]` only adds a blanket impl, you may write the impl by hand on a concrete type: + +```rust +pub struct Person { + pub full_name: String, +} + +impl HasName for Person { + fn name(&self) -> &str { + &self.full_name + } +} +``` + +The explicit form is more verbose but requires no understanding of `HasField` or blanket impls, and it demonstrates that an auto-getter trait is an ordinary Rust trait — the macro's only job is to save you from writing the boilerplate body. + +## Related constructs + +`#[cgp_auto_getter]` is the blanket-impl counterpart to [`#[cgp_getter]`](cgp_getter.md): both read context fields through `HasField`, but `#[cgp_getter]` produces a full CGP component that can be wired to a [`UseField`](../providers/use_field.md) provider, allowing the field name to differ from the method name and the getter to be swapped per context. It builds directly on [`#[derive(HasField)]`](../derives/derive_has_field.md), which generates the per-field `HasField` impls keyed by [`Symbol!`](symbol.md). For field access inside a method body rather than through a dedicated trait, the [`#[implicit]`](../attributes/implicit.md) argument attribute follows the same field-reading semantics. When the only purpose of a getter's associated type is to serve as its return type, the associated-type form here overlaps with abstract-type components defined by [`#[cgp_type]`](cgp_type.md). + +## Source + +The macro entry point is `cgp_auto_getter` in [crates/macros/cgp-macro-lib/src/cgp_auto_getter.rs](../../../crates/macros/cgp-macro-lib/src/cgp_auto_getter.rs), which rejects any attribute argument and runs `ItemCgpAutoGetter::preprocess(...).to_items()`. The logic lives in [crates/macros/cgp-macro-core/src/types/cgp_auto_getter/](../../../crates/macros/cgp-macro-core/src/types/cgp_auto_getter/): `item.rs` sets the `__Context__` context identifier and drives field parsing, and `blanket.rs` builds the single blanket impl. Getter parsing — including the `&str`/`Option<&T>`/`&[T]`/owned shorthands and the associated-type rules — is shared with `#[cgp_getter]` in [crates/macros/cgp-macro-core/src/functions/getter/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/getter/parse.rs), [crates/macros/cgp-macro-core/src/functions/field/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/field/parse.rs), and [crates/macros/cgp-macro-core/src/types/getter/](../../../crates/macros/cgp-macro-core/src/types/getter/). Behavioral and expansion-snapshot tests are in [crates/tests/cgp-tests/tests/getter.rs](../../../crates/tests/cgp-tests/tests/getter.rs) and the `getter_tests/` modules beside it (notably `assoc_type/auto_getter.rs` for the associated-type form and `string.rs` for the `&str` shorthand). diff --git a/docs/reference/macros/cgp_component.md b/docs/reference/macros/cgp_component.md new file mode 100644 index 00000000..a32092dc --- /dev/null +++ b/docs/reference/macros/cgp_component.md @@ -0,0 +1,184 @@ +# `#[cgp_component]` + +`#[cgp_component]` is the foundational CGP macro: applied to a trait, it turns that ordinary Rust trait into a full CGP component — a consumer trait, a matching provider trait, and the blanket implementations that let a context delegate the trait's behavior to a freely chosen provider. + +## Purpose + +`#[cgp_component]` exists to lift a single trait into a pair of traits that separate *using* a capability from *implementing* it. A normal Rust trait conflates the two: the type that implements `Area` is the same type that callers invoke `.area()` on, and Rust's coherence rules then allow only one implementation per type. CGP breaks this conflation by generating two traits from one definition. The **consumer trait** is what callers use (`context.area()`); the **provider trait** is what implementers write, with the original `Self` moved into an explicit `Context` type parameter so that any number of provider types can implement it without colliding. + +The payoff is that providers become first-class, named, swappable units. Because a provider implements the provider trait for a generic `Context` rather than for itself, the usual orphan and overlap restrictions do not bite, and a crate can define many alternative providers for the same component. A concrete context then picks one provider per component through wiring (see [`delegate_components!`](delegate_components.md)), and the generated blanket impls route the consumer-trait call through that choice. `#[cgp_component]` is what makes a trait participate in this mechanism; without it, a trait is just a vanilla Rust trait. + +## Syntax + +The macro is applied as an attribute on a trait definition and takes the provider trait's name as its argument. The simplest form passes a bare identifier: + +```rust +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} +``` + +Here `CanCalculateArea` is the consumer trait (named in verb form, `CanDoSomething`) and `AreaCalculator` is the provider trait (named in noun form). When more control is needed, the macro accepts a key/value form instead of a bare identifier: + +```rust +#[cgp_component { + name: AreaCalculatorComponent, + provider: AreaCalculator, + context: Context, +}] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} +``` + +The three keys correspond to the three names the macro needs, and each has a default. The `provider` key sets the provider trait name and is the only required value; passing a bare identifier is shorthand for setting `provider` alone. The `name` key sets the component name type and defaults to the provider name with a `Component` suffix, so `AreaCalculator` yields `AreaCalculatorComponent`. The `context` key sets the identifier used for the generic context type parameter in the generated provider trait and defaults to `__Context__` — a deliberately unusual name chosen to avoid clashing with the user's own type parameters. + +Two companion attributes extend the macro for special cases and are documented separately. Adding [`#[derive_delegate(...)]`](../attributes/derive_delegate.md) generates `UseDelegate` providers that dispatch on a generic parameter, and adding [`#[extend(...)]`](../attributes/extend.md) adds supertrait bounds to the generated consumer trait. The related macros [`#[cgp_type]`](cgp_type.md) and [`#[cgp_getter]`](cgp_getter.md) build on `#[cgp_component]` to derive additional constructs for abstract-type and getter components respectively. + +## Syntax Grammar + +The attribute argument of `#[cgp_component]` is either a bare provider name or a comma-separated set of keyed values: + +```ebnf +CgpComponentArgs -> ProviderName + | KeyValueArg ( `,` KeyValueArg )* `,`? + +ProviderName -> IDENTIFIER + +KeyValueArg -> `name` `:` ComponentName + | `provider` `:` IDENTIFIER + | `context` `:` IDENTIFIER + +ComponentName -> IDENTIFIER GenericArgs? +``` + +`ProviderName` is the bare-identifier form and is shorthand for setting `provider` alone. In the key/value form each of the three keys may appear at most once and in any order, and `provider` is required — the other two have defaults (`context` is `__Context__`, `name` is the provider name with a `Component` suffix). `IDENTIFIER` is a Rust identifier token, and `GenericArgs` is the Rust grammar's `< … >` argument list (so the component name may carry generic parameters while the provider name may not). The attribute delimiter shown in Syntax — `(...)` for the bare form and `{...}` for the key/value form — is ordinary Rust attribute syntax; the argument tokens inside follow this grammar regardless of which delimiter is used. + +## Expansion + +`#[cgp_component]` replaces the annotated trait with five top-level items plus a set of standard provider impls. Starting from this input: + +```rust +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} +``` + +the macro emits, first, the **consumer trait** unchanged from its definition: + +```rust +pub trait CanCalculateArea { + fn area(&self) -> f64; +} +``` + +Second, it emits the **provider trait**, which is the consumer trait with `Self` replaced by an explicit leading `Context` type parameter and every `self`/`Self` reference rewritten to `context`/`Context`. The provider trait carries an [`IsProviderFor`](../traits/is_provider_for.md) supertrait that captures the component name and context so that unsatisfied dependencies surface as readable compiler errors. The supertrait's third argument is the `Params` tuple of the component's extra type parameters; for a component with no parameters beyond the context it is the empty `()`: + +```rust +pub trait AreaCalculator: + IsProviderFor +{ + fn area(context: &Context) -> f64; +} +``` + +Third, it emits the **consumer blanket impl**, which says that any context implementing the provider trait *for itself* automatically gets the consumer trait. This is the bridge that lets callers write `context.area()`: + +```rust +impl CanCalculateArea for Context +where + Context: AreaCalculator, +{ + fn area(&self) -> f64 { + Context::area(self) + } +} +``` + +Fourth, it emits the **provider blanket impl**, which lets any provider that delegates this component (via [`DelegateComponent`](../traits/delegate_component.md)) inherit the provider trait from the provider it delegates to. This is the mechanism `delegate_components!` drives — it turns a context into a type-level table whose entry for `AreaCalculatorComponent` names the chosen provider: + +```rust +impl AreaCalculator for Provider +where + Provider: DelegateComponent + + IsProviderFor, + Provider::Delegate: AreaCalculator, +{ + fn area(context: &Context) -> f64 { + Provider::Delegate::area(context) + } +} +``` + +Note that the `IsProviderFor` bound sits on `Provider` itself, beside the `DelegateComponent` bound — not on `Provider::Delegate`. The blanket impl of `IsProviderFor` (generated by `delegate_components!`) forwards a component's dependencies through `Provider`, so requiring `Provider: IsProviderFor<…>` is what threads those dependencies down the delegation chain and surfaces them in error messages. + +Fifth, it emits the **component name struct**, a zero-sized marker that serves as the key into delegation tables: + +```rust +pub struct AreaCalculatorComponent; +``` + +Beyond these five items, the macro also generates standard provider impls that make the component usable in the patterns CGP relies on. It emits a [`UseContext`](../providers/use_context.md) impl, so that the provider trait can be satisfied by routing back through a context's own consumer-trait implementation; a `RedirectLookup` impl, used by the namespace and preset machinery; and, for each [`#[derive_delegate(...)]`](../attributes/derive_delegate.md) attribute present, a [`UseDelegate`](../providers/use_delegate.md) impl that dispatches on the named generic parameter. When the component is defined inside a namespace, prefix impls are emitted as well (see [`#[cgp_namespace]`](cgp_namespace.md)). + +Two details of the expansion are worth holding onto because they are easy to get wrong. The generated type parameters carry reserved names, not the readable ones used above: the context parameter is literally `__Context__` unless overridden, and the provider parameter in the provider blanket impl is `__Provider__`. The examples here use `Context` and `Provider` for legibility, but the emitted code uses the reserved names. And the generic parameters of a component with type parameters (for example `CanCalculateArea`) are appended *after* the context parameter in the provider trait and grouped into a parenthesized list in the `IsProviderFor` `Params` position — so `IsProviderFor` for one parameter and `(Shape, Scalar)` for several — see generic parameters in the `/cgp` skill for the multi-parameter rules. + +## Examples + +A complete component, from definition through wiring to use, ties the pieces together. First the component and a provider for it: + +```rust +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_auto_getter] +pub trait HasDimensions { + fn width(&self) -> &f64; + fn height(&self) -> &f64; +} + +#[cgp_impl(new RectangleArea)] +impl AreaCalculator +where + Self: HasDimensions, +{ + fn area(&self) -> f64 { + self.width() * self.height() + } +} +``` + +Then a concrete context that wires the component to that provider and uses it: + +```rust +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +delegate_components! { + Rectangle { + AreaCalculatorComponent: RectangleArea, + } +} + +fn print_area(rect: &Rectangle) { + println!("area = {}", rect.area()); // CanCalculateArea, via RectangleArea +} +``` + +The call `rect.area()` resolves through the consumer blanket impl to `Rectangle::area(rect)`, which resolves through the provider blanket impl to `RectangleArea::area(rect)` because `Rectangle`'s table maps `AreaCalculatorComponent` to `RectangleArea`. + +## Related constructs + +`#[cgp_component]` is the root that most other constructs attach to. [`#[cgp_impl]`](cgp_impl.md) and [`#[cgp_provider]`](cgp_provider.md) are the idiomatic ways to write providers for a component; [`#[cgp_fn]`](cgp_fn.md) is the lighter-weight alternative when only one implementation is ever needed. [`delegate_components!`](delegate_components.md) wires a component to a provider on a concrete context, and [`check_components!`](check_components.md) verifies at compile time that the wiring is complete. The specialized forms [`#[cgp_type]`](cgp_type.md) and [`#[cgp_getter]`](cgp_getter.md) extend `#[cgp_component]` for abstract types and getters. The attributes [`#[derive_delegate]`](../attributes/derive_delegate.md), [`#[extend]`](../attributes/extend.md), and [`#[use_type]`](../attributes/use_type.md) modify what the macro generates. + +## Source + +The macro entry point is `cgp_component` in [crates/macros/cgp-macro-lib/src/cgp_component.rs](../../../crates/macros/cgp-macro-lib/src/cgp_component.rs), which drives the `preprocess → eval → to_items` pipeline. The logic lives in [crates/macros/cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/): argument parsing in `args/`, the provider trait and blanket impls in `preprocessed/`, and the standard provider impls (`UseContext`, `RedirectLookup`, `UseDelegate`) in `evaluated/`. The default identifiers `__Context__` and `{Provider}Component` are set in `args/component_args.rs`. Behavioral and expansion-snapshot tests are in [crates/tests/cgp-tests](../../../crates/tests/cgp-tests) and [crates/tests/cgp-macro-tests](../../../crates/tests/cgp-macro-tests). diff --git a/docs/reference/macros/cgp_computer.md b/docs/reference/macros/cgp_computer.md new file mode 100644 index 00000000..10e41def --- /dev/null +++ b/docs/reference/macros/cgp_computer.md @@ -0,0 +1,148 @@ +# `#[cgp_computer]` + +`#[cgp_computer]` is an attribute macro that turns a plain function into a [`Computer`](../components/computer.md) provider, generating the provider struct, the provider impl, and the wiring that fills in the rest of the handler family by promotion. + +## Purpose + +`#[cgp_computer]` exists to make the simplest handler — a pure function from input to output — definable as an ordinary Rust function, the way [`#[cgp_fn]`](cgp_fn.md) makes a blanket-impl trait definable as a function. A handler in CGP is normally a provider struct with one or more impls across the [`Computer`](../components/computer.md), [`TryComputer`](../components/try_computer.md), `AsyncComputer`, and [`Handler`](../components/handler.md) traits, each carrying the `context`/`code`/`input` plumbing. Writing that by hand for a computation as small as "add two numbers" is disproportionate boilerplate. `#[cgp_computer]` lets the author write just the computation as a function and have the macro synthesize the provider and wire it into every member of the handler family. + +The macro encodes a single decision: whether the function is synchronous or asynchronous, and whether it returns a plain value or a `Result`. From that it picks the right base trait to implement and the right [promotion bundle](../providers/handler_combinators.md) to derive the rest of the family from. The result is that one function definition yields a provider usable wherever any handler shape — `compute`, `try_compute`, `compute_async`, `handle`, and their `…Ref` variants — is expected. + +## Syntax + +`#[cgp_computer]` is applied to a free function and accepts an optional provider name as its argument: + +```rust +#[cgp_computer] +fn add(a: u64, b: u64) -> u64 { + a + b +} + +#[cgp_computer(MyAdder)] +fn add(a: u64, b: u64) -> u64 { + a + b +} +``` + +When the argument is omitted, the generated provider struct takes the function name converted to PascalCase — `add` becomes `Add`. When an argument is given, it is used verbatim as the provider name. The function's parameters become the handler's input, its return type becomes the handler's output, and its generic parameters and `where` clause carry over to the generated impl. The function may not have a `self` receiver, since a handler provider has no receiver — the context is supplied separately by the handler machinery. The function may be `async`, which selects the asynchronous base trait described below. + +## Syntax Grammar + +The attribute argument of `#[cgp_computer]` is a single optional provider name: + +```ebnf +CgpComputerArgs -> ProviderName? + +ProviderName -> IDENTIFIER +``` + +When the argument is omitted, the generated provider struct takes the function name converted to PascalCase; a given `IDENTIFIER` is used verbatim. The shape of the annotated function — its parameters, return type, `async`-ness, generics, and `where` clause — is plain Rust and is read by the macro to choose the base trait and promotion bundle, as described in Expansion. + +## Expansion + +The macro emits three items: the original function unchanged, a `#[cgp_new_provider]` impl of a base handler trait that calls the function, and a `delegate_components!` block that wires the remaining handler components to a promotion bundle. The base trait and the bundle are chosen from two independent axes — sync versus async, and value-returning versus `Result`-returning. + +For a synchronous function returning a plain value, the base trait is [`Computer`](../components/computer.md). The function's parameters are collected into a tuple that becomes the single `Input` type, and the body destructures that tuple back into the original arguments before calling the function. Given + +```rust +#[cgp_computer] +fn add(a: u64, b: u64) -> u64 { + a + b +} +``` + +the macro expands to the function plus: + +```rust +#[cgp_new_provider] +impl<__Context__, __Code__> Computer<__Context__, __Code__, (u64, u64)> for Add { + type Output = u64; + + fn compute( + _context: &__Context__, + _code: PhantomData<__Code__>, + (arg_0, arg_1): (u64, u64), + ) -> Self::Output { + add(arg_0, arg_1) + } +} + +delegate_components! { + Add { + [ + ComputerRefComponent, + TryComputerComponent, + TryComputerRefComponent, + AsyncComputerComponent, + AsyncComputerRefComponent, + HandlerComponent, + HandlerRefComponent, + ] -> + PromoteComputer, + } +} +``` + +The `#[cgp_new_provider]` attribute defines the `Add` struct and the `IsProviderFor` impl alongside the `Computer` impl, exactly as it does for any provider. The context and code generic parameters are introduced under the reserved names `__Context__` and `__Code__`. The `delegate_components!` block routes every other handler component to [`PromoteComputer`](../providers/handler_combinators.md), the promotion bundle that derives `TryComputer`, `AsyncComputer`, `Handler`, and all the `…Ref` variants from a `Computer` base. The result is that `Add` answers `compute`, `try_compute`, `compute_async`, and `handle`, all computing `a + b`. + +When the function returns a `Result`, the base trait stays `Computer` — its `Output` is the `Result` type as written — but the promotion bundle changes to [`PromoteTryComputer`](../providers/handler_combinators.md), which interprets the `Result`-valued computer as a genuinely fallible handler. So + +```rust +#[cgp_computer] +fn add_with_error(a: u64, b: u64) -> Result { + a.checked_add(b).ok_or_else(|| "Overflow".to_string()) +} +``` + +produces a `Computer` impl whose `Output` is `Result`, and a `delegate_components!` block routing the rest of the family to `PromoteTryComputer`. Through that bundle, `try_compute` and `handle` surface the `Ok`/`Err` outcome as the handler's success or failure rather than as a plain value. + +When the function is `async`, the base trait is `AsyncComputer` instead of `Computer`, the generated method is `compute_async` and `.await`s the function call, and the promotion bundle is the async-base counterpart. A plain-value async function wires the remaining components to [`PromoteAsyncComputer`](../providers/handler_combinators.md); an async function returning a `Result` wires them to [`PromoteHandler`](../providers/handler_combinators.md). In both async cases the macro delegates a smaller set of components — `AsyncComputerRefComponent`, `HandlerComponent`, and `HandlerRefComponent` — since the synchronous members of the family are not derived from an async base. + +Generic parameters and bounds on the function flow into the impl. A function such as + +```rust +#[cgp_computer] +pub fn add_generic>(a: T, b: T) -> T { + a + b +} +``` + +carries its `T` and the `T: Add` bound onto the generated `Computer` impl (appended ahead of the introduced `__Context__` and `__Code__` parameters), so the provider is itself generic over `T`. A reference parameter is likewise preserved: `fn to_string_ref(value: &Value) -> String` becomes a `Computer` whose input tuple is `(&Value)`, and the `PromoteComputer` bundle's `PromoteRef` entries make it serve the `…Ref` components as well. + +## Examples + +A self-contained use defines two computers and wires a context that supplies the error type, then exercises every handler shape from the single function definitions: + +```rust +use cgp::prelude::*; +use cgp::extra::handler::{Computer, TryComputer, AsyncComputer, Handler}; + +#[cgp_computer] +fn add(a: u64, b: u64) -> u64 { + a + b +} + +pub struct App; + +delegate_components! { + App { + ErrorTypeProviderComponent: UseType, + } +} + +// All four shapes are answered by the single `add` definition: +// Add::compute(&App, PhantomData::<()>, (1, 2)) == 3 +// Add::try_compute(&App, PhantomData::<()>, (1, 2)) == Ok(3) +// Add::compute_async(&App, PhantomData::<()>, (1, 2)) resolves to 3 +// Add::handle(&App, PhantomData::<()>, (1, 2)) resolves to Ok(3) +``` + +Because the function returns a plain `u64`, the `try_compute` and `handle` forms always succeed with `Ok`. Switching the function to return `Result` instead — as `add_with_error` above — makes those same forms propagate the `Err` when the computation fails, with no change to the call sites beyond the now-fallible outcome. + +## Related constructs + +`#[cgp_computer]` defines a provider for the [`Computer`](../components/computer.md) component (or `AsyncComputer` for async functions), part of the handler family described in [handlers](../../concepts/handlers.md). It is the handler-world analogue of [`#[cgp_fn]`](cgp_fn.md), which defines a blanket-impl trait from a function; and the input-less counterpart [`#[cgp_producer]`](cgp_producer.md) defines a [`Producer`](../components/producer.md) the same way. The generated impl is emitted through [`#[cgp_new_provider]`](cgp_new_provider.md), and the rest of the handler family is filled in by the [promotion bundles](../providers/handler_combinators.md) (`PromoteComputer`, `PromoteTryComputer`, `PromoteAsyncComputer`, `PromoteHandler`) wired through [`delegate_components!`](delegate_components.md). + +## Source + +The macro entrypoint is [crates/macros/cgp-extra-macro/src/lib.rs](../../../crates/macros/cgp-extra-macro/src/lib.rs), forwarding to the implementation in [crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs); the `Result`-versus-value detection is the `MaybeResultType` parser in [crates/macros/cgp-extra-macro-lib/src/parse/maybe_result.rs](../../../crates/macros/cgp-extra-macro-lib/src/parse/maybe_result.rs). The base `Computer`/`AsyncComputer` traits are defined in [crates/extra/cgp-handler/src/components/](../../../crates/extra/cgp-handler/src/components/), and the promotion bundles in [crates/extra/cgp-handler/src/providers/promote_all.rs](../../../crates/extra/cgp-handler/src/providers/promote_all.rs). Behavioral and snapshot tests live in [crates/tests/cgp-tests/tests/handler_tests/computer_macro.rs](../../../crates/tests/cgp-tests/tests/handler_tests/computer_macro.rs). diff --git a/docs/reference/macros/cgp_fn.md b/docs/reference/macros/cgp_fn.md new file mode 100644 index 00000000..a9891669 --- /dev/null +++ b/docs/reference/macros/cgp_fn.md @@ -0,0 +1,173 @@ +# `#[cgp_fn]` + +`#[cgp_fn]` turns a plain Rust function into a single-implementation CGP capability — it generates a trait and a blanket impl for every context from one function body, so a context gains the method with no separate wiring step. + +## Purpose + +`#[cgp_fn]` exists to make the simplest, most common form of CGP reachable with nothing more than a function. Writing a capability by hand means defining a trait, writing a blanket impl over a generic context, and threading the dependencies the body needs through that impl's `where` clause. `#[cgp_fn]` collapses all of that into a single function: you write the body as if `self` were a concrete value, mark the values you want pulled from the context with `#[implicit]`, and the macro produces the trait and the blanket impl that wires it up. + +The result is a capability that any context implements automatically, as long as the context can satisfy the impl-side dependencies. Because the generated impl is a blanket impl over a generic context, there is no `delegate_components!` call, no provider type, and no component name — the method simply becomes available on every type that has the fields the body reads. This is what makes `#[cgp_fn]` the recommended entry point for basic CGP: a reader only needs to understand plain Rust functions to use it, and the trait machinery stays hidden. + +The trade-off against [`#[cgp_component]`](cgp_component.md) is single versus multiple implementations. A `#[cgp_component]` trait can have many alternative providers, one chosen per context through wiring; that flexibility is exactly what costs the extra ceremony. `#[cgp_fn]` permits only one implementation — the function body — and in exchange removes the wiring entirely. Reach for `#[cgp_fn]` when a capability has a single natural definition, and graduate to `#[cgp_component]` only when a context genuinely needs to swap in a different implementation. The two interoperate: a `#[cgp_fn]` capability can depend on a `#[cgp_component]` one and vice versa through [`#[uses]`](../attributes/uses.md). + +## Syntax + +`#[cgp_fn]` is applied as an attribute on a free function whose first parameter is `&self` (or `&mut self`). The function name, in snake case, becomes the generated method name; the trait name defaults to that function name converted to PascalCase. + +```rust +#[cgp_fn] +pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} +``` + +Any parameter marked `#[implicit]` is removed from the method signature and instead fetched from a field of the context whose name matches the parameter. The function above therefore generates a `RectangleArea` trait with a `rectangle_area(&self) -> f64` method, and the body reads `width` and `height` from the context rather than from arguments. + +The trait name can be set explicitly by passing an identifier as the attribute argument, which overrides the PascalCase default. This is useful when the verb-style trait name reads better than the function name: + +```rust +#[cgp_fn(CanCalculateRectangleArea)] +pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} +``` + +Generic parameters and a `where` clause on the function are handled with a deliberate split. Every generic parameter declared in the function's `<...>` list goes onto both the generated trait and the impl. The `where` clause, by contrast, is treated as an impl-side dependency: it lands only on the impl, hidden from the trait interface, exactly as the constraints in a hand-written blanket impl would be. + +```rust +#[cgp_fn] +pub fn rectangle_area( + &self, + #[implicit] width: Scalar, + #[implicit] height: Scalar, +) -> Scalar +where + Scalar: Mul + Copy, +{ + width * height +} +``` + +Here `Scalar` appears on both `RectangleArea` (the trait) and its impl, while `Scalar: Mul + Copy` appears only on the impl. A bound that should apply to the impl but not be a trait parameter can instead be written with the `#[impl_generics(...)]` attribute, which adds generic parameters to the impl block alone: + +```rust +#[cgp_fn] +#[impl_generics(Name: Display)] +pub fn greet(&self, #[implicit] name: &Name) -> String { + format!("Hello, {}!", name) +} +``` + +One restriction is intentional: `#[cgp_fn]` does not support generics on the desugared *method* itself. Generics belong to the trait and impl, not to the generated method signature. Method-level generics are uncommon in CGP and, where genuinely needed, are considered an advanced case better written as an explicit blanket impl or a [`#[cgp_component]`](cgp_component.md) provider. + +Several companion attributes refine the generated code and are documented separately. [`#[uses(...)]`](../attributes/uses.md) adds trait bounds on `Self` as impl-side dependencies; [`#[use_type(...)]`](../attributes/use_type.md) imports an abstract type and rewrites its occurrences to fully-qualified form; [`#[use_provider(...)]`](../attributes/use_provider.md) supports higher-order providers; [`#[extend(...)]`](../attributes/extend.md) adds supertrait bounds to the generated trait; and [`#[extend_where(...)]`](../attributes/extend_where.md) adds `where` predicates to the generated trait definition. + +## Syntax Grammar + +The attribute argument of `#[cgp_fn]` is a single optional trait name: + +```ebnf +CgpFnArgs -> TraitName? + +TraitName -> IDENTIFIER +``` + +When the argument is omitted, the trait name defaults to the function name converted to PascalCase. The `#[implicit]` markers on parameters and the companion attributes (`#[uses]`, `#[use_type]`, `#[extend]`, and the rest) are separate attributes with their own grammars, documented on their own pages. + +## Expansion + +`#[cgp_fn]` emits exactly two items: the trait carrying the method, and a blanket impl of that trait for a generic context. Starting from the basic form: + +```rust +#[cgp_fn] +pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} +``` + +the macro produces the trait — the `#[implicit]` parameters stripped from the signature — followed by the blanket impl over the reserved context type `__Context__`. The implicit parameters become `HasField` bounds on the impl and `get_field` bindings at the top of the body: + +```rust +pub trait RectangleArea { + fn rectangle_area(&self) -> f64; +} + +impl<__Context__> RectangleArea for __Context__ +where + Self: HasField + + HasField, +{ + fn rectangle_area(&self) -> f64 { + let width: f64 = self.get_field(PhantomData::).clone(); + let height: f64 = self.get_field(PhantomData::).clone(); + + width * height + } +} +``` + +The generated context type parameter is literally `__Context__`, not `Context` — the same reserved name `#[cgp_component]` uses — and references to it inside the impl appear as `Self`. The `Symbol!("...")` shorthand stands for the type-level string the macro actually emits (for `width`, `Symbol<5, Chars<'w', Chars<'i', Chars<'d', Chars<'t', Chars<'h', Nil>>>>>>`). Each implicit binding follows the same conversion rules as [`#[cgp_auto_getter]`](cgp_auto_getter.md): an owned value gets a trailing `.clone()`, and a `&str` return gets `.as_str()`, while a borrowed `&Name` is taken by reference with no conversion. + +Generics and the `where` clause expand according to the split described above. Given the `Scalar` example, the generic goes on both trait and impl while the function's `where` bound stays on the impl, ordered before the implicit `HasField` bounds — the implicit bounds are always appended last: + +```rust +pub trait RectangleArea { + fn rectangle_area(&self) -> Scalar; +} + +impl<__Context__, Scalar> RectangleArea for __Context__ +where + Scalar: Mul + Copy, + Self: HasField + + HasField, +{ + fn rectangle_area(&self) -> Scalar { /* ... */ } +} +``` + +The bounds contributed by the companion attributes are layered into this same impl. `#[uses(Trait)]` and `#[extend(Trait)]` push a `Self: Trait` predicate onto the impl's `where` clause; `#[extend(Trait)]` additionally adds `Trait` as a supertrait of the generated trait, and `#[extend_where(...)]` adds its predicates to the trait's own `where` clause. `#[impl_generics(...)]` inserts its parameters into the impl generics only. The implicit-argument bounds are always appended last, after the attribute-contributed predicates. + +## Examples + +A two-layer capability shows `#[cgp_fn]` composing with itself through `#[uses]`. The first function defines the base area calculation; the second builds a scaled version on top of it, declaring its dependency on the first with `#[uses(RectangleArea)]`: + +```rust +use cgp::prelude::*; + +#[cgp_fn] +pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} + +#[cgp_fn] +#[uses(RectangleArea)] +pub fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { + self.rectangle_area() * scale_factor * scale_factor +} +``` + +A concrete context only needs the right fields; no wiring is required. Deriving `HasField` is enough for both capabilities to apply automatically: + +```rust +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, + pub scale_factor: f64, +} + +fn report(rect: &Rectangle) { + println!("base area = {}", rect.rectangle_area()); + println!("scaled area = {}", rect.scaled_rectangle_area()); +} +``` + +Because `Rectangle` derives `HasField` and carries `width`, `height`, and `scale_factor`, it satisfies the `HasField` bounds on both generated impls, so `rect.rectangle_area()` and `rect.scaled_rectangle_area()` both resolve directly through the blanket impls — there is no `delegate_components!` block anywhere in this example. + +## Related constructs + +`#[cgp_fn]` is the lightweight counterpart to [`#[cgp_component]`](cgp_component.md): both produce a capability usable through a method call, but `#[cgp_fn]` allows a single implementation with no wiring, whereas `#[cgp_component]` allows many providers selected per context through [`delegate_components!`](delegate_components.md). It shares its `#[implicit]` argument mechanism with [`#[cgp_impl]`](cgp_impl.md), and the field-access semantics with [`#[cgp_auto_getter]`](cgp_auto_getter.md) and the underlying [`HasField`](../traits/has_field.md) trait. The companion attributes [`#[uses]`](../attributes/uses.md), [`#[use_type]`](../attributes/use_type.md), [`#[use_provider]`](../attributes/use_provider.md), [`#[extend]`](../attributes/extend.md), and [`#[extend_where]`](../attributes/extend_where.md) shape what the macro generates. Like a hand-written extension trait, the impl `#[cgp_fn]` emits is a blanket impl in the style of [`#[blanket_trait]`](blanket_trait.md); the difference is that `#[cgp_fn]` derives the trait and its body from a function rather than from a trait with default methods. + +## Source + +The macro entry point is `cgp_fn` in [crates/macros/cgp-macro-lib/src/cgp_fn.rs](../../../crates/macros/cgp-macro-lib/src/cgp_fn.rs), which parses the optional trait-name identifier and the function, then runs `item.preprocess()?.to_items()?`. The logic lives in [crates/macros/cgp-macro-core/src/types/cgp_fn/](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/): `item.rs` performs the PascalCase default-name derivation (`to_camel_case_str`), implicit-argument extraction, and attribute parsing; `preprocessed.rs` builds the trait in `to_item_trait` and the blanket impl in `to_item_impl`, including the generics/`where`-clause split and the insertion of the leading `__Context__` parameter. Implicit-argument handling is in [crates/macros/cgp-macro-core/src/types/implicits/](../../../crates/macros/cgp-macro-core/src/types/implicits/), and the companion-attribute parsing is in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs). Behavioral and expansion-snapshot tests are in [crates/tests/cgp-tests/tests/cgp_fn_tests/](../../../crates/tests/cgp-tests/tests/cgp_fn_tests/), covering the basic form (`basic.rs`), generics (`generics.rs`, `impl_generics.rs`), and each companion attribute (`uses.rs`, `use_type.rs`, `use_provider.rs`, `extend.rs`). diff --git a/docs/reference/macros/cgp_getter.md b/docs/reference/macros/cgp_getter.md new file mode 100644 index 00000000..cddd24cc --- /dev/null +++ b/docs/reference/macros/cgp_getter.md @@ -0,0 +1,153 @@ +# `#[cgp_getter]` + +`#[cgp_getter]` defines a getter as a full CGP component — the same convenience as [`#[cgp_auto_getter]`](cgp_auto_getter.md), but wired through [`#[cgp_component]`](cgp_component.md) and backed by a [`UseField`](../providers/use_field.md) provider so the field name can differ from the method name and the getter can be swapped per context. + +## Purpose + +`#[cgp_getter]` exists for getters that need to participate in CGP wiring rather than resolve to a single blanket impl. A getter trait describes a value the context can supply, and most of the time the value comes straight from a same-named field — the case [`#[cgp_auto_getter]`](cgp_auto_getter.md) handles. But CGP code routinely needs more flexibility: the context may store the value under a different field name, or different contexts may supply the value in different ways. `#[cgp_getter]` provides that flexibility by making the getter a genuine component with a provider trait, a component name, and a delegation entry. + +The trade-off against `#[cgp_auto_getter]` is wireable versus blanket. `#[cgp_auto_getter]` emits one blanket impl that fires automatically for any context whose field name matches the method name; there is nothing to wire and nothing to choose. `#[cgp_getter]` emits a component that a context must wire to a provider, but in exchange the field name is decoupled from the method name and the implementation can be selected per context. You pay one line of wiring to gain that decoupling. + +The decoupling is delivered through the `UseField` pattern. `#[cgp_getter]` automatically generates a `UseField` provider impl for the getter, so a context wires the getter to `UseField` and names whichever field it actually stores the value in — the field name lives in the wiring, not in the trait. Like any getter trait, a `#[cgp_getter]` trait can also be implemented directly on a concrete context when no wiring is desired. + +## Syntax + +The macro is applied to a getter trait the same way `#[cgp_auto_getter]` is, and accepts the same getter-method forms — `&self`/`&mut self` receivers and the `&str`, `Option<&T>`, `&[T]`, owned, and associated-type return shorthands. The simplest form takes no argument: + +```rust +#[cgp_getter] +pub trait HasName { + fn name(&self) -> &str; +} +``` + +Because `#[cgp_getter]` is an extension of `#[cgp_component]`, it needs a provider trait name, and it derives one from the trait name by default. When the trait name begins with `Has`, the macro strips that prefix and appends `Getter`, so `HasName` yields the provider `NameGetter` (and component name `NameGetterComponent`). You can override the provider name by passing it as an argument, exactly as with `#[cgp_component]`: + +```rust +#[cgp_getter(GetName)] +pub trait HasName { + fn name(&self) -> &str; +} +``` + +Here the provider trait is named `GetName` and the component `GetNameComponent`. The defaulting rule means `#[cgp_getter]` is at its most ergonomic when getter traits follow the `Has{Field}` naming convention. + +## Syntax Grammar + +The attribute argument of `#[cgp_getter]` is the same grammar as [`#[cgp_component]`](cgp_component.md)'s `CgpComponentArgs` — a bare provider name or the keyed `name`/`provider`/`context` form: + +```ebnf +CgpGetterArgs -> CgpComponentArgs // see #[cgp_component] +``` + +The only difference from `#[cgp_component]` is the default applied when `provider` is omitted: the macro derives the provider name from the trait name by stripping a leading `Has` and appending `Getter` (so `HasName` yields `NameGetter`). All other keys and their defaults behave exactly as documented for `#[cgp_component]`. + +## Expansion + +`#[cgp_getter]` expands to everything `#[cgp_component]` emits, plus a set of getter-specific provider impls. The component part is identical to a `#[cgp_component(NameGetter)]` definition — the consumer trait, the provider trait, the consumer and provider blanket impls, the `NameGetterComponent` marker, and the standard `UseContext` and `RedirectLookup` provider impls (see [`#[cgp_component]`](cgp_component.md) for that core expansion). On top of those, the macro adds the getter providers described below. A `UseFields` provider is always emitted; the `UseField` and `WithProvider` providers are emitted only when the getter trait has exactly one method, since both presuppose a single field to read. + +The most important addition is the `UseField` provider impl, which is what lets the field name differ from the method name. Starting from the single-method trait: + +```rust +#[cgp_getter] +pub trait HasName { + fn name(&self) -> &str; +} +``` + +the macro generates this impl for `UseField<__Tag__>`, where `__Tag__` is a free generic parameter standing in for the field name the context will choose at wiring time: + +```rust +impl<__Context__, __Tag__> NameGetter<__Context__> for UseField<__Tag__> +where + __Context__: HasField<__Tag__, Value = String>, +{ + fn name(__context__: &__Context__) -> &str { + __context__.get_field(PhantomData::<__Tag__>).as_str() + } +} +``` + +This is the contrast with `#[cgp_auto_getter]`, whose blanket impl hard-codes the tag to `Symbol!("name")`. Here the tag is a parameter, so wiring to `UseField` supplies `first_name` as `__Tag__` and the getter reads that field instead. The `&str` shorthand is handled the same way in both macros: the `Value` is `String` and the body appends `.as_str()`. + +When the getter trait contains exactly one method, the macro additionally generates a `WithProvider` impl, which adapts a [field-getter](../providers/use_field.md) provider into the getter component: + +```rust +impl<__Context__, __Provider__> NameGetter<__Context__> for WithProvider<__Provider__> +where + __Provider__: FieldGetter<__Context__, NameGetterComponent, Value = String>, +{ + fn name(__context__: &__Context__) -> &str { + __Provider__::get_field(__context__, PhantomData::).as_str() + } +} +``` + +Finally, the macro generates a `UseFields` impl — the analogue of the `#[cgp_auto_getter]` blanket impl, but as a provider — which reads each method's field keyed by the method name as a `Symbol!`: + +```rust +impl<__Context__> NameGetter<__Context__> for UseFields +where + __Context__: HasField, +{ + fn name(__context__: &__Context__) -> &str { + __context__.get_field(PhantomData::).as_str() + } +} +``` + +Each of these provider impls is paired with a matching `IsProviderFor` impl carrying the same `where` bounds, so that delegation propagates the dependency and check traits can report missing fields precisely. As elsewhere, the desugarings show `Symbol!("name")` in sugared form rather than its expanded `Symbol<...>` representation. + +## Examples + +A typical use wires the getter to `UseField` with a field name that differs from the method name, which is the case `#[cgp_auto_getter]` cannot express. The context stores the value in `first_name`, while the trait method is `name`: + +```rust +use cgp::prelude::*; + +#[cgp_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[derive(HasField)] +pub struct Person { + pub first_name: String, +} + +delegate_components! { + Person { + NameGetterComponent: UseField, + } +} + +fn greet(person: &Person) { + println!("Hello, {}!", person.name()); // reads the first_name field +} +``` + +Because `Person` wires `NameGetterComponent` to `UseField`, the generated `UseField` provider reads `Person`'s `first_name` field to implement `name()`, so `person.name()` returns the value stored in `first_name`. + +As with any getter trait, you can skip the wiring entirely and implement the consumer trait directly on a concrete context: + +```rust +pub struct Person { + pub full_name: String, +} + +impl HasName for Person { + fn name(&self) -> &str { + &self.full_name + } +} +``` + +The direct implementation is the most transparent option and shows that a `#[cgp_getter]` trait is, at bottom, an ordinary CGP component whose consumer trait can be implemented like any Rust trait. + +## Related constructs + +`#[cgp_getter]` is the wireable counterpart to [`#[cgp_auto_getter]`](cgp_auto_getter.md): the latter emits a single `HasField` blanket impl keyed by the method name, while `#[cgp_getter]` emits a full component plus a `UseField` provider so the field name can be chosen at wiring time. It is built on [`#[cgp_component]`](cgp_component.md), inheriting that macro's entire expansion and provider-name defaulting, and it is wired with [`delegate_components!`](delegate_components.md) and verified with [`check_components!`](check_components.md). The generated provider keys off [`UseField`](../providers/use_field.md) and reads fields produced by [`#[derive(HasField)]`](../derives/derive_has_field.md), keyed by [`Symbol!`](symbol.md). When the getter's return type is an abstract associated type, the construct overlaps with [`#[cgp_type]`](cgp_type.md). + +## Source + +The macro entry point is `cgp_getter` in [crates/macros/cgp-macro-lib/src/cgp_getter.rs](../../../crates/macros/cgp-macro-lib/src/cgp_getter.rs), which derives the default provider name (strip `Has`, append `Getter`), runs the `#[cgp_component]` `preprocess → eval` pipeline, then converts the result into `ItemCgpGetter` and emits the extra provider impls. The logic lives in [crates/macros/cgp-macro-core/src/types/cgp_getter/](../../../crates/macros/cgp-macro-core/src/types/cgp_getter/): `item.rs` assembles the items, `use_field.rs` builds the `UseField` impl with the free `__Tag__` parameter, `to_use_fields_impl.rs` builds the `UseFields` impl keyed by method name, and `with_provider.rs` builds the `WithProvider` impl. Getter-method parsing and the return-type shorthands are shared with `#[cgp_auto_getter]` in [crates/macros/cgp-macro-core/src/functions/getter/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/getter/parse.rs) and [crates/macros/cgp-macro-core/src/types/getter/](../../../crates/macros/cgp-macro-core/src/types/getter/). Behavioral and expansion-snapshot tests are in [crates/tests/cgp-tests/tests/getter.rs](../../../crates/tests/cgp-tests/tests/getter.rs) and the `getter_tests/` modules beside it (notably `string.rs` for the full `UseField`/`UseFields`/`WithProvider` expansion). diff --git a/docs/reference/macros/cgp_impl.md b/docs/reference/macros/cgp_impl.md new file mode 100644 index 00000000..92e83004 --- /dev/null +++ b/docs/reference/macros/cgp_impl.md @@ -0,0 +1,180 @@ +# `#[cgp_impl]` + +`#[cgp_impl]` is the idiomatic way to write a provider for a CGP component: it lets you write a provider implementation using consumer-trait-style syntax — `impl Trait for Context` with `self` and `Self` — and desugars it into the underlying provider-trait implementation that CGP actually requires. + +## Purpose + +`#[cgp_impl]` exists to hide the awkward shape of a raw provider impl behind syntax that reads like an ordinary trait implementation. A CGP provider trait, as generated by [`#[cgp_component]`](cgp_component.md), moves the original `Self` into an explicit leading `Context` type parameter and is implemented *for the named provider struct*, not for the context. Written by hand, that impl looks inside-out: the trait is `AreaCalculator`, the `Self` type is a dummy struct like `RectangleArea`, the receiver becomes a plain `context: &Context` parameter, and the body can never mention `self`. This is correct but unfamiliar, and it obscures the fact that a provider is conceptually just "an implementation of the consumer trait." + +`#[cgp_impl]` restores the familiar shape. You write the impl as though you were implementing the consumer trait directly on the context — keeping `self`, `Self`, and the consumer trait's method signatures — and the macro mechanically rewrites it into the provider-trait form. The provider's name is given in the attribute argument rather than in the `Self` position, so the impl header reads `impl AreaCalculator for Context` with `RectangleArea` named in the attribute. + +The one fact this convenience must not let you forget is that there is no provider value at runtime. Inside a `#[cgp_impl]` block, `self` and `Self` refer to the **context**, not to the provider struct. The provider struct is a type-level-only marker that names the implementation; it is never instantiated, and it has no fields you can read. The macro converts every `self` to the context value and every `Self` to the context type precisely because the context is the only value that exists when the method runs. + +## Syntax + +`#[cgp_impl]` is applied to an `impl` block, and its attribute argument names the provider. The provider name carries an optional `new` keyword and an optional explicit component type: + +```rust +#[cgp_impl(new RectangleArea)] +impl AreaCalculator +where + Self: HasDimensions, +{ + fn area(&self) -> f64 { + self.width() * self.height() + } +} +``` + +The attribute argument has three parts, of which only the provider name is required. The leading **`new`** keyword is optional and, when present, tells the macro to also emit `pub struct RectangleArea;` so you need not declare the provider struct separately. The **provider name** (`RectangleArea` above) is the type that occupies the `Self` position in the generated provider impl. An optional **`: ComponentType`** suffix overrides the component name used in the generated [`IsProviderFor`](../traits/is_provider_for.md) impl; when omitted, the component defaults to the provider trait's name with a `Component` suffix, so implementing `AreaCalculator` targets `AreaCalculatorComponent`. + +The `for Context` clause is optional. When you write `impl AreaCalculator` with no `for` and no leading context type parameter, the macro inserts a generic context parameter for you, using the reserved identifier `__Context__`. The block above is therefore equivalent to `impl<__Context__> AreaCalculator for __Context__`, with `self`/`Self` standing in for that inserted parameter. You may instead name the context explicitly — `impl AreaCalculator for Context` — when you need to bound it in the impl generics or refer to it by a readable name; both forms are accepted. + +A provider may be generic. Type parameters on the provider, such as a higher-order provider's inner provider, are written in the `Self` position of the attribute and in the impl generics together: + +```rust +#[cgp_impl(new ScaledAreaCalculator)] +#[use_provider(InnerCalculator: AreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + let base_area = InnerCalculator::area(self); + base_area * scale_factor * scale_factor + } +} +``` + +Several companion attributes are supported on a `#[cgp_impl]` block and are processed before the provider-trait rewrite. Method parameters marked [`#[implicit]`](../attributes/implicit.md) are extracted from the signature and turned into `HasField` reads on the context. [`#[uses(...)]`](../attributes/uses.md) adds simple trait bounds on `Self`, [`#[use_type(Trait::Type)]`](../attributes/use_type.md) imports an abstract type and rewrites its occurrences to fully qualified form, and [`#[use_provider(...)]`](../attributes/use_provider.md) supports higher-order providers by adding the `Self` parameter to an inner provider bound. + +## Syntax Grammar + +The attribute argument of `#[cgp_impl]` names the provider, optionally preceded by `new` and optionally followed by a component-type override: + +```ebnf +CgpImplArgs -> `new`? ProviderType ( `:` ComponentType )? + +ProviderType -> Type +ComponentType -> Type +``` + +The optional `new` keyword makes the macro also emit `pub struct ;`. `ProviderType` is the type that takes the `Self` position of the generated provider impl: a plain provider name, a generic provider such as `ScaledAreaCalculator`, or the literal `Self` for the bare-impl passthrough described in Expansion. The optional `: ComponentType` overrides the component used in the generated `IsProviderFor` impl, defaulting otherwise to the provider trait's name with a `Component` suffix. Both `ProviderType` and `ComponentType` are Rust `Type` productions. + +## Expansion + +`#[cgp_impl]` desugars to [`#[cgp_provider]`](cgp_provider.md): it moves the context type back to the leading position of the provider trait, swaps the provider name into the `Self` position, and rewrites every `self`/`Self` reference, then hands the result to the same machinery `#[cgp_provider]` uses. The clearest way to see this is a minimal example with the context named explicitly. Starting from: + +```rust +#[cgp_impl(new ValueToString)] +impl FooProvider for Context { + fn foo(&self, value: u32) -> String { + value.to_string() + } +} +``` + +the macro produces the provider impl, the `IsProviderFor` impl, and (because `new` was given) the provider struct: + +```rust +impl FooProvider for ValueToString { + fn foo(__context__: &Context, value: u32) -> String { + value.to_string() + } +} + +impl IsProviderFor for ValueToString {} + +pub struct ValueToString; +``` + +Three transformations happened here. The trait `FooProvider` gained `Context` as its leading type argument; the `Self` type of the impl changed from `Context` to the provider `ValueToString`; and the method receiver `&self` became the explicit parameter `__context__: &Context`. The receiver identifier is the snake-cased form of the context type wrapped in double underscores: both `Context` and the default `__Context__` become `__context__`. Every use of `self` in the body is rewritten to that identifier, while every use of `Self` is rewritten to the context type. + +When the `for Context` clause is omitted, the only difference is that the inserted context parameter is `__Context__`. The earlier `RectangleArea` example is equivalent to writing the context out by hand: + +```rust +#[cgp_impl(new RectangleArea)] +impl<__Context__> AreaCalculator for __Context__ +where + __Context__: HasDimensions, +{ + fn area(&self) -> f64 { + self.width() * self.height() + } +} +``` + +which desugars to: + +```rust +#[cgp_new_provider] +impl<__Context__> AreaCalculator<__Context__> for RectangleArea +where + __Context__: HasDimensions, +{ + fn area(__context__: &__Context__) -> f64 { + __context__.width() * __context__.height() + } +} +``` + +The generated `IsProviderFor` impl is a copy of the provider impl's signature with its body removed and the same `where` clause retained, so the provider's dependencies are captured for error reporting. The first argument is the component name, the second is the context type, and the third is a tuple of any remaining provider-trait type parameters. For a provider trait with multiple parameters, such as `ComputerRef`, the trailing parameters are grouped into that tuple — the `IsProviderFor` impl reads `IsProviderFor`. + +One special case bypasses the provider rewrite entirely. Writing `#[cgp_impl(Self)]` — naming `Self` as the provider — emits the `impl` block unchanged as an ordinary consumer-trait implementation on the concrete context. This requires the `for Context` clause to be present, and it is useful when you want to implement a consumer trait directly while still applying companion attributes such as [`#[use_provider]`](../attributes/use_provider.md): + +```rust +#[cgp_impl(Self)] +#[use_provider(RectangleArea: AreaCalculator)] +impl CanCalculateArea for Rectangle { + fn area(&self) -> f64 { + RectangleArea::area(self) + } +} +``` + +## Examples + +A complete provider written with `#[cgp_impl]`, using an implicit argument and read start-to-finish, shows the construct in its most idiomatic form. Given the `AreaCalculator` component from [`#[cgp_component]`](cgp_component.md): + +```rust +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_impl(new RectangleArea)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} +``` + +The `#[implicit]` parameters are removed from the signature and replaced by reads of the `width` and `height` fields from the context, so the provider depends on `Self: HasField` and the matching `height` bound. The provider struct `RectangleArea` is defined by the `new` keyword. A concrete context then wires the component to this provider and uses it: + +```rust +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +delegate_components! { + Rectangle { + AreaCalculatorComponent: RectangleArea, + } +} + +fn print_area(rect: &Rectangle) { + println!("area = {}", rect.area()); +} +``` + +The call `rect.area()` resolves through the consumer blanket impl to `Rectangle`'s table, which maps `AreaCalculatorComponent` to `RectangleArea`, and `RectangleArea::area` reads `rect.width` and `rect.height` to compute the result. + +## Related constructs + +`#[cgp_impl]` is the recommended way to implement a component defined by [`#[cgp_component]`](cgp_component.md), and it is one layer of sugar above [`#[cgp_provider]`](cgp_provider.md), which it desugars to; [`#[cgp_new_provider]`](cgp_new_provider.md) is the `new`-keyword equivalent at that lower layer. For the common case where only a single implementation is ever needed and no wiring is desired, [`#[cgp_fn]`](cgp_fn.md) is the lighter alternative. The companion attributes [`#[implicit]`](../attributes/implicit.md), [`#[uses]`](../attributes/uses.md), [`#[use_type]`](../attributes/use_type.md), and [`#[use_provider]`](../attributes/use_provider.md) all apply inside a `#[cgp_impl]` block. Once a provider is written, [`delegate_components!`](delegate_components.md) wires it onto a context and [`check_components!`](check_components.md) verifies the wiring. + +## Source + +The macro entry point is `cgp_impl` in [crates/macros/cgp-macro-lib/src/cgp_impl.rs](../../../crates/macros/cgp-macro-lib/src/cgp_impl.rs), which lowers the impl and emits the lowered provider impl alongside any default impls. The logic lives in [crates/macros/cgp-macro-core/src/types/cgp_impl/](../../../crates/macros/cgp-macro-core/src/types/cgp_impl/): attribute argument parsing (the `new` keyword, provider type, optional component type) in `args.rs`; the lowering that extracts implicit arguments, applies companion attributes, and inserts the `__Context__` parameter when `for` is omitted in `item.rs`; the rewrite of `self`/`Self` and the handoff to `#[cgp_provider]` in `lowered.rs`; and the bare-impl passthrough for `#[cgp_impl(Self)]` in `provider_or_bare.rs`. The `self`/`Self` rewriting is performed by the `ReplaceSelfType`, `ReplaceSelfReceiver`, and `ReplaceSelfValue` visitors in [crates/macros/cgp-macro-core/src/visitors/](../../../crates/macros/cgp-macro-core/src/visitors/). Expansion snapshots are in [crates/tests/cgp-tests](../../../crates/tests/cgp-tests), notably `tests/component_tests/cgp_impl/`. diff --git a/docs/reference/macros/cgp_namespace.md b/docs/reference/macros/cgp_namespace.md new file mode 100644 index 00000000..72b9f81b --- /dev/null +++ b/docs/reference/macros/cgp_namespace.md @@ -0,0 +1,213 @@ +# `#[cgp_namespace]` + +`cgp_namespace!` defines a *namespace* — a reusable, named lookup table that maps component keys to providers — so that a concrete context can inherit a whole group of wirings at once and still override individual entries. + +## Purpose + +`cgp_namespace!` exists to make groups of component wirings reusable across contexts. With [`delegate_components!`](delegate_components.md) alone, every context spells out its own table entry by entry; two contexts that should share the same wiring must repeat it. A namespace lifts that table out of any single context and gives it a name, turning "this exact set of providers" into a thing other contexts can refer to and build on. + +The mechanism that makes this work is a layer of indirection between a context's delegation table and the actual providers. A namespace is not itself a context; it is a trait (named after the namespace) carrying a `Delegate` associated type, implemented per key. A context that opts into a namespace forwards every lookup through that trait, so the namespace's entries become the context's defaults. The forwarding is keyed by a *path* — a type-level list of symbols and component names — rather than by a bare component name, which is what lets one namespace inherit from another and lets a context shadow a single inherited entry without disturbing the rest. + +The payoff is preset-style configuration with selective override. A context can say "use everything in this namespace" and then add a handful of its own entries that win over the inherited ones, because a directly-wired entry on the context resolves before the namespace fallback is consulted. This is the same inheritance-with-override pattern presets rely on, expressed entirely through the trait system with no runtime cost. + +## Syntax + +`cgp_namespace!` is a function-like macro whose body resembles a `delegate_components!` table with an optional namespace header. The simplest form defines a fresh namespace with `new` and lists entries that map component keys to redirect paths: + +```rust +cgp_namespace! { + new MyNamespace { + FooProviderComponent => + @MyFooComponent, + } +} +``` + +The `new` keyword tells the macro to also emit the namespace's marker struct and its lookup trait; omit it only when those are already declared elsewhere. `MyNamespace` is the namespace name, which becomes both a trait and (with `new`) a backing struct. The entries inside the braces are the namespace's wiring. + +Two distinct entry forms appear in the body, and they generate different table contents. A `=>` entry redirects a key to a path: `FooProviderComponent => @MyFooComponent` says "when this namespace is asked for `FooProviderComponent`, look up the path `@MyFooComponent` instead." A `:` entry maps a key directly to a provider, as in `delegate_components!`: `[String, u64]: ShowWithDisplay` makes the namespace resolve those keys straight to the `ShowWithDisplay` provider. Paths written with the `@` sigil — `@MyFooComponent`, `@app.ErrorRaiserComponent`, `@cgp.core.error` — are dotted sequences of symbols and type names that desugar into type-level path lists. + +A namespace can inherit from a parent namespace by naming it after a colon in the header: + +```rust +cgp_namespace! { + new ExtendedNamespace: DefaultNamespace { + @cgp.core.error => + @app, + } +} +``` + +Here `ExtendedNamespace` inherits every entry of `DefaultNamespace` and additionally rewrites the `@cgp.core.error` path prefix to `@app`. The parent may itself be parameterized (it is parsed as a path with type arguments), and the entries in the child body layer on top of the inherited ones. + +Defining a namespace is only half of the pattern; a context joins a namespace through `delegate_components!` using a `namespace` header line, and individual components attach to a namespace through the [`#[prefix(...)]`](cgp_component.md) attribute on their trait. Those two constructs are where namespaces are consumed, and they are described under Examples and Related constructs. + +## Syntax Grammar + +The body of `cgp_namespace!` is an optional generic list and `new` keyword, a namespace name, an optional parent namespace, and a brace-delimited table: + +```ebnf +CgpNamespace -> Generics? `new`? NamespaceName ( `:` ParentNamespace )? `{` NamespaceBody `}` + +NamespaceName -> IDENTIFIER GenericArgs? +ParentNamespace -> TypePath GenericArgs? + +NamespaceBody -> Statement* ( Mapping ( `,` Mapping )* `,`? )? +``` + +The mappings in `NamespaceBody` are the same `Mapping` production as [`delegate_components!`](delegate_components.md) — most often the `` `=>` `` redirect to an `@`-`Path` or a `` `:` `` direct provider. The `` `:` `` between `NamespaceName` and `ParentNamespace` is the inheritance colon, distinct from a mapping's `:`. `NamespaceName` is an identifier with optional generic arguments (it becomes both a trait and, with `new`, a struct); `ParentNamespace` is a type path that may itself be parameterized. + +This macro also owns the two namespace statement forms that a context's [`delegate_components!`](delegate_components.md) table uses to join a namespace: + +```ebnf +Statement -> NamespaceStmt | ForStmt + +NamespaceStmt -> `namespace` IDENTIFIER `;` + +ForStmt -> `for` `<` IDENTIFIER `,` IDENTIFIER `>` `in` TypePath WhereClause? + `{` ( NormalMapping ( `,` NormalMapping )* `,`? )? `}` + +NormalMapping -> Key `:` ProviderValue +``` + +A `NamespaceStmt` forwards every lookup on the table through the named namespace. A `ForStmt` binds a key variable and a provider variable, reads each entry of the table named after `in`, and emits one mapping per entry — its body holds only `` `:` `` mappings (`NormalMapping`), whose `Key` and `ProviderValue` are the shared productions from [`delegate_components!`](delegate_components.md). `TypePath` and `WhereClause` are Rust grammar productions. + +## Expansion + +`cgp_namespace!` emits, in order, an optional marker struct, an optional lookup trait, and one `impl` of that trait per entry (plus one inheritance `impl` when a parent is named). Take the `new` namespace with a single redirect entry: + +```rust +cgp_namespace! { + new MyNamespace { + FooProviderComponent => + @MyFooComponent, + } +} +``` + +Because `new` is present, the macro first emits a backing struct whose name is the namespace name wrapped in `__…Components`, then the lookup trait. The trait carries the table's generic `__Table__` parameter and a single `Delegate` associated type: + +```rust +pub struct __MyNamespaceComponents; + +pub trait MyNamespace<__Table__> { + type Delegate; +} +``` + +Each `=>` entry becomes an `impl` of that trait for the entry's key, whose `Delegate` is a [`RedirectLookup`](../providers/redirect_lookup.md) pointing the table at the entry's path. The `@MyFooComponent` path desugars into a `PathCons<…, Nil>` type-level list: + +```rust +impl<__Table__> MyNamespace<__Table__> for FooProviderComponent { + type Delegate = RedirectLookup<__Table__, PathCons>; +} +``` + +Reading this back: `MyNamespace<__Table__>::Delegate` for the key `FooProviderComponent` is "look up the path `MyFooComponent` inside whatever table `__Table__` is." `RedirectLookup` is a CGP-defined provider that resolves by delegating `Components` along `Path`; the namespace never names a concrete provider for this key, it only re-routes the lookup, so the actual provider is decided wherever the path eventually lands. + +A `:` entry instead maps the key directly to the named provider, with no `RedirectLookup` indirection. From the array form `[String, u64]: ShowWithDisplay`, the macro emits one `impl` per key: + +```rust +impl<__Table__> DefaultShowComponents<__Table__> for String { + type Delegate = ShowWithDisplay; +} +impl<__Table__> DefaultShowComponents<__Table__> for u64 { + type Delegate = ShowWithDisplay; +} +``` + +When a parent namespace is named, the macro prepends one extra blanket `impl` that forwards unmatched keys to the parent. For `new ExtendedNamespace: DefaultNamespace { … }`, the inherited entries arrive through this impl: + +```rust +impl<__Table__, __Key__, __Value__> ExtendedNamespace<__Table__> for __Key__ +where + __Key__: DefaultNamespace<__ExtendedNamespaceComponents>, + __Key__: DefaultNamespace<__Table__, Delegate = __Value__>, +{ + type Delegate = __Value__; +} +``` + +This says: for any `__Key__` the parent `DefaultNamespace` resolves, `ExtendedNamespace` resolves it to the same `__Value__`. The body entries of the child are emitted after this blanket impl and take precedence where their keys are more specific. The path-rewriting entry `@cgp.core.error => @app` becomes an impl keyed on the `cgp.core.error` path prefix whose `Delegate` is a `RedirectLookup` onto the `@app` prefix — rerouting an entire subtree of the parent's namespace rather than a single component. + +The other half of the pattern is what attaches a component to a namespace, via [`#[prefix(...)]`](cgp_component.md) on the component's trait. Given: + +```rust +#[cgp_component(BarProvider)] +#[prefix(@MyBarComponent in MyNamespace)] +pub trait Bar { + fn bar(&self); +} +``` + +`#[cgp_component]` emits its usual items, and `#[prefix]` adds one extra impl that registers `BarProviderComponent` into `MyNamespace` under the prefix path `@MyBarComponent`: + +```rust +impl<__Components__> MyNamespace<__Components__> for BarProviderComponent { + type Delegate = RedirectLookup< + __Components__, + PathCons>, + >; +} +``` + +So `MyNamespace`, asked for `BarProviderComponent`, redirects the lookup to the path `MyBarComponent → BarProviderComponent`. A component may carry several `#[prefix]` attributes to register itself into several namespaces at once. + +Two details of the expansion are worth holding onto. The table parameter is literally named `__Table__` and the inheritance blanket impl uses `__Key__`/`__Value__`; the examples keep those names because they appear verbatim in compiler errors. And every path under `@` becomes a `PathCons`/`Symbol`/`Chars` type-level list — `@my_app.MyFooComponent` expands to `PathCons>, PathCons>`, with dotted lowercase segments becoming `Symbol` string literals and capitalized segments becoming the named type. + +## Examples + +A namespace becomes useful once a context joins it and overrides part of it. Start with a namespace that supplies default per-type providers, defined with `new`: + +```rust +use cgp::prelude::*; + +cgp_namespace! { + new DefaultShowComponents { + [String, u64]: ShowWithDisplay, + } +} +``` + +A context then opts into a namespace inside `delegate_components!` with a `namespace` header line, and may add its own entries that win over the namespace defaults. Joining `DefaultNamespace` and pulling defaults in through a `for` loop over `DefaultShowComponents`: + +```rust +pub struct AppB; + +delegate_components! { + AppB { + namespace DefaultNamespace; + + for in DefaultShowComponents { + @test.ShowImplComponent.T: Provider, + } + } +} +``` + +The `namespace DefaultNamespace;` line makes `AppB` forward every component lookup through `DefaultNamespace`, and the `for … in DefaultShowComponents` block wires `AppB`'s `ShowImplComponent` entries by reading `DefaultShowComponents`'s `Delegate` for each type `T`. To override a single entry, a later direct line on the same context simply names a different provider for that key; because the context's own entry resolves before the namespace fallback, it shadows the inherited one without touching the others: + +```rust +delegate_components! { + AppA { + namespace DefaultNamespace; + + for in DefaultImpls1 { + @test.ShowImplComponent.T: Provider, + } + + @test.ShowImplComponent.u64: + ShowWithDisplay, // overrides the inherited entry for u64 + } +} +``` + +Inheritance composes the same way at the namespace level: `ExtendedNamespace: DefaultNamespace` produces a namespace that resolves everything `DefaultNamespace` does, plus the child's own entries, and any context joining `ExtendedNamespace` gets the merged result. + +## Related constructs + +`cgp_namespace!` sits between component definitions and context wiring, so it relates to constructs on both sides. [`#[cgp_component]`](cgp_component.md) defines the components whose keys a namespace maps, and its [`#[prefix(...)]`](cgp_component.md) attribute is what registers a component into a namespace under a path. [`delegate_components!`](delegate_components.md) is where a context joins a namespace (via its `namespace` header) and where individual overrides are written; [`delegate_and_check_components!`](delegate_and_check_components.md) does the same while also verifying the resulting wiring. The namespace's `Delegate` entries are resolved through [`RedirectLookup`](../providers/redirect_lookup.md), and per-type defaults are commonly expressed through [`use_delegate`](../providers/use_delegate.md)-style dispatch and the `DefaultNamespace` / `DefaultImpls1` traits in `cgp-component`. The underlying per-key table machinery is [`DelegateComponent`](../traits/delegate_component.md), which `RedirectLookup` walks at resolution time. + +## Source + +The macro entry point is `cgp_namespace` in [crates/macros/cgp-macro-lib/src/cgp_namespace.rs](../../../crates/macros/cgp-macro-lib/src/cgp_namespace.rs), which parses a `NamespaceTable` and calls `.eval()`. The logic lives in [crates/macros/cgp-macro-core/src/types/namespace/](../../../crates/macros/cgp-macro-core/src/types/namespace/): `table.rs` parses the header (`new`, namespace name, optional `: parent`) and builds the trait, struct, per-entry impls, and the parent-inheritance impl; `inherit.rs` builds the path-rewriting inheritance entry; `eval.rs` holds the emitted `EvaluatedNamespaceTable`. The `#[prefix(...)]` attribute that attaches a component to a namespace is parsed in [crates/macros/cgp-macro-core/src/types/attributes/prefix.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/prefix.rs), and the matching `RedirectLookup` provider impl is emitted by [crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_redirect_lookup_impl.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_redirect_lookup_impl.rs). The runtime traits `DefaultNamespace`/`DefaultImpls1`/`DefaultImpls2` are in [crates/core/cgp-component/src/namespaces.rs](../../../crates/core/cgp-component/src/namespaces.rs) and `RedirectLookup` in [crates/core/cgp-component/src/providers/redirect_lookup.rs](../../../crates/core/cgp-component/src/providers/redirect_lookup.rs). Expansion snapshots covering the basic, symbol-path, multi-namespace, extended, and default-impls cases are in [crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/](../../../crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/) and [crates/tests/cgp-tests/src/namespaces/](../../../crates/tests/cgp-tests/src/namespaces/). diff --git a/docs/reference/macros/cgp_new_provider.md b/docs/reference/macros/cgp_new_provider.md new file mode 100644 index 00000000..d121a58c --- /dev/null +++ b/docs/reference/macros/cgp_new_provider.md @@ -0,0 +1,118 @@ +# `#[cgp_new_provider]` + +`#[cgp_new_provider]` behaves exactly like [`#[cgp_provider]`](cgp_provider.md) but additionally declares the provider struct, so a provider trait implementation and its `Self` type can be defined in one place. + +## Purpose + +`#[cgp_new_provider]` exists to save the one line of boilerplate that almost always accompanies a fresh provider: the `pub struct ProviderName;` declaration. A provider struct is a type-level-only marker — it is never instantiated and holds no runtime state — so declaring it separately from the impl that gives it meaning is pure ceremony. `#[cgp_new_provider]` folds that declaration into the impl, producing the struct, the provider impl, and the generated [`IsProviderFor`](../traits/is_provider_for.md) impl together. + +Use `#[cgp_new_provider]` when you are introducing a new provider; use [`#[cgp_provider]`](cgp_provider.md) when the struct already exists, for example because it is declared with default generic parameters that the attribute form cannot express, or because several impls share one struct. + +## Syntax + +`#[cgp_new_provider]` is applied to a provider-trait impl and accepts the same optional component-type argument as [`#[cgp_provider]`](cgp_provider.md). The only requirement beyond `#[cgp_provider]` is that the provider struct must *not* already be declared, since the macro declares it: + +```rust +#[cgp_new_provider] +impl AreaCalculator for RectangleArea +where + Context: HasDimensions, +{ + fn area(context: &Context) -> f64 { + context.width() * context.height() + } +} +``` + +As with `#[cgp_provider]`, the impl uses the native provider-trait shape — an explicit leading `Context` type parameter, the provider struct in the `Self` position, and methods taking `context: &Context` rather than `&self`. An optional argument overrides the component type used in the `IsProviderFor` impl, defaulting otherwise to the provider trait's name plus a `Component` suffix. + +## Syntax Grammar + +The attribute argument of `#[cgp_new_provider]` is the same single optional component type as [`#[cgp_provider]`](cgp_provider.md): + +```ebnf +CgpNewProviderArgs -> ComponentType? + +ComponentType -> Type +``` + +The argument behaves exactly as it does for `#[cgp_provider]` — omitted means the component defaults to the provider trait's name plus a `Component` suffix, and a given `Type` overrides it. The struct declaration that distinguishes this macro is implied by the macro name and is not written in the argument. + +## Expansion + +`#[cgp_new_provider]` is implemented as `#[cgp_provider]` with the `new` keyword forced on; its expansion is therefore the `#[cgp_provider]` expansion plus a struct declaration. The example above produces: + +```rust +impl AreaCalculator for RectangleArea +where + Context: HasDimensions, +{ + fn area(context: &Context) -> f64 { + context.width() * context.height() + } +} + +impl IsProviderFor for RectangleArea +where + Context: HasDimensions, +{} + +pub struct RectangleArea; +``` + +The provider impl and the derived `IsProviderFor` impl are exactly what [`#[cgp_provider]`](cgp_provider.md) emits — see its Expansion section for how the component type, context type, and `Params` tuple are assembled. The one addition is `pub struct RectangleArea;`. + +The struct's shape is taken from the `Self` type of the impl. A plain provider name yields a unit struct as above. A generic provider yields a struct with a `PhantomData` field over its parameters, so the parameters are bound. For instance: + +```rust +#[cgp_new_provider] +impl Runner for SpawnAndRun +where + Context: 'static + Send + Clone + CanSendRun, +{ /* ... */ } +``` + +emits, in addition to the provider and `IsProviderFor` impls: + +```rust +pub struct SpawnAndRun(pub ::core::marker::PhantomData<(InCode)>); +``` + +## Examples + +Defining a complete provider in one block, struct included, is the common idiom for an implementation that has no struct yet: + +```rust +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_auto_getter] +pub trait HasDimensions { + fn width(&self) -> &f64; + fn height(&self) -> &f64; +} + +#[cgp_new_provider] +impl AreaCalculator for RectangleArea +where + Context: HasDimensions, +{ + fn area(context: &Context) -> f64 { + context.width() * context.height() + } +} +``` + +This is equivalent to writing `pub struct RectangleArea;` followed by the same impl annotated with [`#[cgp_provider]`](cgp_provider.md). In most code, the same provider would be written even more concisely with [`#[cgp_impl(new RectangleArea)]`](cgp_impl.md), whose `new` keyword plays the identical role of declaring the struct while also letting the body use `self`/`Self`. + +## Related constructs + +`#[cgp_new_provider]` is [`#[cgp_provider]`](cgp_provider.md) plus a struct declaration, and it implements a provider trait generated by [`#[cgp_component]`](cgp_component.md). Its closest relative is [`#[cgp_impl]`](cgp_impl.md) with the `new` keyword, which produces the same three items — struct, provider impl, and `IsProviderFor` impl — from consumer-trait-style syntax; `#[cgp_impl(new ...)]` desugars to `#[cgp_new_provider]`. A provider so defined is wired to a context with [`delegate_components!`](delegate_components.md) and checked with [`check_components!`](check_components.md). + +## Source + +The macro entry point is `cgp_new_provider` in [crates/macros/cgp-macro-lib/src/cgp_new_provider.rs](../../../crates/macros/cgp-macro-lib/src/cgp_new_provider.rs); it parses the same `ProviderArgs`, sets `new` to enabled, and then runs the identical lowering as [`#[cgp_provider]`](cgp_provider.md). All of the generation logic — including the struct declaration emitted when `new` is set — lives in [crates/macros/cgp-macro-core/src/types/cgp_provider/](../../../crates/macros/cgp-macro-core/src/types/cgp_provider/); the struct shape is built in `item.rs` (`to_provider_struct`). Expansion snapshots that exercise `#[cgp_new_provider]`, including the generic-struct case, are in [crates/tests/cgp-tests](../../../crates/tests/cgp-tests), notably `src/tests/compose.rs` and `src/tests/async/spawn.rs`. diff --git a/docs/reference/macros/cgp_producer.md b/docs/reference/macros/cgp_producer.md new file mode 100644 index 00000000..93c31091 --- /dev/null +++ b/docs/reference/macros/cgp_producer.md @@ -0,0 +1,121 @@ +# `#[cgp_producer]` + +`#[cgp_producer]` is an attribute macro that turns a no-argument function into a [`Producer`](../components/producer.md) provider, generating the provider struct, the provider impl, and the wiring that makes the produced value flow out of every handler shape. + +## Purpose + +`#[cgp_producer]` exists for the degenerate handler that takes no input at all: a computation that yields a value from nothing, such as a constant or a value drawn from the context. It is the input-less sibling of [`#[cgp_computer]`](cgp_computer.md), and like that macro it lets the author write a plain function and have the macro synthesize the provider struct and impl, sparing them the handler plumbing. Where `#[cgp_computer]` defines a [`Computer`](../components/computer.md), `#[cgp_producer]` defines a [`Producer`](../components/producer.md) — the handler-family member whose method takes only the context and `Code`, with no input value. + +Because a producer can stand in for any handler — a handler that ignores its input is just a producer with an unused parameter — the macro wires the generated producer into every member of the handler family. A single `#[cgp_producer]` function therefore answers `produce`, `compute`, `try_compute`, `compute_async`, `handle`, and all the `…Ref` variants, every one of them yielding the same produced value. + +## Syntax + +`#[cgp_producer]` is applied to a free function and accepts an optional provider name as its argument: + +```rust +#[cgp_producer] +fn magic_number() -> u64 { + 42 +} + +#[cgp_producer(TheAnswer)] +fn magic_number() -> u64 { + 42 +} +``` + +When the argument is omitted the provider struct takes the function name in PascalCase — `magic_number` becomes `MagicNumber` — and when given it is used verbatim. The function's return type becomes the producer's output. The macro constrains the function tightly to match what a producer can be: it must have no parameters (a producer takes no input and no `self` receiver), it must not be `async` (the producer trait is synchronous), and it must have no generic parameters. Violating any of these is a compile error pointing at the offending part of the signature. + +## Syntax Grammar + +The attribute argument of `#[cgp_producer]` is a single optional provider name: + +```ebnf +CgpProducerArgs -> ProviderName? + +ProviderName -> IDENTIFIER +``` + +When the argument is omitted, the generated provider struct takes the function name converted to PascalCase; a given `IDENTIFIER` is used verbatim. The annotated function is plain Rust, but the macro constrains it to a producer's shape — no parameters, no `async`, and no generic parameters — as described in Syntax above. + +## Expansion + +The macro emits three items: the original function unchanged, a `#[cgp_new_provider]` impl of the [`Producer`](../components/producer.md) trait that calls the function, and a `delegate_components!` block wiring the whole handler family to the [`PromoteProducer`](../providers/handler_combinators.md) bundle. Given + +```rust +#[cgp_producer] +pub fn magic_number() -> u64 { + 42 +} +``` + +the macro expands to the function plus: + +```rust +#[cgp_new_provider] +impl<__Context__, __Code__> Producer<__Context__, __Code__> for MagicNumber { + type Output = u64; + + fn produce(_context: &__Context__, _code: PhantomData<__Code__>) -> Self::Output { + magic_number() + } +} + +delegate_components! { + MagicNumber { + [ + ComputerComponent, + ComputerRefComponent, + TryComputerComponent, + TryComputerRefComponent, + AsyncComputerComponent, + AsyncComputerRefComponent, + HandlerComponent, + HandlerRefComponent, + ]: + PromoteProducer, + } +} +``` + +The `#[cgp_new_provider]` attribute defines the `MagicNumber` struct and its `IsProviderFor` impl alongside the `Producer` impl. The context and code generic parameters are introduced under the reserved names `__Context__` and `__Code__`; the `produce` method ignores both and simply calls the function. The `delegate_components!` block then routes all eight handler components to [`PromoteProducer`](../providers/handler_combinators.md), the promotion bundle built for a producer base. That bundle wires `ComputerComponent` to `Promote` — which discards the computer's input and calls `produce` — and derives the remaining members through `PromoteComputer`, so every handler shape emits the produced value regardless of any input it is handed. + +Unlike `#[cgp_computer]`, the expansion has no variation. Because the function cannot be async, cannot have generics, and is not analyzed for a `Result` return, there is a single base trait and a single promotion bundle for every `#[cgp_producer]` function — the producer's output type is taken exactly as written, whether or not it happens to be a `Result`. + +## Examples + +A self-contained use defines a producer and wires a minimal context, then reads the same value out through every handler shape: + +```rust +use cgp::prelude::*; +use cgp::extra::handler::{Producer, Computer, TryComputer, Handler}; + +#[cgp_producer] +pub fn magic_number() -> u64 { + 42 +} + +pub struct App; + +delegate_components! { + App { + ErrorTypeProviderComponent: UseType, + } +} + +// The single `magic_number` definition answers every shape, all yielding 42: +// MagicNumber::produce(&App, PhantomData::<()>) == 42 +// MagicNumber::compute(&App, PhantomData::<()>, &()) == 42 +// MagicNumber::try_compute(&App, PhantomData::<()>, &()) == Ok(42) +// MagicNumber::handle(&App, PhantomData::<()>, &()) resolves to Ok(42) +``` + +The computer and handler forms accept an input argument and ignore it, since the underlying producer takes none. The error type wired into `App` is what lets the fallible shapes (`try_compute`, `handle`) form their `Result`; the produced value is always returned as `Ok`. + +## Related constructs + +`#[cgp_producer]` defines a provider for the [`Producer`](../components/producer.md) component, part of the handler family described in [handlers](../../concepts/handlers.md). It is the input-less counterpart of [`#[cgp_computer]`](cgp_computer.md), which defines a [`Computer`](../components/computer.md) from a function with parameters, and both are handler-world analogues of [`#[cgp_fn]`](cgp_fn.md). The generated impl is emitted through [`#[cgp_new_provider]`](cgp_new_provider.md), and the producer is lifted into the full handler family by the [`PromoteProducer`](../providers/handler_combinators.md) bundle wired through [`delegate_components!`](delegate_components.md). + +## Source + +The macro entrypoint is [crates/macros/cgp-extra-macro/src/lib.rs](../../../crates/macros/cgp-extra-macro/src/lib.rs), forwarding to the implementation in [crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs). The `Producer` trait is defined in [crates/extra/cgp-handler/src/components/produce.rs](../../../crates/extra/cgp-handler/src/components/produce.rs), and the `PromoteProducer` bundle in [crates/extra/cgp-handler/src/providers/promote_all.rs](../../../crates/extra/cgp-handler/src/providers/promote_all.rs). Behavioral and snapshot tests live in [crates/tests/cgp-tests/tests/handler_tests/producer_macro.rs](../../../crates/tests/cgp-tests/tests/handler_tests/producer_macro.rs). diff --git a/docs/reference/macros/cgp_provider.md b/docs/reference/macros/cgp_provider.md new file mode 100644 index 00000000..72c68741 --- /dev/null +++ b/docs/reference/macros/cgp_provider.md @@ -0,0 +1,141 @@ +# `#[cgp_provider]` + +`#[cgp_provider]` is applied to a provider-trait implementation written directly on a named provider struct, and it auto-generates the matching [`IsProviderFor`](../traits/is_provider_for.md) impl from that implementation's `where` clause. + +## Purpose + +`#[cgp_provider]` exists to remove the one piece of boilerplate that every hand-written provider impl would otherwise have to repeat: the `IsProviderFor` marker impl. CGP requires that, alongside a provider's implementation of a provider trait, the provider also implement `IsProviderFor` under exactly the same constraints. This marker is what lets the compiler produce a readable error — naming the missing dependency — when a context's wiring is incomplete, instead of a terse "trait not implemented" message. Writing it by hand means duplicating the impl's generic parameters and entire `where` clause, and keeping the two copies in sync forever. + +`#[cgp_provider]` writes that second impl for you. You write only the real provider impl — `impl AreaCalculator for RectangleArea where ...` — and the macro emits a copy of it with the body stripped, the trait swapped to `IsProviderFor`, and the same `where` clause preserved. The dependencies are captured automatically and can never drift out of sync, because they are derived from the impl rather than restated. + +This is the form to reach for when you are working in the provider trait's native vocabulary — implementing the provider trait directly, with an explicit `Context` type parameter and a static-method signature that takes `context: &Context` rather than `&self`. It is the lower-level counterpart to [`#[cgp_impl]`](cgp_impl.md), which presents the same implementation in consumer-trait clothing and then desugars down to `#[cgp_provider]`. + +## Syntax + +`#[cgp_provider]` is applied to an `impl` block that implements a provider trait for a provider struct, and its attribute argument is an optional component type: + +```rust +#[cgp_provider] +impl AreaCalculator for RectangleArea +where + Context: HasDimensions, +{ + fn area(context: &Context) -> f64 { + context.width() * context.height() + } +} +``` + +The impl header is a normal provider-trait impl. The provider trait carries an explicit leading `Context` type parameter, the `Self` type is the provider struct (`RectangleArea`), and methods take the context as an ordinary parameter rather than as a `self` receiver. The provider struct must already exist; `#[cgp_provider]` does not define it. Use [`#[cgp_new_provider]`](cgp_new_provider.md) when you want the struct declared for you. + +The attribute takes one optional argument, the **component type** used in the generated `IsProviderFor` impl. When omitted, the component defaults to the provider trait's name with a `Component` suffix, so implementing `AreaCalculator` targets `AreaCalculatorComponent`. Pass the component explicitly when the provider trait's name does not follow that convention or when a provider implements a trait under a differently named component: + +```rust +#[cgp_provider(RunnerComponent)] +impl Runner for RunWithFooBar +where + Context: CanFetchFoo + CanFetchBar + CanRunFooBar, +{ + fn run(context: &Context, _code: PhantomData) -> Result<(), Context::Error> { + /* ... */ + } +} +``` + +## Syntax Grammar + +The attribute argument of `#[cgp_provider]` is a single optional component type: + +```ebnf +CgpProviderArgs -> ComponentType? + +ComponentType -> Type +``` + +When the argument is omitted the component defaults to the provider trait's name with a `Component` suffix; when present, that `Type` is substituted into the first position of the generated `IsProviderFor` impl. `Type` is the Rust type production. + +## Expansion + +`#[cgp_provider]` emits two items: the provider impl, passed through unchanged, and an `IsProviderFor` impl derived from it. Starting from: + +```rust +#[cgp_provider] +impl ComputerRef for FirstNameToString +where + Context: HasField, +{ + type Output = String; + + fn compute_ref(context: &Context, _code: PhantomData, _input: &Input) -> String { + context.get_field(PhantomData).to_string() + } +} +``` + +the macro produces: + +```rust +impl ComputerRef for FirstNameToString +where + Context: HasField, +{ + type Output = String; + + fn compute_ref(context: &Context, _code: PhantomData, _input: &Input) -> String { + context.get_field(PhantomData).to_string() + } +} + +impl IsProviderFor + for FirstNameToString +where + Context: HasField, +{} +``` + +The derived impl is the original impl with its body and associated types removed and its trait replaced. It keeps the same generic parameters (`Context, Code, Input`) and the same `where` clause, so it holds under precisely the conditions that the provider impl holds. Its trait arguments are assembled from the provider trait's arguments: the first is the **component type** (`ComputerRefComponent`, the default derived from the `ComputerRef` trait name); the second is the **context type**, taken from the provider trait's leading type argument (`Context`); and the third is the **`Params` tuple** holding every remaining provider-trait type parameter — here `(Code, Input)`. For a provider trait with no extra parameters beyond the context, the `Params` tuple is the empty `()`. + +The component-type argument is the only thing the attribute argument changes. Passing `#[cgp_provider(RunnerComponent)]` substitutes that type into the first position of the `IsProviderFor` impl in place of the default `{Trait}Component`; everything else about the expansion is unchanged. + +## Examples + +A self-contained provider for the `AreaCalculator` component, with its struct declared separately and its `IsProviderFor` impl generated, shows the construct in context: + +```rust +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_auto_getter] +pub trait HasDimensions { + fn width(&self) -> &f64; + fn height(&self) -> &f64; +} + +pub struct RectangleArea; + +#[cgp_provider] +impl AreaCalculator for RectangleArea +where + Context: HasDimensions, +{ + fn area(context: &Context) -> f64 { + context.width() * context.height() + } +} +``` + +The macro expands this into the provider impl above plus `impl IsProviderFor for RectangleArea where Context: HasDimensions {}`. A concrete context wires the component to `RectangleArea` exactly as it would for any provider, through [`delegate_components!`](delegate_components.md), and the `IsProviderFor` impl ensures that a context missing the `HasDimensions` dependency produces an error naming that dependency rather than an opaque one. + +In most code, the same provider would be written more concisely with [`#[cgp_impl]`](cgp_impl.md), which lets the body use `self`/`Self` and omit the explicit `Context` parameter. `#[cgp_provider]` is the right choice when you prefer to work directly in the provider trait's own form, or when reading code that another tool or macro has already lowered to that form. + +## Related constructs + +`#[cgp_provider]` implements a provider trait generated by [`#[cgp_component]`](cgp_component.md). It is the lower-level form that [`#[cgp_impl]`](cgp_impl.md) desugars to; prefer `#[cgp_impl]` for new code and reach for `#[cgp_provider]` when working in the native provider-trait shape. [`#[cgp_new_provider]`](cgp_new_provider.md) behaves identically but also declares the provider struct. The generated [`IsProviderFor`](../traits/is_provider_for.md) impl is the same marker that [`check_components!`](check_components.md) relies on to verify wiring, and a provider is connected to a context through [`delegate_components!`](delegate_components.md). + +## Source + +The macro entry point is `cgp_provider` in [crates/macros/cgp-macro-lib/src/cgp_provider.rs](../../../crates/macros/cgp-macro-lib/src/cgp_provider.rs), which parses the optional component argument, lowers the impl, and emits the result. The logic lives in [crates/macros/cgp-macro-core/src/types/cgp_provider/](../../../crates/macros/cgp-macro-core/src/types/cgp_provider/): attribute argument parsing (the optional `new` keyword and component type) in `args.rs`; the lowering that derives the component default, the provider struct, and the `IsProviderFor` impl in `item.rs`; the emitted-token assembly in `lower.rs`; and the splitting of provider-trait arguments into context and `Params` tuple in `provider_impl_args.rs`. The `IsProviderFor` derivation itself is in [crates/macros/cgp-macro-core/src/types/provider_impl.rs](../../../crates/macros/cgp-macro-core/src/types/provider_impl.rs). Expansion snapshots are in [crates/tests/cgp-tests](../../../crates/tests/cgp-tests), notably `src/tests/compose.rs` and `src/tests/async/spawn.rs`. diff --git a/docs/reference/macros/cgp_type.md b/docs/reference/macros/cgp_type.md new file mode 100644 index 00000000..6c2aaa10 --- /dev/null +++ b/docs/reference/macros/cgp_type.md @@ -0,0 +1,131 @@ +# `#[cgp_type]` + +`#[cgp_type]` defines an abstract-type component — a trait carrying a single associated type — by extending [`#[cgp_component]`](cgp_component.md) and generating the extra constructs that let a context choose the concrete type through wiring, most notably a [`UseType`](../providers/use_type.md) blanket impl. + +## Purpose + +`#[cgp_type]` exists to make associated types swappable across contexts the same way `#[cgp_component]` makes behavior swappable. An abstract type in CGP is just a trait with one associated type — `trait HasScalarType { type Scalar; }` — that lets generic code refer to `Self::Scalar` without committing to a concrete type. On its own such a trait is wired like any other component, but choosing the concrete type would otherwise mean writing a provider impl by hand for every type you want to plug in. `#[cgp_type]` removes that friction. + +The macro's value is the additional generated constructs layered on top of the component expansion. Because every abstract-type provider follows the same trivial shape — "the associated type *is* this concrete type" — `#[cgp_type]` can generate that shape once and for all as a [`UseType`](../providers/use_type.md) blanket impl. A context then names the concrete type directly in its wiring (`UseType`) instead of defining a bespoke provider. This is the same convenience relationship that `#[cgp_getter]` has to `UseField`: a general-purpose provider parameterized by the thing the context wants to supply. + +Direct implementation remains available and is often the clearest choice. An abstract type can always be implemented straight on a concrete context through its consumer trait, which is barely more verbose than wiring `UseType` and is the most transparent way to show that a CGP abstract type is nothing more than a vanilla Rust trait with an associated type. + +## Syntax + +The macro is applied to a trait that contains exactly one associated type and no methods. The associated type may carry bounds, but it must not be generic or have a `where` clause. The simplest form takes no argument: + +```rust +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} +``` + +Like `#[cgp_component]`, the component needs a provider trait name, and `#[cgp_type]` derives one from the associated type's name by default. The default provider name is the associated type name with a `TypeProvider` suffix, so `Scalar` yields the provider `ScalarTypeProvider` and the component name `ScalarTypeProviderComponent`. Note that the default is keyed off the *associated type* name, not the trait name. You can override it by passing a provider name, as with `#[cgp_component]`: + +```rust +#[cgp_type(ProvideScalar)] +pub trait HasScalarType { + type Scalar; +} +``` + +A bound on the associated type is preserved everywhere the type appears in the expansion. For example `type Scalar: Copy;` carries the `Copy` bound onto the generated provider trait and into the `where` clauses of the generated provider impls. + +## Syntax Grammar + +The attribute argument of `#[cgp_type]` is the same grammar as [`#[cgp_component]`](cgp_component.md)'s `CgpComponentArgs` — a bare provider name or the keyed `name`/`provider`/`context` form: + +```ebnf +CgpTypeArgs -> CgpComponentArgs // see #[cgp_component] +``` + +The only difference from `#[cgp_component]` is the default applied when `provider` is omitted: instead of failing, the macro derives the provider name from the *associated type's* name with a `TypeProvider` suffix (so `type Scalar;` yields `ScalarTypeProvider`). All other keys and their defaults behave exactly as documented for `#[cgp_component]`. + +## Expansion + +`#[cgp_type]` expands to the full `#[cgp_component]` output for the trait, followed by two abstract-type provider impls. The component part is exactly what `#[cgp_component(ScalarTypeProvider)]` would produce for an associated-type trait — the consumer trait, the provider trait, the consumer and provider blanket impls, the `ScalarTypeProviderComponent` marker, and the standard `UseContext` and `RedirectLookup` provider impls. The difference from a behavioral component is that every blanket impl forwards the *associated type* rather than a method; see [`#[cgp_component]`](cgp_component.md) for that core shape. + +The first extra construct is the [`UseType`](../providers/use_type.md) blanket impl, which is the heart of `#[cgp_type]`. It implements the provider trait for `UseType` by setting the abstract associated type to the generic parameter `Scalar`. Starting from: + +```rust +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} +``` + +the macro generates: + +```rust +impl ScalarTypeProvider<__Context__> for UseType { + type Scalar = Scalar; +} +``` + +This says that `UseType` is a provider that supplies `T` as the abstract type. Wiring a context's `ScalarTypeProviderComponent` to `UseType` therefore implements `HasScalarType` for that context with `Scalar = f64`, with no bespoke provider needed. If the associated type carries a bound, that bound is copied into the impl's `where` clause so the concrete type must satisfy it. + +The second extra construct is a `WithProvider` impl, which adapts the foundational [`HasType`/`TypeProvider`](../components/has_type.md) machinery into this component. It implements the provider trait for `WithProvider<__Provider__>` whenever `__Provider__` is a `TypeProvider` for the component: + +```rust +impl<__Provider__, Scalar, __Context__> ScalarTypeProvider<__Context__> + for WithProvider<__Provider__> +where + __Provider__: TypeProvider<__Context__, ScalarTypeProviderComponent, Type = Scalar>, +{ + type Scalar = Scalar; +} +``` + +The `HasType`/`TypeProvider` relationship this builds on is CGP's single built-in abstract-type component: `HasType` is the consumer trait, `TypeProvider` is its provider trait, and `UseType` is itself a `TypeProvider` (`impl TypeProvider for UseType { type Type = Type; }`). The `WithProvider` impl lets a `#[cgp_type]` component be backed by a generic `TypeProvider`, so the same `UseType` value satisfies both the built-in `HasType` and any user-defined `#[cgp_type]` component. + +As with the other macros, each generated provider impl is paired with a matching `IsProviderFor` impl carrying the same bounds, and the desugarings above are the exact shape the macro emits today. + +## Examples + +A complete use defines the abstract type, wires a concrete type through `UseType`, and consumes `Self::Scalar` in generic code: + +```rust +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar: Copy; +} + +pub struct App; + +delegate_components! { + App { + ScalarTypeProviderComponent: UseType, + } +} + +fn zero() -> Context::Scalar +where + Context: HasScalarType, + Context::Scalar: Default, +{ + Default::default() +} +``` + +`App` wires `ScalarTypeProviderComponent` to `UseType`, so the generated `UseType` blanket impl makes `App` implement `HasScalarType` with `Scalar = f64`. The `Copy` bound on the associated type is enforced on `f64` at the wiring site. + +The abstract type can equally be implemented directly on a concrete context, bypassing both `delegate_components!` and `UseType`: + +```rust +impl HasScalarType for App { + type Scalar = f64; +} +``` + +This direct form is only marginally longer than the wired form and is the most approachable for readers new to CGP, since it makes plain that an abstract-type component is an ordinary trait with an associated type. + +## Related constructs + +`#[cgp_type]` is the abstract-type specialization of [`#[cgp_component]`](cgp_component.md), inheriting its full expansion and provider-name override syntax while keying the default name off the associated type. Its central generated construct is the [`UseType`](../providers/use_type.md) provider, the type-level analogue of the [`UseField`](../providers/use_field.md) provider that [`#[cgp_getter]`](cgp_getter.md) generates. It builds on CGP's foundational [`HasType`/`TypeProvider`](../components/has_type.md) component via the generated `WithProvider` impl. Abstract-type components are wired with [`delegate_components!`](delegate_components.md) and checked with [`check_components!`](check_components.md), and they are imported into other definitions with [`#[use_type]`](../attributes/use_type.md). When an abstract type's only role is to be a getter's return type, [`#[cgp_auto_getter]`](cgp_auto_getter.md) can declare it inline instead. + +## Source + +The macro entry point is `cgp_type` in [crates/macros/cgp-macro-lib/src/cgp_type.rs](../../../crates/macros/cgp-macro-lib/src/cgp_type.rs), which extracts the single associated type, derives the default `{Type}TypeProvider` provider name from the associated type's identifier, runs the `#[cgp_component]` `preprocess → eval` pipeline, and converts the result into `ItemCgpType`. The logic lives in [crates/macros/cgp-macro-core/src/types/cgp_type/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_type/item.rs), which validates the trait shape (`extract_item_type_from_trait`) and builds the `UseType` and `WithProvider` provider impls. The runtime `HasType`, `TypeProvider`, and `UseType` definitions are in [crates/core/cgp-type/src/](../../../crates/core/cgp-type/src/) (`traits/has_type.rs` and `impls/use_type.rs`). Behavioral and expansion-snapshot tests are in [crates/tests/cgp-tests/tests/component_tests/abstract_types/](../../../crates/tests/cgp-tests/tests/component_tests/abstract_types/) (notably `basic.rs`). diff --git a/docs/reference/macros/check_components.md b/docs/reference/macros/check_components.md new file mode 100644 index 00000000..84213143 --- /dev/null +++ b/docs/reference/macros/check_components.md @@ -0,0 +1,216 @@ +# `check_components!` + +`check_components!` asserts at compile time that a context's wiring is complete, generating `CanUseComponent`-based checks that force the compiler to report exactly which dependency is missing. + +## Purpose + +`check_components!` exists because CGP wiring is lazy. When [`delegate_components!`](delegate_components.md) records that a context delegates a component to some provider, the type system does not eagerly verify that the provider can actually satisfy that component for that context with all of its transitive dependencies met. The [`DelegateComponent`](../traits/delegate_component.md) impl is accepted on its own terms; whether the provider's `where` bounds hold is only tested when something downstream tries to *use* the component. A context can therefore look fully wired and still fail the moment a consumer trait is invoked. + +When that failure happens far from the wiring, the error is hard to read. Asking only "does this context implement the consumer trait?" makes the compiler report the outermost unmet bound — typically that the provider does not implement the provider trait — without explaining why, because Rust hides the indirect reasoning behind that one conclusion. The root cause, often a single missing getter or type, is buried. + +`check_components!` solves this by turning the question into a `CanUseComponent` check. [`CanUseComponent`](../traits/can_use_component.md) is satisfied only when the context both delegates the component and the delegated provider satisfies [`IsProviderFor`](../traits/is_provider_for.md) for that context. Because `IsProviderFor` carries the provider's real `where` bounds, routing the check through it forces the compiler to evaluate and report those bounds, so an unsatisfied transitive requirement surfaces as a detailed error pointing at the actual missing dependency. The macro writes these checks for you, as a compile-time-only test: a successful build *is* the passing assertion. + +## Syntax + +The macro takes one or more check tables, each a context type followed by a brace-delimited list of the components to check on it. The simplest table lists bare component names: + +```rust +check_components! { + Person { + GreeterComponent, + } +} +``` + +Each entry names a component the macro should confirm `Person` can use. Multiple tables may appear in a single invocation, each beginning with its own context type and optional attributes. + +For a component with generic parameters, the parameters to check are given after a colon. A single parameter is written bare; multiple parameters are grouped into a tuple, mirroring how the provider trait groups them in its `IsProviderFor` `Params` position: + +```rust +check_components! { + MyApp { + AreaOfShapeCalculatorComponent: Rectangle, // one parameter + TransformCalculatorComponent: (Rectangle, f64), // two parameters, as a tuple + } +} +``` + +Array syntax on either side expands to the cartesian product of the bracketed entries, so a set of components can be checked against a set of parameters in one line. A bracketed value checks one component against several parameter sets; a bracketed key checks several components against one parameter set; bracketing both checks every combination: + +```rust +check_components! { + MyApp { + [AreaCalculatorComponent, RotatorComponent]: [Rectangle, Circle], + } +} +``` + +The check trait's name can be set with `#[check_trait(Name)]` on the table. The macro otherwise derives a name of the form `__Check{Context}` (for example `__CheckPerson`), so the override is needed when two `check_components!` tables in the same module would otherwise collide. A leading `<...>` generic list and a trailing `where` clause may also be attached to a table to introduce and constrain generics used by the checked parameters. + +A `#[check_providers(...)]` attribute changes what is checked: instead of verifying the context, it verifies that each listed provider is a provider for the context. This is the form to reach for when a higher-order provider needs each layer checked separately, since each provider in the list is asserted independently. + +## Syntax Grammar + +The input to `check_components!` is one or more check tables, each an optional attribute set and generic list, a context type, an optional `where` clause, and a brace-delimited list of check entries: + +```ebnf +CheckComponents -> CheckTable+ + +CheckTable -> TableAttr* Generics? ContextType WhereClause? `{` CheckEntries `}` + +TableAttr -> `#` `[` `check_trait` `(` IDENTIFIER `)` `]` + | `#` `[` `check_providers` `(` Type ( `,` Type )* `,`? `)` `]` + +ContextType -> Type + +CheckEntries -> ( CheckEntry ( `,` CheckEntry )* `,`? )? + +CheckEntry -> CheckKey ( `:` CheckValue )? + +CheckKey -> Type + | `[` Type ( `,` Type )* `,`? `]` + +CheckValue -> CheckParam + | `[` CheckParam ( `,` CheckParam )* `,`? `]` + +CheckParam -> Generics? Type +``` + +A single invocation may carry several `CheckTable`s, each with its own context type. The optional `#[check_trait(...)]` overrides the derived `__Check{Context}` trait name, and `#[check_providers(...)]` switches the check to verify the listed providers instead of the context. The `where` clause and a leading `Generics` list introduce and constrain generics used by the checked parameters. A `CheckEntry`'s value is omitted for a component with no generic parameters; when present, a bracketed `CheckKey` or `CheckValue` expands to the cartesian product, so a set of components is checked against a set of parameters. `WhereClause`, `Generics`, and `Type` are Rust grammar productions. + +## Expansion + +A check table expands to one marker trait plus one impl per checked entry. The marker trait is an alias whose supertrait is the check being asserted; each impl is an empty body that compiles only if that supertrait holds for the entry. Starting from: + +```rust +check_components! { + Person { + GreeterComponent, + } +} +``` + +the macro emits a check trait carrying `CanUseComponent` as its supertrait, followed by an impl of that trait for `Person` at the listed component and a unit params tuple: + +```rust +trait __CheckPerson<__Component__, __Params__: ?Sized>: + CanUseComponent<__Component__, __Params__> +{} + +impl __CheckPerson for Person {} +``` + +The impl compiles only if `Person: CanUseComponent`, which in turn requires that `Person` delegates `GreeterComponent` and that its delegate satisfies `IsProviderFor`. If the provider's dependencies are unmet — say it needs a `name` field the context lacks — the compiler reports the unsatisfied `HasField` bound rather than a bare "provider trait not implemented", which is the whole point of the indirection through `CanUseComponent`. The check trait name follows the `__Check{Context}` pattern, and the generic parameters are literally `__Component__` and `__Params__` in the emitted code. + +Generic parameters appear in the `__Params__` slot of each impl. A single parameter is placed there directly, and multiple parameters as a tuple: + +```rust +// AreaOfShapeCalculatorComponent: Rectangle +impl __CheckMyApp for MyApp {} + +// TransformCalculatorComponent: (Rectangle, f64) +impl __CheckMyApp for MyApp {} +``` + +Array syntax expands to the cartesian product before emitting impls, so `[AreaCalculatorComponent, RotatorComponent]: [Rectangle, Circle]` produces four impls — each component paired with each parameter — and the table-level generics and `where` clause are merged into every impl. A `<'a, I> Context where I: Clone { FooComponent: &'a I }` table, for instance, expands to `impl<'a, I> __CheckContext for Context where I: Clone {}`. + +The `#[check_providers(...)]` form changes both the supertrait and the implementing type. The check trait gains `IsProviderFor` as its supertrait instead of `CanUseComponent`, and the impls are written for each listed provider rather than for the context: + +```rust +check_components! { + #[check_trait(CheckScaledRectangleProviders)] + #[check_providers( + RectangleAreaCalculator, + ScaledAreaCalculator, + )] + ScaledRectangle { + AreaCalculatorComponent, + } +} +``` + +expands to: + +```rust +trait CheckScaledRectangleProviders<__Component__, __Params__: ?Sized>: + IsProviderFor<__Component__, ScaledRectangle, __Params__> +{} + +impl CheckScaledRectangleProviders for RectangleAreaCalculator {} +impl CheckScaledRectangleProviders + for ScaledAreaCalculator {} +``` + +Because each provider is checked on its own impl, a missing dependency affecting only the outer `ScaledAreaCalculator` produces an error on that line alone, while a dependency affecting the inner `RectangleAreaCalculator` errors on both — letting the failures localize the root cause. + +## Examples + +A check that catches a wiring mistake makes the value concrete. Given a greeter that depends on a `name` field, wired onto a context that has the wrong field name: + +```rust +use cgp::prelude::*; + +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasName, +{ + fn greet(&self) { + println!("Hello, {}!", self.name()); + } +} + +#[derive(HasField)] +pub struct Person { + pub first_name: String, // mismatch: GreetHello needs `name` +} + +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} + +check_components! { + Person { + GreeterComponent, + } +} +``` + +The `delegate_components!` block compiles on its own because wiring is lazy, but `check_components!` forces the assertion `Person: CanUseComponent`, which fails to compile and reports that `Person` is missing the `name` field — pinpointing the mismatch at the wiring site instead of at some distant call to `person.greet()`. + +A generic-parameter check supplies the parameters explicitly: + +```rust +#[cgp_component(AreaOfShapeCalculator)] +pub trait CanCalculateAreaOfShape { + fn area(&self, shape: &Shape) -> f64; +} + +check_components! { + MyApp { + AreaOfShapeCalculatorComponent: [Rectangle, Circle], + } +} +``` + +This verifies `MyApp: CanCalculateAreaOfShape` and `MyApp: CanCalculateAreaOfShape` in one table. + +## Related constructs + +`check_components!` verifies the wiring produced by [`delegate_components!`](delegate_components.md) for components defined with [`#[cgp_component]`](cgp_component.md). Its checks are built on [`CanUseComponent`](../traits/can_use_component.md), which itself relies on [`DelegateComponent`](../traits/delegate_component.md) and [`IsProviderFor`](../traits/is_provider_for.md); the `#[check_providers(...)]` form checks `IsProviderFor` on named providers directly, which is especially useful for higher-order providers wired through [`use_delegate.md`](../providers/use_delegate.md). When you want wiring and checking in a single step — the recommended approach for a main context — use [`delegate_and_check_components!`](delegate_and_check_components.md) instead; note its default check trait name (`__CanUse{Context}`) differs from this macro's (`__Check{Context}`) so the two can coexist in one module. + +## Source + +The macro entry point is `check_components` in [crates/macros/cgp-macro-lib/src/check_components.rs](../../../crates/macros/cgp-macro-lib/src/check_components.rs), which parses `CheckComponentsTables` and emits their items. The logic lives in [crates/macros/cgp-macro-core/src/types/check_components/](../../../crates/macros/cgp-macro-core/src/types/check_components/): table parsing, the `#[check_trait]`/`#[check_providers]` attributes, the `__Check{Context}` name derivation, and the choice between `CanUseComponent` and `IsProviderFor` supertraits are all in `table.rs`; key and value parsing (including array syntax) in `key.rs` and `value.rs`; and the cartesian-product expansion of entries in `entry.rs`. Expansion snapshots are in [crates/tests/cgp-tests/src/tests/check_components.rs](../../../crates/tests/cgp-tests/src/tests/check_components.rs). diff --git a/docs/reference/macros/delegate_and_check_components.md b/docs/reference/macros/delegate_and_check_components.md new file mode 100644 index 00000000..a876cf92 --- /dev/null +++ b/docs/reference/macros/delegate_and_check_components.md @@ -0,0 +1,185 @@ +# `delegate_and_check_components!` + +`delegate_and_check_components!` wires a context's components and asserts the wiring is complete in one step, combining [`delegate_components!`](delegate_components.md) with [`check_components!`](check_components.md). + +## Purpose + +`delegate_and_check_components!` exists so that a context's wiring is checked the moment it is written, with no separate test block to remember. Because CGP wiring is lazy — a [`delegate_components!`](delegate_components.md) entry is accepted without verifying that the chosen provider can actually satisfy the component — it is easy to leave a context that compiles but fails at the first call to a consumer trait. A standalone [`check_components!`](check_components.md) block closes that gap, but keeping it in sync with the wiring is manual: add a delegation and you must remember to add its check. This macro removes that bookkeeping by deriving the checks directly from the delegations. + +The recommendation is to use this macro for a main context's wiring, where catching a broken or incomplete wiring as early as possible is most valuable. Plain `delegate_components!` remains the right tool for intermediary provider tables — bundles that group providers together without being a context in their own right — and for complex cases where the checks need more control than the per-entry derivation gives, in which case a separate `check_components!` block is more flexible. + +Functionally, the macro emits exactly what writing both macros by hand would, with the check entries inferred from the delegation keys. Every delegated component is checked unless explicitly opted out, so the default behavior is "wire it and prove it works." + +## Syntax + +The macro takes the same table shape as [`delegate_components!`](delegate_components.md) — an optional `new` keyword and generics, a target type, and brace-delimited `Key: Value` delegation entries — and additionally accepts a few attributes that control the checking half. The basic form simply wires and checks each entry: + +```rust +delegate_and_check_components! { + ScaledRectangle { + AreaCalculatorComponent: + ScaledAreaCalculator, + } +} +``` + +The check trait's name defaults to `__CanUse{Context}` (for example `__CanUseScaledRectangle`). This deliberately differs from the `__Check{Context}` name that [`check_components!`](check_components.md) derives, so that both macros can be used once each in the same module without a name clash. A table-level `#[check_trait(Name)]` attribute overrides the derived name: + +```rust +delegate_and_check_components! { + #[check_trait(TestScaledRectangle)] + ScaledRectangle { + AreaCalculatorComponent: + ScaledAreaCalculator, + } +} +``` + +A component with generic parameters needs a `#[check_params(...)]` attribute on its entry, because the derived check would otherwise have no parameters to test. The delegation half does not need the parameters — the `DelegateComponent` impl is generic over them — but the check half does, so `#[check_params(...)]` supplies them, with the same single-versus-tuple convention as [`check_components!`](check_components.md): + +```rust +delegate_and_check_components! { + MyApp { + #[check_params( + Rectangle, + Circle, + )] + AreaOfShapeCalculatorComponent: + UseDelegate, + } +} +``` + +A `#[skip_check]` attribute on an entry wires it without generating any check, for cases where that component is verified separately. This avoids having to split out a second plain `delegate_components!` block just to wire one component without a check: + +```rust +delegate_and_check_components! { + ScaledRectangle { + #[skip_check] + AreaCalculatorComponent: + ScaledAreaCalculator, + } +} +``` + +The `#[check_params(...)]` and `#[skip_check]` attributes are mutually exclusive on a given key, and at most one may appear. The same array syntax that `delegate_components!` allows on the key side works here, with check params attaching per bracketed key as needed. + +## Syntax Grammar + +The body of `delegate_and_check_components!` is the same table shape as [`delegate_components!`](delegate_components.md), with an optional table-level check-trait attribute and a per-entry check attribute added: + +```ebnf +DelegateAndCheck -> TableAttr* Generics? `new`? TargetType `{` TableBody `}` + +TableAttr -> `#` `[` `check_trait` `(` IDENTIFIER `)` `]` + +TableBody -> Statement* ( CheckedMapping ( `,` CheckedMapping )* `,`? )? + +CheckedMapping -> EntryAttr? Mapping // Mapping, Key, ProviderValue — see delegate_components! + +EntryAttr -> `#` `[` `check_params` `(` Type ( `,` Type )* `,`? `)` `]` + | `#` `[` `skip_check` `]` +``` + +The `Mapping`, `Key`, `ProviderValue`, and `Statement` productions are exactly those of [`delegate_components!`](delegate_components.md); only the attributes differ. The table-level `#[check_trait(...)]` overrides the derived `__CanUse{Context}` trait name. Each mapping may carry at most one `EntryAttr`, and `#[check_params(...)]` and `#[skip_check]` are mutually exclusive: `#[check_params(...)]` supplies the generic parameters the derived check needs for a component with type parameters, and `#[skip_check]` wires the entry without generating a check at all. + +## Expansion + +The macro emits the delegation impls exactly as [`delegate_components!`](delegate_components.md) would, then appends a check trait and one impl per non-skipped entry, exactly as [`check_components!`](check_components.md) would. Starting from: + +```rust +delegate_and_check_components! { + #[check_trait(CheckMyContext)] + MyContext { + NameTypeProviderComponent: UseType, + NameGetterComponent: UseField, + } +} +``` + +the macro first produces the wiring half — a [`DelegateComponent`](../traits/delegate_component.md) impl and an [`IsProviderFor`](../traits/is_provider_for.md) forwarding impl for each entry: + +```rust +impl DelegateComponent for MyContext { + type Delegate = UseType; +} +impl<__Context__, __Params__> + IsProviderFor for MyContext +where + UseType: IsProviderFor, +{} + +impl DelegateComponent for MyContext { + type Delegate = UseField; +} +impl<__Context__, __Params__> + IsProviderFor for MyContext +where + UseField: IsProviderFor, +{} +``` + +then the checking half — a check trait aliasing [`CanUseComponent`](../traits/can_use_component.md), with one impl per delegated component: + +```rust +trait CheckMyContext<__Component__, __Params__: ?Sized>: + CanUseComponent<__Component__, __Params__> +{} + +impl CheckMyContext for MyContext {} +impl CheckMyContext for MyContext {} +``` + +Without the `#[check_trait(...)]` override, the trait would instead be named `__CanUseMyContext`. The whole output is identical to writing a `delegate_components!` block followed by a `check_components!` block whose check trait carries the `__CanUse{Context}` name. + +A `#[check_params(...)]` entry expands its parameters into the `__Params__` slot of the generated check impls, one impl per listed parameter, while the delegation impl stays generic over the parameter. The earlier `MyApp` table therefore checks `MyApp` at `Rectangle` and at `Circle` (`impl __CanUseMyApp for MyApp {}` and likewise for `Circle`), even though its single `DelegateComponent` impl is parameter-generic. A `#[skip_check]` entry contributes its delegation impls but no check impl, so it appears in the wiring half and is absent from the checking half. + +A generic table threads its generics through both halves. ` MyContext { ... }` yields `impl DelegateComponent<...> for MyContext` delegations alongside `impl __CanUseMyContext<..., ()> for MyContext {}` checks. + +## Examples + +A main context wired and checked together is the intended use: + +```rust +use cgp::prelude::*; + +#[derive(HasField)] +pub struct MyContext { + pub name: String, +} + +delegate_and_check_components! { + MyContext { + NameTypeProviderComponent: UseType, + NameGetterComponent: UseField, + } +} +``` + +If `MyContext` were missing the `name` field, the derived check on `NameGetterComponent` would fail to compile and report the missing `HasField` bound, rather than letting the gap slip through to a later use of `name()`. + +Mixing checked and skipped entries lets a higher-order delegation be verified elsewhere while the rest is checked inline: + +```rust +delegate_and_check_components! { + ScaledRectangle { + AreaCalculatorComponent: + ScaledAreaCalculator, + + #[skip_check] + TransformCalculatorComponent: + ComplexTransform, // checked in a dedicated check_components! block + } +} +``` + +## Related constructs + +`delegate_and_check_components!` is the fusion of [`delegate_components!`](delegate_components.md) and [`check_components!`](check_components.md): the wiring half behaves exactly like the former and the checking half like the latter, so the semantics of [`DelegateComponent`](../traits/delegate_component.md), [`IsProviderFor`](../traits/is_provider_for.md), and [`CanUseComponent`](../traits/can_use_component.md) all carry over unchanged. It wires components defined with [`#[cgp_component]`](cgp_component.md) to providers written with [`#[cgp_impl]`](cgp_impl.md), [`#[cgp_provider]`](cgp_provider.md), or [`#[cgp_fn]`](cgp_fn.md), and supports nested-table values via [`use_delegate.md`](../providers/use_delegate.md) and field getters via [`use_field.md`](../providers/use_field.md). Reach for plain [`delegate_components!`](delegate_components.md) instead when building intermediary provider tables, and for a standalone [`check_components!`](check_components.md) block when a check needs `#[check_providers(...)]` or other control beyond per-entry `#[check_params]`/`#[skip_check]`. + +## Source + +The macro entry point is `delegate_and_check_components` in [crates/macros/cgp-macro-lib/src/delegate_and_check_components.rs](../../../crates/macros/cgp-macro-lib/src/delegate_and_check_components.rs), which parses the table, evaluates the delegation half via the shared `DelegateTable`, derives a `CheckComponentsTable` from the keys, and emits both. The logic lives in [crates/macros/cgp-macro-core/src/types/delegate_and_check_components/](../../../crates/macros/cgp-macro-core/src/types/delegate_and_check_components/): the `__CanUse{Context}` default name and `#[check_trait]` handling in `item.rs`, the `#[check_params]`/`#[skip_check]` parsing and their mutual exclusion in `check_params.rs`, the per-key conversion to check entries in `key_with_check_params.rs`, and the walk over delegation entries in `to_keys_with_check_params.rs`. It reuses the `DelegateTable` from [delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/) and the `CheckComponentsTable` from [check_components/](../../../crates/macros/cgp-macro-core/src/types/check_components/). Expansion snapshots are in [crates/tests/cgp-tests/src/tests/delegate_and_check_components.rs](../../../crates/tests/cgp-tests/src/tests/delegate_and_check_components.rs) and [crates/tests/cgp-tests/src/tests/check_components.rs](../../../crates/tests/cgp-tests/src/tests/check_components.rs). diff --git a/docs/reference/macros/delegate_components.md b/docs/reference/macros/delegate_components.md new file mode 100644 index 00000000..684b7fb8 --- /dev/null +++ b/docs/reference/macros/delegate_components.md @@ -0,0 +1,256 @@ +# `delegate_components!` + +`delegate_components!` builds a context's type-level wiring table, implementing `DelegateComponent` so that each component name maps to the provider that should implement it. + +## Purpose + +`delegate_components!` is how a concrete context chooses which provider implements each of its components. A CGP component (created by [`#[cgp_component]`](cgp_component.md)) separates the consumer trait callers use from the provider trait implementers write, but that separation leaves a question open: for a given context, *which* provider supplies the behavior? `delegate_components!` answers it by recording one entry per component — the component name as the key, the chosen provider as the value — on the context type. + +The mental model is a type-level table, analogous to an object's method table (vtable) in object-oriented languages. Where a vtable maps method names to function pointers resolved at runtime, this table maps component-name types to provider types resolved at compile time. Looking a component up in the table yields the provider, and the blanket impls generated by `#[cgp_component]` route the consumer-trait call through whatever provider the lookup returns. The macro's job is to populate that table without making the user hand-write the underlying [`DelegateComponent`](../traits/delegate_component.md) impls. + +The reason a macro is worth having is that each entry expands to more than a single impl. Besides the `DelegateComponent` impl that stores the entry, the macro emits an [`IsProviderFor`](../traits/is_provider_for.md) impl that forwards the chosen provider's dependencies back through the table, so that a missing transitive requirement still surfaces as a usable compiler error rather than a dead end. Writing both impls by hand for every component, on every context, would be tedious and error-prone; `delegate_components!` generates them from a compact table syntax. + +## Syntax + +The macro takes a target type followed by a brace-delimited list of `Key: Value` entries. The target is the context (or intermediary provider) whose table is being defined; each key is a component-name type and each value is the provider type to delegate that component to: + +```rust +delegate_components! { + Rectangle { + AreaCalculatorComponent: RectangleArea, + } +} +``` + +An optional `new` keyword in front of the target type makes the macro define the target struct as well, saving a separate declaration. `new MyComponents { ... }` emits `struct MyComponents;` (or a generic struct, if the target carries type parameters) in addition to the table impls. This is the idiomatic way to declare a standalone provider-bundle type whose only purpose is to hold a table. + +When several components share the same provider, the array syntax on the key side lets one value serve multiple keys. A bracketed list of component names before the colon expands to one entry per name, all pointing at the same value: + +```rust +delegate_components! { + MyComponents { + [ + FooComponent, + BarComponent, + ]: FooBarProvider, + BazComponent: BazProvider, + } +} +``` + +A leading `<...>` generic list on the target makes the whole table generic, so the same wiring can apply across a family of context types — for example `delegate_components! { MyContext { ... } }` wires every `MyContext` at once. + +The recommended way to dispatch a component on its generic parameter is the `open` statement, which folds the per-value entries directly into the context's own table. A leading `open { AreaCalculatorComponent };` header opens one or more components for per-key wiring, after which a `@`-path entry such as `@AreaCalculatorComponent.Rectangle: RectangleArea` assigns a provider to a single value of that component's dispatch parameter: + +```rust +delegate_components! { + MyApp { + open {AreaCalculatorComponent}; + + @AreaCalculatorComponent.Rectangle: RectangleArea, + @AreaCalculatorComponent.Circle: CircleArea, + } +} +``` + +This wires `MyApp` to calculate the area of a `Rectangle` through `RectangleArea` and a `Circle` through `CircleArea` without naming any separate table. Each `@Component.Key` key may use the array shorthand on its final segment, `@AreaCalculatorComponent.[Rectangle, Circle]: SomeProvider`, and may carry generic parameters, `@SomeComponent.<'a, T> &'a T: SomeProvider`. Under the hood `open` redirects the component's lookup along a [`@`-path](cgp_namespace.md) into the context's own table, resolved by the [`RedirectLookup`](../providers/redirect_lookup.md) impl that every [`#[cgp_component]`](cgp_component.md) generates — so `open`-based dispatch needs no [`#[derive_delegate]`](../attributes/derive_delegate.md) on the component. `open` is a lightweight form of the full [namespace](cgp_namespace.md) feature, suited to small applications and self-contained code where a context wires its own components directly; it does not combine with a joined namespace where the component carries a `#[prefix(...)]`, in which case the per-value entries must be written with the full prefixed path rather than through `open`. The full namespace feature is what lets wiring scale across a large code base with many components. + +> **Legacy:** A value may also itself open a nested table, the older shape for dispatching a generic-parameter component. Writing `UseDelegate` as a value both wires the outer key to [`UseDelegate`](../providers/use_delegate.md) over an inner table and defines that inner table in place: +> +> ```rust +> delegate_components! { +> MyApp { +> AreaCalculatorComponent: +> UseDelegate Rectangle: RectangleArea, +> Circle: CircleArea, +> }>, +> } +> } +> ``` +> +> This nested-table form, the [`UseDelegate`](../providers/use_delegate.md) provider, and the [`#[derive_delegate]`](../attributes/derive_delegate.md) attribute that generates it together form a legacy dispatch mechanism. The `open` statement above achieves the same per-type dispatch with better ergonomics — no separate inner-table type and no `UseDelegate` wrapper — and is preferred for new code. The nested-table form is retained for compatibility and is expected to be deprecated, and eventually removed, once the namespace-based `open` form is shown to cover every dispatch case. + +Beyond plain `Key: Value` entries and `open`, the table body also accepts the other namespace-oriented statement forms used to opt a context into a [`#[cgp_namespace]`](cgp_namespace.md): a leading `namespace SomeNamespace;` header that forwards every lookup through that namespace, `@`-path keys such as `@app.ErrorRaiserComponent` that target a route rather than a bare component name, and `for in SomeTable { ... }` loops that pull entries out of another lookup table. These forms are described under [`#[cgp_namespace]`](cgp_namespace.md), where they are most often written. + +The macro accepts no attributes on the table or its keys and rejects any it finds. Attribute-driven variants such as `#[check_params(...)]` and `#[skip_check]` belong to [`delegate_and_check_components!`](delegate_and_check_components.md), not here. + +## Syntax Grammar + +The body of `delegate_components!` is an optional generic list and `new` keyword, a target type, and a brace-delimited table of mappings: + +```ebnf +DelegateComponents -> Generics? `new`? TargetType `{` TableBody `}` + +TargetType -> Type + +TableBody -> Statement* ( Mapping ( `,` Mapping )* `,`? )? + +Statement -> OpenStmt | NamespaceStmt | ForStmt + +OpenStmt -> `open` `{` Type ( `,` Type )* `,`? `}` `;` // NamespaceStmt, ForStmt — see #[cgp_namespace] + +Mapping -> Key `:` ProviderValue + | Key `->` ProviderValue + | Key `=>` Path + +Key -> SingleKey | MultiKey | PathKey +SingleKey -> Generics? Type +MultiKey -> `[` SingleKey ( `,` SingleKey )* `,`? `]` +PathKey -> Generics? Path + +ProviderValue -> Type + | IDENTIFIER `<` `new` TargetType `{` TableBody `}` `>` + +Path -> `@` PathSegment ( `.` PathSegment )* // see Path! +``` + +A leading `Generics` list (a Rust `< … >`) makes the whole table generic over the target; the `new` keyword additionally emits the target struct. Each `Mapping` chooses one of three operators: `` `:` `` maps a key directly to the named provider — the common form — while `` `->` `` delegates to the value's own entry for that key and `` `=>` `` redirects the lookup along an `@`-`Path`; the operators other than `:` are used mainly by the namespace machinery and detailed under [`#[cgp_namespace]`](cgp_namespace.md). A `Key` may be a single type, a bracketed list expanding to one entry per name, or an `@`-`PathKey`. The nested-table `ProviderValue` form wires the key to a `UseDelegate`-style wrapper while defining the inner table in place. `Mapping`, `Key`, and `ProviderValue` are the shared productions reused by [`delegate_and_check_components!`](delegate_and_check_components.md) and [`#[cgp_namespace]`](cgp_namespace.md). An `OpenStmt` opens each listed component for per-value wiring directly in the context's table, after which `@Component.Key` path keys populate it; the `NamespaceStmt` and `ForStmt` statement forms and the `Path` segment rules are defined under [`#[cgp_namespace]`](cgp_namespace.md) and [`Path!`](path.md). The macro accepts no attributes on the table or its entries and rejects any it finds. + +## Expansion + +Each table entry expands to a pair of impls: a `DelegateComponent` impl that stores the value, and an `IsProviderFor` impl that forwards the value's dependencies. Starting from the single-entry table: + +```rust +delegate_components! { + Rectangle { + AreaCalculatorComponent: RectangleArea, + } +} +``` + +the macro emits, first, the `DelegateComponent` impl that records the entry — `AreaCalculatorComponent` as the key, `RectangleArea` as the `Delegate` value: + +```rust +impl DelegateComponent for Rectangle { + type Delegate = RectangleArea; +} +``` + +This impl alone is what the provider blanket impl from [`#[cgp_component]`](cgp_component.md) reads when it looks the component up: because `Rectangle` delegates `AreaCalculatorComponent` to `RectangleArea`, `Rectangle` inherits `AreaCalculator` from `RectangleArea`, and from there the consumer blanket impl gives `Rectangle` the `CanCalculateArea` consumer trait. The lookup is the whole mechanism; the explicit, hand-written equivalent of one entry is exactly the `impl DelegateComponent<...>` block above. + +Second, the macro emits the matching `IsProviderFor` impl, which propagates the chosen provider's requirements. It is generic over a context and a params tuple, and holds whenever the delegated provider is itself a provider for that component: + +```rust +impl<__Context__, __Params__> + IsProviderFor for Rectangle +where + RectangleArea: IsProviderFor, +{} +``` + +This second impl is the reason missing dependencies stay diagnosable. `RectangleArea`'s own `IsProviderFor` impl (generated by [`#[cgp_provider]`](cgp_provider.md) or [`#[cgp_impl]`](cgp_impl.md)) carries the same `where` bounds it needs to be a provider, so an unsatisfied transitive requirement flows back through this forwarding impl to the point of use. The generic parameters are literally named `__Context__` and `__Params__` in the emitted code, not `Context`/`Params`. + +The array syntax simply repeats this pair per key. The table + +```rust +delegate_components! { + MyComponents { + [FooComponent, BarComponent]: FooBarProvider, + BazComponent: BazProvider, + } +} +``` + +expands as if each bracketed key had been written on its own line, yielding three `DelegateComponent` impls (`FooComponent → FooBarProvider`, `BarComponent → FooBarProvider`, `BazComponent → BazProvider`) and their three corresponding `IsProviderFor` impls. + +A nested-table value expands in two parts. The inner table is lifted out into its own `delegate_components!`-equivalent definition, and the outer entry is wired to `UseDelegate` over the inner table's type. The earlier `MyApp` example is equivalent to: + +```rust +delegate_components! { + MyApp { + AreaCalculatorComponent: UseDelegate, + } +} + +delegate_components! { + new AreaCalculatorComponents { + Rectangle: RectangleArea, + Circle: CircleArea, + } +} +``` + +Because the inner value was written with `new`, the macro also defines `struct AreaCalculatorComponents;`. At the impl level this means `MyApp`'s table maps `AreaCalculatorComponent` to `UseDelegate`, while `AreaCalculatorComponents` is a second table whose `Shape`-keyed entries (`Rectangle`, `Circle`) tell `UseDelegate` which provider to dispatch to for each shape. See [`use_delegate.md`](../providers/use_delegate.md) for how `UseDelegate` performs that inner lookup. This nested-table expansion is the legacy form; the `open` expansion below is the modern equivalent. + +The `open` statement expands each listed component to a redirect entry, with the per-value mappings stored directly on the context. From: + +```rust +delegate_components! { + MyApp { + open {AreaCalculatorComponent}; + + @AreaCalculatorComponent.Rectangle: RectangleArea, + @AreaCalculatorComponent.Circle: CircleArea, + } +} +``` + +the `open { AreaCalculatorComponent };` header wires `AreaCalculatorComponent` to a `RedirectLookup` rooted at the component name inside `MyApp`'s own table: + +```rust +impl DelegateComponent for MyApp { + type Delegate = RedirectLookup>; +} +``` + +Each `@AreaCalculatorComponent.Rectangle: RectangleArea` entry then stores its provider in that same table under the path key, so `MyApp` gains `DelegateComponent>>` with `Delegate = RectangleArea`. The [`RedirectLookup`](../providers/redirect_lookup.md) impl that [`#[cgp_component]`](cgp_component.md) generates for `AreaCalculator` appends the dispatch parameter — here `Rectangle` — onto the redirect path and reads the result back, so `MyApp: CanCalculateArea` resolves to `RectangleArea`. The lookup keys on the same `Shape` parameter the legacy `UseDelegate` form keys on; the difference is only that the per-value entries live on the context itself rather than in a separate table type. + +## Examples + +A complete wiring shows the table feeding the consumer trait. Given a component and a provider for it: + +```rust +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +#[cgp_impl(new RectangleArea)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} +``` + +a concrete context wires the component and then calls through it: + +```rust +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +delegate_components! { + Rectangle { + AreaCalculatorComponent: RectangleArea, + } +} + +fn print_area(rect: &Rectangle) { + println!("area = {}", rect.area()); // resolves via Rectangle's table to RectangleArea +} +``` + +A standalone provider bundle uses `new` to define its own table type, which other contexts can then delegate to as a single unit: + +```rust +delegate_components! { + new GeometryComponents { + AreaCalculatorComponent: RectangleArea, + PerimeterCalculatorComponent: RectanglePerimeter, + } +} +``` + +## Related constructs + +`delegate_components!` is the wiring step for components defined by [`#[cgp_component]`](cgp_component.md), and the providers it names are written with [`#[cgp_impl]`](cgp_impl.md), [`#[cgp_provider]`](cgp_provider.md), or [`#[cgp_fn]`](cgp_fn.md). Each entry expands to a [`DelegateComponent`](../traits/delegate_component.md) impl plus an [`IsProviderFor`](../traits/is_provider_for.md) impl. The `open` statement dispatches a component on its generic parameter through the [`RedirectLookup`](../providers/redirect_lookup.md) impl every component generates, and is the preferred alternative to the legacy nested-table values that rely on [`UseDelegate`](../providers/use_delegate.md); field-backed getters are commonly wired to [`UseField`](../providers/use_field.md). To verify a table is complete, pair it with [`check_components!`](check_components.md), or use [`delegate_and_check_components!`](delegate_and_check_components.md) to wire and check in one step — the recommended choice for a main context's wiring, with plain `delegate_components!` reserved for intermediary provider tables. When a component is defined inside a namespace, see [`#[cgp_namespace]`](cgp_namespace.md). + +## Source + +The macro entry point is `delegate_components` in [crates/macros/cgp-macro-lib/src/delegate_components.rs](../../../crates/macros/cgp-macro-lib/src/delegate_components.rs), which parses a `DelegateTable`, validates that no attributes are present, evaluates it, and emits the tokens. The logic lives in [crates/macros/cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/): the top-level table and the `new` keyword in `table/main.rs`, key parsing (single, array/`Multi`, path) in `key/`, value parsing including the nested-table form in `value/`, the statement forms (`open`, `namespace`, `for`) in `statement/` — with the `open` statement and its `RedirectLookup` expansion in `statement/open.rs` — and the `DelegateComponent`/`IsProviderFor` impl construction in `mapping/eval.rs`. Attribute rejection is in `validate_attributes.rs`. Expansion snapshots covering the single-entry, array, and generic-table forms are in [crates/tests/cgp-tests/tests/component_tests/delegate_components/](../../../crates/tests/cgp-tests/tests/component_tests/delegate_components/), and the namespace-header form (`namespace …;`, `@`-path keys) is exercised by the namespace snapshots in [crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/](../../../crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/). diff --git a/docs/reference/macros/path.md b/docs/reference/macros/path.md new file mode 100644 index 00000000..84cc29f7 --- /dev/null +++ b/docs/reference/macros/path.md @@ -0,0 +1,96 @@ +# `Path!` + +`Path!(@a.B.c)` is the type macro that builds a type-level path — a `PathCons` list of segments naming a route through nested delegation tables — from a dotted, `@`-prefixed sequence of names. + +## Purpose + +`Path!` exists to give a readable surface syntax for the `PathCons` spine that CGP uses to address an entry behind layers of delegation. A path names a route, read left to right, where each segment narrows a lookup one step: through a namespace, through a prefix, down to a component key. Writing that route as a nested `PathCons<…, PathCons<…, Nil>>` by hand is unwieldy and obscures the intent, so `Path!` lets it be written the way it reads — a dotted name like `@app.error.ErrorRaiserComponent` — and folds it into the corresponding spine. + +The macro is the path-shaped sibling of the other type-level construction macros. Where [`Symbol!`](symbol.md) turns a string literal into a single type-level string and [`Product!`](product.md)/`Sum!` build product and sum lists, `Path!` builds the routing list, sharing their right-nested, `Nil`-terminated shape. It is the construction half of the [`PathCons`](../types/path_cons.md) type, and the same `@`-path syntax it accepts is embedded directly inside [`#[cgp_namespace]`](cgp_namespace.md) entries and `#[prefix(...)]` attributes, which is where paths are most often written. + +## Syntax + +`Path!` takes a single `@`-prefixed path made of one or more segments separated by dots. The leading `@` is required — it is the sigil that marks the body as a path rather than a plain type — and at least one segment must follow it: + +```rust +Path!(@app) +Path!(@app.error) +Path!(@app.error.ErrorRaiserComponent) +``` + +Each segment is parsed as a type, and its first character decides how it is encoded. A segment that is a single identifier beginning with a lowercase letter — and is not a primitive type name — becomes a [`Symbol`](../types/chars.md) type-level string of that identifier; every other segment is kept as the named type it spells. So lowercase segments such as `app` and `error` become symbols, while capitalized segments such as `ErrorRaiserComponent` become references to those types (typically component keys or namespace markers). The exception for primitives means a lowercase name like `u32`, `bool`, `usize`, or `str` is treated as the named primitive type, not as a symbol. + +This is the same convention namespaces describe for their `@`-paths: dotted lowercase segments are field-name-style symbols and capitalized segments are named types. Mixing the two is normal — a path like `@my_app.ShowImplComponent` interleaves a symbol segment and a component segment. + +## Syntax Grammar + +The input to `Path!` is a leading `@` followed by one or more dot-separated segments: + +```ebnf +PathInput -> `@` PathSegment ( `.` PathSegment )* + +PathSegment -> Type +``` + +The leading `` `@` `` is required and at least one segment must follow. Each `PathSegment` is parsed as a Rust `Type`, but its encoding is decided semantically (see Expansion): a single lowercase identifier that is not a primitive type name becomes a `Symbol` type-level string, while every other segment — a capitalized name or a primitive — is kept as the named type. This same `@`-path grammar is what [`#[cgp_namespace]`](cgp_namespace.md) entries and `#[prefix(...)]` attributes embed, where it appears as the `Path` production. + +## Expansion + +`Path!` expands to a right-nested chain of [`PathCons`](../types/path_cons.md) terminated by `Nil`, with each segment encoded by the lowercase/capitalized rule. A three-segment path with one lowercase symbol and two named types desugars as follows: + +```rust +// before +Path!(@app.error.ErrorRaiserComponent) +``` + +```rust +// after — readable form +PathCons< + Symbol!("app"), + PathCons< + Symbol!("error"), + PathCons, + >, +> +``` + +The macro builds this by parsing the segments after the `@` into a list and folding them from right to left onto `Nil`, wrapping each segment in a `PathCons` whose tail is the accumulated rest. A single-segment path therefore becomes `PathCons`, and because a lowercase symbol segment is itself a `Symbol`/`Chars`/`Nil` chain, the fully desugared form of `@app` is `PathCons>>>, Nil>`. + +The same expansion appears verbatim inside the wirings that embed `@`-paths. A [`#[cgp_namespace]`](cgp_namespace.md) redirect entry `FooProviderComponent => @MyFooComponent` produces a `RedirectLookup<__Table__, PathCons>`, and a `#[prefix(@MyBarComponent in MyNamespace)]` attribute produces a `PathCons>` — the macro's fold is the same one driving those constructs. + +## Examples + +A path is typically written to express a redirect target. Used directly as a type, `Path!` names a route that a [`RedirectLookup`](../providers/redirect_lookup.md) can resolve against a table: + +```rust +use cgp::prelude::*; + +type ErrorRoute = Path!(@app.error.ErrorRaiserComponent); +// ErrorRoute = PathCons>> +``` + +In practice the same syntax is more often embedded in a namespace table than written through the bare macro, since [`#[cgp_namespace]`](cgp_namespace.md) accepts `@`-paths directly in its entries: + +```rust +use cgp::prelude::*; + +cgp_namespace! { + new MyNamespace { + FooProviderComponent => + @MyFooComponent, + } +} +// the entry's Delegate is RedirectLookup<__Table__, PathCons> +``` + +Either way the path is the same `PathCons` list; the macro and the namespace simply offer two places to write it. + +## Related constructs + +`Path!` constructs the [`PathCons`](../types/path_cons.md) spine, the type it desugars to, and its lowercase segments are [`Symbol!`](symbol.md) type-level strings. It is the routing-list counterpart to the product and sum construction macros [`Product!`](product.md) and [`Sum!`](sum.md), sharing their right-fold-onto-`Nil` shape. The paths it builds are consumed by [`RedirectLookup`](../providers/redirect_lookup.md) when resolving a delegation, and its `@`-path syntax is embedded throughout [`#[cgp_namespace]`](cgp_namespace.md), where namespace entries and `#[prefix(...)]` attributes use the same dotted form. + +## Source + +The macro entry point is `Path` in [crates/macros/cgp-macro-lib/src/path.rs](../../../crates/macros/cgp-macro-lib/src/path.rs), which parses the body into a `UniPath` and emits its tokens. The parsing and codegen live in [crates/macros/cgp-macro-core/src/types/path/](../../../crates/macros/cgp-macro-core/src/types/path/): `unipath.rs` requires the leading `@`, parses the dot-separated segments, and right-folds them with `PathCons` onto `Nil`; `path_element.rs` decides per segment whether a lowercase, non-primitive identifier becomes a `Symbol` or the segment stays a named type. The runtime spine `PathCons` is defined in [crates/core/cgp-base-types/src/types/path.rs](../../../crates/core/cgp-base-types/src/types/path.rs), and the `RedirectLookup` provider that walks a path is in [crates/core/cgp-component/src/providers/redirect_lookup.rs](../../../crates/core/cgp-component/src/providers/redirect_lookup.rs). The `@`-path forms are exercised by the namespace snapshot tests in [crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/](../../../crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/). diff --git a/docs/reference/macros/product.md b/docs/reference/macros/product.md new file mode 100644 index 00000000..bb74a6f3 --- /dev/null +++ b/docs/reference/macros/product.md @@ -0,0 +1,104 @@ +# `Product!` and `product!` + +`Product![A, B, C]` is the type macro that builds a type-level list — a heterogeneous list of types encoded entirely in the type system — and the lowercase `product![a, b, c]` is its value-level counterpart that builds an actual value of that type. + +## Purpose + +`Product!` exists to represent an ordered sequence of types as a single type, so that a collection of fields can be reasoned about generically. CGP uses this to describe the *shape* of a struct: the list of its fields, in order, as one type. A type-level list is sometimes called an anonymous product type, because like a tuple it holds several things at once, but unlike a tuple it is built from a recursive `Cons`/`Nil` spine that generic code can take apart one element at a time. + +The list is what makes structural, field-by-field operations possible. Because the fields of a struct are exposed as a single list type through [`HasFields`](../traits/has_fields.md), a provider can be written once to iterate, transform, or rebuild *any* struct's fields without knowing the concrete struct, by recursing over the `Cons`/`Nil` structure. A plain tuple cannot be decomposed this way in generic code; the recursive list can. + +`Product!` and `product!` are two halves of the same idea, split across the type and value levels. `Product!` produces a *type* and is used in type position — associated types, bounds, `type` aliases. `product!` produces a *value* of a matching type and is used in expression position. The uppercase/lowercase convention mirrors Rust's own split between, say, a struct type and a struct literal. + +## Syntax + +Both macros take a comma-separated list of elements and may be empty. `Product!` takes types and is used wherever a type is expected; `product!` takes expressions and is used wherever a value is expected: + +```rust +Product![u32, String, bool] // a type +product![1u32, "hi".to_string(), true] // a value of that type +Product![] // the empty list type +``` + +The element lists line up positionally, so the value built by `product!` has the type built by `Product!` over the corresponding element types. + +## Syntax Grammar + +The two macros take a possibly-empty, comma-separated list — of types for `Product!` and of expressions for `product!`: + +```ebnf +ProductInput -> ( Type ( `,` Type )* `,`? )? + +ProductExpr -> ( Expression ( `,` Expression )* `,`? )? +``` + +`ProductInput` is the grammar of the type macro `Product!`, used in type position; `ProductExpr` is the grammar of the value macro `product!`, used in expression position. `Type` and `Expression` are the Rust grammar's productions, and both lists may be empty (`Product![]`, `product![]`) or carry a trailing comma. The element lists line up positionally, so a `product!` value has the type the corresponding `Product!` builds. + +## Expansion + +`Product!` expands to a right-nested chain of `Cons`, terminated by `Nil`. The three-element list desugars as follows: + +```rust +// before +Product![A, B, C] +``` + +```rust +// after +Cons>> +``` + +The two building blocks are defined in `cgp-base-types`. `Cons` is a pair holding the first element and the rest of the list as `Cons(pub Head, pub Tail)`; chaining it through `Tail` and terminating with the empty `Nil` struct produces a list of any length. An empty `Product![]` is simply `Nil`. The macro constructs the chain by folding the elements from right to left onto `Nil`. + +The value macro `product!` expands the same way but produces a value rather than a type, using the tuple-struct constructor of `Cons`: + +```rust +// before +product![a, b, c] +``` + +```rust +// after +Cons(a, Cons(b, Cons(c, Nil))) +``` + +Because `Cons` is a real tuple struct and `Nil` a real unit struct, the value built by `product!` is an ordinary owned value whose type is exactly what `Product!` builds over the same elements' types. + +## Examples + +The most common appearance of `Product!` is as the `Fields` of a struct that derives [`HasFields`](../derives/derive_has_fields.md), where each element is a `Field` pairing a field name with its type: + +```rust +use cgp::prelude::*; + +#[derive(HasFields)] +pub struct Person { + pub name: String, + pub age: u8, +} + +// generated: +// impl HasFields for Person { +// type Fields = Product![ +// Field, +// Field, +// ]; +// } +``` + +The field names here are type-level strings — see [`Symbol!`](symbol.md) — so the whole `Product!` is a fully type-level description of `Person`'s layout. Generic code can then walk that list to build, read, or transform a `Person` without being written against `Person` specifically. + +A standalone list and a matching value can also be written directly: + +```rust +type Row = Product![u32, String, bool]; +let row: Row = product![1, "hi".to_string(), true]; +``` + +## Related constructs + +`Product!` is the product (record-like) counterpart to [`Sum!`](sum.md), which builds the coproduct used for enum variants; the two share the same right-nested shape but `Sum!` terminates in `Void` rather than `Nil`. The list elements are most often [`Field`](../types/field.md) entries whose tags are [`Symbol!`](symbol.md) field names or [`Index`](../types/index.md) positions. The list type as a whole is what [`#[derive(HasFields)]`](../derives/derive_has_fields.md) assigns to a struct, building on the per-field [`#[derive(HasField)]`](../derives/derive_has_field.md). The `Chars` list inside `Symbol!` is a specialized version of this same `Cons`/`Nil` structure. + +## Source + +The macro entry points are `Product` and `product` in [crates/macros/cgp-macro-lib/src/product.rs](../../../crates/macros/cgp-macro-lib/src/product.rs). The type form is the `ProductType` construct in [crates/macros/cgp-macro-core/src/types/product/product_type.rs](../../../crates/macros/cgp-macro-core/src/types/product/product_type.rs), whose `eval` right-folds the elements with `Cons` onto `Nil`; the value form is `ProductExpr` in [crates/macros/cgp-macro-core/src/types/product/product_expr.rs](../../../crates/macros/cgp-macro-core/src/types/product/product_expr.rs), which does the same fold using the `Cons(..)` constructor. The runtime types are defined in [crates/core/cgp-base-types/src/types/cons.rs](../../../crates/core/cgp-base-types/src/types/cons.rs) (`Cons`) and [crates/core/cgp-base-types/src/types/nil.rs](../../../crates/core/cgp-base-types/src/types/nil.rs) (`Nil`). diff --git a/docs/reference/macros/sum.md b/docs/reference/macros/sum.md new file mode 100644 index 00000000..2a87f6a3 --- /dev/null +++ b/docs/reference/macros/sum.md @@ -0,0 +1,91 @@ +# `Sum!` + +`Sum![A, B, C]` is the type macro that builds a type-level sum type — a coproduct encoded in the type system whose value is exactly one of the listed types — used by CGP to represent the variants of an enum the way [`Product!`](product.md) represents the fields of a struct. + +## Purpose + +`Sum!` exists to represent a choice among several types as a single type, so that the variants of an enum can be reasoned about generically. Where a [`Product!`](product.md) list holds a value for *every* element at once (a record), a `Sum!` holds a value for exactly *one* element (a tagged union). It is sometimes called an anonymous sum type or coproduct, and it is the structural mirror image of the product: both are right-nested chains over the same kind of spine, but the sum branches at each step instead of pairing. + +The sum is what makes structural, variant-by-variant operations possible. Because an enum's variants are exposed as a single sum type through [`HasFields`](../traits/has_fields.md), a provider can be written once to match, dispatch on, or construct *any* enum's variants without knowing the concrete enum, by recursing over the nested branch structure. This is the basis for CGP's extensible-variant machinery, where each variant is handled by walking the chain rather than by writing a hand-rolled `match` against a fixed enum. + +`Sum!` is the variant-level analogue of `Product!`, and the two are used together. A struct's fields desugar to a `Product!`; an enum's variants desugar to a `Sum!` of the same `Field` entries. Knowing one shape tells you the other. + +## Syntax + +The macro takes a comma-separated list of types and may be empty. It is used wherever a type is expected: + +```rust +Sum![u32, String, bool] +Sum![] // the empty sum +``` + +Each listed type is one possible variant of the sum; a value of the sum type carries exactly one of them. + +## Syntax Grammar + +The input to `Sum!` is a possibly-empty, comma-separated list of types: + +```ebnf +SumInput -> ( Type ( `,` Type )* `,`? )? +``` + +`Type` is the Rust grammar's type production, and the list may be empty (`Sum![]`) or carry a trailing comma. The macro is used in type position, and each listed type is one possible variant of the sum. + +## Expansion + +`Sum!` expands to a right-nested chain of `Either`, terminated by `Void`. The three-element sum desugars as follows: + +```rust +// before +Sum![A, B, C] +``` + +```rust +// after — readable form +Either>> +``` + +The two building blocks are defined in `cgp-field` and differ from the product spine in being branching rather than pairing. `Either` is the sum cell, an enum with two cases — `Left(Head)` selects the head type, and `Right(Tail)` defers to the rest of the chain — so a value of `Either>>` is `Left` for an `A`, `Right(Left(..))` for a `B`, and `Right(Right(Left(..)))` for a `C`. The terminator is `Void`, an empty enum that can never be constructed, which closes the chain off: reaching the `Void` position would mean the value matched none of the listed types, which is impossible. An empty `Sum![]` is therefore just `Void`, a type with no values. + +The choice of `Void` rather than `Nil` is the key difference from [`Product!`](product.md). A product terminates in `Nil` because an empty record is a valid, constructible value (the unit-like `Nil`); a sum terminates in `Void` because an empty choice is *uninhabited* — there is no value to pick. `Void` is functionally the never type, used here specifically to mark the end of a sum. The macro constructs the chain by folding the element types from right to left onto `Void`. + +## Examples + +The primary appearance of `Sum!` is as the `Fields` of an enum that derives [`HasFields`](../derives/derive_has_fields.md), where each branch is a `Field` pairing a variant name with its payload: + +```rust +use cgp::prelude::*; + +#[derive(HasFields)] +pub enum Shape { + Circle(f64), + Rectangle { width: f64, height: f64 }, +} + +// generated (schematically): +// impl HasFields for Shape { +// type Fields = Sum![ +// Field, +// Field, +// Field, +// ]>, +// ]; +// } +``` + +The variant names are type-level strings — see [`Symbol!`](symbol.md) — and a struct-like variant nests a [`Product!`](product.md) of its own fields, so an enum's full shape is a `Sum!` of variants whose payloads may themselves be `Product!` records. Generic code walks the `Sum!` to dispatch on which variant a value holds, and walks any nested `Product!` to reach that variant's fields. + +A standalone sum type can also be written directly: + +```rust +type Token = Sum![u32, String, bool]; +``` + +## Related constructs + +`Sum!` is the coproduct counterpart to [`Product!`](product.md): the two share a right-nested shape, but `Sum!` branches with [`Either`](../types/either.md) and terminates in the uninhabited `Void`, while `Product!` pairs with [`Cons`](../types/cons.md) and terminates in `Nil`. Its branches are typically [`Field`](../types/field.md) entries whose tags are [`Symbol!`](symbol.md) variant names. The sum type as a whole is what [`#[derive(HasFields)]`](../derives/derive_has_fields.md) assigns to an enum, and it underpins the extensible-variant derives [`#[derive(CgpVariant)]`](../derives/derive_cgp_variant.md) and [`#[derive(FromVariant)]`](../derives/derive_from_variant.md), which build and consume individual `Either` branches. For struct fields, the per-field tags are produced by [`#[derive(HasField)]`](../derives/derive_has_field.md). + +## Source + +The macro entry point is `Sum` in [crates/macros/cgp-macro-lib/src/sum.rs](../../../crates/macros/cgp-macro-lib/src/sum.rs), forwarding to the `SumType` construct in [crates/macros/cgp-macro-core/src/types/sum.rs](../../../crates/macros/cgp-macro-core/src/types/sum.rs), whose `eval` right-folds the element types with `Either` onto `Void`. The runtime types `Either` and the uninhabited `Void` are both defined in [crates/core/cgp-field/src/types/sum.rs](../../../crates/core/cgp-field/src/types/sum.rs). The enum `HasFields` derive that emits a `Sum!` of `Field` branches lives in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/sum.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/sum.rs). diff --git a/docs/reference/macros/symbol.md b/docs/reference/macros/symbol.md new file mode 100644 index 00000000..6c86d53c --- /dev/null +++ b/docs/reference/macros/symbol.md @@ -0,0 +1,88 @@ +# `Symbol!` + +`Symbol!("...")` is the type macro that turns a string literal into a type-level string — a distinct type, carrying no runtime value, that CGP uses to name a field so the name can be matched and dispatched on at compile time. + +## Purpose + +`Symbol!` exists because CGP needs field *names* to be types, not values. The core getter mechanism, [`HasField`](../traits/has_field.md), is parameterized by a `Tag` type that identifies which field is being read; to look up a field called `name`, something must stand in for the string `"name"` at the type level. `Symbol!("name")` is that something. It produces a unique type whose entire identity is the character sequence it encodes, so two `Symbol!` invocations with the same string are the same type and two with different strings are different types. + +Encoding strings as types is what lets field access participate in trait resolution. Because `Symbol!("width")` is a type, a context can carry one `HasField` impl and another `HasField` impl side by side, and the compiler picks the right one purely from the tag. The same string-as-type trick drives [`#[cgp_auto_getter]`](cgp_auto_getter.md), `UseField`, and the `Field` entries that make up a struct's [`HasFields`](../traits/has_fields.md) representation. Wherever a name needs to be matched at compile time, it appears as a `Symbol!`. + +The type-level string is distinct from the type-level *number* used for tuple fields. Unnamed (tuple) struct fields have no string name, so [`#[derive(HasField)]`](../derives/derive_has_field.md) tags them with the [`Index`](../types/index.md) type instead — `Index<0>`, `Index<1>`, and so on — which encodes a `usize` at the type level the way `Symbol!` encodes a string. A field is keyed by `Symbol!` when it has a name and by `Index` when it has only a position. + +## Syntax + +The macro takes a single string literal and is used wherever a type is expected. It appears in trait bounds, associated-type positions, and `PhantomData` tags: + +```rust +Symbol!("name") +Symbol!("first_name") +Symbol!("") +``` + +Any valid string literal is accepted, including the empty string and multi-byte Unicode (`Symbol!("世界")`), and the macro is most commonly seen inside a `HasField` bound such as `HasField`. + +## Syntax Grammar + +The input to `Symbol!` is a single string literal: + +```ebnf +SymbolInput -> STRING_LITERAL +``` + +`STRING_LITERAL` is the Rust string-literal token, so any valid string literal is accepted — including the empty string and multi-byte Unicode. The macro is used in type position, so this single literal is the whole of its input. + +## Expansion + +`Symbol!("...")` expands to the `Symbol` type wrapping a `Chars` chain that spells out the string one character at a time. The string `"abc"` desugars as follows: + +```rust +// before +Symbol!("abc") +``` + +```rust +// after +Symbol<3, Chars<'a', Chars<'b', Chars<'c', Nil>>>> +``` + +Two type constructors do the work, and both are defined in `cgp-base-types`. `Chars` is a single character paired with the rest of the string; chained through its `Tail` and terminated by `Nil`, it forms a type-level list of characters — the direct analogue of [`Cons`](../types/cons.md)/`Nil` but specialized so the head is a `const char` rather than a type. `Symbol` then wraps that character list together with the string's length. + +The `LEN` parameter is the part of the expansion most likely to surprise a reader, and it exists to work around a limitation in stable Rust. Stable Rust cannot evaluate the length of a `Chars` chain inside a const-generic context, so the macro precomputes the length and bakes it into the type as a separate const parameter rather than deriving it from the character list. The value is the string's byte length — `str::len()` — so `Symbol!("abc")` records `3` and a four-character Chinese string like `Symbol!("世界你好")` records `12`, not `4`. The character list, by contrast, has one `Chars` node per Unicode scalar value. `LEN` lets length-dependent code read the size off the type directly instead of recursing through the list. + +The macro builds the expansion by folding the characters from right to left onto `Nil`, then wrapping the result in `Symbol` with the precomputed length, so an empty string `Symbol!("")` becomes `Symbol<0, Nil>`. + +## Examples + +`Symbol!` most often appears as the tag in a getter bound. The following provider reads a `name` field from any context that exposes one, without that context ever naming the provider: + +```rust +use cgp::prelude::*; + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasField, +{ + fn greet(&self) { + println!("Hello, {}!", self.get_field(PhantomData::)); + } +} +``` + +A type-level string can also be constructed directly and inspected at runtime through its `Display` impl, which reconstructs the original string from the `Chars` chain: + +```rust +let s = ::default(); +assert_eq!(s.to_string(), "hello"); // prints "hello" +``` + +When a struct derives [`HasField`](../derives/derive_has_field.md), each named field's tag is a `Symbol!` of the field name, and the whole struct's [`HasFields`](../traits/has_fields.md) representation is a [`Product!`](product.md) of `Field` entries — so `Symbol!` is the bridge between a field's source name and its type-level identity. + +## Related constructs + +`Symbol!` is the field-name half of CGP's tagging scheme; [`Index`](../types/index.md) is the field-position half, used for tuple-struct fields. The tags it produces are consumed by [`HasField`](../traits/has_field.md) for single-field access and by the `Field` entries inside [`HasFields`](../traits/has_fields.md). The character list it builds is a specialized [`Product!`](product.md) list — `Chars`/`Nil` mirror `Cons`/`Nil`. Higher-level getters that take a `Symbol!` tag include [`#[cgp_auto_getter]`](cgp_auto_getter.md) and the `UseField` pattern. For enums, the analogous variant names appear as `Symbol!` tags inside a [`Sum!`](sum.md) representation. + +## Source + +The macro entry point is `Symbol` in [crates/macros/cgp-macro-lib/src/symbol.rs](../../../crates/macros/cgp-macro-lib/src/symbol.rs), which forwards to the `Symbol` construct in [crates/macros/cgp-macro-core/src/types/field/symbol.rs](../../../crates/macros/cgp-macro-core/src/types/field/symbol.rs) — its `ToTokens` impl performs the right-fold over the characters, computes `LEN` from `str::len()`, and wraps the result in `Symbol`. The runtime types are defined in [crates/core/cgp-base-types/src/types/symbol.rs](../../../crates/core/cgp-base-types/src/types/symbol.rs) (`Symbol`) and [crates/core/cgp-base-types/src/types/chars.rs](../../../crates/core/cgp-base-types/src/types/chars.rs) (`Chars`), with `Nil` in [crates/core/cgp-base-types/src/types/nil.rs](../../../crates/core/cgp-base-types/src/types/nil.rs). Tests covering display round-tripping and multi-byte strings are in [crates/tests/cgp-tests/src/tests/symbol.rs](../../../crates/tests/cgp-tests/src/tests/symbol.rs). diff --git a/docs/reference/providers/chain_getters.md b/docs/reference/providers/chain_getters.md new file mode 100644 index 00000000..52052825 --- /dev/null +++ b/docs/reference/providers/chain_getters.md @@ -0,0 +1,101 @@ +# `ChainGetters` + +`ChainGetters` is a zero-sized provider that composes a list of field getters into one, navigating from an outer context through a sequence of intermediate values to reach a deeply nested field. + +## Purpose + +`ChainGetters` exists to reach a field that does not live directly on the context but several hops inside it. A single [`UseField`](use_field.md) reads one field of one context. But CGP contexts often nest — a context holds a config, the config holds a connection, the connection holds a timeout — and a getter may need the innermost value. Writing one provider that walks the whole path by hand is tedious and couples the getter to the nesting. `ChainGetters` takes a list of getters, applies them in order, and threads the reference from each step into the next, so the chain reads like the path it traverses: outer getter, then the next, then the next, ending at the target field. + +The list is a type-level [`Cons`](../types/cons.md) spine — `Cons>` — where each element is itself a field getter for the value the previous step produced. `ChainGetters` recurses down this list, so it is naturally written with the [`Product!`](../macros/product.md) macro that builds such spines. Like every CGP provider, `ChainGetters` carries no runtime value; it is a `PhantomData`-only marker named in wiring. + +## Definition + +`ChainGetters` is a phantom-typed struct parameterized by the list of getters it chains, defined in `cgp-field`: + +```rust +pub struct ChainGetters(pub PhantomData); +``` + +`Getters` is a [`Cons`](../types/cons.md)/`Nil` list whose elements are field getters. The provider has no `With...` alias; it is used directly in wiring as the provider for a getter component, or composed inside other getter wiring. `ChainGetters` is not re-exported through `cgp::prelude`; reach it through `cgp::core::field::impls`. + +## Implementations + +`ChainGetters` implements the provider-side getter [`FieldGetter`](../traits/has_field.md) with two impls that together perform the recursion over the list — one for a non-empty `Cons` and one for the empty `Nil`. The `Cons` impl applies the head getter, then delegates the remainder of the path to `ChainGetters` over the tail: + +```rust +impl FieldGetter + for ChainGetters> +where + Getter: FieldMapper, + ChainGetters: FieldGetter, +{ + type Value = ValueB; + + fn get_field(context: &Context, tag: PhantomData) -> &ValueB { + Getter::map_field(context, tag, |value| { + >::get_field(value, tag) + }) + } +} +``` + +The head `Getter` reads `ValueA` from the `Context`, and the rest of the chain — `ChainGetters` — reads `ValueB` from that `ValueA`, so the whole chain's `Value` is `ValueB`, the value at the end of the path. The head is applied through [`FieldMapper`](../traits/has_field.md) rather than `FieldGetter` directly: `map_field` hands the intermediate `ValueA` reference to a closure that runs the rest of the chain on it, which is the construct that keeps the borrowed lifetimes inferring correctly across each hop. Note that every step is asked under the same `Tag` — the chain navigates *contexts*, not different field names per step; each getter in the list decides for itself which field of its input it reads. + +The recursion bottoms out at the empty list, where `ChainGetters` is the identity getter — it returns the context it was given: + +```rust +impl FieldGetter for ChainGetters { + type Value = Context; + + fn get_field(context: &Context, _tag: PhantomData) -> &Context { + context + } +} +``` + +So a chain of one getter resolves to that getter applied to the context (its tail being `Nil` returns the value unchanged), a chain of two applies the first then the second, and so on down the `Cons` spine. + +## Examples + +A typical use reaches a field on a nested inner context by chaining the getter that produces the inner context with the getter that reads the field. Here an `App` holds a `Config`, and the target is the `Config`'s field: + +```rust +use cgp::prelude::*; +use cgp::core::field::impls::ChainGetters; // not re-exported through the prelude + +#[cgp_getter] +pub trait HasPort { + fn port(&self) -> &u16; +} + +#[derive(HasField)] +pub struct Config { + pub port: u16, +} + +#[derive(HasField)] +pub struct App { + pub config: Config, +} + +delegate_components! { + App { + PortGetterComponent: ChainGetters< + Product![ + UseField, + UseField, + ], + >, + } +} +``` + +`App` wires `PortGetterComponent` to `ChainGetters` over a two-element list. The first getter, `UseField`, reads `App`'s `config` field to produce a `&Config`; the second, `UseField`, reads that `Config`'s `port` field to produce the `&u16`. `ChainGetters` threads the reference from the first step into the second, so `app.port()` returns the port nested two levels in, without any hand-written walking code. + +## Related constructs + +`ChainGetters` composes the same provider-side [`FieldGetter`](../traits/has_field.md) that [`UseField`](use_field.md) and [`UseFieldRef`](use_field_ref.md) implement, using each as a step in the path; it applies each step through [`FieldMapper`](../traits/has_field.md) to keep borrowed lifetimes inferring across hops. Its list of getters is a type-level [`Cons`](../types/cons.md)/`Nil` spine, most conveniently written with the [`Product!`](../macros/product.md) macro, and its steps read fields keyed by [`Symbol!`](../macros/symbol.md) on contexts deriving [`#[derive(HasField)]`](../derives/derive_has_field.md). It is wired as the provider for a getter defined with [`#[cgp_getter]`](../macros/cgp_getter.md). + +## Source + +The `ChainGetters` struct and its two `FieldGetter` impls (the `Cons` recursion step and the `Nil` base case) are in [crates/core/cgp-field/src/impls/chain.rs](../../../crates/core/cgp-field/src/impls/chain.rs). The `FieldGetter` and `FieldMapper` traits it builds on are in [crates/core/cgp-field/src/traits/has_field.rs](../../../crates/core/cgp-field/src/traits/has_field.rs) and [crates/core/cgp-field/src/traits/map_field.rs](../../../crates/core/cgp-field/src/traits/map_field.rs), and the `Cons`/`Nil` spine it recurses over is defined in [crates/core/cgp-base-types/src/types/](../../../crates/core/cgp-base-types/src/types/) (`cons.rs` and `nil.rs`). diff --git a/docs/reference/providers/dispatch_combinators.md b/docs/reference/providers/dispatch_combinators.md new file mode 100644 index 00000000..0c38591e --- /dev/null +++ b/docs/reference/providers/dispatch_combinators.md @@ -0,0 +1,269 @@ +# Dispatch combinators + +The dispatch combinators are the `cgp-dispatch` provider structs that route an extensible-data value — a record or a variant — to per-field or per-variant handlers, and assemble or finalize the result. They are all [`Computer`](../components/computer.md)-family providers (and several are also [`Handler`](../components/handler.md)/`TryComputer` providers) built on top of the extractor and builder trait families. + +## Purpose + +The dispatch combinators solve the problem of handling an arbitrary record or enum generically, where the set of fields or variants is not known at the call site and the handling logic for each one lives in a separate provider. A hand-written `match` on a concrete enum names every variant in one place and calls a fixed function for each; these combinators instead drive the extractor family from [`extract_field`](../traits/extract_field.md) and the builder family from [`has_builder`](../traits/has_builder.md) to do the same work over a value whose shape is only known at the type level, dispatching each field or variant to a handler chosen by type. + +The combinators divide into two halves that mirror the two halves of the extensible-data machinery. The *matcher* half consumes a sum type: it tries each variant in turn, hands the matched payload to a handler, and proves the match exhaustive without a wildcard arm. The *builder* half produces a product type: it starts from an empty builder, runs a handler per field to compute that field's value, and finalizes the fully-populated record. Both halves are expressed as handler providers so they compose with the rest of the handler ecosystem — they can be wired into a context with [`delegate_components!`](../macros/delegate_components.md), nested inside [`UseInputDelegate`](../providers/use_delegate.md), and chained with the combinators in [`handler_combinators`](handler_combinators.md). + +## Definition + +The combinators are zero-sized provider structs, each generic over a list of handlers or a per-element provider, that implement one or more of the handler component traits. This section groups them by role: the matcher loop they share, the matchers that drive it directly, the field/variant adapters that prepare a value for a handler, the convenience matchers that build a handler list from a context's fields, and the builders that go the other way. Every struct verified here carries `PhantomData` over its type parameters and is a pure type-level entity with no runtime value, in keeping with how CGP providers work. + +### `DispatchMatchers` — the matcher loop + +`DispatchMatchers` is the engine every matcher delegates to. It is a type alias for a monadic pipeline that runs a list of handlers and stops at the first one that succeeds: + +```rust +pub type DispatchMatchers = PipeMonadic; +``` + +The list `Providers` is a `Product![...]` of handlers, each of which takes the partial value (the extractor) and returns `Result`: `Ok(output)` means that handler matched and produced the output, while `Err(remainder)` means it did not match and hands back the extractor with one more variant ruled out. [`PipeMonadic`](monad_providers.md) under the `OkMonadic` monad threads the pipeline along the `Err` branch and short-circuits on `Ok`, so the list runs handler by handler until one returns `Ok`, carrying the shrinking remainder forward through each `Err`. When the last handler still returns `Err`, the remainder type has every variant ruled out and is therefore uninhabited, which is what lets the enclosing matcher discharge it without a fallback arm. `DispatchMatchers` is an implementation detail of the matchers below and is not normally named by users. + +### `MatchWithHandlers` and its borrowed forms + +`MatchWithHandlers` is the owned-input matcher: given a value, it converts the value to its extractor and runs the handler list over it. Its `Computer`/`AsyncComputer` impls require the input to implement [`HasExtractor`](../traits/extract_field.md), run `DispatchMatchers` over `Input::Extractor` to obtain `Result`, and call `finalize_extract_result` on that result so the uninhabited remainder is discharged and the bare `Output` is returned: + +```rust +pub struct MatchWithHandlers(pub PhantomData); + +impl Computer + for MatchWithHandlers +where + Input: HasExtractor, + DispatchMatchers: + Computer>, + Remainder: FinalizeExtract, +{ + type Output = Output; + // compute: DispatchMatchers::compute(context, code, input.to_extractor()) + // .finalize_extract_result() +} +``` + +`MatchWithHandlersRef` and `MatchWithHandlersMut` are the same provider over a borrowed input. They implement the handler traits for `&'a Input` and `&'a mut Input` respectively, require `HasExtractorRef`/`HasExtractorMut`, and run the handler list over `Input::ExtractorRef<'a>`/`Input::ExtractorMut<'a>`, so a value can be matched without being moved. Each of the three structs implements both the synchronous `Computer` and the `AsyncComputer` form. + +### `MatchFirstWithHandlers` and its borrowed forms + +`MatchFirstWithHandlers` is the matcher for the multi-argument calling convention, where the input is a tuple `(Input, Args)` carrying the value being matched together with extra arguments to pass along to each handler. It threads `Args` through the loop unchanged: the handler list runs over `(Input::Extractor, Args)` and returns `Result`, so on a miss both the shrunken remainder and the still-owned arguments are carried to the next handler. When the loop finishes with an `Err`, the remainder is uninhabited and is discharged directly through `finalize_extract`: + +```rust +pub struct MatchFirstWithHandlers(pub PhantomData); + +impl + Computer for MatchFirstWithHandlers +where + Input: HasExtractor, + DispatchMatchers: Computer< + Context, Code, (Input::Extractor, Args), + Output = Result>, + Remainder: FinalizeExtract, +{ + type Output = Output; + // compute: match DispatchMatchers::compute(context, code, (input.to_extractor(), args)) { + // Ok(output) => output, + // Err((remainder, _)) => remainder.finalize_extract(), + // } +} +``` + +The "first" in the name reflects that the value being matched is the *first* element of the input tuple, with the remaining arguments riding alongside. As with the plain matcher, there are borrowed variants `MatchFirstWithHandlersRef` and `MatchFirstWithHandlersMut` over `(&'a Input, Args)` and `(&'a mut Input, Args)`, and each struct implements both `Computer` and `AsyncComputer`. + +### The field and variant adapters + +The handler list a matcher runs is normally a list of *adapters*, each of which tries one field or variant and forwards the matched payload to an inner provider. Four adapters cover the matching side, and each pairs a value-extraction step with a delegation to a provider that defaults to [`UseContext`](use_context.md). + +`ExtractFieldAndHandle` is the variant adapter for the owned/value calling convention. Its `Output` is `Result`: it calls `ExtractField` on the input, and on success wraps the payload in a `Field` and hands it to `Provider`, returning `Ok` of the provider's output; on failure it returns `Err` of the remainder, the extractor with that variant ruled out. `ExtractFirstFieldAndHandle` is the same adapter for the `(Input, Args)` convention, threading the arguments into the provider call as `(Field, Args)` and returning `Result`: + +```rust +pub struct ExtractFieldAndHandle(pub PhantomData<(Tag, Provider)>); +pub struct ExtractFirstFieldAndHandle(pub PhantomData<(Tag, Provider)>); +``` + +`HandleFieldValue` and `HandleFirstFieldValue` are the unwrapping adapters that sit between an extract adapter and the actual work. An extract adapter delivers a `Field` so the variant name is still attached to the payload; `HandleFieldValue` strips the `Field` wrapper and passes the bare `Value` to `Provider`, and `HandleFirstFieldValue` does the same for the `(Field, Args)` tuple, forwarding `(Input, Args)`: + +```rust +pub struct HandleFieldValue(pub PhantomData); +pub struct HandleFirstFieldValue(pub PhantomData); +``` + +`DowncastAndHandle` is the adapter that matches a *group* of variants at once rather than a single one. Instead of `ExtractField`, it uses `CanDowncastFields` (see [`cast`](../traits/cast.md)) to try to narrow the input to a smaller enum type `Inner`; on success it hands the whole `Inner` value to `Provider` and returns `Ok`, and on failure it returns `Err` of the remainder. This lets a matcher delegate several variants to one sub-matcher in a single step: + +```rust +pub struct DowncastAndHandle(pub PhantomData<(Input, Provider)>); +``` + +Each of these adapters implements both `Computer` and `AsyncComputer`. Because every one returns the `Result` (or `Result`) shape that `DispatchMatchers` expects, a `Product!` of them is exactly the handler list a matcher consumes. + +### `MatchWithValueHandlers` and `MatchWithFieldHandlers` + +Writing out the full handler list for every variant is mechanical, so the convenience matchers build the list automatically from the input type's own field list. `MatchWithFieldHandlers` and `MatchWithValueHandlers` are type aliases over [`UseInputDelegate`](use_delegate.md) that dispatch on the input type and, for each input, synthesize the per-variant handler list from that input's [`HasFields`](../traits/has_fields.md): + +```rust +pub type MatchWithFieldHandlers = + UseInputDelegate>; + +pub type MatchWithValueHandlers = + UseInputDelegate>>; +``` + +The difference between the two is exactly one `HandleFieldValue` wrapper. `MatchWithFieldHandlers` builds a list of `ExtractFieldAndHandle` adapters, so `Provider` receives each matched payload as a `Field` with the variant name still attached. `MatchWithValueHandlers` wraps `Provider` in `HandleFieldValue` first, so `Provider` receives the bare `Value` — this is the form to use when the per-variant handler is an ordinary computer over the payload type, such as one generated by [`#[cgp_computer]`](../macros/cgp_computer.md). The list itself is assembled by the `HasFieldHandlers`/`ToFieldHandlers` machinery described below. + +The borrowed counterparts are `MatchWithFieldHandlersRef`/`MatchWithValueHandlersRef` (over `&Input`) and `MatchWithValueHandlersMut` (over `&mut Input`). These additionally wire the `…RefComponent` handler traits through [`PromoteRef`](handler_combinators.md), so a single struct serves both the by-value-of-reference and the by-reference handler interfaces. The first-argument convenience matchers — `MatchFirstWithFieldHandlers`, `MatchFirstWithValueHandlers`, and their `Ref`/`Mut` variants — are the same aliases built on `ExtractFirstFieldAndHandle`, `HandleFirstFieldValue`, and the `MatchFirstWith…` matchers, for the `(Input, Args)` calling convention. + +### `ToFieldHandlers`, `HasFieldHandlers`, and `MapFieldHandler` + +The convenience matchers turn a context's field list into a handler list through three cooperating traits. `MapFieldHandler` is a type-level function from a field's `Tag` to the adapter that should handle it; `ToFieldHandlers` walks the [`Either`](../types/either.md)-spine of a field list and applies that function to each field, producing a `Cons`-list of adapters; and `HasFieldHandlers` is the convenience entry point that reads a context's `HasFields::Fields` and runs `ToFieldHandlers` over it: + +```rust +pub trait MapFieldHandler { + type FieldHandler; +} + +pub trait ToFieldHandlers { + type Handlers; +} + +pub trait HasFieldHandlers { + type Handlers; +} + +impl HasFieldHandlers for Context +where + Context: HasFields, + Fields: ToFieldHandlers, +{ + type Handlers = Fields::Handlers; +} +``` + +`ToFieldHandlers` is implemented inductively over the sum spine: for `Either, RestFields>` it produces `Cons, RestFields::Handlers>`, and for the terminating [`Void`](../types/either.md) it produces `Nil`. The two `MapFieldHandler` markers supplied by the crate are `MapExtractFieldAndHandle`, whose `FieldHandler` is `ExtractFieldAndHandle`, and `MapExtractFirstFieldAndHandle`, whose `FieldHandler` is `ExtractFirstFieldAndHandle`. Composing these is how `MatchWithValueHandlers` ends up running an `ExtractFieldAndHandle>` for each variant of the input enum without the user spelling out the list. + +### `BuildAndSetField` and `BuildAndMerge` + +The builder side runs in the opposite direction: rather than taking a value apart, it assembles a record field by field, with one handler computing each field's value. `BuildAndSetField` is the single-field builder adapter. It takes a builder (a partial record from [`has_builder`](../traits/has_builder.md)), runs `Provider` over a *reference* to that builder to compute the value for `Tag`, then calls `BuildField` to set that field and returns the advanced builder. Because the provider sees `&Builder`, it can read fields already set on the partial record while computing the next one: + +```rust +pub struct BuildAndSetField(pub PhantomData<(Tag, Provider)>); + +impl Computer + for BuildAndSetField +where + Provider: for<'a> Computer, + Builder: BuildField, +{ + type Output = Output; + // compute: let value = Provider::compute(context, code, &builder); + // builder.build_field(PhantomData::, value) +} +``` + +`BuildAndMerge` is the bulk counterpart. Instead of setting one field, it runs `Provider` over a reference to the builder to produce another record's worth of fields, then uses `CanBuildFrom` (see [`has_builder`](../traits/has_builder.md)) to copy every shared field from that result into the builder in one step — the field-list analogue of `BuildAndSetField`: + +```rust +pub struct BuildAndMerge(pub PhantomData); + +impl Computer + for BuildAndMerge +where + Provider: for<'a> Computer, + Builder: CanBuildFrom, +{ + type Output = Output; + // compute: let output = Provider::compute(context, code, &builder); + // builder.build_from(output) +} +``` + +Both `BuildAndSetField` and `BuildAndMerge` implement `Computer`, `TryComputer`, and `Handler`; the `TryComputer` and `Handler` forms additionally require `Context: HasErrorType` and propagate the inner provider's error. + +### `BuildWithHandlers` and `BuildAndMergeOutputs` + +`BuildWithHandlers` is the entry point that turns a list of builder adapters into a complete record. It starts from `Output::builder()` (an empty partial record, via `HasBuilder`), pipes that builder through the handler list with [`PipeHandlers`](handler_combinators.md) so each adapter sets its field, and calls `finalize_build` on the fully-populated result to recover the concrete `Output`: + +```rust +pub struct BuildWithHandlers(pub PhantomData<(Output, Handlers)>); + +impl Computer + for BuildWithHandlers +where + Output: HasBuilder, + PipeHandlers: Computer, + Res: FinalizeBuild, +{ + type Output = Output; + // compute: PipeHandlers::compute(context, code, Output::builder()).finalize_build() +} +``` + +The original `Input` is discarded — `BuildWithHandlers` produces its output from the builder, not from the input. It implements `Computer`, `TryComputer`, and `Handler`, with the latter two requiring `Context: HasErrorType`. Because `finalize_build` is in scope only for the all-present builder configuration, omitting a handler for some field is a compile error rather than a runtime failure. + +`BuildAndMergeOutputs` is a higher-level wrapper used when the handler list is itself a list of plain field-producing providers rather than builder adapters. It is a `delegate_components!` table that maps the whole handler family (`ComputerComponent`, `TryComputerComponent`, `HandlerComponent`, and their `Ref` forms) to `BuildWithHandlers`, where each provider in `Handlers` has first been wrapped in `BuildAndMerge` by mapping the list through the `ToBuildAndMergeHandler` [`MapType`](../traits/map_type.md) marker. In effect it lets a caller supply a list of result-producing providers and have each one merged into the builder automatically. + +## Examples + +A matcher is normally driven by spelling out one extract adapter per variant and wrapping each payload handler in `HandleFieldValue`. The following dispatches a `Shape` enum to a per-variant area computer: + +```rust +use cgp::extra::dispatch::{ + ExtractFieldAndHandle, HandleFieldValue, MatchWithHandlers, +}; + +#[derive(CgpData)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} + +// ComputeArea is a #[cgp_computer] over the payload types Circle and Rectangle. +let circle = Shape::Circle(Circle { radius: 5.0 }); + +let _area = MatchWithHandlers::< + Product![ + ExtractFieldAndHandle>, + ExtractFieldAndHandle>, + ], +>::compute(&(), PhantomData::<()>, circle); +``` + +The list tries `Circle` first; if the runtime value is a circle, `ComputeArea` runs on the `Circle` payload and the loop stops. Otherwise the remainder carries `Circle` ruled out into the `Rectangle` adapter, which is the last arm, so its failure would leave an uninhabited remainder that `finalize_extract_result` discharges. + +The same dispatch is far shorter through `MatchWithValueHandlers`, which builds that list from the enum's own fields. Wiring it into a context's `Computer` component with [`UseInputDelegate`](use_delegate.md) lets the matcher be selected when the input is a `Shape`: + +```rust +delegate_components! { + App { + ComputerComponent: UseInputDelegate, + } +} +``` + +Here a `Circle` input is handled directly by `ComputeArea`, while a `Shape` input is handled by `MatchWithValueHandlers`, which synthesizes `ExtractFieldAndHandle>` for each variant and routes each payload back through the context's own `ComputerComponent` — so `Circle` and `Rectangle` payloads reach `ComputeArea` after all. + +The builder side mirrors this. The following assembles a `FooBarBaz` by merging a built `FooBar` and computing the remaining `baz` field: + +```rust +use cgp::extra::dispatch::{BuildAndMerge, BuildAndSetField, BuildWithHandlers}; + +type Handlers = Product![ + BuildAndMerge, + BuildAndSetField, +]; + +let foo_bar_baz = BuildWithHandlers::::compute(&context, code, ()); +``` + +`BuildWithHandlers` starts from `FooBarBaz::builder()`, runs `BuildAndMerge` to copy the `foo` and `bar` fields from a built `FooBar`, runs `BuildAndSetField` to compute and set `baz`, and finalizes. Dropping either handler leaves a field unset and fails to compile at `finalize_build`. + +## Related constructs + +The matchers stand on the enum-deconstruction traits in [`extract_field`](../traits/extract_field.md) (`HasExtractor`, `ExtractField`, `FinalizeExtract`) and obtain the variant list from [`has_fields`](../traits/has_fields.md); the builders stand on the record-assembly traits in [`has_builder`](../traits/has_builder.md) (`HasBuilder`, `BuildField`, `FinalizeBuild`, `CanBuildFrom`). The matcher loop is [`PipeMonadic`](monad_providers.md) under the `OkMonadic` monad, and the builder pipeline is [`PipeHandlers`](handler_combinators.md); both produce providers in the [`Computer`](../components/computer.md)/[`Handler`](../components/handler.md) families. The convenience matchers dispatch through [`UseInputDelegate`](use_delegate.md) and reuse the borrowed-input promotion of [`PromoteRef`](handler_combinators.md), and their per-element provider defaults to [`UseContext`](use_context.md). The grouped-variant adapter `DowncastAndHandle` relies on [`cast`](../traits/cast.md). The high-level overview that ties all of these together is [`dispatching`](../../concepts/dispatching.md), and the attribute macro that generates a matcher-backed trait impl automatically is [`#[cgp_auto_dispatch]`](../macros/cgp_auto_dispatch.md). The data-type patterns these combinators serve are [extensible records](../../concepts/extensible-records.md) (the builders) and [extensible variants](../../concepts/extensible-variants.md) (the matchers); `BuildAndMergeOutputs` drives the [application builder](../../examples/application-builder.md) example, while `MatchWithValueHandlers` and the explicit `MatchWithHandlers` form drive the [extensible shapes](../../examples/extensible-shapes.md) example over a non-recursive enum and the [expression interpreter](../../examples/expression-interpreter.md) example over a recursive one. + +## Source + +The provider structs live under [crates/extra/cgp-dispatch/src/providers/](../../../crates/extra/cgp-dispatch/src/providers/): the matchers in `with_handlers/` (`match_with_handlers.rs`, `match_with_handlers_ref.rs`, `match_with_handlers_mut.rs`, `match_first_with_handlers*.rs`, `build_with_handlers.rs`), the matcher loop alias in `dispatchers/dispatch_matchers.rs`, the field/variant adapters in `field_matchers/` (`extract_field.rs`, `extract_first_field.rs`, `extract_handle.rs`, `field_value.rs`, `first_field_value.rs`), the convenience matchers and the `ToFieldHandlers`/`HasFieldHandlers`/`MapFieldHandler` machinery in `matchers/` (`match_with_field_handlers.rs`, `match_first_with_field_handlers.rs`, `to_field_handlers.rs`), and the builders in `field_builders/` (`build_and_set_field.rs`, `build_and_merge.rs`) and `builders/build_and_merge_outputs.rs`. The prelude re-exports the value-handler matchers from [crates/main/cgp-extra/src/prelude.rs](../../../crates/main/cgp-extra/src/prelude.rs); the remaining structs are reached through `cgp::extra::dispatch`. Tests exercising them are in [crates/tests/cgp-tests/tests/extensible_data_tests/variants/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/variants/) (matchers) and [records/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/records/) (builders). diff --git a/docs/reference/providers/error_providers.md b/docs/reference/providers/error_providers.md new file mode 100644 index 00000000..bda3b924 --- /dev/null +++ b/docs/reference/providers/error_providers.md @@ -0,0 +1,192 @@ +# In-tree error providers + +The in-tree error providers are the zero-sized provider structs in `cgp-error-extra` that implement the error-raising and error-wrapping components for any context, independent of which concrete error type that context has chosen. + +## Purpose + +These providers exist so that a context can wire up common error-handling strategies without depending on a specific error library. The [`CanRaiseError`](../components/can_raise_error.md) and `CanWrapError` components define *what* a context can do with errors — turn a foreign error into its abstract `Self::Error`, or attach detail to an existing one — but they say nothing about *how*. The providers here supply the how for the cases that do not need a particular backend: convert through `From`, return an error that already is the abstract type, format a foreign error into a string, discard a detail, panic, or absorb an impossible error. Each is generic over the context, so it works with whatever error type the context's [`HasErrorType`](../components/has_error_type.md) names. + +These are the in-tree counterparts to the standalone error backends in `cgp-error-anyhow`, `cgp-error-eyre`, and `cgp-error-std`. Those crates supply providers specialized to a concrete error type — raising into an `anyhow::Error`, for example — whereas the providers in `cgp-error-extra` stay abstract over the context's error type and capture strategies that are independent of any one library. A context typically wires a mix: a backend for the concrete error type plus these generic providers for the cross-cutting strategies. + +Each provider implements one or both of the two error components through their provider traits. `CanRaiseError`'s provider trait is `ErrorRaiser`, wired to the context with `ErrorRaiserComponent`; `CanWrapError`'s provider trait is `ErrorWrapper`, wired with `ErrorWrapperComponent`. A provider that implements `ErrorRaiser` supplies raising; one that implements `ErrorWrapper` supplies wrapping; some supply both. + +## Implementations + +The six providers divide into three groups by which component they implement and how they treat the error. The pure raisers — `RaiseFrom`, `ReturnError`, and `RaiseInfallible` — implement `ErrorRaiser` to convert a source error into the abstract error. `DiscardDetail` implements `ErrorWrapper` to drop detail. `PanicOnError` implements `ErrorRaiser` to abort rather than produce an error value. The string-formatting providers `DebugError` and `DisplayError` implement both components, redirecting through a string. The following sections describe each, naming the component it provides and the bound it places on the context or the error. + +### `RaiseFrom` — convert via `From` + +`RaiseFrom` is the `ErrorRaiser` provider that raises a source error by converting it into the context's error type with the standard `From` trait. It implements `ErrorRaiser` for any context whose abstract `Error` implements `From`: + +```rust +#[cgp_new_provider] +impl ErrorRaiser for RaiseFrom +where + Context: HasErrorType, + Context::Error: From, +{ + fn raise_error(e: E) -> Context::Error { + e.into() + } +} +``` + +This is the default choice whenever the abstract error already knows how to absorb the source error through `From`. Because the bound is `Context::Error: From`, a single wiring of `RaiseFrom` covers every source error type the context's error has a `From` impl for. + +### `ReturnError` — the source is already the abstract error + +`ReturnError` is the `ErrorRaiser` provider for the case where the source error *is* the context's abstract error, so raising is the identity. It implements `ErrorRaiser` only when the context's `Error` is exactly `E`: + +```rust +#[cgp_new_provider] +impl ErrorRaiser for ReturnError +where + Context: HasErrorType, +{ + fn raise_error(e: E) -> E { + e + } +} +``` + +The `HasErrorType` bound ties the source type to the abstract error, so `raise_error` returns its argument untouched. This is what a context uses when generic code raises a value that is already of the context's chosen error type. + +### `RaiseInfallible` — absorb an impossible error + +`RaiseInfallible` is the `ErrorRaiser` provider for `core::convert::Infallible`, the error type that can never be constructed. It implements `ErrorRaiser` for any context with an error type, producing the abstract error by matching on the uninhabited value: + +```rust +#[cgp_new_provider] +impl ErrorRaiser for RaiseInfallible +where + Context: HasErrorType, +{ + fn raise_error(e: Infallible) -> Context::Error { + match e {} + } +} +``` + +Since an `Infallible` value cannot exist, the empty `match` is total and the function is never actually called at runtime. This provider lets generic code that is parameterized over a fallible operation be wired uniformly even when the operation chosen for a given context cannot fail. + +### `DiscardDetail` — wrap by ignoring the detail + +`DiscardDetail` is the `ErrorWrapper` provider that throws away whatever detail is attached and returns the error unchanged. It implements `ErrorWrapper` for any context and any detail type: + +```rust +#[cgp_new_provider] +impl ErrorWrapper for DiscardDetail +where + Context: HasErrorType, +{ + fn wrap_error(error: Context::Error, _detail: Detail) -> Context::Error { + error + } +} +``` + +This satisfies the `CanWrapError` capability without actually enriching the error, which is useful when a context's error type cannot carry extra context, or when the wrapping detail is deliberately not retained. It is the wrapping counterpart to a no-op: the error propagates as-is. + +### `PanicOnError` — abort instead of producing an error + +`PanicOnError` is the `ErrorRaiser` provider that panics with the source error's debug representation rather than returning an abstract error. It implements `ErrorRaiser` for any context whose error type exists, requiring only that the source error is `Debug`: + +```rust +#[cgp_new_provider] +impl ErrorRaiser for PanicOnError +where + Context: HasErrorType, + E: Debug, +{ + fn raise_error(e: E) -> Context::Error { + panic!("{e:?}") + } +} +``` + +Although the signature promises to return `Context::Error`, the body never does — `panic!` diverges. This provider is for contexts where an error is treated as a programming fault that should abort rather than be handled, such as tests or fail-fast tooling. + +### `DebugError` and `DisplayError` — format through a string + +`DebugError` and `DisplayError` implement *both* error components by redirecting through the context's string-based error handling. Rather than producing the abstract error directly, each formats the source error or detail into a `String` and forwards to the context's own `CanRaiseError` or `CanWrapError` — so they delegate the final step to whatever string-handling provider the context already wires. `DebugError` formats with the `Debug` trait: + +```rust +#[cgp_provider] +impl ErrorRaiser for DebugError +where + Context: CanRaiseError, + E: Debug, +{ + fn raise_error(e: E) -> Context::Error { + Context::raise_error(format!("{e:?}")) + } +} + +#[cgp_provider] +impl ErrorWrapper for DebugError +where + Context: CanWrapError, + Detail: Debug, +{ + fn wrap_error(error: Context::Error, detail: Detail) -> Context::Error { + Context::wrap_error(error, format!("{detail:?}")) + } +} +``` + +`DisplayError` is identical in shape but formats with the `Display` trait and `to_string()` instead, raising `Context::raise_error(e.to_string())` and wrapping `Context::wrap_error(error, detail.to_string())`. Both require the context to already raise and wrap `String`, which is the indirection that lets them reduce any `Debug` or `Display` error to the string case the context knows how to handle. Because they format into an allocated `String`, both live behind the crate's `alloc` feature. + +## Behavior + +A context gains an error strategy by wiring one of these providers to `ErrorRaiserComponent` or `ErrorWrapperComponent` exactly like any other component, and the provider's `where` clause is what determines when that wiring type-checks. `RaiseFrom` requires the abstract error to be `From` the source, `ReturnError` requires the source to be the abstract error itself, `RaiseInfallible` accepts only `Infallible`, and `PanicOnError` accepts any `Debug` source — so the choice of provider is also a statement about which source errors a context will accept and how. Because both components dispatch through `UseDelegate` over the source-error or detail type, a context commonly wires several of these providers at once through a delegation table, one per source error type. + +The string-formatting providers compose with the other raisers rather than replacing them, which is the key to their design. `DebugError` and `DisplayError` do not know the context's error type; they only know how to turn a `Debug` or `Display` value into a `String` and hand it off. The context must separately wire a provider — often `RaiseFrom` or a backend provider — that handles the `String` source, and the formatting providers route every other source error through that single string path. This lets a context handle an open-ended set of error types with one concrete string-raising rule plus a uniform formatting redirect. + +## Examples + +Wiring `RaiseFrom` to the error-raiser component lets a context raise any source error its abstract error can absorb through `From`: + +```rust +use cgp::prelude::*; +use cgp::core::error::ErrorRaiserComponent; +use cgp::extra::error::RaiseFrom; + +delegate_components! { + App { + ErrorRaiserComponent: + RaiseFrom, + } +} +``` + +With this wiring, any generic provider that calls `Context::raise_error(source)` on `App` succeeds for every `source` whose type the `App` error implements `From` for. + +A context can combine the formatting and converting providers by dispatching per source-error type, so that a `String` is raised directly while other `Debug` errors are formatted into a string first: + +```rust +use cgp::prelude::*; +use cgp::core::error::ErrorRaiserComponent; +use cgp::extra::error::{DebugError, RaiseFrom}; + +delegate_components! { + App { + ErrorRaiserComponent: + UseDelegate, + } +} +``` + +Here a raised `String` is converted straight into the abstract error by `RaiseFrom`, while a raised `ParseError` is formatted with `Debug` by `DebugError` and then forwarded back through the `String` entry — which `RaiseFrom` handles — giving a single coherent error type from two different sources. + +## Related constructs + +These providers implement the [`CanRaiseError`](../components/can_raise_error.md) and `CanWrapError` components through their `ErrorRaiser` and `ErrorWrapper` provider traits, and every one of them is generic over a context that supplies [`HasErrorType`](../components/has_error_type.md) to name the abstract `Self::Error` they produce. They are wired to a context with `delegate_components!` on `ErrorRaiserComponent`/`ErrorWrapperComponent` and checked with `check_components!`, and because both components derive `UseDelegate`, they are typically dispatched per source-error or detail type through a delegation table — see [`UseDelegate`](use_delegate.md). The library-specific counterparts that raise into a concrete error type live in the standalone backends `cgp-error-anyhow`, `cgp-error-eyre`, and `cgp-error-std`. The [modular error handling](../../concepts/modular-error-handling.md) concept explains how these providers fit alongside the abstract error type and the backends as interchangeable error-handling strategies. + +## Source + +The providers are defined in `cgp-error-extra`: `RaiseFrom` in [crates/extra/cgp-error-extra/src/impls/raise_from.rs](../../../crates/extra/cgp-error-extra/src/impls/raise_from.rs), `ReturnError` in [return_error.rs](../../../crates/extra/cgp-error-extra/src/impls/return_error.rs), `RaiseInfallible` in [infallible.rs](../../../crates/extra/cgp-error-extra/src/impls/infallible.rs), `DiscardDetail` in [discard_detail.rs](../../../crates/extra/cgp-error-extra/src/impls/discard_detail.rs), `PanicOnError` in [panic_error.rs](../../../crates/extra/cgp-error-extra/src/impls/panic_error.rs), and `DebugError`/`DisplayError` (behind the `alloc` feature) in [impls/alloc/debug_error.rs](../../../crates/extra/cgp-error-extra/src/impls/alloc/debug_error.rs) and [display_error.rs](../../../crates/extra/cgp-error-extra/src/impls/alloc/display_error.rs). The `ErrorRaiser`/`ErrorWrapper` provider traits and the `CanRaiseError`/`CanWrapError`/`HasErrorType` consumer traits they build on are in [crates/core/cgp-error/src/](../../../crates/core/cgp-error/src/). The standalone backend counterparts are in [crates/standalone/error/](../../../crates/standalone/error/). These providers are exercised in the error wiring throughout [crates/tests/cgp-tests/](../../../crates/tests/cgp-tests/). diff --git a/docs/reference/providers/handler_combinators.md b/docs/reference/providers/handler_combinators.md new file mode 100644 index 00000000..873110f9 --- /dev/null +++ b/docs/reference/providers/handler_combinators.md @@ -0,0 +1,220 @@ +# Handler combinators + +The handler combinators are the provider structs of `cgp-handler` that build, sequence, and adapt handlers — composing two providers end to end, threading a whole list through a pipeline, returning the input unchanged, and lifting a provider written for one handler shape into another. + +## Purpose + +The handler combinators exist because the handler family is not one trait but several closely related ones — [`Computer`](../components/computer.md), [`TryComputer`](../components/try_computer.md), `AsyncComputer`, [`Handler`](../components/handler.md), and [`Producer`](../components/producer.md), each with a `…Ref` variant — and code is rarely written against all of them at once. A provider author writes a plain synchronous `Computer`, or a fallible `TryComputer`, or an async `Handler`, depending on what is natural for the computation. The combinators let those single-shape providers be wired wherever a different shape is expected, and let several providers be glued into a larger one, without forcing the author to re-implement each provider across every trait in the family. + +They divide into three jobs. The *composition* combinators (`ComposeHandlers`, `PipeHandlers`) sequence handlers so that the output of one becomes the input of the next. The *identity* combinator (`ReturnInput`) is the neutral element of that composition, passing its input straight through. The *promotion* combinators (`Promote` and friends) lift a provider from one handler trait to another — from a `Computer` up to a `TryComputer`, from a synchronous computer to an async one, from a value handler to a reference handler — so that one written implementation satisfies many traits. Because every combinator is itself a CGP provider, all of this happens at the type level through delegation, and the combinators nest freely inside one another. + +Like all CGP providers, these combinators are zero-sized: their type parameters are inner providers carried in `PhantomData`, never runtime values. The combinator names the providers to compose or promote, and the method bodies forward to those providers' associated functions. + +## The handler family + +Every combinator below is defined in terms of the handler component traits, so a brief orientation helps. The family shares a common method signature: a context reference, a `PhantomData` tag selecting the operation, and an input, producing an associated `Output`. The members differ in fallibility and asynchrony. [`Computer`](../components/computer.md) is the plain synchronous, infallible form, with `compute(context, code, input) -> Output`. [`TryComputer`](../components/try_computer.md) is synchronous but fallible, returning `Result` and requiring `Context: HasErrorType`. `AsyncComputer` is asynchronous and infallible. [`Handler`](../components/handler.md) is asynchronous and fallible, the most general member. Each of these has a `…Ref` companion (`ComputerRef`, `TryComputerRef`, `AsyncComputerRef`, `HandlerRef`) whose method takes the input by reference (`&Input`) instead of by value. [`Producer`](../components/producer.md) is the degenerate case that takes no input at all, producing an `Output` from the context and `Code` alone. + +The promotion combinators trade on the natural orderings among these. A `Computer` is also a valid `TryComputer` (wrap the output in `Ok`) and a valid `AsyncComputer` (the future is ready immediately); a `TryComputer` is a valid `Handler`; a value handler can serve a reference handler by dereferencing. The combinators encode exactly these one-directional lifts. + +## Composing handlers in sequence + +`ComposeHandlers` runs two handlers back to back, feeding the output of the first as the input of the second, and is the fundamental sequencing combinator. It implements every member of the handler family by threading the intermediary value through both providers under the same context and `Code`: + +```rust +pub struct ComposeHandlers(pub PhantomData<(ProviderA, ProviderB)>); +``` + +For the plain `Computer` shape, the impl requires `ProviderA: Computer` and `ProviderB: Computer` — the second provider's input type is pinned to the first's output type — and the composite `Output` is `ProviderB::Output`: + +```rust +impl Computer + for ComposeHandlers +where + ProviderA: Computer, + ProviderB: Computer, +{ + type Output = ProviderB::Output; + + fn compute(context: &Context, code: PhantomData, input: Input) -> Self::Output { + let intermediary = ProviderA::compute(context, code, input); + ProviderB::compute(context, code, intermediary) + } +} +``` + +The same shape is implemented for `TryComputer`, `AsyncComputer`, and `Handler`. The fallible and async variants differ only in how the intermediary is obtained: `TryComputer` and `Handler` use `?` to short-circuit on the first provider's error (and so require `Context: HasErrorType`), and `AsyncComputer` and `Handler` `.await` each step. In every case the two providers share one context and one `Code`; only the value flowing between them changes type. + +## Composing a list of handlers + +`PipeHandlers` generalizes `ComposeHandlers` from two providers to a type-level list of them, composing the whole pipeline right to left into a single nested `ComposeHandlers`. It is parameterized by a [`Product!`](../macros/product.md) list of providers: + +```rust +pub struct PipeHandlers(pub PhantomData); +``` + +`PipeHandlers` carries no handler impls of its own. Instead it delegates every component to whatever single provider the list folds down to, computed by an internal `ComposeProviders` trait that walks the `Cons`/`Nil` list. A list of one provider folds to that provider unchanged; a list `Cons` folds to `ComposeHandlers`. The delegation entry then routes any handler component on `PipeHandlers` to that folded provider: + +```rust +delegate_components! { + > + PipeHandlers { + Component: Provider, + } +} +``` + +The practical effect is that `PipeHandlers` behaves exactly as `ComposeHandlers>`, threading the input through `A`, then `B`, then `C`, with each stage's output type feeding the next stage's input type. Because the delegation is generic over the `Component` key, the same pipeline simultaneously serves as a `Computer`, `TryComputer`, `AsyncComputer`, or `Handler` — whichever the wiring asks for — provided every stage supports that shape. This is the combinator to reach for when wiring a multi-stage transformation: list the stages in order and let `PipeHandlers` build the composition. + +## Returning the input unchanged + +`ReturnInput` is the identity handler: it ignores the context and `Code` and returns its input as its output. It is a plain unit struct with no type parameters: + +```rust +pub struct ReturnInput; +``` + +It implements `Computer`, `TryComputer`, `AsyncComputer`, and `Handler`, in each case setting `Output = Input` and returning the input directly (wrapped in `Ok` for the fallible variants, which therefore require `Context: HasErrorType`). `ReturnInput` is the neutral element of handler composition: composing it before or after any other handler leaves that handler's behavior unchanged. It is useful as a placeholder stage, as the base case of a conditionally-built pipeline, or wherever a handler slot must be filled but no transformation is wanted. + +## Promoting a provider to another handler shape + +The promotion combinators each take a single inner `Provider` and re-expose it under a different member of the handler family, so that an implementation written once satisfies several traits. Each is a one-parameter struct carrying the inner provider in `PhantomData`, and each implements the *target* traits in terms of the inner provider's *source* trait. The lifts they perform are summarized here and detailed below. + +`Promote` lifts upward along the infallible-to-fallible and sync-to-async axes, treating a less capable provider as a more capable one without adding error or async behavior of its own. It gives three impls: + +```rust +pub struct Promote(pub PhantomData); +``` + +As a `Computer`, `Promote` requires the inner `Provider: Producer` and ignores its own input, calling `Provider::produce` — this is how a producer (which takes no input) is adapted to fill a computer slot (which is handed an input it does not need). As a `TryComputer`, it requires `Provider: Computer` and wraps the infallible result in `Ok`. As a `Handler`, it requires `Provider: AsyncComputer` and wraps the awaited result in `Ok`. In each case the promotion adds the missing capability — discarding an input, introducing an always-`Ok` result — without changing what the inner provider computes. + +`PromoteAsync` lifts a synchronous provider into an asynchronous one: + +```rust +pub struct PromoteAsync(pub PhantomData); +``` + +As an `AsyncComputer`, it requires `Provider: Computer` and runs it synchronously inside the async method (the returned future is immediately ready). As a `Handler`, it requires `Provider: TryComputer` and returns that fallible synchronous result, so a synchronous fallible computer becomes an async fallible handler. + +`PromoteRef` bridges between value handlers and reference handlers by dereferencing, and is the most thoroughly implemented promotion — it covers all four families in both directions: + +```rust +pub struct PromoteRef(pub PhantomData); +``` + +For each of `Computer`/`ComputerRef`, `TryComputer`/`TryComputerRef`, `AsyncComputer`/`AsyncComputerRef`, and `Handler`/`HandlerRef`, `PromoteRef` provides two impls. One direction implements the by-value trait given an inner by-reference provider plus `Input: Deref`, calling the inner provider on `input.deref()`. The other direction implements the by-reference trait given an inner by-value provider that works `for<'a>` over `&'a Input`, calling the inner provider on the borrowed input. This lets a provider written to take `&T` serve a slot that hands it a smart pointer to `T`, and vice versa, without manual deref boilerplate. + +`TryPromote` lifts in both directions across the boundary between a `Result`-valued output and a fallible trait, unifying the two ways of expressing fallibility: + +```rust +pub struct TryPromote(pub PhantomData); +``` + +As a `TryComputer`, it requires the inner `Provider: Computer` whose `Output` is itself a `Result`, and unwraps that into the `TryComputer` result — turning a computer that *returns* a `Result` into a genuine fallible computer. As a `Computer`, it goes the other way: given `Provider: TryComputer`, its output type is `Result` and it surfaces the fallible result as an ordinary value. The analogous pair lifts between `Handler` (from an `AsyncComputer` returning a `Result`) and `AsyncComputer` (from a `Handler`). All four impls require `Context: HasErrorType`. + +## Promotion bundles + +Several promotion adapters are not handler impls themselves but delegation tables that wire a whole cluster of handler components to the right single-trait promotion at once. They exist so that a provider author can implement just one trait — say `Computer` — and have the bundle fill in every other member of the family by promotion. Each is defined with [`delegate_components!`](../macros/delegate_components.md) over a generic inner `Provider`, and is what the [`#[cgp_computer]`](../macros/cgp_computer.md) and [`#[cgp_producer]`](../macros/cgp_producer.md) macros wire their generated providers into. + +`PromoteComputer` starts from a provider that implements `Computer` (the by-value, synchronous, infallible base) and fills in every other family member. It routes `TryComputerComponent` to `Promote` (wrap in `Ok`), `AsyncComputerComponent` and `HandlerComponent` to `PromoteAsync` (run synchronously in an async method), and all the `…Ref` components to `PromoteRef` (dereference, then defer to the base): + +```rust +delegate_components! { + + new PromoteComputer { + ComputerRefComponent: PromoteRef, + TryComputerComponent: Promote, + TryComputerRefComponent: PromoteRef, + AsyncComputerComponent: PromoteAsync, + AsyncComputerRefComponent: PromoteRef, + HandlerComponent: PromoteAsync, + HandlerRefComponent: PromoteRef, + } +} +``` + +`PromoteTryComputer` starts from a provider that implements `TryComputer`. It routes `TryComputerComponent` to `TryPromote` and defers all the remaining components to `PromoteComputer`, so the fallible base is first turned into a plain computer and the rest of the family is derived from there. + +`PromoteProducer` starts from a `Producer` — a provider that takes no input. It routes `ComputerComponent` to `Promote` (which discards the computer's input and calls `produce`) and defers the rest to `PromoteComputer`, so a single produced value flows out of every handler shape regardless of input. + +`PromoteAsyncComputer` starts from a provider that implements `AsyncComputer`. It wires `HandlerComponent` to `Promote` (wrap the awaited value in `Ok`) and the `AsyncComputerRefComponent` and `HandlerRefComponent` to `PromoteRef`. It is the async-base counterpart to `PromoteComputer`. + +`PromoteHandler` starts from the most general base, a provider that implements `Handler`. It routes `HandlerComponent` to `TryPromote` and defers the async-ref components to `PromoteAsyncComputer`. + +## Dispatching on the input type with `UseInputDelegate` + +`UseInputDelegate` is a delegate-style dispatcher analogous to [`UseDelegate`](use_delegate.md), but it keys its lookup table on the handler's `Input` type rather than on the `Code` type. It is defined as a one-parameter struct holding the lookup table: + +```rust +pub struct UseInputDelegate(pub PhantomData); +``` + +Whereas `UseDelegate` dispatches on the first generic parameter of a provider trait — `Code` for the handler family — `UseInputDelegate` dispatches on the `Input` parameter, so that the provider handling a value is chosen by the type of that value. The handler component traits enable both dispatchers at once: each is declared with two `#[derive_delegate]` directives, `UseDelegate` and `UseInputDelegate`, as on the `Computer` component: + +```rust +#[cgp_component(Computer)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanCompute { + type Output; + + fn compute(&self, _code: PhantomData, input: Input) -> Self::Output; +} +``` + +For `UseInputDelegate`, the second directive makes `#[cgp_component]` generate a provider impl that looks up `Components` keyed on the `Input` type and forwards to the matching delegate: + +```rust +impl Computer + for UseInputDelegate +where + Components: DelegateComponent, + Delegate: Computer, +{ + type Output = Delegate::Output; + + fn compute(context: &Context, code: PhantomData, input: Input) -> Self::Output { + Delegate::compute(context, code, input) + } +} +``` + +The lookup key is the dispatched parameter — here `Input` — while `Code` and `Context` pass through unchanged, exactly as with `UseDelegate`. (The `#[derive_delegate]` machinery groups the dispatched parameters into a tuple, which for a single parameter collapses to the parameter type itself, so the table is keyed directly on the concrete input type.) `UseInputDelegate` is wired through a nested table inside `delegate_components!` in the same way: the outer entry routes a handler component to `UseInputDelegate`, and that inner table maps each concrete input type to the provider responsible for it. The same impl shape is generated for every member of the handler family, since each declares the `UseInputDelegate` directive. + +## Examples + +A pipeline of computers shows the composition combinators with `PipeHandlers`. Suppose `Multiply` and `Add` are `Computer` providers over `u64` that read a factor or addend from a context field, and a context carries `foo`, `bar`, and `baz`: + +```rust +use cgp::prelude::*; +use cgp::extra::handler::{CanCompute, PipeHandlers}; + +#[derive(HasField)] +pub struct MyContext { + pub foo: u64, + pub bar: u64, + pub baz: u64, +} + +delegate_components! { + MyContext { + ComputerComponent: + PipeHandlers, + Add, + Multiply, + ]>, + } +} +``` + +Wiring `ComputerComponent` to `PipeHandlers` over the three-stage list composes them into `ComposeHandlers, ComposeHandlers, Multiply<…>>>`. Computing over an input of `5` on a context with `foo = 2`, `bar = 3`, `baz = 4` first multiplies by `foo`, then adds `bar`, then multiplies by `baz`, yielding `((5 * 2) + 3) * 4`. The same list could be wired to `HandlerComponent` instead, provided every stage supports the handler shape — and stages of mismatched shapes can be reconciled inline, as in `PromoteAsync>>`, which lifts a plain `Computer` stage up to the async `Handler` shape the pipeline expects. + +The promotion bundles appear most often indirectly. A provider author writing a single `Computer` impl and wiring it with [`#[cgp_computer]`](../macros/cgp_computer.md) gets `PromoteComputer` wired across the rest of the family automatically, so the one implementation answers `try_compute`, `compute_async`, and `handle` as well. Reaching for `PromoteComputer` by hand achieves the same effect when wiring the components explicitly. + +## Related constructs + +The combinators are providers of the handler component traits: [`Computer`](../components/computer.md), [`TryComputer`](../components/try_computer.md), [`Handler`](../components/handler.md), and [`Producer`](../components/producer.md), with the conceptual overview in [handlers](../../concepts/handlers.md). The composition and promotion combinators are wired automatically by the [`#[cgp_computer]`](../macros/cgp_computer.md) and [`#[cgp_producer]`](../macros/cgp_producer.md) macros, which generate a single-trait provider and delegate the rest of the family through the promotion bundles. `UseInputDelegate` is the `Input`-keyed sibling of [`UseDelegate`](use_delegate.md), both generated by the `#[derive_delegate]` directive of [`#[cgp_component]`](../macros/cgp_component.md) and wired through nested tables with [`delegate_components!`](../macros/delegate_components.md). The promotion bundles are themselves delegation tables, so they rely on [`DelegateComponent`](../traits/delegate_component.md) and propagate dependencies through [`IsProviderFor`](../traits/is_provider_for.md) to the [check traits](../../concepts/check-traits.md). + +## Source + +The combinators are defined in `cgp-handler` under [crates/extra/cgp-handler/src/providers/](../../../crates/extra/cgp-handler/src/providers/): `compose.rs` (`ComposeHandlers`), `pipe.rs` (`PipeHandlers` and the internal `ComposeProviders` fold), `return_input.rs` (`ReturnInput`), `promote.rs` (`Promote`), `promote_async.rs` (`PromoteAsync`), `promote_ref.rs` (`PromoteRef`), `try_promote.rs` (`TryPromote`), and `promote_all.rs` (the `PromoteComputer`, `PromoteTryComputer`, `PromoteProducer`, `PromoteAsyncComputer`, and `PromoteHandler` bundles). `UseInputDelegate` is defined in [crates/extra/cgp-handler/src/types.rs](../../../crates/extra/cgp-handler/src/types.rs), and its provider impls are generated from the `#[derive_delegate(UseInputDelegate)]` directive on the handler component traits in [crates/extra/cgp-handler/src/components/](../../../crates/extra/cgp-handler/src/components/). Behavioral tests exercising the combinators live in [crates/tests/cgp-tests/tests/handler_tests/](../../../crates/tests/cgp-tests/tests/handler_tests/), notably `pipe.rs`. diff --git a/docs/reference/providers/monad_providers.md b/docs/reference/providers/monad_providers.md new file mode 100644 index 00000000..48435422 --- /dev/null +++ b/docs/reference/providers/monad_providers.md @@ -0,0 +1,104 @@ +# Monad providers + +The monad providers turn a list of handlers and a choice of monad into a single composed handler that short-circuits on the appropriate branch: `PipeMonadic` is the pipeline builder, the `IdentMonadic` / `OkMonadic` / `ErrMonadic` markers (with their transformer forms) name the monad, and `BindOk` / `BindErr` are the per-step bind providers that implement the branching. + +## Purpose + +These providers implement [monadic handler composition](../../concepts/monadic-handlers.md) on top of the [`Computer`](../components/computer.md) family. They exist so that a sequence of handlers whose outputs carry a "continue" case and a "stop" case can be chained without manually pattern-matching each step: the monad decides which case threads forward and which short-circuits, and the providers assemble the composition in types. The result of building a pipeline is itself a provider for `Computer`, `AsyncComputer`, `TryComputer`, and `Handler`, so a monadic pipeline wires into a context exactly like any other handler provider. + +The providers divide into three groups. `PipeMonadic` is the entry point a user wires or invokes. The monad markers are the zero-sized types that select the short-circuiting behavior. The bind providers are the lower-level building blocks that `PipeMonadic` composes internally, and that can also be used directly with the non-monadic [`PipeHandlers`](../providers/handler_combinators.md) when finer control is wanted. + +## The pipeline provider + +`PipeMonadic` is the provider that composes a handler list `Providers` under a monad `M` into a single short-circuiting handler: + +```rust +pub struct PipeMonadic(pub PhantomData<(M, Providers)>); +``` + +`M` is a monad marker and `Providers` is a [type-level list](../types/cons.md) of handler providers, written with `Product![...]`. `PipeMonadic` implements the handler components by folding the list: it delegates `ComputerComponent` and `AsyncComputerComponent` to the provider that an internal `BindProviders` computation produces from the list. That fold walks the list so that the first provider runs on the input and its result is bound, via the monad, to the monadically-composed rest of the list — each step running only on the previous step's continue branch. + +`PipeMonadic` also implements `TryComputerComponent` and `HandlerComponent`, the fallible and async-fallible handler components, by a bridge through the err monad. For these it first maps every provider in the list to `TryPromote` (demoting fallible handlers to plain `Computer`s whose output is an explicit `Result`), applies `ErrMonadic` as a transformer on top of the given monad `M`, composes that demoted list under the transformed monad, and finally wraps the composed provider back in `TryPromote` to restore the fallible interface. The effect is that a `PipeMonadic` over fallible handlers short-circuits on the context's error type in addition to whatever branching `M` itself contributes. + +## Monad markers + +The monad markers are zero-sized types that select which branch of a step's output continues the pipeline and which short-circuits. CGP defines three base markers and a transformer form for the two that branch on `Result`. + +`IdentMonadic` is the identity monad: it threads every value forward and never short-circuits, so a `PipeMonadic` is equivalent to plain composition with `PipeHandlers`. + +```rust +pub struct IdentMonadic; +``` + +`OkMonadic` short-circuits on `Ok` and continues on `Err`, and `ErrMonadic` short-circuits on `Err` and continues on `Ok`: + +```rust +pub struct OkMonadic; +pub struct ErrMonadic; +``` + +`ErrMonadic` is the familiar early-return-on-error behavior, where the first `Err` produced by any step becomes the pipeline's output and the rest do not run. `OkMonadic` is its mirror, stopping at the first `Ok`. + +Each `Result`-branching marker has a transformer form, `OkMonadicTrans` and `ErrMonadicTrans`, that applies its behavior on top of a base monad `M` so monads can stack over nested result types: + +```rust +pub struct OkMonadicTrans(pub PhantomData); +pub struct ErrMonadicTrans(pub PhantomData); +``` + +Writing `OkMonadicTrans` builds a monad that short-circuits on an outer `Ok` while threading an inner `Result` through the err monad beneath it. The bare `OkMonadic` and `ErrMonadic` markers produce their own transformer forms over `IdentMonadic` when used as transformers, so a single layer of branching needs no explicit transformer. + +## Bind providers + +`BindOk` and `BindErr` are the per-step providers that implement a single bind of the ok and err monads. `PipeMonadic` composes them internally, but they are also usable directly as handler providers — for example inside a [`PipeHandlers`](../providers/handler_combinators.md) list — when a pipeline is built step by step rather than through `PipeMonadic`. + +```rust +pub struct BindOk(pub PhantomData<(M, Cont)>); +pub struct BindErr(pub PhantomData<(M, Cont)>); +``` + +In both, `M` is the monad layer beneath this bind and `Cont` is the continuation provider to run on the continue branch. `BindErr` implements `Computer` and `AsyncComputer` for an input of `Result`: on `Ok(value)` it runs `Cont` on `value` and lifts the continuation's output back through `M`, and on `Err(err)` it short-circuits by lifting the error directly to the output, skipping `Cont`. `BindOk` is the mirror, branching on `Result`: it runs `Cont` on the `Err` payload and short-circuits on `Ok`. The `M` parameter is what lets these binds nest — at the bottom of a single-layer pipeline it is `IdentMonadic`, and a stacked monad threads a deeper monad through it. + +## The `TryPromoteProviders` mapper + +`TryPromoteProviders` is the type-level mapper that `PipeMonadic` uses to demote a whole list of fallible handler providers to infallible ones in one step: + +```rust +pub struct TryPromoteProviders; + +impl MapType for TryPromoteProviders { + type Map = TryPromote; +} +``` + +It implements [`MapType`](../traits/map_type.md) by mapping each provider to `TryPromote`, so applying it to a handler list with `MapFields` rewrites every element to its `TryPromote` form. `PipeMonadic` uses this when implementing the fallible handler components, turning a list of `TryComputer` providers into a list of plain `Computer` providers whose output is an explicit `Result` before composing them under the err-transformed monad. The `TryPromote` provider it wraps each element in is documented with the [handler combinators](../providers/handler_combinators.md); it converts between the fallible and infallible handler interfaces in both directions. + +## Examples + +The simplest use composes a homogeneous list under a base monad. With an `Increment` computer that returns `Result` — `Ok` on success and `Err("overflow")` on overflow — composing three under `ErrMonadic` chains on the `Ok` value and stops at the first error: + +```rust +PipeMonadic::::compute(&context, code, 253) +// 253 -> Ok(254) -> Ok(255) -> Err("overflow") +``` + +A single bind step can be assembled by hand and run through `PipeHandlers`, which is what `PipeMonadic` does internally for a two-element list: + +```rust +PipeHandlers::]>::compute(&context, code, 1) +// 1 -> Ok(2) -> BindErr runs the second Increment on 2 -> Ok(3) +``` + +Stacking monads handles nested results. Composing handlers that return `Result, &str>` under `OkMonadicTrans` short-circuits on the outer `Ok` while threading the inner `Result` through the err monad, and the same list composed under `OkMonadic` can be driven through the fallible `try_compute` and async `handle` entry points because `PipeMonadic` implements `TryComputer` and `Handler` as well: + +```rust +PipeMonadic::::try_compute(&context, code, 1) +``` + +## Related constructs + +`PipeMonadic` generalizes the non-monadic [handler combinators](../providers/handler_combinators.md): `PipeHandlers` and `ComposeHandlers` chain handlers feeding each output straight into the next, which is exactly what `PipeMonadic` reduces to, while the `TryPromote` provider those combinators define is what `PipeMonadic` uses to bridge fallible and infallible handlers. The monads these providers consume are defined by the trait layer in [monad traits](../traits/monad.md) — `MonadicTrans`, `MonadicBind`, `ContainsValue`, and `LiftValue` — and the conceptual overview of why a monadic pipeline short-circuits is in [monadic handlers](../../concepts/monadic-handlers.md). The pipelines build providers for the [`Computer`](../components/computer.md) family, and `TryPromoteProviders` relies on [`MapType`](../traits/map_type.md) and `MapFields` to map over the handler list. For selecting one handler among several by a type-level key rather than running them in sequence, see [dispatch combinators](../providers/dispatch_combinators.md). + +## Source + +The pipeline provider, `TryPromoteProviders`, and the internal `BindProviders` fold are in [crates/extra/cgp-monad/src/providers/pipe_monadic.rs](../../../crates/extra/cgp-monad/src/providers/pipe_monadic.rs). The monad markers and bind providers are in [crates/extra/cgp-monad/src/monadic/](../../../crates/extra/cgp-monad/src/monadic/) — `ident.rs` for `IdentMonadic`, `ok.rs` for `OkMonadic` / `OkMonadicTrans` / `BindOk`, and `err.rs` for `ErrMonadic` / `ErrMonadicTrans` / `BindErr`. Usage is exercised in [crates/tests/cgp-tests/src/tests/monad/](../../../crates/tests/cgp-tests/src/tests/monad/) (`ok.rs`, `err.rs`, `ok_err_trans.rs`). diff --git a/docs/reference/providers/redirect_lookup.md b/docs/reference/providers/redirect_lookup.md new file mode 100644 index 00000000..a5977d50 --- /dev/null +++ b/docs/reference/providers/redirect_lookup.md @@ -0,0 +1,81 @@ +# `RedirectLookup` + +`RedirectLookup` is a zero-sized provider that implements a component's provider trait by looking up a type-level path in a separate table, re-routing the lookup along that path instead of resolving the component against the context directly. + +## Purpose + +`RedirectLookup` decouples *which key* a component is looked up under from *which table* answers it. The ordinary provider blanket impl looks a component up in the context's own [delegation table](../traits/delegate_component.md), keyed by the component-name struct. `RedirectLookup` does the lookup differently: it consults the table `Components` keyed by an arbitrary type-level `Path`, then delegates to whatever provider that entry holds. This indirection is what lets one component's resolution be redirected to a different key in a different table — the basis for organizing wiring into namespaces and presets. + +The redirection makes namespaces possible. A namespace groups a context's components under a path prefix so that several related components can be wired in one place and addressed by a shared path. `RedirectLookup` is the provider that turns a prefixed path back into a concrete provider: the namespace machinery sets a component's delegate to a `RedirectLookup` carrying the path under which the real provider was registered, so a lookup of the component follows that path into the table and lands on the intended provider. Presets are built the same way — a preset is a reusable table whose entries are reached through redirected paths. + +`RedirectLookup` is not written by hand; it is emitted by macros. Every `#[cgp_component]` generates a `RedirectLookup` impl for its provider trait, and the namespace attributes generate `DelegateComponent` entries whose delegate is a `RedirectLookup`. Reading those generated entries is where this provider appears. + +Like every CGP provider, `RedirectLookup` carries no runtime value. Both type parameters are held in `PhantomData`, and the struct exists only as a type-level marker describing a lookup to perform. + +## Definition + +`RedirectLookup` is a struct parameterized by a key and a table, defined in `cgp-component`: + +```rust +pub struct RedirectLookup(pub PhantomData<(Key, Components)>); +``` + +The `Key` parameter is the type-level path to look up — typically a [`PathCons`](../types/path_cons.md) chain of [`Symbol!`](../macros/symbol.md) segments ending in a component-name struct. The `Components` parameter is the table to look it up in, a type implementing [`DelegateComponent`](../traits/delegate_component.md). In the generated impls the two appear in the order `RedirectLookup`, with the table first and the path second. The `PhantomData` makes both parameters part of a valueless struct. + +## Behavior + +`#[cgp_component]` generates a `RedirectLookup` impl of the provider trait alongside the consumer blanket impl, the provider blanket impl, the component-name struct, and the [`UseContext`](use_context.md) impl. The generated impl looks the path up in the table and forwards to the resulting delegate. For a component such as + +```rust +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self) -> String; +} +``` + +the macro generates this impl (shown with the macro's real placeholder identifiers): + +```rust +impl<__Context__, __Components__, __Path__> Greeter<__Context__> + for RedirectLookup<__Components__, __Path__> +where + __Components__: DelegateComponent<__Path__>, + <__Components__ as DelegateComponent<__Path__>>::Delegate: Greeter<__Context__>, +{ + fn greet(__context__: &__Context__) -> String { + <__Components__ as DelegateComponent<__Path__>>::Delegate::greet(__context__) + } +} +``` + +The mechanism is one `DelegateComponent` lookup keyed on `__Path__` rather than on the component name. `RedirectLookup` implements `Greeter` whenever `Components` maps `Path` to a delegate that itself implements `Greeter`, and the method forwards to that delegate. When the consumer trait carries generic type parameters, the impl additionally constrains `Path` with [`ConcatPath`](../traits/static_format.md) so the parameters are appended to the path before the lookup, letting the redirected key encode the generic arguments. As always, the impl is paired with a matching `IsProviderFor` impl so dependencies reach the [check traits](../../concepts/check-traits.md). + +The namespace attributes are what populate the path side. The `#[prefix(@path in Namespace)]` attribute on a component generates a namespace impl whose `Delegate` is `RedirectLookup`, with the prefix path joined onto the component name — so resolving the component under that namespace follows the prefixed path into the table. The `DefaultNamespace` trait plays the same role for the default routing. Together these turn a path-addressed wiring entry into a concrete provider through `RedirectLookup`. + +## Examples + +`RedirectLookup` appears in the delegate that the namespace machinery generates, where a component is registered under a path and reached through that path. Wiring a component under a path prefix produces a `RedirectLookup` entry: + +```rust +use cgp::prelude::*; + +pub struct App; + +delegate_components! { + App { + namespace DefaultNamespace; + + @bar.baz: TestProvider, + } +} +``` + +This registers `TestProvider` under the path `bar`/`baz` in `App`'s default namespace. When a component is later resolved against `App` through that namespace, its delegate is a `RedirectLookup` whose `Path` is the `PathCons` chain `bar` then `baz` then the component name. The lookup follows that path into `App`'s table — matching the entry registered above — and dispatches to `TestProvider`. The component name never keys the context directly; it is the tail of a path that `RedirectLookup` walks. This is the indirection that lets namespaces and presets organize wiring by path while still resolving to ordinary providers. + +## Related constructs + +`RedirectLookup` is generated by [`#[cgp_component]`](../macros/cgp_component.md) for every component, and is central to the namespace and preset machinery driven by [`#[cgp_namespace]`](../macros/cgp_namespace.md) and explained in [namespaces](../../concepts/namespaces.md). Its lookup is a [`DelegateComponent`](../traits/delegate_component.md) read keyed on a type-level path built from [`PathCons`](../types/path_cons.md) and [`Symbol!`](../macros/symbol.md), with generic parameters folded in through [`ConcatPath`](../traits/static_format.md). It sits beside the other `#[cgp_component]`-generated provider [`UseContext`](use_context.md), which routes back to the context rather than through a separate table, and its dependency propagation flows through [`IsProviderFor`](../traits/is_provider_for.md) for the [check traits](../../concepts/check-traits.md). + +## Source + +The struct is defined in [crates/core/cgp-component/src/providers/redirect_lookup.rs](../../../crates/core/cgp-component/src/providers/redirect_lookup.rs), and the related `DefaultNamespace` trait in [crates/core/cgp-component/src/namespaces.rs](../../../crates/core/cgp-component/src/namespaces.rs). The `RedirectLookup` provider impl is generated by `to_redirect_lookup_impl` in [crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_redirect_lookup_impl.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_redirect_lookup_impl.rs), which appends generic parameters through `ConcatPath`. The namespace delegates that target `RedirectLookup` are produced by the `#[prefix]` attribute in [crates/macros/cgp-macro-core/src/types/attributes/prefix.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/prefix.rs) and the redirect mapping in [crates/macros/cgp-macro-core/src/types/delegate_component/mapping/redirect.rs](../../../crates/macros/cgp-macro-core/src/types/delegate_component/mapping/redirect.rs). Expansion snapshots showing the generated impl and the namespace wiring are in [crates/tests/cgp-tests/tests/namespace_tests/redirect.rs](../../../crates/tests/cgp-tests/tests/namespace_tests/redirect.rs). diff --git a/docs/reference/providers/use_context.md b/docs/reference/providers/use_context.md new file mode 100644 index 00000000..f9084131 --- /dev/null +++ b/docs/reference/providers/use_context.md @@ -0,0 +1,88 @@ +# `UseContext` + +`UseContext` is a zero-sized provider that implements any CGP provider trait by forwarding its methods back to the context's own consumer-trait implementation. + +## Purpose + +`UseContext` exists to turn a context's existing consumer-trait implementation into a provider that other providers can call. A provider trait is normally implemented by some dedicated provider type, but sometimes the implementation you want is exactly the one the context already supplies through its consumer trait. `UseContext` is that bridge: it is a provider whose method bodies simply call the consumer method on the context, so wiring a component to `UseContext` means "use whatever this context already does for this trait." + +This makes `UseContext` the exact dual of the consumer-trait blanket implementation. The blanket impl of a consumer trait such as `CanGreet` runs in the consumer-to-provider direction — a context implements `CanGreet` by delegating to whichever provider implements `Greeter` for it. `UseContext` runs in the opposite direction: it implements the provider trait `Greeter` by delegating to whatever `CanGreet` implementation the context has. One forwards the consumer trait to a provider; the other forwards a provider trait back to the consumer trait. + +The pattern matters most for [higher-order providers](../../concepts/higher-order-providers.md), which take another provider as a type parameter. A higher-order provider needs an inner provider to delegate to, and `UseContext` lets that inner provider be "the context's own wiring." When a higher-order provider defaults its inner-provider parameter to `UseContext`, the provider falls back to whatever the main context already wires for that component, rather than forcing the author to name an explicit inner provider. + +Like every CGP provider, `UseContext` carries no runtime value. It is a unit struct used purely as a type-level marker; the `self` position of its provider impls is never read, and there is nothing to construct or store. + +## Definition + +`UseContext` is a unit struct with no fields, defined in `cgp-component`: + +```rust +pub struct UseContext; +``` + +Its only state is its identity as a type. Every implementation `UseContext` carries is generated for it by `#[cgp_component]`, not written by hand, so the struct definition itself is deliberately empty. + +## Behavior + +`#[cgp_component]` emits a `UseContext` implementation of the provider trait for every component it defines, alongside the consumer blanket impl, the provider blanket impl, the component-name struct, and the [`RedirectLookup`](redirect_lookup.md) impl. The generated impl constrains the context to implement the consumer trait and then forwards each method to it. For a component such as + +```rust +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} +``` + +the macro generates the following `UseContext` impl (shown with the macro's real placeholder identifiers): + +```rust +impl<__Context__> Greeter<__Context__> for UseContext +where + __Context__: CanGreet, +{ + fn greet(__context__: &__Context__) { + __Context__::greet(__context__) + } +} +``` + +The provider method `greet` takes the context explicitly and calls the context's own `CanGreet::greet`. Each `UseContext` impl is paired with a matching `IsProviderFor` impl carrying the same `where` clause, so that delegation propagates the dependency and the [check traits](../../concepts/check-traits.md) can report a missing consumer-trait implementation precisely. Supertrait bounds on the consumer trait are reproduced in the `UseContext` impl's `where` clause, so a context must satisfy them before `UseContext` can stand in as a provider. + +The cyclic-delegation caveat is the one rule to respect: a context must never delegate a component to `UseContext`. Doing so asks the context to implement the consumer trait by delegating to a provider (`UseContext`) that in turn implements the provider trait by calling the consumer trait — a cycle the trait solver cannot resolve, surfacing as an overflow or unsatisfied-bound error. `UseContext` is meant to be supplied to *another* provider as its inner provider, not wired as a context's own delegate for the same component. + +## Examples + +The idiomatic use of `UseContext` is as the default inner provider of a higher-order provider, so that the higher-order provider routes through the context's existing wiring unless told otherwise. Consider an area calculator that scales the result of an inner calculator: + +```rust +use cgp::prelude::*; + +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self) -> f64; +} + +pub struct ScaledArea(pub PhantomData); + +#[cgp_impl(ScaledArea)] +impl AreaCalculator +where + InnerCalculator: AreaCalculator, +{ + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + InnerCalculator::area(self) * scale_factor * scale_factor + } +} +``` + +Because `ScaledArea` defaults `InnerCalculator` to `UseContext`, wiring a context to `ScaledArea` (with no explicit inner provider) makes the inner `area` call resolve to the context's own `CanCalculateArea` implementation. The context computes a base area through whatever `AreaCalculator` it already wires, and `ScaledArea` multiplies that by the scale factor. Overriding the parameter — for example `ScaledArea` — instead binds the inner calculation statically to `RectangleArea`, bypassing the context's wiring for that step. + +Note that `UseContext` only acts as a default when a higher-order provider's struct definition gives it as the default generic parameter, as `ScaledArea` does above. A provider without such a default has no inner provider to fall back to, and the inner provider must always be named explicitly. + +## Related constructs + +`UseContext` is the dual of the consumer-trait blanket impl that [`#[cgp_component]`](../macros/cgp_component.md) generates, and the macro emits both for every component. It is most useful with [higher-order providers](../../concepts/higher-order-providers.md) as their default inner provider, and the [dispatch combinators](dispatch_combinators.md) use it the same way — `MatchWithValueHandlers` defaults its per-variant provider to `UseContext`, so each matched payload routes back through the context's own wiring and any one variant's handler can be overridden in the table without disturbing the others, as the [extensible shapes](../../examples/extensible-shapes.md) example shows. It also composes with [`WithProvider`](with_provider.md) through the alias `WithContext = WithProvider`, which adapts a context's foundational getter or type implementation into a component. The closely related [`RedirectLookup`](redirect_lookup.md) provider, also generated by `#[cgp_component]`, routes a lookup through a separate table rather than back to the context. The dependency propagation that keeps `UseContext` honest in [check traits](../../concepts/check-traits.md) flows through [`IsProviderFor`](../traits/is_provider_for.md). + +## Source + +The struct is defined in [crates/core/cgp-component/src/providers/use_context.rs](../../../crates/core/cgp-component/src/providers/use_context.rs), which also declares the `WithContext` alias. The `UseContext` provider impl is generated by `to_use_context_impl` in [crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_use_context_impl.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_use_context_impl.rs), which forwards each provider-trait method to the consumer trait via `trait_items_to_delegated_impl_items`. Expansion snapshots showing the generated `UseContext` impl appear in [crates/tests/cgp-tests/tests/getter_tests/string.rs](../../../crates/tests/cgp-tests/tests/getter_tests/string.rs) and [crates/tests/cgp-tests/tests/component_tests/cgp_component/default_impl.rs](../../../crates/tests/cgp-tests/tests/component_tests/cgp_component/default_impl.rs). diff --git a/docs/reference/providers/use_default.md b/docs/reference/providers/use_default.md new file mode 100644 index 00000000..a1191e0e --- /dev/null +++ b/docs/reference/providers/use_default.md @@ -0,0 +1,108 @@ +# `UseDefault` + +`UseDefault` is a zero-sized marker provider that a component is wired to when its implementation should come entirely from the consumer trait's default method bodies. + +## Purpose + +`UseDefault` names the "use the trait's own defaults" choice in CGP wiring. A consumer trait may define default bodies for its methods, the same way any Rust trait can. When every method of a component has a usable default, there is no behavior left for a provider to supply — the provider only needs to exist so the component can be wired. `UseDefault` is that empty provider: wiring a component to `UseDefault` selects an implementation whose method bodies are inherited from the trait's defaults rather than supplied by a dedicated provider. + +This keeps a default-only component consistent with the rest of CGP wiring. Without it, a component whose methods are all defaulted would still need some provider type and some `delegate_components!` entry to participate in the delegation table; `UseDefault` is the shared name for that role, so authors do not invent a one-off marker each time. A context that wants the defaults wires the component to `UseDefault` and writes no method bodies of its own. + +`UseDefault` is a bare marker that CGP defines but does not implement for any trait. Unlike [`UseContext`](use_context.md), [`UseFields`](use_fields.md), or [`UseField`](use_field.md), no macro generates a provider impl for it; the provider impl is written by the author, typically with [`#[cgp_impl]`](../macros/cgp_impl.md) and an empty body so the trait's defaults take effect. This is what distinguishes `UseDefault` from the providers that carry generated behavior: it is purely a conventional name for an author-supplied, default-bodied implementation. + +Like every CGP provider, `UseDefault` carries no runtime value. It is a unit struct used purely as a type-level marker, and its provider impls never read the `self` position. + +## Definition + +`UseDefault` is a unit struct with no fields, defined in `cgp-component`: + +```rust +pub struct UseDefault; +``` + +It is exported from `cgp-component` but is not part of the `cgp::prelude`, so reaching it requires naming it through `cgp_component::UseDefault`. The struct carries no behavior of its own; meaning is given to it by the provider impl an author writes against it. + +## Behavior + +A component is wired to `UseDefault` by implementing the provider trait for it with empty method bodies, which causes the consumer trait's default bodies to be used. The implementing author leaves each method body out, so the default defined on the consumer trait fills in. Consider a getter and a greeter whose methods both have defaults: + +```rust +#[cgp_getter] +pub trait HasName { + fn name(&self) -> &str { + "John" + } +} + +#[cgp_component(Greeter)] +pub trait CanGreet: HasName { + fn greet(&self) -> String { + format!("Hello, {}!", self.name()) + } +} +``` + +Provider impls against `UseDefault` are written with empty bodies, so the defaults take over: + +```rust +#[cgp_impl(UseDefault)] +impl NameGetter for Context {} + +#[cgp_impl(UseDefault)] +impl Greeter for Context {} +``` + +The first impl makes `UseDefault` a `NameGetter` provider whose `name` is the trait default `"John"`; the second makes it a `Greeter` provider whose `greet` is the trait default that formats around `self.name()`. The `Greeter` impl restates the consumer-side dependency `HasName` in its bound, because the default body of `greet` calls `name`. Each `#[cgp_impl]` also generates the matching [`IsProviderFor`](../traits/is_provider_for.md) impl, so the dependency propagates to the [check traits](../../concepts/check-traits.md) exactly as for any other provider. + +Because `UseDefault` has no generated impls, the author controls precisely which components it serves. A type that has not had a provider impl written for a given component is simply not a provider for it; there is no automatic fallback, and the empty-body `#[cgp_impl]` is the explicit opt-in. + +## Examples + +The use of `UseDefault` is to wire several default-bodied components to one shared marker, then delegate them all to it in the context's table. Building on the traits above: + +```rust +use cgp::prelude::*; +use cgp_component::UseDefault; + +#[cgp_getter] +pub trait HasName { + fn name(&self) -> &str { + "John" + } +} + +#[cgp_component(Greeter)] +pub trait CanGreet: HasName { + fn greet(&self) -> String { + format!("Hello, {}!", self.name()) + } +} + +#[cgp_impl(UseDefault)] +impl NameGetter for Context {} + +#[cgp_impl(UseDefault)] +impl Greeter for Context {} + +pub struct App; + +delegate_components! { + App { + [ + NameGetterComponent, + GreeterComponent, + ]: + UseDefault, + } +} +``` + +`App` delegates both `NameGetterComponent` and `GreeterComponent` to `UseDefault`, so `App.greet()` produces `"Hello, John!"` entirely from the two default bodies — no method is implemented on `App` or on a dedicated provider. The array syntax routes both components to the single `UseDefault` marker in one entry. + +## Related constructs + +`UseDefault` is wired with [`delegate_components!`](../macros/delegate_components.md) and its provider impls are written with [`#[cgp_impl]`](../macros/cgp_impl.md), distinguishing it from [`UseContext`](use_context.md) and the getter providers [`UseFields`](use_fields.md) and [`UseField`](use_field.md), which carry macro-generated behavior rather than relying on author-written empty bodies. Its dependency tracking flows through [`IsProviderFor`](../traits/is_provider_for.md) and is checked with [`check_components!`](../macros/check_components.md), the same as any other provider. + +## Source + +The struct is defined in [crates/core/cgp-component/src/providers/use_default.rs](../../../crates/core/cgp-component/src/providers/use_default.rs) and re-exported in [crates/core/cgp-component/src/providers/mod.rs](../../../crates/core/cgp-component/src/providers/mod.rs); the file contains only the bare struct, with no macro-generated impls. The empty-body `#[cgp_impl]` pattern that wires components to a default-only marker is exercised in [crates/tests/cgp-tests/tests/component_tests/cgp_component/default_impl.rs](../../../crates/tests/cgp-tests/tests/component_tests/cgp_component/default_impl.rs). diff --git a/docs/reference/providers/use_delegate.md b/docs/reference/providers/use_delegate.md new file mode 100644 index 00000000..58d60035 --- /dev/null +++ b/docs/reference/providers/use_delegate.md @@ -0,0 +1,98 @@ +# `UseDelegate` + +`UseDelegate` is a zero-sized provider that implements a generic-parameterized provider trait by using `Components` as a type-level lookup table, dispatching to a different inner provider for each value of a chosen generic parameter. + +> **Legacy:** `UseDelegate`, the [`#[derive_delegate]`](../attributes/derive_delegate.md) attribute that generates its provider impl, and the [`UseDelegate` nested-table wiring](../macros/delegate_components.md) are a legacy dispatch mechanism. The same per-type dispatch is now expressed more ergonomically with the `open` statement of [`delegate_components!`](../macros/delegate_components.md), which folds the per-value entries directly into the context's own table — no separate table type, no `UseDelegate` wrapper, and no `#[derive_delegate]` on the component, since the dispatch rides the [`RedirectLookup`](redirect_lookup.md) impl every [`#[cgp_component]`](../macros/cgp_component.md) already generates. Prefer `open` for new code; `UseDelegate` is retained for compatibility and is expected to be deprecated, and eventually removed, once the namespace-based form is shown to cover every dispatch case. The rest of this document describes the legacy mechanism as it stands today. + +## Purpose + +`UseDelegate` solves the problem of choosing a provider based on a type argument rather than on the component alone. An ordinary CGP component picks its provider by looking up the component name in the context's [delegation table](../traits/delegate_component.md). But when a provider trait carries extra generic parameters — a `SourceError` to convert, a `Shape` to measure, an `Input` to compute over — the right provider often depends on which concrete type that parameter is. `UseDelegate` is the provider that performs this second lookup: it treats one of the generic parameters as a key and reads the matching inner provider out of a table, so a single wiring entry can fan out to many type-specific providers. + +This lets context-generic providers stay generic even when their implementations would otherwise overlap on a parameter. Instead of writing one provider that matches on every possible `Shape`, you write a small provider per shape and let `UseDelegate` route each concrete shape to its own. The dispatch is ad hoc and type-directed: the key is a real type appearing in the trait, and the table maps that type to the provider responsible for it. + +`UseDelegate` follows the same delegation pattern as the provider blanket impl, but with a different key. Where the blanket impl uses the component-name struct as the key into the *context's* table, `UseDelegate` uses the chosen generic parameter as the key into its *own* `Components` table. It is the default dispatcher CGP offers for this purpose, so authors need not invent a fresh dispatcher type for the common case, though defining additional dispatcher types is supported when several parameters must be routed independently. + +Like every CGP provider, `UseDelegate` holds no runtime value. The `Components` type parameter is carried in `PhantomData`, and the struct exists only as a type-level marker that names the lookup table to use. + +## Definition + +`UseDelegate` is a unit-like struct parameterized by the table type, defined in `cgp-component`: + +```rust +pub struct UseDelegate(pub PhantomData); +``` + +The single type parameter `Components` is the type-level lookup table — a type that implements [`DelegateComponent`](../traits/delegate_component.md) keyed on the generic parameter being dispatched. The `PhantomData` exists only to make `Components` a parameter of a struct that has no values; nothing of `Components` is ever constructed. + +## Behavior + +The provider impl for `UseDelegate` is generated by the `#[derive_delegate]` attribute of [`#[cgp_component]`](../macros/cgp_component.md), which names the generic parameter to dispatch on. Given a component whose trait has an extra parameter and a `derive_delegate` directive, + +```rust +#[cgp_component { + provider: ErrorRaiser, + derive_delegate: UseDelegate, +}] +pub trait CanRaiseError: HasErrorType { + fn raise_error(error: SourceError) -> Self::Error; +} +``` + +the macro generates an `ErrorRaiser` impl for `UseDelegate` that uses `Components` as the table and `SourceError` as the key: + +```rust +impl ErrorRaiser + for UseDelegate +where + Context: HasErrorType, + Components: DelegateComponent, + Delegate: ErrorRaiser, +{ + fn raise_error(error: SourceError) -> Context::Error { + Delegate::raise_error(error) + } +} +``` + +The mechanism is a single `DelegateComponent` lookup keyed on `SourceError`. `UseDelegate` implements `ErrorRaiser` for a given `SourceError` exactly when `Components` maps that `SourceError` to a `Delegate` that itself implements `ErrorRaiser` for that `SourceError`, and the method body forwards to that delegate. Only the parameter named inside `UseDelegate<...>` is used as the key — here `SourceError` alone — so the dispatch keys on one parameter while the rest pass through unchanged. As elsewhere in CGP, each provider impl is paired with an `IsProviderFor` impl so dependencies propagate to the [check traits](../../concepts/check-traits.md). + +A component may derive more than one dispatcher when different parameters should be routed differently. Passing a list to `derive_delegate` generates one impl per dispatcher — the default `UseDelegate` keying on the first parameter, plus any custom dispatcher types keying on the others — so each parameter can be looked up through its own table. + +## Examples + +`UseDelegate` is most often wired through a nested table inside `delegate_components!`, which builds both the outer entry and the inner lookup table in one place. Given an area component that dispatches on a `Shape` parameter: + +```rust +use cgp::prelude::*; + +#[cgp_component { + provider: AreaCalculator, + derive_delegate: UseDelegate, +}] +pub trait CanCalculateArea { + fn area(&self, shape: &Shape) -> f64; +} + +pub struct Rectangle; +pub struct Circle; + +delegate_components! { + MyApp { + AreaCalculatorComponent: + UseDelegate, + } +} +``` + +The wiring reads in two layers. `MyApp` delegates `AreaCalculatorComponent` to `UseDelegate`, so `MyApp`'s area calculation routes through `UseDelegate` using `AreaCalculatorComponents` as the table. That inner table — defined inline by the `new` keyword — maps the `Rectangle` type to the `RectangleArea` provider and the `Circle` type to `CircleArea`. The result is that `MyApp` implements `CanCalculateArea` through `RectangleArea` and `CanCalculateArea` through `CircleArea`, with `UseDelegate` selecting between them based on the `Shape` argument. Adding a new shape is one more entry in the inner table; the providers themselves stay untouched. + +## Related constructs + +`UseDelegate`'s provider impl is generated by the [`#[derive_delegate]`](../attributes/derive_delegate.md) attribute of [`#[cgp_component]`](../macros/cgp_component.md), and it is wired — usually through a nested table — with [`delegate_components!`](../macros/delegate_components.md). The lookup itself is a [`DelegateComponent`](../traits/delegate_component.md) read keyed on a generic parameter rather than on a component name, which is the same trait the ordinary provider blanket impl uses with the component name as key. Dependency propagation for the dispatched provider flows through [`IsProviderFor`](../traits/is_provider_for.md), letting [check traits](../../concepts/check-traits.md) report which type-specific provider is missing a dependency. + +## Source + +The struct is defined in [crates/core/cgp-component/src/providers/use_delegate.rs](../../../crates/core/cgp-component/src/providers/use_delegate.rs). The `UseDelegate` provider impl is generated from the `#[derive_delegate]` directive parsed in [crates/macros/cgp-macro-core/src/types/attributes/](../../../crates/macros/cgp-macro-core/src/types/attributes/) and emitted by the `#[cgp_component]` pipeline in [crates/macros/cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/). The nested-table wiring is handled by `delegate_components!` in [crates/macros/cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/). Behavioral tests exercising `UseDelegate` dispatch live in [crates/tests/cgp-tests/src/tests/use_delegate/](../../../crates/tests/cgp-tests/src/tests/use_delegate/). diff --git a/docs/reference/providers/use_delegated_type.md b/docs/reference/providers/use_delegated_type.md new file mode 100644 index 00000000..7d44ca41 --- /dev/null +++ b/docs/reference/providers/use_delegated_type.md @@ -0,0 +1,91 @@ +# `UseDelegatedType` + +`UseDelegatedType` is a zero-sized type provider that resolves an abstract CGP type by looking the type tag up in a delegation table, rather than fixing it to a single concrete type. + +## Purpose + +`UseDelegatedType` exists for the case where the concrete type an abstract type resolves to should itself be decided by a type-level table. The plain [`UseType`](use_type.md) provider binds an abstract type to one fixed `T`. But sometimes a single provider must answer several abstract-type components at once, or route each type tag to a different concrete type chosen elsewhere — for instance when a preset or a higher-order provider supplies a coherent bundle of types. Hand-writing one `UseType` wiring per tag would scatter that decision; `UseDelegatedType` concentrates it into one `Components` table that the provider consults. + +The mechanism is the same indirection that [`UseDelegate`](use_delegate.md) provides for behavioral components, lifted to the type level. Where `UseDelegate` dispatches a *method call* to whichever provider `Components` maps the active tag to, `UseDelegatedType` dispatches a *type resolution* to whichever concrete type `Components` maps the active tag to. It is the type-level analogue of `UseDelegate`: both read an entry out of a [`DelegateComponent`](../traits/delegate_component.md) table keyed by the tag, but one yields behavior and the other yields a type. + +Like every CGP provider, `UseDelegatedType` carries no runtime value — it is a `PhantomData`-only marker named in wiring, never constructed. + +## Definition + +`UseDelegatedType` is a phantom-typed struct parameterized by the lookup table it consults, defined in `cgp-type`: + +```rust +pub struct UseDelegatedType(pub PhantomData); + +pub type WithDelegatedType = WithProvider>; +``` + +The `Components` parameter is a type that implements [`DelegateComponent`](../traits/delegate_component.md) for each type tag the provider must answer — the same kind of type-level key-value map that `delegate_components!` builds. The `WithDelegatedType` alias wraps the provider in [`WithProvider`](with_provider.md), so a user-defined [`#[cgp_type]`](../macros/cgp_type.md) component (whose generated `WithProvider` impl forwards to any `TypeProvider`) can be backed by a delegated lookup as well as the built-in [`HasType`](../components/has_type.md) component. Neither `UseDelegatedType` nor `WithDelegatedType` is re-exported through `cgp::prelude`; reach them through `cgp::core::types`. + +## Behavior + +`UseDelegatedType` implements [`TypeProvider`](../components/has_type.md) by looking the type tag `Tag` up in `Components` and reporting the delegate it finds as the abstract type: + +```rust +#[cgp_provider(TypeProviderComponent)] +impl TypeProvider for UseDelegatedType +where + Components: DelegateComponent, +{ + type Type = Type; +} +``` + +The `where` clause is the whole of the behavior: `Components: DelegateComponent` reads the entry stored at key `Tag` in the `Components` table, and the impl sets the abstract `Type` to that delegate. Because the lookup is keyed by `Tag`, one `UseDelegatedType` provider answers as many distinct type tags as `Components` has entries, each resolving to its own concrete type. If `Components` has no entry for a given tag, the `DelegateComponent` bound is unsatisfied and the context simply does not implement `HasType` for that tag — the missing-entry diagnostic from `DelegateComponent` surfaces the gap. + +Contrast this with `UseType`, whose `TypeProvider` impl is unconditional and always reports the single type `T`. `UseDelegatedType` adds exactly one level of indirection — the `DelegateComponent` lookup — so the concrete type comes from the table instead of from the provider's own parameter. + +## Examples + +A typical use defines a lookup table mapping type tags to concrete types and wires a context's type component to `UseDelegatedType` over that table. The table is an ordinary type carrying `DelegateComponent` entries: + +```rust +use cgp::prelude::*; +use cgp::core::types::UseDelegatedType; // not re-exported through the prelude + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_type] +pub trait HasIndexType { + type Index; +} + +pub struct App; +pub struct AppTypes; + +delegate_components! { + AppTypes { + ScalarTypeProviderComponent: f64, + IndexTypeProviderComponent: usize, + } +} + +delegate_components! { + App { + [ + ScalarTypeProviderComponent, + IndexTypeProviderComponent, + ]: UseDelegatedType, + } +} +``` + +`App` routes both its scalar and index type components through `UseDelegatedType`. When the wiring asks for `App`'s `Scalar`, the provider looks `ScalarTypeProviderComponent` up in `AppTypes` and finds `f64`; for `Index` it finds `usize`. A single provider entry on `App` thus answers two abstract types, with the concrete choices held in one place in `AppTypes`. + +This is what makes `UseDelegatedType` valuable for bundling: the set of concrete types lives in the `AppTypes` table and can be reused, swapped, or supplied by a preset, while each context only points its type components at the table. + +## Related constructs + +`UseDelegatedType` is the type-level counterpart of [`UseDelegate`](use_delegate.md), which performs the same `DelegateComponent` lookup for behavioral (method) components. It resolves through the [`DelegateComponent`](../traits/delegate_component.md) trait, the type-level key-value map that `delegate_components!` populates, and implements the [`HasType` / `TypeProvider`](../components/has_type.md) component it answers for. Its sibling [`UseType`](use_type.md) is the simpler provider that fixes an abstract type to one concrete type without a lookup. Its `WithDelegatedType` alias is one of the named wrappers around [`WithProvider`](with_provider.md), used to back a [`#[cgp_type]`](../macros/cgp_type.md) component with a delegated lookup. + +## Source + +The `UseDelegatedType` struct, its `WithDelegatedType` alias, and the `TypeProvider` impl are in [crates/core/cgp-type/src/impls/use_delegated_type.rs](../../../crates/core/cgp-type/src/impls/use_delegated_type.rs). The `HasType` consumer trait and `TypeProvider` provider trait are in [crates/core/cgp-type/src/traits/has_type.rs](../../../crates/core/cgp-type/src/traits/has_type.rs), and `DelegateComponent` is in [crates/core/cgp-component/src/traits/delegate_component.rs](../../../crates/core/cgp-component/src/traits/delegate_component.rs). diff --git a/docs/reference/providers/use_field.md b/docs/reference/providers/use_field.md new file mode 100644 index 00000000..8a7dc62d --- /dev/null +++ b/docs/reference/providers/use_field.md @@ -0,0 +1,94 @@ +# `UseField` + +`UseField` is a zero-sized provider that implements a getter component by reading a field named by `Tag` from the context through [`HasField`](../traits/has_field.md), letting the field name differ from the getter method name. + +## Purpose + +`UseField` exists to decouple a getter's method name from the field it reads. A getter component defined with [`#[cgp_getter]`](../macros/cgp_getter.md) describes a value the context can supply — `fn name(&self) -> &str` — but the context may store that value under a different field, say `first_name`, or different contexts may store it under different names. `UseField` carries the field name as its type parameter, so wiring a context's getter component to `UseField` makes the getter read `first_name` even though the method is `name`. The field name lives in the wiring, not in the trait. + +This is the provider that [`#[cgp_getter]`](../macros/cgp_getter.md) targets. When a getter trait has a single method, `#[cgp_getter]` generates a `UseField` impl for the getter's provider trait with the field tag left as a free parameter, so a context picks the field by writing `UseField` in its delegation table. `UseField` itself is the general-purpose provider underneath that pattern: it works for any tag the context's [`HasField`](../traits/has_field.md) impl supports. + +The `Tag` is usually a type-level string built with [`Symbol!`](../macros/symbol.md), such as `Symbol!("name")`, or a type-level integer wrapped in `Index` for tuple fields — these are exactly the tags that [`#[derive(HasField)]`](../derives/derive_has_field.md) generates `HasField` impls for. Any other type works as a tag too, but then the context must supply the matching `HasField` impl itself. As with every CGP provider, `UseField` carries no runtime value; it is a `PhantomData`-only marker named in wiring. + +## Definition + +`UseField` is a phantom-typed struct parameterized by the field tag, defined in `cgp-field`: + +```rust +pub struct UseField(pub PhantomData); + +pub type WithField = WithProvider>; +``` + +The `WithField` alias wraps `UseField` in [`WithProvider`](with_provider.md), the adapter that lets a generic field getter back a specific getter component. The `#[cgp_getter]` macro generates a `WithProvider` impl for its component, so a getter can be wired with either `UseField` (through the macro's own generated `UseField` impl) or `WithField` (through `WithProvider`). + +## Implementations + +`UseField` implements three provider traits, each forwarding to the context's [`HasField`](../traits/has_field.md) impl for `Tag`. The central one is the provider-side getter, [`FieldGetter`](../traits/has_field.md), which reads the field by reference: + +```rust +impl FieldGetter for UseField +where + Context: HasField, +{ + type Value = Value; + + fn get_field(context: &Context, _tag: PhantomData) -> &Value { + context.get_field(PhantomData) + } +} +``` + +Two tags appear here for a reason. `OutTag` is the tag the *component* asks under (the getter component's name), while `Tag` is the *field* tag the provider was parameterized with. The impl ignores `OutTag` entirely and reads `Tag` from the context, which is precisely the decoupling: the component's identity and the field name are independent. The associated `Value` is taken from the context's `HasField` impl, so the returned reference is to the real field. + +`UseField` also implements the mutable getter [`MutFieldGetter`](../traits/has_field.md) the same way, requiring `Context: HasFieldMut` and returning `&mut Value` via `get_field_mut`. And it implements [`TypeProvider`](../components/has_type.md), reporting the field's `Value` type as an abstract type — so the *type* of a field can itself be wired as a context's abstract type. Each impl is paired with an `IsProviderFor` impl carrying the same `HasField` bound, so delegation propagates the dependency and check traits report a missing field precisely. + +## Examples + +The defining use wires a getter to a field whose name differs from the method, which is the case the simpler [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md) cannot express. The trait method is `name`, but the context stores the value in `first_name`: + +```rust +use cgp::prelude::*; + +#[cgp_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[derive(HasField)] +pub struct Person { + pub first_name: String, +} + +delegate_components! { + Person { + NameGetterComponent: UseField, + } +} + +fn greet(person: &Person) { + println!("Hello, {}!", person.name()); // reads the first_name field +} +``` + +`Person` wires `NameGetterComponent` to `UseField`. The generated getter resolves through the `FieldGetter` impl above, whose `Tag` is `Symbol!("first_name")`, so `person.name()` reads `Person`'s `first_name` field — the method name and the field name diverge, with the field name supplied entirely by the wiring. + +The same binding can be written with the `WithField` alias, which routes through `WithProvider`: + +```rust +delegate_components! { + Person { + NameGetterComponent: WithField, + } +} +``` + +Both forms read `first_name`; `UseField` is the idiomatic choice for binding a getter to a named field without hand-writing a provider. + +## Related constructs + +`UseField` is the provider that [`#[cgp_getter]`](../macros/cgp_getter.md) generates an impl for, the mechanism that lets a getter's field name differ from its method name. It implements the provider-side [`FieldGetter` / `MutFieldGetter`](../traits/has_field.md) traits by reading the consumer-side [`HasField`](../traits/has_field.md) impl that [`#[derive(HasField)]`](../derives/derive_has_field.md) produces, keyed by tags built with [`Symbol!`](../macros/symbol.md) or `Index`. Its `WithField` alias is one of the named wrappers around [`WithProvider`](with_provider.md). It is the field-level analogue of the [`UseType`](use_type.md) provider that [`#[cgp_type]`](../macros/cgp_type.md) generates for abstract types. For a getter whose value is reached through `AsRef`/`AsMut` rather than read directly, see [`UseFieldRef`](use_field_ref.md); for chaining getters across nested contexts, see [`ChainGetters`](chain_getters.md). + +## Source + +The `UseField` struct, its `WithField` alias, and the `FieldGetter`, `MutFieldGetter`, and `TypeProvider` impls are in [crates/core/cgp-field/src/impls/use_field.rs](../../../crates/core/cgp-field/src/impls/use_field.rs). The `HasField`, `FieldGetter`, and (in `has_field_mut.rs`) `HasFieldMut`, `MutFieldGetter` traits are in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/). The `#[cgp_getter]`-generated `UseField` impl is built in [crates/macros/cgp-macro-core/src/types/cgp_getter/use_field.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_getter/use_field.rs). Behavioral and expansion tests are in [crates/tests/cgp-tests/tests/getter.rs](../../../crates/tests/cgp-tests/tests/getter.rs) and the `getter_tests/` modules beside it. diff --git a/docs/reference/providers/use_field_ref.md b/docs/reference/providers/use_field_ref.md new file mode 100644 index 00000000..fe0c00d2 --- /dev/null +++ b/docs/reference/providers/use_field_ref.md @@ -0,0 +1,87 @@ +# `UseFieldRef` + +`UseFieldRef` is a zero-sized provider that implements a getter component by reading a field named by `Tag` and then dereferencing it through `AsRef`/`AsMut`, so the getter exposes `&Value` rather than the field's own type. + +## Purpose + +`UseFieldRef` exists for getters whose return type is reached *through* a field rather than being the field itself. The plain [`UseField`](use_field.md) provider returns a reference to a field exactly as stored: a `String` field yields `&String`. But a getter often wants to expose a borrowed view — `&str` from a `String`, `&[T]` from a `Vec`, `&Path` from a `PathBuf` — where the stored type implements `AsRef` for the desired `Value`. `UseFieldRef` carries both the field tag and the target `Value`, reads the field, and calls `as_ref()` to produce the view, so the getter's signature can name the borrowed type while the context stores the owning type. + +This complements `UseField` rather than replacing it. `UseField` is the right provider when the getter returns the field's type directly; `UseFieldRef` is the right one when the getter returns a reference obtained by `AsRef` from the field. Both decouple the field name from the method name through `Tag`; `UseFieldRef` additionally decouples the *exposed type* from the *stored type* through `Value`. As with every CGP provider, `UseFieldRef` carries no runtime value — it is a `PhantomData`-only marker named in wiring. + +## Definition + +`UseFieldRef` is a phantom-typed struct parameterized by the field tag and the borrowed value type, defined in `cgp-field`: + +```rust +pub struct UseFieldRef(pub PhantomData<(Tag, Value)>); + +pub type WithFieldRef = WithProvider>; +``` + +`Tag` names the field, as in [`UseField`](use_field.md), and `Value` is the type the getter exposes — the type the stored field can be borrowed as via `AsRef`. The `WithFieldRef` alias wraps the provider in [`WithProvider`](with_provider.md), so a getter component can be backed by a ref-style field accessor through the macro-generated `WithProvider` impl. Unlike the more common [`UseField`](use_field.md), neither `UseFieldRef` nor `WithFieldRef` is re-exported through `cgp::prelude`; reach them through `cgp::core::field::impls`. + +## Implementations + +`UseFieldRef` implements the provider-side getter [`FieldGetter`](../traits/has_field.md) by reading the field at `Tag` and dereferencing it to `&Value`: + +```rust +impl FieldGetter for UseFieldRef +where + Context: HasField + 'static>, +{ + type Value = Value; + + fn get_field(context: &Context, _tag: PhantomData) -> &Value { + context.get_field(PhantomData).as_ref() + } +} +``` + +The `where` clause carries the defining constraint: the context's field at `Tag` must implement `AsRef`, so the stored type can be borrowed as the exposed type. As in `UseField`, `OutTag` is the tag the component asks under and is ignored; the field is read at `Tag`. The body reads the field and calls `as_ref()`, so a `String` field exposed with `Value = str` yields `&str`. The `'static` bound on the field type lets Rust infer the borrow's lifetime through the `AsRef` call. + +`UseFieldRef` also implements the mutable getter [`MutFieldGetter`](../traits/has_field.md), requiring the field type to implement both `AsRef` and `AsMut` and returning `&mut Value` via `as_mut()`. Unlike `UseField`, `UseFieldRef` does not implement `TypeProvider`, because its purpose is borrowed field access rather than abstract-type resolution. + +## Examples + +A typical use exposes a `&str` getter over a context that stores the name as a `String`, with the getter's signature naming the borrowed type. The component returns `&str`, while `Person` stores a `String`: + +```rust +use cgp::prelude::*; +use cgp::core::field::impls::UseFieldRef; // not re-exported through the prelude + +#[cgp_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[derive(HasField)] +pub struct Person { + pub name: String, +} + +delegate_components! { + Person { + NameGetterComponent: UseFieldRef, + } +} +``` + +`Person` wires `NameGetterComponent` to `UseFieldRef`. The `FieldGetter` impl reads the `name` field — a `String` — and, because `String: AsRef`, returns `&str` from `as_ref()`. The getter exposes the borrowed `str` view while the context owns the `String`. + +The same binding can be written with the `WithFieldRef` alias, which routes through `WithProvider`: + +```rust +delegate_components! { + Person { + NameGetterComponent: WithFieldRef, + } +} +``` + +## Related constructs + +`UseFieldRef` is the borrow-through-`AsRef` variant of [`UseField`](use_field.md): both read a field named by `Tag` and implement the provider-side [`FieldGetter` / `MutFieldGetter`](../traits/has_field.md) traits over [`HasField`](../traits/has_field.md), but `UseFieldRef` adds a `Value` parameter and exposes `&Value` via `AsRef`/`AsMut` instead of the field's own type. It reads fields produced by [`#[derive(HasField)]`](../derives/derive_has_field.md), keyed by tags from [`Symbol!`](../macros/symbol.md) or `Index`, and is wired to a getter defined with [`#[cgp_getter]`](../macros/cgp_getter.md). Its `WithFieldRef` alias is one of the named wrappers around [`WithProvider`](with_provider.md). For chaining getters across nested contexts, see [`ChainGetters`](chain_getters.md). + +## Source + +The `UseFieldRef` struct, its `WithFieldRef` alias, and the `FieldGetter` and `MutFieldGetter` impls are in [crates/core/cgp-field/src/impls/use_ref.rs](../../../crates/core/cgp-field/src/impls/use_ref.rs). The `HasField` and `FieldGetter` traits are in [crates/core/cgp-field/src/traits/has_field.rs](../../../crates/core/cgp-field/src/traits/has_field.rs), and `HasFieldMut`/`MutFieldGetter` are in [crates/core/cgp-field/src/traits/has_field_mut.rs](../../../crates/core/cgp-field/src/traits/has_field_mut.rs). diff --git a/docs/reference/providers/use_fields.md b/docs/reference/providers/use_fields.md new file mode 100644 index 00000000..4b09d3d5 --- /dev/null +++ b/docs/reference/providers/use_fields.md @@ -0,0 +1,89 @@ +# `UseFields` + +`UseFields` is a zero-sized provider that implements a getter component by reading each getter method's value from the context field named after the method. + +## Purpose + +`UseFields` is the provider form of the convention "the method `name` reads the field `name`." A getter component defined with [`#[cgp_getter]`](../macros/cgp_getter.md) describes one or more values the context can supply, and the most common arrangement is that each value lives in a same-named field. `UseFields` is the provider that realizes that arrangement: wiring a getter component to `UseFields` makes every getter method read the context field whose name equals the method name, looked up through [`HasField`](../traits/has_field.md) keyed by a [`Symbol!`](../macros/symbol.md). + +This is the provider analogue of the blanket impl that [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md) emits. `#[cgp_auto_getter]` produces a single blanket implementation that fires automatically for any context whose field names match the method names; there is nothing to wire. `UseFields` packages the same field-by-method-name behavior as a provider that a `#[cgp_getter]` component can be wired to, so a getter trait that participates in CGP wiring can still opt into the auto-getter convention when its fields happen to line up with its methods. + +`UseFields` is distinct from its sibling [`UseField`](use_field.md), and the single-versus-plural naming marks the difference. `UseField` keys on a tag the wiring chooses, letting one method read a field of any name; `UseFields` takes no parameter and keys every method on its own name. Reach for `UseField` when the field name must differ from the method name, and for `UseFields` when the convention holds and you simply want the auto-getter behavior inside a wired component. + +Like every CGP provider, `UseFields` carries no runtime value. It is a unit struct used purely as a type-level marker, and its provider impls never read the `self` position. + +## Definition + +`UseFields` is a unit struct with no fields, defined in `cgp-component`: + +```rust +pub struct UseFields; +``` + +It takes no type parameter — the key for every method is fixed to that method's own name — so the struct is a bare marker. The provider impl that gives it meaning is generated by `#[cgp_getter]`, not written by hand. + +## Behavior + +`#[cgp_getter]` generates a `UseFields` impl of the getter's provider trait, reading each method's value from the field whose name matches the method, keyed by a `Symbol!`. For a single-method getter such as + +```rust +#[cgp_getter] +pub trait HasFoo { + fn foo(&self) -> &str; +} +``` + +the macro emits this `UseFields` impl for the generated `FooGetter` provider trait (shown with the macro's real placeholder identifiers, and with `Symbol!("foo")` left in sugared form): + +```rust +impl<__Context__> FooGetter<__Context__> for UseFields +where + __Context__: HasField, +{ + fn foo(__context__: &__Context__) -> &str { + __context__.get_field(PhantomData::).as_str() + } +} +``` + +Each method becomes a `HasField` bound keyed on the method name as a `Symbol!`, and the body reads that field. The same return-type shorthands the getter macros support apply here: the `&str` return makes the field `Value` a `String` and appends `.as_str()`, just as `#[cgp_auto_getter]` does. When a getter trait has several methods, the `UseFields` impl carries one `HasField` bound and one method body per getter, each keyed by its own method name. The impl is paired with a matching `IsProviderFor` impl carrying the same bounds, so the [check traits](../../concepts/check-traits.md) can report a missing field precisely. + +This is one of three provider impls `#[cgp_getter]` generates for a getter component, the other two being [`UseField`](use_field.md) for a wiring-chosen field name and [`WithProvider`](with_provider.md) for adapting a foundational field getter. A context picks among them at wiring time: `UseFields` when method and field names coincide, `UseField` when they differ. + +## Examples + +A context whose field name matches the getter method can be wired to `UseFields` to get the auto-getter convention inside a `#[cgp_getter]` component. The method `foo` and the field `foo` share a name: + +```rust +use cgp::prelude::*; + +#[cgp_getter] +pub trait HasFoo { + fn foo(&self) -> &str; +} + +#[derive(HasField)] +pub struct App { + pub foo: String, +} + +delegate_components! { + App { + FooGetterComponent: UseFields, + } +} + +fn describe(app: &App) -> &str { + app.foo() // reads the `foo` field +} +``` + +Because `App` wires `FooGetterComponent` to `UseFields`, the generated impl reads `App`'s `foo` field — the field whose name equals the method `foo` — to implement the getter. If the value were instead stored under a differently named field, this wiring would not apply, and the context would wire `UseField` with the actual field name instead. + +## Related constructs + +`UseFields` is generated by [`#[cgp_getter]`](../macros/cgp_getter.md) and is the provider analogue of the blanket impl emitted by [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md). It sits beside [`UseField`](use_field.md), which keys on a wiring-chosen tag instead of the method name, and [`WithProvider`](with_provider.md), the third getter provider `#[cgp_getter]` emits. The field reads it performs go through [`HasField`](../traits/has_field.md), keyed by [`Symbol!`](../macros/symbol.md) on the method name and produced by [`#[derive(HasField)]`](../derives/derive_has_field.md). A context selects `UseFields` through [`delegate_components!`](../macros/delegate_components.md) and verifies the wiring with [`check_components!`](../macros/check_components.md). + +## Source + +The struct is defined in [crates/core/cgp-component/src/providers/use_fields.rs](../../../crates/core/cgp-component/src/providers/use_fields.rs). The `UseFields` impl is built by `to_use_fields_impl` in [crates/macros/cgp-macro-core/src/types/cgp_getter/to_use_fields_impl.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_getter/to_use_fields_impl.rs), which keys each method on its name via `Symbol::from_ident` and emits a matching `HasField` bound. Expansion snapshots showing the generated `UseFields` impl alongside the `UseField` and `WithProvider` impls are in [crates/tests/cgp-tests/tests/getter_tests/string.rs](../../../crates/tests/cgp-tests/tests/getter_tests/string.rs). diff --git a/docs/reference/providers/use_type.md b/docs/reference/providers/use_type.md new file mode 100644 index 00000000..8af10ea1 --- /dev/null +++ b/docs/reference/providers/use_type.md @@ -0,0 +1,99 @@ +# `UseType` (provider) + +`UseType` is a zero-sized provider that supplies a concrete `Type` as the value of an abstract CGP type, letting a context bind an abstract associated type to a real type purely through wiring. + +This document describes the `UseType` **provider** — the struct `UseType(PhantomData)`. It is a different construct from the [`#[use_type]` attribute](../attributes/use_type.md), which is a modifier that rewrites bare type names inside `#[cgp_fn]`, `#[cgp_impl]`, and `#[cgp_component]` definitions and adds the owning trait as a bound. The attribute is about *referring to* an abstract type ergonomically; the provider here is about *choosing the concrete type* an abstract type resolves to. They share a name because both center on abstract types, but they live in different places and do different jobs — link between them, do not conflate them. + +## Purpose + +`UseType` removes the need to hand-write a provider impl every time a context wants to fix an abstract type to a concrete one. An abstract type in CGP is a trait with a single associated type, defined with [`#[cgp_type]`](../macros/cgp_type.md) — for example `trait HasScalarType { type Scalar; }`. Generic code refers to `Self::Scalar` without committing to any particular type, and a concrete context decides what `Scalar` actually is. Without `UseType`, making that decision would mean writing a bespoke provider whose only content is `type Scalar = f64;`, repeated for every abstract type and every concrete choice. + +`UseType` is the one provider that captures this trivial shape once and for all: it is a [`TypeProvider`](../components/has_type.md) that reports its type parameter `T` as the abstract type. Wiring a context's type component to `UseType` therefore sets that context's abstract type to `f64` with no custom impl. This is the type-level counterpart of how [`UseField`](use_field.md) lets a getter read an arbitrary field by name — a general-purpose provider parameterized by exactly the thing the context wants to supply. + +Because the provider carries no runtime data — it is a type-level marker, `PhantomData` and nothing more — `UseType` exists only to be named in a delegation table. It is never constructed as a value during normal use. + +## Definition + +`UseType` is a phantom-typed struct parameterized by the concrete type it supplies, defined in `cgp-type`: + +```rust +pub struct UseType(pub PhantomData); + +pub type WithType = WithProvider>; +``` + +The `WithType` alias wraps `UseType` in [`WithProvider`](with_provider.md), the adapter that lets a generic `TypeProvider` stand in as the provider for a specific `#[cgp_type]` component. A `#[cgp_type]` macro generates a `WithProvider` impl for its component, so wiring with either `UseType` (via the component's generated `UseType` impl) or `WithType` (via `WithProvider`) sets the abstract type to `T`. + +## Behavior + +`UseType` implements the built-in provider trait [`TypeProvider`](../components/has_type.md) for every context and tag, setting its associated `Type` to the struct's own type parameter: + +```rust +#[cgp_provider(TypeProviderComponent)] +impl TypeProvider for UseType { + type Type = Type; +} +``` + +The impl is unconditional in `Context` and `Tag` — `UseType` is a `TypeProvider` whose `Type` is `f64` regardless of which context or which type tag asks. `HasType` is the consumer trait that reads this; `TypeProvider` is its provider trait. So once a context's `TypeProviderComponent` is wired to `UseType`, the context implements `HasType` with `Type = f64`, and `TypeOf` resolves to `f64`. + +This same provider is what [`#[cgp_type]`](../macros/cgp_type.md) targets. When you write `#[cgp_type] trait HasScalarType { type Scalar; }`, the macro generates a `UseType` impl for the component's provider trait: + +```rust +impl ScalarTypeProvider<__Context__> for UseType { + type Scalar = Scalar; +} +``` + +so that wiring `ScalarTypeProviderComponent` to `UseType` sets `Scalar = f64` on the context. The built-in `TypeProvider` impl shown above and the per-component impl generated by `#[cgp_type]` are the two faces of the same `UseType` struct: the first makes it a provider for the built-in `HasType` component, the second makes it a provider for a user-defined abstract-type component. A bound on the associated type (such as `type Scalar: Copy`) is copied into the generated impl's `where` clause, so the concrete type must satisfy it at the wiring site. + +## Examples + +A complete use defines an abstract type, wires a concrete type with `UseType`, and reads it back through `HasScalarType`: + +```rust +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar: Copy; +} + +pub struct App; + +delegate_components! { + App { + ScalarTypeProviderComponent: UseType, + } +} + +fn zero() -> Context::Scalar +where + Context: HasScalarType, + Context::Scalar: Default, +{ + Default::default() +} +``` + +`App` wires `ScalarTypeProviderComponent` to `UseType`, so the generated `UseType` impl makes `App` implement `HasScalarType` with `Scalar = f64`. The `Copy` bound on the associated type is enforced against `f64` where the wiring is written. + +The same binding can be expressed with the `WithType` alias, which routes through `WithProvider` instead of the component's own `UseType` impl: + +```rust +delegate_components! { + App { + ScalarTypeProviderComponent: WithType, + } +} +``` + +Both forms produce the same result — `App::Scalar` is `f64` — which is why `UseType` is the idiomatic way to bind an abstract type without writing a provider by hand. + +## Related constructs + +`UseType` is the provider that [`#[cgp_type]`](../macros/cgp_type.md) generates an impl for, so the two are almost always seen together: `#[cgp_type]` defines the abstract type and `UseType` supplies the concrete one at wiring time. It implements the built-in [`HasType` / `TypeProvider`](../components/has_type.md) component, the foundation on which all abstract types rest. Its `WithType` alias is one of the named wrappers around [`WithProvider`](with_provider.md). It is the type-level analogue of the [`UseField`](use_field.md) provider that [`#[cgp_getter]`](../macros/cgp_getter.md) generates, and of [`UseDelegate`](use_delegate.md) for behavioral components. For resolving an abstract type through a lookup table rather than a fixed type, see [`UseDelegatedType`](use_delegated_type.md). Do not confuse this provider with the similarly named [`#[use_type]` attribute](../attributes/use_type.md), which imports and rewrites abstract type names in definitions. + +## Source + +The `UseType` struct, its `WithType` alias, and the built-in `TypeProvider` impl are in [crates/core/cgp-type/src/impls/use_type.rs](../../../crates/core/cgp-type/src/impls/use_type.rs). The `HasType` consumer trait, the `TypeProvider` provider trait, and the `TypeOf` alias are in [crates/core/cgp-type/src/traits/has_type.rs](../../../crates/core/cgp-type/src/traits/has_type.rs). The `#[cgp_type]`-generated `UseType` impl is built in [crates/macros/cgp-macro-core/src/types/cgp_type/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_type/item.rs). Behavioral and expansion tests live in [crates/tests/cgp-tests/tests/component_tests/abstract_types/](../../../crates/tests/cgp-tests/tests/component_tests/abstract_types/). diff --git a/docs/reference/providers/with_provider.md b/docs/reference/providers/with_provider.md new file mode 100644 index 00000000..ea1b546e --- /dev/null +++ b/docs/reference/providers/with_provider.md @@ -0,0 +1,96 @@ +# `WithProvider` + +`WithProvider` is a zero-sized adapter provider that turns a foundational provider — one implementing `TypeProvider` or `FieldGetter` — into a provider of a specific CGP component. + +## Purpose + +`WithProvider` bridges the gap between CGP's two layers of provider trait. Foundational traits like [`TypeProvider`](../components/has_type.md) and [`FieldGetter`](../traits/has_field.md) are generic, component-agnostic mechanisms: a `TypeProvider` supplies *some* abstract type for *some* tag, and a `FieldGetter` reads *some* field for *some* output tag, without either knowing which named component it serves. A CGP component, by contrast, has a specific provider trait — `NameTypeProvider`, `NameGetter` — that a context wires to. `WithProvider` is the adapter that lets a foundational provider stand in as the provider for one of these named components: it implements the component's provider trait by forwarding to the foundational provider's method. + +This adapter is what lets the foundational layer be wired without each foundational provider having to implement every component trait by hand. A field getter implemented once as a `FieldGetter` can serve any number of getter components through `WithProvider`; an abstract-type implementation written once as a `TypeProvider` can serve any type component the same way. The component-specific glue is generated, and `WithProvider` is the type that carries it. + +`WithProvider` is rarely written in full by users, because its common uses are packaged as aliases. The family `WithContext`, `WithType`, `WithField`, `WithFieldRef`, and `WithDelegatedType` are all `WithProvider<...>` specialized to a particular inner provider, and these aliases are what appears in everyday wiring. Understanding `WithProvider` is what explains why those aliases work. + +Like every CGP provider, `WithProvider` carries no runtime value. The `Provider` type parameter is held in `PhantomData`, and the struct exists only as a type-level marker naming the foundational provider to adapt. + +## Definition + +`WithProvider` is a struct parameterized by the inner provider, defined in `cgp-component`: + +```rust +pub struct WithProvider(pub PhantomData); +``` + +The single type parameter `Provider` is the foundational provider being adapted — typically a [`TypeProvider`](../components/has_type.md) or [`FieldGetter`](../traits/has_field.md). The `PhantomData` makes `Provider` a parameter of a valueless struct; nothing of `Provider` is ever constructed. + +## Behavior + +`#[cgp_type]` and `#[cgp_getter]` generate a `WithProvider` impl that forwards a component's provider-trait method to the inner provider's foundational method. For a type component such as + +```rust +#[cgp_type] +pub trait HasNameType { + type Name; +} +``` + +`#[cgp_type]` emits a `WithProvider` impl that defines the component's associated type from the inner `TypeProvider` (shown with the macro's real placeholder identifiers): + +```rust +impl<__Provider__, Name, __Context__> NameTypeProvider<__Context__> for WithProvider<__Provider__> +where + __Provider__: TypeProvider<__Context__, NameTypeProviderComponent, Type = Name>, +{ + type Name = Name; +} +``` + +For a single-method getter, `#[cgp_getter]` emits an analogous `WithProvider` impl that reads the value through the inner `FieldGetter`: + +```rust +impl<__Context__, __Provider__> NameGetter<__Context__> for WithProvider<__Provider__> +where + __Provider__: FieldGetter<__Context__, NameGetterComponent, Value = String>, +{ + fn name(__context__: &__Context__) -> &str { + __Provider__::get_field(__context__, PhantomData::).as_str() + } +} +``` + +In both cases the bound names the foundational trait — `TypeProvider` or `FieldGetter` — keyed by the component-name struct, and the method or associated type forwards to it. `#[cgp_getter]` generates the `WithProvider` impl only when the getter trait has exactly one method, since a single foundational getter cannot serve several methods at once. Each impl is paired with a matching `IsProviderFor` impl so dependencies reach the [check traits](../../concepts/check-traits.md). + +The aliases specialize `WithProvider` to a fixed inner provider so the common cases need no `WithProvider<...>` spelled out. `WithContext = WithProvider` adapts the context's own consumer-trait implementation; `WithType = WithProvider>` and `WithField = WithProvider>` adapt the foundational type and field providers; `WithFieldRef = WithProvider>` adapts a getter that returns a reference borrowed through `AsRef`; and `WithDelegatedType = WithProvider>` adapts a type provider that looks its type up in a delegation table. `WithContext` lives in `cgp-component` beside `WithProvider`, while the `WithType`/`WithDelegatedType` pair is defined in `cgp-type` and the `WithField`/`WithFieldRef` pair in `cgp-field`, each next to the inner provider it wraps. + +## Examples + +The everyday way to use `WithProvider` is through one of its aliases, which read as a single wiring choice. Adapting the context's own field getter into a getter component uses `WithField`: + +```rust +use cgp::prelude::*; + +#[cgp_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[derive(HasField)] +pub struct Person { + pub first_name: String, +} + +delegate_components! { + Person { + NameGetterComponent: WithField, + } +} +``` + +`WithField` expands to `WithProvider>`, so `Person`'s `NameGetter` provider is the adapter wrapping the foundational `UseField` getter for the `first_name` field. The generated `WithProvider` impl forwards `name()` to `UseField`'s `FieldGetter::get_field`, which reads `first_name`. The same shape recurs for abstract types: wiring a type component to `WithType` adapts `UseType`, and to `WithDelegatedType` adapts a `UseDelegatedType` that resolves the type through a table. + +## Related constructs + +`WithProvider`'s impls are generated by [`#[cgp_type]`](../macros/cgp_type.md) and [`#[cgp_getter]`](../macros/cgp_getter.md), adapting the foundational [`TypeProvider`](../components/has_type.md) and [`FieldGetter`](../traits/has_field.md) traits into component providers. Its alias family wraps the foundational providers documented separately: [`UseContext`](use_context.md) via `WithContext`, [`UseType`](use_type.md) via `WithType`, [`UseField`](use_field.md) via `WithField`, [`UseFieldRef`](use_field_ref.md) via `WithFieldRef`, and [`UseDelegatedType`](use_delegated_type.md) via `WithDelegatedType`. Aliases are wired with [`delegate_components!`](../macros/delegate_components.md), and the dependency propagation that makes them checkable flows through [`IsProviderFor`](../traits/is_provider_for.md). + +## Source + +The struct is defined in [crates/core/cgp-component/src/providers/with_provider.rs](../../../crates/core/cgp-component/src/providers/with_provider.rs), and the `WithContext` alias in [crates/core/cgp-component/src/providers/use_context.rs](../../../crates/core/cgp-component/src/providers/use_context.rs). The remaining aliases are defined beside their inner providers: `WithType` and `WithDelegatedType` in [crates/core/cgp-type/src/impls/use_type.rs](../../../crates/core/cgp-type/src/impls/use_type.rs) and [use_delegated_type.rs](../../../crates/core/cgp-type/src/impls/use_delegated_type.rs), and `WithField` and `WithFieldRef` in [crates/core/cgp-field/src/impls/use_field.rs](../../../crates/core/cgp-field/src/impls/use_field.rs) and [use_ref.rs](../../../crates/core/cgp-field/src/impls/use_ref.rs). The component `WithProvider` impls are generated by `#[cgp_type]` in [crates/macros/cgp-macro-core/src/types/cgp_type/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_type/item.rs) and by `#[cgp_getter]` in [crates/macros/cgp-macro-core/src/types/cgp_getter/with_provider.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_getter/with_provider.rs). The generated getter `WithProvider` impl is shown in the expansion snapshot in [crates/tests/cgp-tests/tests/getter_tests/string.rs](../../../crates/tests/cgp-tests/tests/getter_tests/string.rs). diff --git a/docs/reference/traits/can_use_component.md b/docs/reference/traits/can_use_component.md new file mode 100644 index 00000000..c38a3b22 --- /dev/null +++ b/docs/reference/traits/can_use_component.md @@ -0,0 +1,98 @@ +# `CanUseComponent` + +`CanUseComponent` is the context-side check trait that holds whenever a context both delegates a component and its chosen provider is a valid provider for it, giving `check_components!` a single bound to assert that forces readable wiring errors. + +## Purpose + +`CanUseComponent` exists to answer one question about a context: "can this context actually use this component, with every dependency satisfied?" That question is deliberately not the same as "does the context implement the consumer trait?". Asking the consumer-trait question directly makes the compiler report the outermost unmet bound — usually a bare "the provider does not implement the provider trait" — and hide the indirect reasoning behind it, because the provider blanket impl is a competing candidate that suppresses detailed diagnostics. The root cause, often a single missing getter or abstract type, never appears. + +`CanUseComponent` reframes the question along a path Rust will explain. It is satisfied only when two things hold together: the context delegates the component (it has a [`DelegateComponent`](delegate_component.md) entry for it), and the delegated provider satisfies [`IsProviderFor`](is_provider_for.md) for that exact context and parameter set. Because `IsProviderFor` carries the provider's real `where` bounds, requiring it forces the compiler to evaluate and report those bounds. The detailed error that was hidden behind the consumer-trait question becomes visible once the same wiring is probed through `CanUseComponent` instead. + +The trait is mechanism, not API. Application code never names it; it is the bound that [`check_components!`](../macros/check_components.md) emits so that a wiring mistake is reported at the wiring site, pinpointing the absent dependency, rather than erupting at some distant call to the consumer trait. + +## Definition + +`CanUseComponent` is an empty marker trait parameterized by a component and a parameter tuple, with a single blanket impl supplying all of its instances: + +```rust +pub trait CanUseComponent {} + +impl CanUseComponent for Context +where + Context: DelegateComponent, + Context::Delegate: IsProviderFor, +{ +} +``` + +`Self` is the context being checked. `Component` is the component-name type, the same key used in the delegation table, and `Params` collects the component's extra generic parameters — a single parameter directly, several as a tuple, and the unit tuple `()` (the default) when there are none. The trait has no methods and no associated items; its meaning lives entirely in the blanket impl's `where` clause. + +## Behavior + +The blanket impl is the whole behavior, and its two bounds are the two halves of "can use". The first, `Context: DelegateComponent`, requires the context to have a table entry for the component — without a delegation there is nothing to use, and the lookup fails with the `DelegateComponent` diagnostic about a missing entry. The second, `Context::Delegate: IsProviderFor`, requires the delegated provider to be a genuine provider for this component, for this context, at these parameters. This is where dependency checking happens: `IsProviderFor` carries the provider's `where` bounds, so the compiler must satisfy them to grant `CanUseComponent`, and an unmet bound is reported in full. + +`CanUseComponent` is mirror to [`IsProviderFor`](is_provider_for.md): it asks the same readability-restoring question, but indexed on the context (`Self = Context`) rather than the provider (`Self = Provider`). One starts from "the context delegates and the delegate is valid"; the other from "this specific provider is valid". `check_components!` uses `CanUseComponent` for its default, context-oriented checks and switches to `IsProviderFor` for its `#[check_providers(...)]` form, when a particular provider must be verified independently. + +Routing a check through `CanUseComponent` rather than the consumer trait is what makes errors legible. Because the blanket impl threads the dependency bounds through `IsProviderFor`, an unsatisfied transitive requirement is surfaced and attributed to the actual missing piece. The trait is empty and the impl is unconditional in form, so satisfying it costs nothing at runtime; the only effect is the compile-time obligation its `where` clause imposes. + +The two bounds also distinguish the two ways wiring can be wrong, and the diagnostics differ accordingly. A context that never delegated the component fails the first bound, and the `DelegateComponent` `on_unimplemented` note reports a missing table entry — the fix is to add the wiring. A context that delegated the component to a provider whose dependencies are unmet passes the first bound but fails the second, and the error is the provider's own unsatisfied `where` clause, carried up through `IsProviderFor` — the fix is to supply the missing dependency. Reading which bound failed tells a reader whether the wiring is absent or merely incomplete. + +`check_components!` consumes the trait by emitting a private check trait whose supertrait is `CanUseComponent` and then one empty impl of that trait per checked entry; each impl compiles only if its `CanUseComponent` supertrait holds, so the entire table is an assertion that every listed component is usable. Because the assertion is a compile-time construct that produces no values, a successful build is the passing test and the macro adds nothing to the binary. + +## Examples + +`CanUseComponent` is asserted, never called. A `check_components!` table reduces to one impl of an internal check trait whose supertrait is `CanUseComponent`, so the impl compiles only if the bound holds: + +```rust +use cgp::prelude::*; + +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasName, +{ + fn greet(&self) { + println!("Hello, {}!", self.name()); + } +} + +#[derive(HasField)] +pub struct Person { + pub first_name: String, // not `name` +} + +delegate_components! { + Person { GreeterComponent: GreetHello } +} + +check_components! { + Person { GreeterComponent } +} +``` + +The `check_components!` table forces the assertion `Person: CanUseComponent`. Resolving it requires `Person: DelegateComponent` (satisfied by the wiring) and `GreetHello: IsProviderFor` (not satisfied, because `GreetHello` needs `HasName` and `Person` has no `name` field). The compiler reports the absent `name` field at the check, rather than at a future `person.greet()` call. Writing the same bound directly probes the wiring without the macro: + +```rust +fn assert_wiring() +where + Person: CanUseComponent, +{} +``` + +## Related constructs + +`CanUseComponent` is the bound that [`check_components!`](../macros/check_components.md) asserts to verify a context's wiring, and the macro's whole purpose is to route checking through it for readable errors. Its blanket impl is built directly on [`DelegateComponent`](delegate_component.md) — the context must delegate the component — and [`IsProviderFor`](is_provider_for.md) — the delegate must be a valid provider — so it ties the two wiring traits together into a single context-side question. It is the context-indexed counterpart of `IsProviderFor`, which `check_components!`'s `#[check_providers(...)]` form asserts on providers directly when a higher-order provider stack needs each layer checked. The components it checks are defined by [`#[cgp_component]`](../macros/cgp_component.md) and wired by [`delegate_components!`](../macros/delegate_components.md). + +## Source + +The trait and its sole blanket impl are defined in [crates/core/cgp-component/src/traits/can_use_component.rs](../../../crates/core/cgp-component/src/traits/can_use_component.rs) and re-exported through [crates/core/cgp-component/src/macro_prelude.rs](../../../crates/core/cgp-component/src/macro_prelude.rs). The checks that assert it are generated by `check_components!`, whose codegen lives in [crates/macros/cgp-macro-core/src/types/check_components/](../../../crates/macros/cgp-macro-core/src/types/check_components/) — `table.rs` chooses `CanUseComponent` versus `IsProviderFor` as the check trait's supertrait. Expansion snapshots are in [crates/tests/cgp-tests/src/tests/check_components.rs](../../../crates/tests/cgp-tests/src/tests/check_components.rs) and [crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/default_impls.rs](../../../crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/default_impls.rs). diff --git a/docs/reference/traits/cast.md b/docs/reference/traits/cast.md new file mode 100644 index 00000000..ee89b48b --- /dev/null +++ b/docs/reference/traits/cast.md @@ -0,0 +1,108 @@ +# Casting: `CanUpcast`, `CanDowncast`, `CanDowncastFields`, `CanBuildFrom` + +`CanUpcast`, `CanDowncast`, `CanDowncastFields`, and `CanBuildFrom` are the structural conversion traits that move a value between two CGP data types by their shared fields or variants — widening an enum, narrowing it, or assembling a struct from the fields of others — entirely by matching type-level names. + +## Purpose + +These traits exist so that two types sharing a subset of named fields or variants can be converted into one another generically, without any hand-written `From`/`TryFrom` impl. CGP represents every record as a product of named fields and every enum as a sum of named variants (via [`#[derive(HasFields)]`](../derives/derive_has_fields.md)), and once a type's shape is a type-level list, conversion becomes a matter of routing each named entry to the matching slot in the target. The four traits cover the directions that routing can take: enlarging a sum, shrinking a sum, and growing a record out of smaller records. + +Upcast and downcast are the two directions of variant conversion. An *upcast* takes a value of a "narrow" enum whose variants are a subset of a "wide" enum's, and lifts it into the wide enum — always succeeds, because every variant of the source has a home in the target. A *downcast* goes the other way: it tries to narrow a wide enum into a smaller one, succeeding only if the value's current variant exists in the target, and otherwise handing back a remainder so the caller can keep trying other targets. `CanBuildFrom` is the record counterpart: it assembles a target by copying the fields it shares with a source, leaving the rest of the target to be filled in separately. + +## Definition + +`CanUpcast` consumes a value and produces the wider `Target` directly, since an upcast cannot fail. The `PhantomData` argument only names the target type for inference: + +```rust +pub trait CanUpcast { + fn upcast(self, _tag: PhantomData) -> Target; +} +``` + +`CanDowncast` is fallible. It returns `Result`, where the `Remainder` is the source's extractor with the attempted variants removed — what is left to try if this narrowing did not match: + +```rust +pub trait CanDowncast { + type Remainder; + + fn downcast(self, _tag: PhantomData) -> Result; +} +``` + +`CanDowncastFields` is the same operation one level down, implemented directly on an extractor (a partial-sum type) rather than on the original enum. It is what you call on a `Remainder` returned by a previous `downcast` to attempt the next target without re-wrapping: + +```rust +pub trait CanDowncastFields { + type Remainder; + + fn downcast_fields(self, _tag: PhantomData) -> Result; +} +``` + +`CanBuildFrom` assembles a target builder by drawing fields from a source. It is implemented for a builder and consumes a `Source`, moving every field the two share out of the source and into the builder, returning the updated builder as `Output`: + +```rust +pub trait CanBuildFrom { + type Output; + + fn build_from(self, source: Source) -> Self::Output; +} +``` + +## Behavior + +The variant conversions are driven by a shared recursion over the target's sum of variants. `CanUpcast` is implemented for any context that has both a [`HasFields`](has_fields.md) shape and a [`HasExtractor`](extract_field.md): it turns the source into its extractor, then walks the source's own field list, extracting each variant from the extractor and reconstructing it into the target via [`FromVariant`](from_variant.md). Because every source variant is guaranteed to exist in a wider target, the walk is total and the remaining extractor is uninhabited, finalized away with [`FinalizeExtract`](extract_field.md). The result is the source value re-tagged as the target enum. + +`CanDowncast` and `CanDowncastFields` recurse over the *target's* variants instead. For each `Field` in `Target::Fields`, the implementation uses [`ExtractField`](extract_field.md) to try pulling that variant out of the source extractor: on success it rebuilds the target with `FromVariant` and returns `Ok`; on failure it threads the shrunken remainder into the next variant's attempt. If no target variant matches, the terminal `Void` impl returns the whole remainder as `Err`. The difference between the two traits is only the starting point — `CanDowncast` first calls `to_extractor` on the original enum, while `CanDowncastFields` operates on an extractor it is already given, which is exactly the `Remainder` from a prior `downcast`. This is why downcasting against several candidate targets in turn chains a `downcast` followed by `downcast_fields` calls on each remainder. + +`CanBuildFrom` recurses over the source's field product. For each `Field` the source exposes, it uses [`TakeField`](has_builder.md) to remove that field's value from the source and [`BuildField`](has_builder.md) to write it into the target builder, threading both the shrinking source and the growing builder through the recursion. When the source's fields are exhausted, the target builder is returned. The target need not be complete after one `build_from`: a builder can absorb fields from several sources in sequence, and only then be finalized. + +## Examples + +Upcasting and downcasting let independently-defined enums interconvert by their common variants. Given a wide enum and a narrow one that share variant names, conversion is name-driven and needs no manual impl: + +```rust +use cgp::prelude::*; +use core::marker::PhantomData; + +#[derive(Debug, Eq, PartialEq, CgpData)] +pub enum FooBar { + Foo(u64), + Bar(String), +} + +#[derive(Debug, Eq, PartialEq, CgpData)] +pub enum FooBarBaz { + Foo(u64), + Bar(String), + Baz(bool), +} + +// upcast: always succeeds, every FooBar variant exists in FooBarBaz +let wide = FooBar::Foo(1).upcast(PhantomData::); +assert_eq!(wide, FooBarBaz::Foo(1)); + +// downcast: succeeds for shared variants, fails for Baz +assert_eq!(FooBarBaz::Bar("hi".into()).downcast(PhantomData::).ok(), Some(FooBar::Bar("hi".into()))); +assert_eq!(FooBarBaz::Baz(true).downcast(PhantomData::).ok(), None); +``` + +`CanBuildFrom` assembles one struct from several smaller ones by copying their fields into a builder before finalizing: + +```rust +#[derive(CgpData)] pub struct FooBar { pub foo: u64, pub bar: String } +#[derive(CgpData)] pub struct Baz { pub baz: bool } +#[derive(CgpData)] pub struct FooBarBaz { pub foo: u64, pub bar: String, pub baz: bool } + +let combined: FooBarBaz = FooBarBaz::builder() + .build_from(FooBar { foo: 1, bar: "bar".into() }) + .build_from(Baz { baz: true }) + .finalize_build(); +``` + +## Related constructs + +The casting traits sit on top of the extensible-data primitives: variant casts route through [`ExtractField`](extract_field.md), [`FromVariant`](from_variant.md), and [`FinalizeExtract`](extract_field.md) over the [`Either`](../types/either.md)/`Void` sum spine, while `CanBuildFrom` routes through [`HasBuilder`](has_builder.md)'s `TakeField`/`BuildField` over the [`Cons`](../types/cons.md)/`Nil` product spine. All four depend on the type's [`HasFields`](has_fields.md) shape, which is what [`#[derive(CgpData)]`](../derives/derive_cgp_data.md) generates along with the builders and extractors these conversions consume. The names matched during casting are the [`Symbol!`](../macros/symbol.md) tags on each field or variant. The conceptual overviews that frame these conversions are [extensible records](../../concepts/extensible-records.md) (for `CanBuildFrom`) and [extensible variants](../../concepts/extensible-variants.md) (for upcasting and downcasting); `build_from` is used to assemble a context in the [application builder](../../examples/application-builder.md) example, `CanUpcast` to construct partial enums in the [expression interpreter](../../examples/expression-interpreter.md) example, and both `upcast` and `downcast` to convert between sibling shape enums in the [extensible shapes](../../examples/extensible-shapes.md) example. + +## Source + +`CanUpcast`, `CanDowncast`, and `CanDowncastFields`, together with the internal `FieldsExtractor` recursion that drives them, are defined in [crates/core/cgp-field/src/impls/cast.rs](../../../crates/core/cgp-field/src/impls/cast.rs). `CanBuildFrom` and its internal `FieldsBuilder` recursion are in [crates/core/cgp-field/src/impls/build_from.rs](../../../crates/core/cgp-field/src/impls/build_from.rs). The underlying extractor and builder traits are under [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/) (`extract_field.rs`, `from_variant.rs`, `has_builder.rs`). End-to-end tests of upcasting, downcasting, and building-from live in the extensible-data suites under [crates/tests/cgp-tests/tests/extensible_data_tests/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/). diff --git a/docs/reference/traits/default_namespace.md b/docs/reference/traits/default_namespace.md new file mode 100644 index 00000000..b7084260 --- /dev/null +++ b/docs/reference/traits/default_namespace.md @@ -0,0 +1,101 @@ +# `DefaultNamespace`, `DefaultImpls1`, `DefaultImpls2` + +`DefaultNamespace`, `DefaultImpls1`, and `DefaultImpls2` are the hierarchical lookup traits that back namespaces and presets, each mapping a key to a `Delegate` type so that a context can inherit a whole group of default wirings and resolve them by component name, by one type parameter, or by two. + +## Purpose + +This family exists to give namespaces and presets a uniform, per-key lookup surface. A namespace is a reusable table of default wirings that a context can opt into and then selectively override; resolving such a default means asking, "for this key, what does the namespace delegate to?" The three traits are the answer-bearers, differing only in how many type parameters take part in the key. `DefaultNamespace` keys a default purely on the component name. `DefaultImpls1` keys it on the component name *and* one further type — the typical shape for a per-type default, where the same component resolves differently for `String` than for `u64`. `DefaultImpls2` does the same for two further types, for components parameterized by a pair. + +The reason to have all three rather than one variadic trait is that each fixes the arity of the key at the type level, which lets the projection `>` resolve cleanly. A context that joins a namespace forwards its lookups into one of these traits, and a `for … in` loop that pulls per-type defaults reads them by projecting the `Delegate`. The whole mechanism is type-level and inheritance-with-override in spirit: a directly-wired entry on a context wins over a namespace fallback, exactly as a preset is meant to be customizable. + +These traits are the plumbing beneath the [`#[cgp_namespace]`](../macros/cgp_namespace.md) macro and the `namespace` / `for … in` syntax of [`delegate_components!`](../macros/delegate_components.md). A user writing namespaces names them only in the namespace header and in the `for … in` loop target; the macros generate the impls and the forwarding. + +## Definition + +All three are key-value lookup traits carrying a single `Delegate` associated type, differing only in how many lookup-type parameters precede the `Components` table parameter: + +```rust +pub trait DefaultNamespace { + type Delegate; +} + +pub trait DefaultImpls1 { + type Delegate; +} + +pub trait DefaultImpls2 { + type Delegate; +} +``` + +`Self` is the key being looked up — a component-name type for `DefaultNamespace`, and the component-name type for the `DefaultImpls` variants too, with the per-instance types carried as the leading parameters. `Components` is the table the lookup is performed against, threaded through so that the same key can resolve differently depending on which context's table is consulted. The leading `T`, or `T1`/`T2`, are the instance types a per-type or per-pair default is keyed on. `Delegate` is the resolved value: the provider (or further redirect) the key maps to. As with [`DelegateComponent`](delegate_component.md), there is no method and no data; resolution is the projection of `Delegate` from the matching impl. + +## Behavior + +Each trait is implemented once per default entry, and resolving a default is reading `Delegate` from the matching impl. `DefaultNamespace` is implemented for a component-name key when a namespace supplies a default for that component regardless of any type parameter; the [`#[prefix(...)]`](../macros/cgp_namespace.md) attribute that attaches a component to a namespace emits exactly such an impl, with `Delegate` a [`RedirectLookup`](../providers/redirect_lookup.md) that re-routes the lookup along a path. `DefaultImpls1` is implemented for a component-name key carrying one instance type `T`, so the same component resolves per type; the [`#[default_impl(T in DefaultImpls1)]`](../macros/cgp_namespace.md) attribute on a provider impl registers the provider as the default for that `T`, emitting `impl DefaultImpls1 for T { type Delegate = Provider; }`. `DefaultImpls2` does the same with two instance types for a pair-parameterized component. + +The hierarchical part is how a context consumes these defaults, which the [`delegate_components!`](../macros/delegate_components.md) `namespace` header and `for … in` syntax generate. A `namespace N;` header emits a blanket [`DelegateComponent`](delegate_component.md) impl on the context that forwards every key through `N`: `impl DelegateComponent for App where Key: N { type Delegate = Value; }`, paired with the matching [`IsProviderFor`](is_provider_for.md) forwarding so dependencies stay diagnosable. A `for in DefaultImpls1 { … }` loop emits a `DelegateComponent` impl keyed on a path whose `where` clause projects the default: `where T: DefaultImpls1`. Reading the loop: for each type `T` that has a `DefaultImpls1` default, wire that path to the projected `Provider`. The same loop works against a `DefaultNamespace`-style table or any namespace trait by changing the `in` target. + +Inheritance and override compose on top. A namespace that inherits from a parent (`new Child: DefaultNamespace { … }`) emits a blanket impl forwarding any key the parent resolves to the child, so the child resolves everything the parent does plus its own entries. A context's directly-wired entry resolves before the namespace fallback, so it shadows the inherited default for that key without disturbing the rest — the inheritance-with-override pattern presets rely on, expressed entirely through these projections with no runtime cost. + +## Examples + +A per-type default registered with `#[default_impl]` and then pulled into a context shows the chain. A provider declares itself the default for one type: + +```rust +use cgp::prelude::*; +use core::fmt::Display; + +#[cgp_component(ShowImpl)] +#[prefix(@test in DefaultNamespace)] +pub trait Show { + fn show(&self, value: &T) -> String; +} + +#[cgp_impl(new ShowString)] +#[default_impl(String in DefaultImpls1)] +impl ShowImpl { + fn show(&self, value: &String) -> String { + value.clone() + } +} +``` + +The `#[default_impl]` attribute emits `impl DefaultImpls1 for String { type Delegate = ShowString; }`, registering `ShowString` as the per-type default for `String`. A context then joins the namespace and pulls those defaults in with a `for … in` loop, optionally overriding one entry: + +```rust +pub struct App; + +delegate_components! { + App { + namespace DefaultNamespace; + + for in DefaultImpls1 { + @test.ShowImplComponent.T: Provider, + } + + @test.ShowImplComponent.u64: + ShowWithDisplay, // overrides the inherited default for u64 + } +} +``` + +The `namespace DefaultNamespace;` line forwards `App`'s lookups through `DefaultNamespace`, and the loop wires each `T` by projecting `T: DefaultImpls1`. The direct `u64` line shadows whatever the namespace would otherwise supply for that type. A namespace can also be defined wholesale and used as the loop target: + +```rust +cgp_namespace! { + new DefaultShowComponents { + [String, u64]: ShowWithDisplay, + } +} +``` + +Pointing a `for in DefaultShowComponents { … }` loop at this namespace wires the listed types to `ShowWithDisplay` through the same projection mechanism. + +## Related constructs + +`DefaultNamespace`, `DefaultImpls1`, and `DefaultImpls2` are the lookup traits the [`#[cgp_namespace]`](../macros/cgp_namespace.md) macro builds on, and they are consumed by the `namespace` header and `for … in` loop of [`delegate_components!`](../macros/delegate_components.md). Their `Delegate` entries are commonly a [`RedirectLookup`](../providers/redirect_lookup.md), which re-routes a lookup along a type-level path rather than naming a provider outright. A context's `namespace` header forwards through these traits into a blanket [`DelegateComponent`](delegate_component.md) impl, with the matching [`IsProviderFor`](is_provider_for.md) forwarding so dependency errors stay readable. For the broader picture of how namespaces and presets fit together, see [namespaces](../../concepts/namespaces.md). + +## Source + +The three traits are defined in [crates/core/cgp-component/src/namespaces.rs](../../../crates/core/cgp-component/src/namespaces.rs), with `DefaultNamespace` re-exported through [crates/core/cgp-component/src/macro_prelude.rs](../../../crates/core/cgp-component/src/macro_prelude.rs). The namespace macro that builds the namespace trait and its inheritance impl lives in [crates/macros/cgp-macro-core/src/types/namespace/](../../../crates/macros/cgp-macro-core/src/types/namespace/); the `#[default_impl(... in DefaultImpls1<...>)]` attribute that registers a per-type default is parsed and lowered in [crates/macros/cgp-macro-core/src/types/attributes/default_impl/](../../../crates/macros/cgp-macro-core/src/types/attributes/default_impl/). The `namespace` header and `for … in` loop are handled by the `delegate_components!` codegen in [crates/macros/cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/). Expansion snapshots covering `DefaultNamespace`, `DefaultImpls1`, the `for … in` loop, and namespace inheritance are in [crates/tests/cgp-tests/src/namespaces/default_impls.rs](../../../crates/tests/cgp-tests/src/namespaces/default_impls.rs), [crates/tests/cgp-tests/src/namespaces/extended.rs](../../../crates/tests/cgp-tests/src/namespaces/extended.rs), and [crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/default_impls.rs](../../../crates/tests/cgp-tests/tests/namespace_tests/namespace_macro/default_impls.rs). diff --git a/docs/reference/traits/delegate_component.md b/docs/reference/traits/delegate_component.md new file mode 100644 index 00000000..6a0aa726 --- /dev/null +++ b/docs/reference/traits/delegate_component.md @@ -0,0 +1,111 @@ +# `DelegateComponent` + +`DelegateComponent` is the core wiring trait that turns a type into a compile-time key→value table, mapping each `Key` to a single `Delegate` type so that component lookups can resolve to the provider that handles them. + +## Purpose + +`DelegateComponent` exists to record, at the type level, which provider a context has chosen for a given component. A CGP component splits the consumer trait callers use from the provider trait implementers write, but that split leaves one question open: for this context, *which* provider supplies the behavior? `DelegateComponent` answers it by being the single trait whose implementation stores that decision — one impl per entry, the component name as the key and the chosen provider as the value. + +The mental model is a type-level key-value map carried on the `Self` type. Implementing `DelegateComponent` for a type is "setting" the table's entry at `Key` to the associated `Delegate`; naming `Self: DelegateComponent` in a bound and reading `Self::Delegate` is "getting" the value back out. Because both the keys and the values are types, the entire lookup happens during type-checking and compiles down to nothing at runtime. This is the table that the provider blanket impl generated by [`#[cgp_component]`](../macros/cgp_component.md) consults to route a consumer call to a concrete provider. + +The reason this trait is the foundation rather than an internal detail is that the same table shape serves two distinct jobs. When the `Key` is a component-name type, a populated entry causes the context to inherit the corresponding provider trait through the blanket impl. When the `Key` is an arbitrary type — a shape, a tag, a path segment — the table is instead a plain dispatch table that a higher-order provider walks, with no provider trait involved. One trait covers both, which is why `DelegateComponent` underlies both ordinary wiring and the inner tables of providers like [`UseDelegate`](../providers/use_delegate.md). + +## Definition + +`DelegateComponent` is a single-method-free trait parameterized by a key, carrying one associated type: + +```rust +#[diagnostic::on_unimplemented( + message = "{Self} does not contain any DelegateComponent entry for {Key}", + note = "You might want to implement the provider trait for {Key} on {Self}" +)] +pub trait DelegateComponent { + type Delegate; +} +``` + +The `Key` parameter is the table key — the type being looked up. It is `?Sized` so that unsized marker types can serve as keys. The `Delegate` associated type is the value stored at that key: the provider (or further table) the entry resolves to. `Self` is the table itself, the type that owns the entry. There is no method and no data; the trait exists purely to associate a value type with a key type on a carrier type. The `#[diagnostic::on_unimplemented]` annotation tailors the compiler's error when a lookup misses, pointing the reader at the absent entry rather than at an opaque "trait not implemented". + +## Behavior + +A type can hold many `DelegateComponent` entries at once, one per distinct `Key`, and each is an independent impl. Reading the table is the act of writing `T: DelegateComponent` and projecting `>::Delegate`; the compiler resolves that projection to whichever value the matching impl declared. Because Rust forbids two impls of the same trait for the same `Self` and `Key`, each key maps to exactly one value, which is what makes the structure a genuine map rather than a relation. + +These impls are almost never written by hand. [`delegate_components!`](../macros/delegate_components.md) generates one `DelegateComponent` impl per table entry, taking the `Key: Value` line and emitting `impl DelegateComponent for Target { type Delegate = Value; }`. Alongside each one it also emits an [`IsProviderFor`](is_provider_for.md) impl that forwards the chosen provider's dependencies, so that a missing transitive requirement still surfaces as a readable error rather than a silent lookup miss; the two impls together are what makes a wired entry both resolvable and checkable. + +How the value is used depends entirely on what the key is. When the key is a component name such as `GreeterComponent`, the provider blanket impl from `#[cgp_component]` reads the entry and concludes that `Self`'s provider trait is implemented by following the delegate — so the context inherits the provider trait, and then the consumer trait, for that component. The blanket impl's body is itself a `DelegateComponent` lookup: it bounds the provider type by `DelegateComponent` and forwards each method call to `>::Delegate`, so the table read is literally how the call is routed. When the key is an arbitrary type, no provider trait attaches; the entry is just data in a lookup table. A `UseDelegate
` provider, for instance, dispatches on one of its generic parameters by looking that parameter up as a key in `Table`'s `DelegateComponent` entries, which is why the inner tables built by [`delegate_components!`](../macros/delegate_components.md)'s nested-table syntax key on shape or tag types instead of component names. + +Resolution is shallow by default: a single `DelegateComponent` read yields the immediate `Delegate`, and nothing forces that delegate to be a leaf provider rather than another table. Chaining happens when the delegate is itself a `DelegateComponent` carrier — a provider bundle declared with `new` holds its own table, so a context can delegate a whole group of components to the bundle and let each component's blanket impl read through to the bundle's entry. The key types may also be type-level paths rather than plain names: [`RedirectLookup`](../providers/redirect_lookup.md) and the namespace machinery store entries keyed on `PathCons` lists, walking the table one segment at a time, which is the same `DelegateComponent` read applied to structured keys. + +## Examples + +A single wired component shows the table being set and then read. Given a component and a provider: + +```rust +use cgp::prelude::*; + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} + +#[cgp_impl(new GreetHello)] +impl Greeter { + fn greet(&self) { + println!("Hello!"); + } +} +``` + +a context wires it with `delegate_components!`, which emits the `DelegateComponent` entry: + +```rust +pub struct App; + +delegate_components! { + App { + GreeterComponent: GreetHello, + } +} +``` + +This expands to the hand-written equivalent of a single entry — the key is the component name, the value is the provider: + +```rust +impl DelegateComponent for App { + type Delegate = GreetHello; +} +``` + +Because `App` now delegates `GreeterComponent` to `GreetHello`, the provider blanket impl reads the entry and gives `App` the `Greeter` provider trait, and from there the consumer blanket impl gives `App` the `CanGreet` consumer trait, so `app.greet()` type-checks. + +An arbitrary-key table is the other use. The inner table built by the nested-table syntax keys on a shape type rather than a component name, so the same trait stores a dispatch map with no provider trait attached: + +```rust +delegate_components! { + new AreaComponents { + Rectangle: RectangleArea, + Circle: CircleArea, + } +} +``` + +Here `AreaComponents: DelegateComponent` and `AreaComponents: DelegateComponent` are plain lookup entries that a [`UseDelegate`](../providers/use_delegate.md) provider walks at dispatch time, choosing `RectangleArea` or `CircleArea` based on which shape it is asked about. Reading one entry back by hand is the same projection the macros generate internally: + +```rust +fn area_provider() -> PhantomData<>::Delegate> +where + AreaComponents: DelegateComponent, +{ + PhantomData +} +``` + +The bound `AreaComponents: DelegateComponent` is the "get", and `>::Delegate` is the value it returns — `RectangleArea` for `Shape = Rectangle`, `CircleArea` for `Shape = Circle`. + +## Related constructs + +`DelegateComponent` is the table that [`delegate_components!`](../macros/delegate_components.md) populates, one impl per entry. The provider blanket impl generated by [`#[cgp_component]`](../macros/cgp_component.md) is the reader: it looks a component name up in this table to find the provider whose trait the context inherits. The forwarding [`IsProviderFor`](is_provider_for.md) impl emitted next to each entry keeps missing dependencies diagnosable, and [`CanUseComponent`](can_use_component.md) combines the two — a context can use a component only if it both delegates the component here and the delegate is a valid provider for it. When the key is an arbitrary type rather than a component name, the table is the inner dispatch map consumed by [`UseDelegate`](../providers/use_delegate.md), and the path-keyed entries used by [`RedirectLookup`](../providers/redirect_lookup.md) and namespaces are `DelegateComponent` entries keyed on type-level paths. + +## Source + +The trait is defined in [crates/core/cgp-component/src/traits/delegate_component.rs](../../../crates/core/cgp-component/src/traits/delegate_component.rs) and re-exported through the macro prelude in [crates/core/cgp-component/src/macro_prelude.rs](../../../crates/core/cgp-component/src/macro_prelude.rs). The impls are generated by `delegate_components!`, whose codegen lives in [crates/macros/cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/) — entry mapping and the `DelegateComponent`/`IsProviderFor` impl construction are in `mapping/eval.rs`. The provider blanket impl that reads the table is emitted by the `#[cgp_component]` codegen in [crates/macros/cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/). Expansion snapshots are in [crates/tests/cgp-tests/src/tests/check_components.rs](../../../crates/tests/cgp-tests/src/tests/check_components.rs). diff --git a/docs/reference/traits/extract_field.md b/docs/reference/traits/extract_field.md new file mode 100644 index 00000000..68c63820 --- /dev/null +++ b/docs/reference/traits/extract_field.md @@ -0,0 +1,123 @@ +# `ExtractField` and the extractor trait family + +The extractor family is the set of traits that let an enum be matched one variant at a time, with the still-possible variants tracked in the type so that a chain of extractions becomes a provably exhaustive match without a wildcard arm. + +## Purpose + +The extractor family solves the problem of deconstructing a sum type generically and exhaustively. A `match` on a concrete enum names every variant in one place; the extractor instead pulls variants out one at a time, and each failed attempt narrows the set of remaining possibilities. The narrowing happens in the type: a failed extraction hands back a *remainder* whose type has one more variant marked impossible, and once every variant has been ruled out the remainder is an uninhabited type that can be discharged unconditionally. This is how generic code can match an arbitrary enum, variant by variant, and have the compiler confirm the match is complete. + +The family pairs an accessor that turns a value into an extractor with a per-variant extraction operation and a finalize step for the empty remainder. `HasExtractor` and its borrowed forms obtain the extractor; `ExtractField` tries one variant and returns either the payload or the shrunken remainder; `FinalizeExtract` discharges the remainder once it is uninhabited. The traits live in the field crate and are implemented for an enum by [`#[derive(ExtractField)]`](../derives/derive_extract_field.md), which generates the partial companion enums they operate on. + +## Definition + +The family consists of three accessor traits, the extraction operation, and two finalize traits. The accessors turn a value into an extractor in one of three ownership modes — owned, shared-reference, and mutable-reference: + +```rust +pub trait HasExtractor { + type Extractor; + fn to_extractor(self) -> Self::Extractor; + fn from_extractor(extractor: Self::Extractor) -> Self; +} + +pub trait HasExtractorRef { + type ExtractorRef<'a> where Self: 'a; + fn extractor_ref(&self) -> Self::ExtractorRef<'_>; +} + +pub trait HasExtractorMut { + type ExtractorMut<'a> where Self: 'a; + fn extractor_mut(&mut self) -> Self::ExtractorMut<'_>; +} +``` + +`ExtractField` is the extraction operation. It attempts to read the variant named by `Tag` out of the extractor, returning `Ok(value)` if the runtime value is that variant or `Err(remainder)` if it is not, where the remainder is the same extractor with that one variant ruled out: + +```rust +pub trait ExtractField { + type Value; + type Remainder; + fn extract_field(self, _tag: PhantomData) -> Result; +} +``` + +`FinalizeExtract` discharges a remainder that has become uninhabited. Its method returns *any* type, which is sound precisely because there is no value to return it from — it is implemented for the empty [`Void`](../types/either.md) type and for the standard-library `Infallible`, both by matching on the empty value: + +```rust +pub trait FinalizeExtract { + fn finalize_extract(self) -> T; +} + +impl FinalizeExtract for Void { + fn finalize_extract(self) -> T { match self {} } +} + +impl FinalizeExtract for Infallible { + fn finalize_extract(self) -> T { match self {} } +} +``` + +`FinalizeExtractResult` is the convenience wrapper that collapses the `Result` produced by the last extraction. It is implemented for any `Result` whose error half implements `FinalizeExtract`, returning the `Ok` value and discharging the `Err`: + +```rust +pub trait FinalizeExtractResult { + type Output; + fn finalize_extract_result(self) -> Self::Output; +} + +impl FinalizeExtractResult for Result +where E: FinalizeExtract { + type Output = T; + fn finalize_extract_result(self) -> T { + match self { + Ok(value) => value, + Err(remainder) => remainder.finalize_extract(), + } + } +} +``` + +## Behavior + +Extraction proceeds variant by variant against a shrinking remainder until that remainder is uninhabited. The derive generates a partial companion enum with one [`MapType`](map_type.md) parameter per variant; the marker in each position decides whether that variant's payload is present (`IsPresent`) or has been mapped to the empty `Void` type (`IsVoid`). `to_extractor` starts the chain at the all-`IsPresent` configuration, where every variant is still possible. Each `extract_field` call is available only when the requested variant's marker is `IsPresent`; it matches on the value, returning the payload if it is that variant, or returning the remainder with that one marker flipped to `IsVoid` if it is not. + +Exhaustiveness is proven at the type level rather than by a wildcard. As variants are ruled out, the remainder's markers turn to `IsVoid` one by one, and `IsVoid` maps each payload slot to `Void`. When every marker is `IsVoid`, every arm of the partial enum holds a `Void`, so the whole type is uninhabited. The derive supplies a `FinalizeExtract` impl on exactly that all-`IsVoid` configuration, and that impl is sound only because the value cannot exist — `match self {}` has no arms to write. A caller therefore reaches `finalize_extract` (directly, or through `finalize_extract_result` on the last `Result`) only after trying every variant, and the compiler accepts the discharge with no wildcard arm. + +The three accessor traits differ only in ownership. `HasExtractor` consumes the value and yields an owned extractor whose payloads are owned; the `from_extractor` method reverses `to_extractor` for an unmatched value. `HasExtractorRef` and `HasExtractorMut` borrow the value and yield a borrowed extractor over the same partial enum, carrying a `MapTypeRef` marker (`IsRef` or `IsMut`) that maps each payload slot to a shared or mutable reference, so a value can be matched without being moved. + +## Examples + +The family is normally driven through `to_extractor`, a chain of `extract_field` calls, and `finalize_extract_result` to discharge the impossible remainder at the end: + +```rust +use cgp::prelude::*; +use cgp::core::field::traits::FinalizeExtractResult; + +#[derive(ExtractField)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} + +fn area(shape: Shape) -> f64 { + match shape.to_extractor().extract_field(PhantomData::) { + Ok(circle) => core::f64::consts::PI * circle.radius * circle.radius, + Err(remainder) => { + // remainder now has Circle ruled out (IsVoid) + let rect = remainder + .extract_field(PhantomData::) + .finalize_extract_result(); // remainder is now uninhabited; cannot fail + rect.width * rect.height + } + } +} +``` + +After the second extraction the remainder has both markers `IsVoid`, so its type is uninhabited and `finalize_extract_result` is accepted with no wildcard arm. Adding a third variant to `Shape` would make this function fail to compile until the new variant is handled, because the remainder after two extractions would no longer be uninhabited. + +## Related constructs + +The enum-side derive that generates the partial enums and all these impls is [`#[derive(ExtractField)]`](../derives/derive_extract_field.md), whose doc shows the exact expanded code. The presence markers `IsPresent` and `IsVoid` are [`MapType`](map_type.md) implementations, and the empty remainder bottoms out in the [`Void`](../types/either.md) type that anchors the sum spine. The construction counterpart, which puts a variant *into* an enum rather than taking one out, is [`FromVariant`](from_variant.md). The struct analogue of the whole family is the builder family in [`has_builder`](has_builder.md), which assembles a record field by field instead of deconstructing a variant. The conceptual overview that frames this family is [extensible variants](../../concepts/extensible-variants.md), worked through in the [expression interpreter](../../examples/expression-interpreter.md) example. + +## Source + +The traits — `ExtractField`, `HasExtractor`, `HasExtractorRef`, `HasExtractorMut`, `FinalizeExtract`, and `FinalizeExtractResult` — are all defined in [crates/core/cgp-field/src/traits/extract_field.rs](../../../crates/core/cgp-field/src/traits/extract_field.rs), with `PartialData` in [partial_data.rs](../../../crates/core/cgp-field/src/traits/partial_data.rs). The `MapType` markers are in [crates/core/cgp-field/src/impls/map_type.rs](../../../crates/core/cgp-field/src/impls/map_type.rs) and the `MapTypeRef` markers in [map_type_ref.rs](../../../crates/core/cgp-field/src/impls/map_type_ref.rs). Tests are in [crates/tests/cgp-tests/tests/extensible_data_tests/variants/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/variants/). diff --git a/docs/reference/traits/from_variant.md b/docs/reference/traits/from_variant.md new file mode 100644 index 00000000..0147d19b --- /dev/null +++ b/docs/reference/traits/from_variant.md @@ -0,0 +1,74 @@ +# `FromVariant` + +`FromVariant` constructs an enum from a single named variant, with the variant chosen by a type-level tag rather than by writing the concrete variant constructor. + +## Purpose + +`FromVariant` solves the problem of building an enum value in generic code that does not — and cannot — name the concrete variant constructor. Writing `Shape::Circle(circle)` hard-codes both the enum and the variant, so it only works where both are known statically. `FromVariant` instead selects the variant with a type-level [`Symbol!`](../macros/symbol.md) tag, so a provider parameterized over the tag can construct whichever variant it was asked to build, on whatever enum implements the trait for that tag. It is the construction counterpart to the extractor's deconstruction: where [`ExtractField`](extract_field.md) takes one variant *out* of a value, `FromVariant` puts one variant *in*. + +This is the smallest trait of the extensible-variant family. It carries no partial companion type and no presence tracking — there is no state to track when building a single variant — so it is just a direct, tag-selected constructor. It is implemented for an enum by [`#[derive(FromVariant)]`](../derives/derive_from_variant.md), which emits one impl per variant, and it is the construction half that the casts and dispatchers reach for after a match has decided which variant to build. + +## Definition + +`FromVariant` is parameterized by the variant's tag and exposes the variant's payload type as an associated `Value`: + +```rust +pub trait FromVariant { + type Value; + fn from_variant(_tag: PhantomData, value: Self::Value) -> Self; +} +``` + +`Tag` is the variant's name as a type-level string `Symbol!`, `Value` is that variant's payload type, and `from_variant` wraps a payload into the enum as the chosen variant. The `PhantomData` argument carries no data; its sole job is to let the caller select which variant to build when several `FromVariant` impls — one per variant — are in scope on the same enum. The trait is implemented once per variant, each impl fixing its own `Tag` and `Value`, so the choice of impl *is* the choice of variant. + +## Behavior + +Each `FromVariant` impl is a thin wrapper that maps a payload to its variant. The derive emits, for every single-payload variant, an impl keyed by that variant's name symbol with the payload as `Value`, whose `from_variant` simply returns `Self::Variant(value)`. There is no intermediate type, no `MapType` marker, and no validation beyond the type system's own check that the supplied `value` matches the variant's payload type. Because the impls are distinguished only by their `Tag` type parameter, resolving a `from_variant` call comes down to which `Symbol!` the caller names in the `PhantomData` argument — the compiler picks the matching impl and inlines it to the corresponding constructor. + +For an enum `Shape { Circle(Circle), Rectangle(Rectangle) }`, the derive produces: + +```rust +impl FromVariant for Shape { + type Value = Circle; + fn from_variant(_tag: PhantomData, value: Circle) -> Self { + Self::Circle(value) + } +} + +impl FromVariant for Shape { + type Value = Rectangle; + fn from_variant(_tag: PhantomData, value: Rectangle) -> Self { + Self::Rectangle(value) + } +} +``` + +A call to `Shape::from_variant(PhantomData::, circle)` resolves to the first impl and is exactly `Shape::Circle(circle)`. The benefit appears only in generic code: a function that is itself parameterized over a tag `Tag` with a `C: FromVariant` bound can build the variant named by `Tag` without ever mentioning a concrete variant. + +## Examples + +`FromVariant` lifts a value into an enum by naming the variant with a tag, which a concrete call site can do directly and generic code can do over an abstract `Tag`: + +```rust +use cgp::prelude::*; + +#[derive(FromVariant)] +pub enum Shape { + Circle(Circle), + Rectangle(Rectangle), +} + +fn wrap_circle(circle: Circle) -> Shape { + Shape::from_variant(PhantomData::, circle) +} +``` + +The call is equivalent to `Shape::Circle(circle)`, but because the variant is selected by the type-level tag, the same construction pattern works inside code that is generic over the tag and the enum. + +## Related constructs + +The derive that generates the per-variant impls is [`#[derive(FromVariant)]`](../derives/derive_from_variant.md), whose doc shows the exact expanded code; it is also folded into [`#[derive(CgpVariant)]`](../derives/derive_cgp_variant.md) and [`#[derive(CgpData)]`](../derives/derive_cgp_data.md). The reverse operation is [`ExtractField`](extract_field.md), which deconstructs an enum variant by variant rather than constructing one. The variant tag is a [`Symbol!`](../macros/symbol.md) type-level string, the same kind of tag that keys the field traits. For structs, the analogous field-setting building block is the builder family in [`has_builder`](has_builder.md). The conceptual overview that frames variant construction is [extensible variants](../../concepts/extensible-variants.md), worked through in the [expression interpreter](../../examples/expression-interpreter.md) example, where upcasting a small local enum relies on `FromVariant` to rebuild each variant into the target. + +## Source + +The `FromVariant` trait is defined in [crates/core/cgp-field/src/traits/from_variant.rs](../../../crates/core/cgp-field/src/traits/from_variant.rs). The per-variant impls are generated by `derive_from_variant` in [crates/macros/cgp-macro-lib/src/derive_from_variant.rs](../../../crates/macros/cgp-macro-lib/src/derive_from_variant.rs), driving `derive_from_variant_from_enum` in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs). Tests are in [crates/tests/cgp-tests/tests/extensible_data_tests/variants/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/variants/). diff --git a/docs/reference/traits/has_builder.md b/docs/reference/traits/has_builder.md new file mode 100644 index 00000000..1bd84bb6 --- /dev/null +++ b/docs/reference/traits/has_builder.md @@ -0,0 +1,120 @@ +# `HasBuilder` and the builder trait family + +The builder family is the set of traits that let a record be assembled one field at a time, with field presence tracked at the type level so that a value can be finalized only once every field is set. + +## Purpose + +The builder family solves the problem of constructing a record incrementally and generically, where the fields are not all known at the same place in the code and the construction must still be checked at compile time. A plain struct literal requires every field to be supplied at once; these traits instead start from an empty *partial* value and add fields to it one by one, with each addition advancing a type-level record of which fields are present. The payoff is that finalizing an incomplete value is a compile error, not a runtime panic — the trait that turns a partial value back into the concrete struct is implemented only for the fully-present configuration. + +The family is built around a single primitive, `UpdateField`, that changes one field's storage from one state to another. Everything else is either a wrapper over that primitive (`BuildField` sets an absent field present, `TakeField` removes a present field) or an entry/exit point for the whole process (`HasBuilder`/`IntoBuilder` start a build, `FinalizeBuild` ends one). The traits live in the field crate and are implemented for a struct by [`#[derive(BuildField)]`](../derives/derive_build_field.md), which also generates the partial companion type they operate on. + +## Definition + +The family divides into entry points, the update primitive, two wrappers over it, and the finalize step. The entry points obtain a partial value to build into. `HasBuilder` produces an empty one and `IntoBuilder` produces a fully-present one from an existing value: + +```rust +pub trait HasBuilder { + type Builder; + fn builder() -> Self::Builder; +} + +pub trait IntoBuilder { + type Builder; + fn into_builder(self) -> Self::Builder; +} +``` + +`UpdateField` is the primitive every field operation reduces to. It changes the field named by `Tag` from its current marker (`Mapper`) to the new marker `M`, both of which are [`MapType`](map_type.md) markers, and returns the field's old value alongside the rebuilt partial value: + +```rust +pub trait UpdateField { + type Value; + type Mapper: MapType; // the field's marker before the update + type Output; // the partial value with the field now in state M + fn update_field( + self, + _tag: PhantomData, + value: M::Map, + ) -> (::Map, Self::Output); +} +``` + +The `M::Map` argument and the `Mapper::Map` first return component are the field's value as it is *stored* under each marker: `IsPresent` stores the value itself, `IsNothing` stores `()`. So updating an absent field to present takes the real value in and returns `()` as the old value; the reverse takes `()` in and returns the real value. + +`BuildField` and `TakeField` are the two directions of that transition, each defined once in the field crate as a blanket impl over `UpdateField`. `BuildField` is the `IsNothing → IsPresent` direction — set a currently-absent field — and `TakeField` is the `IsPresent → IsNothing` direction — remove a currently-present field: + +```rust +pub trait BuildField { + type Value; + type Output; + fn build_field(self, _tag: PhantomData, value: Self::Value) -> Self::Output; +} + +impl BuildField for Context +where + Context: UpdateField, +{ /* build_field = self.update_field(tag, value).1 */ } + +pub trait TakeField { + type Value; + type Remainder; + fn take_field(self, _tag: PhantomData) -> (Self::Value, Self::Remainder); +} + +impl TakeField for Context +where + Context: UpdateField, +{ /* take_field = self.update_field(tag, ()) */ } +``` + +`PartialData` records which concrete struct a partial value targets, and `FinalizeBuild` — a subtrait of `PartialData` — turns the partial value back into that struct: + +```rust +pub trait PartialData { + type Target; +} + +pub trait FinalizeBuild: PartialData { + fn finalize_build(self) -> Self::Target; +} +``` + +## Behavior + +A build is a sequence of `UpdateField`-driven state changes that only finalizes at the all-present configuration. The entry point fixes the starting state: `HasBuilder::builder()` returns the partial type with every field marker `IsNothing` (an empty value where each field is stored as `()`), while `IntoBuilder::into_builder(self)` returns it with every marker `IsPresent` (a full value carrying the real fields). Each `build_field` call flips one marker from `IsNothing` to `IsPresent` by calling the generated `update_field` and keeping only its `Output`; each `take_field` does the reverse and also hands back the removed value. + +Presence lives entirely in the type. The derive generates the partial struct with one `MapType` parameter per field, and the marker in each position decides whether that field's slot holds the value (`IsPresent`), holds `()` (`IsNothing`), or holds the empty `Void` type (`IsVoid`). Because `BuildField` requires `Mapper = IsNothing` and `TakeField` requires `Mapper = IsPresent`, the compiler rejects building a field that is already set or taking one that is absent. The derive also emits a [`HasField`](has_field.md) impl on the partial type gated on `IsPresent`, so a field that has been set can be read back out of a still-incomplete value. + +Finalizing is what makes the tracking load-bearing. The derive provides exactly one `FinalizeBuild` impl, on the all-`IsPresent` configuration of the partial type, so `finalize_build` is in scope only when every field is present; calling it on a partial value with any `IsNothing` field fails to compile. `PartialData::Target` is implemented for *every* configuration and names the struct being built, which is how generic builder code knows the destination type before the build is complete. + +## Examples + +The family is normally driven through `builder()`, a series of `build_field` calls, and `finalize_build`, with `build_from` (from the field crate's `CanBuildFrom`, itself layered on `BuildField`) copying every shared field from another record in one step: + +```rust +use cgp::prelude::*; +use cgp::core::field::impls::CanBuildFrom; + +#[derive(BuildField)] +pub struct FooBar { pub foo: u64, pub bar: String } + +#[derive(BuildField)] +pub struct FooBarBaz { pub foo: u64, pub bar: String, pub baz: bool } + +fn extend(foo_bar: FooBar) -> FooBarBaz { + FooBarBaz::builder() // all IsNothing + .build_from(foo_bar) // foo, bar now IsPresent + .build_field(PhantomData::, true) // baz now IsPresent + .finalize_build() // only the all-present impl applies +} +``` + +Each line changes the partial type, and the `finalize_build` on the last line type-checks only because every marker has reached `IsPresent` by that point. Reordering the steps so that `finalize_build` ran before `baz` was set would be a compile error rather than a runtime failure. + +## Related constructs + +The struct-side derive that generates the partial type and all these impls is [`#[derive(BuildField)]`](../derives/derive_build_field.md), whose doc shows the exact expanded code. The presence markers `IsPresent`, `IsNothing`, and `IsVoid` are [`MapType`](map_type.md) implementations, and the partial type's `MapType` parameters are what record per-field state. Fields already set on a partial value are read back through [`HasField`](has_field.md). The enum counterparts to this family are the extractor traits in [`extract_field`](extract_field.md), which deconstruct a value variant by variant, and [`FromVariant`](from_variant.md), which constructs an enum from a single variant. The conceptual overview that ties this family into the [extensible builder pattern](../../concepts/extensible-records.md) is in [extensible records](../../concepts/extensible-records.md), worked through in the [application builder](../../examples/application-builder.md) example. + +## Source + +The traits are defined in [crates/core/cgp-field/src/traits/has_builder.rs](../../../crates/core/cgp-field/src/traits/has_builder.rs) (`HasBuilder`, `IntoBuilder`), [build_field.rs](../../../crates/core/cgp-field/src/traits/build_field.rs) (`BuildField`, `FinalizeBuild`), [update_field.rs](../../../crates/core/cgp-field/src/traits/update_field.rs) (`UpdateField`), [take_field.rs](../../../crates/core/cgp-field/src/traits/take_field.rs) (`TakeField`), and [partial_data.rs](../../../crates/core/cgp-field/src/traits/partial_data.rs) (`PartialData`). The `MapType` markers are in [crates/core/cgp-field/src/impls/map_type.rs](../../../crates/core/cgp-field/src/impls/map_type.rs), and `CanBuildFrom` in [crates/core/cgp-field/src/impls/build_from.rs](../../../crates/core/cgp-field/src/impls/build_from.rs). Tests are in [crates/tests/cgp-tests/tests/extensible_data_tests/records/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/records/). diff --git a/docs/reference/traits/has_field.md b/docs/reference/traits/has_field.md new file mode 100644 index 00000000..3eef3881 --- /dev/null +++ b/docs/reference/traits/has_field.md @@ -0,0 +1,131 @@ +# `HasField` + +`HasField` is the consumer trait for reading a single named field out of a context by a type-level tag, with `HasFieldMut` adding mutable access, the provider-side mirrors `FieldGetter` and `MutFieldGetter` supplying the same capability through CGP wiring, and the lifetime helpers `MapField`/`FieldMapper` letting chained field accesses borrow correctly. + +## Purpose + +`HasField` exists so that a provider can demand a specific value from its context without naming the context's concrete type. The recurring problem in CGP is that a provider is generic over the context but still needs a `name`, a `port`, or some other field out of it; it cannot reach into a struct it does not know. `HasField` solves this by keying each field with a *tag type* that stands in for the field's name, so a provider can write `Context: HasField` in its `where` clause and receive the field through the trait system. This makes field access an impl-side dependency rather than part of any public interface — any context that supplies a matching field satisfies the bound automatically. See [impl-side dependencies](../../concepts/impl-side-dependencies.md) for why this constraint-based style is the heart of CGP. + +The trait is deliberately tiny because it is the foundation that the value-injection macros stand on. [`#[derive(HasField)]`](../derives/derive_has_field.md) generates the impls from a struct's fields, and higher-level constructs — `#[cgp_auto_getter]`, `#[cgp_getter]` through the [`UseField`](../providers/use_field.md) provider, and `#[implicit]` arguments — all desugar into `HasField` bounds and `get_field` calls. Understanding `HasField` is understanding how every one of those reaches a field. + +## Definition + +`HasField` carries the field's type as an associated `Value` and returns a reference to it, taking a `PhantomData` argument that exists only to disambiguate which field is meant when several `HasField` impls are in scope: + +```rust +pub trait HasField { + type Value; + + fn get_field(&self, _tag: PhantomData) -> &Self::Value; +} +``` + +The `Tag` parameter is a type-level name. A named struct field is keyed by [`Symbol!("field_name")`](../macros/symbol.md), the type-level string of its identifier; a tuple field is keyed by [`Index`](../types/index.md), the type-level natural number of its position. Because the tag is a type and not a value, `get_field` receives `PhantomData` purely to tell the compiler which impl to select. + +`HasFieldMut` is the mutable extension. It supertraits `HasField` and adds a method returning `&mut Self::Value`: + +```rust +pub trait HasFieldMut: HasField { + fn get_field_mut(&mut self, tag: PhantomData) -> &mut Self::Value; +} +``` + +Both traits carry `#[diagnostic::on_unimplemented]` notes that point a reader at `#[derive(HasField)]` when the bound is unsatisfied, so a missing field surfaces as a readable error rather than an opaque trait failure. + +The consumer side has a provider-side mirror so field access can be wired like any other component rather than only implemented directly on the context. `FieldGetter` is the provider trait corresponding to `HasField`: instead of `&self`, it takes the context as an explicit argument, which is the shape CGP providers use: + +```rust +pub trait FieldGetter { + type Value; + + fn get_field(context: &Context, _tag: PhantomData) -> &Self::Value; +} + +pub trait MutFieldGetter: FieldGetter { + fn get_field_mut(context: &mut Context, tag: PhantomData) -> &mut Self::Value; +} +``` + +Alongside these, `MapField` and `FieldMapper` add a borrow-through-a-closure variant of the same access. They exist to organize lifetime inference: chaining `context.get_field().get_field()` would otherwise force `Self::Value` to be `'static`, so `map_field` takes a `for<'a> FnOnce(&'a Self::Value) -> &'a T` closure and applies it to the borrowed field, letting the compiler infer the correct lifetime: + +```rust +pub trait MapField: HasField { + fn map_field( + &self, + _tag: PhantomData, + mapper: impl for<'a> FnOnce(&'a Self::Value) -> &'a T, + ) -> &T; +} + +pub trait FieldMapper: FieldGetter { + fn map_field( + context: &Context, + _tag: PhantomData, + mapper: impl for<'a> FnOnce(&'a Self::Value) -> &'a T, + ) -> &T; +} +``` + +## Behavior + +The consumer impls of `HasField` come almost entirely from `#[derive(HasField)]`; the trait file itself provides only the blanket impls that make the access compose. The first is a `Deref` forwarding impl: when a context dereferences to a target that has the field, the context inherits it, so a `HasField` bound passes transparently through smart-pointer wrappers. This impl is marked `#[diagnostic::do_not_recommend]` so the compiler does not suggest the blanket path in error messages. `HasFieldMut` carries the analogous `DerefMut` forwarding impl, with the target additionally bounded `'static`. + +The provider side is what connects field access to wiring. `UseContext` implements `FieldGetter` for any context that itself has the field, delegating straight to `context.get_field(...)`: + +```rust +impl FieldGetter for UseContext +where + Context: HasField, +{ + type Value = Field; + fn get_field(context: &Context, _tag: PhantomData) -> &Self::Value { + context.get_field(PhantomData) + } +} +``` + +`FieldMapper` is implemented as a blanket impl for any `FieldGetter` (with the getter and tag `'static`), so every provider-side getter automatically gains the lifetime-friendly `map_field`. Likewise `MapField` is a blanket impl for every `HasField` whose tag is `'static`. The split between the two sides is the standard CGP consumer/provider duality: `HasField`/`HasFieldMut` are what generic code bounds against, while `FieldGetter`/`MutFieldGetter` are what gets wired — the [`UseField`](../providers/use_field.md) provider is the wiring-side implementation that `#[cgp_getter]` targets. + +## Examples + +A provider that needs a value from its context expresses the need as a `HasField` bound and reads the field with `get_field`: + +```rust +use cgp::prelude::*; + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasField, +{ + fn greet(&self) { + println!("Hello, {}!", self.get_field(PhantomData)); + } +} + +#[derive(HasField)] +pub struct Person { + pub name: String, +} + +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} +``` + +Because `Person` derives `HasField`, it implements `HasField`, which is exactly the bound `GreetHello` requires; the wiring type-checks and `person.greet()` prints the name. In practice the explicit bound is rarely hand-written — `#[cgp_auto_getter]`, `#[cgp_getter]`, and `#[implicit]` arguments all generate it for you on top of these impls. + +## Related constructs + +`HasField` is generated by [`#[derive(HasField)]`](../derives/derive_has_field.md), which emits one `HasField` and one `HasFieldMut` impl per struct field. Its tags come from [`Symbol!`](../macros/symbol.md) for named fields and [`Index`](../types/index.md) for tuple fields. The provider-side [`UseField`](../providers/use_field.md) provider is the wiring-side implementation of `FieldGetter` that `#[cgp_getter]` targets, and field access in general is the canonical example of [impl-side dependencies](../../concepts/impl-side-dependencies.md). For the whole-struct structural view rather than single-field access, see [`HasFields`](has_fields.md). + +## Source + +The consumer traits and their blanket impls are in [crates/core/cgp-field/src/traits/has_field.rs](../../../crates/core/cgp-field/src/traits/has_field.rs) (`HasField`, `FieldGetter`, the `Deref` forwarding impl, and the `UseContext` provider impl) and [crates/core/cgp-field/src/traits/has_field_mut.rs](../../../crates/core/cgp-field/src/traits/has_field_mut.rs) (`HasFieldMut`, `MutFieldGetter`, the `DerefMut` forwarding impl). The `MapField`/`FieldMapper` lifetime helpers are in [crates/core/cgp-field/src/traits/map_field.rs](../../../crates/core/cgp-field/src/traits/map_field.rs). The `UseField` provider lives in [crates/core/cgp-field/src/impls/use_field.rs](../../../crates/core/cgp-field/src/impls/use_field.rs). Behavioral tests are in [crates/tests/cgp-tests](../../../crates/tests/cgp-tests). diff --git a/docs/reference/traits/has_fields.md b/docs/reference/traits/has_fields.md new file mode 100644 index 00000000..fafe7493 --- /dev/null +++ b/docs/reference/traits/has_fields.md @@ -0,0 +1,87 @@ +# `HasFields` + +`HasFields` is the consumer trait that exposes a type's complete field structure as a single type-level shape — a [`Product`](../macros/product.md) for a struct, a [`Sum`](../macros/sum.md) for an enum — with `HasFieldsRef` giving the borrowed view and `ToFields`, `FromFields`, and `ToFieldsRef` providing the round-trip conversions between a concrete value and that shape. + +## Purpose + +`HasFields` exists to describe an entire type's shape as one type rather than one entry per field. Where [`HasField`](has_field.md) answers "give me *this* field by name," `HasFields` answers "describe *all* the fields at once": it produces a single associated `Fields` type that is the type-level list of every field, each tagged with its name. This aggregate view is what lets generic code fold over a context's shape uniformly — serialization, builders, conversions, and the extensible-record and extensible-variant machinery all operate on the `Fields` type rather than reaching for fields one at a time. + +The distinction from `HasField` is the whole point. `HasField` is indexed access through many small impls, used for dependency injection where a provider wants one specific value. `HasFields` is the structural mirror — a single impl whose `Fields` type enumerates the full shape, used when an algorithm must process the type as a record or as a tagged union. The two are complementary, and a type that wants both indexed access and structural processing derives both with [`#[derive(HasField, HasFields)]`](../derives/derive_has_fields.md). + +The conversion traits make that structural view a two-way door. `ToFields` turns an owned value into its `Fields` shape, `FromFields` rebuilds the value from the shape, and `ToFieldsRef` borrows the value as a shape of references. Together they let generic code decompose a concrete type into its anonymous structural form, transform it, and reconstruct the concrete type. + +## Definition + +The two core traits each carry a single associated type and nothing else. `HasFields` names the owned shape, and `HasFieldsRef` names the borrowed shape, parameterized by a lifetime: + +```rust +pub trait HasFields { + type Fields; +} + +pub trait HasFieldsRef { + type FieldsRef<'a> + where + Self: 'a; +} +``` + +For a struct, `Fields` is a [`Product`](../macros/product.md) — a `Cons`/`Nil` chain of [`Field`](../types/field.md) entries, each value carrying its type-level name tag. For an enum, `Fields` is a [`Sum`](../macros/sum.md) — an `Either`/`Void` chain whose arms are `Field` entries naming each variant and carrying that variant's own fields as a nested product. Named fields and variants are tagged by [`Symbol!`](../macros/symbol.md); tuple fields by `Index`. `FieldsRef<'a>` is the same shape with each value borrowed for `'a`. + +The three conversion traits each supertrait one of the two shape traits and add a single method. `ToFields` and `FromFields` build on `HasFields`, while `ToFieldsRef` builds on `HasFieldsRef`: + +```rust +pub trait ToFields: HasFields { + fn to_fields(self) -> Self::Fields; +} + +pub trait FromFields: HasFields { + fn from_fields(fields: Self::Fields) -> Self; +} + +pub trait ToFieldsRef: HasFieldsRef { + fn to_fields_ref<'a>(&'a self) -> Self::FieldsRef<'a> + where + Self: 'a; +} +``` + +`to_fields` consumes the value to produce the owned product or sum, `from_fields` consumes the shape to reconstruct the value, and `to_fields_ref` borrows the value to produce a shape of references — the non-consuming counterpart used when the original value must be kept. + +## Behavior + +All five impls come from [`#[derive(HasFields)]`](../derives/derive_has_fields.md); the trait module defines only the bare trait shapes. The derive dispatches on whether the type is a struct or an enum: a struct produces a `Product!` of its fields, an enum produces a `Sum!` of its variants, and a single-field tuple struct (a newtype) is treated specially so its `Fields` is the inner type directly rather than a one-element product. + +The conversions are mechanical inverses of one another. `to_fields` folds the concrete value into a `Cons` chain (or an `Either` arm for an enum), `from_fields` pattern-matches that chain back into the concrete fields, and `to_fields_ref` builds the same `Cons` chain over borrows. Because `from_fields` and `to_fields` round-trip through the identical `Fields` type, generic code can decompose a value, operate on the structural form, and rebuild the value with the guarantee that the shapes line up by construction. + +## Examples + +Deriving `HasFields` lets generic code treat any type as a record without naming its concrete type, and a value can be round-tripped through its `Fields` shape: + +```rust +use cgp::prelude::*; + +#[derive(HasField, HasFields)] +pub struct Config { + pub host: String, + pub port: u16, +} + +let config = Config { host: "localhost".into(), port: 8080 }; + +// ToFields: Config -> Product![Field, Field] +let fields = config.to_fields(); + +// FromFields: the product -> back to Config +let config_again = Config::from_fields(fields); +``` + +For an enum the `Fields` shape is a sum instead of a product. A `Shape` enum with `Circle { radius: f64 }` and `Rectangle { width: f64, height: f64 }` variants produces a `Fields` of `Either, Either, Void>>`, and the same `to_fields`/`from_fields` pair moves a `Shape` value in and out of that sum. Generic algorithms bound `Context: HasFields` (or `ToFields`/`FromFields`) and process `Context::Fields` structurally, which is how the extensible-data machinery operates over arbitrary contexts. + +## Related constructs + +`HasFields` is the structural counterpart to [`HasField`](has_field.md): `HasField` gives indexed, single-field access for dependency injection, while `HasFields` gives the whole-shape view. All five impls are generated by [`#[derive(HasFields)]`](../derives/derive_has_fields.md). The `Fields` shape is built from [`Product`](../macros/product.md) for structs and [`Sum`](../macros/sum.md) for enums, with each entry a [`Field`](../types/field.md) tagged by [`Symbol!`](../macros/symbol.md). + +## Source + +The trait definitions are in [crates/core/cgp-field/src/traits/has_fields.rs](../../../crates/core/cgp-field/src/traits/has_fields.rs) (`HasFields`, `HasFieldsRef`), [crates/core/cgp-field/src/traits/to_fields.rs](../../../crates/core/cgp-field/src/traits/to_fields.rs) (`ToFields`, `ToFieldsRef`), and [crates/core/cgp-field/src/traits/from_fields.rs](../../../crates/core/cgp-field/src/traits/from_fields.rs) (`FromFields`). The `Field`, `Cons`/`Nil`, `Either`/`Void` building blocks live under [crates/core/cgp-field/src/types/](../../../crates/core/cgp-field/src/types/). The derive that emits the impls is in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/); expansion snapshots are in [crates/tests/cgp-macro-tests](../../../crates/tests/cgp-macro-tests). diff --git a/docs/reference/traits/is_provider_for.md b/docs/reference/traits/is_provider_for.md new file mode 100644 index 00000000..ce11dccb --- /dev/null +++ b/docs/reference/traits/is_provider_for.md @@ -0,0 +1,106 @@ +# `IsProviderFor` + +`IsProviderFor` is the marker trait that every CGP provider trait carries as a supertrait, propagating a provider's `where`-bounds so that an unmet dependency surfaces as a readable compiler error instead of being silently hidden. + +## Purpose + +`IsProviderFor` exists to make missing dependencies diagnosable. A CGP provider implements its provider trait under a `where` clause that lists everything it needs from the context — a getter, an abstract type, another component. When that clause is unmet, the natural question "does this provider implement the provider trait?" produces a frustrating answer: Rust reports only that the trait is not implemented, with no explanation, because a competing candidate (the provider blanket impl from [`#[cgp_component]`](../macros/cgp_component.md)) is also in scope and Rust suppresses the detailed reasoning whenever more than one impl could apply. The real cause — a single absent field or type — stays buried. + +`IsProviderFor` is the second, independent path that un-hides those errors. It is an empty marker trait, trivially satisfiable in principle, but the macros implement it for a provider under *exactly the same* `where` bounds as the provider trait itself. Because every generated provider trait names `IsProviderFor` as a supertrait, asking whether a provider satisfies `IsProviderFor` forces the compiler to evaluate those bounds — and since that question has only one candidate impl (the explicit one the macro wrote, not a blanket), Rust does not hide its reasoning and reports the precise unsatisfied constraint. The trait carries no behavior; its entire value is making the dependency set visible to the compiler along a path Rust will explain. + +This is why the dedicated, full treatment lives here while other documents mention `IsProviderFor` only in passing. To a user writing components and providers, it is invisible plumbing generated and consumed by the macros. To anyone reading a wiring error or building higher-order providers, it is the mechanism that turns an opaque failure into a pointer at the actual gap. + +The phenomenon it works around is specific to how Rust prunes diagnostics. When two impls could satisfy a bound — here, the provider blanket impl that routes through the delegation table, and the user's explicit provider impl — Rust reports only that no impl matched, withholding the per-impl reasons because it cannot know which candidate the programmer intended. `IsProviderFor` sidesteps the ambiguity by being implemented along a single, explicit path with no competing blanket, so the compiler commits to that path and prints why its `where` clause failed. + +## Definition + +`IsProviderFor` is an empty trait parameterized by a component, a context, and a parameter tuple: + +```rust +#[diagnostic::on_unimplemented( + note = "You need to add `#[cgp_provider({Component})]` on the impl block for CGP provider traits" +)] +pub trait IsProviderFor {} +``` + +The three parameters identify which provider trait implementation is being asserted. `Component` is the component-name type that corresponds to the provider trait, the same key used in the delegation table. `Context` is the context type the provider trait is implemented for. `Params` collects any additional generic parameters of the provider trait, grouped into a tuple when there is more than one and defaulting to the unit tuple `()` when there are none — so a provider trait with parameters `` becomes `Params = (I, J)`, and a parameterless one uses the default. `Self` is the provider type whose validity is being asserted. The `#[diagnostic::on_unimplemented]` note nudges a reader who hits the error toward the missing `#[cgp_provider]` attribute that would generate the impl. + +## Behavior + +`IsProviderFor` is generated, never hand-written, and it appears in three places that together form the diagnostic chain. First, [`#[cgp_component]`](../macros/cgp_component.md) emits every provider trait with `IsProviderFor` as a supertrait, so that for a component `Foo` the provider trait reads `pub trait FooGetterAt: IsProviderFor`. This supertrait link is what binds a provider's dependency set to the marker. + +Second, [`#[cgp_provider]`](../macros/cgp_provider.md) and [`#[cgp_impl]`](../macros/cgp_impl.md) emit, beside the provider trait impl, an `IsProviderFor` impl for the same provider type under the same `where` clause. A provider implemented as `impl FooGetterAt for GetFooValue where Context: HasField` gains a matching empty impl `impl IsProviderFor for GetFooValue where Context: HasField {}`. The two impls carry identical bounds, so the marker is satisfiable exactly when the provider trait is. + +Third, [`delegate_components!`](../macros/delegate_components.md) propagates the marker through the delegation table. For each entry it emits an `IsProviderFor` impl on the table type that forwards to the delegated provider's own `IsProviderFor`, generic over context and params: + +```rust +impl IsProviderFor for MyAppComponents +where + GetFooValue: IsProviderFor, +{} +``` + +This forwarding is the crucial step. Because it is an explicit impl rather than a blanket, the compiler follows it and surfaces every unsatisfied constraint coming from `GetFooValue` — the table re-exposes the provider's real requirements at the point of lookup. The forwarding is also what carries dependencies across layers: when a provider bundle delegates to another bundle, each bundle's `IsProviderFor` impl forwards to the next, so a requirement unmet several tables deep still propagates back to where the component is checked. The generic parameters are literally named `Context` and `Params` in the emitted code. The `Params` slot is filled by whatever a check supplies: a single parameter goes in directly, multiple parameters as a tuple, and a parameterless component uses `()`. + +The supertrait link on the provider trait is the piece that ties this together for the consumer side. A provider trait generated from `#[cgp_component(FooGetterAt)]` for a component `CanGetFooAt` reads as `pub trait FooGetterAt: IsProviderFor`, so any attempt to use the provider trait must first establish `IsProviderFor`. That is why probing `IsProviderFor` is equivalent to probing the provider trait's dependency set, and why the marker can stand in for the provider trait whenever a readable error is wanted. + +## Examples + +`IsProviderFor` is something you observe rather than invoke. A provider that depends on a field gets a matching marker impl automatically: + +```rust +use cgp::prelude::*; + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasField, +{ + fn greet(&self) { + println!("Hello, {}!", self.get_field(PhantomData)); + } +} +``` + +`#[cgp_impl]` emits both the `Greeter` provider trait impl and an `IsProviderFor` impl for `GreetHello`, each guarded by the same `HasField` bound. When this provider is wired onto a context that lacks a `name` field, the table's forwarding `IsProviderFor` impl carries that unmet `HasField` bound back to the point of use — which is exactly what [`check_components!`](../macros/check_components.md) leverages to report the missing field at the wiring site: + +```rust +#[derive(HasField)] +pub struct App { + pub first_name: String, // not `name` +} + +delegate_components! { + App { GreeterComponent: GreetHello } +} + +check_components! { + App { GreeterComponent } +} +``` + +The check forces `App: CanUseComponent`, which routes through `GreetHello: IsProviderFor`, whose `where` clause names the `HasField` bound — so the compiler reports the absent `name` field rather than a bare "provider trait not implemented". + +The marker can also be probed directly, which is what the `#[check_providers(...)]` form of `check_components!` does for a provider that is not the one a context delegates to. Asserting it by hand looks like a where-bound with no body: + +```rust +fn assert_provider() +where + GreetHello: IsProviderFor, +{} +``` + +This holds only if `GreetHello`'s dependencies are met for `App` — the same condition as the provider trait, surfaced along the explicit path. For a component with several type parameters the final argument is the grouping tuple, so a two-parameter component is asserted as `IsProviderFor`. + +## Related constructs + +`IsProviderFor` is the supertrait that [`#[cgp_component]`](../macros/cgp_component.md) attaches to every provider trait, and the empty impl beside it is generated by [`#[cgp_provider]`](../macros/cgp_provider.md) and [`#[cgp_impl]`](../macros/cgp_impl.md). [`delegate_components!`](../macros/delegate_components.md) forwards it through each [`DelegateComponent`](delegate_component.md) entry so a delegated provider's requirements stay visible. [`CanUseComponent`](can_use_component.md) is the context-side counterpart whose blanket impl requires `IsProviderFor` of the delegate, and [`check_components!`](../macros/check_components.md) is the macro that forces both — its `#[check_providers(...)]` form asserts `IsProviderFor` on named providers directly, which is the tool for localizing failures in higher-order provider stacks. + +## Source + +The trait is defined in [crates/core/cgp-component/src/traits/is_provider.rs](../../../crates/core/cgp-component/src/traits/is_provider.rs) and re-exported through [crates/core/cgp-component/src/macro_prelude.rs](../../../crates/core/cgp-component/src/macro_prelude.rs). The provider-trait supertrait link and the per-impl marker impl are emitted by [crates/macros/cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/) and the `#[cgp_provider]`/`#[cgp_impl]` codegen; the table forwarding impl is built in [crates/macros/cgp-macro-core/src/types/delegate_component/mapping/eval.rs](../../../crates/macros/cgp-macro-core/src/types/delegate_component/mapping/eval.rs). Expansion snapshots showing the supertrait, the marker impl, and the forwarding impl are in [crates/tests/cgp-tests/src/namespaces/default_impls.rs](../../../crates/tests/cgp-tests/src/namespaces/default_impls.rs) and [crates/tests/cgp-tests/src/tests/check_components.rs](../../../crates/tests/cgp-tests/src/tests/check_components.rs). diff --git a/docs/reference/traits/map_type.md b/docs/reference/traits/map_type.md new file mode 100644 index 00000000..75aaeefd --- /dev/null +++ b/docs/reference/traits/map_type.md @@ -0,0 +1,110 @@ +# `MapType` + +`MapType` is the type-level marker trait whose associated `Map` describes how a single field's value type is wrapped, so that builders and extractors can track each field's presence at the type level rather than at runtime. + +## Purpose + +`MapType` exists to make "what state is this field in?" a question the compiler answers, not the program. CGP's extensible-record machinery represents a half-built struct or a partially-extracted enum as a single type whose every field is independently wrapped: a field that is present carries its value, a field that has been consumed carries nothing, and a field that may or may not be there carries an `Option`. Rather than hard-code those three shapes, CGP parameterizes each field by a `MapType` marker and reads the field's actual storage type out of that marker's `Map`. The marker is the field's state, expressed as a type. + +This is what lets the builder family in [`HasBuilder`](has_builder.md) and the extractor family in [`ExtractField`](extract_field.md) move a record through intermediate states without ever changing the runtime representation of a value. A builder starts every field as "nothing" and flips each one to "present" as it is filled; an extractor starts every variant as "present" and flips each one to "void" as it is consumed. Because the flip is a change of type parameter, the compiler tracks exactly which fields are filled and refuses to finalize a record with a missing field. `MapType` is the vocabulary for those per-field states. + +The companion trait [`MapTypeRef`](#maptyperef) does the same job for borrowed views, where the wrapping additionally introduces a lifetime — a reference, a mutable reference, or an owned value. + +## Definition + +`MapType` is a trait with a single generic associated type. The implementor is a zero-sized marker, and `Map` names the storage type that the marker assigns to a field whose value type is `T`: + +```rust +pub trait MapType { + type Map; +} +``` + +The four standard markers in `cgp-field` cover the states a field passes through. `IsPresent` is the identity wrapping — the field holds its value directly. `IsNothing` erases the value to the unit type, marking the field as absent. `IsVoid` maps to the uninhabited [`Void`](../types/either.md) type, marking a variant that can never be reached. `IsOptional` wraps the value in `Option`, marking a field that is optionally present: + +```rust +impl MapType for IsPresent { type Map = T; } +impl MapType for IsNothing { type Map = (); } +impl MapType for IsVoid { type Map = Void; } +impl MapType for IsOptional { type Map = Option; } +``` + +## `MapTypeRef` + +`MapTypeRef` is the borrowed-view counterpart, used when a record is viewed by reference rather than by value. Its `Map<'a, T>` additionally threads a lifetime, so the marker decides not just whether the value is present but whether it is borrowed shared, borrowed mutably, or owned: + +```rust +pub trait MapTypeRef { + type Map<'a, T: 'a>: 'a; +} +``` + +The three standard markers correspond to the three ways of holding a value behind a lifetime. `IsRef` maps to `&'a T`, `IsMut` to `&'a mut T`, and `IsOwned` to a plain owned `T`: + +```rust +impl MapTypeRef for IsRef { type Map<'a, T: 'a> = &'a T; } +impl MapTypeRef for IsMut { type Map<'a, T: 'a> = &'a mut T; } +impl MapTypeRef for IsOwned { type Map<'a, T: 'a> = T; } +``` + +A borrowed extractor combines the two families: the partial type carries one outer `MapTypeRef` marker (`IsRef` for `extractor_ref`, `IsMut` for `extractor_mut`) shared across all fields, and one `MapType` marker per field. A field's storage type is then `MapType::Map>` — the per-field presence marker applied to the borrowed value type. + +## `TransformMap` and `TransformMapFields` + +`TransformMap` is a value-level natural transformation that converts a field from one `MapType` wrapping to another. Where `MapType` only names storage types, `TransformMap` carries the function that turns an `M1::Map` value into an `M2::Map` value: + +```rust +pub trait TransformMap { + fn transform_mapped(value: M1::Map) -> M2::Map; +} +``` + +`TransformMapFields` lifts such a transform across every field of a whole partial record. It is implemented for any context that is a [`PartialData`](has_builder.md), and it walks the target's [`HasFields`](has_fields.md) product field by field, applying the `Transform` to re-wrap each field from its current marker into `TargetMap`: + +```rust +pub trait TransformMapFields { + type Output; + + fn transform_map_fields(self) -> Self::Output; +} +``` + +The recursion is the load-bearing part. For each `Field` in the spine, `TransformMapFields` uses [`UpdateField`](has_builder.md) twice: it first takes the field out (replacing its marker with `IsNothing`), reads its current marker, applies `Transform::transform_mapped` to convert the value to the `TargetMap` wrapping, then writes it back under `TargetMap`. The result type therefore has every field re-marked to `TargetMap`, with the values transformed accordingly. + +## Examples + +These markers are mostly seen through the generated partial types of [`#[derive(CgpData)]`](../derives/derive_cgp_data.md), but the transform family is directly useful. A common application fills in default values for absent fields by transforming every field's marker to `IsPresent`, supplying `Default::default()` wherever a field was `IsNothing` or an empty `Option`: + +```rust +use cgp::prelude::*; + +pub struct FillDefaults; + +impl TransformMap for FillDefaults { + fn transform_mapped(value: T) -> T { + value + } +} + +impl TransformMap for FillDefaults { + fn transform_mapped(_value: ()) -> T { + T::default() + } +} + +impl TransformMap for FillDefaults { + fn transform_mapped(value: Option) -> T { + value.unwrap_or_default() + } +} +``` + +Applied through `transform_map_fields`, this turns a partially-built record — where some fields are present, some absent, and some optional — into a fully present builder whose missing fields have been filled with their defaults, ready to finalize. + +## Related constructs + +`MapType` is the per-field state vocabulary that [`HasBuilder`](has_builder.md) and its `PartialData`/`UpdateField` family use to track which fields are filled, and that [`ExtractField`](extract_field.md) uses to track which variants are still reachable. The `IsPresent`/`IsNothing`/`IsVoid`/`IsOptional` markers wrap field values; `IsVoid` maps to the uninhabited [`Void`](../types/either.md), the terminator of the [`Either`](../types/either.md) sum spine. The borrowed markers `IsRef`/`IsMut`/`IsOwned` of `MapTypeRef` feed the reference extractors. [`MapFields`](product_ops.md) applies a single `MapType` marker uniformly to every entry of a [`Cons`](../types/cons.md) product or `Either` sum, producing the partial-type field lists these markers populate. The whole scheme is generated by [`#[derive(CgpData)]`](../derives/derive_cgp_data.md). The [optional-field traits](optional_fields.md) build on this vocabulary with the `TransformMapDefault` and `TransformOptional` markers, which fill or wrap fields when finalizing a partially-built record. The conceptual overviews that show these markers tracking field and variant state are [extensible records](../../concepts/extensible-records.md) and [extensible variants](../../concepts/extensible-variants.md). + +## Source + +`MapType` is defined in [crates/core/cgp-field/src/traits/map_type.rs](../../../crates/core/cgp-field/src/traits/map_type.rs) and `MapTypeRef` in [crates/core/cgp-field/src/traits/map_type_ref.rs](../../../crates/core/cgp-field/src/traits/map_type_ref.rs). The standard markers are in [crates/core/cgp-field/src/impls/map_type.rs](../../../crates/core/cgp-field/src/impls/map_type.rs) (`IsPresent`, `IsNothing`, `IsVoid`, `IsOptional`) and [crates/core/cgp-field/src/impls/map_type_ref.rs](../../../crates/core/cgp-field/src/impls/map_type_ref.rs) (`IsRef`, `IsMut`, `IsOwned`). `TransformMap` and `TransformMapFields` are in [crates/core/cgp-field/src/traits/transform_map.rs](../../../crates/core/cgp-field/src/traits/transform_map.rs). The default-filling transform built on this machinery lives in [crates/extra/cgp-field-extra/src/impls/build_default.rs](../../../crates/extra/cgp-field-extra/src/impls/build_default.rs). The generated partial types that carry these markers appear in the extensible-data tests under [crates/tests/cgp-tests/tests/extensible_data_tests/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/). diff --git a/docs/reference/traits/monad.md b/docs/reference/traits/monad.md new file mode 100644 index 00000000..1925d125 --- /dev/null +++ b/docs/reference/traits/monad.md @@ -0,0 +1,71 @@ +# Monad traits + +The monad traits — `MonadicTrans`, `MonadicBind`, `LiftValue`, and `ContainsValue` — are the type-level interface that defines what a monad is for [monadic handler composition](../../concepts/monadic-handlers.md): which branch of an output value continues a pipeline, which branch short-circuits, and how values move between those branches. + +## Purpose + +These four traits factor a monad into the distinct capabilities the monadic pipeline machinery needs from it. A monad in CGP is a zero-sized marker type (`IdentMonadic`, `OkMonadic`, `ErrMonadic`, and their transformer forms), and these traits are what give that marker meaning. They are plain capability traits rather than CGP components — they have no generated provider trait or `…Component` marker and are not wired through `delegate_components!`; instead, the [`PipeMonadic`](../providers/monad_providers.md) provider and the per-step `BindOk` / `BindErr` providers consume them as ordinary trait bounds while building a pipeline at compile time. + +The split exists because building a monadic pipeline requires three separable decisions. Composing a list of handlers under a monad needs to know how to turn a continuation provider into a bind step — that is `MonadicBind`. Each bind step needs to know, for a given output type, what the underlying value beneath the monad's wrapper is, and how to put a value back into that output type — that is `ContainsValue` and `LiftValue`. Stacking one monad on top of another needs to apply a monad as a transformer over a base monad — that is `MonadicTrans`. Keeping these as separate traits lets the same marker serve all three roles and lets monads stack by composing their implementations. + +## Definition + +`MonadicBind` maps a continuation provider to the provider that runs one bind step of the monad: + +```rust +pub trait MonadicBind { + type Provider; +} +``` + +The `Provider` parameter is the continuation — the handler that should run on the monad's continue branch — and the `Provider` associated type is the bind provider that wraps it. For the base monads this resolves to a `BindOk` or `BindErr` provider; for `IdentMonadic` it is the continuation unchanged. + +`MonadicTrans` applies a monad as a transformer onto a base monad: + +```rust +pub trait MonadicTrans { + type M; +} +``` + +The `M` parameter is the base monad being transformed, and the `M` associated type is the resulting stacked monad. This is what lets `OkMonadic` be written as `OkMonadicTrans` when a pipeline operates over a nested result type, layering the ok behavior on top of the err behavior. + +`ContainsValue` reads, for a given monadic output type, the value type that sits beneath this monad's wrapper: + +```rust +pub trait ContainsValue { + type Value; +} +``` + +The `Output` parameter is the full output type a step produces; the `Value` associated type is the type carried in the branch the monad threads through, which a continuation handler consumes or which a deeper monad layer unwraps further. + +`LiftValue` moves values into a step's output type, in two directions: + +```rust +pub trait LiftValue { + type Output; + + fn lift_value(value: Value) -> Self::Output; + + fn lift_output(output: Output) -> Self::Output; +} +``` + +The associated `Output` is the final output type of the lift. `lift_value` takes a bare value from the continue or short-circuit branch and wraps it into that output type, and `lift_output` takes a value already in the inner `Output` shape and re-wraps it. The two methods correspond to the two branches a bind step takes: one lifts the short-circuit value back out as the result, the other forwards a continuation's already-computed output. + +## Implementations + +`IdentMonadic` implements all four traits as identities, which is what makes it thread every value forward without ever short-circuiting. `MonadicTrans` returns `M` unchanged, `MonadicBind` returns `Provider` unchanged, `ContainsValue::Value` is `T`, and `LiftValue` is the identity on `T` for both methods. + +`OkMonadic` and `ErrMonadic` implement the traits to branch on a `Result`, in mirror image of each other. For `ErrMonadic`, `ContainsValue>::Value` is `T` — the `Ok` payload is the value threaded forward — and `LiftValue>::lift_value` is `Ok`, so the continue branch is `Ok` and an `Err` short-circuits. For `OkMonadic` the roles are swapped: `ContainsValue>::Value` is `E`, and `lift_value` is `Err`, so the continue branch is `Err` and an `Ok` short-circuits. The `MonadicBind` impl of each base monad produces the corresponding `BindOk` or `BindErr` step over `IdentMonadic`, and the `MonadicTrans` impl wraps the marker in its transformer form (`OkMonadicTrans`, `ErrMonadicTrans`). + +The transformer forms `OkMonadicTrans` and `ErrMonadicTrans` implement the same traits by delegating one layer down to the base monad `M`. Their `ContainsValue` and `LiftValue` impls require `M: ContainsValue>`, peeling their own `Result` layer and handing the rest to `M`; their `MonadicTrans` impl composes transformers so a stack like `OkMonadicTrans` resolves layer by layer. This delegation is what allows monads to stack to arbitrary depth over nested result types. + +## Related constructs + +These traits are consumed by the monad providers in [monad providers](../providers/monad_providers.md): `PipeMonadic` uses `MonadicTrans` and `MonadicBind` to fold a handler list into a single pipeline provider, while `BindOk` and `BindErr` use `ContainsValue` and `LiftValue` in their `Computer` and `AsyncComputer` implementations to split and re-lift each step's output. The high-level picture of how the pieces fit — why a pipeline short-circuits and how the monads compose — is in [monadic handlers](../../concepts/monadic-handlers.md). The pipelines built from these traits implement the [`Computer`](../components/computer.md) family, so they slot into the same wiring as the [handler combinators](../providers/handler_combinators.md) `ComposeHandlers` and `PipeHandlers`, which compose handlers without the short-circuiting branch. + +## Source + +The traits are defined in [crates/extra/cgp-monad/src/traits/](../../../crates/extra/cgp-monad/src/traits/) — `monadic_trans.rs`, `bind.rs`, `lift.rs`, and `value.rs`. Their implementations for each monad marker are in [crates/extra/cgp-monad/src/monadic/](../../../crates/extra/cgp-monad/src/monadic/) (`ident.rs`, `ok.rs`, `err.rs`). The behavior is exercised by [crates/tests/cgp-tests/src/tests/monad/](../../../crates/tests/cgp-tests/src/tests/monad/). diff --git a/docs/reference/traits/optional_fields.md b/docs/reference/traits/optional_fields.md new file mode 100644 index 00000000..e65fc1ad --- /dev/null +++ b/docs/reference/traits/optional_fields.md @@ -0,0 +1,248 @@ +# Optional and defaulted field extensions + +The optional-field extensions are the traits and providers that let a record be finalized even when some fields were never set, by filling the gaps from `Default` or treating them as optional, rather than requiring every field to be present. + +## Purpose + +These extensions solve the problem that the core builder family is deliberately strict: a partial record can be turned back into its concrete struct only once every field has been set, because [`FinalizeBuild`](has_builder.md) is implemented solely on the all-`IsPresent` configuration of the partial type. That strictness is exactly what catches a missing field at compile time, but it is too rigid for records where some fields have sensible defaults or are genuinely allowed to be absent. The traits in `cgp-field-extra` relax it in two controlled ways — filling unset fields with `Default::default()`, and tracking each field as an `Option` so absence is a runtime condition rather than a compile error — while reusing the same `UpdateField`-driven machinery underneath. + +The whole layer is built by composing three core mechanisms rather than inventing new ones. It reuses [`UpdateField`](has_builder.md) to move individual fields between states, [`TransformMapFields`](map_type.md) to re-wrap every field of a partial record at once, and the [`MapType`](map_type.md) markers `IsPresent`, `IsNothing`, and `IsOptional` to name what state each field is in. The extensions are therefore best understood as a small set of `TransformMap` natural transformations plus the entry-point traits that drive them, layered on top of the builder and extractor traits in core. + +## Definition + +The layer splits into two complementary capabilities — defaulted finalization and optional fields — that share the same underlying transform pattern. Defaulted finalization fills any unset field from `Default` so a record can be completed without setting everything; the optional-field traits convert a builder so every field becomes an `Option`, allow those optional slots to be set and replaced individually, and finalize either by requiring presence with an error or by defaulting. The `TransformMap` markers `TransformMapDefault` and `TransformOptional` carry the actual per-field conversions, and the entry-point traits `CanBuildWithDefault`, `CanFinalizeWithDefault`, `HasOptionalBuilder`, `ToOptional`, `SetOptional`, and `FinalizeOptional` expose them as usable operations. + +### `TransformMapDefault` — filling unset fields from `Default` + +`TransformMapDefault` is the [`TransformMap`](map_type.md) natural transformation that re-wraps every field into `IsPresent`, supplying `Default::default()` wherever a field has no value. It is a zero-sized marker with one impl per source marker, each describing how a field in that state becomes a present value: + +```rust +pub struct TransformMapDefault; + +impl TransformMap for TransformMapDefault { + fn transform_mapped(value: T) -> T { value } +} + +impl TransformMap for TransformMapDefault { + fn transform_mapped(_value: ()) -> T { T::default() } +} + +impl TransformMap for TransformMapDefault { + fn transform_mapped(value: Option) -> T { value.unwrap_or_default() } +} +``` + +A field that is already present passes through unchanged; a field that is `IsNothing` (never set) becomes its type's default; and a field that is `IsOptional` becomes the contained value or, when empty, the default. Because all three target `IsPresent`, applying this transform across a record leaves every field present, which is precisely the configuration `FinalizeBuild` accepts. + +### `CanFinalizeWithDefault` — finalize, defaulting whatever is unset + +`CanFinalizeWithDefault` finalizes a partial record into its target struct, defaulting any field that is not present. It is implemented for any partial value whose fields can be transformed by `TransformMapDefault` into the all-present configuration that `FinalizeBuild` then consumes: + +```rust +pub trait CanFinalizeWithDefault { + type Output; + fn finalize_with_default(self) -> Self::Output; +} + +impl CanFinalizeWithDefault for Builder +where + Builder: TransformMapFields, + Builder::Output: FinalizeBuild, +{ + type Output = Output; + fn finalize_with_default(self) -> Output { + self.transform_map_fields().finalize_build() + } +} +``` + +The body is the layer's core motion: `transform_map_fields` walks the record and applies `TransformMapDefault` to every field, producing an all-`IsPresent` partial value, and `finalize_build` turns that into the concrete struct. The strict presence check still applies — but it always succeeds, because the transform guarantees presence before `finalize_build` is reached. + +### `CanBuildWithDefault` — build from a source, defaulting the rest + +`CanBuildWithDefault` constructs the target struct by copying whatever fields a `Source` record shares with it and defaulting every remaining field. It chains the core [`CanBuildFrom`](has_builder.md) copy step into a defaulted finalize: + +```rust +pub trait CanBuildWithDefault { + fn build_with_default(source: Source) -> Self; +} + +impl CanBuildWithDefault for Target +where + Target: HasBuilder, + Builder: CanBuildFrom, + Builder::Output: CanFinalizeWithDefault, +{ + fn build_with_default(source: Source) -> Target { + Target::builder().build_from(source).finalize_with_default() + } +} +``` + +The pipeline reads top to bottom: start an empty builder for the target with [`HasBuilder`](has_builder.md), copy across every field the source and target have in common with `build_from`, then finalize with defaults for the fields the source did not supply. This is the field-level "widening cast" — turning a `Point2d` into a `Point3d` whose extra `z` is `0`, for instance — without naming any field explicitly. + +### `ToOptional` and `TransformOptional` — re-wrap every field as `Option` + +`ToOptional` converts a partial record so every field is wrapped in `IsOptional`, and `TransformOptional` is the `TransformMap` that performs the per-field conversion. Where `TransformMapDefault` targets `IsPresent`, `TransformOptional` targets `IsOptional`, mapping a present value to `Some` and an absent field to `None`: + +```rust +pub struct TransformOptional; + +impl TransformMap for TransformOptional { + fn transform_mapped(value: T) -> Option { Some(value) } +} + +impl TransformMap for TransformOptional { + fn transform_mapped(_value: ()) -> Option { None } +} + +pub trait ToOptional { + type Output; + fn to_optional(self) -> Self::Output; +} + +impl ToOptional for Context +where + Context: TransformMapFields, +{ + type Output = Context::Output; + fn to_optional(self) -> Self::Output { self.transform_map_fields() } +} +``` + +After `to_optional`, the partial type's every field marker is `IsOptional`, so each field's storage is an `Option`. A field already set becomes `Some`, an unset field becomes `None`, and from then on every field can be assigned or reassigned freely, because an `IsOptional` slot can always be overwritten. + +### `HasOptionalBuilder` — start an all-optional builder + +`HasOptionalBuilder` is the entry point that hands back a fresh builder in which every field is already optional. It composes `HasBuilder::builder()` with `ToOptional`, so the resulting builder starts with every field `None`: + +```rust +pub trait HasOptionalBuilder { + type Builder; + fn optional_builder() -> Self::Builder; +} + +impl HasOptionalBuilder for Context +where + Context: HasBuilder, + Context::Builder: ToOptional, +{ + type Builder = Builder; + fn optional_builder() -> Self::Builder { Self::builder().to_optional() } +} +``` + +This is the usual starting point for the optional-field workflow: `Context::optional_builder()` gives a builder whose fields can be set in any order and any number of times, deferring the decision about which fields must ultimately be present until finalization. + +### `SetOptional` — set or replace an optional field + +`SetOptional` sets the value of an optional field, optionally returning whatever value it replaced. It is implemented for any context whose `Tag` field is in the `IsOptional` state and stays there after the update: + +```rust +pub trait SetOptional { + type Value; + fn set(self, _tag: PhantomData, value: Self::Value) -> Self; + fn set_optional( + self, + _tag: PhantomData, + value: Self::Value, + ) -> (Option, Self); +} + +impl SetOptional for Context +where + Context: UpdateField, +{ + type Value = Context::Value; + fn set(self, tag, value) -> Self { self.set_optional(tag, value).1 } + fn set_optional(self, tag, value) -> (Option, Self) { + self.update_field(tag, Some(value)) + } +} +``` + +Both methods reduce to a single `UpdateField` call that writes `Some(value)` into the `IsOptional` slot. The crucial detail is that the field's marker is `IsOptional` both before and after — the `Mapper = IsOptional, Output = Context` bounds keep the builder's type unchanged — so a field can be set repeatedly, and `set_optional` returns the previous `Option` while `set` discards it. This is what makes the optional builder freely mutable, in contrast to the core `build_field` that consumes an absent slot exactly once. + +### `FinalizeOptional` — finalize, erroring on a genuinely missing field + +`FinalizeOptional` finalizes an optional builder into its concrete struct, succeeding only if every field actually holds a value and returning an error naming the first missing field otherwise. Unlike `CanFinalizeWithDefault`, it does not substitute defaults — absence is a recoverable runtime error rather than a silent fill: + +```rust +pub trait FinalizeOptional: PartialData { + fn finalize_optional(self) -> Result; +} +``` + +The implementation walks the target's [`HasFields`](has_fields.md) spine field by field. For each field it pulls the `Option` out of the `IsOptional` slot with `UpdateField`, and if the value is present it writes it back as `IsPresent` with `BuildField`; if the value is `None` it returns `Err(Tag::VALUE)` — the field's name as a static string — without finalizing. Only when every field has yielded a value does it call `finalize_build` on the now all-present partial value and return `Ok`. The error type `&'static str` is the missing field's own name, so a caller learns exactly which field was left unset. + +## Behavior + +Two finalization strategies sit on top of the same optional builder, differing only in how they treat a field that was never set. After `optional_builder` and a series of `set` calls, calling `finalize_with_default` completes the record by defaulting every unset field, whereas calling `finalize_optional` completes it only if nothing is missing and otherwise reports the missing field by name. The choice is made at the finalize call site, not when the builder is created, so the same builder value can be finalized either way depending on whether absence should be tolerated. + +The defaulted path and the optional path reuse the identical `transform_map_fields` recursion with different markers, which is why their behavior is so symmetric. `CanFinalizeWithDefault` drives `TransformMapDefault` toward `IsPresent`; `ToOptional` drives `TransformOptional` toward `IsOptional`. Both walk the same field spine, both rebuild the partial type one field at a time through `UpdateField`, and neither changes a value's runtime layout beyond wrapping or unwrapping an `Option` or substituting a default. The strict, all-present `FinalizeBuild` from core remains the only way a partial value becomes a concrete struct; these extensions simply guarantee the all-present configuration is reached before it is invoked. + +## Examples + +The optional-field workflow starts an all-optional builder, sets fields freely, and finalizes with one of the two strategies. Setting a field that already holds a value reports the replaced value, and finalizing with defaults fills whatever was never set: + +```rust +use cgp::prelude::*; +use cgp::extra::field::impls::{ + CanFinalizeWithDefault, FinalizeOptional, HasOptionalBuilder, SetOptional, +}; + +#[derive(CgpData)] +pub struct Context { + pub foo: String, + pub bar: u64, +} + +// Every field present: finalize_optional succeeds. +let builder = Context::optional_builder() + .set(PhantomData::, "foo".to_owned()) + .set(PhantomData::, 42); + +let (replaced, builder) = + builder.set_optional(PhantomData::, "bar".to_owned()); +assert_eq!(replaced, Some("foo".to_owned())); // the previous value comes back + +let context = builder.finalize_optional().unwrap(); +assert_eq!(context.foo, "bar"); +assert_eq!(context.bar, 42); + +// bar left unset: finalize_with_default fills it from Default. +let context = Context::optional_builder() + .set(PhantomData::, "foo".to_owned()) + .finalize_with_default(); +assert_eq!(context.foo, "foo"); +assert_eq!(context.bar, 0); +``` + +Had the second case used `finalize_optional` instead, it would have returned `Err("bar")` because `bar` was never set, rather than defaulting it to `0`. + +The defaulted-build path constructs a wider record from a narrower one in a single call, defaulting the fields the source lacks: + +```rust +use cgp::prelude::*; +use cgp::extra::field::impls::CanBuildWithDefault; + +#[derive(Debug, Clone, Eq, PartialEq, CgpData)] +struct Point2d { x: u64, y: u64 } + +#[derive(Debug, Clone, Eq, PartialEq, CgpData)] +struct Point3d { x: u64, y: u64, z: u64 } + +let point_2d = Point2d { x: 1, y: 2 }; +let point_3d = Point3d::build_with_default(point_2d); +assert_eq!(point_3d, Point3d { x: 1, y: 2, z: 0 }); // z defaulted +``` + +`build_with_default` copies `x` and `y` from the source through `build_from`, then defaults the unmatched `z` to `0` during the defaulted finalize. + +## Related constructs + +These extensions layer directly on the core builder family in [`HasBuilder`](has_builder.md): `CanBuildWithDefault` chains its `CanBuildFrom` copy step and `HasBuilder` entry point, and every finalize path ultimately calls `FinalizeBuild`. They are driven by the [`MapType`](map_type.md) machinery — `TransformMapDefault` and `TransformOptional` are `TransformMap` natural transformations, and `TransformMapFields` is the recursion that applies them across a whole record, re-marking each field's `IsPresent`/`IsNothing`/`IsOptional` state. `SetOptional` and `FinalizeOptional` reduce to the same `UpdateField` primitive used by the core `BuildField` and `TakeField`. The partial types these operate on are generated by [`#[derive(BuildField)]`](../derives/derive_build_field.md) (also exposed through `#[derive(CgpData)]`). The enum-side counterpart that takes fields apart variant by variant is [`ExtractField`](extract_field.md). + +## Source + +The extensions are defined in `cgp-field-extra`: `CanBuildWithDefault`, `CanFinalizeWithDefault`, and `TransformMapDefault` in [crates/extra/cgp-field-extra/src/impls/build_default.rs](../../../crates/extra/cgp-field-extra/src/impls/build_default.rs); `FinalizeOptional` in [crates/extra/cgp-field-extra/src/impls/finalize_optional.rs](../../../crates/extra/cgp-field-extra/src/impls/finalize_optional.rs); `SetOptional` in [crates/extra/cgp-field-extra/src/impls/set_optional.rs](../../../crates/extra/cgp-field-extra/src/impls/set_optional.rs); and `HasOptionalBuilder`, `ToOptional`, and `TransformOptional` in [crates/extra/cgp-field-extra/src/impls/to_optional.rs](../../../crates/extra/cgp-field-extra/src/impls/to_optional.rs). They build on the core traits in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/) (`UpdateField`, `BuildField`, `FinalizeBuild`, `PartialData`, `HasFields`, `TransformMap`, `TransformMapFields`) and the `MapType` markers in [crates/core/cgp-field/src/impls/map_type.rs](../../../crates/core/cgp-field/src/impls/map_type.rs). Behavioral tests are in [crates/tests/cgp-tests/tests/extensible_data_tests/records/optional.rs](../../../crates/tests/cgp-tests/tests/extensible_data_tests/records/optional.rs) and [point.rs](../../../crates/tests/cgp-tests/tests/extensible_data_tests/records/point.rs). diff --git a/docs/reference/traits/product_ops.md b/docs/reference/traits/product_ops.md new file mode 100644 index 00000000..c21ad253 --- /dev/null +++ b/docs/reference/traits/product_ops.md @@ -0,0 +1,112 @@ +# Product operations: `AppendProduct`, `ConcatProduct`, `MapFields` + +`AppendProduct`, `ConcatProduct`, and `MapFields` are type-level operations that transform [`Cons`](../types/cons.md)/`Nil` products (and, for `MapFields`, [`Either`](../types/either.md)/`Void` sums), letting generic code grow and reshape a context's field list without naming the concrete types. + +## Purpose + +These three traits are the list algebra that CGP's structural machinery is built from. A struct's whole shape is a type-level product of fields — a [`Cons`](../types/cons.md) spine terminated by `Nil`, produced by [`#[derive(HasFields)]`](../derives/derive_has_fields.md) — and an enum's shape is the analogous [`Either`](../types/either.md) sum terminated by `Void`. To process such a shape generically, code needs to compute new shapes from old ones at the type level: add one field to the end of a product, splice two products together, or rewrite every entry uniformly. `AppendProduct`, `ConcatProduct`, and `MapFields` are exactly those three computations, each expressed as a trait whose recursion over the spine the compiler evaluates during type checking. + +Because they operate purely on types, these operations carry no runtime cost and impose no ordering on the program — they are pure functions from type lists to type lists. They are the plumbing beneath higher-level constructs: building a record one field at a time appends to a product, merging two records concatenates them, and producing a partial-record representation maps a marker over every field. + +## Definition + +`AppendProduct` adds a single entry to the end of a product, exposing the extended product as its `Output`. It recurses down the [`Cons`](../types/cons.md) spine, rebuilding each node, until it reaches `Nil`, where it inserts the new `Cons`: + +```rust +pub trait AppendProduct { + type Output; +} + +impl AppendProduct for Nil { + type Output = Cons; +} + +impl AppendProduct for Cons +where + Tail: AppendProduct, +{ + type Output = Cons; +} +``` + +`ConcatProduct` splices a second product onto the end of the first. It has the same spine recursion, but at `Nil` it substitutes the entire `Items` list rather than a single element, so the result is the first product's entries followed by all of the second's: + +```rust +pub trait ConcatProduct { + type Output; +} + +impl ConcatProduct for Nil { + type Output = Items; +} + +impl ConcatProduct for Cons +where + Tail: ConcatProduct, +{ + type Output = Cons; +} +``` + +`MapFields` rewrites every entry of a list through a [`MapType`](map_type.md) marker, exposing the rewritten list as `Mapped`. Unlike the other two, it is defined over both spines: for the product spine it walks `Cons`/`Nil`, and for the sum spine it walks `Either`/`Void`, in each case applying `Mapper::Map` to the head and recursing on the tail: + +```rust +pub trait MapFields { + type Mapped; +} + +impl MapFields for Cons +where + Mapper: MapType, + Rest: MapFields, +{ + type Mapped = Cons, Rest::Mapped>; +} + +impl MapFields for Nil { + type Mapped = Nil; +} +``` + +The `Either`/`Void` impls mirror these exactly, replacing `Cons` with `Either` and `Nil` with `Void`, so the same `Mapper` applies uniformly whether the shape is a record or a tagged union. + +## Behavior + +The defining behavior of all three is that they are evaluated at compile time by the trait resolver walking the spine. `AppendProduct` and `ConcatProduct` preserve every existing entry and differ only in what they graft on at the `Nil` terminator — one element versus a whole list — which makes append a special case of concat with a single-element tail. Neither touches the values' types beyond reordering them into a longer list. + +`MapFields` is the transforming operation: it leaves the spine's length and shape unchanged but replaces each entry type `T` with `Mapper::Map`. With `IsPresent` it is the identity; with `IsNothing` it collapses every entry to the unit type; with `IsOptional` it wraps every entry in `Option`. This is how a concrete product of field values becomes the field list of a partial-record representation, where each field is independently marked. Because `MapFields` covers both spines, the same marker turns a struct's product into a partial struct and an enum's sum into a partial enum. + +## Examples + +These operations underlie the extensible-record machinery, so they are most visible through its higher-level interface, but they can be exercised directly on type-level lists. Appending and concatenating compute new product types: + +```rust +use cgp::prelude::*; + +type Base = Product![Field]; + +// AppendProduct adds one field to the end +type WithPort = >>::Output; +// = Product![Field, Field] + +// ConcatProduct splices a whole product onto the end +type Extra = Product![Field]; +type Full = >::Output; +// = Product![host, port, tls] +``` + +`MapFields` rewrites every entry uniformly. Applying `IsOptional` turns a product of plain values into a product of optionals, the shape a partial builder uses to track which fields are not yet filled: + +```rust +type Fields = Product![String, u16, bool]; +type Optional = >::Mapped; +// = Product![Option, Option, Option] +``` + +## Related constructs + +These operations act on the [`Cons`](../types/cons.md)/`Nil` product spine and, for `MapFields`, the [`Either`](../types/either.md)/`Void` sum spine — the two type-level lists that [`#[derive(HasFields)]`](../derives/derive_has_fields.md) produces from structs and enums. The convenient surface syntax for those lists is the [`Product!`](../macros/product.md) macro. `MapFields` applies a [`MapType`](map_type.md) marker to every entry, which is the same per-field state vocabulary that the builder family in [`HasBuilder`](has_builder.md) and the extractor family in [`ExtractField`](extract_field.md) use to track presence; `AppendProduct` and `ConcatProduct` are the list-growing operations behind assembling and merging records. + +## Source + +`AppendProduct` is defined in [crates/core/cgp-field/src/traits/append_product.rs](../../../crates/core/cgp-field/src/traits/append_product.rs), `ConcatProduct` in [crates/core/cgp-field/src/traits/concat_product.rs](../../../crates/core/cgp-field/src/traits/concat_product.rs), and `MapFields` in [crates/core/cgp-field/src/traits/map_fields.rs](../../../crates/core/cgp-field/src/traits/map_fields.rs). The [`MapType`](map_type.md) markers `MapFields` applies are in [crates/core/cgp-field/src/impls/map_type.rs](../../../crates/core/cgp-field/src/impls/map_type.rs), and the [`Cons`](../types/cons.md)/`Nil`/[`Either`](../types/either.md)/`Void` building blocks are under [crates/core/cgp-field/src/types/](../../../crates/core/cgp-field/src/types/). These operations are exercised end-to-end by the extensible-data tests under [crates/tests/cgp-tests/tests/extensible_data_tests/](../../../crates/tests/cgp-tests/tests/extensible_data_tests/). diff --git a/docs/reference/traits/static_format.md b/docs/reference/traits/static_format.md new file mode 100644 index 00000000..ac94caf6 --- /dev/null +++ b/docs/reference/traits/static_format.md @@ -0,0 +1,95 @@ +# `StaticFormat`, `StaticString`, `ConcatPath` + +`StaticFormat`, `StaticString`, and `ConcatPath` turn CGP's type-level strings and paths back into runtime data — formatting a [`Chars`](../types/chars.md) chain character by character, decoding a [`Symbol!`](../macros/symbol.md) into a `&'static str` constant, and concatenating two type-level paths into one. + +## Purpose + +These traits close the loop between CGP's compile-time names and the runtime world. CGP encodes field and variant names as types — a [`Symbol!`](../macros/symbol.md) is a length plus a [`Chars`](../types/chars.md) list, one node per character — so that names can drive trait resolution. But a program eventually needs those names as ordinary strings: to print a field name in an error message, to build a key, or to render a navigation path. `StaticFormat` and `StaticString` are the two ways to recover the string, and `ConcatPath` is the corresponding type-level operation on paths. + +`StaticFormat` recovers the string lazily, by writing into a formatter; it is what backs the `Display` impl on `Symbol` and `Chars`, so a type-level string can be printed with `to_string()` or `{}`. `StaticString` recovers it eagerly, as a compile-time `&'static str` constant computed by const evaluation, so the decoded string is available wherever a `const` is needed and costs nothing at runtime. `ConcatPath` works one level up: a [`PathCons`](../types/path_cons.md) path is a type-level list of path segments, and joining two paths — the common operation when composing nested accessors — is splicing one such list onto another. + +## Definition + +`StaticFormat` is a trait with a single associated function that writes the type-level string into a `Formatter`. The implementor is the type-level string itself, so the function takes no `self` — there is no runtime value, only the type: + +```rust +pub trait StaticFormat { + fn fmt(f: &mut Formatter<'_>) -> Result<(), fmt::Error>; +} +``` + +It is implemented by recursion over the [`Chars`](../types/chars.md) spine. Each `Chars` writes its own `CHAR` and then defers to `Tail`, and the terminating `Nil` writes nothing: + +```rust +impl StaticFormat for Chars +where + Tail: StaticFormat, +{ + fn fmt(f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{CHAR}")?; + Tail::fmt(f) + } +} + +impl StaticFormat for Nil { /* writes nothing */ } +``` + +`StaticString` exposes the decoded string as an associated constant rather than a formatting action: + +```rust +pub trait StaticString { + const VALUE: &'static str; +} +``` + +It is implemented for every type via a blanket impl over an internal `StaticBytes` trait. `Symbol` computes a `[u8; LEN]` byte array at const-evaluation time by walking the `Chars` list and UTF-8-encoding each character into the array, then `StaticString::VALUE` validates those bytes as UTF-8 and exposes the result as a `&'static str`. The `LEN` const parameter of the `Symbol` is the precomputed byte length that sizes this array, which is why `Symbol!` records a byte length rather than a character count. + +`ConcatPath` joins two type-level paths, exposing the joined path as `Output`. Both the input and output may be unsized, since path types are markers: + +```rust +pub trait ConcatPath { + type Output: ?Sized; +} +``` + +It recurses over the [`PathCons`](../types/path_cons.md) spine just as `ConcatProduct` does over a product: each `PathCons` keeps its `Head` and concatenates `Other` onto the `Tail`, and the terminating `Nil` becomes `Other` itself, so the result is the first path's segments followed by the second's. + +## Behavior + +`StaticFormat` and `StaticString` decode the same type-level string but differ in when and how. `StaticFormat` runs at the moment of formatting, recursing through the `Chars` nodes and emitting each character into the formatter; because both `Symbol` and `Chars` implement `Display` by delegating to it, any type-level string can be turned into an owned `String` or interpolated directly. `StaticString` instead produces the string once, in a `const` context, so `::VALUE` is a plain `&'static str` with no per-call work — preferable when the name is needed as a constant or in a hot path. Both faithfully round-trip multi-byte Unicode: the byte length in `Symbol` accounts for UTF-8 width, and the empty symbol decodes to `""`. + +`ConcatPath` is a pure type-level computation evaluated during trait resolution. It never touches values; it only names the combined path type, which a getter or accessor then uses to descend through nested fields. + +## Examples + +A type-level string can be recovered both ways. The `Display` route reconstructs it at runtime, and the `StaticString` route exposes it as a constant: + +```rust +use cgp::prelude::*; +use cgp::core::field::traits::StaticString; + +// via StaticFormat / Display +let s = ::default(); +assert_eq!(s.to_string(), "hello"); + +// via StaticString — a compile-time constant, multi-byte safe +assert_eq!(::VALUE, "世界你好"); +assert_eq!(::VALUE, ""); +``` + +`ConcatPath` composes two paths into one at the type level, the operation behind chaining nested accessors: + +```rust +type Outer = Path!(a.b); +type Inner = Path!(c.d); +type Joined = >::Output; +// the path a.b.c.d +``` + +## Related constructs + +`StaticFormat` and `StaticString` decode the [`Chars`](../types/chars.md) chain and [`Symbol`](../macros/symbol.md) wrapper that the [`Symbol!`](../macros/symbol.md) macro builds from a string literal — the type-level string at the heart of CGP's field naming. `ConcatPath` operates on the [`PathCons`](../types/path_cons.md) spine constructed by the [`Path!`](../macros/path.md) macro, and is the path-level analogue of the product-level `ConcatProduct`. Together they let the names that drive [`HasField`](has_field.md) lookups and nested-getter composition surface as ordinary strings and paths. + +## Source + +`StaticFormat` and its `Chars`/`Nil` impls are defined in [crates/core/cgp-base-types/src/traits/static_format.rs](../../../crates/core/cgp-base-types/src/traits/static_format.rs); the `Display` impls that delegate to it are on the [`Chars`](../types/chars.md) and [`Symbol`](../macros/symbol.md) types in [crates/core/cgp-base-types/src/types/](../../../crates/core/cgp-base-types/src/types/). `StaticString`, with its const-evaluated UTF-8 decoding, is in [crates/core/cgp-field/src/traits/static_string.rs](../../../crates/core/cgp-field/src/traits/static_string.rs). `ConcatPath` is in [crates/core/cgp-base-types/src/traits/concat_path.rs](../../../crates/core/cgp-base-types/src/traits/concat_path.rs), and `PathCons` in [crates/core/cgp-base-types/src/types/path.rs](../../../crates/core/cgp-base-types/src/types/path.rs). Tests covering display round-tripping and const decoding of multi-byte strings are in [crates/tests/cgp-tests/src/tests/symbol.rs](../../../crates/tests/cgp-tests/src/tests/symbol.rs). diff --git a/docs/reference/types/chars.md b/docs/reference/types/chars.md new file mode 100644 index 00000000..8da131e7 --- /dev/null +++ b/docs/reference/types/chars.md @@ -0,0 +1,74 @@ +# `Chars` and `Symbol` + +`Chars` is a type-level character list and `Symbol` wraps such a list together with the string's byte length — together they are CGP's encoding of a string as a type. + +## Purpose + +`Chars` and `Symbol` exist so that a string can be a *type* rather than a value, which is what lets a field name participate in trait resolution. CGP keys field access by a `Tag` type: to read a field called `name`, something must stand in for the string `"name"` at the type level so the compiler can match one `HasField` impl against another purely from the tag. A `Symbol` is that something — a unique type whose entire identity is the character sequence it encodes, so two symbols built from the same string are the same type and two built from different strings are different types. + +The reason the encoding is a *list of characters* rather than a single const value is a limitation in stable Rust: a `String` or `&str` cannot be used as a const-generic parameter, but a single `char` can. CGP works around this by spelling the string out one character at a time through a recursive `Chars` spine, the same way a heterogeneous list spells out its elements through a [`Cons`](cons.md)/[`Nil`](cons.md) spine. `Chars` is the specialized analogue of `Cons` in which the head is a `const char` rather than a type. + +These two types are almost never written by hand. They are produced by the [`Symbol!`](../macros/symbol.md) macro, which takes a string literal and folds it into the corresponding `Symbol`/`Chars`/`Nil` chain. This document describes the runtime types and the traits they carry; the construction syntax and the macro's right-fold expansion live in the `Symbol!` macro document. + +## Definition + +`Chars` is a zero-sized struct carrying one character as a const parameter and the rest of the string as its `Tail`: + +```rust +pub struct Chars(pub PhantomData); +``` + +The `Tail` is expected to be either the next `Chars` node or `Nil` to mark the end of the string, so a chain of `Chars` terminated by `Nil` is a type-level list of characters. `Chars` carries no runtime data — the character lives in the const parameter and the tail in a `PhantomData` — so the whole list is erased to a zero-sized value at runtime. + +`Symbol` wraps a `Chars` chain and records the string's byte length as a separate const parameter: + +```rust +pub struct Symbol(pub PhantomData); +``` + +The `Chars` type parameter is the head of the character list (despite the name, it is the whole chain, not a single node), and `LEN` is the string's byte length — the value of `str::len()`. The length is stored explicitly because stable Rust cannot compute the length of a `Chars` chain inside a const-generic context; baking it into the type lets length-dependent code read the size off the type directly instead of recursing through the list. Because `LEN` is the *byte* length, a four-character string of multi-byte scalars such as `Symbol!("世界你好")` records `12`, while its character list has one `Chars` node per Unicode scalar value. + +## Behavior + +Both types reconstruct their original string on demand through the [`StaticFormat`](../traits/static_format.md) trait, which formats a type-level string into a `Formatter` without needing a value. `Chars` implements `StaticFormat` by writing `CHAR` and then recursing into `Tail::fmt`, and `Nil` terminates the recursion by writing nothing; `Symbol` forwards to its inner `Chars`. Each type also has a `Display` impl that defers to `StaticFormat`, so `::default().to_string()` yields `"hello"`. + +The other trait the `LEN` parameter enables is [`StaticString`](../traits/static_format.md), which exposes the string as a single `const VALUE: &'static str` rather than a formatting routine. Its blanket impl decodes the `Symbol`'s characters into a `[u8; LEN]` at compile time — this is the consumer for which `LEN` exists, since the byte buffer must be sized by a const. A reader needing the string as a value at runtime uses `Display`; a reader needing it as a const uses `StaticString::VALUE`. + +`Symbol` also implements `Default` unconditionally (it is a zero-sized marker, so the default is just the empty `PhantomData`), which is what allows a `Symbol!("…")` type to be materialized as a value where one is needed, such as the tag passed to `get_field`. + +## Examples + +A type-level string most often appears as the `Tag` in a [`HasField`](../traits/has_field.md) bound, where it names the field a provider reads: + +```rust +use cgp::prelude::*; + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasField, +{ + fn greet(&self) { + println!("Hello, {}!", self.get_field(PhantomData::)); + } +} +``` + +The same type can be constructed directly and inspected at runtime through its `Display` impl, which walks the `Chars` chain to rebuild the string: + +```rust +use cgp::prelude::*; + +let s = ::default(); +assert_eq!(s.to_string(), "hello"); +``` + +Because the encoding is a list, an empty string is `Symbol<0, Nil>` — a `Symbol` whose character list is just the terminator and whose recorded length is zero. + +## Related constructs + +`Chars` is the character-level specialization of the [`Cons`](cons.md)/`Nil` product spine: where `Cons` holds an arbitrary type as its head, `Chars` holds a `const char`, and both terminate in `Nil`. The pair is built by the [`Symbol!`](../macros/symbol.md) macro and consumed by [`StaticFormat`/`StaticString`](../traits/static_format.md) for display and const access. A `Symbol` is the field-name half of CGP's tagging scheme — its position counterpart for tuple fields is [`Index`](index.md) — and the tags it produces are matched by [`HasField`](../traits/has_field.md) and carried inside the [`Field`](field.md) entries of a struct's [`HasFields`](../traits/has_fields.md) representation. Symbols also appear as the lowercase segments of a type-level [`PathCons`](path_cons.md) path built by [`Path!`](../macros/path.md). + +## Source + +The runtime types are defined in [crates/core/cgp-base-types/src/types/chars.rs](../../../crates/core/cgp-base-types/src/types/chars.rs) (`Chars`) and [crates/core/cgp-base-types/src/types/symbol.rs](../../../crates/core/cgp-base-types/src/types/symbol.rs) (`Symbol`), with `Nil` in [crates/core/cgp-base-types/src/types/nil.rs](../../../crates/core/cgp-base-types/src/types/nil.rs). The `StaticFormat` impls that drive `Display` are in [crates/core/cgp-base-types/src/traits/static_format.rs](../../../crates/core/cgp-base-types/src/traits/static_format.rs), and the const-decoding `StaticString` impl that consumes `LEN` is in [crates/core/cgp-field/src/traits/static_string.rs](../../../crates/core/cgp-field/src/traits/static_string.rs). The constructing macro is [`Symbol!`](../macros/symbol.md). Tests covering display round-tripping and multi-byte strings are in [crates/tests/cgp-tests/src/tests/symbol.rs](../../../crates/tests/cgp-tests/src/tests/symbol.rs). diff --git a/docs/reference/types/cons.md b/docs/reference/types/cons.md new file mode 100644 index 00000000..3691e24f --- /dev/null +++ b/docs/reference/types/cons.md @@ -0,0 +1,74 @@ +# `Cons` and `Nil` + +`Cons` and `Nil` are the two cells of CGP's product spine — a recursive, right-nested type-level list that generic code folds over to handle any struct's fields one at a time. + +## Purpose + +`Cons` and `Nil` exist to represent an ordered sequence of types as a single type, so that a collection of fields can be reasoned about generically. A plain Rust tuple holds several things at once but cannot be taken apart by generic code element by element; a recursive list can. By pairing a head with the rest of the list and terminating with an empty marker, CGP builds an *anonymous product type* — a record-shaped value whose structure a provider can walk without knowing the concrete struct it came from. + +The product spine is what makes structural, field-by-field operations possible across every struct uniformly. Because a struct's fields are exposed as one list type through [`HasFields`](../traits/has_fields.md), a provider written once to recurse over `Cons`/`Nil` can iterate, transform, read, or rebuild *any* struct's fields. Each step handles the `Head`, then recurses into the `Tail`, until it reaches `Nil` and stops. This recursion over a fixed two-case shape — a pair or the empty list — is the mechanism behind builders, extractors, and field mappers alike. + +`Cons` and `Nil` are the constructible building blocks; the [`Product!`](../macros/product.md) macro is how a programmer writes a list of them. `Product![A, B, C]` is sugar for the right-nested `Cons` chain, and the value macro `product![a, b, c]` builds a matching value. The list elements are most often [`Field`](field.md) entries pairing a name with a value, so a struct's layout becomes a `Product!` of `Field` cells over this same spine. + +## Definition + +`Cons` is a tuple struct holding the first element and the rest of the list, and `Nil` is a unit struct marking the end: + +```rust +#[derive(Eq, PartialEq, Clone, Default, Debug)] +pub struct Cons(pub Head, pub Tail); + +#[derive(Eq, PartialEq, Clone, Default, Debug)] +pub struct Nil; +``` + +The `Head` parameter is the first element's type and the `Tail` parameter is the rest of the list — itself another `Cons` or, at the end, `Nil`. Both positional fields are public, so `Cons(head, tail)` constructs a cell and `.0`/`.1` reach its parts. `Nil` carries no data; used as a `Tail` it terminates the chain, and used on its own it is the empty list. Both derive `Eq`, `PartialEq`, `Clone`, `Default`, and `Debug`, so a list of values that themselves implement these traits inherits them structurally — equality compares head-to-head down the chain, and `Default` yields the all-defaults list. + +## Behavior + +A list of any length is a `Cons` chain ending in `Nil`, nested to the right. The type `Product![A, B, C]` is `Cons>>`, and the empty `Product![]` is just `Nil`. The corresponding value is built with the tuple-struct constructor, `Cons(a, Cons(b, Cons(c, Nil)))`, so a `product!` value is an ordinary owned value whose type is exactly what `Product!` produces over the same elements' types. Because `Cons` and `Nil` are real structs, nothing about the list is virtual or boxed; the whole structure is flat data laid out by nesting. + +Generic code consumes the spine by recursing on its two cases. A trait implemented for `Nil` supplies the base case — the empty list — and a blanket impl for `Cons` supplies the recursive step, typically constraining `Tail` to implement the same trait so the recursion bottoms out at `Nil`. This pairing of a `Nil` impl with a `Cons` impl is the standard shape for any operation that folds over a product, and it is how the field machinery processes a struct of arbitrary width without per-field code. + +## Examples + +The product spine appears most visibly as the `Fields` of a struct that derives [`HasFields`](../derives/derive_has_fields.md), where the [`Product!`](../macros/product.md) sugar hides the `Cons`/`Nil` chain: + +```rust +use cgp::prelude::*; + +#[derive(HasFields)] +pub struct Person { + pub name: String, + pub age: u8, +} + +// generated: +// impl HasFields for Person { +// type Fields = Product![ +// Field, +// Field, +// ]; +// // i.e. Cons, +// // Cons, Nil>> +// } +``` + +A standalone list type and a matching value can also be written directly, which expands to the nested `Cons` form: + +```rust +use cgp::prelude::*; + +type Row = Product![u32, String, bool]; +let row: Row = product![1, "hi".to_string(), true]; +// Row == Cons>> +// row == Cons(1, Cons("hi".to_string(), Cons(true, Nil))) +``` + +## Related constructs + +`Cons`/`Nil` are the product (record-like) spine; their sum (choice-like) counterpart is [`Either`/`Void`](./either.md), which shares the same right-nested shape but branches at each step and terminates in the uninhabited `Void` rather than the constructible `Nil`. A list of these cells is written with the [`Product!`](../macros/product.md) macro, and its elements are usually [`Field`](field.md) entries whose tags come from [`Symbol!`](../macros/symbol.md) or [`Index`](index.md). The whole list is what [`#[derive(HasFields)]`](../derives/derive_has_fields.md) assigns to a struct and what [`HasFields`](../traits/has_fields.md) exposes. The `Chars` list inside [`Symbol!`](../macros/symbol.md) is a specialized version of this same spine, with a `const char` head in place of a type. + +## Source + +`Cons` is defined in [crates/core/cgp-base-types/src/types/cons.rs](../../../crates/core/cgp-base-types/src/types/cons.rs) and `Nil` in [crates/core/cgp-base-types/src/types/nil.rs](../../../crates/core/cgp-base-types/src/types/nil.rs). The `Product!`/`product!` macros that fold elements onto this spine are driven by the constructs under [crates/macros/cgp-macro-core/src/types/product/](../../../crates/macros/cgp-macro-core/src/types/product/), and the `HasFields` machinery that recurses over it lives in [crates/core/cgp-field/src/traits/has_fields.rs](../../../crates/core/cgp-field/src/traits/has_fields.rs). diff --git a/docs/reference/types/either.md b/docs/reference/types/either.md new file mode 100644 index 00000000..744f4fa5 --- /dev/null +++ b/docs/reference/types/either.md @@ -0,0 +1,82 @@ +# `Either` and `Void` + +`Either` and `Void` are the two cells of CGP's sum spine — a recursive, right-nested type-level choice that generic code walks to handle any enum's variants one branch at a time. + +## Purpose + +`Either` and `Void` exist to represent a choice among several types as a single type, so that the variants of an enum can be reasoned about generically. Where the product spine ([`Cons`/`Nil`](./cons.md)) holds a value for *every* element at once, the sum spine holds a value for exactly *one* of its branches — a tagged union, or *anonymous sum type*. By branching at each step and terminating with an uninhabited marker, CGP builds a coproduct whose structure a provider can walk without knowing the concrete enum it came from. + +The sum spine is what makes structural, variant-by-variant operations possible across every enum uniformly. Because an enum's variants are exposed as one sum type through [`HasFields`](../traits/has_fields.md), a provider written once to recurse over the `Either`/`Void` branches can match, dispatch on, or construct *any* enum's variants. This is the basis for CGP's extensible-variant machinery: each variant is reached by walking the nested branches rather than by writing a hand-rolled `match` against a fixed enum. + +`Either` and `Void` are the building blocks; the [`Sum!`](../macros/sum.md) macro is how a programmer writes a chain of them. `Sum![A, B, C]` is sugar for the right-nested `Either` chain terminated by `Void`. The branches are most often [`Field`](field.md) entries pairing a variant name with its payload, so an enum's shape becomes a `Sum!` of `Field` branches over this spine. + +## Definition + +`Either` is a two-case enum selecting the head or deferring to the rest of the chain, and `Void` is an empty enum that can never be constructed: + +```rust +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum Either { + Left(Head), + Right(Tail), +} + +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum Void {} +``` + +The `Head` parameter is the type of the current branch and `Tail` is the rest of the chain — itself another `Either` or, at the end, `Void`. The `Left(Head)` case carries a value of the head type; the `Right(Tail)` case carries a value belonging somewhere further down the chain. `Void` has no variants, so it has no values: it is uninhabited. Both types derive `Eq`, `PartialEq`, `Debug`, and `Clone`, so a sum of values that implement these traits inherits them. + +## Behavior + +A sum of any width is an `Either` chain ending in `Void`, nested to the right. The type `Sum![A, B, C]` is `Either>>`, and the empty `Sum![]` is just `Void`. A value selects exactly one branch by how deep it sits: `Left(a)` is an `A`, `Right(Left(b))` is a `B`, and `Right(Right(Left(c)))` is a `C`. Reaching the `Void` position would mean the value matched none of the listed branches — which is impossible, because `Void` has no values, so the chain is closed off at its end. + +Generic code consumes the sum by recursing on its two cases, mirroring how it folds the product spine but branching instead of pairing. A `Left` is handled directly as the head; a `Right` defers to a trait impl on the `Tail`, recursing until a `Left` is found. The base case is the `Void` terminator, and here the difference from the product spine matters: a product ends in [`Nil`](cons.md), a constructible empty record, but a sum ends in the *uninhabited* `Void`, because an empty choice has no value to pick. `Void` is functionally the never type, used specifically to mark the end of a sum. + +The uninhabitedness of `Void` is load-bearing for the extractor machinery, where [`FinalizeExtract`](../traits/extract_field.md) relies on it to discharge the impossible remainder. After an extractor has tried every variant of a sum and matched none, the leftover value has type `Void` — a value that cannot exist. `FinalizeExtract for Void` turns that into any required type with an empty `match self {}`, since there are no cases to handle, so a fully-handled sum extraction is total at compile time with no unreachable runtime branch. A constructible terminator like `Nil` could not be discharged this way; only an uninhabited type lets the compiler accept the empty match. + +## Examples + +The sum spine appears most visibly as the `Fields` of an enum that derives [`HasFields`](../derives/derive_has_fields.md), where the [`Sum!`](../macros/sum.md) sugar hides the `Either`/`Void` chain: + +```rust +use cgp::prelude::*; + +#[derive(HasFields)] +pub enum Shape { + Circle(f64), + Rectangle { width: f64, height: f64 }, +} + +// generated (schematically): +// impl HasFields for Shape { +// type Fields = Sum![ +// Field, +// Field, +// Field, +// ]>, +// ]; +// // i.e. Either, +// // Either, Void>> +// } +``` + +A standalone sum type can also be written directly, and a value picks one branch by its nesting depth: + +```rust +use cgp::prelude::*; + +type Token = Sum![u32, String, bool]; +// Token == Either>> + +let t: Token = Either::Right(Either::Left("hi".to_string())); // the String branch +``` + +## Related constructs + +`Either`/`Void` are the sum (choice-like) spine; their product (record-like) counterpart is [`Cons`/`Nil`](./cons.md), which shares the same right-nested shape but pairs a head with the rest at each step and terminates in the constructible `Nil` rather than the uninhabited `Void`. A chain of these cells is written with the [`Sum!`](../macros/sum.md) macro, and its branches are usually [`Field`](field.md) entries whose tags come from [`Symbol!`](../macros/symbol.md). The whole chain is what [`#[derive(HasFields)]`](../derives/derive_has_fields.md) assigns to an enum and what [`HasFields`](../traits/has_fields.md) exposes, and `Void`'s uninhabitedness is what the `FinalizeExtract` part of the [extractor family](../traits/extract_field.md) depends on to close a total variant match. + +## Source + +`Either` and `Void` are both defined in [crates/core/cgp-field/src/types/sum.rs](../../../crates/core/cgp-field/src/types/sum.rs). The `Sum!` macro that folds element types onto this spine is the `SumType` construct in [crates/macros/cgp-macro-core/src/types/sum.rs](../../../crates/macros/cgp-macro-core/src/types/sum.rs). `FinalizeExtract for Void`, which discharges the uninhabited remainder of a variant extraction, lives in [crates/core/cgp-field/src/traits/extract_field.rs](../../../crates/core/cgp-field/src/traits/extract_field.rs), and the enum `HasFields` derive that emits a `Sum!` of `Field` branches is under [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/sum.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/sum.rs). diff --git a/docs/reference/types/field.md b/docs/reference/types/field.md new file mode 100644 index 00000000..f63f6eca --- /dev/null +++ b/docs/reference/types/field.md @@ -0,0 +1,73 @@ +# `Field` + +`Field` is the named-entry building block of CGP's structural data, pairing a `Value` with a type-level `Tag` that records the field's name without carrying any runtime cost. + +## Purpose + +`Field` exists so that a field's *name* and its *value* travel together as a single type, letting generic code know not just what a struct holds but what each piece is called. A bare [`Product!`](../macros/product.md) list such as `Product![String, u8]` records only the types and their order; it cannot tell `name: String` from any other `String`. Wrapping each element in a `Field` — `Field` — attaches the name as a phantom type, so the structural representation of a struct is self-describing: a provider walking the list can match on the tag to find the field it wants. + +The tag is carried as a phantom type precisely because the name is needed only at compile time, for trait resolution and dispatch, never at run time. A `Field` is therefore exactly as large as its `Value` — the `PhantomData` occupies no space — so encoding names this way costs nothing at run time while making field-by-field generic code possible. This is the same string-as-type trick that [`Symbol!`](../macros/symbol.md) provides for names and [`Index`](index.md) provides for tuple positions; `Field` is where that tag meets the value it labels. + +`Field` is the element type that fills both spines of structural data. In a product (a record), the [`HasFields`](../traits/has_fields.md) representation of a struct is a `Product!` of `Field` entries, one per field. In a sum (an enum), it is a [`Sum!`](../macros/sum.md) of `Field` entries, one per variant, where the `Value` is the variant's payload. The same `Field` shape names a field in a record and a variant in an enum. + +## Definition + +`Field` is a two-parameter struct holding the value alongside a phantom tag: + +```rust +pub struct Field { + pub value: Value, + pub phantom: PhantomData, +} +``` + +The `Tag` parameter is the type-level name of the field. It is a phantom — it appears only inside `PhantomData` and never in a runtime field — and is typically a type-level string such as `Symbol!("name")` for a named field or a type-level number such as `Index<0>` for a tuple-struct position. The `Value` parameter is the field's actual type, and `value` is the only data the struct stores. Aside from the tag, a `Field` is a thin wrapper around its `Value`. + +## Behavior + +A `Field` is built from a value with no tag argument, because the tag is fixed by the target type rather than passed in. The `From` impl constructs the wrapper directly, so `let f: Field = "Alice".to_string().into();` fills in `value` and sets `phantom` to `PhantomData`. The tag is inferred from the expected type, which is why generated `HasFields` code can build each entry with a plain `.into()`. + +The remaining trait impls all defer to the value and ignore the tag, so a `Field` behaves like its `Value` for comparison and printing. `Debug` forwards to the value's `Debug` (the tag is not shown), and `PartialEq`/`Eq` compare only the `value`, each gated on the corresponding bound on `Value`. Two `Field` values are equal when their values are equal; the tag is a compile-time matter and plays no part in these runtime operations. + +Because the tag lives only in `PhantomData`, a `Field` carries the name purely at the type level. Code that needs the name reads it from the `Tag` parameter through trait resolution — for example matching a `Field` against a `HasField` bound — rather than from any stored data. + +## Examples + +`Field` appears most often inside the `HasFields` representation that a derive generates, where each struct field becomes one entry tagged by its `Symbol!` name: + +```rust +use cgp::prelude::*; + +#[derive(HasFields)] +pub struct Person { + pub name: String, + pub age: u8, +} + +// generated: +// impl HasFields for Person { +// type Fields = Product![ +// Field, +// Field, +// ]; +// } +``` + +A single `Field` can also be constructed directly from its value, with the tag supplied by the type annotation: + +```rust +use cgp::prelude::*; + +let name: Field = "Alice".to_string().into(); +assert_eq!(name.value, "Alice"); +``` + +For a tuple-struct field, the tag is an [`Index`](index.md) rather than a `Symbol!`, so the same wrapper names a positional field: `Field, u32>`. + +## Related constructs + +`Field` is the element type of the two structural spines: it fills a [`Product!`](../macros/product.md) (built from `Cons`/`Nil`, see [`cons.md`](./cons.md)) for a record and a [`Sum!`](../macros/sum.md) (built from `Either`/`Void`, see [`either.md`](./either.md)) for an enum. Its `Tag` is produced by [`Symbol!`](../macros/symbol.md) for a named field and by [`Index`](index.md) for a tuple-struct position. The list of `Field` entries for a whole type is assigned by [`#[derive(HasFields)]`](../derives/derive_has_fields.md) and surfaced through the [`HasFields`](../traits/has_fields.md) trait, while single-field access against a matching tag is the job of [`HasField`](../traits/has_field.md), built per field by [`#[derive(HasField)]`](../derives/derive_has_field.md). + +## Source + +`Field` and its `From`, `Debug`, `PartialEq`, and `Eq` impls are defined in [crates/core/cgp-field/src/types/field.rs](../../../crates/core/cgp-field/src/types/field.rs). It is consumed by the field machinery: [`HasFields`](../traits/has_fields.md) in [crates/core/cgp-field/src/traits/has_fields.rs](../../../crates/core/cgp-field/src/traits/has_fields.rs), with the record/variant rebuilding traits in [crates/core/cgp-field/src/traits/from_fields.rs](../../../crates/core/cgp-field/src/traits/from_fields.rs) and [crates/core/cgp-field/src/traits/to_fields.rs](../../../crates/core/cgp-field/src/traits/to_fields.rs). The derive that emits `Product!`/`Sum!` lists of `Field` entries lives under [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/). diff --git a/docs/reference/types/index.md b/docs/reference/types/index.md new file mode 100644 index 00000000..7a5a3160 --- /dev/null +++ b/docs/reference/types/index.md @@ -0,0 +1,71 @@ +# `Index` + +`Index` encodes a `usize` at the type level, giving a tuple-struct field a type-level name based on its position the way [`Symbol!`](../macros/symbol.md) names a field by its string. + +## Purpose + +`Index` exists because CGP's getter mechanism keys every field by a *type* tag, and a tuple-struct field has no string name to turn into a [`Symbol!`](../macros/symbol.md) — it has only a position. To make positional fields participate in the same trait-resolution machinery as named fields, the position itself must become a type. `Index` is that type: it carries a `usize` as a const-generic parameter and nothing else, so `Index<0>`, `Index<1>`, and `Index<2>` are three distinct types standing in for the first, second, and third fields of a tuple struct. + +Encoding the position as a type is what lets positional field access resolve through traits. Because `Index<0>` is a type, a context can carry a `HasField>` impl for its first field and a `HasField>` impl for its second side by side, and the compiler selects the right one purely from the tag — exactly as it would for two differently-named `Symbol!` tags. `Index` is therefore the numeric counterpart to `Symbol!`: a field is keyed by a `Symbol!` when it has a name and by an `Index` when it has only a position. + +`Index` carries no runtime data of its own; it is a zero-sized marker. Its sole job is to make a number available at the type level, so it can appear as a [`HasField`](../traits/has_field.md) tag, as the `Tag` of a [`Field`](field.md) entry inside a tuple struct's [`HasFields`](../traits/has_fields.md) list, and inside `PhantomData` wherever a positional name is needed at compile time. + +## Definition + +`Index` is a zero-sized struct parameterized only by a const-generic `usize`: + +```rust +#[derive(Eq, PartialEq, Clone, Copy, Default)] +pub struct Index; +``` + +The single const parameter `I` is the position the type represents — `Index<0>` for the field at offset zero, and so on. The struct has no fields, so a value of `Index` carries no data; the number lives entirely in the type. The derived `Default`, `Clone`, and `Copy` make a value trivially available when one is needed (for instance as a `PhantomData`-free tag value), and `Eq`/`PartialEq` compare two values of the same `Index` as always equal, since there is nothing to differ. + +`Index` implements both `Display` and `Debug`, and both print the underlying number `I` — `Index<0>` displays as `0`. The number a tag stands for is therefore visible directly in formatted output and in compiler diagnostics, with no Greek-letter or other alias substituted for it. + +## Behavior + +A tuple struct keys each of its fields by `Index`, counting from zero, so the field at position `N` is read through the tag `Index`. When a tuple struct derives [`HasField`](../derives/derive_has_field.md), the generated impl uses `Index<0>` for the `.0` field, `Index<1>` for `.1`, and so on, mapping each `get_field(PhantomData::>)` call to the corresponding positional access. The same `Index` tags then appear as the `Tag` of each [`Field`](field.md) entry in the tuple struct's [`HasFields`](../traits/has_fields.md) representation, so generic code walking the field list reads positions where it would read `Symbol!` names for a named struct. + +Because `Index` is zero-sized and the position lives in the type, accessing a field by index resolves entirely at compile time: there is no array bound check and no runtime indexing. Selecting the wrong index is a type error, not a runtime panic, because `Index<5>` on a three-field struct simply has no matching `HasField` impl. + +## Examples + +When a tuple struct derives `HasField`, each positional field is tagged by an `Index`: + +```rust +use cgp::prelude::*; + +pub struct Pair(pub u32, pub String); + +// generated for the first field: +// impl HasField> for Pair { +// type Value = u32; +// fn get_field(&self, _tag: PhantomData>) -> &u32 { +// &self.0 +// } +// } +``` + +A field can then be read by supplying the `Index` tag, and the chosen position is fixed at compile time: + +```rust +use cgp::prelude::*; + +let pair = Pair(7, "hi".to_string()); +assert_eq!(*pair.get_field(PhantomData::>), 7); +``` + +The number an `Index` carries is also visible through its `Display` impl, which prints the position: + +```rust +assert_eq!(Index::<2>.to_string(), "2"); +``` + +## Related constructs + +`Index` is the field-position half of CGP's tagging scheme; [`Symbol!`](../macros/symbol.md) is the field-name half, used for named struct fields and enum variants. The tags it produces are consumed by [`HasField`](../traits/has_field.md) for single-field access — built per field by [`#[derive(HasField)]`](../derives/derive_has_field.md) — and appear as the `Tag` of [`Field`](field.md) entries inside the [`HasFields`](../traits/has_fields.md) list of a tuple struct. Both `Index` and `Symbol!` encode a primitive at the type level: `Index` a `usize`, `Symbol!` a string. + +## Source + +`Index` and its `Display` and `Debug` impls are defined in [crates/core/cgp-field/src/types/index.rs](../../../crates/core/cgp-field/src/types/index.rs). The `#[derive(HasField)]` codegen that tags tuple-struct fields with `Index` lives under [crates/macros/cgp-macro-core/src/types/cgp_data/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/), and the `HasField` trait it targets is in [crates/core/cgp-field/src/traits/has_field.rs](../../../crates/core/cgp-field/src/traits/has_field.rs). diff --git a/docs/reference/types/life.md b/docs/reference/types/life.md new file mode 100644 index 00000000..a5d84964 --- /dev/null +++ b/docs/reference/types/life.md @@ -0,0 +1,57 @@ +# `Life` + +`Life<'a>` is a zero-sized type that lifts a lifetime into a type, so that a lifetime parameter from a CGP trait can travel through machinery that only accepts types. + +## Purpose + +`Life` exists because CGP's wiring is parameterized by *types*, not lifetimes, yet a CGP trait may carry a lifetime generic of its own. The marker that surfaces a provider's dependencies, [`IsProviderFor`](../traits/is_provider_for.md), takes a tuple of the trait's generic parameters as a single type argument so that the compiler can match a provider against the exact instantiation it is asked for. A lifetime cannot appear directly in that tuple — a tuple is a type and its members must be types — so any lifetime parameter on the trait must first be turned into a type. `Life<'a>` is that conversion: it packages the lifetime `'a` as a concrete type that can sit alongside the trait's other type parameters. + +Without this lift, a consumer or provider trait that borrows — one declaring `fn get_reference(&self) -> &'a T` — could not record its `'a` in the dependency marker, and the wiring would be unable to distinguish one lifetime instantiation from another. `Life` lets the lifetime ride through `IsProviderFor` as `(Life<'a>, T)`, preserving it as part of the provider's identity while keeping the marker's argument a plain type. The macros insert `Life` automatically when generating provider traits for lifetime-carrying components; a user rarely writes it by hand but will see it in generated code and in compiler errors about provider resolution. + +## Definition + +`Life` is a tuple struct wrapping a single `PhantomData` over a raw pointer to a borrowed unit: + +```rust +pub struct Life<'a>(pub PhantomData<*mut &'a ()>); +``` + +The struct holds no runtime data — it is a zero-sized marker whose only job is to carry the lifetime `'a` in the type system. The choice of `PhantomData<*mut &'a ()>` for the phantom type is deliberate and controls how `Life<'a>` relates to other lifetimes under subtyping. A `*mut T` is *invariant* in `T`, so wrapping `&'a ()` behind a `*mut` makes `Life<'a>` invariant in `'a`: a `Life<'long>` is neither a subtype nor a supertype of a `Life<'short>`. Invariance is the correct choice here because the lifetime is being used as an exact identity in the dependency marker — two providers wired for different lifetimes must be treated as wired for genuinely different things, and a variant `Life` would let the compiler silently coerce one instantiation into another and pick the wrong provider. The raw pointer also keeps `Life<'a>` from carrying any auto-trait obligations tied to an actual borrow, since it does not own or reference a real value. + +## Behavior + +`Life` has no methods and implements no CGP traits of its own; its entire behavior is to occupy a type position. In a generated provider trait for a component with a lifetime, the lifetime is collected into the `IsProviderFor` argument tuple as `Life<'a>` so that the provider's dependency obligation reads the same way it would for any type parameter. The provider trait, its blanket forwarding impl, and the impls that satisfy it all agree on the same `(Life<'a>, T)` shape, which is what lets a borrowing component be wired and checked exactly like a non-borrowing one. + +## Examples + +`Life` appears in the wiring generated for a component whose consumer trait carries a lifetime. Given a borrowing getter component: + +```rust +use cgp::prelude::*; + +#[cgp_component(ReferenceGetter)] +pub trait HasReference<'a, T: 'a + ?Sized> { + fn get_reference(&self) -> &'a T; +} +``` + +the generated provider trait records the lifetime in its dependency marker through `Life`, so its `IsProviderFor` bound names the lifetime as the type `Life<'a>` rather than as a bare `'a`: + +```rust +// generated, in readable form: +// pub trait ReferenceGetter<'a, __Context__, T: 'a + ?Sized>: +// IsProviderFor, T)> +// { +// fn get_reference(__context__: &__Context__) -> &'a T; +// } +``` + +Every impl that wires this component — whether through `UseContext`, a `UseField` getter, or a hand-written provider — carries the same `(Life<'a>, T)` tuple, so the lifetime is preserved end to end through the resolution machinery. + +## Related constructs + +`Life` is consumed by [`IsProviderFor`](../traits/is_provider_for.md), whose final type argument is the tuple of a component's generic parameters and into which a lifetime parameter is lifted as `Life<'a>`. It is emitted by the component-defining macros, principally [`#[cgp_component]`](../macros/cgp_component.md), when a consumer trait declares a lifetime. Conceptually it sits alongside the other type-level markers CGP uses to make non-type things addressable in trait resolution — [`Index`](index.md) lifts a `usize` and [`Symbol`](chars.md) lifts a string the way `Life` lifts a lifetime. + +## Source + +The type is defined in [crates/core/cgp-field/src/types/life.rs](../../../crates/core/cgp-field/src/types/life.rs). The macro logic that wraps a trait's lifetime parameters in `Life` when building the `IsProviderFor` argument tuple is in [crates/macros/cgp-macro-core/src/functions/is_provider_params.rs](../../../crates/macros/cgp-macro-core/src/functions/is_provider_params.rs), with related placement in [crates/macros/cgp-macro-core/src/types/empty_struct.rs](../../../crates/macros/cgp-macro-core/src/types/empty_struct.rs) and [crates/macros/cgp-macro-core/src/types/cgp_provider/provider_impl_args.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_provider/provider_impl_args.rs). The generated wiring for a lifetime-carrying component is exercised by the snapshot tests in [crates/tests/cgp-tests/tests/component_tests/cgp_component/lifetime.rs](../../../crates/tests/cgp-tests/tests/component_tests/cgp_component/lifetime.rs). diff --git a/docs/reference/types/mref.md b/docs/reference/types/mref.md new file mode 100644 index 00000000..ce830f65 --- /dev/null +++ b/docs/reference/types/mref.md @@ -0,0 +1,60 @@ +# `MRef` + +`MRef<'a, T>` is a "maybe-reference" — an enum that holds either a borrow of a `T` or an owned `T` — so a getter can return whichever it has without committing every implementor to one or the other. + +## Purpose + +`MRef` exists to let a single getter signature accommodate both the context that already stores a value and the context that must produce one on the fly. A getter that returns `&'a T` forces every context to keep a `T` it can lend out; a getter that returns `T` forces every context to hand over ownership, cloning even when it has a perfectly good reference to share. `MRef<'a, T>` removes that dilemma by being either case at runtime: a context with the value in a field returns `MRef::Ref` and lends it, while a context that computes or assembles the value returns `MRef::Owned` and gives it away. The caller treats both uniformly because `MRef` derefs to `T`. + +The type earns its keep in CGP's getter machinery, where the return type chosen for a getter method decides what body the macro generates. When a getter is declared to return `MRef<'a, T>` and takes `&self`, the generated field accessor wraps the borrowed field as `MRef::Ref(...)`, so the common case — reading a stored field — costs nothing extra, while the same interface still permits a provider elsewhere to return an owned value. This is what lets a getter abstract over "do I have this value, or do I make it?" without splitting into two traits. + +## Definition + +`MRef` is a two-variant enum parameterized by a lifetime and an element type: + +```rust +pub enum MRef<'a, T> { + Ref(&'a T), + Owned(T), +} +``` + +The `Ref` variant borrows a `T` for the lifetime `'a`; the `Owned` variant carries a `T` by value. The lifetime applies only to the borrowed case, so an `MRef` built from an owned value is effectively unbounded in `'a`. The enum is an ordinary owned value — there is nothing type-level about it — and it is the runtime payload a getter passes back to its caller. + +## Behavior + +`MRef` behaves like a smart pointer to `T`, which is what makes the two variants interchangeable at the call site. It implements `Deref` by matching on the variant and returning a `&T` either way, so `&*my_ref` and any method call that auto-derefs work regardless of which case is inside. It also implements `AsRef` over the same logic, giving an explicit `as_ref()` for code that prefers it. + +Constructing an `MRef` is frictionless because it implements `From` in both directions: `From` builds the `Owned` variant and `From<&'a T>` builds the `Ref` variant, so a value or a reference converts with `.into()`. When a caller needs to take ownership unconditionally, `get_or_clone` resolves the enum to a plain `T` — returning the owned value as is, or cloning the borrowed one — and is available whenever `T: Clone`. These three pieces — transparent `Deref`/`AsRef`, the two `From` impls, and `get_or_clone` — are the whole surface; a borrowed `MRef` is read cheaply and promoted to ownership only when explicitly asked. + +## Examples + +`MRef` is used as the return type of a getter that should work whether the context stores the value or produces it. The following getter reads a borrowed field and hands it back as a borrowing `MRef`: + +```rust +use cgp::prelude::*; + +let stored = String::from("hello"); + +// a context lending a stored value: +let borrowed: MRef<'_, String> = MRef::from(&stored); +assert_eq!(&*borrowed, "hello"); + +// a provider returning a freshly built value through the same type: +let made: MRef<'_, String> = MRef::from(String::from("world")); +assert_eq!(made.as_ref(), "world"); + +// promote either to an owned value when ownership is required: +let owned: String = borrowed.get_or_clone(); +assert_eq!(owned, "hello"); +``` + +Both `borrowed` and `made` have the same type and are consumed the same way; only the construction differs, and `get_or_clone` clones the borrowed case while moving the owned one. + +## Related constructs + +`MRef` is one of the getter return modes recognized by the field-getter macros: a getter declared to return `MRef<'a, T>` over `&self` generates a borrowing accessor, parallel to how returning `&T`, `Option<&T>`, or `&str` selects other accessor shapes. It is therefore commonly seen with [`#[cgp_getter]`](../macros/cgp_getter.md) and the [`HasField`](../traits/has_field.md) access it builds on, and with the [`UseField`](../providers/use_field.md) provider that wires those getters. Its lifetime is an ordinary borrow lifetime and is unrelated to the type-level lifetime lift [`Life`](life.md), which serves a different purpose in provider wiring. + +## Source + +The type is defined in [crates/core/cgp-field/src/types/mref.rs](../../../crates/core/cgp-field/src/types/mref.rs), including its `Deref`, `AsRef`, the two `From` impls, and `get_or_clone`. The macro logic that recognizes an `MRef<'a, T>` getter return type and emits an `MRef::Ref(...)` body is in [crates/macros/cgp-macro-core/src/functions/field/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/field/parse.rs) (the `MRef` field mode) and [crates/macros/cgp-macro-core/src/types/getter/get_field_with_mode_expr.rs](../../../crates/macros/cgp-macro-core/src/types/getter/get_field_with_mode_expr.rs). diff --git a/docs/reference/types/path_cons.md b/docs/reference/types/path_cons.md new file mode 100644 index 00000000..795b2715 --- /dev/null +++ b/docs/reference/types/path_cons.md @@ -0,0 +1,81 @@ +# `PathCons` + +`PathCons` is the type-level path spine — a recursive list of segments, where both the head and the tail may be unsized — that CGP uses to address an entry deep inside a delegation table. + +## Purpose + +`PathCons` exists to express a *route* through nested delegation tables as a single type. Where a bare component name picks one entry out of a context's table, CGP sometimes needs to point at an entry that lives behind one or more layers of indirection — inside a namespace, behind another namespace it inherits from, under a prefix. A path is the type that names such a route: a list of segments read left to right, each segment narrowing the lookup one step further. `PathCons` is the cons cell of that list, and [`Nil`](cons.md) terminates it, so `PathCons>` is the two-step path "first `A`, then `B`." + +A path is a distinct spine from the [`Cons`](cons.md) product list even though both are right-nested and `Nil`-terminated, and the difference is the unsized bound. `PathCons` declares `Head: ?Sized` and `Tail: ?Sized`, which lets a path segment be a trait object or other unsized type and lets the whole path be manipulated without requiring its parts to have a known size. A product list keys a struct's fields and its elements are always sized values; a path keys a lookup and its segments are pure type-level markers that never need to be `Sized`. + +The segments themselves are the same markers CGP uses elsewhere: a lowercase dotted name becomes a [`Symbol`](chars.md) type-level string, and a capitalized name becomes that named type (typically a component key such as `FooProviderComponent` or a namespace marker). A path is therefore an interleaving of symbols and component names — the form `@a.B.c` — assembled into a `PathCons` chain. Paths are written with the [`Path!`](../macros/path.md) macro rather than spelled by hand, so this document describes the runtime spine; the `@`-segment syntax and the expansion live in that macro's document. + +## Definition + +`PathCons` is a zero-sized struct holding two `PhantomData` markers, one for the head segment and one for the rest of the path: + +```rust +pub struct PathCons(pub PhantomData, pub PhantomData); +``` + +The `Head` is the first segment of the path and the `Tail` is the remainder, expected to be either another `PathCons` or `Nil` at the end. Both bounds are `?Sized` so that any type — sized or not — can occupy a segment. The struct carries no runtime data; like the other type-level building blocks it exists purely so that a route can be named and matched in trait resolution. + +## Behavior + +`PathCons` participates in path concatenation through the [`ConcatPath`](../traits/static_format.md) trait, which appends one path onto the end of another at the type level. The trait recurses down the spine: `PathCons` concatenates with `Other` by keeping `Head` and concatenating `Tail` with `Other`, while `Nil` concatenates with `Other` by simply becoming `Other`. The result is the expected behavior of list append, computed entirely as an associated-type projection: + +```rust +pub trait ConcatPath { + type Output: ?Sized; +} + +impl ConcatPath for PathCons +where + Tail: ConcatPath, +{ + type Output = PathCons>::Output>; +} + +impl ConcatPath for Nil { + type Output = Other; +} +``` + +Beyond concatenation, a `PathCons` path is consumed by [`RedirectLookup`](../providers/redirect_lookup.md), the provider that resolves a delegation by walking a context's table along a path. When a namespace or a prefixed component re-routes a lookup, it does so by producing a `RedirectLookup` whose `Path` is a `PathCons` chain; `RedirectLookup` follows the chain segment by segment until it lands on a concrete provider. The path itself never names a provider — it only describes where to look — so the same path can resolve to different providers depending on the table it is walked against. + +## Examples + +Paths are produced by the [`Path!`](../macros/path.md) macro and most often appear inside the wirings emitted by [`#[cgp_namespace]`](../macros/cgp_namespace.md). A namespace entry that redirects one component key to a path desugars into a `RedirectLookup` over a `PathCons` chain: + +```rust +use cgp::prelude::*; + +cgp_namespace! { + new MyNamespace { + FooProviderComponent => + @MyFooComponent, + } +} + +// the emitted entry, in readable form: +// impl<__Table__> MyNamespace<__Table__> for FooProviderComponent { +// type Delegate = RedirectLookup<__Table__, PathCons>; +// } +``` + +A path with both a lowercase symbol segment and a capitalized component segment interleaves the two marker kinds. Registering a component into a namespace under a prefix produces a two-segment path: + +```rust +// @MyBarComponent.BarProviderComponent expands to +// PathCons> +``` + +Here the lookup steps first through `MyBarComponent` and then through `BarProviderComponent` before resolving. A single-segment path is `PathCons`, and the empty path is `Nil` alone. + +## Related constructs + +`PathCons` is the routing counterpart to the product spine [`Cons`](cons.md)/`Nil`; it shares the right-nested, `Nil`-terminated shape but its segments are `?Sized` markers rather than sized field values. Its segments are [`Symbol`](chars.md) type-level strings (for lowercase names) and named component or namespace types (for capitalized names). Paths are built by the [`Path!`](../macros/path.md) macro, appended through [`ConcatPath`](../traits/static_format.md), and walked by [`RedirectLookup`](../providers/redirect_lookup.md) when resolving a delegation. They are produced throughout [`#[cgp_namespace]`](../macros/cgp_namespace.md), which uses them to reroute namespace entries and to register prefixed components. + +## Source + +The runtime type is defined in [crates/core/cgp-base-types/src/types/path.rs](../../../crates/core/cgp-base-types/src/types/path.rs) (`PathCons`), with `Nil` in [crates/core/cgp-base-types/src/types/nil.rs](../../../crates/core/cgp-base-types/src/types/nil.rs). The `ConcatPath` trait and its impls are in [crates/core/cgp-base-types/src/traits/concat_path.rs](../../../crates/core/cgp-base-types/src/traits/concat_path.rs). The constructing macro is [`Path!`](../macros/path.md) ([crates/macros/cgp-macro-lib/src/path.rs](../../../crates/macros/cgp-macro-lib/src/path.rs)), whose fold over the segments lives in [crates/macros/cgp-macro-core/src/types/path/unipath.rs](../../../crates/macros/cgp-macro-core/src/types/path/unipath.rs). `RedirectLookup`, which consumes a path at resolution time, is in [crates/core/cgp-component/src/providers/redirect_lookup.rs](../../../crates/core/cgp-component/src/providers/redirect_lookup.rs). diff --git a/docs/skills/README.md b/docs/skills/README.md new file mode 100644 index 00000000..7be5e6e8 --- /dev/null +++ b/docs/skills/README.md @@ -0,0 +1,13 @@ +# CGP Agent Skills + +This directory holds the [agent skills](https://agentskills.io/) built from the CGP knowledge base — self-contained guides an LLM coding agent loads to read, write, debug, and explain Context-Generic Programming code. Where the [reference/](../reference/README.md), [concepts/](../concepts/README.md), and [examples/](../examples/README.md) directories are the exhaustive, version-controlled record, a skill is the distilled working subset of that record: enough for an agent to become proficient without loading everything, organized for progressive disclosure so the agent reads only the parts a task needs. + +## How a skill differs from the rest of the knowledge base + +A skill is a *teaching artifact optimized for an agent's context window*, whereas a reference document is an *exhaustive per-construct record* and a concept document is a *cross-cutting overview*. The skill draws on all three but reproduces none of them wholesale — it carries the mental model, the common constructs, and worked examples in enough depth to act, and points to the online knowledge base for the corner cases it deliberately omits. The relationship is one-directional: the knowledge base is the source of truth, and the skill is a synthesis kept in sync with it. + +A skill is also deployed differently. It is copied out of this repository and run on its own, so it cannot rely on any file outside its own directory — every cross-link is either to a sibling sub-skill (a plain relative filename) or to the online knowledge base on GitHub. The authoring and synchronization rules that govern this, including how to keep a skill current when a construct changes, live in [../CLAUDE.md](../CLAUDE.md) under "The skills directory." + +## The catalog + +- [`cgp`](cgp/SKILL.md) — the foundational skill for working with CGP in Rust. Its `SKILL.md` establishes the paradigm (the consumer/provider trait split, wiring, impl-side dependencies) and routes to a set of topic sub-skills under [cgp/references/](cgp/references/) covering components, wiring, checking, functions and getters, abstract types, higher-order providers, error handling, handlers, extensible data, namespaces, the type-level primitives, and the modularity hierarchy. It is the skill `/cgp` resolves to. diff --git a/docs/skills/cgp/SKILL.md b/docs/skills/cgp/SKILL.md new file mode 100644 index 00000000..41f3afec --- /dev/null +++ b/docs/skills/cgp/SKILL.md @@ -0,0 +1,734 @@ +--- +name: cgp +description: >- + Read, write, debug, and explain Context-Generic Programming (CGP) code in Rust. + Use this skill whenever you encounter or are asked to work with CGP — any code that + uses `cgp::prelude::*`, the `#[cgp_component]`, `#[cgp_impl]`, `#[cgp_provider]`, + `#[cgp_fn]`, `#[cgp_type]`, `#[cgp_getter]`, `#[cgp_auto_getter]`, `#[cgp_computer]`, + `#[cgp_producer]`, or `#[cgp_namespace]` macros, the `delegate_components!`, + `check_components!`, or `delegate_and_check_components!` macros, the `Symbol!`, + `Product!`, `Sum!`, or `Path!` type-level macros, the `HasField`/`HasFields` traits or + their derives, providers such as `UseContext`/`UseDelegate`/`UseField`/`UseType`, the + handler family (`Computer`/`Producer`/`Handler`), or terms like consumer trait, + provider trait, provider, wiring, impl-side dependency, or context-generic. Trigger it + even when the user does not say "CGP" by name but is clearly working with these + constructs, when a Rust trait error mentions `IsProviderFor`/`DelegateComponent`, or + when someone wants modular, dependency-injected, multiple-implementation Rust traits. +--- + +# Context-Generic Programming (CGP) in Rust + +CGP is a modular programming paradigm for Rust that works around the language's coherence +restrictions, letting you write many overlapping or "orphan" trait implementations and then +choose which one applies by **wiring** them onto a concrete context type. This file is a +self-contained primer: read it top to bottom and you can read, write, debug, and explain the great +majority of CGP code without opening anything else. It is longer than a typical skill on purpose — +CGP's surface is broad, and the goal is that an agent who never opens a sub-skill still understands +the fundamentals. For exhaustive per-construct depth, the `references/` sub-skills (listed near the +end) go further; load one only when a task needs that depth. + +CGP is implemented almost entirely as procedural macros. Every macro desugars to ordinary Rust +traits and impls, so the reliable way to understand any construct is to know the code it expands +to. This primer shows those expansions where they matter and keeps them out of the way where they +do not. + +## The problem CGP solves + +Rust's coherence rules permit at most one implementation of a trait for a given type, and forbid +implementing a foreign trait for a foreign type (the orphan rule). This makes it hard to offer +several interchangeable implementations of one interface, or to let a downstream crate choose an +implementation for a type it does not own. CGP sidesteps both limits with a two-trait split: an +implementation is written against a **provider trait** whose `Self` is a dummy marker type the +crate owns, so coherence never blocks it, and a concrete **context** type later picks which +provider it uses through a small type-level table. The choice is local to the context, so two +different contexts can wire the same interface to different implementations. + +## Blanket traits and impl-side dependencies + +CGP grows out of blanket trait implementations (extension traits), and the single most important +idea to internalize is the **impl-side dependency**: a blanket impl can require constraints in its +`where` clause that are *not* part of the trait interface. Those hidden requirements are the +paradigm's form of dependency injection. For example: + +```rust +pub trait CanGreet { + fn greet(&self); +} + +pub trait HasName { + fn name(&self) -> &str; +} + +impl CanGreet for Context +where + Context: HasName, +{ + fn greet(&self) { + println!("Hello, {}!", self.name()); + } +} +``` + +`CanGreet` hides its `HasName` dependency inside the blanket impl, so a caller that is itself +generic does not have to forward `HasName` the way it would with a free generic function. In CGP +you typically start by writing generic logic as a blanket trait like this, and promote it to a +full CGP component later only when you need more than one implementation. Blanket traits are not +themselves CGP components, but the technique recurs throughout CGP. + +## The core vocabulary + +CGP code is readable once you hold five terms in mind. A **consumer trait** is the ordinary, +`self`-style trait you call (`CanGreet`, `CanCalculateArea`); it reads as a verb (`CanDoX`). A +**provider trait**, generated by `#[cgp_component]`, is the same interface with `Self` moved to an +explicit `Context` generic parameter (`Greeter`, `AreaCalculator`); it reads as +a noun (`SomethingDoer`, or `…Provider` when no noun fits). A **provider** is a zero-sized marker +struct (e.g. `GreetHello`, `RectangleArea`) that implements a provider trait — it carries no +runtime value and exists only as a name at the type level. **Wiring** is the act of telling a +context which provider implements each component, recorded in a type-level table. An +**impl-side dependency** is a `where`-clause constraint a provider needs but the consumer trait +does not expose. A **component** is the whole bundle a macro generates: consumer trait, provider +trait, and a `…Component` marker type that keys the wiring table. + +The duality is the crux: a consumer trait is what you *use*, a provider trait is what you +*implement*, and the macro-generated blanket impls connect them so that wiring a context to a +provider makes the context implement the consumer trait. When you see a provider trait method take +`context: &Context` where the consumer took `&self`, that is the same method — `self` became +`context`. + +A persistent source of confusion to avoid: inside a provider, `self`/`Self` (in `#[cgp_impl]`) or +the `context`/`Context` parameter (in the raw provider-trait form) always refer to the **context**, +never to the provider struct. The provider struct is a pure type-level name with no fields and no +runtime value — you cannot store state in it, and any attempt to read a "field" of a provider at +runtime is a mistake. + +## Reading CGP code on sight + +To follow most CGP code you only need to recognize a handful of shapes: + +- `#[cgp_component(Greeter)] trait CanGreet { fn greet(&self); }` defines a component. `CanGreet` + is the consumer trait you call; `Greeter` is the provider trait implementations target; + `GreeterComponent` is the wiring key. +- `#[cgp_impl(new GreetHello)] impl Greeter where Self: HasName { fn greet(&self) { … } }` writes a + provider named `GreetHello` for the `Greeter` component. Inside, `self`/`Self` mean the + *context*, and the `where` clause lists impl-side dependencies. +- `#[cgp_fn] fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { … }` + defines a single-implementation capability as a blanket-impl trait, pulling `width`/`height` from + the context's fields automatically. +- `delegate_components! { Person { GreeterComponent: GreetHello } }` wires the `Person` context: it + says "for the `Greeter` component, `Person` uses the `GreetHello` provider." After this, `Person` + implements `CanGreet`. +- `check_components! { Person { GreeterComponent } }` is a compile-time assertion that the wiring is + complete and all transitive dependencies are satisfied. + +A consumer trait can also be implemented directly on a context like any normal Rust trait +(`impl CanGreet for Person { … }`) — CGP traits are a superset of vanilla traits, and the macros +only save boilerplate. + +## The prelude and version + +Almost everything CGP exports comes through one import, which belongs at the top of every module +that uses CGP: + +```rust +use cgp::prelude::*; +``` + +This skill describes CGP **v0.7.0**. A few names are intentionally *not* in the prelude and must be +imported from their module — most notably the error-handling wiring keys and backends (see Error +handling below). Inside documentation code blocks you may omit the prelude import for brevity. + +--- + +# Components: the heart of CGP + +A component is what `#[cgp_component]` builds from one trait so that *using* a capability and +*implementing* it become separate, swappable things. Applying it to a consumer trait: + +```rust +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} +``` + +generates five items, of which you write or call only the first. The **consumer trait** `CanGreet` +is emitted unchanged — callers write `person.greet()`. The **provider trait** is the same interface +with `Self` moved to a leading `Context` parameter and `self` rewritten to `context`: + +```rust +pub trait Greeter: IsProviderFor { + fn greet(context: &Context); +} +``` + +The **component marker** `pub struct GreeterComponent;` is the zero-sized key into the wiring table. +Two **blanket impls** connect the sides: one makes any context that implements the provider trait +*for itself* automatically implement the consumer trait; the other lets a context that delegates +this component (via `DelegateComponent`) inherit the provider trait from whatever provider it +delegates to. You never write these blanket impls; they are the routing machinery, and it is enough +to think of wiring as a table lookup. Crucially, that lookup is resolved **entirely at compile +time**: the table is a set of trait impls, so the compiler picks the provider during type +resolution and monomorphizes the call to a direct, statically-dispatched one. CGP wiring is +therefore zero-cost — there is no runtime table, no dynamic dispatch, and no `vtable` in the +generated code, even though "table lookup" is a useful mental model. In real generated code the +context parameter is named `__Context__` and the provider parameter `__Provider__` (reserved +identifiers chosen so they never clash with your types); the names `Context`/`Provider` here are +for readability. + +## `IsProviderFor` and error messages + +`IsProviderFor` is an empty marker trait that rides as a supertrait on +every provider trait. Its only purpose is good error messages: a provider lists its dependencies in +a `where` clause, and the macros implement `IsProviderFor` for the provider under the *same* bounds, +so when a dependency is unmet the compiler can name the missing bound instead of vaguely saying "the +trait is not implemented." When you see an error that some provider does not implement +`IsProviderFor<…>`, read it as "the provider trait is not implemented, because the named dependency +is missing." You never write `IsProviderFor` yourself — the provider macros generate it. + +## Writing providers + +A provider can be written at three levels of sugar over the same machinery. **`#[cgp_impl]` is the +form to prefer**, because it lets you write the provider in consumer-style syntax — keeping `self`, +`Self`, and the consumer method signatures — and the macro rewrites it into the provider-trait +shape: + +```rust +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasName, +{ + fn greet(&self) { + println!("Hello, {}!", self.name()); + } +} +``` + +The provider name goes in the attribute argument; a leading `new` keyword also declares the +`struct GreetHello;`. You may omit `for Context` entirely (as above) and the macro inserts the +context parameter; if you write it explicitly it must be declared, as `impl Greeter for +Context`. Remember that `self`/`Self` here mean the context. `#[cgp_impl]` desugars to: + +```rust +#[cgp_new_provider] +impl Greeter for GreetHello +where + Context: HasName, +{ + fn greet(context: &Context) { + println!("Hello, {}!", context.name()); + } +} +``` + +The lower forms are what you mostly *read* rather than write. `#[cgp_provider]` is applied to a +provider-trait impl written directly on an existing provider struct; it passes the impl through and +auto-generates the matching `IsProviderFor` impl from the same `where` clause. +`#[cgp_new_provider]` is the same but also declares the provider struct (a generic provider gets a +`PhantomData` field over its parameters, e.g. `pub struct Multiply(PhantomData);`). +The attribute argument can override the component name, which otherwise defaults to the provider +trait's name plus `Component`. + +The provider's `where` clause is where **impl-side dependencies** live: `GreetHello` requires +`Self: HasName`, but `CanGreet` exposes no such bound, so a caller bounding on `CanGreet` never sees +`HasName`. The wiring satisfies each dependency by resolving it through the same context. + +A consumer trait is still an ordinary trait: when you don't need multiple implementations, write +`impl CanGreet for Person { … }` directly and skip the provider machinery entirely. + +--- + +# Wiring: connecting a context to providers + +Wiring records, on a context type, which provider supplies each component. The underlying mechanism +is the `DelegateComponent` trait — a type-level table whose key is the `…Component` marker and whose +`Delegate` associated type is the chosen provider — but you almost always write it through +`delegate_components!`: + +```rust +#[derive(HasField)] +pub struct Person { + pub name: String, +} + +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} +``` + +After this, `Person` implements `CanGreet` and `person.greet()` resolves through the table to +`GreetHello`. Swapping the table entry is the only change needed to swap behavior. + +The macro has a few shorthands. An **array key** maps several components to one provider: +`[FooComponent, BarComponent]: FooBarProvider`. A leading **`new`** keyword +(`delegate_components! { new MyComponents { … } }`) also defines `struct MyComponents;`, which is +handy for building reusable intermediary provider tables. A leading **generic list** +(`delegate_components! { MyContext { … } }`) wires a whole family of contexts at once. + +To understand what wiring *does*, picture the explicit version: `delegate_components!` is equivalent +to implementing the consumer trait by hand and forwarding to the provider — +`impl CanGreet for Person { fn greet(&self) { >::greet(self) } }`. +The macro just generates that plumbing (plus the `IsProviderFor` propagation) for you. + +## `UseContext` + +`UseContext` is a provider that implements a provider trait by routing back through the context's +*own* consumer-trait impl — the dual of the consumer blanket impl. Wiring a component to +`UseContext` means "use whatever this context already does for this trait," which is mainly useful +as the default inner provider of a higher-order provider (below). Delegating a component directly to +`UseContext` when the context's only implementation of that component *is* that delegation creates a +circular dependency and fails to compile. + +## Dispatching a generic-parameter component per type with `open` + +When a component is generic over a type parameter, you often want a different provider per value of +that parameter. The modern, preferred way is the **`open` statement** inside `delegate_components!`. +Given a component `CanCalculateArea` (provider `AreaCalculator`), a context dispatches per +shape like this: + +```rust +delegate_components! { + MyApp { + open { AreaCalculatorComponent }; + + @AreaCalculatorComponent.Rectangle: RectangleArea, + @AreaCalculatorComponent.Circle: CircleArea, + } +} +``` + +The `open { … };` header opens one or more components for per-value wiring and **must lead** the +block (it comes before any plain `Component: Provider` mappings, or the macro fails to parse). Each +`@Component.Key: Provider` entry then assigns a provider for one value of the dispatch parameter; a +brace group on the final segment shares one provider across several values +(`@AreaCalculatorComponent.{u32, u64, bool}: SomeProvider`), and a key may carry generics +(`@SomeComponent.<'a, T> &'a T: SomeProvider`). `open` works through the `RedirectLookup` impl that +every `#[cgp_component]` already generates, so it needs no extra attribute on the component. It does +not combine with a joined namespace (`#[prefix(...)]`); that is the full namespace feature. + +**Legacy form (read but don't write):** older code dispatches the same way by wrapping a nested +table in the `UseDelegate` provider — +`AreaCalculatorComponent: UseDelegate` +— generated by a `#[derive_delegate(UseDelegate)]` attribute on the component. This still +works and is common in existing code, but it is slated for deprecation; prefer `open` for new code. +Note that some CGP-shipped components (the error and handler families) are still *defined* with +`#[derive_delegate]` in the library, so you will see `UseDelegate` tables wiring them. + +--- + +# Checking: verifying wiring at compile time + +CGP wiring is **lazy**: defining a `delegate_components!` entry does not itself check that the +provider's transitive dependencies are satisfied. A missing dependency therefore surfaces only when +the consumer trait is finally used, often as a confusing error. To catch it early and clearly, assert +the wiring with a check: + +```rust +check_components! { + Person { + GreeterComponent, + } +} +``` + +This generates a check trait whose supertrait is `CanUseComponent` for +`Person`; if `Person` cannot actually use the component, the compiler reports the missing dependency +at this site, walking through `IsProviderFor` so the real cause (e.g. a missing +`HasField`) is named rather than hidden. For a component with generic parameters, +list the parameter after the component (`GreeterComponent: Rectangle`), group multiple parameters as +a tuple (`(Rectangle, f64)`), and use array syntax to check several at once. + +**Prefer `delegate_and_check_components!` for a context's main wiring** — it wires and checks in one +step, so every delegation is verified: + +```rust +delegate_and_check_components! { + Person { + GreeterComponent: GreetHello, + } +} +``` + +Its check trait is named `__CanUse{Context}` (vs. `__Check{Context}` for `check_components!`), so +both macros can appear once per module without clashing; override with `#[check_trait(Name)]`. When +the delegated component has generic parameters, add `#[check_params(...)]` on the entry; skip a +single entry's check with `#[skip_check]`. Use plain `delegate_components!` (no check) for +intermediary provider tables. Not every unsatisfied bound is a CGP component — some are ordinary or +blanket traits that `check_components!` cannot verify. + +--- + +# Functions and getters: the ergonomic surface + +Most basic CGP reads and writes values from the context, and the constructs here make that look like +plain Rust. + +## `HasField` and `#[derive(HasField)]` + +`HasField` is tag-keyed field access. The `Tag` is a type-level name: `Symbol!("width")` for a +named field or `Index<0>` for a tuple field. `#[derive(HasField)]` generates one impl per field: + +```rust +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} +// generates HasField and HasField +``` + +Field values are read with `self.get_field(PhantomData::)`; the `PhantomData` +carries the tag so type inference knows which field is meant. + +## `#[cgp_fn]` and `#[implicit]` arguments + +`#[cgp_fn]` turns one function into a single-implementation blanket-impl trait — the simplest entry +point to CGP. Arguments marked `#[implicit]` are removed from the signature and pulled from context +fields via `HasField`: + +```rust +#[cgp_fn] +fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} +``` + +This generates a `RectangleArea` trait (named from the function in PascalCase; override with +`#[cgp_fn(MyName)]`) with a blanket impl for any context that has `width: f64` and `height: f64` +fields. Implicit arguments get `.clone()` added automatically for owned values and `.as_str()` for +`&str`. Generic parameters on the function move to the trait and impl; the `where` clause becomes +impl-side dependencies on the impl only; generic *method* parameters are intentionally unsupported. +Prefer implicit arguments for basic code — they make CGP look like ordinary functions. + +## `#[uses]`, `#[extend]`, `#[extend_where]` + +`#[uses(TraitA, TraitB)]` (on `#[cgp_fn]` or `#[cgp_impl]`) imports `Self` trait bounds, read +like a `use` statement — it accepts only the simple `Trait` form (no associated-type +equality; write those as explicit `where` clauses): + +```rust +#[cgp_fn] +#[uses(RectangleArea)] +fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { + self.rectangle_area() * scale_factor * scale_factor +} +``` + +`#[extend(Trait)]` adds *supertrait* bounds to the generated trait — the only way to add supertraits +in `#[cgp_fn]` (whose `where` clauses are impl-side dependencies), and usable on `#[cgp_component]` +too. `#[extend_where(Bound)]` adds `where` clauses to the generated trait definition (`#[cgp_fn]` +only). + +## Getters: `#[cgp_auto_getter]`, `#[cgp_getter]`, `UseField` + +`#[cgp_auto_getter]` generates a blanket getter impl over `HasField`, with the field name taken from +the method name: + +```rust +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} +// blanket impl for any context with a `name` field; &str/&String shorthands handled +``` + +A single-getter trait may instead declare a local associated type used as the return type, inferred +from the field — `trait HasName { type Name; fn name(&self) -> &Self::Name; }`. + +`#[cgp_getter]` is like `#[cgp_component]` but also provides a `UseField` blanket impl, so the +getter's source field can be chosen by *wiring* rather than fixed to the method name. The +`UseField` provider implements a getter by reading the field named `Tag`, which may differ from +the method name: + +```rust +delegate_components! { + Person { + NameGetterComponent: UseField, + } +} +// Person::name() now returns the `first_name` field +``` + +`UseFieldRef` is the `AsRef`/`AsMut`-based variant. Any getter can also be implemented by hand — the +macros only save boilerplate. + +--- + +# Abstract types + +CGP abstracts over types with associated types in components. `#[cgp_type]` is the dedicated macro +(use it instead of `#[cgp_component]` for an abstract-type trait): + +```rust +#[cgp_type] +pub trait HasNameType { + type Name; +} +``` + +This defaults the provider name to the type name plus `TypeProvider` (here `NameTypeProvider`, marker +`NameTypeProviderComponent`) and additionally generates a `UseType` blanket impl. A context fixes the +type by wiring the component to `UseType`: + +```rust +delegate_components! { + Person { + NameTypeProviderComponent: UseType, + } +} +// or directly: impl HasNameType for Person { type Name = String; } +``` + +The direct impl is just as valid and shows that abstract types are ordinary associated-type traits. + +The `#[use_type(HasScalarType::Scalar)]` attribute is the recommended way to *use* an abstract type +inside `#[cgp_fn]`/`#[cgp_impl]`/`#[cgp_component]`: it rewrites bare `Scalar` to the fully-qualified +`::Scalar` everywhere and adds the supertrait/where bound, removing `Self::` +boilerplate and ambiguity. CGP's built-in abstract-type component is `HasType` (provider +`TypeProvider`). + +--- + +# Higher-order providers + +A **higher-order provider** takes another provider as a generic parameter and constrains it with a +provider-trait bound, so its inner behavior is chosen by wiring rather than fixed: + +```rust +#[cgp_impl(new ScaledAreaCalculator)] +#[use_provider(InnerCalculator: AreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + let base_area = InnerCalculator::area(self); + base_area * scale_factor * scale_factor + } +} +``` + +`#[use_provider(InnerCalculator: AreaCalculator)]` completes the inner provider's bound by adding the +`Self` parameter for you (you write `: AreaCalculator`, it means `AreaCalculator`) and moves it +into the `where` clause — that is the *only* thing the attribute does. Inside the body you still call +the provider explicitly with the associated-function form `InnerCalculator::area(self)`; there is no +call-site rewriting. A context then chooses the inner provider when wiring, e.g. +`AreaCalculatorComponent: ScaledAreaCalculator`. + +A higher-order provider often defaults its inner parameter to `UseContext` +(`pub struct IterSumArea(PhantomData);`), so that when no inner provider is +named, the inner step falls back to the context's own wiring. Not every provider with a generic +parameter is higher-order: a provider like `GetName` that uses `Tag` only as a `HasField` key +(no provider-trait bound) is not. + +When a component itself is generic (`#[cgp_component(AreaCalculator)] trait CanCalculateArea`), +the provider trait appends the parameters after the context (`AreaCalculator`), +`IsProviderFor` groups them into its `Params` tuple, and lifetimes are lifted into the `Life<'a>` +type. Such a component is most useful for **cross-context dependencies**: when the main target is a +generic parameter (e.g. `CanCalculateArea: HasScalarType`), individual shape types need not +implement shared capabilities — the common context supplies the shared abstract type, value-level +injection (a global scale factor via a getter), and lazy per-context provider binding, so two apps +can wire the same shape to different providers. + +--- + +# Error handling + +CGP makes the error type abstract so generic code can fail without naming a concrete error. +`HasErrorType` (an abstract-type component, `type Error: Debug`) gives a context one shared +`Self::Error`; `CanRaiseError` constructs it from a concrete source error +(`Context::raise_error(source)`); `CanWrapError` attaches detail. Both supertrait +`HasErrorType` and are associated-function (no `self`) components that dispatch per source/detail +type: + +```rust +#[cgp_component(Loader)] +pub trait CanLoad: HasErrorType { + fn load(&self, path: &str) -> Result; +} + +#[cgp_impl(new LoadOrFail)] +impl Loader +where + Self: CanRaiseError, +{ + fn load(&self, path: &str) -> Result { + if path.is_empty() { + return Err(Self::raise_error("empty path".to_owned())); + } + Ok(format!("contents of {path}")) + } +} +``` + +A context wires its error type and the raise/wrap behavior. The backend providers — `RaiseFrom` +(convert via `From`), `ReturnError`, `RaiseInfallible`, `PanicOnError`, `DebugError`/`DisplayError` +(format into a `String` and forward), `DiscardDetail` — plug in per source type, modern-style with +`open`: + +```rust +delegate_components! { + App { + open { ErrorRaiserComponent }; + + ErrorTypeProviderComponent: UseType, + @ErrorRaiserComponent.String: RaiseFrom, + @ErrorRaiserComponent.ParseError: DebugError, + } +} +``` + +**Imports:** `HasErrorType`, `CanRaiseError`, and `CanWrapError` come from the prelude, but the +wiring keys (`ErrorTypeProviderComponent`, `ErrorRaiserComponent`, `ErrorWrapperComponent`) live +under `cgp::core::error`, and the backend providers (`RaiseFrom`, `DebugError`, …) under +`cgp::extra::error` — import the specific names you wire. Standalone backends (`cgp-error-anyhow`, +`cgp-error-eyre`, `cgp-error-std`) provide ready error types. + +--- + +# Handlers: the computation family + +CGP models computation as a family of components along three axes — synchronous vs. async, +infallible vs. fallible, and input-taking vs. input-free: + +- **`Computer` / `CanCompute`** — a synchronous, infallible transform `compute(&self, PhantomData, input) -> Output`. By-reference (`ComputerRef`) and async (`AsyncComputer`) variants exist. +- **`TryComputer` / `CanTryCompute`** — the fallible computer. +- **`Producer` / `CanProduce`** — input-free production (only a context and a `Code` tag). +- **`Handler` / `CanHandle`** — the general **async, fallible, error-aware** computation; the workhorse for I/O and pipelines. It supertraits `HasErrorType`. +- **`CanRun` / `CanSendRun`** — task runners. + +`#[cgp_computer]` and `#[cgp_producer]` define a `Computer`/`Producer` provider from a function. +Providers compose through combinators: `PipeHandlers` chains handlers +left-to-right, `ComposeHandlers` nests them, `ReturnInput` passes input through, and the `Promote*` +adapters lift a simpler handler (e.g. a sync `Computer`) into a more capable one (an async +`Handler`). For example, a context wires a pipeline of field-reading computers: + +```rust +delegate_components! { + MyContext { + ComputerComponent: + PipeHandlers, + Add, + Multiply, + ]>, + } +} +// context.compute(PhantomData::<()>, 5) runs ((5*foo)+bar)*baz +``` + +Dispatch routes an extensible-data input to per-variant handlers: `#[cgp_auto_dispatch]` generates a +handler from a trait, and the combinators `MatchWithHandlers` / `MatchWithValueHandlers` / +`ExtractFieldAndHandle` match an enum's variants to sub-handlers, proving exhaustiveness without a +wildcard. Monadic handlers (`PipeMonadic`, `BindOk`, `BindErr`, the identity/ok/err monads) compose +handlers through a monad. The handler family is broad — read [handlers](references/handlers.md) for +the full set, including `Send`-bound recovery for async trait methods. + +--- + +# Extensible data + +CGP can build and read structs and enums generically, by their named fields and variants. The +`#[derive(HasFields)]` derive exposes a type's whole field list; `#[derive(CgpData)]` (and the +record/variant-specific `CgpRecord`/`CgpVariant`) derive the full extensible-data machinery: + +```rust +#[derive(CgpData)] +pub struct Person { + pub first_name: String, + pub last_name: String, +} +``` + +Records are built field-by-field through the builder family (`HasBuilder`, `BuildField`) — the +**extensible builder pattern** assembles a context from independent per-field outputs. Variants are +constructed with `FromVariant`, deconstructed with the `ExtractField` extractor family, and the +**extensible visitor pattern** handles each variant. The type-level spines underneath are the +product list (`Product![A, B, C]` over `Cons`/`Nil`) for records and the sum list (`Sum![A, B]` over +`Either`/`Void`) for variants. Structural **casts** convert between shapes — `CanUpcast` widens a +smaller enum into a larger one, `CanDowncast` narrows, `CanBuildFrom` rebuilds a record from a +superset. Dispatching extensible-data inputs to handlers uses the dispatch combinators above. + +--- + +# Namespaces + +Namespaces are reusable, inheritable wiring tables (a preset mechanism) that keep top-level wiring +short as component counts grow. `#[cgp_namespace]` groups components under a namespace; a context +inherits a namespace's wiring and can override entries. The underlying mechanism is the +`RedirectLookup` provider, which re-routes a component lookup along a type-level `Path!`; the `open` +statement above is a lightweight special case of it. `DefaultNamespace` resolves a default provider +when a context does not override one. Read [namespaces](references/namespaces.md) for the preset and +inheritance syntax. + +--- + +# Type-level primitives: a decoder ring + +CGP encodes lists, strings, and numbers as types. You mostly use the sugared macros and only need to +*recognize* the expanded forms in errors: + +- **`Symbol!("name")`** — a type-level string (field-name tag). Expands to `Symbol<4, Chars<'n', Chars<'a', Chars<'m', Chars<'e', Nil>>>>`; the compiler may show the Greek `ψ`/`ζ` shorthands. The leading length works around missing const-generics. +- **`Product![A, B, C]`** — a type-level list. Expands to `Cons>>` (Greek `π`/`ε`). `product![…]` is the value-level form. Used for field lists and handler pipelines. +- **`Sum![A, B]`** — a type-level sum (the dual of `Product!`), over the `Either`/`Void` spine. Used for enum variant lists. +- **`Index`** — a type-level natural number (Greek `δ`), tags tuple-struct fields. +- **`Field`** — a value paired with its type-level name tag. +- **`Path!`** / `PathCons` — a type-level path, used by namespaces and `RedirectLookup`. +- **`Life<'a>`** — a lifetime lifted into a type, used when a component has lifetime parameters. +- **`MRef`** — an owned-or-borrowed value. + +Prefer the sugar (`Symbol!`, `Product!`) and the readable names (`Cons`/`Nil`) over the Greek forms +in anything you write. + +--- + +# Sub-skills: where to go for more depth + +This primer covers the fundamentals. Each sub-skill below goes deeper on one area — exact +expansions, more corner cases, more worked examples. Load one when a task needs that depth. + +- **[references/components.md](references/components.md)** — `#[cgp_component]` internals, the blanket impls, `IsProviderFor`, and the three provider-writing macros in full. +- **[references/wiring.md](references/wiring.md)** — `DelegateComponent`, all `delegate_components!` forms, `open` dispatch, `UseContext`, and the legacy `UseDelegate` tables. +- **[references/checking.md](references/checking.md)** — check traits, `CanUseComponent`, every `check_components!`/`delegate_and_check_components!` option, and reading wiring errors. +- **[references/functions-and-getters.md](references/functions-and-getters.md)** — `HasField`, `#[cgp_fn]`, `#[implicit]`, `#[uses]`/`#[extend]`/`#[extend_where]`, `#[cgp_auto_getter]`, `#[cgp_getter]`, `UseField`. +- **[references/abstract-types.md](references/abstract-types.md)** — `#[cgp_type]`, `HasType`/`TypeProvider`, `UseType`, `#[use_type]`. +- **[references/higher-order-providers.md](references/higher-order-providers.md)** — the higher-order pattern, `#[use_provider]`, `UseContext` defaults, generic-parameter components, cross-context dependencies. +- **[references/error-handling.md](references/error-handling.md)** — `HasErrorType`, `CanRaiseError`/`CanWrapError`, the backend providers, and the imports. +- **[references/handlers.md](references/handlers.md)** — the full `Computer`/`Producer`/`Handler` family, combinators, dispatch, monadic handlers, `Send` recovery. +- **[references/extensible-data.md](references/extensible-data.md)** — the `CgpData` derives, builders/extractors, `Product!`/`Sum!`, casts, and the builder/visitor patterns. +- **[references/namespaces.md](references/namespaces.md)** — `#[cgp_namespace]`, `RedirectLookup`, `Path!`, default resolution. +- **[references/type-level-primitives.md](references/type-level-primitives.md)** — every type-level building block in detail. +- **[references/modularity-hierarchy.md](references/modularity-hierarchy.md)** — the spectrum from one-impl blanket traits to fully context-wired components, to help choose how much CGP a problem needs. + +## Exhaustive online reference + +For the exact macro expansion of any construct, every accepted syntax form, corner cases, or the +implementing source, consult the online knowledge base at +**https://github.com/contextgeneric/cgp/tree/main/docs** — its `reference/`, `concepts/`, and +`examples/` directories are the authoritative, exhaustive record. Fetch the relevant page when a +detail is not covered here; do not assume a local copy exists, since this skill is deployed on its +own. + +--- + +# Instructions for explaining CGP to users + +Assume by default that the user has only basic Rust experience and is new to CGP, but do not +over-explain: when code merely uses CGP concepts, write or modify it without lecturing, and add +explanation only when asked. When you do explain, assume unfamiliarity with advanced Rust (generics, +traits, blanket impls, coherence) and with functional/type-level programming — describe type-level +tables, lists, and strings through familiar analogies such as a map or a lookup table, and expand on +advanced Rust as needed. Keep the simplified picture front and center: present wiring as choosing a +table entry, and keep `IsProviderFor`, `DelegateComponent`, and generated blanket impls out of the +explanation unless the user is asking specifically about the internals. One caveat when you reach +for an analogy: the "table lookup" is resolved at compile time and compiles down to direct static +calls, so if you use a runtime-flavored analogy like a vtable, say explicitly that — unlike a real +vtable — CGP's resolution is static and zero-cost, with no runtime table or dynamic dispatch. Never +leave a reader thinking CGP wiring has runtime lookup overhead. + +When asked to explain a specific piece of code, look up the definitions it depends on before +answering. To explain a `delegate_components!` entry, find the consumer and provider traits behind +the component key and the body of the provider it maps to. To explain a provider, read its own +definition and the definitions of every capability in its `where` clause. To explain how a context +implements something, follow its wiring to see which providers are chosen and trace a method call +through them — for instance, if `NameGetterComponent` is wired to `UseField`, +then a `self.name()` call inside another provider returns the context's `first_name` field. diff --git a/docs/skills/cgp/references/abstract-types.md b/docs/skills/cgp/references/abstract-types.md new file mode 100644 index 00000000..7fdba373 --- /dev/null +++ b/docs/skills/cgp/references/abstract-types.md @@ -0,0 +1,158 @@ +# Abstract types + +An abstract type is a CGP trait carrying a single associated type that generic code names as `Self::Foo` without committing to a concrete type, leaving the concrete choice to wiring so it can differ from one context to another. + +## The idea + +An abstract type lets generic code name a type it does not fix. Instead of hard-coding `f64` or `String`, a trait declares one associated type — `trait HasNameType { type Name; }` — and code written against it refers to `Self::Name`, leaving the actual type open. The trait is the abstraction; the associated type is the slot a context fills in. This is the type-level analogue of an impl-side dependency: just as a getter lets a context supply a *value* a provider needs, an abstract-type trait lets a context supply a *type* a provider builds on. + +The payoff is the same one CGP gives for behavior. A provider written in terms of `Self::Name` works unchanged whether a context chooses `String`, `&'static str`, or a custom name type, and two contexts can make different choices from the same generic code. Under the hood an abstract type is nothing more than an ordinary Rust trait with one associated type — generic functions constrain `Context: HasNameType` and use `Context::Name` exactly as they would any associated type. The CGP machinery only makes declaring and wiring these traits cheap. + +## Direct implementation: it's just a trait + +Because an abstract type is a vanilla associated-type trait, the most transparent way to bind it is to implement it directly on a concrete context. Given the trait, a context names the concrete type in a plain `impl`: + +```rust +#[cgp_type] +pub trait HasNameType { + type Name; +} + +pub struct Person; + +impl HasNameType for Person { + type Name = String; +} +``` + +This makes `Person::Name` resolve to `String`, and any generic code constrained on `HasNameType` sees that choice. The direct form is the right mental model for newcomers — there is no hidden machinery, just an associated type pinned to a concrete one — and it is barely longer than the wired form shown below. The rest of this reference explains the `#[cgp_type]` macro and the wiring conveniences that make the choice swappable through a delegation table instead. + +## Making a type swappable with `#[cgp_type]` + +The `#[cgp_type]` macro turns an abstract-type trait into a full component, so the concrete type can be chosen through wiring rather than a hand-written impl. It is the abstract-type specialization of `#[cgp_component]`: applied to a trait with exactly one associated type and no methods, it produces everything `#[cgp_component]` would — the consumer trait, the provider trait, the blanket impls, the `…Component` marker — but each impl forwards the *associated type* rather than a method. + +```rust +#[cgp_type] +pub trait HasNameType { + type Name; +} +``` + +The default provider name is keyed off the *associated type* name, not the trait name, with a `TypeProvider` suffix. So `type Name;` yields the provider `NameTypeProvider` and the component marker `NameTypeProviderComponent`. A bound on the associated type — `type Name: Clone;` — is carried everywhere the type appears in the expansion and enforced on whatever concrete type a context chooses. You can override the derived provider name by passing one, exactly as with `#[cgp_component]`: `#[cgp_type(ProvideName)]`. + +The construct that distinguishes `#[cgp_type]` from a plain component is an extra blanket impl it generates for the `UseType` provider, described next, which is what lets a context pick a concrete type without writing a provider of its own. + +## Wiring a concrete type with `UseType` + +A context binds an abstract type to a concrete one by wiring its provider component to `UseType`. Because every abstract-type provider has the same trivial shape — "the associated type *is* this concrete type" — `#[cgp_type]` generates that shape once as a blanket impl of the provider trait for `UseType`, setting the associated type to the generic parameter. The context then names the concrete type directly in its delegation table: + +```rust +delegate_components! { + Person { + NameTypeProviderComponent: UseType, + } +} +``` + +Wiring `NameTypeProviderComponent` to `UseType` makes `Person` implement `HasNameType` with `Name = String`, with no bespoke provider, and any bound on the associated type is checked against `String` at the wiring site. `UseType` is a zero-sized marker struct carrying no runtime value — it exists only to be named in a delegation table, never constructed. This is the type-level mirror of how `UseField` supplies a value-level getter (see [functions and getters](functions-and-getters.md)). + +The blanket impl that `#[cgp_type]` generates for the example above is: + +```rust +impl NameTypeProvider<__Context__> for UseType { + type Name = Name; +} +``` + +This says `UseType` is a provider that supplies `T` as the abstract type, for any context. If the associated type carried a bound, that bound would be copied into the impl's `where` clause so the concrete type must satisfy it. + +A word of caution on the name: the `UseType` *provider struct* shown here is a different construct from the `#[use_type]` *attribute* covered below. The provider wires a concrete type *into* a context; the attribute imports an abstract type *into* a definition and rewrites bare mentions of it. They share a name because both center on abstract types, but they live in different places and do different jobs. + +## The built-in `HasType` / `TypeProvider` component + +Underneath every named abstract type sits CGP's single built-in abstract-type component, `HasType`. It is tag-indexed: `HasType` is the consumer trait, `TypeProvider` is its provider trait, and a context can carry many distinct abstract types — one per `Tag` — resolving each through wiring. + +```rust +#[cgp_component(TypeProvider)] +pub trait HasType { + type Type; +} +``` + +Every `#[cgp_type]` component you define is wired on top of this substrate: the macro generates an internal `WithProvider` impl that adapts a `TypeProvider` into the named component, so the same `UseType` marker satisfies both the built-in `HasType` and any user-defined abstract type at once. In practice you rarely name `HasType` directly — you define a readable `HasNameType` with `#[cgp_type]` and get `Self::Name` and its own provider, all resolving down to this `HasType` machinery. The takeaway is that `UseType` is itself a `TypeProvider`, which is why one marker serves every abstract type. + +## Abstract type as a getter return type + +When an abstract type's only role is to be the return type of a getter, you can declare it inline with `#[cgp_auto_getter]` rather than defining a separate `#[cgp_type]` trait. The getter trait carries the associated type locally, and the field's type is inferred from it: + +```rust +#[cgp_auto_getter] +pub trait HasName { + type Name; + + fn name(&self) -> &Self::Name; +} +``` + +A context implementing this through its field wiring supplies both the concrete `Name` and the value. This keeps a one-off abstract type local to the getter that uses it instead of promoting it to a shared, wired component. See [functions and getters](functions-and-getters.md) for how `#[cgp_auto_getter]` derives the getter from a field. + +## Importing an abstract type with `#[use_type]` + +The strongly recommended way to *refer to* an abstract type from another definition is the `#[use_type]` attribute. A provider or component often needs a type that lives on a different trait — a `Scalar` from `HasScalarType`, an `Error` from `HasErrorType` — and Rust requires every mention to be written in fully-qualified form, `::Scalar`, because a bare `Scalar` is not a type the compiler knows. Writing that prefix on every occurrence is verbose and easy to get wrong. + +`#[use_type]` lets you write the bare identifier everywhere and have the macro expand it. You declare the import once alongside `#[cgp_fn]`, `#[cgp_impl]`, or `#[cgp_component]`, and the macro rewrites each standalone `Scalar` into `::Scalar` while also adding `HasScalarType` as a supertrait (for `#[cgp_component]`) or a `where`-clause bound (for `#[cgp_impl]` and `#[cgp_fn]`). Consider a `rectangle_area` function that multiplies two implicit fields: + +```rust +pub trait HasScalarType { + type Scalar: Clone + Mul; +} + +#[cgp_fn] +#[use_type(HasScalarType::Scalar)] +fn rectangle_area( + &self, + #[implicit] width: Scalar, + #[implicit] height: Scalar, +) -> Scalar { + width * height +} +``` + +The macro rewrites every bare `Scalar` to the qualified path and adds the supertrait/bound, so the effective trait and impl read: + +```rust +pub trait RectangleArea: HasScalarType { + fn rectangle_area(&self) -> ::Scalar; +} + +impl RectangleArea for Context +where + Self: HasField::Scalar> + + HasField::Scalar>, + Self: HasScalarType, +{ + fn rectangle_area(&self) -> ::Scalar { + let width: ::Scalar = + self.get_field(PhantomData::).clone(); + let height: ::Scalar = + self.get_field(PhantomData::).clone(); + width * height + } +} +``` + +The substitution is purely textual at the type level — it matches single-segment, argument-free type paths whose identifier equals the imported name — so a bare `Scalar` in the return type, an implicit-argument annotation, or a `let` binding inside the body is all rewritten the same way. Beyond saving keystrokes, the always-qualified rewrite removes the ambiguity the bare form cannot express, which is why this is the default way to import abstract types in all three macros. + +The attribute has a few richer forms worth knowing. A leading `@` changes the rewrite target from `Self` to a named type, which imports a *foreign* abstract type from a generic parameter: `#[use_type(@Types::HasScalarType::Scalar)]` rewrites `Scalar` to `::Scalar` and adds `Types: HasScalarType` as a `where` bound rather than a supertrait. A braced list imports several types from one trait, each optionally renamed with `as` or constrained with `=`: `#[use_type(HasScalarType::{Scalar = f64})]` both imports `Scalar` and emits `Self: HasScalarType`, pinning it. The `= ...` equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side equality constraint it produces; it belongs on `#[cgp_fn]` and `#[cgp_impl]`. + +## Sharing one type across contexts + +The value of an abstract type compounds when several pieces of generic code share it. Because the type lives on a trait the context implements, every provider and trait that needs a `Scalar` refers to the *same* `Self::Scalar`, so a context fixes the choice once and all of them agree. This is sharpest when a trait's main subject is a generic parameter rather than the context itself — a `CanCalculateAreaOfShape` implemented by one context for many shapes. The shapes carry no scalar type of their own; the shared context supplies a single `Scalar` through its `HasScalarType` wiring, and switching `UseType` to `UseType` changes the scalar for every shape at once. The same arrangement is how CGP shares one error type across an application: `HasErrorType` is itself defined with `#[cgp_type]`, and every fallible provider refers to the same `Self::Error` (see [error handling](error-handling.md)). + +## Related references + +Abstract types are wired into a context with [components](components.md) and consumed by [functions and getters](functions-and-getters.md). `UseType` is one of the higher-order providers described in [higher-order providers](higher-order-providers.md), and the shared error type pattern is covered in [error handling](error-handling.md). + +## Further reference + +Online docs: [`#[cgp_type]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_type.md), [`HasType`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/has_type.md), [`UseType` provider](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/use_type.md), [`#[use_type]` attribute](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/use_type.md). diff --git a/docs/skills/cgp/references/checking.md b/docs/skills/cgp/references/checking.md new file mode 100644 index 00000000..02c92285 --- /dev/null +++ b/docs/skills/cgp/references/checking.md @@ -0,0 +1,205 @@ +# Checking wiring + +How to verify at compile time that a context's [wiring](wiring.md) is complete, using check traits and the `check_components!` / `delegate_and_check_components!` macros to turn confusing use-site errors into precise ones at the wiring site. + +## Why wiring is lazy + +CGP wiring is lazy: recording that a context delegates a [component](components.md) to a provider does not verify that the provider can actually satisfy that component for that context. When you write a `DelegateComponent` entry mapping `GreeterComponent` to `GreetHello`, the type system stores "this key points to this provider" as an associated type and asks no further questions. Whether the provider's own `where` bounds — its impl-side dependencies — hold for this particular context is simply not checked at the point of delegation. The check is deferred until something downstream actually *uses* the component, which is the first moment the compiler is forced to evaluate the provider's bounds against the concrete context. + +This laziness is what makes CGP composable, but it has a cost: a context can look fully wired and still be broken. Every entry compiles, the struct compiles, the whole module compiles — and then the first call to a consumer trait method fails, often far from the wiring that caused it. A missing field, a missing abstract type, an unsatisfied transitive bound three providers deep: none of these surface where the mistake was made. + +Consider a greeter provider that depends on a `name` field, wired onto a context whose field is misnamed: + +```rust +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasName, +{ + fn greet(&self) { + println!("Hello, {}!", self.name()); + } +} + +#[derive(HasField)] +pub struct Person { + pub first_name: String, // mismatch: GreetHello needs `name` +} + +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} +``` + +This whole block compiles. `GreetHello` needs `HasName`, `Person` has no `name` field, and nothing complains — until some distant `person.greet()` call fails to typecheck. + +## Why the resulting errors are poor + +When a lazily-wired context is finally used and a dependency is missing, the compiler reports the outermost unmet conclusion and hides the reasoning behind it. Asking the plain question "does `Person` implement `CanGreet`?" makes Rust answer with the last link in the chain — typically that `GreetHello` does not implement the provider trait for `Person` — without explaining *why* the provider's bounds were not met. The provider blanket impl is a competing candidate that suppresses the detailed diagnostic. The root cause, a single missing `name` getter, is buried beneath a conclusion that points at the provider rather than at the gap, and the surface error spells field names in their verbose type-level form (`Symbol<…, Chars<…>>`), making even that hard to read. + +The fix is to force the compiler to evaluate the provider's bounds *and report them in detail*, at a location you control — the wiring site — rather than wherever the component happens to be used first. + +## How check traits force readable errors + +A check trait is a dummy trait whose supertrait is the requirement being asserted; implementing it for a context with an empty body compiles only if that requirement holds. The hand-written form is plain Rust: + +```rust +trait CanUsePerson: CanGreet {} +impl CanUsePerson for Person {} +``` + +The `impl` block has nothing to prove on its own, so it succeeds exactly when `Person: CanGreet` holds and fails otherwise. Placed next to the wiring, it converts a latent gap into an immediate compile error at a known line. But asserting the *consumer* trait directly is not enough: it reproduces the same vague error as before, naming the provider rather than the missing dependency. The remedy is to route the assertion through `CanUseComponent` instead. + +`CanUseComponent` is satisfied only when the context both delegates the component and the delegated provider satisfies `IsProviderFor` for that context. The crucial property is that `IsProviderFor` carries the provider's *real* `where` bounds — the same impl-side dependencies the provider needs to implement its provider trait. Routing a check through `CanUseComponent` therefore forces the compiler to evaluate those bounds and, because they are stated explicitly through the marker, to report the specific one that failed. A missing `name` field surfaces as an unsatisfied `HasName`/`HasField` bound pointing at the context, not as a bare "provider not implemented." The two bounds also distinguish the two ways wiring goes wrong: failing the `DelegateComponent` bound means the component was never wired (add the delegation), while failing the `IsProviderFor` bound means it was wired to a provider whose dependencies are unmet (supply the missing dependency). You rarely name `CanUseComponent` directly — its job is to be the bound the check macros emit. + +## Generating checks with `check_components!` + +Rather than spell out check traits by hand, `check_components!` generates them from a short table of components to verify. Given a context and a list of components, it emits a marker trait aliasing `CanUseComponent` and one empty impl per component: + +```rust +check_components! { + Person { + GreeterComponent, + } +} +``` + +The generated impl compiles only if `Person: CanUseComponent`, which drags in `GreetHello`'s `IsProviderFor` bounds and reports the first that fails — here, the missing `name` field, pinpointed at the wiring site instead of at a future `person.greet()`. A successful build *is* the passing assertion; these checks have no runtime existence. + +The check trait is named `__Check{Context}` by default — `__CheckPerson` here. When two `check_components!` tables in the same module would collide on that name, override it with `#[check_trait(Name)]` on the table: + +```rust +check_components! { + #[check_trait(CheckPersonGreeting)] + Person { + GreeterComponent, + } +} +``` + +A component with generic parameters cannot be checked bare, because the check must name concrete parameters to have anything to verify. List them after a colon: a single parameter bare, multiple parameters grouped into a tuple, mirroring how the provider trait groups them in its `IsProviderFor` `Params` slot. Given an area calculator generic over a shape: + +```rust +#[cgp_component(AreaOfShapeCalculator)] +pub trait CanCalculateAreaOfShape { + fn area(&self, shape: &Shape) -> f64; +} + +check_components! { + MyApp { + AreaOfShapeCalculatorComponent: Rectangle, // one parameter + TransformCalculatorComponent: (Rectangle, f64), // two, as a tuple + } +} +``` + +Array syntax on either side of the colon expands to the cartesian product, so a set of components can be checked against a set of parameters in one line. A bracketed value checks one component against several parameter sets; a bracketed key checks several components against one set; bracketing both checks every combination: + +```rust +check_components! { + MyApp { + AreaOfShapeCalculatorComponent: [Rectangle, Circle], // one component, two shapes + } +} +``` + +This verifies `MyApp: CanCalculateAreaOfShape` and `MyApp: CanCalculateAreaOfShape` in one entry. + +## Checking providers directly with `#[check_providers(...)]` + +For [higher-order providers](higher-order-providers.md), checking the context as a whole tells you a layer is broken but not which one. The `#[check_providers(...)]` attribute changes what is checked: instead of asserting `CanUseComponent` on the context, it asserts `IsProviderFor` directly on each named provider, so each layer is verified on its own impl: + +```rust +check_components! { + #[check_trait(CheckScaledRectangleProviders)] + #[check_providers( + RectangleAreaCalculator, + ScaledAreaCalculator, + )] + ScaledRectangle { + AreaCalculatorComponent, + } +} +``` + +Because each provider is checked independently, a dependency missing only from the outer `ScaledAreaCalculator` wrapper errors on the wrapper's line alone, while one missing from the inner `RectangleAreaCalculator` errors on both lines — which narrows down where in a nested stack the gap lives. + +## Wiring and checking together with `delegate_and_check_components!` + +Keeping a standalone `check_components!` block in sync with the wiring is manual bookkeeping: add a delegation and you must remember to add its check. `delegate_and_check_components!` fuses the two — it wires each entry exactly as `delegate_components!` would and derives a check for each delegated key, so every wiring is proven the moment it is written: + +```rust +#[derive(HasField)] +pub struct MyContext { + pub name: String, +} + +delegate_and_check_components! { + MyContext { + NameTypeProviderComponent: UseType, + NameGetterComponent: UseField, + } +} +``` + +If `MyContext` were missing the `name` field, the derived check on `NameGetterComponent` would fail to compile and report the missing bound, rather than letting the gap slip through to a later use. + +Its check trait is named `__CanUse{Context}` by default — deliberately distinct from `check_components!`'s `__Check{Context}` — so one of each macro can appear in the same module without a clash. As with `check_components!`, `#[check_trait(Name)]` overrides the derived name. + +Because the delegation half is generic over a component's parameters but the check half needs concrete ones, an entry for a component with generic parameters *requires* a `#[check_params(...)]` attribute supplying them, using the same single-versus-tuple convention: + +```rust +delegate_and_check_components! { + MyApp { + #[check_params(Rectangle, Circle)] + AreaOfShapeCalculatorComponent: + UseDelegate, + } +} +``` + +The nested `UseDelegate` table shown here is the legacy form of per-type dispatch; the modern equivalent opens the component with the `open` statement of `delegate_components!` (see [wiring](wiring.md)). The `#[check_params(...)]` requirement is the same either way — whichever wiring form supplies the dispatch entries, the check half still needs the concrete parameters spelled out. + +To wire an entry without checking it — for instance a higher-order delegation you verify separately with a `#[check_providers(...)]` block — mark it `#[skip_check]`. The two attributes are mutually exclusive on a given entry: + +```rust +delegate_and_check_components! { + ScaledRectangle { + AreaCalculatorComponent: + ScaledAreaCalculator, + + #[skip_check] + TransformCalculatorComponent: + ComplexTransform, // checked in a dedicated check_components! block + } +} +``` + +The recommendation follows from these defaults: reach for `delegate_and_check_components!` for a main context's wiring, where catching a broken or incomplete wiring as early as possible is most valuable, and keep plain `delegate_components!` for intermediary provider-bundle tables that group providers without being a context in their own right. Use a standalone `check_components!` block when a check needs `#[check_providers(...)]` or other control beyond per-entry `#[check_params]` and `#[skip_check]`. + +## Debugging an unsatisfied check + +When a check fails, the error names the unmet bound — read it as a thread to pull rather than a final verdict. The reported bound is the first impl-side dependency the compiler could not satisfy; walk the transitive dependencies from there. If `GreetHello` requires `HasName`, the error names `HasName` on the context, and the fix is to supply that field or wire the getter component. A bound several providers deep names the innermost requirement, so trace from the failing component through each provider it delegates into. + +When a large `delegate_and_check_components!` table reports a tangle of errors, narrow it down by adding the suspect component to a separate `check_components!` block on its own. Checking one component in isolation, with its parameters spelled out, strips away the noise from the other entries and forces the compiler to report just that component's unmet dependency. For a higher-order stack, switch to `#[check_providers(...)]` to see which layer fails on its own line. + +Finally, remember that not every unsatisfied bound is a CGP component. A check trait only verifies wiring routed through `CanUseComponent` — it cannot prove a plain trait or a blanket-impl bound that a provider also depends on. If the error names a trait that has no `…Component` marker and no entry in any delegation table, no amount of checking will surface it through the wiring; that bound must be satisfied by ordinary Rust means (an `impl`, a derive, a `where` clause on the context), and the check will pass only once it is. + +## Further reference + +Online docs: [`check_components.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/check_components.md), [`delegate_and_check_components.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/delegate_and_check_components.md), and the conceptual overview [`concepts/check-traits.md`](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/check-traits.md). diff --git a/docs/skills/cgp/references/components.md b/docs/skills/cgp/references/components.md new file mode 100644 index 00000000..e23f7487 --- /dev/null +++ b/docs/skills/cgp/references/components.md @@ -0,0 +1,189 @@ +# Components + +A CGP **component** is the bundle that `#[cgp_component]` generates from one trait so that *using* a capability and *implementing* it become separate, swappable things — this is the central reference; read it first. + +## What a component is + +A component is a single trait definition compiled into a small machine: a **consumer trait** that callers invoke, a **provider trait** that implementations target, a `…Component` marker key, and the blanket impls that connect them. An ordinary Rust trait conflates using a capability with implementing it — the type you call `.area()` on is the same type that supplies the `area` body — and Rust's coherence rules then allow only one implementation per type. A component breaks that conflation, which is what lets many independent implementations of the same capability coexist and lets you implement a capability for a type you do not own. + +The split is produced by `#[cgp_component]`, applied to an ordinary trait. Throughout this file the running examples are the greeting component (`CanGreet` / `Greeter` / `GreetHello`, on a `Person`) and the area component (`CanCalculateArea` / `AreaCalculator` / `RectangleArea`, on a `Rectangle`). Assume `use cgp::prelude::*;` in every snippet; the CGP version is v0.7.0. + +## What `#[cgp_component(Greeter)]` generates + +Applying the macro to a consumer trait produces five items: the consumer trait, the provider trait, two blanket impls, and the marker struct. Start from the trait, naming the provider trait in the attribute argument: + +```rust +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} +``` + +The first item is the **consumer trait**, emitted unchanged. This is the self-style trait callers use (`CanDoX`), so a caller writes `person.greet()` exactly as with any trait: + +```rust +pub trait CanGreet { + fn greet(&self); +} +``` + +The second item is the **provider trait**, the same interface with `Self` moved out into an explicit leading `Context` type parameter and every `self`/`Self` rewritten to `context`/`Context`. It is named in noun form (`SomethingDoer`, or `…Provider` when no noun fits), and it carries an `IsProviderFor` supertrait that captures the component, the context, and a `Params` tuple of any extra type parameters — `()` when there are none: + +```rust +pub trait Greeter: + IsProviderFor +{ + fn greet(context: &Context); +} +``` + +A provider trait is implemented not for the context but for a dedicated zero-sized **provider** struct — a type-level marker that is never instantiated and carries no runtime value. Because a provider implements `Greeter` for *its own* struct over a generic `Context`, the orphan and overlap rules never bite, so `GreetHello`, `GreetGoodbye`, and any number of further providers for the same component can all exist at once. See [bypassing coherence](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/coherence.md) (online) for why moving `Self` to a parameter is what sidesteps the rules, and [modularity hierarchy](modularity-hierarchy.md) for the spectrum of how far to take the split. + +## How the two traits connect + +Two generated blanket impls bridge the consumer and provider sides, and together they make `person.greet()` resolve to a chosen provider without the caller naming it. Read them as the wiring machinery; you never write them. + +The first is the **consumer blanket impl**, which says that any context implementing the provider trait *for itself* automatically gets the consumer trait. It forwards `context.greet()` to `Context::greet(self)`: + +```rust +impl CanGreet for Context +where + Context: Greeter, +{ + fn greet(&self) { + Context::greet(self) + } +} +``` + +The second is the **provider blanket impl**, which lets any provider that delegates this component inherit the provider trait from whatever it delegates to. The delegation is a type-level table lookup through `DelegateComponent`, keyed on the `GreeterComponent` marker: + +```rust +impl Greeter for Provider +where + Provider: DelegateComponent + + IsProviderFor, + Provider::Delegate: Greeter, +{ + fn greet(context: &Context) { + Provider::Delegate::greet(context) + } +} +``` + +The fifth item is the **component marker**, a zero-sized key into those delegation tables: + +```rust +pub struct GreeterComponent; +``` + +These two examples use readable names for clarity; the emitted code uses reserved identifiers — `__Context__` for the context parameter (overridable) and `__Provider__` for the provider parameter — chosen so they never clash with a user's own type names. + +## Wiring is the table lookup that ties it all together + +**Wiring** is the step that supplies the delegation the provider blanket impl reads, by making a context into a type-level table whose entry for each component names the chosen provider. With the greeting component wired, the chain resolves end to end: `person.greet()` goes through the consumer impl to `Person` implementing the provider trait for itself, which goes through the provider impl to the table entry, landing on the selected provider's `greet`. Swapping the table entry is the only change needed to swap behavior; no caller is touched. + +```rust +#[derive(HasField)] +pub struct Person { + pub name: String, +} + +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} +``` + +The full table grammar — including per-value dispatch and presets — lives in [wiring](wiring.md). This file shows only the single-entry form needed to make the resolution concrete. + +## Why `IsProviderFor` exists + +`IsProviderFor` is an empty marker trait that rides along on every provider trait as a supertrait, and its only job is to make a missing dependency produce a readable error. A provider lists what it needs from the context in a `where` clause; when that clause is unmet, the bare question "does this provider implement the provider trait?" yields only "trait not implemented", because the provider blanket impl is also a candidate and Rust suppresses its detailed reasoning whenever more than one impl could apply. + +`IsProviderFor` is the second, independent path that un-hides the real reason. The macros implement it for a provider under *exactly the same* `where` bounds as the provider trait, and because that impl is the only candidate (no competing blanket), Rust commits to it and prints the precise unsatisfied constraint. The practical translation a reader needs: an `IsProviderFor` not implemented error means the provider trait is not implemented, and the named bound is the missing dependency. The trait is generated and consumed entirely by the macros — you observe it in errors, you never write it. + +```rust +#[diagnostic::on_unimplemented( + note = "You need to add `#[cgp_provider({Component})]` on the impl block for CGP provider traits" +)] +pub trait IsProviderFor {} +``` + +## Writing providers + +A provider can be written at three levels of sugar over the same machinery, and `#[cgp_impl]` is the one to reach for. The lower forms exist for when the native provider-trait shape is wanted explicitly, and they are what `#[cgp_impl]` desugars to. + +The lowest form is `#[cgp_provider]`, applied to a provider-trait impl written directly on a provider struct. It passes the impl through unchanged and auto-generates the matching `IsProviderFor` impl from the same `where` clause, so the dependency set can never drift. The provider struct must already exist: + +```rust +pub struct RectangleArea; + +#[cgp_provider] +impl AreaCalculator for RectangleArea +where + Context: HasDimensions, +{ + fn area(context: &Context) -> f64 { + context.width() * context.height() + } +} +``` + +That expands to the impl above plus the empty marker impl carrying the same bound, which is what surfaces a missing `HasDimensions` as a named error: + +```rust +impl IsProviderFor for RectangleArea +where + Context: HasDimensions, +{} +``` + +The optional attribute argument overrides the component type, defaulting otherwise to the provider trait's name plus a `Component` suffix; pass it explicitly only when the trait name does not follow that convention. + +The middle form is `#[cgp_new_provider]`, identical to `#[cgp_provider]` but it also declares the provider struct, folding `pub struct RectangleArea;` into the same block. Use it when introducing a fresh provider so you need not write the struct separately; a generic provider yields a struct with a `PhantomData` field over its parameters. + +The preferred form is `#[cgp_impl]`, which lets you write the provider in **consumer-style syntax** — keeping `self`, `Self`, and the consumer trait's method signatures — and mechanically rewrites it into the provider-trait shape. The provider name moves into the attribute argument instead of the `Self` position; a leading `new` keyword declares the struct; and you may omit `for Context` entirely, letting the macro insert the context parameter for you: + +```rust +#[cgp_impl(new RectangleArea)] +impl AreaCalculator +where + Self: HasDimensions, +{ + fn area(&self) -> f64 { + self.width() * self.height() + } +} +``` + +The one rule this convenience must not let you forget: inside a `#[cgp_impl]` block, `self` and `Self` mean the **context**, never the provider struct. The provider has no runtime value, so the macro rewrites every `self` to the context value and every `Self` to the context type — those are the only things that exist when the method runs. + +`#[cgp_impl]` desugars to `#[cgp_provider]` (or `#[cgp_new_provider]` when `new` is given). The block above is equivalent to writing the context out by hand and lowering it: + +```rust +#[cgp_new_provider] +impl<__Context__> AreaCalculator<__Context__> for RectangleArea +where + __Context__: HasDimensions, +{ + fn area(__context__: &__Context__) -> f64 { + __context__.width() * __context__.height() + } +} +``` + +The receiver `&self` became `__context__: &__Context__` — the receiver identifier is the snake-cased context name wrapped in double underscores — and `Self` in the `where` clause became the context type. When you write `for Context` explicitly, that name is used instead of `__Context__`, which is worth doing when you need to bound or refer to the context readably. + +## Impl-side dependencies + +A provider states what it needs from the context as bounds in its `where` clause, and those bounds — **impl-side dependencies** — are constraints the consumer trait never exposes. `CanCalculateArea` declares only `area`, while `RectangleArea` requires `Context: HasDimensions`; a caller bounding on `CanCalculateArea` never sees `HasDimensions`, so the requirement stays one level down and does not cascade through transitive callers. This is dependency injection through the `where` clause: any context satisfying the bound gains the capability automatically, and the wiring satisfies each bound by resolving it through the same context. See [impl-side dependencies](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/impl-side-dependencies.md) (online) for the full treatment, and [functions and getters](functions-and-getters.md) for the value-injection forms (`#[implicit]` arguments and getters) that read fields off the context through the same mechanism. + +## A consumer trait is still an ordinary trait + +A consumer trait can also be implemented directly on a context, exactly like a vanilla Rust trait, when code reuse across providers is not the goal. The consumer/provider split is a superset of ordinary traits, not a replacement: the provider machinery is what you opt into when a capability needs more than one implementation, and skipping it costs nothing for the simple case. You write `impl CanGreet for Person { ... }` as usual, and `person.greet()` resolves to that direct impl with no wiring involved. + +## Further reference + +For the full expansion and accepted syntax of each macro, see the online docs: [`#[cgp_component]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_component.md), [`#[cgp_impl]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_impl.md), [`#[cgp_provider]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_provider.md), [`#[cgp_new_provider]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_new_provider.md), and [`IsProviderFor`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/is_provider_for.md). For the concepts behind the split, see [consumer and provider traits](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/consumer-and-provider-traits.md), [bypassing coherence](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/coherence.md), and [impl-side dependencies](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/impl-side-dependencies.md). For sibling sub-skills, see [wiring](wiring.md), [checking](checking.md), [functions and getters](functions-and-getters.md), and [higher-order providers](higher-order-providers.md). diff --git a/docs/skills/cgp/references/error-handling.md b/docs/skills/cgp/references/error-handling.md new file mode 100644 index 00000000..4badd360 --- /dev/null +++ b/docs/skills/cgp/references/error-handling.md @@ -0,0 +1,132 @@ +# Error handling + +CGP's modular error handling: one shared abstract `Error` type per context, plus pluggable behavior for constructing that error from a source error and for attaching detail to it. + +Generic CGP code must be able to fail without naming a concrete error type. A provider that hits a fallible operation gets back a `parse error`, an `io::Error`, or a string, yet it is generic over the context and cannot commit to `anyhow::Error` or any other choice — that choice belongs to the application assembling the context. CGP resolves this with three cooperating [components](components.md): `HasErrorType` gives the context one abstract `Self::Error`, `CanRaiseError` constructs that abstract error from a concrete source error, and `CanWrapError` enriches an existing abstract error with extra detail. The concrete error type and the raising/wrapping behavior are decided once, at [wiring](wiring.md) time, by whichever error backend the context plugs in. + +A note on imports: the consumer traits `HasErrorType`, `CanRaiseError`, and `CanWrapError` come through `use cgp::prelude::*;`, but the wiring keys and backend providers below do not. The component markers (`ErrorTypeProviderComponent`, `ErrorRaiserComponent`, `ErrorWrapperComponent`) live under `cgp::core::error`, and the backend providers (`RaiseFrom`, `ReturnError`, `DebugError`, `DisplayError`, and the rest) live under `cgp::extra::error`, so a module that wires error handling imports the specific names it uses, for example `use cgp::core::error::ErrorRaiserComponent;` and `use cgp::extra::error::RaiseFrom;`. + +## `HasErrorType`: the shared abstract error + +`HasErrorType` is the abstract-type component that gives a context a single shared `Error` type, and every fallible CGP operation refers to it. It is declared with `#[cgp_type]`, so it behaves like any other [abstract type](abstract-types.md) — a trait with one associated type, wired through a provider rather than hand-implemented: + +```rust +#[cgp_type] +pub trait HasErrorType { + type Error: Debug; +} + +pub type ErrorOf = ::Error; +``` + +The `Debug` bound lets `Self::Error` flow into `.unwrap()` and straightforward logging without an extra constraint, and it is enforced on whatever concrete type a context chooses. `ErrorOf` is the convenient spelling of the associated-type path. Generic code that may fail returns `Result` (or `Result>`) and never names a concrete error. + +Centralizing the error type on one trait is what lets errors compose. A context trait that may fail supertraits `HasErrorType`, so every such trait refers to the *same* `Self::Error`; if each declared its own associated `Error`, a context bounded by several of them would face several incompatible error types with no way to unify them. `HasErrorType` carries no methods — it only declares the type. The behavior of producing errors lives in the traits that supertrait it. + +Because `#[cgp_type]` generates a `UseType` blanket impl, a context fixes its error type by wiring the error-type component to `UseType`, exactly as for any abstract type: + +```rust +#[cgp_component(Validator)] +pub trait CanValidate: HasErrorType { + fn validate(&self) -> Result<(), Self::Error>; +} + +pub struct App; + +delegate_components! { + App { + ErrorTypeProviderComponent: UseType, + } +} +``` + +Here `CanValidate` supertraits `HasErrorType`, so its `Self::Error` is the context's shared abstract error, and `App` fixes that error to `String`. The standalone backends (`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`) supply ready-made providers that set `Error` to their respective library types instead. A context can equally implement the trait directly — `impl HasErrorType for App { type Error = String; }` — which makes plain that it is an ordinary trait with a `Debug`-bounded associated type. + +## `CanRaiseError` and `CanWrapError`: producing and enriching the error + +`CanRaiseError` is the consumer trait for turning a concrete source error into the context's abstract `Self::Error`, and `CanWrapError` is the companion that attaches detail to an existing one. Both supertrait `HasErrorType`, and both are `#[cgp_component]`s that delegate per type so a context can handle each source error or detail with a different provider: + +```rust +#[cgp_component(ErrorRaiser)] +#[derive_delegate(UseDelegate)] +pub trait CanRaiseError: HasErrorType { + fn raise_error(error: SourceError) -> Self::Error; +} + +#[cgp_component(ErrorWrapper)] +#[derive_delegate(UseDelegate)] +pub trait CanWrapError: HasErrorType { + fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; +} +``` + +`raise_error` takes the source error by value and returns the abstract error; `wrap_error` takes the current abstract error plus a `Detail` and returns an enriched one. Both are associated functions with no `self` receiver — raising and wrapping are properties of the context *type*, so generic code calls `Context::raise_error(source)` and `Context::wrap_error(err, detail)` where only the type parameter is in scope. The `#[cgp_component(...)]` attribute names the provider traits `ErrorRaiser` and `ErrorWrapper`, and `#[derive_delegate(UseDelegate<...>)]` makes each dispatch per `SourceError` or `Detail` type through a delegation table. + +A provider written against these bounds names neither the context nor its concrete error type: + +```rust +#[cgp_component(Loader)] +pub trait CanLoad: HasErrorType { + fn load(&self, path: &str) -> Result; +} + +#[cgp_impl(new LoadOrFail)] +impl Loader for Context +where + Context: CanRaiseError + CanWrapError, +{ + fn load(&self, path: &str) -> Result { + if path.is_empty() { + let err = Context::raise_error("empty path".to_owned()); + return Err(Context::wrap_error(err, format!("while loading {path}"))); + } + Ok(format!("contents of {path}")) + } +} +``` + +The provider requires `CanRaiseError` to turn a message into the abstract error and `CanWrapError` to attach context as it propagates — both [impl-side dependencies](components.md) that any wired context satisfies by plugging in providers. The context decides, through wiring, what concrete error type `load` actually produces. + +## Wiring the behavior: error-backend providers + +A context gains raising and wrapping by wiring `ErrorRaiserComponent` and `ErrorWrapperComponent` to providers, exactly like any other component. The `cgp-error-extra` crate supplies a family of zero-sized providers (the [providers](components.md) are markers with no runtime value) that are generic over the context's error type and capture cross-cutting strategies independent of any one error library; they sit alongside the standalone backends, which specialize to a concrete library error such as `anyhow::Error`. A typical context wires a mix: a backend for the concrete error type plus these generic providers for the strategies. The raisers, wrappers, and their bounds are: + +- `RaiseFrom` implements `ErrorRaiser` by converting through `From` — it raises any source error whose `Context::Error: From`, the default choice when the abstract error already absorbs the source. +- `ReturnError` implements `ErrorRaiser` for the case where the source *is* the abstract error (`HasErrorType`), returning it untouched. +- `RaiseInfallible` implements `ErrorRaiser` for `core::convert::Infallible`, producing the error by an empty `match` that can never run — letting code parameterized over a fallible operation be wired uniformly even when the chosen operation cannot fail. +- `PanicOnError` implements `ErrorRaiser` by `panic!`-ing with the source error's `Debug` output instead of returning a value — for contexts that treat an error as a fail-fast fault, such as tests. +- `DiscardDetail` implements `ErrorWrapper` by dropping the detail and returning the error unchanged — the wrapping no-op, for error types that cannot carry extra context. +- `DebugError` and `DisplayError` implement *both* components by formatting the source error or detail into a `String` and forwarding to the context's own `CanRaiseError` / `CanWrapError` — `DebugError` via `Debug`, `DisplayError` via `Display`/`to_string()`. Both live behind the crate's `alloc` feature. + +The simplest wiring delegates to a single provider. Wiring `RaiseFrom` lets `App` raise any source error its abstract error implements `From` for: + +```rust +delegate_components! { + App { + ErrorRaiserComponent: RaiseFrom, + } +} +``` + +The string-formatting providers are designed to *compose* with the others rather than replace them: `DebugError` and `DisplayError` do not know the context's error type, they only reduce a `Debug` or `Display` value to a `String` and hand it off, so the context must separately wire a provider that handles the `String` source. Because both components dispatch per source-error type, the idiomatic wiring lists one entry per source error — a concrete string-raising rule plus formatting redirects for everything else — opened on the component with the `open` statement of `delegate_components!`: + +```rust +delegate_components! { + App { + open { ErrorRaiserComponent }; + + @ErrorRaiserComponent.String: RaiseFrom, + @ErrorRaiserComponent.ParseError: DebugError, + } +} +``` + +The `open { ErrorRaiserComponent };` header opens the component for per-type wiring, and each `@ErrorRaiserComponent.: Provider` entry assigns the provider for one source-error type, folded directly into `App`'s own table — `open` needs no `#[derive_delegate]` of its own because every `#[cgp_component]` already generates the `RedirectLookup` impl it dispatches through. The legacy equivalent writes the same per-type entries into a separate `UseDelegate` nested table; that form is still common in existing code but is slated for deprecation, so prefer `open` for new wiring. See [wiring](wiring.md) for both forms. + +A raised `String` is converted straight into the abstract error by `RaiseFrom`, while a raised `ParseError` is formatted with `Debug` by `DebugError` and then routed back through the `String` entry — which `RaiseFrom` handles — yielding one coherent error type from two unrelated sources. The choice of provider is therefore also a statement about which source errors the context accepts and how, and each wiring is verified with `check_components!` like any other. + +## Related constructs + +`HasErrorType` is an [abstract type](abstract-types.md) declared with `#[cgp_type]`, so it is wired with `UseType` or a backend provider the same way every abstract type is. `CanRaiseError` and `CanWrapError` are ordinary [components](components.md) that supertrait it, wired with `delegate_components!` as covered in [wiring](wiring.md), and their `#[derive_delegate(UseDelegate<...>)]` is what lets a context dispatch raising and wrapping per source-error or detail type through a delegation table. + +Further reference (online): [components/has_error_type.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/has_error_type.md), [components/can_raise_error.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/can_raise_error.md), [providers/error_providers.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/error_providers.md). diff --git a/docs/skills/cgp/references/extensible-data.md b/docs/skills/cgp/references/extensible-data.md new file mode 100644 index 00000000..90172e14 --- /dev/null +++ b/docs/skills/cgp/references/extensible-data.md @@ -0,0 +1,143 @@ +# Extensible data + +Treat a struct as a product of named fields and an enum as a sum of named variants, so that generic code can read, build, deconstruct, and convert any data type by its type-level field and variant names rather than by naming the concrete type. + +A plain Rust `struct` or `enum` is opaque to generic code: nothing about "the `first_name` field" or "the `Circle` variant" can be named through a type parameter, so a struct literal must list every field at one site and a `match` must spell out every variant. Extensible data breaks that by deriving a type-level description of a type's shape — its fields or variants, each tagged by name — and the machinery to build, take apart, and convert values through that description. This brings row polymorphism and structural sum types to Rust, resolved entirely at compile time, and it is what lets independent components each contribute one field or handle one variant without knowing the whole type. The result feeds directly into CGP wiring: builders, variant dispatchers, and structural casts all consume the field-level machinery these derives generate. A context is just such a data type when it flows through providers this way. + +The whole family is dual. A record is a product (every field present at once); an enum is a sum (exactly one variant present). The two share their presence markers, their casting traits, and their dispatch machinery, so reading one half tells you the shape of the other. + +## The umbrella derive and its shape-specific faces + +`#[derive(CgpData)]` is the high-level entry point: applied to a struct it emits the full record machinery, applied to an enum the full variant machinery, dispatching on the item kind. `#[derive(CgpRecord)]` and `#[derive(CgpVariant)]` are the same code paths restricted to one shape — use them when the type is always a struct or always an enum and you want the name to say so; applying the wrong one is a type error. The narrower building-block derives (`HasFields`, `HasField`, `BuildField`, `ExtractField`, `FromVariant`) each emit one slice of that output when you want only part of it. + +```rust +#[derive(CgpData)] +pub struct Person { pub first_name: String, pub last_name: String } + +#[derive(CgpData)] +pub enum Shape { Circle(Circle), Rectangle(Rectangle) } +``` + +Field tags drive everything generated. A named struct field or an enum variant is keyed by the type-level string `Symbol!("name")`, and an unnamed field of a tuple struct by its positional `Index`; that tag is what addresses the field across every generated impl. One restriction shapes the variant side: a derivable enum must follow the sum-of-products form, where each variant holds exactly one unnamed payload — wrap a richer payload in a dedicated struct so the variant's value stays a single nameable type. A fieldless, multi-field, or struct-style variant is a compile error. + +## Records: the whole-struct field view + +`#[derive(HasFields)]` gives a type its whole-shape view, the foundation everything else builds on. For a struct it implements `HasFields` with a `Fields` associated type that is a [`Product!`](type-level-primitives.md) of `Field` entries — the type-level spelling of the struct's layout, one entry per field tagged by name: + +```rust +impl HasFields for Person { + type Fields = Product![ + Field, + Field, + ]; +} +``` + +The derive also emits the conversions that move values in and out of that representation: `ToFields` turns an owned value into its `Fields` product, `FromFields` rebuilds the value from one, and `ToFieldsRef` borrows it as a product of references. Generic algorithms bound `Context: HasFields` (or `ToFields`/`FromFields`) and fold over `Context::Fields` structurally, never naming the concrete struct. This whole-shape view is distinct from per-field indexed access: reading a single field by name is `HasField`, the dependency-injection capability that getter traits resolve against (see [functions and getters](functions-and-getters.md)). A struct that wants both derives both. + +## Records: building a struct field by field + +The capability `#[derive(CgpRecord)]` and `#[derive(BuildField)]` add on top of reading is incremental, type-checked construction, exposed through the builder trait family. Construction runs through a *partial record* — a generated companion struct `__Partial{Name}` carrying one `MapType` marker per field that records whether that field is present yet. The marker `IsPresent` stores the field's real value, `IsNothing` stores `()`, so a partial record with every marker `IsNothing` is an empty builder and one with every marker `IsPresent` is a fully populated value. The family walks between these states. + +```rust +let employee: Employee = Employee::builder() // every field IsNothing + .build_from(person) // first_name + last_name now IsPresent + .build_field(PhantomData::, 1) // employee_id now IsPresent + .finalize_build(); // exists only at the all-present configuration +``` + +`HasBuilder::builder()` produces the empty partial record; `BuildField::build_field` flips one marker from `IsNothing` to `IsPresent`; and `FinalizeBuild::finalize_build` turns the partial record back into the concrete struct. Underneath, both `BuildField` and its reverse `TakeField` (the `IsPresent → IsNothing` direction) are blanket impls over a single per-field primitive `UpdateField`, which changes one field's marker and returns the old value alongside the rebuilt partial. The tracking is what makes the pattern safe: `finalize_build` is implemented *only* for the all-`IsPresent` configuration, so finalizing with any field still absent is a compile error rather than a runtime panic, and the order in which fields are built does not matter. The reverse direction is equally useful — `IntoBuilder` turns a complete struct into an all-present partial, and `TakeField` removes fields one at a time. + +## Records: the extensible builder pattern + +`CanBuildFrom` (one of the structural casts below) is what lets a builder absorb the shared fields of an entire source struct in one `build_from` step: it recurses over the source's field product, using `TakeField` to pull each field out and `BuildField` to write it into the target builder. So a small struct like a database client can be merged into a larger application struct without either type naming the other — they share only field names, matched at the type level. This is the basis of the *extensible builder pattern*, where the construction of one context is split across several independent providers, one per subsystem, none of which knows the final type or each other. Each provider builds a small output struct, and a dispatcher merges every output into the target's builder before finalizing. + +```rust +delegate_components! { + FullAppBuilder { + HandlerComponent: + BuildAndMergeOutputs, + } +} +``` + +Because the dispatcher is generic over the target struct and the provider list, swapping a subsystem means changing one entry, and selecting among several target structs is a matter of code-based dispatch. The `BuildAndMergeOutputs` combinator and the rest of this routing live in [handlers](handlers.md). + +## Variants: constructing and deconstructing an enum + +For an enum, `#[derive(HasFields)]` produces a `Fields` that is a [`Sum!`](type-level-primitives.md) of `Field` entries built on the `Either`/`Void` spine, mirroring the product side. Construction is `FromVariant`, generated per variant by `#[derive(FromVariant)]`: it builds the enum from one variant chosen by a type-level tag, so generic code parameterized over a `Tag` can construct whichever variant it was asked for. + +```rust +fn wrap_circle(circle: Circle) -> Shape { + Shape::from_variant(PhantomData::, circle) // == Shape::Circle(circle) +} +``` + +Deconstruction is the dual of the builder and runs through a *partial variant* — the companion enum `__Partial{Name}` generated by `#[derive(ExtractField)]`, carrying one `MapType` marker per variant just as a partial record carries one per field. The crucial difference is the absence marker: a record uses `IsNothing` (storing `()`), while a variant uses `IsVoid`, mapping a ruled-out variant to the uninhabited `Void` type. `HasExtractor::to_extractor` converts the enum into a partial variant with every variant still `IsPresent`, and `ExtractField::extract_field` tries to pull out one variant, returning `Ok(value)` if the value is that variant or `Err(remainder)` — the partial variant with that variant flipped to `IsVoid` — otherwise. + +```rust +fn area(shape: Shape) -> f64 { + match shape.to_extractor().extract_field(PhantomData::) { + Ok(circle) => core::f64::consts::PI * circle.radius * circle.radius, + Err(remainder) => { + // remainder's type now has Circle ruled out (IsVoid); try the next variant + let rect = remainder + .extract_field(PhantomData::) + .finalize_extract_result(); // remainder is now empty; this cannot fail + rect.width * rect.height + } + } +} +``` + +Marking extracted variants as `IsVoid` is what gives compile-time exhaustiveness without a wildcard arm. Each failed `extract_field` rules out one more variant in the type, so after every variant has been tried the remainder has every marker `IsVoid` and is therefore uninhabited. `FinalizeExtract::finalize_extract` discharges such a remainder with an empty `match`, sound precisely because no value can reach it — and `FinalizeExtractResult::finalize_extract_result` is the convenience wrapper that collapses the final `Result` into its `Ok` value. Add a variant to the enum without handling it and the final remainder becomes inhabited again, so the code fails to compile until the new variant is covered, recovering the guarantee a concrete `match` gives. `HasExtractorRef` and `HasExtractorMut` provide the same machinery over borrows. + +## Variants: the extensible visitor pattern + +Routing a value to the handler for its current variant is the *extensible visitor pattern*, which solves the expression problem: new variants can be added without touching the handlers for the others, and the same handler set can serve several enums that share variants. The logic for each variant lives in its own provider, and a dispatcher derives one extract-and-handle step per variant from the enum's `Fields`, running them as a pipeline that short-circuits on the first matching variant and threads the remainder forward otherwise. + +```rust +delegate_components! { + Interpreter { + ComputerComponent: + UseInputDelegate: EvalAdd, // one provider per variant + Times: EvalMultiply, + Literal: EvalLiteral, + }>, + } +} +``` + +The `MathExpr` entry routes the whole enum through a thin context-specific provider that defers to the matcher combinator; that wrapper exists to break the trait-resolution cycle between the matcher and the per-variant providers it dispatches to. The matcher combinators (`MatchWithValueHandlers` and its by-reference form) and the per-variant handler families live in [handlers](handlers.md). + +## The type-level spines underneath + +Both halves rest on the same right-nested type-level lists, kept brief here and covered fully in [type-level primitives](type-level-primitives.md). A struct's `Fields` is a `Product![A, B, C]`, which desugars to `Cons>>` over the `Cons`/`Nil` record spine — a list terminated by the constructible empty `Nil`, because an empty record is a valid value. An enum's `Fields` is a `Sum![A, B]`, which desugars to `Either>` over the `Either`/`Void` variant spine — a chain that branches at each step and terminates in the uninhabited `Void`, because an empty choice has no value to pick. The lowercase `product![..]` builds a value of the matching `Product!` type. Generic providers walk these spines one element at a time, which is exactly what no plain tuple or enum permits in generic code. + +## Structural casts between records and variants + +Two types that share a subset of named fields or variants convert into one another generically, with no hand-written `From`/`TryFrom`, through the casting traits — every conversion is just routing each named entry to the matching slot in the target. `CanUpcast` lifts a value of a narrow enum into a wider one whose variants are a superset; it always succeeds, since every source variant has a home in the target, and it walks the source's variants, extracting each and reconstructing it via `FromVariant`. `CanDowncast` goes the other way, narrowing a wide enum into a smaller one and succeeding only if the value's current variant exists in the target, otherwise handing back a remainder; `CanDowncastFields` is the same operation on a remainder, so downcasting against several candidates chains a `downcast` followed by `downcast_fields`. `CanBuildFrom` is the record counterpart already met above, assembling a target builder out of the fields of one or more sources. + +```rust +let wide = FooBar::Foo(1).upcast(PhantomData::); // always succeeds +assert_eq!(wide, FooBarBaz::Foo(1)); + +FooBarBaz::Bar("hi".into()).downcast(PhantomData::).ok(); // Some(FooBar::Bar(..)) +FooBarBaz::Baz(true).downcast(PhantomData::).ok(); // None — Baz has no home in FooBar +``` + +Upcasting is also how a provider constructs a value using only the subset of variants it cares about: it builds a small local enum and upcasts it into the full type — the variant-side analog of reading a field through a getter. + +## Dispatching over extensible data + +The payoff of exposing data shape at the type level is that *dispatch* becomes generic: a record builder routes each field to the provider that produces it, and a variant visitor routes each value to the provider for its current variant. Both are realized with the dispatch combinators — `BuildAndMergeOutputs` on the record side, `MatchWithValueHandlers` on the variant side — which derive one per-field or per-variant step from the type's `Fields` and sequence them. Those combinators and the handler families they build on are documented in [handlers](handlers.md); the wiring tables that name a provider per [component](components.md) are the ordinary `delegate_components!` entries shown above. + +## Further reference + +Online docs (current as of CGP v0.7.0): [concepts/extensible-records.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/extensible-records.md), [concepts/extensible-variants.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/extensible-variants.md), the derive references under [reference/derives/](https://github.com/contextgeneric/cgp/tree/main/docs/reference/derives) (`derive_cgp_data.md`, `derive_cgp_record.md`, `derive_cgp_variant.md`, `derive_has_fields.md`, `derive_build_field.md`, `derive_extract_field.md`, `derive_from_variant.md`), the trait references [traits/has_builder.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/has_builder.md), [traits/extract_field.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/extract_field.md), [traits/from_variant.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/from_variant.md), [traits/has_fields.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/has_fields.md), and [traits/cast.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/cast.md), and the type macros [macros/product.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/product.md) and [macros/sum.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/sum.md). diff --git a/docs/skills/cgp/references/functions-and-getters.md b/docs/skills/cgp/references/functions-and-getters.md new file mode 100644 index 00000000..e24a7b36 --- /dev/null +++ b/docs/skills/cgp/references/functions-and-getters.md @@ -0,0 +1,269 @@ +# Functions and Getters + +The ergonomic surface of basic CGP — `HasField` field access, `#[cgp_fn]` single-implementation capabilities, `#[implicit]` arguments, and getter traits — the constructs that let CGP code read like ordinary Rust functions and accessors. + +## How field access works underneath: `HasField` + +Every value a provider reads out of its context flows through one tiny consumer trait, `HasField`, which keys a single field by a *type-level* name rather than by the concrete struct it lives in. A provider is generic over its context and cannot reach into a struct it does not know, so instead of naming the field directly it demands one by tag: a `where`-clause bound `Context: HasField` says "any context wired to me must carry a `String` field called `name`," and the trait system supplies it. This makes field access an [impl-side dependency](components.md) — a requirement hidden from the trait interface and satisfied automatically by any matching context. Assume `use cgp::prelude::*;` throughout; the CGP version is v0.7.0. + +The trait carries the field's type as an associated `Value` and returns a reference, taking a `PhantomData` argument whose only job is to tell the compiler which field is meant when several `HasField` impls are in scope: + +```rust +pub trait HasField { + type Value; + fn get_field(&self, _tag: PhantomData) -> &Self::Value; +} +``` + +The `Tag` is a type-level name, and CGP has two kinds. A named struct field is keyed by `Symbol!("field_name")`, the type-level string of its identifier; a tuple field is keyed by `Index`, the type-level natural number of its position. Both are [type-level primitives](type-level-primitives.md) — types with no values — which is exactly why `get_field` needs the `PhantomData` argument to carry one at the call site. A `HasFieldMut: HasField` companion adds `get_field_mut` returning `&mut Self::Value` for the rarer mutable case. + +Reading a field is the PhantomData tag-inference trick in practice. Inside a provider body you write `self.get_field(PhantomData)` and let Rust infer the tag from the surrounding bound, or pin it explicitly with `self.get_field(PhantomData::)` when more than one field could match: + +```rust +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasField, +{ + fn greet(&self) { + println!("Hello, {}!", self.get_field(PhantomData)); + } +} +``` + +You almost never write `HasField` impls by hand. The companion derive does it for you. + +## Generating field access: `#[derive(HasField)]` + +`#[derive(HasField)]` turns a struct's concrete fields into the type-level entries the trait system looks up, emitting one `HasField` and one `HasFieldMut` impl per field and leaving the struct definition untouched. It is the bridge between an ordinary Rust struct and constraint-based field access: without it, a struct's fields are invisible to CGP, and every getter would be hand-written. For a named struct, + +```rust +#[derive(HasField)] +pub struct Person { + pub name: String, + pub age: u8, +} +``` + +the derive emits a `Symbol!`-keyed pair per field — `impl HasField for Person { type Value = String; … }` reading `&self.name`, and the same for `age` — so `Person` satisfies `HasField` exactly as the `GreetHello` bound above requires. A tuple struct expands identically except each field is keyed by its positional `Index` instead of a `Symbol!`: + +```rust +#[derive(HasField)] +pub struct Rectangle(pub f64, pub f64); +// → impl HasField> for Rectangle { type Value = f64; … &self.0 } +// → impl HasField> for Rectangle { type Value = f64; … &self.1 } +``` + +Generic parameters thread through faithfully, and field access also flows through smart pointers — `HasField` has a blanket impl over any `Deref` target that has the field, so a `Box` resolves `get_field` to the inner struct. Deriving `HasField` is the one thing a context must do for every higher-level construct on this page — `#[cgp_fn]`, `#[implicit]`, and both getter macros all desugar into `HasField` bounds. + +## Single-implementation capabilities: `#[cgp_fn]` + +`#[cgp_fn]` turns a plain function into a CGP capability that every context gains automatically, with no wiring step at all. A full [component](components.md) defines a consumer trait, a provider trait, and a delegation table so that many providers can be swapped per context; `#[cgp_fn]` is the lightweight counterpart for the common case where a capability has a single natural definition. You write the body as if `self` were concrete, and the macro emits a trait plus a *blanket* impl over a generic context — so the method becomes available on every type that satisfies the body's impl-side dependencies, with no `delegate_components!` block and no provider struct anywhere. + +The function name in snake case becomes the method name, and the trait name defaults to that name in PascalCase. Given: + +```rust +#[cgp_fn] +pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height +} +``` + +the macro emits a `RectangleArea` trait whose method takes no arguments, and a blanket impl over the reserved context type `__Context__` in which each `#[implicit]` parameter became a `HasField` bound and a `get_field` binding at the top of the body: + +```rust +pub trait RectangleArea { + fn rectangle_area(&self) -> f64; +} + +impl<__Context__> RectangleArea for __Context__ +where + Self: HasField + + HasField, +{ + fn rectangle_area(&self) -> f64 { + let width: f64 = self.get_field(PhantomData::).clone(); + let height: f64 = self.get_field(PhantomData::).clone(); + width * height + } +} +``` + +That generated blanket impl is the whole point of the macro, so it is worth seeing: the context type parameter is literally `__Context__` and references to it inside the impl read as `Self`. Pass an identifier to override the default trait name, which is useful when a verb-style name reads better — `#[cgp_fn(CanCalculateRectangleArea)]` generates `CanCalculateRectangleArea` instead of `RectangleArea`. + +Generics and `where` clauses are handled with a deliberate split: every generic parameter in the function's `<...>` list goes onto *both* the trait and the impl, while the function's `where` bounds land only on the impl, hidden from the trait as impl-side dependencies. A generic area function makes this concrete: + +```rust +#[cgp_fn] +pub fn rectangle_area( + &self, + #[implicit] width: Scalar, + #[implicit] height: Scalar, +) -> Scalar +where + Scalar: Mul + Copy, +{ + width * height +} +``` + +Here `Scalar` appears on `RectangleArea` and its impl, while `Scalar: Mul + Copy` stays on the impl only, ordered before the implicit `HasField` bounds (which are always appended last). One restriction is intentional: `#[cgp_fn]` does not support generics on the *method* itself — method-level generics belong to the trait and impl, and the rare genuine need for them is an advanced case better written as an explicit blanket impl or a full component. + +## Field access dressed as a function argument: `#[implicit]` + +The `#[implicit]` attribute marks a function argument as sourced from a context field instead of from the caller, so a provider reads like a function taking parameters while behaving like one injecting dependencies. This is the recommended on-ramp to CGP: a programmer who understands functions and arguments can write a complete provider without first meeting `HasField`, `Symbol!`, or `PhantomData`. Strongly prefer implicit arguments in basic code — they keep CGP looking like ordinary Rust. The argument name doubles as the field name, so `#[implicit] width: f64` reads as "this function needs a `width` of type `f64`," and the macro removes the argument from the signature, adds the matching `HasField` bound, and binds the value at the top of the body (as the `#[cgp_fn]` expansion above shows). + +The argument type controls how the field is read, following a small set of rules so the body always receives exactly the declared type. An owned type such as `f64` or `String` reads the field by reference and appends `.clone()`, leaving the context's field intact; the one special case to memorize is `&str`, which is backed by a `String` field and read with `.as_str()` rather than `.clone()`, letting the body borrow without forcing the context to store a `&str`: + +```rust +#[cgp_fn] +pub fn greet(&self, #[implicit] name: &str) { + println!("Hello, {}!", name); +} +// bound: HasField +// binding: let name: &str = self.get_field(PhantomData::).as_str(); +``` + +Three rules constrain where `#[implicit]` may appear. The function must take `self` as its first argument, since the field is read from it; the argument pattern must be a bare identifier, not a destructuring or `mut` pattern (clone inside the body for a mutable local); and a `&mut self` receiver allows at most one implicit argument, since each borrows from the same context. `#[implicit]` is usable in both `#[cgp_fn]` and the methods of a `#[cgp_impl]` provider, with the same desugaring in each — inside `#[cgp_impl]` the `HasField` bounds simply join the provider impl's `where` clause. + +## Importing capabilities: `#[uses]` + +`#[uses(...)]` adds `Self: Trait<...>` bounds to a provider's `where` clause, written to read like a `use` import of the capabilities the body depends on. A provider that calls another `#[cgp_fn]` capability, or a [component](components.md) consumer trait, must require the context to implement it — a `where Self: SomeTrait` bound that reads as machinery. `#[uses(RectangleArea)]` instead reads as "this function uses the `RectangleArea` capability," and the macro turns each listed name into the corresponding `Self` bound on the impl so the body can call those methods directly on `self`. Building a scaled area on top of the base one: + +```rust +#[cgp_fn] +#[uses(RectangleArea)] +pub fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { + self.rectangle_area() * scale_factor * scale_factor +} +``` + +This adds `Self: RectangleArea` to the generated impl's `where` clause, alongside the `HasField` bound from the implicit `scale_factor` — the imported bound lands on the impl only, never on the trait, exactly like writing `where Self: RectangleArea` by hand. The syntax accepts only the simplified `TraitIdent` form, deliberately: because it is meant to read like an import, it does not accept associated-type-equality bounds such as `Iterator` — write those as an explicit `where` clause in the body instead. `#[uses(...)]` works in both `#[cgp_fn]` and `#[cgp_impl]`, and the imported capability may itself be defined either way. + +## Adding supertraits and trait bounds: `#[extend]` and `#[extend_where]` + +In `#[cgp_fn]`, the function's own `where` clauses are impl-side dependencies kept off the trait, so there is no place to write a supertrait by hand — `#[extend(...)]` is the only way to add one. Where `#[uses]` adds a hidden impl-side bound (the `use` equivalent), `#[extend]` promotes its bound to a *supertrait* of the generated trait — a public requirement every implementor satisfies and every caller may rely on (the `pub use` equivalent). The bound appears in two places: as a supertrait on the trait, so an associated type like `Self::Scalar` resolves and callers know the bound holds, and in the impl's `where` clause so the body can use it. A `#[cgp_fn]` over an abstract scalar type: + +```rust +pub trait HasScalarType { + type Scalar: Clone + Mul; +} + +#[cgp_fn] +#[extend(HasScalarType)] +pub fn rectangle_area( + &self, + #[implicit] width: Self::Scalar, + #[implicit] height: Self::Scalar, +) -> Self::Scalar { + width * height +} +// → pub trait RectangleArea: HasScalarType { fn rectangle_area(&self) -> Self::Scalar; } +``` + +`#[extend]` accepts the same simplified `TraitIdent` syntax as `#[uses]`, and is also usable on `#[cgp_component]` (where it simply duplicates the native `pub trait Foo: Bar` supertrait syntax for stylistic consistency). Its sibling `#[extend_where(...)]` adds *`where`-clause* predicates to the generated trait definition rather than supertraits, and is `#[cgp_fn]`-only. Unlike `#[uses]` and `#[extend]`, it accepts arbitrary predicates — including associated-type equality — so a generic parameter can carry a publicly visible bound: + +```rust +#[cgp_fn] +#[extend_where(Scalar: Clone)] +pub fn rectangle_area(/* … */) -> Scalar +where + Scalar: Mul, +{ /* … */ } +// → pub trait RectangleArea where Scalar: Clone { fn rectangle_area(&self) -> Scalar; } +``` + +The `Scalar: Mul` bound from the body stays an impl-side dependency, while `Scalar: Clone` from `#[extend_where]` is promoted onto the trait so any code naming `RectangleArea` can rely on it without restating it. + +## Getter traits: `#[cgp_auto_getter]` + +A getter trait exposes a context field as a reusable `self.name()` accessor, and `#[cgp_auto_getter]` generates its single blanket impl by reading the field whose name matches the method name. Where an [implicit argument](#field-access-dressed-as-a-function-argument-implicit) injects a value once at the start of one method, a getter is the better tool when the same field is read across many methods or part-way through a body. The macro takes no arguments and re-emits the trait verbatim, adding a blanket impl over `__Context__` keyed by the method name as a `Symbol!`: + +```rust +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +// generated: +impl<__Context__> HasName for __Context__ +where + __Context__: HasField, +{ + fn name(&self) -> &str { + self.get_field(PhantomData::).as_str() + } +} +``` + +That generated blanket impl is the point of the macro, and it follows the same access rules as `#[implicit]`: a plain `&T` return reads a `T` field directly, while the `&str` shorthand reads a `String` field and appends `.as_str()`. Other shorthands include `Option<&T>` (an `Option` field via `.as_ref()`), `&[T]` (a field implementing `AsRef<[T]>`), an owned `T` (a `Copy` field by value), and `&mut T` with a `&mut self` receiver (mutable access via `get_field_mut`). A trait may declare several methods, each mapping independently to its own field — `fn width(&self) -> &f64; fn height(&self) -> &f64;` produces one `where` predicate and one body per field in the same impl. + +A single getter may also declare a local associated type and use it as its return type, which lets the abstract type be inferred from the field. The trait must then contain exactly one method returning `&Self::AssocType`; the macro lifts the type into a generic parameter on the impl and binds it through the `HasField` `Value`: + +```rust +#[cgp_auto_getter] +pub trait HasName { + type Name: Display; + fn name(&self) -> &Self::Name; +} +// → impl<__Context__, Name> HasName for __Context__ +// where Name: Display, __Context__: HasField +// { type Name = Name; … } +``` + +A context gains the getter just by deriving `HasField` with a matching field — `person.name()` resolves through the blanket impl with no wiring. The cost of that simplicity is rigidity: the field name *must* equal the method name, and there is no way to swap the implementation. When you need either, reach for `#[cgp_getter]`. + +## Wireable getters: `#[cgp_getter]` and `UseField` + +`#[cgp_getter]` defines a getter as a full CGP [component](components.md) instead of a blanket impl, so the field name can differ from the method name and the getter can be swapped per context through [wiring](wiring.md). It accepts the same getter-method forms as `#[cgp_auto_getter]`, but because it is an extension of `#[cgp_component]` it needs a provider trait name. The default derives one from the trait name by stripping a leading `Has` and appending `Getter`, so `HasName` yields the provider `NameGetter` and the component marker `NameGetterComponent`; pass an argument like `#[cgp_getter(GetName)]` to override it. + +The decoupling is delivered by an automatically generated `UseField` provider impl. `UseField` is a zero-sized provider (a `PhantomData`-only marker named in wiring, carrying no runtime value) that implements the getter by reading the field named `Tag` from the context — and crucially, `Tag` need not be the method name: + +```rust +#[cgp_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[derive(HasField)] +pub struct Person { + pub first_name: String, +} + +delegate_components! { + Person { + NameGetterComponent: UseField, + } +} +// person.name() now reads the first_name field +``` + +The trait method is `name` but the context stores the value in `first_name`, and the wiring `NameGetterComponent: UseField` bridges the two — the field name lives in the wiring, not in the trait. Internally `#[cgp_getter]` generates a `UseField` impl whose tag is left as a free parameter, in contrast to the `#[cgp_auto_getter]` blanket impl that hard-codes the tag to the method name. The macro also emits a `UseFields` provider (the provider-side analogue of the auto-getter blanket impl, keyed by method name) and, for single-method getters, a `WithProvider` adapter. + +For getters whose return type is reached *through* a field by `AsRef`/`AsMut` rather than being the field itself, the related `UseFieldRef` provider reads the field at `Tag` and calls `as_ref()` to expose `&Value` — for example `UseFieldRef` exposes `&str` from a `String` field. It decouples the exposed type from the stored type as well as the field name from the method name. Unlike `UseField`, it is not re-exported through the prelude; reach it through `cgp::core::field::impls`. + +## Getters are just traits: explicit implementation + +Every getter the macros produce is an ordinary trait, and explicit implementation is always available — the macros only save boilerplate. This matters when a context does not derive `HasField`, or stores the value under a name no tag matches. Because `#[cgp_auto_getter]` adds only a blanket impl and `#[cgp_getter]` a component whose consumer trait is plain Rust, you can write the impl by hand on a concrete type: + +```rust +pub struct Person { + pub full_name: String, +} + +impl HasName for Person { + fn name(&self) -> &str { + &self.full_name + } +} +``` + +The explicit form is more verbose but requires no understanding of `HasField` or blanket impls — a reminder that the whole apparatus on this page is convenience layered over vanilla Rust traits. + +## Choosing between the constructs + +The constructs here divide along two axes: how the value is read, and how flexible the implementation is. For a value consumed once at the start of a method, an `#[implicit]` argument keeps the access local and the code reading like a plain function. For a field read across several methods or mid-body, a getter trait exposes it as a reusable accessor; pick `#[cgp_auto_getter]` when the field name always matches the method name and no alternative is needed, and `#[cgp_getter]` (with `UseField`) when the field name must differ or the getter must be swappable per context. For a whole capability rather than a single field, `#[cgp_fn]` defines one with no wiring when a single implementation suffices, and a full [component](components.md) when many providers must coexist. All of them rest on the same `HasField` machinery and the same access rules, so mixing them carries no conceptual overhead. + +## Further reference + +Online docs (current v0.7.0): [`#[cgp_fn]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_fn.md), [`HasField`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/has_field.md), [`#[derive(HasField)]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/derives/derive_has_field.md), [`#[implicit]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/implicit.md), [`#[uses]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/uses.md), [`#[extend]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/extend.md), [`#[extend_where]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/extend_where.md), [`#[cgp_auto_getter]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_auto_getter.md), [`#[cgp_getter]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_getter.md), [`UseField`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/use_field.md), [`UseFieldRef`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/use_field_ref.md), and the [implicit-arguments concept](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/implicit-arguments.md). diff --git a/docs/skills/cgp/references/handlers.md b/docs/skills/cgp/references/handlers.md new file mode 100644 index 00000000..3350f45f --- /dev/null +++ b/docs/skills/cgp/references/handlers.md @@ -0,0 +1,242 @@ +# Handlers + +The handler family is a spectrum of CGP components that all transform an `Input` into an `Output` under a phantom `Code` tag, varying along three axes — synchronous vs. async, infallible vs. fallible, and input vs. no-input — together with the macros that build handlers from functions, the combinators that compose and lift them, the dispatchers that route over extensible data, and the monads that chain them. + +Assume `use cgp::prelude::*;` throughout; the CGP version is v0.7.0. Every handler is an ordinary [component](components.md) — a consumer trait the context calls, a provider trait a zero-sized provider implements, and a `…Component` marker that [wiring](wiring.md) maps to a provider — so nothing here needs machinery beyond what you already know. + +## The shared shape and the three axes + +Every handler maps `(context, Code, Input) -> Output`, where `Code` is a phantom tag carried as `PhantomData` and `Output` is an *associated type* the provider chooses, not a parameter the caller fixes. The `Code` tag holds no data; it exists so one context can host many handlers keyed by distinct tags, and so wiring can dispatch on it. The family is large because real computations differ along three independent axes, and each combination gets its own component so a provider declares exactly the capabilities it has. + +The first axis is **synchronous vs. async**: an async component returns a future and its method is `async`, marked by `Async` in the name (`AsyncComputer` is the async `Computer`). The second is **infallible vs. fallible**: a fallible component returns `Result` against the context's abstract error and so supertraits [`HasErrorType`](abstract-types.md), marked by the `Try` prefix. The third is **owned vs. by-reference input**: every base component has a `*Ref` sibling that borrows `&Input` instead of consuming `Input`. The general principle is that you write the *weakest* variant that fits and let combinators promote it upward, because an infallible computation is trivially fallible, a sync one trivially async, and an owned-input one can serve a borrow — but never the reverse. + +The components, by corner, are these. `Computer`/`CanCompute` is the pure-computation base — synchronous and infallible, with `AsyncComputer` and the `*Ref` variants alongside it. `TryComputer`/`CanTryCompute` is fallible but synchronous. `Handler`/`CanHandle` is the fully general async-and-fallible computation, the bound a generic consumer targets because every simpler provider promotes up to it. `Producer`/`CanProduce` sits apart as the no-input case, producing a value from the context and `Code` alone. A related pair, `CanRun`/`CanSendRun`, runs a named task rather than transforming a value. + +## The computation components + +`CanCompute` is the simplest member and the one to reach for first. Its method takes the `Code` tag and the input by value and returns the chosen `Output` with no failure path: + +```rust +#[cgp_component(Computer)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanCompute { + type Output; + fn compute(&self, _code: PhantomData, input: Input) -> Self::Output; +} +``` + +The provider trait `Computer` moves the context into an explicit first parameter, as for any component; the marker is `ComputerComponent`. The two `#[derive_delegate]` directives let a context route to different providers by `Code` (via `UseDelegate`) or by `Input` type (via the family's `UseInputDelegate`), the basis of [dispatching](#dispatching-over-extensible-data). `AsyncComputer`/`CanComputeAsync` is identical but declares `async fn compute_async` under `#[async_trait]`, and `ComputerRef`/`AsyncComputerRef` borrow the input as `&Input`. None of the four supertrait `HasErrorType`, because none can fail. + +`CanTryCompute` adds the failure path. It supertraits `HasErrorType` so its `Result` can name the context's abstract error, which is what keeps a provider generic over the error backend: + +```rust +#[cgp_component(TryComputer)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanTryCompute: HasErrorType { + type Output; + fn try_compute(&self, _code: PhantomData, input: Input) + -> Result; +} +``` + +A `TryComputer` provider carries a `Context: HasErrorType` bound and typically converts a concrete source error into the abstract one with [`CanRaiseError`](error-handling.md). `TryComputerRef` is the by-reference sibling. + +`CanHandle` is the general corner — async *and* fallible — and is the bound generic pipeline code targets, since `Computer`, `AsyncComputer`, and `TryComputer` all promote up to it: + +```rust +#[async_trait] +#[cgp_component(Handler)] +#[derive_delegate(UseDelegate)] +#[derive_delegate(UseInputDelegate)] +pub trait CanHandle: HasErrorType { + type Output; + async fn handle(&self, _tag: PhantomData, input: Input) + -> Result; +} +``` + +A function bounded by `Context: CanHandle` accepts any wired computation regardless of which capabilities the underlying provider actually uses. `HandlerRef` borrows the input. + +`CanProduce` is the no-input case: it threads no `Input`, only the context and a `Code` tag, and so carries a single `#[derive_delegate(UseDelegate)]` with no `UseInputDelegate` counterpart and does not supertrait `HasErrorType`: + +```rust +#[cgp_component(Producer)] +#[derive_delegate(UseDelegate)] +pub trait CanProduce { + type Output; + fn produce(&self, _code: PhantomData) -> Self::Output; +} +``` + +A producer is the natural source of values that flow into a pipeline — a constant or a context-derived default — and promotes into any input-taking handler by ignoring the supplied input. + +A handler provider is an ordinary zero-sized provider implementing the provider trait for a generic context. The built-in `UseField` is a `Computer` that forwards the computation to the value held in the context's `Tag` field; beyond it, the combinators below supply the rest. + +## Task runners: `CanRun` and `CanSendRun` + +The runner pair executes a unit of work selected by a `Code` tag rather than transforming a value, completing to `Result<(), Error>`. `CanRun` is the base async form; `CanSendRun` exists to recover a `Send` future for spawning: + +```rust +#[cgp_component(Runner)] +#[async_trait] +#[derive_delegate(UseDelegate)] +pub trait CanRun: HasErrorType { + async fn run(&self, _code: PhantomData) -> Result<(), Self::Error>; +} + +#[cgp_component(SendRunner)] +#[async_trait] +#[derive_delegate(UseDelegate)] +pub trait CanSendRun: HasErrorType { + fn send_run(&self, _code: PhantomData) + -> impl Future> + Send; +} +``` + +A context hosts many tasks by wiring `RunnerComponent` to a `UseDelegate` table keyed on `Code`, so `app.run(PhantomData::)` and `app.run(PhantomData::)` reach distinct providers. A runner provider reaches the runtime through `HasRuntime` to spawn or await work; the `CanSendRun` variant is the mechanism for the `Send`-recovery pattern described under [recovering `Send` bounds](#recovering-send-bounds). + +## Defining handlers from functions + +`#[cgp_computer]` turns a plain function into a `Computer` provider and wires the rest of the family by promotion, so a computation as small as "add two numbers" needs no hand-written plumbing: + +```rust +#[cgp_computer] +fn add(a: u64, b: u64) -> u64 { + a + b +} +``` + +The function name becomes the provider name in PascalCase (`Add`), unless an explicit name is given as `#[cgp_computer(MyAdder)]`. The parameters are collected into a tuple that becomes the single `Input` type and destructured back inside the generated method; the return type becomes `Output`. The macro emits the function unchanged, a `#[cgp_new_provider]` impl of the base trait, and a `delegate_components!` block routing every other handler component to a promotion bundle: + +```rust +#[cgp_new_provider] +impl<__Context__, __Code__> Computer<__Context__, __Code__, (u64, u64)> for Add { + type Output = u64; + fn compute(_context: &__Context__, _code: PhantomData<__Code__>, + (arg_0, arg_1): (u64, u64)) -> Self::Output { + add(arg_0, arg_1) + } +} +// delegate_components! routes the rest of the family to PromoteComputer +``` + +The base trait and bundle are chosen from two axes the macro reads off the signature. A synchronous plain-value function uses `Computer` + `PromoteComputer`; a synchronous `Result`-returning one keeps `Computer` as the base (its `Output` is the `Result`) but uses `PromoteTryComputer`, which surfaces the `Ok`/`Err` as genuine success/failure; an `async` plain-value function uses `AsyncComputer` + `PromoteAsyncComputer`; an `async` `Result` function uses `AsyncComputer` + `PromoteHandler`. Generic parameters and `where` clauses carry onto the impl; a `&Value` parameter makes the input tuple borrow and the bundle's `PromoteRef` entries serve the `*Ref` components. The result is that one `add` answers `compute`, `try_compute`, `compute_async`, and `handle`. + +`#[cgp_producer]` is the input-less sibling, turning a zero-argument function into a `Producer` and wiring `PromoteProducer` across the whole family: + +```rust +#[cgp_producer] +fn magic_number() -> u64 { + 42 +} +``` + +The function must take no parameters, must not be `async`, and must have no generics — a producer's shape is fixed, so there is no variation in the expansion. The generated `MagicNumber` answers `produce` and, because `PromoteProducer` discards the input that each computer slot supplies, also `compute`, `try_compute`, `handle`, and the `*Ref` forms, every one yielding `42`. + +## Composing and promoting with handler combinators + +The handler combinators are zero-sized providers of `cgp-handler` that build, sequence, and lift handlers, carrying their inner providers in `PhantomData`. They divide into composition, the identity element, and promotion. + +`ComposeHandlers` runs two handlers back to back, pinning the second's input to the first's output and exposing the pair as a member of every handler family; the fallible variants `?`-short-circuit and the async variants `.await` each step. `PipeHandlers` generalizes it to a `Product![...]` list, folding right to left so `PipeHandlers` behaves as `ComposeHandlers>` and serves whichever handler shape the wiring asks for, provided every stage supports it: + +```rust +delegate_components! { + MyContext { + ComputerComponent: + PipeHandlers, + Add, + Multiply, + ]>, + } +} +// input 5 over foo=2, bar=3, baz=4 -> ((5 * 2) + 3) * 4 +``` + +`ReturnInput` is the identity handler — it ignores the context and `Code` and returns its input unchanged (wrapped in `Ok` for the fallible variants) — and is the neutral element of composition, useful as a placeholder stage. + +The promotion combinators each take one inner provider and re-expose it under a different, more capable family member, encoding the one-directional lifts the axes permit. `Promote` lifts upward without adding behavior: a `Producer` into a `Computer` (ignoring the input), a `Computer` into a `TryComputer` (wrapping in `Ok`), an `AsyncComputer` into a `Handler`. `PromoteAsync` lifts a sync provider into an async one whose future is immediately ready. `PromoteRef` bridges value and reference handlers by dereferencing or re-borrowing. `TryPromote` bridges a `Result`-valued `Computer` and a genuine `TryComputer` in both directions. You rarely name these one at a time; instead the *promotion bundles* — `PromoteComputer`, `PromoteTryComputer`, `PromoteProducer`, `PromoteAsyncComputer`, `PromoteHandler` — are `delegate_components!` tables that wire a whole cluster of components to the right single-step promoter from a given base. These are exactly what `#[cgp_computer]` and `#[cgp_producer]` wire for you, and reaching for `PromoteComputer` by hand achieves the same when wiring explicitly. + +## Dispatching over extensible data + +Dispatching routes an [extensible-data](extensible-data.md) value to per-variant or per-field handlers: for an enum, match the current variant and run its handler; for a record, run a handler per field to build it. It keeps the per-variant/per-field structure of a concrete `match` or struct literal but lets the shape and handlers be chosen by type, so the same matcher serves many enums. + +`#[cgp_auto_dispatch]` is the highest-level entry point. Written above a trait that already has one impl per payload type, it generates a blanket impl of that trait for any extensible enum of those types, dispatching each variant to that payload's impl: + +```rust +#[cgp_auto_dispatch] +pub trait HasArea { + fn area(&self) -> f64; +} +// with impls for Circle and Rectangle, and a #[derive(CgpData)] enum Shape, +// HasArea is now implemented for Shape too: +let shape = Shape::Rectangle(Rectangle { width: 2.0, height: 2.0 }); +assert_eq!(shape.area(), 4.0); +``` + +For each method it emits a per-variant computer via `#[cgp_computer]` (named `Compute` plus the method name) and an enum-level impl that runs the appropriate value-handler matcher — `MatchWithValueHandlers` for `&self`, its `Mut` form for `&mut self`, and the `MatchFirstWith…` family when the method takes extra arguments, with the `Async` form for `async` methods. A method may not have non-lifetime generic parameters, since the generated impl would need a quantified bound Rust lacks; such a method must use the combinators directly. + +Underneath, the dispatch combinators of `cgp-dispatch` express both directions as handler providers. On the matching side, `MatchWithHandlers` (with `Ref`/`Mut` and `MatchFirstWith…` forms) converts the input to its extractor and runs a list of per-variant adapters — `ExtractFieldAndHandle` tries one variant and forwards its payload, `HandleFieldValue` strips the `Field` wrapper to hand the bare value to a computer, and `DowncastAndHandle` matches a group of variants to a sub-matcher. The list runs as a monadic pipeline that short-circuits on the first match and proves exhaustiveness without a wildcard, because each miss rules out one variant until the final remainder is uninhabited. `MatchWithValueHandlers` builds that list automatically from the enum's own field list. Dispatch on the *input* type uses the `Computer` component's `UseInputDelegate` directive, wired as a nested table with one entry per input type: + +```rust +delegate_components! { + App { + ComputerComponent: + UseInputDelegate, + } +} +``` + +Each entry routes one input type (the array form sharing a provider across several), so a `Circle` or `Rectangle` input reaches `ComputeArea` while a whole `Shape` enum reaches the matcher. This input-type dispatch keys on the `Input` parameter through the component's `UseInputDelegate` directive, which is why it is written as a nested table rather than through the `open` statement — `open` rides the default `RedirectLookup` that keys on the primary `Code` parameter (see [wiring](wiring.md) for the `open` form, which is the modern way to do `Code`-keyed dispatch). The nested-table form is the established way to dispatch on the input type, and this concerns only the *wiring* — the dispatch combinators above are unaffected. + +On the building side, `BuildWithHandlers` starts from an empty builder, pipes it through per-field adapters — `BuildAndSetField` computes and sets one field, `BuildAndMerge` merges a whole record's shared fields — and finalizes. Because finalization is available only when every field is present, omitting a handler for a field is a compile error rather than a runtime half-built value. + +## Composing through a monad + +Plain composition feeds each output straight into the next step, which is wrong the moment a step can produce a value meaning "stop here." Monadic handlers solve this: the monad decides which branch of a step's output threads forward and which short-circuits out as the final result. `PipeMonadic` is the entry point — a monad marker plus a `Product![...]` list — and the pipeline it builds is itself a `Computer`-family provider: + +```rust +PipeMonadic::::compute(&context, code, 253) +// 253 -> Ok(254) -> Ok(255) -> Err("overflow"); the third overflow becomes the output +``` + +CGP ships three monad markers. `IdentMonadic` threads every value forward and never short-circuits, recovering plain `PipeHandlers` composition. `ErrMonadic` short-circuits on `Err` and continues on `Ok` — the `?`-style early return where the first error wins. `OkMonadic` is the mirror, stopping at the first `Ok`, which suits retry-until-success and is what the dispatch matcher loop runs under. The two `Result`-branching markers have transformer forms, `OkMonadicTrans` and `ErrMonadicTrans`, that stack their behavior on a base monad so a pipeline over a nested `Result, F>` can short-circuit on the outer error while threading the inner result. + +The per-step providers `BindOk` and `BindErr` implement a single bind and are what `PipeMonadic` composes internally; they can also be dropped into a `PipeHandlers` list directly for step-by-step control. `BindErr` runs `Cont` on an `Ok` payload and short-circuits an `Err`; `BindOk` is its mirror. `PipeMonadic` also implements the fallible and async-fallible components by demoting each provider through `TryPromote`, applying `ErrMonadic` as a transformer over `M`, and re-wrapping — so a monadic pipeline reached through `try_compute` or `handle` short-circuits on the context's error type as well. + +## Recovering `Send` bounds + +An async trait method advertises a future whose auto-traits the caller cannot name. The `#[async_trait]` rewrite turns `async fn handle(..)` into `fn handle(..) -> impl Future<..>` with no boxing and no `Send`, which is faithful and zero-cost but drops the `Send` guarantee. The bound becomes load-bearing when the future is spawned onto a work-stealing executor (the default Tokio runtime an Axum server uses), which may migrate a suspended task between threads. The clause you want — "the future of `handle` is `Send` for any arguments" — is Return Type Notation (`handle(..): Send`), which stable Rust does not yet offer. + +The workaround is a second, ordinary trait whose method states `+ Send` directly in its return type, sidestepping RTN: + +```rust +pub trait CanHandleApiSend: + CanHandleApi + Send + Sync +{ + fn handle_api_send(&self, _api: PhantomData, request: Self::Request) + -> impl Future> + Send; +} +``` + +This is a plain trait, not a component — it adds nothing to the wiring and exists only to carry the stronger bound. It cannot be implemented with a single generic blanket impl, because the body wraps `self.handle_api(..)` in an `async` block whose `Send`-ness depends on the opaque future it awaits — the same gap restated, just RTN in disguise. The impl must therefore be written per *concrete* `(context, API)` pair, where `self.handle_api(api, request)` resolves through the wiring to a concrete future whose auto-traits the compiler computes structurally and finds `Send`: + +```rust +impl CanHandleApiSend for MockApp { + async fn handle_api_send(&self, api: PhantomData, request: Self::Request) + -> Result { + self.handle_api(api, request).await + } +} +``` + +Each impl is mechanical forwarding, yet each is also a proof accepted only because the future really is `Send` at that instantiation. One concrete impl per API per context replaces the single generic impl RTN would have allowed — the cost of the missing notation. The built-in `CanSendRun` runner applies the same pattern as a `SendRunner` proxy on the concrete context, letting a spawning runner provider clone the context into a `Send` future without `Send` bounds leaking into any abstract interface. + +## Further reference + +Online docs: [concepts/handlers.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/handlers.md), [concepts/dispatching.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/dispatching.md), [concepts/monadic-handlers.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/monadic-handlers.md), [concepts/send-bounds.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/send-bounds.md); reference docs [components/computer.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/computer.md), [components/try_computer.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/try_computer.md), [components/handler.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/handler.md), [components/producer.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/producer.md), [components/runner.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/runner.md), [macros/cgp_computer.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_computer.md), [macros/cgp_producer.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_producer.md), [macros/cgp_auto_dispatch.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_auto_dispatch.md), [providers/handler_combinators.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/handler_combinators.md), [providers/dispatch_combinators.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/dispatch_combinators.md), [providers/monad_providers.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/monad_providers.md). diff --git a/docs/skills/cgp/references/higher-order-providers.md b/docs/skills/cgp/references/higher-order-providers.md new file mode 100644 index 00000000..e440ef2e --- /dev/null +++ b/docs/skills/cgp/references/higher-order-providers.md @@ -0,0 +1,151 @@ +# Higher-order providers + +How one provider takes another provider as a generic parameter and builds on whatever that inner provider computes — the type-level counterpart to passing a function to a function. + +A higher-order provider is a [provider](components.md) parameterized by another provider, so part of its behavior is supplied by an inner provider it delegates to rather than fixed in its own code. The motivating shape is a wrapper that transforms the output of an existing implementation: a `ScaledArea` that multiplies whatever area an inner calculator produces, an `IterSumArea` that sums the areas an inner calculator returns over a collection, a `GloballyScaledArea` that applies a context-wide factor on top of a base calculation. In each case the outer provider knows the *transformation* but not the *base case* — the base case is a parameter. Because a provider carries no runtime value (it is a zero-sized marker naming an implementation), nesting providers costs nothing: the composition happens entirely in types. Assume `use cgp::prelude::*;` throughout; the CGP version is v0.7.0. + +## The shape, and the stray `` + +A higher-order provider is written like any other provider with `#[cgp_impl]`, with the inner provider declared as a generic parameter in the provider's `Self` position and bound to a *provider trait* in the `where` clause. The detail that makes it look stranger than it is the extra `` on that inner bound. Recall that a [provider trait](components.md) moves the consumer trait's `Self` into an explicit leading `Context` parameter — `AreaCalculator`, not the consumer trait's `CanCalculateArea`. So when the outer provider depends on its inner provider, the bound must name that context argument explicitly: + +```rust +#[cgp_impl(new ScaledArea)] +impl AreaCalculator +where + InnerCalculator: AreaCalculator, +{ + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + InnerCalculator::area(self) * scale_factor * scale_factor + } +} +``` + +The `` in `InnerCalculator: AreaCalculator` is exactly that leading context slot, filled with the context the outer provider is implementing for. Inside `#[cgp_impl]`, `self`/`Self` mean the *context*, so `` is the context type. The same asymmetry shows at the call site: the inner provider is invoked as the associated function `InnerCalculator::area(self)`, not as a method `self.area()`, because the provider trait's method takes the context as an explicit first argument. A reader who thinks of a provider as "just the consumer trait implemented elsewhere" is caught off guard by both the bound and the call. + +## Hiding the friction with `#[use_provider]` + +The `#[use_provider]` attribute exists to erase the bound surprise, and is the idiomatic way to write higher-order providers — always reach for it, since it preserves the illusion that a provider trait has the same shape as the consumer trait it came from. Written alongside `#[cgp_impl]`, it takes the inner bound *without* the context argument and fills the `` back in for you: + +```rust +#[cgp_impl(new ScaledArea)] +#[use_provider(InnerCalculator: AreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] scale_factor: f64) -> f64 { + let base_area = InnerCalculator::area(self); + base_area * scale_factor * scale_factor + } +} +``` + +The author writes `InnerCalculator: AreaCalculator` and the macro inserts the context type at index 0 of the trait's generic arguments, emitting `InnerCalculator: AreaCalculator` into the `where` clause — the two snippets above are equivalent. The shape it parses is `Provider: Trait`: a provider type, a colon, and one or more provider-trait bounds joined by `+`; the trait may carry further generic arguments after the context slot, preserved in order behind the inserted `Self`. Bind several inner providers by separating bounds with commas in one attribute or by stacking multiple attributes. The same attribute works on `#[cgp_fn]`. + +What `#[use_provider]` does *not* do is rewrite the body. It completes the bound only; the inner provider is still invoked as the associated function `InnerCalculator::area(self)`, with `self` passed as the explicit context. There is no call-site form — a bare `#[use_provider(InnerCalculator)]` on an expression is not accepted, and no pass turns `receiver.method(args)` into `Provider::method(receiver, args)`. The body must spell the associated-function call out itself. Calling the inner provider as a method (`self.area()`) would instead route through whatever provider the context has wired for the component, a different dispatch and usually not what a higher-order provider wants. + +## `UseContext` as a default inner provider + +A higher-order provider can default its inner provider to [`UseContext`](wiring.md) so that, absent an explicit choice, it falls back to whatever the context itself is already wired to. This requires giving the provider an explicit struct definition with a default generic parameter: + +```rust +pub struct IterSumArea(pub PhantomData); +``` + +`UseContext` is the provider that implements any provider trait by deferring to the context's own consumer-trait implementation — the dual of the consumer blanket impl. With `IterSumArea` as the default, an unparameterized `IterSumArea` computes each inner element through the context's existing area wiring, so the wrapper drops in without restating the base case. Overriding the parameter — `IterSumArea` — instead binds the inner behavior statically, bypassing the context's wiring, which is useful for overriding or short-circuiting what the context would otherwise resolve. This default exists *only* when the provider has an explicit struct carrying the default parameter; a provider defined purely through `#[cgp_impl(new ...)]` has no default and must always have its inner provider named. + +## Not every generic provider is higher-order + +A provider that merely has generic parameters is not a higher-order provider; it becomes one only when a parameter is constrained to implement a provider trait. Many providers are generic for unrelated reasons. The `UseField`-style getter provider is generic over a field tag: + +```rust +#[cgp_impl(new GetName)] +impl NameGetter +where + Self: HasField, +{ + fn name(&self) -> &str { + self.get_field(PhantomData) + } +} +``` + +Here `Tag` is a type-level field name used only as a `HasField` key, with no provider-trait bound — so `GetName` is an ordinary parameterized provider, not a higher-order one. The defining trait of a higher-order provider is that a generic parameter appears in a *provider-trait* bound and is invoked to do part of the work, as `InnerCalculator: AreaCalculator` does. When that bound is absent, `#[use_provider]` has nothing to fill in and none of the machinery applies. + +## Generic-parameter CGP traits + +A component's trait may itself carry generic parameters, and `#[cgp_component]` appends them after the context slot. Declaring the area component to dispatch on a `Shape`: + +```rust +#[cgp_component(AreaCalculator)] +pub trait CanCalculateArea { + fn area(&self, shape: &Shape) -> f64; +} +``` + +generates the provider trait `AreaCalculator` — the original trait's parameters land *after* the leading `Context`, in declaration order. The `IsProviderFor` supertrait that every provider trait carries groups all those extra parameters into a single tuple in its `Params` slot (`()` when there are none), and any lifetime parameters are lifted into the `Life<'a>` type rather than appearing as bare lifetimes. A provider then writes `impl AreaCalculator for RectangleArea`, naming the concrete `Shape` it handles. + +## Dispatching on a generic parameter with `open` + +When the right provider depends on *which* concrete type a generic parameter is — `Rectangle` handled by one provider, `Circle` by another — the choice is made by a second lookup keyed on that parameter rather than on the component marker. The modern way a context writes this per-type dispatch is the `open` statement of `delegate_components!`, which folds the per-value entries directly into the context's own table. Given a `CanCalculateArea` consumer trait whose `Shape` parameter selects the area formula, a context opens the component and then assigns a provider per shape: + +```rust +delegate_components! { + MyApp { + open { AreaCalculatorComponent }; + + @AreaCalculatorComponent.Rectangle: RectangleArea, + @AreaCalculatorComponent.Circle: CircleArea, + } +} +``` + +The leading `open { AreaCalculatorComponent };` header opens the component for per-value wiring, and each `@AreaCalculatorComponent.Key: Provider` entry assigns a provider for one value of the dispatch parameter: when `Shape` is `Rectangle`, `MyApp` calculates area through `RectangleArea`, and `Circle` resolves to `CircleArea`. After this wiring, `MyApp` implements `CanCalculateArea` through `RectangleArea` and `CanCalculateArea` through `CircleArea`, with the dispatch parameter selecting between them. Adding a shape is one more entry; the providers stay untouched. The `open` form needs no extra macro on the component — it rides the `RedirectLookup` impl that every `#[cgp_component]` already generates, so dispatching a component per type requires no `#[derive_delegate]` on the trait. Two shorthands keep the entries compact: an array on the final path segment shares one provider across several values (`@AreaCalculatorComponent.[Rectangle, Circle]: SomeProvider`), and generic parameters precede the dispatch value when it needs them (`@AreaCalculatorComponent.<'a, T> &'a T: SomeProvider`). The [wiring](wiring.md) reference is the canonical home of the `open` statement and its grammar. + +### Legacy: `derive_delegate` and `UseDelegate` nested tables + +An older form generates a `UseDelegate` provider and wires the per-type entries into a separate table it points at. The `derive_delegate` option asks `#[cgp_component]` to generate that provider: + +```rust +#[cgp_component { + provider: AreaCalculator, + derive_delegate: UseDelegate, +}] +pub trait CanCalculateArea { + fn area(&self, shape: &Shape) -> f64; +} +``` + +`UseDelegate` is a zero-sized provider that treats its `Components` type as a type-level table — a `DelegateComponent` table keyed on the named parameter (`Shape`) — and forwards each method to the delegate that table maps the concrete type to. A context wires it through a nested table inside `delegate_components!`, building the outer entry and the inner lookup in one place: + +```rust +delegate_components! { + MyApp { + AreaCalculatorComponent: + UseDelegate, + } +} +``` + +This reads in two layers: `MyApp` delegates `AreaCalculatorComponent` to `UseDelegate`, and the inner table — defined inline by `new` — maps `Rectangle` to `RectangleArea` and `Circle` to `CircleArea`. The end effect matches the `open` example above, but the dispatch values live in a named side table reached through `UseDelegate` instead of in `MyApp`'s table directly. This is a legacy dispatch mechanism, retained for compatibility and expected to be deprecated; prefer `open` for new code, but expect to still encounter the `derive_delegate`/`UseDelegate` form when reading existing wiring (see [wiring](wiring.md) for both forms side by side). + +A component may dispatch on more than one parameter by listing several dispatchers. `derive_delegate: [ UseDelegate, UseInputDelegate ]` generates one impl per dispatcher — the default `UseDelegate` keying on `Code`, plus a user-defined `UseInputDelegate` (an ordinary struct of the same single-parameter shape) keying on `Input` — so each parameter is looked up through its own table. Only the parameter named in a dispatcher's angle brackets is used as the key; the others flow through. Per-type dispatch and higher-order providers compose either way: an `open` entry or a nested delegation table can map each shape to a different `ScaledArea<...>`. + +## Cross-context dependencies through a shared context + +The real leverage of generic-parameter dispatch appears when the main target of a capability is itself a generic parameter, and a supertrait constrains the *context* rather than each parameter type. Consider an area component whose result type is an abstract scalar the context supplies: + +```rust +#[cgp_component(AreaCalculator)] +pub trait CanCalculateAreaOfShape: HasScalarType { + fn area_of_shape(&self, shape: &Shape) -> Self::Scalar; +} +``` + +Because the shared capability — `HasScalarType` and any value-level injection — lives on the common context, the individual shape types (`Rectangle`, `Circle`) need not implement it themselves; they only know their own geometry. The context provides the shared abstract type ([abstract types](abstract-types.md)): one app might wire `Scalar` to `f64`, another to a fixed-point type, and every shape provider produces that scalar. The context also provides value-level injection — a `GloballyScaledArea` provider can read a global scale factor through a getter on the context and multiply every shape's area by it, so the scale is configured once per context rather than per shape. Most importantly, provider binding is *lazy and per-context*: a `BaseApp` and a `ScaledApp` can wire the very same `Shape` to different providers — `BaseApp` opening `AreaOfShapeCalculatorComponent` and mapping `@AreaOfShapeCalculatorComponent.Rectangle: RectangleArea` to a plain `RectangleArea`, `ScaledApp` mapping `@AreaOfShapeCalculatorComponent.Rectangle: GloballyScaledArea` instead — so the same generic capability resolves to different behavior in each context without either app's shapes knowing about the other. Each context writes its per-shape choices with the `open` statement detailed in [wiring](wiring.md); the legacy `derive_delegate`/`UseDelegate` nested-table form wires the same dispatch and still appears in existing code. Each layer of such nesting can be verified independently with the `#[check_providers]` form of `check_components!`, which is what makes higher-order wiring debuggable; see [checking](checking.md). + +## Related constructs + +Higher-order providers build on the consumer/provider split and the `IsProviderFor` propagation described in [components](components.md), and they are connected to contexts through [wiring](wiring.md), where `UseContext`, the `open` statement, and the legacy nested-table `UseDelegate` forms all live. `UseContext` lets the inner provider fall back to the context's own consumer-trait impl; the `open` statement dispatches a component on a generic parameter by folding per-value entries into the context's own table, with the legacy `UseDelegate` provider (generated by the `derive_delegate` option) doing the same through a separate side table; the abstract result types that make cross-context dependencies work are covered in [abstract types](abstract-types.md) and the type-level keys in [type-level primitives](type-level-primitives.md). Each layer of a nested provider is checked independently through [checking](checking.md). + +Further reference (online docs): [concepts/higher-order-providers.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/higher-order-providers.md), [reference/attributes/use_provider.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/use_provider.md), [reference/providers/use_delegate.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/use_delegate.md), [reference/attributes/derive_delegate.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/derive_delegate.md). diff --git a/docs/skills/cgp/references/modularity-hierarchy.md b/docs/skills/cgp/references/modularity-hierarchy.md new file mode 100644 index 00000000..57a55437 --- /dev/null +++ b/docs/skills/cgp/references/modularity-hierarchy.md @@ -0,0 +1,162 @@ +# Modularity hierarchy + +A spectrum of how decoupled an implementation can be from the type it serves, from plain generic functions up to per-provider wiring, so you can pick how much CGP machinery a problem actually needs. + +CGP is not all-or-nothing. The same capability — here, serializing a value with Serde — can be expressed at several levels of modularity, each more decoupled than the last and each carrying more machinery in exchange. This page walks the spectrum on one running example so a reader can stop at the first level that solves the problem rather than reaching for the heaviest tool by reflex. Assume `use cgp::prelude::*;` throughout; the CGP version is v0.7.0. + +## The coherence problem the hierarchy escapes + +What forces this hierarchy to exist is Rust's coherence rules, which guarantee that every trait lookup resolves to one globally unique implementation. Two rules enforce that uniqueness. The **overlap rule** forbids two implementations that could both apply to the same type — you cannot blanket-implement `Serialize` for every `T: Display` *and* for every `T: AsRef<[u8]>`, because a `String` satisfies both and the compiler has no principled way to choose. The **orphan rule** forbids implementing a trait for a type unless your crate owns either the trait or the type — you cannot implement someone else's `Serialize` for someone else's `Vec`. Each level below loosens one more of these constraints. CGP's escape route is to move the type that coherence ranges over — the `Self` of the implementation — into a position the implementing crate always owns, then restore a single unambiguous answer locally, one [context](components.md) at a time, through [wiring](wiring.md). See [coherence](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/coherence.md) for the full framing. + +## Level 1 — one implementation per interface + +The least machinery is a generic function or a blanket trait impl, which both define exactly one implementation behind an interface. A generic function captures the logic and its bounds in one place: + +```rust +pub fn serialize_bytes, S: Serializer>( + value: &Value, + serializer: S, +) -> Result { ... } +``` + +A blanket trait carries the same one-implementation limitation but reads more ergonomically at the call site, since the bound hides behind the trait impl and the caller writes a method: + +```rust +pub trait CanSerializeBytes { + fn serialize_bytes(&self, serializer: S) -> Result; +} + +impl> CanSerializeBytes for Value { + fn serialize_bytes(&self, serializer: S) -> Result { ... } +} +``` + +The gain is reuse with zero ceremony. The limitation is absolute: there can be exactly one blanket impl, so you cannot offer two ways to serialize bytes and let a caller pick between them. + +## Level 2 — one unique implementation per type per interface + +A vanilla Rust trait lifts the one-implementation limit slightly: many types may share the interface, but coherence still permits at most one implementation per type. Each type that wants the behavior writes its own impl: + +```rust +pub trait Serialize { + fn serialize(&self, serializer: S) -> Result; +} + +impl Serialize for Vec { + fn serialize(&self, serializer: S) -> Result { + self.serialize_bytes(serializer) + } +} + +impl<'a> Serialize for &'a [u8] { + fn serialize(&self, serializer: S) -> Result { + self.serialize_bytes(serializer) + } +} +``` + +The gain is that different types can be serialized differently. The cost is duplication: `Vec` and `&[u8]` each need an explicit impl even though the logic is identical. The body can still call out to a Level-1 building block such as `CanSerializeBytes` to share the actual work, so the duplication is confined to the boilerplate of forwarding. The remaining limitation is the one-impl-per-type ceiling — there is still no way to give `Vec` two serialization strategies and choose between them. + +## Level 3 — multiple implementations per type, globally unique wiring + +Applying basic CGP to a vanilla trait removes the duplication of Level 2 by turning the shared logic into a reusable [provider](components.md) and letting each type [wire](wiring.md) to it. The trait keeps its original shape; `#[cgp_component]` generates the [consumer trait](components.md) and [provider trait](components.md) pair, `#[cgp_impl(new ...)]` defines a named provider once, and `delegate_components!` points each type at it: + +```rust +#[cgp_component(ValueSerializer)] +pub trait Serialize { + fn serialize(&self, serializer: S) -> Result; +} + +#[cgp_impl(new SerializeBytes)] +impl> ValueSerializer for Value { + fn serialize(&self, serializer: S) -> Result { ... } +} + +delegate_components! { + Vec { + ValueSerializerComponent: SerializeBytes, + } +} + +delegate_components! { + <'a> &'a [u8] { + ValueSerializerComponent: SerializeBytes, + } +} +``` + +The gain is real reuse without modifying the interface: `Serialize` is unchanged, so a type can still implement it directly without opting into CGP at all, and existing users of the trait are unaffected. The `ValueSerializer` provider trait removes the need for ad-hoc interfaces like `CanSerializeBytes`, and `delegate_components!` removes the manual forwarding of Level 2. The limitation is that coherence still binds the wiring itself: each type carries one global wiring, so a `Vec` entry conflicts with any overlapping `Vec` entry, the choice cannot be overridden per context, and the orphan rule still means you can only wire `Vec` from a crate that owns either `Serialize` or `Vec`. + +## Level 4 — unique wiring per type, per context + +Adding an explicit context parameter fully decouples the implementation from the type, so each context wires its own choices and the orphan rule lifts entirely. The trait changes shape: the original `Self` becomes an explicit `Value` parameter, so the component now dispatches on which concrete value type it serializes. Each context then folds its per-type choices straight into its own table with the `open` statement of `delegate_components!`: + +```rust +#[cgp_component(ValueSerializer)] +pub trait CanSerializeValue { + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer; +} + +delegate_components! { + new MyAppA { + open { ValueSerializerComponent }; + + @ValueSerializerComponent.Vec: SerializeBytes, + @ValueSerializerComponent.Vec: SerializeIterator, + } +} + +delegate_components! { + new MyAppB { + open { ValueSerializerComponent }; + + @ValueSerializerComponent.Vec: SerializeHex, + @ValueSerializerComponent.Vec: SerializeIterator, + } +} +``` + +The `open { ValueSerializerComponent };` header opens the component for per-value wiring, and each `@ValueSerializerComponent.Value: Provider` entry assigns a provider for one concrete value type. The gain is that `MyAppA` and `MyAppB` resolve `Vec` to different providers — bytes versus hex — with no conflict, because each choice is coherent only within its own context. The orphan rule no longer applies: a context can wire `Vec` even when its crate owns neither `CanSerializeValue` nor `Vec`, as long as it owns the context type, so you never commit to a global serialization for `Vec` up front. The costs are that the trait must be modified to add the context parameter, and that every value type a context touches must be wired explicitly, which grows tedious for a large type set. + +The `open` form rides the dispatch machinery that every `#[cgp_component]` already generates, so the trait needs no extra option. A legacy alternative writes the same dispatch with `derive_delegate: UseDelegate` on the trait and a `UseDelegate: SerializeBytes, ... }>` nested table in each context's wiring; it is retained for compatibility but `open` is preferred for new code, and the two forms appear side by side in [wiring](wiring.md). + +## Level 5 — explicit wiring per type, per provider + +The finest grain overrides wiring *inside* a provider rather than at the context, using a [higher-order provider](higher-order-providers.md) whose inner provider defaults to `UseContext`. The default routes nested lookups back through the context as usual, while an explicit inner provider overrides one branch locally without touching the context's table: + +```rust +pub struct SerializeIteratorWith(pub PhantomData); + +#[cgp_impl(SerializeIteratorWith)] +impl ValueSerializer for Context +where + for<'a> &'a Value: IntoIterator, + Provider: for<'a> ValueSerializer::Item>, +{ + fn serialize(&self, value: &Value, serializer: S) -> Result + where + S: serde::Serializer, + { ... } +} + +delegate_components! { + new MyAppA { + open { ValueSerializerComponent }; + + @ValueSerializerComponent.Vec: SerializeBytes, + @ValueSerializerComponent.Vec>: SerializeIteratorWith, + @ValueSerializerComponent.Vec: SerializeIteratorWith, + @ValueSerializerComponent.[u8, u64]: UseSerde, + } +} +``` + +Here `Vec>` serializes its inner `Vec` as hex strings, while a bare `Vec` elsewhere in the same context still serializes as bytes — the inner provider is overridden for that one branch only. Where `SerializeIteratorWith` is left without an argument, as for `Vec`, the `UseContext` default takes over and the item lookup goes back through the context, so the `u64` items resolve to `UseSerde` from the table. The gain is per-provider control: a wiring decision can be pinned at the point of use instead of globally at the context level. The cost is the higher-order plumbing itself — the extra provider parameter, the explicit context argument in the inner bound, and the discipline of choosing when to override versus when to defer to the context. + +## Choosing a level + +Read the spectrum as a ladder and stop at the first rung that fits. Levels 1 and 2 are plain Rust and need no CGP at all — reach for them when one implementation, or one per type, is genuinely all you need. Level 3 buys reuse and swappable providers while leaving the trait and its existing users untouched, the right entry point for retrofitting CGP onto an established trait. Level 4 is the canonical CGP shape, paying a modified interface for full per-context freedom and escape from the orphan rule. Level 5 is a local refinement layered on top of Level 4, used only where a single nested branch must diverge from the context's global choice. Each step up trades ceremony for decoupling, so the discipline is to climb only as far as the problem demands. + +Further reference: [coherence](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/coherence.md) for the rules this hierarchy escapes, and [modular serialization](https://github.com/contextgeneric/cgp/blob/main/docs/examples/modular-serialization.md) for the full worked example. diff --git a/docs/skills/cgp/references/namespaces.md b/docs/skills/cgp/references/namespaces.md new file mode 100644 index 00000000..8429d33e --- /dev/null +++ b/docs/skills/cgp/references/namespaces.md @@ -0,0 +1,167 @@ +# Namespaces + +A namespace is a reusable, named lookup table of component wirings that a context inherits wholesale and then selectively overrides — CGP's preset mechanism, expressed entirely at the type level with no runtime cost. + +As the component count of an application grows, the [wiring](wiring.md) on each context grows with it: every context spells out its own `delegate_components!` table entry by entry, and two contexts that should share the same set of providers must repeat the whole block. A namespace lifts that block out of any single context, gives it a name, and lets other contexts say "use everything in this namespace" to pull in the entire group at once. This is exactly the preset pattern — a curated bundle of defaults you adopt and then customize — and CGP has no separate `cgp_preset!` construct because a preset *is* a namespace. This file covers `#[cgp_namespace]` (defining and inheriting a namespace), the `RedirectLookup` provider that makes the indirection work, the `Path!` macro that addresses entries, and the `DefaultNamespace` family of default-resolution traits. + +## What a namespace is + +A namespace is not a context — it is a trait, named after the namespace, that carries a single `Delegate` associated type and is implemented once per key. A context that joins a namespace forwards its lookups through that trait, so the namespace supplies defaults without ever being instantiated or holding any wiring at the context level. The defining behavior is inheritance with override: a context inherits the namespace's entries as defaults, and any entry it wires directly on itself wins, because a context's own [`DelegateComponent`](wiring.md) entry resolves before the namespace fallback is consulted. + +The forwarding is keyed by a *path* rather than a bare component name, and that is what makes inheritance and selective override possible. A path is a type-level list of symbols and component names; keying on it lets one namespace inherit from another, lets a parent's whole subtree be rerouted at once, and lets a child context shadow a single inherited entry without disturbing the rest. + +## Defining a namespace with `#[cgp_namespace]` + +`cgp_namespace!` defines a namespace from a body that resembles a `delegate_components!` table. The `new` keyword tells the macro to emit the namespace's marker struct and its lookup trait; the entries inside map keys to redirect paths or to providers: + +```rust +cgp_namespace! { + new DefaultShowComponents { + [String, u64]: ShowWithDisplay, + } +} +``` + +Two entry forms appear in the body and generate different table contents. A `:` entry maps a key straight to a provider, exactly as in `delegate_components!` — the `[String, u64]: ShowWithDisplay` line above resolves both keys to the `ShowWithDisplay` provider. A `=>` entry instead redirects a key along a path: `FooProviderComponent => @MyFooComponent` says "when this namespace is asked for `FooProviderComponent`, look up the path `@MyFooComponent` instead of naming a provider outright," leaving the actual provider to be decided wherever the path lands. + +A namespace inherits from a parent by naming it after a colon in the header. The child then resolves everything the parent does, plus its own entries: + +```rust +cgp_namespace! { + new ExtendedNamespace: DefaultNamespace { + @cgp.core.error => + @app, + } +} +``` + +`ExtendedNamespace` inherits every entry `DefaultNamespace` resolves and additionally reroutes the entire `@cgp.core.error` subtree to `@app` — a single path-rewriting entry redirects a whole prefix of the parent namespace at once, not just one component. + +### What the macro generates + +With `new` present, the macro emits a backing struct (named with an `__…Components` wrapper) and the lookup trait carrying the table's `__Table__` generic parameter and a `Delegate` associated type: + +```rust +pub struct __MyNamespaceComponents; + +pub trait MyNamespace<__Table__> { + type Delegate; +} +``` + +Each `=>` entry becomes an `impl` of that trait for the entry's key, whose `Delegate` is a `RedirectLookup` pointing the table at the entry's path; each `:` entry becomes an `impl` whose `Delegate` is the named provider directly. For `FooProviderComponent => @MyFooComponent` the macro emits: + +```rust +impl<__Table__> MyNamespace<__Table__> for FooProviderComponent { + type Delegate = RedirectLookup<__Table__, PathCons>; +} +``` + +When a parent is named, the macro prepends a blanket impl that forwards every key the parent resolves down to the child, so the child inherits the parent's full table; the child's own entries are emitted after it and take precedence where their keys are more specific. + +## Attaching components and joining namespaces + +A namespace is consumed from two sides: components register into it, and contexts join it. A [component](components.md) attaches to a namespace through the `#[prefix(@path in Namespace)]` attribute on its [`#[cgp_component]`](components.md) trait, which emits one extra impl registering the component into the named namespace under a path prefix. CGP's own [`HasErrorType`](components.md), for example, carries `#[prefix(@cgp.core.error in DefaultNamespace)]`, placing the standard error wiring into the built-in `DefaultNamespace` so any context joining that namespace inherits it. The generated impl routes the component's lookup under the prefix path: + +```rust +impl<__Components__> MyNamespace<__Components__> for BarProviderComponent { + type Delegate = RedirectLookup< + __Components__, + PathCons>, + >; +} +``` + +A context joins a namespace inside `delegate_components!` with a `namespace` header line, after which every lookup it cannot resolve directly forwards through the namespace. A direct entry on the same context shadows just that key, leaving the rest of the inherited wiring intact: + +```rust +delegate_components! { + AppA { + namespace DefaultNamespace; + + @test.ShowImplComponent.u64: + ShowWithDisplay, // overrides only the u64 entry + } +} +``` + +The `namespace DefaultNamespace;` line emits a blanket `DelegateComponent` impl on `AppA` that forwards every key through `DefaultNamespace`, paired with the matching `IsProviderFor` forwarding so dependency errors stay diagnosable through [checking](checking.md). The direct `@test.ShowImplComponent.u64` line resolves first, so it wins for `u64` only — the inherit-and-override pattern in action. Joining through `delegate_and_check_components!` instead does the same while verifying the merged wiring. + +## Paths with `Path!` + +A path is the type-level address that namespace entries redirect along, and `Path!` is the macro that builds one from a readable dotted, `@`-prefixed form. Each segment narrows the lookup one step — through a namespace, through a prefix, down to a component key — and the leading `@` is the sigil marking the body as a path rather than a plain type: + +```rust +type ErrorRoute = Path!(@app.error.ErrorRaiserComponent); +// PathCons>> +``` + +The encoding of each segment is decided by its first character: a single lowercase identifier that is not a primitive type name (like `app` or `error`) becomes a [`Symbol`](abstract-types.md) type-level string, while every capitalized segment (like `ErrorRaiserComponent`) is kept as the named type — typically a component key or namespace marker. The macro folds the segments right-to-left onto `Nil`, wrapping each in a `PathCons`. You rarely call `Path!` directly; the same `@`-path syntax is embedded inside `#[cgp_namespace]` entries and `#[prefix(...)]` attributes, which is where paths are most often written. These [type-level primitives](type-level-primitives.md) carry no runtime value. + +## The `RedirectLookup` provider + +`RedirectLookup` is the [provider](components.md) — a zero-sized marker struct, no runtime value — that turns a path-addressed entry back into a concrete provider, and it is the mechanism every namespace runs on under the hood. The ordinary provider blanket impl looks a component up in the context's own table keyed by the component-name marker; `RedirectLookup` instead consults a table keyed by an arbitrary type-level path, then delegates to whatever provider that entry holds. This decouples *which key* a component is looked up under from *which table* answers it — the basis for organizing wiring into namespaces. + +`RedirectLookup` is never written by hand; it is emitted by the macros. Every `#[cgp_component]` generates a `RedirectLookup` impl of its provider trait, and the namespace `=>` entries and `#[prefix]` attributes generate entries whose `Delegate` is a `RedirectLookup`. The generated impl performs one `DelegateComponent` lookup keyed on the path rather than on the component name: + +```rust +impl<__Context__, __Components__, __Path__> Greeter<__Context__> + for RedirectLookup<__Components__, __Path__> +where + __Components__: DelegateComponent<__Path__>, + <__Components__ as DelegateComponent<__Path__>>::Delegate: Greeter<__Context__>, +{ + fn greet(__context__: &__Context__) -> String { + <__Components__ as DelegateComponent<__Path__>>::Delegate::greet(__context__) + } +} +``` + +So `RedirectLookup` implements the provider trait whenever the table maps the path to a delegate that itself implements that trait, and forwards the call to it. When the provider trait carries a generic type parameter, the impl additionally appends that parameter onto the path before the lookup, so the redirected key can encode the generic argument — this is how per-type dispatch through a namespace works. + +## Default resolution: the `DefaultNamespace` family + +The traits that back namespaces come in three arities, differing only in how many type parameters take part in the key. `DefaultNamespace` keys a default purely on the component name; `DefaultImpls1` keys it on the component name *and* one further type — the shape for a per-type default where the same component resolves differently for `String` than for `u64` — and `DefaultImpls2` does the same for a pair. Each carries a single `Delegate` associated type and no method or data; resolution is the projection of `Delegate` from the matching impl: + +```rust +pub trait DefaultNamespace { + type Delegate; +} + +pub trait DefaultImpls1 { + type Delegate; +} +``` + +`DefaultNamespace` is the built-in namespace that `#[prefix(... in DefaultNamespace)]` registers components into and that a context joins with `namespace DefaultNamespace;`. A per-type default is registered with the `#[default_impl(T in DefaultImpls1)]` attribute on a provider impl, which emits `impl DefaultImpls1 for T { type Delegate = Provider; }`. A context then pulls those defaults in with a `for … in` loop that projects the `Delegate` for each type: + +```rust +delegate_components! { + App { + namespace DefaultNamespace; + + for in DefaultImpls1 { + @test.ShowImplComponent.T: Provider, + } + + @test.ShowImplComponent.u64: + ShowWithDisplay, // overrides the inherited default for u64 + } +} +``` + +The `for in DefaultImpls1` loop wires each type `T` by reading `T: DefaultImpls1`, and the direct `u64` line shadows whatever the namespace would otherwise supply for that type. The loop target can equally be a whole namespace defined with `cgp_namespace!`, such as the `DefaultShowComponents` namespace shown earlier — `for in DefaultShowComponents { … }` wires its listed types through the same projection. + +## Defining a preset once, reusing it across contexts + +The payoff is that a bundle of wiring is defined once and reused everywhere. A library publishes a namespace of sensible defaults — possibly extended from a base namespace as `ExtendedNamespace: DefaultNamespace` does — and any number of applications join it, inherit the whole bundle, and override only the entries specific to their needs. Each context's table shrinks to a `namespace` header plus a short list of overrides, no matter how many components the namespace bundles. Because the inheritance, the overrides, and the redirections are all resolved through trait projection and type-level paths, the entire arrangement is resolved at compile time with no runtime cost — a preset is simply one more namespace in the chain. + +## Related constructs + +Namespaces build directly on [wiring](wiring.md): a namespace is a `DelegateComponent` table addressed by paths, and a context joins one inside `delegate_components!`. The keys a namespace maps are the `…Component` markers of [components](components.md), attached via the `#[prefix(...)]` attribute on a `#[cgp_component]` trait, and the `IsProviderFor` forwarding a `namespace` header emits feeds the completeness guarantees in [checking](checking.md). The `@`-paths and `Symbol` segments are [type-level primitives](type-level-primitives.md). The per-type dispatch `RedirectLookup` enables — appending a generic parameter onto the lookup path — is the same mechanism the type-parameter dispatch forms in [higher-order providers](higher-order-providers.md) use. + +## Further reference + +Online docs: [concepts/namespaces.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/namespaces.md), [reference/macros/cgp_namespace.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_namespace.md), [reference/providers/redirect_lookup.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/redirect_lookup.md), [reference/macros/path.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/path.md). diff --git a/docs/skills/cgp/references/type-level-primitives.md b/docs/skills/cgp/references/type-level-primitives.md new file mode 100644 index 00000000..53adb980 --- /dev/null +++ b/docs/skills/cgp/references/type-level-primitives.md @@ -0,0 +1,197 @@ +# Type-level primitives + +The handful of zero-sized types and type macros that CGP folds strings, numbers, lists, choices, and routes into the type system, so the compiler can match a field name or a wiring route through trait resolution alone. + +## The idea + +CGP keys nearly everything by *type*, not by value. A getter looks up a field by a tag type; a [wiring](wiring.md) table selects a [provider](components.md) by a [component](components.md) key type; a [namespace](namespaces.md) re-routes a lookup along a path type. For that to work, things that are normally values — a field name string, a tuple position, a list of fields, a lifetime — have to be encoded as types the compiler can compare and dispatch on. The primitives in this reference are those encodings. Each is a familiar value-level idea lifted into a type: a string becomes a type-level character list, a number becomes a const-generic marker, a list becomes a recursive cons cell, a sum becomes a recursive branch. + +These types are almost never written by hand. They are produced by macros (`Symbol!`, `Product!`, `Sum!`, `Path!`) or emitted by derives, and a reader mostly meets them when *decoding a type the compiler prints* — in an error message, a `cargo expand` dump, or a hover. This reference is a decoder ring: skim it to read off what a long nested type means. One note on the printed forms first: the compiler abbreviates the busiest spines with Greek letters — `π` for `Cons`, `ε` for `Nil`, `ψ` for `Symbol`, `ζ` for `Chars`, `δ` for `Index` — so a printed `π>` is just `Cons>`. The prose below always uses the readable `Cons`/`Nil`/`Symbol!` forms; treat the Greek as a transcription you may have to reverse. + +Assume `use cgp::prelude::*;` throughout. + +## Type-level lists: `Product!`, `Cons`, `Nil` + +A type-level list is a compile-time linked list — the analogue of a tuple that generic code can take apart one element at a time. It is built from two cells: `Cons`, a pair holding the first element and the rest of the list, and `Nil`, a unit struct marking the end. Chained to the right and terminated by `Nil`, they form an *anonymous product type* — a record-shaped type whose width and contents a provider can walk without knowing the concrete struct it came from. + +```rust +pub struct Cons(pub Head, pub Tail); +pub struct Nil; +``` + +The `Product!` macro is the sugar a programmer writes instead of nesting `Cons` by hand, and the lowercase `product!` builds a matching value with the tuple-struct constructor: + +```rust +type Row = Product![u32, String, bool]; +// Row == Cons>> + +let row: Row = product![1, "hi".to_string(), true]; +// row == Cons(1, Cons("hi".to_string(), Cons(true, Nil))) +``` + +The list is what makes structural, field-by-field code possible. A struct's fields are exposed as one `Product!` type through `HasFields`, so a provider written once to recurse over `Cons`/`Nil` — a `Nil` impl for the base case, a `Cons` impl for the step — iterates, reads, or rebuilds *any* struct's fields. The elements are usually [`Field`](#field-a-named-value) entries pairing a name with a value, so a derived struct's layout reads as a `Product!` of `Field` cells. See [extensible data](extensible-data.md) for how that machinery is used. + +## Type-level sums: `Sum!`, `Either`, `Void` + +A type-level sum is the dual of the list: where a `Product!` holds a value for *every* element at once, a `Sum!` holds a value for exactly *one* branch — a tagged union the compiler can walk variant by variant. It shares the same right-nested shape but branches at each step instead of pairing, and terminates in an uninhabited marker instead of a constructible one. + +```rust +pub enum Either { Left(Head), Right(Tail) } +pub enum Void {} +``` + +`Either` is the sum cell — `Left(head)` selects this branch, `Right(tail)` defers to the rest — and `Void`, an empty enum with no values, closes the chain. The `Sum!` macro folds a list of types onto that spine, and a value picks one branch by how deep it sits: + +```rust +type Token = Sum![u32, String, bool]; +// Token == Either>> + +let t: Token = Either::Right(Either::Left("hi".to_string())); // the String branch +``` + +The terminator is the one real difference from the product spine, and it is load-bearing. A product ends in the constructible `Nil` because an empty record is a valid value; a sum ends in the *uninhabited* `Void` because an empty choice has no value to pick. After an extractor has tried every variant and matched none, the leftover has type `Void` — a value that cannot exist — which the machinery discharges with an empty `match self {}`, making a fully-handled variant match total at compile time with no unreachable runtime branch. An enum's variants are exposed as a `Sum!` of `Field` entries through `HasFields`, mirroring how a struct's fields are a `Product!`. + +## Type-level strings: `Symbol!`, `Symbol`, `Chars` + +A type-level string is a field name encoded as a type, so the name can drive trait resolution. CGP's getter, `HasField`, keys each field by a `Tag` type; to read a field called `name`, the string `"name"` must become a unique type, so that two symbols from the same string are the *same* type and two from different strings are *different* types. `Symbol!("name")` produces exactly that. + +The reason it is a *list of characters* rather than one const value is a stable-Rust limitation: a `&str` cannot be a const-generic parameter, but a single `char` can. So `Chars` spells the string out one character at a time — the specialized analogue of `Cons` where the head is a `const char` — and `Symbol` wraps that list together with the byte length: + +```rust +pub struct Chars(pub PhantomData); +pub struct Symbol(pub PhantomData); +``` + +The `Symbol!` macro hides all of this. The leading `LEN` const is the part most likely to puzzle a reader: stable Rust cannot compute a `Chars` chain's length inside a const-generic context, so the macro precomputes `str::len()` — the *byte* length — and bakes it into the type: + +```rust +// before +Symbol!("abc") +// after +Symbol<3, Chars<'a', Chars<'b', Chars<'c', Nil>>>> +``` + +Because `LEN` is the byte length, a multi-byte string records its UTF-8 width: `Symbol!("世界你好")` records `12`, while its character list still has one `Chars` node per scalar. The empty string is `Symbol<0, Nil>`. A type-level string is most often seen as the tag in a getter bound, where it names the field a provider reads without that context naming the provider: + +```rust +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasField, +{ + fn greet(&self) { + println!("Hello, {}!", self.get_field(PhantomData::)); + } +} +``` + +The same string can be recovered at runtime — see [`StaticFormat`](#staticformat-recovering-strings-and-paths) below. + +## `Index`: type-level numbers + +`Index` is the numeric counterpart to `Symbol!` — a `usize` lifted into a type, used to tag a tuple-struct field that has a position but no name. Where a named field is keyed by `Symbol!("name")`, the field at position `N` is keyed by `Index`, so `Index<0>`, `Index<1>`, and `Index<2>` are three distinct tag types standing in for `.0`, `.1`, and `.2`. + +```rust +pub struct Index; +``` + +It is a zero-sized marker: the number lives entirely in the type, so a tuple struct can carry a `HasField>` impl and a `HasField>` impl side by side and the compiler selects the right one purely from the tag. Selecting a wrong position — `Index<5>` on a three-field struct — is a type error, not a runtime panic, because no matching impl exists. Unlike the Greek-abbreviated spines, `Index` prints its number directly through `Display`, so `Index::<2>.to_string()` is `"2"` and the tag is legible in diagnostics. + +## `Field`: a named value + +`Field` is the element type that fills both spines — a value paired with the type-level tag naming it. A bare `Product![String, u8]` records only types and order; wrapping each element as `Field` attaches the name as a phantom type, making the structural representation self-describing so a provider can match on the tag to find the field it wants. + +```rust +pub struct Field { + pub value: Value, + pub phantom: PhantomData, +} +``` + +The tag is a phantom — needed only at compile time for resolution — so a `Field` is exactly as large as its `Value` and costs nothing at runtime. It is built from a value with no tag argument, since the tag is fixed by the target type: `let f: Field = "Alice".to_string().into();`. The same shape names a record field (tag from `Symbol!` or `Index`) and an enum variant (tag from `Symbol!`, value being the payload), which is why a derived `HasFields` is a `Product!` or `Sum!` of `Field` entries: + +```rust +#[derive(HasFields)] +pub struct Person { pub name: String, pub age: u8 } + +// generated: +// type Fields = Product![ +// Field, +// Field, +// ]; +``` + +## `Path!` and `PathCons`: type-level routes + +A type-level path is a route through nested [wiring](wiring.md) tables, expressed as a single type. Where a bare component key picks one entry out of a context's table, a path points at an entry behind one or more layers of indirection — inside a [namespace](namespaces.md), under a prefix — by listing the segments to walk left to right. `PathCons` is the cons cell of that route, terminated by `Nil`, and it differs from the `Cons` product spine in one way: both `Head` and `Tail` are `?Sized`, because a path segment is a pure type-level marker that never needs a known size. + +```rust +pub struct PathCons(pub PhantomData, pub PhantomData); +``` + +The `Path!` macro builds the route from a dotted, `@`-prefixed name, encoding each segment by case: a lowercase, non-primitive identifier becomes a `Symbol!` type-level string, and a capitalized name stays the named type it spells (typically a component key or namespace marker): + +```rust +type ErrorRoute = Path!(@app.error.ErrorRaiserComponent); +// PathCons>> +``` + +A path names only *where to look*, never a provider directly, so the same path resolves to different providers depending on the table it is walked against — the job of the `RedirectLookup` provider that consumes it. This same `@`-path syntax appears verbatim inside namespace entries, which is where paths are most often written rather than through the bare macro. See [namespaces](namespaces.md) for the redirected-lookup mechanism. + +## `Life<'a>`: a lifetime as a type + +`Life<'a>` lifts a lifetime into a type, so a CGP trait that borrows can still ride through wiring machinery that only accepts types. CGP's dependency marker, `IsProviderFor`, takes a tuple of a component's generic parameters as one type argument — and a tuple member must be a type, never a bare lifetime. A consumer trait declaring `fn get_reference(&self) -> &'a T` therefore cannot record its `'a` directly; `Life<'a>` is the conversion that packages the lifetime as a type so it can sit in the tuple as `(Life<'a>, T)`. + +```rust +pub struct Life<'a>(pub PhantomData<*mut &'a ()>); +``` + +The `*mut &'a ()` phantom is deliberate: a raw pointer is *invariant* in its lifetime, so `Life<'a>` is invariant in `'a`. That is correct here — the lifetime is an exact identity in the dependency marker, and a variant `Life` would let the compiler silently coerce one instantiation into another and pick the wrong provider. The macros insert `Life` automatically when a component carries a lifetime; a reader meets it in the generated provider trait, where `IsProviderFor<…, (Life<'a>, T)>` names the lifetime as a type rather than a bare `'a`. Conceptually it joins `Index` (which lifts a `usize`) and `Symbol` (which lifts a string) as another marker making a non-type thing addressable in trait resolution. + +## `MRef<'a, T>`: owned-or-borrowed + +`MRef<'a, T>` is a "maybe-reference" — an enum holding either a borrow of a `T` or an owned `T` — so a single getter signature serves both the context that already stores a value and the one that must produce it. Unlike the rest of this reference, there is nothing type-level about it: it is an ordinary runtime value, the payload a getter hands back. + +```rust +pub enum MRef<'a, T> { Ref(&'a T), Owned(T) } +``` + +A getter declared to return `MRef<'a, T>` lets a context with the value in a field return `MRef::Ref` and lend it, while a context that computes the value returns `MRef::Owned` and gives it away — with no extra cost in the common stored-field case. The caller treats both uniformly because `MRef` derefs to `T`: it implements `Deref` and `AsRef`, builds either variant through `From` and `From<&'a T>`, and promotes a borrow to ownership with `get_or_clone` when `T: Clone`. + +```rust +let stored = String::from("hello"); +let borrowed: MRef<'_, String> = MRef::from(&stored); // lends a stored value +let made: MRef<'_, String> = MRef::from(String::from("world")); // hands over a built one +assert_eq!(&*borrowed, "hello"); +let owned: String = borrowed.get_or_clone(); // clones the borrowed case +``` + +It is one of the getter return modes recognized by the field macros, parallel to `&T`, `Option<&T>`, or `&str`; see [functions and getters](functions-and-getters.md). Its lifetime is an ordinary borrow and is unrelated to the `Life<'a>` lift above. + +## `StaticFormat`: recovering strings and paths + +The type-level encodings need a way back to runtime data, and three traits provide it. `StaticFormat` recovers a type-level string *lazily* by writing into a formatter — it backs the `Display` impls on `Symbol` and `Chars`, recursing down the `Chars` spine to emit each character, so any symbol prints with `to_string()` or `{}`: + +```rust +let s = ::default(); +assert_eq!(s.to_string(), "hello"); +``` + +`StaticString` recovers it *eagerly*, as a compile-time `&'static str` constant: a blanket impl walks the `Chars` list and UTF-8-encodes it into a `[u8; LEN]` at const-evaluation time — which is the consumer that `Symbol`'s `LEN` byte length exists to size — then validates the bytes as a `&'static str`. Use `Display` when a runtime value will do; use `StaticString::VALUE` when a `const` is needed or in a hot path. Both round-trip multi-byte Unicode faithfully: + +```rust +use cgp::core::field::traits::StaticString; +assert_eq!(::VALUE, "世界你好"); +``` + +`ConcatPath` works one level up, joining two `PathCons` paths into one as a pure type-level computation — it keeps each `Head` and splices the second path on where the first reaches `Nil`, the operation behind composing nested accessors: + +```rust +type Joined = >::Output; // the path a.b.c.d +``` + +## Further reference + +Online source-of-truth documents: the [types directory](https://github.com/contextgeneric/cgp/tree/main/docs/reference/types) (`cons.md`, `either.md`, `chars.md`, `index.md`, `field.md`, `life.md`, `mref.md`, `path_cons.md`), the construction macros [`macros/symbol.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/symbol.md), [`macros/product.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/product.md), [`macros/sum.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/sum.md), [`macros/path.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/path.md), and the recovery traits [`traits/static_format.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/static_format.md). diff --git a/docs/skills/cgp/references/wiring.md b/docs/skills/cgp/references/wiring.md new file mode 100644 index 00000000..546ada4c --- /dev/null +++ b/docs/skills/cgp/references/wiring.md @@ -0,0 +1,181 @@ +# Wiring + +How a context selects which provider implements each of its components, by recording a type-level table that maps every `…Component` marker key to a chosen provider. + +A CGP [component](components.md) splits the consumer trait callers use (`CanDoX`) from the provider trait implementers write (`SomethingDoer`), but that split leaves one question open: for a given context, *which* provider supplies the behavior? Wiring is the answer. It is the act of telling a context, component by component, which provider stands behind each consumer-trait call — and the mechanism is a small type-level lookup table carried on the context type. This file explains that table, the `delegate_components!` macro that populates it, and the `UseContext` provider that lets a provider trait route back through the context's own consumer-trait impl. + +## The table: `DelegateComponent` + +The table is the trait `DelegateComponent`, which maps one key type to one value type: + +```rust +pub trait DelegateComponent { + type Delegate; +} +``` + +There is no method and no data — the trait exists purely to associate a value type (`Delegate`) with a key type (`Key`) on a carrier type (`Self`). The mental model is a type-level key-value map living on the `Self` type, analogous to an object's method table (vtable) in object-oriented languages: where a vtable maps method names to function pointers resolved at runtime, this table maps `…Component` marker types to provider types resolved entirely at compile time. Implementing `DelegateComponent` is "setting" the entry at `Key`; naming `Self: DelegateComponent` in a bound and reading `Self::Delegate` is "getting" the value back out. Because Rust forbids two impls of the same trait for the same `Self` and `Key`, each key maps to exactly one value, which is what makes the structure a genuine map. + +When the key is a component marker such as `GreeterComponent`, a populated entry causes the context to inherit the matching provider trait through the blanket impl, and from there the consumer trait — so `app.greet()` type-checks. The blanket impl's body is itself a `DelegateComponent` lookup: it reads the entry, finds the provider, and forwards the call to it. The lookup *is* the whole routing mechanism. (When the key is some arbitrary type instead of a component marker — a shape, a tag — the same trait serves as a plain dispatch table walked by a higher-order provider, with no provider trait attached; see [higher-order providers](higher-order-providers.md).) + +## `delegate_components!`: populating the table + +You almost never write `DelegateComponent` impls by hand. The `delegate_components!` macro populates the table from a compact `Key: Value` syntax, one entry per component, the marker as the key and the chosen provider as the value: + +```rust +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +delegate_components! { + Rectangle { + AreaCalculatorComponent: RectangleArea, + } +} +``` + +This wires `Rectangle` to use the `RectangleArea` provider for its `AreaCalculatorComponent`. After this, calling `rect.area()` resolves through `Rectangle`'s table to `RectangleArea`. The target before the brace is the [context](checking.md) (or an intermediary provider table) whose table is being defined; each key is a `…Component` marker type and each value is the provider type to delegate it to. + +When several components share one provider, the array form on the key side lets a bracketed list of markers expand to one entry each, all pointing at the same value: + +```rust +delegate_components! { + Rectangle { + [ + AreaCalculatorComponent, + PerimeterCalculatorComponent, + ]: RectangleGeometry, + GreeterComponent: GreetHello, + } +} +``` + +This is exactly equivalent to writing `AreaCalculatorComponent: RectangleGeometry` and `PerimeterCalculatorComponent: RectangleGeometry` on separate lines, plus the `GreeterComponent` entry — three entries in all. + +A `new` keyword in front of the target makes the macro define the target struct as well, saving a separate declaration. `new GeometryComponents { … }` emits `struct GeometryComponents;` alongside the table impls. This is the idiomatic way to declare a standalone provider bundle — a type whose only purpose is to hold a table that other contexts can then delegate to as a single unit: + +```rust +delegate_components! { + new GeometryComponents { + AreaCalculatorComponent: RectangleArea, + PerimeterCalculatorComponent: RectanglePerimeter, + } +} +``` + +A leading generic list on the target makes the whole table generic, so one wiring applies across a family of contexts: `delegate_components! { MyContext { … } }` wires every `MyContext` at once. + +### What the macro generates + +Each entry expands to a pair of impls. The first is the `DelegateComponent` impl that stores the entry — for the `Rectangle` example above, the macro emits: + +```rust +impl DelegateComponent for Rectangle { + type Delegate = RectangleArea; +} +``` + +This impl alone is what the provider blanket impl reads when it looks the component up. The second impl is an `IsProviderFor` impl that forwards the chosen provider's requirements back through the table, so a missing transitive [impl-side dependency](components.md) still surfaces as a usable compiler error rather than a dead end. The details of that propagation belong to [components](components.md) and [checking](checking.md) — here it is enough to know that wiring an entry makes it both *resolvable* (the `DelegateComponent` half) and *checkable* (the `IsProviderFor` half). + +## Explicit delegation: what wiring effectively does + +To see what `delegate_components!` buys you, it helps to write the routing out by hand. Without any table, a context can implement a consumer trait directly by calling a provider's provider-trait method itself. Given the `AreaCalculator` provider trait (whose method takes the context as an explicit `Context` parameter rather than `&self`), a context can hand-route its `CanCalculateArea` consumer impl through the `RectangleArea` provider: + +```rust +impl CanCalculateArea for Rectangle { + fn area(&self) -> f64 { + >::area(self) + } +} +``` + +This is the manual equivalent of one `delegate_components!` entry: it names the provider explicitly, invokes its provider-trait method, and passes `self` as the context. The single line `AreaCalculatorComponent: RectangleArea` in the table achieves the same routing generically, through the generated `DelegateComponent` impl and the blanket impl that reads it — for every method on the trait, without you writing out each call. Use this explicit form only as a teaching device or in the rare case where you want a context to bypass the table for one trait; the table form is the idiom. + +## Direct implementation of a consumer trait + +A context need not delegate at all. When the behavior is specific to one context and there is no provider worth naming, you can implement the consumer trait directly — plain vanilla Rust, no table involved: + +```rust +impl CanGreet for Person { + fn greet(&self) { + println!("Hello, I am {}", self.name); + } +} +``` + +This bypasses the provider-trait machinery entirely: `person.greet()` calls this impl directly. Direct implementation and table-driven wiring are mutually exclusive for a given component on a given context — the table's blanket impl already supplies the consumer trait, so a hand-written consumer impl would collide with it. Reach for direct implementation when a component has exactly one context-specific behavior and the indirection of a separate provider adds nothing. + +## `UseContext`: routing a provider trait back to the consumer trait + +`UseContext` is a [provider](components.md) — a zero-sized marker struct with no runtime value — that implements *any* provider trait by forwarding its methods back to the context's own consumer-trait implementation: + +```rust +pub struct UseContext; +``` + +It is the exact dual of the consumer-trait blanket impl. That blanket impl runs consumer-to-provider — a context implements `CanGreet` by delegating to whichever provider implements `Greeter` for it. `UseContext` runs the opposite direction: it implements the provider trait `Greeter` by calling whatever `CanGreet` implementation the context already has. `#[cgp_component]` generates a `UseContext` impl of the provider trait for every component, of roughly the shape: + +```rust +impl Greeter for UseContext +where + Context: CanGreet, +{ + fn greet(context: &Context) { + Context::greet(context) + } +} +``` + +So wiring a component to `UseContext` means "use whatever this context already does for this trait." Its purpose is to turn a context's existing consumer-trait impl into a provider that *another* provider can call. The pattern matters most for [higher-order providers](higher-order-providers.md), which take an inner provider as a type parameter and often default it to `UseContext`, so the inner step falls back to the context's own wiring unless an explicit provider is named. + +The one rule to respect is the circular-dependency caveat: never delegate a component to `UseContext` when the context's only impl of that component *is* the delegation itself. Doing so asks the context to implement the consumer trait by delegating to a provider (`UseContext`) that in turn implements the provider trait by calling the consumer trait — a cycle the trait solver cannot resolve, surfacing as an overflow or unsatisfied-bound compile error. `UseContext` is meant to be supplied to another provider as its inner provider, not wired as a context's own delegate for the same component. + +## Dispatching a component per type with `open` + +When a provider trait carries an extra generic parameter and the right provider depends on which concrete type that parameter is, the choice is made by a second lookup keyed on that parameter rather than on the component marker. The `open` statement inside `delegate_components!` is the way to write that per-value wiring: it folds the per-type entries directly into the context's own table, one provider per concrete value of the dispatch parameter. Given a `CanCalculateArea` consumer trait whose `Shape` parameter selects the area formula, a context wires each shape to its own provider like this: + +```rust +delegate_components! { + MyApp { + open { AreaCalculatorComponent }; + + @AreaCalculatorComponent.Rectangle: RectangleArea, + @AreaCalculatorComponent.Circle: CircleArea, + } +} +``` + +The leading `open { AreaCalculatorComponent };` header opens one or more components for per-value wiring — list several inside the braces to open them together. The header is a *leading* statement: when a single block mixes plain `Component: Provider` mappings with an `open` block, the `open { … };` header must come first, before any plain mappings, or the macro fails to parse. Each subsequent `@Component.Key: Provider` entry then assigns a provider for one value of that component's dispatch parameter: `@AreaCalculatorComponent.Rectangle: RectangleArea` says that when `Shape` is `Rectangle`, `MyApp` calculates area through `RectangleArea`, and the `Circle` line does the same for `Circle`. After this wiring, `MyApp` implements `CanCalculateArea` via `RectangleArea` and `CanCalculateArea` via `CircleArea`. + +Two shorthands keep the entries compact. When several values of the dispatch parameter share one provider, an array on the final path segment expands to one entry each — `@AreaCalculatorComponent.[Rectangle, Circle]: SomeProvider` wires both shapes to `SomeProvider`. When a dispatch value needs generic parameters of its own, they precede the value: `@SomeComponent.<'a, T> &'a T: SomeProvider` dispatches on the type `&'a T` for all `'a` and `T`. + +The `open` form needs no extra macro on the component. It works through the `RedirectLookup` impl that every `#[cgp_component]` already generates, so dispatching a component per type requires no `#[derive_delegate]` on the trait — the same component you wire by marker is the one you open by value. `open` is a lightweight form of the full namespace feature, suited to a context wiring its own components directly; it does not combine with a joined namespace where the component carries `#[prefix(...)]`. The full namespace machinery, including `@`-path keys and the `namespace` statement, is described in [namespaces](namespaces.md). + +### Legacy: `UseDelegate` nested tables + +An older form writes the per-type entries into a separate table wrapped in the `UseDelegate` provider, rather than folding them into the context's own table with `open`. It nests a `new` table inside a single `UseDelegate<…>` value: + +```rust +delegate_components! { + MyApp { + AreaCalculatorComponent: UseDelegate, + } +} +``` + +This desugars `UseDelegate` into a standalone `delegate_components! { new AreaCalculatorComponents { … } }` plus an outer entry `AreaCalculatorComponent: UseDelegate` pointing the component at that inner table. The end effect matches the `open` example above — `MyApp` dispatches `Rectangle` to `RectangleArea` and `Circle` to `CircleArea` — but the dispatch values live in a named side table reached through `UseDelegate` instead of in `MyApp`'s table directly. + +This is a legacy dispatch mechanism, retained for compatibility and expected to be deprecated and removed. It is documented here so that the nested-table form can be *read* where it still appears in existing code; for *writing* new wiring, prefer `open`, which dispatches the same component without the extra `UseDelegate` indirection or the separate inner table. The detailed mechanics of the `UseDelegate` provider — how it reads its inner table at the provider level — belong to [higher-order providers](higher-order-providers.md). + +## Related constructs + +Wiring is the step that connects [components](components.md) — the consumer/provider trait pairs and `…Component` markers — to the providers that implement them, and the `IsProviderFor` impls it generates feed the completeness guarantees described in [checking](checking.md). The `open` statement shown above dispatches a generic-parameter component per value through the `RedirectLookup` impl every component generates; the full namespace feature it derives from, including `@`-path keys and the `namespace` statement, is described in [namespaces](namespaces.md). `UseContext` and the legacy `UseDelegate` provider — including how it reads an inner dispatch table — are covered in [higher-order providers](higher-order-providers.md). + +## Further reference + +Online docs: [delegate_components.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/delegate_components.md), [traits/delegate_component.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/delegate_component.md), [providers/use_context.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/use_context.md).