Skip to content

Instantly share code, notes, and snippets.

@dstogov
Last active August 29, 2015 14:08
Show Gist options
  • Save dstogov/8deb8b17e41c1a5abf88 to your computer and use it in GitHub Desktop.
Save dstogov/8deb8b17e41c1a5abf88 to your computer and use it in GitHub Desktop.
diff --git a/Zend/tests/return_types/001.phpt b/Zend/tests/return_types/001.phpt
new file mode 100644
index 0000000..a751bd3
--- /dev/null
+++ b/Zend/tests/return_types/001.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Returned nothing, expected array
+
+--FILE--
+<?php
+function test1() : array {
+}
+
+test1();
+
+--EXPECTF--
+Catchable fatal error: Return value of test1() must be of the type array, none returned in %s on line %d
diff --git a/Zend/tests/return_types/002.phpt b/Zend/tests/return_types/002.phpt
new file mode 100644
index 0000000..d8b7792
--- /dev/null
+++ b/Zend/tests/return_types/002.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Returned null, expected array
+
+--FILE--
+<?php
+function test1() : array {
+ return null;
+}
+
+test1();
+
+--EXPECTF--
+Catchable fatal error: Return value of test1() must be of the type array, null returned in %s on line %d
diff --git a/Zend/tests/return_types/003.phpt b/Zend/tests/return_types/003.phpt
new file mode 100644
index 0000000..0ca023b
--- /dev/null
+++ b/Zend/tests/return_types/003.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Returned 1, expected array
+
+--FILE--
+<?php
+function test1() : array {
+ return 1;
+}
+test1();
+
+--EXPECTF--
+Catchable fatal error: Return value of test1() must be of the type array, integer returned in %s on line %d
diff --git a/Zend/tests/return_types/004.phpt b/Zend/tests/return_types/004.phpt
new file mode 100644
index 0000000..44712f5
--- /dev/null
+++ b/Zend/tests/return_types/004.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Returned string, expected array
+
+--FILE--
+<?php
+function test1() : array {
+ return "hello";
+}
+
+test1();
+
+--EXPECTF--
+Catchable fatal error: Return value of test1() must be of the type array, string returned in %s on line %d
diff --git a/Zend/tests/return_types/005.phpt b/Zend/tests/return_types/005.phpt
new file mode 100644
index 0000000..5b979a3
--- /dev/null
+++ b/Zend/tests/return_types/005.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Return value fails inheritance check in method
+
+--FILE--
+<?php
+class foo {}
+
+class qux {
+ public function foo() : foo {
+ return $this;
+ }
+}
+
+$qux = new qux();
+$qux->foo();
+
+--EXPECTF--
+Catchable fatal error: Return value of qux::foo() must be an instance of foo, instance of qux returned in %s on line %d
diff --git a/Zend/tests/return_types/006.phpt b/Zend/tests/return_types/006.phpt
new file mode 100644
index 0000000..359b25a
--- /dev/null
+++ b/Zend/tests/return_types/006.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Return type allowed in child when parent does not have return type
+
+--FILE--
+<?php
+class Comment {}
+
+class CommentsIterator extends ArrayIterator implements Iterator {
+ function current() : Comment {
+ return parent::current();
+ }
+}
+
+$comments = new CommentsIterator([new Comment]);
+foreach ($comments as $comment) {
+ var_dump($comment);
+}
+
+--EXPECTF--
+object(Comment)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/007.phpt b/Zend/tests/return_types/007.phpt
new file mode 100644
index 0000000..0bbfb9f
--- /dev/null
+++ b/Zend/tests/return_types/007.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Return value is subclass of return type
+
+--FILE--
+<?php
+class foo {}
+
+class qux extends foo {
+ public function foo() : foo {
+ return $this;
+ }
+}
+
+$qux = new qux();
+var_dump($qux->foo());
+
+--EXPECTF--
+object(qux)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/008.phpt b/Zend/tests/return_types/008.phpt
new file mode 100644
index 0000000..68262af
--- /dev/null
+++ b/Zend/tests/return_types/008.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Return type covariance in interface implementation
+
+--FILE--
+<?php
+interface foo {
+ public function bar() : foo;
+}
+
+
+class qux implements foo {
+ public function bar() : qux {
+ return $this;
+ }
+}
+
+$qux = new qux();
+var_dump($qux->bar());
+
+--EXPECTF--
+Fatal error: Declaration of qux::bar() must be compatible with foo::bar(): foo in %s008.php on line 7
diff --git a/Zend/tests/return_types/009.phpt b/Zend/tests/return_types/009.phpt
new file mode 100644
index 0000000..0c2b437
--- /dev/null
+++ b/Zend/tests/return_types/009.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Return type covariance error
+
+--FILE--
+<?php
+interface foo {
+ public function bar() : foo;
+}
+
+interface biz {}
+
+class qux implements foo {
+ public function bar() : biz {
+ return $this;
+ }
+}
+
+--EXPECTF--
+Fatal error: Declaration of qux::bar() must be compatible with foo::bar(): foo in %s on line %d
diff --git a/Zend/tests/return_types/010.phpt b/Zend/tests/return_types/010.phpt
new file mode 100644
index 0000000..75deb3a
--- /dev/null
+++ b/Zend/tests/return_types/010.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Returned null, expected array reference
+
+--FILE--
+<?php
+function &foo(array &$in) : array {
+ return null;
+}
+
+$array = [1, 2, 3];
+var_dump(foo($array));
+
+--EXPECTF--
+Catchable fatal error: Return value of foo() must be of the type array, null returned in %s on line %d
diff --git a/Zend/tests/return_types/011.phpt b/Zend/tests/return_types/011.phpt
new file mode 100644
index 0000000..f3b000a
--- /dev/null
+++ b/Zend/tests/return_types/011.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Function returned callable, expected callable
+
+--FILE--
+<?php
+function foo() : callable {
+ return function() {};
+}
+
+var_dump(foo());
+
+--EXPECTF--
+object(Closure)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/012.phpt b/Zend/tests/return_types/012.phpt
new file mode 100644
index 0000000..39fa8e0
--- /dev/null
+++ b/Zend/tests/return_types/012.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Method returned callable, expected callable
+
+--FILE--
+<?php
+class foo {
+ public function bar() : callable {
+ $test = "one";
+ return function() use($test) : array {
+ return array($test);
+ };
+ }
+}
+
+$baz = new foo();
+var_dump($baz->bar());
+
+--EXPECT--
+object(Closure)#2 (2) {
+ ["static"]=>
+ array(1) {
+ ["test"]=>
+ string(3) "one"
+ }
+ ["this"]=>
+ object(foo)#1 (0) {
+ }
+}
diff --git a/Zend/tests/return_types/013.phpt b/Zend/tests/return_types/013.phpt
new file mode 100644
index 0000000..ca03d2f
--- /dev/null
+++ b/Zend/tests/return_types/013.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Closure inside method returned null, expected array
+
+--FILE--
+<?php
+class foo {
+ public function bar() : callable {
+ $test = "one";
+ return function() use($test) : array {
+ return null;
+ };
+ }
+}
+
+$baz = new foo();
+var_dump($func=$baz->bar(), $func());
+
+--EXPECTF--
+Catchable fatal error: Return value of foo::{closure}() must be of the type array, null returned in %s on line %d
diff --git a/Zend/tests/return_types/014.phpt b/Zend/tests/return_types/014.phpt
new file mode 100644
index 0000000..abf8b12
--- /dev/null
+++ b/Zend/tests/return_types/014.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Constructors cannot declare a return type
+
+--FILE--
+<?php
+
+class Foo {
+ function __construct() : Foo {}
+}
+
+--EXPECTF--
+Fatal error: Constructor %s::%s() cannot declare a return type in %s on line %s
diff --git a/Zend/tests/return_types/015.phpt b/Zend/tests/return_types/015.phpt
new file mode 100644
index 0000000..1c2cd56
--- /dev/null
+++ b/Zend/tests/return_types/015.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Return types allowed in namespace
+
+--FILE--
+<?php
+
+namespace Collections;
+
+interface Collection {
+ function values(): Collection;
+}
+
+class Vector implements Collection {
+ function values(): Collection {
+ return $this;
+ }
+}
+
+$v = new Vector;
+var_dump($v->values());
+
+--EXPECTF--
+object(Collections\Vector)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/016.phpt b/Zend/tests/return_types/016.phpt
new file mode 100644
index 0000000..f363675
--- /dev/null
+++ b/Zend/tests/return_types/016.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Fully qualified classes are allowed in return types
+
+--FILE--
+<?php
+
+namespace Collections;
+
+class Foo {
+ function foo(\Iterator $i): \Iterator {
+ return $i;
+ }
+}
+
+$foo = new Foo;
+var_dump($foo->foo(new \EmptyIterator()));
+
+--EXPECTF--
+object(EmptyIterator)#%d (0) {
+}
diff --git a/Zend/tests/return_types/017.phpt b/Zend/tests/return_types/017.phpt
new file mode 100644
index 0000000..d44b26f
--- /dev/null
+++ b/Zend/tests/return_types/017.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Fully qualified classes in trait return types
+
+--FILE--
+<?php
+
+namespace FooSpace;
+
+trait Fooable {
+ function foo(): \Iterator {
+ return new \EmptyIterator();
+ }
+}
+
+class Foo {
+ use Fooable;
+}
+
+$foo = new Foo;
+var_dump($foo->foo([]));
+
+--EXPECTF--
+object(EmptyIterator)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/018.phpt b/Zend/tests/return_types/018.phpt
new file mode 100644
index 0000000..6d6e0c7
--- /dev/null
+++ b/Zend/tests/return_types/018.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Destructors cannot declare a return type
+
+--FILE--
+<?php
+
+class Foo {
+ function __destruct() : Foo {}
+}
+
+--EXPECTF--
+Fatal error: Destructor %s::%s() cannot declare a return type in %s on line %s
diff --git a/Zend/tests/return_types/019.phpt b/Zend/tests/return_types/019.phpt
new file mode 100644
index 0000000..cebf483
--- /dev/null
+++ b/Zend/tests/return_types/019.phpt
@@ -0,0 +1,12 @@
+--TEST--
+__clone cannot declare a return type
+
+--FILE--
+<?php
+
+class Foo {
+ function __clone() : Foo {}
+}
+
+--EXPECTF--
+Fatal error: %s::%s() cannot declare a return type in %s on line %s
diff --git a/Zend/tests/return_types/020.phpt b/Zend/tests/return_types/020.phpt
new file mode 100644
index 0000000..d43496b
--- /dev/null
+++ b/Zend/tests/return_types/020.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Exception thrown from function with return type
+
+--FILE--
+<?php
+function test() : array {
+ throw new Exception();
+}
+
+test();
+
+--EXPECTF--
+Fatal error: Uncaught exception 'Exception' in %s:%d
+Stack trace:
+#0 %s(%d): test()
+#1 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/return_types/022.phpt b/Zend/tests/return_types/022.phpt
new file mode 100644
index 0000000..47cbb79
--- /dev/null
+++ b/Zend/tests/return_types/022.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Hint on closure with lexical vars
+
+--FILE--
+<?php
+$foo = "bar";
+$test = function() use($foo) : Closure {
+ return function() use ($foo) {
+ return $foo;
+ };
+};
+
+$callable = $test();
+var_dump($callable());
+
+--EXPECTF--
+string(3) "bar"
diff --git a/Zend/tests/return_types/023.phpt b/Zend/tests/return_types/023.phpt
new file mode 100644
index 0000000..051f7dc
--- /dev/null
+++ b/Zend/tests/return_types/023.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Return type allows self
+
+--FILE--
+<?php
+class Foo {
+ public static function getInstance() : self {
+ return new static();
+ }
+}
+
+class Bar extends Foo {}
+
+var_dump(Foo::getInstance());
+var_dump(Bar::getInstance());
+
+--EXPECTF--
+object(Foo)#%d (%d) {
+}
+object(Bar)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/029.phpt b/Zend/tests/return_types/029.phpt
new file mode 100644
index 0000000..61a9e1c
--- /dev/null
+++ b/Zend/tests/return_types/029.phpt
@@ -0,0 +1,12 @@
+--TEST--
+PHP 4 Constructors cannot declare a return type
+
+--FILE--
+<?php
+
+class Foo {
+ function foo() : Foo {}
+}
+
+--EXPECTF--
+Fatal error: Constructor %s::%s() cannot declare a return type in %s on line %s
diff --git a/Zend/tests/return_types/030.phpt b/Zend/tests/return_types/030.phpt
new file mode 100644
index 0000000..9f2691f
--- /dev/null
+++ b/Zend/tests/return_types/030.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Return type of self is not allowed in function
+
+--FILE--
+<?php
+
+function test(): self {}
+
+--EXPECTF--
+Fatal error: Cannot declare a return type of self outside of a class scope in %s on line 3
diff --git a/Zend/tests/return_types/031.phpt b/Zend/tests/return_types/031.phpt
new file mode 100644
index 0000000..650fb08
--- /dev/null
+++ b/Zend/tests/return_types/031.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Return type of self is not allowed in closure
+
+--FILE--
+<?php
+
+$c = function(): self {};
+
+--EXPECTF--
+Fatal error: Cannot declare a return type of self outside of a class scope in %s on line 3
diff --git a/Zend/tests/return_types/classes.php.inc b/Zend/tests/return_types/classes.php.inc
new file mode 100644
index 0000000..e4b691e
--- /dev/null
+++ b/Zend/tests/return_types/classes.php.inc
@@ -0,0 +1,5 @@
+<?php
+
+class A {}
+class B extends A {}
+
diff --git a/Zend/tests/return_types/generators001.phpt b/Zend/tests/return_types/generators001.phpt
new file mode 100644
index 0000000..f2ac88a
--- /dev/null
+++ b/Zend/tests/return_types/generators001.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Valid generator return types
+
+--FILE--
+<?php
+function test1() : Generator {
+ yield 1;
+}
+
+function test2() : Iterator {
+ yield 2;
+}
+
+function test3() : Traversable {
+ yield 3;
+}
+
+var_dump(
+ test1(),
+ test2(),
+ test3()
+);
+
+--EXPECTF--
+object(Generator)#%d (%d) {
+}
+object(Generator)#%d (%d) {
+}
+object(Generator)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/generators002.phpt b/Zend/tests/return_types/generators002.phpt
new file mode 100644
index 0000000..f7dbfda
--- /dev/null
+++ b/Zend/tests/return_types/generators002.phpt
@@ -0,0 +1,11 @@
+--TEST--
+Generator return type must be Generator, Iterator or Traversable
+
+--FILE--
+<?php
+function test1() : StdClass {
+ yield 1;
+}
+
+--EXPECTF--
+Fatal error: Generators may only declare a return type of Generator, Iterator or Traversable, StdClass is not permitted in %s on line %d
diff --git a/Zend/tests/return_types/generators003.phpt b/Zend/tests/return_types/generators003.phpt
new file mode 100644
index 0000000..b90f67a
--- /dev/null
+++ b/Zend/tests/return_types/generators003.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Return type covariance works with generators
+
+--FILE--
+<?php
+interface Collection extends IteratorAggregate {
+ function getIterator(): Iterator;
+}
+
+class SomeCollection implements Collection {
+ function getIterator(): Generator {
+ foreach ($this->data as $key => $value) {
+ yield $key => $value;
+ }
+ }
+}
+
+$some = new SomeCollection();
+var_dump($some->getIterator());
+
+--EXPECTF--
+Fatal error: Declaration of SomeCollection::getIterator() must be compatible with Collection::getIterator(): Iterator in %sgenerators003.php on line 6
diff --git a/Zend/tests/return_types/generators004.phpt b/Zend/tests/return_types/generators004.phpt
new file mode 100644
index 0000000..74aa801
--- /dev/null
+++ b/Zend/tests/return_types/generators004.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Generator with return type does not fail with empty return
+
+--FILE--
+<?php
+
+$a = function(): \Iterator {
+ yield 1;
+ return;
+};
+
+foreach($a() as $value) {
+ echo $value;
+}
+
+--EXPECT--
+1
diff --git a/Zend/tests/return_types/generators005.phpt b/Zend/tests/return_types/generators005.phpt
new file mode 100644
index 0000000..21e6c4b
--- /dev/null
+++ b/Zend/tests/return_types/generators005.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Return type covariance works with generators
+
+--FILE--
+<?php
+interface Collection extends IteratorAggregate {
+ function getIterator(): Iterator;
+}
+
+class SomeCollection implements Collection {
+ function getIterator(): Iterator {
+ foreach ($this->data as $key => $value) {
+ yield $key => $value;
+ }
+ }
+}
+
+$some = new SomeCollection();
+var_dump($some->getIterator());
+
+--EXPECTF--
+object(Generator)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/inheritance001.phpt b/Zend/tests/return_types/inheritance001.phpt
new file mode 100644
index 0000000..f6e88c1
--- /dev/null
+++ b/Zend/tests/return_types/inheritance001.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Return type covariance; extends class
+
+--FILE--
+<?php
+
+class A {
+ function foo(): A {}
+}
+
+class B extends A {
+ function foo(): StdClass {}
+}
+
+--EXPECTF--
+Strict Standards: Declaration of B::foo() should be compatible with A::foo(): A in %s on line %d
diff --git a/Zend/tests/return_types/inheritance002.phpt b/Zend/tests/return_types/inheritance002.phpt
new file mode 100644
index 0000000..f63a8eb
--- /dev/null
+++ b/Zend/tests/return_types/inheritance002.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Return type covariance; extends abstract class
+
+--FILE--
+<?php
+
+abstract class A {
+ abstract function foo(): A;
+}
+
+class B extends A {
+ function foo(): StdClass {}
+}
+
+--EXPECTF--
+Fatal error: Declaration of B::foo() must be compatible with A::foo(): A in %s on line %d
diff --git a/Zend/tests/return_types/inheritance003.phpt b/Zend/tests/return_types/inheritance003.phpt
new file mode 100644
index 0000000..e627363
--- /dev/null
+++ b/Zend/tests/return_types/inheritance003.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Return type mismatch; implements interface
+
+--FILE--
+<?php
+
+interface A {
+ function foo(): A;
+}
+
+class B implements A {
+ function foo(): StdClass {}
+}
+
+--EXPECTF--
+Fatal error: Declaration of B::foo() must be compatible with A::foo(): A in %s on line %d
diff --git a/Zend/tests/return_types/inheritance004.phpt b/Zend/tests/return_types/inheritance004.phpt
new file mode 100644
index 0000000..66d3556
--- /dev/null
+++ b/Zend/tests/return_types/inheritance004.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Internal covariant return type of self
+
+--FILE--
+<?php
+class Foo {
+ public static function test() : self {
+ return new Foo;
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : parent {
+ return new Bar;
+ }
+}
+
+var_dump(Bar::test());
+var_dump(Foo::test());
+
+--EXPECTF--
+object(Bar)#%d (0) {
+}
+object(Foo)#%d (0) {
+}
diff --git a/Zend/tests/return_types/inheritance005.phpt b/Zend/tests/return_types/inheritance005.phpt
new file mode 100644
index 0000000..df45ec3
--- /dev/null
+++ b/Zend/tests/return_types/inheritance005.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Internal covariant return type of self
+
+--FILE--
+<?php
+class Foo {
+ public static function test() : self {
+ return new Foo;
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : self {
+ return new Bar;
+ }
+}
+
+var_dump(Bar::test());
+var_dump(Foo::test());
+
+--EXPECTF--
+Strict Standards: Declaration of Bar::test() should be compatible with Foo::test(): Foo in %sinheritance005.php on line 12
+object(Bar)#%d (0) {
+}
+object(Foo)#%d (0) {
+}
diff --git a/Zend/tests/return_types/inheritance006.phpt b/Zend/tests/return_types/inheritance006.phpt
new file mode 100644
index 0000000..b973f98
--- /dev/null
+++ b/Zend/tests/return_types/inheritance006.phpt
@@ -0,0 +1,31 @@
+--TEST--
+External covariant return type of self
+
+--INI--
+opcache.enable_cli=1
+
+--FILE--
+<?php
+require __DIR__ . "/classes.php.inc";
+
+class Foo {
+ public static function test() : A {
+ return new A;
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : B {
+ return new B;
+ }
+}
+
+var_dump(Bar::test());
+var_dump(Foo::test());
+
+--EXPECTF--
+Strict Standards: Declaration of Bar::test() should be compatible with Foo::test(): A in %sinheritance006.php on line 14
+object(B)#%d (0) {
+}
+object(A)#%d (0) {
+}
diff --git a/Zend/tests/return_types/inheritance007.phpt b/Zend/tests/return_types/inheritance007.phpt
new file mode 100644
index 0000000..e165c57
--- /dev/null
+++ b/Zend/tests/return_types/inheritance007.phpt
@@ -0,0 +1,43 @@
+--TEST--
+Inheritance Hinting Compile Checking Failure Internal Classes
+
+--INI--
+opcache.enable_cli=1
+
+--FILE--
+<?php
+class Foo {
+ public static function test() : Traversable {
+ return new ArrayIterator([1, 2]);
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : ArrayObject {
+ return new ArrayObject([1, 2]);
+ }
+}
+
+var_dump(Bar::test());
+var_dump(Foo::test());
+
+--EXPECTF--
+Strict Standards: Declaration of Bar::test() should be compatible with Foo::test(): Traversable in %sinheritance007.php on line 12
+object(ArrayObject)#%d (1) {
+ ["storage":"ArrayObject":private]=>
+ array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ }
+}
+object(ArrayIterator)#%d (1) {
+ ["storage":"ArrayIterator":private]=>
+ array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ }
+}
diff --git a/Zend/tests/return_types/inheritance008.phpt b/Zend/tests/return_types/inheritance008.phpt
new file mode 100644
index 0000000..242c50d
--- /dev/null
+++ b/Zend/tests/return_types/inheritance008.phpt
@@ -0,0 +1,30 @@
+--TEST--
+External covariant return type of self
+
+--INI--
+opcache.enable_cli=1
+
+--FILE--
+<?php
+require __DIR__ . "/classes.php.inc";
+
+class Foo {
+ public static function test() : A {
+ return new A;
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : A {
+ return new B;
+ }
+}
+
+var_dump(Bar::test());
+var_dump(Foo::test());
+
+--EXPECTF--
+object(B)#%d (0) {
+}
+object(A)#%d (0) {
+}
diff --git a/Zend/tests/return_types/inheritance009.phpt b/Zend/tests/return_types/inheritance009.phpt
new file mode 100644
index 0000000..382007e
--- /dev/null
+++ b/Zend/tests/return_types/inheritance009.phpt
@@ -0,0 +1,42 @@
+--TEST--
+Inheritance Hinting Compile Checking Failure Internal Classes
+
+--INI--
+opcache.enable_cli=1
+
+--FILE--
+<?php
+class Foo {
+ public static function test() : Traversable {
+ return new ArrayIterator([1, 2]);
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : Traversable {
+ return new ArrayObject([1, 2]);
+ }
+}
+
+var_dump(Bar::test());
+var_dump(Foo::test());
+
+--EXPECTF--
+object(ArrayObject)#%d (1) {
+ ["storage":"ArrayObject":private]=>
+ array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ }
+}
+object(ArrayIterator)#%d (1) {
+ ["storage":"ArrayIterator":private]=>
+ array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ }
+}
diff --git a/Zend/tests/return_types/reflection001.phpt b/Zend/tests/return_types/reflection001.phpt
new file mode 100644
index 0000000..631516a
--- /dev/null
+++ b/Zend/tests/return_types/reflection001.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Reflection::hasReturnType true
+
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70000) {
+ print 'skip';
+}
+
+--FILE--
+<?php
+
+$func = function(): array {
+ return [];
+};
+
+$rf = new ReflectionFunction($func);
+var_dump($rf->hasReturnType());
+
+--EXPECT--
+bool(true)
diff --git a/Zend/tests/return_types/reflection002.phpt b/Zend/tests/return_types/reflection002.phpt
new file mode 100644
index 0000000..ac57aef
--- /dev/null
+++ b/Zend/tests/return_types/reflection002.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Reflection::hasReturnType false
+
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70000) {
+ print 'skip';
+}
+
+--FILE--
+<?php
+
+$func = function() {
+ return [];
+};
+
+$rf = new ReflectionFunction($func);
+var_dump($rf->hasReturnType());
+
+--EXPECT--
+bool(false)
diff --git a/Zend/tests/return_types/reflection003.phpt b/Zend/tests/return_types/reflection003.phpt
new file mode 100644
index 0000000..2add813
--- /dev/null
+++ b/Zend/tests/return_types/reflection003.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Reflection::getReturnType empty
+
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70000) {
+ print 'skip';
+}
+
+--FILE--
+<?php
+
+$func = function() {
+ return [];
+};
+
+$rf = new ReflectionFunction($func);
+var_dump($rf->getReturnType());
+--EXPECT--
+string(0) ""
diff --git a/Zend/tests/return_types/reflection004.phpt b/Zend/tests/return_types/reflection004.phpt
new file mode 100644
index 0000000..fb6e9a8
--- /dev/null
+++ b/Zend/tests/return_types/reflection004.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Reflection::getReturnType array
+
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70000) {
+ print 'skip';
+}
+
+--FILE--
+<?php
+
+$func = function(): array {
+ return [];
+};
+
+$rf = new ReflectionFunction($func);
+var_dump($rf->getReturnType());
+--EXPECT--
+string(5) "array"
diff --git a/Zend/tests/return_types/reflection005.phpt b/Zend/tests/return_types/reflection005.phpt
new file mode 100644
index 0000000..c939036
--- /dev/null
+++ b/Zend/tests/return_types/reflection005.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Reflection::getReturnType callable
+
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70000) {
+ print 'skip';
+}
+
+--FILE--
+<?php
+
+$func = function(): callable {
+ return 'strlen';
+};
+
+$rf = new ReflectionFunction($func);
+var_dump($rf->getReturnType());
+--EXPECT--
+string(8) "callable"
diff --git a/Zend/tests/return_types/reflection006.phpt b/Zend/tests/return_types/reflection006.phpt
new file mode 100644
index 0000000..1544c68
--- /dev/null
+++ b/Zend/tests/return_types/reflection006.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Reflection::getReturnType object
+
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70000) {
+ print 'skip';
+}
+
+--FILE--
+<?php
+
+$func = function(): StdClass {
+ return new StdClass;
+};
+
+$rf = new ReflectionFunction($func);
+var_dump($rf->getReturnType());
+--EXPECT--
+string(8) "StdClass"
diff --git a/Zend/tests/return_types/reflection007.phpt b/Zend/tests/return_types/reflection007.phpt
new file mode 100644
index 0000000..1c8f3c9
--- /dev/null
+++ b/Zend/tests/return_types/reflection007.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Reflection::getReturnType object
+
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70000) {
+ print 'skip';
+}
+
+--FILE--
+<?php
+
+interface A {
+ function foo(): A;
+}
+
+$rc = new ReflectionClass("A");
+var_dump($rc->getMethod("foo")->getReturnType());
+--EXPECT--
+string(1) "A"
diff --git a/Zend/tests/return_types/reflection008.phpt b/Zend/tests/return_types/reflection008.phpt
new file mode 100644
index 0000000..04bb324
--- /dev/null
+++ b/Zend/tests/return_types/reflection008.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Reflection::getReturnType object
+
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70000) {
+ print 'skip';
+}
+
+--FILE--
+<?php
+
+class A {
+ function foo(): array {
+ return [];
+ }
+}
+
+$rc = new ReflectionClass("A");
+var_dump($rc->getMethod("foo")->getReturnType());
+--EXPECT--
+string(5) "array"
diff --git a/Zend/tests/return_types/reflection009.phpt b/Zend/tests/return_types/reflection009.phpt
new file mode 100644
index 0000000..c42f409
--- /dev/null
+++ b/Zend/tests/return_types/reflection009.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Return type hinting and Reflection::export()
+
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70000) {
+ print 'skip';
+}
+
+--FILE--
+<?php
+
+class A {
+ function foo(array $a): array {
+ return $a;
+ }
+}
+
+ReflectionClass::export("A");
+--EXPECTF--
+Class [ <user> class A ] {
+ @@ %sreflection009.php 3-7
+
+ - Constants [0] {
+ }
+
+ - Static properties [0] {
+ }
+
+ - Static methods [0] {
+ }
+
+ - Properties [0] {
+ }
+
+ - Methods [1] {
+ Method [ <user> public method foo ] {
+ @@ %sreflection009.php 4 - 6
+
+ - Parameters [1] {
+ Parameter #0 [ <required> array $a ]
+ }
+ - Return [ array ]
+ }
+ }
+}
+
diff --git a/Zend/tests/return_types/rfc001.phpt b/Zend/tests/return_types/rfc001.phpt
new file mode 100644
index 0000000..4d52752
--- /dev/null
+++ b/Zend/tests/return_types/rfc001.phpt
@@ -0,0 +1,14 @@
+--TEST--
+RFC example: returned type does not match the type declaration
+
+--FILE--
+<?php
+
+function get_config(): array {
+ return 42;
+}
+
+get_config();
+
+--EXPECTF--
+Catchable fatal error: Return value of get_config() must be of the type array, integer returned in %s on line %d
diff --git a/Zend/tests/return_types/rfc002.phpt b/Zend/tests/return_types/rfc002.phpt
new file mode 100644
index 0000000..29b5ed3
--- /dev/null
+++ b/Zend/tests/return_types/rfc002.phpt
@@ -0,0 +1,13 @@
+--TEST--
+RFC example: expected class int and returned integer
+
+--FILE--
+<?php
+function answer(): int {
+ return 42;
+}
+
+answer();
+
+--EXPECTF--
+Catchable fatal error: Return value of answer() must be an instance of int, integer returned in %s on line %d
diff --git a/Zend/tests/return_types/rfc003.phpt b/Zend/tests/return_types/rfc003.phpt
new file mode 100644
index 0000000..3aad309
--- /dev/null
+++ b/Zend/tests/return_types/rfc003.phpt
@@ -0,0 +1,13 @@
+--TEST--
+RFC example: cannot return null with a return type declaration
+
+--FILE--
+<?php
+function foo(): DateTime {
+ return null;
+}
+
+foo();
+
+--EXPECTF--
+Catchable fatal error: Return value of foo() must be an instance of DateTime, null returned in %s on line %d
diff --git a/Zend/tests/return_types/rfc004.phpt b/Zend/tests/return_types/rfc004.phpt
new file mode 100644
index 0000000..13bfe72
--- /dev/null
+++ b/Zend/tests/return_types/rfc004.phpt
@@ -0,0 +1,21 @@
+--TEST--
+RFC example: missing return type on override
+
+--FILE--
+<?php
+
+class User {}
+
+interface UserGateway {
+ function find($id) : User;
+}
+
+class UserGateway_MySql implements UserGateway {
+ // must return User or subtype of User
+ function find($id) {
+ return new User;
+ }
+}
+
+--EXPECTF--
+Fatal error: Declaration of UserGateway_MySql::find() must be compatible with UserGateway::find($id): User in %s on line 9
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index 01d3f9a..63e23c6 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -2042,6 +2042,9 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
/* Don't count the variadic argument */
internal_function->num_args--;
}
+ if (info->class_name || info->type_hint) {
+ internal_function->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE_HINT;
+ }
} else {
internal_function->arg_info = NULL;
internal_function->num_args = 0;
diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c
index d5e739c..ddfa5fd 100644
--- a/Zend/zend_ast.c
+++ b/Zend/zend_ast.c
@@ -65,7 +65,7 @@ ZEND_API zend_ast *zend_ast_create_zval_ex(zval *zv, zend_ast_attr attr) {
ZEND_API zend_ast *zend_ast_create_decl(
zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment,
- zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2
+ zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3
) {
zend_ast_decl *ast;
@@ -81,6 +81,7 @@ ZEND_API zend_ast *zend_ast_create_decl(
ast->child[0] = child0;
ast->child[1] = child1;
ast->child[2] = child2;
+ ast->child[3] = child3;
return (zend_ast *) ast;
}
@@ -394,6 +395,7 @@ static void zend_ast_destroy_ex(zend_ast *ast, zend_bool free) {
zend_ast_destroy_ex(decl->child[0], free);
zend_ast_destroy_ex(decl->child[1], free);
zend_ast_destroy_ex(decl->child[2], free);
+ zend_ast_destroy_ex(decl->child[3], free);
break;
}
default:
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h
index 774e73c..7e7a189 100644
--- a/Zend/zend_ast.h
+++ b/Zend/zend_ast.h
@@ -183,7 +183,7 @@ typedef struct _zend_ast_decl {
unsigned char *lex_pos;
zend_string *doc_comment;
zend_string *name;
- zend_ast *child[3];
+ zend_ast *child[4];
} zend_ast_decl;
ZEND_API zend_ast *zend_ast_create_zval_ex(zval *zv, zend_ast_attr attr);
@@ -193,7 +193,7 @@ ZEND_API zend_ast *zend_ast_create(zend_ast_kind kind, ...);
ZEND_API zend_ast *zend_ast_create_decl(
zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment,
- zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2
+ zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3
);
ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind kind, ...);
diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c
index 2198291..63c985c 100644
--- a/Zend/zend_closures.c
+++ b/Zend/zend_closures.c
@@ -359,7 +359,7 @@ static HashTable *zend_closure_get_debug_info(zval *object, int *is_temp) /* {{{
zend_hash_str_update(closure->debug_info, "this", sizeof("this")-1, &closure->this_ptr);
}
- if (arg_info) {
+ if (arg_info && closure->func.common.num_args) {
uint32_t i, num_args, required = closure->func.common.required_num_args;
array_init(&val);
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index c06ebc4..a448bf8 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -436,14 +436,6 @@ static int zend_add_const_name_literal(zend_op_array *op_array, zend_string *nam
op.constant = zend_add_literal(CG(active_op_array), &_c); \
} while (0)
-#define MAKE_NOP(opline) do { \
- opline->opcode = ZEND_NOP; \
- memset(&opline->result, 0, sizeof(opline->result)); \
- memset(&opline->op1, 0, sizeof(opline->op1)); \
- memset(&opline->op2, 0, sizeof(opline->op2)); \
- opline->result_type = opline->op1_type = opline->op2_type = IS_UNUSED; \
-} while (0)
-
void zend_stop_lexing(void) {
LANG_SCNG(yy_cursor) = LANG_SCNG(yy_limit);
}
@@ -1025,7 +1017,7 @@ void zend_do_early_binding(void) /* {{{ */
if (((ce = zend_lookup_class(Z_STR_P(parent_name))) == NULL) ||
((CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES) &&
(ce->type == ZEND_INTERNAL_CLASS))) {
- if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) {
+ if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) {
uint32_t *opline_num = &CG(active_op_array)->early_binding;
while (*opline_num != (uint32_t)-1) {
@@ -3104,6 +3096,16 @@ static void zend_free_foreach_and_switch_variables(void) /* {{{ */
}
/* }}} */
+
+static void zend_emit_return_type_check(znode *expr, zend_arg_info *ret_info) /* {{{ */
+{
+ if (ret_info->class_name || ret_info->type_hint) {
+ zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL);
+ }
+}
+/* }}} */
+
+
void zend_compile_return(zend_ast *ast) /* {{{ */
{
zend_ast *expr_ast = ast->child[0];
@@ -3129,6 +3131,9 @@ void zend_compile_return(zend_ast *ast) /* {{{ */
opline->op1.var = CG(context).fast_call_var;
}
+ if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ zend_emit_return_type_check(&expr_node, CG(active_op_array)->arg_info - 1);
+ }
opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN,
&expr_node, NULL);
@@ -3757,18 +3762,50 @@ void zend_compile_stmt_list(zend_ast *ast) /* {{{ */
}
/* }}} */
-void zend_compile_params(zend_ast *ast) /* {{{ */
+void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, zend_bool is_method) /* {{{ */
{
zend_ast_list *list = zend_ast_get_list(ast);
uint32_t i;
zend_op_array *op_array = CG(active_op_array);
zend_arg_info *arg_infos;
+
+ if (return_type_ast) {
+ /* Use op_array->arg_info[-1] for return type hinting */
+ arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children + 1, 0);
+ arg_infos->name = NULL;
+ arg_infos->pass_by_reference = (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0;
+ arg_infos->is_variadic = 0;
+ arg_infos->type_hint = 0;
+ arg_infos->allow_null = 0;
+ arg_infos->class_name = NULL;
+
+ if (return_type_ast->kind == ZEND_AST_TYPE) {
+ arg_infos->type_hint = return_type_ast->attr;
+ } else {
+ zend_string *class_name = zend_ast_get_str(return_type_ast);
- if (list->children == 0) {
- return;
+ if (zend_is_const_default_class_ref(return_type_ast)) {
+ class_name = zend_resolve_class_name_ast(return_type_ast);
+ } else {
+ zend_string_addref(class_name);
+ if (!is_method) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare a return type of %s outside of a class scope", class_name->val);
+ return;
+ }
+ }
+
+ arg_infos->type_hint = IS_OBJECT;
+ arg_infos->class_name = class_name;
+ }
+
+ arg_infos++;
+ op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE_HINT;
+ } else {
+ if (list->children == 0) {
+ return;
+ }
+ arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children, 0);
}
-
- arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children, 0);
for (i = 0; i < list->children; ++i) {
zend_ast *param_ast = list->child[i];
zend_ast *type_ast = param_ast->child[0];
@@ -4141,6 +4178,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */
zend_ast *params_ast = decl->child[0];
zend_ast *uses_ast = decl->child[1];
zend_ast *stmt_ast = decl->child[2];
+ zend_ast *return_type_ast = decl->child[3];
zend_bool is_method = decl->kind == ZEND_AST_METHOD;
zend_op_array *orig_op_array = CG(active_op_array);
@@ -4184,7 +4222,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */
zend_stack_push(&CG(loop_var_stack), (void *) &dummy_var);
}
- zend_compile_params(params_ast);
+ zend_compile_params(params_ast, return_type_ast, is_method);
if (uses_ast) {
zend_compile_closure_uses(uses_ast);
}
@@ -4195,6 +4233,9 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */
CG(active_class_entry), (zend_function *) op_array, E_COMPILE_ERROR);
}
+ if (return_type_ast) {
+ zend_emit_return_type_check(NULL, op_array->arg_info - 1);
+ }
zend_do_extended_info();
zend_emit_final_return(NULL);
@@ -4574,12 +4615,21 @@ void zend_compile_class_decl(zend_ast *ast) /* {{{ */
zend_error_noreturn(E_COMPILE_ERROR, "Constructor %s::%s() cannot be static",
ce->name->val, ce->constructor->common.function_name->val);
}
+ if (ce->constructor->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "Constructor %s::%s() cannot declare a return type",
+ ce->name->val, ce->constructor->common.function_name->val);
+ }
}
if (ce->destructor) {
ce->destructor->common.fn_flags |= ZEND_ACC_DTOR;
if (ce->destructor->common.fn_flags & ZEND_ACC_STATIC) {
zend_error_noreturn(E_COMPILE_ERROR, "Destructor %s::%s() cannot be static",
ce->name->val, ce->destructor->common.function_name->val);
+ } else if (ce->destructor->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "Destructor %s::%s() cannot declare a return type",
+ ce->name->val, ce->destructor->common.function_name->val);
}
}
if (ce->clone) {
@@ -4587,6 +4637,10 @@ void zend_compile_class_decl(zend_ast *ast) /* {{{ */
if (ce->clone->common.fn_flags & ZEND_ACC_STATIC) {
zend_error_noreturn(E_COMPILE_ERROR, "Clone method %s::%s() cannot be static",
ce->name->val, ce->clone->common.function_name->val);
+ } else if (ce->clone->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "%s::%s() cannot declare a return type",
+ ce->name->val, ce->clone->common.function_name->val);
}
}
@@ -5399,6 +5453,21 @@ void zend_compile_yield(znode *result, zend_ast *ast) /* {{{ */
zend_error_noreturn(E_COMPILE_ERROR,
"The \"yield\" expression can only be used inside a function");
}
+ if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ const char *msg = "Generators may only declare a return type of Generator, Iterator or Traversable, %s is not permitted";
+ if (!CG(active_op_array)->arg_info[-1].class_name) {
+ zend_error_noreturn(E_COMPILE_ERROR, msg,
+ zend_get_type_by_const(CG(active_op_array)->arg_info[-1].type_hint));
+ }
+ if (!(CG(active_op_array)->arg_info[-1].class_name->len == sizeof("Traversable")-1
+ && zend_binary_strcasecmp(CG(active_op_array)->arg_info[-1].class_name->val, sizeof("Traversable")-1, "Traversable", sizeof("Traversable")-1) == 0) &&
+ !(CG(active_op_array)->arg_info[-1].class_name->len == sizeof("Iterator")-1
+ && zend_binary_strcasecmp(CG(active_op_array)->arg_info[-1].class_name->val, sizeof("Iterator")-1, "Iterator", sizeof("Iterator")-1) == 0) &&
+ !(CG(active_op_array)->arg_info[-1].class_name->len == sizeof("Generator")-1
+ && zend_binary_strcasecmp(CG(active_op_array)->arg_info[-1].class_name->val, sizeof("Generator")-1, "Generator", sizeof("Generator")-1) == 0)) {
+ zend_error_noreturn(E_COMPILE_ERROR, msg, CG(active_op_array)->arg_info[-1].class_name->val);
+ }
+ }
CG(active_op_array)->fn_flags |= ZEND_ACC_GENERATOR;
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 7909e98..4ac84c4 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -35,6 +35,14 @@
#define SET_UNUSED(op) op ## _type = IS_UNUSED
+#define MAKE_NOP(opline) do { \
+ opline->opcode = ZEND_NOP; \
+ memset(&opline->result, 0, sizeof(opline->result)); \
+ memset(&opline->op1, 0, sizeof(opline->op1)); \
+ memset(&opline->op2, 0, sizeof(opline->op2)); \
+ opline->result_type = opline->op1_type = opline->op2_type = IS_UNUSED; \
+} while (0)
+
#define RESET_DOC_COMMENT() do { \
if (CG(doc_comment)) { \
zend_string_release(CG(doc_comment)); \
@@ -243,6 +251,9 @@ typedef struct _zend_try_catch_element {
/* internal function is allocated at arena */
#define ZEND_ACC_ARENA_ALLOCATED 0x20000000
+/* Function has a return type hint (or class has such non-private function) */
+#define ZEND_ACC_HAS_RETURN_TYPE_HINT 0x40000000
+
#define ZEND_CE_IS_TRAIT(ce) (((ce)->ce_flags & ZEND_ACC_TRAIT) == ZEND_ACC_TRAIT)
char *zend_visibility_string(uint32_t fn_flags);
@@ -288,13 +299,14 @@ typedef struct _zend_arg_info {
/* the following structure repeats the layout of zend_internal_arg_info,
* but its fields have different meaning. It's used as the first element of
* arg_info array to define properties of internal functions.
+ * It's also used for return type hinting.
*/
typedef struct _zend_internal_function_info {
zend_uintptr_t required_num_args;
- const char *_class_name;
- zend_uchar _type_hint;
+ const char *class_name;
+ zend_uchar type_hint;
zend_bool return_reference;
- zend_bool _allow_null;
+ zend_bool allow_null;
zend_bool _is_variadic;
} zend_internal_function_info;
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index 3c01b28..5c23dd0 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -751,6 +751,87 @@ static void zend_verify_missing_arg(zend_execute_data *execute_data, uint32_t ar
}
}
+ZEND_API void zend_verify_return_error(int error_type, const zend_function *zf, const char *need_msg, const char *need_kind, const char *returned_msg, const char *returned_kind)
+{
+ const char *fname = zf->common.function_name->val;
+ const char *fsep;
+ const char *fclass;
+
+ if (zf->common.scope) {
+ fsep = "::";
+ fclass = zf->common.scope->name->val;
+ } else {
+ fsep = "";
+ fclass = "";
+ }
+
+ zend_error(error_type,
+ "Return value of %s%s%s() must %s%s, %s%s returned",
+ fclass, fsep, fname, need_msg, need_kind, returned_msg, returned_kind);
+}
+
+static void zend_verify_return_type(zend_function *zf, zval *ret)
+{
+ zend_arg_info *ret_info = zf->common.arg_info - 1;
+ char *need_msg;
+ zend_class_entry *ce;
+
+ if (ret_info->class_name) {
+ char *class_name;
+
+ if (Z_TYPE_P(ret) == IS_OBJECT) {
+ need_msg = zend_verify_arg_class_kind(ret_info, &class_name, &ce);
+ if (!ce || !instanceof_function(Z_OBJCE_P(ret), ce)) {
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, need_msg, class_name, "instance of ", Z_OBJCE_P(ret)->name->val);
+ }
+ } else if (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null) {
+ need_msg = zend_verify_arg_class_kind(ret_info, &class_name, &ce);
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, need_msg, class_name, zend_zval_type_name(ret), "");
+ }
+ } else if (ret_info->type_hint) {
+ if (ret_info->type_hint == IS_ARRAY) {
+ if (Z_TYPE_P(ret) != IS_ARRAY && (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null)) {
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, "be of the type array", "", zend_zval_type_name(ret), "");
+ }
+ } else if (ret_info->type_hint == IS_CALLABLE) {
+ if (!zend_is_callable(ret, IS_CALLABLE_CHECK_SILENT, NULL) && (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null)) {
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, "be callable", "", zend_zval_type_name(ret), "");
+ }
+#if ZEND_DEBUG
+ } else {
+ zend_error(E_ERROR, "Unknown typehint");
+#endif
+ }
+ }
+}
+
+static inline int zend_verify_missing_return_type(zend_function *zf)
+{
+ zend_arg_info *ret_info = zf->common.arg_info - 1;
+ char *need_msg;
+ zend_class_entry *ce;
+
+ if (ret_info->class_name) {
+ char *class_name;
+
+ need_msg = zend_verify_arg_class_kind(ret_info, &class_name, &ce);
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, need_msg, class_name, "none", "");
+ return 0;
+ } else if (ret_info->type_hint) {
+ if (ret_info->type_hint == IS_ARRAY) {
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, "be of the type array", "", "none", "");
+ } else if (ret_info->type_hint == IS_CALLABLE) {
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, "be callable", "", "none", "");
+#if ZEND_DEBUG
+ } else {
+ zend_error(E_ERROR, "Unknown typehint");
+#endif
+ }
+ return 0;
+ }
+ return 1;
+}
+
static zend_always_inline void zend_assign_to_object(zval *retval, zval *object, uint32_t object_op_type, zval *property_name, uint32_t property_op_type, int value_type, znode_op value_op, const zend_execute_data *execute_data, void **cache_slot)
{
zend_free_op free_value;
diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h
index a94299f..58f1927 100644
--- a/Zend/zend_execute.h
+++ b/Zend/zend_execute.h
@@ -50,6 +50,7 @@ ZEND_API int zend_eval_stringl_ex(char *str, size_t str_len, zval *retval_ptr, c
ZEND_API char * zend_verify_internal_arg_class_kind(const zend_internal_arg_info *cur_arg_info, char **class_name, zend_class_entry **pce);
ZEND_API char * zend_verify_arg_class_kind(const zend_arg_info *cur_arg_info, char **class_name, zend_class_entry **pce);
ZEND_API void zend_verify_arg_error(int error_type, const zend_function *zf, uint32_t arg_num, const char *need_msg, const char *need_kind, const char *given_msg, const char *given_kind, zval *arg);
+ZEND_API void zend_verify_return_error(int error_type, const zend_function *zf, const char *need_msg, const char *need_kind, const char *returned_msg, const char *returned_kind);
static zend_always_inline zval* zend_assign_to_variable(zval *variable_ptr, zval *value, zend_uchar value_type)
{
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c
index c140a18..e829dbb 100644
--- a/Zend/zend_inheritance.c
+++ b/Zend/zend_inheritance.c
@@ -21,6 +21,7 @@
#include "zend_API.h"
#include "zend_compile.h"
#include "zend_execute.h"
+#include "zend_inheritance.h"
#include "zend_smart_str.h"
static void ptr_dtor(zval *zv) /* {{{ */
@@ -211,6 +212,90 @@ static zend_function *do_inherit_method(zend_function *old_function, zend_class_
}
/* }}} */
+static int zend_do_perform_type_hint_check(zend_function *fe, zend_arg_info *fe_arg_info, zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */
+{
+ if (ZEND_LOG_XOR(fe_arg_info->class_name, proto_arg_info->class_name)) {
+ /* Only one has a type hint and the other one doesn't */
+ return 0;
+ }
+
+ if (fe_arg_info->class_name) {
+ zend_string *fe_class_name, *proto_class_name;
+ const char *class_name;
+
+ if (fe->type == ZEND_INTERNAL_FUNCTION) {
+ fe_class_name = NULL;
+ class_name = ((zend_internal_arg_info*)fe_arg_info)->class_name;
+ } else {
+ fe_class_name = fe_arg_info->class_name;
+ class_name = fe_arg_info->class_name->val;
+ }
+ if (!strcasecmp(class_name, "parent") && proto->common.scope) {
+ fe_class_name = zend_string_copy(proto->common.scope->name);
+ } else if (!strcasecmp(class_name, "self") && fe->common.scope) {
+ fe_class_name = zend_string_copy(fe->common.scope->name);
+ } else if (fe_class_name) {
+ zend_string_addref(fe_class_name);
+ } else {
+ fe_class_name = zend_string_init(class_name, strlen(class_name), 0);
+ }
+
+ if (proto->type == ZEND_INTERNAL_FUNCTION) {
+ proto_class_name = NULL;
+ class_name = ((zend_internal_arg_info*)proto_arg_info)->class_name;
+ } else {
+ proto_class_name = proto_arg_info->class_name;
+ class_name = proto_arg_info->class_name->val;
+ }
+ if (!strcasecmp(class_name, "parent") && proto->common.scope && proto->common.scope->parent) {
+ proto_class_name = zend_string_copy(proto->common.scope->parent->name);
+ } else if (!strcasecmp(class_name, "self") && proto->common.scope) {
+ proto_class_name = zend_string_copy(proto->common.scope->name);
+ } else if (proto_class_name) {
+ zend_string_addref(proto_class_name);
+ } else {
+ proto_class_name = zend_string_init(class_name, strlen(class_name), 0);
+ }
+
+ if (strcasecmp(fe_class_name->val, proto_class_name->val)!=0) {
+ const char *colon;
+
+ if (fe->common.type != ZEND_USER_FUNCTION) {
+ zend_string_release(proto_class_name);
+ zend_string_release(fe_class_name);
+ return 0;
+ } else if (strchr(proto_class_name->val, '\\') != NULL ||
+ (colon = zend_memrchr(fe_class_name->val, '\\', fe_class_name->len)) == NULL ||
+ strcasecmp(colon+1, proto_class_name->val) != 0) {
+ zend_class_entry *fe_ce, *proto_ce;
+
+ fe_ce = zend_lookup_class(fe_class_name);
+ proto_ce = zend_lookup_class(proto_class_name);
+
+ /* Check for class alias */
+ if (!fe_ce || !proto_ce ||
+ fe_ce->type == ZEND_INTERNAL_CLASS ||
+ proto_ce->type == ZEND_INTERNAL_CLASS ||
+ fe_ce != proto_ce) {
+ zend_string_release(proto_class_name);
+ zend_string_release(fe_class_name);
+ return 0;
+ }
+ }
+ }
+ zend_string_release(proto_class_name);
+ zend_string_release(fe_class_name);
+ }
+
+ if (fe_arg_info->type_hint != proto_arg_info->type_hint) {
+ /* Incompatible type hint */
+ return 0;
+ }
+
+ return 1;
+}
+/* }}} */
+
static zend_bool zend_do_perform_implementation_check(const zend_function *fe, const zend_function *proto) /* {{{ */
{
uint32_t i, num_args;
@@ -279,90 +364,62 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c
proto_arg_info = &proto->common.arg_info[proto->common.num_args];
}
- if (ZEND_LOG_XOR(fe_arg_info->class_name, proto_arg_info->class_name)) {
- /* Only one has a type hint and the other one doesn't */
+ if (!zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) {
return 0;
}
- if (fe_arg_info->class_name) {
- zend_string *fe_class_name, *proto_class_name;
- const char *class_name;
-
- if (fe->type == ZEND_INTERNAL_FUNCTION) {
- fe_class_name = NULL;
- class_name = ((zend_internal_arg_info*)fe_arg_info)->class_name;
- } else {
- fe_class_name = fe_arg_info->class_name;
- class_name = fe_arg_info->class_name->val;
- }
- if (!strcasecmp(class_name, "parent") && proto->common.scope) {
- fe_class_name = zend_string_copy(proto->common.scope->name);
- } else if (!strcasecmp(class_name, "self") && fe->common.scope) {
- fe_class_name = zend_string_copy(fe->common.scope->name);
- } else if (fe_class_name) {
- zend_string_addref(fe_class_name);
- } else {
- fe_class_name = zend_string_init(class_name, strlen(class_name), 0);
- }
+ /* by-ref constraints on arguments are invariant */
+ if (fe_arg_info->pass_by_reference != proto_arg_info->pass_by_reference) {
+ return 0;
+ }
+ }
- if (proto->type == ZEND_INTERNAL_FUNCTION) {
- proto_class_name = NULL;
- class_name = ((zend_internal_arg_info*)proto_arg_info)->class_name;
- } else {
- proto_class_name = proto_arg_info->class_name;
- class_name = proto_arg_info->class_name->val;
- }
- if (!strcasecmp(class_name, "parent") && proto->common.scope && proto->common.scope->parent) {
- proto_class_name = zend_string_copy(proto->common.scope->parent->name);
- } else if (!strcasecmp(class_name, "self") && proto->common.scope) {
- proto_class_name = zend_string_copy(proto->common.scope->name);
- } else if (proto_class_name) {
- zend_string_addref(proto_class_name);
- } else {
- proto_class_name = zend_string_init(class_name, strlen(class_name), 0);
- }
+ /* check return type compataibility */
+ if ((proto->common.fn_flags | fe->common.fn_flags) & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ if ((proto->common.fn_flags ^ fe->common.fn_flags) & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ return 0;
+ }
+ if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) {
+ return 0;
+ }
+ }
+ return 1;
+}
+/* }}} */
- if (strcasecmp(fe_class_name->val, proto_class_name->val)!=0) {
- const char *colon;
+static void zend_append_type_hint(smart_str *str, zend_function *fptr, zend_arg_info *arg_info, int return_hint) /* {{{ */
+{
+ if (arg_info->class_name) {
+ const char *class_name;
+ size_t class_name_len;
- if (fe->common.type != ZEND_USER_FUNCTION) {
- zend_string_release(proto_class_name);
- zend_string_release(fe_class_name);
- return 0;
- } else if (strchr(proto_class_name->val, '\\') != NULL ||
- (colon = zend_memrchr(fe_class_name->val, '\\', fe_class_name->len)) == NULL ||
- strcasecmp(colon+1, proto_class_name->val) != 0) {
- zend_class_entry *fe_ce, *proto_ce;
-
- fe_ce = zend_lookup_class(fe_class_name);
- proto_ce = zend_lookup_class(proto_class_name);
-
- /* Check for class alias */
- if (!fe_ce || !proto_ce ||
- fe_ce->type == ZEND_INTERNAL_CLASS ||
- proto_ce->type == ZEND_INTERNAL_CLASS ||
- fe_ce != proto_ce) {
- zend_string_release(proto_class_name);
- zend_string_release(fe_class_name);
- return 0;
- }
- }
- }
- zend_string_release(proto_class_name);
- zend_string_release(fe_class_name);
+ if (fptr->type == ZEND_INTERNAL_FUNCTION) {
+ class_name = ((zend_internal_arg_info*)arg_info)->class_name;
+ class_name_len = strlen(class_name);
+ } else {
+ class_name = arg_info->class_name->val;
+ class_name_len = arg_info->class_name->len;
}
- if (fe_arg_info->type_hint != proto_arg_info->type_hint) {
- /* Incompatible type hint */
- return 0;
+
+ if (!strcasecmp(class_name, "self") && fptr->common.scope) {
+ class_name = fptr->common.scope->name->val;
+ class_name_len = fptr->common.scope->name->len;
+ } else if (!strcasecmp(class_name, "parent") && fptr->common.scope->parent) {
+ class_name = fptr->common.scope->parent->name->val;
+ class_name_len = fptr->common.scope->parent->name->len;
}
- /* by-ref constraints on arguments are invariant */
- if (fe_arg_info->pass_by_reference != proto_arg_info->pass_by_reference) {
- return 0;
+ smart_str_appendl(str, class_name, class_name_len);
+ if (!return_hint) {
+ smart_str_appendc(str, ' ');
+ }
+ } else if (arg_info->type_hint) {
+ const char *type_name = zend_get_type_by_const(arg_info->type_hint);
+ smart_str_appends(str, type_name);
+ if (!return_hint) {
+ smart_str_appendc(str, ' ');
}
}
-
- return 1;
}
/* }}} */
@@ -392,33 +449,7 @@ static zend_string *zend_get_function_declaration(zend_function *fptr) /* {{{ */
num_args++;
}
for (i = 0; i < num_args;) {
- if (arg_info->class_name) {
- const char *class_name;
- size_t class_name_len;
-
- if (fptr->type == ZEND_INTERNAL_FUNCTION) {
- class_name = ((zend_internal_arg_info*)arg_info)->class_name;
- class_name_len = strlen(class_name);
- } else {
- class_name = arg_info->class_name->val;
- class_name_len = arg_info->class_name->len;
- }
-
- if (!strcasecmp(class_name, "self") && fptr->common.scope) {
- class_name = fptr->common.scope->name->val;
- class_name_len = fptr->common.scope->name->len;
- } else if (!strcasecmp(class_name, "parent") && fptr->common.scope->parent) {
- class_name = fptr->common.scope->parent->name->val;
- class_name_len = fptr->common.scope->parent->name->len;
- }
-
- smart_str_appendl(&str, class_name, class_name_len);
- smart_str_appendc(&str, ' ');
- } else if (arg_info->type_hint) {
- const char *type_name = zend_get_type_by_const(arg_info->type_hint);
- smart_str_appends(&str, type_name);
- smart_str_appendc(&str, ' ');
- }
+ zend_append_type_hint(&str, fptr, arg_info, 0);
if (arg_info->pass_by_reference) {
smart_str_appendc(&str, '&');
@@ -501,6 +532,11 @@ static zend_string *zend_get_function_declaration(zend_function *fptr) /* {{{ */
}
smart_str_appendc(&str, ')');
+
+ if (fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ smart_str_appends(&str, ": ");
+ zend_append_type_hint(&str, fptr, fptr->common.arg_info - 1, 1);
+ }
smart_str_0(&str);
return str.s;
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index da94e0e..6958d09 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -239,7 +239,6 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> variable_class_name dereferencable_scalar class_name_scalar constant dereferencable
%type <ast> callable_expr callable_variable static_member new_variable
%type <ast> assignment_list_element array_pair encaps_var encaps_var_offset isset_variables
-%type <ast> isset_variable
%type <ast> top_statement_list use_declarations const_list inner_statement_list if_stmt
%type <ast> alt_if_stmt for_exprs switch_case_list global_var_list static_var_list
%type <ast> echo_expr_list unset_variables catch_list parameter_list class_statement_list
@@ -248,7 +247,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> class_const_list name_list trait_adaptations method_body non_empty_for_exprs
%type <ast> ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars
%type <ast> lexical_var_list encaps_list array_pair_list non_empty_array_pair_list
-%type <ast> assignment_list
+%type <ast> assignment_list isset_variable type return_type
%type <num> returns_ref function is_reference is_variadic class_type variable_modifiers
%type <num> method_modifiers trait_modifiers non_empty_member_modifiers member_modifier
@@ -401,10 +400,10 @@ unset_variable:
;
function_declaration_statement:
- function returns_ref T_STRING '(' parameter_list ')' backup_doc_comment
- '{' inner_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2, $1, $7,
- zend_ast_get_str($3), $5, NULL, $9); }
+ function returns_ref T_STRING '(' parameter_list ')' return_type
+ backup_doc_comment '{' inner_statement_list '}'
+ { $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2, $1, $8,
+ zend_ast_get_str($3), $5, NULL, $10, $7); }
;
is_reference:
@@ -421,11 +420,11 @@ class_declaration_statement:
class_type { $<num>$ = CG(zend_lineno); }
T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}'
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $<num>2, $6,
- zend_ast_get_str($3), $4, $5, $8); }
+ zend_ast_get_str($3), $4, $5, $8, NULL); }
| T_INTERFACE { $<num>$ = CG(zend_lineno); }
T_STRING interface_extends_list backup_doc_comment '{' class_statement_list '}'
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $<num>2, $5,
- zend_ast_get_str($3), NULL, $4, $7); }
+ zend_ast_get_str($3), NULL, $4, $7, NULL); }
;
class_type:
@@ -552,11 +551,20 @@ parameter:
optional_type:
/* empty */ { $$ = NULL; }
- | T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); }
+ | type { $$ = $1; }
+;
+
+type:
+ T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); }
| T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); }
| name { $$ = $1; }
;
+return_type:
+ /* empty */ { $$ = NULL; }
+ | ':' type { $$ = $2; }
+;
+
argument_list:
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
| '(' non_empty_argument_list ')' { $$ = $2; }
@@ -611,10 +619,10 @@ class_statement:
{ $$ = $2; RESET_DOC_COMMENT(); }
| T_USE name_list trait_adaptations
{ $$ = zend_ast_create(ZEND_AST_USE_TRAIT, $2, $3); }
- | method_modifiers function returns_ref T_STRING '(' parameter_list ')' backup_doc_comment
- method_body
- { $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1, $2, $8,
- zend_ast_get_str($4), $6, NULL, $9); }
+ | method_modifiers function returns_ref T_STRING '(' parameter_list ')'
+ return_type backup_doc_comment method_body
+ { $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1, $2, $9,
+ zend_ast_get_str($4), $6, NULL, $10, $8); }
;
name_list:
@@ -848,16 +856,16 @@ expr_without_variable:
| T_YIELD { $$ = zend_ast_create(ZEND_AST_YIELD, NULL, NULL); }
| T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); }
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); }
- | function returns_ref '(' parameter_list ')' lexical_vars backup_doc_comment
- '{' inner_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2, $1, $7,
+ | function returns_ref '(' parameter_list ')' lexical_vars return_type
+ backup_doc_comment '{' inner_statement_list '}'
+ { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2, $1, $8,
zend_string_init("{closure}", sizeof("{closure}") - 1, 0),
- $4, $6, $9); }
- | T_STATIC function returns_ref '(' parameter_list ')' lexical_vars backup_doc_comment
- '{' inner_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $3 | ZEND_ACC_STATIC, $2, $8,
+ $4, $6, $10, $7); }
+ | T_STATIC function returns_ref '(' parameter_list ')' lexical_vars
+ return_type backup_doc_comment '{' inner_statement_list '}'
+ { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $3 | ZEND_ACC_STATIC, $2, $9,
zend_string_init("{closure}", sizeof("{closure}") - 1, 0),
- $5, $7, $10); }
+ $5, $7, $11, $8); }
;
function:
diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c
index fc29e2a..688b291 100644
--- a/Zend/zend_opcode.c
+++ b/Zend/zend_opcode.c
@@ -763,6 +763,11 @@ ZEND_API int pass_two(zend_op_array *op_array)
case ZEND_FE_FETCH:
ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, opline->op2);
break;
+ case ZEND_VERIFY_RETURN_TYPE:
+ if (op_array->fn_flags & ZEND_ACC_GENERATOR) {
+ MAKE_NOP(opline);
+ }
+ break;
case ZEND_RETURN:
case ZEND_RETURN_BY_REF:
if (op_array->fn_flags & ZEND_ACC_GENERATOR) {
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index aedd52c..f233999 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -2921,6 +2921,24 @@ ZEND_VM_C_LABEL(fcall_end):
ZEND_VM_NEXT_OPCODE();
}
+ZEND_VM_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV, UNUSED)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (OP1_TYPE == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+ zend_free_op free_op1;
+
+ retval_ptr = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R);
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
ZEND_VM_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY)
{
USE_OPLINE
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index bc5eb60..2c15d91 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -5959,6 +5959,24 @@ static int ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_UNUSED_HANDLER
ZEND_VM_NEXT_OPCODE();
}
+static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (IS_CONST == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+
+
+ retval_ptr = EX_CONSTANT(opline->op1);
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
static int ZEND_FASTCALL ZEND_ADD_ARRAY_ELEMENT_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -9924,6 +9942,24 @@ static int ZEND_FASTCALL ZEND_FETCH_DIM_FUNC_ARG_SPEC_TMP_UNUSED_HANDLER(ZEND_O
ZEND_VM_NEXT_OPCODE();
}
+static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (IS_TMP_VAR == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+ zend_free_op free_op1;
+
+ retval_ptr = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1);
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
static int ZEND_FASTCALL ZEND_ADD_ARRAY_ELEMENT_SPEC_TMP_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -14752,6 +14788,24 @@ static int ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_VAR_UNUSED_HANDLER(Z
ZEND_VM_NEXT_OPCODE();
}
+static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (IS_VAR == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+ zend_free_op free_op1;
+
+ retval_ptr = _get_zval_ptr_var_deref(opline->op1.var, execute_data, &free_op1);
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
static int ZEND_FASTCALL ZEND_ADD_ARRAY_ELEMENT_SPEC_VAR_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -20001,6 +20055,24 @@ static int ZEND_FASTCALL ZEND_ASSIGN_BW_XOR_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPC
#endif
}
+static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (IS_UNUSED == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+
+
+ retval_ptr = NULL;
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
static int ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zval *array;
@@ -28034,6 +28106,24 @@ try_assign_dim_array:
ZEND_VM_NEXT_OPCODE();
}
+static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (IS_CV == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+
+
+ retval_ptr = _get_zval_ptr_cv_deref_BP_VAR_R(execute_data, opline->op1.var);
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
static int ZEND_FASTCALL ZEND_ADD_ARRAY_ELEMENT_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -38902,27 +38992,27 @@ void zend_init_opcodes_handlers(void)
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
+ ZEND_VERIFY_RETURN_TYPE_SPEC_CONST_UNUSED_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
+ ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UNUSED_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
+ ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UNUSED_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
+ ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED_UNUSED_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
- ZEND_NULL_HANDLER,
- ZEND_NULL_HANDLER,
- ZEND_NULL_HANDLER,
- ZEND_NULL_HANDLER,
- ZEND_NULL_HANDLER,
+ ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNUSED_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c
index aaaf8df..a545630 100644
--- a/Zend/zend_vm_opcodes.c
+++ b/Zend/zend_vm_opcodes.c
@@ -146,7 +146,7 @@ const char *zend_vm_opcodes_map[170] = {
"ZEND_STRLEN",
"ZEND_DEFINED",
"ZEND_TYPE_CHECK",
- NULL,
+ "ZEND_VERIFY_RETURN_TYPE",
NULL,
NULL,
NULL,
diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h
index e31a26a..e504fe0 100644
--- a/Zend/zend_vm_opcodes.h
+++ b/Zend/zend_vm_opcodes.h
@@ -148,6 +148,7 @@ END_EXTERN_C()
#define ZEND_STRLEN 121
#define ZEND_DEFINED 122
#define ZEND_TYPE_CHECK 123
+#define ZEND_VERIFY_RETURN_TYPE 124
#define ZEND_PRE_INC_OBJ 132
#define ZEND_PRE_DEC_OBJ 133
#define ZEND_POST_INC_OBJ 134
diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c
index d9e8765..0f1fd2f 100644
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@ -414,27 +414,36 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
}
if (op_array->arg_info) {
+ zend_arg_info *arg_info = op_array->arg_info;
+ uint32_t num_args = op_array->num_args;
+
+ if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ arg_info--;
+ num_args++;
+ }
if (already_stored) {
- zend_arg_info *new_ptr = zend_shared_alloc_get_xlat_entry(op_array->arg_info);
- ZEND_ASSERT(new_ptr != NULL);
- op_array->arg_info = new_ptr;
+ arg_info = zend_shared_alloc_get_xlat_entry(arg_info);
+ ZEND_ASSERT(arg_info != NULL);
} else {
- uint32_t i, num_args;
+ uint32_t i;
- num_args = op_array->num_args;
if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
num_args++;
}
- zend_accel_store(op_array->arg_info, sizeof(zend_arg_info) * num_args);
+ zend_accel_store(arg_info, sizeof(zend_arg_info) * num_args);
for (i = 0; i < num_args; i++) {
- if (op_array->arg_info[i].name) {
- zend_accel_store_interned_string(op_array->arg_info[i].name);
+ if (arg_info[i].name) {
+ zend_accel_store_interned_string(arg_info[i].name);
}
- if (op_array->arg_info[i].class_name) {
- zend_accel_store_interned_string(op_array->arg_info[i].class_name);
+ if (arg_info[i].class_name) {
+ zend_accel_store_interned_string(arg_info[i].class_name);
}
}
}
+ if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ arg_info++;
+ }
+ op_array->arg_info = arg_info;
}
if (op_array->brk_cont_array) {
diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c
index f06a871..7c66a52 100644
--- a/ext/opcache/zend_persist_calc.c
+++ b/ext/opcache/zend_persist_calc.c
@@ -194,21 +194,27 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array)
}
if (op_array->arg_info) {
- uint32_t i, num_args;
+ zend_arg_info *arg_info = op_array->arg_info;
+ uint32_t num_args = op_array->num_args;
+ uint32_t i;
num_args = op_array->num_args;
if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
num_args++;
}
+ if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ arg_info--;
+ num_args++;
+ }
ADD_DUP_SIZE(op_array->arg_info, sizeof(zend_arg_info) * num_args);
+ ADD_DUP_SIZE(arg_info, sizeof(zend_arg_info) * num_args);
for (i = 0; i < num_args; i++) {
- if (op_array->arg_info[i].name) {
- ADD_INTERNED_STRING(op_array->arg_info[i].name, 1);
+ if (arg_info[i].name) {
+ ADD_INTERNED_STRING(arg_info[i].name, 1);
}
- if (op_array->arg_info[i].class_name) {
- ADD_INTERNED_STRING(op_array->arg_info[i].class_name, 1);
+ if (arg_info[i].class_name) {
+ ADD_INTERNED_STRING(arg_info[i].class_name, 1);
}
-
}
}
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 1cd0a51..9038dbc 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -907,6 +907,24 @@ static void _function_string(string *str, zend_function *fptr, zend_class_entry
}
_function_parameter_string(str, fptr, param_indent.buf->val);
string_free(&param_indent);
+ if (fptr->op_array.fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ string_printf(str, " %s- Return [ ", indent);
+ if (fptr->common.arg_info[-1].class_name) {
+ string_printf(str, "%s ",
+ (fptr->type == ZEND_INTERNAL_FUNCTION) ?
+ ((zend_internal_arg_info*)(fptr->common.arg_info - 1))->class_name :
+ fptr->common.arg_info[-1].class_name->val);
+ if (fptr->common.arg_info[-1].allow_null) {
+ string_printf(str, "or NULL ");
+ }
+ } else if (fptr->common.arg_info[-1].type_hint) {
+ string_printf(str, "%s ", zend_get_type_by_const(fptr->common.arg_info[-1].type_hint));
+ if (fptr->common.arg_info[-1].allow_null) {
+ string_printf(str, "or NULL ");
+ }
+ }
+ string_printf(str, "]\n");
+ }
string_printf(str, "%s}\n", indent);
}
/* }}} */
@@ -2010,6 +2028,45 @@ ZEND_METHOD(reflection_function, returnsReference)
}
/* }}} */
+/* {{{ proto public bool ReflectionFunction::hasReturnType()
+ Returns whether this function has a return type */
+ZEND_METHOD(reflection_function, hasReturnType)
+{
+ reflection_object *intern;
+ zend_function *func;
+
+ GET_REFLECTION_OBJECT_PTR(func);
+
+ RETURN_BOOL(func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT);
+}
+/* }}} */
+
+/* {{{ proto public string ReflectionFunction::getReturnType()
+ Gets the return type as a string; "" if there is not a return type */
+ZEND_METHOD(reflection_function, getReturnType)
+{
+ reflection_object *intern;
+ zend_function *func;
+
+ GET_REFLECTION_OBJECT_PTR(func);
+
+ if (func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE_HINT) {
+ zend_arg_info *ret_info = func->common.arg_info - 1;
+
+ if (ret_info->class_name) {
+ if (func->type == ZEND_INTERNAL_FUNCTION) {
+ RETURN_STRING(((zend_internal_arg_info*)ret_info)->class_name);
+ } else {
+ RETURN_STR(zend_string_copy(ret_info->class_name));
+ }
+ } else if (ret_info->type_hint) {
+ RETURN_STRING(zend_get_type_by_const(ret_info->type_hint));
+ }
+ }
+ RETURN_EMPTY_STRING();
+}
+/* }}} */
+
/* {{{ proto public bool ReflectionFunction::getNumberOfParameters()
Gets the number of required parameters */
ZEND_METHOD(reflection_function, getNumberOfParameters)
@@ -5778,6 +5835,8 @@ static const zend_function_entry reflection_function_abstract_functions[] = {
ZEND_ME(reflection_function, getShortName, arginfo_reflection__void, 0)
ZEND_ME(reflection_function, getStartLine, arginfo_reflection__void, 0)
ZEND_ME(reflection_function, getStaticVariables, arginfo_reflection__void, 0)
+ ZEND_ME(reflection_function, hasReturnType, arginfo_reflection__void, 0)
+ ZEND_ME(reflection_function, getReturnType, arginfo_reflection__void, 0)
ZEND_ME(reflection_function, returnsReference, arginfo_reflection__void, 0)
PHP_FE_END
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment