Skip to content

Commit 60af931

Browse files
committed
Expose optional return types to userland
1 parent a29be3c commit 60af931

14 files changed

+374
-5
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Test that a notice is emitted when the return type/value of the overriding method is incompatible with the optional return type/value of the overridden method
3+
--FILE--
4+
<?php
5+
class Foo
6+
{
7+
#[OptionalReturnType]
8+
public function x(): string|false
9+
{
10+
return "x";
11+
}
12+
}
13+
14+
class Bar extends Foo
15+
{
16+
public function x(): array
17+
{
18+
return [];
19+
}
20+
}
21+
22+
$bar = new Bar();
23+
var_dump($bar->x());
24+
?>
25+
--EXPECTF--
26+
Strict Standards: Declaration of Bar::x(): array should be compatible with Foo::x(): string|false in %s on line %d
27+
array(0) {
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Test that a notice is emitted when the return type/value of the overriding method is incompatible with the optional return type/value of the overridden method
3+
--FILE--
4+
<?php
5+
class Foo
6+
{
7+
#[OptionalReturnType]
8+
public function x(): string|false
9+
{
10+
return "x";
11+
}
12+
}
13+
14+
class Bar extends Foo
15+
{
16+
public function x()
17+
{
18+
return [];
19+
}
20+
}
21+
22+
$bar = new Bar();
23+
var_dump($bar->x());
24+
?>
25+
--EXPECTF--
26+
Strict Standards: Declaration of Bar::x() should be compatible with Foo::x(): string|false in %s on line %d
27+
array(0) {
28+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
Test that the OptionalReturnType attribute is not inherited
3+
--FILE--
4+
<?php
5+
class Foo
6+
{
7+
#[OptionalReturnType]
8+
public function x(): string|false
9+
{
10+
return "x";
11+
}
12+
}
13+
14+
class Bar extends Foo
15+
{
16+
public function x(): array
17+
{
18+
return [];
19+
}
20+
}
21+
22+
class Baz extends Bar
23+
{
24+
public function x()
25+
{
26+
return [];
27+
}
28+
}
29+
30+
?>
31+
--EXPECTF--
32+
Strict Standards: Declaration of Bar::x(): array should be compatible with Foo::x(): string|false in %s on line %d
33+
34+
Fatal error: Declaration of Baz::x() must be compatible with Bar::x(): array in %s on line %d
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Test that the OptionalReturnType attribute doesn't suppress type errors
3+
--FILE--
4+
<?php
5+
class Foo
6+
{
7+
public function x(): string
8+
{
9+
return "x";
10+
}
11+
}
12+
13+
class Bar extends Foo
14+
{
15+
#[OptionalReturnType]
16+
public function x(): array
17+
{
18+
return [];
19+
}
20+
}
21+
22+
?>
23+
--EXPECTF--
24+
Fatal error: Declaration of Bar::x(): array must be compatible with Foo::x(): string in %s on line %d
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Test that a compile error is emitted if the OptionalReturnType attribute is used when the method doesn't have a return type
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
#[OptionalReturnType]
9+
public function x()
10+
{
11+
return "x";
12+
}
13+
}
14+
15+
?>
16+
--EXPECTF--
17+
Fatal error: OptionalReturnType attribute cannot be used when a method doesn't have any return type in %s on line %d
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Test that the notice can be suppressed when the return type/value of the overriding method is incompatible with the optional return type/value of the overridden method
3+
--FILE--
4+
<?php
5+
class Foo
6+
{
7+
#[OptionalReturnType]
8+
public function x(): string|false
9+
{
10+
return "x";
11+
}
12+
}
13+
14+
class Bar extends Foo
15+
{
16+
#[SuppressOptionalReturnTypeNotice]
17+
public function x(): array
18+
{
19+
return [];
20+
}
21+
}
22+
23+
$bar = new Bar();
24+
var_dump($bar->x());
25+
?>
26+
--EXPECTF--
27+
array(0) {
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Test that the notice can be suppressed when the return type/value of the overriding method is incompatible with the optional return type/value of the overridden method
3+
--FILE--
4+
<?php
5+
class Foo
6+
{
7+
#[OptionalReturnType]
8+
public function x(): string|false
9+
{
10+
return "x";
11+
}
12+
}
13+
14+
class Bar extends Foo
15+
{
16+
#[SuppressOptionalReturnTypeNotice]
17+
public function x()
18+
{
19+
return [];
20+
}
21+
}
22+
23+
$bar = new Bar();
24+
var_dump($bar->x());
25+
?>
26+
--EXPECTF--
27+
array(0) {
28+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
Test that the notice can be suppressed when the return type/value of the overriding method is incompatible with the optional return type/value of the overridden method
3+
--FILE--
4+
<?php
5+
class Foo
6+
{
7+
#[OptionalReturnType]
8+
public function bar(): string|false
9+
{
10+
return "x";
11+
}
12+
}
13+
14+
class Bar extends Foo
15+
{
16+
#[OptionalReturnType]
17+
#[SuppressOptionalReturnTypeNotice]
18+
public function x(): array
19+
{
20+
return [];
21+
}
22+
}
23+
24+
class Baz extends Bar
25+
{
26+
public function x(): array|false
27+
{
28+
return [];
29+
}
30+
}
31+
32+
$baz = new Baz();
33+
var_dump($baz->x());
34+
?>
35+
--EXPECTF--
36+
Strict Standards: Declaration of Baz::x(): array|false should be compatible with Bar::x(): array in %s on line %d
37+
array(0) {
38+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Test that the notice can be suppressed when the return type/value of the overriding method is incompatible with the optional return type/value of the overridden method
3+
--FILE--
4+
<?php
5+
class Foo
6+
{
7+
#[OptionalReturnType]
8+
public function x(): string|false
9+
{
10+
return "x";
11+
}
12+
}
13+
14+
class Bar extends Foo
15+
{
16+
public function x(): array
17+
{
18+
return [];
19+
}
20+
}
21+
22+
class Baz extends Bar
23+
{
24+
#[SuppressOptionalReturnTypeNotice]
25+
public function x(): array
26+
{
27+
return [];
28+
}
29+
}
30+
31+
$baz = new Baz();
32+
var_dump($baz->x());
33+
?>
34+
--EXPECTF--
35+
Strict Standards: Declaration of Bar::x(): array should be compatible with Foo::x(): string|false in %s on line %d
36+
array(0) {
37+
}

Zend/zend_attributes.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include "zend_smart_str.h"
2525

2626
ZEND_API zend_class_entry *zend_ce_attribute;
27+
ZEND_API zend_class_entry *zend_ce_optional_return_type_attribute;
28+
ZEND_API zend_class_entry *zend_ce_suppress_optional_return_type_notice_attribute;
2729

2830
static HashTable internal_attributes;
2931

@@ -55,6 +57,20 @@ void validate_attribute(zend_attribute *attr, uint32_t target, zend_class_entry
5557
}
5658
}
5759

60+
void validate_optional_return_type_attribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
61+
{
62+
if (target != ZEND_ATTRIBUTE_TARGET_METHOD) {
63+
zend_error(E_COMPILE_ERROR, "Only methods can be marked with #[OptionalReturnType]");
64+
}
65+
}
66+
67+
void validate_suppress_optional_return_type_notice_attribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
68+
{
69+
if (target != ZEND_ATTRIBUTE_TARGET_METHOD) {
70+
zend_error(E_COMPILE_ERROR, "Only methods can be marked with #[SuppressOptionalReturnTypeNotice]");
71+
}
72+
}
73+
5874
ZEND_METHOD(Attribute, __construct)
5975
{
6076
zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL;
@@ -67,6 +83,16 @@ ZEND_METHOD(Attribute, __construct)
6783
ZVAL_LONG(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), flags);
6884
}
6985

86+
ZEND_METHOD(OptionalReturnType, __construct)
87+
{
88+
ZEND_PARSE_PARAMETERS_NONE();
89+
}
90+
91+
ZEND_METHOD(SuppressOptionalReturnTypeNotice, __construct)
92+
{
93+
ZEND_PARSE_PARAMETERS_NONE();
94+
}
95+
7096
static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset)
7197
{
7298
if (attributes) {
@@ -278,6 +304,14 @@ void zend_register_attribute_ce(void)
278304
zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_PARAMETER"), ZEND_ATTRIBUTE_TARGET_PARAMETER);
279305
zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("TARGET_ALL"), ZEND_ATTRIBUTE_TARGET_ALL);
280306
zend_declare_class_constant_long(zend_ce_attribute, ZEND_STRL("IS_REPEATABLE"), ZEND_ATTRIBUTE_IS_REPEATABLE);
307+
308+
zend_ce_optional_return_type_attribute = register_class_OptionalReturnType();
309+
attr = zend_internal_attribute_register(zend_ce_optional_return_type_attribute, ZEND_ATTRIBUTE_TARGET_METHOD);
310+
attr->validator = validate_optional_return_type_attribute;
311+
312+
zend_ce_suppress_optional_return_type_notice_attribute = register_class_SuppressOptionalReturnTypeNotice();
313+
attr = zend_internal_attribute_register(zend_ce_suppress_optional_return_type_notice_attribute, ZEND_ATTRIBUTE_TARGET_METHOD);
314+
attr->validator = validate_suppress_optional_return_type_notice_attribute;
281315
}
282316

283317
void zend_attributes_shutdown(void)

Zend/zend_attributes.stub.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,13 @@ final class Attribute
88

99
public function __construct(int $flags = Attribute::TARGET_ALL) {}
1010
}
11+
12+
final class OptionalReturnType
13+
{
14+
public function __construct() {}
15+
}
16+
17+
final class SuppressOptionalReturnTypeNotice
18+
{
19+
public function __construct() {}
20+
}

0 commit comments

Comments
 (0)