diff --git a/phpunit/code/inheritance_error_const_final.php b/phpunit/code/inheritance_error_const_final.php new file mode 100644 index 00000000..e2e57cc7 --- /dev/null +++ b/phpunit/code/inheritance_error_const_final.php @@ -0,0 +1,13 @@ +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'); diff --git a/src/Parser/ClassConstantFetchTrait.php b/src/Parser/ClassConstantFetchTrait.php index 527a9c42..d5b01a94 100644 --- a/src/Parser/ClassConstantFetchTrait.php +++ b/src/Parser/ClassConstantFetchTrait.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 } } - diff --git a/src/Translator.php b/src/Translator.php index 7b61f827..70d063a9 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -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 diff --git a/tests/compiler/const/class-const-parent-cross-ns.phpt b/tests/compiler/const/class-const-parent-cross-ns.phpt new file mode 100644 index 00000000..f550fb66 --- /dev/null +++ b/tests/compiler/const/class-const-parent-cross-ns.phpt @@ -0,0 +1,43 @@ +--TEST-- +parent class constants resolve across namespaces +--FILE-- + +--EXPECT-- +string(12) "Library\Base" +string(19) "Application\Sibling" +string(12) "Library\Base" +string(4) "base"