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