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