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