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