Skip to content

Commit 532c60c

Browse files
committed
Add support for tentative return types of internal methods
RFC: https://wiki.php.net/rfc/internal_method_return_types Closses GH-6971
1 parent 008bfcc commit 532c60c

39 files changed

+668
-291
lines changed

Diff for: UPGRADING

+4
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ PHP 8.1 UPGRADE NOTES
5757
This means that static variables in methods now behave the same way as
5858
static properties.
5959
RFC: https://wiki.php.net/rfc/static_variable_inheritance
60+
. Most non-final internal methods now require overriding methods to declare a
61+
compatible return type, otherwise a deprecated notice is emitted during
62+
inheritance validation.
63+
RFC: https://wiki.php.net/rfc/internal_method_return_types
6064

6165
- Fileinfo:
6266
. The fileinfo functions now accept and return, respectively, finfo objects

Diff for: Zend/Optimizer/zend_func_info.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -844,7 +844,7 @@ ZEND_API uint32_t zend_get_func_info(
844844
#endif
845845

846846
ret = zend_get_return_info_from_signature_only(
847-
callee_func, /* script */ NULL, ce, ce_is_instanceof);
847+
callee_func, /* script */ NULL, ce, ce_is_instanceof, /* use_tentative_return_info */ !call_info->is_prototype);
848848

849849
#if ZEND_DEBUG
850850
if (internal_ret) {
@@ -884,7 +884,7 @@ ZEND_API uint32_t zend_get_func_info(
884884
}
885885
if (!ret) {
886886
ret = zend_get_return_info_from_signature_only(
887-
callee_func, /* TODO: script */ NULL, ce, ce_is_instanceof);
887+
callee_func, /* TODO: script */ NULL, ce, ce_is_instanceof, /* use_tentative_return_info */ !call_info->is_prototype);
888888
}
889889
}
890890
return ret;

Diff for: Zend/Optimizer/zend_inference.c

