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