Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions crates/macros/cgp-macro-core/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,13 @@ 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`).

**6. Keep inline docs brief and current as you go.** When you review a file — whether to change it
or to write its [implementation document](../../../docs/implementation/README.md) — improve the
inline docs in the same pass. Add a one-line `///` to any public struct, trait, or function that
lacks one, saying what it is or does (for a pipeline stage, its role in the sequence); prefer naming
the *why* or a corner case over restating the signature. Fix a doc that no longer matches the code,
and clarify genuinely confusing code (for example, why `generic_params_to_path` keeps only type
parameters) with a short comment. Keep them terse: delete a comment that only restates obvious code,
and do not narrate line by line. Deep mechanics belong in the implementation document, not in a wall
of inline prose — link to it rather than duplicating it.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use syn::{ImplItem, ItemTrait, Type};
use crate::functions::trait_items_to_delegated_impl_items;
use crate::parse_internal;

/// Forward a provider trait's own items to `delegate_type`, reconstructing the
/// trait path from the trait itself (for impls whose trait *is* the provider trait).
pub fn provider_trait_to_impl_items(
item_trait: &ItemTrait,
delegate_type: &Type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::functions::{
parse_internal, signature_to_delegated_impl_item_fn, trait_to_impl_item_type,
};

/// Build impl items that forward each trait item (method, associated type, or
/// const) to `delegate_type`, projecting types/consts through `provider_trait_path`.
pub fn trait_items_to_delegated_impl_items(
trait_items: &[TraitItem],
delegate_type: &Type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ use crate::exports::Life;
use crate::parse_internal;
use crate::types::generics::TypeGenerics;

/// Convert a trait's generics into the `Params` tuple types of an `IsProviderFor`
/// bound: type params pass through, lifetimes are lifted into `Life<'a>`.
///
/// Panics on a const generic parameter — see the const-generic limitation in
/// docs/implementation/entrypoints/cgp_component.md.
pub fn parse_is_provider_params(generics: &Generics) -> syn::Result<Punctuated<Type, Comma>> {
let params = TypeGenerics::try_from(generics)?.generics.params;

Expand Down
3 changes: 3 additions & 0 deletions crates/macros/cgp-macro-core/src/functions/parse_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use syn::{Error, parse2};
use crate::functions::strip_macro_prelude;
pub use crate::macros::parse_internal;

/// Parse a token stream into a `syn` type `T`, attaching an error that names both
/// `T` and the offending tokens (prelude prefix stripped) on failure. Usually
/// invoked through the `parse_internal!` macro.
pub fn parse_internal<T>(body: TokenStream) -> Result<T, Error>
where
T: Parse,
Expand Down
2 changes: 2 additions & 0 deletions crates/macros/cgp-macro-core/src/macros/keyword.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/// Declare a custom-keyword marker: a zero-sized struct plus its `IsKeyword` impl
/// carrying the keyword's spelling, which the parsers peek against.
#[macro_export]
macro_rules! define_keyword {
( $struct_ident:ident, $value:literal ) => {
Expand Down
3 changes: 3 additions & 0 deletions crates/macros/cgp-macro-core/src/macros/parse.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/// Quasi-quote tokens and parse them into an inferred `syn` type via the
/// `parse_internal` function. Expands to a `?` expression, so it must be called
/// inside a `syn::Result`-returning function.
#[macro_export]
macro_rules! parse_internal {
( $($body:tt)* ) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use syn::{Error, Ident};
use crate::types::cgp_component::CgpComponentRawArgs;
use crate::types::ident::IdentWithTypeGenerics;

/// The `#[cgp_component]` attribute args with defaults applied: the context type
/// identifier (`__Context__` by default), the required provider trait name, and
/// the component marker name (`{Provider}Component` by default).
#[derive(Clone)]
pub struct CgpComponentArgs {
pub context_ident: Ident,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use syn::{Error, Ident};

use crate::types::ident::IdentWithTypeGenerics;

/// The attribute args exactly as written, before defaults are applied. Parses
/// either a bare provider identifier or the `key: value` form; [`CgpComponentArgs`]
/// resolves the defaults via `TryFrom`.
#[derive(Default)]
pub struct CgpComponentRawArgs {
pub context_ident: Option<Ident>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use crate::types::cgp_component::CgpComponentArgs;
use crate::types::empty_struct::EmptyStruct;
use crate::types::provider_impl::{ItemProviderImpl, ItemProviderImpls};

/// Final pipeline stage: all derived items, plus the args and attributes needed
/// to render the standard provider impls (`UseContext`, `RedirectLookup`, and the
/// per-attribute `UseDelegate`/prefix impls).
pub struct EvaluatedCgpComponent {
pub component_struct: EmptyStruct,
pub consumer_trait: ItemTrait,
Expand All @@ -16,6 +19,8 @@ pub struct EvaluatedCgpComponent {
}

impl EvaluatedCgpComponent {
/// Emit the five core items in fixed order (consumer trait, consumer impl,
/// provider trait, provider impl, marker struct), then the provider impls.
pub fn to_items(&self) -> syn::Result<Vec<Item>> {
let mut items = vec![
Item::Trait(self.consumer_trait.clone()),
Expand Down Expand Up @@ -55,6 +60,7 @@ impl EvaluatedCgpComponent {
Ok(provider_impls)
}

/// One `UseDelegate` provider impl per `#[derive_delegate]` attribute.
pub fn to_use_delegate_impls(&self) -> syn::Result<ItemProviderImpls> {
let provider_trait = &self.provider_trait;
let component_type = self.args.component_name.to_type();
Expand All @@ -71,6 +77,7 @@ impl EvaluatedCgpComponent {
Ok(provider_impls)
}

/// One namespace prefix impl per `#[prefix]` attribute (from `#[cgp_namespace]`).
pub fn to_prefix_impls(&self) -> syn::Result<Vec<ItemImpl>> {
let component_name = &self.args.component_name;
let mut provider_impls = Vec::new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use crate::types::path::{PathElement, UniPath};
use crate::types::provider_impl::ItemProviderImpl;

impl EvaluatedCgpComponent {
/// Build the `RedirectLookup` provider impl used by namespaces and `open`. A
/// component's type parameters extend the lookup path via `ConcatPath`.
pub fn to_redirect_lookup_impl(&self) -> syn::Result<ItemProviderImpl> {
let consumer_trait = &self.consumer_trait;
let provider_trait = &self.provider_trait;
Expand Down Expand Up @@ -76,6 +78,9 @@ impl EvaluatedCgpComponent {
}
}

/// Collect the component's *type* parameters into a lookup path. Lifetimes and
/// const params are deliberately excluded (only types key the redirect path),
/// so this returns `None` when the component has no type parameters.
fn generic_params_to_path(generics: &Generics) -> syn::Result<Option<UniPath>> {
let type_params = generics
.params
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use crate::types::cgp_component::EvaluatedCgpComponent;
use crate::types::provider_impl::ItemProviderImpl;

impl EvaluatedCgpComponent {
/// Build the `UseContext` provider impl, which satisfies the provider trait by
/// routing back through the context's own consumer-trait impl.
pub fn to_use_context_impl(&self) -> syn::Result<ItemProviderImpl> {
let component_name = &self.args.component_name;
let context_type_ident = &self.args.context_ident;
Expand Down
3 changes: 3 additions & 0 deletions crates/macros/cgp-macro-core/src/types/cgp_component/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ use syn::ItemTrait;
use crate::types::attributes::CgpComponentAttributes;
use crate::types::cgp_component::{CgpComponentArgs, PreprocessedCgpComponent};

/// Raw input stage: the parsed attribute args and trait, before CGP attributes
/// are stripped. First stage of the `#[cgp_component]` pipeline.
pub struct ItemCgpComponent {
pub args: CgpComponentArgs,
pub item_trait: ItemTrait,
}

impl ItemCgpComponent {
/// Split the CGP modifier attributes off the trait, yielding the next stage.
pub fn preprocess(&self) -> syn::Result<PreprocessedCgpComponent> {
let (attributes, item_trait) = CgpComponentAttributes::preprocess(&self.item_trait)?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ use crate::types::attributes::CgpComponentAttributes;
use crate::types::cgp_component::{CgpComponentArgs, EvaluatedCgpComponent};
use crate::types::empty_struct::EmptyStruct;

/// Pipeline stage after preprocessing: the plain trait plus the CGP modifier
/// attributes, carrying the methods that derive the provider trait, the blanket
/// impls, and the component marker.
pub struct PreprocessedCgpComponent {
pub args: CgpComponentArgs,
pub item_trait: ItemTrait,
pub attributes: CgpComponentAttributes,
}

impl PreprocessedCgpComponent {
/// Build the zero-sized `{Provider}Component` marker struct.
pub fn to_component_struct(&self) -> EmptyStruct {
let component_name = &self.args.component_name;
EmptyStruct {
Expand All @@ -19,6 +23,8 @@ impl PreprocessedCgpComponent {
}
}

/// Derive the marker struct, provider trait, and both blanket impls, yielding
/// the final stage.
pub fn eval(&self) -> syn::Result<EvaluatedCgpComponent> {
let component_struct = self.to_component_struct();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use crate::types::cgp_component::PreprocessedCgpComponent;
use crate::types::generics::TypeGenerics;

impl PreprocessedCgpComponent {
/// Build the consumer blanket impl: any context implementing the provider
/// trait for itself gets the consumer trait, forwarding each method to it.
pub fn to_consumer_item_impl(&self) -> syn::Result<ItemImpl> {
let consumer_trait = &self.item_trait;
let provider_ident = &self.args.provider_ident;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use crate::functions::{parse_internal, parse_is_provider_params, provider_trait_
use crate::types::cgp_component::PreprocessedCgpComponent;

impl PreprocessedCgpComponent {
/// Build the provider trait together with its blanket impl for `__Provider__`,
/// which inherits the provider trait from whatever `DelegateComponent` names.
/// Returns both so they share one construction of the provider trait.
pub fn to_provider_trait_and_blanket_impl(&self) -> syn::Result<(ItemTrait, ItemImpl)> {
let consumer_trait = &self.item_trait;
let context_type = &self.args.context_ident;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ use crate::visitors::{
};

impl PreprocessedCgpComponent {
/// Derive the provider trait from the consumer trait: insert the leading
/// `Context` parameter, lower supertraits to a `Context` where-bound, set the
/// `IsProviderFor` supertrait, and rewrite `self`/`Self` to the context.
pub fn to_provider_trait(&self) -> syn::Result<ItemTrait> {
let component_name = &self.args.component_name;
let provider_name = &self.args.provider_ident;
Expand Down
2 changes: 2 additions & 0 deletions crates/macros/cgp-macro-lib/src/cgp_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::ItemTrait;

/// `#[cgp_component]` entry point: parse the attribute args and the trait, then
/// run the `preprocess → eval → to_items` pipeline and emit the derived items.
pub fn cgp_component(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let args: CgpComponentArgs = syn::parse2(attr)?;
let item_trait: ItemTrait = syn::parse2(item)?;
Expand Down
39 changes: 25 additions & 14 deletions crates/tests/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,13 @@ that test exercises.
## Explain what each test covers

Open every test file with a brief comment stating **what behavior it exercises**,
and annotate individual tricky cases inline. Where it helps a reader, link to the
owning reference document (for example `// see docs/reference/macros/cgp_impl.md`).
Tests link **to** the documentation; the documentation never links back to a test
(per `docs/CLAUDE.md`).
and annotate individual tricky cases inline. Link to the owning **implementation
document** — the one under `docs/implementation/` whose Tests and Snapshots
sections index this test (for example `// see docs/implementation/entrypoints/cgp_impl.md`);
that document is where test pointers live, since a reference document never links
to a test (per `docs/CLAUDE.md`). You may additionally link to a reference
document when a reader needs the user-facing semantics. Tests link **to** the
documentation; the reference documents never link back to a test.

## Use macro snapshots sparingly

Expand Down Expand Up @@ -117,19 +120,27 @@ failure-case target:
would not), and add a code comment explaining **why** the output is wrong and
**what the correct output should be**.

Every failure case must also be recorded in the reference document that owns the
construct, under its `## Known issues` section (the heading `docs/CLAUDE.md`
mandates), describing the behavior without referring to the test. Put a link from
the test's comment to that reference document.
Every failure case must also be recorded in the construct's **implementation
document** under `docs/implementation/`, in its `## Known issues` section and
indexed from its `## Tests` section, describing the behavior without referring to
the test. When the failure has a user-visible consequence, note that in the
reference document's `## Known issues` section too and cross-link the two. Put a
link from the test's comment to the implementation document.

## Keep the docs in sync

This suite is one of the four views of CGP's truth, alongside the macro
implementation in `cgp-macro-core`, the reference documents in `docs/reference`, and
the `/cgp` skill (see `docs/CLAUDE.md`). When a test reveals or pins a behavior
worth documenting, update the reference document to explain that behavior directly —
without referring to the test. When you move a test that a reference document's
`## Source` section links to, update the link in the same change.
This suite is one of the views of CGP's truth, alongside the macro implementation
in `cgp-macro-core`, the reference documents in `docs/reference`, the
implementation documents in `docs/implementation`, and the `/cgp` skill (see
`docs/CLAUDE.md`). The implementation documents are the ones tightly coupled to
this suite: each macro's implementation document has a `## Tests` section linking
every behavioral test and failure case that exercises it, and every entrypoint
document a `## Snapshots` section indexing the expansion snapshots and calling out
which variants are still missing. When a test reveals or pins a behavior worth
documenting, update the implementation document to explain that behavior directly —
and the reference document when the behavior is user-facing — without referring to
the test. When you add, move, or rename a test, update the implementation
document's Tests or Snapshots section in the same change.

## Running the suite

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! `#[cgp_component]` must be applied to a trait; applying it to another item
//! is rejected at parse time.
//!
//! See docs/reference/macros/cgp_component.md.
//! See docs/implementation/entrypoints/cgp_component.md (Tests) for this failure
//! case, and docs/reference/macros/cgp_component.md for the user-facing semantics.

use quote::quote;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
//! This is the reference snapshot for that expansion; other concepts reuse
//! `#[cgp_component]` without re-snapshotting it.
//!
//! See docs/reference/macros/cgp_component.md.
//! See docs/implementation/entrypoints/cgp_component.md (Snapshots) for the index
//! of `#[cgp_component]` expansion snapshots, and docs/reference/macros/cgp_component.md
//! for the user-facing expansion.

use cgp_macro_test_util::snapshot_cgp_component;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
//! the distinct component-expansion variant with a supertrait bound and a default
//! method, kept alongside the plain snapshot in `component_macro`.
//!
//! See docs/reference/macros/cgp_component.md and
//! docs/reference/attributes/extend.md.
//! See docs/implementation/entrypoints/cgp_component.md (Snapshots) for this
//! supertrait-plus-default-method variant, and docs/reference/macros/cgp_component.md
//! and docs/reference/attributes/extend.md for the user-facing semantics.

use cgp::prelude::*;
use cgp_macro_test_util::snapshot_cgp_component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
//! `delegate_components!`, and `check_components!` wiring below is written plainly
//! (its expansion is owned by the `basic_delegation` and `checking` concepts).
//!
//! See docs/reference/macros/cgp_component.md and docs/reference/types/life.md.
//! See docs/implementation/entrypoints/cgp_component.md (Snapshots) for this
//! lifetime-and-type-parameter variant, and docs/reference/macros/cgp_component.md
//! and docs/reference/types/life.md for the user-facing semantics.

use cgp::prelude::*;
use cgp_macro_test_util::{snapshot_cgp_component, snapshot_cgp_provider};
Expand Down
Loading
Loading