1<?php declare(strict_types=1); 2/* 3 * This file is part of sebastian/type. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10namespace SebastianBergmann\Type; 11 12use function assert; 13use function sprintf; 14use ReflectionMethod; 15use ReflectionNamedType; 16use ReflectionType; 17use ReflectionUnionType; 18 19final class ReflectionMapper 20{ 21 public function fromMethodReturnType(ReflectionMethod $method): Type 22 { 23 if (!$this->reflectionMethodHasReturnType($method)) { 24 return new UnknownType; 25 } 26 27 $returnType = $this->reflectionMethodGetReturnType($method); 28 29 assert($returnType instanceof ReflectionNamedType || $returnType instanceof ReflectionUnionType); 30 31 if ($returnType instanceof ReflectionNamedType) { 32 if ($returnType->getName() === 'self') { 33 return ObjectType::fromName( 34 $method->getDeclaringClass()->getName(), 35 $returnType->allowsNull() 36 ); 37 } 38 39 if ($returnType->getName() === 'static') { 40 return new StaticType( 41 TypeName::fromReflection($method->getDeclaringClass()), 42 $returnType->allowsNull() 43 ); 44 } 45 46 if ($returnType->getName() === 'mixed') { 47 return new MixedType; 48 } 49 50 if ($returnType->getName() === 'parent') { 51 $parentClass = $method->getDeclaringClass()->getParentClass(); 52 53 // @codeCoverageIgnoreStart 54 if ($parentClass === false) { 55 throw new RuntimeException( 56 sprintf( 57 '%s::%s() has a "parent" return type declaration but %s does not have a parent class', 58 $method->getDeclaringClass()->getName(), 59 $method->getName(), 60 $method->getDeclaringClass()->getName() 61 ) 62 ); 63 } 64 // @codeCoverageIgnoreEnd 65 66 return ObjectType::fromName( 67 $parentClass->getName(), 68 $returnType->allowsNull() 69 ); 70 } 71 72 return Type::fromName( 73 $returnType->getName(), 74 $returnType->allowsNull() 75 ); 76 } 77 78 assert($returnType instanceof ReflectionUnionType); 79 80 $types = []; 81 82 foreach ($returnType->getTypes() as $type) { 83 assert($type instanceof ReflectionNamedType); 84 85 if ($type->getName() === 'self') { 86 $types[] = ObjectType::fromName( 87 $method->getDeclaringClass()->getName(), 88 false 89 ); 90 } else { 91 $types[] = Type::fromName($type->getName(), false); 92 } 93 } 94 95 return new UnionType(...$types); 96 } 97 98 private function reflectionMethodHasReturnType(ReflectionMethod $method): bool 99 { 100 if ($method->hasReturnType()) { 101 return true; 102 } 103 104 if (!method_exists($method, 'hasTentativeReturnType')) { 105 return false; 106 } 107 108 return $method->hasTentativeReturnType(); 109 } 110 111 private function reflectionMethodGetReturnType(ReflectionMethod $method): ?ReflectionType 112 { 113 if ($method->hasReturnType()) { 114 return $method->getReturnType(); 115 } 116 117 if (!method_exists($method, 'getTentativeReturnType')) { 118 return null; 119 } 120 121 return $method->getTentativeReturnType(); 122 } 123} 124