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 publicgetX()method returning the property value.#[Setter]emits a publicsetX($value)void method assigning the property.#[With]emits a fluentwithX($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 byPrinterLowering.php) creates a__toString()implementation that formats property values into a readable string.#[Arrayable](processed byArrayableLowering.php) addstoArray()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 throwingCompileTimeAttributeErrorif the argument isnull.#[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.phplibrary 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.phpand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →