Skip to content

[WIP] Support 'static const CONST_NAME = expr;' #10

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
36 changes: 36 additions & 0 deletions Zend/tests/static_const.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
static const supports dynamic expressions
--FILE--
<?php

namespace {
function trace($val) {
echo "Declaring\n";
var_dump($val);
return $val;
}

$eof = "\n";
static const VAL1 = "World"; // sprintf("Hello, %s", "World");
// echo "VAL1 is " . VAL1 . "\n";
static const VAL2 = sprintf("Hello, %s%s", VAL1, $eof);

function main() {
echo VAL2;
}
}

namespace {

$local = "Other string\n";
static const VAL2 = $local;

main();
echo VAL2;
main();
}

--EXPECT--
Hello, World
Other string
Hello, World
20 changes: 20 additions & 0 deletions Zend/tests/static_const_is_const.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
static const cannot be modified
--FILE--
<?php

namespace Example;
$var = "Example";
static const VAL1 = [$var];
try {
preg_match('/\w+/', 'Test message', VAL1);
} catch (\Error $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
var_dump(VAL1);
--EXPECT--
Caught: Cannot pass parameter 3 by reference
array(1) {
[0]=>
string(7) "Example"
}
11 changes: 11 additions & 0 deletions Zend/tests/static_const_not_repeatable.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
static const cannot be repeated in the same namespace block
--FILE--
<?php

namespace {
static const VAL = "First";
static const VAL = "Second";
}
--EXPECTF--
Fatal error: Cannot declare static const VAL because the name is already in use in %s on line 5
11 changes: 11 additions & 0 deletions Zend/tests/static_const_only_top_level.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
static const can only be used at the top level
--FILE--
<?php

if (true) {
static const VAL = "First";
}
echo VAL;
--EXPECTF--
Parse error: syntax error, unexpected 'const' (T_CONST), expecting :: (T_PAAMAYIM_NEKUDOTAYIM) in %s on line 4
1 change: 1 addition & 0 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio

case ZEND_AST_CONST_DECL:
case ZEND_AST_CLASS_CONST_DECL:
case ZEND_AST_STATIC_CONST_DECL:
smart_str_appends(str, "const ");
goto simple_list;
case ZEND_AST_NAME_LIST:
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ enum _zend_ast_kind {
ZEND_AST_PROP_DECL,
ZEND_AST_CONST_DECL,
ZEND_AST_CLASS_CONST_DECL,
ZEND_AST_STATIC_CONST_DECL,
ZEND_AST_NAME_LIST,
ZEND_AST_TRAIT_ADAPTATIONS,
ZEND_AST_USE,
Expand Down
132 changes: 132 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ void zend_file_context_begin(zend_file_context *prev_context) /* {{{ */
FC(current_namespace) = NULL;
FC(in_namespace) = 0;
FC(has_bracketed_namespaces) = 0;
FC(namespace_id) = 0;
FC(declarables).ticks = 0;
zend_hash_init(&FC(seen_symbols), 8, NULL, NULL, 0);
}
Expand Down Expand Up @@ -835,6 +836,29 @@ uint32_t zend_add_member_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */
}
/* }}} */

zend_string *zend_concat6(char *str1, size_t str1_len, char *str2, size_t str2_len, char *str3, size_t str3_len, char *str4, size_t str4_len, char* str5, size_t str5_len, char* str6, size_t str6_len) /* {{{ */
{
size_t len = str1_len + str2_len + str3_len + str4_len + str5_len + str6_len;
zend_string *res = zend_string_alloc(len, 0);

char* ptr = ZSTR_VAL(res);
memcpy(ptr, str1, str1_len);
ptr += str1_len;
memcpy(ptr, str2, str2_len);
ptr += str2_len;
memcpy(ptr, str3, str3_len);
ptr += str3_len;
memcpy(ptr, str4, str4_len);
ptr += str4_len;
memcpy(ptr, str5, str5_len);
ptr += str5_len;
memcpy(ptr, str6, str6_len);
ptr[str6_len] = '\0';

return res;
}
/* }}} */

zend_string *zend_concat3(char *str1, size_t str1_len, char *str2, size_t str2_len, char *str3, size_t str3_len) /* {{{ */
{
size_t len = str1_len + str2_len + str3_len;
Expand All @@ -847,11 +871,28 @@ zend_string *zend_concat3(char *str1, size_t str1_len, char *str2, size_t str2_l

return res;
}
/* }}} */

zend_string *zend_concat_names(char *name1, size_t name1_len, char *name2, size_t name2_len) {
return zend_concat3(name1, name1_len, "\\", 1, name2, name2_len);
}

/* NOTE: Using the \0 byte seems to make tests fail. */
zend_string *zend_prefix_with_file(zend_string *name) { /* {{{ */
zend_string *filename = CG(compiled_filename);
zend_string *namespace_id_str = zend_long_to_str(FC(namespace_id));
zend_string *result = zend_concat6(
"static const ", sizeof("static const ") - 1,
ZSTR_VAL(name), ZSTR_LEN(name),
" at ", sizeof(" at ") - 1,
ZSTR_VAL(filename), ZSTR_LEN(filename),
"#", 1,
ZSTR_VAL(namespace_id_str), ZSTR_LEN(namespace_id_str));
zend_string_release_ex(namespace_id_str, 0);
return result;
}
/* }}} */

zend_string *zend_prefix_with_ns(zend_string *name) {
if (FC(current_namespace)) {
zend_string *ns = FC(current_namespace);
Expand Down Expand Up @@ -6947,6 +6988,93 @@ void zend_compile_const_decl(zend_ast *ast) /* {{{ */
}
/* }}}*/

void zend_compile_static_const_elem(zend_ast *const_ast) { /* {{{ */
zend_ast *name_ast = const_ast->child[0];
zend_ast *value_ast = const_ast->child[1];
zend_string *unqualified_name = zend_ast_get_str(name_ast);

zend_string *name;
if (zend_get_special_const(ZSTR_VAL(unqualified_name), ZSTR_LEN(unqualified_name))) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot redeclare static constant '%s'", ZSTR_VAL(unqualified_name));
}

// "static const" lasts for the life of a namespace block.
name = zend_prefix_with_file(unqualified_name);
name = zend_new_interned_string(name);

if (!zend_hash_add_ptr(zend_get_import_ht(ZEND_SYMBOL_CONST), unqualified_name, name)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare static const %s because "
"the name is already in use", ZSTR_VAL(unqualified_name));
}
zend_eval_const_expr(&value_ast);

/* 0. Check if it can be evaluated at compile time. */
if (value_ast->kind != ZEND_AST_ZVAL) {
/*
* This is a dynamic expression, and could not be evaluated with the information available while compiling the file.
* 1. JMPNZ if defined('UNIQUE_CONST_NAME')
* 2. Call define('UNIQUE_CONST_NAME', $dynamicValue)
* 3. update target of JMPNZ
*/
uint32_t opnum_jmpz;
znode defined_opline_result;
zend_op *defined_opline;
// TODO: Skip emitting the DEFINED and just emit DECLARE_CONST directly
// if this can be determined at compile time.
defined_opline = zend_emit_op_tmp(&defined_opline_result, ZEND_DEFINED, NULL, NULL);
defined_opline->op1_type = IS_CONST;
LITERAL_STR(defined_opline->op1, name);
defined_opline->extended_value = zend_alloc_cache_slot();

opnum_jmpz = zend_emit_cond_jump(ZEND_JMPNZ, &defined_opline_result, 0);

{
/* Generate the AST for a call to define('GLOBAL_UNIQUE_CONST', $dynamic) and compile it. */
zend_ast *define_name_ast;
zend_ast *define_call_ast;
zend_ast *args_ast;
zend_string *define_str = zend_new_interned_string(zend_string_init("define", 6, 0));

define_name_ast = zend_ast_create_zval_from_str(define_str);
define_name_ast->attr = ZEND_NAME_FQ;
args_ast = zend_ast_create_list(2, ZEND_AST_ARG_LIST, zend_ast_create_zval_from_str(name), value_ast);
define_call_ast = zend_ast_create(ZEND_AST_CALL, define_name_ast, args_ast);

zend_compile_expr(NULL, define_call_ast);
/* Temporary ASTs get freed when the arena is freed */
}
zend_update_jump_target_to_next(opnum_jmpz);
} else {
/* This expression can be evaluated at compile time */
znode name_node, value_node;
zval *value_zv = &value_node.u.constant;

value_node.op_type = IS_CONST;

zend_const_expr_to_zval(value_zv, value_ast);
zend_string_addref(name);

name_node.op_type = IS_CONST;
ZVAL_STR(&name_node.u.constant, name);

zend_emit_op(NULL, ZEND_DECLARE_CONST, &name_node, &value_node);
}

zend_register_seen_symbol(name, ZEND_SYMBOL_CONST);
}
/* }}} */

