Merge branch 'coalesce_operator'

* coalesce_operator:
  Extended coalesce operator test case for ordering/short-circuiting
  Ensure not evaluated twice
  Added test
  Initial coalesce operator implementation
This commit is contained in:
Andrea Faulds 2014-09-28 00:07:04 +01:00
commit 2d069f640e
12 changed files with 3587 additions and 3261 deletions

View File

@ -115,6 +115,7 @@ enum _zend_ast_kind {
ZEND_AST_NEW,
ZEND_AST_INSTANCEOF,
ZEND_AST_YIELD,
ZEND_AST_COALESCE,
ZEND_AST_STATIC,
ZEND_AST_WHILE,

View File

@ -5324,6 +5324,30 @@ void zend_compile_conditional(znode *result, zend_ast *ast TSRMLS_DC) /* {{{ */
}
/* }}} */
void zend_compile_coalesce(znode *result, zend_ast *ast TSRMLS_DC) /* {{{ */
{
zend_ast *expr_ast = ast->child[0];
zend_ast *default_ast = ast->child[1];
znode expr_node, default_node;
zend_op *opline;
uint32_t opnum;
zend_compile_var(&expr_node, expr_ast, BP_VAR_IS TSRMLS_CC);
opnum = get_next_op_number(CG(active_op_array));
zend_emit_op(result, ZEND_COALESCE, &expr_node, NULL TSRMLS_CC);
zend_compile_expr(&default_node, default_ast TSRMLS_CC);
opline = zend_emit_op(NULL, ZEND_QM_ASSIGN, &default_node, NULL TSRMLS_CC);
SET_NODE(opline->result, result);
opline = &CG(active_op_array)->opcodes[opnum];
opline->op2.opline_num = get_next_op_number(CG(active_op_array));
}
/* }}} */
void zend_compile_print(znode *result, zend_ast *ast TSRMLS_DC) /* {{{ */
{
zend_ast *expr_ast = ast->child[0];
@ -6158,6 +6182,9 @@ void zend_compile_expr(znode *result, zend_ast *ast TSRMLS_DC) /* {{{ */
case ZEND_AST_CONDITIONAL:
zend_compile_conditional(result, ast TSRMLS_CC);
return;
case ZEND_AST_COALESCE:
zend_compile_coalesce(result, ast TSRMLS_CC);
return;
case ZEND_AST_PRINT:
zend_compile_print(result, ast TSRMLS_CC);
return;

View File

@ -67,6 +67,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%right T_YIELD
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL
%left '?' ':'
%right T_COALESCE
%left T_BOOLEAN_OR
%left T_BOOLEAN_AND
%left '|'
@ -221,6 +222,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%token T_NS_C "__NAMESPACE__ (T_NS_C)"
%token T_NS_SEPARATOR "\\ (T_NS_SEPARATOR)"
%token T_ELLIPSIS "... (T_ELLIPSIS)"
%token T_COALESCE "?? (T_COALESCE)"
%token T_POW "** (T_POW)"
%token T_POW_EQUAL "**= (T_POW_EQUAL)"
@ -827,6 +829,8 @@ expr_without_variable:
{ $$ = zend_ast_create(ZEND_AST_CONDITIONAL, $1, $3, $5); }
| expr '?' ':' expr
{ $$ = zend_ast_create(ZEND_AST_CONDITIONAL, $1, NULL, $4); }
| expr T_COALESCE expr
{ $$ = zend_ast_create(ZEND_AST_COALESCE, $1, $3); }
| internal_functions_in_yacc { $$ = $1; }
| T_INT_CAST expr { $$ = zend_ast_create_cast(IS_LONG, $2); }
| T_DOUBLE_CAST expr { $$ = zend_ast_create_cast(IS_DOUBLE, $2); }

File diff suppressed because it is too large Load Diff

View File

@ -1202,6 +1202,10 @@ NEWLINE ("\r"|"\n"|"\r\n")
return T_ELLIPSIS;
}
<ST_IN_SCRIPTING>"??" {
return T_COALESCE;
}
<ST_IN_SCRIPTING>"new" {
return T_NEW;
}

