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