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