From 8f487424888a01bad380a1060fe6f18086f18286 Mon Sep 17 00:00:00 2001 From: Rob Landers Date: Mon, 1 May 2023 20:44:02 +0200 Subject: [PATCH] add nameof operator --- Zend/zend_ast.c | 2 ++ Zend/zend_ast.h | 1 + Zend/zend_compile.c | 32 ++++++++++++++++++++++++++ Zend/zend_language_parser.y | 4 +++- Zend/zend_language_scanner.l | 4 ++++ ext/tokenizer/tests/001.phpt | 2 ++ ext/tokenizer/tokenizer_data.c | 1 + ext/tokenizer/tokenizer_data.stub.php | 5 ++++ ext/tokenizer/tokenizer_data_arginfo.h | 3 ++- tests/basic/020.post | 1 + tests/basic/nameof.phpt | 9 ++++++++ 11 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/basic/020.post create mode 100644 tests/basic/nameof.phpt diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 6d5c5aaa4463..3d0d7a669add 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1994,6 +1994,8 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio FUNC_OP("empty"); case ZEND_AST_ISSET: FUNC_OP("isset"); + case ZEND_AST_NAMEOF: + FUNC_OP("nameof"); case ZEND_AST_SILENCE: PREFIX_OP("@", 240, 241); case ZEND_AST_SHELL_EXEC: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 73e4fed7a997..4922c56c14ce 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -96,6 +96,7 @@ enum _zend_ast_kind { ZEND_AST_POST_DEC, ZEND_AST_YIELD_FROM, ZEND_AST_CLASS_NAME, + ZEND_AST_NAMEOF, ZEND_AST_GLOBAL, ZEND_AST_UNSET, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 084c47f45bc4..d6a3de60c896 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9443,6 +9443,35 @@ static void zend_compile_include_or_eval(znode *result, zend_ast *ast) /* {{{ */ } /* }}} */ +static void zend_compile_nameof(znode *result, zend_ast *ast) /* {{{ */ +{ + zend_ast *var_ast = ast->child[0]; + zend_ast *name_ast; + + // todo: proper error handling and better error messages + + switch (var_ast->kind) { + case ZEND_AST_VAR: + case ZEND_AST_CALL: + name_ast = var_ast->child[0]; + ZVAL_COPY(&result->u.constant, zend_ast_get_zval(name_ast)); + result->op_type = IS_CONST; + break; + case ZEND_AST_PROP: + case ZEND_AST_NULLSAFE_PROP: + case ZEND_AST_STATIC_PROP: + case ZEND_AST_CLASS_CONST: + name_ast = var_ast->child[1]; + ZVAL_COPY(&result->u.constant, zend_ast_get_zval(name_ast)); + result->op_type = IS_CONST; + break; + default: + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use nameof() on the result of an expression or something with an ambiguous name"); + } +} +/* }}} */ + static void zend_compile_isset_or_empty(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *var_ast = ast->child[0]; @@ -10423,6 +10452,9 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_EMPTY: zend_compile_isset_or_empty(result, ast); return; + case ZEND_AST_NAMEOF: + zend_compile_nameof(result, ast); + return; case ZEND_AST_SILENCE: zend_compile_silence(result, ast); return; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 9b663887264f..b8e1ab468517 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -149,6 +149,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_INSTEADOF "'insteadof'" %token T_GLOBAL "'global'" %token T_STATIC "'static'" +%token T_NAMEOF "'nameof'" %token T_ABSTRACT "'abstract'" %token T_FINAL "'final'" %token T_PRIVATE "'private'" @@ -299,7 +300,7 @@ reserved_non_modifiers: T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND | T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE | T_ENDWHILE | T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH | T_FINALLY - | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO + | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_NAMEOF | T_EMPTY | T_CONTINUE | T_GOTO | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN | T_MATCH | T_ENUM @@ -1575,6 +1576,7 @@ encaps_var_offset: internal_functions_in_yacc: T_ISSET '(' isset_variables possible_comma ')' { $$ = $3; } + | T_NAMEOF '(' expr ')' { $$ = zend_ast_create(ZEND_AST_NAMEOF, $3); } | T_EMPTY '(' expr ')' { $$ = zend_ast_create(ZEND_AST_EMPTY, $3); } | T_INCLUDE expr { $$ = zend_ast_create_ex(ZEND_AST_INCLUDE_OR_EVAL, ZEND_INCLUDE, $2); } diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 054ed7bdc1ef..bbc4b0da778a 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1703,6 +1703,10 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_ISSET); } +"nameof" { + RETURN_TOKEN_WITH_IDENT(T_NAMEOF); +} + "empty" { RETURN_TOKEN_WITH_IDENT(T_EMPTY); } diff --git a/ext/tokenizer/tests/001.phpt b/ext/tokenizer/tests/001.phpt index 52eb469a970f..5aeeac299647 100644 --- a/ext/tokenizer/tests/001.phpt +++ b/ext/tokenizer/tests/001.phpt @@ -125,6 +125,7 @@ echo token_name(T_THROW), "\n"; echo token_name(T_TRY), "\n"; echo token_name(T_CLONE), "\n"; echo token_name(T_HALT_COMPILER), "\n"; +echo token_name(T_NAMEOF), "\n"; echo token_name(-1), "\n"; echo token_name(0x8000000F), "\n"; @@ -249,6 +250,7 @@ T_THROW T_TRY T_CLONE T_HALT_COMPILER +T_NAMEOF UNKNOWN UNKNOWN Done diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 77b67de6b1c6..bd0521726e57 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -89,6 +89,7 @@ char *get_token_type_name(int token_type) case T_INSTEADOF: return "T_INSTEADOF"; case T_GLOBAL: return "T_GLOBAL"; case T_STATIC: return "T_STATIC"; + case T_NAMEOF: return "T_NAMEOF"; case T_ABSTRACT: return "T_ABSTRACT"; case T_FINAL: return "T_FINAL"; case T_PRIVATE: return "T_PRIVATE"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 42c69c4f82ef..b326e28f8e7f 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -312,6 +312,11 @@ * @cvalue T_STATIC */ const T_STATIC = UNKNOWN; +/** + * @var int + * @cvalue T_NAMEOF + */ +const T_NAMEOF = UNKNOWN; /** * @var int * @cvalue T_ABSTRACT diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index ef665193b2ff..070d8872ed47 100644 --- a/ext/tokenizer/tokenizer_data_arginfo.h +++ b/ext/tokenizer/tokenizer_data_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 1dd42ee5b5b818c5bd131b5c4bbb13c153d99499 */ + * Stub hash: 05397546c23f24a8620e4d1c5fec92035961ef99 */ @@ -67,6 +67,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_INSTEADOF", T_INSTEADOF, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_GLOBAL", T_GLOBAL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_STATIC", T_STATIC, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_NAMEOF", T_NAMEOF, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ABSTRACT", T_ABSTRACT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_FINAL", T_FINAL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PRIVATE", T_PRIVATE, CONST_PERSISTENT); diff --git a/tests/basic/020.post b/tests/basic/020.post new file mode 100644 index 000000000000..50aa32b12027 --- /dev/null +++ b/tests/basic/020.post @@ -0,0 +1 @@ +a[a[]]=1&a[b[]]=3 \ No newline at end of file diff --git a/tests/basic/nameof.phpt b/tests/basic/nameof.phpt new file mode 100644 index 000000000000..c32ebfef6478 --- /dev/null +++ b/tests/basic/nameof.phpt @@ -0,0 +1,9 @@ +--TEST-- +nameof operator +--FILE-- + +--EXPECT-- +name: a \ No newline at end of file