Typed class constants (#10444)

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

Co-Authored-By: Ben <7127204+moliata@users.noreply.github.com>
Co-Authored-By: Bob Weinand <3154871+bwoebi@users.noreply.github.com>
Co-Authored-By: Ilija Tovilo <ilija.tovilo@me.com>
This commit is contained in:
Máté Kocsis 2023-04-16 22:20:26 +02:00 committed by GitHub
parent 4dad419ae6
commit 414f71a902
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 1227 additions and 92 deletions

View file

@ -52,6 +52,8 @@ PHP 8.3 UPGRADE NOTES
. Anonymous classes may now be marked as readonly.
. Readonly properties can now be reinitialized during cloning.
RFC: https://wiki.php.net/rfc/readonly_amendments
. Class, interface, trait, and enum constants now support type
declarations. RFC: https://wiki.php.net/rfc/typed_class_constants
- Posix
. posix_getrlimit() now takes an optional $res parameter to allow fetching a

View file

@ -0,0 +1,16 @@
--TEST--
Typed class constants (diamond error with self)
--FILE--
<?php
class A {
public const self CONST1 = C;
}
try {
define("C", new A());
} catch (Error $exception) {
echo $exception->getMessage() . "\n";
}
?>
--EXPECT--
Undefined constant "C"

View file

@ -0,0 +1,14 @@
--TEST--
Typed class constants (incompatible inheritance; simple)
--FILE--
<?php
class A {
public const int CONST1 = 1;
}
class B extends A {
public const string CONST1 = 'a';
}
?>
--EXPECTF--
Fatal error: Type of B::CONST1 must be compatible with A::CONST1 of type int in %s on line %d

View file

@ -0,0 +1,14 @@
--TEST--
Typed class constants (incompatible inheritance; missing type in child)
--FILE--
<?php
class A {
public const int CONST1 = 1;
}
class B extends A {
public const CONST1 = 0;
}
?>
--EXPECTF--
Fatal error: Type of B::CONST1 must be compatible with A::CONST1 of type int in %s on line %d

View file

@ -0,0 +1,18 @@
--TEST--
Typed class constants (incompatible composition; traits)
--FILE--
<?php
trait T {
public const ?array CONST1 = [];
}
class C {
use T;
public const CONST1 = [];
}
?>
--EXPECTF--
Fatal error: C and T define the same constant (CONST1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d

View file

@ -0,0 +1,18 @@
--TEST--
Typed class constants (incompatible covariant composition; traits)
--FILE--
<?php
trait T {
public const ?array CONST1 = [];
}
class C {
use T;
public const array CONST1 = [];
}
?>
--EXPECTF--
Fatal error: C and T define the same constant (CONST1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d

View file

@ -0,0 +1,18 @@
--TEST--
Typed class constants (incompatible contravariant composition; traits)
--FILE--
<?php
trait T {
public const array CONST1 = [];
}
class C {
use T;
public const ?array CONST1 = [];
}
?>
--EXPECTF--
Fatal error: C and T define the same constant (CONST1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d

View file

@ -0,0 +1,29 @@
--TEST--
Typed class constants (inheritance success)
--FILE--
<?php
class A {
public const CONST1 = 1;
public const CONST2 = 1;
public const mixed CONST3 = 1;
public const iterable CONST4 = [];
}
class B extends A {
public const int CONST1 = 0;
public const mixed CONST2 = 0;
public const mixed CONST3 = 0;
public const array CONST4 = [];
}
var_dump(B::CONST1);
var_dump(B::CONST2);
var_dump(B::CONST3);
var_dump(B::CONST4);
?>
--EXPECT--
int(0)
int(0)
int(0)
array(0) {
}

View file

@ -0,0 +1,48 @@
--TEST--
Typed class constants (inheritance success - object types)
--FILE--
<?php
class S implements Stringable {
public function __toString() {
return "";
}
}
class Z extends S {}
class A {
public const object CONST1 = S;
public const S CONST2 = S;
public const S|Stringable CONST3 = S;
public const S CONST4 = S;
public const ?S CONST5 = S;
}
class B extends A {
public const S CONST1 = Z;
public const Z CONST2 = Z;
public const S CONST3 = Z;
public const S&Stringable CONST4 = Z;
public const (S&Stringable)|null CONST5 = Z;
}
define("S", new S());
define("Z", new Z());
var_dump(B::CONST1);
var_dump(B::CONST2);
var_dump(B::CONST3);
var_dump(B::CONST4);
var_dump(B::CONST5);
?>
--EXPECTF--
object(Z)#%d (%d) {
}
object(Z)#%d (%d) {
}
object(Z)#%d (%d) {
}
object(Z)#%d (%d) {
}
object(Z)#%d (%d) {
}

View file

@ -0,0 +1,16 @@
--TEST--
Typed class constants (inheritance; private constants)
--FILE--
<?php
class A {
private const int CONST1 = 1;
}
class B extends A {
public const string CONST1 = 'a';
}
var_dump(B::CONST1);
?>
--EXPECT--
string(1) "a"

View file

@ -0,0 +1,39 @@
--TEST--
Typed class constants (composition; traits)
--FILE--
<?php
const G = new stdClass();
enum E {
case Case1;
}
trait T {
public const int CONST1 = 1;
public const ?array CONST2 = [];
public const E CONST3 = E::Case1;
public const stdClass CONST4 = G;
}
class C {
use T;
public const int CONST1 = 1;
public const ?array CONST2 = [];
public const E CONST3 = E::Case1;
public const stdClass CONST4 = G;
}
var_dump(C::CONST1);
var_dump(C::CONST2);
var_dump(C::CONST3);
var_dump(C::CONST4);
?>
--EXPECT--
int(1)
array(0) {
}
enum(E::Case1)
object(stdClass)#1 (0) {
}

View file

@ -0,0 +1,26 @@
--TEST--
Typed class constants (redefinition; interfaces and traits)
--FILE--
<?php
enum E {
case Case1;
}
interface I {
public const E CONST1 = E::Case1;
}
trait T {
public const E CONST1 = E::Case1;
}
class C implements I {
use T;
public const E CONST1 = E::Case1;
}
var_dump(C::CONST1);
?>
--EXPECT--
enum(E::Case1)

View file

@ -0,0 +1,10 @@
--TEST--
Typed class constants (type mismatch; simple)
--FILE--
<?php
class A {
public const string CONST1 = 1;
}
?>
--EXPECTF--
Fatal error: Cannot use int as value for class constant A::CONST1 of type string in %s on line %d

View file

@ -0,0 +1,15 @@
--TEST--
Typed class constants (type error)
--FILE--
<?php
class A1 {
const ?B1 C = null;
}
class A2 extends A1 {
const ?B2 C = null;
}
?>
--EXPECTF--
Fatal error: Type of A2::C must be compatible with A1::C of type ?B1 in %s on line %d

View file

@ -0,0 +1,21 @@
--TEST--
Typed class constants (static type error)
--FILE--
<?php
enum E1 {
const static C = E2::Foo;
}
enum E2 {
case Foo;
}
try {
var_dump(E1::C);
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
Cannot assign E2 to class constant E1::C of type static

View file

@ -0,0 +1,15 @@
--TEST--
Typed class constants with static in union
--FILE--
<?php
class A {
public const static|B CONST1 = B;
public const static|stdClass|B CONST2 = B;
}
class B {}
define("B", new B());
var_dump(new A());
?>
--EXPECT--
object(A)#2 (0) {
}

View file

@ -0,0 +1,26 @@
--TEST--
Typed class constants (type mismatch; runtime simple)
--FILE--
<?php
class A {
public const int CONST1 = C;
}
define("C", "c");
try {
var_dump(A::CONST1);
} catch (TypeError $exception) {
echo $exception->getMessage() . "\n";
}
try {
var_dump(A::CONST1);
} catch (TypeError $exception) {
echo $exception->getMessage() . "\n";
}
?>
--EXPECT--
Cannot assign string to class constant A::CONST1 of type int
Cannot assign string to class constant A::CONST1 of type int

View file

@ -0,0 +1,25 @@
--TEST--
Typed class constants (type mismatch; runtime object)
--FILE--
<?php
class A {
public const string CONST1 = C;
}
define('C', new stdClass);
try {
var_dump(A::CONST1);
} catch (TypeError $exception) {
echo $exception->getMessage() . "\n";
}
try {
var_dump(A::CONST1);
} catch (TypeError $exception) {
echo $exception->getMessage() . "\n";
}
?>
--EXPECT--
Cannot assign stdClass to class constant A::CONST1 of type string
Cannot assign stdClass to class constant A::CONST1 of type string

View file

@ -0,0 +1,10 @@
--TEST--
Typed class constants (type not allowed; callable)
--FILE--
<?php
class A {
public const callable CONST1 = 1;
}
?>
--EXPECTF--
Fatal error: Class constant A::CONST1 cannot have type callable in %s on line %d

View file

@ -0,0 +1,10 @@
--TEST--
Typed class constants (type not allowed; void)
--FILE--
<?php
class A {
public const void CONST1 = 1;
}
?>
--EXPECTF--
Fatal error: Class constant A::CONST1 cannot have type void in %s on line %d

View file

@ -0,0 +1,10 @@
--TEST--
Typed class constants (type not allowed; never)
--FILE--
<?php
class A {
public const never CONST1 = 1;
}
?>
--EXPECTF--
Fatal error: Class constant A::CONST1 cannot have type never in %s on line %d

View file

@ -0,0 +1,25 @@
--TEST--
Typed class constants (type mismatch; runtime)
--FILE--
<?php
class A {
public const stdClass&Stringable CONST1 = C;
}
define("C", new stdClass);
try {
var_dump(A::CONST1);
} catch (TypeError $exception) {
echo $exception->getMessage() . "\n";
}
try {
var_dump(A::CONST1);
} catch (TypeError $exception) {
echo $exception->getMessage() . "\n";
}
?>
--EXPECT--
Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable
Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable

View file

@ -0,0 +1,40 @@
--TEST--
Typed class constants (type mismatch; runtime)
--FILE--
<?php
class A {
public const stdClass&Stringable CONST1 = C;
public const stdClass&Stringable CONST2 = A::CONST1;
}
define("C", new stdClass);
try {
var_dump(A::CONST2);
} catch (TypeError $exception) {
echo $exception->getMessage() . "\n";
}
try {
var_dump(A::CONST2);
} catch (TypeError $exception) {
echo $exception->getMessage() . "\n";
}
try {
var_dump(A::CONST1);
} catch (TypeError $exception) {
echo $exception->getMessage() . "\n";
}
try {
var_dump(A::CONST1);
} catch (TypeError $exception) {
echo $exception->getMessage() . "\n";
}
?>
--EXPECT--
Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable
Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable
Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable
Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable

View file

@ -0,0 +1,25 @@
--TEST--
Typed class constants (type coercion is unsupported)
--FILE--
<?php
class S {
public function __toString() {
echo "Side effect!\n";
return 'S';
}
}
class A {
public const string S = S;
}
define("S", new S());
try {
var_dump(A::S);
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
Cannot assign S to class constant A::S of type string

View file

@ -0,0 +1,66 @@
--TEST--
Typed class constants (declaration; compile-type simple)
--FILE--
<?php
class A {
public const null CONST1 = null;
public const false CONST2 = false;
public const true CONST3 = true;
public const bool CONST4 = true;
public const int CONST5 = 0;
public const float CONST6 = 3.14;
public const float CONST7 = 3;
public const string CONST8 = "";
public const array CONST9 = [];
public const array|string CONST10 = "";
public const array|string|null CONST11 = null;
}
var_dump(A::CONST1);
var_dump(A::CONST1);
var_dump(A::CONST2);
var_dump(A::CONST2);
var_dump(A::CONST3);
var_dump(A::CONST3);
var_dump(A::CONST4);
var_dump(A::CONST4);
var_dump(A::CONST5);
var_dump(A::CONST5);
var_dump(A::CONST6);
var_dump(A::CONST6);
var_dump(A::CONST7);
var_dump(A::CONST7);
var_dump(A::CONST8);
var_dump(A::CONST8);
var_dump(A::CONST9);
var_dump(A::CONST9);
var_dump(A::CONST10);
var_dump(A::CONST10);
var_dump(A::CONST11);
var_dump(A::CONST11);
?>
--EXPECT--
NULL
NULL
bool(false)
bool(false)
bool(true)
bool(true)
bool(true)
bool(true)
int(0)
int(0)
float(3.14)
float(3.14)
float(3)
float(3)
string(0) ""
string(0) ""
array(0) {
}
array(0) {
}
string(0) ""
string(0) ""
NULL
NULL

View file

@ -0,0 +1,52 @@
--TEST--
Typed class constants (declaration; runtime)
--FILE--
<?php
class A {
public const object CONST1 = C;
public const ?object CONST2 = C;
public const B|stdClass CONST3 = C;
public const B&Stringable CONST4 = C;
public const (B&Stringable)|null CONST5 = C;
}
class B implements Stringable {
public function __toString() {
return "";
}
}
const C = new B();
var_dump(A::CONST1);
var_dump(A::CONST1);
var_dump(A::CONST2);
var_dump(A::CONST2);
var_dump(A::CONST3);
var_dump(A::CONST3);
var_dump(A::CONST4);
var_dump(A::CONST4);
var_dump(A::CONST5);
var_dump(A::CONST5);
?>
--EXPECTF--
object(B)#%d (%d) {
}
object(B)#%d (%d) {
}
object(B)#%d (%d) {
}
object(B)#%d (%d) {
}
object(B)#%d (%d) {
}
object(B)#%d (%d) {
}
object(B)#%d (%d) {
}
object(B)#%d (%d) {
}
object(B)#%d (%d) {
}
object(B)#%d (%d) {
}

View file

@ -0,0 +1,52 @@
--TEST--
Typed enum constants (self/static)
--FILE--
<?php
enum E {
public const E CONST1 = E::Foo;
public const self CONST2 = E::Foo;
public const static CONST3 = E::Foo;
public const static|stdClass CONST4 = E::Foo;
case Foo;
}
class A {
public const E ENUM_CONST = E::Foo;
public const E CONST1 = E::CONST1;
public const E CONST2 = E::CONST2;
public const E CONST3 = E::CONST3;
public const E CONST4 = E::CONST4;
}
var_dump(A::ENUM_CONST);
var_dump(A::ENUM_CONST);
var_dump(A::CONST1);
var_dump(A::CONST1);
var_dump(A::CONST2);
var_dump(A::CONST2);
var_dump(E::CONST3);
var_dump(E::CONST3);
var_dump(A::CONST3);
var_dump(A::CONST3);
var_dump(E::CONST4);
var_dump(E::CONST4);
var_dump(A::CONST4);
var_dump(A::CONST4);
?>
--EXPECT--
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)
enum(E::Foo)

View file