void zend_compile_static_const_decl(zend_ast *ast) /* {{{ */
{
zend_ast_list *list = zend_ast_get_list(ast);
uint32_t i;
for (i = 0; i < list->children; ++i) {
zend_compile_static_const_elem(list->child[i]);
}
}
/* }}}*/

void zend_compile_namespace(zend_ast *ast) /* {{{ */
{
zend_ast *name_ast = ast->child[0];
Expand Down Expand Up @@ -7011,6 +7139,7 @@ void zend_compile_namespace(zend_ast *ast) /* {{{ */
if (with_bracket) {
FC(has_bracketed_namespaces) = 1;
}
FC(namespace_id)++;

if (stmt_ast) {
zend_compile_top_stmt(stmt_ast);
Expand Down Expand Up @@ -8706,6 +8835,9 @@ void zend_compile_stmt(zend_ast *ast) /* {{{ */
case ZEND_AST_CONST_DECL:
zend_compile_const_decl(ast);
break;
case ZEND_AST_STATIC_CONST_DECL:
zend_compile_static_const_decl(ast);
break;
case ZEND_AST_NAMESPACE:
zend_compile_namespace(ast);
break;
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ typedef struct _zend_file_context {
HashTable *imports_const;

HashTable seen_symbols;
zend_long namespace_id;
} zend_file_context;

typedef union _zend_parser_stack_elem {
Expand Down
8 changes: 7 additions & 1 deletion Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> variable_class_name dereferencable_scalar constant dereferencable
%type <ast> callable_expr callable_variable static_member new_variable
%type <ast> encaps_var encaps_var_offset isset_variables
%type <ast> top_statement_list use_declarations const_list inner_statement_list if_stmt
%type <ast> top_statement_list use_declarations const_list static_const_list inner_statement_list if_stmt
%type <ast> alt_if_stmt for_exprs switch_case_list global_var_list static_var_list
%type <ast> echo_expr_list unset_variables catch_name_list catch_list parameter_list class_statement_list
%type <ast> implements_list case_list if_stmt_without_else
Expand Down Expand Up @@ -334,6 +334,7 @@ top_statement:
| T_USE use_declarations ';' { $$ = $2; $$->attr = ZEND_SYMBOL_CLASS; }
| T_USE use_type use_declarations ';' { $$ = $3; $$->attr = $2; }
| T_CONST const_list ';' { $$ = $2; }
| T_STATIC T_CONST static_const_list ';' { $$ = $3; }
;

use_type:
Expand Down Expand Up @@ -403,6 +404,11 @@ const_list:
| const_decl { $$ = zend_ast_create_list(1, ZEND_AST_CONST_DECL, $1); }
;

static_const_list:
static_const_list ',' const_decl { $$ = zend_ast_list_add($1, $3); }
| const_decl { $$ = zend_ast_create_list(1, ZEND_AST_STATIC_CONST_DECL, $1); }
;

inner_statement_list:
inner_statement_list inner_statement
{ $$ = zend_ast_list_add($1, $2); }
Expand Down
Loading