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