From 7e0a65ee59e135982fce3e29b546d54a6103191e Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Sat, 11 Apr 2020 16:44:10 -0500 Subject: [PATCH 1/9] Implement pipe operator. --- Zend/tests/pipe_operator/ast.phpt | 18 ++++++++ Zend/tests/pipe_operator/call_by_ref.phpt | 20 +++++++++ .../compound_userland_calls.phpt | 19 ++++++++ .../pipe_operator/function_not_found.phpt | 15 +++++++ .../pipe_operator/mixed_callable_call.phpt | 44 +++++++++++++++++++ .../pipe_operator/optional_parameters.phpt | 15 +++++++ .../pipe_operator/precedence_addition.phpt | 17 +++++++ .../pipe_operator/precedence_coalesce.phpt | 17 +++++++ .../pipe_operator/precedence_ternary.phpt | 21 +++++++++ .../pipe_operator/simple_builtin_call.phpt | 11 +++++ .../pipe_operator/simple_userland_call.phpt | 15 +++++++ .../pipe_operator/too_many_parameters.phpt | 21 +++++++++ Zend/tests/pipe_operator/type_mismatch.phpt | 20 +++++++++ Zend/tests/pipe_operator/void_return.phpt | 21 +++++++++ Zend/tests/pipe_operator/wrapped_chains.phpt | 21 +++++++++ Zend/zend_ast.c | 15 +++++-- Zend/zend_compile.h | 1 + Zend/zend_language_parser.y | 4 ++ Zend/zend_language_scanner.l | 4 ++ ext/tokenizer/tokenizer_data.c | 2 + 20 files changed, 317 insertions(+), 4 deletions(-) create mode 100644 Zend/tests/pipe_operator/ast.phpt create mode 100644 Zend/tests/pipe_operator/call_by_ref.phpt create mode 100644 Zend/tests/pipe_operator/compound_userland_calls.phpt create mode 100644 Zend/tests/pipe_operator/function_not_found.phpt create mode 100644 Zend/tests/pipe_operator/mixed_callable_call.phpt create mode 100644 Zend/tests/pipe_operator/optional_parameters.phpt create mode 100644 Zend/tests/pipe_operator/precedence_addition.phpt create mode 100644 Zend/tests/pipe_operator/precedence_coalesce.phpt create mode 100644 Zend/tests/pipe_operator/precedence_ternary.phpt create mode 100644 Zend/tests/pipe_operator/simple_builtin_call.phpt create mode 100644 Zend/tests/pipe_operator/simple_userland_call.phpt create mode 100644 Zend/tests/pipe_operator/too_many_parameters.phpt create mode 100644 Zend/tests/pipe_operator/type_mismatch.phpt create mode 100644 Zend/tests/pipe_operator/void_return.phpt create mode 100644 Zend/tests/pipe_operator/wrapped_chains.phpt diff --git a/Zend/tests/pipe_operator/ast.phpt b/Zend/tests/pipe_operator/ast.phpt new file mode 100644 index 0000000000000..4a5be8e4ea662 --- /dev/null +++ b/Zend/tests/pipe_operator/ast.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test that a pipe operator displays as a pipe operator when outputting syntax. +--FILE-- + '_test') == 99); +} catch (AssertionError $e) { + print $e->getMessage(); +} + +?> +--EXPECTF-- +assert(5 |> \_test == 99) diff --git a/Zend/tests/pipe_operator/call_by_ref.phpt b/Zend/tests/pipe_operator/call_by_ref.phpt new file mode 100644 index 0000000000000..e3320d0b1ffd6 --- /dev/null +++ b/Zend/tests/pipe_operator/call_by_ref.phpt @@ -0,0 +1,20 @@ +--TEST-- +Pipe operator accepts by-reference functions +--FILE-- + '_modify'; + +var_dump($res1); +var_dump($a); +?> +--EXPECT-- +string(3) "foo" +int(6) diff --git a/Zend/tests/pipe_operator/compound_userland_calls.phpt b/Zend/tests/pipe_operator/compound_userland_calls.phpt new file mode 100644 index 0000000000000..922c3c5ac23d8 --- /dev/null +++ b/Zend/tests/pipe_operator/compound_userland_calls.phpt @@ -0,0 +1,19 @@ +--TEST-- +Pipe operator chains +--FILE-- + '_test1' |> '_test2'; + +var_dump($res1); +?> +--EXPECT-- +int(12) diff --git a/Zend/tests/pipe_operator/function_not_found.phpt b/Zend/tests/pipe_operator/function_not_found.phpt new file mode 100644 index 0000000000000..cebb73fedcbb8 --- /dev/null +++ b/Zend/tests/pipe_operator/function_not_found.phpt @@ -0,0 +1,15 @@ +--TEST-- +Pipe operator throws normally on missing function +--FILE-- + '_test'; +} +catch (Throwable $e) { + printf("Expected %s thrown, got %s", Error::class, get_class($e)); +} + +?> +--EXPECT-- +Expected Error thrown, got Error diff --git a/Zend/tests/pipe_operator/mixed_callable_call.phpt b/Zend/tests/pipe_operator/mixed_callable_call.phpt new file mode 100644 index 0000000000000..3f1a41a1db0f4 --- /dev/null +++ b/Zend/tests/pipe_operator/mixed_callable_call.phpt @@ -0,0 +1,44 @@ +--TEST-- +Pipe operator handles all callable styles +--FILE-- + _add($x, 3); + +$res1 = 2 + |> [$test, 'message'] + |> 'strlen' + |> $add3 + |> fn($x) => _area($x, 2) +; + +var_dump($res1); +?> +--EXPECT-- +int(20) diff --git a/Zend/tests/pipe_operator/optional_parameters.phpt b/Zend/tests/pipe_operator/optional_parameters.phpt new file mode 100644 index 0000000000000..53cce2e9972e8 --- /dev/null +++ b/Zend/tests/pipe_operator/optional_parameters.phpt @@ -0,0 +1,15 @@ +--TEST-- +Pipe operator accepts optional-parameter functions +--FILE-- + '_test'; + +var_dump($res1); +?> +--EXPECT-- +int(8) diff --git a/Zend/tests/pipe_operator/precedence_addition.phpt b/Zend/tests/pipe_operator/precedence_addition.phpt new file mode 100644 index 0000000000000..d047907a84bd2 --- /dev/null +++ b/Zend/tests/pipe_operator/precedence_addition.phpt @@ -0,0 +1,17 @@ +--TEST-- +Pipe binds lower than addition +--FILE-- + '_test1'; + +var_dump($res1); +?> +--EXPECT-- +int(8) diff --git a/Zend/tests/pipe_operator/precedence_coalesce.phpt b/Zend/tests/pipe_operator/precedence_coalesce.phpt new file mode 100644 index 0000000000000..812075d8a75ad --- /dev/null +++ b/Zend/tests/pipe_operator/precedence_coalesce.phpt @@ -0,0 +1,17 @@ +--TEST-- +Pipe binds lower than coalesce +--FILE-- + $bad_func ?? '_test1'; + +var_dump($res1); +?> +--EXPECT-- +int(10) diff --git a/Zend/tests/pipe_operator/precedence_ternary.phpt b/Zend/tests/pipe_operator/precedence_ternary.phpt new file mode 100644 index 0000000000000..9fe886be892d6 --- /dev/null +++ b/Zend/tests/pipe_operator/precedence_ternary.phpt @@ -0,0 +1,21 @@ +--TEST-- +Pipe binds lower than ternary +--FILE-- + $bad_func ? '_test1' : '_test2'; + +var_dump($res1); +?> +--EXPECT-- +int(10) diff --git a/Zend/tests/pipe_operator/simple_builtin_call.phpt b/Zend/tests/pipe_operator/simple_builtin_call.phpt new file mode 100644 index 0000000000000..72f5968dd0b65 --- /dev/null +++ b/Zend/tests/pipe_operator/simple_builtin_call.phpt @@ -0,0 +1,11 @@ +--TEST-- +Pipe operator supports built-in functions +--FILE-- + 'strlen'; + +var_dump($res1); +?> +--EXPECT-- +int(5) diff --git a/Zend/tests/pipe_operator/simple_userland_call.phpt b/Zend/tests/pipe_operator/simple_userland_call.phpt new file mode 100644 index 0000000000000..7f311f9a10474 --- /dev/null +++ b/Zend/tests/pipe_operator/simple_userland_call.phpt @@ -0,0 +1,15 @@ +--TEST-- +Pipe operator supports user-defined functions +--FILE-- + '_test'; + +var_dump($res1); +?> +--EXPECT-- +int(6) diff --git a/Zend/tests/pipe_operator/too_many_parameters.phpt b/Zend/tests/pipe_operator/too_many_parameters.phpt new file mode 100644 index 0000000000000..34487aeb97565 --- /dev/null +++ b/Zend/tests/pipe_operator/too_many_parameters.phpt @@ -0,0 +1,21 @@ +--TEST-- +Pipe operator fails on multi-parameter functions +--FILE-- + '_test'; +} +catch (Throwable $e) { + printf("Expected %s thrown, got %s", ArgumentCountError::class, get_class($e)); +} + + +?> +--EXPECT-- +Expected ArgumentCountError thrown, got ArgumentCountError diff --git a/Zend/tests/pipe_operator/type_mismatch.phpt b/Zend/tests/pipe_operator/type_mismatch.phpt new file mode 100644 index 0000000000000..6cac9473c84d9 --- /dev/null +++ b/Zend/tests/pipe_operator/type_mismatch.phpt @@ -0,0 +1,20 @@ +--TEST-- +Pipe operator respects types +--FILE-- + '_test'; + var_dump($res1); +} +catch (Throwable $e) { + printf("Expected %s thrown, got %s", TypeError::class, get_class($e)); +} + +?> +--EXPECT-- +Expected TypeError thrown, got TypeError diff --git a/Zend/tests/pipe_operator/void_return.phpt b/Zend/tests/pipe_operator/void_return.phpt new file mode 100644 index 0000000000000..e9a71dda44f85 --- /dev/null +++ b/Zend/tests/pipe_operator/void_return.phpt @@ -0,0 +1,21 @@ +--TEST-- +Pipe operator fails void return chaining in strict mode +--FILE-- + 'nonReturnFunction' + |> 'strlen'; + var_dump($result); +} +catch (Throwable $e) { + printf("Expected %s thrown, got %s", TypeError::class, get_class($e)); +} + +?> +--EXPECT-- +Expected TypeError thrown, got TypeError diff --git a/Zend/tests/pipe_operator/wrapped_chains.phpt b/Zend/tests/pipe_operator/wrapped_chains.phpt new file mode 100644 index 0000000000000..e2b6f39f7f342 --- /dev/null +++ b/Zend/tests/pipe_operator/wrapped_chains.phpt @@ -0,0 +1,21 @@ +--TEST-- +Pipe operator chains saved as a closure +--FILE-- + $x |> '_test1' |> '_test2'; + +$res1 = $func(5); + +var_dump($res1); +?> +--EXPECT-- +int(12) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index ba46b7feb6cba..957812af3ccc2 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1871,10 +1871,17 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio zend_ast_export_var(str, ast->child[1], 0, indent); break; case ZEND_AST_CALL: - zend_ast_export_ns_name(str, ast->child[0], 0, indent); - smart_str_appendc(str, '('); - zend_ast_export_ex(str, ast->child[1], 0, indent); - smart_str_appendc(str, ')'); + if (ast->attr & ZEND_CALL_SYNTAX_PIPE) { + zend_ast_export_ex(str, ast->child[1], 0, indent); + smart_str_appends(str, " |> "); + zend_ast_export_ns_name(str, ast->child[0], 0, indent); + } + else { + zend_ast_export_ns_name(str, ast->child[0], 0, indent); + smart_str_appendc(str, '('); + zend_ast_export_ex(str, ast->child[1], 0, indent); + smart_str_appendc(str, ')'); + } break; case ZEND_AST_CLASS_CONST: zend_ast_export_ns_name(str, ast->child[0], 0, indent); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 76405a3689dc9..2819d9d3bc7f6 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -922,6 +922,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); /* These should not clash with ZEND_ACC_(PUBLIC|PROTECTED|PRIVATE) */ #define ZEND_PARAM_REF (1<<3) #define ZEND_PARAM_VARIADIC (1<<4) +#define ZEND_CALL_SYNTAX_PIPE (1u << 2u) #define ZEND_NAME_FQ 0 #define ZEND_NAME_NOT_FQ 1 diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 67a972c53bb9c..d86405da1515a 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -62,6 +62,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %precedence T_DOUBLE_ARROW %precedence T_YIELD_FROM %precedence '=' 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 T_COALESCE_EQUAL +%left T_PIPE %left '?' ':' %right T_COALESCE %left T_BOOLEAN_OR @@ -231,6 +232,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_COALESCE "'??'" %token T_POW "'**'" %token T_POW_EQUAL "'**='" +%token T_PIPE "|>" /* We need to split the & token in two to avoid a shift/reduce conflict. For T1&$v and T1&T2, * with only one token lookahead, bison does not know whether to reduce T1 as a complete type, * or shift to continue parsing an intersection type. */ @@ -1141,6 +1143,8 @@ expr: { $$ = zend_ast_create_binary_op(ZEND_IS_EQUAL, $1, $3); } | expr T_IS_NOT_EQUAL expr { $$ = zend_ast_create_binary_op(ZEND_IS_NOT_EQUAL, $1, $3); } + | expr T_PIPE expr + { $$ = zend_ast_create(ZEND_AST_CALL, $3, zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1) ); $$->attr = ZEND_CALL_SYNTAX_PIPE; } | expr '<' expr { $$ = zend_ast_create_binary_op(ZEND_IS_SMALLER, $1, $3); } | expr T_IS_SMALLER_OR_EQUAL expr diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 1155f97d759c2..6dca731555dd4 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1826,6 +1826,10 @@ NEWLINE ("\r"|"\n"|"\r\n") RETURN_TOKEN(T_COALESCE_EQUAL); } +"|>" { + RETURN_TOKEN(T_PIPE); +} + "||" { RETURN_TOKEN(T_BOOLEAN_OR); } diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 271184fb107d5..b3ea57d674f8a 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -169,6 +169,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_POW_EQUAL", T_POW_EQUAL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_PIPE", T_PIPE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_COLON", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT); } @@ -321,6 +322,7 @@ char *get_token_type_name(int token_type) case T_POW_EQUAL: return "T_POW_EQUAL"; case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG"; case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG"; + case T_PIPE: return "T_PIPE"; case T_BAD_CHARACTER: return "T_BAD_CHARACTER"; } From a8e2a39b1d675b0ee07910cc425272ad0f3ac5e8 Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Sat, 26 Jun 2021 18:00:09 -0500 Subject: [PATCH 2/9] Try using an AST node. --- Zend/zend_ast.h | 1 + Zend/zend_compile.c | 33 +++++++++++++++++++++++++++++++++ Zend/zend_language_parser.y | 2 +- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 1bf8f263fa45e..717357f0b4772 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -148,6 +148,7 @@ enum _zend_ast_kind { ZEND_AST_MATCH, ZEND_AST_MATCH_ARM, ZEND_AST_NAMED_ARG, + ZEND_AST_PIPE, /* 3 child nodes */ ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 78a39fbc6cb02..509bbc1e992f2 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8586,6 +8586,36 @@ void zend_compile_greater(znode *result, zend_ast *ast) /* {{{ */ } /* }}} */ +/* We do not use zend_compile_binary_op for this because we want to retain the left-to-right + * evaluation order. */ +void zend_compile_pipe(znode *result, zend_ast *ast) /* {{{ */ +{ + zend_ast *left_ast = ast->child[0]; + zend_ast *right_ast = ast->child[1]; + znode left_node, right_node; + + zend_compile_expr(&left_node, left_ast); + zend_compile_expr(&right_node, right_ast); + + zend_compile_dynamic_call(result, left_ast, right_ast); + +/* + if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) { + result->op_type = IS_CONST; + zend_ct_eval_greater(&result->u.constant, ast->kind, + &left_node.u.constant, &right_node.u.constant); + zval_ptr_dtor(&left_node.u.constant); + zval_ptr_dtor(&right_node.u.constant); + return; + } + + zend_emit_op_tmp(result, + ast->kind == ZEND_AST_GREATER ? ZEND_IS_SMALLER : ZEND_IS_SMALLER_OR_EQUAL, + &right_node, &left_node); +*/ +} +/* }}} */ + void zend_compile_unary_op(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *expr_ast = ast->child[0]; @@ -9938,6 +9968,9 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_GREATER_EQUAL: zend_compile_greater(result, ast); return; + case ZEND_AST_PIPE: + zend_compile_pipe(result, ast); + return; case ZEND_AST_UNARY_OP: zend_compile_unary_op(result, ast); return; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index d86405da1515a..bd0a8be4310fd 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -1144,7 +1144,7 @@ expr: | expr T_IS_NOT_EQUAL expr { $$ = zend_ast_create_binary_op(ZEND_IS_NOT_EQUAL, $1, $3); } | expr T_PIPE expr - { $$ = zend_ast_create(ZEND_AST_CALL, $3, zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1) ); $$->attr = ZEND_CALL_SYNTAX_PIPE; } + { $$ = zend_ast_create(ZEND_AST_PIPE, $1, $3); } | expr '<' expr { $$ = zend_ast_create_binary_op(ZEND_IS_SMALLER, $1, $3); } | expr T_IS_SMALLER_OR_EQUAL expr From f6d81c2d1b4285318d0abb3aa53886996593e7ad Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Sat, 26 Jun 2021 18:28:21 -0500 Subject: [PATCH 3/9] Have the AST compiler just map to another AST. --- Zend/tests/pipe_operator/ast.phpt | 2 +- Zend/zend_ast.c | 20 +++++++++----------- Zend/zend_compile.c | 27 +++++---------------------- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/Zend/tests/pipe_operator/ast.phpt b/Zend/tests/pipe_operator/ast.phpt index 4a5be8e4ea662..861f2df01c78f 100644 --- a/Zend/tests/pipe_operator/ast.phpt +++ b/Zend/tests/pipe_operator/ast.phpt @@ -15,4 +15,4 @@ try { ?> --EXPECTF-- -assert(5 |> \_test == 99) +assert(5 |> '_test' == 99) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 957812af3ccc2..a92aa528857fc 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1870,18 +1870,16 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appends(str, "::$"); zend_ast_export_var(str, ast->child[1], 0, indent); break; + case ZEND_AST_PIPE: + zend_ast_export_ns_name(str, ast->child[0], 0, indent); + smart_str_appends(str, " |> "); + zend_ast_export_ex(str, ast->child[1], 0, indent); + break; case ZEND_AST_CALL: - if (ast->attr & ZEND_CALL_SYNTAX_PIPE) { - zend_ast_export_ex(str, ast->child[1], 0, indent); - smart_str_appends(str, " |> "); - zend_ast_export_ns_name(str, ast->child[0], 0, indent); - } - else { - zend_ast_export_ns_name(str, ast->child[0], 0, indent); - smart_str_appendc(str, '('); - zend_ast_export_ex(str, ast->child[1], 0, indent); - smart_str_appendc(str, ')'); - } + zend_ast_export_ns_name(str, ast->child[0], 0, indent); + smart_str_appendc(str, '('); + zend_ast_export_ex(str, ast->child[1], 0, indent); + smart_str_appendc(str, ')'); break; case ZEND_AST_CLASS_CONST: zend_ast_export_ns_name(str, ast->child[0], 0, indent); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 509bbc1e992f2..b26ef3c3f454b 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8590,29 +8590,12 @@ void zend_compile_greater(znode *result, zend_ast *ast) /* {{{ */ * evaluation order. */ void zend_compile_pipe(znode *result, zend_ast *ast) /* {{{ */ { - zend_ast *left_ast = ast->child[0]; - zend_ast *right_ast = ast->child[1]; - znode left_node, right_node; - - zend_compile_expr(&left_node, left_ast); - zend_compile_expr(&right_node, right_ast); - - zend_compile_dynamic_call(result, left_ast, right_ast); - -/* - if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) { - result->op_type = IS_CONST; - zend_ct_eval_greater(&result->u.constant, ast->kind, - &left_node.u.constant, &right_node.u.constant); - zval_ptr_dtor(&left_node.u.constant); - zval_ptr_dtor(&right_node.u.constant); - return; - } + zend_ast *expr_ast = ast->child[0]; + zend_ast *name_ast = ast->child[1]; - zend_emit_op_tmp(result, - ast->kind == ZEND_AST_GREATER ? ZEND_IS_SMALLER : ZEND_IS_SMALLER_OR_EQUAL, - &right_node, &left_node); -*/ + zend_ast *call = zend_ast_create(ZEND_AST_CALL, name_ast, + zend_ast_create_list(1, ZEND_AST_ARG_LIST, expr_ast)); + zend_compile_expr(result, call); } /* }}} */ From 1b347934b45b4d92d54e268fc8cefd33e8d00eec Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Sat, 26 Jun 2021 18:34:20 -0500 Subject: [PATCH 4/9] Add test for pipe evaluation order. --- .../tests/pipe_operator/evaluation_order.phpt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Zend/tests/pipe_operator/evaluation_order.phpt diff --git a/Zend/tests/pipe_operator/evaluation_order.phpt b/Zend/tests/pipe_operator/evaluation_order.phpt new file mode 100644 index 0000000000000..de30f461a918f --- /dev/null +++ b/Zend/tests/pipe_operator/evaluation_order.phpt @@ -0,0 +1,35 @@ +--TEST-- +Function evaluation order +--FILE-- + '_test1' |> _test2() |> '_test4'; + +var_dump($res1); +?> +--EXPECT-- +_test1 +_test2 +_test3 +_test4 +int(5) From 4a785260dd47c360948b6a5f326cff62d28fa108 Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Sat, 3 Jul 2021 16:55:42 -0500 Subject: [PATCH 5/9] Playing around with alternate approaches. --- Zend/zend_compile.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b26ef3c3f454b..d9e09e4704225 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8593,8 +8593,24 @@ void zend_compile_pipe(znode *result, zend_ast *ast) /* {{{ */ zend_ast *expr_ast = ast->child[0]; zend_ast *name_ast = ast->child[1]; - zend_ast *call = zend_ast_create(ZEND_AST_CALL, name_ast, - zend_ast_create_list(1, ZEND_AST_ARG_LIST, expr_ast)); + /* + znode expr_node, name_node; + + expr_node = *zend_ast_get_znode(expr_ast); + + zend_ast *call = zend_ast_create(ZEND_AST_CALL, name_ast, zend_ast_create_list(1, ZEND_AST_ARG_LIST, expr_node)); + zend_compile_expr(result, call); + */ +// zend_compile_expr(&expr_node, expr_ast); +// zend_compile_expr(&name_node, name_ast); + +// This doesn't work. Need to do something with SEND_VAR_EX, Levi says. +// 3v4l.org/IOJQg/vld#output +// zend_emit_op(NULL, ZEND_INIT_DYNAMIC_CALL, NULL, &name_node); + //zend_compile_call_common(result, name_ast, NULL); + + //This version works, but evaluates out of order. + zend_ast *call = zend_ast_create(ZEND_AST_CALL, name_ast, zend_ast_create_list(1, ZEND_AST_ARG_LIST, expr_ast)); zend_compile_expr(result, call); } /* }}} */ From 2c56dd708d2c985cfb0a8157e959227025e96c2d Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Sat, 3 Jul 2021 20:05:22 -0500 Subject: [PATCH 6/9] Fix compile step to enforce left to right evaluation. --- Zend/zend_compile.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d9e09e4704225..3254211e81f08 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8590,27 +8590,27 @@ void zend_compile_greater(znode *result, zend_ast *ast) /* {{{ */ * evaluation order. */ void zend_compile_pipe(znode *result, zend_ast *ast) /* {{{ */ { + // Convenience variables to give meaningful names. zend_ast *expr_ast = ast->child[0]; zend_ast *name_ast = ast->child[1]; - /* - znode expr_node, name_node; - - expr_node = *zend_ast_get_znode(expr_ast); + // AST wrapper to hold the opcodes for the LHS expression. + znode expr_node; - zend_ast *call = zend_ast_create(ZEND_AST_CALL, name_ast, zend_ast_create_list(1, ZEND_AST_ARG_LIST, expr_node)); - zend_compile_expr(result, call); - */ -// zend_compile_expr(&expr_node, expr_ast); -// zend_compile_expr(&name_node, name_ast); + // Evaluate the LHS first. (Or rather, generate the + // opcodes that will evaluate the LHS. + zend_compile_expr(&expr_node, expr_ast); -// This doesn't work. Need to do something with SEND_VAR_EX, Levi says. -// 3v4l.org/IOJQg/vld#output -// zend_emit_op(NULL, ZEND_INIT_DYNAMIC_CALL, NULL, &name_node); - //zend_compile_call_common(result, name_ast, NULL); + // Wrap the LHS back up into an AST, and replace the RHS + // with a call operation that wraps the original RHS expression. + zend_ast *call = zend_ast_create(ZEND_AST_CALL, + name_ast, + zend_ast_create_list(1, + ZEND_AST_ARG_LIST, + zend_ast_create_znode(&expr_node))); - //This version works, but evaluates out of order. - zend_ast *call = zend_ast_create(ZEND_AST_CALL, name_ast, zend_ast_create_list(1, ZEND_AST_ARG_LIST, expr_ast)); + // Compile all of that back down to opcodes and save + // to the result znode. zend_compile_expr(result, call); } /* }}} */ From 552d31c8bb9512244ddbc9f6daa687f2520528ee Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Sat, 3 Jul 2021 20:30:42 -0500 Subject: [PATCH 7/9] Remove comments, improve variable names. --- Zend/zend_compile.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 3254211e81f08..e18f1a7bab996 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8590,27 +8590,19 @@ void zend_compile_greater(znode *result, zend_ast *ast) /* {{{ */ * evaluation order. */ void zend_compile_pipe(znode *result, zend_ast *ast) /* {{{ */ { - // Convenience variables to give meaningful names. zend_ast *expr_ast = ast->child[0]; - zend_ast *name_ast = ast->child[1]; + zend_ast *func_ast = ast->child[1]; - // AST wrapper to hold the opcodes for the LHS expression. znode expr_node; - // Evaluate the LHS first. (Or rather, generate the - // opcodes that will evaluate the LHS. zend_compile_expr(&expr_node, expr_ast); - // Wrap the LHS back up into an AST, and replace the RHS - // with a call operation that wraps the original RHS expression. zend_ast *call = zend_ast_create(ZEND_AST_CALL, - name_ast, + func_ast, zend_ast_create_list(1, ZEND_AST_ARG_LIST, zend_ast_create_znode(&expr_node))); - // Compile all of that back down to opcodes and save - // to the result znode. zend_compile_expr(result, call); } /* }}} */ From 0a901adf459ace0c948a21f6f99477cb87044d5e Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Mon, 5 Jul 2021 10:21:28 -0500 Subject: [PATCH 8/9] Rebase fixes. --- ext/tokenizer/tokenizer_data.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index b3ea57d674f8a..2defab95e4db0 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -167,9 +167,9 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_COALESCE", T_COALESCE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_POW", T_POW, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_POW_EQUAL", T_POW_EQUAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_PIPE", T_PIPE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("T_PIPE", T_PIPE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_COLON", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT); } @@ -320,9 +320,9 @@ char *get_token_type_name(int token_type) case T_COALESCE: return "T_COALESCE"; case T_POW: return "T_POW"; case T_POW_EQUAL: return "T_POW_EQUAL"; + case T_PIPE: return "T_PIPE"; case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG"; case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG"; - case T_PIPE: return "T_PIPE"; case T_BAD_CHARACTER: return "T_BAD_CHARACTER"; } From 5515ddf53fefca4ab6a4fd1b430f3da122b1df3a Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Tue, 6 Jul 2021 11:58:58 -0500 Subject: [PATCH 9/9] Flesh out by-reference tests for pipes. --- Zend/tests/pipe_operator/call_by_ref.phpt | 37 +++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/Zend/tests/pipe_operator/call_by_ref.phpt b/Zend/tests/pipe_operator/call_by_ref.phpt index e3320d0b1ffd6..2d616adc9dea9 100644 --- a/Zend/tests/pipe_operator/call_by_ref.phpt +++ b/Zend/tests/pipe_operator/call_by_ref.phpt @@ -1,5 +1,5 @@ --TEST-- -Pipe operator accepts by-reference functions +Pipe handles reference variables the same as normal functions. --FILE-- '_modify'; var_dump($res1); var_dump($a); + +try { + $res2 = 5 |> '_modify'; +} catch (Error $e) { + print $e->getMessage() . PHP_EOL; +} + +function &return_by_ref(string $s): string { + $ret = $s . ' bar'; + return $ret; +} + +function receive_by_ref(string &$b): string { + $b .= " baz"; + print $b . PHP_EOL; + return $b . ' beep'; +} + +$a = 'foo'; +$res2 = $a |> 'return_by_ref' |> 'receive_by_ref'; +var_dump($res2); + +try { + $not_defined |> '_modify'; +} catch (Error $e) { + print $e->getMessage() . PHP_EOL; +} + ?> ---EXPECT-- +--EXPECTF-- string(3) "foo" int(6) +_modify(): Argument #1 ($a) cannot be passed by reference +foo bar baz +string(16) "foo bar baz beep" +_modify(): Argument #1 ($a) must be of type int, null given, called in %s on line %d