Skip to content

[RFC] Typed class constants #5815

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Typed class constants (declaration; simple)
--FILE--
<?php
class A {
public const int A = 1;
}

echo A::A;
?>
--EXPECT--
1
10 changes: 10 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Typed class constants (type mismatch; compile-time)
--FILE--
<?php
class A {
public const string A = 1;
}
?>
--EXPECTF--
Fatal error: Cannot use int as value for class constant A::A of type string in %s on line %d
17 changes: 17 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Typed class constants (type mismatch; runtime)
--FILE--
<?php
class A {
public const string A = A;
}

define('A', 1);

echo A::A;
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Cannot assign int to class constant A::A of type string in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
14 changes: 14 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Typed class constants (inheritance; simple)
--FILE--
<?php
class A {
public const int A = 1;
}

class B extends A {
public const string A = 'a';
}
?>
--EXPECTF--
Fatal error: Type of B::A must be int (as in class A) in %s on line %d
14 changes: 14 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Typed class constants (inheritance; missing type in child)
--FILE--
<?php
class A {
public const int A = 1;
}

class B extends A {
public const A = 0;
}
?>
--EXPECTF--
Fatal error: Type of B::A must be int (as in class A) in %s on line %d
14 changes: 14 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Typed class constants (inheritance; missing type in parent)
--FILE--
<?php
class A {
public const A = 1;
}

class B extends A {
public const int A = 0;
}
?>
--EXPECTF--
Fatal error: Type of B::A must not be defined (as in class A) in %s on line %d
10 changes: 10 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_007.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Typed class constants (type not allowed; void)
--FILE--
<?php
class A {
public const void A = 1;
}
?>
--EXPECTF--
Fatal error: Class constant A::A cannot have type void in %s on line %d
10 changes: 10 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_008.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Typed class constants (type not allowed; callable)
--FILE--
<?php
class A {
public const callable A = 1;
}
?>
--EXPECTF--
Fatal error: Class constant A::A cannot have type callable in %s on line %d
16 changes: 16 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_009.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Typed class constants (inheritance; private constants)
--FILE--
<?php
class A {
private const int A = 1;
}

class B extends A {
public const string A = 'a';
}

echo B::A;
?>
--EXPECT--
a
10 changes: 10 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_010.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Typed class constants (type not allowed; class name)
--FILE--
<?php
class A {
public const self A = 1;
}
?>
--EXPECTF--
Fatal error: Class constant A::A cannot have type self in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_011.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Typed class constants (declaration; nullable)
--FILE--
<?php
class A {
public const ?int A = null;
}

var_dump(A::A);
?>
--EXPECT--
NULL
15 changes: 15 additions & 0 deletions Zend/tests/type_declarations/typed_class_constants_012.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Typed class constants (declaration; iterable)
--FILE--
<?php
class A {
public const iterable A = [1];
}

var_dump(A::A);
?>
--EXPECT--
array(1) {
[0]=>
int(1)
}
17 changes: 16 additions & 1 deletion Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,10 @@ ZEND_API int zend_update_class_constants(zend_class_entry *class_type) /* {{{ */
if (UNEXPECTED(zval_update_constant_ex(val, c->ce) != SUCCESS)) {
return FAILURE;
}

if (ZEND_TYPE_IS_SET(c->type) && UNEXPECTED(!zend_verify_class_constant_type(c, val))) {
return FAILURE;
}
}
} ZEND_HASH_FOREACH_END();

Expand Down Expand Up @@ -3835,7 +3839,7 @@ ZEND_API int zend_declare_property_stringl(zend_class_entry *ce, const char *nam
}
/* }}} */

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_typed_class_constant(zend_class_entry *ce, zend_string *name, zval *value, int access_type, zend_string *doc_comment, zend_type type) /* {{{ */
{
zend_class_constant *c;

Expand All @@ -3859,11 +3863,16 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
} else {
c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant));
}

ZVAL_COPY_VALUE(&c->value, value);
Z_ACCESS_FLAGS(c->value) = access_type;

c->name = name;
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;
}
Expand All @@ -3877,6 +3886,12 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
}
/* }}} */

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) /* {{{ */
{
return zend_declare_typed_class_constant(ce, name, value, access_type, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE(0));
}
/* }}} */

ZEND_API int zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value) /* {{{ */
{
zend_string *key;
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ ZEND_API int zend_declare_property_double(zend_class_entry *ce, const char *name
ZEND_API int zend_declare_property_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value, int access_type);
ZEND_API int 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 int zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value);
ZEND_API int zend_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length);
Expand Down
13 changes: 9 additions & 4 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1621,14 +1621,19 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
smart_str_appends(str, "const ");
goto simple_list;
case ZEND_AST_CLASS_CONST_GROUP:
if (ast->child[1]) {
zend_ast_export_attributes(str, ast->child[1], indent, 1);
if (ast->child[2]) {
zend_ast_export_attributes(str, ast->child[2], indent, 1);
}

zend_ast_export_visibility(str, ast->attr);
smart_str_appends(str, "const ");

ast = ast->child[0];
if (ast->child[0]) {
zend_ast_export_type(str, ast->child[0], indent);
smart_str_appendc(str, ' ');
}

ast = ast->child[1];

goto simple_list;
case ZEND_AST_NAME_LIST:
Expand Down Expand Up @@ -2231,7 +2236,7 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr)
ast->child[3] = attr;
break;
case ZEND_AST_CLASS_CONST_GROUP:
ast->child[1] = attr;
ast->child[2] = attr;
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ enum _zend_ast_kind {
ZEND_AST_USE_ELEM,
ZEND_AST_TRAIT_ALIAS,
ZEND_AST_GROUP_USE,
ZEND_AST_CLASS_CONST_GROUP,
ZEND_AST_ATTRIBUTE,

/* 3 child nodes */
Expand All @@ -152,6 +151,7 @@ enum _zend_ast_kind {
ZEND_AST_PROP_GROUP,
ZEND_AST_PROP_ELEM,
ZEND_AST_CONST_ELEM,
ZEND_AST_CLASS_CONST_GROUP,

/* 4 child nodes */
ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT,
Expand Down
34 changes: 29 additions & 5 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -6640,14 +6640,15 @@ static void zend_check_const_and_trait_alias_attr(uint32_t attr, const char* ent
}
/* }}} */

void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */
void zend_compile_class_const_decl(zend_ast *ast, zend_ast *type_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);
uint32_t i, children = list->children;

if ((ce->ce_flags & ZEND_ACC_TRAIT) != 0) {
zend_error_noreturn(E_COMPILE_ERROR, "Traits cannot have constants");

return;
}

Expand All @@ -6660,13 +6661,35 @@ void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr
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, /* use_arena */ 1);

if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_CALLABLE|MAY_BE_VOID) || ZEND_TYPE_HAS_NAME(type)) {
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));

return;
}
}

if (UNEXPECTED(flags & (ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT|ZEND_ACC_FINAL))) {
zend_check_const_and_trait_alias_attr(flags, "constant");
}

zend_const_expr_to_zval(&value_zv, value_ast);
c = zend_declare_class_constant_ex(ce, name, &value_zv, flags, doc_comment);

if (!Z_CONSTANT(value_zv) && !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);
Expand All @@ -6677,16 +6700,17 @@ void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr

void zend_compile_class_const_group(zend_ast *ast) /* {{{ */
{
zend_ast *const_ast = ast->child[0];
zend_ast *attr_ast = ast->child[1];
zend_ast *type_ast = ast->child[0];
zend_ast *const_ast = ast->child[1];
zend_ast *attr_ast = ast->child[2];

if (attr_ast && zend_ast_get_list(const_ast)->children > 1) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot apply attributes to a group of constants");

return;
}

zend_compile_class_const_decl(const_ast, ast->attr, attr_ast);
zend_compile_class_const_decl(const_ast, type_ast, ast->attr, attr_ast);
}
/* }}} */

Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,11 @@ typedef struct _zend_property_info {

typedef struct _zend_class_constant {
zval value; /* access flags are stored in reserved: zval.u2.access_flags */
zend_string *name;
zend_string *doc_comment;
HashTable *attributes;
zend_class_entry *ce;
zend_type type;
} zend_class_constant;

/* arg_info for internal functions */
Expand Down
29 changes: 29 additions & 0 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,35 @@ ZEND_API zend_bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, z
return zend_verify_weak_scalar_type_hint(type_mask, arg);
}

ZEND_COLD zend_never_inline void zend_verify_class_constant_type_error(zend_class_constant *c, 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(c->name), ZSTR_VAL(type_str));
}

zend_bool zend_never_inline zend_verify_class_constant_type(zend_class_constant *c, zval *constant)
{
if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(c->type, Z_TYPE_P(constant)))) {
return 1;
}

uint32_t type_mask = ZEND_TYPE_FULL_MASK(c->type);

if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(constant)) {
return 1;
}

if (zend_verify_scalar_type_hint(type_mask, constant, /* strict */ 1, /* is_internal_arg */ 0)) {
return 1;
}

zend_verify_class_constant_type_error(c, constant);

return 0;
}

ZEND_COLD zend_never_inline void zend_verify_property_type_error(zend_property_info *info, zval *property)
{
zend_string *type_str;
Expand Down
Loading