Skip to content

New in initializers (reduced version) #7153

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 2 commits into from
Closed
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
62 changes: 62 additions & 0 deletions Zend/tests/constexpr/new.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 35 additions & 0 deletions Zend/tests/constexpr/new_allowed.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions Zend/tests/constexpr/new_anon_class.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 44 additions & 0 deletions Zend/tests/constexpr/new_arg_eval.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions Zend/tests/constexpr/new_arg_unpack.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions Zend/tests/constexpr/new_dynamic_class_name.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions Zend/tests/constexpr/new_invalid_operation_in_arg.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 51 additions & 0 deletions Zend/tests/constexpr/new_named_params.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 18 additions & 0 deletions Zend/tests/constexpr/new_not_allowed_class_constant.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions Zend/tests/constexpr/new_not_allowed_property.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions Zend/tests/constexpr/new_positional_after_named.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 39 additions & 0 deletions Zend/tests/constexpr/new_self_parent.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions Zend/tests/constexpr/new_static.phpt
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading