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, null, $selfClass, false), $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, null, $selfClass, false);
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: } elseif ($phpDocType !== null) {
72: $phpDocType = TypeCombinator::removeNull($phpDocType);
73: }
74:
75: return self::decideType($type, $phpDocType);
76: }
77:
78: public static function decideType(
79: Type $type,
80: ?Type $phpDocType = null,
81: ): Type
82: {
83: if ($type instanceof BenevolentUnionType) {
84: return $type;
85: }
86:
87: if ($phpDocType !== null && !$phpDocType instanceof ErrorType) {
88: if ($phpDocType instanceof NeverType && $phpDocType->isExplicit()) {
89: return $phpDocType;
90: }
91: if (
92: $type instanceof MixedType
93: && !$type->isExplicitMixed()
94: && $phpDocType->isVoid()->yes()
95: ) {
96: return $phpDocType;
97: }
98:
99: if (TypeCombinator::removeNull($type) instanceof IterableType) {
100: if ($phpDocType instanceof UnionType) {
101: $innerTypes = [];
102: foreach ($phpDocType->getTypes() as $innerType) {
103: if ($innerType instanceof ArrayType || $innerType instanceof ConstantArrayType) {
104: $innerTypes[] = new IterableType(
105: $innerType->getIterableKeyType(),
106: $innerType->getItemType(),
107: );
108: } else {
109: $innerTypes[] = $innerType;
110: }
111: }
112: $phpDocType = new UnionType($innerTypes);
113: } elseif ($phpDocType instanceof ArrayType || $phpDocType instanceof ConstantArrayType) {
114: $phpDocType = new IterableType(
115: $phpDocType->getKeyType(),
116: $phpDocType->getItemType(),
117: );
118: }
119: }
120:
121: if (
122: (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed()))
123: && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes()
124: ) {
125: $resultType = $phpDocType;
126: } else {
127: $resultType = $type;
128: }
129:
130: if ($type instanceof UnionType) {
131: $addToUnionTypes = [];
132: foreach ($type->getTypes() as $innerType) {
133: if (!$innerType->isSuperTypeOf($resultType)->no()) {
134: continue;
135: }
136:
137: $addToUnionTypes[] = $innerType;
138: }
139:
140: if (count($addToUnionTypes) > 0) {
141: $type = TypeCombinator::union($resultType, ...$addToUnionTypes);
142: } else {
143: $type = $resultType;
144: }
145: } elseif (TypeCombinator::containsNull($type)) {
146: $type = TypeCombinator::addNull($resultType);
147: } else {
148: $type = $resultType;
149: }
150: }
151:
152: return $type;
153: }
154:
155: }
156: