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