+13-11
Original file line numberDiff line numberDiff line change
@@ -4001,9 +4001,11 @@ static int is_recursive_tail_call(const zend_op_array *op_array,
40014001

40024002
uint32_t zend_get_return_info_from_signature_only(
40034003
const zend_function *func, const zend_script *script,
4004-
zend_class_entry **ce, bool *ce_is_instanceof) {
4004+
zend_class_entry **ce, bool *ce_is_instanceof, bool use_tentative_return_info) {
40054005
uint32_t type;
4006-
if (func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
4006+
if (func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE &&
4007+
(use_tentative_return_info || !ZEND_ARG_TYPE_IS_TENTATIVE(func->common.arg_info - 1))
4008+
) {
40074009
zend_arg_info *ret_info = func->common.arg_info - 1;
40084010
type = zend_fetch_arg_info_type(script, ret_info, ce);
40094011
*ce_is_instanceof = ce != NULL;
@@ -4025,15 +4027,15 @@ uint32_t zend_get_return_info_from_signature_only(
40254027
ZEND_API void zend_init_func_return_info(
40264028
const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret)
40274029
{
4028-
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
4029-
zend_ssa_range tmp_range = {0, 0, 0, 0};
4030-
bool is_instanceof = false;
4031-
ret->type = zend_get_return_info_from_signature_only(
4032-
(zend_function *) op_array, script, &ret->ce, &is_instanceof);
4033-
ret->is_instanceof = is_instanceof;
4034-
ret->range = tmp_range;
4035-
ret->has_range = 0;
4036-
}
4030+
ZEND_ASSERT((op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE));
4031+
4032+
zend_ssa_range tmp_range = {0, 0, 0, 0};
4033+
bool is_instanceof = false;
4034+
ret->type = zend_get_return_info_from_signature_only(
4035+
(zend_function *) op_array, script, &ret->ce, &is_instanceof, /* use_tentative_return_info */ 1);
4036+
ret->is_instanceof = is_instanceof;
4037+
ret->range = tmp_range;
4038+
ret->has_range = 0;
40374039
}
40384040

40394041
void zend_func_return_info(const zend_op_array *op_array,

Diff for: Zend/Optimizer/zend_inference.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ ZEND_API void zend_init_func_return_info(
271271
const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret);
272272
uint32_t zend_get_return_info_from_signature_only(
273273
const zend_function *func, const zend_script *script,
274-
zend_class_entry **ce, bool *ce_is_instanceof);
274+
zend_class_entry **ce, bool *ce_is_instanceof, bool use_tentative_return_info);
275275
void zend_func_return_info(const zend_op_array *op_array,
276276
const zend_script *script,
277277
int recursive,

Diff for: Zend/tests/parameter_default_values/internal_declaration_error_class_const.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ The default value is a class constant in the parent class method's signature.
44
<?php
55
class MyDateTimeZone extends DateTimeZone
66
{
7-
public static function listIdentifiers()
7+
public static function listIdentifiers(): array
88
{
99
}
1010
}
1111
?>
1212
--EXPECTF--
13-
Fatal error: Declaration of MyDateTimeZone::listIdentifiers() must be compatible with DateTimeZone::listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null) in %s on line %d
13+
Fatal error: Declaration of MyDateTimeZone::listIdentifiers(): array must be compatible with DateTimeZone::listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): array in %s on line %d

Diff for: Zend/tests/parameter_default_values/internal_declaration_error_const.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ The default value is a constant in the parent class method's signature.
44
<?php
55
class MyDateTimeZone extends DateTimeZone
66
{
7-
public function getTransitions()
7+
public function getTransitions(): array|false
88
{
99
}
1010
}
1111
?>
1212
--EXPECTF--
13-
Fatal error: Declaration of MyDateTimeZone::getTransitions() must be compatible with DateTimeZone::getTransitions(int $timestampBegin = PHP_INT_MIN, int $timestampEnd = PHP_INT_MAX) in %s on line %d
13+
Fatal error: Declaration of MyDateTimeZone::getTransitions(): array|false must be compatible with DateTimeZone::getTransitions(int $timestampBegin = PHP_INT_MIN, int $timestampEnd = PHP_INT_MAX): array|false in %s on line %d

Diff for: Zend/tests/parameter_default_values/internal_declaration_error_false.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ The default value is false in the parent class method's signature.
55

66
interface MyDateTimeInterface extends DateTimeInterface
77
{
8-
public function diff();
8+
public function diff(): DateInterval;
99
}
1010
?>
1111
--EXPECTF--
12-
Fatal error: Declaration of MyDateTimeInterface::diff() must be compatible with DateTimeInterface::diff(DateTimeInterface $targetObject, bool $absolute = false) in %s on line %d
12+
Fatal error: Declaration of MyDateTimeInterface::diff(): DateInterval must be compatible with DateTimeInterface::diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval in %s on line %d

Diff for: Zend/tests/parameter_default_values/internal_declaration_error_int.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ The default value is an integer in the parent class method's signature.
44
<?php
55
class MyDateTime extends DateTime
66
{
7-
public function setTime(int $hour, int $minute, int $second = 0, bool $microsecond = false)
7+
public function setTime(int $hour, int $minute, int $second = 0, bool $microsecond = false): DateTime
88
{
99
}
1010
}
1111
?>
1212
--EXPECTF--
13-
Fatal error: Declaration of MyDateTime::setTime(int $hour, int $minute, int $second = 0, bool $microsecond = false) must be compatible with DateTime::setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0) in %s on line %d
13+
Fatal error: Declaration of MyDateTime::setTime(int $hour, int $minute, int $second = 0, bool $microsecond = false): DateTime must be compatible with DateTime::setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): DateTime in %s on line %d

Diff for: Zend/tests/parameter_default_values/internal_declaration_error_null.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ The default value is null in the parent class method's signature.
44
<?php
55
class MyDateTime extends DateTime
66
{
7-
public static function createFromFormat()
7+
public static function createFromFormat(): DateTime|false
88
{
99
}
1010
}
1111
?>
1212
--EXPECTF--
13-
Fatal error: Declaration of MyDateTime::createFromFormat() must be compatible with DateTime::createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null) in %s on line %d
13+
Fatal error: Declaration of MyDateTime::createFromFormat(): DateTime|false must be compatible with DateTime::createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTime|false in %s on line %d

