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