Package groovy.transform.stc

# Annotation Interface ClassTag

@Documented @Incubating @Retention(RUNTIME) @Target(PARAMETER) public @interface ClassTag
Parameter annotation marking a `Class` parameter as compiler-supplied under static compilation. On the JVM, a method that needs the runtime type of a generic type parameter cannot recover it (erasure), so the API must take an explicit `Class<T>` token. That token is redundant with a type the compiler already knows — the receiver's type argument. `@ClassTag` removes the redundancy.

Under `@CompileStatic` / `@TypeChecked`, when a call omits the `Class` token argument(s), the type checker may select an overload whose parameters, beyond those matching the supplied arguments, are all `@ClassTag Class<X>` and synthesise the `X.class` argument(s) from the receiver's matching type argument(s), rewriting the call. Each `@ClassTag` parameter is filled at its declared position — anywhere in the parameter list, not just trailing — and the caller's supplied arguments map to the remaining (non-tag) parameters in order.

As an example, `asChecked` declares an explicit token:

     public static <T> List<T> asChecked(List<T> self, @ClassTag Class<T> type)

which lets statically-typed callers omit it:

     List<String> list = [].asChecked()      // compiler injects String.class

Selection is normally additive: the tagged overload is chosen only when the call as written matches no method (as above — there is no token-less `asChecked()`). When a token-less overload also matches, choosing the tagged one instead (preemption) changes the meaning of existing source, so three conditions gate it:

- Intent — the API author must declare the tagged overload preemptive with `preempt=true`; without it the overload is only ever selected additively. Groovy's own checked `withDefault` overloads declare it (their lenient sibling predates them and could silently corrupt a typed map's key set).
- Containment — a tagged overload may only preempt a token-less overload declared by the same class (or, for instance methods, the same class hierarchy). A library can upgrade callers of its own lenient API but can never capture calls owned by another library, so adding a jar to the compile classpath cannot re-route existing calls it does not already own.
- Consent — the consumer may veto all preemption globally via `CompilerConfiguration.setClassTagPreemptionDisabled(boolean)` (also seeded from the `groovy.classtag.preemption.disable` system property). Code compiled with the veto behaves exactly as it did before any library declared preemption; a finer-grained (per-method or per-call-site) opt-out may be added later if experience shows the need.

Boundaries:

- Static only. Dynamic Groovy ignores the annotation and binds the original overload; the shorter (token-less) spelling is static-only syntax. If no token can be synthesised there is no zero-token method to bind, so the call fails to resolve.
- The type variable must be statically known. `List<String>` works; `def`, raw `List`, and wildcard-only types do not — no token is synthesised.
- Receiver type arguments only. A type variable declared by the method itself (which shadows any like-named class variable) cannot be reified from the receiver; tagging one on an instance method compiled from source is a compile-time error. Static methods are exempt: in the extension-method authoring pattern the type variable is necessarily method-declared and connects to the receiver through the self parameter.
- Erased fidelity. The synthesised `Class<X>` reifies only the erased class: `List<String>` and `List<Integer>` are indistinguishable.
- Nothing-to-gain guard. When every token would erase to `Object` (e.g. a `List<Object>` receiver), no token is synthesised — a checked `Object` view could never reject anything — so an additive call fails to resolve and a preemptive upgrade is skipped (the lenient overload is kept).
- Escape hatch. Passing the `Class` explicitly always works, in every mode.

Since:
  6.0.0

- ## Optional Element Summary

  Optional Elements
  Modifier and Type
  Optional Element
  Description
  `boolean`
  `preempt`
  Declares that the overload carrying this parameter may preempt a matching token-less overload declared by the same class (or class hierarchy): an existing call that binds the token-less overload is transparently upgraded to this one under static compilation.
  `String`
  `value`
  Optionally names the type variable to reify, for when the parameter type cannot carry it (e.g.

- ## Element Details

  - ### value

    String value
    Optionally names the type variable to reify, for when the parameter type cannot carry it (e.g. a raw or wildcard `Class` token, or a method whose own type variables do not line up with the receiver's). When empty (the default), the type variable is read from the parameter type `Class<X>`.

    Note: `for` is a Java reserved word and cannot be an annotation member name, so the override is spelled `@ClassTag("K")` (or `@ClassTag(value = "K")`). For a method compiled from source in the same unit, the static type checker rejects a name that does not resolve to a type variable in scope, so typos fail at compile time. The check cannot reach an extension method supplied by an already-compiled library (it is never visited as source); such a typo silently disables injection rather than being reported.

    Returns:
      the name of the type variable to reify, or empty to read it from the parameter type
    Default:
      ""

  - ### preempt

    boolean preempt
    Declares that the overload carrying this parameter may preempt a matching token-less overload declared by the same class (or class hierarchy): an existing call that binds the token-less overload is transparently upgraded to this one under static compilation. Because that changes the meaning of existing source, it is off by default; without it the overload is only selected additively (when the call as written matches no method). Consumers retain a veto via `CompilerConfiguration.setClassTagPreemptionDisabled(boolean)`. By convention, set it on every `@ClassTag` parameter of the overload (any one suffices to declare the intent).
    Returns:
      whether this overload may preempt its same-owner token-less sibling
    Default:
      false