Diff for: Zend/tests/type_declarations/variance/internal_parent.phpt

-12
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Test that no notice is emitted when the return type/value of the overriding method is compatible with the tentative return type/value of the overridden method
3+
--FILE--
4+
<?php
5+
class MyDateTimeZone extends DateTimeZone
6+
{
7+
public static function listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): array
8+
{
9+
return [];
10+
}
11+
}
12+
13+
var_dump(MyDateTimeZone::listIdentifiers());
14+
?>
15+
--EXPECT--
16+
array(0) {
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Test that a notice is emitted when the return type/value of the overriding method is incompatible with the tentative return type/value of the overridden method
3+
--FILE--
4+
<?php
5+
class MyDateTimeZone extends DateTimeZone
6+
{
7+
public static function listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): string
8+
{
9+
return "";
10+
}
11+
}
12+
13+
var_dump(MyDateTimeZone::listIdentifiers());
14+
?>
15+
--EXPECTF--
16+
Deprecated: Declaration of MyDateTimeZone::listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): string should be compatible with DateTimeZone::listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): array in %s on line %d
17+
string(0) ""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Test that a notice is emitted when the tentative return type of the overridden method is omitted
3+
--FILE--
4+
<?php
5+
class MyDateTimeZone extends DateTimeZone
6+
{
7+
public static function listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null)
8+
{
9+
}
10+
}
11+
?>
12+
--EXPECTF--
13+
Deprecated: Declaration of MyDateTimeZone::listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null) should be compatible with DateTimeZone::listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): array in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Test unresolvable inheritance check due to unavailable parameter type when the parent has a tentative return type.
3+
--FILE--
4+
<?php
5+
6+
class Test extends DateTime {
7+
public static function createFromFormat($format, $datetime, Wrong $timezone = null): DateTime|false {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Could not check compatibility between Test::createFromFormat($format, $datetime, ?Wrong $timezone = null): DateTime|false and DateTime::createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTime|false, because class Wrong is not available in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Test unresolvable inheritance check due to unavailable return type when the parent has a tentative return type.
3+
--FILE--
4+
<?php
5+
6+
class Test extends DateTime {
7+
public static function createFromFormat($format, $datetime, $timezone = null): Wrong { }
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Could not check compatibility between Test::createFromFormat($format, $datetime, $timezone = null): Wrong and DateTime::createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTime|false, because class Wrong is not available in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Test that the ReturnTypeWillChange attribute cannot target classes
3+
--FILE--
4+
<?php
5+
6+
#[ReturnTypeWillChange]
7+
class Foo
8+
{
9+
}
10+
11+
?>
12+
--EXPECTF--
13+
Fatal error: Attribute "ReturnTypeWillChange" cannot target class (allowed targets: method) in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Test that the ReturnTypeWillChange attribute cannot target functions
3+
--FILE--
4+
<?php
5+
6+
#[ReturnTypeWillChange]
7+
function foo() {}
8+
9+
?>
10+
--EXPECTF--
11+
Fatal error: Attribute "ReturnTypeWillChange" cannot target function (allowed targets: method) in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Test that the ReturnTypeWillChange attribute cannot be used with functions
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
#[ReturnTypeWillChange]
9+
public int $bar;
10+
}
11+
12+
?>
13+
--EXPECTF--
14+
Fatal error: Attribute "ReturnTypeWillChange" cannot target property (allowed targets: method) in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Test that the notice can be suppressed when the return type/value of the overriding method is incompatible with the tentative return type/value of the overridden method
3+
--FILE--
4+
<?php
5+
6+
class MyDateTime extends DateTime
7+
{
8+
/**
9+
* @return DateTime|false
10+
*/
11+
#[ReturnTypeWillChange]
12+
public function modify(string $modifier) {
13+
return false;
14+
}
15+
}
16+
17+
$date = new MyDateTime("2021-01-01 00:00:00");
18+
var_dump($date->modify("+1 sec"));
19+
?>
20+
--EXPECT--
21+
bool(false)

0 commit comments

Comments
 (0)