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: $closureTypes[] = new ClosureType(
962: $parameters,
963: $returnType,
964: $variant->isVariadic(),
965: $variant->getTemplateTypeMap(),
966: $variant->getResolvedTemplateTypeMap(),
967: $variant instanceof ExtendedParametersAcceptor ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
968: $templateTags,
969: $throwPoints,
970: $impurePoints,
971: acceptsNamedArguments: $acceptsNamedArguments,
972: mustUseReturnValue: $mustUseReturnValue,
973: );
974: }
975:
976: return TypeCombinator::union(...$closureTypes);
977: }
978:
979: /**
980: * @param callable(Expr): Type $getTypeCallback
981: */
982: public function getBitwiseAndType(Expr $left, Expr $right, callable $getTypeCallback): Type
983: {
984: $leftType = $getTypeCallback($left);
985: $rightType = $getTypeCallback($right);
986:
987: return $this->getBitwiseAndTypeFromTypes($leftType, $rightType);
988: }
989:
990: public function getBitwiseAndTypeFromTypes(Type $leftType, Type $rightType): Type
991: {
992: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
993: return $this->getNeverType($leftType, $rightType);
994: }
995:
996: $result = $this->getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a & $b);
997: if ($result instanceof Type) {
998: return $result;
999: } elseif ($result === self::IS_SCALAR_TYPE) {
1000: $leftType = $this->optimizeScalarType($leftType);
1001: $rightType = $this->optimizeScalarType($rightType);
1002: }
1003:
1004: if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1005: return new BenevolentUnionType([new IntegerType(), new StringType()]);
1006: }
1007:
1008: $leftIsString = $leftType->isString();
1009: $rightIsString = $rightType->isString();
1010: if (
1011: ($leftIsString->yes() || $leftType instanceof MixedType)
1012: && ($rightIsString->yes() || $rightType instanceof MixedType)
1013: ) {
1014: return new StringType();
1015: }
1016: if ($leftIsString->maybe() && $rightIsString->maybe()) {
1017: return new ErrorType();
1018: }
1019:
1020: $leftNumberType = $leftType->toNumber();
1021: $rightNumberType = $rightType->toNumber();
1022:
1023: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1024: return new ErrorType();
1025: }
1026:
1027: if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() >= 0) {
1028: return IntegerRangeType::fromInterval(0, $rightNumberType->getValue());
1029: }
1030: if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() >= 0) {
1031: return IntegerRangeType::fromInterval(0, $leftNumberType->getValue());
1032: }
1033:
1034: return new IntegerType();
1035: }
1036:
1037: /**
1038: * @param callable(Expr): Type $getTypeCallback
1039: */
1040: public function getBitwiseOrType(Expr $left, Expr $right, callable $getTypeCallback): Type
1041: {
1042: $leftType = $getTypeCallback($left);
1043: $rightType = $getTypeCallback($right);
1044:
1045: return $this->getBitwiseOrTypeFromTypes($leftType, $rightType);
1046: }
1047:
1048: public function getBitwiseOrTypeFromTypes(Type $leftType, Type $rightType): Type
1049: {
1050: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1051: return $this->getNeverType($leftType, $rightType);
1052: }
1053:
1054: $result = $this->getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a | $b);
1055: if ($result instanceof Type) {
1056: return $result;
1057: } elseif ($result === self::IS_SCALAR_TYPE) {
1058: $leftType = $this->optimizeScalarType($leftType);
1059: $rightType = $this->optimizeScalarType($rightType);
1060: }
1061:
1062: if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1063: return new BenevolentUnionType([new IntegerType(), new StringType()]);
1064: }
1065:
1066: $leftIsString = $leftType->isString();
1067: $rightIsString = $rightType->isString();
1068: if (
1069: ($leftIsString->yes() || $leftType instanceof MixedType)
1070: && ($rightIsString->yes() || $rightType instanceof MixedType)
1071: ) {
1072: return new StringType();
1073: }
1074: if ($leftIsString->maybe() && $rightIsString->maybe()) {
1075: return new ErrorType();
1076: }
1077:
1078: if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) {
1079: return new ErrorType();
1080: }
1081:
1082: return new IntegerType();
1083: }
1084:
1085: /**
1086: * @param callable(Expr): Type $getTypeCallback
1087: */
1088: public function getBitwiseXorType(Expr $left, Expr $right, callable $getTypeCallback): Type
1089: {
1090: $leftType = $getTypeCallback($left);
1091: $rightType = $getTypeCallback($right);
1092:
1093: return $this->getBitwiseXorTypeFromTypes($leftType, $rightType);
1094: }
1095:
1096: private const IS_SCALAR_TYPE = 1;
1097: private const IS_UNKNOWN = 2;
1098:
1099: /**
1100: * @param callable(bool|float|int|string|null, bool|float|int|string|null):string $operationCallable
1101: *
1102: * @return self::IS_UNKNOWN|self::IS_SCALAR_TYPE|Type
1103: */
1104: private function getFiniteOrConstantScalarTypes(Type $leftType, Type $rightType, callable $operationCallable): int|Type
1105: {
1106: if ($leftType instanceof IntegerRangeType) {
1107: $leftTypes = $leftType->getFiniteTypes();
1108: } else {
1109: $leftTypes = $leftType->getConstantScalarTypes();
1110: }
1111: if ($rightType instanceof IntegerRangeType) {
1112: $rightTypes = $rightType->getFiniteTypes();
1113: } else {
1114: $rightTypes = $rightType->getConstantScalarTypes();
1115: }
1116:
1117: $leftTypesCount = count($leftTypes);
1118: $rightTypesCount = count($rightTypes);
1119:
1120: if ($leftTypesCount === 0 || $rightTypesCount === 0) {
1121: return self::IS_UNKNOWN;
1122: }
1123:
1124: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1125: if ($generalize) {
1126: return self::IS_SCALAR_TYPE;
1127: }
1128:
1129: $resultTypes = [];
1130: foreach ($leftTypes as $leftTypeInner) {
1131: foreach ($rightTypes as $rightTypeInner) {
1132: if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) {
1133: $resultValue = $operationCallable($leftTypeInner->getValue(), $rightTypeInner->getValue());
1134: $resultType = $this->getTypeFromValue($resultValue);
1135: } else {
1136: $leftNumberType = $leftTypeInner->toNumber();
1137: $rightNumberType = $rightTypeInner->toNumber();
1138:
1139: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1140: return new ErrorType();
1141: }
1142:
1143: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1144: throw new ShouldNotHappenException();
1145: }
1146:
1147: $resultValue = $operationCallable($leftNumberType->getValue(), $rightNumberType->getValue());
1148: $resultType = $this->getTypeFromValue($resultValue);
1149: }
1150: $resultTypes[] = $resultType;
1151: }
1152: }
1153: return TypeCombinator::union(...$resultTypes);
1154: }
1155:
1156: public function getBitwiseXorTypeFromTypes(Type $leftType, Type $rightType): Type
1157: {
1158: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1159: return $this->getNeverType($leftType, $rightType);
1160: }
1161:
1162: $result = $this->getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a ^ $b);
1163: if ($result instanceof Type) {
1164: return $result;
1165: } elseif ($result === self::IS_SCALAR_TYPE) {
1166: $leftType = $this->optimizeScalarType($leftType);
1167: $rightType = $this->optimizeScalarType($rightType);
1168: }
1169:
1170: if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1171: return new BenevolentUnionType([new IntegerType(), new StringType()]);
1172: }
1173:
1174: $leftIsString = $leftType->isString();
1175: $rightIsString = $rightType->isString();
1176: if (
1177: ($leftIsString->yes() || $leftType instanceof MixedType)
1178: && ($rightIsString->yes() || $rightType instanceof MixedType)
1179: ) {
1180: return new StringType();
1181: }
1182: if ($leftIsString->maybe() && $rightIsString->maybe()) {
1183: return new ErrorType();
1184: }
1185:
1186: if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) {
1187: return new ErrorType();
1188: }
1189:
1190: return new IntegerType();
1191: }
1192:
1193: /**
1194: * @param callable(Expr): Type $getTypeCallback
1195: */
1196: public function getSpaceshipType(Expr $left, Expr $right, callable $getTypeCallback): Type
1197: {
1198: $callbackLeftType = $getTypeCallback($left);
1199: $callbackRightType = $getTypeCallback($right);
1200:
1201: return $this->getSpaceshipTypeFromTypes($callbackLeftType, $callbackRightType);
1202: }
1203:
1204: public function getSpaceshipTypeFromTypes(Type $leftTypes, Type $rightTypes): Type
1205: {
1206: if ($leftTypes instanceof NeverType || $rightTypes instanceof NeverType) {
1207: return $this->getNeverType($leftTypes, $rightTypes);
1208: }
1209:
1210: $leftTypes = $leftTypes->getConstantScalarTypes();
1211: $rightTypes = $rightTypes->getConstantScalarTypes();
1212:
1213: $leftTypesCount = count($leftTypes);
1214: $rightTypesCount = count($rightTypes);
1215: if ($leftTypesCount > 0 && $rightTypesCount > 0 && $leftTypesCount * $rightTypesCount <= self::CALCULATE_SCALARS_LIMIT) {
1216: $resultTypes = [];
1217: foreach ($leftTypes as $leftType) {
1218: foreach ($rightTypes as $rightType) {
1219: $leftValue = $leftType->getValue();
1220: $rightValue = $rightType->getValue();
1221: $resultType = $this->getTypeFromValue($leftValue <=> $rightValue);
1222: $resultTypes[] = $resultType;
1223: }
1224: }
1225: return TypeCombinator::union(...$resultTypes);
1226: }
1227:
1228: return IntegerRangeType::fromInterval(-1, 1);
1229: }
1230:
1231: /**
1232: * @param callable(Expr): Type $getTypeCallback
1233: */
1234: public function getDivType(Expr $left, Expr $right, callable $getTypeCallback): Type
1235: {
1236: $leftType = $getTypeCallback($left);
1237: $rightType = $getTypeCallback($right);
1238:
1239: return $this->getDivTypeFromTypes($left, $right, $leftType, $rightType);
1240: }
1241:
1242: public function getDivTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1243: {
1244: $leftTypes = $leftType->getConstantScalarTypes();
1245: $rightTypes = $rightType->getConstantScalarTypes();
1246: $leftTypesCount = count($leftTypes);
1247: $rightTypesCount = count($rightTypes);
1248: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1249: $resultTypes = [];
1250: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1251: if (!$generalize) {
1252: foreach ($leftTypes as $leftTypeInner) {
1253: foreach ($rightTypes as $rightTypeInner) {
1254: $leftNumberType = $leftTypeInner->toNumber();
1255: $rightNumberType = $rightTypeInner->toNumber();
1256:
1257: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1258: return new ErrorType();
1259: }
1260:
1261: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1262: throw new ShouldNotHappenException();
1263: }
1264:
1265: if (in_array($rightNumberType->getValue(), [0, 0.0], true)) {
1266: return new ErrorType();
1267: }
1268:
1269: $resultType = $this->getTypeFromValue($leftNumberType->getValue() / $rightNumberType->getValue()); // @phpstan-ignore binaryOp.invalid
1270: $resultTypes[] = $resultType;
1271: }
1272: }
1273: return TypeCombinator::union(...$resultTypes);
1274: }
1275:
1276: $leftType = $this->optimizeScalarType($leftType);
1277: $rightType = $this->optimizeScalarType($rightType);
1278: }
1279:
1280: $rightScalarValues = $rightType->toNumber()->getConstantScalarValues();
1281: foreach ($rightScalarValues as $scalarValue) {
1282: if (in_array($scalarValue, [0, 0.0], true)) {
1283: return new ErrorType();
1284: }
1285: }
1286:
1287: return $this->resolveCommonMath(new BinaryOp\Div($left, $right), $leftType, $rightType);
1288: }
1289:
1290: /**
1291: * @param callable(Expr): Type $getTypeCallback
1292: */
1293: public function getModType(Expr $left, Expr $right, callable $getTypeCallback): Type
1294: {
1295: $leftType = $getTypeCallback($left);
1296: $rightType = $getTypeCallback($right);
1297:
1298: return $this->getModTypeFromTypes($left, $right, $leftType, $rightType);
1299: }
1300:
1301: public function getModTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1302: {
1303: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1304: return $this->getNeverType($leftType, $rightType);
1305: }
1306:
1307: $extensionSpecified = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
1308: ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Mod($left, $right), $leftType, $rightType);
1309: if ($extensionSpecified !== null) {
1310: return $extensionSpecified;
1311: }
1312:
1313: if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) {
1314: return new ErrorType();
1315: }
1316:
1317: $leftTypes = $leftType->getConstantScalarTypes();
1318: $rightTypes = $rightType->getConstantScalarTypes();
1319: $leftTypesCount = count($leftTypes);
1320: $rightTypesCount = count($rightTypes);
1321: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1322: $resultTypes = [];
1323: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1324: if (!$generalize) {
1325: foreach ($leftTypes as $leftTypeInner) {
1326: foreach ($rightTypes as $rightTypeInner) {
1327: $leftNumberType = $leftTypeInner->toNumber();
1328: $rightNumberType = $rightTypeInner->toNumber();
1329:
1330: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1331: return new ErrorType();
1332: }
1333:
1334: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1335: throw new ShouldNotHappenException();
1336: }
1337:
1338: $rightIntegerValue = (int) $rightNumberType->getValue();
1339: if ($rightIntegerValue === 0) {
1340: return new ErrorType();
1341: }
1342:
1343: $resultType = $this->getTypeFromValue((int) $leftNumberType->getValue() % $rightIntegerValue);
1344: $resultTypes[] = $resultType;
1345: }
1346: }
1347: return TypeCombinator::union(...$resultTypes);
1348: }
1349:
1350: $leftType = $this->optimizeScalarType($leftType);
1351: $rightType = $this->optimizeScalarType($rightType);
1352: }
1353:
1354: $integerType = $rightType->toInteger();
1355: if ($integerType instanceof ConstantIntegerType && $integerType->getValue() === 1) {
1356: return new ConstantIntegerType(0);
1357: }
1358:
1359: $rightScalarValues = $rightType->toNumber()->getConstantScalarValues();
1360: foreach ($rightScalarValues as $scalarValue) {
1361:
1362: if (in_array($scalarValue, [0, 0.0], true)) {
1363: return new ErrorType();
1364: }
1365: }
1366:
1367: $positiveInt = IntegerRangeType::fromInterval(0, null);
1368: if ($rightType->isInteger()->yes()) {
1369: $rangeMin = null;
1370: $rangeMax = null;
1371:
1372: if ($rightType instanceof IntegerRangeType) {
1373: $rangeMax = $rightType->getMax() !== null ? $rightType->getMax() - 1 : null;
1374: } elseif ($rightType instanceof ConstantIntegerType) {
1375: $rangeMax = $rightType->getValue() - 1;
1376: } elseif ($rightType instanceof UnionType) {
1377: foreach ($rightType->getTypes() as $type) {
1378: if ($type instanceof IntegerRangeType) {
1379: if ($type->getMax() === null) {
1380: $rangeMax = null;
1381: } else {
1382: $rangeMax = max($rangeMax, $type->getMax());
1383: }
1384: } elseif ($type instanceof ConstantIntegerType) {
1385: $rangeMax = max($rangeMax, $type->getValue() - 1);
1386: }
1387: }
1388: }
1389:
1390: if ($positiveInt->isSuperTypeOf($leftType)->yes()) {
1391: $rangeMin = 0;
1392: } elseif ($rangeMax !== null) {
1393: $rangeMin = $rangeMax * -1;
1394: }
1395:
1396: return IntegerRangeType::fromInterval($rangeMin, $rangeMax);
1397: } elseif ($positiveInt->isSuperTypeOf($leftType)->yes()) {
1398: return IntegerRangeType::fromInterval(0, null);
1399: }
1400:
1401: return new IntegerType();
1402: }
1403:
1404: /**
1405: * @param callable(Expr): Type $getTypeCallback
1406: */
1407: public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): Type
1408: {
1409: $leftType = $getTypeCallback($left);
1410: $rightType = $getTypeCallback($right);
1411:
1412: return $this->getPlusTypeFromTypes($left, $right, $leftType, $rightType);
1413: }
1414:
1415: public function getPlusTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1416: {
1417: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1418: return $this->getNeverType($leftType, $rightType);
1419: }
1420:
1421: $leftTypes = $leftType->getConstantScalarTypes();
1422: $rightTypes = $rightType->getConstantScalarTypes();
1423: $leftTypesCount = count($leftTypes);
1424: $rightTypesCount = count($rightTypes);
1425: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1426: $resultTypes = [];
1427: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1428: if (!$generalize) {
1429: foreach ($leftTypes as $leftTypeInner) {
1430: foreach ($rightTypes as $rightTypeInner) {
1431: $leftNumberType = $leftTypeInner->toNumber();
1432: $rightNumberType = $rightTypeInner->toNumber();
1433:
1434: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1435: return new ErrorType();
1436: }
1437:
1438: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1439: throw new ShouldNotHappenException();
1440: }
1441:
1442: $resultType = $this->getTypeFromValue($leftNumberType->getValue() + $rightNumberType->getValue());
1443: $resultTypes[] = $resultType;
1444: }
1445: }
1446:
1447: return TypeCombinator::union(...$resultTypes);
1448: }
1449:
1450: $leftType = $this->optimizeScalarType($leftType);
1451: $rightType = $this->optimizeScalarType($rightType);
1452: }
1453:
1454: $leftConstantArrays = $leftType->getConstantArrays();
1455: $rightConstantArrays = $rightType->getConstantArrays();
1456:
1457: $leftCount = count($leftConstantArrays);
1458: $rightCount = count($rightConstantArrays);
1459: if ($leftCount > 0 && $rightCount > 0
1460: && ($leftCount + $rightCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT)) {
1461: $resultTypes = [];
1462: foreach ($rightConstantArrays as $rightConstantArray) {
1463: foreach ($leftConstantArrays as $leftConstantArray) {
1464: $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray);
1465: foreach ($leftConstantArray->getKeyTypes() as $i => $leftKeyType) {
1466: $optional = $leftConstantArray->isOptionalKey($i);
1467: $valueType = $leftConstantArray->getOffsetValueType($leftKeyType);
1468: if (!$optional) {
1469: if ($rightConstantArray->hasOffsetValueType($leftKeyType)->maybe()) {
1470: $valueType = TypeCombinator::union($valueType, $rightConstantArray->getOffsetValueType($leftKeyType));
1471: }
1472: }
1473: $newArrayBuilder->setOffsetValueType(
1474: $leftKeyType,
1475: $valueType,
1476: $optional,
1477: );
1478: }
1479: $resultTypes[] = $newArrayBuilder->getArray();
1480: }
1481: }
1482: return TypeCombinator::union(...$resultTypes);
1483: }
1484:
1485: $leftIsArray = $leftType->isArray();
1486: $rightIsArray = $rightType->isArray();
1487: if ($leftIsArray->yes() && $rightIsArray->yes()) {
1488: if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) {
1489: // to preserve BenevolentUnionType
1490: $keyType = $leftType->getIterableKeyType();
1491: } else {
1492: $keyTypes = [];
1493: foreach ([
1494: $leftType->getIterableKeyType(),
1495: $rightType->getIterableKeyType(),
1496: ] as $keyType) {
1497: $keyTypes[] = $keyType;
1498: }
1499: $keyType = TypeCombinator::union(...$keyTypes);
1500: }
1501:
1502: $arrayType = new ArrayType(
1503: $keyType,
1504: TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType()),
1505: );
1506:
1507: $accessories = [];
1508: foreach ($leftType->getConstantArrays() as $type) {
1509: foreach ($type->getKeyTypes() as $i => $offsetType) {
1510: if ($type->isOptionalKey($i)) {
1511: continue;
1512: }
1513: $valueType = $type->getValueTypes()[$i];
1514: $accessories[] = new HasOffsetValueType($offsetType, $valueType);
1515: }
1516: }
1517:
1518: if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) {
1519: $accessories[] = new NonEmptyArrayType();
1520: }
1521: if ($leftType->isList()->yes() && $rightType->isList()->yes()) {
1522: $accessories[] = new AccessoryArrayListType();
1523: }
1524:
1525: if (count($accessories) > 0) {
1526: $arrayType = TypeCombinator::intersect($arrayType, ...$accessories);
1527: }
1528:
1529: return $arrayType;
1530: }
1531:
1532: if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1533: if (
1534: ($leftIsArray->no() && $rightIsArray->no())
1535: ) {
1536: return new BenevolentUnionType([
1537: new FloatType(),
1538: new IntegerType(),
1539: ]);
1540: }
1541: return new BenevolentUnionType([
1542: new FloatType(),
1543: new IntegerType(),
1544: new ArrayType(new MixedType(), new MixedType()),
1545: ]);
1546: }
1547:
1548: if (
1549: ($leftIsArray->yes() && $rightIsArray->no())
1550: || ($leftIsArray->no() && $rightIsArray->yes())
1551: ) {
1552: return new ErrorType();
1553: }
1554:
1555: if (
1556: ($leftIsArray->yes() && $rightIsArray->maybe())
1557: || ($leftIsArray->maybe() && $rightIsArray->yes())
1558: ) {
1559: $resultType = new ArrayType(new MixedType(), new MixedType());
1560: if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) {
1561: return TypeCombinator::intersect($resultType, new NonEmptyArrayType());
1562: }
1563:
1564: return $resultType;
1565: }
1566:
1567: if ($leftIsArray->maybe() && $rightIsArray->maybe()) {
1568: $plusable = new UnionType([
1569: new StringType(),
1570: new FloatType(),
1571: new IntegerType(),
1572: new ArrayType(new MixedType(), new MixedType()),
1573: new BooleanType(),
1574: ]);
1575:
1576: $plusableSuperTypeOfLeft = $plusable->isSuperTypeOf($leftType)->yes();
1577: $plusableSuperTypeOfRight = $plusable->isSuperTypeOf($rightType)->yes();
1578: if ($plusableSuperTypeOfLeft && $plusableSuperTypeOfRight) {
1579: return TypeCombinator::union($leftType, $rightType);
1580: }
1581: if ($plusableSuperTypeOfLeft && $rightType instanceof MixedType) {
1582: return $leftType;
1583: }
1584: if ($plusableSuperTypeOfRight && $leftType instanceof MixedType) {
1585: return $rightType;
1586: }
1587: }
1588:
1589: return $this->resolveCommonMath(new BinaryOp\Plus($left, $right), $leftType, $rightType);
1590: }
1591:
1592: /**
1593: * @param callable(Expr): Type $getTypeCallback
1594: */
1595: public function getMinusType(Expr $left, Expr $right, callable $getTypeCallback): Type
1596: {
1597: $leftType = $getTypeCallback($left);
1598: $rightType = $getTypeCallback($right);
1599:
1600: return $this->getMinusTypeFromTypes($left, $right, $leftType, $rightType);
1601: }
1602:
1603: public function getMinusTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1604: {
1605: $leftTypes = $leftType->getConstantScalarTypes();
1606: $rightTypes = $rightType->getConstantScalarTypes();
1607: $leftTypesCount = count($leftTypes);
1608: $rightTypesCount = count($rightTypes);
1609: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1610: $resultTypes = [];
1611: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1612: if (!$generalize) {
1613: foreach ($leftTypes as $leftTypeInner) {
1614: foreach ($rightTypes as $rightTypeInner) {
1615: $leftNumberType = $leftTypeInner->toNumber();
1616: $rightNumberType = $rightTypeInner->toNumber();
1617:
1618: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1619: return new ErrorType();
1620: }
1621:
1622: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1623: throw new ShouldNotHappenException();
1624: }
1625:
1626: $resultType = $this->getTypeFromValue($leftNumberType->getValue() - $rightNumberType->getValue());
1627: $resultTypes[] = $resultType;
1628: }
1629: }
1630:
1631: return TypeCombinator::union(...$resultTypes);
1632: }
1633:
1634: $leftType = $this->optimizeScalarType($leftType);
1635: $rightType = $this->optimizeScalarType($rightType);
1636: }
1637:
1638: return $this->resolveCommonMath(new BinaryOp\Minus($left, $right), $leftType, $rightType);
1639: }
1640:
1641: /**
1642: * @param callable(Expr): Type $getTypeCallback
1643: */
1644: public function getMulType(Expr $left, Expr $right, callable $getTypeCallback): Type
1645: {
1646: $leftType = $getTypeCallback($left);
1647: $rightType = $getTypeCallback($right);
1648:
1649: return $this->getMulTypeFromTypes($left, $right, $leftType, $rightType);
1650: }
1651:
1652: public function getMulTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1653: {
1654: $leftTypes = $leftType->getConstantScalarTypes();
1655: $rightTypes = $rightType->getConstantScalarTypes();
1656: $leftTypesCount = count($leftTypes);
1657: $rightTypesCount = count($rightTypes);
1658: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1659: $resultTypes = [];
1660: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1661: if (!$generalize) {
1662: foreach ($leftTypes as $leftTypeInner) {
1663: foreach ($rightTypes as $rightTypeInner) {
1664: $leftNumberType = $leftTypeInner->toNumber();
1665: $rightNumberType = $rightTypeInner->toNumber();
1666:
1667: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1668: return new ErrorType();
1669: }
1670:
1671: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1672: throw new ShouldNotHappenException();
1673: }
1674:
1675: $resultType = $this->getTypeFromValue($leftNumberType->getValue() * $rightNumberType->getValue());
1676: $resultTypes[] = $resultType;
1677: }
1678: }
1679:
1680: return TypeCombinator::union(...$resultTypes);
1681: }
1682:
1683: $leftType = $this->optimizeScalarType($leftType);
1684: $rightType = $this->optimizeScalarType($rightType);
1685: }
1686:
1687: $leftNumberType = $leftType->toNumber();
1688: if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0) {
1689: if ($rightType->isFloat()->yes()) {
1690: return new ConstantFloatType(0.0);
1691: }
1692: return new ConstantIntegerType(0);
1693: }
1694: $rightNumberType = $rightType->toNumber();
1695: if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0) {
1696: if ($leftType->isFloat()->yes()) {
1697: return new ConstantFloatType(0.0);
1698: }
1699: return new ConstantIntegerType(0);
1700: }
1701:
1702: return $this->resolveCommonMath(new BinaryOp\Mul($left, $right), $leftType, $rightType);
1703: }
1704:
1705: /**
1706: * @param callable(Expr): Type $getTypeCallback
1707: */
1708: public function getPowType(Expr $left, Expr $right, callable $getTypeCallback): Type
1709: {
1710: $leftType = $getTypeCallback($left);
1711: $rightType = $getTypeCallback($right);
1712:
1713: return $this->getPowTypeFromTypes($left, $right, $leftType, $rightType);
1714: }
1715:
1716: public function getPowTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1717: {
1718: $extensionSpecified = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
1719: ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Pow($left, $right), $leftType, $rightType);
1720: if ($extensionSpecified !== null) {
1721: return $extensionSpecified;
1722: }
1723:
1724: $exponentiatedTyped = $leftType->exponentiate($rightType);
1725: if (!$exponentiatedTyped instanceof ErrorType) {
1726: return $exponentiatedTyped;
1727: }
1728:
1729: return new ErrorType();
1730: }
1731:
1732: /**
1733: * @param callable(Expr): Type $getTypeCallback
1734: */
1735: public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallback): Type
1736: {
1737: $leftType = $getTypeCallback($left);
1738: $rightType = $getTypeCallback($right);
1739:
1740: return $this->getShiftLeftTypeFromTypes($left, $right, $leftType, $rightType);
1741: }
1742:
1743: public function getShiftLeftTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1744: {
1745: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1746: return $this->getNeverType($leftType, $rightType);
1747: }
1748:
1749: $leftTypes = $leftType->getConstantScalarTypes();
1750: $rightTypes = $rightType->getConstantScalarTypes();
1751: $leftTypesCount = count($leftTypes);
1752: $rightTypesCount = count($rightTypes);
1753: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1754: $resultTypes = [];
1755: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1756: if (!$generalize) {
1757: foreach ($leftTypes as $leftTypeInner) {
1758: foreach ($rightTypes as $rightTypeInner) {
1759: $leftNumberType = $leftTypeInner->toNumber();
1760: $rightNumberType = $rightTypeInner->toNumber();
1761:
1762: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1763: return new ErrorType();
1764: }
1765:
1766: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1767: throw new ShouldNotHappenException();
1768: }
1769:
1770: if ($rightNumberType->getValue() < 0) {
1771: return new ErrorType();
1772: }
1773:
1774: $resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) << intval($rightNumberType->getValue()));
1775: $resultTypes[] = $resultType;
1776: }
1777: }
1778:
1779: return TypeCombinator::union(...$resultTypes);
1780: }
1781:
1782: $leftType = $this->optimizeScalarType($leftType);
1783: $rightType = $this->optimizeScalarType($rightType);
1784: }
1785:
1786: $leftNumberType = $leftType->toNumber();
1787: $rightNumberType = $rightType->toNumber();
1788:
1789: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1790: return new ErrorType();
1791: }
1792:
1793: return $this->resolveCommonMath(new Expr\BinaryOp\ShiftLeft($left, $right), $leftType, $rightType);
1794: }
1795:
1796: /**
1797: * @param callable(Expr): Type $getTypeCallback
1798: */
1799: public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCallback): Type
1800: {
1801: $leftType = $getTypeCallback($left);
1802: $rightType = $getTypeCallback($right);
1803:
1804: return $this->getShiftRightTypeFromTypes($left, $right, $leftType, $rightType);
1805: }
1806:
1807: public function getShiftRightTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1808: {
1809: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1810: return $this->getNeverType($leftType, $rightType);
1811: }
1812:
1813: $leftTypes = $leftType->getConstantScalarTypes();
1814: $rightTypes = $rightType->getConstantScalarTypes();
1815: $leftTypesCount = count($leftTypes);
1816: $rightTypesCount = count($rightTypes);
1817: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1818: $resultTypes = [];
1819: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1820: if (!$generalize) {
1821: foreach ($leftTypes as $leftTypeInner) {
1822: foreach ($rightTypes as $rightTypeInner) {
1823: $leftNumberType = $leftTypeInner->toNumber();
1824: $rightNumberType = $rightTypeInner->toNumber();
1825:
1826: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1827: return new ErrorType();
1828: }
1829:
1830: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1831: throw new ShouldNotHappenException();
1832: }
1833:
1834: if ($rightNumberType->getValue() < 0) {
1835: return new ErrorType();
1836: }
1837:
1838: $resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) >> intval($rightNumberType->getValue()));
1839: $resultTypes[] = $resultType;
1840: }
1841: }
1842:
1843: return TypeCombinator::union(...$resultTypes);
1844: }
1845:
1846: $leftType = $this->optimizeScalarType($leftType);
1847: $rightType = $this->optimizeScalarType($rightType);
1848: }
1849:
1850: $leftNumberType = $leftType->toNumber();
1851: $rightNumberType = $rightType->toNumber();
1852:
1853: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1854: return new ErrorType();
1855: }
1856:
1857: return $this->resolveCommonMath(new Expr\BinaryOp\ShiftRight($left, $right), $leftType, $rightType);
1858: }
1859:
1860: private function optimizeScalarType(Type $type): Type
1861: {
1862: $types = [];
1863: if ($type->isInteger()->yes()) {
1864: $types[] = new IntegerType();
1865: }
1866: if ($type->isString()->yes()) {
1867: $types[] = new StringType();
1868: }
1869: if ($type->isFloat()->yes()) {
1870: $types[] = new FloatType();
1871: }
1872: if ($type->isNull()->yes()) {
1873: $types[] = new NullType();
1874: }
1875:
1876: if (count($types) === 0) {
1877: return new ErrorType();
1878: }
1879:
1880: if (count($types) === 1) {
1881: return $types[0];
1882: }
1883:
1884: return new UnionType($types);
1885: }
1886:
1887: /**
1888: * @return TypeResult<BooleanType>
1889: */
1890: public function resolveIdenticalType(Type $leftType, Type $rightType): TypeResult
1891: {
1892: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1893: return new TypeResult(new ConstantBooleanType(false), []);
1894: }
1895:
1896: if ($leftType instanceof ConstantScalarType && $rightType instanceof ConstantScalarType) {
1897: return new TypeResult(new ConstantBooleanType($leftType->getValue() === $rightType->getValue()), []);
1898: }
1899:
1900: $leftTypeFiniteTypes = $leftType->getFiniteTypes();
1901: $rightTypeFiniteType = $rightType->getFiniteTypes();
1902: if (count($leftTypeFiniteTypes) === 1 && count($rightTypeFiniteType) === 1) {
1903: return new TypeResult(new ConstantBooleanType($leftTypeFiniteTypes[0]->equals($rightTypeFiniteType[0])), []);
1904: }
1905:
1906: $leftIsSuperTypeOfRight = $leftType->isSuperTypeOf($rightType);
1907: $rightIsSuperTypeOfLeft = $rightType->isSuperTypeOf($leftType);
1908: if ($leftIsSuperTypeOfRight->no() && $rightIsSuperTypeOfLeft->no()) {
1909: return new TypeResult(new ConstantBooleanType(false), array_merge($leftIsSuperTypeOfRight->reasons, $rightIsSuperTypeOfLeft->reasons));
1910: }
1911:
1912: if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) {
1913: return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): TypeResult => $this->resolveIdenticalType($leftValueType, $rightValueType));
1914: }
1915:
1916: return new TypeResult(new BooleanType(), []);
1917: }
1918:
1919: /**
1920: * @return TypeResult<BooleanType>
1921: */
1922: public function resolveEqualType(Type $leftType, Type $rightType): TypeResult
1923: {
1924: if (
1925: ($leftType->isEnum()->yes() && $rightType->isTrue()->no())
1926: || ($rightType->isEnum()->yes() && $leftType->isTrue()->no())
1927: ) {
1928: return $this->resolveIdenticalType($leftType, $rightType);
1929: }
1930:
1931: if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) {
1932: return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): TypeResult => $this->resolveEqualType($leftValueType, $rightValueType));
1933: }
1934:
1935: return new TypeResult($leftType->looseCompare($rightType, $this->phpVersion), []);
1936: }
1937:
1938: /**
1939: * @param callable(Type, Type): TypeResult<BooleanType> $valueComparisonCallback
1940: * @return TypeResult<BooleanType>
1941: */
1942: private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, ConstantArrayType $rightType, callable $valueComparisonCallback): TypeResult
1943: {
1944: $leftKeyTypes = $leftType->getKeyTypes();
1945: $rightKeyTypes = $rightType->getKeyTypes();
1946: $leftValueTypes = $leftType->getValueTypes();
1947: $rightValueTypes = $rightType->getValueTypes();
1948:
1949: $resultType = new ConstantBooleanType(true);
1950:
1951: foreach ($leftKeyTypes as $i => $leftKeyType) {
1952: $leftOptional = $leftType->isOptionalKey($i);
1953: if ($leftOptional) {
1954: $resultType = new BooleanType();
1955: }
1956:
1957: if (count($rightKeyTypes) === 0) {
1958: if (!$leftOptional) {
1959: return new TypeResult(new ConstantBooleanType(false), []);
1960: }
1961: continue;
1962: }
1963:
1964: $found = false;
1965: foreach ($rightKeyTypes as $j => $rightKeyType) {
1966: unset($rightKeyTypes[$j]);
1967:
1968: if ($leftKeyType->equals($rightKeyType)) {
1969: $found = true;
1970: break;
1971: } elseif (!$rightType->isOptionalKey($j)) {
1972: return new TypeResult(new ConstantBooleanType(false), []);
1973: }
1974: }
1975:
1976: if (!$found) {
1977: if (!$leftOptional) {
1978: return new TypeResult(new ConstantBooleanType(false), []);
1979: }
1980: continue;
1981: }
1982:
1983: if (!isset($j)) {
1984: throw new ShouldNotHappenException();
1985: }
1986:
1987: $rightOptional = $rightType->isOptionalKey($j);
1988: if ($rightOptional) {
1989: $resultType = new BooleanType();
1990: if ($leftOptional) {
1991: continue;
1992: }
1993: }
1994:
1995: $leftIdenticalToRightResult = $valueComparisonCallback($leftValueTypes[$i], $rightValueTypes[$j]);
1996: $leftIdenticalToRight = $leftIdenticalToRightResult->type;
1997: if ($leftIdenticalToRight->isFalse()->yes()) {
1998: return $leftIdenticalToRightResult;
1999: }
2000: $resultType = TypeCombinator::union($resultType, $leftIdenticalToRight);
2001: }
2002:
2003: foreach (array_keys($rightKeyTypes) as $j) {
2004: if (!$rightType->isOptionalKey($j)) {
2005: return new TypeResult(new ConstantBooleanType(false), []);
2006: }
2007: $resultType = new BooleanType();
2008: }
2009:
2010: return new TypeResult($resultType->toBoolean(), []);
2011: }
2012:
2013: /**
2014: * @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $expr
2015: */
2016: private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type
2017: {
2018: $types = TypeCombinator::union($leftType, $rightType);
2019: $leftNumberType = $leftType->toNumber();
2020: $rightNumberType = $rightType->toNumber();
2021:
2022: if (
2023: !$types instanceof MixedType
2024: && (
2025: $rightNumberType instanceof IntegerRangeType
2026: || $rightNumberType instanceof ConstantIntegerType
2027: || $rightNumberType instanceof UnionType
2028: )
2029: ) {
2030: if ($leftNumberType instanceof IntegerRangeType || $leftNumberType instanceof ConstantIntegerType) {
2031: return $this->integerRangeMath(
2032: $leftNumberType,
2033: $expr,
2034: $rightNumberType,
2035: );
2036: } elseif ($leftNumberType instanceof UnionType) {
2037: $unionParts = [];
2038:
2039: foreach ($leftNumberType->getTypes() as $type) {
2040: $numberType = $type->toNumber();
2041: if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) {
2042: $unionParts[] = $this->integerRangeMath($numberType, $expr, $rightNumberType);
2043: } else {
2044: $unionParts[] = $numberType;
2045: }
2046: }
2047:
2048: $union = TypeCombinator::union(...$unionParts);
2049: if ($leftNumberType instanceof BenevolentUnionType) {
2050: return TypeUtils::toBenevolentUnion($union)->toNumber();
2051: }
2052:
2053: return $union->toNumber();
2054: }
2055: }
2056:
2057: $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()
2058: ->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType);
2059: if ($specifiedTypes !== null) {
2060: return $specifiedTypes;
2061: }
2062:
2063: if (
2064: $leftType->isArray()->yes()
2065: || $rightType->isArray()->yes()
2066: || $types->isArray()->yes()
2067: ) {
2068: return new ErrorType();
2069: }
2070:
2071: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
2072: return new ErrorType();
2073: }
2074: if ($leftNumberType instanceof NeverType || $rightNumberType instanceof NeverType) {
2075: return $this->getNeverType($leftNumberType, $rightNumberType);
2076: }
2077:
2078: if (
2079: $leftNumberType->isFloat()->yes()
2080: || $rightNumberType->isFloat()->yes()
2081: ) {
2082: if ($expr instanceof Expr\BinaryOp\ShiftLeft || $expr instanceof Expr\BinaryOp\ShiftRight) {
2083: return new IntegerType();
2084: }
2085: return new FloatType();
2086: }
2087:
2088: $resultType = TypeCombinator::union($leftNumberType, $rightNumberType);
2089: if ($expr instanceof Expr\BinaryOp\Div) {
2090: if ($types instanceof MixedType || $resultType->isInteger()->yes()) {
2091: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
2092: }
2093:
2094: return new UnionType([new IntegerType(), new FloatType()]);
2095: }
2096:
2097: if ($types instanceof MixedType
2098: || $leftType instanceof BenevolentUnionType
2099: || $rightType instanceof BenevolentUnionType
2100: ) {
2101: return TypeUtils::toBenevolentUnion($resultType);
2102: }
2103:
2104: return $resultType;
2105: }
2106:
2107: /**
2108: * @param ConstantIntegerType|IntegerRangeType $range
2109: * @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $node
2110: */
2111: private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): Type
2112: {
2113: if ($range instanceof IntegerRangeType) {
2114: $rangeMin = $range->getMin();
2115: $rangeMax = $range->getMax();
2116: } else {
2117: $rangeMin = $range->getValue();
2118: $rangeMax = $rangeMin;
2119: }
2120:
2121: if ($operand instanceof UnionType) {
2122:
2123: $unionParts = [];
2124:
2125: foreach ($operand->getTypes() as $type) {
2126: $numberType = $type->toNumber();
2127: if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) {
2128: $unionParts[] = $this->integerRangeMath($range, $node, $numberType);
2129: } else {
2130: $unionParts[] = $type->toNumber();
2131: }
2132: }
2133:
2134: $union = TypeCombinator::union(...$unionParts);
2135: if ($operand instanceof BenevolentUnionType) {
2136: return TypeUtils::toBenevolentUnion($union)->toNumber();
2137: }
2138:
2139: return $union->toNumber();
2140: }
2141:
2142: $operand = $operand->toNumber();
2143: if ($operand instanceof IntegerRangeType) {
2144: $operandMin = $operand->getMin();
2145: $operandMax = $operand->getMax();
2146: } elseif ($operand instanceof ConstantIntegerType) {
2147: $operandMin = $operand->getValue();
2148: $operandMax = $operand->getValue();
2149: } else {
2150: return $operand;
2151: }
2152:
2153: if ($node instanceof BinaryOp\Plus) {
2154: if ($operand instanceof ConstantIntegerType) {
2155: /** @var int|float|null $min */
2156: $min = $rangeMin !== null ? $rangeMin + $operand->getValue() : null;
2157:
2158: /** @var int|float|null $max */
2159: $max = $rangeMax !== null ? $rangeMax + $operand->getValue() : null;
2160: } else {
2161: /** @var int|float|null $min */
2162: $min = $rangeMin !== null && $operand->getMin() !== null ? $rangeMin + $operand->getMin() : null;
2163:
2164: /** @var int|float|null $max */
2165: $max = $rangeMax !== null && $operand->getMax() !== null ? $rangeMax + $operand->getMax() : null;
2166: }
2167: } elseif ($node instanceof BinaryOp\Minus) {
2168: if ($operand instanceof ConstantIntegerType) {
2169: /** @var int|float|null $min */
2170: $min = $rangeMin !== null ? $rangeMin - $operand->getValue() : null;
2171:
2172: /** @var int|float|null $max */
2173: $max = $rangeMax !== null ? $rangeMax - $operand->getValue() : null;
2174: } else {
2175: if ($rangeMin === $rangeMax && $rangeMin !== null
2176: && ($operand->getMin() === null || $operand->getMax() === null)) {
2177: $min = null;
2178: $max = $rangeMin;
2179: } else {
2180: if ($operand->getMin() === null) {
2181: $min = null;
2182: } elseif ($rangeMin !== null) {
2183: if ($operand->getMax() !== null) {
2184: /** @var int|float $min */
2185: $min = $rangeMin - $operand->getMax();
2186: } else {
2187: /** @var int|float $min */
2188: $min = $rangeMin - $operand->getMin();
2189: }
2190: } else {
2191: $min = null;
2192: }
2193:
2194: if ($operand->getMax() === null) {
2195: $min = null;
2196: $max = null;
2197: } elseif ($rangeMax !== null) {
2198: if ($rangeMin !== null && $operand->getMin() === null) {
2199: /** @var int|float $min */
2200: $min = $rangeMin - $operand->getMax();
2201: $max = null;
2202: } elseif ($operand->getMin() !== null) {
2203: /** @var int|float $max */
2204: $max = $rangeMax - $operand->getMin();
2205: } else {
2206: $max = null;
2207: }
2208: } else {
2209: $max = null;
2210: }
2211:
2212: if ($min !== null && $max !== null && $min > $max) {
2213: [$min, $max] = [$max, $min];
2214: }
2215: }
2216: }
2217: } elseif ($node instanceof Expr\BinaryOp\Mul) {
2218: $min1 = $rangeMin === 0 || $operandMin === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMin ?? -INF);
2219: $min2 = $rangeMin === 0 || $operandMax === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMax ?? INF);
2220: $max1 = $rangeMax === 0 || $operandMin === 0 ? 0 : ($rangeMax ?? INF) * ($operandMin ?? -INF);
2221: $max2 = $rangeMax === 0 || $operandMax === 0 ? 0 : ($rangeMax ?? INF) * ($operandMax ?? INF);
2222:
2223: $min = min($min1, $min2, $max1, $max2);
2224: $max = max($min1, $min2, $max1, $max2);
2225:
2226: if (!is_finite($min)) {
2227: $min = null;
2228: }
2229: if (!is_finite($max)) {
2230: $max = null;
2231: }
2232: } elseif ($node instanceof Expr\BinaryOp\Div) {
2233: if ($operand instanceof ConstantIntegerType) {
2234: $min = $rangeMin !== null && $operand->getValue() !== 0 ? $rangeMin / $operand->getValue() : null;
2235: $max = $rangeMax !== null && $operand->getValue() !== 0 ? $rangeMax / $operand->getValue() : null;
2236: } else {
2237: // Avoid division by zero when looking for the min and the max by using the closest int
2238: $operandMin = $operandMin !== 0 ? $operandMin : 1;
2239: $operandMax = $operandMax !== 0 ? $operandMax : -1;
2240:
2241: if (
2242: ($operandMin < 0 || $operandMin === null)
2243: && ($operandMax > 0 || $operandMax === null)
2244: ) {
2245: $negativeOperand = IntegerRangeType::fromInterval($operandMin, 0);
2246: assert($negativeOperand instanceof IntegerRangeType);
2247: $positiveOperand = IntegerRangeType::fromInterval(0, $operandMax);
2248: assert($positiveOperand instanceof IntegerRangeType);
2249:
2250: $result = TypeCombinator::union(
2251: $this->integerRangeMath($range, $node, $negativeOperand),
2252: $this->integerRangeMath($range, $node, $positiveOperand),
2253: )->toNumber();
2254:
2255: if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) {
2256: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
2257: }
2258:
2259: return $result;
2260: }
2261: if (
2262: ($rangeMin < 0 || $rangeMin === null)
2263: && ($rangeMax > 0 || $rangeMax === null)
2264: ) {
2265: $negativeRange = IntegerRangeType::fromInterval($rangeMin, 0);
2266: assert($negativeRange instanceof IntegerRangeType);
2267: $positiveRange = IntegerRangeType::fromInterval(0, $rangeMax);
2268: assert($positiveRange instanceof IntegerRangeType);
2269:
2270: $result = TypeCombinator::union(
2271: $this->integerRangeMath($negativeRange, $node, $operand),
2272: $this->integerRangeMath($positiveRange, $node, $operand),
2273: )->toNumber();
2274:
2275: if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) {
2276: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
2277: }
2278:
2279: return $result;
2280: }
2281:
2282: $rangeMinSign = ($rangeMin ?? -INF) <=> 0;
2283: $rangeMaxSign = ($rangeMax ?? INF) <=> 0;
2284:
2285: $min1 = $operandMin !== null ? ($rangeMin ?? -INF) / $operandMin : $rangeMinSign * -0.1;
2286: $min2 = $operandMax !== null ? ($rangeMin ?? -INF) / $operandMax : $rangeMinSign * 0.1;
2287: $max1 = $operandMin !== null ? ($rangeMax ?? INF) / $operandMin : $rangeMaxSign * -0.1;
2288: $max2 = $operandMax !== null ? ($rangeMax ?? INF) / $operandMax : $rangeMaxSign * 0.1;
2289:
2290: $min = min($min1, $min2, $max1, $max2);
2291: $max = max($min1, $min2, $max1, $max2);
2292:
2293: if ($min === -INF) {
2294: $min = null;
2295: }
2296: if ($max === INF) {
2297: $max = null;
2298: }
2299: }
2300:
2301: if ($min !== null && $max !== null && $min > $max) {
2302: [$min, $max] = [$max, $min];
2303: }
2304:
2305: if (is_float($min)) {
2306: $min = (int) ceil($min);
2307: }
2308: if (is_float($max)) {
2309: $max = (int) floor($max);
2310: }
2311:
2312: // invert maximas on division with negative constants
2313: if ((($range instanceof ConstantIntegerType && $range->getValue() < 0)
2314: || ($operand instanceof ConstantIntegerType && $operand->getValue() < 0))
2315: && ($min === null || $max === null)) {
2316: [$min, $max] = [$max, $min];
2317: }
2318:
2319: if ($min === null && $max === null) {
2320: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
2321: }
2322:
2323: return TypeCombinator::union(IntegerRangeType::fromInterval($min, $max), new FloatType());
2324: } elseif ($node instanceof Expr\BinaryOp\ShiftLeft) {
2325: if (!$operand instanceof ConstantIntegerType) {
2326: return new IntegerType();
2327: }
2328: if ($operand->getValue() < 0) {
2329: return new ErrorType();
2330: }
2331: $min = $rangeMin !== null ? intval($rangeMin) << $operand->getValue() : null;
2332: $max = $rangeMax !== null ? intval($rangeMax) << $operand->getValue() : null;
2333: } elseif ($node instanceof Expr\BinaryOp\ShiftRight) {
2334: if (!$operand instanceof ConstantIntegerType) {
2335: return new IntegerType();
2336: }
2337: if ($operand->getValue() < 0) {
2338: return new ErrorType();
2339: }
2340: $min = $rangeMin !== null ? intval($rangeMin) >> $operand->getValue() : null;
2341: $max = $rangeMax !== null ? intval($rangeMax) >> $operand->getValue() : null;
2342: } else {
2343: throw new ShouldNotHappenException();
2344: }
2345:
2346: if (is_float($min)) {
2347: $min = null;
2348: }
2349: if (is_float($max)) {
2350: $max = null;
2351: }
2352:
2353: return IntegerRangeType::fromInterval($min, $max);
2354: }
2355:
2356: /**
2357: * @param callable(Expr): Type $getTypeCallback
2358: */
2359: public function getClassConstFetchTypeByReflection(Name|Expr $class, string $constantName, ?ClassReflection $classReflection, callable $getTypeCallback): Type
2360: {
2361: $isObject = false;
2362: if ($class instanceof Name) {
2363: $constantClass = (string) $class;
2364: $constantClassType = new ObjectType($constantClass);
2365: $namesToResolve = [
2366: 'self',
2367: 'parent',
2368: ];
2369: if ($classReflection !== null) {
2370: if ($classReflection->isFinal()) {
2371: $namesToResolve[] = 'static';
2372: } elseif (strtolower($constantClass) === 'static') {
2373: if (strtolower($constantName) === 'class') {
2374: return new GenericClassStringType(new StaticType($classReflection));
2375: }
2376:
2377: $namesToResolve[] = 'static';
2378: $isObject = true;
2379: }
2380: }
2381: if (in_array(strtolower($constantClass), $namesToResolve, true)) {
2382: $resolvedName = $this->resolveName($class, $classReflection);
2383: if (strtolower($resolvedName) === 'parent' && strtolower($constantName) === 'class') {
2384: return new ClassStringType();
2385: }
2386: $constantClassType = $this->resolveTypeByName($class, $classReflection);
2387: }
2388:
2389: if (strtolower($constantName) === 'class') {
2390: return new ConstantStringType($constantClassType->getClassName(), true);
2391: }
2392: } elseif ($class instanceof String_ && strtolower($constantName) === 'class') {
2393: return new ConstantStringType($class->value, true);
2394: } else {
2395: $constantClassType = $getTypeCallback($class);
2396: $isObject = true;
2397: }
2398:
2399: if (strtolower($constantName) === 'class') {
2400: return TypeTraverser::map(
2401: $constantClassType,
2402: function (Type $type, callable $traverse): Type {
2403: if ($type instanceof UnionType || $type instanceof IntersectionType) {
2404: return $traverse($type);
2405: }
2406:
2407: if ($type instanceof NullType) {
2408: return $type;
2409: }
2410:
2411: if ($type instanceof EnumCaseObjectType) {
2412: return new IntersectionType([
2413: new GenericClassStringType(new ObjectType($type->getClassName())),
2414: new AccessoryLiteralStringType(),
2415: ]);
2416: }
2417:
2418: $objectClassNames = $type->getObjectClassNames();
2419: if (count($objectClassNames) > 1) {
2420: throw new ShouldNotHappenException();
2421: }
2422:
2423: if ($type instanceof TemplateType && $objectClassNames === []) {
2424: return new IntersectionType([
2425: new GenericClassStringType($type),
2426: new AccessoryLiteralStringType(),
2427: ]);
2428: } elseif ($objectClassNames !== [] && $this->getReflectionProvider()->hasClass($objectClassNames[0])) {
2429: $reflection = $this->getReflectionProvider()->getClass($objectClassNames[0]);
2430: if ($reflection->isFinalByKeyword()) {
2431: return new ConstantStringType($reflection->getName(), true);
2432: }
2433:
2434: return new IntersectionType([
2435: new GenericClassStringType($type),
2436: new AccessoryLiteralStringType(),
2437: ]);
2438: } elseif ($type->isObject()->yes()) {
2439: return new IntersectionType([
2440: new ClassStringType(),
2441: new AccessoryLiteralStringType(),
2442: ]);
2443: }
2444:
2445: return new ErrorType();
2446: },
2447: );
2448: }
2449:
2450: if ($constantClassType->isClassString()->yes()) {
2451: if ($constantClassType->isConstantScalarValue()->yes()) {
2452: $isObject = false;
2453: }
2454: $constantClassType = $constantClassType->getClassStringObjectType();
2455: }
2456:
2457: $types = [];
2458: foreach ($constantClassType->getObjectClassNames() as $referencedClass) {
2459: if (!$this->getReflectionProvider()->hasClass($referencedClass)) {
2460: continue;
2461: }
2462:
2463: $constantClassReflection = $this->getReflectionProvider()->getClass($referencedClass);
2464: if (!$constantClassReflection->hasConstant($constantName)) {
2465: if ($constantClassReflection->getName() === 'Attribute' && $constantName === 'TARGET_CONSTANT') {
2466: return new ConstantIntegerType(1 << 16);
2467: }
2468: continue;
2469: }
2470:
2471: if ($constantClassReflection->isEnum() && $constantClassReflection->hasEnumCase($constantName)) {
2472: $types[] = new EnumCaseObjectType($constantClassReflection->getName(), $constantName);
2473: continue;
2474: }
2475:
2476: $resolvingName = sprintf('%s::%s', $constantClassReflection->getName(), $constantName);
2477: if (array_key_exists($resolvingName, $this->currentlyResolvingClassConstant)) {
2478: $types[] = new MixedType();
2479: continue;
2480: }
2481:
2482: $this->currentlyResolvingClassConstant[$resolvingName] = true;
2483:
2484: if (!$isObject) {
2485: $reflectionConstant = $constantClassReflection->getNativeReflection()->getReflectionConstant($constantName);
2486: if ($reflectionConstant === false) {
2487: unset($this->currentlyResolvingClassConstant[$resolvingName]);
2488: continue;
2489: }
2490: $reflectionConstantDeclaringClass = $reflectionConstant->getDeclaringClass();
2491: $constantType = $this->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($reflectionConstantDeclaringClass->getName(), $reflectionConstantDeclaringClass->getFileName() ?: null));
2492: $nativeType = null;
2493: if ($reflectionConstant->getType() !== null) {
2494: $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $constantClassReflection);
2495: }
2496: $types[] = $this->constantResolver->resolveClassConstantType(
2497: $constantClassReflection->getName(),
2498: $constantName,
2499: $constantType,
2500: $nativeType,
2501: );
2502: unset($this->currentlyResolvingClassConstant[$resolvingName]);
2503: continue;
2504: }
2505:
2506: $constantReflection = $constantClassReflection->getConstant($constantName);
2507: if (
2508: !$constantClassReflection->isFinal()
2509: && !$constantReflection->isFinal()
2510: && !$constantReflection->hasPhpDocType()
2511: && !$constantReflection->hasNativeType()
2512: ) {
2513: unset($this->currentlyResolvingClassConstant[$resolvingName]);
2514: return new MixedType();
2515: }
2516:
2517: if (!$constantClassReflection->isFinal()) {
2518: $constantType = $constantReflection->getValueType();
2519: } else {
2520: $constantType = $this->getType($constantReflection->getValueExpr(), InitializerExprContext::fromClassReflection($constantReflection->getDeclaringClass()));
2521: }
2522:
2523: $nativeType = $constantReflection->getNativeType();
2524: $constantType = $this->constantResolver->resolveClassConstantType(
2525: $constantClassReflection->getName(),
2526: $constantName,
2527: $constantType,
2528: $nativeType,
2529: );
2530: unset($this->currentlyResolvingClassConstant[$resolvingName]);
2531: $types[] = $constantType;
2532: }
2533:
2534: if (count($types) > 0) {
2535: return TypeCombinator::union(...$types);
2536: }
2537:
2538: if (!$constantClassType->hasConstant($constantName)->yes()) {
2539: return new ErrorType();
2540: }
2541:
2542: return $constantClassType->getConstant($constantName)->getValueType();
2543: }
2544:
2545: /**
2546: * @param callable(Expr): Type $getTypeCallback
2547: */
2548: public function getClassConstFetchType(Name|Expr $class, string $constantName, ?string $className, callable $getTypeCallback): Type
2549: {
2550: $classReflection = null;
2551: if ($className !== null && $this->getReflectionProvider()->hasClass($className)) {
2552: $classReflection = $this->getReflectionProvider()->getClass($className);
2553: }
2554:
2555: return $this->getClassConstFetchTypeByReflection($class, $constantName, $classReflection, $getTypeCallback);
2556: }
2557:
2558: /**
2559: * @param callable(Expr): Type $getTypeCallback
2560: */
2561: public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type
2562: {
2563: $type = $getTypeCallback($expr);
2564:
2565: $type = $this->getUnaryMinusTypeFromType($expr, $type);
2566: if ($type instanceof IntegerRangeType) {
2567: return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new Int_(-1)));
2568: }
2569:
2570: return $type;
2571: }
2572:
2573: public function getUnaryMinusTypeFromType(Expr $expr, Type $type): Type
2574: {
2575: $type = $type->toNumber();
2576: $scalarValues = $type->getConstantScalarValues();
2577:
2578: if (count($scalarValues) > 0) {
2579: $newTypes = [];
2580: foreach ($scalarValues as $scalarValue) {
2581: if (is_int($scalarValue)) {
2582: /** @var int|float $newValue */
2583: $newValue = -$scalarValue;
2584: if (!is_int($newValue)) {
2585: return $type;
2586: }
2587: $newTypes[] = new ConstantIntegerType($newValue);
2588: } elseif (is_float($scalarValue)) {
2589: $newTypes[] = new ConstantFloatType(-$scalarValue);
2590: }
2591: }
2592:
2593: return TypeCombinator::union(...$newTypes);
2594: }
2595:
2596: return $type;
2597: }
2598:
2599: /**
2600: * @param callable(Expr): Type $getTypeCallback
2601: */
2602: public function getBitwiseNotType(Expr $expr, callable $getTypeCallback): Type
2603: {
2604: $exprType = $getTypeCallback($expr);
2605:
2606: return $this->getBitwiseNotTypeFromType($exprType);
2607: }
2608:
2609: public function getBitwiseNotTypeFromType(Type $exprType): Type
2610: {
2611: return TypeTraverser::map($exprType, static function (Type $type, callable $traverse): Type {
2612: if ($type instanceof UnionType || $type instanceof IntersectionType) {
2613: return $traverse($type);
2614: }
2615: if ($type instanceof ConstantStringType) {
2616: return new ConstantStringType(~$type->getValue());
2617: }
2618: if ($type->isString()->yes()) {
2619: $accessories = [];
2620: if (!$type->isNonEmptyString()->yes()) {
2621: return new StringType();
2622: }
2623:
2624: $accessories[] = new AccessoryNonEmptyStringType();
2625: // it is not useful to apply numeric and literal strings here.
2626: // numeric string isn't certainly kept numeric: 3v4l.org/JERDB
2627:
2628: return new IntersectionType([new StringType(), ...$accessories]);
2629: }
2630: if ($type instanceof ConstantIntegerType || $type instanceof ConstantFloatType) {
2631: return new ConstantIntegerType(~ (int) $type->getValue());
2632: }
2633: if ($type->isInteger()->yes() || $type->isFloat()->yes()) {
2634: return new IntegerType();
2635: }
2636: return new ErrorType();
2637: });
2638: }
2639:
2640: private function resolveName(Name $name, ?ClassReflection $classReflection): string
2641: {
2642: $originalClass = (string) $name;
2643: if ($classReflection !== null) {
2644: $lowerClass = strtolower($originalClass);
2645:
2646: if (in_array($lowerClass, [
2647: 'self',
2648: 'static',
2649: ], true)) {
2650: return $classReflection->getName();
2651: } elseif ($lowerClass === 'parent') {
2652: if ($classReflection->getParentClass() !== null) {
2653: return $classReflection->getParentClass()->getName();
2654: }
2655: }
2656: }
2657:
2658: return $originalClass;
2659: }
2660:
2661: private function resolveTypeByName(Name $name, ?ClassReflection $classReflection): TypeWithClassName
2662: {
2663: if ($name->toLowerString() === 'static' && $classReflection !== null) {
2664: return new StaticType($classReflection);
2665: }
2666:
2667: $originalClass = $this->resolveName($name, $classReflection);
2668: if ($classReflection !== null) {
2669: $thisType = new ThisType($classReflection);
2670: $ancestor = $thisType->getAncestorWithClassName($originalClass);
2671: if ($ancestor !== null) {
2672: return $ancestor;
2673: }
2674: }
2675:
2676: return new ObjectType($originalClass);
2677: }
2678:
2679: private function resolveTypeByNameWithLateStaticBinding(Name $class, Type $classType, MethodReflection $methodReflectionCandidate): Type
2680: {
2681: if (
2682: $classType instanceof StaticType
2683: && !in_array($class->toLowerString(), ['self', 'static', 'parent'], true)
2684: ) {
2685: if ($methodReflectionCandidate->isStatic()) {
2686: $classType = $classType->getStaticObjectType();
2687: }
2688: }
2689:
2690: return $classType;
2691: }
2692:
2693: /**
2694: * @param mixed $value
2695: */
2696: private function getTypeFromValue($value): Type
2697: {
2698: return ConstantTypeHelper::getTypeFromValue($value);
2699: }
2700:
2701: private function getReflectionProvider(): ReflectionProvider
2702: {
2703: return $this->reflectionProviderProvider->getReflectionProvider();
2704: }
2705:
2706: private function getNeverType(Type $leftType, Type $rightType): Type
2707: {
2708: // make sure we don't lose the explicit flag in the process
2709: if ($leftType instanceof NeverType && $leftType->isExplicit()) {
2710: return $leftType;
2711: }
2712: if ($rightType instanceof NeverType && $rightType->isExplicit()) {
2713: return $rightType;
2714: }
2715: return new NeverType();
2716: }
2717:
2718: }
2719: