{"record":{"id":"5eaf296fe511c854","repo":"GraphiteEditor/Graphite","slug":"command-functions-may-not-have-attributes-anythin","errorCode":null,"errorMessage":"command functions may not have attributes; anything that doesn't fit the `fn name(args…) -> Message` contract belongs in a plain impl block","messagePattern":"command functions may not have attributes; anything that doesn't fit the `fn name\\(args…\\) -> Message` contract belongs in a plain impl block","errorType":"validation","errorClass":"syn::Error","httpStatus":null,"severity":"error","filePath":"proc-macros/src/editor_commands.rs","lineNumber":37,"sourceCode":"\n\tlet mut imports: Vec<ItemUse> = Vec::new();\n\tlet mut functions: Vec<ItemFn> = Vec::new();\n\tfor item in items {\n\t\tmatch item {\n\t\t\tItem::Use(import) => imports.push(import),\n\t\t\tItem::Fn(function) => functions.push(function),\n\t\t\tother => return Err(Error::new(other.span(), \"only `use` imports and command functions may appear in an #[editor_commands] module\")),\n\t\t}\n\t}\n\n\tlet mut variants = TokenStream::new();\n\tlet mut stubs = TokenStream::new();\n\tlet mut arms = TokenStream::new();\n\n\tfor function in &functions {\n\t\tfor attr in &function.attrs {\n\t\t\tif !attr.path().is_ident(\"doc\") {\n\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\"));","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/proc-macros/src/editor_commands.rs#L19-L55","documentation":"Command functions inside an #[editor_commands] module may carry only doc comments; any other attribute is rejected. The macro re-emits each function as a generated JS-facing stub and synthesizes the enum variant itself, so extra attributes like #[cfg], #[inline], or derives have no defined place to go and would change semantics silently.","triggerScenarios":"Adding #[cfg(feature = \"…\")], #[allow(…)], #[inline], #[test], or any derive/helper attribute to a function inside the #[editor_commands] module. Only `#[doc …]` (doc comments) passes the filter loop.","commonSituations":"Feature-gating a single command, silencing a clippy lint on one function, or annotating a command for documentation tooling. Developers used to sprinkling #[allow(clippy::…)] everywhere hit this quickly.","solutions":["Delete the non-doc attribute from the command function.","If you need #[cfg] gating, apply it to the whole #[editor_commands] module or split the gated command into a separately gated module.","If you were annotating a helper rather than a command, move that helper into a plain impl block outside the macro module where attributes are unrestricted."],"exampleFix":"// before\n#[editor_commands]\nmod commands {\n\t#[cfg(feature = \"layers\")]\n\tfn select_layer(layer: u64) -> Message { ... }\n}\n\n// after\n#[cfg(feature = \"layers\")]\n#[editor_commands]\nmod commands {\n\tfn select_layer(layer: u64) -> Message { ... }\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Allow only doc comments on command functions; use module-level #[cfg] for feature gating.","Configure clippy/IDE snippets that insert attributes to skip the command module.","Review diffs touching the commands module for stray attribute additions."],"tags":["rust","proc-macro","editor-commands","attributes"],"backgroundTag":"proc-macro-input-validation","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}