View File

@ -1,4 +1,4 @@
/* Generated by re2c 0.13.5 */
/* Generated by re2c 0.13.6 */
#line 3 "Zend/zend_language_scanner_defs.h"
enum YYCONDTYPE {

View File

@ -741,6 +741,7 @@ ZEND_API int pass_two(zend_op_array *op_array TSRMLS_DC)
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
case ZEND_JMP_SET:
case ZEND_COALESCE:
case ZEND_NEW:
case ZEND_FE_RESET:
case ZEND_FE_FETCH:

View File

@ -5138,6 +5138,41 @@ ZEND_VM_HANDLER(152, ZEND_JMP_SET, CONST|TMP|VAR|CV, ANY)
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HANDLER(169, ZEND_COALESCE, CONST|TMP|VAR|CV, ANY)
{
USE_OPLINE
zend_free_op free_op1;
zval *value;
int is_ref = 0;
SAVE_OPLINE();
value = GET_OP1_ZVAL_PTR(BP_VAR_IS);
if ((OP1_TYPE == IS_VAR || OP1_TYPE == IS_CV) && Z_ISREF_P(value)) {
is_ref = 1;
value = Z_REFVAL_P(value);
}
if (Z_TYPE_P(value) > IS_NULL) {
ZVAL_COPY_VALUE(EX_VAR(opline->result.var), value);
if (OP1_TYPE == IS_CONST) {
if (UNEXPECTED(Z_OPT_COPYABLE_P(value))) {
zval_copy_ctor_func(EX_VAR(opline->result.var));
}
} else if (OP1_TYPE == IS_CV) {
if (Z_OPT_REFCOUNTED_P(value)) Z_ADDREF_P(value);
} else if (OP1_TYPE == IS_VAR && is_ref) {
if (Z_OPT_REFCOUNTED_P(value)) Z_ADDREF_P(value);
FREE_OP1();
}
ZEND_VM_JMP(opline->op2.jmp_addr);
}
FREE_OP1();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HANDLER(22, ZEND_QM_ASSIGN, CONST|TMP|VAR|CV, ANY)
{
USE_OPLINE

View File

@ -3291,6 +3291,40 @@ static int ZEND_FASTCALL ZEND_JMP_SET_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_AR
ZEND_VM_NEXT_OPCODE();
}
static int ZEND_FASTCALL ZEND_COALESCE_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *value;
int is_ref = 0;
SAVE_OPLINE();
value = opline->op1.zv;
if ((IS_CONST == IS_VAR || IS_CONST == IS_CV) && Z_ISREF_P(value)) {
is_ref = 1;
value = Z_REFVAL_P(value);
}
if (Z_TYPE_P(value) > IS_NULL) {
ZVAL_COPY_VALUE(EX_VAR(opline->result.var), value);
if (IS_CONST == IS_CONST) {
if (UNEXPECTED(Z_OPT_COPYABLE_P(value))) {
zval_copy_ctor_func(EX_VAR(opline->result.var));
}
} else if (IS_CONST == IS_CV) {
if (Z_OPT_REFCOUNTED_P(value)) Z_ADDREF_P(value);
} else if (IS_CONST == IS_VAR && is_ref) {
if (Z_OPT_REFCOUNTED_P(value)) Z_ADDREF_P(value);
}
ZEND_VM_JMP(opline->op2.jmp_addr);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
static int ZEND_FASTCALL ZEND_QM_ASSIGN_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@ -10011,6 +10045,41 @@ static int ZEND_FASTCALL ZEND_JMP_SET_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS
ZEND_VM_NEXT_OPCODE();
}
static int ZEND_FASTCALL ZEND_COALESCE_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zend_free_op free_op1;
zval *value;
int is_ref = 0;
SAVE_OPLINE();
value = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if ((IS_TMP_VAR == IS_VAR || IS_TMP_VAR == IS_CV) && Z_ISREF_P(value)) {
is_ref = 1;
value = Z_REFVAL_P(value);
}
if (Z_TYPE_P(value) > IS_NULL) {
ZVAL_COPY_VALUE(EX_VAR(opline->result.var), value);
if (IS_TMP_VAR == IS_CONST) {
if (UNEXPECTED(Z_OPT_COPYABLE_P(value))) {
zval_copy_ctor_func(EX_VAR(opline->result.var));
}
} else if (IS_TMP_VAR == IS_CV) {
if (Z_OPT_REFCOUNTED_P(value)) Z_ADDREF_P(value);
} else if (IS_TMP_VAR == IS_VAR && is_ref) {
if (Z_OPT_REFCOUNTED_P(value)) Z_ADDREF_P(value);
zval_dtor(free_op1.var);
}
ZEND_VM_JMP(opline->op2.jmp_addr);
}
zval_dtor(free_op1.var);
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
static int ZEND_FASTCALL ZEND_QM_ASSIGN_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@ -16791,6 +16860,41 @@ static int ZEND_FASTCALL ZEND_JMP_SET_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS
ZEND_VM_NEXT_OPCODE();
}
static int ZEND_FASTCALL ZEND_COALESCE_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zend_free_op free_op1;
zval *value;
int is_ref = 0;
SAVE_OPLINE();
value = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if ((IS_VAR == IS_VAR || IS_VAR == IS_CV) && Z_ISREF_P(value)) {
is_ref = 1;
value = Z_REFVAL_P(value);
}
if (Z_TYPE_P(value) > IS_NULL) {
ZVAL_COPY_VALUE(EX_VAR(opline->result.var), value);
if (IS_VAR == IS_CONST) {
if (UNEXPECTED(Z_OPT_COPYABLE_P(value))) {
zval_copy_ctor_func(EX_VAR(opline->result.var));
}
} else if (IS_VAR == IS_CV) {
if (Z_OPT_REFCOUNTED_P(value)) Z_ADDREF_P(value);
} else if (IS_VAR == IS_VAR && is_ref) {
if (Z_OPT_REFCOUNTED_P(value)) Z_ADDREF_P(value);
zval_ptr_dtor_nogc(free_op1.var);
}
ZEND_VM_JMP(opline->op2.jmp_addr);
}
zval_ptr_dtor_nogc(free_op1.var);
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
static int ZEND_FASTCALL ZEND_QM_ASSIGN_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@ -33794,6 +33898,40 @@ static int ZEND_FASTCALL ZEND_JMP_SET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
ZEND_VM_NEXT_OPCODE();
}
static int ZEND_FASTCALL ZEND_COALESCE_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *value;
int is_ref = 0;
SAVE_OPLINE();
value = _get_zval_ptr_cv_BP_VAR_IS(execute_data, opline->op1.var TSRMLS_CC);
if ((IS_CV == IS_VAR || IS_CV == IS_CV) && Z_ISREF_P(value)) {
is_ref = 1;
value = Z_REFVAL_P(value);
}
if (Z_TYPE_P(value) > IS_NULL) {
ZVAL_COPY_VALUE(EX_VAR(opline->result.var), value);
if (IS_CV == IS_CONST) {
if (UNEXPECTED(Z_OPT_COPYABLE_P(value))) {
zval_copy_ctor_func(EX_VAR(opline->result.var));
}
} else if (IS_CV == IS_CV) {
if (Z_OPT_REFCOUNTED_P(value)) Z_ADDREF_P(value);
} else if (IS_CV == IS_VAR && is_ref) {
if (Z_OPT_REFCOUNTED_P(value)) Z_ADDREF_P(value);
}
ZEND_VM_JMP(opline->op2.jmp_addr);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
static int ZEND_FASTCALL ZEND_QM_ASSIGN_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@ -47388,6 +47526,31 @@ void zend_init_opcodes_handlers(void)
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_COALESCE_SPEC_CONST_HANDLER,
ZEND_COALESCE_SPEC_CONST_HANDLER,
ZEND_COALESCE_SPEC_CONST_HANDLER,
ZEND_COALESCE_SPEC_CONST_HANDLER,
ZEND_COALESCE_SPEC_CONST_HANDLER,
ZEND_COALESCE_SPEC_TMP_HANDLER,
ZEND_COALESCE_SPEC_TMP_HANDLER,
ZEND_COALESCE_SPEC_TMP_HANDLER,
ZEND_COALESCE_SPEC_TMP_HANDLER,
ZEND_COALESCE_SPEC_TMP_HANDLER,
ZEND_COALESCE_SPEC_VAR_HANDLER,
ZEND_COALESCE_SPEC_VAR_HANDLER,
ZEND_COALESCE_SPEC_VAR_HANDLER,
ZEND_COALESCE_SPEC_VAR_HANDLER,
ZEND_COALESCE_SPEC_VAR_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_COALESCE_SPEC_CV_HANDLER,
ZEND_COALESCE_SPEC_CV_HANDLER,
ZEND_COALESCE_SPEC_CV_HANDLER,
ZEND_COALESCE_SPEC_CV_HANDLER,
ZEND_COALESCE_SPEC_CV_HANDLER,
ZEND_NULL_HANDLER
};
zend_opcode_handlers = (opcode_handler_t*)labels;

View File

@ -21,7 +21,7 @@
#include <stdio.h>
#include <zend.h>
const char *zend_vm_opcodes_map[169] = {
const char *zend_vm_opcodes_map[170] = {
"ZEND_NOP",
"ZEND_ADD",
"ZEND_SUB",
@ -191,6 +191,7 @@ const char *zend_vm_opcodes_map[169] = {
"ZEND_POW",
"ZEND_ASSIGN_POW",
"ZEND_BIND_GLOBAL",
"ZEND_COALESCE",
};
ZEND_API const char* zend_get_opcode_name(zend_uchar opcode) {

View File

@ -179,5 +179,6 @@ ZEND_API const char *zend_get_opcode_name(zend_uchar opcode);
#define ZEND_POW 166
#define ZEND_ASSIGN_POW 167
#define ZEND_BIND_GLOBAL 168
#define ZEND_COALESCE 169
#endif

View File

@ -0,0 +1,79 @@
--TEST--
Test ?? operator
--FILE--
<?php
$var = 7;
$var2 = NULL;
$obj = new StdClass;
$obj->boo = 7;
$arr = [
2 => 7,
"foo" => "bar",
"foobar" => NULL,
"qux" => $obj,
"bing" => [
"bang"
]
];
function foobar() {
echo "called\n";
return ['a'];
}
var_dump($nonexistant_variable ?? 3);
echo PHP_EOL;
var_dump($var ?? 3);
var_dump($var2 ?? 3);
echo PHP_EOL;
var_dump($obj->boo ?? 3);
var_dump($obj->bing ?? 3);
var_dump($arr["qux"]->boo ?? 3);
var_dump($arr["qux"]->bing ?? 3);
echo PHP_EOL;
var_dump($arr[2] ?? 3);
var_dump($arr["foo"] ?? 3);
var_dump($arr["foobar"] ?? 3);
var_dump($arr["qux"] ?? 3);
var_dump($arr["bing"][0] ?? 3);
var_dump($arr["bing"][1] ?? 3);
echo PHP_EOL;
var_dump(foobar()[0] ?? false);
echo PHP_EOL;
function f($x)
{
printf("%s(%d)\n", __FUNCTION__, $x);
return $x;
}
$a = f(null) ?? f(1) ?? f(2);
?>
--EXPECTF--
int(3)
int(7)
int(3)
int(7)
int(3)
int(7)
int(3)
int(7)
string(3) "bar"
int(3)
object(stdClass)#%d (%d) {
["boo"]=>
int(7)
}
string(4) "bang"
int(3)
called
string(1) "a"
f(0)
f(1)