GraphiteEditor/Graphite · error · syn::Error
#[editor_commands] requires a module with an inline body
Error message
#[editor_commands] requires a module with an inline body
What it means
Thrown by the #[editor_commands] attribute macro when it is applied to a module declared without an inline body (e.g. `mod commands;`). The macro must walk the module's items at compile time to collect `use` imports and command functions, so it can only see contents that are syntactically present inside `mod name { ... }`. An out-of-line module file gives it nothing to expand.
Source
Thrown at proc-macros/src/editor_commands.rs:17
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{Error, FnArg, Ident, Item, ItemFn, ItemMod, ItemUse, Pat, Visibility};
pub fn editor_commands_impl(attr: TokenStream, module: ItemMod) -> syn::Result<TokenStream> {
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 {View on GitHub (pinned to c507b35645)
Solutions
- Replace `mod commands;` with an inline `mod commands { ... }` containing the `use` statements and `fn name(args…) -> Message` command functions.
- Keep the attribute directly above the inline module: `#[editor_commands] mod commands { ... }`.
- Move any helper code that motivated the file split into a plain impl block outside the macro module.
Example fix
// before
#[editor_commands]
mod commands;
// after
#[editor_commands]
mod commands {
use crate::Message;
fn select_layer(layer: u64) -> Message { Message::SelectLayer(layer) }
} Defensive patterns
Strategy: validation
Prevention
- Never point #[editor_commands] at an out-of-line module file; keep the body inline.
- Run `cargo check` immediately after moving module code so the macro rejects the shape at compile time.
- Document the required inline form next to the macro definition in onboarding docs.
When it happens
Trigger: Applying #[editor_commands] to `mod commands;` (semicolon form pointing at commands.rs) instead of an inline `mod commands { ... }` block. The check is `module.content` being None, which only happens for the semicolon form.
Common situations: Developers splitting a growing commands module into its own file, or copy-pasting an attribute onto a module declaration that was already out-of-line. Also appears when a formatter or refactor tool rewrites `mod x { }` into `mod x;` + file.
Related errors
- only `use` imports and command functions may appear in an #[
- 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/9d3f534baff1e70c.
Report an issue: GitHub.