diff --git a/NEWS b/NEWS index 6fde7abcbc48..3c5ffd7802b4 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,8 @@ PHP NEWS . Fixed AST printing of interface extends list as implements. (Jorg Sowa) . Fixed AST printing dropping fully qualified and namespace-relative prefixes of class names in implements, catch, and trait use lists. (Jorg Sowa) + . Fixed AST export dropping the braces of dynamic class constant fetches. + (Jorg Sowa) - Curl: . Fixed curl_reset() retaining CURLOPT_PRIVATE after reset. (lacatoire) diff --git a/Zend/tests/assert/ast_export_dynamic_class_const.phpt b/Zend/tests/assert/ast_export_dynamic_class_const.phpt new file mode 100644 index 000000000000..43a5e31c714d --- /dev/null +++ b/Zend/tests/assert/ast_export_dynamic_class_const.phpt @@ -0,0 +1,50 @@ +--TEST-- +AST export of dynamic class constant fetches +--INI-- +zend.assertions=1 +--FILE-- + assert(false && C::FOO), + fn() => assert(false && C::list), + fn() => assert(false && C::class), + fn() => assert(false && C::{$x}), + fn() => assert(false && C::{FOO}), + fn() => assert(false && C::{foo()}), + fn() => assert(false && C::{$a . $b}), + fn() => assert(false && C::{1}), + fn() => assert(false && C::{'foo'}), + fn() => assert(false && C::{'a b'}), + fn() => assert(false && C::{''}), + fn() => assert(false && C::{'class'}), + fn() => assert(false && C::{'CLASS'}), + fn() => assert(false && $o::{$x}), + fn() => assert(false && static::{$x}), +]; + +foreach ($tests as $test) { + try { + $test(); + } catch (AssertionError $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; + } +} + +?> +--EXPECT-- +AssertionError: assert(false && C::FOO) +AssertionError: assert(false && C::list) +AssertionError: assert(false && C::class) +AssertionError: assert(false && C::{$x}) +AssertionError: assert(false && C::{FOO}) +AssertionError: assert(false && C::{foo()}) +AssertionError: assert(false && C::{$a . $b}) +AssertionError: assert(false && C::{1}) +AssertionError: assert(false && C::foo) +AssertionError: assert(false && C::{'a b'}) +AssertionError: assert(false && C::{''}) +AssertionError: assert(false && C::{'class'}) +AssertionError: assert(false && C::{'CLASS'}) +AssertionError: assert(false && $o::{$x}) +AssertionError: assert(false && static::{$x}) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index dc40c15fc45e..7d6038a194de 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1429,6 +1429,24 @@ static ZEND_COLD bool zend_ast_var_needs_braces(char ch) return ch == '[' || zend_ast_valid_var_char(ch); } +static ZEND_COLD void zend_ast_export_class_const_name(smart_str *str, zend_ast *ast, int indent) +{ + if (ast->kind == ZEND_AST_ZVAL) { + const zval *zv = zend_ast_get_zval(ast); + + /* A bare "class" would turn the dynamic fetch into a class name fetch. */ + if (Z_TYPE_P(zv) == IS_STRING + && zend_ast_valid_var_name(Z_STRVAL_P(zv), Z_STRLEN_P(zv)) + && !zend_string_equals_ci(Z_STR_P(zv), ZSTR_KNOWN(ZEND_STR_CLASS))) { + smart_str_append(str, Z_STR_P(zv)); + return; + } + } + smart_str_appendc(str, '{'); + zend_ast_export_ex(str, ast, 0, indent); + smart_str_appendc(str, '}'); +} + static ZEND_COLD void zend_ast_export_var(smart_str *str, zend_ast *ast, int priority, int indent) { if (ast->kind == ZEND_AST_ZVAL) { @@ -2237,7 +2255,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio case ZEND_AST_CLASS_CONST: zend_ast_export_ns_name(str, ast->child[0], 0, indent); smart_str_appends(str, "::"); - zend_ast_export_name(str, ast->child[1], 0, indent); + zend_ast_export_class_const_name(str, ast->child[1], indent); break; case ZEND_AST_CLASS_NAME: if (ast->child[0] == NULL) {