Skip to content

Commit b8ea2ed

Browse files
kocsismatemoliatabwoebi
committed
Implement typed class constants
RFC: https://wiki.php.net/rfc/typed_class_constants Co-Authored-By: Ben <[email protected]> Co-Authored-By: Bob Weinand <[email protected]>
1 parent 908d954 commit b8ea2ed

31 files changed

+460
-18
lines changed

Zend/tests/enum/name-property.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ array(1) {
3333
string(3) "Bar"
3434
array(2) {
3535
[0]=>
36-
object(ReflectionProperty)#3 (2) {
36+
object(ReflectionProperty)#4 (2) {
3737
["name"]=>
3838
string(4) "name"
3939
["class"]=>
4040
string(6) "IntFoo"
4141
}
4242
[1]=>
43-
object(ReflectionProperty)#4 (2) {
43+
object(ReflectionProperty)#5 (2) {
4444
["name"]=>
4545
string(5) "value"
4646
["class"]=>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
Typed class constants (declaration; simple)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const null A = null;
7+
public const false B = false;
8+
public const true C = true;
9+
public const bool D = true;
10+
public const int E = 0;
11+
public const float F = 3.14;
12+
public const string G = "";
13+
public const array H = [];
14+
public const array|string I = "";
15+
public const array|string|null J = null;
16+
}
17+
18+
var_dump(A::A);
19+
var_dump(A::B);
20+
var_dump(A::C);
21+
var_dump(A::D);
22+
var_dump(A::E);
23+
var_dump(A::F);
24+
var_dump(A::G);
25+
var_dump(A::H);
26+
var_dump(A::I);
27+
var_dump(A::J);
28+
?>
29+
--EXPECT--
30+
NULL
31+
bool(false)
32+
bool(true)
33+
bool(true)
34+
int(0)
35+
float(3.14)
36+
string(0) ""
37+
array(0) {
38+
}
39+
string(0) ""
40+
NULL
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Typed class constants (type mismatch; compile-time)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const string A = 1;
7+
}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot use int as value for class constant A::A of type string in %s on line %d
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Typed class constants (type mismatch; runtime)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const string A = A;
7+
}
8+
9+
define('A', 1);
10+
11+
echo A::A;
12+
?>
13+
--EXPECTF--
14+
Fatal error: Uncaught TypeError: Cannot assign int to class constant A::A of type string in %s:%d
15+
Stack trace:
16+
#0 {main}
17+
thrown in %s on line %d
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Typed class constants (inheritance; simple)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const int A = 1;
7+
}
8+
9+
class B extends A {
10+
public const string A = 'a';
11+
}
12+
?>
13+
--EXPECTF--
14+
Fatal error: Declaration of B::A must be compatible with A::A in %s on line %d
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Typed class constants (inheritance; missing type in child)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const int A = 1;
7+
}
8+
9+
class B extends A {
10+
public const A = 0;
11+
}
12+
?>
13+
--EXPECTF--
14+
Fatal error: Declaration of B::A must be compatible with A::A in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Typed class constants (type not allowed; object)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const object A = 1;
7+
}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Class constant A::A cannot have type object in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Typed class constants (type not allowed; void)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const void A = 1;
7+
}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Class constant A::A cannot have type void in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Typed class constants (type not allowed; callable)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const callable A = 1;
7+
}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Class constant A::A cannot have type callable in %s on line %d
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Typed class constants (inheritance; private constants)
3+
--FILE--
4+
<?php
5+
class A {
6+
private const int A = 1;
7+
}
8+
9+
class B extends A {
10+
public const string A = 'a';
11+
}
12+
13+
echo B::A;
14+
?>
15+
--EXPECT--
16+
a
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Typed class constants (type not allowed; class name)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const self A = 1;
7+
}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Class constant A::A cannot have type self in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Typed class constants (declaration; iterable)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const iterable A = [1];
7+
}
8+
9+
var_dump(A::A);
10+
?>
11+
--EXPECTF--
12+
Fatal error: Class constant A::A cannot have type Traversable|array in %s on line %d
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Typed class constants doesn't support intersection types
3+
--FILE--
4+
<?php
5+
class A {
6+
public const stdClass&A A = 1;
7+
}
8+
9+
?>
10+
--EXPECTF--
11+
Fatal error: Class constant A::A cannot have type stdClass&A in %s on line %d
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Typed class constants doesn't support static types
3+
--FILE--
4+
<?php
5+
class A {
6+
public const static A = 1;
7+
}
8+
9+
?>
10+
--EXPECTF--
11+
Parse error: syntax error, unexpected identifier "A", expecting "=" in %s on line %d
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Typed class constants (inheritance success)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const A = 1;
7+
public const B = 1;
8+
public const mixed C = 1;
9+
}
10+
11+
class B extends A {
12+
public const int A = 0;
13+
public const mixed B = 0;
14+
public const mixed C = 0;
15+
}
16+
17+
var_dump(B::A);
18+
var_dump(B::B);
19+
var_dump(B::C);
20+
?>
21+
--EXPECT--
22+
int(0)
23+
int(0)
24+
int(0)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Typed class constants (type not allowed; never)
3+
--FILE--
4+
<?php
5+
class A {
6+
public const never A = 1;
7+
}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Class constant A::A cannot have type never in %s on line %d

Zend/zend_API.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,6 +1463,10 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /
14631463
if (UNEXPECTED(zval_update_constant_ex(val, c->ce) != SUCCESS)) {
14641464
return FAILURE;
14651465
}
1466+
1467+
if (ZEND_TYPE_IS_SET(c->type) && UNEXPECTED(!zend_verify_class_constant_type(c, val))) {
1468+
return FAILURE;
1469+
}
14661470
}
14671471
} ZEND_HASH_FOREACH_END();
14681472
}
@@ -4516,7 +4520,7 @@ ZEND_API void zend_declare_property_stringl(zend_class_entry *ce, const char *na
45164520
}
45174521
/* }}} */
45184522

4519-
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) /* {{{ */
4523+
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) /* {{{ */
45204524
{
45214525
zend_class_constant *c;
45224526

@@ -4540,11 +4544,15 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
45404544
} else {
45414545
c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant));
45424546
}
4547+
45434548
ZVAL_COPY_VALUE(&c->value, value);
45444549
ZEND_CLASS_CONST_FLAGS(c) = flags;
4550+
c->name = name;
45454551
c->doc_comment = doc_comment;
45464552
c->attributes = NULL;
45474553
c->ce = ce;
4554+
c->type = type;
4555+
45484556
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
45494557
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
45504558
ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS;
@@ -4560,7 +4568,11 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
45604568

45614569
return c;
45624570
}
4563-
/* }}} */
4571+
4572+
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)
4573+
{
4574+
return zend_declare_typed_class_constant(ce, name, value, flags, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE(0));
4575+
}
45644576

45654577
ZEND_API void zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value) /* {{{ */
45664578
{

Zend/zend_API.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ ZEND_API void zend_declare_property_double(zend_class_entry *ce, const char *nam
423423
ZEND_API void zend_declare_property_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value, int access_type);
424424
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);
425425

426+
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);
426427
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);
427428
ZEND_API void zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value);
428429
ZEND_API void zend_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length);

Zend/zend_ast.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ enum _zend_ast_kind {
161161
ZEND_AST_CATCH,
162162
ZEND_AST_PROP_GROUP,
163163
ZEND_AST_PROP_ELEM,
164-
ZEND_AST_CONST_ELEM,
165164

166165
// Pseudo node for initializing enums
167166
ZEND_AST_CONST_ENUM_INIT,
@@ -170,6 +169,7 @@ enum _zend_ast_kind {
170169
ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT,
171170
ZEND_AST_FOREACH,
172171
ZEND_AST_ENUM_CASE,
172+
ZEND_AST_CONST_ELEM,
173173

174174
/* 5 child nodes */
175175
ZEND_AST_PARAM = 5 << ZEND_AST_NUM_CHILDREN_SHIFT,

Zend/zend_compile.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7676,7 +7676,7 @@ static void zend_check_trait_alias_modifiers(uint32_t attr) /* {{{ */
76767676
}
76777677
/* }}} */
76787678

7679-
static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */
7679+
static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr_ast)
76807680
{
76817681
zend_ast_list *list = zend_ast_get_list(ast);
76827682
zend_class_entry *ce = CG(active_class_entry);
@@ -7688,9 +7688,28 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as
76887688
zend_ast *name_ast = const_ast->child[0];
76897689
zend_ast **value_ast_ptr = &const_ast->child[1];
76907690
zend_ast *doc_comment_ast = const_ast->child[2];
7691+
zend_ast *type_ast = const_ast->child[3];
76917692
zend_string *name = zval_make_interned_string(zend_ast_get_zval(name_ast));
76927693
zend_string *doc_comment = doc_comment_ast ? zend_string_copy(zend_ast_get_str(doc_comment_ast)) : NULL;
76937694
zval value_zv;
7695+
zend_type type = ZEND_TYPE_INIT_NONE(0);
7696+
7697+
if (type_ast) {
7698+
type = zend_compile_typename(type_ast, /* force_allow_null */ 0);
7699+
7700+
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
7701+
7702+
if (ZEND_TYPE_IS_INTERSECTION(type) || (
7703+
type_mask != MAY_BE_ANY &&
7704+
(type_mask & (MAY_BE_NEVER|MAY_BE_OBJECT|MAY_BE_CALLABLE|MAY_BE_VOID) || ZEND_TYPE_HAS_NAME(type))
7705+
)
7706+
) {
7707+
zend_string *type_str = zend_type_to_string(type);
7708+
7709+
zend_error_noreturn(E_COMPILE_ERROR, "Class constant %s::%s cannot have type %s",
7710+
ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(type_str));
7711+
}
7712+
}
76947713

76957714
if (UNEXPECTED((flags & ZEND_ACC_PRIVATE) && (flags & ZEND_ACC_FINAL))) {
76967715
zend_error_noreturn(
@@ -7700,14 +7719,21 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as
77007719
}
77017720

77027721
zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false);
7703-
c = zend_declare_class_constant_ex(ce, name, &value_zv, flags, doc_comment);
7722+
7723+
if (!Z_CONSTANT(value_zv) && ZEND_TYPE_IS_SET(type) && !zend_is_valid_default_value(type, &value_zv)) {
7724+
zend_string *type_str = zend_type_to_string(type);
7725+
7726+
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use %s as value for class constant %s::%s of type %s",
7727+
zend_zval_type_name(&value_zv), ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(type_str));
7728+
}
7729+
7730+
c = zend_declare_typed_class_constant(ce, name, &value_zv, flags, doc_comment, type);
77047731

77057732
if (attr_ast) {
77067733
zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, 0);
77077734
}
77087735
}
77097736
}
7710-
/* }}} */
77117737

77127738
static void zend_compile_class_const_group(zend_ast *ast) /* {{{ */
77137739
{

Zend/zend_compile.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,11 @@ typedef struct _zend_property_info {
404404

405405
typedef struct _zend_class_constant {
406406
zval value; /* flags are stored in u2 */
407+
zend_string *name;
407408
zend_string *doc_comment;
408409
HashTable *attributes;
409410
zend_class_entry *ce;
411+
zend_type type;
410412
} zend_class_constant;
411413

412414
#define ZEND_CLASS_CONST_FLAGS(c) Z_CONSTANT_FLAGS((c)->value)

0 commit comments

Comments
 (0)