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