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