This is an automated email from the ASF dual-hosted git repository.

paulk-asert pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new 61dcbd6  draft retrospective GEP for traits
61dcbd6 is described below

commit 61dcbd64cc889254f41e898638b33edaec0f30bf
Author: Paul King <[email protected]>
AuthorDate: Wed May 6 12:27:28 2026 +1000

    draft retrospective GEP for traits
---
 site/src/site/wiki/GEP-22.adoc | 425 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 425 insertions(+)

diff --git a/site/src/site/wiki/GEP-22.adoc b/site/src/site/wiki/GEP-22.adoc
new file mode 100644
index 0000000..4b4a8c1
--- /dev/null
+++ b/site/src/site/wiki/GEP-22.adoc
@@ -0,0 +1,425 @@
+= GEP-22: Traits
+
+:icons: font
+
+.Metadata
+****
+[horizontal,options="compact"]
+*Number*:: GEP-22
+*Title*:: Traits
+*Version*:: 1
+*Type*:: Feature
+*Status*:: Final
+*Comment*:: Delivered in Groovy 2.3 and refined in later versions; 
static-member support remains incubating
+*Leader*:: Cédric Champeau
+*Created*:: 2026-05-06
+*Last modification*:: 2026-05-06
+****
+
+== Abstract: Traits
+
+Traits are a structural construct of the Groovy language that allow:
+
+* composition of behaviour
+* runtime implementation of interfaces
+* behaviour overriding
+* compatibility with static type checking and static compilation
+
+Conceptually a trait is an *interface* that may carry both *default 
implementations*
+and *state*. A class declares its participation in a trait via the `implements`
+clause exactly as it would for any other interface. Traits compose into the
+implementing class at compile time (or, optionally, at runtime via coercion)
+without requiring multiple inheritance of classes.
+
+This GEP specifies the language semantics of traits, captures the bytecode
+compilation model, and records the evolution of the feature across Groovy
+releases. Worked examples and tutorial-style code samples live in the
+language specification (`_traits.adoc`); this document is intentionally
+terse and prescriptive.
+
+=== Motivation
+
+Object-oriented codebases routinely need to reuse behaviour across class
+hierarchies that cannot share a common ancestor. The available pre-existing
+mechanisms each carry trade-offs:
+
+* *Single inheritance* forces all reuse through a single line, leading to
+deep hierarchies, fragile base-class problems, and inability to mix
+unrelated capabilities.
+* *Java 8 default methods* attach behaviour to interfaces but are stateless
+by design, do not support stackable composition through `super`, and
+their resolution rules are designed around binary compatibility rather
+than composition.
+* *Runtime mixins* (Groovy's pre-2.3 `@Mixin` annotation, since deprecated)
+weave behaviour at runtime but the resulting object is not an
+`instanceof` the mixed-in type, defeating type-based dispatch and
+compile-time checks.
+* *Delegation via `@Delegate`* expresses _has-a_ relationships well but is
+verbose for genuine _is-a_ composition and does not support overriding
+delegated behaviour transparently.
+
+Traits address these gaps. Drawing on the work of Schärli, Ducasse, Black
+and Nierstrasz (ECOOP 2003), and on Scala's analogous construct, a trait
+combines the contract role of an interface with the reusable-implementation
+role of a class while imposing deterministic composition rules. As Cédric
+Champeau put it when introducing them: traits *extend the benefit of
+interfaces to concrete classes* without producing inheritance pyramids,
+allowing new APIs to be layered onto existing classes without modification.
+
+== Specification
+
+=== Declaration
+
+A trait is declared with the `trait` keyword:
+
+[source,groovy]
+----
+trait FlyingAbility {
+    String fly() { "I'm flying!" }
+}
+----
+
+The annotation `@groovy.transform.Trait` is an exact synonym and may be
+applied to any class declaration that would otherwise satisfy the trait
+shape; the AST transform produces equivalent output. The `trait` keyword
+is preferred in source.
+
+A trait may declare any of: methods (public or private, abstract or
+concrete, static or instance), properties, fields (public or private,
+instance or static), and an `implements` clause naming interfaces and/or
+super-traits. A trait may extend at most one super-trait via `extends`,
+or any number of super-traits by listing them in `implements`.
+
+Constructors are not permitted on traits.
+
+=== Methods
+
+* Public and private methods are supported. `protected` and package-private
+are *not* supported.
+* Abstract methods are permitted; concrete classes that implement the
+trait must provide an implementation unless they too are abstract.
+* Private methods do not appear on the generated trait interface and are
+not visible to other classes.
+* The `final` modifier records the intended modifier of the woven method
+in the implementing class. Mixing `final` and non-`final` declarations of
+the same signature across multiple inherited traits is permitted; normal
+trait method-selection rules apply and the resulting method takes the
+modifier of the selected source.
+
+=== Fields
+
+Fields declared in a trait are not stored on the generated trait
+interface (interfaces cannot hold instance state). Instead they are stored
+on a synthetic _field helper_ class and woven into the implementing class
+under name-mangled identifiers:
+
+* For a field `bar` of type `T` declared in trait `my.pkg.Foo`, the
+implementing class gains a field named `my_pkg_Foo__bar` of type `T`.
+* The mangling rule is: replace each `.` in the package name with `_`,
+append `_<TraitSimpleName>__<fieldName>`. The double underscore separates
+the trait identity from the field identity.
+* Private fields follow the same scheme but are also marked private; the
+mangling guarantees no collisions when a class implements multiple
+traits that declare fields of the same name (the diamond problem for
+state).
+
+Use of public fields in traits is discouraged in favour of properties.
+
+=== Properties
+
+A property declared in a trait yields the usual auto-generated accessor
+pair (`getX`/`setX`, or `isX` for `boolean`) on the generated trait
+interface, and a backing field on the implementing class woven via the
+field-helper mechanism. Property semantics are otherwise identical to
+those of a regular Groovy class.
+
+=== `this`, `super`, and stackable traits
+
+Inside a trait method:
+
+. `this` refers to the *implementing instance*, never to a trait-level
+artefact. A trait should be reasoned about as if it were a superclass of
+the implementing class, despite the fact that no such class exists in
+the runtime hierarchy.
+. An unqualified `super.m(...)` call delegates to the *next trait in the
+implementation chain* (in the order produced by the `implements` clause
+of the implementing class, walked right-to-left). When the chain is
+exhausted, `super` resolves to the actual superclass of the implementing
+class.
+. A qualified `T.super.m(...)` call resolves to trait `T`'s implementation
+of `m`, regardless of position in the chain. This is the explicit
+override form.
+
+This rule set yields *stackable traits*: behaviours that delegate to the
+next trait via unqualified `super` can be composed in different orders
+to produce different overall behaviours, without those traits needing to
+know about each other.
+
+=== Multiple inheritance and conflict resolution
+
+When a class implements two or more traits that supply conflicting
+implementations of a method with the same signature, the *last trait
+listed in the `implements` clause wins*. This is the deterministic
+default. The implementing class may override the resolution:
+
+* by providing its own implementation of the method, or
+* by calling `T.super.m(...)` for the desired trait `T`.
+
+The same rule applies to property accessors and field-helper-backed
+state. Diamond conflicts on field state cannot arise: each trait's
+fields live under their own mangled names.
+
+=== SAM coercion
+
+A trait that declares exactly one abstract method is a *Single Abstract
+Method* type for the purposes of closure coercion. A `Closure` may be
+assigned or coerced to such a trait, in which case the closure body
+becomes the implementation of the abstract method. SAM coercion of
+traits composes with the rules in GEP-12.
+
+=== Runtime application
+
+Traits may be applied to an existing object at runtime:
+
+[source,groovy]
+----
+def proxy = subject as SomeTrait
+def proxy = subject.withTraits(TraitA, TraitB)
+----
+
+Both forms produce a *new proxy instance* — never the original object —
+that:
+
+* implements `SomeTrait` (or `TraitA` and `TraitB`),
+* implements every interface that the original object implemented, and
+* delegates to the original object for behaviour not supplied by the
+applied trait(s).
+
+A trait method always takes precedence over the corresponding method on
+the proxied object when both exist. The proxy is *not* an instance of
+the original class; consumers that rely on `instanceof` against the
+original concrete class must continue to use the underlying object.
+
+=== Static members
+
+Static methods, properties and fields in a trait are supported with
+the following constraints (collectively flagged *incubating* /
+experimental):
+
+* Each implementing class receives its *own copy* of static members.
+Static state is not shared across implementing classes — a static field
+declared in a trait is conceptually a template, instantiated per
+implementer.
+* Static members are *not* exposed on the generated trait interface.
+Calls of the form `Trait.staticMethod(...)` are not statically valid.
+* Static members are accessed dynamically and are not subject to static
+type checking; traits with static methods cannot be `@CompileStatic`.
+* Mixing static and instance methods of the same signature across
+multiple traits is undefined behaviour and should be avoided. If
+selection chooses a static variant where an instance variant is
+required, a compilation error is raised; conversely, an instance
+selection silently shadows the static variant.
+
+=== `@SelfType`
+
+`@groovy.transform.SelfType` declares a list of types that any class
+implementing the annotated trait must extend or implement. The compiler
+verifies the constraint at compile time and reports a clear error if it
+is violated. Self types make traits that depend on the surrounding
+class's API safe to type-check and statically compile, without resorting
+to explicit `(this as SomeBaseClass)` casts inside trait method bodies.
+
+=== Interaction with `@Sealed`
+
+Traits may be sealed (see GEP-13). `@Sealed` and `@SelfType` are
+orthogonal:
+
+* `@SelfType` constrains *what an implementing class must already be*
+(its supertype shape).
+* `@Sealed` constrains *which specific classes are allowed to implement*
+the trait at all.
+
+A trait may carry both annotations. For the degenerate single-permitted-
+implementer case, `@SelfType(Foo)` is preferred over
+`@Sealed(permittedSubclasses = ['Foo'])` for clarity.
+
+=== Bytecode and stub model
+
+A trait `pkg.T` produces, after compilation, the following artefacts:
+
+. *An interface* `pkg.T` containing the abstract and public-method
+signatures, plus accessor signatures for any properties. This is the
+type that participates in `instanceof`, generics, and Java interop.
+The interface contains *no* default methods — trait method bodies are
+not bytecode-default-method bodies, even on JDK 8+.
+. *A trait helper class* named `pkg.T$Trait$Helper` (constant
+`Traits.TRAIT_HELPER`). Each non-abstract trait method `m(args)` is
+emitted as a `static` method on the helper, taking the receiver as an
+explicit first parameter (and threading any captured trait state
+through field-helper accessors). Bridge methods are woven into each
+implementing class to forward instance calls to the helper.
+. *A field helper class* named `pkg.T$Trait$FieldHelper` (constant
+`Traits.FIELD_HELPER`). It exposes get/set accessors for trait
+instance fields, parameterised by the receiver. The implementing class
+is given mangled fields and small bridge accessors that delegate to
+this helper.
+. *A static field helper class* named `pkg.T$Trait$StaticFieldHelper`
+(constant `Traits.STATIC_FIELD_HELPER`) when the trait declares static
+fields, providing per-implementer storage for static state.
+
+This four-class model is what makes traits visible to Java callers as
+plain interfaces (with no default methods to surprise Java consumers),
+while still supplying state, stackable `super`, and conflict resolution
+that a Java-default-method approach cannot.
+
+=== Limitations
+
+* *AST transform compatibility* is best-effort. Some transforms
+(`@CompileStatic`, `@TypeChecked`, logging transforms) apply cleanly to
+traits; others apply only to the implementing class; others are
+unsupported. New transforms should be evaluated case by case.
+* *Prefix and postfix operators* (`++`, `--`) on a trait field are
+rejected at compile time. The workaround is `+=` / `-=`. The reason is
+that the field-helper indirection cannot represent the read-modify-write
+sequence atomically.
+* *Constructors* on traits are not supported. Initialisation logic
+should be expressed via abstract methods supplied by the implementing
+class, or via property defaults.
+* *Inheritance-of-state gotcha*: trait method bodies that read a trait
+field directly read the *trait's own* field, not any same-named property
+declared by the implementing class. To pick up an overriding value,
+trait code should access state through accessors (`getX()`), not by
+direct field reference.
+
+== Cross-version evolution
+
+[cols="1,1,5",options="header"]
+|===
+| Version | Year | Change
+
+| 2.3.0 | 2014
+| Initial release of traits. `trait` keyword and `@Trait` annotation
+introduced; public/private methods; abstract methods; instance fields
+with name mangling; properties; multiple inheritance with last-wins
+conflict resolution; explicit `T.super.m()` resolution; stackable
+unqualified `super`; runtime application via `as` and
+`Object.withTraits(...)`; SAM coercion of single-abstract-method
+traits.
+
+| 2.4.0 | 2015
+| `@SelfType` annotation introduced (GROOVY-7134), enabling statically
+checked traits whose method bodies depend on members supplied by the
+implementing class's superclass hierarchy.
+
+| 3.0   | 2020
+| Default methods declared in `interface` types using the Java 8 syntax
+were accepted by the parser and implemented under the hood by
+delegating to the trait machinery (incubating). No spec-level change to
+trait semantics.
+
+| 4.0   | 2022
+| Sealed traits supported via `@Sealed` / the `sealed` keyword and the
+`permits` clause (see GEP-13). Distinction between `@Sealed` and
+`@SelfType` formalised in the spec. Java stub generation for static
+trait properties hardened.
+
+| 5.0   | 2024
+| Default, private and static methods in `interface` types are now
+implemented as *native* JVM bytecode rather than via the trait
+machinery (GROOVY-8299). This decouples the interface-default-method
+feature from traits and improves Java interop. Multiple bug fixes
+landed for trait + `@TupleConstructor` default-value handling
+(GROOVY-8219, GROOVY-8788) and for trait static-field generation under
+static compilation (GROOVY-11817, GROOVY-11907).
+
+| 6.0+  | TBD
+| No spec-level changes to traits planned at the time of writing. This
+GEP is the canonical location to record any future change.
+|===
+
+== Non-goals and potential future extensions
+
+The following items are deliberately out of scope for this GEP and for
+the current implementation. Any of them would warrant a follow-up
+revision of this document or a successor GEP.
+
+* Promoting static-member support out of incubating status. The current
+per-implementer template semantics is a deliberate JVM-shaped
+compromise; a "true" shared-static-member model would require either
+breaking that semantics or moving static state into a separate runtime
+holder.
+* Emitting trait methods as native interface default methods on JDK 8+.
+The four-class helper model deliberately avoids this so that Java
+callers see traits as plain interfaces. Switching would be a
+Java-interop change.
+* First-class compatibility guarantees for arbitrary AST transforms on
+traits.
+* Constructor support on traits.
+* `protected` or package-private trait methods.
+
+== References and useful links
+
+* Schärli, Ducasse, Nierstrasz, Black. _Traits: Composable Units of
+Behaviour._ ECOOP 2003. — the academic foundation.
+* https://docs.scala-lang.org/tour/traits.html[Scala traits] — the
+closest sibling construct in another JVM language.
+* https://openjdk.org/jeps/126[JEP 126: Default Methods] — Java's
+stateless analogue, contrasted in the Motivation section.
+* https://groovy.apache.org/wiki/GEP-12.html[GEP-12: SAM coercion] —
+underlies trait SAM coercion.
+* https://groovy.apache.org/wiki/GEP-13.html[GEP-13: Sealed classes] —
+applies to traits (sealed traits).
+* https://speakerdeck.com/melix/rethinking-api-design-with-traits[Cédric
+Champeau, _Rethinking API design with traits_] — original design
+motivation and worked examples.
+* The language specification chapter on traits in the Groovy
+documentation contains worked tutorial examples that complement this
+spec-only document.
+
+=== Reference implementation
+
+Package: `org.codehaus.groovy.transform.trait`
+
+* `TraitASTTransformation` — entry point invoked for every trait
+declaration; produces the interface, helper, field-helper and (where
+applicable) static-field-helper artefacts.
+* `TraitComposer` — weaves trait methods, accessors and bridge methods
+into each implementing class.
+* `TraitReceiverTransformer` — rewrites references inside trait method
+bodies so that `this`, `super` and field accesses resolve to the
+implementing-instance receiver and to helper-routed state.
+* `Traits` — utility class holding helper-class naming constants
+(`$Trait$Helper`, `$Trait$FieldHelper`, `$Trait$StaticFieldHelper`),
+trait detection predicates, and self-type collection logic.
+* `TraitTypeCheckingExtension` — integrates traits with the static type
+checker (GEP-8).
+
+Public API:
+
+* `groovy.transform.Trait` (since 2.3.0)
+* `groovy.transform.SelfType` (since 2.4.0)
+
+=== Representative JIRA issues
+
+* https://issues.apache.org/jira/browse/GROOVY-7134[GROOVY-7134]:
+introduce `@SelfType` for statically checked traits.
+* https://issues.apache.org/jira/browse/GROOVY-8233[GROOVY-8233]: Java
+stub generation for static properties of traits.
+* https://issues.apache.org/jira/browse/GROOVY-8299[GROOVY-8299]: native
+default/private/static methods in interfaces (decoupling from trait
+machinery in 5.0).
+* https://issues.apache.org/jira/browse/GROOVY-8219[GROOVY-8219],
+https://issues.apache.org/jira/browse/GROOVY-8788[GROOVY-8788]:
+`@TupleConstructor` interaction with traits and default values.
+* https://issues.apache.org/jira/browse/GROOVY-8951[GROOVY-8951]: trait
+getter conflicts with generated getter (pre-compiled case).
+* https://issues.apache.org/jira/browse/GROOVY-11674[GROOVY-11674]:
+deterministic ordering of trait methods for reproducible builds.
+* https://issues.apache.org/jira/browse/GROOVY-11907[GROOVY-11907]:
+trait field reference transform restructure.
+
+== Update history
+
+1 (2026-05-06) Initial draft. Retrospective specification capturing
+trait semantics as shipped in 2.3.0 and refined through 5.0, the
+four-class bytecode model, and the cross-version evolution table.

Reply via email to