{"record":{"id":"211b4156c33a898b","repo":"GraphiteEditor/Graphite","slug":"command-parameters-must-be-plain-identifiers","errorCode":null,"errorMessage":"command parameters must be plain identifiers","messagePattern":"command parameters must be plain identifiers","errorType":"validation","errorClass":"syn::Error","httpStatus":null,"severity":"error","filePath":"proc-macros/src/editor_commands.rs","lineNumber":68,"sourceCode":"\t\tlet signature = &function.sig;\n\t\tif let Some(receiver) = signature.receiver() {\n\t\t\treturn Err(Error::new(receiver.span(), \"command functions take no `self`; they are pure `args… -> Message` translations\"));\n\t\t}\n\t\tif !signature.generics.params.is_empty() || signature.asyncness.is_some() || signature.unsafety.is_some() {\n\t\t\treturn Err(Error::new(signature.span(), \"command functions must be plain non-generic, non-async, safe functions\"));\n\t\t}\n\n\t\tlet docs = &function.attrs;\n\t\tlet fn_name = &signature.ident;\n\t\tlet variant = Ident::new(&fn_name.to_string().to_case(Case::Pascal), fn_name.span());\n\t\tlet js_name = Ident::new(&fn_name.to_string().to_case(Case::Camel), fn_name.span());\n\n\t\tlet mut param_names = Vec::new();\n\t\tlet mut param_types = Vec::new();\n\t\tfor parameter in &signature.inputs {\n\t\t\tlet FnArg::Typed(pat_type) = parameter else { unreachable!(\"receiver is rejected above\") };\n\t\t\tlet Pat::Ident(pat_ident) = &*pat_type.pat else {\n\t\t\t\treturn Err(Error::new(pat_type.span(), \"command parameters must be plain identifiers\"));\n\t\t\t};\n\t\t\tparam_names.push(&pat_ident.ident);\n\t\t\tparam_types.push(&*pat_type.ty);\n\t\t}\n\n\t\tlet return_type = &signature.output;\n\t\tlet body = &function.block;\n\n\t\tlet span = fn_name.span();\n\t\tvariants.extend(quote_spanned! {span=>\n\t\t\t#(#docs)*\n\t\t\t#variant { #(#param_names: #param_types,)* },\n\t\t});\n\t\tstubs.extend(quote_spanned! {span=>\n\t\t\t#(#docs)*\n\t\t\t#[cfg(not(feature = \"native\"))]\n\t\t\t#[wasm_bindgen(js_name = #js_name)]\n\t\t\tpub fn #fn_name(&self, #(#param_names: #param_types,)*) {","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/proc-macros/src/editor_commands.rs#L50-L86","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n#[editor_commands]\nmod commands {\n\tfn move_layer((dx, dy): (f64, f64)) -> Message { ... }\n\tfn noop(_: String) -> Message { ... }\n}\n\n// after\n#[editor_commands]\nmod commands {\n\tfn move_layer(dx: f64, dy: f64) -> Message { ... }\n\tfn noop(_s: String) -> Message { ... }\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["rust","proc-macro","editor-commands","parameter-patterns"],"backgroundTag":"proc-macro-input-validation","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}