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