|
|
@@ -23,6 +23,7 @@ use PHPUnit\Framework\SelfDescribing;
|
|
|
use PHPUnit\Util\Type;
|
|
|
use SebastianBergmann\Exporter\Exporter;
|
|
|
use stdClass;
|
|
|
+use Throwable;
|
|
|
|
|
|
/**
|
|
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
|
|
@@ -117,68 +118,146 @@ final class Invocation implements SelfDescribing
|
|
|
public function generateReturnValue()
|
|
|
{
|
|
|
if ($this->isReturnTypeNullable || $this->proxiedCall) {
|
|
|
- return;
|
|
|
+ return null;
|
|
|
}
|
|
|
|
|
|
- $returnType = $this->returnType;
|
|
|
+ $intersection = false;
|
|
|
+ $union = false;
|
|
|
+
|
|
|
+ if (strpos($this->returnType, '|') !== false) {
|
|
|
+ $types = explode('|', $this->returnType);
|
|
|
+ $union = true;
|
|
|
+ } elseif (strpos($this->returnType, '&') !== false) {
|
|
|
+ $types = explode('&', $this->returnType);
|
|
|
+ $intersection = true;
|
|
|
+ } else {
|
|
|
+ $types = [$this->returnType];
|
|
|
+ }
|
|
|
|
|
|
- if (strpos($returnType, '|') !== false) {
|
|
|
- $types = explode('|', $returnType);
|
|
|
- $returnType = $types[0];
|
|
|
+ $types = array_map('strtolower', $types);
|
|
|
|
|
|
- foreach ($types as $type) {
|
|
|
- if ($type === 'null') {
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (!$intersection) {
|
|
|
+ if (in_array('', $types, true) ||
|
|
|
+ in_array('null', $types, true) ||
|
|
|
+ in_array('mixed', $types, true) ||
|
|
|
+ in_array('void', $types, true)) {
|
|
|
+ return null;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- switch (strtolower($returnType)) {
|
|
|
- case '':
|
|
|
- case 'void':
|
|
|
- return;
|
|
|
|
|
|
- case 'string':
|
|
|
- return '';
|
|
|
+ if (in_array('false', $types, true) ||
|
|
|
+ in_array('bool', $types, true)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
- case 'float':
|
|
|
+ if (in_array('float', $types, true)) {
|
|
|
return 0.0;
|
|
|
+ }
|
|
|
|
|
|
- case 'int':
|
|
|
+ if (in_array('int', $types, true)) {
|
|
|
return 0;
|
|
|
+ }
|
|
|
|
|
|
- case 'bool':
|
|
|
- return false;
|
|
|
+ if (in_array('string', $types, true)) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
|
|
|
- case 'array':
|
|
|
+ if (in_array('array', $types, true)) {
|
|
|
return [];
|
|
|
+ }
|
|
|
|
|
|
- case 'static':
|
|
|
- return (new Instantiator)->instantiate(get_class($this->object));
|
|
|
+ if (in_array('static', $types, true)) {
|
|
|
+ try {
|
|
|
+ return (new Instantiator)->instantiate(get_class($this->object));
|
|
|
+ } catch (Throwable $t) {
|
|
|
+ throw new RuntimeException(
|
|
|
+ $t->getMessage(),
|
|
|
+ (int) $t->getCode(),
|
|
|
+ $t
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- case 'object':
|
|
|
+ if (in_array('object', $types, true)) {
|
|
|
return new stdClass;
|
|
|
+ }
|
|
|
|
|
|
- case 'callable':
|
|
|
- case 'closure':
|
|
|
- return static function (): void {
|
|
|
+ if (in_array('callable', $types, true) ||
|
|
|
+ in_array('closure', $types, true)) {
|
|
|
+ return static function (): void
|
|
|
+ {
|
|
|
};
|
|
|
+ }
|
|
|
|
|
|
- case 'traversable':
|
|
|
- case 'generator':
|
|
|
- case 'iterable':
|
|
|
- $generator = static function (): \Generator {
|
|
|
- yield;
|
|
|
+ if (in_array('traversable', $types, true) ||
|
|
|
+ in_array('generator', $types, true) ||
|
|
|
+ in_array('iterable', $types, true)) {
|
|
|
+ $generator = static function (): \Generator
|
|
|
+ {
|
|
|
+ yield from [];
|
|
|
};
|
|
|
|
|
|
return $generator();
|
|
|
+ }
|
|
|
|
|
|
- case 'mixed':
|
|
|
- return null;
|
|
|
+ if (!$union) {
|
|
|
+ try {
|
|
|
+ return (new Generator)->getMock($this->returnType, [], [], '', false);
|
|
|
+ } catch (Throwable $t) {
|
|
|
+ if ($t instanceof Exception) {
|
|
|
+ throw $t;
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new RuntimeException(
|
|
|
+ $t->getMessage(),
|
|
|
+ (int) $t->getCode(),
|
|
|
+ $t
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $reason = '';
|
|
|
+
|
|
|
+ if ($union) {
|
|
|
+ $reason = ' because the declared return type is a union';
|
|
|
+ } elseif ($intersection) {
|
|
|
+ $reason = ' because the declared return type is an intersection';
|
|
|
+
|
|
|
+ $onlyInterfaces = true;
|
|
|
|
|
|
- default:
|
|
|
- return (new Generator)->getMock($this->returnType, [], [], '', false);
|
|
|
+ foreach ($types as $type) {
|
|
|
+ if (!interface_exists($type)) {
|
|
|
+ $onlyInterfaces = false;
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($onlyInterfaces) {
|
|
|
+ try {
|
|
|
+ return (new Generator)->getMockForInterfaces($types);
|
|
|
+ } catch (Throwable $t) {
|
|
|
+ throw new RuntimeException(
|
|
|
+ sprintf(
|
|
|
+ 'Return value for %s::%s() cannot be generated: %s',
|
|
|
+ $this->className,
|
|
|
+ $this->methodName,
|
|
|
+ $t->getMessage(),
|
|
|
+ ),
|
|
|
+ (int) $t->getCode(),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ throw new RuntimeException(
|
|
|
+ sprintf(
|
|
|
+ 'Return value for %s::%s() cannot be generated%s, please configure a return value for this method',
|
|
|
+ $this->className,
|
|
|
+ $this->methodName,
|
|
|
+ $reason
|
|
|
+ )
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
public function toString(): string
|