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