# AOT/PHP Incompatibility List This document lists only the key areas in which the current AOT compiler is incompatible with or more restrictive than standard PHP. ## Program structure - Executable statements are not allowed at global scope; only static constructs such as declarations, `use`, `declare`, and constant definitions are allowed. - Function declarations are not allowed inside functions or methods. - Named class declarations are not allowed inside functions or methods. - Binary mode must define a global `main()`. - `main()` may either take no parameters or have the signature `(int $argc, array $argv)`. - `main()` must return `void`. ## Declarations and types - Variable variables `$$var` are not supported. - PHP 8.5 `#[NoDiscard]` is not supported yet. - The PHP 8.5 `(void)` cast is supported for explicitly discarding a value; the operand is still evaluated and its side effects are preserved, and the cast cannot be used in value contexts such as assignments, returns, arguments, or conditions. - Support for PHP 8.5 `clone()` / clone-with requires the linked `libphp` to be version 8.5 or later. Public and dynamic properties, private/protected/readonly properties, property hooks, call ordering, error propagation, and callable paths are all covered by PHPT tests. - PHP 8.4 property hooks are compiled into AOT getters/setters and register the corresponding Zend hook metadata; direct property reads/writes, Reflection, and object iteration are all supported. Taking a reference to a hooked property is currently not supported. - Interface property hooks do not currently support an explicitly declared `set` parameter; use the implicit setter value parameter. - PHP 8.4 Reflection Lazy Objects cannot be used with TypePHP AOT classes. AOT classes are registered as persistent internal classes, and Zend's `zend_object_make_lazy()` explicitly rejects internal classes. Zend PHP user classes loaded dynamically at runtime are not subject to this restriction. - `private(set)` and `protected(set)` asymmetric property visibility is supported, including constructor property promotion. Zend-backed objects perform the scope check through the PHP 8.4+ class-level object handler and preserve the promoted / set-visibility / implicit-final reflection flags; Native objects enforce the equivalent scope rules through compile-time access checks. - Final properties declared through constructor promotion are supported, but TypePHP requires an explicit `public`, `protected`, or `private` modifier; PHP 8.5's implicitly public form, `final int $value`, is not accepted. As a TypePHP extension, this syntax is independent of the PHP source-syntax version supported by the linked `libphp` and remains available when using a PHP 8.4 `libphp.so`. - TypePHP forbids attributes on global or namespaced constant declarations; PHP 8.5 global constant attributes are out of scope. Class constant attributes are not affected by this restriction. - A `.stub.php` file only declares Zend ABI symbols supplied by external C++ and must not use `#[Native]` on its classes. Native Class object layouts must be generated and owned by the TypePHP compiler. - Returning by reference from closures or arrow functions is not supported. - PHP 8.5 `static function` expressions in global constants, class constants, parameter defaults, or property defaults are not supported yet. Closures nested inside initializer expressions are likewise rejected at compile time. - `__construct()` may not have a return value. - A parameter with a default value may not appear before a required parameter (PHP permits this legacy pattern but treats the former parameter as required). - By-reference variadic parameters `&...$args` are supported for ordinary functions and methods whose signature is known at compile time, including direct, named, and unpacked arguments. A by-reference variadic declaration on a dynamic Closure is not supported. - Union, intersection, and nullable types are still represented as `mixed/any` in C++, but the static analysis phase uses known expression types to reject definitely incompatible arguments, return values, and property assignments ahead of time; dynamic values still retain their runtime type checks. - Once a local variable's type has been statically inferred as a concrete native type, reassigning it to an incompatible type within the same scope is not supported. ## declare - `declare(ticks=...)` is not supported. - `declare(encoding=...)` accepts only `UTF-8`. - `declare(strict_types=...)` accepts only `strict_types=1`. - No other `declare` directives are supported. ## Calls and references - `exit(message: $value)` is available as a TypePHP named-argument extension; it enters the same exit path as the positional form `exit($value)`. - TypePHP uses strict argument-count rules: non-variadic functions do not accept extra arguments beyond the declared signature, and `func_get_args()` does not implicitly relax the signature. - Reference parameters and write-back semantics are supported for ordinary functions, ordinary methods, and native direct calls with known signatures; do not mistakenly describe the compiler's internal cross-trait dynamic-dispatch limitation as "TypePHP does not support reference parameters". - Closures and arrow functions support fixed by-reference parameters. Because a Closure invocation is dynamically dispatched, the caller must still mark reference arguments explicitly with `refval()` / `toRef()`; Zend callbacks use the generated Closure arginfo automatically. - Reference assignment cannot create a reference from a complex static-property expression. - Calls whose argument signature cannot be determined at compile time — dynamic calls, closure calls, and the like — cannot convert reference parameters automatically; `refval()` or the equivalent keyword method `toRef()` must be used explicitly. - `refval()` / `toRef()` only accept variables, array elements, or object properties. - A call that uses argument unpacking followed by named arguments falls back to dynamic dispatch and cannot use the native call path. ## Object model - Reserved keyword methods such as `toInt()`, `toString()`, and `toArray()` are resolved before ordinary object methods; an application method of the same name that takes arguments is not called with ordinary object-method semantics. - `toAny()` and `toRef()` are non-overridable TypePHP keyword methods, and ordinary class-like declarations must not define methods with these names (method names are case-insensitive, per PHP rules). A Native class may only explicitly define a `toAny()` conversion method returning `mixed/any`; no implicit conversion is provided. Native classes do not support `toRef()`. - Fixed-layout typed properties that are not explicitly initialized use the zero value of their type and do not preserve Zend PHP's full uninitialized state; expressions such as `??` that depend on the uninitialized state may therefore behave differently. - A subclass may not shadow a parent's private property with a `private` property of the same name; `public` / `protected` declarations of the same name are treated as the same inherited property slot and must still satisfy the type, visibility, and `readonly` compatibility requirements. - Dynamic writes to typed properties still use strict type checks. When the right-hand side cannot be determined at compile time, TypePHP preserves a runtime check rather than falling back to Zend weak scalar conversion. - Native Classes use a separate fixed-layout object model. They cannot be used as ordinary Zend Objects, PHP array keys/values, or arbitrary `mixed` values, and they restrict dynamic members, references, static/readonly members, and several operators. See [Native Class Object Design](NATIVE_CLASS_OBJECT.md) for the complete boundary. ## Expressions and control flow - A `match` arm condition may not itself be a `match` expression. - The value target in a by-reference `foreach` may only be a variable. - `foreach` list destructuring does not support binding elements by reference. - On the non-`int/bool` lowering path, every non-empty `switch` case must end in `return`, `break`, `continue`, `exit`, or `throw`; do not rely on implicit PHP case fallthrough. The native `int/bool` switch path can currently retain C++ fallthrough, so project code should terminate every non-empty case explicitly. - Appending, inserting, `unset()`, and wholesale replacement of `std::vector`, `std::map`, and `std::ordered_map` are forbidden during a `foreach`; non-structural updates of existing elements can still be done with assignment operators. - Fixed native typed object properties cannot be freely `unset()` with PHP's standard uninitialized-property semantics. - Calling `unset()` on a native-typed variable does not delete the variable as it would in standard PHP. ## Runtime dynamic capabilities - Ordinary Zend objects and dynamic class expressions support runtime `::class` and class-constant lookup. Native Classes still require the relevant class target to be known at compile time. - `static::class` is not supported in positions that require a compile-time constant class name. - `__CLASS__` may only be used within a `class` definition (PHP allows it elsewhere and returns an empty string). - `__TRAIT__` may only be used within a `trait` definition (PHP allows it elsewhere and returns an empty string). - Dynamic property chains, dynamic class names, dynamic function names, and dynamic callbacks all go through the Zend runtime fallback and are not guaranteed to be natively optimized; reference parameters of dynamic calls still require an explicit `refval()` or `toRef()`. - `Closure::bind()`, `Closure::bindTo()`, and `Closure::call()` are not supported. A closure cannot be rebound to an object or class scope in AOT code. - All source files must be encoded as `UTF-8`. ## Generators TypePHP generators use `FiberGenerator` rather than Zend `Generator`. Code must therefore not rely on `instanceof Generator`, `ReflectionGenerator`, Zend Generator's internal object layout, or identical exception traces. Generators do not support returning by reference, by-reference yield, by-reference `foreach`, by-reference parameters, or variadic parameters. Fiber and Generator are not currently supported by the WASI target. See [Generators](YIELD_GENERATOR.md) for the complete boundary.