Last active
August 29, 2015 14:08
-
-
Save dstogov/8deb8b17e41c1a5abf88 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(¶m_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