Add support for final class constants

RFC: https://wiki.php.net/rfc/final_class_const

Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>
This commit is contained in:
Máté Kocsis 2021-07-06 21:38:44 +02:00
parent c6d4f60827
commit a5360e80c2
No known key found for this signature in database
GPG Key ID: FD055E41728BF310
28 changed files with 341 additions and 95 deletions

View File

@ -67,6 +67,8 @@ PHP 8.1 UPGRADE NOTES
. Added support for intersection types.
They cannot be combined with union types.
RFC: https://wiki.php.net/rfc/pure-intersection-types
. Added support for the final modifier for class constants.
RFC: https://wiki.php.net/rfc/final_class_const
- Fileinfo:
. The fileinfo functions now accept and return, respectively, finfo objects

View File

@ -23,5 +23,6 @@ class FooBar extends Foo implements ia {
new FooBar;
?>
--EXPECTF--
Fatal error: Cannot inherit previously-inherited or override constant c from interface ia in %s on line %d
===DONE===
--EXPECT--
===DONE===

View File

@ -0,0 +1,13 @@
--TEST--
Class constants support the final modifier
--FILE--
<?php
class Foo
{
final const A = "foo";
final public const B = "foo";
}
?>
--EXPECT--

View File

@ -0,0 +1,22 @@
--TEST--
Interface constants inherited from other interfaces can be redeclared
--FILE--
<?php
interface I1
{
const C = 1;
}
interface I2
{
const C = 2;
}
interface I3 extends I1, I2
{
const C = 3;
}
?>
--EXPECT--

View File

@ -0,0 +1,22 @@
--TEST--
Class constants cannot be inherited from both a class and an interface
--FILE--
<?php
class C
{
const C = 1;
}
interface I
{
const C = 1;
}
class C2 extends C implements I
{
}
?>
--EXPECTF--
Fatal error: Class C2 inherits both C::C and I::C, which is ambiguous in %s on line %d

View File

@ -0,0 +1,22 @@
--TEST--
Interface constants cannot be inherited from other interfaces
--FILE--
<?php
interface I1
{
const C = 1;
}
interface I2
{
const C = 2;
}
interface I3 extends I1, I2
{
}
?>
--EXPECTF--
Fatal error: Class I3 inherits both I1::C and I2::C, which is ambiguous in %s on line %d

View File

@ -0,0 +1,18 @@
--TEST--
Final class constants cannot be overridden
--FILE--
<?php
class Foo
{
final const A = "foo";
}
class Bar extends Foo
{
const A = "bar";
}
?>
--EXPECTF--
Fatal error: Bar::A cannot override final constant Foo::A in %s on line %d

View File

@ -0,0 +1,13 @@
--TEST--
Private class constants cannot be final
--FILE--
<?php
class Foo
{
private final const A = "foo";
}
?>
--EXPECTF--
Fatal error: Private constant Foo::A cannot be final as it is not visible to other classes in %s on line %d

View File

@ -0,0 +1,17 @@
--TEST--
Interface constants can be overridden directly
--FILE--
<?php
interface I
{
const X = 1;
}
class C implements I
{
const X = 2;
}
?>
--EXPECT--

View File

@ -0,0 +1,18 @@
--TEST--
Final interface constants cannot be overridden directly
--FILE--
<?php
interface I
{
final public const X = 1;
}
class C implements I
{
const X = 2;
}
?>
--EXPECTF--
Fatal error: C::X cannot override final constant I::X in %s on line %d

View File

@ -0,0 +1,16 @@
--TEST--
Final interface constants can be inherited
--FILE--
<?php
interface I
{
final public const X = 1;
}
class C implements I
{
}
?>
--EXPECT--

View File

@ -0,0 +1,19 @@
--TEST--
Interface constants can be overridden indirectly
--FILE--
<?php
interface I
{
const X = 1;
}
class C implements I {}
class D extends C
{
const X = 2;
}
?>
--EXPECT--

View File

@ -0,0 +1,22 @@
--TEST--
Class constants cannot be inherited from two different interfaces
--FILE--
<?php
interface I1
{
const C = 1;
}
interface I2
{
const C = 1;
}
class C implements I1, I2
{
}
?>
--EXPECTF--
Fatal error: Class C inherits both I1::C and I2::C, which is ambiguous in %s on line %d

View File

@ -0,0 +1,22 @@
--TEST--
Class constants inherited from interfaces can be redeclared
--FILE--
<?php
interface I1
{
const C = 1;
}
interface I2
{
const C = 2;
}
class C implements I1, I2
{
const C = 3;
}
?>
--EXPECT--

View File

@ -17,4 +17,4 @@ class test implements test1, test2 {
echo "Done\n";
?>
--EXPECTF--
Fatal error: Cannot inherit previously-inherited or override constant FOO from interface test2 in %s on line %d
Fatal error: Class test inherits both test1::FOO and test2::FOO, which is ambiguous in %s on line %d

View File

@ -10,4 +10,4 @@ class test {
echo "Done\n";
?>
--EXPECTF--
Fatal error: Cannot declare property test::$var final, the final modifier is allowed only for methods and classes in %s on line %d
Fatal error: Cannot declare property test::$var final, the final modifier is allowed only for methods, classes, and class constants in %s on line %d

View File

@ -1,18 +0,0 @@
--TEST--
Inherited constant from interface
--FILE--
<?php
interface foo {
const foo = 'foobar';
public function bar($x = foo);
}
class foobar implements foo {
const foo = 'bar';
public function bar($x = foo::foo) {
var_dump($x);
}
}
?>
--EXPECTF--
Fatal error: Cannot inherit previously-inherited or override constant foo from interface foo in %s on line %d

View File

@ -4331,12 +4331,12 @@ ZEND_API void zend_declare_property_stringl(zend_class_entry *ce, const char *na
}
/* }}} */
ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *name, zval *value, int access_type, zend_string *doc_comment) /* {{{ */
ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *name, zval *value, int flags, zend_string *doc_comment) /* {{{ */
{
zend_class_constant *c;
if (ce->ce_flags & ZEND_ACC_INTERFACE) {
if (access_type != ZEND_ACC_PUBLIC) {
if (!(flags & ZEND_ACC_PUBLIC)) {
zend_error_noreturn(E_COMPILE_ERROR, "Access type for interface constant %s::%s must be public", ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
}
@ -4356,7 +4356,7 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant));
}
ZVAL_COPY_VALUE(&c->value, value);
ZEND_CLASS_CONST_FLAGS(c) = access_type;
ZEND_CLASS_CONST_FLAGS(c) = flags;
c->doc_comment = doc_comment;
c->attributes = NULL;
c->ce = ce;

View File

@ -7270,7 +7270,7 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z
if (flags & ZEND_ACC_FINAL) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare property %s::$%s final, "
"the final modifier is allowed only for methods and classes",
"the final modifier is allowed only for methods, classes, and class constants",
ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
@ -7358,10 +7358,17 @@ void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr
zend_string *doc_comment = doc_comment_ast ? zend_string_copy(zend_ast_get_str(doc_comment_ast)) : NULL;
zval value_zv;
if (UNEXPECTED(flags & (ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT|ZEND_ACC_FINAL))) {
if (UNEXPECTED(flags & (ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT))) {
zend_check_const_and_trait_alias_attr(flags, "constant");
}
if (UNEXPECTED((flags & ZEND_ACC_PRIVATE) && (flags & ZEND_ACC_FINAL))) {
zend_error_noreturn(
E_COMPILE_ERROR, "Private constant %s::%s cannot be final as it is not visible to other classes",
ZSTR_VAL(ce->name), ZSTR_VAL(name)
);
}
zend_const_expr_to_zval(&value_zv, value_ast_ptr);
c = zend_declare_class_constant_ex(ce, name, &value_zv, flags, doc_comment);

View File

@ -1337,6 +1337,13 @@ static void do_inherit_class_constant(zend_string *name, zend_class_constant *pa
zend_error_noreturn(E_COMPILE_ERROR, "Access level to %s::%s must be %s (as in class %s)%s",
ZSTR_VAL(ce->name), ZSTR_VAL(name), zend_visibility_string(ZEND_CLASS_CONST_FLAGS(parent_const)), ZSTR_VAL(parent_const->ce->name), (ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PUBLIC) ? "" : " or weaker");
}
if (UNEXPECTED((ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_FINAL))) {
zend_error_noreturn(
E_COMPILE_ERROR, "%s::%s cannot override final constant %s::%s",
ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(parent_const->ce->name), ZSTR_VAL(name)
);
}
} else if (!(ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PRIVATE)) {
if (Z_TYPE(parent_const->value) == IS_CONSTANT_AST) {
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
@ -1622,25 +1629,37 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
}
/* }}} */
static bool do_inherit_constant_check(HashTable *child_constants_table, zend_class_constant *parent_constant, zend_string *name, const zend_class_entry *iface) /* {{{ */
{
zval *zv = zend_hash_find_ex(child_constants_table, name, 1);
zend_class_constant *old_constant;
if (zv != NULL) {
old_constant = (zend_class_constant*)Z_PTR_P(zv);
if (old_constant->ce != parent_constant->ce) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot inherit previously-inherited or override constant %s from interface %s", ZSTR_VAL(name), ZSTR_VAL(iface->name));
}
return 0;
static bool do_inherit_constant_check(
zend_class_entry *ce, zend_class_constant *parent_constant, zend_string *name
) {
zval *zv = zend_hash_find_ex(&ce->constants_table, name, 1);
if (zv == NULL) {
return true;
}
return 1;
zend_class_constant *old_constant = Z_PTR_P(zv);
if ((ZEND_CLASS_CONST_FLAGS(parent_constant) & ZEND_ACC_FINAL)) {
zend_error_noreturn(E_COMPILE_ERROR, "%s::%s cannot override final constant %s::%s",
ZSTR_VAL(old_constant->ce->name), ZSTR_VAL(name),
ZSTR_VAL(parent_constant->ce->name), ZSTR_VAL(name)
);
}
if (old_constant->ce != parent_constant->ce && old_constant->ce != ce) {
zend_error_noreturn(E_COMPILE_ERROR,
"Class %s inherits both %s::%s and %s::%s, which is ambiguous",
ZSTR_VAL(ce->name),
ZSTR_VAL(old_constant->ce->name), ZSTR_VAL(name),
ZSTR_VAL(parent_constant->ce->name), ZSTR_VAL(name));
}
return false;
}
/* }}} */
static void do_inherit_iface_constant(zend_string *name, zend_class_constant *c, zend_class_entry *ce, zend_class_entry *iface) /* {{{ */
{
if (do_inherit_constant_check(&ce->constants_table, c, name, iface)) {
if (do_inherit_constant_check(ce, c, name)) {
zend_class_constant *ct;
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
@ -1706,8 +1725,8 @@ ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry
}
if (ignore) {
/* Check for attempt to redeclare interface constants */
ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->constants_table, key, c) {
do_inherit_constant_check(&iface->constants_table, c, key, iface);
ZEND_HASH_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) {
do_inherit_constant_check(ce, c, key);
} ZEND_HASH_FOREACH_END();
} else {
if (ce->num_interfaces >= current_iface_num) {
@ -1751,8 +1770,8 @@ static void zend_do_implement_interfaces(zend_class_entry *ce, zend_class_entry
return;
}
/* skip duplications */
ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->constants_table, key, c) {
do_inherit_constant_check(&iface->constants_table, c, key, iface);
ZEND_HASH_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) {
do_inherit_constant_check(ce, c, key);
} ZEND_HASH_FOREACH_END();
iface = NULL;

View File

@ -565,9 +565,10 @@ static void _class_const_string(smart_str *str, char *name, zend_class_constant
}
const char *visibility = zend_visibility_string(ZEND_CLASS_CONST_FLAGS(c));
const char *final = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_FINAL ? "final " : "";
const char *type = zend_zval_type_name(&c->value);
smart_str_append_printf(str, "%sConstant [ %s %s %s ] { ",
indent, visibility, type, name);
smart_str_append_printf(str, "%sConstant [ %s%s %s %s ] { ",
indent, final, visibility, type, name);
if (Z_TYPE(c->value) == IS_ARRAY) {
smart_str_appends(str, "Array");
} else if (Z_TYPE(c->value) == IS_OBJECT) {
@ -3831,18 +3832,25 @@ ZEND_METHOD(ReflectionClassConstant, isProtected)
}
/* }}} */
/* Returns whether this constant is final */
ZEND_METHOD(ReflectionClassConstant, isFinal)
{
_class_constant_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL);
}
/* {{{ Returns a bitfield of the access modifiers for this constant */
ZEND_METHOD(ReflectionClassConstant, getModifiers)
{
reflection_object *intern;
zend_class_constant *ref;
uint32_t keep_flags = ZEND_ACC_FINAL | ZEND_ACC_PPP_MASK;
if (zend_parse_parameters_none() == FAILURE) {
RETURN_THROWS();
}
GET_REFLECTION_OBJECT_PTR(ref);
RETURN_LONG(ZEND_CLASS_CONST_FLAGS(ref) & ZEND_ACC_PPP_MASK);
RETURN_LONG(ZEND_CLASS_CONST_FLAGS(ref) & keep_flags);
}
/* }}} */
@ -7102,6 +7110,7 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */
REGISTER_REFLECTION_CLASS_CONST_LONG(class_constant, "IS_PUBLIC", ZEND_ACC_PUBLIC);
REGISTER_REFLECTION_CLASS_CONST_LONG(class_constant, "IS_PROTECTED", ZEND_ACC_PROTECTED);
REGISTER_REFLECTION_CLASS_CONST_LONG(class_constant, "IS_PRIVATE", ZEND_ACC_PRIVATE);
REGISTER_REFLECTION_CLASS_CONST_LONG(class_constant, "IS_FINAL", ZEND_ACC_FINAL);
reflection_extension_ptr = register_class_ReflectionExtension(reflector_ptr);
reflection_init_class_handlers(reflection_extension_ptr);

View File

@ -475,6 +475,8 @@ class ReflectionClassConstant implements Reflector
/** @tentative-return-type */
public function isProtected(): bool {}
public function isFinal(): bool {}
/** @tentative-return-type */
public function getModifiers(): int {}

View File

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: d8e686125cf213e019c1d706867e3c178fa057d2 */
* Stub hash: 2564122a201ca462ee35ead1562c94da3ea3c8a3 */
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0)
@ -396,6 +396,8 @@ ZEND_END_ARG_INFO()
#define arginfo_class_ReflectionClassConstant_isProtected arginfo_class_ReflectionFunctionAbstract_inNamespace
#define arginfo_class_ReflectionClassConstant_isFinal arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType
#define arginfo_class_ReflectionClassConstant_getModifiers arginfo_class_ReflectionFunctionAbstract_getNumberOfParameters
#define arginfo_class_ReflectionClassConstant_getDeclaringClass arginfo_class_ReflectionMethod_getDeclaringClass
@ -736,6 +738,7 @@ ZEND_METHOD(ReflectionClassConstant, getValue);
ZEND_METHOD(ReflectionClassConstant, isPublic);
ZEND_METHOD(ReflectionClassConstant, isPrivate);
ZEND_METHOD(ReflectionClassConstant, isProtected);
ZEND_METHOD(ReflectionClassConstant, isFinal);
ZEND_METHOD(ReflectionClassConstant, getModifiers);
ZEND_METHOD(ReflectionClassConstant, getDeclaringClass);
ZEND_METHOD(ReflectionClassConstant, getDocComment);
@ -1015,6 +1018,7 @@ static const zend_function_entry class_ReflectionClassConstant_methods[] = {
ZEND_ME(ReflectionClassConstant, isPublic, arginfo_class_ReflectionClassConstant_isPublic, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionClassConstant, isPrivate, arginfo_class_ReflectionClassConstant_isPrivate, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionClassConstant, isProtected, arginfo_class_ReflectionClassConstant_isProtected, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionClassConstant, isFinal, arginfo_class_ReflectionClassConstant_isFinal, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionClassConstant, getModifiers, arginfo_class_ReflectionClassConstant_getModifiers, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionClassConstant, getDeclaringClass, arginfo_class_ReflectionClassConstant_getDeclaringClass, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionClassConstant, getDocComment, arginfo_class_ReflectionClassConstant_getDocComment, ZEND_ACC_PUBLIC)

View File

@ -20,6 +20,8 @@ function reflectClassConstant($base, $constant) {
var_dump($constInfo->isPrivate());
echo "isProtected():\n";
var_dump($constInfo->isProtected());
echo "isFinal():\n";
var_dump($constInfo->isFinal());
echo "getModifiers():\n";
var_dump($constInfo->getModifiers());
echo "getDeclaringClass():\n";
@ -34,12 +36,14 @@ class TestClass {
/** Another doc comment */
protected const PROT = 4;
private const PRIV = "keepOut";
public final const FINAL = "foo";
}
$instance = new TestClass();
reflectClassConstant("TestClass", "PUB");
reflectClassConstant("TestClass", "PROT");
reflectClassConstant("TestClass", "PRIV");
reflectClassConstant("TestClass", "FINAL");
reflectClassConstant($instance, "PRIV");
reflectClassConstant($instance, "BAD_CONST");
@ -61,6 +65,8 @@ isPrivate():
bool(false)
isProtected():
bool(false)
isFinal():
bool(false)
getModifiers():
int(1)
getDeclaringClass():
@ -88,6 +94,8 @@ isPrivate():
bool(false)
isProtected():
bool(true)
isFinal():
bool(false)
getModifiers():
int(2)
getDeclaringClass():
@ -115,6 +123,8 @@ isPrivate():
bool(true)
isProtected():
bool(false)
isFinal():
bool(false)
getModifiers():
int(4)
getDeclaringClass():
@ -125,6 +135,35 @@ object(ReflectionClass)#3 (1) {
getDocComment():
bool(false)
**********************************
**********************************
Reflecting on class constant TestClass::FINAL
__toString():
string(47) "Constant [ final public string FINAL ] { foo }
"
getName():
string(5) "FINAL"
getValue():
string(3) "foo"
isPublic():
bool(true)
isPrivate():
bool(false)
isProtected():
bool(false)
isFinal():
bool(true)
getModifiers():
int(33)
getDeclaringClass():
object(ReflectionClass)#3 (1) {
["name"]=>
string(9) "TestClass"
}
getDocComment():
bool(false)
**********************************
**********************************
Reflecting on class constant TestClass::PRIV
@ -142,6 +181,8 @@ isPrivate():
bool(true)
isProtected():
bool(false)
isFinal():
bool(false)
getModifiers():
int(4)
getDeclaringClass():

View File

@ -1,10 +0,0 @@
--TEST--
Final constants are not allowed
--FILE--
<?php
class A {
final const X = 1;
}
?>
--EXPECTF--
Fatal error: Cannot use 'final' as constant modifier in %s on line 3

View File

@ -12,5 +12,5 @@ interface I2 extends I1 {
echo "Done\n";
?>
--EXPECTF--
Fatal error: Cannot inherit previously-inherited or override constant FOO from interface I1 in %s on line 6
--EXPECT--
Done

View File

@ -1,16 +0,0 @@
--TEST--
Ensure a class may not shadow a constant inherited from an interface.
--FILE--
<?php
interface I {
const FOO = 10;
}
class C implements I {
const FOO = 10;
}
echo "Done\n";
?>
--EXPECTF--
Fatal error: Cannot inherit previously-inherited or override constant FOO from interface I in %s on line 6

View File

@ -1,19 +0,0 @@
--TEST--
Ensure a class may not inherit two constants with the same name from two separate interfaces.
--FILE--
<?php
interface I1 {
const FOO = 10;
}
interface I2 {
const FOO = 10;
}
class C implements I1,I2 {
}
echo "Done\n";
?>
--EXPECTF--
Fatal error: Cannot inherit previously-inherited or override constant FOO from interface I2 in %s on line 10