Skip to content
cccskills
FreePublished on GitHub

macro

Use this skill when building procedural macros for Topcoat in Rust. It explains how to preserve parsed syntax in AST nodes, separate parsing from meaning, and keep macro entry points focused on parsing input, expanding valid code, and reporting errors.

OriginalShow the original description

Always use this skill before writing procedural macros for Topcoat

tokio-rs/topcoat6,224 stars(GitHub 6,224 + cccskills 0)

Run score Not tested

See how to install

Good for

  • Designing AST nodes for a procedural macro
  • Parsing custom syntax while preserving tokens
  • Organizing a macro entry point and code generation

Files included (1)

  • SKILL.md3.0 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

AST nodes

  • Like syn nodes, an AST node is a lossless record of the syntax it matched: every field is pub and holds the tokens as written, spans included. The original source should be reconstructible from the node. Field order does not have to match the source, but spans must be preserved.
  • Parsing validates, it does not interpret. A Parse impl rejects what is not valid syntax and stores everything else verbatim in token fields; it never normalizes, resolves, or drops information on the way in.
  • Semantics are derived from the AST, not baked into it. Put the interpretation in methods on the node that read its own fields, so the node stays a faithful representation of the source and the meaning stays separate from it.

Parsing

  • In Parse impls, parse directly into the Self { ... } fields (Self { x: input.parse()? }) rather than through let bindings. Use a let only when a parsed value must be inspected to decide how to parse a later field.
  • To parse custom keywords that are not re-emitted into the generated code, create a private mod kw with syn::custom_keyword! invocations instead of parsing syn::Ident. Use input.lookahead1() if it makes sense.
  • Instead of manually peeking for optional AST nodes in the input stream at the call site, implement ParseOption and parse them with input.call(Node::parse_option)?.

Entry points

The macro/ crate only bridges proc_macro::TokenStream to the grammar crate that holds the AST and the codegen. Every entry point is a parse, a match, and a to_compile_error() on the error arm; no logic lives in lib.rs.

An attribute macro receives two token streams, so it gets three types: one Parse node per stream and a tuple struct pairing them, which is the node the codegen hangs off.

// grammar crate
pub struct ProcedureAttr {}
pub struct ProcedureItem { ... }

pub struct Procedure(ProcedureAttr, ProcedureItem);

impl Procedure {
    #[must_use]
    pub fn new(attr: ProcedureAttr, item: ProcedureItem) -> Self {
        Self(attr, item)
    }

    pub fn parse(attr: TokenStream, item: TokenStream) -> syn::Result<Self> {
        Ok(Self::new(syn::parse2(attr)?, syn::parse2(item)?))
    }
}

impl ToTokens for Procedure { ... }

Parse stays per stream, since neither stream alone is the macro input. The pairing type owns the inherent parse that joins them and the ToTokens impl that expands them, which keeps the entry point a one-liner:

// macro crate
#[doc = include_str!("../docs/procedure.md")]
#[proc_macro_attribute]
pub fn procedure(attr: TokenStream, item: TokenStream) -> TokenStream {
    match topcoat_runtime_grammar::procedure::Procedure::parse(attr.into(), item.into()) {
        Ok(value) => quote! { #value }.into(),
        Err(error) => error.to_compile_error().into(),
    }
}

A function-like macro has a single input, so it needs no pairing type: syn::parse_macro_input! straight into its AST node, then expand.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

check

FreeSummary

Use this skill to check Topcoat changes locally before committing or opening a pull request. It runs the project’s Rust formatting, lint, test, and documentation commands, plus browser project checks when relevant, so problems can be caught before review.

  • Checking a Rust change before committing
  • Validating browser project changes
  • Building project documentation locally
tokio-rs/topcoat6.2KUpdated Oct 25, 2026

commit

FreeSummary

Use this skill to write concise commit messages in the Topcoat project’s Conventional Commits format. It helps choose a change type and scope, describe the update clearly, and mark breaking changes so release notes and version bumps can be based on commit history.

  • Writing a commit message for a code change
  • Choosing a commit type and scope
  • Marking a breaking change and migration note
tokio-rs/topcoat6.2KUpdated Oct 25, 2026

pr

FreeSummary

Use this skill to prepare a Topcoat pull request from the full branch diff. It guides you to write a Conventional Commits title and a concise body covering the change, verification performed, and an AI-use disclaimer when an agent creates the PR.

  • Writing a pull request title from the net branch change
  • Summarizing a diff and the checks performed
  • Adding the required AI-use disclosure to a PR
tokio-rs/topcoat6.2KUpdated Oct 25, 2026

prose

FreeSummary

Use this skill to write long-form Markdown guides for Topcoat. It covers where documentation sources belong, how to structure explanations from basic to advanced, and how to keep prose plain, concise, and easier to maintain as implementations change.

  • Planning where a guide and its links should live
  • Structuring a guide from basic to advanced use
  • Editing Markdown for clear, maintainable prose
tokio-rs/topcoat6.2KUpdated Oct 25, 2026

style

FreeSummary

Apply the project’s Rust conventions when editing code or documentation. The guidance covers module and type organization, workspace dependency declarations, plain English documentation, and tests that check meaningful behavior rather than fixed text.

  • Organizing Rust modules and implementations
  • Writing or revising Rust documentation
  • Reviewing whether a test protects real behavior
tokio-rs/topcoat6.2KUpdated Oct 25, 2026

See all skills by tokio-rs

Report a problem with this skill