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