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