1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection;
4:
5: use PhpParser\Node\Arg;
6: use PhpParser\Node\Expr;
7: use PhpParser\Node\Expr\BinaryOp;
8: use PhpParser\Node\Expr\ClassConstFetch;
9: use PhpParser\Node\Expr\ConstFetch;
10: use PhpParser\Node\Expr\PropertyFetch;
11: use PhpParser\Node\Identifier;
12: use PhpParser\Node\Name;
13: use PhpParser\Node\Scalar\DNumber;
14: use PhpParser\Node\Scalar\LNumber;
15: use PhpParser\Node\Scalar\MagicConst;
16: use PhpParser\Node\Scalar\MagicConst\Dir;
17: use PhpParser\Node\Scalar\MagicConst\File;
18: use PhpParser\Node\Scalar\MagicConst\Line;
19: use PhpParser\Node\Scalar\String_;
20: use PHPStan\Analyser\ConstantResolver;
21: use PHPStan\Analyser\OutOfClassScope;
22: use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
23: use PHPStan\Node\Expr\TypeExpr;
24: use PHPStan\Php\PhpVersion;
25: use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
26: use PHPStan\ShouldNotHappenException;
27: use PHPStan\Type\Accessory\AccessoryArrayListType;
28: use PHPStan\Type\Accessory\AccessoryLiteralStringType;
29: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
30: use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
31: use PHPStan\Type\Accessory\NonEmptyArrayType;
32: use PHPStan\Type\ArrayType;
33: use PHPStan\Type\BenevolentUnionType;
34: use PHPStan\Type\BooleanType;
35: use PHPStan\Type\ClassStringType;
36: use PHPStan\Type\Constant\ConstantArrayType;
37: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
38: use PHPStan\Type\Constant\ConstantBooleanType;
39: use PHPStan\Type\Constant\ConstantFloatType;
40: use PHPStan\Type\Constant\ConstantIntegerType;
41: use PHPStan\Type\Constant\ConstantStringType;
42: use PHPStan\Type\Constant\OversizedArrayBuilder;
43: use PHPStan\Type\ConstantScalarType;
44: use PHPStan\Type\ConstantTypeHelper;
45: use PHPStan\Type\Enum\EnumCaseObjectType;
46: use PHPStan\Type\ErrorType;
47: use PHPStan\Type\FloatType;
48: use PHPStan\Type\GeneralizePrecision;
49: use PHPStan\Type\Generic\GenericClassStringType;
50: use PHPStan\Type\Generic\TemplateType;
51: use PHPStan\Type\IntegerRangeType;
52: use PHPStan\Type\IntegerType;
53: use PHPStan\Type\IntersectionType;
54: use PHPStan\Type\MixedType;
55: use PHPStan\Type\NeverType;
56: use PHPStan\Type\NullType;
57: use PHPStan\Type\ObjectType;
58: use PHPStan\Type\ObjectWithoutClassType;
59: use PHPStan\Type\StaticType;
60: use PHPStan\Type\StringType;
61: use PHPStan\Type\ThisType;
62: use PHPStan\Type\Type;
63: use PHPStan\Type\TypeCombinator;
64: use PHPStan\Type\TypeTraverser;
65: use PHPStan\Type\TypeUtils;
66: use PHPStan\Type\TypeWithClassName;
67: use PHPStan\Type\UnionType;
68: use function array_keys;
69: use function array_merge;
70: use function assert;
71: use function ceil;
72: use function count;
73: use function dirname;
74: use function floor;
75: use function in_array;
76: use function is_float;
77: use function is_int;
78: use function max;
79: use function min;
80: use function sprintf;
81: use function strtolower;
82: use const INF;
83:
84: class InitializerExprTypeResolver
85: {
86:
87: public const CALCULATE_SCALARS_LIMIT = 128;
88:
89: public function __construct(
90: private ConstantResolver $constantResolver,
91: private ReflectionProviderProvider $reflectionProviderProvider,
92: private PhpVersion $phpVersion,
93: private OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider,
94: private OversizedArrayBuilder $oversizedArrayBuilder,
95: private bool $usePathConstantsAsConstantString = false,
96: )
97: {
98: }
99:
100: /** @api */
101: public function getType(Expr $expr, InitializerExprContext $context): Type
102: {
103: if ($expr instanceof TypeExpr) {
104: return $expr->getExprType();
105: }
106: if ($expr instanceof LNumber) {
107: return new ConstantIntegerType($expr->value);
108: }
109: if ($expr instanceof DNumber) {
110: return new ConstantFloatType($expr->value);
111: }
112: if ($expr instanceof String_) {
113: return new ConstantStringType($expr->value);
114: }
115: if ($expr instanceof ConstFetch) {
116: $constName = (string) $expr->name;
117: $loweredConstName = strtolower($constName);
118: if ($loweredConstName === 'true') {
119: return new ConstantBooleanType(true);
120: } elseif ($loweredConstName === 'false') {
121: return new ConstantBooleanType(false);
122: } elseif ($loweredConstName === 'null') {
123: return new NullType();
124: }
125:
126: $constant = $this->constantResolver->resolveConstant($expr->name, $context);
127: if ($constant !== null) {
128: return $constant;
129: }
130:
131: return new ErrorType();
132: }
133: if ($expr instanceof File) {
134: $file = $context->getFile();
135: if ($file === null) {
136: return new StringType();
137: }
138: $stringType = new ConstantStringType($file);
139: return $this->usePathConstantsAsConstantString ? $stringType : $stringType->generalize(GeneralizePrecision::moreSpecific());
140: }
141: if ($expr instanceof Dir) {
142: $file = $context->getFile();
143: if ($file === null) {
144: return new StringType();
145: }
146: $stringType = new ConstantStringType(dirname($file));
147: return $this->usePathConstantsAsConstantString ? $stringType : $stringType->generalize(GeneralizePrecision::moreSpecific());
148: }
149: if ($expr instanceof Line) {
150: return new ConstantIntegerType($expr->getLine());
151: }
152: if ($expr instanceof Expr\New_) {
153: if ($expr->class instanceof Name) {
154: return new ObjectType((string) $expr->class);
155: }
156:
157: return new ObjectWithoutClassType();
158: }
159: if ($expr instanceof Expr\Array_) {
160: return $this->getArrayType($expr, fn (Expr $expr): Type => $this->getType($expr, $context));
161: }
162: if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
163: $var = $this->getType($expr->var, $context);
164: $dim = $this->getType($expr->dim, $context);
165: return $var->getOffsetValueType($dim);
166: }
167: if ($expr instanceof ClassConstFetch && $expr->name instanceof Identifier) {
168: return $this->getClassConstFetchType($expr->class, $expr->name->toString(), $context->getClassName(), fn (Expr $expr): Type => $this->getType($expr, $context));
169: }
170: if ($expr instanceof Expr\UnaryPlus) {
171: return $this->getType($expr->expr, $context)->toNumber();
172: }
173: if ($expr instanceof Expr\UnaryMinus) {
174: return $this->getUnaryMinusType($expr->expr, fn (Expr $expr): Type => $this->getType($expr, $context));
175: }
176: if ($expr instanceof Expr\BinaryOp\Coalesce) {
177: $leftType = $this->getType($expr->left, $context);
178: $rightType = $this->getType($expr->right, $context);
179:
180: return TypeCombinator::union(TypeCombinator::removeNull($leftType), $rightType);
181: }
182:
183: if ($expr instanceof Expr\Ternary) {
184: $condType = $this->getType($expr->cond, $context);
185: $elseType = $this->getType($expr->else, $context);
186: if ($expr->if === null) {
187: return TypeCombinator::union(
188: TypeCombinator::removeFalsey($condType),
189: $elseType,
190: );
191: }
192:
193: $ifType = $this->getType($expr->if, $context);
194:
195: return TypeCombinator::union(
196: TypeCombinator::removeFalsey($ifType),
197: $elseType,
198: );
199: }
200:
201: if ($expr instanceof Expr\FuncCall && $expr->name instanceof Name && $expr->name->toLowerString() === 'constant') {
202: $firstArg = $expr->args[0] ?? null;
203: if ($firstArg instanceof Arg && $firstArg->value instanceof String_) {
204: $constant = $this->constantResolver->resolvePredefinedConstant($firstArg->value->value);
205: if ($constant !== null) {
206: return $constant;
207: }
208: }
209: }
210:
211: if ($expr instanceof Expr\BooleanNot) {
212: $exprBooleanType = $this->getType($expr->expr, $context)->toBoolean();
213:
214: if ($exprBooleanType instanceof ConstantBooleanType) {
215: return new ConstantBooleanType(!$exprBooleanType->getValue());
216: }
217:
218: return new BooleanType();
219: }
220:
221: if ($expr instanceof Expr\BitwiseNot) {
222: return $this->getBitwiseNotType($expr->expr, fn (Expr $expr): Type => $this->getType($expr, $context));
223: }
224:
225: if ($expr instanceof Expr\BinaryOp\Concat) {
226: return $this->getConcatType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
227: }
228:
229: if ($expr instanceof Expr\BinaryOp\BitwiseAnd) {
230: return $this->getBitwiseAndType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
231: }
232:
233: if ($expr instanceof Expr\BinaryOp\BitwiseOr) {
234: return $this->getBitwiseOrType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
235: }
236:
237: if ($expr instanceof Expr\BinaryOp\BitwiseXor) {
238: return $this->getBitwiseXorType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
239: }
240:
241: if ($expr instanceof Expr\BinaryOp\Spaceship) {
242: return $this->getSpaceshipType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
243: }
244:
245: if (
246: $expr instanceof Expr\BinaryOp\BooleanAnd
247: || $expr instanceof Expr\BinaryOp\LogicalAnd
248: || $expr instanceof Expr\BinaryOp\BooleanOr
249: || $expr instanceof Expr\BinaryOp\LogicalOr
250: ) {
251: return new BooleanType();
252: }
253:
254: if ($expr instanceof Expr\BinaryOp\Div) {
255: return $this->getDivType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
256: }
257:
258: if ($expr instanceof Expr\BinaryOp\Mod) {
259: return $this->getModType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
260: }
261:
262: if ($expr instanceof Expr\BinaryOp\Plus) {
263: return $this->getPlusType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
264: }
265:
266: if ($expr instanceof Expr\BinaryOp\Minus) {
267: return $this->getMinusType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
268: }
269:
270: if ($expr instanceof Expr\BinaryOp\Mul) {
271: return $this->getMulType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
272: }
273:
274: if ($expr instanceof Expr\BinaryOp\Pow) {
275: return $this->getPowType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
276: }
277:
278: if ($expr instanceof Expr\BinaryOp\ShiftLeft) {
279: return $this->getShiftLeftType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
280: }
281:
282: if ($expr instanceof Expr\BinaryOp\ShiftRight) {
283: return $this->getShiftRightType($expr->left, $expr->right, fn (Expr $expr): Type => $this->getType($expr, $context));
284: }
285:
286: if ($expr instanceof BinaryOp\Identical) {
287: return $this->resolveIdenticalType(
288: $this->getType($expr->left, $context),
289: $this->getType($expr->right, $context),
290: );
291: }
292:
293: if ($expr instanceof BinaryOp\NotIdentical) {
294: return $this->getType(new Expr\BooleanNot(new BinaryOp\Identical($expr->left, $expr->right)), $context);
295: }
296:
297: if ($expr instanceof BinaryOp\Equal) {
298: return $this->resolveEqualType(
299: $this->getType($expr->left, $context),
300: $this->getType($expr->right, $context),
301: );
302: }
303:
304: if ($expr instanceof BinaryOp\NotEqual) {
305: return $this->getType(new Expr\BooleanNot(new BinaryOp\Equal($expr->left, $expr->right)), $context);
306: }
307:
308: if ($expr instanceof Expr\BinaryOp\Smaller) {
309: return $this->getType($expr->left, $context)->isSmallerThan($this->getType($expr->right, $context))->toBooleanType();
310: }
311:
312: if ($expr instanceof Expr\BinaryOp\SmallerOrEqual) {
313: return $this->getType($expr->left, $context)->isSmallerThanOrEqual($this->getType($expr->right, $context))->toBooleanType();
314: }
315:
316: if ($expr instanceof Expr\BinaryOp\Greater) {
317: return $this->getType($expr->right, $context)->isSmallerThan($this->getType($expr->left, $context))->toBooleanType();
318: }
319:
320: if ($expr instanceof Expr\BinaryOp\GreaterOrEqual) {
321: return $this->getType($expr->right, $context)->isSmallerThanOrEqual($this->getType($expr->left, $context))->toBooleanType();
322: }
323:
324: if ($expr instanceof Expr\BinaryOp\LogicalXor) {
325: $leftBooleanType = $this->getType($expr->left, $context)->toBoolean();
326: $rightBooleanType = $this->getType($expr->right, $context)->toBoolean();
327:
328: if (
329: $leftBooleanType instanceof ConstantBooleanType
330: && $rightBooleanType instanceof ConstantBooleanType
331: ) {
332: return new ConstantBooleanType(
333: $leftBooleanType->getValue() xor $rightBooleanType->getValue(),
334: );
335: }
336:
337: return new BooleanType();
338: }
339:
340: if ($expr instanceof MagicConst\Class_) {
341: if ($context->getTraitName() !== null) {
342: return TypeCombinator::intersect(
343: new ClassStringType(),
344: new AccessoryLiteralStringType(),
345: );
346: }
347:
348: if ($context->getClassName() === null) {
349: return new ConstantStringType('');
350: }
351:
352: return new ConstantStringType($context->getClassName(), true);
353: }
354:
355: if ($expr instanceof MagicConst\Namespace_) {
356: if ($context->getTraitName() !== null) {
357: return TypeCombinator::intersect(
358: new StringType(),
359: new AccessoryLiteralStringType(),
360: );
361: }
362:
363: return new ConstantStringType($context->getNamespace() ?? '');
364: }
365:
366: if ($expr instanceof MagicConst\Method) {
367: return new ConstantStringType($context->getMethod() ?? '');
368: }
369:
370: if ($expr instanceof MagicConst\Function_) {
371: return new ConstantStringType($context->getFunction() ?? '');
372: }
373:
374: if ($expr instanceof MagicConst\Trait_) {
375: if ($context->getTraitName() === null) {
376: return new ConstantStringType('');
377: }
378:
379: return new ConstantStringType($context->getTraitName(), true);
380: }
381:
382: if ($expr instanceof PropertyFetch && $expr->name instanceof Identifier) {
383: $fetchedOnType = $this->getType($expr->var, $context);
384: if (!$fetchedOnType->hasProperty($expr->name->name)->yes()) {
385: return new ErrorType();
386: }
387:
388: return $fetchedOnType->getProperty($expr->name->name, new OutOfClassScope())->getReadableType();
389: }
390:
391: return new MixedType();
392: }
393:
394: /**
395: * @param callable(Expr): Type $getTypeCallback
396: */
397: public function getConcatType(Expr $left, Expr $right, callable $getTypeCallback): Type
398: {
399: $leftType = $getTypeCallback($left);
400: $rightType = $getTypeCallback($right);
401:
402: return $this->resolveConcatType($leftType, $rightType);
403: }
404:
405: public function resolveConcatType(Type $left, Type $right): Type
406: {
407: $leftStringType = $left->toString();
408: $rightStringType = $right->toString();
409: if (TypeCombinator::union(
410: $leftStringType,
411: $rightStringType,
412: ) instanceof ErrorType) {
413: return new ErrorType();
414: }
415:
416: if ($leftStringType instanceof ConstantStringType && $leftStringType->getValue() === '') {
417: return $rightStringType;
418: }
419:
420: if ($rightStringType instanceof ConstantStringType && $rightStringType->getValue() === '') {
421: return $leftStringType;
422: }
423:
424: if ($leftStringType instanceof ConstantStringType && $rightStringType instanceof ConstantStringType) {
425: return $leftStringType->append($rightStringType);
426: }
427:
428: $leftConstantStrings = $leftStringType->getConstantStrings();
429: $rightConstantStrings = $rightStringType->getConstantStrings();
430: $combinedConstantStringsCount = count($leftConstantStrings) * count($rightConstantStrings);
431:
432: // we limit the number of union-types for performance reasons
433: if ($combinedConstantStringsCount > 0 && $combinedConstantStringsCount <= 16) {
434: $strings = [];
435:
436: foreach ($leftConstantStrings as $leftConstantString) {
437: if ($leftConstantString->getValue() === '') {
438: $strings = array_merge($strings, $rightConstantStrings);
439:
440: continue;
441: }
442:
443: foreach ($rightConstantStrings as $rightConstantString) {
444: if ($rightConstantString->getValue() === '') {
445: $strings[] = $leftConstantString;
446:
447: continue;
448: }
449:
450: $strings[] = $leftConstantString->append($rightConstantString);
451: }
452: }
453:
454: if (count($strings) > 0) {
455: return TypeCombinator::union(...$strings);
456: }
457: }
458:
459: $accessoryTypes = [];
460: if ($leftStringType->isNonEmptyString()->and($rightStringType->isNonEmptyString())->yes()) {
461: $accessoryTypes[] = new AccessoryNonFalsyStringType();
462: } elseif ($leftStringType->isNonFalsyString()->or($rightStringType->isNonFalsyString())->yes()) {
463: $accessoryTypes[] = new AccessoryNonFalsyStringType();
464: } elseif ($leftStringType->isNonEmptyString()->or($rightStringType->isNonEmptyString())->yes()) {
465: $accessoryTypes[] = new AccessoryNonEmptyStringType();
466: }
467:
468: if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) {
469: $accessoryTypes[] = new AccessoryLiteralStringType();
470: }
471:
472: if (count($accessoryTypes) > 0) {
473: $accessoryTypes[] = new StringType();
474: return new IntersectionType($accessoryTypes);
475: }
476:
477: return new StringType();
478: }
479:
480: /**
481: * @param callable(Expr): Type $getTypeCallback
482: */
483: public function getArrayType(Expr\Array_ $expr, callable $getTypeCallback): Type
484: {
485: if (count($expr->items) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
486: return $this->oversizedArrayBuilder->build($expr, $getTypeCallback);
487: }
488:
489: $arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
490: $isList = null;
491: foreach ($expr->items as $arrayItem) {
492: if ($arrayItem === null) {
493: continue;
494: }
495:
496: $valueType = $getTypeCallback($arrayItem->value);
497: if ($arrayItem->unpack) {
498: if (count($valueType->getConstantArrays()) === 1) {
499: $constantArrayType = $valueType->getConstantArrays()[0];
500: $hasStringKey = false;
501: foreach ($constantArrayType->getKeyTypes() as $keyType) {
502: if ($keyType->isString()->yes()) {
503: $hasStringKey = true;
504: break;
505: }
506: }
507:
508: foreach ($constantArrayType->getValueTypes() as $i => $innerValueType) {
509: if ($hasStringKey && $this->phpVersion->supportsArrayUnpackingWithStringKeys()) {
510: $arrayBuilder->setOffsetValueType($constantArrayType->getKeyTypes()[$i], $innerValueType, $constantArrayType->isOptionalKey($i));
511: } else {
512: $arrayBuilder->setOffsetValueType(null, $innerValueType, $constantArrayType->isOptionalKey($i));
513: }
514: }
515: } else {
516: $arrayBuilder->degradeToGeneralArray();
517:
518: if ($this->phpVersion->supportsArrayUnpackingWithStringKeys() && !$valueType->getIterableKeyType()->isString()->no()) {
519: $isList = false;
520: $offsetType = $valueType->getIterableKeyType();
521: } else {
522: $isList ??= $arrayBuilder->isList();
523: $offsetType = new IntegerType();
524: }
525:
526: $arrayBuilder->setOffsetValueType($offsetType, $valueType->getIterableValueType(), !$valueType->isIterableAtLeastOnce()->yes());
527: }
528: } else {
529: $arrayBuilder->setOffsetValueType(
530: $arrayItem->key !== null ? $getTypeCallback($arrayItem->key) : null,
531: $valueType,
532: );
533: }
534: }
535:
536: $arrayType = $arrayBuilder->getArray();
537: if ($isList === true) {
538: return AccessoryArrayListType::intersectWith($arrayType);
539: }
540:
541: return $arrayType;
542: }
543:
544: /**
545: * @param callable(Expr): Type $getTypeCallback
546: */
547: public function getBitwiseAndType(Expr $left, Expr $right, callable $getTypeCallback): Type
548: {
549: $leftType = $getTypeCallback($left);
550: $rightType = $getTypeCallback($right);
551:
552: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
553: return $this->getNeverType($leftType, $rightType);
554: }
555:
556: $leftTypes = $leftType->getConstantScalarTypes();
557: $rightTypes = $rightType->getConstantScalarTypes();
558: $leftTypesCount = count($leftTypes);
559: $rightTypesCount = count($rightTypes);
560: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
561: $resultTypes = [];
562: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
563: foreach ($leftTypes as $leftTypeInner) {
564: foreach ($rightTypes as $rightTypeInner) {
565: if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) {
566: $resultType = $this->getTypeFromValue($leftTypeInner->getValue() & $rightTypeInner->getValue());
567: } else {
568: $leftNumberType = $leftTypeInner->toNumber();
569: $rightNumberType = $rightTypeInner->toNumber();
570:
571: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
572: return new ErrorType();
573: }
574:
575: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
576: throw new ShouldNotHappenException();
577: }
578:
579: $resultType = $this->getTypeFromValue($leftNumberType->getValue() & $rightNumberType->getValue());
580: }
581: if ($generalize) {
582: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
583: }
584: $resultTypes[] = $resultType;
585: }
586: }
587: return TypeCombinator::union(...$resultTypes);
588: }
589:
590: if ($leftType->isString()->yes() && $rightType->isString()->yes()) {
591: return new StringType();
592: }
593:
594: $leftNumberType = $leftType->toNumber();
595: $rightNumberType = $rightType->toNumber();
596:
597: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
598: return new ErrorType();
599: }
600:
601: if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() >= 0) {
602: return IntegerRangeType::fromInterval(0, $rightNumberType->getValue());
603: }
604: if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() >= 0) {
605: return IntegerRangeType::fromInterval(0, $leftNumberType->getValue());
606: }
607:
608: return new IntegerType();
609: }
610:
611: /**
612: * @param callable(Expr): Type $getTypeCallback
613: */
614: public function getBitwiseOrType(Expr $left, Expr $right, callable $getTypeCallback): Type
615: {
616: $leftType = $getTypeCallback($left);
617: $rightType = $getTypeCallback($right);
618:
619: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
620: return $this->getNeverType($leftType, $rightType);
621: }
622:
623: $leftTypes = $leftType->getConstantScalarTypes();
624: $rightTypes = $rightType->getConstantScalarTypes();
625: $leftTypesCount = count($leftTypes);
626: $rightTypesCount = count($rightTypes);
627: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
628: $resultTypes = [];
629: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
630: foreach ($leftTypes as $leftTypeInner) {
631: foreach ($rightTypes as $rightTypeInner) {
632: if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) {
633: $resultType = $this->getTypeFromValue($leftTypeInner->getValue() | $rightTypeInner->getValue());
634: } else {
635: $leftNumberType = $leftTypeInner->toNumber();
636: $rightNumberType = $rightTypeInner->toNumber();
637:
638: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
639: return new ErrorType();
640: }
641:
642: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
643: throw new ShouldNotHappenException();
644: }
645:
646: $resultType = $this->getTypeFromValue($leftNumberType->getValue() | $rightNumberType->getValue());
647: }
648: if ($generalize) {
649: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
650: }
651: $resultTypes[] = $resultType;
652: }
653: }
654: return TypeCombinator::union(...$resultTypes);
655: }
656:
657: if ($leftType->isString()->yes() && $rightType->isString()->yes()) {
658: return new StringType();
659: }
660:
661: if (TypeCombinator::union($leftType->toNumber(), $rightType->toNumber()) instanceof ErrorType) {
662: return new ErrorType();
663: }
664:
665: return new IntegerType();
666: }
667:
668: /**
669: * @param callable(Expr): Type $getTypeCallback
670: */
671: public function getBitwiseXorType(Expr $left, Expr $right, callable $getTypeCallback): Type
672: {
673: $leftType = $getTypeCallback($left);
674: $rightType = $getTypeCallback($right);
675:
676: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
677: return $this->getNeverType($leftType, $rightType);
678: }
679:
680: $leftTypes = $leftType->getConstantScalarTypes();
681: $rightTypes = $rightType->getConstantScalarTypes();
682: $leftTypesCount = count($leftTypes);
683: $rightTypesCount = count($rightTypes);
684: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
685: $resultTypes = [];
686: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
687: foreach ($leftTypes as $leftTypeInner) {
688: foreach ($rightTypes as $rightTypeInner) {
689: if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) {
690: $resultType = $this->getTypeFromValue($leftTypeInner->getValue() ^ $rightTypeInner->getValue());
691: } else {
692: $leftNumberType = $leftTypeInner->toNumber();
693: $rightNumberType = $rightTypeInner->toNumber();
694:
695: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
696: return new ErrorType();
697: }
698:
699: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
700: throw new ShouldNotHappenException();
701: }
702:
703: $resultType = $this->getTypeFromValue($leftNumberType->getValue() ^ $rightNumberType->getValue());
704: }
705: if ($generalize) {
706: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
707: }
708: $resultTypes[] = $resultType;
709: }
710: }
711: return TypeCombinator::union(...$resultTypes);
712: }
713:
714: if ($leftType->isString()->yes() && $rightType->isString()->yes()) {
715: return new StringType();
716: }
717:
718: if (TypeCombinator::union($leftType->toNumber(), $rightType->toNumber()) instanceof ErrorType) {
719: return new ErrorType();
720: }
721:
722: return new IntegerType();
723: }
724:
725: /**
726: * @param callable(Expr): Type $getTypeCallback
727: */
728: public function getSpaceshipType(Expr $left, Expr $right, callable $getTypeCallback): Type
729: {
730: $callbackLeftType = $getTypeCallback($left);
731: $callbackRightType = $getTypeCallback($right);
732:
733: if ($callbackLeftType instanceof NeverType || $callbackRightType instanceof NeverType) {
734: return $this->getNeverType($callbackLeftType, $callbackRightType);
735: }
736:
737: $leftTypes = $callbackLeftType->getConstantScalarTypes();
738: $rightTypes = $callbackRightType->getConstantScalarTypes();
739:
740: $leftTypesCount = count($leftTypes);
741: $rightTypesCount = count($rightTypes);
742: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
743: $resultTypes = [];
744: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
745: foreach ($leftTypes as $leftType) {
746: foreach ($rightTypes as $rightType) {
747: $leftValue = $leftType->getValue();
748: $rightValue = $rightType->getValue();
749: $resultType = $this->getTypeFromValue($leftValue <=> $rightValue);
750: if ($generalize) {
751: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
752: }
753: $resultTypes[] = $resultType;
754: }
755: }
756: return TypeCombinator::union(...$resultTypes);
757: }
758:
759: return IntegerRangeType::fromInterval(-1, 1);
760: }
761:
762: /**
763: * @param callable(Expr): Type $getTypeCallback
764: */
765: public function getDivType(Expr $left, Expr $right, callable $getTypeCallback): Type
766: {
767: $leftType = $getTypeCallback($left);
768: $rightType = $getTypeCallback($right);
769:
770: $leftTypes = $leftType->getConstantScalarTypes();
771: $rightTypes = $rightType->getConstantScalarTypes();
772: $leftTypesCount = count($leftTypes);
773: $rightTypesCount = count($rightTypes);
774: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
775: $resultTypes = [];
776: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
777: foreach ($leftTypes as $leftTypeInner) {
778: foreach ($rightTypes as $rightTypeInner) {
779: $leftNumberType = $leftTypeInner->toNumber();
780: $rightNumberType = $rightTypeInner->toNumber();
781:
782: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
783: return new ErrorType();
784: }
785:
786: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
787: throw new ShouldNotHappenException();
788: }
789:
790: if ($rightNumberType->getValue() === 0 || $rightNumberType->getValue() === 0.0) {
791: return new ErrorType();
792: }
793:
794: $resultType = $this->getTypeFromValue($leftNumberType->getValue() / $rightNumberType->getValue()); // @phpstan-ignore-line
795: if ($generalize) {
796: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
797: }
798: $resultTypes[] = $resultType;
799: }
800: }
801: return TypeCombinator::union(...$resultTypes);
802: }
803:
804: $rightScalarValues = $rightType->toNumber()->getConstantScalarValues();
805: foreach ($rightScalarValues as $scalarValue) {
806: if ($scalarValue === 0 || $scalarValue === 0.0) {
807: return new ErrorType();
808: }
809: }
810:
811: return $this->resolveCommonMath(new BinaryOp\Div($left, $right), $leftType, $rightType);
812: }
813:
814: /**
815: * @param callable(Expr): Type $getTypeCallback
816: */
817: public function getModType(Expr $left, Expr $right, callable $getTypeCallback): Type
818: {
819: $leftType = $getTypeCallback($left);
820: $rightType = $getTypeCallback($right);
821:
822: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
823: return $this->getNeverType($leftType, $rightType);
824: }
825:
826: $leftTypes = $leftType->getConstantScalarTypes();
827: $rightTypes = $rightType->getConstantScalarTypes();
828: $leftTypesCount = count($leftTypes);
829: $rightTypesCount = count($rightTypes);
830: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
831: $resultTypes = [];
832: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
833: foreach ($leftTypes as $leftTypeInner) {
834: foreach ($rightTypes as $rightTypeInner) {
835: $leftNumberType = $leftTypeInner->toNumber();
836: $rightNumberType = $rightTypeInner->toNumber();
837:
838: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
839: return new ErrorType();
840: }
841:
842: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
843: throw new ShouldNotHappenException();
844: }
845:
846: $rightIntegerValue = (int) $rightNumberType->getValue();
847: if ($rightIntegerValue === 0) {
848: return new ErrorType();
849: }
850:
851: $resultType = $this->getTypeFromValue((int) $leftNumberType->getValue() % $rightIntegerValue);
852: if ($generalize) {
853: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
854: }
855: $resultTypes[] = $resultType;
856: }
857: }
858: return TypeCombinator::union(...$resultTypes);
859: }
860:
861: $integerType = $rightType->toInteger();
862: if ($integerType instanceof ConstantIntegerType && $integerType->getValue() === 1) {
863: return new ConstantIntegerType(0);
864: }
865:
866: $rightScalarValues = $rightType->toNumber()->getConstantScalarValues();
867: foreach ($rightScalarValues as $scalarValue) {
868:
869: if ($scalarValue === 0 || $scalarValue === 0.0) {
870: return new ErrorType();
871: }
872: }
873:
874: $integer = new IntegerType();
875: $positiveInt = IntegerRangeType::fromInterval(0, null);
876: if ($integer->isSuperTypeOf($rightType)->yes()) {
877: $rangeMin = null;
878: $rangeMax = null;
879:
880: if ($rightType instanceof IntegerRangeType) {
881: $rangeMax = $rightType->getMax() !== null ? $rightType->getMax() - 1 : null;
882: } elseif ($rightType instanceof ConstantIntegerType) {
883: $rangeMax = $rightType->getValue() - 1;
884: } elseif ($rightType instanceof UnionType) {
885: foreach ($rightType->getTypes() as $type) {
886: if ($type instanceof IntegerRangeType) {
887: if ($type->getMax() === null) {
888: $rangeMax = null;
889: } else {
890: $rangeMax = max($rangeMax, $type->getMax());
891: }
892: } elseif ($type instanceof ConstantIntegerType) {
893: $rangeMax = max($rangeMax, $type->getValue() - 1);
894: }
895: }
896: }
897:
898: if ($positiveInt->isSuperTypeOf($leftType)->yes()) {
899: $rangeMin = 0;
900: } elseif ($rangeMax !== null) {
901: $rangeMin = $rangeMax * -1;
902: }
903:
904: return IntegerRangeType::fromInterval($rangeMin, $rangeMax);
905: } elseif ($positiveInt->isSuperTypeOf($leftType)->yes()) {
906: return IntegerRangeType::fromInterval(0, null);
907: }
908:
909: return new IntegerType();
910: }
911:
912: /**
913: * @param callable(Expr): Type $getTypeCallback
914: */
915: public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): Type
916: {
917: $leftType = $getTypeCallback($left);
918: $rightType = $getTypeCallback($right);
919:
920: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
921: return $this->getNeverType($leftType, $rightType);
922: }
923:
924: $leftTypes = $leftType->getConstantScalarTypes();
925: $rightTypes = $rightType->getConstantScalarTypes();
926: $leftTypesCount = count($leftTypes);
927: $rightTypesCount = count($rightTypes);
928: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
929: $resultTypes = [];
930: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
931: foreach ($leftTypes as $leftTypeInner) {
932: foreach ($rightTypes as $rightTypeInner) {
933: $leftNumberType = $leftTypeInner->toNumber();
934: $rightNumberType = $rightTypeInner->toNumber();
935:
936: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
937: return new ErrorType();
938: }
939:
940: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
941: throw new ShouldNotHappenException();
942: }
943:
944: $resultType = $this->getTypeFromValue($leftNumberType->getValue() + $rightNumberType->getValue());
945: if ($generalize) {
946: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
947: }
948: $resultTypes[] = $resultType;
949: }
950: }
951:
952: return TypeCombinator::union(...$resultTypes);
953: }
954:
955: $leftConstantArrays = $leftType->getConstantArrays();
956: $rightConstantArrays = $rightType->getConstantArrays();
957:
958: $leftCount = count($leftConstantArrays);
959: $rightCount = count($rightConstantArrays);
960: if ($leftCount > 0 && $rightCount > 0
961: && ($leftCount + $rightCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT)) {
962: $resultTypes = [];
963: foreach ($rightConstantArrays as $rightConstantArray) {
964: foreach ($leftConstantArrays as $leftConstantArray) {
965: $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray);
966: foreach ($leftConstantArray->getKeyTypes() as $i => $leftKeyType) {
967: $optional = $leftConstantArray->isOptionalKey($i);
968: $valueType = $leftConstantArray->getOffsetValueType($leftKeyType);
969: if (!$optional) {
970: if ($rightConstantArray->hasOffsetValueType($leftKeyType)->maybe()) {
971: $valueType = TypeCombinator::union($valueType, $rightConstantArray->getOffsetValueType($leftKeyType));
972: }
973: }
974: $newArrayBuilder->setOffsetValueType(
975: $leftKeyType,
976: $valueType,
977: $optional,
978: );
979: }
980: $resultTypes[] = $newArrayBuilder->getArray();
981: }
982: }
983: return TypeCombinator::union(...$resultTypes);
984: }
985:
986: $leftIsArray = $leftType->isArray();
987: $rightIsArray = $rightType->isArray();
988: if ($leftIsArray->yes() && $rightIsArray->yes()) {
989: if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) {
990: // to preserve BenevolentUnionType
991: $keyType = $leftType->getIterableKeyType();
992: } else {
993: $keyTypes = [];
994: foreach ([
995: $leftType->getIterableKeyType(),
996: $rightType->getIterableKeyType(),
997: ] as $keyType) {
998: $keyTypes[] = $keyType;
999: }
1000: $keyType = TypeCombinator::union(...$keyTypes);
1001: }
1002:
1003: $arrayType = new ArrayType(
1004: $keyType,
1005: TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType()),
1006: );
1007:
1008: if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) {
1009: $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
1010: }
1011: if ($leftType->isList()->yes() && $rightType->isList()->yes()) {
1012: $arrayType = AccessoryArrayListType::intersectWith($arrayType);
1013: }
1014:
1015: return $arrayType;
1016: }
1017:
1018: if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1019: if (
1020: ($leftIsArray->no() && $rightIsArray->no())
1021: ) {
1022: return new BenevolentUnionType([
1023: new FloatType(),
1024: new IntegerType(),
1025: ]);
1026: }
1027: return new BenevolentUnionType([
1028: new FloatType(),
1029: new IntegerType(),
1030: new ArrayType(new MixedType(), new MixedType()),
1031: ]);
1032: }
1033:
1034: if (
1035: ($leftIsArray->yes() && $rightIsArray->no())
1036: || ($leftIsArray->no() && $rightIsArray->yes())
1037: ) {
1038: return new ErrorType();
1039: }
1040:
1041: if (
1042: ($leftIsArray->yes() && $rightIsArray->maybe())
1043: || ($leftIsArray->maybe() && $rightIsArray->yes())
1044: ) {
1045: $resultType = new ArrayType(new MixedType(), new MixedType());
1046: if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) {
1047: return TypeCombinator::intersect($resultType, new NonEmptyArrayType());
1048: }
1049:
1050: return $resultType;
1051: }
1052:
1053: if ($leftIsArray->maybe() && $rightIsArray->maybe()) {
1054: $plusable = new UnionType([
1055: new StringType(),
1056: new FloatType(),
1057: new IntegerType(),
1058: new ArrayType(new MixedType(), new MixedType()),
1059: new BooleanType(),
1060: ]);
1061:
1062: if ($plusable->isSuperTypeOf($leftType)->yes() && $plusable->isSuperTypeOf($rightType)->yes()) {
1063: return TypeCombinator::union($leftType, $rightType);
1064: }
1065: }
1066:
1067: return $this->resolveCommonMath(new BinaryOp\Plus($left, $right), $leftType, $rightType);
1068: }
1069:
1070: /**
1071: * @param callable(Expr): Type $getTypeCallback
1072: */
1073: public function getMinusType(Expr $left, Expr $right, callable $getTypeCallback): Type
1074: {
1075: $leftType = $getTypeCallback($left);
1076: $rightType = $getTypeCallback($right);
1077:
1078: $leftTypes = $leftType->getConstantScalarTypes();
1079: $rightTypes = $rightType->getConstantScalarTypes();
1080: $leftTypesCount = count($leftTypes);
1081: $rightTypesCount = count($rightTypes);
1082: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1083: $resultTypes = [];
1084: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1085: foreach ($leftTypes as $leftTypeInner) {
1086: foreach ($rightTypes as $rightTypeInner) {
1087: $leftNumberType = $leftTypeInner->toNumber();
1088: $rightNumberType = $rightTypeInner->toNumber();
1089:
1090: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1091: return new ErrorType();
1092: }
1093:
1094: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1095: throw new ShouldNotHappenException();
1096: }
1097:
1098: $resultType = $this->getTypeFromValue($leftNumberType->getValue() - $rightNumberType->getValue());
1099: if ($generalize) {
1100: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
1101: }
1102: $resultTypes[] = $resultType;
1103: }
1104: }
1105:
1106: return TypeCombinator::union(...$resultTypes);
1107: }
1108:
1109: return $this->resolveCommonMath(new BinaryOp\Minus($left, $right), $leftType, $rightType);
1110: }
1111:
1112: /**
1113: * @param callable(Expr): Type $getTypeCallback
1114: */
1115: public function getMulType(Expr $left, Expr $right, callable $getTypeCallback): Type
1116: {
1117: $leftType = $getTypeCallback($left);
1118: $rightType = $getTypeCallback($right);
1119:
1120: $leftTypes = $leftType->getConstantScalarTypes();
1121: $rightTypes = $rightType->getConstantScalarTypes();
1122: $leftTypesCount = count($leftTypes);
1123: $rightTypesCount = count($rightTypes);
1124: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1125: $resultTypes = [];
1126: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1127: foreach ($leftTypes as $leftTypeInner) {
1128: foreach ($rightTypes as $rightTypeInner) {
1129: $leftNumberType = $leftTypeInner->toNumber();
1130: $rightNumberType = $rightTypeInner->toNumber();
1131:
1132: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1133: return new ErrorType();
1134: }
1135:
1136: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1137: throw new ShouldNotHappenException();
1138: }
1139:
1140: $resultType = $this->getTypeFromValue($leftNumberType->getValue() * $rightNumberType->getValue());
1141: if ($generalize) {
1142: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
1143: }
1144: $resultTypes[] = $resultType;
1145: }
1146: }
1147:
1148: return TypeCombinator::union(...$resultTypes);
1149: }
1150:
1151: $floatType = new FloatType();
1152: $leftNumberType = $leftType->toNumber();
1153: if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0) {
1154: if ($floatType->isSuperTypeOf($rightType)->yes()) {
1155: return new ConstantFloatType(0.0);
1156: }
1157: return new ConstantIntegerType(0);
1158: }
1159: $rightNumberType = $rightType->toNumber();
1160: if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0) {
1161: if ($floatType->isSuperTypeOf($leftType)->yes()) {
1162: return new ConstantFloatType(0.0);
1163: }
1164: return new ConstantIntegerType(0);
1165: }
1166:
1167: return $this->resolveCommonMath(new BinaryOp\Mul($left, $right), $leftType, $rightType);
1168: }
1169:
1170: /**
1171: * @param callable(Expr): Type $getTypeCallback
1172: */
1173: public function getPowType(Expr $left, Expr $right, callable $getTypeCallback): Type
1174: {
1175: $leftType = $getTypeCallback($left);
1176: $rightType = $getTypeCallback($right);
1177:
1178: $exponentiatedTyped = $leftType->exponentiate($rightType);
1179: if (!$exponentiatedTyped instanceof ErrorType) {
1180: return $exponentiatedTyped;
1181: }
1182:
1183: $extensionSpecified = $this->callOperatorTypeSpecifyingExtensions(new BinaryOp\Pow($left, $right), $leftType, $rightType);
1184: if ($extensionSpecified !== null) {
1185: return $extensionSpecified;
1186: }
1187:
1188: return new ErrorType();
1189: }
1190:
1191: /**
1192: * @param callable(Expr): Type $getTypeCallback
1193: */
1194: public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallback): Type
1195: {
1196: $leftType = $getTypeCallback($left);
1197: $rightType = $getTypeCallback($right);
1198:
1199: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1200: return $this->getNeverType($leftType, $rightType);
1201: }
1202:
1203: $leftTypes = $leftType->getConstantScalarTypes();
1204: $rightTypes = $rightType->getConstantScalarTypes();
1205: $leftTypesCount = count($leftTypes);
1206: $rightTypesCount = count($rightTypes);
1207: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1208: $resultTypes = [];
1209: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1210: foreach ($leftTypes as $leftTypeInner) {
1211: foreach ($rightTypes as $rightTypeInner) {
1212: $leftNumberType = $leftTypeInner->toNumber();
1213: $rightNumberType = $rightTypeInner->toNumber();
1214:
1215: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1216: return new ErrorType();
1217: }
1218:
1219: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1220: throw new ShouldNotHappenException();
1221: }
1222:
1223: if ($rightNumberType->getValue() < 0) {
1224: return new ErrorType();
1225: }
1226:
1227: $resultType = $this->getTypeFromValue($leftNumberType->getValue() << $rightNumberType->getValue());
1228: if ($generalize) {
1229: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
1230: }
1231: $resultTypes[] = $resultType;
1232: }
1233: }
1234:
1235: return TypeCombinator::union(...$resultTypes);
1236: }
1237:
1238: $leftNumberType = $leftType->toNumber();
1239: $rightNumberType = $rightType->toNumber();
1240:
1241: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1242: return new ErrorType();
1243: }
1244:
1245: return new IntegerType();
1246: }
1247:
1248: /**
1249: * @param callable(Expr): Type $getTypeCallback
1250: */
1251: public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCallback): Type
1252: {
1253: $leftType = $getTypeCallback($left);
1254: $rightType = $getTypeCallback($right);
1255:
1256: if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
1257: return $this->getNeverType($leftType, $rightType);
1258: }
1259:
1260: $leftTypes = $leftType->getConstantScalarTypes();
1261: $rightTypes = $rightType->getConstantScalarTypes();
1262: $leftTypesCount = count($leftTypes);
1263: $rightTypesCount = count($rightTypes);
1264: if ($leftTypesCount > 0 && $rightTypesCount > 0) {
1265: $resultTypes = [];
1266: $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT;
1267: foreach ($leftTypes as $leftTypeInner) {
1268: foreach ($rightTypes as $rightTypeInner) {
1269: $leftNumberType = $leftTypeInner->toNumber();
1270: $rightNumberType = $rightTypeInner->toNumber();
1271:
1272: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1273: return new ErrorType();
1274: }
1275:
1276: if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) {
1277: throw new ShouldNotHappenException();
1278: }
1279:
1280: if ($rightNumberType->getValue() < 0) {
1281: return new ErrorType();
1282: }
1283:
1284: $resultType = $this->getTypeFromValue($leftNumberType->getValue() >> $rightNumberType->getValue());
1285: if ($generalize) {
1286: $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific());
1287: }
1288: $resultTypes[] = $resultType;
1289: }
1290: }
1291:
1292: return TypeCombinator::union(...$resultTypes);
1293: }
1294:
1295: $leftNumberType = $leftType->toNumber();
1296: $rightNumberType = $rightType->toNumber();
1297:
1298: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1299: return new ErrorType();
1300: }
1301:
1302: return new IntegerType();
1303: }
1304:
1305: public function resolveIdenticalType(Type $leftType, Type $rightType): BooleanType
1306: {
1307: if ($leftType instanceof ConstantScalarType && $rightType instanceof ConstantScalarType) {
1308: return new ConstantBooleanType($leftType->getValue() === $rightType->getValue());
1309: }
1310:
1311: $leftTypeEnumCases = $leftType->getEnumCases();
1312: $rightTypeEnumCases = $rightType->getEnumCases();
1313: if (count($leftTypeEnumCases) === 1 && count($rightTypeEnumCases) === 1) {
1314: return new ConstantBooleanType($leftTypeEnumCases[0]->equals($rightTypeEnumCases[0]));
1315: }
1316:
1317: $isSuperset = $leftType->isSuperTypeOf($rightType);
1318: if ($isSuperset->no()) {
1319: return new ConstantBooleanType(false);
1320: }
1321:
1322: if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) {
1323: return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): BooleanType => $this->resolveIdenticalType($leftValueType, $rightValueType));
1324: }
1325:
1326: return new BooleanType();
1327: }
1328:
1329: public function resolveEqualType(Type $leftType, Type $rightType): BooleanType
1330: {
1331: $integerType = new IntegerType();
1332: $floatType = new FloatType();
1333:
1334: if (
1335: (count($leftType->getEnumCases()) === 1 && count($rightType->getEnumCases()) === 1)
1336: || ($leftType->isString()->yes() && $rightType->isString()->yes())
1337: || ($integerType->isSuperTypeOf($leftType)->yes() && $integerType->isSuperTypeOf($rightType)->yes())
1338: || ($floatType->isSuperTypeOf($leftType)->yes() && $floatType->isSuperTypeOf($rightType)->yes())
1339: ) {
1340: return $this->resolveIdenticalType($leftType, $rightType);
1341: }
1342:
1343: if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) {
1344: return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): BooleanType => $this->resolveEqualType($leftValueType, $rightValueType));
1345: }
1346:
1347: return $leftType->looseCompare($rightType, $this->phpVersion);
1348: }
1349:
1350: /**
1351: * @param callable(Type, Type): BooleanType $valueComparisonCallback
1352: */
1353: private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, ConstantArrayType $rightType, callable $valueComparisonCallback): BooleanType
1354: {
1355: $leftKeyTypes = $leftType->getKeyTypes();
1356: $rightKeyTypes = $rightType->getKeyTypes();
1357: $leftValueTypes = $leftType->getValueTypes();
1358: $rightValueTypes = $rightType->getValueTypes();
1359:
1360: $resultType = new ConstantBooleanType(true);
1361:
1362: foreach ($leftKeyTypes as $i => $leftKeyType) {
1363: $leftOptional = $leftType->isOptionalKey($i);
1364: if ($leftOptional) {
1365: $resultType = new BooleanType();
1366: }
1367:
1368: if (count($rightKeyTypes) === 0) {
1369: if (!$leftOptional) {
1370: return new ConstantBooleanType(false);
1371: }
1372: continue;
1373: }
1374:
1375: $found = false;
1376: foreach ($rightKeyTypes as $j => $rightKeyType) {
1377: unset($rightKeyTypes[$j]);
1378:
1379: if ($leftKeyType->equals($rightKeyType)) {
1380: $found = true;
1381: break;
1382: } elseif (!$rightType->isOptionalKey($j)) {
1383: return new ConstantBooleanType(false);
1384: }
1385: }
1386:
1387: if (!$found) {
1388: if (!$leftOptional) {
1389: return new ConstantBooleanType(false);
1390: }
1391: continue;
1392: }
1393:
1394: if (!isset($j)) {
1395: throw new ShouldNotHappenException();
1396: }
1397:
1398: $rightOptional = $rightType->isOptionalKey($j);
1399: if ($rightOptional) {
1400: $resultType = new BooleanType();
1401: if ($leftOptional) {
1402: continue;
1403: }
1404: }
1405:
1406: $leftIdenticalToRight = $valueComparisonCallback($leftValueTypes[$i], $rightValueTypes[$j]);
1407: if ($leftIdenticalToRight->isFalse()->yes()) {
1408: return new ConstantBooleanType(false);
1409: }
1410: $resultType = TypeCombinator::union($resultType, $leftIdenticalToRight);
1411: }
1412:
1413: foreach (array_keys($rightKeyTypes) as $j) {
1414: if (!$rightType->isOptionalKey($j)) {
1415: return new ConstantBooleanType(false);
1416: }
1417: $resultType = new BooleanType();
1418: }
1419:
1420: return $resultType->toBoolean();
1421: }
1422:
1423: private function callOperatorTypeSpecifyingExtensions(Expr\BinaryOp $expr, Type $leftType, Type $rightType): ?Type
1424: {
1425: $operatorSigil = $expr->getOperatorSigil();
1426: $operatorTypeSpecifyingExtensions = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()->getOperatorTypeSpecifyingExtensions($operatorSigil, $leftType, $rightType);
1427:
1428: /** @var Type[] $extensionTypes */
1429: $extensionTypes = [];
1430:
1431: foreach ($operatorTypeSpecifyingExtensions as $extension) {
1432: $extensionTypes[] = $extension->specifyType($operatorSigil, $leftType, $rightType);
1433: }
1434:
1435: if (count($extensionTypes) > 0) {
1436: return TypeCombinator::union(...$extensionTypes);
1437: }
1438:
1439: return null;
1440: }
1441:
1442: /**
1443: * @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div $expr
1444: */
1445: private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type
1446: {
1447: if (($leftType instanceof IntegerRangeType || $leftType instanceof ConstantIntegerType || $leftType instanceof UnionType) &&
1448: ($rightType instanceof IntegerRangeType || $rightType instanceof ConstantIntegerType || $rightType instanceof UnionType)
1449: ) {
1450:
1451: if ($leftType instanceof ConstantIntegerType) {
1452: return $this->integerRangeMath(
1453: $leftType,
1454: $expr,
1455: $rightType,
1456: );
1457: } elseif ($leftType instanceof UnionType) {
1458:
1459: $unionParts = [];
1460:
1461: foreach ($leftType->getTypes() as $type) {
1462: if ($type instanceof IntegerRangeType || $type instanceof ConstantIntegerType) {
1463: $unionParts[] = $this->integerRangeMath($type, $expr, $rightType);
1464: } else {
1465: $unionParts[] = $type;
1466: }
1467: }
1468:
1469: $union = TypeCombinator::union(...$unionParts);
1470: if ($leftType instanceof BenevolentUnionType) {
1471: return TypeUtils::toBenevolentUnion($union)->toNumber();
1472: }
1473:
1474: return $union->toNumber();
1475: }
1476:
1477: return $this->integerRangeMath($leftType, $expr, $rightType);
1478: }
1479:
1480: $specifiedTypes = $this->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType);
1481: if ($specifiedTypes !== null) {
1482: return $specifiedTypes;
1483: }
1484:
1485: $types = TypeCombinator::union($leftType, $rightType);
1486: if (
1487: $leftType->isArray()->yes()
1488: || $rightType->isArray()->yes()
1489: || $types->isArray()->yes()
1490: ) {
1491: return new ErrorType();
1492: }
1493:
1494: $leftNumberType = $leftType->toNumber();
1495: $rightNumberType = $rightType->toNumber();
1496: if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
1497: return new ErrorType();
1498: }
1499: if ($leftNumberType instanceof NeverType || $rightNumberType instanceof NeverType) {
1500: return $this->getNeverType($leftNumberType, $rightNumberType);
1501: }
1502:
1503: if (
1504: (new FloatType())->isSuperTypeOf($leftNumberType)->yes()
1505: || (new FloatType())->isSuperTypeOf($rightNumberType)->yes()
1506: ) {
1507: return new FloatType();
1508: }
1509:
1510: $resultType = TypeCombinator::union($leftNumberType, $rightNumberType);
1511: if ($expr instanceof Expr\BinaryOp\Div) {
1512: if ($types instanceof MixedType || $resultType->isInteger()->yes()) {
1513: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
1514: }
1515:
1516: return new UnionType([new IntegerType(), new FloatType()]);
1517: }
1518:
1519: if ($types instanceof MixedType
1520: || $leftType instanceof BenevolentUnionType
1521: || $rightType instanceof BenevolentUnionType
1522: ) {
1523: return TypeUtils::toBenevolentUnion($resultType);
1524: }
1525:
1526: return $resultType;
1527: }
1528:
1529: /**
1530: * @param ConstantIntegerType|IntegerRangeType $range
1531: * @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus $node
1532: * @param IntegerRangeType|ConstantIntegerType|UnionType $operand
1533: */
1534: private function integerRangeMath(Type $range, BinaryOp $node, Type $operand): Type
1535: {
1536: if ($range instanceof IntegerRangeType) {
1537: $rangeMin = $range->getMin();
1538: $rangeMax = $range->getMax();
1539: } else {
1540: $rangeMin = $range->getValue();
1541: $rangeMax = $rangeMin;
1542: }
1543:
1544: if ($operand instanceof UnionType) {
1545:
1546: $unionParts = [];
1547:
1548: foreach ($operand->getTypes() as $type) {
1549: if ($type instanceof IntegerRangeType || $type instanceof ConstantIntegerType) {
1550: $unionParts[] = $this->integerRangeMath($range, $node, $type);
1551: } else {
1552: $unionParts[] = $type->toNumber();
1553: }
1554: }
1555:
1556: $union = TypeCombinator::union(...$unionParts);
1557: if ($operand instanceof BenevolentUnionType) {
1558: return TypeUtils::toBenevolentUnion($union)->toNumber();
1559: }
1560:
1561: return $union->toNumber();
1562: }
1563:
1564: if ($operand instanceof IntegerRangeType) {
1565: $operandMin = $operand->getMin();
1566: $operandMax = $operand->getMax();
1567: } else {
1568: $operandMin = $operand->getValue();
1569: $operandMax = $operand->getValue();
1570: }
1571:
1572: if ($node instanceof BinaryOp\Plus) {
1573: if ($operand instanceof ConstantIntegerType) {
1574: /** @var int|float|null $min */
1575: $min = $rangeMin !== null ? $rangeMin + $operand->getValue() : null;
1576:
1577: /** @var int|float|null $max */
1578: $max = $rangeMax !== null ? $rangeMax + $operand->getValue() : null;
1579: } else {
1580: /** @var int|float|null $min */
1581: $min = $rangeMin !== null && $operand->getMin() !== null ? $rangeMin + $operand->getMin() : null;
1582:
1583: /** @var int|float|null $max */
1584: $max = $rangeMax !== null && $operand->getMax() !== null ? $rangeMax + $operand->getMax() : null;
1585: }
1586: } elseif ($node instanceof BinaryOp\Minus) {
1587: if ($operand instanceof ConstantIntegerType) {
1588: /** @var int|float|null $min */
1589: $min = $rangeMin !== null ? $rangeMin - $operand->getValue() : null;
1590:
1591: /** @var int|float|null $max */
1592: $max = $rangeMax !== null ? $rangeMax - $operand->getValue() : null;
1593: } else {
1594: if ($rangeMin === $rangeMax && $rangeMin !== null
1595: && ($operand->getMin() === null || $operand->getMax() === null)) {
1596: $min = null;
1597: $max = $rangeMin;
1598: } else {
1599: if ($operand->getMin() === null) {
1600: $min = null;
1601: } elseif ($rangeMin !== null) {
1602: if ($operand->getMax() !== null) {
1603: /** @var int|float $min */
1604: $min = $rangeMin - $operand->getMax();
1605: } else {
1606: /** @var int|float $min */
1607: $min = $rangeMin - $operand->getMin();
1608: }
1609: } else {
1610: $min = null;
1611: }
1612:
1613: if ($operand->getMax() === null) {
1614: $min = null;
1615: $max = null;
1616: } elseif ($rangeMax !== null) {
1617: if ($rangeMin !== null && $operand->getMin() === null) {
1618: /** @var int|float $min */
1619: $min = $rangeMin - $operand->getMax();
1620: $max = null;
1621: } elseif ($operand->getMin() !== null) {
1622: /** @var int|float $max */
1623: $max = $rangeMax - $operand->getMin();
1624: } else {
1625: $max = null;
1626: }
1627: } else {
1628: $max = null;
1629: }
1630:
1631: if ($min !== null && $max !== null && $min > $max) {
1632: [$min, $max] = [$max, $min];
1633: }
1634: }
1635: }
1636: } elseif ($node instanceof Expr\BinaryOp\Mul) {
1637: $min1 = $rangeMin === 0 || $operandMin === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMin ?? -INF);
1638: $min2 = $rangeMin === 0 || $operandMax === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMax ?? INF);
1639: $max1 = $rangeMax === 0 || $operandMin === 0 ? 0 : ($rangeMax ?? INF) * ($operandMin ?? -INF);
1640: $max2 = $rangeMax === 0 || $operandMax === 0 ? 0 : ($rangeMax ?? INF) * ($operandMax ?? INF);
1641:
1642: $min = min($min1, $min2, $max1, $max2);
1643: $max = max($min1, $min2, $max1, $max2);
1644:
1645: if ($min === -INF) {
1646: $min = null;
1647: }
1648: if ($max === INF) {
1649: $max = null;
1650: }
1651: } else {
1652: if ($operand instanceof ConstantIntegerType) {
1653: $min = $rangeMin !== null && $operand->getValue() !== 0 ? $rangeMin / $operand->getValue() : null;
1654: $max = $rangeMax !== null && $operand->getValue() !== 0 ? $rangeMax / $operand->getValue() : null;
1655: } else {
1656: // Avoid division by zero when looking for the min and the max by using the closest int
1657: $operandMin = $operandMin !== 0 ? $operandMin : 1;
1658: $operandMax = $operandMax !== 0 ? $operandMax : -1;
1659:
1660: if (
1661: ($operandMin < 0 || $operandMin === null)
1662: && ($operandMax > 0 || $operandMax === null)
1663: ) {
1664: $negativeOperand = IntegerRangeType::fromInterval($operandMin, 0);
1665: assert($negativeOperand instanceof IntegerRangeType);
1666: $positiveOperand = IntegerRangeType::fromInterval(0, $operandMax);
1667: assert($positiveOperand instanceof IntegerRangeType);
1668:
1669: $result = TypeCombinator::union(
1670: $this->integerRangeMath($range, $node, $negativeOperand),
1671: $this->integerRangeMath($range, $node, $positiveOperand),
1672: )->toNumber();
1673:
1674: if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) {
1675: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
1676: }
1677:
1678: return $result;
1679: }
1680: if (
1681: ($rangeMin < 0 || $rangeMin === null)
1682: && ($rangeMax > 0 || $rangeMax === null)
1683: ) {
1684: $negativeRange = IntegerRangeType::fromInterval($rangeMin, 0);
1685: assert($negativeRange instanceof IntegerRangeType);
1686: $positiveRange = IntegerRangeType::fromInterval(0, $rangeMax);
1687: assert($positiveRange instanceof IntegerRangeType);
1688:
1689: $result = TypeCombinator::union(
1690: $this->integerRangeMath($negativeRange, $node, $operand),
1691: $this->integerRangeMath($positiveRange, $node, $operand),
1692: )->toNumber();
1693:
1694: if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) {
1695: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
1696: }
1697:
1698: return $result;
1699: }
1700:
1701: $rangeMinSign = ($rangeMin ?? -INF) <=> 0;
1702: $rangeMaxSign = ($rangeMax ?? INF) <=> 0;
1703:
1704: $min1 = $operandMin !== null ? ($rangeMin ?? -INF) / $operandMin : $rangeMinSign * -0.1;
1705: $min2 = $operandMax !== null ? ($rangeMin ?? -INF) / $operandMax : $rangeMinSign * 0.1;
1706: $max1 = $operandMin !== null ? ($rangeMax ?? INF) / $operandMin : $rangeMaxSign * -0.1;
1707: $max2 = $operandMax !== null ? ($rangeMax ?? INF) / $operandMax : $rangeMaxSign * 0.1;
1708:
1709: $min = min($min1, $min2, $max1, $max2);
1710: $max = max($min1, $min2, $max1, $max2);
1711:
1712: if ($min === -INF) {
1713: $min = null;
1714: }
1715: if ($max === INF) {
1716: $max = null;
1717: }
1718: }
1719:
1720: if ($min !== null && $max !== null && $min > $max) {
1721: [$min, $max] = [$max, $min];
1722: }
1723:
1724: if ($operand instanceof IntegerRangeType
1725: || ($rangeMin === null || $rangeMax === null)
1726: || is_float($min)
1727: || is_float($max)
1728: ) {
1729: if (is_float($min)) {
1730: $min = (int) ceil($min);
1731: }
1732: if (is_float($max)) {
1733: $max = (int) floor($max);
1734: }
1735:
1736: // invert maximas on division with negative constants
1737: if ((($range instanceof ConstantIntegerType && $range->getValue() < 0)
1738: || ($operand instanceof ConstantIntegerType && $operand->getValue() < 0))
1739: && ($min === null || $max === null)) {
1740: [$min, $max] = [$max, $min];
1741: }
1742:
1743: if ($min === null && $max === null) {
1744: return new BenevolentUnionType([new IntegerType(), new FloatType()]);
1745: }
1746:
1747: return TypeCombinator::union(IntegerRangeType::fromInterval($min, $max), new FloatType());
1748: }
1749: }
1750:
1751: if (is_float($min)) {
1752: $min = null;
1753: }
1754: if (is_float($max)) {
1755: $max = null;
1756: }
1757:
1758: return IntegerRangeType::fromInterval($min, $max);
1759: }
1760:
1761: /**
1762: * @param callable(Expr): Type $getTypeCallback
1763: */
1764: public function getClassConstFetchTypeByReflection(Name|Expr $class, string $constantName, ?ClassReflection $classReflection, callable $getTypeCallback): Type
1765: {
1766: $isObject = false;
1767: if ($class instanceof Name) {
1768: $constantClass = (string) $class;
1769: $constantClassType = new ObjectType($constantClass);
1770: $namesToResolve = [
1771: 'self',
1772: 'parent',
1773: ];
1774: if ($classReflection !== null) {
1775: if ($classReflection->isFinal()) {
1776: $namesToResolve[] = 'static';
1777: } elseif (strtolower($constantClass) === 'static') {
1778: if (strtolower($constantName) === 'class') {
1779: return new GenericClassStringType(new StaticType($classReflection));
1780: }
1781:
1782: $namesToResolve[] = 'static';
1783: $isObject = true;
1784: }
1785: }
1786: if (in_array(strtolower($constantClass), $namesToResolve, true)) {
1787: $resolvedName = $this->resolveName($class, $classReflection);
1788: if ($resolvedName === 'parent' && strtolower($constantName) === 'class') {
1789: return new ClassStringType();
1790: }
1791: $constantClassType = $this->resolveTypeByName($class, $classReflection);
1792: }
1793:
1794: if (strtolower($constantName) === 'class') {
1795: return new ConstantStringType($constantClassType->getClassName(), true);
1796: }
1797: } elseif ($class instanceof String_ && strtolower($constantName) === 'class') {
1798: return new ConstantStringType($class->value, true);
1799: } else {
1800: $constantClassType = $getTypeCallback($class);
1801: $isObject = true;
1802: }
1803:
1804: if (strtolower($constantName) === 'class') {
1805: return TypeTraverser::map(
1806: $constantClassType,
1807: function (Type $type, callable $traverse): Type {
1808: if ($type instanceof UnionType || $type instanceof IntersectionType) {
1809: return $traverse($type);
1810: }
1811:
1812: if ($type instanceof NullType) {
1813: return $type;
1814: }
1815:
1816: if ($type instanceof EnumCaseObjectType) {
1817: return TypeCombinator::intersect(
1818: new GenericClassStringType(new ObjectType($type->getClassName())),
1819: new AccessoryLiteralStringType(),
1820: );
1821: }
1822:
1823: $objectClassNames = $type->getObjectClassNames();
1824: if (count($objectClassNames) > 1) {
1825: throw new ShouldNotHappenException();
1826: }
1827:
1828: if ($type instanceof TemplateType && $objectClassNames === []) {
1829: return TypeCombinator::intersect(
1830: new GenericClassStringType($type),
1831: new AccessoryLiteralStringType(),
1832: );
1833: } elseif ($objectClassNames !== [] && $this->getReflectionProvider()->hasClass($objectClassNames[0])) {
1834: $reflection = $this->getReflectionProvider()->getClass($objectClassNames[0]);
1835: if ($reflection->isFinalByKeyword()) {
1836: return new ConstantStringType($reflection->getName(), true);
1837: }
1838:
1839: return TypeCombinator::intersect(
1840: new GenericClassStringType($type),
1841: new AccessoryLiteralStringType(),
1842: );
1843: } elseif ($type->isObject()->yes()) {
1844: return TypeCombinator::intersect(
1845: new ClassStringType(),
1846: new AccessoryLiteralStringType(),
1847: );
1848: }
1849:
1850: return new ErrorType();
1851: },
1852: );
1853: }
1854:
1855: $types = [];
1856: foreach ($constantClassType->getObjectClassNames() as $referencedClass) {
1857: if (!$this->getReflectionProvider()->hasClass($referencedClass)) {
1858: continue;
1859: }
1860:
1861: $constantClassReflection = $this->getReflectionProvider()->getClass($referencedClass);
1862: if (!$constantClassReflection->hasConstant($constantName)) {
1863: continue;
1864: }
1865:
1866: if ($constantClassReflection->isEnum() && $constantClassReflection->hasEnumCase($constantName)) {
1867: $types[] = new EnumCaseObjectType($constantClassReflection->getName(), $constantName);
1868: continue;
1869: }
1870:
1871: $constantReflection = $constantClassReflection->getConstant($constantName);
1872: if (
1873: $constantReflection instanceof ClassConstantReflection
1874: && $isObject
1875: && !$constantClassReflection->isFinal()
1876: && !$constantReflection->hasPhpDocType()
1877: ) {
1878: return new MixedType();
1879: }
1880:
1881: if (
1882: $isObject
1883: && (
1884: !$constantReflection instanceof ClassConstantReflection
1885: || !$constantClassReflection->isFinal()
1886: )
1887: ) {
1888: $constantType = $constantReflection->getValueType();
1889: } else {
1890: $constantType = $this->getType($constantReflection->getValueExpr(), InitializerExprContext::fromClassReflection($constantReflection->getDeclaringClass()));
1891: }
1892:
1893: $constantType = $this->constantResolver->resolveConstantType(
1894: sprintf('%s::%s', $constantClassReflection->getName(), $constantName),
1895: $constantType,
1896: );
1897: $types[] = $constantType;
1898: }
1899:
1900: if (count($types) > 0) {
1901: return TypeCombinator::union(...$types);
1902: }
1903:
1904: if (!$constantClassType->hasConstant($constantName)->yes()) {
1905: return new ErrorType();
1906: }
1907:
1908: return $constantClassType->getConstant($constantName)->getValueType();
1909: }
1910:
1911: /**
1912: * @param callable(Expr): Type $getTypeCallback
1913: */
1914: public function getClassConstFetchType(Name|Expr $class, string $constantName, ?string $className, callable $getTypeCallback): Type
1915: {
1916: $classReflection = null;
1917: if ($className !== null && $this->getReflectionProvider()->hasClass($className)) {
1918: $classReflection = $this->getReflectionProvider()->getClass($className);
1919: }
1920:
1921: return $this->getClassConstFetchTypeByReflection($class, $constantName, $classReflection, $getTypeCallback);
1922: }
1923:
1924: /**
1925: * @param callable(Expr): Type $getTypeCallback
1926: */
1927: public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type
1928: {
1929: $type = $getTypeCallback($expr)->toNumber();
1930: $scalarValues = $type->getConstantScalarValues();
1931:
1932: if (count($scalarValues) > 0) {
1933: $newTypes = [];
1934: foreach ($scalarValues as $scalarValue) {
1935: if (is_int($scalarValue)) {
1936: /** @var int|float $newValue */
1937: $newValue = -$scalarValue;
1938: if (!is_int($newValue)) {
1939: return $type;
1940: }
1941: $newTypes[] = new ConstantIntegerType($newValue);
1942: } elseif (is_float($scalarValue)) {
1943: $newTypes[] = new ConstantFloatType(-$scalarValue);
1944: }
1945: }
1946:
1947: return TypeCombinator::union(...$newTypes);
1948: }
1949:
1950: if ($type instanceof IntegerRangeType) {
1951: return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new LNumber(-1)));
1952: }
1953:
1954: return $type;
1955: }
1956:
1957: /**
1958: * @param callable(Expr): Type $getTypeCallback
1959: */
1960: public function getBitwiseNotType(Expr $expr, callable $getTypeCallback): Type
1961: {
1962: $exprType = $getTypeCallback($expr);
1963: return TypeTraverser::map($exprType, static function (Type $type, callable $traverse): Type {
1964: if ($type instanceof UnionType || $type instanceof IntersectionType) {
1965: return $traverse($type);
1966: }
1967: if ($type instanceof ConstantStringType) {
1968: return new ConstantStringType(~$type->getValue());
1969: }
1970: if ($type->isString()->yes()) {
1971: $accessories = [
1972: new StringType(),
1973: ];
1974: if ($type->isNonEmptyString()->yes()) {
1975: $accessories[] = new AccessoryNonEmptyStringType();
1976: }
1977: // it is not useful to apply numeric and literal strings here.
1978: // numeric string isn't certainly kept numeric: 3v4l.org/JERDB
1979:
1980: return TypeCombinator::intersect(...$accessories);
1981: }
1982: if ($type->isInteger()->yes() || $type->isFloat()->yes()) {
1983: return new IntegerType(); //no const types here, result depends on PHP_INT_SIZE
1984: }
1985: return new ErrorType();
1986: });
1987: }
1988:
1989: private function resolveName(Name $name, ?ClassReflection $classReflection): string
1990: {
1991: $originalClass = (string) $name;
1992: if ($classReflection !== null) {
1993: if (in_array(strtolower($originalClass), [
1994: 'self',
1995: 'static',
1996: ], true)) {
1997: return $classReflection->getName();
1998: } elseif ($originalClass === 'parent') {
1999: if ($classReflection->getParentClass() !== null) {
2000: return $classReflection->getParentClass()->getName();
2001: }
2002: }
2003: }
2004:
2005: return $originalClass;
2006: }
2007:
2008: private function resolveTypeByName(Name $name, ?ClassReflection $classReflection): TypeWithClassName
2009: {
2010: if ($name->toLowerString() === 'static' && $classReflection !== null) {
2011: return new StaticType($classReflection);
2012: }
2013:
2014: $originalClass = $this->resolveName($name, $classReflection);
2015: if ($classReflection !== null) {
2016: $thisType = new ThisType($classReflection);
2017: $ancestor = $thisType->getAncestorWithClassName($originalClass);
2018: if ($ancestor !== null) {
2019: return $ancestor;
2020: }
2021: }
2022:
2023: return new ObjectType($originalClass);
2024: }
2025:
2026: /**
2027: * @param mixed $value
2028: */
2029: private function getTypeFromValue($value): Type
2030: {
2031: return ConstantTypeHelper::getTypeFromValue($value);
2032: }
2033:
2034: private function getReflectionProvider(): ReflectionProvider
2035: {
2036: return $this->reflectionProviderProvider->getReflectionProvider();
2037: }
2038:
2039: private function getNeverType(Type $leftType, Type $rightType): Type
2040: {
2041: // make sure we don't lose the explicit flag in the process
2042: if ($leftType instanceof NeverType && $leftType->isExplicit()) {
2043: return $leftType;
2044: }
2045: if ($rightType instanceof NeverType && $rightType->isExplicit()) {
2046: return $rightType;
2047: }
2048: return new NeverType();
2049: }
2050:
2051: }
2052: