docs / guide / The Cajeta Guide

Chapters — 18. Annotations & synthesis
  1. Introduction
  2. 0. Introduction
  3. 1. Installation
  4. 2. Kick the tires
  5. 3. Your first project
  6. 4. Running your application
  7. 5. Debugging
  8. 6. Keywords
  9. 7. Comments
  10. 8. Native types
  11. 9. Type kinds
  12. 10. Allocation
  13. 11. Ownership & borrowing
  14. 12. Control flow
  15. 13. Strings & formatting
  16. 14. Templates & wildcards
  17. 15. Lambdas & captures
  18. 16. Operator overloading
  19. 17. Inheritance
  20. 18. Annotations & synthesis
  21. 19. DI & aspects
  22. 20. Error handling
  23. 21. Reflection
  24. 22. Differentiation
  25. 23. Code coverage
  26. 23. Profiling
  27. 24. Notebooks
  28. 25. Writing XPU Kernels

18 — Annotations & synthesis

An annotation is @Name before a declaration, optionally with arguments: @Order(2), @Component(name = "primary"), @Labels({"hot", "live"}). Any identifier is a valid annotation. One the compiler doesn’t recognize is inert — no code generated, no overhead — until an aspect pointcut (chapter 19) or reflection (chapter 21) reads it back.

Tour demo: AnnotationsDemo.

The synthesis family

A Lombok-style family of annotations makes the compiler write the boilerplate. Each synthesized method is an ordinary method: visible to reflection, debuggable, and skipped if you declare the same signature by hand — your version wins.

AnnotationSynthesizes
@Getter / @SetterAccessors, size()-style naming: name(), name(v)
@ToStringtoString() as ClassName(field=value, ...)
@AutoHashThe hash method
@NoArgsConstructor / @AllArgsConstructor / @RequiredArgsConstructorConstructors
@BuilderA fluent builder + static builder()
@WithPer-field copy-with methods (withX(...))
@NonNullNull checks at method entry / construction / return
@DataBundle: getters, setters, toString, hash, required-args ctor
@ValueImmutable bundle: getters, toString, hash, all-args ctor — no setters

@Cleanup (Lombok’s scoped close) is planned but not implemented; the drop chain already releases owned locals at scope exit (chapter 20).

@Builder

@Builder
@ToString
public class Book {
    public String title;
    @Builder.Default public int32 pages = 100;
}

@Builder.Default makes the field initializer the fallback when the caller skips that setter:

Book b #= Book.builder()
    .title("The Mythical Man-Month")
    .pages(322)
    .build();
Book d #= Book.builder().title("Untitled").build();
System.stdout.println("default pages = " + d.pages);

@Value

@Value
public class Rgb {
    int32 r;
    int32 g;
    int32 b;
}

@Value gives an all-args constructor and getters named after the fields:

Rgb c = stack Rgb(230, 126, 34);
System.stdout.println("r = " + c.r());
System.stdout.println(c.toString());

Declaring your own

The annotation keyword declares a new annotation type:

annotation Traced { }

Apply it like any other:

public class PaymentService {
    @Traced public void charge(int32 cents) {
        return;
    }
}

@Traced here does nothing by itself. It becomes load-bearing as an aspect pointcut (an @Before(Traced.class) advice fires on every @Traced method — next chapter) or through reflection (method.hasAnnotation(...), chapter 21). Annotations can carry arguments — scalars, strings, class literals, lists — and reflection reads the values back.

Full surface: the annotations specification.

Next: DI & aspects.

Source: docs/guide/18-annotations.md · 1 min · 317 words