diff --git a/Zend/tests/short_function/global_declaration.phpt b/Zend/tests/short_function/global_declaration.phpt new file mode 100644 index 000000000000..83de9fd0af59 --- /dev/null +++ b/Zend/tests/short_function/global_declaration.phpt @@ -0,0 +1,26 @@ +--TEST-- +Short global function declaration +--FILE-- + 123; +echo buz() . PHP_EOL; + +function compare(int $what, int $with): string => match(true) { + $what > $with => "greater", + $what < $with => "less", + $what == $with => "equals", + default => throw new Exception("Unreachable statement"), +}; + +var_dump(compare(1, 2)); +var_dump(compare(20, 10)); +var_dump(compare(5, 5)); + +echo "done"; +?> +--EXPECT-- +123 +string(4) "less" +string(7) "greater" +string(6) "equals" +done \ No newline at end of file diff --git a/Zend/tests/short_function/inline_function.phpt b/Zend/tests/short_function/inline_function.phpt new file mode 100644 index 000000000000..a18a59a52496 --- /dev/null +++ b/Zend/tests/short_function/inline_function.phpt @@ -0,0 +1,17 @@ +--TEST-- +Inline short declaration function +--FILE-- + 123; + +var_dump($f()); +var_dump((function() => null)()); +var_dump(get_debug_type(function() => null())); +var_dump(get_debug_type((function() => null)())); + +?> +--EXPECT-- +int(123) +NULL +string(7) "Closure" +string(4) "null" diff --git a/Zend/tests/short_function/inline_function_deref.phpt b/Zend/tests/short_function/inline_function_deref.phpt new file mode 100644 index 000000000000..c58654a844fb --- /dev/null +++ b/Zend/tests/short_function/inline_function_deref.phpt @@ -0,0 +1,28 @@ +--TEST-- +Inline short declaration function dereference +--FILE-- + 123; + $b = function() => $a; + + return $b; +} + +var_dump(get_debug_type( a()() )); + +function b () { + $a = fn() => 123; + $b = fn() => $a; + + return $b; +} + +var_dump(get_debug_type( b()() )); + +?> +--EXPECTF-- +Warning: Undefined variable $a in %s on line %d +string(4) "null" +string(7) "Closure" \ No newline at end of file diff --git a/Zend/tests/short_function/method_declaration.phpt b/Zend/tests/short_function/method_declaration.phpt new file mode 100644 index 000000000000..00922cb97849 --- /dev/null +++ b/Zend/tests/short_function/method_declaration.phpt @@ -0,0 +1,40 @@ +--TEST-- +Short function method declaration +--FILE-- + $this->proxy->id; + function getName() => $this->proxy->name; + + function setId($value) => $this->proxy->id = $value; + function setName($value) => $this->proxy->name = $value; +} + +$decorated = new stdClass; +$decorated->id = null; +$decorated->name = null; + +$decorator = new Decorator; +$decorator->proxy = $decorated; + +var_dump($decorated); + +$decorator->setId(1); +$decorator->setName('Dmitrii'); + +var_dump($decorated); +?> +--EXPECT-- +object(stdClass)#1 (2) { + ["id"]=> + NULL + ["name"]=> + NULL +} +object(stdClass)#1 (2) { + ["id"]=> + int(1) + ["name"]=> + string(7) "Dmitrii" +} \ No newline at end of file diff --git a/Zend/tests/short_function/method_declaration2.phpt b/Zend/tests/short_function/method_declaration2.phpt new file mode 100644 index 000000000000..16953efdad23 --- /dev/null +++ b/Zend/tests/short_function/method_declaration2.phpt @@ -0,0 +1,14 @@ +--TEST-- +Short function method declaration +--FILE-- + $this->type; + function getTypeName(): string => match($type) { + "variable" => "Variable", + "function_return" => "Function Return Type", + default => "unknown" + }; +} +?> +--EXPECT-- diff --git a/Zend/tests/short_function/possible_declaration.phpt b/Zend/tests/short_function/possible_declaration.phpt new file mode 100644 index 000000000000..b6a9b20c603c --- /dev/null +++ b/Zend/tests/short_function/possible_declaration.phpt @@ -0,0 +1,11 @@ +--TEST-- +Test possible declarations for expr&stmt options +--FILE-- + 123; // returns 123 + +var_dump(expr()); + +?> +--EXPECT-- +int(123) \ No newline at end of file diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt index 1c35dfdf91c7..9b0f0788f6fa 100644 --- a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt @@ -7,4 +7,4 @@ function foo(): ?Countable&Iterator {} ?> --EXPECTF-- -Parse error: syntax error, unexpected token "&", expecting "{" in %s on line %d +Parse error: syntax error, unexpected token "&", expecting "=>" or "{" in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index cb64729806a2..b2b9d4e52337 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8365,7 +8365,7 @@ static zend_op_array *zend_compile_func_decl_ex( zend_compile_closure_uses(uses_ast); } - if (ast->kind == ZEND_AST_ARROW_FUNC && decl->child[2]->kind != ZEND_AST_RETURN) { + if (decl->flags & ZEND_ACC_SHORT_DECLARATION && stmt_ast->kind != ZEND_AST_RETURN) { bool needs_return = true; if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { zend_arg_info *return_info = CG(active_op_array)->arg_info - 1; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 63572ab6623c..06feca640242 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -333,7 +333,7 @@ typedef struct _zend_oparray_context { /* Class cannot be serialized or unserialized | | | */ #define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ /* | | | */ -/* Function Flags (unused: 29-30) | | | */ +/* Function Flags (unused: 30) | | | */ /* ============== | | | */ /* | | | */ /* deprecation flag | | | */ @@ -395,6 +395,9 @@ typedef struct _zend_oparray_context { /* has #[\Override] attribute | | | */ #define ZEND_ACC_OVERRIDE (1 << 28) /* | X | | */ /* | | | */ +/* Function returning by reference | | | */ +#define ZEND_ACC_SHORT_DECLARATION (1 << 29) /* | X | | */ +/* | | | */ /* op_array uses strict mode types | | | */ #define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */ diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index d2a29e670d8b..8fcb2f8f84e8 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -248,7 +248,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); /* Token used to force a parse error from the lexer */ %token T_ERROR -%type top_statement namespace_name name statement function_declaration_statement +%type top_statement namespace_name name statement function_declaration_statement function_body inline_function_body %type class_declaration_statement trait_declaration_statement legacy_namespace_name %type interface_declaration_statement interface_extends_list %type group_use_declaration inline_use_declarations inline_use_declaration @@ -578,9 +578,15 @@ function_name: function_declaration_statement: function returns_ref function_name backup_doc_comment '(' parameter_list ')' return_type - backup_fn_flags '{' inner_statement_list '}' backup_fn_flags - { $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2 | $13, $1, $4, - zend_ast_get_str($3), $6, NULL, $11, $8, NULL); CG(extra_fn_flags) = $9; } + backup_fn_flags function_body backup_fn_flags + { $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2 | $11, $1, $4, + zend_ast_get_str($3), $6, NULL, $10, $8, NULL); CG(extra_fn_flags) = $9; } +; + +function_body: + '{' inner_statement_list '}' { $$ = $2; } + | T_DOUBLE_ARROW expr ';' + { $$ = $2; CG(extra_fn_flags) |= ZEND_ACC_SHORT_DECLARATION; } ; is_reference: @@ -1030,6 +1036,8 @@ absolute_trait_method_reference: method_body: ';' /* abstract method */ { $$ = NULL; } | '{' inner_statement_list '}' { $$ = $2; } + | T_DOUBLE_ARROW expr ';' + { $$ = $2; CG(extra_fn_flags) |= ZEND_ACC_SHORT_DECLARATION; } ; property_modifiers: @@ -1335,17 +1343,23 @@ expr: inline_function: function returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars return_type - backup_fn_flags '{' inner_statement_list '}' backup_fn_flags - { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2 | $13, $1, $3, + backup_fn_flags inline_function_body backup_fn_flags + { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2 | $11, $1, $3, NULL, - $5, $7, $11, $8, NULL); CG(extra_fn_flags) = $9; } + $5, $7, $10, $8, NULL); CG(extra_fn_flags) = $9; } | fn returns_ref backup_doc_comment '(' parameter_list ')' return_type T_DOUBLE_ARROW backup_fn_flags backup_lex_pos expr backup_fn_flags - { $$ = zend_ast_create_decl(ZEND_AST_ARROW_FUNC, $2 | $12, $1, $3, + { $$ = zend_ast_create_decl(ZEND_AST_ARROW_FUNC, $2 | $12 | ZEND_ACC_SHORT_DECLARATION, $1, $3, NULL, $5, NULL, $11, $7, NULL); CG(extra_fn_flags) = $9; } ; +inline_function_body: + '{' inner_statement_list '}' { $$ = $2; } + | T_DOUBLE_ARROW expr + { $$ = $2; CG(extra_fn_flags) |= ZEND_ACC_SHORT_DECLARATION; } +; + fn: T_FN { $$ = CG(zend_lineno); } ;