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

Proposal: Make match expression a constant expression (when parts are constant) #5951

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 47 additions & 0 deletions Zend/tests/match/043.phpt
@@ -0,0 +1,47 @@
--TEST--
Match expression can be used as a constant expression
--FILE--
<?php

// Here, this test uses strtolower because the locale dependence and change to the string
// ensures PHP has to use a temporary value instead of a literal, to test memory management.
//
// It's already possible to throw from a constant expression (e.g. undeclared constants),
// and match() can be seen as a much shorter way than changed ternary conditions on the same constant expressions.
define('dynamic_value', strtolower('FOO'));
function my_test($invalid = match(dynamic_value) {}) {
return $invalid;
}

const X = match (dynamic_value) {
default => 123,
};
echo "X=",X,"\n";
const Y = match (dynamic_value) {
'a', 'b' => 'default',
default => dynamic_value . 'd',
};
echo "Y=",Y,"\n";
const Z = match (dynamic_value) {
'a', 'foo', 'b' => [dynamic_value],
default => dynamic_value,
};
echo "Z=",json_encode(Z),"\n";

echo json_encode(my_test([dynamic_value])), "\n";
for ($i = 0; $i < 2; $i++) {
try {
my_test();
} catch (UnhandledMatchError $e) {
echo "Caught {$e->getMessage()} at line {$e->getLine()}\n";
}
}

?>
--EXPECT--
X=123
Y=food
Z=["foo"]
["foo"]
Caught Unhandled match value of type string at line 9
Caught Unhandled match value of type string at line 9
12 changes: 12 additions & 0 deletions Zend/tests/match/044.phpt
@@ -0,0 +1,12 @@
--TEST--
Match expression can be used as a constant expression unless parts aren't constant
--FILE--
<?php

// This is an uncatchable fatal error caused by compiling this file.
const X = match ($x) {
default => 123,
};
?>
--EXPECTF--
Fatal error: Constant expression contains invalid operations in %s044.php on line 4
45 changes: 45 additions & 0 deletions Zend/zend_ast.c
Expand Up @@ -654,6 +654,51 @@ ZEND_API int ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_c
zval_ptr_dtor_nogc(&op1);
}
break;
case ZEND_AST_MATCH:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

zend_const_expr_to_zval would also benefit from being updated to support ZEND_AST_MATCH, but it'd only matter if this were to be supported in constant expressions

{
zend_ast_list *list;
uint32_t i;
if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
ret = FAILURE;
break;
}
list = zend_ast_get_list(ast->child[1]);
for (i = 0; i < list->children; i++) {
zend_ast *arm = list->child[i];
zend_ast_list *arm_expr_list;
uint32_t j;
if (arm->child[0] == NULL) {
return_arm_expr:
if (UNEXPECTED(zend_ast_evaluate(result, arm->child[1], scope) != SUCCESS)) {
zval_ptr_dtor_nogc(&op1);
return FAILURE;
}
zval_ptr_dtor_nogc(&op1);
return SUCCESS;
}
arm_expr_list = zend_ast_get_list(arm->child[0]);
for (j = 0; j < arm_expr_list->children; j++) {
zval is_identical_zv;
if (UNEXPECTED(zend_ast_evaluate(&op2, arm_expr_list->child[j], scope) != SUCCESS)) {
return FAILURE;
}
ret = is_identical_function(&is_identical_zv, &op1, &op2);
zval_ptr_dtor_nogc(&op2);
if (ret != SUCCESS) {
zval_ptr_dtor_nogc(&op1);
return ret;
}
if (Z_TYPE(is_identical_zv) == IS_TRUE) {
goto return_arm_expr;
}
}
}

zend_throw_exception_ex(zend_ce_unhandled_match_error, 0, "Unhandled match value of type %s", zend_zval_type_name(&op1));
zval_ptr_dtor_nogc(&op1);
ret = FAILURE;
break;
}
case ZEND_AST_COALESCE:
if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
ret = FAILURE;
Expand Down
4 changes: 3 additions & 1 deletion Zend/zend_compile.c
Expand Up @@ -9144,7 +9144,9 @@ zend_bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
|| kind == ZEND_AST_UNPACK
|| 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_MAGIC_CONST || kind == ZEND_AST_COALESCE
|| kind == ZEND_AST_MATCH || kind == ZEND_AST_MATCH_ARM
|| kind == ZEND_AST_MATCH_ARM_LIST || kind == ZEND_AST_EXPR_LIST;
}
/* }}} */

Expand Down