fix(compiler): enforce class constant override rules

pull/27/head
韩天峰 1 month ago
parent a1b09926a3
commit c75b4d565b
  1. 13
      phpunit/code/inheritance_error_const_final.php
  2. 13
      phpunit/code/inheritance_error_const_missing_type.php
  3. 10
      phpunit/src/InheritanceErrorTest.php
  4. 14
      src/Parser/ClassConstantFetchTrait.php
  5. 9
      src/Translator.php
  6. 43
      tests/compiler/const/class-const-parent-cross-ns.phpt

@ -0,0 +1,13 @@
<?php
class FinalConstantParent
{
final public const VALUE = 1;
}
class FinalConstantChild extends FinalConstantParent
{
public const VALUE = 2;
}
function main() {}

@ -0,0 +1,13 @@
<?php
class TypedConstantParent
{
public const int VALUE = 1;
}
class UntypedConstantChild extends TypedConstantParent
{
public const VALUE = 1;
}
function main() {}

@ -168,6 +168,16 @@ class InheritanceErrorTest extends TestCase
$this->exec('must be compatible', 'inheritance_error_const_type.php');
}
public function testTypedConstantCannotBeOverriddenWithoutDeclaredType()
{
$this->exec('must be compatible', 'inheritance_error_const_missing_type.php');
}
public function testFinalConstantCannotBeOverridden()
{
$this->exec('Cannot override final constant', 'inheritance_error_const_final.php');
}
public function testConstantVisibilityMismatch()
{
$this->exec('must be compatible', 'inheritance_error_const_visibility.php');

@ -32,15 +32,12 @@ trait ClassConstantFetchTrait
$class = $this->class;
}
} elseif ($class === 'parent') {
// `parent::` refers to the parent of the current class. Resolve it to
// the real parent class name and treat it like `self` for the purpose
// of constant/magic-class resolution.
$parentClass = $this->getParentClass($this->class);
if ($parentClass !== '' && $this->hasClass($parentClass)) {
$class = $this->getClass($parentClass)->name;
} else {
$class = $parentClass;
if (!$this->classDef || !$this->classDef->extends) {
$this->fatalError($expr, 'Cannot use "parent" outside a class or class does not extend any class');
}
// extends is already fully resolved. Keep the leading slash so the
// current namespace is not applied again below.
$class = '\\' . $this->classDef->extends;
$self = true;
}
@ -109,4 +106,3 @@ trait ClassConstantFetchTrait
}
}

@ -3477,11 +3477,20 @@ CODE;
if ($parentConst->flags & Modifiers::PRIVATE) {
continue;
}
if ($parentConst->flags & Modifiers::FINAL) {
$this->fatalError($classStmt,
"Cannot override final constant `{$parentClass}::{$name}`");
}
// PHP only enforces type compatibility when the parent constant
// carries an explicit declared type. Overriding an untyped constant
// with a value of any type is permitted, so the type check is skipped
// in that case. Visibility is always enforced below.
if ($parentConst->declaredType !== null) {
if ($childConst->declaredType === null) {
$this->fatalError($classStmt,
"Declaration of `{$className}::{$name}` must be compatible " .
"with `{$parentClass}::{$name}`");
}
// An untyped child constant whose value is an expression (e.g.
// `X = ParentClass::Y`) is inferred as a variant. Resolve its real
// type from the referenced constant so the compatibility check uses

@ -0,0 +1,43 @@
--TEST--
parent class constants resolve across namespaces
--FILE--
<?php
namespace Library {
class Base
{
public const TOKEN = 'base';
}
}
namespace Application {
class Sibling
{
}
class Child extends \Library\Base
{
public const PARENT_NAME = parent::class;
public const SIBLING_NAME = Sibling::class;
public static function dumpParent(): void
{
var_dump(parent::class, parent::TOKEN);
}
}
}
namespace {
function main(): void
{
var_dump(\Application\Child::PARENT_NAME);
var_dump(\Application\Child::SIBLING_NAME);
\Application\Child::dumpParent();
}
}
?>
--EXPECT--
string(12) "Library\Base"
string(19) "Application\Sibling"
string(12) "Library\Base"
string(4) "base"
Loading…
Cancel
Save