* 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
- Replace reinterpret_cast<void **> with direct reference operator &
- Remove unnecessary casting for local variable slot registration
- Maintain same functionality while improving code clarity
docs(runtime): add comprehensive runtime lifecycle documentation
- Create detailed RUNTIME_LIFECYCLE.html explaining four-layer architecture
- Document different modes: ext, bin, lib, and WASM with their lifecycle flows
- Add visual flowcharts showing initialization and shutdown sequences
- Include troubleshooting section with shutdown order constraints
- Add naming conventions and maintenance rules for lifecycle management
test(array): add null key append behavior test case
- Create test for null array key appending behavior
- Verify boolean keys convert to integer indexes properly
- Document expected output for null and boolean key handling
- Add EntryScriptCodegenTest to verify entry script generation
- Create temporary project directory setup and teardown methods
- Implement testMainLinePaddingDoesNotBloatGeneratedExtension method
- Verify line padding uses runtime string construction instead of embedded newlines
- Add recursive directory removal utility for cleanup
- Ensure proper evaluation of main function with correct line offset
- Replace direct typephp_runtime_init calls with TYPEPHP_RUNTIME_INIT macro
- Add conditional linking for Unix PHP extensions to prevent duplicate ZendVM
- Implement dynamic lookup for macOS extensions to resolve host symbols
- Move typephp_helper.h from misc to include directory
- Update WASM interface generator to use project-specific runtime symbols
- Add tests for native module linking behavior and macOS extension options
- Refactor Translator to generate proper embed module entry functions
- Update documentation for new runtime initialization patterns
- Replace php_get_class with get_class throughout the codebase
- Replace php_get_persistent_class with get_persistent_class
- Replace php_get_func with get_func and update related helpers
- Replace php_get_persistent_method with get_persistent_method
- Replace php_get_persistent_prop with get_persistent_prop
- Replace php_globals_array with php::globalsArray
- Replace php_deindirect with php::deindirect
- Replace php_get_called_ce with php::getCalledCe
- Replace php_get_callable_scope with php::getCallableScope
- Replace php_get_str with get_str for literal string access
- Update build script to handle TypePHP project module accessor
- Add test case for helper symbol collision prevention
- Modify module initialization from app_init/app_clean to module_init/module_clean
- Introduce $allowLocalClassEntryHoisting flag to control class entry hoisting behavior
- Add withoutLocalClassEntryHoisting method to temporarily disable hoisting during callback execution
- Modify getLocalClassEntryPtr to respect hoisting allowance when checking process stable class
- Wrap parameter default parsing in withoutLocalClassEntryHoisting to prevent unwanted hoisting
- Update array initialization in Preprocessor to use hoisting control for literal arrays
- Add support for new expressions in array parameter defaults with proper class resolution
- Include tests for array parameter defaults containing new expressions
- Add tests for runtime array property defaults with copy-on-write behavior
- Rename generated request array default symbols with 'typephp_' prefix for consistency
- Update documentation to reflect implemented optimizations and remaining costs
- Add optimized array write operations that preserve references and avoid temporaries
- Implement direct array append and item assignment for known array types
- Optimize compound assignment operations like += for known array slots
- Add compile-time expansion for $this::CONST class constant fetches
- Cache process-stable class entry pointers at function level to avoid repeated lookups
- Optimize string concatenation with two scalar operands using efficient overload
- Skip unnecessary type conversions for arguments that already have exact types
- Add comprehensive test coverage for optimized array operations and object creation
- Implement lazy initialization of request-scoped array default templates
- Generate efficient