TypePHP Compile-Time Attributes: Supported Registry and Code Generation Explained

TypePHP supports 18 compile-time attributes—from #[Getter] and #[NotNull] to #[WasmExport]—that generate accessor methods, runtime validation checks, and WebAssembly exports during the compilation phase.

TypePHP (swoole/typephp) extends standard PHP with a compile-time attribute system processed before runtime. These attributes eliminate boilerplate by triggering specific code generation phases defined in CompileTimeAttributeRegistry.php. Understanding the registry and its associated lowering classes reveals exactly what PHP code the compiler emits for each annotation.

How the Compile-Time Attribute Registry Works

All supported attributes are declared centrally in src/Transform/CompileTimeAttributeRegistry.php. This registry maps each attribute name to its valid targets (classes, properties, methods, functions, parameters), the compilation phase when it executes, and any conflicts with other attributes. The compiler references this registry during three primary phases:

  • Preprocess – Influences stub generation and native-class handling before AST transformation.
  • Enter – Validates semantics (e.g., override correctness) as the compiler enters a node.
  • Class-leave – Generates methods after processing an entire class definition.
  • Function-leave – Inserts validation checks after processing function parameters.

Accessor and Utility Generation (Class-Leave Phase)

When the compiler finishes analyzing a class, it executes lowering classes that generate methods for properties annotated with specific attributes.

Getter, Setter, and With Methods

The #[Getter], #[Setter], and #[With] attributes target properties and trigger code generation in GetterLowering.php and PropertyMethodLowering.php.

  • #[Getter] emits a public getX() method returning the property value.
  • #[Setter] emits a public setX($value) void method assigning the property.
  • #[With] emits a fluent withX($value) method returning a clone with the new value.
class User
{
    #[Getter]
    #[Setter]
    private string $name;
}

Generated output:

public function getName(): string {
    return $this->name;
}
public function setName(string $value): void {
    $this->name = $value;
}

Printer and Arrayable Implementations

Class-level attributes #[Printer] and #[Arrayable] generate utility methods for string representation and array conversion.

  • #[Printer] (processed by PrinterLowering.php) creates a __toString() implementation that formats property values into a readable string.
  • #[Arrayable] (processed by ArrayableLowering.php) adds toArray() and related helpers for serializing the object.
#[Printer]
class Point {
    public int $x;
    public int $y;
}

Generated output:

public function __toString(): string {
    return sprintf('Point(x=%d, y=%d)', $this->x, $this->y);
}

Constructor Property Promotion

The #[Constructor] attribute (processed by ConstructorLowering.php) targets declared properties to generate a constructor that automatically assigns them.

class Config
{
    #[Constructor]
    private string $env;
}

Generated output:

public function __construct(string $env) {
    $this->env = $env;
}

Parameter Validation and Safety Checks (Function-Leave Phase)

Attributes applied to function or method parameters trigger validation code insertion after the function signature is parsed.

NotNull, NotEmpty, and Validate Constraints

The #[NotNull], #[NotEmpty], and #[Validate] attributes target parameters and are processed by ParameterValidationLowering.php.

  • #[NotNull] inserts a runtime check throwing CompileTimeAttributeError if the argument is null.
  • #[NotEmpty] validates that strings or arrays are non-empty.
  • #[Validate] accepts custom rules (e.g., email format) and throws on validation failure.
function createUser(
    #[NotNull] $id,
    #[Validate(['type' => 'email'])] $email
): void {}

Generated runtime checks:

if ($id === null) {
    throw new CompileTimeAttributeError('NotNull violation');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    throw new CompileTimeAttributeError('Validate violation');
}

Compilation Control and Optimization (Preprocess and Enter Phases)

Certain attributes influence the compilation pipeline itself rather than generating runtime PHP code.

Native, NoExport, WasmExport, and MethodsFor

Processed during the preprocess phase (primarily in Preprocessor.php):

  • #[Native] marks a class as a native implementation, preventing PHP stub emission while signaling the compiler to handle it as an internal type.
  • #[NoExport] prevents the annotated symbol from appearing in generated *.stub.php library files, keeping implementation details private.
  • #[WasmExport] registers the function in the WebAssembly export table when targeting WASM backends.
  • #[MethodsFor] generates "method-for" implementations used by the compiler to create automatic method families.
  • #[ArrayDef] generates array-definition metadata for properties, used by the runtime for type-checking.
