GraphiteEditor/Graphite · error · syn::Error
command parameters must be plain identifiers
Error message
command parameters must be plain identifiers
What it means
Each parameter of a command function must be a plain identifier pattern (`name: Type`). The macro uses the identifier as the generated enum variant's field name, so tuple patterns, wildcard `_`, reference patterns (`&x`), or destructuring patterns cannot be represented and are rejected with a span on the whole `pat: Type` pair.
Source
Thrown at proc-macros/src/editor_commands.rs:68
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());
let mut param_names = Vec::new();
let mut param_types = Vec::new();
for parameter in &signature.inputs {
let FnArg::Typed(pat_type) = parameter else { unreachable!("receiver is rejected above") };
let Pat::Ident(pat_ident) = &*pat_type.pat else {
return Err(Error::new(pat_type.span(), "command parameters must be plain identifiers"));
};
param_names.push(&pat_ident.ident);
param_types.push(&*pat_type.ty);
}
let return_type = &signature.output;
let body = &function.block;
let span = fn_name.span();
variants.extend(quote_spanned! {span=>
#(#docs)*
#variant { #(#param_names: #param_types,)* },
});
stubs.extend(quote_spanned! {span=>
#(#docs)*
#[cfg(not(feature = "native"))]
#[wasm_bindgen(js_name = #js_name)]
pub fn #fn_name(&self, #(#param_names: #param_types,)*) {View on GitHub (pinned to c507b35645)
Solutions
- Split tuple destructuring into separate parameters: `fn move_layer(dx: f64, dy: f64)`.
- For `_`, keep the parameter named and prefix with underscore (`_unused: String`) — an underscore-prefixed ident is still `Pat::Ident`.
- For `&x`, take the value by reference at the type level (`x: &String`) instead of using a reference pattern.
Example fix
// before
#[editor_commands]
mod commands {
fn move_layer((dx, dy): (f64, f64)) -> Message { ... }
fn noop(_: String) -> Message { ... }
}
// after
#[editor_commands]
mod commands {
fn move_layer(dx: f64, dy: f64) -> Message { ... }
fn noop(_s: String) -> Message { ... }
} Defensive patterns
Strategy: validation
Prevention
- Use one identifier per parameter; split tuple args into separate parameters.
- Use underscore-prefixed names (`_x`) instead of `_` for unused parameters.
- Avoid reference patterns; declare the parameter type as a reference instead.
When it happens
Trigger: Writing `fn move_layer((dx, dy): (f64, f64)) -> Message`, `fn foo(_: String) -> Message`, or `fn bar(&name: &String) -> Message` inside the #[editor_commands] module. Only `Pat::Ident` passes the let-else.
Common situations: Copy-pasting math-heavy code that destructures pairs inline, or silencing unused-parameter warnings with `_`. Common when commands wrap geometry tuples (Vec2 as a tuple) or Option destructuring.
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 have no visibility modifier; the macro gen
- command functions take no `self`; they are pure `args… -> Me
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/211b4156c33a898b.
Report an issue: GitHub.