denoland/deno · error · syn::Error

Expected a function or impl block for #[op2]

Error message

Expected a function or impl block for #[op2]

What it means

The `#[op2]` macro entry point (libs/ops/op2/mod.rs) tries to parse the annotated item first as `syn::ItemFn`, then as `syn::ItemImpl`. If both parses fail it combines the two underlying parse errors with this message, so the real cause is usually visible in the combined error chain below it.

Source

Thrown at libs/ops/op2/mod.rs:179

    syn::Error::new(span, msg)
  }
}

pub type V8MappingError = &'static str;

/// Generate the op2 macro expansion.
pub(crate) fn op2(
  attr: TokenStream,
  item: TokenStream,
) -> Result<TokenStream, Op2Error> {
  let func = match parse2::<ItemFn>(item.clone()) {
    Ok(func) => func,
    Err(fn_err) => match parse2::<syn::ItemImpl>(item) {
      Ok(impl_block) => {
        return object_wrap::generate_impl_ops(attr, impl_block);
      }
      Err(impl_err) => {
        let mut err = syn::Error::new(
          fn_err.span(),
          "Expected a function or impl block for #[op2]",
        );
        err.combine(fn_err);
        err.combine(impl_err);
        return Err(err.into());
      }
    },
  };

  let span = attr.span();

  let metas =
    Punctuated::<config::CustomMeta, syn::Token![,]>::parse_terminated
      .parse2(attr)?
      .into_iter()
      .collect::<Vec<_>>();

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Move `#[op2]` so it annotates a plain `fn` (for a standalone op) or an `impl` block (for wrapped-object methods).
  2. If it is already on a function, read the combined parse errors (fn_err/impl_err are attached) — there is usually a syntax error in the signature or body that breaks parsing.
  3. Remove the attribute from non-op items such as structs, consts, or traits.

Example fix

// before
#[op2]
struct OpStateHolder { /* ... */ }

// after
#[op2(fast)]
fn op_get_state() -> u32 { /* ... */ }
Defensive patterns

Strategy: validation

Validate before calling

// CI: `cargo check` catches this instantly. Structural guard if you generate code:
// only emit `#[op2]` onto `fn` items or `impl` blocks, never onto other items.

Prevention

When it happens

Trigger: Attaching `#[op2]` to anything that is not a free function or an `impl` block: structs, enums, traits, `const`s, `type` aliases, `trait` default methods, or a function with syntax errors that also breaks ItemFn parsing.

Common situations: Pasting the attribute above the wrong declaration while wiring up new ops; a syntax error inside the function body that makes `parse2::<ItemFn>` fail even though the item looks like a function.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/d0406e16acf06c15. Report an issue: GitHub.