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 ($phpDocType instanceof ClassConstantAccessType && !$phpDocType->isResolvable()) {
95: // static::FOO_* or T::* - which constants it stands for is known only for the class the
96: // method is called on, and cannot be compared with the native type before that
97: return $phpDocType->withNativeType($type);
98: }
99: if (
100: $type instanceof MixedType
101: && !$type->isExplicitMixed()
102: && $phpDocType->isVoid()->yes()
103: ) {
104: return $phpDocType;
105: }
106:
107: if (TypeCombinator::removeNull($type) instanceof IterableType) {
108: if ($phpDocType instanceof UnionType) {
109: $innerTypes = [];
110: foreach ($phpDocType->getTypes() as $innerType) {
111: if ($innerType instanceof ArrayType && $innerType->getKeyType()->describe(VerbosityLevel::typeOnly()) === 'mixed') {
112: $innerTypes[] = new IterableType(
113: $innerType->getIterableKeyType(),
114: $innerType->getItemType(),
115: );
116: } else {
117: $innerTypes[] = $innerType;
118: }
119: }
120: $phpDocType = new UnionType($innerTypes);
121: } elseif ($phpDocType instanceof ArrayType && $phpDocType->getKeyType()->describe(VerbosityLevel::typeOnly()) === 'mixed') {
122: $phpDocType = new IterableType(
123: $phpDocType->getKeyType(),
124: $phpDocType->getItemType(),
125: );
126: }
127: }
128:
129: if (
130: ($type->isCallable()->yes() && $phpDocType->isCallable()->yes())
131: || (
132: (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed()))
133: && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes()
134: )
135: ) {
136: $resultType = $phpDocType;
137: } else {
138: $resultType = $type;
139: }
140:
141: if ($type instanceof UnionType) {
142: $addToUnionTypes = [];
143: foreach ($type->getTypes() as $innerType) {
144: if (!$innerType->isSuperTypeOf($resultType)->no()) {
145: continue;
146: }
147:
148: $addToUnionTypes[] = $innerType;
149: }
150:
151: if (count($addToUnionTypes) > 0) {
152: $type = TypeCombinator::union($resultType, ...$addToUnionTypes);
153: } else {
154: $type = $resultType;
155: }
156: } elseif (TypeCombinator::containsNull($type)) {
157: $type = TypeCombinator::addNull($resultType);
158: } else {
159: $type = $resultType;
160: }
161: }
162:
163: return $type;
164: }
165:
166: }
167: