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