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