fix(translator): covariant typed class constants and interface constant contracts - #58
Conversation
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.
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.
matyhtf
left a comment
There was a problem hiding this comment.
The constant covariance and effective-interface-constant table logic are consistent with PHP 8.4 behavior. I also checked direct/transitive final constants, ambiguity ordering, inherited class constants, trait-injected constants, typed narrowing, and integration with the current master; the focused tests pass and the branch merges cleanly.
Non-blocking follow-up: these new tests only exercise the compiler's dry generation path. A real native build of const_override_covariant.php currently fails because the existing gen_stub.php emits ZVAL_LONG(&const_N_value, ); for const ?int N = null; DNF/intersection class-constant types also hit the existing PropertyInfo assertion in VariableLike::getTypeCode(). Those are generator gaps outside this PR's modified contract-validation path and should be covered by end-to-end PHPTs separately.
Two related gaps in class-constant type contracts:
const int Xoverridingconst int|string Xwas rejected, while PHP 8.3 constants are covariant (narrowing legal, widening not — widening stays rejected). ConstantDef now records the declared type's accepted-types DNF (composite types were previously collapsed to one variant type) and the override check uses the covariance machinery.final const, incompatible types, non-public visibility, and the same constant name arriving ambiguously from two interfaces (including via parent classes and enum implements) all compiled. An effective-constants table preserving the original declaring interface now enforces Zend's rules for classes, enums, and interface-extends-interface.Verified against Zend 8.4.13, 32-case probe matrix.
Part of the split of #39.