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
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ impl PreprocessedCgpComponent {
/// Build the zero-sized `{Provider}Component` marker struct.
pub fn to_component_struct(&self) -> EmptyStruct {
let component_name = &self.args.component_name;
let consumer_trait = &self.item_trait.ident;
EmptyStruct {
ident: component_name.ident.clone(),
generics: component_name.type_generics.to_generics(),
doc: Some(format!("Component marker for [`{consumer_trait}`].")),
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ pub fn derive_builder_struct(
builder_struct.attrs.clear();
builder_struct.ident = builder_ident.clone();

// The companion type carries a doc line, so a crate that enables
// `missing_docs` does not report it.
let context_ident = &context_struct.ident;
let builder_doc = format!("The partial builder for [`{context_ident}`].");
builder_struct
.attrs
.push(syn::parse_quote!(#[doc = #builder_doc]));

let generics = &mut builder_struct.generics;

for (i, field) in builder_struct.fields.iter_mut().enumerate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ pub fn derive_extractor_enum(

extractor_enum.ident = extractor_ident.clone();

// The companion type carries a doc line, so a crate that enables
// `missing_docs` does not report it.
let context_ident = &context_enum.ident;
let extractor_doc = format!("The partial extractor for [`{context_ident}`].");
extractor_enum
.attrs
.push(syn::parse_quote!(#[doc = #extractor_doc]));

let generics = &mut extractor_enum.generics;

for (i, variant) in extractor_enum.variants.iter_mut().enumerate() {
Expand Down Expand Up @@ -59,6 +67,14 @@ pub fn derive_extractor_enum_ref(

extractor_enum.ident = extractor_ident.clone();

// The companion type carries a doc line, so a crate that enables
// `missing_docs` does not report it.
let context_ident = &context_enum.ident;
let extractor_doc = format!("The borrowed partial extractor for [`{context_ident}`].");
extractor_enum
.attrs
.push(syn::parse_quote!(#[doc = #extractor_doc]));

// A variantless enum borrows nothing, so the `'__a__` lifetime and the
// `__R__: MapTypeRef` selector would be unused parameters (`E0392`). Emit the
// borrowed partial enum as a bare empty enum instead, and the impls that
Expand Down
27 changes: 24 additions & 3 deletions crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use syn::punctuated::Punctuated;
use syn::token::Plus;
use syn::{
Generics, Ident, Item, ItemFn, ItemImpl, ItemTrait, TraitItemFn, Type, TypeParamBound,
Visibility,
Visibility, parse_quote,
};

use crate::functions::parse_internal;
Expand Down Expand Up @@ -37,8 +37,26 @@ impl PreprocessedItemCgpFn {
..
} = self;

// The function's doc comment describes the operation, so it stays on the
// method, where the function's body lands. The trait gets a line of its
// own, and the method gets one when the function carries no doc, so a
// crate that enables `missing_docs` reports neither.
let (doc_attrs, other_attrs): (Vec<_>, Vec<_>) = attributes
.raw_attributes
.iter()
.cloned()
.partition(|attr| attr.path().is_ident("doc"));

let mut method_attrs = item_fn.attrs.clone();
method_attrs.extend(doc_attrs);
if !method_attrs.iter().any(|attr| attr.path().is_ident("doc")) {
let fn_ident = &item_fn.sig.ident;
let method_doc = format!("The `{fn_ident}` operation.");
method_attrs.push(parse_quote!(#[doc = #method_doc]));
}

let trait_item_fn = TraitItemFn {
attrs: item_fn.attrs.clone(),
attrs: method_attrs,
sig: item_fn.sig.clone(),
default: None,
semi_token: None,
Expand All @@ -65,7 +83,10 @@ impl PreprocessedItemCgpFn {

attributes.use_type.transform_item_trait(&mut item_trait)?;

item_trait.attrs.extend(attributes.raw_attributes.clone());
item_trait.attrs.extend(other_attrs);
let fn_ident = &item_fn.sig.ident;
let trait_doc = format!("The trait for the `{fn_ident}` operation.");
item_trait.attrs.push(parse_quote!(#[doc = #trait_doc]));
item_trait.vis = visibility.clone();

Ok(item_trait)
Expand Down
13 changes: 13 additions & 0 deletions crates/macros/cgp-macro-core/src/types/cgp_provider/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,22 @@ impl ItemCgpProvider {

let provider_type: IdentWithTypeGenerics = parse_internal!( #impl_self_type );

let provider_trait = provider_impl
.trait_
.as_ref()
.and_then(|(_, path, _)| path.segments.last().map(|s| s.ident.to_string()))
.unwrap_or_default();
let provider_ident = &provider_type.ident;
let doc = if provider_trait.is_empty() {
format!("Provider `{provider_ident}`.")
} else {
format!("Provider `{provider_ident}` for [`{provider_trait}`].")
};

let provider_struct = EmptyStruct {
ident: provider_type.ident.clone(),
generics: provider_type.type_generics.to_generics(),
doc: Some(doc),
};

Ok(Some(provider_struct))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ impl InnerDelegateTable {
let ident = self.table_ident.clone();
let generics = self.table_generics.generics.clone();

EmptyStruct { ident, generics }
let doc = Some(format!("Wiring table `{ident}`."));
EmptyStruct {
ident,
generics,
doc,
}
}

pub fn build_impls(&self) -> syn::Result<Vec<ItemImpl>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@ impl DelegateTable {
}
}

let doc = Some(format!("Wiring table `{}`.", struct_type.ident));
item_structs.push(EmptyStruct {
ident: struct_type.ident,
generics,
doc,
});
}

Expand Down
9 changes: 9 additions & 0 deletions crates/macros/cgp-macro-core/src/types/empty_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use crate::exports::Life;
pub struct EmptyStruct {
pub ident: Ident,
pub generics: Generics,
/// The doc line emitted above the struct, so a crate that enables
/// `missing_docs` does not report the generated item.
pub doc: Option<String>,
}

impl EmptyStruct {
Expand All @@ -20,6 +23,10 @@ impl ToTokens for EmptyStruct {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let struct_ident = &self.ident;
let struct_generics = &self.generics;
let struct_doc = self
.doc
.as_ref()
.map(|doc| quote_spanned! { struct_ident.span() => #[doc = #doc] });

// Stamp the synthesized `pub struct … ;` tokens with the struct ident's
// span rather than the macro `call_site`, so a redefinition error
Expand All @@ -30,6 +37,7 @@ impl ToTokens for EmptyStruct {

if struct_generics.params.is_empty() {
tokens.extend(quote_spanned! { span =>
#struct_doc
pub struct #struct_ident;
});
} else {
Expand Down Expand Up @@ -68,6 +76,7 @@ impl ToTokens for EmptyStruct {
};

tokens.extend(quote_spanned! { span =>
#struct_doc
pub struct #struct_ident < #generic_params > (
pub ::core::marker::PhantomData< #phantom_type >
);
Expand Down
8 changes: 8 additions & 0 deletions crates/macros/cgp-macro-core/src/types/namespace/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,14 @@ impl NamespaceTable {
let namespace_trait = self.build_namespace_trait()?;

let item_trait: Option<ItemTrait> = if self.new.is_some() {
let namespace_ident = &self.namespace.ident;
let trait_doc = format!("The `{namespace_ident}` namespace's wiring table.");
let delegate_doc =
format!("The provider table the `{namespace_ident}` namespace delegates to.");
let item_trait = parse_internal! {
#[doc = #trait_doc]
pub trait #namespace_trait {
#[doc = #delegate_doc]
type Delegate;
}
};
Expand Down Expand Up @@ -153,7 +159,9 @@ impl NamespaceTable {
namespace_ident.span(),
);

let namespace_doc = format!("Wiring table for the `{namespace_ident}` namespace.");
let namespace_struct: ItemStruct = parse_internal! {
#[doc = #namespace_doc]
pub struct #namespace_struct_ident;
};

Expand Down
8 changes: 7 additions & 1 deletion crates/tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ getters, field access, extensible records, extensible variants, checking,
dispatching, namespaces, handlers, monadic handlers, async and Send bounds,
blanket traits, and the standalone error backends (`error_backends`). The crate's build script
turns the example in each error backend's README into a test module, which the `readme_*.rs` files
in `error_backends` include, so those examples are tested without being copied. The set grows and subdivides over time. `cgp-macro-tests` follows
in `error_backends` include, so those examples are tested without being copied.

The library crate's `generated_docs` module denies `missing_docs` while using every macro, so a
generated item that loses its doc line fails the build. The `implicit_arguments` and
`basic_delegation` targets pin where a user's own doc comment lands.

The set grows and subdivides over time. `cgp-macro-tests` follows
the same shape, with `ident_with_type_params` and `shape_macro_parsing` for parser corner cases,
`companion_attrs` for the attributes the data derives copy onto their companion types, and the
failure-case targets `parser_rejections` and `invalid_expansion`.
Expand Down
115 changes: 115 additions & 0 deletions crates/tests/cgp-tests/src/generated_docs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! Every macro that declares a public item documents it.
//!
//! The module denies `missing_docs`, so this file fails to compile if a macro
//! stops emitting a doc line for an item it generates. It lives in the library
//! crate rather than an integration target because `missing_docs` only fires on
//! items exported from a library; a test binary exports nothing. The snapshot
//! modules pin the text of the generated docs, and this pins their presence.

#![deny(missing_docs)]
// A doc comment on a macro invocation is consumed by the macro, which emits it on
// the generated items, so rustdoc never sees it here.
#![allow(unused_doc_comments)]

use cgp::core::error::ErrorTypeProviderComponent;
use cgp::prelude::*;

/// A component, whose marker struct the macro documents.
#[cgp_component(Greeter)]
pub trait CanGreet {
/// Greets.
fn greet(&self) -> String;
}

/// A provider, whose struct the macro documents.
#[cgp_impl(new GreetHello)]
impl Greeter {
fn greet(&self) -> String {
"Hello".to_owned()
}
}

/// A generic provider, whose struct and `PhantomData` field the macro documents.
#[cgp_impl(new GreetWith<Name>)]
impl<Name> Greeter
where
Name: Send + Sync,
{
fn greet(&self) -> String {
"Hello".to_owned()
}
}

/// A function-style trait, whose trait and method the macro documents.
#[cgp_fn]
pub fn greet_fn(&self, #[implicit] name: &str) -> String {
name.to_owned()
}

/// An abstract type, whose marker struct the macro documents.
#[cgp_type]
pub trait HasProbeType {
/// The probe type.
type Probe;
}

/// A getter, whose component the macro documents.
#[cgp_getter]
pub trait HasProbeGetter {
/// Reads the probe.
fn probe(&self) -> &str;
}

/// An auto getter, whose blanket impl the macro documents.
#[cgp_auto_getter]
pub trait HasProbeField {
/// Reads the field.
fn probe_field(&self) -> &str;
}

/// A computer, whose provider struct the macro documents.
#[cgp_computer]
pub fn probe_computer(input: u32) -> u32 {
input
}

/// A producer, whose provider struct the macro documents.
#[cgp_producer]
pub fn probe_producer() -> u32 {
1
}

/// A dispatch trait, whose provider struct the macro documents.
#[cgp_auto_dispatch]
pub trait CanProbeDispatch {
/// Dispatches.
fn dispatch(&self) -> u32;
}

/// A namespace, whose struct, trait, and associated type the macro documents.
cgp_namespace! {
new ProbeNamespace: DefaultNamespace {
@cgp.core.error.ErrorTypeProviderComponent: UseType<String>,
}
}

/// A wiring table, whose struct the macro documents.
delegate_components! {
new ProbeTable {
GreeterComponent: GreetHello,
}
}

/// A record, whose partial builder the derive documents.
#[derive(CgpData)]
pub struct ProbeRecord {
/// A field.
pub field: u32,
}

/// A variant, whose partial extractor the derive documents.
#[derive(CgpData)]
pub enum ProbeVariant {
/// A variant.
A(u32),
}
7 changes: 5 additions & 2 deletions crates/tests/cgp-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
//!
//! All tests live in the integration targets under `tests/`, organized by CGP
//! concept (one target per concept, one file per unit test). This library crate
//! holds only shared support code; today there is none, so it is intentionally
//! empty. See `../README.md` and `../AGENTS.md` for the organization and the
//! holds the one check that needs a library: `generated_docs`, which denies
//! `missing_docs` so a macro that stops documenting a generated item fails the
//! build. See `../README.md` and `../AGENTS.md` for the organization and the
//! rules for maintaining the suite.

pub mod generated_docs;
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ snapshot_cgp_type! {
TypesTypeProviderComponent,
>>::Delegate as TypesTypeProvider<__Context__>>::Types;
}
///Component marker for [`HasTypes`].
pub struct TypesTypeProviderComponent;
impl<__Context__> TypesTypeProvider<__Context__> for UseContext
where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ snapshot_cgp_type! {
ScalarTypeProviderComponent,
>>::Delegate as ScalarTypeProvider<__Context__>>::Scalar;
}
///Component marker for [`HasScalarType`].
pub struct ScalarTypeProviderComponent;
impl<__Context__> ScalarTypeProvider<__Context__> for UseContext
where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ snapshot_cgp_type! {
ScalarTypeProviderComponent,
>>::Delegate as ScalarTypeProvider<__Context__>>::Scalar;
}
///Component marker for [`HasScalarType`].
pub struct ScalarTypeProviderComponent;
impl<__Context__> ScalarTypeProvider<__Context__> for UseContext
where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ snapshot_cgp_type! {
ProvideFooTypeComponent,
>>::Delegate as ProvideFooType<__Context__, T>>::Foo;
}
///Component marker for [`HasFooType`].
pub struct ProvideFooTypeComponent;
impl<__Context__, T: ?Sized> ProvideFooType<__Context__, T> for UseContext
where
Expand Down
Loading
Loading