@ -1412,6 +1412,34 @@ static zend_result update_property(zval *val, zend_property_info *prop_info) {
return zval_update_constant_ex(val, prop_info->ce);
}
ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, const zend_string *name, zend_class_entry *scope)
{
ZEND_ASSERT(Z_TYPE(c->value) == IS_CONSTANT_AST);
if (EXPECTED(!ZEND_TYPE_IS_SET(c->type) || ZEND_TYPE_PURE_MASK(c->type) == MAY_BE_ANY)) {
return zval_update_constant_ex(&c->value, scope);
}
zval tmp;
ZVAL_COPY(&tmp, &c->value);
zend_result result = zval_update_constant_ex(&tmp, scope);
if (result == FAILURE) {
zval_ptr_dtor(&tmp);
return FAILURE;
}
if (UNEXPECTED(!zend_verify_class_constant_type(c, name, &tmp))) {
zval_ptr_dtor(&tmp);
return FAILURE;
}
zval_ptr_dtor(&c->value);
ZVAL_COPY_VALUE(&c->value, &tmp);
return SUCCESS;
}
ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /* {{{ */
{
zend_class_mutable_data *mutable_data = NULL;
@ -1470,7 +1498,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /
}
val = &c->value;
if (UNEXPECTED(zval_update_constant_ex(val, c->ce) != SUCCESS)) {
if (UNEXPECTED(zend_update_class_constant(c, name, c->ce) != SUCCESS)) {
return FAILURE;
}
}
@ -4532,7 +4560,7 @@ 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 flags, zend_string *doc_comment) /* {{{ */
ZEND_API zend_class_constant *zend_declare_typed_class_constant(zend_class_entry *ce, zend_string *name, zval *value, int flags, zend_string *doc_comment, zend_type type) /* {{{ */
{
zend_class_constant *c;
@ -4561,6 +4589,8 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
c->doc_comment = doc_comment;
c->attributes = NULL;
c->ce = ce;
c->type = type;
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS;
@ -4576,7 +4606,11 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
return c;
}
/* }}} */
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)
{
return zend_declare_typed_class_constant(ce, name, value, flags, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE(0));
}
ZEND_API void zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value) /* {{{ */
{

View file

@ -429,6 +429,7 @@ ZEND_API void zend_declare_property_double(zend_class_entry *ce, const char *nam
ZEND_API void zend_declare_property_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value, int access_type);
ZEND_API void zend_declare_property_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_len, int access_type);
ZEND_API zend_class_constant *zend_declare_typed_class_constant(zend_class_entry *ce, zend_string *name, zval *value, int access_type, zend_string *doc_comment, zend_type type);
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 void zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value);
ZEND_API void zend_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length);
@ -438,6 +439,7 @@ ZEND_API void zend_declare_class_constant_double(zend_class_entry *ce, const cha
ZEND_API void zend_declare_class_constant_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_length);
ZEND_API void zend_declare_class_constant_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value);
ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, const zend_string *name, zend_class_entry *scope);
ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type);
ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type);

View file

@ -161,7 +161,6 @@ enum _zend_ast_kind {
ZEND_AST_CATCH,
ZEND_AST_PROP_GROUP,
ZEND_AST_PROP_ELEM,
ZEND_AST_CONST_ELEM,
// Pseudo node for initializing enums
ZEND_AST_CONST_ENUM_INIT,
@ -170,6 +169,7 @@ enum _zend_ast_kind {
ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_FOREACH,
ZEND_AST_ENUM_CASE,
ZEND_AST_CONST_ELEM,
/* 5 child nodes */
ZEND_AST_PARAM = 5 << ZEND_AST_NUM_CHILDREN_SHIFT,

View file

@ -7693,7 +7693,7 @@ static void zend_check_trait_alias_modifiers(uint32_t attr) /* {{{ */
}
/* }}} */
static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */
static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr_ast)
{
zend_ast_list *list = zend_ast_get_list(ast);
zend_class_entry *ce = CG(active_class_entry);
@ -7705,9 +7705,24 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as
zend_ast *name_ast = const_ast->child[0];
zend_ast **value_ast_ptr = &const_ast->child[1];
zend_ast *doc_comment_ast = const_ast->child[2];
zend_ast *type_ast = const_ast->child[3];
zend_string *name = zval_make_interned_string(zend_ast_get_zval(name_ast));
zend_string *doc_comment = doc_comment_ast ? zend_string_copy(zend_ast_get_str(doc_comment_ast)) : NULL;
zval value_zv;
zend_type type = ZEND_TYPE_INIT_NONE(0);
if (type_ast) {
type = zend_compile_typename(type_ast, /* force_allow_null */ 0);
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
if (type_mask != MAY_BE_ANY && (type_mask & (MAY_BE_CALLABLE|MAY_BE_VOID|MAY_BE_NEVER))) {
zend_string *type_str = zend_type_to_string(type);
zend_error_noreturn(E_COMPILE_ERROR, "Class constant %s::%s cannot have type %s",
ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(type_str));
}
}
if (UNEXPECTED((flags & ZEND_ACC_PRIVATE) && (flags & ZEND_ACC_FINAL))) {
zend_error_noreturn(
@ -7717,14 +7732,21 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as
}
zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false);
c = zend_declare_class_constant_ex(ce, name, &value_zv, flags, doc_comment);
if (!Z_CONSTANT(value_zv) && ZEND_TYPE_IS_SET(type) && !zend_is_valid_default_value(type, &value_zv)) {
zend_string *type_str = zend_type_to_string(type);
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use %s as value for class constant %s::%s of type %s",
zend_zval_type_name(&value_zv), ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(type_str));
}
c = zend_declare_typed_class_constant(ce, name, &value_zv, flags, doc_comment, type);
if (attr_ast) {
zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, 0);
}
}
}
/* }}} */
static void zend_compile_class_const_group(zend_ast *ast) /* {{{ */
{

View file

@ -407,6 +407,7 @@ typedef struct _zend_class_constant {
zend_string *doc_comment;
HashTable *attributes;
zend_class_entry *ce;
zend_type type;
} zend_class_constant;
#define ZEND_CLASS_CONST_FLAGS(c) Z_CONSTANT_FLAGS((c)->value)

View file

@ -361,7 +361,7 @@ ZEND_API zval *zend_get_class_constant_ex(zend_string *class_name, zend_string *
}
MARK_CONSTANT_VISITED(ret_constant);
ret = zval_update_constant_ex(ret_constant, c->ce);
ret = zend_update_class_constant(c, constant_name, c->ce);
RESET_CONSTANT_VISITED(ret_constant);
if (UNEXPECTED(ret != SUCCESS)) {

View file

@ -820,6 +820,16 @@ ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool s
return zend_verify_weak_scalar_type_hint(type_mask, arg);
}
ZEND_COLD zend_never_inline void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant)
{
zend_string *type_str = zend_type_to_string(c->type);
zend_type_error("Cannot assign %s to class constant %s::%s of type %s",
zend_zval_type_name(constant), ZSTR_VAL(c->ce->name), ZSTR_VAL(name), ZSTR_VAL(type_str));
zend_string_release(type_str);
}
ZEND_COLD zend_never_inline void zend_verify_property_type_error(const zend_property_info *info, const zval *property)
{
zend_string *type_str;
@ -908,7 +918,7 @@ static const zend_class_entry *resolve_single_class_type(zend_string *name, cons
}
static zend_always_inline const zend_class_entry *zend_ce_from_type(
const zend_property_info *info, const zend_type *type) {
const zend_class_entry *scope, const zend_type *type) {
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*type));
zend_string *name = ZEND_TYPE_NAME(*type);
if (ZSTR_HAS_CE_CACHE(name)) {
@ -918,52 +928,61 @@ static zend_always_inline const zend_class_entry *zend_ce_from_type(
}
return ce;
}
return resolve_single_class_type(name, info->ce);
return resolve_single_class_type(name, scope);
}
static bool zend_check_intersection_for_property_class_type(zend_type_list *intersection_type_list,
const zend_property_info *info, const zend_class_entry *object_ce)
static bool zend_check_intersection_for_property_or_class_constant_class_type(
const zend_class_entry *scope, zend_type_list *intersection_type_list, const zend_class_entry *value_ce)
{
zend_type *list_type;
ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) {
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
const zend_class_entry *ce = zend_ce_from_type(info, list_type);
if (!ce || !instanceof_function(object_ce, ce)) {
const zend_class_entry *ce = zend_ce_from_type(scope, list_type);
if (!ce || !instanceof_function(value_ce, ce)) {
return false;
}
} ZEND_TYPE_LIST_FOREACH_END();
return true;
}
static bool zend_check_and_resolve_property_class_type(
const zend_property_info *info, const zend_class_entry *object_ce) {
if (ZEND_TYPE_HAS_LIST(info->type)) {
static bool zend_check_and_resolve_property_or_class_constant_class_type(
const zend_class_entry *scope, zend_type member_type, const zend_class_entry *value_ce) {
if (ZEND_TYPE_HAS_LIST(member_type)) {
zend_type *list_type;
if (ZEND_TYPE_IS_INTERSECTION(info->type)) {
return zend_check_intersection_for_property_class_type(
ZEND_TYPE_LIST(info->type), info, object_ce);
if (ZEND_TYPE_IS_INTERSECTION(member_type)) {
return zend_check_intersection_for_property_or_class_constant_class_type(
scope, ZEND_TYPE_LIST(member_type), value_ce);
} else {
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) {
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(member_type), list_type) {
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
if (zend_check_intersection_for_property_class_type(
ZEND_TYPE_LIST(*list_type), info, object_ce)) {
if (zend_check_intersection_for_property_or_class_constant_class_type(
scope, ZEND_TYPE_LIST(*list_type), value_ce)) {
return true;
}
continue;
}
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
const zend_class_entry *ce = zend_ce_from_type(info, list_type);
if (ce && instanceof_function(object_ce, ce)) {
const zend_class_entry *ce = zend_ce_from_type(scope, list_type);
if (ce && instanceof_function(value_ce, ce)) {
return true;
}
} ZEND_TYPE_LIST_FOREACH_END();
if ((ZEND_TYPE_PURE_MASK(member_type) & MAY_BE_STATIC)) {
return value_ce == scope;
}
return false;
}
} else {
const zend_class_entry *ce = zend_ce_from_type(info, &info->type);
return ce && instanceof_function(object_ce, ce);
} else if ((ZEND_TYPE_PURE_MASK(member_type) & MAY_BE_STATIC) && value_ce == scope) {
return true;
} else if (ZEND_TYPE_HAS_NAME(member_type)) {
const zend_class_entry *ce = zend_ce_from_type(scope, &member_type);
return ce && instanceof_function(value_ce, ce);
}
return false;
}
static zend_always_inline bool i_zend_check_property_type(const zend_property_info *info, zval *property, bool strict)
@ -974,7 +993,7 @@ static zend_always_inline bool i_zend_check_property_type(const zend_property_in
}
if (ZEND_TYPE_IS_COMPLEX(info->type) && Z_TYPE_P(property) == IS_OBJECT
&& zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(property))) {
&& zend_check_and_resolve_property_or_class_constant_class_type(info->ce, info->type, Z_OBJCE_P(property))) {
return 1;
}
@ -1455,6 +1474,33 @@ static ZEND_COLD void zend_verify_missing_return_type(const zend_function *zf)
zend_verify_return_error(zf, NULL);
}
static zend_always_inline bool zend_check_class_constant_type(zend_class_constant *c, zval *constant)
{
ZEND_ASSERT(!Z_ISREF_P(constant));
if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(c->type, Z_TYPE_P(constant)))) {
return 1;
}
if (((ZEND_TYPE_PURE_MASK(c->type) & MAY_BE_STATIC) || ZEND_TYPE_IS_COMPLEX(c->type)) && Z_TYPE_P(constant) == IS_OBJECT
&& zend_check_and_resolve_property_or_class_constant_class_type(c->ce, c->type, Z_OBJCE_P(constant))) {
return 1;
}
uint32_t type_mask = ZEND_TYPE_FULL_MASK(c->type);
ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_NEVER|MAY_BE_VOID)));
return zend_verify_scalar_type_hint(type_mask, constant, true, false);
}
ZEND_API bool zend_never_inline zend_verify_class_constant_type(zend_class_constant *c, const zend_string *name, zval *constant)
{
if (!zend_check_class_constant_type(c, constant)) {
zend_verify_class_constant_type_error(c, name, constant);
return 0;
}
return 1;
}
static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_use_object_as_array(void)
{
zend_throw_error(NULL, "Cannot use object as array");
@ -3473,7 +3519,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval(
}
if (ZEND_TYPE_IS_COMPLEX(type) && zv_type == IS_OBJECT
&& zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(zv))) {
&& zend_check_and_resolve_property_or_class_constant_class_type(info->ce, info->type, Z_OBJCE_P(zv))) {
return 1;
}

View file

@ -484,6 +484,10 @@ ZEND_API int ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *call);
#define ZEND_CLASS_HAS_TYPE_HINTS(ce) ((ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) == ZEND_ACC_HAS_TYPE_HINTS)
#define ZEND_CLASS_HAS_READONLY_PROPS(ce) ((ce->ce_flags & ZEND_ACC_HAS_READONLY_PROPS) == ZEND_ACC_HAS_READONLY_PROPS)
ZEND_API bool zend_verify_class_constant_type(zend_class_constant *c, const zend_string *name, zval *constant);
ZEND_COLD void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant);
ZEND_API bool zend_verify_property_type(const zend_property_info *info, zval *property, bool strict);
ZEND_COLD void zend_verify_property_type_error(const zend_property_info *info, const zval *property);
ZEND_COLD void zend_magic_get_property_type_inconsistency_error(const zend_property_info *info, const zval *property);

View file

@ -51,6 +51,9 @@ static void add_compatibility_obligation(
static void add_property_compatibility_obligation(
zend_class_entry *ce, const zend_property_info *child_prop,
const zend_property_info *parent_prop);
static void add_class_constant_compatibility_obligation(
zend_class_entry *ce, const zend_class_constant *child_const,
const zend_class_constant *parent_const, const zend_string *const_name);
static void ZEND_COLD emit_incompatible_method_error(
const zend_function *child, zend_class_entry *child_scope,
@ -1359,6 +1362,29 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en
}
/* }}} */
static void emit_incompatible_class_constant_error(
const zend_class_constant *child, const zend_class_constant *parent, const zend_string *const_name) {
zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce);
zend_error_noreturn(E_COMPILE_ERROR,
"Type of %s::%s must be compatible with %s::%s of type %s",
ZSTR_VAL(child->ce->name),
ZSTR_VAL(const_name),
ZSTR_VAL(parent->ce->name),
ZSTR_VAL(const_name),
ZSTR_VAL(type_str));
}
static inheritance_status class_constant_types_compatible(const zend_class_constant *parent, const zend_class_constant *child)
{
ZEND_ASSERT(ZEND_TYPE_IS_SET(parent->type));
if (!ZEND_TYPE_IS_SET(child->type)) {
return INHERITANCE_ERROR;
}
return zend_perform_covariant_type_check(child->ce, child->type, parent->ce, parent->type);
}
static void do_inherit_class_constant(zend_string *name, zend_class_constant *parent_const, zend_class_entry *ce) /* {{{ */
{
zval *zv = zend_hash_find_known_hash(&ce->constants_table, name);
@ -1366,9 +1392,14 @@ static void do_inherit_class_constant(zend_string *name, zend_class_constant *pa
if (zv != NULL) {
c = (zend_class_constant*)Z_PTR_P(zv);
if (UNEXPECTED((ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_PPP_MASK) > (ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PPP_MASK))) {
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");
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))) {
@ -1377,6 +1408,15 @@ static void do_inherit_class_constant(zend_string *name, zend_class_constant *pa
ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(parent_const->ce->name), ZSTR_VAL(name)
);
}
if (!(ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PRIVATE) && UNEXPECTED(ZEND_TYPE_IS_SET(parent_const->type))) {
inheritance_status status = class_constant_types_compatible(parent_const, c);
if (status == INHERITANCE_ERROR) {
emit_incompatible_class_constant_error(c, parent_const, name);
} else if (status == INHERITANCE_UNRESOLVED) {
add_class_constant_compatibility_obligation(ce, c, parent_const, 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;
@ -1641,12 +1681,18 @@ static zend_always_inline bool check_trait_property_or_constant_value_compatibil
/* if any of the values is a constant, we try to resolve it */
if (UNEXPECTED(Z_TYPE_P(op1) == IS_CONSTANT_AST)) {
ZVAL_COPY_OR_DUP(&op1_tmp, op1);
zval_update_constant_ex(&op1_tmp, ce);
if (UNEXPECTED(zval_update_constant_ex(&op1_tmp, ce) != SUCCESS)) {
zval_ptr_dtor(&op1_tmp);
return false;
}
op1 = &op1_tmp;
}
if (UNEXPECTED(Z_TYPE_P(op2) == IS_CONSTANT_AST)) {
ZVAL_COPY_OR_DUP(&op2_tmp, op2);
zval_update_constant_ex(&op2_tmp, ce);
if (UNEXPECTED(zval_update_constant_ex(&op2_tmp, ce) != SUCCESS)) {
zval_ptr_dtor(&op2_tmp);
return false;
}
op2 = &op2_tmp;
}
@ -2231,8 +2277,22 @@ static zend_class_entry* find_first_constant_definition(zend_class_entry *ce, ze
}
/* }}} */
static bool do_trait_constant_check(zend_class_entry *ce, zend_class_constant *trait_constant, zend_string *name, zend_class_entry **traits, size_t current_trait) /* {{{ */
{
static void emit_incompatible_trait_constant_error(
zend_class_entry *ce, zend_class_constant *existing_constant, zend_class_constant *trait_constant, zend_string *name,
zend_class_entry **traits, size_t current_trait
) {
zend_error_noreturn(E_COMPILE_ERROR,
"%s and %s define the same constant (%s) in the composition of %s. However, the definition differs and is considered incompatible. Class was composed",
ZSTR_VAL(find_first_constant_definition(ce, traits, current_trait, name, existing_constant->ce)->name),
ZSTR_VAL(trait_constant->ce->name),
ZSTR_VAL(name),
ZSTR_VAL(ce->name)
);
}
static bool do_trait_constant_check(
zend_class_entry *ce, zend_class_constant *trait_constant, zend_string *name, zend_class_entry **traits, size_t current_trait
) {
uint32_t flags_mask = ZEND_ACC_PPP_MASK | ZEND_ACC_FINAL;
zval *zv = zend_hash_find_known_hash(&ce->constants_table, name);
@ -2243,21 +2303,32 @@ static bool do_trait_constant_check(zend_class_entry *ce, zend_class_constant *t
zend_class_constant *existing_constant = Z_PTR_P(zv);
if ((ZEND_CLASS_CONST_FLAGS(trait_constant) & flags_mask) != (ZEND_CLASS_CONST_FLAGS(existing_constant) & flags_mask) ||
!check_trait_property_or_constant_value_compatibility(ce, &trait_constant->value, &existing_constant->value)) {
if ((ZEND_CLASS_CONST_FLAGS(trait_constant) & flags_mask) != (ZEND_CLASS_CONST_FLAGS(existing_constant) & flags_mask)) {
emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait);
return false;
}
if (ZEND_TYPE_IS_SET(trait_constant->type) != ZEND_TYPE_IS_SET(existing_constant->type)) {
emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait);
return false;
} else if (ZEND_TYPE_IS_SET(trait_constant->type)) {
inheritance_status status1 = zend_perform_covariant_type_check(ce, existing_constant->type, traits[current_trait], trait_constant->type);
inheritance_status status2 = zend_perform_covariant_type_check(traits[current_trait], trait_constant->type, ce, existing_constant->type);
if (status1 == INHERITANCE_ERROR || status2 == INHERITANCE_ERROR) {
emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait);
return false;
}
}
if (!check_trait_property_or_constant_value_compatibility(ce, &trait_constant->value, &existing_constant->value)) {
/* There is an existing constant of the same name, and it conflicts with the new one, so let's throw a fatal error */
zend_error_noreturn(E_COMPILE_ERROR,
"%s and %s define the same constant (%s) in the composition of %s. However, the definition differs and is considered incompatible. Class was composed",
ZSTR_VAL(find_first_constant_definition(ce, traits, current_trait, name, existing_constant->ce)->name),
ZSTR_VAL(trait_constant->ce->name),
ZSTR_VAL(name),
ZSTR_VAL(ce->name));
emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait);
return false;
}
/* There is an existing constant which is compatible with the new one, so no need to add it */
return false;
}
/* }}} */
static void zend_do_traits_constant_binding(zend_class_entry *ce, zend_class_entry **traits) /* {{{ */
{
@ -2508,7 +2579,8 @@ typedef struct {
enum {
OBLIGATION_DEPENDENCY,
OBLIGATION_COMPATIBILITY,
OBLIGATION_PROPERTY_COMPATIBILITY
OBLIGATION_PROPERTY_COMPATIBILITY,
OBLIGATION_CLASS_CONSTANT_COMPATIBILITY
} type;
union {
zend_class_entry *dependency_ce;
@ -2524,6 +2596,11 @@ typedef struct {
const zend_property_info *parent_prop;
const zend_property_info *child_prop;
};
struct {
const zend_string *const_name;
const zend_class_constant *parent_const;
const zend_class_constant *child_const;
};
};
} variance_obligation;
@ -2599,6 +2676,18 @@ static void add_property_compatibility_obligation(
zend_hash_next_index_insert_ptr(obligations, obligation);
}
static void add_class_constant_compatibility_obligation(
zend_class_entry *ce, const zend_class_constant *child_const,
const zend_class_constant *parent_const, const zend_string *const_name) {
HashTable *obligations = get_or_init_obligations_for_class(ce);
variance_obligation *obligation = emalloc(sizeof(variance_obligation));
obligation->type = OBLIGATION_CLASS_CONSTANT_COMPATIBILITY;
obligation->const_name = const_name;
obligation->child_const = child_const;
obligation->parent_const = parent_const;
zend_hash_next_index_insert_ptr(obligations, obligation);
}
static void resolve_delayed_variance_obligations(zend_class_entry *ce);
static void check_variance_obligation(variance_obligation *obligation) {
@ -2622,13 +2711,19 @@ static void check_variance_obligation(variance_obligation *obligation) {
&obligation->parent_fn, obligation->parent_scope, status);
}
/* Either the compatibility check was successful or only threw a warning. */
} else {
ZEND_ASSERT(obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY);
} else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) {
inheritance_status status =
property_types_compatible(obligation->parent_prop, obligation->child_prop);
if (status != INHERITANCE_SUCCESS) {
emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop);
}
} else {
ZEND_ASSERT(obligation->type == OBLIGATION_CLASS_CONSTANT_COMPATIBILITY);
inheritance_status status =
class_constant_types_compatible(obligation->parent_const, obligation->child_const);
if (status != INHERITANCE_SUCCESS) {
emit_incompatible_class_constant_error(obligation->child_const, obligation->parent_const, obligation->const_name);
}
}
}
@ -3092,6 +3187,7 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
zend_string *key;
zend_function *parent_func;
zend_property_info *parent_info;
zend_class_constant *parent_const;
inheritance_status overall_status = INHERITANCE_SUCCESS;
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, parent_func) {
@ -3130,6 +3226,25 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
}
} ZEND_HASH_FOREACH_END();
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&parent_ce->constants_table, key, parent_const) {
zval *zv;
if ((ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PRIVATE) || !ZEND_TYPE_IS_SET(parent_const->type)) {
continue;
}
zv = zend_hash_find_known_hash(&ce->constants_table, key);
if (zv) {
zend_class_constant *child_const = Z_PTR_P(zv);
if (ZEND_TYPE_IS_SET(child_const->type)) {
inheritance_status status = class_constant_types_compatible(parent_const, child_const);
ZEND_ASSERT(status != INHERITANCE_WARNING);
if (UNEXPECTED(status != INHERITANCE_SUCCESS)) {
return status;
}
}
}
} ZEND_HASH_FOREACH_END();
return overall_status;
}
/* }}} */

View file

@ -267,7 +267,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> echo_expr_list unset_variables catch_name_list catch_list optional_variable parameter_list class_statement_list
%type <ast> implements_list case_list if_stmt_without_else
%type <ast> non_empty_parameter_list argument_list non_empty_argument_list property_list
%type <ast> class_const_list class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs
%type <ast> class_const_list first_class_const_decl class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs
%type <ast> ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars
%type <ast> lexical_var_list encaps_list
%type <ast> array_pair non_empty_array_pair_list array_pair_list possible_array_pair
@ -812,7 +812,6 @@ parameter:
NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL); }
;
optional_type_without_static:
%empty { $$ = NULL; }
| type_expr_without_static { $$ = $1; }
@ -1077,15 +1076,21 @@ property:
class_const_list:
class_const_list ',' class_const_decl { $$ = zend_ast_list_add($1, $3); }
| class_const_decl { $$ = zend_ast_create_list(1, ZEND_AST_CLASS_CONST_DECL, $1); }
| first_class_const_decl { $$ = zend_ast_create_list(1, ZEND_AST_CLASS_CONST_DECL, $1); }
;
first_class_const_decl:
T_STRING '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), NULL); }
| semi_reserved '=' expr backup_doc_comment { zval zv; if (zend_lex_tstring(&zv, $1) == FAILURE) { YYABORT; } $$ = zend_ast_create(ZEND_AST_CONST_ELEM, zend_ast_create_zval(&zv), $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), NULL); }
| type_expr identifier '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $2, $4, ($5 ? zend_ast_create_zval_from_str($5) : NULL), $1); }
;
class_const_decl:
identifier '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL)); }
identifier '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), NULL); }
;
const_decl:
T_STRING '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL)); }
T_STRING '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), NULL); }
;
echo_expr_list:
@ -1317,8 +1322,8 @@ function_call:
{ $$ = zend_ast_create(ZEND_AST_STATIC_CALL, $1, $3, $4); }
| variable_class_name T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
{ $$ = zend_ast_create(ZEND_AST_STATIC_CALL, $1, $3, $4); }
| callable_expr { $<num>$ = CG(zend_lineno); } argument_list {
$$ = zend_ast_create(ZEND_AST_CALL, $1, $3);
| callable_expr { $<num>$ = CG(zend_lineno); } argument_list {
$$ = zend_ast_create(ZEND_AST_CALL, $1, $3);
$$->lineno = $<num>2;
}
;

View file

@ -5991,8 +5991,7 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
}
}
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
zval_update_constant_ex(value, c->ce);
if (UNEXPECTED(EG(exception) != NULL)) {
if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
FREE_OP2();
HANDLE_EXCEPTION();

18
Zend/zend_vm_execute.h generated
View file

@ -7265,8 +7265,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_CONS
}
}
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
zval_update_constant_ex(value, c->ce);
if (UNEXPECTED(EG(exception) != NULL)) {
if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
HANDLE_EXCEPTION();
@ -8419,8 +8418,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_CONS
}
}
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
zval_update_constant_ex(value, c->ce);
if (UNEXPECTED(EG(exception) != NULL)) {
if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
FREE_OP(opline->op2_type, opline->op2.var);
HANDLE_EXCEPTION();
@ -25084,8 +25082,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_
}
}
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
zval_update_constant_ex(value, c->ce);
if (UNEXPECTED(EG(exception) != NULL)) {
if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
HANDLE_EXCEPTION();
@ -25647,8 +25644,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_
}
}
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
zval_update_constant_ex(value, c->ce);
if (UNEXPECTED(EG(exception) != NULL)) {
if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
FREE_OP(opline->op2_type, opline->op2.var);
HANDLE_EXCEPTION();
@ -34232,8 +34228,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUS
}
}
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
zval_update_constant_ex(value, c->ce);
if (UNEXPECTED(EG(exception) != NULL)) {
if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
HANDLE_EXCEPTION();
@ -34585,8 +34580,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUS
}
}
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
zval_update_constant_ex(value, c->ce);
if (UNEXPECTED(EG(exception) != NULL)) {
if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
FREE_OP(opline->op2_type, opline->op2.var);
HANDLE_EXCEPTION();

View file

@ -3749,15 +3749,16 @@ static bool preload_try_resolve_constants(zend_class_entry *ce)
bool ok, changed, was_changed = false;
zend_class_constant *c;
zval *val;
zend_string *key;
EG(exception) = (void*)(uintptr_t)-1; /* prevent error reporting */
do {
ok = true;
changed = false;
ZEND_HASH_MAP_FOREACH_PTR(&ce->constants_table, c) {
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->constants_table, key, c) {
val = &c->value;
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
if (EXPECTED(zval_update_constant_ex(val, c->ce) == SUCCESS)) {
if (EXPECTED(zend_update_class_constant(c, key, c->ce) == SUCCESS)) {
was_changed = changed = true;
} else {
ok = false;

View file

@ -692,12 +692,12 @@ static void zend_file_cache_serialize_class_constant(zval *z
SERIALIZE_PTR(c->ce);
zend_file_cache_serialize_zval(&c->value, script, info, buf);
if (c->doc_comment) {
SERIALIZE_STR(c->doc_comment);
}
SERIALIZE_ATTRIBUTES(c->attributes);
zend_file_cache_serialize_type(&c->type, script, info, buf);
}
}
}
@ -1531,6 +1531,7 @@ static void zend_file_cache_unserialize_class_constant(zval *
UNSERIALIZE_STR(c->doc_comment);
}
UNSERIALIZE_ATTRIBUTES(c->attributes);
zend_file_cache_unserialize_type(&c->type, c->ce, script, buf);
}
}
}

View file

@ -840,6 +840,7 @@ static void zend_persist_class_constant(zval *zv)
if (c->attributes) {
c->attributes = zend_persist_attributes(c->attributes);
}
zend_persist_type(&c->type);
}
zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce)

View file

@ -399,6 +399,7 @@ static void zend_persist_class_constant_calc(zval *zv)
if (c->attributes) {
zend_persist_attributes_calc(c->attributes);
}
zend_persist_type_calc(&c->type);
}
}

View file

@ -297,7 +297,7 @@ static zval *reflection_instantiate(zend_class_entry *pce, zval *object) /* {{{
static void _const_string(smart_str *str, char *name, zval *value, char *indent);
static void _function_string(smart_str *str, zend_function *fptr, zend_class_entry *scope, char* indent);
static void _property_string(smart_str *str, zend_property_info *prop, const char *prop_name, char* indent);
static void _class_const_string(smart_str *str, char *name, zend_class_constant *c, char* indent);
static void _class_const_string(smart_str *str, zend_string *name, zend_class_constant *c, char* indent);
static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, char *indent);
static void _extension_string(smart_str *str, zend_module_entry *module, char *indent);
static void _zend_extension_string(smart_str *str, zend_extension *extension, char *indent);
@ -384,7 +384,7 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, char
zend_class_constant *c;
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(CE_CONSTANTS_TABLE(ce), key, c) {
_class_const_string(str, ZSTR_VAL(key), c, ZSTR_VAL(sub_indent));
_class_const_string(str, key, c, ZSTR_VAL(sub_indent));
if (UNEXPECTED(EG(exception))) {
zend_string_release(sub_indent);
return;
@ -556,17 +556,18 @@ static void _const_string(smart_str *str, char *name, zval *value, char *indent)
/* }}} */
/* {{{ _class_const_string */
static void _class_const_string(smart_str *str, char *name, zend_class_constant *c, char *indent)
static void _class_const_string(smart_str *str, zend_string *name, zend_class_constant *c, char *indent)
{
if (zval_update_constant_ex(&c->value, c->ce) == FAILURE) {
if (Z_TYPE(c->value) == IS_CONSTANT_AST && zend_update_class_constant(c, name, c->ce) == FAILURE) {
return;
}
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);
zend_string *type_str = ZEND_TYPE_IS_SET(c->type) ? zend_type_to_string(c->type) : NULL;
const char *type = type_str ? ZSTR_VAL(type_str) : zend_zval_type_name(&c->value);
smart_str_append_printf(str, "%sConstant [ %s%s %s %s ] { ",
indent, final, visibility, type, name);
indent, final, visibility, type, ZSTR_VAL(name));
if (Z_TYPE(c->value) == IS_ARRAY) {
smart_str_appends(str, "Array");
} else if (Z_TYPE(c->value) == IS_OBJECT) {
@ -578,6 +579,10 @@ static void _class_const_string(smart_str *str, char *name, zend_class_constant
zend_tmp_string_release(tmp_value_str);
}
smart_str_appends(str, " }\n");
if (type_str) {
zend_string_release(type_str);
}
}
/* }}} */
@ -3796,7 +3801,7 @@ ZEND_METHOD(ReflectionClassConstant, __toString)
ZVAL_DEREF(name);
ZEND_ASSERT(Z_TYPE_P(name) == IS_STRING);
_class_const_string(&str, Z_STRVAL_P(name), ref, "");
_class_const_string(&str, Z_STR_P(name), ref, "");
RETURN_STR(smart_str_extract(&str));
}
/* }}} */
@ -3820,6 +3825,39 @@ ZEND_METHOD(ReflectionClassConstant, getName)
}
/* }}} */
/* Returns the type associated with the class constant */
ZEND_METHOD(ReflectionClassConstant, getType)
{
reflection_object *intern;
zend_class_constant *ref;
if (zend_parse_parameters_none() == FAILURE) {
RETURN_THROWS();
}
GET_REFLECTION_OBJECT_PTR(ref);
if (!ZEND_TYPE_IS_SET(ref->type)) {
RETURN_NULL();
}
reflection_type_factory(ref->type, return_value, 1);
}
/* Returns whether class constant has a type */
ZEND_METHOD(ReflectionClassConstant, hasType)
{
reflection_object *intern;
zend_class_constant *ref;
if (zend_parse_parameters_none() == FAILURE) {
RETURN_THROWS();
}
GET_REFLECTION_OBJECT_PTR(ref);
RETVAL_BOOL(ZEND_TYPE_IS_SET(ref->type));
}
static void _class_constant_check_flag(INTERNAL_FUNCTION_PARAMETERS, int mask) /* {{{ */
{
reflection_object *intern;
@ -3887,8 +3925,19 @@ ZEND_METHOD(ReflectionClassConstant, getValue)
}
GET_REFLECTION_OBJECT_PTR(ref);
zval *name = reflection_prop_name(ZEND_THIS);
if (Z_ISUNDEF_P(name)) {
zend_throw_error(NULL,
"Typed property ReflectionClassConstant::$name "
"must not be accessed before initialization");
RETURN_THROWS();
}
if (Z_TYPE(ref->value) == IS_CONSTANT_AST) {
zval_update_constant_ex(&ref->value, ref->ce);
zend_result result = zend_update_class_constant(ref, Z_STR_P(name), ref->ce);
if (result == FAILURE) {
RETURN_THROWS();
}
}
ZVAL_COPY_OR_DUP(return_value, &ref->value);
}
@ -4694,7 +4743,7 @@ ZEND_METHOD(ReflectionClass, getConstants)
array_init(return_value);
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(CE_CONSTANTS_TABLE(ce), key, constant) {
if (UNEXPECTED(zval_update_constant_ex(&constant->value, constant->ce) != SUCCESS)) {
if (UNEXPECTED(Z_TYPE(constant->value) == IS_CONSTANT_AST && zend_update_class_constant(constant, key, constant->ce) != SUCCESS)) {
RETURN_THROWS();
}
@ -4744,7 +4793,7 @@ ZEND_METHOD(ReflectionClass, getConstant)
zend_class_entry *ce;
HashTable *constants_table;
zend_class_constant *c;
zend_string *name;
zend_string *name, *key;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name) == FAILURE) {
RETURN_THROWS();
@ -4752,8 +4801,8 @@ ZEND_METHOD(ReflectionClass, getConstant)
GET_REFLECTION_OBJECT_PTR(ce);
constants_table = CE_CONSTANTS_TABLE(ce);
ZEND_HASH_MAP_FOREACH_PTR(constants_table, c) {
if (UNEXPECTED(zval_update_constant_ex(&c->value, c->ce) != SUCCESS)) {
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(constants_table, key, c) {
if (UNEXPECTED(Z_TYPE(c->value) == IS_CONSTANT_AST && zend_update_class_constant(c, key, c->ce) != SUCCESS)) {
RETURN_THROWS();
}
} ZEND_HASH_FOREACH_END();
@ -6954,7 +7003,7 @@ ZEND_METHOD(ReflectionEnumBackedCase, getBackingValue)
if (Z_TYPE(ref->value) == IS_CONSTANT_AST) {
zval_update_constant_ex(&ref->value, ref->ce);
if (EG(exception)) {
return;
RETURN_THROWS();
}
}

View file

@ -346,7 +346,7 @@ class ReflectionClass implements Reflector
public function getReflectionConstants(?int $filter = null): array {}
/** @tentative-return-type */
public function getConstant(string $name): mixed {}
public function getConstant(string $name): mixed {} // TODO throw exception when the constant doesn't exist
/** @tentative-return-type */
public function getReflectionConstant(string $name): ReflectionClassConstant|false {}
@ -609,6 +609,10 @@ class ReflectionClassConstant implements Reflector
public function getAttributes(?string $name = null, int $flags = 0): array {}
public function isEnumCase(): bool {}
public function hasType(): bool {}
public function getType(): ?ReflectionType {}
}
/** @not-serializable */

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: f640c1b592a7e9e7a8e92195df579bfaaa3da6dc */
* Stub hash: 75d10a475cce503d94bd8471764adf495f0ddd34 */
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)
@ -414,6 +414,10 @@ ZEND_END_ARG_INFO()
#define arginfo_class_ReflectionClassConstant_isEnumCase arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType
#define arginfo_class_ReflectionClassConstant_hasType arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType
#define arginfo_class_ReflectionClassConstant_getType arginfo_class_ReflectionFunctionAbstract_getTentativeReturnType
#define arginfo_class_ReflectionParameter___clone arginfo_class_ReflectionFunctionAbstract___clone
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionParameter___construct, 0, 0, 2)
@ -760,6 +764,8 @@ ZEND_METHOD(ReflectionClassConstant, getDeclaringClass);
ZEND_METHOD(ReflectionClassConstant, getDocComment);
ZEND_METHOD(ReflectionClassConstant, getAttributes);
ZEND_METHOD(ReflectionClassConstant, isEnumCase);
ZEND_METHOD(ReflectionClassConstant, hasType);
ZEND_METHOD(ReflectionClassConstant, getType);
ZEND_METHOD(ReflectionParameter, __construct);
ZEND_METHOD(ReflectionParameter, __toString);
ZEND_METHOD(ReflectionParameter, getName);
@ -1046,6 +1052,8 @@ static const zend_function_entry class_ReflectionClassConstant_methods[] = {
ZEND_ME(ReflectionClassConstant, getDocComment, arginfo_class_ReflectionClassConstant_getDocComment, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionClassConstant, getAttributes, arginfo_class_ReflectionClassConstant_getAttributes, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionClassConstant, isEnumCase, arginfo_class_ReflectionClassConstant_isEnumCase, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionClassConstant, hasType, arginfo_class_ReflectionClassConstant_hasType, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionClassConstant, getType, arginfo_class_ReflectionClassConstant_getType, ZEND_ACC_PUBLIC)
ZEND_FE_END
};

