1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection;
4:
5: use PhpParser\Node\Arg;
6: use PhpParser\Node\Expr;
7: use PhpParser\Node\Expr\BinaryOp;
8: use PhpParser\Node\Expr\ClassConstFetch;
9: use PhpParser\Node\Expr\ConstFetch;
10: use PhpParser\Node\Expr\PropertyFetch;
11: use PhpParser\Node\Identifier;
12: use PhpParser\Node\Name;
13: use PhpParser\Node\Scalar\DNumber;
14: use PhpParser\Node\Scalar\LNumber;
15: use PhpParser\Node\Scalar\MagicConst;
16: use PhpParser\Node\Scalar\MagicConst\Dir;
17: use PhpParser\Node\Scalar\MagicConst\File;
18: use PhpParser\Node\Scalar\MagicConst\Line;
19: use PhpParser\Node\Scalar\String_;
20: use PHPStan\Analyser\ConstantResolver;
21: use PHPStan\Analyser\OutOfClassScope;
22: use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
23: use PHPStan\Node\Expr\TypeExpr;
24: use PHPStan\Php\PhpVersion;
25: use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
26: use PHPStan\ShouldNotHappenException;
27: use PHPStan\Type\Accessory\AccessoryArrayListType;
28: use PHPStan\Type\Accessory\AccessoryLiteralStringType;
29: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
30: use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
31: use PHPStan\Type\Accessory\NonEmptyArrayType;
32: use PHPStan\Type\ArrayType;
33: use PHPStan\Type\BenevolentUnionType;
34: use PHPStan\Type\BooleanType;
35: use PHPStan\Type\ClassStringType;
36: use PHPStan\Type\Constant\ConstantArrayType;
37: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
38: use PHPStan\Type\Constant\ConstantBooleanType;
39: use PHPStan\Type\Constant\ConstantFloatType;
40: use PHPStan\Type\Constant\ConstantIntegerType;
41: use PHPStan\Type\Constant\ConstantStringType;
42: use PHPStan\Type\Constant\OversizedArrayBuilder;
43: use PHPStan\Type\ConstantScalarType;
44: use PHPStan\Type\ConstantTypeHelper;
45: use PHPStan\Type\Enum\EnumCaseObjectType;
46: use PHPStan\Type\ErrorType;
47: use PHPStan\Type\FloatType;
48: use PHPStan\Type\GeneralizePrecision;
49: use PHPStan\Type\Generic\GenericClassStringType;
50: use PHPStan\Type\Generic\TemplateType;
51: use PHPStan\Type\IntegerRangeType;
52: use PHPStan\Type\IntegerType;
53: use PHPStan\Type\IntersectionType;
54: use PHPStan\Type\MixedType;
55: use PHPStan\Type\NeverType;
56: use PHPStan\Type\NullType;
57: use PHPStan\Type\ObjectType;
58: use PHPStan\Type\ObjectWithoutClassType;
59: use PHPStan\Type\StaticType;
60: use PHPStan\Type\StringType;
61: use PHPStan\Type\ThisType;
62: use PHPStan\Type\Type;
63: use PHPStan\Type\TypeCombinator;
64: use PHPStan\Type\TypeTraverser;
65: use PHPStan\Type\TypeUtils;
66: use PHPStan\Type\TypeWithClassName;
67: use PHPStan\Type\UnionType;
68: use function array_keys;
69: use function array_merge;
70: use function assert;
71: use function ceil;
72: use function count;
73: use function dirname;
74: use function floor;
75: use function in_array;
76: use function is_finite;
77: use function is_float;
78: use function is_int;
79: use function max;
80: use function min;
81: use function sprintf;
82: use function strtolower;
83: use const INF;
84:
85: class InitializerExprTypeResolver
86: {
87:
88: public const CALCULATE_SCALARS_LIMIT = 128;
89:
90: public function __construct(
91: private ConstantResolver $constantResolver,
92: private ReflectionProviderProvider $reflectionProviderProvider,
93: private PhpVersion $phpVersion,
94: private OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider,
95: private OversizedArrayBuilder $oversizedArrayBuilder,
96: private bool $usePathConstantsAsConstantString = false,
97: )
98: {
99: }
100:
101: /** @api */
102: public function getType(Expr $expr, InitializerExprContext $context): Type
103: {
104: if ($expr instanceof TypeExpr) {
105: return $expr->getExprType();
106: }
107: if ($expr instanceof LNumber) {
108: return new ConstantIntegerType($expr->value);
109: }
110: if ($expr instanceof DNumber) {
111: return new ConstantFloatType($expr->value);
112: }
113: if ($expr instanceof String_) {
114: return new ConstantStringType($expr->value);
115: }
116: if ($expr instanceof ConstFetch) {
117: $constName = (string) $expr->name;
118: $loweredConstName = strtolower($constName);
119: if ($loweredConstName === 'true') {
120: return new ConstantBooleanType(true);
121: } elseif ($loweredConstName === 'false') {
122: return new ConstantBooleanType(false);
123: } elseif ($loweredConstName === 'null') {
124: return new NullType();
125: }
126:
127: $constant = $this->constantResolver->resolveConstant($expr->name, $context);
128: if ($constant !== null) {
129: return $constant;
130: }
131:
132: return new ErrorType();
133: }
134: if ($expr instanceof File) {
135: $file = $context->getFile();
136: if ($file === null) {
137: return new StringType();
138: }
139: $stringType = new ConstantStringType($file);
140: return $this->usePathConstantsAsConstantString ? $stringType : $stringType->generalize(GeneralizePrecision::moreSpecific());
141: }
142: if ($expr instanceof Dir) {
143: $file = $context->getFile();
144: if ($file === null) {
145: return new StringType();
146: }
147: $stringType = new ConstantStringType(dirname($file));
148: return $this->usePathConstantsAsConstantString ? $stringType : $stringType->generalize(GeneralizePrecision::moreSpecific());
149: }
150: if ($expr instanceof Line) {
151: return new ConstantIntegerType($expr->getLine());
152: }
153: if ($expr instanceof Expr\New_) {
154: if ($expr->class instanceof Name) {
155: return new ObjectType((string) $expr->class);
156: }
157:
158: return new ObjectWithoutClassType();
159: }
160: if ($expr instanceof Expr\Array_) {
161: return $this->getArrayType($expr, fn (Expr $expr): Type => $this->getType($expr, $context));
162: }
163: if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
164: $var = $this->getType($expr->var, $context);
165: $dim = $this->getType($expr->dim, $context);
166: return $var->getOffsetValueType($dim);
167: }
168: if ($expr instanceof ClassConstFetch && $expr->name instanceof Identifier) {
169: return $this->getClassConstFetchType($expr->class, $expr->name->toString(), $context->getClassName(), fn (Expr $expr): Type => $this->getType($expr, $context));
170: }
171: if ($expr instanceof Expr\UnaryPlus) {
172: return $this->getType($expr->expr, $context)->toNumber();
173: }
174: if ($expr instanceof Expr\UnaryMinus) {
175: return $this->getUnaryMinusType($expr->expr, fn (Expr $expr): Type => $this->getType($expr, $context));
176: }
177: if ($expr instanceof Expr\BinaryOp\Coalesce) {
178: $leftType = $this->getType($expr->left, $context);
179: $rightType = $this->getType($expr->right, $context);
180:
181: return TypeCombinator::union(TypeCombinator::removeNull($leftType), $rightType);
182: }
183:
184: if ($expr instanceof Expr\Ternary) {
185: $condType = $this->getType($expr->cond, $context);
186: $elseType = $this->getType($expr->else, $context);
187: if ($expr->if === null) {
188: return TypeCombinator::union(
189: TypeCombinator::removeFalsey($condType),
190: $elseType,
191: );
192: }
193:
194: $ifType = $this->getType($expr->if, $context);
195:
196: return TypeCombinator::union(
197: TypeCombinator::removeFalsey($ifType),
198: $elseType,
199: );
200: }
201:
202: if ($expr instanceof Expr\FuncCall && $expr->name instanceof Name && $expr->name->toLowerString() === 'constant') {
203: $firstArg = $expr->args[0] ?? null;
204: if ($firstArg instanceof Arg && $firstArg->value instanceof String_) {
205: $constant = $this->constantResolver->resolvePredefinedConstant($firstArg->value->value);
206: if ($constant !== null) {
207: return $constant;
208: }
209: }
210: }
211:
212: if ($expr instanceof Expr\BooleanNot) {
213: $exprBooleanType = $this->getType($expr->expr, $context)->toBoolean();
214:
215: if ($exprBooleanType instanceof ConstantBooleanType) {
216: return new ConstantBooleanType(!$exprBooleanType->getValue());
217: }
218:
219: return new BooleanType();
220: }
221:
222: if ($expr instanceof Expr\BitwiseNot) {
223: return $this->getBitwiseNotType($expr->expr, fn (Expr $expr): Type => $this->getType($expr, $context));
224: }
225:
226: if ($expr instanceof Expr\BinaryOp\Concat) {
227: return $this->getConcatType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
228: }
229:
230: if ($expr instanceof Expr\BinaryOp\BitwiseAnd) {
231: return $this->getBitwiseAndType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
232: }
233:
234: if ($expr instanceof Expr\BinaryOp\BitwiseOr) {
235: return $this->getBitwiseOrType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
236: }
237:
238: if ($expr instanceof Expr\BinaryOp\BitwiseXor) {
239: return $this->getBitwiseXorType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
240: }
241:
242: if ($expr instanceof Expr\BinaryOp\Spaceship) {
243: return $this->getSpaceshipType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
244: }
245:
246: if (
247: $expr instanceof Expr\BinaryOp\BooleanAnd
248: || $expr instanceof Expr\BinaryOp\LogicalAnd
249: || $expr instanceof Expr\BinaryOp\BooleanOr
250: || $expr instanceof Expr\BinaryOp\LogicalOr
251: ) {
252: return new BooleanType();
253: }
254:
255: if ($expr instanceof Expr\BinaryOp\Div) {
256: return $this->getDivType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
257: }
258:
259: if ($expr instanceof Expr\BinaryOp\Mod) {
260: return $this->getModType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
261: }
262:
263: if ($expr instanceof Expr\BinaryOp\Plus) {
264: return $this->getPlusType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
265: }
266:
267: if ($expr instanceof Expr\BinaryOp\Minus) {
268: return $this->getMinusType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
269: }
270:
271: if ($expr instanceof Expr\BinaryOp\Mul) {
272: return $this->getMulType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
273: }
274:
275: if ($expr instanceof Expr\BinaryOp\Pow) {
276: return $this->getPowType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
277: }
278:
279: if ($expr instanceof Expr\BinaryOp\ShiftLeft) {
280: return $this->getShiftLeftType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
281: }
282:
283: if ($expr instanceof Expr\BinaryOp\ShiftRight) {
284: return $this->getShiftRightType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
285: }
286:
287: if ($expr instanceof BinaryOp\Identical) {
288: return $this->resolveIdenticalType(
289: $this->getType($expr->left, $context),
290: $this->getType($expr->right, $context),
291: );
292: }
293:
294: if ($expr instanceof BinaryOp\NotIdentical) {
295: return $this->getType(new Expr\BooleanNot(new BinaryOp\Identical($expr->left, $expr->right)), $context);
296: }
297:
298: if ($expr instanceof BinaryOp\Equal) {
299: return $this->resolveEqualType(
300: $this->getType($expr->left, $context),
301: $this->getType($expr->right, $context),
302: );
303: }
304:
305: if ($expr instanceof BinaryOp\NotEqual) {
306: return $this->getType(new Expr\BooleanNot(new BinaryOp\Equal($expr->left, $expr->right)), $context);
307: }
308:
309: if ($expr instanceof Expr\BinaryOp\Smaller) {
310: return $this->getType($expr->left, $context)->isSmallerThan($this->getType($expr->right, $context))->toBooleanType();
311: }
312:
313: if ($expr instanceof Expr\BinaryOp\SmallerOrEqual) {
314: return $this->getType($expr->left, $context)->isSmallerThanOrEqual($this->getType($expr->right, $context))->toBooleanType();
315: }
316:
317: if ($expr instanceof Expr\BinaryOp\Greater) {
318: return $this->getType($expr->right, $context)->isSmallerThan($this->getType($expr->left, $context))->toBooleanType();
319: }
320:
321: if ($expr instanceof Expr\BinaryOp\GreaterOrEqual) {
322: return $this->getType($expr->right, $context)->isSmallerThanOrEqual($this->getType($expr->left, $context))->toBooleanType();
323: }
324:
325: if ($expr instanceof Expr\BinaryOp\LogicalXor) {
326: $leftBooleanType = $this->getType($expr->left, $context)->toBoolean();
327: $rightBooleanType = $this->getType($expr->right, $context)->toBoolean();
328:
329: if (
330: $leftBooleanType instanceof ConstantBooleanType
331: && $rightBooleanType instanceof ConstantBooleanType
332: ) {
333: return new ConstantBooleanType(
334: $leftBooleanType->getValue() xor $rightBooleanType->getValue(),
335: );
336: }
337:
338: return new BooleanType();
339: }
340:
341: if ($expr instanceof MagicConst\Class_) {
342: if ($context->getTraitName() !== null) {
343: return TypeCombinator::intersect(
344: new ClassStringType(),
345: new AccessoryLiteralStringType(),
346: );
347: }
348:
349: if ($context->getClassName() === null) {
350: return new ConstantStringType('');
351: }
352:
353: return new ConstantStringType($context->getClassName(), true);
354: }
355:
356: if ($expr instanceof MagicConst\Namespace_) {
357: if ($context->getTraitName() !== null) {
358: return TypeCombinator::intersect(
359: new StringType(),
360: new AccessoryLiteralStringType(),
361: );
362: }
363:
364: return new ConstantStringType($context->getNamespace() ?? '');
365: }
366:
367: if ($expr instanceof MagicConst\Method) {
368: return new ConstantStringType($context->getMethod() ?? '');
369: }
370:
371: if ($expr instanceof MagicConst\Function_) {
372: return new ConstantStringType($context->getFunction() ?? '');
373: }
374:
375: if ($expr instanceof MagicConst\Trait_) {
376: if ($context->getTraitName() === null) {
377: return new ConstantStringType('');
378: }
379:
380: return new ConstantStringType($context->getTraitName(), true);
381: }
382:
383: if ($expr instanceof PropertyFetch && $expr->name instanceof Identifier) {
384: $fetchedOnType = $this->getType($expr->var, $context);
385: if (!$fetchedOnType->hasProperty($expr->name->name)->yes()) {
386: return new ErrorType();
387: }
388:
389: return $fetchedOnType->getProperty($expr->name->name, new OutOfClassScope())->getReadableType();
390: }
391:
392: return new MixedType();
393: }
394:
395: /**
396: * @param callable(Expr): Type $getTypeCallback
397: */
398: public function getConcatType(Expr $left, Expr $right, callable $getTypeCallback): Type
399: {
400: $leftType = $getTypeCallback($left);
401: $rightType = $getTypeCallback($right);
402:
403: return $this->resolveConcatType($leftType, $rightType);
404: }
405:
406: public function resolveConcatType(Type $left, Type $right): Type
407: {
408: $leftStringType = $left->toString();
409: $rightStringType = $right->toString();
410: if (TypeCombinator::union(
411: $leftStringType,
412: $rightStringType,
413: ) instanceof ErrorType) {
414: return new ErrorType();
415: }
416:
417: if ($leftStringType instanceof ConstantStringType && $leftStringType->getValue() === '') {
418: return $rightStringType;
419: }
420:
421: if ($rightStringType instanceof ConstantStringType && $rightStringType->getValue() === '') {
422: return $leftStringType;
423: }
424:
425: if ($leftStringType instanceof ConstantStringType && $rightStringType instanceof ConstantStringType) {
426: return $leftStringType->append($rightStringType);
427: }
428:
429: $leftConstantStrings = $leftStringType->getConstantStrings();
430: $rightConstantStrings = $rightStringType->getConstantStrings();
431: $combinedConstantStringsCount = count($leftConstantStrings) * count($rightConstantStrings);
432:
433: // we limit the number of union-types for performance reasons
434: if ($combinedConstantStringsCount > 0 && $combinedConstantStringsCount <= 16) {
435: $strings = [];
436:
437: foreach ($leftConstantStrings as $leftConstantString) {
438: if ($leftConstantString->getValue() === '') {
439: $strings = array_merge($strings, $rightConstantStrings);
440:
441: continue;
442: }
443:
444: foreach ($rightConstantStrings as $rightConstantString) {
445: if ($rightConstantString->getValue() === '') {
446: $strings[] = $leftConstantString;
447:
448: continue;
449: }
450:
451: $strings[] = $leftConstantString->append($rightConstantString);
452: }
453: }
454:
455: if (count($strings) > 0) {
456: return TypeCombinator::union(...$strings);
457: }
458: }
459:
460: $accessoryTypes = [];
461: if ($leftStringType->isNonEmptyString()->and($rightStringType->isNonEmptyString())->yes()) {
462: $accessoryTypes[] = new AccessoryNonFalsyStringType();
463: } elseif ($leftStringType->isNonFalsyString()->or($rightStringType->isNonFalsyString())->yes()) {
464: $accessoryTypes[] = new AccessoryNonFalsyStringType();
465: } elseif ($leftStringType->isNonEmptyString()->or($rightStringType->isNonEmptyString())->yes()) {
466: $accessoryTypes[] = new AccessoryNonEmptyStringType();
467: }
468:
469: if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) {
470: $accessoryTypes[] = new AccessoryLiteralStringType();
471: }
472:
473: if (count($accessoryTypes) > 0) {
474: $accessoryTypes[] = new StringType();
475: return new IntersectionType($accessoryTypes);
476: }
477:
478: return new StringType();
479: }
480:
481: /**
482: * @param callable(Expr): Type $getTypeCallback
483: */
484: public function getArrayType(Expr\Array_ $expr, callable $getTypeCallback): Type
485: {
486: if (count($expr->items) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
487: return $this->oversizedArrayBuilder->build($expr, $getTypeCallback);
488: }
489:
490: $arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
491: $isList = null;
492: foreach ($expr->items as $arrayItem) {
493: if ($arrayItem === null) {
494: continue;
495: }
496:
497: $valueType = $getTypeCallback($arrayItem->value);
498: if ($arrayItem->unpack) {
499: if (count($valueType->getConstantArrays()) === 1) {
500: $constantArrayType = $valueType->getConstantArrays()[0];
501: $hasStringKey = false;
502: foreach ($constantArrayType->getKeyTypes() as $keyType) {
503: if ($keyType->isString()->yes()) {
504: $hasStringKey = true;
505: break;
506: }
507: }
508:
509: foreach ($constantArrayType->getValueTypes() as $i => $innerValueType) {
510: if ($hasStringKey && $this->phpVersion->supportsArrayUnpackingWithStringKeys()) {
511: $arrayBuilder->setOffsetValueType($constantArrayType->getKeyTypes()[$i], $innerValueType, $constantArrayType->isOptionalKey($i));
512: } else {
513: $arrayBuilder->setOffsetValueType(null, $innerValueType, $constantArrayType->isOptionalKey($i));
514: }
515: }
516: } else {
517: $arrayBuilder->degradeToGeneralArray();
518:
519: if ($this->phpVersion->supportsArrayUnpackingWithStringKeys() && !$valueType->getIterableKeyType()->isString()->no()) {
520: $isList = false;
521: $offsetType = $valueType->getIterableKeyType();
522: } else {
523: $isList ??= $arrayBuilder->isList();
524: $offsetType = new IntegerType();
525: }
526:
527: $arrayBuilder->setOffsetValueType($offsetType, $valueType->getIterableValueType(), !$valueType->isIterableAtLeastOnce()->yes());
528: }
529: } else {
530: $arrayBuilder->setOffsetValueType(
531: $arrayItem->key !== null ? $getTypeCallback($arrayItem->key) : null,
532: $valueType,
533: );
534: }
535: }
536:
537: $arrayType = $arrayBuilder->getArray();
538: if ($isList === true) {
539: return AccessoryArrayListType::intersectWith($arrayType);
540: }
541:
542: return $arrayType;
543: }
544:
545: /**
546: * @param callable(Expr): Type $getTypeCallback
547: */
548: public function getBitwiseAndType(Expr $left, Expr $right, callable $getTypeCallback): Type
549: {
550: $leftType = $getTypeCallback($left);
551: $rightType = $getTypeCallback($right);
552:
553: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
554: return $this->getNeverType($leftType, $rightType);
555: }
556:
557: $leftTypes = $leftType->getConstantScalarTypes();
558: $rightTypes = $rightType->getConstantScalarTypes();
559: $leftTypesCount = count($leftTypes);
560: $rightTypesCount = count($rightTypes);
561: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
562: $resultTypes = [];
563: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
564: foreach ($leftTypes as $leftTypeInner) {
565: foreach ($rightTypes as $rightTypeInner) {
566: if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) {
567: $resultType = $this->getTypeFromValue($leftTypeInner->getValue() & $rightTypeInner->getValue());
568: } else {
569: $leftNumberType = $leftTypeInner->toNumber();
570: $rightNumberType = $rightTypeInner->toNumber();
571:
572: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
573: return new ErrorType();
574: }
575:
576: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
577: throw new ShouldNotHappenException();
578: }
579:
580: $resultType = $this->getTypeFromValue($leftNumberType->getValue() & $rightNumberType->getValue());
581: }
582: if ($generalize) {
583: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
584: }
585: $resultTypes[] = $resultType;
586: }
587: }
588: return TypeCombinator::union(...$resultTypes);
589: }
590:
591: if ($leftType->isString()->yes() && $rightType->isString()->yes()) {
592: return new StringType();
593: }
594:
595: $leftNumberType = $leftType->toNumber();
596: $rightNumberType = $rightType->toNumber();
597:
598: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
599: return new ErrorType();
600: }
601:
602: if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() >= 0) {
603: return IntegerRangeType::fromInterval(0, $rightNumberType->getValue());
604: }
605: if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() >= 0) {
606: return IntegerRangeType::fromInterval(0, $leftNumberType->getValue());
607: }
608:
609: return new IntegerType();
610: }
611:
612: /**
613: * @param callable(Expr): Type $getTypeCallback
614: */
615: public function getBitwiseOrType(Expr $left, Expr $right, callable $getTypeCallback): Type
616: {
617: $leftType = $getTypeCallback($left);
618: $rightType = $getTypeCallback($right);
619:
620: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
621: return $this->getNeverType($leftType, $rightType);
622: }
623:
624: $leftTypes = $leftType->getConstantScalarTypes();
625: $rightTypes = $rightType->getConstantScalarTypes();
626: $leftTypesCount = count($leftTypes);
627: $rightTypesCount = count($rightTypes);
628: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
629: $resultTypes = [];
630: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
631: foreach ($leftTypes as $leftTypeInner) {
632: foreach ($rightTypes as $rightTypeInner) {
633: if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) {
634: $resultType = $this->getTypeFromValue($leftTypeInner->getValue() | $rightTypeInner->getValue());
635: } else {
636: $leftNumberType = $leftTypeInner->toNumber();
637: $rightNumberType = $rightTypeInner->toNumber();
638:
639: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
640: return new ErrorType();
641: }
642:
643: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
644: throw new ShouldNotHappenException();
645: }
646:
647: $resultType = $this->getTypeFromValue($leftNumberType->getValue() | $rightNumberType->getValue());
648: }
649: if ($generalize) {
650: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
651: }
652: $resultTypes[] = $resultType;
653: }
654: }
655: return TypeCombinator::union(...$resultTypes);
656: }
657:
658: if ($leftType->isString()->yes() && $rightType->isString()->yes()) {
659: return new StringType();
660: }
661:
662: if (TypeCombinator::union($leftType->toNumber(), $rightType->toNumber()) instanceof ErrorType) {
663: return new ErrorType();
664: }
665:
666: return new IntegerType();
667: }
668:
669: /**
670: * @param callable(Expr): Type $getTypeCallback
671: */
672: public function getBitwiseXorType(Expr $left, Expr $right, callable $getTypeCallback): Type
673: {
674: $leftType = $getTypeCallback($left);
675: $rightType = $getTypeCallback($right);
676:
677: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
678: return $this->getNeverType($leftType, $rightType);
679: }
680:
681: $leftTypes = $leftType->getConstantScalarTypes();
682: $rightTypes = $rightType->getConstantScalarTypes();
683: $leftTypesCount = count($leftTypes);
684: $rightTypesCount = count($rightTypes);
685: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
686: $resultTypes = [];
687: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
688: foreach ($leftTypes as $leftTypeInner) {
689: foreach ($rightTypes as $rightTypeInner) {
690: if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) {
691: $resultType = $this->getTypeFromValue($leftTypeInner->getValue() ^ $rightTypeInner->getValue());
692: } else {
693: $leftNumberType = $leftTypeInner->toNumber();
694: $rightNumberType = $rightTypeInner->toNumber();
695:
696: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
697: return new ErrorType();
698: }
699:
700: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
701: throw new ShouldNotHappenException();
702: }
703:
704: $resultType = $this->getTypeFromValue($leftNumberType->getValue() ^ $rightNumberType->getValue());
705: }
706: if ($generalize) {
707: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
708: }
709: $resultTypes[] = $resultType;
710: }
711: }
712: return TypeCombinator::union(...$resultTypes);
713: }
714:
715: if ($leftType->isString()->yes() && $rightType->isString()->yes()) {
716: return new StringType();
717: }
718:
719: if (TypeCombinator::union($leftType->toNumber(), $rightType->toNumber()) instanceof ErrorType) {
720: return new ErrorType();
721: }
722:
723: return new IntegerType();
724: }
725:
726: /**
727: * @param callable(Expr): Type $getTypeCallback
728: */
729: public function getSpaceshipType(Expr $left, Expr $right, callable $getTypeCallback): Type
730: {
731: $callbackLeftType = $getTypeCallback($left);
732: $callbackRightType = $getTypeCallback($right);
733:
734: if ($callbackLeftType instanceof NeverType || $callbackRightType instanceof NeverType) {
735: return $this->getNeverType($callbackLeftType, $callbackRightType);
736: }
737:
738: $leftTypes = $callbackLeftType->getConstantScalarTypes();
739: $rightTypes = $callbackRightType->getConstantScalarTypes();
740:
741: $leftTypesCount = count($leftTypes);
742: $rightTypesCount = count($rightTypes);
743: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
744: $resultTypes = [];
745: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
746: foreach ($leftTypes as $leftType) {
747: foreach ($rightTypes as $rightType) {
748: $leftValue = $leftType->getValue();
749: $rightValue = $rightType->getValue();
750: $resultType = $this->getTypeFromValue($leftValue <=> $rightValue);
751: if ($generalize) {
752: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
753: }
754: $resultTypes[] = $resultType;
755: }
756: }
757: return TypeCombinator::union(...$resultTypes);
758: }
759:
760: return IntegerRangeType::fromInterval(-1, 1);
761: }
762:
763: /**
764: * @param callable(Expr): Type $getTypeCallback
765: */
766: public function getDivType(Expr $left, Expr $right, callable $getTypeCallback): Type
767: {
768: $leftType = $getTypeCallback($left);
769: $rightType = $getTypeCallback($right);
770:
771: $leftTypes = $leftType->getConstantScalarTypes();
772: $rightTypes = $rightType->getConstantScalarTypes();
773: $leftTypesCount = count($leftTypes);
774: $rightTypesCount = count($rightTypes);
775: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
776: $resultTypes = [];
777: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
778: foreach ($leftTypes as $leftTypeInner) {
779: foreach ($rightTypes as $rightTypeInner) {
780: $leftNumberType = $leftTypeInner->toNumber();
781: $rightNumberType = $rightTypeInner->toNumber();
782:
783: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
784: return new ErrorType();
785: }
786:
787: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
788: throw new ShouldNotHappenException();
789: }
790:
791: if ($rightNumberType->getValue() === 0 || $rightNumberType->getValue() === 0.0) {
792: return new ErrorType();
793: }
794:
795: $resultType = $this->getTypeFromValue($leftNumberType->getValue() / $rightNumberType->getValue()); // @phpstan-ignore binaryOp.invalid
796: if ($generalize) {
797: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
798: }
799: $resultTypes[] = $resultType;
800: }
801: }
802: return TypeCombinator::union(...$resultTypes);
803: }
804:
805: $rightScalarValues = $rightType->toNumber()->getConstantScalarValues();
806: foreach ($rightScalarValues as $scalarValue) {
807: if ($scalarValue === 0 || $scalarValue === 0.0) {
808: return new ErrorType();
809: }
810: }
811:
812: return $this->resolveCommonMath(new BinaryOp\Div($left, $right), $leftType, $rightType);
813: }
814:
815: /**
816: * @param callable(Expr): Type $getTypeCallback
817: */
818: public function getModType(Expr $left, Expr $right, callable $getTypeCallback): Type
819: {
820: $leftType = $getTypeCallback($left);
821: $rightType = $getTypeCallback($right);
822:
823: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
824: return $this->getNeverType($leftType, $rightType);
825: }
826:
827: $leftTypes = $leftType->getConstantScalarTypes();
828: $rightTypes = $rightType->getConstantScalarTypes();
829: $leftTypesCount = count($leftTypes);
830: $rightTypesCount = count($rightTypes);
831: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
832: $resultTypes = [];
833: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
834: foreach ($leftTypes as $leftTypeInner) {
835: foreach ($rightTypes as $rightTypeInner) {
836: $leftNumberType = $leftTypeInner->toNumber();
837: $rightNumberType = $rightTypeInner->toNumber();
838:
839: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
840: return new ErrorType();
841: }
842:
843: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
844: throw new ShouldNotHappenException();
845: }
846:
847: $rightIntegerValue = (int) $rightNumberType->getValue();
848: if ($rightIntegerValue === 0) {
849: return new ErrorType();
850: }
851:
852: $resultType = $this->getTypeFromValue((int) $leftNumberType->getValue() % $rightIntegerValue);
853: if ($generalize) {
854: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
855: }
856: $resultTypes[] = $resultType;
857: }
858: }
859: return TypeCombinator::union(...$resultTypes);
860: }
861:
862: $integerType = $rightType->toInteger();
863: if ($integerType instanceof ConstantIntegerType && $integerType->getValue() === 1) {
864: return new ConstantIntegerType(0);
865: }
866:
867: $rightScalarValues = $rightType->toNumber()->getConstantScalarValues();
868: foreach ($rightScalarValues as $scalarValue) {
869:
870: if ($scalarValue === 0 || $scalarValue === 0.0) {
871: return new ErrorType();
872: }
873: }
874:
875: $integer = new IntegerType();
876: $positiveInt = IntegerRangeType::fromInterval(0, null);
877: if ($integer->isSuperTypeOf($rightType)->yes()) {
878: $rangeMin = null;
879: $rangeMax = null;
880:
881: if ($rightType instanceof IntegerRangeType) {
882: $rangeMax = $rightType->getMax() !== null ? $rightType->getMax() - 1 : null;
883: } elseif ($rightType instanceof ConstantIntegerType) {
884: $rangeMax = $rightType->getValue() - 1;
885: } elseif ($rightType instanceof UnionType) {
886: foreach ($rightType->getTypes() as $type) {
887: if ($type instanceof IntegerRangeType) {
888: if ($type->getMax() === null) {
889: $rangeMax = null;
890: } else {
891: $rangeMax = max($rangeMax, $type->getMax());
892: }
893: } elseif ($type instanceof ConstantIntegerType) {
894: $rangeMax = max($rangeMax, $type->getValue() - 1);
895: }
896: }
897: }
898:
899: if ($positiveInt->isSuperTypeOf($leftType)->yes()) {
900: $rangeMin = 0;
901: } elseif ($rangeMax !== null) {
902: $rangeMin = $rangeMax * -1;
903: }
904:
905: return IntegerRangeType::fromInterval($rangeMin, $rangeMax);
906: } elseif ($positiveInt->isSuperTypeOf($leftType)->yes()) {
907: return IntegerRangeType::fromInterval(0, null);
908: }
909:
910: return new IntegerType();
911: }
912:
913: /**
914: * @param callable(Expr): Type $getTypeCallback
915: */
916: public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): Type
917: {
918: $leftType = $getTypeCallback($left);
919: $rightType = $getTypeCallback($right);
920:
921: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
922: return $this->getNeverType($leftType, $rightType);
923: }
924:
925: $leftTypes = $leftType->getConstantScalarTypes();
926: $rightTypes = $rightType->getConstantScalarTypes();
927: $leftTypesCount = count($leftTypes);
928: $rightTypesCount = count($rightTypes);
929: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
930: $resultTypes = [];
931: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
932: foreach ($leftTypes as $leftTypeInner) {
933: foreach ($rightTypes as $rightTypeInner) {
934: $leftNumberType = $leftTypeInner->toNumber();
935: $rightNumberType = $rightTypeInner->toNumber();
936:
937: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
938: return new ErrorType();
939: }
940:
941: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
942: throw new ShouldNotHappenException();
943: }
944:
945: $resultType = $this->getTypeFromValue($leftNumberType->getValue() + $rightNumberType->getValue());
946: if ($generalize) {
947: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
948: }
949: $resultTypes[] = $resultType;
950: }
951: }
952:
953: return TypeCombinator::union(...$resultTypes);
954: }
955:
956: $leftConstantArrays = $leftType->getConstantArrays();
957: $rightConstantArrays = $rightType->getConstantArrays();
958:
959: $leftCount = count($leftConstantArrays);
960: $rightCount = count($rightConstantArrays);
961: if ($leftCount > 0 && $rightCount > 0
962: && ($leftCount + $rightCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT)) {
963: $resultTypes = [];
964: foreach ($rightConstantArrays as $rightConstantArray) {
965: foreach ($leftConstantArrays as $leftConstantArray) {
966: $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray);
967: foreach ($leftConstantArray->getKeyTypes() as $i => $leftKeyType) {
968: $optional = $leftConstantArray->isOptionalKey($i);
969: $valueType = $leftConstantArray->getOffsetValueType($leftKeyType);
970: if (!$optional) {
971: if ($rightConstantArray->hasOffsetValueType($leftKeyType)->maybe()) {
972: $valueType = TypeCombinator::union($valueType, $rightConstantArray->getOffsetValueType($leftKeyType));
973: }
974: }
975: $newArrayBuilder->setOffsetValueType(
976: $leftKeyType,
977: $valueType,
978: $optional,
979: );
980: }
981: $resultTypes[] = $newArrayBuilder->getArray();
982: }
983: }
984: return TypeCombinator::union(...$resultTypes);
985: }
986:
987: $leftIsArray = $leftType->isArray();
988: $rightIsArray = $rightType->isArray();
989: if ($leftIsArray->yes() && $rightIsArray->yes()) {
990: if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) {
991: // to preserve BenevolentUnionType
992: $keyType = $leftType->getIterableKeyType();
993: } else {
994: $keyTypes = [];
995: foreach ([
996: $leftType->getIterableKeyType(),
997: $rightType->getIterableKeyType(),
998: ] as $keyType) {
999: $keyTypes[] = $keyType;
1000: }
1001: $keyType = TypeCombinator::union(...$keyTypes);
1002: }
1003:
1004: $arrayType = new ArrayType(
1005: $keyType,
1006: TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType()),
1007: );
1008:
1009: if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) {
1010: $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
1011: }
1012: if ($leftType->isList()->yes() && $rightType->isList()->yes()) {
1013: $arrayType = AccessoryArrayListType::intersectWith($arrayType);
1014: }
1015:
1016: return $arrayType;
1017: }
1018:
1019: if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1020: if (
1021: ($leftIsArray->no() && $rightIsArray->no())
1022: ) {
1023: return new BenevolentUnionType([
1024: new FloatType(),
1025: new IntegerType(),
1026: ]);
1027: }
1028: return new BenevolentUnionType([
1029: new FloatType(),
1030: new IntegerType(),
1031: new ArrayType(new MixedType(), new MixedType()),
1032: ]);
1033: }
1034:
1035: if (
1036: ($leftIsArray->yes() && $rightIsArray->no())
1037: || ($leftIsArray->no() && $rightIsArray->yes())
1038: ) {
1039: return new ErrorType();
1040: }
1041:
1042: if (
1043: ($leftIsArray->yes() && $rightIsArray->maybe())
1044: || ($leftIsArray->maybe() && $rightIsArray->yes())
1045: ) {
1046: $resultType = new ArrayType(new MixedType(), new MixedType());
1047: if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) {
1048: return TypeCombinator::intersect($resultType, new NonEmptyArrayType());
1049: }
1050:
1051: return $resultType;
1052: }
1053:
1054: if ($leftIsArray->maybe() && $rightIsArray->maybe()) {
1055: $plusable = new UnionType([
1056: new StringType(),
1057: new FloatType(),
1058: new IntegerType(),
1059: new ArrayType(new MixedType(), new MixedType()),
1060: new BooleanType(),
1061: ]);
1062:
1063: if ($plusable->isSuperTypeOf($leftType)->yes() && $plusable->isSuperTypeOf($rightType)->yes()) {
1064: return TypeCombinator::union($leftType, $rightType);
1065: }
1066: }
1067:
1068: return $this->resolveCommonMath(new BinaryOp\Plus($left, $right), $leftType, $rightType);
1069: }
1070:
1071: /**
1072: * @param callable(Expr): Type $getTypeCallback
1073: */
1074: public function getMinusType(Expr $left, Expr $right, callable $getTypeCallback): Type
1075: {
1076: $leftType = $getTypeCallback($left);
1077: $rightType = $getTypeCallback($right);
1078:
1079: $leftTypes = $leftType->getConstantScalarTypes();
1080: $rightTypes = $rightType->getConstantScalarTypes();
1081: $leftTypesCount = count($leftTypes);
1082: $rightTypesCount = count($rightTypes);
1083: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1084: $resultTypes = [];
1085: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1086: foreach ($leftTypes as $leftTypeInner) {
1087: foreach ($rightTypes as $rightTypeInner) {
1088: $leftNumberType = $leftTypeInner->toNumber();
1089: $rightNumberType = $rightTypeInner->toNumber();
1090:
1091: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1092: return new ErrorType();
1093: }
1094:
1095: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1096: throw new ShouldNotHappenException();
1097: }
1098:
1099: $resultType = $this->getTypeFromValue($leftNumberType->getValue() - $rightNumberType->getValue());
1100: if ($generalize) {
1101: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
1102: }
1103: $resultTypes[] = $resultType;
1104: }
1105: }
1106:
1107: return TypeCombinator::union(...$resultTypes);
1108: }
1109:
1110: return $this->resolveCommonMath(new BinaryOp\Minus($left, $right), $leftType, $rightType);
1111: }
1112:
1113: /**
1114: * @param callable(Expr): Type $getTypeCallback
1115: */
1116: public function getMulType(Expr $left, Expr $right, callable $getTypeCallback): Type
1117: {
1118: $leftType = $getTypeCallback($left);
1119: $rightType = $getTypeCallback($right);
1120:
1121: $leftTypes = $leftType->getConstantScalarTypes();
1122: $rightTypes = $rightType->getConstantScalarTypes();
1123: $leftTypesCount = count($leftTypes);
1124: $rightTypesCount = count($rightTypes);
1125: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1126: $resultTypes = [];
1127: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1128: foreach ($leftTypes as $leftTypeInner) {
1129: foreach ($rightTypes as $rightTypeInner) {
1130: $leftNumberType = $leftTypeInner->toNumber();
1131: $rightNumberType = $rightTypeInner->toNumber();
1132:
1133: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1134: return new ErrorType();
1135: }
1136:
1137: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1138: throw new ShouldNotHappenException();
1139: }
1140:
1141: $resultType = $this->getTypeFromValue($leftNumberType->getValue() * $rightNumberType->getValue());
1142: if ($generalize) {
1143: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
1144: }
1145: $resultTypes[] = $resultType;
1146: }
1147: }
1148:
1149: return TypeCombinator::union(...$resultTypes);
1150: }
1151:
1152: $floatType = new FloatType();
1153: $leftNumberType = $leftType->toNumber();
1154: if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0) {
1155: if ($floatType->isSuperTypeOf($rightType)->yes()) {
1156: return new ConstantFloatType(0.0);
1157: }
1158: return new ConstantIntegerType(0);
1159: }
1160: $rightNumberType = $rightType->toNumber();
1161: if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0) {
1162: if ($floatType->isSuperTypeOf($leftType)->yes()) {
1163: return new ConstantFloatType(0.0);
1164: }
1165: return new ConstantIntegerType(0);
1166: }
1167:
1168: return $this->resolveCommonMath(new BinaryOp\Mul($left, $right), $leftType, $rightType);
1169: }
1170:
1171: /**
1172: * @param callable(Expr): Type $getTypeCallback
1173: */
1174: public function getPowType(Expr $left, Expr $right, callable $getTypeCallback): Type
1175: {
1176: $leftType = $getTypeCallback($left);
1177: $rightType = $getTypeCallback($right);
1178:
1179: $exponentiatedTyped = $leftType->exponentiate($rightType);
1180: if (!$exponentiatedTyped instanceof ErrorType) {
1181: return $exponentiatedTyped;
1182: }
1183:
1184: $extensionSpecified = $this->callOperatorTypeSpecifyingExtensions(new BinaryOp\Pow($left, $right), $leftType, $rightType);
1185: if ($extensionSpecified !== null) {
1186: return $extensionSpecified;
1187: }
1188:
1189: return new ErrorType();
1190: }
1191:
1192: /**
1193: * @param callable(Expr): Type $getTypeCallback
1194: */
1195: public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallback): Type
1196: {
1197: $leftType = $getTypeCallback($left);
1198: $rightType = $getTypeCallback($right);
1199:
1200: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1201: return $this->getNeverType($leftType, $rightType);
1202: }
1203:
1204: $leftTypes = $leftType->getConstantScalarTypes();
1205: $rightTypes = $rightType->getConstantScalarTypes();
1206: $leftTypesCount = count($leftTypes);
1207: $rightTypesCount = count($rightTypes);
1208: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1209: $resultTypes = [];
1210: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1211: foreach ($leftTypes as $leftTypeInner) {
1212: foreach ($rightTypes as $rightTypeInner) {
1213: $leftNumberType = $leftTypeInner->toNumber();
1214: $rightNumberType = $rightTypeInner->toNumber();
1215:
1216: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1217: return new ErrorType();
1218: }
1219:
1220: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1221: throw new ShouldNotHappenException();
1222: }
1223:
1224: if ($rightNumberType->getValue() < 0) {
1225: return new ErrorType();
1226: }
1227:
1228: $resultType = $this->getTypeFromValue($leftNumberType->getValue() << $rightNumberType->getValue());
1229: if ($generalize) {
1230: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
1231: }
1232: $resultTypes[] = $resultType;
1233: }
1234: }
1235:
1236: return TypeCombinator::union(...$resultTypes);
1237: }
1238:
1239: $leftNumberType = $leftType->toNumber();
1240: $rightNumberType = $rightType->toNumber();
1241:
1242: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1243: return new ErrorType();
1244: }
1245:
1246: return new IntegerType();
1247: }
1248:
1249: /**
1250: * @param callable(Expr): Type $getTypeCallback
1251: */
1252: public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCallback): Type
1253: {
1254: $leftType = $getTypeCallback($left);
1255: $rightType = $getTypeCallback($right);
1256:
1257: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1258: return $this->getNeverType($leftType, $rightType);
1259: }
1260:
1261: $leftTypes = $leftType->getConstantScalarTypes();
1262: $rightTypes = $rightType->getConstantScalarTypes();
1263: $leftTypesCount = count($leftTypes);
1264: $rightTypesCount = count($rightTypes);
1265: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1266: $resultTypes = [];
1267: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1268: foreach ($leftTypes as $leftTypeInner) {
1269: foreach ($rightTypes as $rightTypeInner) {
1270: $leftNumberType = $leftTypeInner->toNumber();
1271: $rightNumberType = $rightTypeInner->toNumber();
1272:
1273: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1274: return new ErrorType();
1275: }
1276:
1277: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1278: throw new ShouldNotHappenException();
1279: }
1280:
1281: if ($rightNumberType->getValue() < 0) {
1282: return new ErrorType();
1283: }
1284:
1285: $resultType = $this->getTypeFromValue($leftNumberType->getValue() >> $rightNumberType->getValue());
1286: if ($generalize) {
1287: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
1288: }
1289: $resultTypes[] = $resultType;
1290: }
1291: }
1292:
1293: return TypeCombinator::union(...$resultTypes);
1294: }
1295:
1296: $leftNumberType = $leftType->toNumber();
1297: $rightNumberType = $rightType->toNumber();
1298:
1299: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1300: return new ErrorType();
1301: }
1302:
1303: return new IntegerType();
1304: }
1305:
1306: public function resolveIdenticalType(Type $leftType, Type $rightType): BooleanType
1307: {
1308: if ($leftType instanceof ConstantScalarType && $rightType instanceof ConstantScalarType) {
1309: return new ConstantBooleanType($leftType->getValue() === $rightType->getValue());
1310: }
1311:
1312: $leftTypeFiniteTypes = $leftType->getFiniteTypes();
1313: $rightTypeFiniteType = $rightType->getFiniteTypes();
1314: if (count($leftTypeFiniteTypes) === 1 && count($rightTypeFiniteType) === 1) {
1315: return new ConstantBooleanType($leftTypeFiniteTypes[0]->equals($rightTypeFiniteType[0]));
1316: }
1317:
1318: $isSuperset = $leftType->isSuperTypeOf($rightType);
1319: if ($isSuperset->no()) {
1320: return new ConstantBooleanType(false);
1321: }
1322:
1323: if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) {
1324: return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): BooleanType => $this->resolveIdenticalType($leftValueType, $rightValueType));
1325: }
1326:
1327: return new BooleanType();
1328: }
1329:
1330: public function resolveEqualType(Type $leftType, Type $rightType): BooleanType
1331: {
1332: $integerType = new IntegerType();
1333: $floatType = new FloatType();
1334:
1335: if (
1336: ($leftType->isEnum()->yes() && $rightType->isTrue()->no())
1337: || ($rightType->isEnum()->yes() && $leftType->isTrue()->no())
1338: || ($leftType->isString()->yes() && $rightType->isString()->yes())
1339: || ($integerType->isSuperTypeOf($leftType)->yes() && $integerType->isSuperTypeOf($rightType)->yes())
1340: || ($floatType->isSuperTypeOf($leftType)->yes() && $floatType->isSuperTypeOf($rightType)->yes())
1341: ) {
1342: return $this->resolveIdenticalType($leftType, $rightType);
1343: }
1344:
1345: if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) {
1346: return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): BooleanType => $this->resolveEqualType($leftValueType, $rightValueType));
1347: }
1348:
1349: return $leftType->looseCompare($rightType, $this->phpVersion);
1350: }
1351:
1352: /**
1353: * @param callable(Type, Type): BooleanType $valueComparisonCallback
1354: */
1355: private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, ConstantArrayType $rightType, callable $valueComparisonCallback): BooleanType
1356: {
1357: $leftKeyTypes = $leftType->getKeyTypes();
1358: $rightKeyTypes = $rightType->getKeyTypes();
1359: $leftValueTypes = $leftType->getValueTypes();
1360: $rightValueTypes = $rightType->getValueTypes();
1361:
1362: $resultType = new ConstantBooleanType(true);
1363:
1364: foreach ($leftKeyTypes as $i => $leftKeyType) {
1365: $leftOptional = $leftType->isOptionalKey($i);
1366: if ($leftOptional) {
1367: $resultType = new BooleanType();
1368: }
1369:
1370: if (count($rightKeyTypes) === 0) {
1371: if (!$leftOptional) {
1372: return new ConstantBooleanType(false);
1373: }
1374: continue;
1375: }
1376:
1377: $found = false;
1378: foreach ($rightKeyTypes as $j => $rightKeyType) {
1379: unset($rightKeyTypes[$j]);
1380:
1381: if ($leftKeyType->equals($rightKeyType)) {
1382: $found = true;
1383: break;
1384: } elseif (!$rightType->isOptionalKey($j)) {
1385: return new ConstantBooleanType(false);
1386: }
1387: }
1388:
1389: if (!$found) {
1390: if (!$leftOptional) {
1391: return new ConstantBooleanType(false);
1392: }
1393: continue;
1394: }
1395:
1396: if (!isset($j)) {
1397: throw new ShouldNotHappenException();
1398: }
1399:
1400: $rightOptional = $rightType->isOptionalKey($j);
1401: if ($rightOptional) {
1402: $resultType = new BooleanType();
1403: if ($leftOptional) {
1404: continue;
1405: }
1406: }
1407:
1408: $leftIdenticalToRight = $valueComparisonCallback($leftValueTypes[$i], $rightValueTypes[$j]);
1409: if ($leftIdenticalToRight->isFalse()->yes()) {
1410: return new ConstantBooleanType(false);
1411: }
1412: $resultType = TypeCombinator::union($resultType, $leftIdenticalToRight);
1413: }
1414:
1415: foreach (array_keys($rightKeyTypes) as $j) {
1416: if (!$rightType->isOptionalKey($j)) {
1417: return new ConstantBooleanType(false);
1418: }
1419: $resultType = new BooleanType();
1420: }
1421:
1422: return $resultType->toBoolean();
1423: }
1424:
1425: private function callOperatorTypeSpecifyingExtensions(Expr\BinaryOp $expr, Type $leftType, Type $rightType): ?Type
1426: {
1427: $operatorSigil = $expr->getOperatorSigil();
1428: $operatorTypeSpecifyingExtensions = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()->getOperatorTypeSpecifyingExtensions($operatorSigil, $leftType, $rightType);
1429:
1430: /** @var Type[] $extensionTypes */
1431: $extensionTypes = [];
1432:
1433: foreach ($operatorTypeSpecifyingExtensions as $extension) {
1434: $extensionTypes[] = $extension->specifyType($operatorSigil, $leftType, $rightType);
1435: }
1436:
1437: if (count($extensionTypes) > 0) {
1438: return TypeCombinator::union(...$extensionTypes);
1439: }
1440:
1441: return null;
1442: }
1443:
1444: /**
1445: * @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div $expr
1446: */
1447: private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type
1448: {
1449: $types = TypeCombinator::union($leftType, $rightType);
1450: $leftNumberType = $leftType->toNumber();
1451: $rightNumberType = $rightType->toNumber();
1452:
1453: if (
1454: !$types instanceof MixedType
1455: && (
1456: $rightNumberType instanceof IntegerRangeType
1457: || $rightNumberType instanceof ConstantIntegerType
1458: || $rightNumberType instanceof UnionType
1459: )
1460: ) {
1461: if ($leftNumberType instanceof IntegerRangeType || $leftNumberType instanceof ConstantIntegerType) {
1462: return $this->integerRangeMath(
1463: $leftNumberType,
1464: $expr,
1465: $rightNumberType,
1466: );
1467: } elseif ($leftNumberType instanceof UnionType) {
1468: $unionParts = [];
1469:
1470: foreach ($leftNumberType->getTypes() as $type) {
1471: $numberType = $type->toNumber();
1472: if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) {
1473: $unionParts[] = $this->integerRangeMath($numberType, $expr, $rightNumberType);
1474: } else {
1475: $unionParts[] = $numberType;
1476: }
1477: }
1478:
1479: $union = TypeCombinator::union(...$unionParts);
1480: if ($leftNumberType instanceof BenevolentUnionType) {
1481: return TypeUtils::toBenevolentUnion($union)->toNumber();
1482: }
1483:
1484: return $union->toNumber();
1485: }
1486: }
1487:
1488: $specifiedTypes = $this->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType);
1489: if ($specifiedTypes !== null) {
1490: return $specifiedTypes;
1491: }
1492:
1493: if (
1494: $leftType->isArray()->yes()
1495: || $rightType->isArray()->yes()
1496: || $types->isArray()->yes()
1497: ) {
1498: return new ErrorType();
1499: }
1500:
1501: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1502: return new ErrorType();
1503: }
1504: if ($leftNumberType instanceof NeverType || $rightNumberType instanceof NeverType) {
1505: return $this->getNeverType($leftNumberType, $rightNumberType);
1506: }
1507:
1508: if (
1509: (new FloatType())->isSuperTypeOf($leftNumberType)->yes()
1510: || (new FloatType())->isSuperTypeOf($rightNumberType)->yes()
1511: ) {
1512: return new FloatType();
1513: }
1514:
1515: $resultType = TypeCombinator::union($leftNumberType, $rightNumberType);
1516: if ($expr instanceof Expr\BinaryOp\Div) {
1517: if ($types instanceof MixedType || $resultType->isInteger()->yes()) {
1518: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
1519: }
1520:
1521: return new UnionType([new IntegerType(), new FloatType()]);
1522: }
1523:
1524: if ($types instanceof MixedType
1525: || $leftType instanceof BenevolentUnionType
1526: || $rightType instanceof BenevolentUnionType
1527: ) {
1528: return TypeUtils::toBenevolentUnion($resultType);
1529: }
1530:
1531: return $resultType;
1532: }
1533:
1534: /**
1535: * @param ConstantIntegerType|IntegerRangeType $range
1536: * @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus $node
1537: */
1538: private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): Type
1539: {
1540: if ($range instanceof IntegerRangeType) {
1541: $rangeMin = $range->getMin();
1542: $rangeMax = $range->getMax();
1543: } else {
1544: $rangeMin = $range->getValue();
1545: $rangeMax = $rangeMin;
1546: }
1547:
1548: if ($operand instanceof UnionType) {
1549:
1550: $unionParts = [];
1551:
1552: foreach ($operand->getTypes() as $type) {
1553: $numberType = $type->toNumber();
1554: if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) {
1555: $unionParts[] = $this->integerRangeMath($range, $node, $numberType);
1556: } else {
1557: $unionParts[] = $type->toNumber();
1558: }
1559: }
1560:
1561: $union = TypeCombinator::union(...$unionParts);
1562: if ($operand instanceof BenevolentUnionType) {
1563: return TypeUtils::toBenevolentUnion($union)->toNumber();
1564: }
1565:
1566: return $union->toNumber();
1567: }
1568:
1569: $operand = $operand->toNumber();
1570: if ($operand instanceof IntegerRangeType) {
1571: $operandMin = $operand->getMin();
1572: $operandMax = $operand->getMax();
1573: } elseif ($operand instanceof ConstantIntegerType) {
1574: $operandMin = $operand->getValue();
1575: $operandMax = $operand->getValue();
1576: } else {
1577: return $operand;
1578: }
1579:
1580: if ($node instanceof BinaryOp\Plus) {
1581: if ($operand instanceof ConstantIntegerType) {
1582: /** @var int|float|null $min */
1583: $min = $rangeMin !== null ? $rangeMin + $operand->getValue() : null;
1584:
1585: /** @var int|float|null $max */
1586: $max = $rangeMax !== null ? $rangeMax + $operand->getValue() : null;
1587: } else {
1588: /** @var int|float|null $min */
1589: $min = $rangeMin !== null && $operand->getMin() !== null ? $rangeMin + $operand->getMin() : null;
1590:
1591: /** @var int|float|null $max */
1592: $max = $rangeMax !== null && $operand->getMax() !== null ? $rangeMax + $operand->getMax() : null;
1593: }
1594: } elseif ($node instanceof BinaryOp\Minus) {
1595: if ($operand instanceof ConstantIntegerType) {
1596: /** @var int|float|null $min */
1597: $min = $rangeMin !== null ? $rangeMin - $operand->getValue() : null;
1598:
1599: /** @var int|float|null $max */
1600: $max = $rangeMax !== null ? $rangeMax - $operand->getValue() : null;
1601: } else {
1602: if ($rangeMin === $rangeMax && $rangeMin !== null
1603: && ($operand->getMin() === null || $operand->getMax() === null)) {
1604: $min = null;
1605: $max = $rangeMin;
1606: } else {
1607: if ($operand->getMin() === null) {
1608: $min = null;
1609: } elseif ($rangeMin !== null) {
1610: if ($operand->getMax() !== null) {
1611: /** @var int|float $min */
1612: $min = $rangeMin - $operand->getMax();
1613: } else {
1614: /** @var int|float $min */
1615: $min = $rangeMin - $operand->getMin();
1616: }
1617: } else {
1618: $min = null;
1619: }
1620:
1621: if ($operand->getMax() === null) {
1622: $min = null;
1623: $max = null;
1624: } elseif ($rangeMax !== null) {
1625: if ($rangeMin !== null && $operand->getMin() === null) {
1626: /** @var int|float $min */
1627: $min = $rangeMin - $operand->getMax();
1628: $max = null;
1629: } elseif ($operand->getMin() !== null) {
1630: /** @var int|float $max */
1631: $max = $rangeMax - $operand->getMin();
1632: } else {
1633: $max = null;
1634: }
1635: } else {
1636: $max = null;
1637: }
1638:
1639: if ($min !== null && $max !== null && $min > $max) {
1640: [$min, $max] = [$max, $min];
1641: }
1642: }
1643: }
1644: } elseif ($node instanceof Expr\BinaryOp\Mul) {
1645: $min1 = $rangeMin === 0 || $operandMin === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMin ?? -INF);
1646: $min2 = $rangeMin === 0 || $operandMax === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMax ?? INF);
1647: $max1 = $rangeMax === 0 || $operandMin === 0 ? 0 : ($rangeMax ?? INF) * ($operandMin ?? -INF);
1648: $max2 = $rangeMax === 0 || $operandMax === 0 ? 0 : ($rangeMax ?? INF) * ($operandMax ?? INF);
1649:
1650: $min = min($min1, $min2, $max1, $max2);
1651: $max = max($min1, $min2, $max1, $max2);
1652:
1653: if (!is_finite($min)) {
1654: $min = null;
1655: }
1656: if (!is_finite($max)) {
1657: $max = null;
1658: }
1659: } else {
1660: if ($operand instanceof ConstantIntegerType) {
1661: $min = $rangeMin !== null && $operand->getValue() !== 0 ? $rangeMin / $operand->getValue() : null;
1662: $max = $rangeMax !== null && $operand->getValue() !== 0 ? $rangeMax / $operand->getValue() : null;
1663: } else {
1664: // Avoid division by zero when looking for the min and the max by using the closest int
1665: $operandMin = $operandMin !== 0 ? $operandMin : 1;
1666: $operandMax = $operandMax !== 0 ? $operandMax : -1;
1667:
1668: if (
1669: ($operandMin < 0 || $operandMin === null)
1670: && ($operandMax > 0 || $operandMax === null)
1671: ) {
1672: $negativeOperand = IntegerRangeType::fromInterval($operandMin, 0);
1673: assert($negativeOperand instanceof IntegerRangeType);
1674: $positiveOperand = IntegerRangeType::fromInterval(0, $operandMax);
1675: assert($positiveOperand instanceof IntegerRangeType);
1676:
1677: $result = TypeCombinator::union(
1678: $this->integerRangeMath($range, $node, $negativeOperand),
1679: $this->integerRangeMath($range, $node, $positiveOperand),
1680: )->toNumber();
1681:
1682: if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) {
1683: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
1684: }
1685:
1686: return $result;
1687: }
1688: if (
1689: ($rangeMin < 0 || $rangeMin === null)
1690: && ($rangeMax > 0 || $rangeMax === null)
1691: ) {
1692: $negativeRange = IntegerRangeType::fromInterval($rangeMin, 0);
1693: assert($negativeRange instanceof IntegerRangeType);
1694: $positiveRange = IntegerRangeType::fromInterval(0, $rangeMax);
1695: assert($positiveRange instanceof IntegerRangeType);
1696:
1697: $result = TypeCombinator::union(
1698: $this->integerRangeMath($negativeRange, $node, $operand),
1699: $this->integerRangeMath($positiveRange, $node, $operand),
1700: )->toNumber();
1701:
1702: if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) {
1703: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
1704: }
1705:
1706: return $result;
1707: }
1708:
1709: $rangeMinSign = ($rangeMin ?? -INF) <=> 0;
1710: $rangeMaxSign = ($rangeMax ?? INF) <=> 0;
1711:
1712: $min1 = $operandMin !== null ? ($rangeMin ?? -INF) / $operandMin : $rangeMinSign * -0.1;
1713: $min2 = $operandMax !== null ? ($rangeMin ?? -INF) / $operandMax : $rangeMinSign * 0.1;
1714: $max1 = $operandMin !== null ? ($rangeMax ?? INF) / $operandMin : $rangeMaxSign * -0.1;
1715: $max2 = $operandMax !== null ? ($rangeMax ?? INF) / $operandMax : $rangeMaxSign * 0.1;
1716:
1717: $min = min($min1, $min2, $max1, $max2);
1718: $max = max($min1, $min2, $max1, $max2);
1719:
1720: if ($min === -INF) {
1721: $min = null;
1722: }
1723: if ($max === INF) {
1724: $max = null;
1725: }
1726: }
1727:
1728: if ($min !== null && $max !== null && $min > $max) {
1729: [$min, $max] = [$max, $min];
1730: }
1731:
1732: if ($operand instanceof IntegerRangeType
1733: || ($rangeMin === null || $rangeMax === null)
1734: || is_float($min)
1735: || is_float($max)
1736: ) {
1737: if (is_float($min)) {
1738: $min = (int) ceil($min);
1739: }
1740: if (is_float($max)) {
1741: $max = (int) floor($max);
1742: }
1743:
1744: // invert maximas on division with negative constants
1745: if ((($range instanceof ConstantIntegerType && $range->getValue() < 0)
1746: || ($operand instanceof ConstantIntegerType && $operand->getValue() < 0))
1747: && ($min === null || $max === null)) {
1748: [$min, $max] = [$max, $min];
1749: }
1750:
1751: if ($min === null && $max === null) {
1752: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
1753: }
1754:
1755: return TypeCombinator::union(IntegerRangeType::fromInterval($min, $max), new FloatType());
1756: }
1757: }
1758:
1759: if (is_float($min)) {
1760: $min = null;
1761: }
1762: if (is_float($max)) {
1763: $max = null;
1764: }
1765:
1766: return IntegerRangeType::fromInterval($min, $max);
1767: }
1768:
1769: /**
1770: * @param callable(Expr): Type $getTypeCallback
1771: */
1772: public function getClassConstFetchTypeByReflection(Name|Expr $class, string $constantName, ?ClassReflection $classReflection, callable $getTypeCallback): Type
1773: {
1774: $isObject = false;
1775: if ($class instanceof Name) {
1776: $constantClass = (string) $class;
1777: $constantClassType = new ObjectType($constantClass);
1778: $namesToResolve = [
1779: 'self',
1780: 'parent',
1781: ];
1782: if ($classReflection !== null) {
1783: if ($classReflection->isFinal()) {
1784: $namesToResolve[] = 'static';
1785: } elseif (strtolower($constantClass) === 'static') {
1786: if (strtolower($constantName) === 'class') {
1787: return new GenericClassStringType(new StaticType($classReflection));
1788: }
1789:
1790: $namesToResolve[] = 'static';
1791: $isObject = true;
1792: }
1793: }
1794: if (in_array(strtolower($constantClass), $namesToResolve, true)) {
1795: $resolvedName = $this->resolveName($class, $classReflection);
1796: if ($resolvedName === 'parent' && strtolower($constantName) === 'class') {
1797: return new ClassStringType();
1798: }
1799: $constantClassType = $this->resolveTypeByName($class, $classReflection);
1800: }
1801:
1802: if (strtolower($constantName) === 'class') {
1803: return new ConstantStringType($constantClassType->getClassName(), true);
1804: }
1805: } elseif ($class instanceof String_ && strtolower($constantName) === 'class') {
1806: return new ConstantStringType($class->value, true);
1807: } else {
1808: $constantClassType = $getTypeCallback($class);
1809: $isObject = true;
1810: }
1811:
1812: if (strtolower($constantName) === 'class') {
1813: return TypeTraverser::map(
1814: $constantClassType,
1815: function (Type $type, callable $traverse): Type {
1816: if ($type instanceof UnionType || $type instanceof IntersectionType) {
1817: return $traverse($type);
1818: }
1819:
1820: if ($type instanceof NullType) {
1821: return $type;
1822: }
1823:
1824: if ($type instanceof EnumCaseObjectType) {
1825: return TypeCombinator::intersect(
1826: new GenericClassStringType(new ObjectType($type->getClassName())),
1827: new AccessoryLiteralStringType(),
1828: );
1829: }
1830:
1831: $objectClassNames = $type->getObjectClassNames();
1832: if (count($objectClassNames) > 1) {
1833: throw new ShouldNotHappenException();
1834: }
1835:
1836: if ($type instanceof TemplateType && $objectClassNames === []) {
1837: return TypeCombinator::intersect(
1838: new GenericClassStringType($type),
1839: new AccessoryLiteralStringType(),
1840: );
1841: } elseif ($objectClassNames !== [] && $this->getReflectionProvider()->hasClass($objectClassNames[0])) {
1842: $reflection = $this->getReflectionProvider()->getClass($objectClassNames[0]);
1843: if ($reflection->isFinalByKeyword()) {
1844: return new ConstantStringType($reflection->getName(), true);
1845: }
1846:
1847: return TypeCombinator::intersect(
1848: new GenericClassStringType($type),
1849: new AccessoryLiteralStringType(),
1850: );
1851: } elseif ($type->isObject()->yes()) {
1852: return TypeCombinator::intersect(
1853: new ClassStringType(),
1854: new AccessoryLiteralStringType(),
1855: );
1856: }
1857:
1858: return new ErrorType();
1859: },
1860: );
1861: }
1862:
1863: $types = [];
1864: foreach ($constantClassType->getObjectClassNames() as $referencedClass) {
1865: if (!$this->getReflectionProvider()->hasClass($referencedClass)) {
1866: continue;
1867: }
1868:
1869: $constantClassReflection = $this->getReflectionProvider()->getClass($referencedClass);
1870: if (!$constantClassReflection->hasConstant($constantName)) {
1871: continue;
1872: }
1873:
1874: if ($constantClassReflection->isEnum() && $constantClassReflection->hasEnumCase($constantName)) {
1875: $types[] = new EnumCaseObjectType($constantClassReflection->getName(), $constantName);
1876: continue;
1877: }
1878:
1879: $constantReflection = $constantClassReflection->getConstant($constantName);
1880: if (
1881: $constantReflection instanceof ClassConstantReflection
1882: && $isObject
1883: && !$constantClassReflection->isFinal()
1884: && !$constantReflection->hasPhpDocType()
1885: ) {
1886: return new MixedType();
1887: }
1888:
1889: if (
1890: $isObject
1891: && (
1892: !$constantReflection instanceof ClassConstantReflection
1893: || !$constantClassReflection->isFinal()
1894: )
1895: ) {
1896: $constantType = $constantReflection->getValueType();
1897: } else {
1898: $constantType = $this->getType($constantReflection->getValueExpr(), InitializerExprContext::fromClassReflection($constantReflection->getDeclaringClass()));
1899: }
1900:
1901: $constantType = $this->constantResolver->resolveConstantType(
1902: sprintf('%s::%s', $constantClassReflection->getName(), $constantName),
1903: $constantType,
1904: );
1905: $types[] = $constantType;
1906: }
1907:
1908: if (count($types) > 0) {
1909: return TypeCombinator::union(...$types);
1910: }
1911:
1912: if (!$constantClassType->hasConstant($constantName)->yes()) {
1913: return new ErrorType();
1914: }
1915:
1916: return $constantClassType->getConstant($constantName)->getValueType();
1917: }
1918:
1919: /**
1920: * @param callable(Expr): Type $getTypeCallback
1921: */
1922: public function getClassConstFetchType(Name|Expr $class, string $constantName, ?string $className, callable $getTypeCallback): Type
1923: {
1924: $classReflection = null;
1925: if ($className !== null && $this->getReflectionProvider()->hasClass($className)) {
1926: $classReflection = $this->getReflectionProvider()->getClass($className);
1927: }
1928:
1929: return $this->getClassConstFetchTypeByReflection($class, $constantName, $classReflection, $getTypeCallback);
1930: }
1931:
1932: /**
1933: * @param callable(Expr): Type $getTypeCallback
1934: */
1935: public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type
1936: {
1937: $type = $getTypeCallback($expr)->toNumber();
1938: $scalarValues = $type->getConstantScalarValues();
1939:
1940: if (count($scalarValues) > 0) {
1941: $newTypes = [];
1942: foreach ($scalarValues as $scalarValue) {
1943: if (is_int($scalarValue)) {
1944: /** @var int|float $newValue */
1945: $newValue = -$scalarValue;
1946: if (!is_int($newValue)) {
1947: return $type;
1948: }
1949: $newTypes[] = new ConstantIntegerType($newValue);
1950: } elseif (is_float($scalarValue)) {
1951: $newTypes[] = new ConstantFloatType(-$scalarValue);
1952: }
1953: }
1954:
1955: return TypeCombinator::union(...$newTypes);
1956: }
1957:
1958: if ($type instanceof IntegerRangeType) {
1959: return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new LNumber(-1)));
1960: }
1961:
1962: return $type;
1963: }
1964:
1965: /**
1966: * @param callable(Expr): Type $getTypeCallback
1967: */
1968: public function getBitwiseNotType(Expr $expr, callable $getTypeCallback): Type
1969: {
1970: $exprType = $getTypeCallback($expr);
1971: return TypeTraverser::map($exprType, static function (Type $type, callable $traverse): Type {
1972: if ($type instanceof UnionType || $type instanceof IntersectionType) {
1973: return $traverse($type);
1974: }
1975: if ($type instanceof ConstantStringType) {
1976: return new ConstantStringType(~$type->getValue());
1977: }
1978: if ($type->isString()->yes()) {
1979: $accessories = [
1980: new StringType(),
1981: ];
1982: if ($type->isNonEmptyString()->yes()) {
1983: $accessories[] = new AccessoryNonEmptyStringType();
1984: }
1985: // it is not useful to apply numeric and literal strings here.
1986: // numeric string isn't certainly kept numeric: 3v4l.org/JERDB
1987:
1988: return TypeCombinator::intersect(...$accessories);
1989: }
1990: if ($type->isInteger()->yes() || $type->isFloat()->yes()) {
1991: return new IntegerType(); //no const types here, result depends on PHP_INT_SIZE
1992: }
1993: return new ErrorType();
1994: });
1995: }
1996:
1997: private function resolveName(Name $name, ?ClassReflection $classReflection): string
1998: {
1999: $originalClass = (string) $name;
2000: if ($classReflection !== null) {
2001: if (in_array(strtolower($originalClass), [
2002: 'self',
2003: 'static',
2004: ], true)) {
2005: return $classReflection->getName();
2006: } elseif ($originalClass === 'parent') {
2007: if ($classReflection->getParentClass() !== null) {
2008: return $classReflection->getParentClass()->getName();
2009: }
2010: }
2011: }
2012:
2013: return $originalClass;
2014: }
2015:
2016: private function resolveTypeByName(Name $name, ?ClassReflection $classReflection): TypeWithClassName
2017: {
2018: if ($name->toLowerString() === 'static' && $classReflection !== null) {
2019: return new StaticType($classReflection);
2020: }
2021:
2022: $originalClass = $this->resolveName($name, $classReflection);
2023: if ($classReflection !== null) {
2024: $thisType = new ThisType($classReflection);
2025: $ancestor = $thisType->getAncestorWithClassName($originalClass);
2026: if ($ancestor !== null) {
2027: return $ancestor;
2028: }
2029: }
2030:
2031: return new ObjectType($originalClass);
2032: }
2033:
2034: /**
2035: * @param mixed $value
2036: */
2037: private function getTypeFromValue($value): Type
2038: {
2039: return ConstantTypeHelper::getTypeFromValue($value);
2040: }
2041:
2042: private function getReflectionProvider(): ReflectionProvider
2043: {
2044: return $this->reflectionProviderProvider->getReflectionProvider();
2045: }
2046:
2047: private function getNeverType(Type $leftType, Type $rightType): Type
2048: {
2049: // make sure we don't lose the explicit flag in the process
2050: if ($leftType instanceof NeverType && $leftType->isExplicit()) {
2051: return $leftType;
2052: }
2053: if ($rightType instanceof NeverType && $rightType->isExplicit()) {
2054: return $rightType;
2055: }
2056: return new NeverType();
2057: }
2058:
2059: }
2060: