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