Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ PHP NEWS
operators" RFC, meaning that incrementing non-numeric strings is now
deprecated. (Girgias).
. Various closure binding issues are now deprecated. (alexandre-daubois)
. Fixed bug GH-18373 (Don't substitute self/parent with anonymous class).
(ilutov)
. Prohibit pipe & arrow function combination that leads to confusing parse
trees. (ilutov)

- Filter:
. Added support for configuring the URI parser for FILTER_VALIDATE_URL
Expand Down Expand Up @@ -65,6 +69,10 @@ PHP NEWS
. Fixed bug GH-19507 (Corrupted result after recursive tokenization during
token_get_all()). (kubawerlos, nielsdos, Arnaud)

- URI:
. Clean up naming of internal API (header names, symbol names).
(Máté Kocsis, timwolla)

14 Aug 2025, PHP 8.5.0beta1

- Core:
Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/arrow_functions/gh7900.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ try {
?>
--EXPECT--
Here
assert(fn(): never => 42 && false)
assert((fn(): never => 42) && false)
2 changes: 1 addition & 1 deletion Zend/tests/gh19304.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ try {

?>
--EXPECT--
Cannot assign int to property class@anonymous::$v of type class@anonymous
Cannot assign int to property class@anonymous::$v of type self
2 changes: 1 addition & 1 deletion Zend/tests/pipe_operator/mixed_callable_call.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ $res1 = 1
|> [StaticTest::class, 'times17']
|> new Times23()
|> $times29
|> fn($x) => times2($x)
|> (fn($x) => times2($x))
;

var_dump($res1);
Expand Down
12 changes: 12 additions & 0 deletions Zend/tests/pipe_operator/prec_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Pipe precedence 001
--FILE--
<?php

42
|> fn($x) => $x < 42
|> fn($x) => var_dump($x);

?>
--EXPECTF--
Fatal error: Arrow functions on the right hand side of |> must be parenthesized in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/pipe_operator/prec_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Pipe precedence 002
--FILE--
<?php

42
|> (fn($x) => $x < 42)
|> (fn($x) => var_dump($x));

?>
--EXPECT--
bool(false)
12 changes: 12 additions & 0 deletions Zend/tests/pipe_operator/prec_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Pipe precedence 003
--FILE--
<?php

null
|> fn() => print (new Exception)->getTraceAsString() . "\n\n"
|> fn() => print (new Exception)->getTraceAsString() . "\n\n";

?>
--EXPECTF--
Fatal error: Arrow functions on the right hand side of |> must be parenthesized in %s on line %d
16 changes: 16 additions & 0 deletions Zend/tests/pipe_operator/prec_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Pipe precedence 004
--FILE--
<?php

null
|> (fn() => print (new Exception)->getTraceAsString() . "\n\n")
|> (fn() => print (new Exception)->getTraceAsString() . "\n\n");

?>
--EXPECTF--
#0 %s(%d): {closure:%s:%d}(NULL)
#1 {main}

#0 %s(%d): {closure:%s:%d}(1)
#1 {main}
14 changes: 14 additions & 0 deletions Zend/tests/pipe_operator/prec_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Pipe precedence 005
--FILE--
<?php

try {
assert(false && 1 |> (fn() => 2));
} catch (AssertionError $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
assert(false && 1 |> (fn() => 2))
13 changes: 13 additions & 0 deletions Zend/tests/pipe_operator/prec_006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Pipe precedence 006
--FILE--
<?php

$value = null;
$value
|> fn ($x) => $x ?? throw new Exception('Value may not be null')
|> fn ($x) => var_dump($x);

?>
--EXPECTF--
Fatal error: Arrow functions on the right hand side of |> must be parenthesized in %s on line %d
24 changes: 24 additions & 0 deletions Zend/tests/pipe_operator/prec_007.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
Pipe precedence 007
--FILE--
<?php

$value = 42;
$value
|> (fn ($x) => $x ?? throw new Exception('Value may not be null'))
|> (fn ($x) => var_dump($x));

$value = null;
$value
|> (fn ($x) => $x ?? throw new Exception('Value may not be null'))
|> (fn ($x) => var_dump($x));

?>
--EXPECTF--
int(42)

Fatal error: Uncaught Exception: Value may not be null in %s:%d
Stack trace:
#0 %s(%d): {closure:%s:%d}(NULL)
#1 {main}
thrown in %s on line %d
6 changes: 6 additions & 0 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -2070,6 +2070,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
case ZEND_AST_ARROW_FUNC:
case ZEND_AST_METHOD:
decl = (const zend_ast_decl *) ast;
if (decl->kind == ZEND_AST_ARROW_FUNC && (decl->attr & ZEND_PARENTHESIZED_ARROW_FUNC)) {
smart_str_appendc(str, '(');
}
if (decl->child[4]) {
bool newlines = !(ast->kind == ZEND_AST_CLOSURE || ast->kind == ZEND_AST_ARROW_FUNC);
zend_ast_export_attributes(str, decl->child[4], indent, newlines);
Expand Down Expand Up @@ -2113,6 +2116,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
}
smart_str_appends(str, " => ");
zend_ast_export_ex(str, body, 0, indent);
if (decl->attr & ZEND_PARENTHESIZED_ARROW_FUNC) {
smart_str_appendc(str, ')');
}
break;
}

Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ typedef struct _zend_ast_op_array {
/* Separate structure for function and class declaration, as they need extra information. */
typedef struct _zend_ast_decl {
zend_ast_kind kind;
zend_ast_attr attr; /* Unused - for structure compatibility */
zend_ast_attr attr;
uint32_t start_lineno;
uint32_t end_lineno;
uint32_t flags;
Expand Down
12 changes: 10 additions & 2 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -6467,6 +6467,10 @@ static void zend_compile_pipe(znode *result, zend_ast *ast)
zend_ast *operand_ast = ast->child[0];
zend_ast *callable_ast = ast->child[1];

if (callable_ast->kind == ZEND_AST_ARROW_FUNC && !(callable_ast->attr & ZEND_PARENTHESIZED_ARROW_FUNC)) {
zend_error_noreturn(E_COMPILE_ERROR, "Arrow functions on the right hand side of |> must be parenthesized");
}

/* Compile the left hand side down to a value first. */
znode operand_result;
zend_compile_expr(&operand_result, operand_ast);
Expand Down Expand Up @@ -7091,16 +7095,20 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_SELF || fetch_type == ZEND_FETCH_CLASS_PARENT);

zend_ensure_valid_class_fetch_type(fetch_type);

bool substitute_self_parent = zend_is_scope_known()
&& !(CG(active_class_entry)->ce_flags & ZEND_ACC_ANON_CLASS);

if (fetch_type == ZEND_FETCH_CLASS_SELF) {
/* Scope might be unknown for unbound closures and traits */
if (zend_is_scope_known()) {
if (substitute_self_parent) {
class_name = CG(active_class_entry)->name;
ZEND_ASSERT(class_name && "must know class name when resolving self type at compile time");
}
} else {
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_PARENT);
/* Scope might be unknown for unbound closures and traits */
if (zend_is_scope_known()) {
if (substitute_self_parent) {
class_name = CG(active_class_entry)->parent_name;
ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time");
}
Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,9 @@ static zend_always_inline bool zend_check_arg_send_type(const zend_function *zf,
/* Used to distinguish (parent::$prop)::get() from parent hook call. */
#define ZEND_PARENTHESIZED_STATIC_PROP 1

/* Used to disallow pipes with arrow functions that lead to confusing parse trees. */
#define ZEND_PARENTHESIZED_ARROW_FUNC 1

/* For "use" AST nodes and the seen symbol table */
#define ZEND_SYMBOL_CLASS (1<<0)
#define ZEND_SYMBOL_FUNCTION (1<<1)
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,7 @@ expr:
| '(' expr ')' {
$$ = $2;
if ($$->kind == ZEND_AST_CONDITIONAL) $$->attr = ZEND_PARENTHESIZED_CONDITIONAL;
if ($$->kind == ZEND_AST_ARROW_FUNC) $$->attr = ZEND_PARENTHESIZED_ARROW_FUNC;
}
| new_dereferenceable { $$ = $1; }
| new_non_dereferenceable { $$ = $1; }
Expand Down
14 changes: 14 additions & 0 deletions ext/reflection/tests/gh18373.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
GH-18373: Don't substitute self/parent with anonymous class
--FILE--
<?php

$o = new class() {
public function test(): self {}
};

echo (new ReflectionClass($o))->getMethod('test')->getReturnType()->getName(), "\n";

?>
--EXPECT--
self
14 changes: 7 additions & 7 deletions ext/uri/php_uri.c
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ static void create_rfc3986_uri(INTERNAL_FUNCTION_PARAMETERS, bool is_constructor
Z_PARAM_OBJ_OF_CLASS_OR_NULL(base_url_object, uri_rfc3986_uri_ce)
ZEND_PARSE_PARAMETERS_END();

php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, &uriparser_uri_parser, uri_str, base_url_object, is_constructor, is_constructor, NULL);
php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, &php_uri_parser_rfc3986, uri_str, base_url_object, is_constructor, is_constructor, NULL);
}

PHP_METHOD(Uri_Rfc3986_Uri, parse)
Expand Down Expand Up @@ -480,7 +480,7 @@ static void create_whatwg_uri(INTERNAL_FUNCTION_PARAMETERS, bool is_constructor)
Z_PARAM_ZVAL(errors)
ZEND_PARSE_PARAMETERS_END();

php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, &lexbor_uri_parser, uri_str, base_url_object, is_constructor, is_constructor, errors);
php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, &php_uri_parser_whatwg, uri_str, base_url_object, is_constructor, is_constructor, errors);
}

PHP_METHOD(Uri_WhatWg_Url, parse)
Expand Down Expand Up @@ -510,7 +510,7 @@ static void read_uriparser_userinfo(INTERNAL_FUNCTION_PARAMETERS, uri_component_
uri_internal_t *internal_uri = Z_URI_INTERNAL_P(ZEND_THIS);
URI_ASSERT_INITIALIZATION(internal_uri);

if (UNEXPECTED(uriparser_read_userinfo(internal_uri, read_mode, return_value) == FAILURE)) {
if (UNEXPECTED(php_uri_parser_rfc3986_userinfo_read(internal_uri, read_mode, return_value) == FAILURE)) {
zend_throw_error(NULL, "The userinfo component cannot be retrieved");
RETURN_THROWS();
}
Expand Down Expand Up @@ -794,7 +794,7 @@ static void uri_unserialize(INTERNAL_FUNCTION_PARAMETERS, const char *uri_parser

PHP_METHOD(Uri_Rfc3986_Uri, __unserialize)
{
uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PARSER_RFC3986);
uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PARSER_RFC3986);
}

PHP_METHOD(Uri_Rfc3986_Uri, __debugInfo)
Expand Down Expand Up @@ -947,7 +947,7 @@ PHP_METHOD(Uri_WhatWg_Url, __serialize)

PHP_METHOD(Uri_WhatWg_Url, __unserialize)
{
uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PARSER_WHATWG);
uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PARSER_WHATWG);
}

PHP_METHOD(Uri_WhatWg_Url, __debugInfo)
Expand Down Expand Up @@ -1049,11 +1049,11 @@ static PHP_MINIT_FUNCTION(uri)

zend_hash_init(&uri_parsers, 4, NULL, NULL, true);

if (php_uri_parser_register(&uriparser_uri_parser) == FAILURE) {
if (php_uri_parser_register(&php_uri_parser_rfc3986) == FAILURE) {
return FAILURE;
}

if (php_uri_parser_register(&lexbor_uri_parser) == FAILURE) {
if (php_uri_parser_register(&php_uri_parser_whatwg) == FAILURE) {
return FAILURE;
}

Expand Down
4 changes: 2 additions & 2 deletions ext/uri/php_uri_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ static inline uri_internal_t *uri_internal_from_obj(const zend_object *object) {
#define Z_URI_OBJECT_P(zv) uri_object_from_obj(Z_OBJ_P((zv)))
#define Z_URI_INTERNAL_P(zv) uri_internal_from_obj(Z_OBJ_P((zv)))

#define URI_PARSER_RFC3986 "Uri\\Rfc3986\\Uri"
#define URI_PARSER_WHATWG "Uri\\WhatWg\\Url"
#define PHP_URI_PARSER_RFC3986 "Uri\\Rfc3986\\Uri"
#define PHP_URI_PARSER_WHATWG "Uri\\WhatWg\\Url"
#define URI_PARSER_PHP "parse_url"
#define URI_SERIALIZED_PROPERTY_NAME "uri"

Expand Down
Loading