View file

@ -28,11 +28,15 @@ function reflectClassConstant($base, $constant) {
var_dump($constInfo->getDeclaringClass());
echo "getDocComment():\n";
var_dump($constInfo->getDocComment());
echo "hasType():\n";
var_dump($constInfo->hasType());
echo "getType():\n";
echo $constInfo->getType() ?? "NULL";
echo "\n**********************************\n";
}
class TestClass {
public const /** My Doc comment */ PUB = true;
public const bool /** My Doc comment */ PUB = true;
/** Another doc comment */
protected const PROT = 4;
private const PRIV = "keepOut";
@ -76,7 +80,10 @@ object(ReflectionClass)#3 (1) {
}
getDocComment():
string(21) "/** My Doc comment */"
hasType():
bool(true)
getType():
bool
**********************************
**********************************
Reflecting on class constant TestClass::PROT
@ -105,7 +112,10 @@ object(ReflectionClass)#3 (1) {
}
getDocComment():
string(26) "/** Another doc comment */"
hasType():
bool(false)
getType():
NULL
**********************************
**********************************
Reflecting on class constant TestClass::PRIV
@ -134,7 +144,10 @@ object(ReflectionClass)#3 (1) {
}
getDocComment():
bool(false)
hasType():
bool(false)
getType():
NULL
**********************************
**********************************
Reflecting on class constant TestClass::FINAL
@ -163,7 +176,10 @@ object(ReflectionClass)#3 (1) {
}
getDocComment():
bool(false)
hasType():
bool(false)
getType():
NULL
**********************************
**********************************
Reflecting on class constant TestClass::PRIV
@ -192,7 +208,10 @@ object(ReflectionClass)#3 (1) {
}
getDocComment():
bool(false)
hasType():
bool(false)
getType():
NULL
**********************************
Fatal error: Uncaught ReflectionException: Constant TestClass::BAD_CONST does not exist in %s:%d

View file

@ -0,0 +1,39 @@
--TEST--
Test variations of getting typed class constant values
--FILE--
<?php
/* Use separate classes to make sure that in-place constant updates don't interfere */
class A {
const object CONST1 = C;
}
class B {
const array CONST1 = C;
}
define("C", new stdClass());
$rc = new ReflectionClassConstant(A::class, 'CONST1');
var_dump($rc->getValue());
echo $rc;
$rc = new ReflectionClassConstant(B::class, 'CONST1');
try {
$rc->getValue();
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
try {
echo $rc;
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
object(stdClass)#1 (0) {
}
Constant [ public object CONST1 ] { Object }
Cannot assign stdClass to class constant B::CONST1 of type array
Cannot assign stdClass to class constant B::CONST1 of type array

View file

@ -0,0 +1,29 @@
--TEST--
ReflectionClass::getConstant() with typed class constants
--FILE--
<?php
class C {
const object CONST1 = C;
}
class D {
const array CONST1 = C;
}
const C = new stdClass();
$rc = new ReflectionClass(C::class);
var_dump($rc->getConstant("CONST1"));
$rc = new ReflectionClass(D::class);
try {
$rc->getConstant("CONST1");
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
object(stdClass)#1 (0) {
}
Cannot assign stdClass to class constant D::CONST1 of type array

View file

@ -0,0 +1,32 @@
--TEST--
Using ReflectionClass::__toString() with typed class constants
--FILE--
<?php
class Foo {
const ?int CONST1 = 1;
}
echo new ReflectionClass(Foo::class);
?>
--EXPECTF--
Class [ <user> class Foo ] {
@@ %s 3-5
- Constants [1] {
Constant [ public ?int CONST1 ] { 1 }
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [0] {
}
- Methods [0] {
}
}

View file

@ -0,0 +1,20 @@
--TEST--
Using ReflectionClass::__toString() with typed class constants when there is a type mismatch
--FILE--
<?php
class Foo {
const array CONST1 = C;
}
define("C", new stdClass());
try {
(string) new ReflectionClass(Foo::class);
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
Cannot assign stdClass to class constant Foo::CONST1 of type array

View file

@ -0,0 +1,24 @@
--TEST--
Typed class constant reflection
--FILE--
<?php
class Foo {
const int CONST1 = C;
public int $prop1 = Foo::CONST1;
}
define("C", "bar");
$foo = new ReflectionClass(Foo::class);
try {
$foo->getStaticProperties();
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
Cannot assign string to class constant Foo::CONST1 of type int

View file

@ -0,0 +1,25 @@
--TEST--
Calling constant() with a typed class constant
--FILE--
<?php
class Foo {
const object CONST1 = C;
const array CONST2 = C;
}
define("C", new stdClass());
var_dump(constant("FOO::CONST1"));
try {
constant("FOO::CONST2");
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
object(stdClass)#1 (0) {
}
Cannot assign stdClass to class constant Foo::CONST2 of type array