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