GraphiteEditor/Graphite · error · syn::Error
command functions have no visibility modifier; the macro gen
Error message
command functions have no visibility modifier; the macro generates the public JS-facing stub
What it means
Command functions declared inside an #[editor_commands] module must have inherited (private) visibility. The macro itself generates the public JS-facing dispatch stub, so a hand-written `pub`, `pub(crate)`, or `pub(in …)` modifier is a compile error — otherwise callers could bypass the generated stub or the visibility would conflict with it.
Source
Thrown at proc-macros/src/editor_commands.rs:44
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(
function.span(),
"command functions have no visibility modifier; the macro generates the public JS-facing stub",
));
}
let signature = &function.sig;
if let Some(receiver) = signature.receiver() {
return Err(Error::new(receiver.span(), "command functions take no `self`; they are pure `args… -> Message` translations"));
}
if !signature.generics.params.is_empty() || signature.asyncness.is_some() || signature.unsafety.is_some() {
return Err(Error::new(signature.span(), "command functions must be plain non-generic, non-async, safe functions"));
}
let docs = &function.attrs;
let fn_name = &signature.ident;
let variant = Ident::new(&fn_name.to_string().to_case(Case::Pascal), fn_name.span());
let js_name = Ident::new(&fn_name.to_string().to_case(Case::Camel), fn_name.span());
View on GitHub (pinned to c507b35645)
Solutions
- Remove the visibility modifier so the command reads `fn name(args…) -> Message`.
- Keep the function private and rely on the macro-generated public stub for external access.
- If the function genuinely needs manual publicity outside the command dispatch, move it to a plain impl block instead of the #[editor_commands] module.
Example fix
// before
#[editor_commands]
mod commands {
pub fn select_layer(layer: u64) -> Message { ... }
}
// after
#[editor_commands]
mod commands {
fn select_layer(layer: u64) -> Message { ... }
} Defensive patterns
Strategy: validation
Prevention
- Write commands as private `fn name(args…) -> Message`; the macro publishes the stub.
- Add a code-review rule: no visibility keywords inside #[editor_commands] modules.
- When copy-pasting a command, strip `pub` first.
When it happens
Trigger: Writing `pub fn`, `pub(crate) fn`, or any other visibility modifier on a function inside the #[editor_commands] module. The macro checks `matches!(function.vis, Visibility::Inherited)`.
Common situations: Copy-pasting an existing public command implementation from a plain module into the macro module, or instinctively marking new API surface as `pub` because it is called from JS elsewhere in the file.
Related errors
- #[editor_commands] requires a module with an inline body
- only `use` imports and command functions may appear in an #[
- command functions may not have attributes; anything that doe
- 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/9ab58d7d0a9e0c6d.
Report an issue: GitHub.