1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PhpParser\Node\Identifier;
6: use PhpParser\Node\Name\FullyQualified;
7: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType;
8: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType;
9: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType;
10: use PHPStan\Reflection\ClassReflection;
11: use PHPStan\ShouldNotHappenException;
12: use PHPStan\Type\Constant\ConstantArrayType;
13: use PHPStan\Type\Generic\TemplateTypeHelper;
14: use ReflectionType;
15: use function array_map;
16: use function count;
17: use function get_class;
18: use function sprintf;
19:
20: final class TypehintHelper
21: {
22:
23: /** @api */
24: public static function decideTypeFromReflection(
25: ?ReflectionType $reflectionType,
26: ?Type $phpDocType = null,
27: ClassReflection|null $selfClass = null,
28: bool $isVariadic = false,
29: ): Type
30: {
31: if ($reflectionType === null) {
32: if ($isVariadic && ($phpDocType instanceof ArrayType || $phpDocType instanceof ConstantArrayType)) {
33: $phpDocType = $phpDocType->getItemType();
34: }
35: return $phpDocType ?? new MixedType();
36: }
37:
38: if ($reflectionType instanceof ReflectionUnionType) {
39: $type = TypeCombinator::union(...array_map(static fn (ReflectionType $type): Type => self::decideTypeFromReflection($type, selfClass: $selfClass), $reflectionType->getTypes()));
40:
41: return self::decideType($type, $phpDocType);
42: }
43:
44: if ($reflectionType instanceof ReflectionIntersectionType) {
45: $types = [];
46: foreach ($reflectionType->getTypes() as $innerReflectionType) {
47: $innerType = self::decideTypeFromReflection($innerReflectionType, selfClass: $selfClass);
48: if (!$innerType->isObject()->yes()) {
49: return new NeverType();
50: }
51:
52: $types[] = $innerType;
53: }
54:
55: return self::decideType(TypeCombinator::intersect(...$types), $phpDocType);
56: }
57:
58: if (!$reflectionType instanceof ReflectionNamedType) {
59: throw new ShouldNotHappenException(sprintf('Unexpected type: %s', get_class($reflectionType)));
60: }
61:
62: if ($reflectionType->isIdentifier()) {
63: $typeNode = new Identifier($reflectionType->getName());
64: } else {
65: $typeNode = new FullyQualified($reflectionType->getName());
66: }
67:
68: $type = ParserNodeTypeToPHPStanType::resolve($typeNode, $selfClass);
69: if ($reflectionType->allowsNull()) {
70: $type = TypeCombinator::addNull($type);
71: }
72:
73: return self::decideType($type, $phpDocType);
74: }
75:
76: public static function decideType(
77: Type $type,
78: ?Type $phpDocType,
79: ): Type
80: {
81: if ($phpDocType !== null && $type->isNull()->no()) {
82: $phpDocType = TypeCombinator::removeNull($phpDocType);
83: }
84: if ($type instanceof BenevolentUnionType) {
85: return $type;
86: }
87:
88: if ($phpDocType !== null && !$phpDocType instanceof ErrorType) {
89: if ($phpDocType instanceof NeverType && $phpDocType->isExplicit()) {
90: return $phpDocType;
91: }
92: if (
93: $type instanceof MixedType
94: && !$type->isExplicitMixed()
95: && $phpDocType->isVoid()->yes()
96: ) {
97: return $phpDocType;
98: }
99:
100: if (TypeCombinator::removeNull($type) instanceof IterableType) {
101: if ($phpDocType instanceof UnionType) {
102: $innerTypes = [];
103: foreach ($phpDocType->getTypes() as $innerType) {
104: if ($innerType instanceof ArrayType || $innerType instanceof ConstantArrayType) {
105: $innerTypes[] = new IterableType(
106: $innerType->getIterableKeyType(),
107: $innerType->getItemType(),
108: );
109: } else {
110: $innerTypes[] = $innerType;
111: }
112: }
113: $phpDocType = new UnionType($innerTypes);
114: } elseif ($phpDocType instanceof ArrayType || $phpDocType instanceof ConstantArrayType) {
115: $phpDocType = new IterableType(
116: $phpDocType->getKeyType(),
117: $phpDocType->getItemType(),
118: );
119: }
120: }
121:
122: if (
123: ($type->isCallable()->yes() && $phpDocType->isCallable()->yes())
124: || (
125: (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed()))
126: && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes()
127: )
128: ) {
129: $resultType = $phpDocType;
130: } else {
131: $resultType = $type;
132: }
133:
134: if ($type instanceof UnionType) {
135: $addToUnionTypes = [];
136: foreach ($type->getTypes() as $innerType) {
137: if (!$innerType->isSuperTypeOf($resultType)->no()) {
138: continue;
139: }
140:
141: $addToUnionTypes[] = $innerType;
142: }
143:
144: if (count($addToUnionTypes) > 0) {
145: $type = TypeCombinator::union($resultType, ...$addToUnionTypes);
146: } else {
147: $type = $resultType;
148: }
149: } elseif (TypeCombinator::containsNull($type)) {
150: $type = TypeCombinator::addNull($resultType);
151: } else {
152: $type = $resultType;
153: }
154: }
155:
156: return $type;
157: }
158:
159: }
160:
OSZAR »