mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
Add limited support for new in initializers
Add support for new expressions inside parameter default values, static variable initializers, global constant initializers and attribute arguments. RFC: https://wiki.php.net/rfc/new_in_initializers Closes GH-7153.
This commit is contained in:
parent
cce31657d6
commit
52d3d0d8d7
21 changed files with 629 additions and 22 deletions
|
@ -193,6 +193,10 @@ PHP 8.1 UPGRADE NOTES
|
|||
RFC: https://wiki.php.net/rfc/noreturn_type
|
||||
. Added support for fibers.
|
||||
RFC: https://wiki.php.net/rfc/fibers
|
||||
. It is now possible to use "new ClassName()" expressions as parameter
|
||||
default values, static variable and global constant initializers, as well
|
||||
as attribute arguments.
|
||||
RFC: https://wiki.php.net/rfc/new_in_initializers
|
||||
. File uploads now provide an additional full_path key, which contains the
|
||||
full path (rather than just the basename) of the uploaded file. This is
|
||||
intended for use in conjunction with "upload webkitdirectory".
|
||||
|
|
62
Zend/tests/constexpr/new.phpt
Normal file
62
Zend/tests/constexpr/new.phpt
Normal file
|
@ -0,0 +1,62 @@
|
|||
--TEST--
|
||||
new in constant expressions
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
try {
|
||||
eval('static $a = new DoesNotExist;');
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
static $b = new stdClass;
|
||||
var_dump($b);
|
||||
|
||||
try {
|
||||
eval('static $c = new stdClass([] + 0);');
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
class Test {
|
||||
public function __construct(public $a, public $b) {}
|
||||
}
|
||||
|
||||
try {
|
||||
eval('static $d = new Test(new stdClass, [] + 0);');
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
static $e = new Test(new stdClass, 42);
|
||||
var_dump($e);
|
||||
|
||||
class Test2 {
|
||||
public function __construct() {
|
||||
echo "Side-effect\n";
|
||||
throw new Exception("Failed to construct");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
eval('static $f = new Test2();');
|
||||
} catch (Exception $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Class "DoesNotExist" not found
|
||||
object(stdClass)#2 (0) {
|
||||
}
|
||||
Unsupported operand types: array + int
|
||||
Unsupported operand types: array + int
|
||||
object(Test)#4 (2) {
|
||||
["a"]=>
|
||||
object(stdClass)#1 (0) {
|
||||
}
|
||||
["b"]=>
|
||||
int(42)
|
||||
}
|
||||
Side-effect
|
||||
Failed to construct
|
35
Zend/tests/constexpr/new_allowed.phpt
Normal file
35
Zend/tests/constexpr/new_allowed.phpt
Normal file
|
@ -0,0 +1,35 @@
|
|||
--TEST--
|
||||
Places where new is allowed
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[SomeAttribute(new stdClass)]
|
||||
class Test {
|
||||
public function __construct(
|
||||
public $prop = new stdClass,
|
||||
) {
|
||||
var_dump($prop);
|
||||
}
|
||||
}
|
||||
|
||||
function test($param = new stdClass) {
|
||||
static $var = new stdClass;
|
||||
var_dump($param, $var);
|
||||
}
|
||||
|
||||
const TEST = new stdClass;
|
||||
|
||||
new Test;
|
||||
test();
|
||||
var_dump(TEST);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(stdClass)#3 (0) {
|
||||
}
|
||||
object(stdClass)#2 (0) {
|
||||
}
|
||||
object(stdClass)#3 (0) {
|
||||
}
|
||||
object(stdClass)#1 (0) {
|
||||
}
|
10
Zend/tests/constexpr/new_anon_class.phpt
Normal file
10
Zend/tests/constexpr/new_anon_class.phpt
Normal file
|
@ -0,0 +1,10 @@
|
|||
--TEST--
|
||||
New with anonymous class is not supported in constant expressions
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
static $x = new class {};
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use anonymous class in constant expression in %s on line %d
|
44
Zend/tests/constexpr/new_arg_eval.phpt
Normal file
44
Zend/tests/constexpr/new_arg_eval.phpt
Normal file
|
@ -0,0 +1,44 @@
|
|||
--TEST--
|
||||
Check that const exprs are pre-evaluated in new arguments
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class C {
|
||||
public function __construct(public $x) {}
|
||||
}
|
||||
function test(
|
||||
$a = new C(__CLASS__),
|
||||
$b = new C(__FUNCTION__),
|
||||
$c = new C(x: __FILE__),
|
||||
) {
|
||||
var_dump($a, $b, $c);
|
||||
}
|
||||
test();
|
||||
|
||||
// Check that nested new works as well.
|
||||
function test2($p = new C(new C(__FUNCTION__))) {
|
||||
var_dump($p);
|
||||
}
|
||||
test2();
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(C)#1 (1) {
|
||||
["x"]=>
|
||||
string(0) ""
|
||||
}
|
||||
object(C)#2 (1) {
|
||||
["x"]=>
|
||||
string(4) "test"
|
||||
}
|
||||
object(C)#3 (1) {
|
||||
["x"]=>
|
||||
string(%d) "%snew_arg_eval.php"
|
||||
}
|
||||
object(C)#3 (1) {
|
||||
["x"]=>
|
||||
object(C)#2 (1) {
|
||||
["x"]=>
|
||||
string(5) "test2"
|
||||
}
|
||||
}
|
10
Zend/tests/constexpr/new_arg_unpack.phpt
Normal file
10
Zend/tests/constexpr/new_arg_unpack.phpt
Normal file
|
@ -0,0 +1,10 @@
|
|||
--TEST--
|
||||
Argument unpacking in new arguments in const expr (not yet supported)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
static $x = new stdClass(...[0]);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Argument unpacking in constant expressions is not supported in %s on line %d
|
10
Zend/tests/constexpr/new_dynamic_class_name.phpt
Normal file
10
Zend/tests/constexpr/new_dynamic_class_name.phpt
Normal file
|
@ -0,0 +1,10 @@
|
|||
--TEST--
|
||||
Dynamic class name in new is not supported
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
static $x = new (FOO);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use dynamic class name in constant expression in %s on line %d
|
10
Zend/tests/constexpr/new_invalid_operation_in_arg.phpt
Normal file
10
Zend/tests/constexpr/new_invalid_operation_in_arg.phpt
Normal file
|
@ -0,0 +1,10 @@
|
|||
--TEST--
|
||||
Invalid operation in new arg in const expr
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
static $x = new stdClass($foo);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Constant expression contains invalid operations in %s on line %d
|
51
Zend/tests/constexpr/new_named_params.phpt
Normal file
51
Zend/tests/constexpr/new_named_params.phpt
Normal file
|
@ -0,0 +1,51 @@
|
|||
--TEST--
|
||||
Named params in new in const expr
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Vec {
|
||||
public function __construct(public float $x, public float $y, public float $z) {}
|
||||
}
|
||||
|
||||
static $a = new Vec(x: 0.0, y: 1.0, z: 2.0);
|
||||
var_dump($a);
|
||||
|
||||
static $b = new Vec(z: 0.0, y: 1.0, x: 2.0);
|
||||
var_dump($b);
|
||||
|
||||
static $c = new Vec(0.0, z: 1.0, y: 2.0);
|
||||
var_dump($c);
|
||||
|
||||
try {
|
||||
eval('static $d = new Vec(x: 0.0, x: 1.0);');
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(Vec)#1 (3) {
|
||||
["x"]=>
|
||||
float(0)
|
||||
["y"]=>
|
||||
float(1)
|
||||
["z"]=>
|
||||
float(2)
|
||||
}
|
||||
object(Vec)#2 (3) {
|
||||
["x"]=>
|
||||
float(2)
|
||||
["y"]=>
|
||||
float(1)
|
||||
["z"]=>
|
||||
float(0)
|
||||
}
|
||||
object(Vec)#3 (3) {
|
||||
["x"]=>
|
||||
float(0)
|
||||
["y"]=>
|
||||
float(2)
|
||||
["z"]=>
|
||||
float(1)
|
||||
}
|
||||
Named parameter $x overwrites previous argument
|
18
Zend/tests/constexpr/new_not_allowed_class_constant.phpt
Normal file
18
Zend/tests/constexpr/new_not_allowed_class_constant.phpt
Normal file
|
@ -0,0 +1,18 @@
|
|||
--TEST--
|
||||
New not allowed in class constant
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
// New in class constants (and static properties) brings up evaluation order questions: When
|
||||
// should the (potentially side-effecting) new expression be evaluated? Evaluating it when the
|
||||
// class is declared would break references to classes that are declared later in the same
|
||||
// file. On the other hand, the current lazy evaluation of initializers is somewhat ill-defined
|
||||
// when we start considering side-effecting expressions.
|
||||
|
||||
class Test {
|
||||
const X = new stdClass;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: New expressions are not supported in this context in %s on line %d
|
23
Zend/tests/constexpr/new_not_allowed_property.phpt
Normal file
23
Zend/tests/constexpr/new_not_allowed_property.phpt
Normal file
|
@ -0,0 +1,23 @@
|
|||
--TEST--
|
||||
New not allowed in property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
// New in (non-static) properties is a particularly tricky case. The initializer needs to be
|
||||
// evaluated on each object construction. Currently, the places where this can happen is
|
||||
// during object creation, or as part of the constructor. Doing this during object creation
|
||||
// can issues for use-cases such as serialization or generally anything that is effectively
|
||||
// based on newInstanceWithoutConstructor(). Handling this via the constructor is more
|
||||
// promising, but requires injecting code in the constructor, which may require adding a
|
||||
// constructor which is not explicitly declared, which may also require a child class to
|
||||
// call a constructor that has not been explicitly declared, otherwise properties may be
|
||||
// left uninitialized. A third option is another mechanism between object creation and
|
||||
// constructor invocation. Overall, the best way to address this is not clear right now.
|
||||
|
||||
class Test {
|
||||
public $prop = new stdClass;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: New expressions are not supported in this context in %s on line %d
|
10
Zend/tests/constexpr/new_positional_after_named.phpt
Normal file
10
Zend/tests/constexpr/new_positional_after_named.phpt
Normal file
|
@ -0,0 +1,10 @@
|
|||
--TEST--
|
||||
Positional argument after named argument in new arguments
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
static $x = new stdClass(x: 0, 1);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use positional argument after named argument in %s on line %d
|
39
Zend/tests/constexpr/new_self_parent.phpt
Normal file
39
Zend/tests/constexpr/new_self_parent.phpt
Normal file
|
@ -0,0 +1,39 @@
|
|||
--TEST--
|
||||
new self / new parent in constant expression
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public static function invalid($x = new parent) {
|
||||
}
|
||||
}
|
||||
class B extends A {
|
||||
public static function method($x = new self, $y = new parent) {
|
||||
var_dump($x, $y);
|
||||
}
|
||||
}
|
||||
|
||||
function invalid($x = new self) {}
|
||||
|
||||
B::method();
|
||||
|
||||
try {
|
||||
invalid();
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
try {
|
||||
B::invalid();
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(B)#1 (0) {
|
||||
}
|
||||
object(A)#2 (0) {
|
||||
}
|
||||
Cannot access "self" when no class scope is active
|
||||
Cannot access "parent" when current class scope has no parent
|
10
Zend/tests/constexpr/new_static.phpt
Normal file
10
Zend/tests/constexpr/new_static.phpt
Normal file
|
@ -0,0 +1,10 @@
|
|||
--TEST--
|
||||
Static in new is not supported
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
static $x = new static;
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: "static" is not allowed in compile-time constants in %s on line %d
|
|
@ -4803,7 +4803,7 @@ static zend_result get_default_via_ast(zval *default_value_zval, const char *def
|
|||
/* Disable constant substitution, to make getDefaultValueConstant() work. */
|
||||
CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION | ZEND_COMPILE_NO_PERSISTENT_CONSTANT_SUBSTITUTION;
|
||||
zend_file_context_begin(&original_file_context);
|
||||
zend_const_expr_to_zval(default_value_zval, const_expr_ast_ptr);
|
||||
zend_const_expr_to_zval(default_value_zval, const_expr_ast_ptr, /* allow_dynamic */ true);
|
||||
CG(ast_arena) = original_ast_arena;
|
||||
CG(compiler_options) = original_compiler_options;
|
||||
zend_file_context_end(&original_file_context);
|
||||
|
|
|
@ -504,6 +504,11 @@ static zend_result zend_ast_add_unpacked_element(zval *result, zval *expr) {
|
|||
return FAILURE;
|
||||
}
|
||||
|
||||
zend_class_entry *zend_ast_fetch_class(zend_ast *ast, zend_class_entry *scope)
|
||||
{
|
||||
return zend_fetch_class(zend_ast_get_str(ast), ast->attr | ZEND_FETCH_CLASS_EXCEPTION);
|
||||
}
|
||||
|
||||
ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope)
|
||||
{
|
||||
zval op1, op2;
|
||||
|
@ -797,6 +802,88 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast
|
|||
ZVAL_COPY_OR_DUP(result, zv);
|
||||
break;
|
||||
}
|
||||
case ZEND_AST_NEW:
|
||||
{
|
||||
zend_class_entry *ce = zend_ast_fetch_class(ast->child[0], scope);
|
||||
if (!ce) {
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
if (object_init_ex(result, ce) != SUCCESS) {
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
zend_ast_list *args_ast = zend_ast_get_list(ast->child[1]);
|
||||
if (args_ast->attr) {
|
||||
/* Has named arguments. */
|
||||
HashTable *args = zend_new_array(args_ast->children);
|
||||
for (uint32_t i = 0; i < args_ast->children; i++) {
|
||||
zend_ast *arg_ast = args_ast->child[i];
|
||||
zend_string *name = NULL;
|
||||
zval arg;
|
||||
if (arg_ast->kind == ZEND_AST_NAMED_ARG) {
|
||||
name = zend_ast_get_str(arg_ast->child[0]);
|
||||
arg_ast = arg_ast->child[1];
|
||||
}
|
||||
if (zend_ast_evaluate(&arg, arg_ast, scope) == FAILURE) {
|
||||
zend_array_destroy(args);
|
||||
zval_ptr_dtor(result);
|
||||
return FAILURE;
|
||||
}
|
||||
if (name) {
|
||||
if (!zend_hash_add(args, name, &arg)) {
|
||||
zend_throw_error(NULL,
|
||||
"Named parameter $%s overwrites previous argument",
|
||||
ZSTR_VAL(name));
|
||||
zend_array_destroy(args);
|
||||
zval_ptr_dtor(result);
|
||||
return FAILURE;
|
||||
}
|
||||
} else {
|
||||
zend_hash_next_index_insert(args, &arg);
|
||||
}
|
||||
}
|
||||
|
||||
zend_function *ctor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result));
|
||||
if (ctor) {
|
||||
zend_call_known_function(
|
||||
ctor, Z_OBJ_P(result), Z_OBJCE_P(result), NULL, 0, NULL, args);
|
||||
}
|
||||
|
||||
zend_array_destroy(args);
|
||||
} else {
|
||||
ALLOCA_FLAG(use_heap)
|
||||
zval *args = do_alloca(sizeof(zval) * args_ast->children, use_heap);
|
||||
for (uint32_t i = 0; i < args_ast->children; i++) {
|
||||
if (zend_ast_evaluate(&args[i], args_ast->child[i], scope) == FAILURE) {
|
||||
for (uint32_t j = 0; j < i; j++) {
|
||||
zval_ptr_dtor(&args[j]);
|
||||
}
|
||||
free_alloca(args, use_heap);
|
||||
zval_ptr_dtor(result);
|
||||
return FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
zend_function *ctor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result));
|
||||
if (ctor) {
|
||||
zend_call_known_instance_method(
|
||||
ctor, Z_OBJ_P(result), NULL, args_ast->children, args);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < args_ast->children; i++) {
|
||||
zval_ptr_dtor(&args[i]);
|
||||
}
|
||||
free_alloca(args, use_heap);
|
||||
}
|
||||
|
||||
if (EG(exception)) {
|
||||
zend_object_store_ctor_failed(Z_OBJ_P(result));
|
||||
zval_ptr_dtor(result);
|
||||
return FAILURE;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
default:
|
||||
zend_throw_error(NULL, "Unsupported constant expression");
|
||||
ret = FAILURE;
|
||||
|
@ -949,17 +1036,17 @@ ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast)
|
|||
efree(ast);
|
||||
}
|
||||
|
||||
ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn) {
|
||||
ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *context) {
|
||||
if (zend_ast_is_list(ast)) {
|
||||
zend_ast_list *list = zend_ast_get_list(ast);
|
||||
uint32_t i;
|
||||
for (i = 0; i < list->children; ++i) {
|
||||
fn(&list->child[i]);
|
||||
fn(&list->child[i], context);
|
||||
}
|
||||
} else {
|
||||
uint32_t i, children = zend_ast_get_num_children(ast);
|
||||
for (i = 0; i < children; ++i) {
|
||||
fn(&ast->child[i]);
|
||||
fn(&ast->child[i], context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -303,8 +303,8 @@ ZEND_API zend_ast_ref * ZEND_FASTCALL zend_ast_copy(zend_ast *ast);
|
|||
ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast);
|
||||
ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast);
|
||||
|
||||
typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr);
|
||||
ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn);
|
||||
typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr, void *context);
|
||||
ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *context);
|
||||
|
||||
static zend_always_inline bool zend_ast_is_special(zend_ast *ast) {
|
||||
return (ast->kind >> ZEND_AST_SPECIAL_SHIFT) & 1;
|
||||
|
|
|
@ -4738,7 +4738,7 @@ void zend_compile_static_var(zend_ast *ast) /* {{{ */
|
|||
zval value_zv;
|
||||
|
||||
if (*value_ast_ptr) {
|
||||
zend_const_expr_to_zval(&value_zv, value_ast_ptr);
|
||||
zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ true);
|
||||
} else {
|
||||
ZVAL_NULL(&value_zv);
|
||||
}
|
||||
|
@ -6069,7 +6069,7 @@ void zend_compile_declare(zend_ast *ast) /* {{{ */
|
|||
|
||||
if (zend_string_equals_literal_ci(name, "ticks")) {
|
||||
zval value_zv;
|
||||
zend_const_expr_to_zval(&value_zv, value_ast_ptr);
|
||||
zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false);
|
||||
FC(declarables).ticks = zval_get_long(&value_zv);
|
||||
zval_ptr_dtor_nogc(&value_zv);
|
||||
} else if (zend_string_equals_literal_ci(name, "encoding")) {
|
||||
|
@ -6091,7 +6091,7 @@ void zend_compile_declare(zend_ast *ast) /* {{{ */
|
|||
"use block mode");
|
||||
}
|
||||
|
||||
zend_const_expr_to_zval(&value_zv, value_ast_ptr);
|
||||
zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false);
|
||||
|
||||
if (Z_TYPE(value_zv) != IS_LONG || (Z_LVAL(value_zv) != 0 && Z_LVAL(value_zv) != 1)) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "strict_types declaration must have 0 or 1 as its value");
|
||||
|
@ -6477,7 +6477,8 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3
|
|||
"Cannot use positional argument after named argument");
|
||||
}
|
||||
|
||||
zend_const_expr_to_zval(&attr->args[j].value, arg_ast_ptr);
|
||||
zend_const_expr_to_zval(
|
||||
&attr->args[j].value, arg_ast_ptr, /* allow_dynamic */ true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6607,7 +6608,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
|
|||
CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION | ZEND_COMPILE_NO_PERSISTENT_CONSTANT_SUBSTITUTION;
|
||||
opcode = ZEND_RECV_INIT;
|
||||
default_node.op_type = IS_CONST;
|
||||
zend_const_expr_to_zval(&default_node.u.constant, default_ast_ptr);
|
||||
zend_const_expr_to_zval(
|
||||
&default_node.u.constant, default_ast_ptr, /* allow_dynamic */ true);
|
||||
CG(compiler_options) = cops;
|
||||
|
||||
if (last_required_param != (uint32_t) -1 && i < last_required_param) {
|
||||
|
@ -7285,7 +7287,7 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z
|
|||
}
|
||||
|
||||
if (*value_ast_ptr) {
|
||||
zend_const_expr_to_zval(&value_zv, value_ast_ptr);
|
||||
zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false);
|
||||
|
||||
if (ZEND_TYPE_IS_SET(type) && !Z_CONSTANT(value_zv)
|
||||
&& !zend_is_valid_default_value(type, &value_zv)) {
|
||||
|
@ -7374,7 +7376,7 @@ void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr
|
|||
);
|
||||
}
|
||||
|
||||
zend_const_expr_to_zval(&value_zv, value_ast_ptr);
|
||||
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 (attr_ast) {
|
||||
|
@ -7833,7 +7835,7 @@ static void zend_compile_enum_case(zend_ast *ast)
|
|||
zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_zval_ast);
|
||||
|
||||
zval value_zv;
|
||||
zend_const_expr_to_zval(&value_zv, &const_enum_init_ast);
|
||||
zend_const_expr_to_zval(&value_zv, &const_enum_init_ast, /* allow_dynamic */ false);
|
||||
|
||||
/* Doc comment has been appended as second last element in ZEND_AST_ENUM ast - attributes are conventionally last */
|
||||
zend_ast *doc_comment_ast = ast->child[2];
|
||||
|
@ -8015,7 +8017,7 @@ void zend_compile_const_decl(zend_ast *ast) /* {{{ */
|
|||
zval *value_zv = &value_node.u.constant;
|
||||
|
||||
value_node.op_type = IS_CONST;
|
||||
zend_const_expr_to_zval(value_zv, value_ast_ptr);
|
||||
zend_const_expr_to_zval(value_zv, value_ast_ptr, /* allow_dynamic */ true);
|
||||
|
||||
if (zend_get_special_const(ZSTR_VAL(unqualified_name), ZSTR_LEN(unqualified_name))) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
|
@ -9600,7 +9602,9 @@ bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
|
|||
|| kind == ZEND_AST_CONST || kind == ZEND_AST_CLASS_CONST
|
||||
|| kind == ZEND_AST_CLASS_NAME
|
||||
|| kind == ZEND_AST_MAGIC_CONST || kind == ZEND_AST_COALESCE
|
||||
|| kind == ZEND_AST_CONST_ENUM_INIT;
|
||||
|| kind == ZEND_AST_CONST_ENUM_INIT
|
||||
|| kind == ZEND_AST_NEW || kind == ZEND_AST_ARG_LIST
|
||||
|| kind == ZEND_AST_NAMED_ARG;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
@ -9704,8 +9708,61 @@ void zend_compile_const_expr_magic_const(zend_ast **ast_ptr) /* {{{ */
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
void zend_compile_const_expr(zend_ast **ast_ptr) /* {{{ */
|
||||
static void zend_compile_const_expr_new(zend_ast **ast_ptr)
|
||||
{
|
||||
zend_ast *class_ast = (*ast_ptr)->child[0];
|
||||
if (class_ast->kind == ZEND_AST_CLASS) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Cannot use anonymous class in constant expression");
|
||||
}
|
||||
if (class_ast->kind != ZEND_AST_ZVAL) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Cannot use dynamic class name in constant expression");
|
||||
}
|
||||
|
||||
zend_string *class_name = zend_resolve_class_name_ast(class_ast);
|
||||
int fetch_type = zend_get_class_fetch_type(class_name);
|
||||
if (ZEND_FETCH_CLASS_STATIC == fetch_type) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"\"static\" is not allowed in compile-time constants");
|
||||
}
|
||||
|
||||
zval *class_ast_zv = zend_ast_get_zval(class_ast);
|
||||
zval_ptr_dtor_nogc(class_ast_zv);
|
||||
ZVAL_STR(class_ast_zv, class_name);
|
||||
class_ast->attr = fetch_type;
|
||||
}
|
||||
|
||||
static void zend_compile_const_expr_args(zend_ast **ast_ptr)
|
||||
{
|
||||
zend_ast_list *list = zend_ast_get_list(*ast_ptr);
|
||||
bool uses_named_args = false;
|
||||
for (uint32_t i = 0; i < list->children; i++) {
|
||||
zend_ast *arg = list->child[i];
|
||||
if (arg->kind == ZEND_AST_UNPACK) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Argument unpacking in constant expressions is not supported");
|
||||
}
|
||||
if (arg->kind == ZEND_AST_NAMED_ARG) {
|
||||
uses_named_args = true;
|
||||
} else if (uses_named_args) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Cannot use positional argument after named argument");
|
||||
}
|
||||
}
|
||||
if (uses_named_args) {
|
||||
list->attr = 1;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
/* Whether the value of this expression may differ on each evaluation. */
|
||||
bool allow_dynamic;
|
||||
} const_expr_context;
|
||||
|
||||
void zend_compile_const_expr(zend_ast **ast_ptr, void *context) /* {{{ */
|
||||
{
|
||||
const_expr_context *ctx = (const_expr_context *) context;
|
||||
zend_ast *ast = *ast_ptr;
|
||||
if (ast == NULL || ast->kind == ZEND_AST_ZVAL) {
|
||||
return;
|
||||
|
@ -9728,17 +9785,29 @@ void zend_compile_const_expr(zend_ast **ast_ptr) /* {{{ */
|
|||
case ZEND_AST_MAGIC_CONST:
|
||||
zend_compile_const_expr_magic_const(ast_ptr);
|
||||
break;
|
||||
default:
|
||||
zend_ast_apply(ast, zend_compile_const_expr);
|
||||
case ZEND_AST_NEW:
|
||||
if (!ctx->allow_dynamic) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"New expressions are not supported in this context");
|
||||
}
|
||||
zend_compile_const_expr_new(ast_ptr);
|
||||
break;
|
||||
case ZEND_AST_ARG_LIST:
|
||||
zend_compile_const_expr_args(ast_ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
zend_ast_apply(ast, zend_compile_const_expr, context);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
void zend_const_expr_to_zval(zval *result, zend_ast **ast_ptr) /* {{{ */
|
||||
void zend_const_expr_to_zval(zval *result, zend_ast **ast_ptr, bool allow_dynamic) /* {{{ */
|
||||
{
|
||||
const_expr_context context;
|
||||
context.allow_dynamic = allow_dynamic;
|
||||
|
||||
zend_eval_const_expr(ast_ptr);
|
||||
zend_compile_const_expr(ast_ptr);
|
||||
zend_compile_const_expr(ast_ptr, &context);
|
||||
if ((*ast_ptr)->kind != ZEND_AST_ZVAL) {
|
||||
/* Replace with compiled AST zval representation. */
|
||||
zval ast_zv;
|
||||
|
@ -10376,6 +10445,25 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */
|
|||
}
|
||||
break;
|
||||
}
|
||||
// TODO: We should probably use zend_ast_apply to recursively walk nodes without
|
||||
// special handling. It is required that all nodes that are part of a const expr
|
||||
// are visited. Probably we should be distinguishing evaluation of const expr and
|
||||
// normal exprs here.
|
||||
case ZEND_AST_ARG_LIST:
|
||||
{
|
||||
zend_ast_list *list = zend_ast_get_list(ast);
|
||||
for (uint32_t i = 0; i < list->children; i++) {
|
||||
zend_eval_const_expr(&list->child[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ZEND_AST_NEW:
|
||||
zend_eval_const_expr(&ast->child[0]);
|
||||
zend_eval_const_expr(&ast->child[1]);
|
||||
return;
|
||||
case ZEND_AST_NAMED_ARG:
|
||||
zend_eval_const_expr(&ast->child[1]);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ void zend_compile_stmt(zend_ast *ast);
|
|||
void zend_compile_expr(znode *node, zend_ast *ast);
|
||||
zend_op *zend_compile_var(znode *node, zend_ast *ast, uint32_t type, bool by_ref);
|
||||
void zend_eval_const_expr(zend_ast **ast_ptr);
|
||||
void zend_const_expr_to_zval(zval *result, zend_ast **ast_ptr);
|
||||
void zend_const_expr_to_zval(zval *result, zend_ast **ast_ptr, bool allow_dynamic);
|
||||
|
||||
typedef int (*user_opcode_handler_t) (zend_execute_data *execute_data);
|
||||
|
||||
|
|
69
ext/reflection/tests/new_in_attributes.phpt
Normal file
69
ext/reflection/tests/new_in_attributes.phpt
Normal file
|
@ -0,0 +1,69 @@
|
|||
--TEST--
|
||||
new in attribute arguments
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[Attribute]
|
||||
class MyAttribute {
|
||||
public function __construct(public $x, public $y) {}
|
||||
}
|
||||
|
||||
#[MyAttribute(null, new stdClass)]
|
||||
class Test1 {
|
||||
}
|
||||
|
||||
$rc = new ReflectionClass(Test1::class);
|
||||
$ra = $rc->getAttributes()[0];
|
||||
$args1 = $ra->getArguments();
|
||||
$obj1 = $ra->newInstance();
|
||||
var_dump($args1, $obj1);
|
||||
|
||||
// Check that we get fresh instances each time:
|
||||
$args2 = $ra->getArguments();
|
||||
$obj2 = $ra->newInstance();
|
||||
var_dump($args1[1] !== $args2[1]);
|
||||
var_dump($obj1->y !== $obj2->y);
|
||||
|
||||
// Check that named args work:
|
||||
#[MyAttribute(y: new stdClass, x: null)]
|
||||
class Test2 {
|
||||
}
|
||||
|
||||
$rc = new ReflectionClass(Test2::class);
|
||||
$ra = $rc->getAttributes()[0];
|
||||
$args = $ra->getArguments();
|
||||
$obj = $ra->newInstance();
|
||||
var_dump($args, $obj);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
array(2) {
|
||||
[0]=>
|
||||
NULL
|
||||
[1]=>
|
||||
object(stdClass)#3 (0) {
|
||||
}
|
||||
}
|
||||
object(MyAttribute)#4 (2) {
|
||||
["x"]=>
|
||||
NULL
|
||||
["y"]=>
|
||||
object(stdClass)#5 (0) {
|
||||
}
|
||||
}
|
||||
bool(true)
|
||||
bool(true)
|
||||
array(2) {
|
||||
["y"]=>
|
||||
object(stdClass)#2 (0) {
|
||||
}
|
||||
["x"]=>
|
||||
NULL
|
||||
}
|
||||
object(MyAttribute)#10 (2) {
|
||||
["x"]=>
|
||||
NULL
|
||||
["y"]=>
|
||||
object(stdClass)#11 (0) {
|
||||
}
|
||||
}
|
27
ext/reflection/tests/new_in_constexpr.phpt
Normal file
27
ext/reflection/tests/new_in_constexpr.phpt
Normal file
|
@ -0,0 +1,27 @@
|
|||
--TEST--
|
||||
Handling of new in constant expressions in reflection
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test1() {
|
||||
static $x = new stdClass;
|
||||
return $x;
|
||||
}
|
||||
|
||||
$rf = new ReflectionFunction('test1');
|
||||
$s = $rf->getStaticVariables();
|
||||
var_dump($s['x'] === test1());
|
||||
|
||||
function test2($x = new stdClass) {
|
||||
return $x;
|
||||
}
|
||||
|
||||
$rf = new ReflectionFunction('test2');
|
||||
$rp = $rf->getParameters()[0];
|
||||
// Parameter default should *not* be the same.
|
||||
var_dump($rp->getDefaultValue() !== test2());
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
bool(true)
|
||||
bool(true)
|
Loading…
Add table
Add a link
Reference in a new issue