#[NoExport]
function internalHelper() { /* ... */ }

#[WasmExport]
function add(int $a, int $b): int { return $a + $b; }

Override, MustUse, Immutable, Hot, and Cold

Processed during the enter phase (primarily in Visitor.php):

  • #[Override] validates that a method or property actually overrides a parent member, emitting an error if no matching parent exists.
  • #[MustUse] marks return values as required, triggering compiler warnings if the result is discarded.
  • #[Immutable] enforces that the annotated method, property hook, or parameter cannot mutate after assignment.
  • #[Hot] and #[Cold] provide optimization hints to the compiler, indicating frequently or rarely executed code paths.
#[Hot]
public function calculate(): int { /* ... */ }

#[Override]
public function toString(): string { /* ... */ }

Source Code Architecture and Lowering Classes

Each attribute category delegates to specific lowering implementations that register generated methods via CompileTimeAttributeDiagnostic::markGenerated():

Attribute Lowering Class (Source Path)
#[Getter] src/Transform/GetterLowering.php
#[Setter] / #[With] src/Transform/PropertyMethodLowering.php
#[Printer] src/Transform/PrinterLowering.php
#[Arrayable] src/Transform/ArrayableLowering.php
#[NotNull] / #[NotEmpty] / #[Validate] src/Transform/ParameterValidationLowering.php
#[Override] / #[MustUse] / #[Hot] / #[Cold] / #[Immutable] src/Transform/Visitor.php
#[Native] / #[NoExport] / #[WasmExport] / #[MethodsFor] / #[ArrayDef] src/Preprocessor.php
#[Constructor] src/Transform/ConstructorLowering.php

Summary

  • TypePHP compile-time attributes are defined in CompileTimeAttributeRegistry.php and processed across four phases: Preprocess, Enter, Class-leave, and Function-leave.
  • Accessor attributes (#[Getter], #[Setter], #[With]) generate boilerplate methods during the Class-leave phase via dedicated lowering classes.
  • Validation attributes (#[NotNull], #[NotEmpty], #[Validate]) inject runtime checks at the start of functions during the Function-leave phase.
  • Utility attributes (#[Printer], #[Arrayable], #[Constructor]) auto-generate __toString(), toArray(), and constructor implementations.
  • Control attributes (#[NoExport], #[WasmExport], #[Native]) influence stub generation and export tables without emitting PHP runtime code.
  • Semantic attributes (#[Override], #[MustUse], #[Immutable], #[Hot], #[Cold]) enforce correctness and optimization hints during the Enter phase.

Frequently Asked Questions

What is the difference between the Preprocess and Class-leave phases in TypePHP?

The Preprocess phase executes before the abstract syntax tree (AST) is fully traversed, handling attributes like #[NoExport] and #[WasmExport] that determine whether code appears in stub files or export tables. The Class-leave phase runs after the compiler finishes analyzing an entire class, allowing attributes like #[Getter] to generate methods based on the complete property set.

How does TypePHP handle validation failures for #[NotNull] or #[Validate]?

When ParameterValidationLowering.php processes these attributes during the Function-leave phase, it injects conditional checks at the top of the function body. If a check fails, the generated code throws a CompileTimeAttributeError with a descriptive message indicating which constraint was violated.

Can multiple compile-time attributes be applied to the same property or method?

Yes, the registry allows stacking compatible attributes. For example, a property can simultaneously use #[Getter], #[Setter], and #[NotNull] (the latter affecting constructor parameters). However, the registry defines conflicts—for instance, certain optimization hints or mutually exclusive export controls cannot be combined.

Where does TypePHP store the mapping between attribute names and their generation logic?

The authoritative mapping lives in src/Transform/CompileTimeAttributeRegistry.php, which defines each attribute's target eligibility (class, property, method, etc.), processing phase, and argument parser. The actual code generation logic resides in the respective lowering classes referenced by the compiler's transformation pipeline.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →