fix(translator): covariant typed class constants and interface constant contracts - #58
Open
AlessioGiacobbe wants to merge 2 commits into
Open
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.