Skip to content

Commit

Permalink
Allow arbitrary expressions for empty()
Browse files Browse the repository at this point in the history
This change is as per RFC https://wiki.php.net/rfc/empty_isset_exprs.

The change allows passing the result of function calls and other
expressions to the empty() language construct. This is accomplished by
simply rewriting empty(expr) to !expr.

The change does not affect the suppression of errors when using empty()
on variables. empty($undefinedVar) will continue not to throw errors.
When an expression is used inside empty() on the other hand, errors will
not be suppressed. Thus empty($undefinedVar + $somethingElse) *will*
throw a notice.

The change also does not make empty() into a real function, so using
'empty' as a callback is still not possible.

In addition to the empty() changes the commit adds nicer error messages
when isset() is used on function call results or other expressions.
  • Loading branch information
nikic committed May 13, 2012
1 parent 5852e5f commit ec061a9
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 4 deletions.
3 changes: 3 additions & 0 deletions NEWS
Expand Up @@ -7,6 +7,9 @@ PHP NEWS
. World domination
. Improve set_exception_handler while doing reset.(Laruence)
. Support constant array/string dereferencing. (Laruence)
. Add support for using empty() on the result of function calls and
other expressions (https://wiki.php.net/rfc/empty_isset_exprs).
(Nikita Popov)

- Core:
. Fixed bug #61681 (Malformed grammar). (Nikita Popov, Etienne, Laruence).
Expand Down
5 changes: 4 additions & 1 deletion UPGRADING
Expand Up @@ -28,6 +28,9 @@ PHP X.Y UPGRADE NOTES

- Support constant array/string dereferencing. (Laruence)
(https://wiki.php.net/rfc/constdereference)
- Add support for using empty() on the result of function calls and
other expressions. Thus it is now possible to write empty(getArray()),
for example. (https://wiki.php.net/rfc/empty_isset_exprs)

========================================
2. Changes in SAPI modules
Expand All @@ -51,7 +54,7 @@ PHP X.Y UPGRADE NOTES
- Implemented format character "Z": NUL-padded string
- "a" now does not remove trailing NUL characters on unpack() anymore
- "A" will now strip all trailing ASCII whitespace on unpack() (it used to
remove only trailing spaces.
remove only trailing spaces)

========================================
5. New Functions
Expand Down
32 changes: 32 additions & 0 deletions Zend/tests/empty_with_expr.phpt
@@ -0,0 +1,32 @@
--TEST--
empty() with arbitrary expressions
--FILE--
<?php

function getEmptyArray() { return []; }
function getNonEmptyArray() { return [1, 2, 3]; }

var_dump(empty([]));
var_dump(empty([1, 2, 3]));

var_dump(empty(getEmptyArray()));
var_dump(empty(getNonEmptyArray()));

var_dump(empty([] + []));
var_dump(empty([1, 2, 3] + []));

var_dump(empty("string"));
var_dump(empty(""));
var_dump(empty(true));
var_dump(empty(false));
--EXPECT--
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(false)
bool(true)
bool(false)
bool(true)
8 changes: 8 additions & 0 deletions Zend/tests/isset_expr_error.phpt
@@ -0,0 +1,8 @@
--TEST--
Error message for isset(func())
--FILE--
<?php
isset(1 + 1);
?>
--EXPECTF--
Fatal error: Cannot use isset() on the result of an expression (you can use "null !== expression" instead) in %s on line %d
8 changes: 8 additions & 0 deletions Zend/tests/isset_func_error.phpt
@@ -0,0 +1,8 @@
--TEST--
Error message for isset(func())
--FILE--
<?php
isset(abc());
?>
--EXPECTF--
Fatal error: Cannot use isset() on the result of a function call (you can use "null !== func()" instead) in %s on line %d
11 changes: 10 additions & 1 deletion Zend/zend_compile.c
Expand Up @@ -6089,7 +6089,16 @@ void zend_do_isset_or_isempty(int type, znode *result, znode *variable TSRMLS_DC

zend_do_end_variable_parse(variable, BP_VAR_IS, 0 TSRMLS_CC);

zend_check_writable_variable(variable);
if (zend_is_function_or_method_call(variable)) {
if (type == ZEND_ISEMPTY) {
/* empty(func()) can be transformed to !func() */
zend_do_unary_op(ZEND_BOOL_NOT, result, variable TSRMLS_CC);
} else {
zend_error(E_COMPILE_ERROR, "Cannot use isset() on the result of a function call (you can use \"null !== func()\" instead)");
}

return;
}

if (variable->op_type == IS_CV) {
last_op = get_next_op(CG(active_op_array) TSRMLS_CC);
Expand Down
10 changes: 8 additions & 2 deletions Zend/zend_language_parser.y
Expand Up @@ -1158,6 +1158,7 @@ encaps_var_offset:
internal_functions_in_yacc:
T_ISSET '(' isset_variables ')' { $$ = $3; }
| T_EMPTY '(' variable ')' { zend_do_isset_or_isempty(ZEND_ISEMPTY, &$$, &$3 TSRMLS_CC); }
| T_EMPTY '(' expr_without_variable ')' { zend_do_unary_op(ZEND_BOOL_NOT, &$$, &$3 TSRMLS_CC); }
| T_INCLUDE expr { zend_do_include_or_eval(ZEND_INCLUDE, &$$, &$2 TSRMLS_CC); }
| T_INCLUDE_ONCE expr { zend_do_include_or_eval(ZEND_INCLUDE_ONCE, &$$, &$2 TSRMLS_CC); }
| T_EVAL '(' expr ')' { zend_do_include_or_eval(ZEND_EVAL, &$$, &$3 TSRMLS_CC); }
Expand All @@ -1166,8 +1167,13 @@ internal_functions_in_yacc:
;

isset_variables:
variable { zend_do_isset_or_isempty(ZEND_ISSET, &$$, &$1 TSRMLS_CC); }
| isset_variables ',' { zend_do_boolean_and_begin(&$1, &$2 TSRMLS_CC); } variable { znode tmp; zend_do_isset_or_isempty(ZEND_ISSET, &tmp, &$4 TSRMLS_CC); zend_do_boolean_and_end(&$$, &$1, &tmp, &$2 TSRMLS_CC); }
isset_variable { $$ = $1; }
| isset_variables ',' { zend_do_boolean_and_begin(&$1, &$2 TSRMLS_CC); } isset_variable { zend_do_boolean_and_end(&$$, &$1, &$4, &$2 TSRMLS_CC); }
;

isset_variable:
variable { zend_do_isset_or_isempty(ZEND_ISSET, &$$, &$1 TSRMLS_CC); }
| expr_without_variable { zend_error(E_COMPILE_ERROR, "Cannot use isset() on the result of an expression (you can use \"null !== expression\" instead)"); }
;

class_constant:
Expand Down

0 comments on commit ec061a9

Please sign in to comment.