Package groovy.transform

# Annotation Interface PackedClosures

@Incubating @Retention(SOURCE) @Target({TYPE,METHOD}) public @interface PackedClosures
Opt-in for compact closure compilation: within the annotated class or method, an eligible closure literal's body is hoisted into a synthetic method on the enclosing class and the literal is replaced by a shared `PackedClosure` adapter, instead of generating one inner class per closure. The value stays a real `groovy.lang.Closure`, so `curry`, `memoize`, `trampoline` and iteration keep working; captured values are threaded by value (read-only) or via a shared `groovy.lang.Reference` (when written), so a packed closure behaves identically to the class-based form.

Packing is best-effort: a closure that cannot be packed is compiled exactly as today (a generated closure class), so the annotation is always safe to add. A closure is kept as a class when it:

- references `owner`, `delegate`, `thisObject`, `resolveStrategy`, `directive`, `metaClass` or `super` — it needs a real `Closure`;
- has default parameter values, or contains an anonymous inner class;
- is nested inside another closure, or visibly escapes its method (returned, stored to a field/property/index, initialising a field, appended, or placed in a collection literal);
- is visibly serialization-bound (cast or coerced to a `Serializable` type, passed directly to a `writeObject` call, or held in a local that is) — it keeps its class so serialization works;
- is written where the adapter cannot stand in for a generated class: as an argument to a `this(...)`/`super(...)` constructor call, inside a trait, or cast to an intersection type;
- under dynamic compilation, resolves any free name — an implicit-this call, a bare field/property-bound or dynamic variable, or an explicit `this`-property — that the delegate chain or the MOP could intercept;
- under `@CompileStatic`, resolves a name against a delegate (e.g. via `@DelegatesTo` or `with`), is left to runtime resolution by a type-checking extension (mixed-mode dynamic islands), or touches `this`-properties of a `Map`-implementing owner — packing is otherwise proven sound from the type checker's resolution.

Under dynamic compilation the annotation is a trust assertion the compiler cannot verify; the `PackedClosure` adapter then fails fast if a delegate, or a delegate-consulting `resolveStrategy`, is later set on a packed closure. Automatic packing of provably-safe `@CompileStatic` closures — without this annotation — is available behind the `groovy.target.closure.pack` flag. Use `mode()` to have declines reported (or to opt a scope out); on the un-annotated flag path, `groovy.target.closure.pack.report=true` reports every decline with its reason as a compiler warning — the operational way to see where the packability boundary falls in a codebase.

Because every packed closure is an instance of a small fixed-arity adapter family (`PackedClosure$Fixed0..4`, `$FixedIt`, `$FixedN`) rather than its own class, three differences from the class-based form remain that cannot, in general, be detected at compile time (the visibly serialization-bound cases above are the detectable exception):

- Serialization: a packed closure is not serializable — attempting it fails fast at runtime with a message naming the closure and this opt-out (note `dehydrate()` cannot make it serializable — the dispatch state remains — although a dehydrated packed closure stays callable), so scopes that serialize their closures should not be packed;
- Class identity: `closure.getClass()` distinguishes arity (enough for class-level introspection such as SAM-overload selection) but not individual literals, so code keyed on per-closure generated class names or types will not find them;
- Class-level metaclass changes: modifying a family member's metaclass affects every packed closure of that arity, where a per-closure-class change was scoped to one literal. Per-instance `setMetaClass` is fully honoured — a packed closure whose metaclass has been replaced or wrapped routes all dispatch through it, exactly as a generated closure class does.

See Also:
  - `PackedClosures.PackMode`

- ## Nested Class Summary

  Nested Classes
  Modifier and Type
  Class
  Description
  `static enum`
  `PackedClosures.PackMode`
  The packing behaviour of a `PackedClosures` scope.

- ## Optional Element Summary

  Optional Elements
  Modifier and Type
  Optional Element
  Description
  `PackedClosures.PackMode`
  `mode`
  The packing behaviour of this scope: `PackedClosures.PackMode.LENIENT` (pack, silent on declines, the default), `PackedClosures.PackMode.WARN` (pack, a compiler warning per decline with the reason), `PackedClosures.PackMode.STRICT` (pack, a compiler error on any decline), or `PackedClosures.PackMode.DISABLED` (do not pack).

- ## Element Details

  - ### mode

    PackedClosures.PackMode mode
    The packing behaviour of this scope: `PackedClosures.PackMode.LENIENT` (pack, silent on declines, the default), `PackedClosures.PackMode.WARN` (pack, a compiler warning per decline with the reason), `PackedClosures.PackMode.STRICT` (pack, a compiler error on any decline), or `PackedClosures.PackMode.DISABLED` (do not pack). The most-specific declaration wins, so a `DISABLED` method opts out of a packed class (and `DISABLED` also overrides the automatic `groovy.target.closure.pack` flag); the WARN/STRICT diagnostics apply only to the annotated scope, never to the flag.
    Default:
      LENIENT