vercel/next.js · critical · syn::Error

evict = "last" is only valid with serialization = "skip"

Error message

evict = "last" is only valid with serialization = "skip"

What it means

Compile-time error from the #[turbo_tasks::value] proc-macro. evict = "last" marks a cell as expensive to re-derive so the eviction policy prefers evicting cheaper cells first — but re-derivation is only the recovery path for serialization = "skip". Persistable cells restore from disk and HashOnly cells short-circuit on an unchanged hash, so evict = "last" with those modes is meaningless and the macro rejects it. (evict = "never" is allowed with any serialization mode.)

Source

Thrown at turbopack/crates/turbo-tasks-macros/src/value_macro.rs:311

            proc_macro2::Span::call_site(),
            "hash = \"manual\" only makes sense with serialization = \"hash\"",
        )
        .to_compile_error()
        .into();
    }

    // `evict = "last"` only makes sense for `serialization = "skip"`: it
    // says "re-deriving this cell is expensive", and re-derivation is the
    // recovery path only for skip mode. Persistable cells restore from disk
    // (predictable cost), HashOnly cells short-circuit on unchanged hash.
    //
    // `evict = "never"` is allowed with any serialization mode — a value
    // type can be persistable AND hold session-scoped state that must not
    // leave memory (e.g. `DiskFileSystem` carrying file watchers).
    if matches!(evict_mode, EvictMode::Last)
        && !matches!(serialization_mode, SerializationMode::Skip)
    {
        return syn::Error::new(
            proc_macro2::Span::call_site(),
            "evict = \"last\" is only valid with serialization = \"skip\"",
        )
        .to_compile_error()
        .into();
    }

    let mut struct_attributes = vec![quote! {
        #[derive(
            turbo_tasks::ShrinkToFit,
            turbo_tasks::trace::TraceRawVcs,
            turbo_tasks::NonLocalValue
        )]
        #[shrink_to_fit(crate = "turbo_tasks::macro_helpers::shrink_to_fit")]
    }];

    let mut inner_type = None;
    if transparent {

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Set serialization = "skip" to make evict = "last" meaningful: #[turbo_tasks::value(serialization = "skip", evict = "last")].
  2. Or remove evict = "last" if the value is persistable and eviction ordering is not needed.
  3. If you need non-eviction for a persistable type, use evict = "never" instead, which is valid with any serialization mode.

Example fix

// before
#[turbo_tasks::value(serialization = "auto", evict = "last")]
struct HeavyCache { ... }

// after
#[turbo_tasks::value(serialization = "skip", evict = "last")]
struct HeavyCache { ... }
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Annotating a value type with evict = "last" and a serialization mode other than "skip", e.g. #[turbo_tasks::value(serialization = "auto", evict = "last")] or serialization = "hash".

Common situations: A contributor marking a heavy-to-compute value as evict = "last" without realizing the hint only applies to skip-serialization types; copy-pasting attributes across types with different serialization modes.

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/9f86a33702b38e14. Report an issue: GitHub.