1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection;
4:
5: use Closure;
6: use Nette\Utils\Strings;
7: use PhpParser\Node\Arg;
8: use PhpParser\Node\ComplexType;
9: use PhpParser\Node\Expr;
10: use PhpParser\Node\Expr\BinaryOp;
11: use PhpParser\Node\Expr\Cast\Array_;
12: use PhpParser\Node\Expr\Cast\Bool_;
13: use PhpParser\Node\Expr\Cast\Double;
14: use PhpParser\Node\Expr\Cast\Object_;
15: use PhpParser\Node\Expr\ClassConstFetch;
16: use PhpParser\Node\Expr\ConstFetch;
17: use PhpParser\Node\Expr\FuncCall;
18: use PhpParser\Node\Expr\New_;
19: use PhpParser\Node\Expr\PropertyFetch;
20: use PhpParser\Node\Expr\Variable;
21: use PhpParser\Node\Identifier;
22: use PhpParser\Node\Name;
23: use PhpParser\Node\Param;
24: use PhpParser\Node\Scalar\Float_;
25: use PhpParser\Node\Scalar\Int_;
26: use PhpParser\Node\Scalar\MagicConst;
27: use PhpParser\Node\Scalar\MagicConst\Dir;
28: use PhpParser\Node\Scalar\MagicConst\File;
29: use PhpParser\Node\Scalar\MagicConst\Line;
30: use PhpParser\Node\Scalar\String_;
31: use PHPStan\Analyser\ConstantResolver;
32: use PHPStan\Analyser\OutOfClassScope;
33: use PHPStan\DependencyInjection\AutowiredParameter;
34: use PHPStan\DependencyInjection\AutowiredService;
35: use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
36: use PHPStan\DependencyInjection\Type\UnaryOperatorTypeSpecifyingExtensionRegistryProvider;
37: use PHPStan\Node\Expr\TypeExpr;
38: use PHPStan\Php\PhpVersion;
39: use PHPStan\PhpDoc\Tag\TemplateTag;
40: use PHPStan\Reflection\Callables\CallableParametersAcceptor;
41: use PHPStan\Reflection\Callables\SimpleImpurePoint;
42: use PHPStan\Reflection\Callables\SimpleThrowPoint;
43: use PHPStan\Reflection\Native\NativeParameterReflection;
44: use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
45: use PHPStan\ShouldNotHappenException;
46: use PHPStan\TrinaryLogic;
47: use PHPStan\Type\Accessory\AccessoryArrayListType;
48: use PHPStan\Type\Accessory\AccessoryLiteralStringType;
49: use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
50: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
51: use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
52: use PHPStan\Type\Accessory\AccessoryNumericStringType;
53: use PHPStan\Type\Accessory\AccessoryUppercaseStringType;
54: use PHPStan\Type\Accessory\HasOffsetValueType;
55: use PHPStan\Type\Accessory\NonEmptyArrayType;
56: use PHPStan\Type\ArrayType;
57: use PHPStan\Type\BenevolentUnionType;
58: use PHPStan\Type\BooleanType;
59: use PHPStan\Type\ClassStringType;
60: use PHPStan\Type\ClosureType;
61: use PHPStan\Type\Constant\ConstantArrayType;
62: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
63: use PHPStan\Type\Constant\ConstantBooleanType;
64: use PHPStan\Type\Constant\ConstantFloatType;
65: use PHPStan\Type\Constant\ConstantIntegerType;
66: use PHPStan\Type\Constant\ConstantStringType;
67: use PHPStan\Type\Constant\OversizedArrayBuilder;
68: use PHPStan\Type\ConstantScalarType;
69: use PHPStan\Type\ConstantTypeHelper;
70: use PHPStan\Type\Enum\EnumCaseObjectType;
71: use PHPStan\Type\ErrorType;
72: use PHPStan\Type\FloatType;
73: use PHPStan\Type\GeneralizePrecision;
74: use PHPStan\Type\Generic\GenericClassStringType;
75: use PHPStan\Type\Generic\TemplateType;
76: use PHPStan\Type\Generic\TemplateTypeMap;
77: use PHPStan\Type\Generic\TemplateTypeVarianceMap;
78: use PHPStan\Type\IntegerRangeType;
79: use PHPStan\Type\IntegerType;
80: use PHPStan\Type\IntersectionType;
81: use PHPStan\Type\MixedType;
82: use PHPStan\Type\NeverType;
83: use PHPStan\Type\NonexistentParentClassType;
84: use PHPStan\Type\NullType;
85: use PHPStan\Type\ObjectShapeType;
86: use PHPStan\Type\ObjectType;
87: use PHPStan\Type\ObjectWithoutClassType;
88: use PHPStan\Type\ParserNodeTypeToPHPStanType;
89: use PHPStan\Type\StaticType;
90: use PHPStan\Type\StringType;
91: use PHPStan\Type\ThisType;
92: use PHPStan\Type\Type;
93: use PHPStan\Type\TypeCombinator;
94: use PHPStan\Type\TypehintHelper;
95: use PHPStan\Type\TypeResult;
96: use PHPStan\Type\TypeUtils;
97: use PHPStan\Type\TypeWithClassName;
98: use PHPStan\Type\UnionType;
99: use stdClass;
100: use Throwable;
101: use function array_key_exists;
102: use function array_keys;
103: use function array_map;
104: use function array_merge;
105: use function array_values;
106: use function assert;
107: use function ceil;
108: use function count;
109: use function dirname;
110: use function floor;
111: use function in_array;
112: use function intval;
113: use function is_finite;
114: use function is_float;
115: use function is_int;
116: use function is_numeric;
117: use function is_string;
118: use function max;
119: use function min;
120: use function sprintf;
121: use function str_starts_with;
122: use function strtolower;
123: use const INF;
124:
125: #[AutowiredService]
126: final class InitializerExprTypeResolver
127: {
128:
129: public const CALCULATE_SCALARS_LIMIT = 128;
130:
131: /** @var array<string, true> */
132: private array $currentlyResolvingClassConstant = [];
133:
134: /** @var array<string, Type> */
135: private array $classConstantValueTypeCache = [];
136:
137: public function __construct(
138: private ConstantResolver $constantResolver,
139: private ReflectionProviderProvider $reflectionProviderProvider,
140: private PhpVersion $phpVersion,
141: private OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider,
142: private UnaryOperatorTypeSpecifyingExtensionRegistryProvider $unaryOperatorTypeSpecifyingExtensionRegistryProvider,
143: private OversizedArrayBuilder $oversizedArrayBuilder,
144: #[AutowiredParameter]
145: private bool $usePathConstantsAsConstantString,
146: )
147: {
148: }
149:
150: /** @api */
151: public function getType(Expr $expr, InitializerExprContext $context): Type
152: {
153: if ($expr instanceof TypeExpr) {
154: return $expr->getExprType();
155: }
156: if ($expr instanceof Int_) {
157: return new ConstantIntegerType($expr->value);
158: }
159: if ($expr instanceof Float_) {
160: return new ConstantFloatType($expr->value);
161: }
162: if ($expr instanceof String_) {
163: return new ConstantStringType($expr->value);
164: }
165: if ($expr instanceof ConstFetch) {
166: $constName = (string) $expr->name;
167: $loweredConstName = strtolower($constName);
168: if ($loweredConstName === 'true') {
169: return new ConstantBooleanType(true);
170: } elseif ($loweredConstName === 'false') {
171: return new ConstantBooleanType(false);
172: } elseif ($loweredConstName === 'null') {
173: return new NullType();
174: }
175:
176: $constant = $this->constantResolver->resolveConstant($expr->name, $context);
177: if ($constant !== null) {
178: return $constant;
179: }
180:
181: return new ErrorType();
182: }
183: if ($expr instanceof File) {
184: $file = $context->getFile();
185: if ($file === null) {
186: return new StringType();
187: }
188: $stringType = new ConstantStringType($file);
189: return $this->usePathConstantsAsConstantString ? $stringType : $stringType->generalize(GeneralizePrecision::moreSpecific());
190: }
191: if ($expr instanceof Dir) {
192: $file = $context->getFile();
193: if ($file === null) {
194: return new StringType();
195: }
196: $stringType = new ConstantStringType(dirname($file));
197: return $this->usePathConstantsAsConstantString ? $stringType : $stringType->generalize(GeneralizePrecision::moreSpecific());
198: }
199: if ($expr instanceof Line) {
200: return new ConstantIntegerType($expr->getStartLine());
201: }
202: if ($expr instanceof Expr\New_) {
203: if ($expr->class instanceof Name) {
204: return new ObjectType((string) $expr->class);
205: }
206:
207: return new ObjectWithoutClassType();
208: }
209: if ($expr instanceof Expr\Array_) {
210: return $this->getArrayType($expr, fn (Expr $expr): Type => $this->getType($expr, $context));
211: }
212: if ($expr instanceof Expr\Cast) {
213: return $this->getCastType($expr, fn (Expr $expr): Type => $this->getType($expr, $context));
214: }
215: if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) {
216: return $this->getFirstClassCallableType($expr, $context, false);
217: }
218: if ($expr instanceof Expr\Closure && $expr->static) {
219: $parameters = [];
220: $isVariadic = false;
221: $firstOptionalParameterIndex = null;
222: foreach ($expr->params as $i => $param) {
223: $isOptionalCandidate = $param->default !== null || $param->variadic;
224:
225: if ($isOptionalCandidate) {
226: if ($firstOptionalParameterIndex === null) {
227: $firstOptionalParameterIndex = $i;
228: }
229: } else {
230: $firstOptionalParameterIndex = null;
231: }
232: }
233:
234: foreach ($expr->params as $i => $param) {
235: if ($param->variadic) {
236: $isVariadic = true;
237: }
238: if (!$param->var instanceof Variable || !is_string($param->var->name)) {
239: throw new ShouldNotHappenException();
240: }
241: $parameters[] = new NativeParameterReflection(
242: $param->var->name,
243: $firstOptionalParameterIndex !== null && $i >= $firstOptionalParameterIndex,
244: $this->getFunctionType($param->type, $this->isParameterValueNullable($param), false, $context),
245: $param->byRef
246: ? PassedByReference::createCreatesNewVariable()
247: : PassedByReference::createNo(),
248: $param->variadic,
249: $param->default !== null ? $this->getType($param->default, $context) : null,
250: );
251: }
252:
253: $returnType = new MixedType(false);
254: if ($expr->returnType !== null) {
255: $returnType = $this->getFunctionType($expr->returnType, false, false, $context);
256: }
257:
258: return new ClosureType(
259: $parameters,
260: $returnType,
261: $isVariadic,
262: TemplateTypeMap::createEmpty(),
263: TemplateTypeMap::createEmpty(),
264: TemplateTypeVarianceMap::createEmpty(),
265: acceptsNamedArguments: TrinaryLogic::createYes(),
266: );
267: }
268: if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
269: $var = $this->getType($expr->var, $context);
270: $dim = $this->getType($expr->dim, $context);
271: return $var->getOffsetValueType($dim);
272: }
273: if ($expr instanceof ClassConstFetch && $expr->name instanceof Identifier) {
274: return $this->getClassConstFetchType($expr->class, $expr->name->toString(), $context->getClassName(), fn (Expr $expr): Type => $this->getType($expr, $context));
275: }
276: if ($expr instanceof Expr\UnaryPlus) {
277: return $this->getUnaryPlusType($expr->expr, fn (Expr $expr): Type => $this->getType($expr, $context));
278: }
279: if ($expr instanceof Expr\UnaryMinus) {
280: return $this->getUnaryMinusType($expr->expr, fn (Expr $expr): Type => $this->getType($expr, $context));
281: }
282: if ($expr instanceof Expr\BinaryOp\Coalesce) {
283: $leftType = $this->getType($expr->left, $context);
284: $rightType = $this->getType($expr->right, $context);
285:
286: return TypeCombinator::union(TypeCombinator::removeNull($leftType), $rightType);
287: }
288:
289: if ($expr instanceof Expr\Ternary) {
290: $elseType = $this->getType($expr->else, $context);
291: if ($expr->if === null) {
292: $condType = $this->getType($expr->cond, $context);
293:
294: return TypeCombinator::union(
295: TypeCombinator::removeFalsey($condType),
296: $elseType,
297: );
298: }
299:
300: $ifType = $this->getType($expr->if, $context);
301:
302: return TypeCombinator::union(
303: TypeCombinator::removeFalsey($ifType),
304: $elseType,
305: );
306: }
307:
308: if ($expr instanceof Expr\FuncCall && $expr->name instanceof Name && $expr->name->toLowerString() === 'constant') {
309: $firstArg = $expr->args[0] ?? null;
310: if ($firstArg instanceof Arg && $firstArg->value instanceof String_) {
311: $constant = $this->constantResolver->resolvePredefinedConstant($firstArg->value->value);
312: if ($constant !== null) {
313: return $constant;
314: }
315: }
316: }
317:
318: if ($expr instanceof Expr\BooleanNot) {
319: $exprBooleanType = $this->getType($expr->expr, $context)->toBoolean();
320:
321: if ($exprBooleanType instanceof ConstantBooleanType) {
322: return new ConstantBooleanType(!$exprBooleanType->getValue());
323: }
324:
325: return new BooleanType();
326: }
327:
328: if ($expr instanceof Expr\BitwiseNot) {
329: return $this->getBitwiseNotType($expr->expr, fn (Expr $expr): Type => $this->getType($expr, $context));
330: }
331:
332: if ($expr instanceof Expr\BinaryOp\Concat) {
333: return $this->getConcatType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
334: }
335:
336: if ($expr instanceof Expr\BinaryOp\BitwiseAnd) {
337: return $this->getBitwiseAndType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
338: }
339:
340: if ($expr instanceof Expr\BinaryOp\BitwiseOr) {
341: return $this->getBitwiseOrType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
342: }
343:
344: if ($expr instanceof Expr\BinaryOp\BitwiseXor) {
345: return $this->getBitwiseXorType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
346: }
347:
348: if ($expr instanceof Expr\BinaryOp\Spaceship) {
349: return $this->getSpaceshipType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
350: }
351:
352: if (
353: $expr instanceof Expr\BinaryOp\BooleanAnd
354: || $expr instanceof Expr\BinaryOp\LogicalAnd
355: || $expr instanceof Expr\BinaryOp\BooleanOr
356: || $expr instanceof Expr\BinaryOp\LogicalOr
357: ) {
358: return new BooleanType();
359: }
360:
361: if ($expr instanceof Expr\BinaryOp\Div) {
362: return $this->getDivType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
363: }
364:
365: if ($expr instanceof Expr\BinaryOp\Mod) {
366: return $this->getModType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
367: }
368:
369: if ($expr instanceof Expr\BinaryOp\Plus) {
370: return $this->getPlusType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
371: }
372:
373: if ($expr instanceof Expr\BinaryOp\Minus) {
374: return $this->getMinusType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
375: }
376:
377: if ($expr instanceof Expr\BinaryOp\Mul) {
378: return $this->getMulType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
379: }
380:
381: if ($expr instanceof Expr\BinaryOp\Pow) {
382: return $this->getPowType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
383: }
384:
385: if ($expr instanceof Expr\BinaryOp\ShiftLeft) {
386: return $this->getShiftLeftType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
387: }
388:
389: if ($expr instanceof Expr\BinaryOp\ShiftRight) {
390: return $this->getShiftRightType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
391: }
392:
393: if ($expr instanceof BinaryOp\Identical) {
394: return $this->resolveIdenticalType(
395: $this->getType($expr->left, $context),
396: $this->getType($expr->right, $context),
397: )->type;
398: }
399:
400: if ($expr instanceof BinaryOp\NotIdentical) {
401: return $this->getType(new Expr\BooleanNot(new BinaryOp\Identical($expr->left, $expr->right)), $context);
402: }
403:
404: if ($expr instanceof BinaryOp\Equal) {
405: return $this->resolveEqualType(
406: $this->getType($expr->left, $context),
407: $this->getType($expr->right, $context),
408: )->type;
409: }
410:
411: if ($expr instanceof BinaryOp\NotEqual) {
412: return $this->getType(new Expr\BooleanNot(new BinaryOp\Equal($expr->left, $expr->right)), $context);
413: }
414:
415: if ($expr instanceof Expr\BinaryOp\Smaller) {
416: return $this->getType($expr->left, $context)->isSmallerThan($this->getType($expr->right, $context), $this->phpVersion)->toBooleanType();
417: }
418:
419: if ($expr instanceof Expr\BinaryOp\SmallerOrEqual) {
420: return $this->getType($expr->left, $context)->isSmallerThanOrEqual($this->getType($expr->right, $context), $this->phpVersion)->toBooleanType();
421: }
422:
423: if ($expr instanceof Expr\BinaryOp\Greater) {
424: return $this->getType($expr->right, $context)->isSmallerThan($this->getType($expr->left, $context), $this->phpVersion)->toBooleanType();
425: }
426:
427: if ($expr instanceof Expr\BinaryOp\GreaterOrEqual) {
428: return $this->getType($expr->right, $context)->isSmallerThanOrEqual($this->getType($expr->left, $context), $this->phpVersion)->toBooleanType();
429: }
430:
431: if ($expr instanceof Expr\BinaryOp\LogicalXor) {
432: $leftBooleanType = $this->getType($expr->left, $context)->toBoolean();
433: $rightBooleanType = $this->getType($expr->right, $context)->toBoolean();
434:
435: if (
436: $leftBooleanType instanceof ConstantBooleanType
437: && $rightBooleanType instanceof ConstantBooleanType
438: ) {
439: return new ConstantBooleanType(
440: $leftBooleanType->getValue() xor $rightBooleanType->getValue(),
441: );
442: }
443:
444: return new BooleanType();
445: }
446:
447: if ($expr instanceof MagicConst\Class_) {
448: if ($context->getTraitName() !== null) {
449: return new IntersectionType([
450: new ClassStringType(),
451: new AccessoryLiteralStringType(),
452: ]);
453: }
454:
455: if ($context->getClassName() === null) {
456: return new ConstantStringType('');
457: }
458:
459: return new ConstantStringType($context->getClassName(), true);
460: }
461:
462: if ($expr instanceof MagicConst\Namespace_) {
463: if ($context->getTraitName() !== null) {
464: return new IntersectionType([
465: new StringType(),
466: new AccessoryLiteralStringType(),
467: ]);
468: }
469:
470: return new ConstantStringType($context->getNamespace() ?? '');
471: }
472:
473: if ($expr instanceof MagicConst\Method) {
474: return new ConstantStringType($context->getMethod() ?? '');
475: }
476:
477: if ($expr instanceof MagicConst\Function_) {
478: return new ConstantStringType($context->getFunction() ?? '');
479: }
480:
481: if ($expr instanceof MagicConst\Trait_) {
482: if ($context->getTraitName() === null) {
483: return new ConstantStringType('');
484: }
485:
486: return new ConstantStringType($context->getTraitName(), true);
487: }
488:
489: if ($expr instanceof MagicConst\Property) {
490: $contextProperty = $context->getProperty();
491: if ($contextProperty === null) {
492: return new ConstantStringType('');
493: }
494:
495: return new ConstantStringType($contextProperty);
496: }
497:
498: if ($expr instanceof PropertyFetch && $expr->name instanceof Identifier) {
499: $fetchedOnType = $this->getType($expr->var, $context);
500: if (!$fetchedOnType->hasInstanceProperty($expr->name->name)->yes()) {
501: return new ErrorType();
502: }
503:
504: return $fetchedOnType->getInstanceProperty($expr->name->name, new OutOfClassScope())->getReadableType();
505: }
506:
507: return new MixedType();
508: }
509:
510: /**
511: * @param callable(Expr): Type $getTypeCallback
512: */
513: public function getConcatType(Expr $left, Expr $right, callable $getTypeCallback): Type
514: {
515: $leftType = $getTypeCallback($left);
516: $rightType = $getTypeCallback($right);
517:
518: return $this->resolveConcatType($leftType, $rightType);
519: }
520:
521: public function resolveConcatType(Type $left, Type $right): Type
522: {
523: $leftStringType = $left->toString();
524: $rightStringType = $right->toString();
525: if (TypeCombinator::union(
526: $leftStringType,
527: $rightStringType,
528: ) instanceof ErrorType) {
529: return new ErrorType();
530: }
531:
532: if ($leftStringType instanceof ConstantStringType && $leftStringType->getValue() === '') {
533: return $rightStringType;
534: }
535:
536: if ($rightStringType instanceof ConstantStringType && $rightStringType->getValue() === '') {
537: return $leftStringType;
538: }
539:
540: if ($leftStringType instanceof ConstantStringType && $rightStringType instanceof ConstantStringType) {
541: return $leftStringType->append($rightStringType);
542: }
543:
544: $leftConstantStrings = $leftStringType->getConstantStrings();
545: $rightConstantStrings = $rightStringType->getConstantStrings();
546: $combinedConstantStringsCount = count($leftConstantStrings) * count($rightConstantStrings);
547:
548: // we limit the number of union-types for performance reasons
549: if ($combinedConstantStringsCount > 0 && $combinedConstantStringsCount <= self::CALCULATE_SCALARS_LIMIT) {
550: $strings = [];
551:
552: foreach ($leftConstantStrings as $leftConstantString) {
553: if ($leftConstantString->getValue() === '') {
554: $strings = array_merge($strings, $rightConstantStrings);
555:
556: continue;
557: }
558:
559: foreach ($rightConstantStrings as $rightConstantString) {
560: if ($rightConstantString->getValue() === '') {
561: $strings[] = $leftConstantString;
562:
563: continue;
564: }
565:
566: $strings[] = $leftConstantString->append($rightConstantString);
567: }
568: }
569:
570: if (count($strings) > 0) {
571: return TypeCombinator::union(...$strings);
572: }
573: }
574:
575: $accessoryTypes = [];
576: if ($leftStringType->isNonEmptyString()->and($rightStringType->isNonEmptyString())->yes()) {
577: $accessoryTypes[] = new AccessoryNonFalsyStringType();
578: } elseif ($leftStringType->isNonFalsyString()->or($rightStringType->isNonFalsyString())->yes()) {
579: $accessoryTypes[] = new AccessoryNonFalsyStringType();
580: } elseif ($leftStringType->isNonEmptyString()->or($rightStringType->isNonEmptyString())->yes()) {
581: $accessoryTypes[] = new AccessoryNonEmptyStringType();
582: }
583:
584: if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) {
585: $accessoryTypes[] = new AccessoryLiteralStringType();
586: }
587:
588: if ($leftStringType->isLowercaseString()->and($rightStringType->isLowercaseString())->yes()) {
589: $accessoryTypes[] = new AccessoryLowercaseStringType();
590: }
591:
592: if ($leftStringType->isUppercaseString()->and($rightStringType->isUppercaseString())->yes()) {
593: $accessoryTypes[] = new AccessoryUppercaseStringType();
594: }
595:
596: $leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType(''));
597: if ($leftNumericStringNonEmpty->isNumericString()->yes()) {
598: $validationCallback = $left->isInteger()->yes()
599: ? static fn (string $value): bool => !str_starts_with($value, '-')
600: : static fn (string $value): bool => Strings::match($value, '#^\d+$#') !== null;
601:
602: $allRightConstantsZeroOrMore = false;
603: foreach ($rightConstantStrings as $rightConstantString) {
604: if ($rightConstantString->getValue() === '') {
605: continue;
606: }
607:
608: if (
609: !is_numeric($rightConstantString->getValue())
610: || !$validationCallback($rightConstantString->getValue())
611: ) {
612: $allRightConstantsZeroOrMore = false;
613: break;
614: }
615:
616: $allRightConstantsZeroOrMore = true;
617: }
618:
619: $zeroOrMoreInteger = IntegerRangeType::fromInterval(0, null);
620: $nonNegativeRight = $allRightConstantsZeroOrMore || $zeroOrMoreInteger->isSuperTypeOf($right)->yes();
621: if ($nonNegativeRight) {
622: $accessoryTypes[] = new AccessoryNumericStringType();
623: }
624: }
625:
626: if (count($accessoryTypes) > 0) {
627: $accessoryTypes[] = new StringType();
628: return new IntersectionType($accessoryTypes);
629: }
630:
631: return new StringType();
632: }
633:
634: /**
635: * @param callable(Expr): Type $getTypeCallback
636: */
637: public function getArrayType(Expr\Array_ $expr, callable $getTypeCallback): Type
638: {
639: if (count($expr->items) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
640: return $this->oversizedArrayBuilder->build($expr, $getTypeCallback);
641: }
642:
643: $arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
644: $isList = null;
645: $hasOffsetValueTypes = [];
646: foreach ($expr->items as $arrayItem) {
647: $valueType = $getTypeCallback($arrayItem->value);
648: if ($arrayItem->unpack) {
649: $constantArrays = $valueType->getConstantArrays();
650: if (count($constantArrays) === 1) {
651: $constantArrayType = $constantArrays[0];
652:
653: $hasStringKey = false;
654: if ($this->phpVersion->supportsArrayUnpackingWithStringKeys()) {
655: foreach ($constantArrayType->getKeyTypes() as $keyType) {
656: if ($keyType->isString()->yes()) {
657: $hasStringKey = true;
658: break;
659: }
660: }
661: }
662:
663: foreach ($constantArrayType->getValueTypes() as $i => $innerValueType) {
664: if ($hasStringKey) {
665: $arrayBuilder->setOffsetValueType($constantArrayType->getKeyTypes()[$i], $innerValueType, $constantArrayType->isOptionalKey($i));
666: if (!$constantArrayType->isOptionalKey($i)) {
667: $hasOffsetValueTypes[$constantArrayType->getKeyTypes()[$i]->getValue()] = new HasOffsetValueType($constantArrayType->getKeyTypes()[$i], $innerValueType);
668: }
669: } else {
670: $arrayBuilder->setOffsetValueType(null, $innerValueType, $constantArrayType->isOptionalKey($i));
671: }
672: }
673: } else {
674: $arrayBuilder->degradeToGeneralArray();
675:
676: if ($this->phpVersion->supportsArrayUnpackingWithStringKeys() && !$valueType->getIterableKeyType()->isString()->no()) {
677: $isList = false;
678: $offsetType = $valueType->getIterableKeyType();
679:
680: foreach ($hasOffsetValueTypes as $key => $hasOffsetValueType) {
681: if (!$offsetType->isSuperTypeOf($hasOffsetValueType->getOffsetType())->yes()) {
682: continue;
683: }
684:
685: unset($hasOffsetValueTypes[$key]);
686: }
687: } else {
688: $isList ??= $arrayBuilder->isList();
689: $offsetType = new IntegerType();
690: }
691:
692: $arrayBuilder->setOffsetValueType($offsetType, $valueType->getIterableValueType(), !$valueType->isIterableAtLeastOnce()->yes());
693: }
694: } else {
695: $arrayBuilder->setOffsetValueType(
696: $arrayItem->key !== null ? $getTypeCallback($arrayItem->key) : null,
697: $valueType,
698: );
699: }
700: }
701:
702: $arrayType = $arrayBuilder->getArray();
703: if ($isList === true) {
704: $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType());
705: }
706:
707: if (count($hasOffsetValueTypes) > 0 && !$arrayType->isConstantArray()->yes()) {
708: $arrayType = TypeCombinator::intersect($arrayType, ...array_values($hasOffsetValueTypes));
709: }
710:
711: return $arrayType;
712: }
713:
714: /**
715: * @param callable(Expr): Type $getTypeCallback
716: */
717: public function getCastType(Expr\Cast $expr, callable $getTypeCallback): Type
718: {
719: if ($expr instanceof \PhpParser\Node\Expr\Cast\Int_) {
720: return $getTypeCallback($expr->expr)->toInteger();
721: }
722: if ($expr instanceof Bool_) {
723: return $getTypeCallback($expr->expr)->toBoolean();
724: }
725: if ($expr instanceof Double) {
726: return $getTypeCallback($expr->expr)->toFloat();
727: }
728: if ($expr instanceof \PhpParser\Node\Expr\Cast\String_) {
729: return $getTypeCallback($expr->expr)->toString();
730: }
731: if ($expr instanceof Array_) {
732: return $getTypeCallback($expr->expr)->toArray();
733: }
734: if ($expr instanceof Object_) {
735: return $this->getCastObjectType($getTypeCallback($expr->expr));
736: }
737:
738: return new MixedType();
739: }
740:
741: public function getCastObjectType(Type $exprType): Type
742: {
743: $castToObject = static function (Type $type): Type {
744: $constantArrays = $type->getConstantArrays();
745: if (count($constantArrays) > 0) {
746: $objects = [];
747: foreach ($constantArrays as $constantArray) {
748: $properties = [];
749: $optionalProperties = [];
750: foreach ($constantArray->getKeyTypes() as $i => $keyType) {
751: $valueType = $constantArray->getValueTypes()[$i];
752: $optional = $constantArray->isOptionalKey($i);
753: if ($optional) {
754: $optionalProperties[] = $keyType->getValue();
755: }
756: $properties[$keyType->getValue()] = $valueType;
757: }
758:
759: $objects[] = new IntersectionType([new ObjectShapeType($properties, $optionalProperties), new ObjectType(stdClass::class)]);
760: }
761:
762: return TypeCombinator::union(...$objects);
763: }
764: if ($type->isObject()->yes()) {
765: return $type;
766: }
767:
768: return new ObjectType('stdClass');
769: };
770:
771: if ($exprType instanceof UnionType) {
772: return TypeCombinator::union(...array_map($castToObject, $exprType->getTypes()));
773: }
774:
775: return $castToObject($exprType);
776: }
777:
778: /**
779: * @param Name|Identifier|ComplexType|null $type
780: */
781: public function getFunctionType($type, bool $isNullable, bool $isVariadic, InitializerExprContext $context): Type
782: {
783: if ($isNullable) {
784: return TypeCombinator::addNull(
785: $this->getFunctionType($type, false, $isVariadic, $context),
786: );
787: }
788: if ($isVariadic) {
789: if (!$this->phpVersion->supportsNamedArguments()) {
790: return new ArrayType(new UnionType([new IntegerType(), new StringType()]), $this->getFunctionType(
791: $type,
792: false,
793: false,
794: $context,
795: ));
796: }
797:
798: return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $this->getFunctionType(
799: $type,
800: false,
801: false,
802: $context,
803: )), new AccessoryArrayListType()]);
804: }
805:
806: if ($type instanceof Name) {
807: $className = (string) $type;
808: $lowercasedClassName = strtolower($className);
809: if ($lowercasedClassName === 'parent') {
810: $classReflection = null;
811: if ($context->getClassName() !== null && $this->getReflectionProvider()->hasClass($context->getClassName())) {
812: $classReflection = $this->getReflectionProvider()->getClass($context->getClassName());
813: }
814: if ($classReflection !== null && $classReflection->getParentClass() !== null) {
815: return new ObjectType($classReflection->getParentClass()->getName());
816: }
817:
818: return new NonexistentParentClassType();
819: }
820: }
821:
822: $classReflection = null;
823: if ($context->getClassName() !== null && $this->getReflectionProvider()->hasClass($context->getClassName())) {
824: $classReflection = $this->getReflectionProvider()->getClass($context->getClassName());
825: }
826:
827: return ParserNodeTypeToPHPStanType::resolve($type, $classReflection);
828: }
829:
830: private function isParameterValueNullable(Param $parameter): bool
831: {
832: if ($parameter->default instanceof ConstFetch) {
833: return strtolower((string) $parameter->default->name) === 'null';
834: }
835:
836: return false;
837: }
838:
839: public function getFirstClassCallableType(Expr\CallLike $expr, InitializerExprContext $context, bool $nativeTypesPromoted): Type
840: {
841: if ($expr instanceof FuncCall) {
842: if ($expr->name instanceof Name) {
843: if ($this->getReflectionProvider()->hasFunction($expr->name, $context)) {
844: $function = $this->getReflectionProvider()->getFunction($expr->name, $context);
845: return $this->createFirstClassCallable(
846: $function,
847: $function->getVariants(),
848: $nativeTypesPromoted,
849: );
850: }
851:
852: return new ObjectType(Closure::class);
853: }
854: }
855:
856: if ($expr instanceof Expr\StaticCall) {
857: if (!$expr->class instanceof Name) {
858: return new ObjectType(Closure::class);
859: }
860:
861: if (!$expr->name instanceof Identifier) {
862: return new ObjectType(Closure::class);
863: }
864:
865: $classReflection = null;
866: if ($context->getClassName() !== null && $this->getReflectionProvider()->hasClass($context->getClassName())) {
867: $classReflection = $this->getReflectionProvider()->getClass($context->getClassName());
868: }
869:
870: $classType = $this->resolveTypeByName($expr->class, $classReflection);
871: $methodName = $expr->name->toString();
872: if (!$classType->hasMethod($methodName)->yes()) {
873: return new ObjectType(Closure::class);
874: }
875:
876: $method = $classType->getMethod($methodName, new OutOfClassScope());
877: $classType = $this->resolveTypeByNameWithLateStaticBinding($expr->class, $classType, $method);
878: if (!$classType->hasMethod($methodName)->yes()) {
879: return new ObjectType(Closure::class);
880: }
881: $method = $classType->getMethod($methodName, new OutOfClassScope());
882:
883: return $this->createFirstClassCallable(
884: $method,
885: $method->getVariants(),
886: $nativeTypesPromoted,
887: );
888: }
889:
890: if ($expr instanceof New_) {
891: return new ErrorType();
892: }
893:
894: throw new ShouldNotHappenException();
895: }
896:
897: /**
898: * @param ParametersAcceptor[] $variants
899: */
900: public function createFirstClassCallable(
901: FunctionReflection|ExtendedMethodReflection|null $function,
902: array $variants,
903: bool $nativeTypesPromoted,
904: ): Type
905: {
906: $closureTypes = [];
907:
908: foreach ($variants as $variant) {
909: $returnType = $variant->getReturnType();
910: if ($variant instanceof ExtendedParametersAcceptor) {
911: $returnType = $nativeTypesPromoted ? $variant->getNativeReturnType() : $returnType;
912: }
913:
914: $templateTags = [];
915: foreach ($variant->getTemplateTypeMap()->getTypes() as $templateType) {
916: if (!$templateType instanceof TemplateType) {
917: continue;
918: }
919: $templateTags[$templateType->getName()] = new TemplateTag(
920: $templateType->getName(),
921: $templateType->getBound(),
922: $templateType->getDefault(),
923: $templateType->getVariance(),
924: );
925: }
926:
927: $throwPoints = [];
928: $impurePoints = [];
929: $acceptsNamedArguments = TrinaryLogic::createYes();
930: $mustUseReturnValue = TrinaryLogic::createMaybe();
931: if ($variant instanceof CallableParametersAcceptor) {
932: $throwPoints = $variant->getThrowPoints();
933: $impurePoints = $variant->getImpurePoints();
934: $acceptsNamedArguments = $variant->acceptsNamedArguments();
935: $mustUseReturnValue = $variant->mustUseReturnValue();
936: } elseif ($function !== null) {
937: $returnTypeForThrow = $variant->getReturnType();
938: $throwType = $function->getThrowType();
939: if ($throwType === null) {
940: if ($returnTypeForThrow instanceof NeverType && $returnTypeForThrow->isExplicit()) {
941: $throwType = new ObjectType(Throwable::class);
942: }
943: }
944:
945: if ($throwType !== null) {
946: if (!$throwType->isVoid()->yes()) {
947: $throwPoints[] = SimpleThrowPoint::createExplicit($throwType, true);
948: }
949: } else {
950: if (!(new ObjectType(Throwable::class))->isSuperTypeOf($returnTypeForThrow)->yes()) {
951: $throwPoints[] = SimpleThrowPoint::createImplicit();
952: }
953: }
954:
955: $impurePoint = SimpleImpurePoint::createFromVariant($function, $variant);
956: if ($impurePoint !== null) {
957: $impurePoints[] = $impurePoint;
958: }
959:
960: $acceptsNamedArguments = $function->acceptsNamedArguments();
961: $mustUseReturnValue = $function->mustUseReturnValue();
962: }
963:
964: $parameters = $variant->getParameters();
965: $assertions = $function !== null ? $function->getAsserts() : Assertions::createEmpty();
966: $closureTypes[] = new ClosureType(
967: $parameters,
968: $returnType,
969: $variant->isVariadic(),
970: $variant->getTemplateTypeMap(),
971: $variant->getResolvedTemplateTypeMap(),
972: $variant instanceof ExtendedParametersAcceptor ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
973: $templateTags,
974: $throwPoints,
975: $impurePoints,
976: acceptsNamedArguments: $acceptsNamedArguments,
977: mustUseReturnValue: $mustUseReturnValue,
978: assertions: $assertions,
979: );
980: }
981:
982: return TypeCombinator::union(...$closureTypes);
983: }
984:
985: /**
986: * @param callable(Expr): Type $getTypeCallback
987: */
988: public function getBitwiseAndType(Expr $left, Expr $right, callable $getTypeCallback): Type
989: {
990: $leftType = $getTypeCallback($left);
991: $rightType = $getTypeCallback($right);
992:
993: $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
994: ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseAnd($left, $right), $leftType, $rightType);
995: if ($specifiedTypes !== null) {
996: return $specifiedTypes;
997: }
998:
999: return $this->getBitwiseAndTypeFromTypes($leftType, $rightType);
1000: }
1001:
1002: public function getBitwiseAndTypeFromTypes(Type $leftType, Type $rightType): Type
1003: {
1004: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1005: return $this->getNeverType($leftType, $rightType);
1006: }
1007:
1008: $result = $this->getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a & $b);
1009: if ($result instanceof Type) {
1010: return $result;
1011: } elseif ($result === self::IS_SCALAR_TYPE) {
1012: $leftType = $this->optimizeScalarType($leftType);
1013: $rightType = $this->optimizeScalarType($rightType);
1014: }
1015:
1016: if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1017: return new BenevolentUnionType([new IntegerType(), new StringType()]);
1018: }
1019:
1020: $leftIsString = $leftType->isString();
1021: $rightIsString = $rightType->isString();
1022: if (
1023: ($leftIsString->yes() || $leftType instanceof MixedType)
1024: && ($rightIsString->yes() || $rightType instanceof MixedType)
1025: ) {
1026: return new StringType();
1027: }
1028: if ($leftIsString->maybe() && $rightIsString->maybe()) {
1029: return new ErrorType();
1030: }
1031:
1032: $leftNumberType = $leftType->toNumber();
1033: $rightNumberType = $rightType->toNumber();
1034:
1035: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1036: return new ErrorType();
1037: }
1038:
1039: if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() >= 0) {
1040: return IntegerRangeType::fromInterval(0, $rightNumberType->getValue());
1041: }
1042: if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() >= 0) {
1043: return IntegerRangeType::fromInterval(0, $leftNumberType->getValue());
1044: }
1045:
1046: return new IntegerType();
1047: }
1048:
1049: /**
1050: * @param callable(Expr): Type $getTypeCallback
1051: */
1052: public function getBitwiseOrType(Expr $left, Expr $right, callable $getTypeCallback): Type
1053: {
1054: $leftType = $getTypeCallback($left);
1055: $rightType = $getTypeCallback($right);
1056:
1057: $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
1058: ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseOr($left, $right), $leftType, $rightType);
1059: if ($specifiedTypes !== null) {
1060: return $specifiedTypes;
1061: }
1062:
1063: return $this->getBitwiseOrTypeFromTypes($leftType, $rightType);
1064: }
1065:
1066: public function getBitwiseOrTypeFromTypes(Type $leftType, Type $rightType): Type
1067: {
1068: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1069: return $this->getNeverType($leftType, $rightType);
1070: }
1071:
1072: $result = $this->getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a | $b);
1073: if ($result instanceof Type) {
1074: return $result;
1075: } elseif ($result === self::IS_SCALAR_TYPE) {
1076: $leftType = $this->optimizeScalarType($leftType);
1077: $rightType = $this->optimizeScalarType($rightType);
1078: }
1079:
1080: if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1081: return new BenevolentUnionType([new IntegerType(), new StringType()]);
1082: }
1083:
1084: $leftIsString = $leftType->isString();
1085: $rightIsString = $rightType->isString();
1086: if (
1087: ($leftIsString->yes() || $leftType instanceof MixedType)
1088: && ($rightIsString->yes() || $rightType instanceof MixedType)
1089: ) {
1090: return new StringType();
1091: }
1092: if ($leftIsString->maybe() && $rightIsString->maybe()) {
1093: return new ErrorType();
1094: }
1095:
1096: if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) {
1097: return new ErrorType();
1098: }
1099:
1100: return new IntegerType();
1101: }
1102:
1103: /**
1104: * @param callable(Expr): Type $getTypeCallback
1105: */
1106: public function getBitwiseXorType(Expr $left, Expr $right, callable $getTypeCallback): Type
1107: {
1108: $leftType = $getTypeCallback($left);
1109: $rightType = $getTypeCallback($right);
1110:
1111: $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
1112: ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseXor($left, $right), $leftType, $rightType);
1113: if ($specifiedTypes !== null) {
1114: return $specifiedTypes;
1115: }
1116:
1117: return $this->getBitwiseXorTypeFromTypes($leftType, $rightType);
1118: }
1119:
1120: private const IS_SCALAR_TYPE = 1;
1121: private const IS_UNKNOWN = 2;
1122:
1123: /**
1124: * @param callable(bool|float|int|string|null, bool|float|int|string|null):string $operationCallable
1125: *
1126: * @return self::IS_UNKNOWN|self::IS_SCALAR_TYPE|Type
1127: */
1128: private function getFiniteOrConstantScalarTypes(Type $leftType, Type $rightType, callable $operationCallable): int|Type
1129: {
1130: if ($leftType instanceof IntegerRangeType) {
1131: $leftTypes = $leftType->getFiniteTypes();
1132: } else {
1133: $leftTypes = $leftType->getConstantScalarTypes();
1134: }
1135: if ($rightType instanceof IntegerRangeType) {
1136: $rightTypes = $rightType->getFiniteTypes();
1137: } else {
1138: $rightTypes = $rightType->getConstantScalarTypes();
1139: }
1140:
1141: $leftTypesCount = count($leftTypes);
1142: $rightTypesCount = count($rightTypes);
1143:
1144: if ($leftTypesCount === 0 || $rightTypesCount === 0) {
1145: return self::IS_UNKNOWN;
1146: }
1147:
1148: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1149: if ($generalize) {
1150: return self::IS_SCALAR_TYPE;
1151: }
1152:
1153: $resultTypes = [];
1154: foreach ($leftTypes as $leftTypeInner) {
1155: foreach ($rightTypes as $rightTypeInner) {
1156: if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) {
1157: $resultValue = $operationCallable($leftTypeInner->getValue(), $rightTypeInner->getValue());
1158: $resultType = $this->getTypeFromValue($resultValue);
1159: } else {
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: $resultValue = $operationCallable($leftNumberType->getValue(), $rightNumberType->getValue());
1172: $resultType = $this->getTypeFromValue($resultValue);
1173: }
1174: $resultTypes[] = $resultType;
1175: }
1176: }
1177: return TypeCombinator::union(...$resultTypes);
1178: }
1179:
1180: public function getBitwiseXorTypeFromTypes(Type $leftType, Type $rightType): Type
1181: {
1182: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1183: return $this->getNeverType($leftType, $rightType);
1184: }
1185:
1186: $result = $this->getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a ^ $b);
1187: if ($result instanceof Type) {
1188: return $result;
1189: } elseif ($result === self::IS_SCALAR_TYPE) {
1190: $leftType = $this->optimizeScalarType($leftType);
1191: $rightType = $this->optimizeScalarType($rightType);
1192: }
1193:
1194: if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1195: return new BenevolentUnionType([new IntegerType(), new StringType()]);
1196: }
1197:
1198: $leftIsString = $leftType->isString();
1199: $rightIsString = $rightType->isString();
1200: if (
1201: ($leftIsString->yes() || $leftType instanceof MixedType)
1202: && ($rightIsString->yes() || $rightType instanceof MixedType)
1203: ) {
1204: return new StringType();
1205: }
1206: if ($leftIsString->maybe() && $rightIsString->maybe()) {
1207: return new ErrorType();
1208: }
1209:
1210: if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) {
1211: return new ErrorType();
1212: }
1213:
1214: return new IntegerType();
1215: }
1216:
1217: /**
1218: * @param callable(Expr): Type $getTypeCallback
1219: */
1220: public function getSpaceshipType(Expr $left, Expr $right, callable $getTypeCallback): Type
1221: {
1222: $callbackLeftType = $getTypeCallback($left);
1223: $callbackRightType = $getTypeCallback($right);
1224:
1225: return $this->getSpaceshipTypeFromTypes($callbackLeftType, $callbackRightType);
1226: }
1227:
1228: public function getSpaceshipTypeFromTypes(Type $leftTypes, Type $rightTypes): Type
1229: {
1230: if ($leftTypes instanceof NeverType || $rightTypes instanceof NeverType) {
1231: return $this->getNeverType($leftTypes, $rightTypes);
1232: }
1233:
1234: $leftTypes = $leftTypes->getConstantScalarTypes();
1235: $rightTypes = $rightTypes->getConstantScalarTypes();
1236:
1237: $leftTypesCount = count($leftTypes);
1238: $rightTypesCount = count($rightTypes);
1239: if ($leftTypesCount > 0 && $rightTypesCount > 0 && $leftTypesCount * $rightTypesCount <= self::CALCULATE_SCALARS_LIMIT) {
1240: $resultTypes = [];
1241: foreach ($leftTypes as $leftType) {
1242: foreach ($rightTypes as $rightType) {
1243: $leftValue = $leftType->getValue();
1244: $rightValue = $rightType->getValue();
1245: $resultType = $this->getTypeFromValue($leftValue <=> $rightValue);
1246: $resultTypes[] = $resultType;
1247: }
1248: }
1249: return TypeCombinator::union(...$resultTypes);
1250: }
1251:
1252: return IntegerRangeType::fromInterval(-1, 1);
1253: }
1254:
1255: /**
1256: * @param callable(Expr): Type $getTypeCallback
1257: */
1258: public function getDivType(Expr $left, Expr $right, callable $getTypeCallback): Type
1259: {
1260: $leftType = $getTypeCallback($left);
1261: $rightType = $getTypeCallback($right);
1262:
1263: return $this->getDivTypeFromTypes($left, $right, $leftType, $rightType);
1264: }
1265:
1266: public function getDivTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1267: {
1268: $leftTypes = $leftType->getConstantScalarTypes();
1269: $rightTypes = $rightType->getConstantScalarTypes();
1270: $leftTypesCount = count($leftTypes);
1271: $rightTypesCount = count($rightTypes);
1272: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1273: $resultTypes = [];
1274: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1275: if (!$generalize) {
1276: foreach ($leftTypes as $leftTypeInner) {
1277: foreach ($rightTypes as $rightTypeInner) {
1278: $leftNumberType = $leftTypeInner->toNumber();
1279: $rightNumberType = $rightTypeInner->toNumber();
1280:
1281: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1282: return new ErrorType();
1283: }
1284:
1285: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1286: throw new ShouldNotHappenException();
1287: }
1288:
1289: if (in_array($rightNumberType->getValue(), [0, 0.0], true)) {
1290: return new ErrorType();
1291: }
1292:
1293: $resultType = $this->getTypeFromValue($leftNumberType->getValue() / $rightNumberType->getValue()); // @phpstan-ignore binaryOp.invalid
1294: $resultTypes[] = $resultType;
1295: }
1296: }
1297: return TypeCombinator::union(...$resultTypes);
1298: }
1299:
1300: $leftType = $this->optimizeScalarType($leftType);
1301: $rightType = $this->optimizeScalarType($rightType);
1302: }
1303:
1304: $rightScalarValues = $rightType->toNumber()->getConstantScalarValues();
1305: foreach ($rightScalarValues as $scalarValue) {
1306: if (in_array($scalarValue, [0, 0.0], true)) {
1307: return new ErrorType();
1308: }
1309: }
1310:
1311: return $this->resolveCommonMath(new BinaryOp\Div($left, $right), $leftType, $rightType);
1312: }
1313:
1314: /**
1315: * @param callable(Expr): Type $getTypeCallback
1316: */
1317: public function getModType(Expr $left, Expr $right, callable $getTypeCallback): Type
1318: {
1319: $leftType = $getTypeCallback($left);
1320: $rightType = $getTypeCallback($right);
1321:
1322: return $this->getModTypeFromTypes($left, $right, $leftType, $rightType);
1323: }
1324:
1325: public function getModTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1326: {
1327: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1328: return $this->getNeverType($leftType, $rightType);
1329: }
1330:
1331: $extensionSpecified = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
1332: ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Mod($left, $right), $leftType, $rightType);
1333: if ($extensionSpecified !== null) {
1334: return $extensionSpecified;
1335: }
1336:
1337: if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) {
1338: return new ErrorType();
1339: }
1340:
1341: $leftTypes = $leftType->getConstantScalarTypes();
1342: $rightTypes = $rightType->getConstantScalarTypes();
1343: $leftTypesCount = count($leftTypes);
1344: $rightTypesCount = count($rightTypes);
1345: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1346: $resultTypes = [];
1347: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1348: if (!$generalize) {
1349: foreach ($leftTypes as $leftTypeInner) {
1350: foreach ($rightTypes as $rightTypeInner) {
1351: $leftNumberType = $leftTypeInner->toNumber();
1352: $rightNumberType = $rightTypeInner->toNumber();
1353:
1354: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1355: return new ErrorType();
1356: }
1357:
1358: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1359: throw new ShouldNotHappenException();
1360: }
1361:
1362: $rightIntegerValue = (int) $rightNumberType->getValue();
1363: if ($rightIntegerValue === 0) {
1364: return new ErrorType();
1365: }
1366:
1367: $resultType = $this->getTypeFromValue((int) $leftNumberType->getValue() % $rightIntegerValue);
1368: $resultTypes[] = $resultType;
1369: }
1370: }
1371: return TypeCombinator::union(...$resultTypes);
1372: }
1373:
1374: $leftType = $this->optimizeScalarType($leftType);
1375: $rightType = $this->optimizeScalarType($rightType);
1376: }
1377:
1378: $integerType = $rightType->toInteger();
1379: if ($integerType instanceof ConstantIntegerType && $integerType->getValue() === 1) {
1380: return new ConstantIntegerType(0);
1381: }
1382:
1383: $rightScalarValues = $rightType->toNumber()->getConstantScalarValues();
1384: foreach ($rightScalarValues as $scalarValue) {
1385:
1386: if (in_array($scalarValue, [0, 0.0], true)) {
1387: return new ErrorType();
1388: }
1389: }
1390:
1391: $positiveInt = IntegerRangeType::fromInterval(0, null);
1392: if ($rightType->isInteger()->yes()) {
1393: $rangeMin = null;
1394: $rangeMax = null;
1395:
1396: if ($rightType instanceof IntegerRangeType) {
1397: $rangeMax = $rightType->getMax() !== null ? $rightType->getMax() - 1 : null;
1398: } elseif ($rightType instanceof ConstantIntegerType) {
1399: $rangeMax = $rightType->getValue() - 1;
1400: } elseif ($rightType instanceof UnionType) {
1401: foreach ($rightType->getTypes() as $type) {
1402: if ($type instanceof IntegerRangeType) {
1403: if ($type->getMax() === null) {
1404: $rangeMax = null;
1405: } else {
1406: $rangeMax = max($rangeMax, $type->getMax());
1407: }
1408: } elseif ($type instanceof ConstantIntegerType) {
1409: $rangeMax = max($rangeMax, $type->getValue() - 1);
1410: }
1411: }
1412: }
1413:
1414: if ($positiveInt->isSuperTypeOf($leftType)->yes()) {
1415: $rangeMin = 0;
1416: } elseif ($rangeMax !== null) {
1417: $rangeMin = $rangeMax * -1;
1418: }
1419:
1420: return IntegerRangeType::fromInterval($rangeMin, $rangeMax);
1421: } elseif ($positiveInt->isSuperTypeOf($leftType)->yes()) {
1422: return IntegerRangeType::fromInterval(0, null);
1423: }
1424:
1425: return new IntegerType();
1426: }
1427:
1428: /**
1429: * @param callable(Expr): Type $getTypeCallback
1430: */
1431: public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): Type
1432: {
1433: $leftType = $getTypeCallback($left);
1434: $rightType = $getTypeCallback($right);
1435:
1436: return $this->getPlusTypeFromTypes($left, $right, $leftType, $rightType);
1437: }
1438:
1439: public function getPlusTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1440: {
1441: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1442: return $this->getNeverType($leftType, $rightType);
1443: }
1444:
1445: $leftTypes = $leftType->getConstantScalarTypes();
1446: $rightTypes = $rightType->getConstantScalarTypes();
1447: $leftTypesCount = count($leftTypes);
1448: $rightTypesCount = count($rightTypes);
1449: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1450: $resultTypes = [];
1451: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1452: if (!$generalize) {
1453: foreach ($leftTypes as $leftTypeInner) {
1454: foreach ($rightTypes as $rightTypeInner) {
1455: $leftNumberType = $leftTypeInner->toNumber();
1456: $rightNumberType = $rightTypeInner->toNumber();
1457:
1458: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1459: return new ErrorType();
1460: }
1461:
1462: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1463: throw new ShouldNotHappenException();
1464: }
1465:
1466: $resultType = $this->getTypeFromValue($leftNumberType->getValue() + $rightNumberType->getValue());
1467: $resultTypes[] = $resultType;
1468: }
1469: }
1470:
1471: return TypeCombinator::union(...$resultTypes);
1472: }
1473:
1474: $leftType = $this->optimizeScalarType($leftType);
1475: $rightType = $this->optimizeScalarType($rightType);
1476: }
1477:
1478: $leftConstantArrays = $leftType->getConstantArrays();
1479: $rightConstantArrays = $rightType->getConstantArrays();
1480:
1481: $leftCount = count($leftConstantArrays);
1482: $rightCount = count($rightConstantArrays);
1483: if ($leftCount > 0 && $rightCount > 0
1484: && ($leftCount + $rightCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT)) {
1485: $resultTypes = [];
1486: foreach ($rightConstantArrays as $rightConstantArray) {
1487: foreach ($leftConstantArrays as $leftConstantArray) {
1488: $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray);
1489: foreach ($leftConstantArray->getKeyTypes() as $i => $leftKeyType) {
1490: $optional = $leftConstantArray->isOptionalKey($i);
1491: $valueType = $leftConstantArray->getOffsetValueType($leftKeyType);
1492: if (!$optional) {
1493: if ($rightConstantArray->hasOffsetValueType($leftKeyType)->maybe()) {
1494: $valueType = TypeCombinator::union($valueType, $rightConstantArray->getOffsetValueType($leftKeyType));
1495: }
1496: }
1497: $newArrayBuilder->setOffsetValueType(
1498: $leftKeyType,
1499: $valueType,
1500: $optional,
1501: );
1502: }
1503: $resultTypes[] = $newArrayBuilder->getArray();
1504: }
1505: }
1506: return TypeCombinator::union(...$resultTypes);
1507: }
1508:
1509: $leftIsArray = $leftType->isArray();
1510: $rightIsArray = $rightType->isArray();
1511: if ($leftIsArray->yes() && $rightIsArray->yes()) {
1512: if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) {
1513: // to preserve BenevolentUnionType
1514: $keyType = $leftType->getIterableKeyType();
1515: } else {
1516: $keyTypes = [];
1517: foreach ([
1518: $leftType->getIterableKeyType(),
1519: $rightType->getIterableKeyType(),
1520: ] as $keyType) {
1521: $keyTypes[] = $keyType;
1522: }
1523: $keyType = TypeCombinator::union(...$keyTypes);
1524: }
1525:
1526: $leftIterableValueType = $leftType->getIterableValueType();
1527: $arrayType = new ArrayType(
1528: $keyType,
1529: TypeCombinator::union($leftIterableValueType, $rightType->getIterableValueType()),
1530: );
1531:
1532: $accessories = [];
1533: if ($leftCount > 0) {
1534: // Use the first constant array as a reference to list potential offsets.
1535: // We only need to check the first array because we're looking for offsets that exist in ALL arrays.
1536: $constantArray = $leftConstantArrays[0];
1537: foreach ($constantArray->getKeyTypes() as $offsetType) {
1538: if (!$leftType->hasOffsetValueType($offsetType)->yes()) {
1539: continue;
1540: }
1541:
1542: $valueType = $leftType->getOffsetValueType($offsetType);
1543: $accessories[] = new HasOffsetValueType($offsetType, $valueType);
1544: }
1545: }
1546:
1547: if ($rightCount > 0) {
1548: // Use the first constant array as a reference to list potential offsets.
1549: // We only need to check the first array because we're looking for offsets that exist in ALL arrays.
1550: $constantArray = $rightConstantArrays[0];
1551: foreach ($constantArray->getKeyTypes() as $offsetType) {
1552: if (!$rightType->hasOffsetValueType($offsetType)->yes()) {
1553: continue;
1554: }
1555:
1556: $valueType = TypeCombinator::union($leftIterableValueType, $rightType->getOffsetValueType($offsetType));
1557: $accessories[] = new HasOffsetValueType($offsetType, $valueType);
1558: }
1559: }
1560:
1561: if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) {
1562: $accessories[] = new NonEmptyArrayType();
1563: }
1564: if ($leftType->isList()->yes() && $rightType->isList()->yes()) {
1565: $accessories[] = new AccessoryArrayListType();
1566: }
1567:
1568: if (count($accessories) > 0) {
1569: $arrayType = TypeCombinator::intersect($arrayType, ...$accessories);
1570: }
1571:
1572: return $arrayType;
1573: }
1574:
1575: if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1576: if ($leftIsArray->no() && $rightIsArray->no()) {
1577: return new BenevolentUnionType([
1578: new FloatType(),
1579: new IntegerType(),
1580: ]);
1581: }
1582: return new BenevolentUnionType([
1583: new FloatType(),
1584: new IntegerType(),
1585: new ArrayType(new MixedType(), new MixedType()),
1586: ]);
1587: }
1588:
1589: if (
1590: ($leftIsArray->yes() && $rightIsArray->no())
1591: || ($leftIsArray->no() && $rightIsArray->yes())
1592: ) {
1593: return new ErrorType();
1594: }
1595:
1596: if (
1597: ($leftIsArray->yes() && $rightIsArray->maybe())
1598: || ($leftIsArray->maybe() && $rightIsArray->yes())
1599: ) {
1600: $resultType = new ArrayType(new MixedType(), new MixedType());
1601: if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) {
1602: return TypeCombinator::intersect($resultType, new NonEmptyArrayType());
1603: }
1604:
1605: return $resultType;
1606: }
1607:
1608: if ($leftIsArray->maybe() && $rightIsArray->maybe()) {
1609: $plusable = new UnionType([
1610: new StringType(),
1611: new FloatType(),
1612: new IntegerType(),
1613: new ArrayType(new MixedType(), new MixedType()),
1614: new BooleanType(),
1615: ]);
1616:
1617: $plusableSuperTypeOfLeft = $plusable->isSuperTypeOf($leftType)->yes();
1618: $plusableSuperTypeOfRight = $plusable->isSuperTypeOf($rightType)->yes();
1619: if ($plusableSuperTypeOfLeft && $plusableSuperTypeOfRight) {
1620: return TypeCombinator::union($leftType, $rightType);
1621: }
1622: if ($plusableSuperTypeOfLeft && $rightType instanceof MixedType) {
1623: return $leftType;
1624: }
1625: if ($plusableSuperTypeOfRight && $leftType instanceof MixedType) {
1626: return $rightType;
1627: }
1628: }
1629:
1630: return $this->resolveCommonMath(new BinaryOp\Plus($left, $right), $leftType, $rightType);
1631: }
1632:
1633: /**
1634: * @param callable(Expr): Type $getTypeCallback
1635: */
1636: public function getMinusType(Expr $left, Expr $right, callable $getTypeCallback): Type
1637: {
1638: $leftType = $getTypeCallback($left);
1639: $rightType = $getTypeCallback($right);
1640:
1641: return $this->getMinusTypeFromTypes($left, $right, $leftType, $rightType);
1642: }
1643:
1644: public function getMinusTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1645: {
1646: $leftTypes = $leftType->getConstantScalarTypes();
1647: $rightTypes = $rightType->getConstantScalarTypes();
1648: $leftTypesCount = count($leftTypes);
1649: $rightTypesCount = count($rightTypes);
1650: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1651: $resultTypes = [];
1652: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1653: if (!$generalize) {
1654: foreach ($leftTypes as $leftTypeInner) {
1655: foreach ($rightTypes as $rightTypeInner) {
1656: $leftNumberType = $leftTypeInner->toNumber();
1657: $rightNumberType = $rightTypeInner->toNumber();
1658:
1659: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1660: return new ErrorType();
1661: }
1662:
1663: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1664: throw new ShouldNotHappenException();
1665: }
1666:
1667: $resultType = $this->getTypeFromValue($leftNumberType->getValue() - $rightNumberType->getValue());
1668: $resultTypes[] = $resultType;
1669: }
1670: }
1671:
1672: return TypeCombinator::union(...$resultTypes);
1673: }
1674:
1675: $leftType = $this->optimizeScalarType($leftType);
1676: $rightType = $this->optimizeScalarType($rightType);
1677: }
1678:
1679: return $this->resolveCommonMath(new BinaryOp\Minus($left, $right), $leftType, $rightType);
1680: }
1681:
1682: /**
1683: * @param callable(Expr): Type $getTypeCallback
1684: */
1685: public function getMulType(Expr $left, Expr $right, callable $getTypeCallback): Type
1686: {
1687: $leftType = $getTypeCallback($left);
1688: $rightType = $getTypeCallback($right);
1689:
1690: return $this->getMulTypeFromTypes($left, $right, $leftType, $rightType);
1691: }
1692:
1693: public function getMulTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1694: {
1695: $leftTypes = $leftType->getConstantScalarTypes();
1696: $rightTypes = $rightType->getConstantScalarTypes();
1697: $leftTypesCount = count($leftTypes);
1698: $rightTypesCount = count($rightTypes);
1699: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1700: $resultTypes = [];
1701: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1702: if (!$generalize) {
1703: foreach ($leftTypes as $leftTypeInner) {
1704: foreach ($rightTypes as $rightTypeInner) {
1705: $leftNumberType = $leftTypeInner->toNumber();
1706: $rightNumberType = $rightTypeInner->toNumber();
1707:
1708: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1709: return new ErrorType();
1710: }
1711:
1712: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1713: throw new ShouldNotHappenException();
1714: }
1715:
1716: $resultType = $this->getTypeFromValue($leftNumberType->getValue() * $rightNumberType->getValue());
1717: $resultTypes[] = $resultType;
1718: }
1719: }
1720:
1721: return TypeCombinator::union(...$resultTypes);
1722: }
1723:
1724: $leftType = $this->optimizeScalarType($leftType);
1725: $rightType = $this->optimizeScalarType($rightType);
1726: }
1727:
1728: $leftNumberType = $leftType->toNumber();
1729: if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0) {
1730: if ($rightType->isFloat()->yes()) {
1731: return new ConstantFloatType(0.0);
1732: }
1733: return new ConstantIntegerType(0);
1734: }
1735: $rightNumberType = $rightType->toNumber();
1736: if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0) {
1737: if ($leftType->isFloat()->yes()) {
1738: return new ConstantFloatType(0.0);
1739: }
1740: return new ConstantIntegerType(0);
1741: }
1742:
1743: return $this->resolveCommonMath(new BinaryOp\Mul($left, $right), $leftType, $rightType);
1744: }
1745:
1746: /**
1747: * @param callable(Expr): Type $getTypeCallback
1748: */
1749: public function getPowType(Expr $left, Expr $right, callable $getTypeCallback): Type
1750: {
1751: $leftType = $getTypeCallback($left);
1752: $rightType = $getTypeCallback($right);
1753:
1754: return $this->getPowTypeFromTypes($left, $right, $leftType, $rightType);
1755: }
1756:
1757: public function getPowTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1758: {
1759: $extensionSpecified = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
1760: ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Pow($left, $right), $leftType, $rightType);
1761: if ($extensionSpecified !== null) {
1762: return $extensionSpecified;
1763: }
1764:
1765: $exponentiatedTyped = $leftType->exponentiate($rightType);
1766: if (!$exponentiatedTyped instanceof ErrorType) {
1767: return $exponentiatedTyped;
1768: }
1769:
1770: return new ErrorType();
1771: }
1772:
1773: /**
1774: * @param callable(Expr): Type $getTypeCallback
1775: */
1776: public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallback): Type
1777: {
1778: $leftType = $getTypeCallback($left);
1779: $rightType = $getTypeCallback($right);
1780:
1781: $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
1782: ->callOperatorTypeSpecifyingExtensions(new BinaryOp\ShiftLeft($left, $right), $leftType, $rightType);
1783: if ($specifiedTypes !== null) {
1784: return $specifiedTypes;
1785: }
1786:
1787: return $this->getShiftLeftTypeFromTypes($left, $right, $leftType, $rightType);
1788: }
1789:
1790: public function getShiftLeftTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1791: {
1792: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1793: return $this->getNeverType($leftType, $rightType);
1794: }
1795:
1796: $leftTypes = $leftType->getConstantScalarTypes();
1797: $rightTypes = $rightType->getConstantScalarTypes();
1798: $leftTypesCount = count($leftTypes);
1799: $rightTypesCount = count($rightTypes);
1800: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1801: $resultTypes = [];
1802: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1803: if (!$generalize) {
1804: foreach ($leftTypes as $leftTypeInner) {
1805: foreach ($rightTypes as $rightTypeInner) {
1806: $leftNumberType = $leftTypeInner->toNumber();
1807: $rightNumberType = $rightTypeInner->toNumber();
1808:
1809: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1810: return new ErrorType();
1811: }
1812:
1813: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1814: throw new ShouldNotHappenException();
1815: }
1816:
1817: if ($rightNumberType->getValue() < 0) {
1818: return new ErrorType();
1819: }
1820:
1821: $resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) << intval($rightNumberType->getValue()));
1822: $resultTypes[] = $resultType;
1823: }
1824: }
1825:
1826: return TypeCombinator::union(...$resultTypes);
1827: }
1828:
1829: $leftType = $this->optimizeScalarType($leftType);
1830: $rightType = $this->optimizeScalarType($rightType);
1831: }
1832:
1833: $leftNumberType = $leftType->toNumber();
1834: $rightNumberType = $rightType->toNumber();
1835:
1836: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1837: return new ErrorType();
1838: }
1839:
1840: return $this->resolveCommonMath(new Expr\BinaryOp\ShiftLeft($left, $right), $leftType, $rightType);
1841: }
1842:
1843: /**
1844: * @param callable(Expr): Type $getTypeCallback
1845: */
1846: public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCallback): Type
1847: {
1848: $leftType = $getTypeCallback($left);
1849: $rightType = $getTypeCallback($right);
1850:
1851: $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
1852: ->callOperatorTypeSpecifyingExtensions(new BinaryOp\ShiftRight($left, $right), $leftType, $rightType);
1853: if ($specifiedTypes !== null) {
1854: return $specifiedTypes;
1855: }
1856:
1857: return $this->getShiftRightTypeFromTypes($left, $right, $leftType, $rightType);
1858: }
1859:
1860: public function getShiftRightTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1861: {
1862: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1863: return $this->getNeverType($leftType, $rightType);
1864: }
1865:
1866: $leftTypes = $leftType->getConstantScalarTypes();
1867: $rightTypes = $rightType->getConstantScalarTypes();
1868: $leftTypesCount = count($leftTypes);
1869: $rightTypesCount = count($rightTypes);
1870: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1871: $resultTypes = [];
1872: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1873: if (!$generalize) {
1874: foreach ($leftTypes as $leftTypeInner) {
1875: foreach ($rightTypes as $rightTypeInner) {
1876: $leftNumberType = $leftTypeInner->toNumber();
1877: $rightNumberType = $rightTypeInner->toNumber();
1878:
1879: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1880: return new ErrorType();
1881: }
1882:
1883: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1884: throw new ShouldNotHappenException();
1885: }
1886:
1887: if ($rightNumberType->getValue() < 0) {
1888: return new ErrorType();
1889: }
1890:
1891: $resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) >> intval($rightNumberType->getValue()));
1892: $resultTypes[] = $resultType;
1893: }
1894: }
1895:
1896: return TypeCombinator::union(...$resultTypes);
1897: }
1898:
1899: $leftType = $this->optimizeScalarType($leftType);
1900: $rightType = $this->optimizeScalarType($rightType);
1901: }
1902:
1903: $leftNumberType = $leftType->toNumber();
1904: $rightNumberType = $rightType->toNumber();
1905:
1906: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1907: return new ErrorType();
1908: }
1909:
1910: return $this->resolveCommonMath(new Expr\BinaryOp\ShiftRight($left, $right), $leftType, $rightType);
1911: }
1912:
1913: private function optimizeScalarType(Type $type): Type
1914: {
1915: $types = [];
1916: if ($type->isInteger()->yes()) {
1917: $types[] = new IntegerType();
1918: }
1919: if ($type->isString()->yes()) {
1920: $types[] = new StringType();
1921: }
1922: if ($type->isFloat()->yes()) {
1923: $types[] = new FloatType();
1924: }
1925: if ($type->isNull()->yes()) {
1926: $types[] = new NullType();
1927: }
1928:
1929: if (count($types) === 0) {
1930: return new ErrorType();
1931: }
1932:
1933: if (count($types) === 1) {
1934: return $types[0];
1935: }
1936:
1937: return new UnionType($types);
1938: }
1939:
1940: /**
1941: * @return TypeResult<BooleanType>
1942: */
1943: public function resolveIdenticalType(Type $leftType, Type $rightType): TypeResult
1944: {
1945: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1946: return new TypeResult(new ConstantBooleanType(false), []);
1947: }
1948:
1949: if ($leftType instanceof ConstantScalarType && $rightType instanceof ConstantScalarType) {
1950: return new TypeResult(new ConstantBooleanType($leftType->getValue() === $rightType->getValue()), []);
1951: }
1952:
1953: $leftTypeFiniteTypes = $leftType->getFiniteTypes();
1954: $rightTypeFiniteType = $rightType->getFiniteTypes();
1955: if (count($leftTypeFiniteTypes) === 1 && count($rightTypeFiniteType) === 1) {
1956: return new TypeResult(new ConstantBooleanType($leftTypeFiniteTypes[0]->equals($rightTypeFiniteType[0])), []);
1957: }
1958:
1959: $leftIsSuperTypeOfRight = $leftType->isSuperTypeOf($rightType);
1960: $rightIsSuperTypeOfLeft = $rightType->isSuperTypeOf($leftType);
1961: if ($leftIsSuperTypeOfRight->no() && $rightIsSuperTypeOfLeft->no()) {
1962: return new TypeResult(new ConstantBooleanType(false), array_merge($leftIsSuperTypeOfRight->reasons, $rightIsSuperTypeOfLeft->reasons));
1963: }
1964:
1965: if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) {
1966: return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): TypeResult => $this->resolveIdenticalType($leftValueType, $rightValueType));
1967: }
1968:
1969: return new TypeResult(new BooleanType(), []);
1970: }
1971:
1972: /**
1973: * @return TypeResult<BooleanType>
1974: */
1975: public function resolveEqualType(Type $leftType, Type $rightType): TypeResult
1976: {
1977: if (
1978: ($leftType->isEnum()->yes() && $rightType->isTrue()->no())
1979: || ($rightType->isEnum()->yes() && $leftType->isTrue()->no())
1980: ) {
1981: return $this->resolveIdenticalType($leftType, $rightType);
1982: }
1983:
1984: if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) {
1985: return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): TypeResult => $this->resolveEqualType($leftValueType, $rightValueType));
1986: }
1987:
1988: return new TypeResult($leftType->looseCompare($rightType, $this->phpVersion), []);
1989: }
1990:
1991: /**
1992: * @param callable(Type, Type): TypeResult<BooleanType> $valueComparisonCallback
1993: * @return TypeResult<BooleanType>
1994: */
1995: private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, ConstantArrayType $rightType, callable $valueComparisonCallback): TypeResult
1996: {
1997: $leftKeyTypes = $leftType->getKeyTypes();
1998: $rightKeyTypes = $rightType->getKeyTypes();
1999: $leftValueTypes = $leftType->getValueTypes();
2000: $rightValueTypes = $rightType->getValueTypes();
2001:
2002: $resultType = new ConstantBooleanType(true);
2003:
2004: foreach ($leftKeyTypes as $i => $leftKeyType) {
2005: $leftOptional = $leftType->isOptionalKey($i);
2006: if ($leftOptional) {
2007: $resultType = new BooleanType();
2008: }
2009:
2010: if (count($rightKeyTypes) === 0) {
2011: if (!$leftOptional) {
2012: return new TypeResult(new ConstantBooleanType(false), []);
2013: }
2014: continue;
2015: }
2016:
2017: $found = false;
2018: foreach ($rightKeyTypes as $j => $rightKeyType) {
2019: unset($rightKeyTypes[$j]);
2020:
2021: if ($leftKeyType->equals($rightKeyType)) {
2022: $found = true;
2023: break;
2024: } elseif (!$rightType->isOptionalKey($j)) {
2025: return new TypeResult(new ConstantBooleanType(false), []);
2026: }
2027: }
2028:
2029: if (!$found) {
2030: if (!$leftOptional) {
2031: return new TypeResult(new ConstantBooleanType(false), []);
2032: }
2033: continue;
2034: }
2035:
2036: if (!isset($j)) {
2037: throw new ShouldNotHappenException();
2038: }
2039:
2040: $rightOptional = $rightType->isOptionalKey($j);
2041: if ($rightOptional) {
2042: $resultType = new BooleanType();
2043: if ($leftOptional) {
2044: continue;
2045: }
2046: }
2047:
2048: $leftIdenticalToRightResult = $valueComparisonCallback($leftValueTypes[$i], $rightValueTypes[$j]);
2049: $leftIdenticalToRight = $leftIdenticalToRightResult->type;
2050: if ($leftIdenticalToRight->isFalse()->yes()) {
2051: return $leftIdenticalToRightResult;
2052: }
2053: $resultType = TypeCombinator::union($resultType, $leftIdenticalToRight);
2054: }
2055:
2056: foreach (array_keys($rightKeyTypes) as $j) {
2057: if (!$rightType->isOptionalKey($j)) {
2058: return new TypeResult(new ConstantBooleanType(false), []);
2059: }
2060: $resultType = new BooleanType();
2061: }
2062:
2063: return new TypeResult($resultType->toBoolean(), []);
2064: }
2065:
2066: /**
2067: * @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $expr
2068: */
2069: private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type
2070: {
2071: $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
2072: ->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType);
2073: if ($specifiedTypes !== null) {
2074: return $specifiedTypes;
2075: }
2076:
2077: $types = TypeCombinator::union($leftType, $rightType);
2078: $leftNumberType = $leftType->toNumber();
2079: $rightNumberType = $rightType->toNumber();
2080:
2081: if (
2082: !$types instanceof MixedType
2083: && (
2084: $rightNumberType instanceof IntegerRangeType
2085: || $rightNumberType instanceof ConstantIntegerType
2086: || $rightNumberType instanceof UnionType
2087: )
2088: ) {
2089: if ($leftNumberType instanceof IntegerRangeType || $leftNumberType instanceof ConstantIntegerType) {
2090: return $this->integerRangeMath(
2091: $leftNumberType,
2092: $expr,
2093: $rightNumberType,
2094: );
2095: } elseif ($leftNumberType instanceof UnionType) {
2096: $unionParts = [];
2097:
2098: foreach ($leftNumberType->getTypes() as $type) {
2099: $numberType = $type->toNumber();
2100: if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) {
2101: $unionParts[] = $this->integerRangeMath($numberType, $expr, $rightNumberType);
2102: } else {
2103: $unionParts[] = $numberType;
2104: }
2105: }
2106:
2107: $union = TypeCombinator::union(...$unionParts);
2108: if ($leftNumberType instanceof BenevolentUnionType) {
2109: return TypeUtils::toBenevolentUnion($union)->toNumber();
2110: }
2111:
2112: return $union->toNumber();
2113: }
2114: }
2115:
2116: if (
2117: $leftType->isArray()->yes()
2118: || $rightType->isArray()->yes()
2119: || $types->isArray()->yes()
2120: ) {
2121: return new ErrorType();
2122: }
2123:
2124: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
2125: return new ErrorType();
2126: }
2127: if ($leftNumberType instanceof NeverType || $rightNumberType instanceof NeverType) {
2128: return $this->getNeverType($leftNumberType, $rightNumberType);
2129: }
2130:
2131: if (
2132: $leftNumberType->isFloat()->yes()
2133: || $rightNumberType->isFloat()->yes()
2134: ) {
2135: if ($expr instanceof Expr\BinaryOp\ShiftLeft || $expr instanceof Expr\BinaryOp\ShiftRight) {
2136: return new IntegerType();
2137: }
2138: return new FloatType();
2139: }
2140:
2141: $resultType = TypeCombinator::union($leftNumberType, $rightNumberType);
2142: if ($expr instanceof Expr\BinaryOp\Div) {
2143: if ($types instanceof MixedType || $resultType->isInteger()->yes()) {
2144: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
2145: }
2146:
2147: return new UnionType([new IntegerType(), new FloatType()]);
2148: }
2149:
2150: if ($types instanceof MixedType
2151: || $leftType instanceof BenevolentUnionType
2152: || $rightType instanceof BenevolentUnionType
2153: ) {
2154: return TypeUtils::toBenevolentUnion($resultType);
2155: }
2156:
2157: return $resultType;
2158: }
2159:
2160: /**
2161: * @param ConstantIntegerType|IntegerRangeType $range
2162: * @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $node
2163: */
2164: private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): Type
2165: {
2166: if ($range instanceof IntegerRangeType) {
2167: $rangeMin = $range->getMin();
2168: $rangeMax = $range->getMax();
2169: } else {
2170: $rangeMin = $range->getValue();
2171: $rangeMax = $rangeMin;
2172: }
2173:
2174: if ($operand instanceof UnionType) {
2175:
2176: $unionParts = [];
2177:
2178: foreach ($operand->getTypes() as $type) {
2179: $numberType = $type->toNumber();
2180: if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) {
2181: $unionParts[] = $this->integerRangeMath($range, $node, $numberType);
2182: } else {
2183: $unionParts[] = $type->toNumber();
2184: }
2185: }
2186:
2187: $union = TypeCombinator::union(...$unionParts);
2188: if ($operand instanceof BenevolentUnionType) {
2189: return TypeUtils::toBenevolentUnion($union)->toNumber();
2190: }
2191:
2192: return $union->toNumber();
2193: }
2194:
2195: $operand = $operand->toNumber();
2196: if ($operand instanceof IntegerRangeType) {
2197: $operandMin = $operand->getMin();
2198: $operandMax = $operand->getMax();
2199: } elseif ($operand instanceof ConstantIntegerType) {
2200: $operandMin = $operand->getValue();
2201: $operandMax = $operand->getValue();
2202: } else {
2203: return $operand;
2204: }
2205:
2206: if ($node instanceof BinaryOp\Plus) {
2207: if ($operand instanceof ConstantIntegerType) {
2208: /** @var int|float|null $min */
2209: $min = $rangeMin !== null ? $rangeMin + $operand->getValue() : null;
2210:
2211: /** @var int|float|null $max */
2212: $max = $rangeMax !== null ? $rangeMax + $operand->getValue() : null;
2213: } else {
2214: /** @var int|float|null $min */
2215: $min = $rangeMin !== null && $operand->getMin() !== null ? $rangeMin + $operand->getMin() : null;
2216:
2217: /** @var int|float|null $max */
2218: $max = $rangeMax !== null && $operand->getMax() !== null ? $rangeMax + $operand->getMax() : null;
2219: }
2220: } elseif ($node instanceof BinaryOp\Minus) {
2221: if ($operand instanceof ConstantIntegerType) {
2222: /** @var int|float|null $min */
2223: $min = $rangeMin !== null ? $rangeMin - $operand->getValue() : null;
2224:
2225: /** @var int|float|null $max */
2226: $max = $rangeMax !== null ? $rangeMax - $operand->getValue() : null;
2227: } else {
2228: if ($rangeMin === $rangeMax && $rangeMin !== null
2229: && ($operand->getMin() === null || $operand->getMax() === null)) {
2230: $min = null;
2231: $max = $rangeMin;
2232: } else {
2233: if ($operand->getMin() === null) {
2234: $min = null;
2235: } elseif ($rangeMin !== null) {
2236: if ($operand->getMax() !== null) {
2237: /** @var int|float $min */
2238: $min = $rangeMin - $operand->getMax();
2239: } else {
2240: /** @var int|float $min */
2241: $min = $rangeMin - $operand->getMin();
2242: }
2243: } else {
2244: $min = null;
2245: }
2246:
2247: if ($operand->getMax() === null) {
2248: $min = null;
2249: $max = null;
2250: } elseif ($rangeMax !== null) {
2251: if ($rangeMin !== null && $operand->getMin() === null) {
2252: /** @var int|float $min */
2253: $min = $rangeMin - $operand->getMax();
2254: $max = null;
2255: } elseif ($operand->getMin() !== null) {
2256: /** @var int|float $max */
2257: $max = $rangeMax - $operand->getMin();
2258: } else {
2259: $max = null;
2260: }
2261: } else {
2262: $max = null;
2263: }
2264:
2265: if ($min !== null && $max !== null && $min > $max) {
2266: [$min, $max] = [$max, $min];
2267: }
2268: }
2269: }
2270: } elseif ($node instanceof Expr\BinaryOp\Mul) {
2271: $min1 = $rangeMin === 0 || $operandMin === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMin ?? -INF);
2272: $min2 = $rangeMin === 0 || $operandMax === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMax ?? INF);
2273: $max1 = $rangeMax === 0 || $operandMin === 0 ? 0 : ($rangeMax ?? INF) * ($operandMin ?? -INF);
2274: $max2 = $rangeMax === 0 || $operandMax === 0 ? 0 : ($rangeMax ?? INF) * ($operandMax ?? INF);
2275:
2276: $min = min($min1, $min2, $max1, $max2);
2277: $max = max($min1, $min2, $max1, $max2);
2278:
2279: if (!is_finite($min)) {
2280: $min = null;
2281: }
2282: if (!is_finite($max)) {
2283: $max = null;
2284: }
2285: } elseif ($node instanceof Expr\BinaryOp\Div) {
2286: if ($operand instanceof ConstantIntegerType) {
2287: $min = $rangeMin !== null && $operand->getValue() !== 0 ? $rangeMin / $operand->getValue() : null;
2288: $max = $rangeMax !== null && $operand->getValue() !== 0 ? $rangeMax / $operand->getValue() : null;
2289: } else {
2290: // Avoid division by zero when looking for the min and the max by using the closest int
2291: $operandMin = $operandMin !== 0 ? $operandMin : 1;
2292: $operandMax = $operandMax !== 0 ? $operandMax : -1;
2293:
2294: if (
2295: ($operandMin < 0 || $operandMin === null)
2296: && ($operandMax > 0 || $operandMax === null)
2297: ) {
2298: $negativeOperand = IntegerRangeType::fromInterval($operandMin, 0);
2299: assert($negativeOperand instanceof IntegerRangeType);
2300: $positiveOperand = IntegerRangeType::fromInterval(0, $operandMax);
2301: assert($positiveOperand instanceof IntegerRangeType);
2302:
2303: $result = TypeCombinator::union(
2304: $this->integerRangeMath($range, $node, $negativeOperand),
2305: $this->integerRangeMath($range, $node, $positiveOperand),
2306: )->toNumber();
2307:
2308: if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) {
2309: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
2310: }
2311:
2312: return $result;
2313: }
2314: if (
2315: ($rangeMin < 0 || $rangeMin === null)
2316: && ($rangeMax > 0 || $rangeMax === null)
2317: ) {
2318: $negativeRange = IntegerRangeType::fromInterval($rangeMin, 0);
2319: assert($negativeRange instanceof IntegerRangeType);
2320: $positiveRange = IntegerRangeType::fromInterval(0, $rangeMax);
2321: assert($positiveRange instanceof IntegerRangeType);
2322:
2323: $result = TypeCombinator::union(
2324: $this->integerRangeMath($negativeRange, $node, $operand),
2325: $this->integerRangeMath($positiveRange, $node, $operand),
2326: )->toNumber();
2327:
2328: if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) {
2329: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
2330: }
2331:
2332: return $result;
2333: }
2334:
2335: $rangeMinSign = ($rangeMin ?? -INF) <=> 0;
2336: $rangeMaxSign = ($rangeMax ?? INF) <=> 0;
2337:
2338: $min1 = $operandMin !== null ? ($rangeMin ?? -INF) / $operandMin : $rangeMinSign * -0.1;
2339: $min2 = $operandMax !== null ? ($rangeMin ?? -INF) / $operandMax : $rangeMinSign * 0.1;
2340: $max1 = $operandMin !== null ? ($rangeMax ?? INF) / $operandMin : $rangeMaxSign * -0.1;
2341: $max2 = $operandMax !== null ? ($rangeMax ?? INF) / $operandMax : $rangeMaxSign * 0.1;
2342:
2343: $min = min($min1, $min2, $max1, $max2);
2344: $max = max($min1, $min2, $max1, $max2);
2345:
2346: if ($min === -INF) {
2347: $min = null;
2348: }
2349: if ($max === INF) {
2350: $max = null;
2351: }
2352: }
2353:
2354: if ($min !== null && $max !== null && $min > $max) {
2355: [$min, $max] = [$max, $min];
2356: }
2357:
2358: if (is_float($min)) {
2359: $min = (int) ceil($min);
2360: }
2361: if (is_float($max)) {
2362: $max = (int) floor($max);
2363: }
2364:
2365: // invert maximas on division with negative constants
2366: if ((($range instanceof ConstantIntegerType && $range->getValue() < 0)
2367: || ($operand instanceof ConstantIntegerType && $operand->getValue() < 0))
2368: && ($min === null || $max === null)) {
2369: [$min, $max] = [$max, $min];
2370: }
2371:
2372: if ($min === null && $max === null) {
2373: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
2374: }
2375:
2376: return TypeCombinator::union(IntegerRangeType::fromInterval($min, $max), new FloatType());
2377: } elseif ($node instanceof Expr\BinaryOp\ShiftLeft) {
2378: if (!$operand instanceof ConstantIntegerType) {
2379: return new IntegerType();
2380: }
2381: if ($operand->getValue() < 0) {
2382: return new ErrorType();
2383: }
2384: $min = $rangeMin !== null ? intval($rangeMin) << $operand->getValue() : null;
2385: $max = $rangeMax !== null ? intval($rangeMax) << $operand->getValue() : null;
2386: } elseif ($node instanceof Expr\BinaryOp\ShiftRight) {
2387: if (!$operand instanceof ConstantIntegerType) {
2388: return new IntegerType();
2389: }
2390: if ($operand->getValue() < 0) {
2391: return new ErrorType();
2392: }
2393: $min = $rangeMin !== null ? intval($rangeMin) >> $operand->getValue() : null;
2394: $max = $rangeMax !== null ? intval($rangeMax) >> $operand->getValue() : null;
2395: } else {
2396: throw new ShouldNotHappenException();
2397: }
2398:
2399: if (is_float($min)) {
2400: $min = null;
2401: }
2402: if (is_float($max)) {
2403: $max = null;
2404: }
2405:
2406: return IntegerRangeType::fromInterval($min, $max);
2407: }
2408:
2409: /**
2410: * @param callable(Expr): Type $getTypeCallback
2411: */
2412: public function getClassConstFetchTypeByReflection(Name|Expr $class, string $constantName, ?ClassReflection $classReflection, callable $getTypeCallback): Type
2413: {
2414: $isObject = false;
2415: if ($class instanceof Name) {
2416: $constantClass = (string) $class;
2417: $constantClassType = new ObjectType($constantClass);
2418: $namesToResolve = [
2419: 'self',
2420: 'parent',
2421: ];
2422: if ($classReflection !== null) {
2423: if ($classReflection->isFinal()) {
2424: $namesToResolve[] = 'static';
2425: } elseif (strtolower($constantClass) === 'static') {
2426: if (strtolower($constantName) === 'class') {
2427: return new GenericClassStringType(new StaticType($classReflection));
2428: }
2429:
2430: $namesToResolve[] = 'static';
2431: $isObject = true;
2432: }
2433: }
2434: if (in_array(strtolower($constantClass), $namesToResolve, true)) {
2435: $resolvedName = $this->resolveName($class, $classReflection);
2436: if (strtolower($resolvedName) === 'parent' && strtolower($constantName) === 'class') {
2437: return new ClassStringType();
2438: }
2439: $constantClassType = $this->resolveTypeByName($class, $classReflection);
2440: }
2441:
2442: if (strtolower($constantName) === 'class') {
2443: return new ConstantStringType($constantClassType->getClassName(), true);
2444: }
2445: } elseif ($class instanceof String_ && strtolower($constantName) === 'class') {
2446: return new ConstantStringType($class->value, true);
2447: } else {
2448: $constantClassType = $getTypeCallback($class);
2449: $isObject = true;
2450: }
2451:
2452: if (strtolower($constantName) === 'class') {
2453: return $constantClassType->toClassConstantType($this->getReflectionProvider());
2454: }
2455:
2456: if ($constantClassType->isClassString()->yes()) {
2457: if ($constantClassType->isConstantScalarValue()->yes()) {
2458: $isObject = false;
2459: }
2460: $constantClassType = $constantClassType->getClassStringObjectType();
2461: }
2462:
2463: $types = [];
2464: foreach ($constantClassType->getObjectClassNames() as $referencedClass) {
2465: if (!$this->getReflectionProvider()->hasClass($referencedClass)) {
2466: continue;
2467: }
2468:
2469: $constantClassReflection = $this->getReflectionProvider()->getClass($referencedClass);
2470: if (!$constantClassReflection->hasConstant($constantName)) {
2471: if ($constantClassReflection->getName() === 'Attribute' && $constantName === 'TARGET_CONSTANT') {
2472: return new ConstantIntegerType(1 << 16);
2473: }
2474: continue;
2475: }
2476:
2477: if ($constantClassReflection->isEnum() && $constantClassReflection->hasEnumCase($constantName)) {
2478: $types[] = new EnumCaseObjectType($constantClassReflection->getName(), $constantName);
2479: continue;
2480: }
2481:
2482: $resolvingName = sprintf('%s::%s', $constantClassReflection->getName(), $constantName);
2483: if (array_key_exists($resolvingName, $this->currentlyResolvingClassConstant)) {
2484: $types[] = new MixedType();
2485: continue;
2486: }
2487:
2488: if (!$isObject && array_key_exists($resolvingName, $this->classConstantValueTypeCache)) {
2489: $types[] = $this->classConstantValueTypeCache[$resolvingName];
2490: continue;
2491: }
2492:
2493: $this->currentlyResolvingClassConstant[$resolvingName] = true;
2494:
2495: if (!$isObject) {
2496: $reflectionConstant = $constantClassReflection->getNativeReflection()->getReflectionConstant($constantName);
2497: if ($reflectionConstant === false) {
2498: unset($this->currentlyResolvingClassConstant[$resolvingName]);
2499: continue;
2500: }
2501: $reflectionConstantDeclaringClass = $reflectionConstant->getDeclaringClass();
2502: $constantType = $this->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($reflectionConstantDeclaringClass->getName(), $reflectionConstantDeclaringClass->getFileName() ?: null));
2503: $nativeType = null;
2504: if ($reflectionConstant->getType() !== null) {
2505: $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $constantClassReflection);
2506: }
2507: $resolvedType = $this->constantResolver->resolveClassConstantType(
2508: $constantClassReflection->getName(),
2509: $constantName,
2510: $constantType,
2511: $nativeType,
2512: $constantClassReflection->getConstantPhpDocType($constantName),
2513: );
2514: $this->classConstantValueTypeCache[$resolvingName] = $resolvedType;
2515: $types[] = $resolvedType;
2516: unset($this->currentlyResolvingClassConstant[$resolvingName]);
2517: continue;
2518: }
2519:
2520: $constantReflection = $constantClassReflection->getConstant($constantName);
2521: if (
2522: !$constantClassReflection->isFinal()
2523: && !$constantReflection->isFinal()
2524: && !$constantReflection->hasPhpDocType()
2525: && !$constantReflection->hasNativeType()
2526: ) {
2527: unset($this->currentlyResolvingClassConstant[$resolvingName]);
2528: return new MixedType();
2529: }
2530:
2531: if (!$constantClassReflection->isFinal()) {
2532: $constantType = $constantReflection->getValueType();
2533: } else {
2534: $constantType = $this->getType($constantReflection->getValueExpr(), InitializerExprContext::fromClassReflection($constantReflection->getDeclaringClass()));
2535: }
2536:
2537: $nativeType = $constantReflection->getNativeType();
2538: $constantType = $this->constantResolver->resolveClassConstantType(
2539: $constantClassReflection->getName(),
2540: $constantName,
2541: $constantType,
2542: $nativeType,
2543: $constantClassReflection->getConstantPhpDocType($constantName),
2544: );
2545: unset($this->currentlyResolvingClassConstant[$resolvingName]);
2546: $types[] = $constantType;
2547: }
2548:
2549: if (count($types) > 0) {
2550: return TypeCombinator::union(...$types);
2551: }
2552:
2553: if (!$constantClassType->hasConstant($constantName)->yes()) {
2554: return new ErrorType();
2555: }
2556:
2557: return $constantClassType->getConstant($constantName)->getValueType();
2558: }
2559:
2560: /**
2561: * @param callable(Expr): Type $getTypeCallback
2562: */
2563: public function getClassConstFetchType(Name|Expr $class, string $constantName, ?string $className, callable $getTypeCallback): Type
2564: {
2565: $classReflection = null;
2566: if ($className !== null && $this->getReflectionProvider()->hasClass($className)) {
2567: $classReflection = $this->getReflectionProvider()->getClass($className);
2568: }
2569:
2570: return $this->getClassConstFetchTypeByReflection($class, $constantName, $classReflection, $getTypeCallback);
2571: }
2572:
2573: /**
2574: * @param callable(Expr): Type $getTypeCallback
2575: */
2576: public function getUnaryPlusType(Expr $expr, callable $getTypeCallback): Type
2577: {
2578: $type = $getTypeCallback($expr);
2579:
2580: $specifiedTypes = $this->unaryOperatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
2581: ->callUnaryOperatorTypeSpecifyingExtensions('+', $type);
2582: if ($specifiedTypes !== null) {
2583: return $specifiedTypes;
2584: }
2585:
2586: return $type->toNumber();
2587: }
2588:
2589: /**
2590: * @param callable(Expr): Type $getTypeCallback
2591: */
2592: public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type
2593: {
2594: $type = $getTypeCallback($expr);
2595:
2596: $specifiedTypes = $this->unaryOperatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
2597: ->callUnaryOperatorTypeSpecifyingExtensions('-', $type);
2598: if ($specifiedTypes !== null) {
2599: return $specifiedTypes;
2600: }
2601:
2602: $type = $this->getUnaryMinusTypeFromType($expr, $type);
2603: if ($type instanceof IntegerRangeType) {
2604: return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new Int_(-1)));
2605: }
2606:
2607: return $type;
2608: }
2609:
2610: public function getUnaryMinusTypeFromType(Expr $expr, Type $type): Type
2611: {
2612: $type = $type->toNumber();
2613: $scalarValues = $type->getConstantScalarValues();
2614:
2615: if (count($scalarValues) > 0) {
2616: $newTypes = [];
2617: foreach ($scalarValues as $scalarValue) {
2618: if (is_int($scalarValue)) {
2619: /** @var int|float $newValue */
2620: $newValue = -$scalarValue;
2621: if (!is_int($newValue)) {
2622: return $type;
2623: }
2624: $newTypes[] = new ConstantIntegerType($newValue);
2625: } elseif (is_float($scalarValue)) {
2626: $newTypes[] = new ConstantFloatType(-$scalarValue);
2627: }
2628: }
2629:
2630: return TypeCombinator::union(...$newTypes);
2631: }
2632:
2633: return $type;
2634: }
2635:
2636: /**
2637: * @param callable(Expr): Type $getTypeCallback
2638: */
2639: public function getBitwiseNotType(Expr $expr, callable $getTypeCallback): Type
2640: {
2641: $exprType = $getTypeCallback($expr);
2642:
2643: $specifiedTypes = $this->unaryOperatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
2644: ->callUnaryOperatorTypeSpecifyingExtensions('~', $exprType);
2645: if ($specifiedTypes !== null) {
2646: return $specifiedTypes;
2647: }
2648:
2649: return $this->getBitwiseNotTypeFromType($exprType);
2650: }
2651:
2652: public function getBitwiseNotTypeFromType(Type $exprType): Type
2653: {
2654: return $exprType->toBitwiseNotType();
2655: }
2656:
2657: private function resolveName(Name $name, ?ClassReflection $classReflection): string
2658: {
2659: $originalClass = (string) $name;
2660: if ($classReflection !== null) {
2661: $lowerClass = strtolower($originalClass);
2662:
2663: if (in_array($lowerClass, [
2664: 'self',
2665: 'static',
2666: ], true)) {
2667: return $classReflection->getName();
2668: } elseif ($lowerClass === 'parent') {
2669: if ($classReflection->getParentClass() !== null) {
2670: return $classReflection->getParentClass()->getName();
2671: }
2672: }
2673: }
2674:
2675: return $originalClass;
2676: }
2677:
2678: private function resolveTypeByName(Name $name, ?ClassReflection $classReflection): TypeWithClassName
2679: {
2680: if ($name->toLowerString() === 'static' && $classReflection !== null) {
2681: return new StaticType($classReflection);
2682: }
2683:
2684: $originalClass = $this->resolveName($name, $classReflection);
2685: if ($classReflection !== null) {
2686: $thisType = new ThisType($classReflection);
2687: $ancestor = $thisType->getAncestorWithClassName($originalClass);
2688: if ($ancestor !== null) {
2689: return $ancestor;
2690: }
2691: }
2692:
2693: return new ObjectType($originalClass);
2694: }
2695:
2696: private function resolveTypeByNameWithLateStaticBinding(Name $class, Type $classType, MethodReflection $methodReflectionCandidate): Type
2697: {
2698: if (
2699: $classType instanceof StaticType
2700: && !in_array($class->toLowerString(), ['self', 'static', 'parent'], true)
2701: ) {
2702: if ($methodReflectionCandidate->isStatic()) {
2703: $classType = $classType->getStaticObjectType();
2704: }
2705: }
2706:
2707: return $classType;
2708: }
2709:
2710: /**
2711: * @param mixed $value
2712: */
2713: private function getTypeFromValue($value): Type
2714: {
2715: return ConstantTypeHelper::getTypeFromValue($value);
2716: }
2717:
2718: private function getReflectionProvider(): ReflectionProvider
2719: {
2720: return $this->reflectionProviderProvider->getReflectionProvider();
2721: }
2722:
2723: private function getNeverType(Type $leftType, Type $rightType): Type
2724: {
2725: // make sure we don't lose the explicit flag in the process
2726: if ($leftType instanceof NeverType && $leftType->isExplicit()) {
2727: return $leftType;
2728: }
2729: if ($rightType instanceof NeverType && $rightType->isExplicit()) {
2730: return $rightType;
2731: }
2732: return new NeverType();
2733: }
2734:
2735: }
2736: