{"record":{"id":"ec710c225aa4c991","repo":"GraphiteEditor/Graphite","slug":"command-functions-must-be-plain-non-generic-non-a","errorCode":null,"errorMessage":"command functions must be plain non-generic, non-async, safe functions","messagePattern":"command functions must be plain non-generic, non-async, safe functions","errorType":"validation","errorClass":"syn::Error","httpStatus":null,"severity":"error","filePath":"proc-macros/src/editor_commands.rs","lineNumber":55,"sourceCode":"\t\t\t\treturn Err(Error::new(\n\t\t\t\t\tattr.span(),\n\t\t\t\t\t\"command functions may not have attributes; anything that doesn't fit the `fn name(args…) -> Message` contract belongs in a plain impl block\",\n\t\t\t\t));\n\t\t\t}\n\t\t}\n\t\tif !matches!(function.vis, Visibility::Inherited) {\n\t\t\treturn Err(Error::new(\n\t\t\t\tfunction.span(),\n\t\t\t\t\"command functions have no visibility modifier; the macro generates the public JS-facing stub\",\n\t\t\t));\n\t\t}\n\n\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","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/proc-macros/src/editor_commands.rs#L37-L73","documentation":"Command functions must be plain, monomorphic, synchronous, safe functions. The macro generates a matching enum variant and dispatch code that has no way to instantiate generics, await futures, or uphold unsafe contracts, so generic params, `async`, or `unsafe` signatures are compile errors.","triggerScenarios":"Declaring a command with lifetime or type generics (`fn foo<T>(x: T) -> Message`), `async fn foo(…) -> Message`, `unsafe fn foo(…)`, or a where clause inside the #[editor_commands] module. The check rejects non-empty `sig.generics.params`, `asyncness`, or `unsafety`.","commonSituations":"Porting an async data-loading function into the command module, or trying to share one generic implementation across numeric parameter types. Also hit when adding a const generic for array lengths.","solutions":["Remove `async`/`unsafe`/generic parameters and make the command a plain synchronous fn with concrete types.","If generics were for code reuse, keep a generic helper in a plain module and have the non-generic command delegate to it.","If async work is needed, make the command return the Message that schedules the work and run the async part in the editor's executor instead."],"exampleFix":"// before\n#[editor_commands]\nmod commands {\n\tasync fn load_document(path: String) -> Message { ... }\n\tfn set_value<T: Into<f64>>(v: T) -> Message { ... }\n}\n\n// after\n#[editor_commands]\nmod commands {\n\tfn load_document(path: String) -> Message { ... }\n\tfn set_value(v: f64) -> Message { ... }\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep commands synchronous, non-generic, and safe; do async work via returned Messages.","Place generic helpers in a plain module and let the concrete command delegate to them."],"tags":["rust","proc-macro","editor-commands","function-signature"],"backgroundTag":"proc-macro-input-validation","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}