From 09ea55cb4e733f4e5221dfff5b66d6feb9695613 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 9 Apr 2019 11:04:13 +0200 Subject: [PATCH] Deprecate left-associative ternary Deprecate nesting ternary operators without explicit parentheses. RFC: https://wiki.php.net/rfc/ternary_associativity --- UPGRADING | 10 +++++++++ Zend/tests/bug72944.phpt | 2 +- Zend/tests/ternary_associativity.phpt | 30 +++++++++++++++++++++++++++ Zend/zend_compile.c | 24 +++++++++++++++++++++ Zend/zend_compile.h | 3 +++ Zend/zend_language_parser.y | 5 ++++- 6 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/ternary_associativity.phpt diff --git a/UPGRADING b/UPGRADING index 4c0f7a6ca1615..3f6dcb742784e 100644 --- a/UPGRADING +++ b/UPGRADING @@ -27,6 +27,16 @@ PHP 7.4 UPGRADE NOTES . "fn" is now a reserved keyword. In particular it can no longer be used as a function or class name. It can still be used as a method or class constant name. + . Nesting ternary operators without explicit parentheses is deprecated: + + // Code like + $a ? $b : $c ? $d : $e + // should be replaced by (current interpretation) + ($a ? $b : $c) ? $d : $e + // or (likely intended interpretation) + $a ? $b : ($c ? $d : $e) + + RFC: https://wiki.php.net/rfc/ternary_associativity - Curl: . Attempting to serialize a CURLFile class will now generate an exception. diff --git a/Zend/tests/bug72944.phpt b/Zend/tests/bug72944.phpt index 5f494beb40dc9..0ee8bd62c6f73 100644 --- a/Zend/tests/bug72944.phpt +++ b/Zend/tests/bug72944.phpt @@ -2,7 +2,7 @@ Bug #72944 (Null pointer deref in zval_delref_p). --FILE-- --EXPECTF-- diff --git a/Zend/tests/ternary_associativity.phpt b/Zend/tests/ternary_associativity.phpt new file mode 100644 index 0000000000000..32326513ca8a1 --- /dev/null +++ b/Zend/tests/ternary_associativity.phpt @@ -0,0 +1,30 @@ +--TEST-- +Using ternary associativity is deprecated +--FILE-- + +--EXPECTF-- +Deprecated: Unparenthesized `a ? b : c ? d : e` is deprecated. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)` in %s on line 3 + +Deprecated: Unparenthesized `a ?: b ? c : d` is deprecated. Use either `(a ?: b) ? c : d` or `a ?: (b ? c : d)` in %s on line 13 + +Deprecated: Unparenthesized `a ? b : c ?: d` is deprecated. Use either `(a ? b : c) ?: d` or `a ? b : (c ?: d)` in %s on line 17 diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 6e9b42151fc7f..71d3bc769b85f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7373,6 +7373,30 @@ void zend_compile_conditional(znode *result, zend_ast *ast) /* {{{ */ zend_op *opline_qm_assign2; uint32_t opnum_jmpz, opnum_jmp; + if (cond_ast->kind == ZEND_AST_CONDITIONAL + && cond_ast->attr != ZEND_PARENTHESIZED_CONDITIONAL) { + if (cond_ast->child[1]) { + if (true_ast) { + zend_error(E_DEPRECATED, + "Unparenthesized `a ? b : c ? d : e` is deprecated. " + "Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)`"); + } else { + zend_error(E_DEPRECATED, + "Unparenthesized `a ? b : c ?: d` is deprecated. " + "Use either `(a ? b : c) ?: d` or `a ? b : (c ?: d)`"); + } + } else { + if (true_ast) { + zend_error(E_DEPRECATED, + "Unparenthesized `a ?: b ? c : d` is deprecated. " + "Use either `(a ?: b) ? c : d` or `a ?: (b ? c : d)`"); + } else { + /* This case is harmless: (a ?: b) ?: c always produces the same result + * as a ?: (b ?: c). */ + } + } + } + if (!true_ast) { zend_compile_shorthand_conditional(result, ast); return; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index e03408a5725b4..7aa83ee3b6b68 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -994,6 +994,9 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, #define ZEND_ARRAY_NOT_PACKED (1<<1) #define ZEND_ARRAY_SIZE_SHIFT 2 +/* Attribute for ternary inside parentheses */ +#define ZEND_PARENTHESIZED_CONDITIONAL 1 + /* For "use" AST nodes and the seen symbol table */ #define ZEND_SYMBOL_CLASS (1<<0) #define ZEND_SYMBOL_FUNCTION (1<<1) diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 55376ede3d624..770a027fdac7a 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -961,7 +961,10 @@ expr: { $$ = zend_ast_create_binary_op(ZEND_SPACESHIP, $1, $3); } | expr T_INSTANCEOF class_name_reference { $$ = zend_ast_create(ZEND_AST_INSTANCEOF, $1, $3); } - | '(' expr ')' { $$ = $2; } + | '(' expr ')' { + $$ = $2; + if ($$->kind == ZEND_AST_CONDITIONAL) $$->attr = ZEND_PARENTHESIZED_CONDITIONAL; + } | new_expr { $$ = $1; } | expr '?' expr ':' expr { $$ = zend_ast_create(ZEND_AST_CONDITIONAL, $1, $3, $5); }