Skip to content
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

Add (void) cast #18115

Merged
merged 2 commits into from
Mar 24, 2025
Merged
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
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -38,6 +38,8 @@ PHP NEWS
. Fixed bug GH-18033 (NULL-ptr dereference when using register_tick_function
in destructor). (nielsdos)
. Fixed bug GH-18026 (Improve "expecting token" error for ampersand). (ilutov)
. Added the (void) cast to indicate that not using a value is intentional.
(timwolla)

- Curl:
. Added curl_multi_get_handles(). (timwolla)
3 changes: 3 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
@@ -106,6 +106,9 @@ PHP 8.5 UPGRADE NOTES
. Fatal Errors (such as an exceeded maximum execution time) now include a
backtrace.
RFC: https://wiki.php.net/rfc/error_backtraces_v2
. Added the (void) to indicate that not using a value is intentional. The
(void) cast does nothing by itself.
RFC: https://wiki.php.net/rfc/marking_return_value_as_important

- Curl:
. Added support for share handles that are persisted across multiple PHP
4 changes: 3 additions & 1 deletion Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
@@ -274,7 +274,9 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
* If it's not local, then the other blocks successors must also eventually either FREE or consume the temporary,
* hence removing the temporary is not safe in the general case, especially when other consumers are not FREE.
* A FREE may not be removed without also removing the source's result, because otherwise that would cause a memory leak. */
if (opline->op1_type == IS_TMP_VAR) {
if (opline->extended_value == ZEND_FREE_VOID_CAST) {
/* Keep the ZEND_FREE opcode alive. */
} else if (opline->op1_type == IS_TMP_VAR) {
src = VAR_SOURCE(opline->op1);
if (src) {
switch (src->opcode) {
3 changes: 2 additions & 1 deletion Zend/Optimizer/dce.c
Original file line number Diff line number Diff line change
@@ -80,7 +80,6 @@ static inline bool may_have_side_effects(
case ZEND_IS_IDENTICAL:
case ZEND_IS_NOT_IDENTICAL:
case ZEND_QM_ASSIGN:
case ZEND_FREE:
case ZEND_FE_FREE:
case ZEND_TYPE_CHECK:
case ZEND_DEFINED:
@@ -127,6 +126,8 @@ static inline bool may_have_side_effects(
case ZEND_ARRAY_KEY_EXISTS:
/* No side effects */
return 0;
case ZEND_FREE:
return opline->extended_value == ZEND_FREE_VOID_CAST;
case ZEND_ADD_ARRAY_ELEMENT:
/* TODO: We can't free two vars. Keep instruction alive. <?php [0, "$a" => "$b"]; */
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && (opline->op2_type & (IS_VAR|IS_TMP_VAR))) {
47 changes: 47 additions & 0 deletions Zend/tests/type_casts/cast_to_void.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
casting different variables to void
--FILE--
<?php

$r = fopen(__FILE__, "r");

class test {
private $var1 = 1;
public $var2 = 2;
protected $var3 = 3;

function __toString() {
return "10";
}
}

$o = new test;

$vars = array(
"string",
"",
"\0",
"8754456",
9876545,
0.10,
array(),
array(1,2,3),
false,
true,
NULL,
$r,
$o
);

foreach ($vars as $var) {
(void)$var;
}

// Cast literals to verify behavior for optimized const zvals
(void)"foo";
(void)["foo", "bar"];

echo "Done\n";
?>
--EXPECTF--
Done
18 changes: 18 additions & 0 deletions Zend/tests/type_casts/cast_to_void_ast.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
(void) is included in AST printing
--FILE--
<?php

try {
assert(false && function () {
(void) somefunc();
});
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
assert(false && function () {
(void)somefunc();
})
30 changes: 30 additions & 0 deletions Zend/tests/type_casts/cast_to_void_destructor.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
casting to void destroys the value.
--FILE--
<?php

class WithDestructor {
public function __destruct() {
echo __METHOD__, PHP_EOL;
}
}

function test(): WithDestructor {
return new WithDestructor();
}

function do_it(): void {
echo "Before", PHP_EOL;

(void)test();

echo "After", PHP_EOL;
}

do_it();

?>
--EXPECT--
Before
WithDestructor::__destruct
After
11 changes: 11 additions & 0 deletions Zend/tests/type_casts/cast_to_void_statement.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
casting to void is a statement
--FILE--
<?php

$tmp = (void)$dummy;

echo "Done\n";
?>
--EXPECTF--
Parse error: syntax error, unexpected token "(void)" in %s on line %d
3 changes: 3 additions & 0 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
@@ -2261,6 +2261,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
EMPTY_SWITCH_DEFAULT_CASE();
}
break;
case ZEND_AST_CAST_VOID:
PREFIX_OP("(void)", 240, 241);
break;
case ZEND_AST_EMPTY:
FUNC_OP("empty");
case ZEND_AST_ISSET:
1 change: 1 addition & 0 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
@@ -84,6 +84,7 @@ enum _zend_ast_kind {
ZEND_AST_UNARY_PLUS,
ZEND_AST_UNARY_MINUS,
ZEND_AST_CAST,
ZEND_AST_CAST_VOID,
ZEND_AST_EMPTY,
ZEND_AST_ISSET,
ZEND_AST_SILENCE,
23 changes: 23 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
@@ -10589,6 +10589,26 @@ static void zend_compile_include_or_eval(znode *result, zend_ast *ast) /* {{{ */
}
/* }}} */

static void zend_compile_void_cast(znode *result, zend_ast *ast)
{
zend_ast *expr_ast = ast->child[0];
znode expr_node;
zend_op *opline;

zend_compile_expr(&expr_node, expr_ast);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you could optimize it a bit by not even handling IS_CONSTANT_AST in the first place, i.e. early returning.
I would still keep the IS_CONST handling below though because it generalises.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with the (void)(bool) cast, this feels like needless added complexity for a case that is unlikely to happen in practice and also no longer matters once the file is compiled and OPcached.


switch (expr_node.op_type) {
case IS_TMP_VAR:
case IS_VAR:
opline = zend_emit_op(NULL, ZEND_FREE, &expr_node, NULL);
opline->extended_value = ZEND_FREE_VOID_CAST;
break;
case IS_CONST:
zend_do_free(&expr_node);
break;
}
}

static void zend_compile_isset_or_empty(znode *result, zend_ast *ast) /* {{{ */
{
zend_ast *var_ast = ast->child[0];
@@ -11547,6 +11567,9 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */
case ZEND_AST_THROW:
zend_compile_expr(NULL, ast);
break;
case ZEND_AST_CAST_VOID:
zend_compile_void_cast(NULL, ast);
break;
default:
{
znode result;
1 change: 1 addition & 0 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
@@ -1092,6 +1092,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);

#define ZEND_FREE_ON_RETURN (1<<0)
#define ZEND_FREE_SWITCH (1<<1)
#define ZEND_FREE_VOID_CAST (1<<2)

#define ZEND_SEND_BY_VAL 0u
#define ZEND_SEND_BY_REF 1u
2 changes: 2 additions & 0 deletions Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
@@ -217,6 +217,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%token T_OBJECT_CAST "'(object)'"
%token T_BOOL_CAST "'(bool)'"
%token T_UNSET_CAST "'(unset)'"
%token T_VOID_CAST "'(void)'"
%token T_OBJECT_OPERATOR "'->'"
%token T_NULLSAFE_OBJECT_OPERATOR "'?->'"
%token T_DOUBLE_ARROW "'=>'"
@@ -534,6 +535,7 @@ statement:
{ $$ = zend_ast_create(ZEND_AST_TRY, $3, $5, $6); }
| T_GOTO T_STRING ';' { $$ = zend_ast_create(ZEND_AST_GOTO, $2); }
| T_STRING ':' { $$ = zend_ast_create(ZEND_AST_LABEL, $1); }
| T_VOID_CAST expr ';' { $$ = zend_ast_create(ZEND_AST_CAST_VOID, $2); }
;

catch_list:
4 changes: 4 additions & 0 deletions Zend/zend_language_scanner.l
Original file line number Diff line number Diff line change
@@ -1657,6 +1657,10 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
RETURN_TOKEN(T_UNSET_CAST);
}

<ST_IN_SCRIPTING>"("{TABS_AND_SPACES}("void"){TABS_AND_SPACES}")" {
RETURN_TOKEN(T_VOID_CAST);
}

<ST_IN_SCRIPTING>"eval" {
RETURN_TOKEN_WITH_IDENT(T_EVAL);
}
1 change: 1 addition & 0 deletions ext/tokenizer/tokenizer_data.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions ext/tokenizer/tokenizer_data.stub.php
Original file line number Diff line number Diff line change
@@ -642,6 +642,11 @@
* @cvalue T_UNSET_CAST
*/
const T_UNSET_CAST = UNKNOWN;
/**
* @var int
* @cvalue T_VOID_CAST
*/
const T_VOID_CAST = UNKNOWN;
/**
* @var int
* @cvalue T_OBJECT_OPERATOR
3 changes: 2 additions & 1 deletion ext/tokenizer/tokenizer_data_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Oops, something went wrong.