Macro Boundaries

Why macro boundaries matter

Rust source code lies. A #[derive(Debug)] on a struct generates an impl Debug for Foo that exists at compile time but is invisible to a tree-sitter parser. Likewise, an #[async_trait] attribute rewrites an async method into a function returning a Pin<Box<dyn Future>> — the source AST shows a clean signature, but the symbol the linker sees is different. Tree-sitter alone cannot resolve any of this.

sqry’s macro boundary analysis extends the unified graph with metadata about generated symbols. You can:

This page covers the CLI flags, the sqry cache expand workflow, and the expand_cache_status MCP tool that exposes cache state to AI assistants.

Search-time flags

Three flags on sqry search and the underlying graph queries control macro boundary handling:

FlagEffect
--cfg-filter <PREDICATE>Only return symbols active under the given cfg predicate. Examples: --cfg-filter test, --cfg-filter "target_os = \"linux\"", --cfg-filter "feature = \"simd\"".
--include-generatedInclude macro-generated symbols in results. Default: excluded — derive impls, async-trait stubs, and similar synthesized symbols don’t appear unless you opt in.
--macro-boundariesAdd macro-boundary metadata to each result row: cfg condition, macro source, and a generated:true marker. Useful for auditing what your macros actually emit.
# Find every #[cfg(test)] function in the workspace
sqry search "" --kind function --cfg-filter test

# Show all symbols a derive macro generates for a given struct
sqry search "MyStruct" --include-generated --macro-boundaries

# Audit feature-gated code
sqry search "" --cfg-filter "feature = \"simd\"" --kind function

# Combine with platform filters
sqry search "" --cfg-filter "target_os = \"linux\"" --kind function

The flags compose with all the other sqry search filters (--kind, --lang, --file, --limit, …).

Live expansion via cargo expand

Tree-sitter parses source. To reach what the compiler actually sees, sqry can run cargo expand on opted-in workspaces and ingest the expanded output into the graph. This is gated by --enable-macro-expansion:

# Index with live macro expansion enabled (Rust workspaces)
sqry index . --enable-macro-expansion

--enable-macro-expansion runs cargo expand on every workspace member, which executes build scripts and proc macros. Only enable it on trusted codebases. The expanded output is cached at .sqry-cache/expansion/ and reused on subsequent index runs as long as the source has not changed.

Enabling live expansion adds two more flags:

FlagEffect
--cfg <PREDICATE>Pass a #[cfg] predicate into the expansion (e.g. --cfg test to expand #[cfg(test)] blocks).
--cfg-filter <PREDICATE>At query time, restrict results to that predicate (same flag as above; behaves consistently across index and query).

The expansion cache

sqry cache expand is the explicit entrypoint for managing the expansion cache outside an index run.

sqry cache expand                              # refresh stale entries
sqry cache expand --refresh                    # rebuild the entire cache
sqry cache expand --crate-name my_crate        # scope to a single workspace member
sqry cache expand --dry-run                    # show what would be expanded, write nothing
sqry cache expand --output /tmp/expand-out     # write expanded sources to a directory for inspection
FlagEffect
--refreshForce-rebuild the entire cache, ignoring per-file freshness.
--crate-name <NAME>Restrict the operation to a single workspace member.
--dry-runPrint the planned set of crates to expand and exit; no cargo expand is invoked.
--output <PATH>Write expanded sources into <PATH> for offline inspection.

The cache directory is .sqry-cache/expansion/ by default (override with the global --cache-dir flag on sqry index).

Boundary metadata in the graph

Internally, every macro-touched node carries a MacroNodeMetadata record:

FieldMeaning
macro_generatedtrue if the symbol came out of a macro expansion (excluded by default).
cfg_conditionThe #[cfg(...)] predicate the symbol is gated by, if any.
macro_sourceThe macro that produced the symbol (e.g. derive(Debug), async_trait, custom proc-macros).
proc_macro_kindderive, attribute, or function-like for the originating macro.
unresolved_attributesAttributes the analyzer could not classify — usually a sign of an unknown proc-macro.

The graph also carries MacroExpansion edges (kind CfgGate) that link a source symbol to the cfg predicate gating it. sqry graph nodes --kind function and sqry graph edges --kind macro-expansion expose these directly.

MCP tool: expand_cache_status

AI assistants can read the cache state via the expand_cache_status MCP tool (MCP Tools reference). It returns:

Use this when an assistant is about to run a query that depends on --include-generated or --cfg-filter — if the cache is stale or empty, the assistant can recommend sqry cache expand --refresh before continuing.

Six sub-analyzers

Macro boundary analysis is implemented as six cooperating sub-analyzers (Rust-only, in the unified graph build pipeline):

  1. Attribute macro analyzer — detects #[attr]-wrapped items and resolves the attribute proc-macro.
  2. Metavariable analyzer — tracks $ident substitutions inside macro_rules! rule arms.
  3. Proc-macro classifier — categorises each detected proc-macro by kind (derive / attribute / function-like).
  4. Cross-crate macro resolver — follows pub use re-exports of macros across crate boundaries.
  5. Cfg analyzer — extracts the active #[cfg(...)] predicate for every item and records the gate edge.
  6. Expand cache analyzer — runs cargo expand (when enabled), parses the expanded output, and unifies its nodes with the source graph.

The analyzers run during Phase 1 (parallel parse) of the unified graph build pipeline. Their output lands in the shared NodeMetadataStore alongside the node arena.

Troubleshooting