* fix(gen_stub): register enum-case class constants as persistent AST constants
A class constant valued by an enum case was registered with the folded
scalar (the backing value, or the case name for pure cases), so
constant('K::CB'), $cls::CB and reflection observed an int/string where
PHP has the case object, and K::CB === E::B was false on every dynamic
path. Enum case objects have request lifetime and can never sit in the
persistent class-entry tables, in any request-init rebinding scheme
least of all: writing a request-owned object into the shared table is
unsafe under concurrent ZTS requests.
Reuse the engine's own mechanism for internal enums instead: the
constant is declared as a persistent IS_CONSTANT_AST holding the
Enum::Case fetch, so Zend separates the class constants table into
request-local mutable storage on first access, evaluates the fetch
there, and cleans it up at request shutdown. Identity is preserved for
static access, constant(), dynamic class access and reflection, with no
module-lifecycle hooks and no registration-order sensitivity.
Case identity flows through compile-time constant evaluation as an
EnumCaseRef value instead of a scalar, so it also survives constant
expressions (true ? E::A : E::B), constant chains, typed class
constants (declared type and AST value are registered together), and
internal enum cases such as RoundingMode::HalfEven, which previously
aborted stub generation. The runtime expression path (php::getEnumCase)
is unchanged.
The preprocessor also no longer reads the raw ->value property off
arbitrary case expressions (`case A = 1 + 1;` warned and was recorded
as a pure case): only literal backing values are recorded eagerly, and
no compile-time consumer needs the evaluated scalar - gen_stub
evaluates the registration value from the AST itself.
* fix(gen_stub): constrain enum-case AST registration to class constants
The persistent IS_CONSTANT_AST representation leaked into property and
parameter defaults, whose persistent tables reject refcounted zvals:
startup died with "Internal zvals cannot be refcounted". EvaluatedValue
now carries the case identity in a dedicated field while its value
degrades to what those consumers read before case identity existed
(the host case object for internal enums, the literal backing value or
case name for compiled ones), and only class-constant registration opts
into the AST. Property/parameter defaults keep flowing through their
existing runtime-restore machinery unchanged.
Also parenthesize a folded constant operand before appending a member
access: the C++ ternary of `const VALUE = cond ? E::A : E::B;` bound
`.attr("value")` to its else branch only, so `K::VALUE->value`
evaluated to the case object instead of its backing value.
* fix(gen_stub): give persistent AST constants a complete teardown lifecycle
destroy_zend_class() asserts (in debug builds) that every persistent
AST constant remaining on an internal class is CONST_ENUM_INIT, and its
teardown frees only the allocation referenced by Z_AST — the previous
representation left a CLASS_CONST root behind (assertion failure at
shutdown on 8.4/8.5 debug builds) and leaked the two separately
allocated children.
The AST is now built in one contiguous persistent allocation (ast_ref,
root, both zval children — mirroring Zend's own persistent enum AST
builder), and every generated file with AST constants emits a release
function that runs from the module's MSHUTDOWN, before Zend's class
teardown: it frees the single block and restores the constant slot to
null, so destroy_zend_class() never sees a foreign AST. Request-local
mutable copies are unaffected (no request is live at MSHUTDOWN).
CONST_ENUM_INIT itself is not usable here: that node constructs a new
case object rather than fetching the canonical registered one, which
would break case identity again.
* fix(codegen): reject lifecycles that cannot release AST constants
The typephp_release_ast_constants_*() teardown ran only from MSHUTDOWN,
which is not a general pre-class-destruction hook: for a
MODULE_TEMPORARY module loaded through dl(), module_destructor() runs
clean_module_classes() before the shutdown callback, so the foreign
ZEND_AST_CLASS_CONST reached destroy_zend_class() first and still
tripped the debug assertion; and a MINIT that fails after registering
such a constant never sets module_started, so MSHUTDOWN is not
guaranteed to run at all.
The generated module now enforces the lifecycle contract instead of
assuming it. When the module declares any enum-case AST constant, MINIT
opens with a guard that rejects MODULE_TEMPORARY (zend_error E_WARNING,
return FAILURE) before a single class is registered — with nothing in
the class table, teardown is trivially safe. MINIT is also restructured
so that every step that can return FAILURE precedes the first
register_class_*() call: the AST constants are installed by the
infallible tail (class registration, then symbol registration), so a
FAILURE return can never leave a foreign AST in the persistent tables.
The generator itself throws if a future change introduces a FAILURE
return after registration begins. The MSHUTDOWN release is unchanged
and remains the supported, persistent-module path.
EnumCaseAstConstantLifecycleTest asserts the guard exists exactly when
AST constants exist, that it precedes every registration step, that no
FAILURE return follows the first class registration, and that MSHUTDOWN
releases the constants before any other teardown. The enum-case phpt
gains a never-accessed constant so the full process shutdown it already
performs also covers a pristine persistent AST; a dl()-path phpt is not
feasible because the harness only builds standalone binaries whose
module is registered persistently (documented in the test file).
* fix(preprocessor): enforce Zend interface member declaration rules
parseInterface accepted several declarations Zend rejects at compile
time (all wordings probed on 8.4.13, which renamed the modifier errors
to "must not be abstract/final"):
- interface method with a body ("Interface function I::f() cannot
contain body")
- private/protected interface method ("Access type for interface
method I::f() must be public")
- explicit `abstract` modifier on an interface method ("Interface
method I::f() must not be abstract")
- `final` interface method ("Interface method I::f() must not be
final")
- private/protected interface constant ("Access type for interface
constant I::X must be public"); `final` interface constants remain
legal per PHP 8.1
- explicit `abstract` on an interface hooked property ("Property in
interface cannot be explicitly abstract...")
- `interface I extends A` where A is a known class, enum, or trait
("I cannot implement A - it is not an interface"); only checked when
A's declaration has already been prepared - a parent declared later
is left to the Translator (deferred to integrator)
- the same interface listed twice in extends ("Interface I cannot
implement previously implemented interface A")
Zend's precedence for combined modifier violations (visibility, then
abstract, then final, then body) is preserved.
* fix(translator): validate same-name methods when interfaces merge
Two interfaces declaring the same method were never cross-checked:
`interface J extends I1, I2` and a class implementing both compiled
even when the declarations were mutually incompatible (Zend:
"Declaration of I1::f(): int must be compatible with I2::f(): string").
The first-seen declaration is now validated as an override of every
later one, mirroring Zend's merge order; diamond inheritance of one
original declaration never conflicts, and a method the class chain
defines silences the pairwise check (it is validated against each
interface individually instead) — all probed against Zend 8.4.
* fix(translator): reject an interface extending a class, validate merged methods
An interface can only extend other interfaces: naming a class either
fataled with a misleading missing-symbol message (declaration seen
earlier) or compiled silently (declaration appearing later). The
translation phase now rejects both with Zend's wording.
Same-name methods arriving from several extended interfaces (or from
several interfaces a class implements without defining the method) were
never cross-checked; the first-seen declaration is now validated as an
override of every later one, matching Zend's merge order, with diamond
inheritance of one original declaration exempt.
* fix(translator): guard the interface method table against extends cycles
getEffectiveInterfaceMethodTable() recursed forever on a cyclic extends
graph (interface A extends B; interface B extends A). Zend never reaches
this state - declarations are linked one at a time, so the first one
already fails with 'Interface "B" not found' - but ahead-of-time the
whole graph exists before linking, so the cycle must be detected.
Track the tables being built in a visiting set (cleared with
try/finally) and fail promptly with the same stable diagnostic the
constants table uses ('Interface inheritance cycle detected at ...'),
so the helper is safe regardless of which validation pass reaches the
cycle first. Diamond (non-cyclic) graphs still converge through the
memoized table.
Covered by a negative test on the two-interface cycle; the diamond case
is already exercised by interface_collision_valid.php.
* fix(preprocessor): enforce Zend readonly property declaration rules
The readonly checks previously lived only in the Native-class branch;
ZendVM-backed classes accepted declarations Zend rejects at compile
time. addClassProperty now enforces, for declared and promoted
properties alike (probed against Zend 8.4.13):
- readonly property with a default value ("Readonly property A::$x
cannot have default value") - a readonly property carries runtime
initialization state, so a compile-time default is meaningless
- untyped readonly property, including untyped promoted readonly ctor
params ("Readonly property A::$x must have type")
- static readonly ("Static property A::$x cannot be readonly")
- a `readonly class` applies the same three rules to every property:
the class-level Modifiers::READONLY flag (already recorded on
ClassDef->flags for the Translator-side inheritance check) is OR-ed
into the per-property check
Promoted readonly params keep accepting parameter defaults: the default
belongs to the constructor argument, not the property (Zend-verified).
The inheritance_error_prop_readonly fixture used `readonly int $x = 2`,
which Zend itself rejects with the default-value error before ever
reaching the readonly-mismatch link error; the default is dropped so the
fixture still exercises the inheritance mismatch.
* fix(translator): enforce readonly-class inheritance in both directions
Zend seals readonly-ness across a hierarchy: a non-readonly class cannot
extend a readonly one and vice versa. Both directions compiled silently.
* fix(translator): complete readonly-class contracts for traits, internal parents and attributes
Three readonly-class rules Zend enforces at compile time were still
accepted (all probed on 8.4.13):
- A trait property keeps its own declaration; the consuming class's
readonly modifier does not upgrade it, so composing a non-readonly
(or static, which can never be readonly) trait property into a
readonly class fails: "Readonly class C cannot use trait with a
non-readonly property T::$value". The check runs in composeTraitAst's
property pass, which also matches Zend's naming of the directly used
trait when the property originates in a nested trait. A trait property
declared readonly composes fine.
- The readonly inheritance check only covered compiled parents; classes
extending internal ones skipped it entirely, so `readonly class C
extends ArrayObject {}` compiled. Internal parents now consult host
reflection (ReflectionClass::isReadOnly), keeping the contract
two-directional: the host runtime also knows internal readonly classes
(BcMath\Number, Dom\NamespaceInfo — both final in 8.4, so only the
readonly-child direction is reachable today).
- #[AllowDynamicProperties] contradicts readonly semantics (every
property is readonly and declared); Zend rejects the combination:
"Cannot apply #[AllowDynamicProperties] to readonly class C". The
pre-existing readonly-class.phpt carried exactly this invalid
combination and is adjusted to stay a valid positive test.
- Replace multiple TLS arrays with single php_request_cache_storage struct
- Use one TLS pointer instead of multiple static THREAD_LOCAL arrays
- Implement new/delete allocation for request-scoped cache storage
- Remove memset calls in favor of proper object lifecycle management
- Add proper initialization checks and error handling in RINIT
- Update all cache access patterns to use php_request_cache pointer
- Move persistent caches outside of TLS to static storage
- Include <new> header for allocation operations
- Update tests to verify new cache storage structure and behavior
- Replace global $translator variable with Translator::getInstance() method
- Add singleton instance management to Translator class
- Update compiler.php to use singleton pattern instead of global variable
- Update gen_stub.php to use getTranslator() helper function
- Add translator instance test to verify singleton behavior
- Remove unnecessary global declarations throughout codebase
* fix(translator): enforce final and abstract parent constructor rules
checkParentMethodCanBeOverridden() returned immediately for
__construct, so overriding a FINAL parent constructor was accepted
(Zend: "Cannot override final method A::__construct()") and an
ABSTRACT parent constructor's signature was never validated (Zend
checks it exactly like an interface constructor).
Zend's constructor rules (zend_do_inheritance):
- a concrete parent constructor imposes no signature contract: the
child may change parameters and even narrow visibility — this
exemption is kept;
- a private parent constructor may be redeclared freely, but FINAL
still wins: `final private function __construct()` cannot be
overridden (constructors are the one place PHP allows final
private);
- an abstract parent constructor's signature is a real contract.
Keep walking the parent chain for constructors, skipping only the
private-override error and the concrete-signature validation; final
checks (userland and built-in parents) and abstract-constructor
validation now run.
* fix(translator): allow redeclaring a parent's private method
checkParentMethodCanBeOverridden() fataled with "Cannot override
private method" when a child declared a method whose nearest parent
declaration is PRIVATE. Zend inherits no private methods: a child may
redeclare one with any signature, visibility or staticness, and FINAL
is ignored on non-constructor private methods (declaring one only
raises "Private methods cannot be final..."). Only the final private
CONSTRUCTOR remains protected, which the constructor path already
enforces.
Dispatch stays correct after removing the fatal:
- canDevirtualize() (Parser/MethodCallTrait) devirtualizes any call
whose resolved method is private to the DECLARING class's body.
That is exactly PHP's private-scope binding (zend_std_get_method
prefers the calling scope's private copy), verified against the
manual's Bar/Foo::testPrivate example;
- method resolution walks from the receiver's static class, so code
in the child binds the child's redeclaration;
- a call on a receiver statically typed as the declaring class from
OUTSIDE its scope is rejected by getNativeMethod()'s accessibility
check, and dynamically typed receivers go through Zend dispatch;
- Native (C++) classes give private methods no virtual slot
(isNativeVirtualMethod() excludes PRIVATE), so no C++ override can
reroute a parent's internal private call.
The two tests asserting the old fatal encoded rejects-valid programs
(both run fine under Zend 8.4, printing the parent's private result);
they now assert successful compilation.
* fix(translator): validate abstract method redeclarations against the parent chain
An abstract method declared by a class was never checked against its
parent: turning a concrete inherited method abstract compiled (Zend:
"Cannot make non abstract method A::f() abstract in class B"), and an
abstract redeclaration of an inherited abstract contract skipped the
signature check entirely. Both now run through
checkParentMethodCanBeOverridden with a childIsAbstract mode, covering
userland and built-in parents.
Trait-originated abstract requirements are exempt: Zend lets an
inherited concrete method satisfy them, so only abstract methods the
class itself declares participate.
---------
Co-authored-by: tianfenghan <rango@swoole.com>
checkParentMethodCanBeOverridden()'s internal-parent branch only checked
the PRIVATE and FINAL modifiers via reflection and then stopped, so an
override of any Zend built-in method was never signature-checked:
narrowed parameters, static/instance mismatches, narrowed visibility and
incompatible real return types were all accepted (all fatal in Zend,
e.g. "Declaration of C::offsetGet(int $key): string must be compatible
with ArrayObject::offsetGet(mixed $key): mixed").
Add validateInternalMethodOverrideSignature(), mirroring Zend's
zend_do_perform_implementation_check on host ReflectionMethod data:
- visibility may widen but not narrow; staticness must match;
- a by-ref return may be added but not dropped;
- the child may not require more arguments; extra parameters must be
optional or variadic;
- parameters are contravariant with invariant by-ref-ness, and a
trailing child variadic absorbs remaining parent positions (a
variadic parent requires a variadic child);
- the return type is covariant, enforced ONLY for real return types:
ReflectionMethod::getReturnType() is null for TENTATIVE return
types, which Zend merely deprecates on mismatch, never fatals.
ReflectionType data (named/nullable/union/intersection, incl. self,
parent and static) is mapped into the existing accepted-types DNF so the
comparison reuses isReturnTypeCoveredBy()/isAcceptedTypeSubset().
* fix(translator): make typed class-constant overrides covariant
checkConstantOverride() required exact type equality between a child
constant and the parent's declared type, rejecting valid PHP 8.3
programs: class constant types are covariant, so a child may narrow
(parent `const int|string X` overridden by `const int X`, or
`?int` by `int`) but never widen or move to an unrelated type
(Zend: "Type of B::X must be compatible with A::X of type int").
Composite declared types (unions, nullables) were also collapsed to a
single variant type at parse time, making them unrepresentable in the
check. ConstantDef now records the accepted-types DNF of its declared
type (built by the existing buildTypeCheckFromNode machinery in a
parseClassConstDef override, while the declaration's name-resolution
context is still active), and the override check reuses the DNF
clause-subtyping used for covariant returns. Untyped parent constants
remain unchecked, and a typed parent still requires a typed child.
* fix(translator): validate interface constant contracts
Interface constants were never validated: checkInterfaceImplementation()
had no constants loop and checkConstantOverride() only walks the class
extends chain. Incompatible retypings, final-constant overrides,
narrowed visibility and ambiguous multi-interface inheritance were all
accepted (all fatal in Zend 8.4).
Model Zend's constants-table merge (zend_do_inheritance +
do_inherit_constant_check): a class-like's effective table is built from
the parent class's table (private constants are not inherited), its own
declarations, then its interfaces, each entry keeping the ORIGINAL
declaring class/interface. When a same-name constant arrives from a
different declaration:
- a FINAL inherited constant cannot be overridden — "C::X cannot
override final constant I::X" — including through an ancestor class
that implemented the interface (the origin travels with the entry);
- two different declarations are ambiguous unless the type declares
the constant itself — "Class C inherits both I1::X and I2::X,
which is ambiguous" (a diamond of one declaration is fine);
- an override of an interface constant must stay public — "Access
level to C::X must be public (as in interface I)";
- a typed interface constant requires a typed, covariant override; an
untyped one may be redefined freely.
The same validation runs for interfaces extending interfaces and for
enums implementing interfaces. Enum cases live in a separate table in
Zend and never conflict with inherited constants.
checkPropertyOverride() compared type, visibility, set-visibility,
readonly and final between a child property and the parent's, but never
Modifiers::STATIC. Redeclaring `public static int $x` as
`public int $x` (or the reverse) was accepted, while Zend fatals with
"Cannot redeclare static A::$x as non static B::$x" (and "Cannot
redeclare non static ... as static ..." in the other direction):
static and instance properties are different kinds of storage and can
never override one another.
Zend's zend_do_perform_implementation_check does not compare variadic-ness
per position. Its rules are:
- a variadic parent requires a variadic child (unbounded contract);
- a trailing child variadic stands in for every remaining parent
position (decorator pattern), with the variadic's type checked for
contravariance against each covered parent parameter and by-ref-ness
matched per position;
- when the parent is variadic, extra child parameters are validated
against the parent's variadic slot.
validateMethodOverrideSignature required an exact per-position variadic
match, rejecting valid programs such as parent f(int $a, int $b)
overridden by f(int ...$args). Rework the position loop per the Zend
rules; the required-argument-count and extra-optional-parameter checks
are unchanged.
The pre-existing testVariadicMismatch expectation (untyped f($x)
overridden by f(...$x) must fail) contradicts Zend 8.4, which accepts
it; the test now asserts the program compiles.
* test(phpunit): anchor TypePhp autoloading to the current checkout
A git worktree shares vendor/ with the primary checkout via a symlink, and
Composer's generated autoloader resolves the TypePhp\ prefix relative to the
realpath of vendor/. The suite then silently loads and tests the OTHER
checkout's src/ tree. Prepend an autoloader anchored to this checkout so the
tests always exercise the sources they ship with; in a standalone checkout
this is a no-op.
* fix(translator): allow overrides to add a by-ref return
Zend's inheritance check treats return-by-reference as covariant
(zend_do_perform_implementation_check): an error is raised only when the
parent returns by reference and the child does not. The child adding `&`
is a strictly stronger guarantee and is accepted:
class A { public function f(): array {} }
class B extends A { public function &f(): array {} } // OK in Zend
validateMethodOverrideSignature compared returnsByRef with exact equality,
rejecting this valid program. Make the check one-directional; dropping a
parent's by-ref return remains fatal.
The classMethodOverride registration in prepareClassMethod() depends on
file preprocessing order. With a "sandwich" order (ancestor first, leaf
second, intermediate class last), the ancestor method's override flag is
missed: the upward marking cannot cross the not-yet-registered
intermediate class, and the downward subclass lookup ran before the leaf
was prepared. findNativeMethod() then devirtualizes the late-bound call
into a direct native call, silently ignoring the override. In Hyperf this
turns SoftDeletes::delete() into a physical DELETE.
Finalize the flags once the complete class graph is known:
finalizeMethodOverrideFlags() walks every declared method's complete
parent chain and marks each existing ancestor method of the same name as
overridden (method count x inheritance depth). It runs from
Translator::convertFile() next to finalizeDeclarationExpressions(), so
both the project pipeline and the public prepareFile()/convertFile() API
share the same pre-conversion finalization, guarded by a dirty/finalized
flag reset in prepareFile().
Tests:
- tests/compiler/devirtualize/override-order-sandwich.phpt (fails on
master with "base", passes with "leaf")
- tests/compiler/devirtualize/override-order-normal.phpt (control)
- phpunit/src/DevirtualizeOrderTest.php driving the public
prepareFile()/convertFile() API in both orders, asserting the
generated Base::delete() body dispatches dynamically
Address both review findings on the trait composition fix:
1. An abstract trait requirement was discarded without validating the
concrete implementation. composeTraitAst now validates the
implementation - the class's own method, a concrete method from
another trait (in either collection order), or a class method
matching an aliased abstract - against the abstract declaration
before dropping it, following Zend's trait-composition rules:
matching staticness, a kept by-reference return, no additional
required parameters, contravariant parameter types, and a covariant
return type. Type variance reuses the existing override-check
machinery on the preprocessed definitions; late-bound self/static/
parent keywords are unified through the recorded type keywords, and
self-in-trait resolutions are remapped to the consuming class.
Visibility is intentionally not restricted: Zend allows an
implementation of any visibility to fulfill an abstract trait
requirement (verified against Zend 8.4).
2. Multiple alias adaptations of the same method depended on source
order because a same-name visibility change mutated the statement
that later adaptations cloned. Every adaptation now derives its
flags from the immutable original flags, and the original statement
is only mutated after all adaptations are processed, so
`value as protected; value as alias;` leaves `alias` public in both
adaptation orders (matching Zend, where each adaptation derives from
the original and the last same-name adaptation wins).
Both behaviors were pinned against Zend PHP 8.4 before implementing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Updated version from 0.6.7 to 0.6.8 in project.yml
- Updated file-version from 0.6.7.1112 to 0.6.8.1112 in project.yml
- Updated product-version from 0.6.7 to 0.6.8 in project.yml
- Updated VERSION constant from 0.6.7 to 0.6.8 in Translator.php
- Added bump-version.sh script for automated version management
- Script synchronizes version across project.yml and Translator.php files
Three defects in composeTraitAst diverged from PHP trait semantics:
1. When a later trait supplied a concrete method for a name an earlier
trait declared abstract, the branch unset the new concrete statement
while the already-merged abstract one stayed in the class AST. The
composed class kept only the abstract declaration, so instantiating
it failed with "Cannot instantiate abstract class" even though the
method body existed. The reverse trait order worked. Now the merged
abstract declaration is dropped and the concrete method is kept.
2. Alias adaptations assigned the new modifier over the whole flag set,
wiping static/final/abstract: `use Maker { make as protected; }`
turned a static method into an instance method, and static:: calls
on it miscompiled. PHP replaces only the visibility bits (and keeps
the original visibility when the modifier carries none, e.g.
`as final`).
3. A method defined by the class itself did not suppress the
trait-vs-trait conflict check, so `class C { use A, B; function f(){} }`
with f() in both traits died with a spurious "method already exists"
fatal. The class-method check now runs before conflict resolution and
suppressed trait copies are no longer registered as trait methods.
- Updated swoole/phpx dependency from ~2.6.4 to ~2.6.6
- Bumped project version from 0.6.6 to 0.6.7 in project.yml
- Updated file version from 0.6.6.1112 to 0.6.7.1112 in project.yml
- Updated product version from 0.6.6 to 0.6.7 in project.yml
- Updated VERSION constant from '0.6.6' to '0.6.7' in Translator.php
- Updated swoole/phpx dependency from ~2.6.4 to ~2.6.6
- Bumped project version from 0.6.6 to 0.6.7 in project.yml
- Updated file version from 0.6.6.1112 to 0.6.7.1112 in project.yml
- Updated product version from 0.6.6 to 0.6.7 in project.yml
- Updated VERSION constant from '0.6.6' to '0.6.7' in Translator.php
- Update project.yml version from 0.1.0 to 0.6.6
- Update project.yml file-version from 0.6.5.1112 to 0.6.6.1112
- Update project.yml product-version from 0.6.5 to 0.6.6
- Update Translator.php VERSION constant from 0.6.5 to 0.6.6
- Updated file-version in project.yml from 0.6.3.1112 to 0.6.5.1112
- Updated product-version in project.yml from 0.6.3 to 0.6.5
- Updated VERSION constant in Translator.php from 0.6.1 to 0.6.5
- Remove TYPEPHP_PROJECT_NAME and TYPEPHP_RUNTIME_EXPORTS from common compile options
- Add dedicated getProjectRuntimeEntryCompileCommandOptions method for project-specific defines
- Update testEmbeddedCompileOptionsPassProjectNameForModuleAccessor to reflect new behavior
- Create new testProjectRuntimeEntryHasTargetDefineWithoutPchOrObjectCache
- Add testProjectIndependentMiscObjectsUseSharedCacheScope and testProjectIndependentMiscObjectCacheSurvivesTargetNameChange
- Update composer.json to use swoole/phpx ~2.6.4
- Modify object file caching logic to handle project-specific vs shared misc objects
- Add isProjectRuntimeEntryFile helper method for determining cache scope
- Update compileFile method to use source-specific compile options
- Handle different PHPX runtime source file layouts during embed mode builds
- Added support for `ext-deps` as equivalent shorthand name for `extension-dependencies`
- Implemented validation to prevent using both config names simultaneously
- Updated documentation to reflect the new alias and usage restrictions
- Added test cases to verify ext-deps alias parsing works correctly
- Added test cases to ensure configuration error occurs when both names are used
- Refactored Translator.php to handle both configuration key variants
- Maintained backward compatibility with existing extension-dependencies usage
- Extend Override attribute to support properties in addition to methods
- Add comprehensive property override validation including parent property matching
- Implement property shadowing prevention for private parent properties
- Add trait property override validation at use site
- Support promoted and hooked properties in override validation
- Update error messages to include property targets for override rejection
- Add internal marker preservation for property override validation
- Implement final property and property hook inheritance restrictions
- Add parent property hook call syntax support with validation
- Support PHP 8.4 final property metadata preservation in reflection
- Add property hook inheritance and reflection test cases
- Update native class property validation to prevent private property hiding
- Implement interface property hook final restriction
- Add property get hook reference return validation error
- Update stub generation with property
- Implement DNF method signatures with parameter contravariance and return covariance
- Add new test cases for DNF inheritance variance scenarios
- Extend stub generation to handle DNF union and intersection types
- Create DNF type declaration code generation in gen_stub.php
- Add support for DNF properties and dynamic property writes
- Implement closure support with DNF parameters and return types
- Add error handling for invalid DNF type operations
- Update inheritance checks to validate DNF parameter and return type compatibility
- Introduce test cases for DNF inheritance errors
- Refactor type comparison logic to support DNF clause subtyping relations
- Add test for property hooks preservation and invocation after __clone
- Add test for private/protected/readonly property scope handling in clone-with
- Add test for trait method scope usage inside clone-with operations
- Update documentation to reflect completed clone-with implementation coverage
- Remove pending classification for clone-with scoped/readonly/hooked properties
- Remove TODO comment about property handlers preservation in clone-with operation
- Implement clone-with functionality with one/two argument support
- Add preservation of evaluation order for clone-with operations
- Support dynamic, numeric and object-valued properties in clone-with
- Handle error cases and stop property updates at first error
- Reject active references and unwrap sole remaining references
- Respect property scope and unlock readonly properties during cloning
- Add first-class callable and string callable support for clone
- Preserve lexical class scope in Zend call frames for method calls
- Update documentation with clone-with compatibility requirements
- Add comprehensive test coverage for clone-with features
- Implement scope argument handling in runtime function calls
- Add support for __NAMESPACE__ magic constant resolution in namespace, class, and global scopes
- Add support for __PROPERTY__ magic constant that resolves to property name within property context
- Implement namespace\name relative name resolution for functions, constants, classes, and members
- Update AnonClassGenerator to properly parse identifiers instead of using toString()
- Extend CompilerBase to handle Scalar_MagicConst_Namespace and Scalar_MagicConst_Property
- Modify class inheritance and interface implementation to use parseIdentifier for proper name resolution
- Add comprehensive tests for namespace magic constant behavior across different scopes
- Add tests for property magic constant resolving correctly in property contexts only
- Implement runtime attribute factory lowering with proper namespace context handling
- Update visitor pattern to track property magic boundaries and namespace context correctly
- Document incompatibility with PHP 8.5 #[NoDiscard]
- Removed isConstFetch and isAssignOp methods from AstNodeType
- Removed corresponding test cases for isConstFetch and isAssignOp
- Updated CallArgumentGenerator to use setValue and appendValue instead of set and append
- Removed parseNamedCallArgs method from CallArgumentGenerator
- Removed unused getPlatformFullLinkFlags method from Clang backend
- Updated ClosureGenerator to use appendValue instead of append
- Removed abstract compileFile and linkObjects methods from CompilerBackend
- Removed unused getObjectPropVarInfo and getDecimalLiteralString methods
- Removed resetReturnType and mustNoCall methods from CompilerBase
- Removed testParseIncludesUsesNewArchitecture, testParseLdflagsUsesNewArchitecture,
testParseLibsUsesNewArchitecture and testBackwardCompatibility methods
- Removed isUniversalCtags property and related logic from Extractor
- Removed extractWithMetadata, generateStatistics and exportToMarkdown methods from Extractor
- Removed extractMetadata, extractComments, isPHPFunction and parseDocTags methods from Extractor
- Removed FINAL_TEST_SUCCESS.md file containing old test results