GraphiteEditor/Graphite · error · syn::Error
only `use` imports and command functions may appear in an #[
Error message
only `use` imports and command functions may appear in an #[editor_commands] module
What it means
The #[editor_commands] macro only understands two kinds of items inside its module: `use` imports and plain command functions. Any other item (struct, enum, const, static, impl block, nested module, macro invocation) hits the catch-all arm and is rejected with this compile-time error, because the macro would otherwise silently drop the item when it regenerates the module.
Source
Thrown at proc-macros/src/editor_commands.rs:26
if !attr.is_empty() {
return Err(Error::new(attr.span(), "#[editor_commands] takes no arguments"));
}
for attr in &module.attrs {
if !attr.path().is_ident("doc") {
return Err(Error::new(attr.span(), "the #[editor_commands] module may not have other attributes"));
}
}
let Some((_, items)) = module.content else {
return Err(Error::new(module.mod_token.span, "#[editor_commands] requires a module with an inline body"));
};
let mut imports: Vec<ItemUse> = Vec::new();
let mut functions: Vec<ItemFn> = Vec::new();
for item in items {
match item {
Item::Use(import) => imports.push(import),
Item::Fn(function) => functions.push(function),
other => return Err(Error::new(other.span(), "only `use` imports and command functions may appear in an #[editor_commands] module")),
}
}
let mut variants = TokenStream::new();
let mut stubs = TokenStream::new();
let mut arms = TokenStream::new();
for function in &functions {
for attr in &function.attrs {
if !attr.path().is_ident("doc") {
return Err(Error::new(
attr.span(),
"command functions may not have attributes; anything that doesn't fit the `fn name(args…) -> Message` contract belongs in a plain impl block",
));
}
}
if !matches!(function.vis, Visibility::Inherited) {
return Err(Error::new(View on GitHub (pinned to c507b35645)
Solutions
- Move the non-`use`/non-`fn` item out of the #[editor_commands] module to the surrounding scope or a plain module.
- If the item is a helper the commands need, import it with a `use` statement instead of defining it inline.
- If the item is a test module (`#[cfg(test)] mod tests`), relocate it outside the attributed module.
Example fix
// before
#[editor_commands]
mod commands {
struct LayerRef(u64); // rejected item
fn select_layer(l: LayerRef) -> Message { ... }
}
// after
struct LayerRef(u64);
#[editor_commands]
mod commands {
use super::LayerRef;
fn select_layer(l: LayerRef) -> Message { ... }
} Defensive patterns
Strategy: validation
Prevention
- Keep only `use` imports and plain command fns inside the #[editor_commands] module.
- Put structs, consts, impls, and test modules in a sibling plain module.
- Treat 'item does not generate a command' as a signal to move it out, not to work around the macro.
When it happens
Trigger: Putting a `struct`, `impl`, `const`, `static`, `trait`, nested `mod`, or macro call inside the `#[editor_commands]` module body. The match on parsed items only has arms for `Item::Use` and `Item::Fn`.
Common situations: Growing a command module over time until someone adds a shared helper struct, a const for a default value, or an impl of a convenience method — all of which must live outside the macro module. Also hit when a merge accidentally leaves a test module inside.
Related errors
- #[editor_commands] requires a module with an inline body
- command functions may not have attributes; anything that doe
- command functions have no visibility modifier; the macro gen
- command functions take no `self`; they are pure `args… -> Me
- command functions must be plain non-generic, non-async, safe
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/51ad1273989d9e2d.
Report an issue: GitHub.