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:
- Filter results to symbols active only under specific
#[cfg]predicates (e.g. only#[cfg(test)]code, or only#[cfg(target_os = "linux")]). - Include or exclude macro-generated symbols at query time. By default they are excluded — the source view stays clean — but you can opt in when auditing what a macro actually emits.
- Inspect macro-source provenance, expansion kind, and cfg condition for any node that originated from a macro.
- Verify expansions against an opt-in
cargo expandcache, so the graph reflects what the compiler will actually see, not just what tree-sitter parsed.
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:
| Flag | Effect |
|---|---|
--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-generated | Include macro-generated symbols in results. Default: excluded — derive impls, async-trait stubs, and similar synthesized symbols don’t appear unless you opt in. |
--macro-boundaries | Add 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:
| Flag | Effect |
|---|---|
--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
| Flag | Effect |
|---|---|
--refresh | Force-rebuild the entire cache, ignoring per-file freshness. |
--crate-name <NAME> | Restrict the operation to a single workspace member. |
--dry-run | Print 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:
| Field | Meaning |
|---|---|
macro_generated | true if the symbol came out of a macro expansion (excluded by default). |
cfg_condition | The #[cfg(...)] predicate the symbol is gated by, if any. |
macro_source | The macro that produced the symbol (e.g. derive(Debug), async_trait, custom proc-macros). |
proc_macro_kind | derive, attribute, or function-like for the originating macro. |
unresolved_attributes | Attributes 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:
- Whether live expansion is enabled for the active workspace.
- Cache directory, total cached entries, and last refresh timestamp.
- Per-workspace-member cache hit rate.
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):
- Attribute macro analyzer — detects
#[attr]-wrapped items and resolves the attribute proc-macro. - Metavariable analyzer — tracks
$identsubstitutions insidemacro_rules!rule arms. - Proc-macro classifier — categorises each detected proc-macro by kind (derive / attribute / function-like).
- Cross-crate macro resolver — follows
pub usere-exports of macros across crate boundaries. - Cfg analyzer — extracts the active
#[cfg(...)]predicate for every item and records the gate edge. - 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
--cfg-filterreturns zero results, but I know the cfg matches: the workspace was indexed without live expansion. Re-runsqry index --enable-macro-expansion --cfg <predicate> .to materialise cfg-gated symbols.cargo expandfails insidesqry cache expand: sqry first triescargo expand --lib, then falls back tocargo expand(no flags) for binary crates. If both fail, runcargo expand --lib(orcargo expand) by hand to surface the underlying compiler error and fix it before retrying.- Generated symbols appear when I didn’t ask for them: another query in your pipeline passed
--include-generated. Macro-generated symbols are excluded by default — the flag must be opted in explicitly. - Cache too large:
rm -rf .sqry-cache/expansionand let it rebuild on the nextsqry cache expand(the cache is rebuildable from source and never authoritative).
Related
- Query Syntax — combine boundary flags with structured queries.
- Configuration — cache-tuning environment variables.
- MCP Tools —
expand_cache_statusand the wider tool catalogue. - Daemon (sqryd) — daemon-mode incremental rebuilds honor the same boundary flags.