Skip to content

Commit a9db173

Browse files
committed
Add compile-time restrictions on #[\NoDiscard] usage
1 parent c9c7e08 commit a9db173

8 files changed

+133
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
#[\NoDiscard]: Not allowed on '__clone'.
3+
--FILE--
4+
<?php
5+
6+
class Clazz {
7+
#[\NoDiscard]
8+
public function __clone() {
9+
}
10+
}
11+
12+
$cls = new Clazz();
13+
14+
?>
15+
--EXPECTF--
16+
Fatal error: Method Clazz::__clone cannot be #[\NoDiscard] 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+
#[\NoDiscard]: Not allowed on '__construct'.
3+
--FILE--
4+
<?php
5+
6+
class Clazz {
7+
#[\NoDiscard]
8+
public function __construct() {
9+
}
10+
}
11+
12+
$cls = new Clazz();
13+
14+
?>
15+
--EXPECTF--
16+
Fatal error: Method Clazz::__construct cannot be #[\NoDiscard] in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
#[\NoDiscard]: Not allowed on never function.
3+
--FILE--
4+
<?php
5+
6+
#[\NoDiscard]
7+
function test(): never {
8+
throw new \Exception('Error!');
9+
}
10+
11+
test();
12+
13+
?>
14+
--EXPECTF--
15+
Fatal error: A never returning function does not return a value, but #[\NoDiscard] requires a return value in %s on line %d
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
#[\NoDiscard]: Not allowed on 'get' property hook.
3+
--FILE--
4+
<?php
5+
6+
class Clazz {
7+
public string $test {
8+
#[\NoDiscard]
9+
get {
10+
return 'asd';
11+
}
12+
}
13+
}
14+
15+
$cls = new Clazz();
16+
$cls->test;
17+
18+
?>
19+
--EXPECTF--
20+
Fatal error: #[\NoDiscard] is not supported for property hooks in %s on line %d
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
#[\NoDiscard]: Not allowed on 'set' property hook.
3+
--FILE--
4+
<?php
5+
6+
class Clazz {
7+
public string $test {
8+
#[\NoDiscard]
9+
set(string $value) {
10+
$this->test = $value;
11+
}
12+
}
13+
}
14+
15+
$cls = new Foo();
16+
$cls->test = 'foo';
17+
18+
?>
19+
--EXPECTF--
20+
Fatal error: #[\NoDiscard] is not supported for property hooks in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
#[\NoDiscard]: Not allowed on void function.
3+
--FILE--
4+
<?php
5+
6+
#[\NoDiscard]
7+
function test(): void {
8+
return;
9+
}
10+
11+
test();
12+
13+
?>
14+
--EXPECTF--
15+
Fatal error: A void function does not return a value, but #[\NoDiscard] requires a return value in %s on line %d

Zend/zend_API.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,6 +2698,12 @@ static void zend_check_magic_method_arg_type(uint32_t arg_num, const zend_class_
26982698

26992699
static void zend_check_magic_method_return_type(const zend_class_entry *ce, const zend_function *fptr, int error_type, int return_type)
27002700
{
2701+
if (return_type == MAY_BE_VOID) {
2702+
if (fptr->common.fn_flags & ZEND_ACC_NODISCARD) {
2703+
zend_error_noreturn(error_type, "Method %s::%s cannot be #[\\NoDiscard]", ZSTR_VAL(ce->name), ZSTR_VAL(fptr->common.function_name));
2704+
}
2705+
}
2706+
27012707
if (!(fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
27022708
/* For backwards compatibility reasons, do not enforce the return type if it is not set. */
27032709
return;
@@ -2757,6 +2763,10 @@ static void zend_check_magic_method_no_return_type(
27572763
zend_error_noreturn(error_type, "Method %s::%s() cannot declare a return type",
27582764
ZSTR_VAL(ce->name), ZSTR_VAL(fptr->common.function_name));
27592765
}
2766+
2767+
if (fptr->common.fn_flags & ZEND_ACC_NODISCARD) {
2768+
zend_error_noreturn(error_type, "Method %s::%s cannot be #[\\NoDiscard]", ZSTR_VAL(ce->name), ZSTR_VAL(fptr->common.function_name));
2769+
}
27602770
}
27612771

27622772
ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce, const zend_function *fptr, zend_string *lcname, int error_type) /* {{{ */

Zend/zend_compile.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8413,6 +8413,27 @@ static zend_op_array *zend_compile_func_decl_ex(
84138413
}
84148414
}
84158415

8416+
if (op_array->fn_flags & ZEND_ACC_NODISCARD) {
8417+
if (is_hook) {
8418+
zend_error_noreturn(E_COMPILE_ERROR, "#[\\NoDiscard] is not supported for property hooks");
8419+
}
8420+
8421+
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
8422+
zend_arg_info *return_info = CG(active_op_array)->arg_info - 1;
8423+
if (ZEND_TYPE_CONTAINS_CODE(return_info->type, IS_VOID)) {
8424+
zend_error_noreturn(E_COMPILE_ERROR,
8425+
"A void %s does not return a value, but #[\\NoDiscard] requires a return value",
8426+
CG(active_class_entry) != NULL ? "method" : "function");
8427+
}
8428+
8429+
if (ZEND_TYPE_CONTAINS_CODE(return_info->type, IS_NEVER)) {
8430+
zend_error_noreturn(E_COMPILE_ERROR,
8431+
"A never returning %s does not return a value, but #[\\NoDiscard] requires a return value",
8432+
CG(active_class_entry) != NULL ? "method" : "function");
8433+
}
8434+
}
8435+
}
8436+
84168437
zend_compile_stmt(stmt_ast);
84178438

84188439
if (is_method) {

0 commit comments

Comments
 (0)