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