GraphiteEditor/Graphite · error · syn::Error

command functions take no `self`; they are pure `args… -> Me

Error message

command functions take no `self`; they are pure `args… -> Message` translations

What it means

Command functions in an #[editor_commands] module are pure `args… -> Message` translations and must not take `self`, `&self`, `&mut self`, or any other receiver. The macro packs the parameters into a generated enum variant, and a receiver has no representation in that data model.

Source

Thrown at proc-macros/src/editor_commands.rs:52

	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());

		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);

View on GitHub (pinned to c507b35645)

Solutions

  1. Remove the receiver parameter; pass any needed state explicitly as a normal argument.
  2. If the function needs `&self` context, it is not a command — move it to a plain impl block on the context type.
  3. Check that the return type is still a Message variant translation after the move.

Example fix

// before
#[editor_commands]
mod commands {
	fn select_layer(&self, layer: u64) -> Message { ... }
}

// after
#[editor_commands]
mod commands {
	fn select_layer(layer: u64) -> Message { ... }
}
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Declaring a method-style function (`fn select(&self, id: u64) -> Message`, `&mut self`, `self: Box<Self>`, etc.) inside the module. The check is `signature.receiver()` returning Some.

Common situations: Refactoring methods off an editor-state struct into the commands module and forgetting to strip the receiver, or writing `fn foo(mut self)` while converting a builder-style API into commands.

Related errors


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/96804b3ea1e99af0. Report an issue: GitHub.