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