TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
4.1 KiB
114 lines
4.1 KiB
# `#[Immutable]` compile-time effect checking
|
|
|
|
## Purpose
|
|
|
|
`#[Immutable]` is a TypePHP compile-time annotation modelled after C++ `const`.
|
|
It prevents accidental mutation in statically compiled code without adding a
|
|
wrapper object, Zend metadata, runtime branch, or ABI change.
|
|
|
|
It is intentionally a best-effort static tool rather than a security boundary.
|
|
Calls whose target is deliberately made dynamic are an escape hatch and do not
|
|
receive a runtime guard.
|
|
|
|
## Supported targets
|
|
|
|
```php
|
|
#[Immutable]
|
|
public function name(): string
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
function inspect(#[Immutable] User $user): string
|
|
{
|
|
return $user->name();
|
|
}
|
|
```
|
|
|
|
The attribute is valid on methods and on function, method, and closure
|
|
parameters. On an instance method it makes `$this` immutable. On a parameter it
|
|
makes the binding immutable and, when it can contain an object, treats the
|
|
referenced object as immutable as well.
|
|
|
|
## Rejected operations
|
|
|
|
For an immutable root such as `$this` or `$user`, the compiler rejects:
|
|
|
|
- assignment, destructuring, and array-element or object-property writes;
|
|
- compound assignment, `++`, `--`, `unset()`, taking a reference, and
|
|
`foreach (... as &$value)`;
|
|
- a statically named method call unless the resolved method is also marked
|
|
`#[Immutable]`;
|
|
- a mutating value extension such as `$array->sort()`; read-only array/string
|
|
methods remain available;
|
|
- passing an object to a statically resolved parameter that is not itself
|
|
`#[Immutable]`;
|
|
- passing any immutable value to a mutable by-reference parameter, including
|
|
extension functions such as `sort()`;
|
|
- storing an immutable object identity in an object property, array,
|
|
global/static variable, or returning/yielding it as a mutable value.
|
|
|
|
An immutable by-reference parameter is supported. It acts like a C++ `const &`:
|
|
the reference is accepted because the callee is checked against mutation.
|
|
|
|
`#[MethodsFor]` follows the same contract. An object extension is callable on
|
|
an immutable receiver only when its receiver parameter is marked
|
|
`#[Immutable]`.
|
|
|
|
## Aliases, closures, generators, and inheritance
|
|
|
|
Local aliases of immutable objects remain immutable:
|
|
|
|
```php
|
|
$alias = $user;
|
|
$alias->rename('new'); // compile-time error
|
|
```
|
|
|
|
`clone` creates a distinct mutable object and therefore intentionally drops the
|
|
annotation. Captured variables, arrow functions, closure `$this`, and Fiber
|
|
generator bodies carry immutable metadata into their generated function
|
|
contexts.
|
|
|
|
An overriding class or interface method may strengthen an ordinary contract by
|
|
adding `#[Immutable]`, but it cannot remove `#[Immutable]` from an inherited
|
|
method or parameter.
|
|
|
|
## Value versus object semantics
|
|
|
|
Scalar values and PHP copy-on-write values can be read and copied normally. For
|
|
example, `count($values)` and `$copy = $values` do not modify an immutable array.
|
|
The compiler propagates immutability through an expression only when object
|
|
identity is possible.
|
|
|
|
## Explicit escape hatches
|
|
|
|
The following intentionally bypass static method-effect checking:
|
|
|
|
```php
|
|
$method = 'rename';
|
|
$user->$method('new');
|
|
|
|
$callable = getRuntimeCallable();
|
|
$callable($user);
|
|
```
|
|
|
|
The same applies to other runtime-only mechanisms that hide the target from the
|
|
compiler, including reflection and dynamic ZendVM code. TypePHP neither inserts
|
|
a runtime read-only proxy nor attempts to recover the escaped value later.
|
|
|
|
This boundary is deliberate: `#[Immutable]` should cost nothing in generated
|
|
code and should not complicate PHPX/ZendVM object semantics.
|
|
|
|
## Property hooks and magic access
|
|
|
|
Property-hook reads are lowered to generated method calls. Consequently, a hook
|
|
used through an immutable receiver must itself carry an `#[Immutable]` method
|
|
contract; otherwise the generated call is rejected. Fully dynamic magic access
|
|
is covered by the same escape-hatch rule as other runtime-only behavior.
|
|
|
|
## Implementation boundaries
|
|
|
|
The implementation is isolated in `src/Immutable/ImmutableSupportTrait.php`.
|
|
`FunctionDef` and `ArgInfo` retain only compile-time effect bits, while each
|
|
`FunctionContext` stores the immutable roots and object aliases relevant to that
|
|
body. Checks run during AST lowering and emit no C++ code when successful.
|
|
|