1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Analyser;
4:
5: use ArrayAccess;
6: use Closure;
7: use Generator;
8: use PhpParser\Node;
9: use PhpParser\Node\Arg;
10: use PhpParser\Node\Expr;
11: use PhpParser\Node\Expr\Array_;
12: use PhpParser\Node\Expr\BinaryOp;
13: use PhpParser\Node\Expr\Cast\Bool_;
14: use PhpParser\Node\Expr\Cast\Double;
15: use PhpParser\Node\Expr\Cast\Int_;
16: use PhpParser\Node\Expr\Cast\Object_;
17: use PhpParser\Node\Expr\Cast\Unset_;
18: use PhpParser\Node\Expr\ConstFetch;
19: use PhpParser\Node\Expr\FuncCall;
20: use PhpParser\Node\Expr\MethodCall;
21: use PhpParser\Node\Expr\New_;
22: use PhpParser\Node\Expr\PropertyFetch;
23: use PhpParser\Node\Expr\Variable;
24: use PhpParser\Node\InterpolatedStringPart;
25: use PhpParser\Node\Name;
26: use PhpParser\Node\Name\FullyQualified;
27: use PhpParser\Node\Scalar\String_;
28: use PhpParser\NodeFinder;
29: use PHPStan\Node\ExecutionEndNode;
30: use PHPStan\Node\Expr\AlwaysRememberedExpr;
31: use PHPStan\Node\Expr\ExistingArrayDimFetch;
32: use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
33: use PHPStan\Node\Expr\GetIterableValueTypeExpr;
34: use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
35: use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
36: use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
37: use PHPStan\Node\Expr\PropertyInitializationExpr;
38: use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr;
39: use PHPStan\Node\Expr\SetOffsetValueTypeExpr;
40: use PHPStan\Node\Expr\TypeExpr;
41: use PHPStan\Node\Expr\UnsetOffsetExpr;
42: use PHPStan\Node\InvalidateExprNode;
43: use PHPStan\Node\IssetExpr;
44: use PHPStan\Node\Printer\ExprPrinter;
45: use PHPStan\Node\PropertyAssignNode;
46: use PHPStan\Parser\ArrayMapArgVisitor;
47: use PHPStan\Parser\NewAssignedToPropertyVisitor;
48: use PHPStan\Parser\Parser;
49: use PHPStan\Php\PhpVersion;
50: use PHPStan\PhpDoc\Tag\TemplateTag;
51: use PHPStan\Reflection\Assertions;
52: use PHPStan\Reflection\Callables\CallableParametersAcceptor;
53: use PHPStan\Reflection\Callables\SimpleImpurePoint;
54: use PHPStan\Reflection\Callables\SimpleThrowPoint;
55: use PHPStan\Reflection\ClassConstantReflection;
56: use PHPStan\Reflection\ClassMemberReflection;
57: use PHPStan\Reflection\ClassReflection;
58: use PHPStan\Reflection\Dummy\DummyConstructorReflection;
59: use PHPStan\Reflection\ExtendedMethodReflection;
60: use PHPStan\Reflection\ExtendedParametersAcceptor;
61: use PHPStan\Reflection\ExtendedPropertyReflection;
62: use PHPStan\Reflection\FunctionReflection;
63: use PHPStan\Reflection\InitializerExprContext;
64: use PHPStan\Reflection\InitializerExprTypeResolver;
65: use PHPStan\Reflection\MethodReflection;
66: use PHPStan\Reflection\Native\NativeParameterReflection;
67: use PHPStan\Reflection\ParameterReflection;
68: use PHPStan\Reflection\ParametersAcceptor;
69: use PHPStan\Reflection\ParametersAcceptorSelector;
70: use PHPStan\Reflection\PassedByReference;
71: use PHPStan\Reflection\Php\DummyParameter;
72: use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection;
73: use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
74: use PHPStan\Reflection\PropertyReflection;
75: use PHPStan\Reflection\ReflectionProvider;
76: use PHPStan\Reflection\TrivialParametersAcceptor;
77: use PHPStan\Rules\Properties\PropertyReflectionFinder;
78: use PHPStan\ShouldNotHappenException;
79: use PHPStan\TrinaryLogic;
80: use PHPStan\Type\Accessory\AccessoryArrayListType;
81: use PHPStan\Type\Accessory\AccessoryLiteralStringType;
82: use PHPStan\Type\Accessory\HasOffsetValueType;
83: use PHPStan\Type\Accessory\NonEmptyArrayType;
84: use PHPStan\Type\Accessory\OversizedArrayType;
85: use PHPStan\Type\ArrayType;
86: use PHPStan\Type\BenevolentUnionType;
87: use PHPStan\Type\BooleanType;
88: use PHPStan\Type\ClosureType;
89: use PHPStan\Type\ConditionalTypeForParameter;
90: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
91: use PHPStan\Type\Constant\ConstantBooleanType;
92: use PHPStan\Type\Constant\ConstantFloatType;
93: use PHPStan\Type\Constant\ConstantIntegerType;
94: use PHPStan\Type\Constant\ConstantStringType;
95: use PHPStan\Type\ConstantTypeHelper;
96: use PHPStan\Type\DynamicReturnTypeExtensionRegistry;
97: use PHPStan\Type\ErrorType;
98: use PHPStan\Type\ExpressionTypeResolverExtensionRegistry;
99: use PHPStan\Type\FloatType;
100: use PHPStan\Type\GeneralizePrecision;
101: use PHPStan\Type\Generic\GenericClassStringType;
102: use PHPStan\Type\Generic\GenericObjectType;
103: use PHPStan\Type\Generic\TemplateType;
104: use PHPStan\Type\Generic\TemplateTypeHelper;
105: use PHPStan\Type\Generic\TemplateTypeMap;
106: use PHPStan\Type\Generic\TemplateTypeVarianceMap;
107: use PHPStan\Type\IntegerRangeType;
108: use PHPStan\Type\IntegerType;
109: use PHPStan\Type\IntersectionType;
110: use PHPStan\Type\MixedType;
111: use PHPStan\Type\NeverType;
112: use PHPStan\Type\NonAcceptingNeverType;
113: use PHPStan\Type\NonexistentParentClassType;
114: use PHPStan\Type\NullType;
115: use PHPStan\Type\ObjectShapeType;
116: use PHPStan\Type\ObjectType;
117: use PHPStan\Type\ParserNodeTypeToPHPStanType;
118: use PHPStan\Type\StaticType;
119: use PHPStan\Type\StringType;
120: use PHPStan\Type\ThisType;
121: use PHPStan\Type\Type;
122: use PHPStan\Type\TypeCombinator;
123: use PHPStan\Type\TypeTraverser;
124: use PHPStan\Type\TypeUtils;
125: use PHPStan\Type\TypeWithClassName;
126: use PHPStan\Type\UnionType;
127: use PHPStan\Type\VerbosityLevel;
128: use PHPStan\Type\VoidType;
129: use stdClass;
130: use Throwable;
131: use function abs;
132: use function array_filter;
133: use function array_key_exists;
134: use function array_key_first;
135: use function array_keys;
136: use function array_map;
137: use function array_merge;
138: use function array_pop;
139: use function array_slice;
140: use function array_values;
141: use function count;
142: use function explode;
143: use function get_class;
144: use function implode;
145: use function in_array;
146: use function is_bool;
147: use function is_numeric;
148: use function is_string;
149: use function ltrim;
150: use function md5;
151: use function sprintf;
152: use function str_starts_with;
153: use function strlen;
154: use function strtolower;
155: use function substr;
156: use function usort;
157: use const PHP_INT_MAX;
158: use const PHP_INT_MIN;
159:
160: final class MutatingScope implements Scope
161: {
162:
163: private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4;
164:
165: private const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid';
166:
167: /** @var Type[] */
168: private array $resolvedTypes = [];
169:
170: /** @var array<string, self> */
171: private array $truthyScopes = [];
172:
173: /** @var array<string, self> */
174: private array $falseyScopes = [];
175:
176: /** @var non-empty-string|null */
177: private ?string $namespace;
178:
179: private ?self $scopeOutOfFirstLevelStatement = null;
180:
181: private ?self $scopeWithPromotedNativeTypes = null;
182:
183: private static int $resolveClosureTypeDepth = 0;
184:
185: /**
186: * @param array<string, ExpressionTypeHolder> $expressionTypes
187: * @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
188: * @param list<string> $inClosureBindScopeClasses
189: * @param array<string, true> $currentlyAssignedExpressions
190: * @param array<string, true> $currentlyAllowedUndefinedExpressions
191: * @param array<string, ExpressionTypeHolder> $nativeExpressionTypes
192: * @param list<array{MethodReflection|FunctionReflection|null, ParameterReflection|null}> $inFunctionCallsStack
193: */
194: public function __construct(
195: private InternalScopeFactory $scopeFactory,
196: private ReflectionProvider $reflectionProvider,
197: private InitializerExprTypeResolver $initializerExprTypeResolver,
198: private DynamicReturnTypeExtensionRegistry $dynamicReturnTypeExtensionRegistry,
199: private ExpressionTypeResolverExtensionRegistry $expressionTypeResolverExtensionRegistry,
200: private ExprPrinter $exprPrinter,
201: private TypeSpecifier $typeSpecifier,
202: private PropertyReflectionFinder $propertyReflectionFinder,
203: private Parser $parser,
204: private NodeScopeResolver $nodeScopeResolver,
205: private ConstantResolver $constantResolver,
206: private ScopeContext $context,
207: private PhpVersion $phpVersion,
208: private bool $declareStrictTypes = false,
209: private PhpFunctionFromParserNodeReflection|null $function = null,
210: ?string $namespace = null,
211: private array $expressionTypes = [],
212: private array $nativeExpressionTypes = [],
213: private array $conditionalExpressions = [],
214: private array $inClosureBindScopeClasses = [],
215: private ?ParametersAcceptor $anonymousFunctionReflection = null,
216: private bool $inFirstLevelStatement = true,
217: private array $currentlyAssignedExpressions = [],
218: private array $currentlyAllowedUndefinedExpressions = [],
219: private array $inFunctionCallsStack = [],
220: private bool $afterExtractCall = false,
221: private ?Scope $parentScope = null,
222: private bool $nativeTypesPromoted = false,
223: )
224: {
225: if ($namespace === '') {
226: $namespace = null;
227: }
228:
229: $this->namespace = $namespace;
230: }
231:
232: /** @api */
233: public function getFile(): string
234: {
235: return $this->context->getFile();
236: }
237:
238: /** @api */
239: public function getFileDescription(): string
240: {
241: if ($this->context->getTraitReflection() === null) {
242: return $this->getFile();
243: }
244:
245: /** @var ClassReflection $classReflection */
246: $classReflection = $this->context->getClassReflection();
247:
248: $className = $classReflection->getDisplayName();
249: if (!$classReflection->isAnonymous()) {
250: $className = sprintf('class %s', $className);
251: }
252:
253: $traitReflection = $this->context->getTraitReflection();
254: if ($traitReflection->getFileName() === null) {
255: throw new ShouldNotHappenException();
256: }
257:
258: return sprintf(
259: '%s (in context of %s)',
260: $traitReflection->getFileName(),
261: $className,
262: );
263: }
264:
265: /** @api */
266: public function isDeclareStrictTypes(): bool
267: {
268: return $this->declareStrictTypes;
269: }
270:
271: public function enterDeclareStrictTypes(): self
272: {
273: return $this->scopeFactory->create(
274: $this->context,
275: true,
276: null,
277: null,
278: $this->expressionTypes,
279: $this->nativeExpressionTypes,
280: );
281: }
282:
283: /** @api */
284: public function isInClass(): bool
285: {
286: return $this->context->getClassReflection() !== null;
287: }
288:
289: /** @api */
290: public function isInTrait(): bool
291: {
292: return $this->context->getTraitReflection() !== null;
293: }
294:
295: /** @api */
296: public function getClassReflection(): ?ClassReflection
297: {
298: return $this->context->getClassReflection();
299: }
300:
301: /** @api */
302: public function getTraitReflection(): ?ClassReflection
303: {
304: return $this->context->getTraitReflection();
305: }
306:
307: /**
308: * @api
309: */
310: public function getFunction(): ?PhpFunctionFromParserNodeReflection
311: {
312: return $this->function;
313: }
314:
315: /** @api */
316: public function getFunctionName(): ?string
317: {
318: return $this->function !== null ? $this->function->getName() : null;
319: }
320:
321: /** @api */
322: public function getNamespace(): ?string
323: {
324: return $this->namespace;
325: }
326:
327: /** @api */
328: public function getParentScope(): ?Scope
329: {
330: return $this->parentScope;
331: }
332:
333: /** @api */
334: public function canAnyVariableExist(): bool
335: {
336: return ($this->function === null && !$this->isInAnonymousFunction()) || $this->afterExtractCall;
337: }
338:
339: public function afterExtractCall(): self
340: {
341: return $this->scopeFactory->create(
342: $this->context,
343: $this->isDeclareStrictTypes(),
344: $this->getFunction(),
345: $this->getNamespace(),
346: $this->expressionTypes,
347: $this->nativeExpressionTypes,
348: [],
349: $this->inClosureBindScopeClasses,
350: $this->anonymousFunctionReflection,
351: $this->isInFirstLevelStatement(),
352: $this->currentlyAssignedExpressions,
353: $this->currentlyAllowedUndefinedExpressions,
354: $this->inFunctionCallsStack,
355: true,
356: $this->parentScope,
357: $this->nativeTypesPromoted,
358: );
359: }
360:
361: public function afterClearstatcacheCall(): self
362: {
363: $expressionTypes = $this->expressionTypes;
364: foreach (array_keys($expressionTypes) as $exprString) {
365: // list from https://www.php.net/manual/en/function.clearstatcache.php
366:
367: // stat(), lstat(), file_exists(), is_writable(), is_readable(), is_executable(), is_file(), is_dir(), is_link(), filectime(), fileatime(), filemtime(), fileinode(), filegroup(), fileowner(), filesize(), filetype(), and fileperms().
368: foreach ([
369: 'stat',
370: 'lstat',
371: 'file_exists',
372: 'is_writable',
373: 'is_writeable',
374: 'is_readable',
375: 'is_executable',
376: 'is_file',
377: 'is_dir',
378: 'is_link',
379: 'filectime',
380: 'fileatime',
381: 'filemtime',
382: 'fileinode',
383: 'filegroup',
384: 'fileowner',
385: 'filesize',
386: 'filetype',
387: 'fileperms',
388: ] as $functionName) {
389: if (!str_starts_with($exprString, $functionName . '(') && !str_starts_with($exprString, '\\' . $functionName . '(')) {
390: continue;
391: }
392:
393: unset($expressionTypes[$exprString]);
394: continue 2;
395: }
396: }
397: return $this->scopeFactory->create(
398: $this->context,
399: $this->isDeclareStrictTypes(),
400: $this->getFunction(),
401: $this->getNamespace(),
402: $expressionTypes,
403: $this->nativeExpressionTypes,
404: $this->conditionalExpressions,
405: $this->inClosureBindScopeClasses,
406: $this->anonymousFunctionReflection,
407: $this->isInFirstLevelStatement(),
408: $this->currentlyAssignedExpressions,
409: $this->currentlyAllowedUndefinedExpressions,
410: $this->inFunctionCallsStack,
411: $this->afterExtractCall,
412: $this->parentScope,
413: $this->nativeTypesPromoted,
414: );
415: }
416:
417: public function afterOpenSslCall(string $openSslFunctionName): self
418: {
419: $expressionTypes = $this->expressionTypes;
420:
421: if (in_array($openSslFunctionName, [
422: 'openssl_cipher_iv_length',
423: 'openssl_cms_decrypt',
424: 'openssl_cms_encrypt',
425: 'openssl_cms_read',
426: 'openssl_cms_sign',
427: 'openssl_cms_verify',
428: 'openssl_csr_export_to_file',
429: 'openssl_csr_export',
430: 'openssl_csr_get_public_key',
431: 'openssl_csr_get_subject',
432: 'openssl_csr_new',
433: 'openssl_csr_sign',
434: 'openssl_decrypt',
435: 'openssl_dh_compute_key',
436: 'openssl_digest',
437: 'openssl_encrypt',
438: 'openssl_get_curve_names',
439: 'openssl_get_privatekey',
440: 'openssl_get_publickey',
441: 'openssl_open',
442: 'openssl_pbkdf2',
443: 'openssl_pkcs12_export_to_file',
444: 'openssl_pkcs12_export',
445: 'openssl_pkcs12_read',
446: 'openssl_pkcs7_decrypt',
447: 'openssl_pkcs7_encrypt',
448: 'openssl_pkcs7_read',
449: 'openssl_pkcs7_sign',
450: 'openssl_pkcs7_verify',
451: 'openssl_pkey_derive',
452: 'openssl_pkey_export_to_file',
453: 'openssl_pkey_export',
454: 'openssl_pkey_get_private',
455: 'openssl_pkey_get_public',
456: 'openssl_pkey_new',
457: 'openssl_private_decrypt',
458: 'openssl_private_encrypt',
459: 'openssl_public_decrypt',
460: 'openssl_public_encrypt',
461: 'openssl_random_pseudo_bytes',
462: 'openssl_seal',
463: 'openssl_sign',
464: 'openssl_spki_export_challenge',
465: 'openssl_spki_export',
466: 'openssl_spki_new',
467: 'openssl_spki_verify',
468: 'openssl_verify',
469: 'openssl_x509_checkpurpose',
470: 'openssl_x509_export_to_file',
471: 'openssl_x509_export',
472: 'openssl_x509_fingerprint',
473: 'openssl_x509_read',
474: 'openssl_x509_verify',
475: ], true)) {
476: unset($expressionTypes['\openssl_error_string()']);
477: }
478:
479: return $this->scopeFactory->create(
480: $this->context,
481: $this->isDeclareStrictTypes(),
482: $this->getFunction(),
483: $this->getNamespace(),
484: $expressionTypes,
485: $this->nativeExpressionTypes,
486: $this->conditionalExpressions,
487: $this->inClosureBindScopeClasses,
488: $this->anonymousFunctionReflection,
489: $this->isInFirstLevelStatement(),
490: $this->currentlyAssignedExpressions,
491: $this->currentlyAllowedUndefinedExpressions,
492: $this->inFunctionCallsStack,
493: $this->afterExtractCall,
494: $this->parentScope,
495: $this->nativeTypesPromoted,
496: );
497: }
498:
499: /** @api */
500: public function hasVariableType(string $variableName): TrinaryLogic
501: {
502: if ($this->isGlobalVariable($variableName)) {
503: return TrinaryLogic::createYes();
504: }
505:
506: $varExprString = '$' . $variableName;
507: if (!isset($this->expressionTypes[$varExprString])) {
508: if ($this->canAnyVariableExist()) {
509: return TrinaryLogic::createMaybe();
510: }
511:
512: return TrinaryLogic::createNo();
513: }
514:
515: return $this->expressionTypes[$varExprString]->getCertainty();
516: }
517:
518: /** @api */
519: public function getVariableType(string $variableName): Type
520: {
521: if ($this->hasVariableType($variableName)->maybe()) {
522: if ($variableName === 'argc') {
523: return IntegerRangeType::fromInterval(1, null);
524: }
525: if ($variableName === 'argv') {
526: return TypeCombinator::intersect(
527: new ArrayType(new IntegerType(), new StringType()),
528: new NonEmptyArrayType(),
529: new AccessoryArrayListType(),
530: );
531: }
532: if ($this->canAnyVariableExist()) {
533: return new MixedType();
534: }
535: }
536:
537: if ($this->isGlobalVariable($variableName)) {
538: return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
539: }
540:
541: if ($this->hasVariableType($variableName)->no()) {
542: throw new UndefinedVariableException($this, $variableName);
543: }
544:
545: $varExprString = '$' . $variableName;
546: if (!array_key_exists($varExprString, $this->expressionTypes)) {
547: return new MixedType();
548: }
549:
550: return TypeUtils::resolveLateResolvableTypes($this->expressionTypes[$varExprString]->getType());
551: }
552:
553: /**
554: * @api
555: * @return list<string>
556: */
557: public function getDefinedVariables(): array
558: {
559: $variables = [];
560: foreach ($this->expressionTypes as $exprString => $holder) {
561: if (!$holder->getExpr() instanceof Variable) {
562: continue;
563: }
564: if (!$holder->getCertainty()->yes()) {
565: continue;
566: }
567:
568: $variables[] = substr($exprString, 1);
569: }
570:
571: return $variables;
572: }
573:
574: /**
575: * @api
576: * @return list<string>
577: */
578: public function getMaybeDefinedVariables(): array
579: {
580: $variables = [];
581: foreach ($this->expressionTypes as $exprString => $holder) {
582: if (!$holder->getExpr() instanceof Variable) {
583: continue;
584: }
585: if (!$holder->getCertainty()->maybe()) {
586: continue;
587: }
588:
589: $variables[] = substr($exprString, 1);
590: }
591:
592: return $variables;
593: }
594:
595: private function isGlobalVariable(string $variableName): bool
596: {
597: return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true);
598: }
599:
600: /** @api */
601: public function hasConstant(Name $name): bool
602: {
603: $isCompilerHaltOffset = $name->toString() === '__COMPILER_HALT_OFFSET__';
604: if ($isCompilerHaltOffset) {
605: return $this->fileHasCompilerHaltStatementCalls();
606: }
607:
608: if (!$name->isFullyQualified() && $this->getNamespace() !== null) {
609: if ($this->hasExpressionType(new ConstFetch(new FullyQualified([$this->getNamespace(), $name->toString()])))->yes()) {
610: return true;
611: }
612: }
613: if ($this->hasExpressionType(new ConstFetch(new FullyQualified($name->toString())))->yes()) {
614: return true;
615: }
616:
617: return $this->reflectionProvider->hasConstant($name, $this);
618: }
619:
620: private function fileHasCompilerHaltStatementCalls(): bool
621: {
622: $nodes = $this->parser->parseFile($this->getFile());
623: foreach ($nodes as $node) {
624: if ($node instanceof Node\Stmt\HaltCompiler) {
625: return true;
626: }
627: }
628:
629: return false;
630: }
631:
632: /** @api */
633: public function isInAnonymousFunction(): bool
634: {
635: return $this->anonymousFunctionReflection !== null;
636: }
637:
638: /** @api */
639: public function getAnonymousFunctionReflection(): ?ParametersAcceptor
640: {
641: return $this->anonymousFunctionReflection;
642: }
643:
644: /** @api */
645: public function getAnonymousFunctionReturnType(): ?Type
646: {
647: if ($this->anonymousFunctionReflection === null) {
648: return null;
649: }
650:
651: return $this->anonymousFunctionReflection->getReturnType();
652: }
653:
654: /** @api */
655: public function getType(Expr $node): Type
656: {
657: if ($node instanceof GetIterableKeyTypeExpr) {
658: return $this->getIterableKeyType($this->getType($node->getExpr()));
659: }
660: if ($node instanceof GetIterableValueTypeExpr) {
661: return $this->getIterableValueType($this->getType($node->getExpr()));
662: }
663: if ($node instanceof GetOffsetValueTypeExpr) {
664: return $this->getType($node->getVar())->getOffsetValueType($this->getType($node->getDim()));
665: }
666: if ($node instanceof ExistingArrayDimFetch) {
667: return $this->getType(new Expr\ArrayDimFetch($node->getVar(), $node->getDim()));
668: }
669: if ($node instanceof UnsetOffsetExpr) {
670: return $this->getType($node->getVar())->unsetOffset($this->getType($node->getDim()));
671: }
672: if ($node instanceof SetOffsetValueTypeExpr) {
673: return $this->getType($node->getVar())->setOffsetValueType(
674: $node->getDim() !== null ? $this->getType($node->getDim()) : null,
675: $this->getType($node->getValue()),
676: );
677: }
678: if ($node instanceof SetExistingOffsetValueTypeExpr) {
679: return $this->getType($node->getVar())->setExistingOffsetValueType(
680: $this->getType($node->getDim()),
681: $this->getType($node->getValue()),
682: );
683: }
684: if ($node instanceof TypeExpr) {
685: return $node->getExprType();
686: }
687:
688: if ($node instanceof OriginalPropertyTypeExpr) {
689: $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node->getPropertyFetch(), $this);
690: if ($propertyReflection === null) {
691: return new ErrorType();
692: }
693:
694: return $propertyReflection->getReadableType();
695: }
696:
697: $key = $this->getNodeKey($node);
698:
699: if (!array_key_exists($key, $this->resolvedTypes)) {
700: $this->resolvedTypes[$key] = TypeUtils::resolveLateResolvableTypes($this->resolveType($key, $node));
701: }
702: return $this->resolvedTypes[$key];
703: }
704:
705: private function getNodeKey(Expr $node): string
706: {
707: $key = $this->exprPrinter->printExpr($node);
708:
709: if (
710: $node instanceof Node\FunctionLike
711: && $node->hasAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME)
712: && $node->hasAttribute('startFilePos')
713: ) {
714: $key .= '/*' . $node->getAttribute('startFilePos') . '*/';
715: }
716:
717: if ($node->getAttribute(self::KEEP_VOID_ATTRIBUTE_NAME) === true) {
718: $key .= '/*' . self::KEEP_VOID_ATTRIBUTE_NAME . '*/';
719: }
720:
721: return $key;
722: }
723:
724: private function getClosureScopeCacheKey(): string
725: {
726: $parts = [];
727: foreach ($this->expressionTypes as $exprString => $expressionTypeHolder) {
728: $parts[] = sprintf('%s::%s', $exprString, $expressionTypeHolder->getType()->describe(VerbosityLevel::cache()));
729: }
730: $parts[] = '---';
731: foreach ($this->nativeExpressionTypes as $exprString => $expressionTypeHolder) {
732: $parts[] = sprintf('%s::%s', $exprString, $expressionTypeHolder->getType()->describe(VerbosityLevel::cache()));
733: }
734:
735: $parts[] = sprintf(':%d', count($this->inFunctionCallsStack));
736: foreach ($this->inFunctionCallsStack as [$method, $parameter]) {
737: if ($parameter === null) {
738: $parts[] = ',null';
739: continue;
740: }
741:
742: $parts[] = sprintf(',%s', $parameter->getType()->describe(VerbosityLevel::cache()));
743: }
744:
745: return md5(implode("\n", $parts));
746: }
747:
748: private function resolveType(string $exprString, Expr $node): Type
749: {
750: foreach ($this->expressionTypeResolverExtensionRegistry->getExtensions() as $extension) {
751: $type = $extension->getType($node, $this);
752: if ($type !== null) {
753: return $type;
754: }
755: }
756:
757: if ($node instanceof Expr\Exit_ || $node instanceof Expr\Throw_) {
758: return new NonAcceptingNeverType();
759: }
760:
761: if (!$node instanceof Variable && $this->hasExpressionType($node)->yes()) {
762: return $this->expressionTypes[$exprString]->getType();
763: }
764:
765: if ($node instanceof AlwaysRememberedExpr) {
766: return $node->getExprType();
767: }
768:
769: if ($node instanceof Expr\BinaryOp\Smaller) {
770: return $this->getType($node->left)->isSmallerThan($this->getType($node->right), $this->phpVersion)->toBooleanType();
771: }
772:
773: if ($node instanceof Expr\BinaryOp\SmallerOrEqual) {
774: return $this->getType($node->left)->isSmallerThanOrEqual($this->getType($node->right), $this->phpVersion)->toBooleanType();
775: }
776:
777: if ($node instanceof Expr\BinaryOp\Greater) {
778: return $this->getType($node->right)->isSmallerThan($this->getType($node->left), $this->phpVersion)->toBooleanType();
779: }
780:
781: if ($node instanceof Expr\BinaryOp\GreaterOrEqual) {
782: return $this->getType($node->right)->isSmallerThanOrEqual($this->getType($node->left), $this->phpVersion)->toBooleanType();
783: }
784:
785: if ($node instanceof Expr\BinaryOp\Equal) {
786: if (
787: $node->left instanceof Variable
788: && is_string($node->left->name)
789: && $node->right instanceof Variable
790: && is_string($node->right->name)
791: && $node->left->name === $node->right->name
792: ) {
793: return new ConstantBooleanType(true);
794: }
795:
796: $leftType = $this->getType($node->left);
797: $rightType = $this->getType($node->right);
798:
799: return $this->initializerExprTypeResolver->resolveEqualType($leftType, $rightType);
800: }
801:
802: if ($node instanceof Expr\BinaryOp\NotEqual) {
803: return $this->getType(new Expr\BooleanNot(new BinaryOp\Equal($node->left, $node->right)));
804: }
805:
806: if ($node instanceof Expr\Empty_) {
807: $result = $this->issetCheck($node->expr, static function (Type $type): ?bool {
808: $isNull = $type->isNull();
809: $isFalsey = $type->toBoolean()->isFalse();
810: if ($isNull->maybe()) {
811: return null;
812: }
813: if ($isFalsey->maybe()) {
814: return null;
815: }
816:
817: if ($isNull->yes()) {
818: return $isFalsey->no();
819: }
820:
821: return !$isFalsey->yes();
822: });
823: if ($result === null) {
824: return new BooleanType();
825: }
826:
827: return new ConstantBooleanType(!$result);
828: }
829:
830: if ($node instanceof Node\Expr\BooleanNot) {
831: $exprBooleanType = $this->getType($node->expr)->toBoolean();
832: if ($exprBooleanType instanceof ConstantBooleanType) {
833: return new ConstantBooleanType(!$exprBooleanType->getValue());
834: }
835:
836: return new BooleanType();
837: }
838:
839: if ($node instanceof Node\Expr\BitwiseNot) {
840: return $this->initializerExprTypeResolver->getBitwiseNotType($node->expr, fn (Expr $expr): Type => $this->getType($expr));
841: }
842:
843: if (
844: $node instanceof Node\Expr\BinaryOp\BooleanAnd
845: || $node instanceof Node\Expr\BinaryOp\LogicalAnd
846: ) {
847: $leftBooleanType = $this->getType($node->left)->toBoolean();
848: if ($leftBooleanType->isFalse()->yes()) {
849: return new ConstantBooleanType(false);
850: }
851:
852: if ($this->getBooleanExpressionDepth($node->left) <= self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH) {
853: $noopCallback = static function (): void {
854: };
855: $leftResult = $this->nodeScopeResolver->processExprNode(new Node\Stmt\Expression($node->left), $node->left, $this, $noopCallback, ExpressionContext::createDeep());
856: $rightBooleanType = $leftResult->getTruthyScope()->getType($node->right)->toBoolean();
857: } else {
858: $rightBooleanType = $this->filterByTruthyValue($node->left)->getType($node->right)->toBoolean();
859: }
860:
861: if ($rightBooleanType->isFalse()->yes()) {
862: return new ConstantBooleanType(false);
863: }
864:
865: if (
866: $leftBooleanType->isTrue()->yes()
867: && $rightBooleanType->isTrue()->yes()
868: ) {
869: return new ConstantBooleanType(true);
870: }
871:
872: return new BooleanType();
873: }
874:
875: if (
876: $node instanceof Node\Expr\BinaryOp\BooleanOr
877: || $node instanceof Node\Expr\BinaryOp\LogicalOr
878: ) {
879: $leftBooleanType = $this->getType($node->left)->toBoolean();
880: if ($leftBooleanType->isTrue()->yes()) {
881: return new ConstantBooleanType(true);
882: }
883:
884: if ($this->getBooleanExpressionDepth($node->left) <= self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH) {
885: $noopCallback = static function (): void {
886: };
887: $leftResult = $this->nodeScopeResolver->processExprNode(new Node\Stmt\Expression($node->left), $node->left, $this, $noopCallback, ExpressionContext::createDeep());
888: $rightBooleanType = $leftResult->getFalseyScope()->getType($node->right)->toBoolean();
889: } else {
890: $rightBooleanType = $this->filterByFalseyValue($node->left)->getType($node->right)->toBoolean();
891: }
892:
893: if ($rightBooleanType->isTrue()->yes()) {
894: return new ConstantBooleanType(true);
895: }
896:
897: if (
898: $leftBooleanType->isFalse()->yes()
899: && $rightBooleanType->isFalse()->yes()
900: ) {
901: return new ConstantBooleanType(false);
902: }
903:
904: return new BooleanType();
905: }
906:
907: if ($node instanceof Node\Expr\BinaryOp\LogicalXor) {
908: $leftBooleanType = $this->getType($node->left)->toBoolean();
909: $rightBooleanType = $this->getType($node->right)->toBoolean();
910:
911: if (
912: $leftBooleanType instanceof ConstantBooleanType
913: && $rightBooleanType instanceof ConstantBooleanType
914: ) {
915: return new ConstantBooleanType(
916: $leftBooleanType->getValue() xor $rightBooleanType->getValue(),
917: );
918: }
919:
920: return new BooleanType();
921: }
922:
923: if ($node instanceof Expr\BinaryOp\Identical) {
924: if (
925: $node->left instanceof Variable
926: && is_string($node->left->name)
927: && $node->right instanceof Variable
928: && is_string($node->right->name)
929: && $node->left->name === $node->right->name
930: ) {
931: return new ConstantBooleanType(true);
932: }
933:
934: $leftType = $this->getType($node->left);
935: $rightType = $this->getType($node->right);
936:
937: if (
938: (
939: $node->left instanceof Node\Expr\PropertyFetch
940: || $node->left instanceof Node\Expr\StaticPropertyFetch
941: )
942: && $rightType->isNull()->yes()
943: && !$this->hasPropertyNativeType($node->left)
944: ) {
945: return new BooleanType();
946: }
947:
948: if (
949: (
950: $node->right instanceof Node\Expr\PropertyFetch
951: || $node->right instanceof Node\Expr\StaticPropertyFetch
952: )
953: && $leftType->isNull()->yes()
954: && !$this->hasPropertyNativeType($node->right)
955: ) {
956: return new BooleanType();
957: }
958:
959: return $this->initializerExprTypeResolver->resolveIdenticalType($leftType, $rightType);
960: }
961:
962: if ($node instanceof Expr\BinaryOp\NotIdentical) {
963: return $this->getType(new Expr\BooleanNot(new BinaryOp\Identical($node->left, $node->right)));
964: }
965:
966: if ($node instanceof Expr\Instanceof_) {
967: $expressionType = $this->getType($node->expr);
968: if (
969: $this->isInTrait()
970: && TypeUtils::findThisType($expressionType) !== null
971: ) {
972: return new BooleanType();
973: }
974: if ($expressionType instanceof NeverType) {
975: return new ConstantBooleanType(false);
976: }
977:
978: $uncertainty = false;
979:
980: if ($node->class instanceof Node\Name) {
981: $unresolvedClassName = $node->class->toString();
982: if (
983: strtolower($unresolvedClassName) === 'static'
984: && $this->isInClass()
985: ) {
986: $classType = new StaticType($this->getClassReflection());
987: } else {
988: $className = $this->resolveName($node->class);
989: $classType = new ObjectType($className);
990: }
991: } else {
992: $classType = $this->getType($node->class);
993: $classType = TypeTraverser::map($classType, static function (Type $type, callable $traverse) use (&$uncertainty): Type {
994: if ($type instanceof UnionType || $type instanceof IntersectionType) {
995: return $traverse($type);
996: }
997: if ($type->getObjectClassNames() !== []) {
998: $uncertainty = true;
999: return $type;
1000: }
1001: if ($type instanceof GenericClassStringType) {
1002: $uncertainty = true;
1003: return $type->getGenericType();
1004: }
1005: if ($type instanceof ConstantStringType) {
1006: return new ObjectType($type->getValue());
1007: }
1008: return new MixedType();
1009: });
1010: }
1011:
1012: if ($classType->isSuperTypeOf(new MixedType())->yes()) {
1013: return new BooleanType();
1014: }
1015:
1016: $isSuperType = $classType->isSuperTypeOf($expressionType);
1017:
1018: if ($isSuperType->no()) {
1019: return new ConstantBooleanType(false);
1020: } elseif ($isSuperType->yes() && !$uncertainty) {
1021: return new ConstantBooleanType(true);
1022: }
1023:
1024: return new BooleanType();
1025: }
1026:
1027: if ($node instanceof Node\Expr\UnaryPlus) {
1028: return $this->getType($node->expr)->toNumber();
1029: }
1030:
1031: if ($node instanceof Expr\ErrorSuppress
1032: || $node instanceof Expr\Assign
1033: ) {
1034: return $this->getType($node->expr);
1035: }
1036:
1037: if ($node instanceof Node\Expr\UnaryMinus) {
1038: return $this->initializerExprTypeResolver->getUnaryMinusType($node->expr, fn (Expr $expr): Type => $this->getType($expr));
1039: }
1040:
1041: if ($node instanceof Expr\BinaryOp\Concat) {
1042: return $this->initializerExprTypeResolver->getConcatType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1043: }
1044:
1045: if ($node instanceof Expr\AssignOp\Concat) {
1046: return $this->initializerExprTypeResolver->getConcatType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1047: }
1048:
1049: if ($node instanceof BinaryOp\BitwiseAnd) {
1050: return $this->initializerExprTypeResolver->getBitwiseAndType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1051: }
1052:
1053: if ($node instanceof Expr\AssignOp\BitwiseAnd) {
1054: return $this->initializerExprTypeResolver->getBitwiseAndType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1055: }
1056:
1057: if ($node instanceof BinaryOp\BitwiseOr) {
1058: return $this->initializerExprTypeResolver->getBitwiseOrType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1059: }
1060:
1061: if ($node instanceof Expr\AssignOp\BitwiseOr) {
1062: return $this->initializerExprTypeResolver->getBitwiseOrType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1063: }
1064:
1065: if ($node instanceof BinaryOp\BitwiseXor) {
1066: return $this->initializerExprTypeResolver->getBitwiseXorType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1067: }
1068:
1069: if ($node instanceof Expr\AssignOp\BitwiseXor) {
1070: return $this->initializerExprTypeResolver->getBitwiseXorType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1071: }
1072:
1073: if ($node instanceof Expr\BinaryOp\Spaceship) {
1074: return $this->initializerExprTypeResolver->getSpaceshipType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1075: }
1076:
1077: if ($node instanceof BinaryOp\Div) {
1078: return $this->initializerExprTypeResolver->getDivType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1079: }
1080:
1081: if ($node instanceof Expr\AssignOp\Div) {
1082: return $this->initializerExprTypeResolver->getDivType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1083: }
1084:
1085: if ($node instanceof BinaryOp\Mod) {
1086: return $this->initializerExprTypeResolver->getModType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1087: }
1088:
1089: if ($node instanceof Expr\AssignOp\Mod) {
1090: return $this->initializerExprTypeResolver->getModType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1091: }
1092:
1093: if ($node instanceof BinaryOp\Plus) {
1094: return $this->initializerExprTypeResolver->getPlusType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1095: }
1096:
1097: if ($node instanceof Expr\AssignOp\Plus) {
1098: return $this->initializerExprTypeResolver->getPlusType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1099: }
1100:
1101: if ($node instanceof BinaryOp\Minus) {
1102: return $this->initializerExprTypeResolver->getMinusType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1103: }
1104:
1105: if ($node instanceof Expr\AssignOp\Minus) {
1106: return $this->initializerExprTypeResolver->getMinusType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1107: }
1108:
1109: if ($node instanceof BinaryOp\Mul) {
1110: return $this->initializerExprTypeResolver->getMulType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1111: }
1112:
1113: if ($node instanceof Expr\AssignOp\Mul) {
1114: return $this->initializerExprTypeResolver->getMulType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1115: }
1116:
1117: if ($node instanceof BinaryOp\Pow) {
1118: return $this->initializerExprTypeResolver->getPowType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1119: }
1120:
1121: if ($node instanceof Expr\AssignOp\Pow) {
1122: return $this->initializerExprTypeResolver->getPowType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1123: }
1124:
1125: if ($node instanceof BinaryOp\ShiftLeft) {
1126: return $this->initializerExprTypeResolver->getShiftLeftType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1127: }
1128:
1129: if ($node instanceof Expr\AssignOp\ShiftLeft) {
1130: return $this->initializerExprTypeResolver->getShiftLeftType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1131: }
1132:
1133: if ($node instanceof BinaryOp\ShiftRight) {
1134: return $this->initializerExprTypeResolver->getShiftRightType($node->left, $node->right, fn (Expr $expr): Type => $this->getType($expr));
1135: }
1136:
1137: if ($node instanceof Expr\AssignOp\ShiftRight) {
1138: return $this->initializerExprTypeResolver->getShiftRightType($node->var, $node->expr, fn (Expr $expr): Type => $this->getType($expr));
1139: }
1140:
1141: if ($node instanceof Expr\Clone_) {
1142: return $this->getType($node->expr);
1143: }
1144:
1145: if ($node instanceof Node\Scalar\Int_) {
1146: return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this));
1147: } elseif ($node instanceof String_) {
1148: return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this));
1149: } elseif ($node instanceof Node\Scalar\InterpolatedString) {
1150: $resultType = null;
1151: foreach ($node->parts as $part) {
1152: if ($part instanceof InterpolatedStringPart) {
1153: $partType = new ConstantStringType($part->value);
1154: } else {
1155: $partType = $this->getType($part)->toString();
1156: }
1157: if ($resultType === null) {
1158: $resultType = $partType;
1159: continue;
1160: }
1161:
1162: $resultType = $this->initializerExprTypeResolver->resolveConcatType($resultType, $partType);
1163: if (count($resultType->getConstantStrings()) === 0) {
1164: return $resultType;
1165: }
1166: }
1167:
1168: return $resultType ?? new ConstantStringType('');
1169: } elseif ($node instanceof Node\Scalar\Float_) {
1170: return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this));
1171: } elseif ($node instanceof Expr\CallLike && $node->isFirstClassCallable()) {
1172: if ($node instanceof FuncCall) {
1173: if ($node->name instanceof Name) {
1174: if ($this->reflectionProvider->hasFunction($node->name, $this)) {
1175: $function = $this->reflectionProvider->getFunction($node->name, $this);
1176: return $this->createFirstClassCallable(
1177: $function,
1178: $function->getVariants(),
1179: );
1180: }
1181:
1182: return new ObjectType(Closure::class);
1183: }
1184:
1185: $callableType = $this->getType($node->name);
1186: if (!$callableType->isCallable()->yes()) {
1187: return new ObjectType(Closure::class);
1188: }
1189:
1190: return $this->createFirstClassCallable(
1191: null,
1192: $callableType->getCallableParametersAcceptors($this),
1193: );
1194: }
1195:
1196: if ($node instanceof MethodCall) {
1197: if (!$node->name instanceof Node\Identifier) {
1198: return new ObjectType(Closure::class);
1199: }
1200:
1201: $varType = $this->getType($node->var);
1202: $method = $this->getMethodReflection($varType, $node->name->toString());
1203: if ($method === null) {
1204: return new ObjectType(Closure::class);
1205: }
1206:
1207: return $this->createFirstClassCallable(
1208: $method,
1209: $method->getVariants(),
1210: );
1211: }
1212:
1213: if ($node instanceof Expr\StaticCall) {
1214: if (!$node->class instanceof Name) {
1215: return new ObjectType(Closure::class);
1216: }
1217:
1218: if (!$node->name instanceof Node\Identifier) {
1219: return new ObjectType(Closure::class);
1220: }
1221:
1222: $classType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name);
1223: $methodName = $node->name->toString();
1224: if (!$classType->hasMethod($methodName)->yes()) {
1225: return new ObjectType(Closure::class);
1226: }
1227:
1228: $method = $classType->getMethod($methodName, $this);
1229: return $this->createFirstClassCallable(
1230: $method,
1231: $method->getVariants(),
1232: );
1233: }
1234:
1235: if ($node instanceof New_) {
1236: return new ErrorType();
1237: }
1238:
1239: throw new ShouldNotHappenException();
1240: } elseif ($node instanceof Expr\Closure || $node instanceof Expr\ArrowFunction) {
1241: $parameters = [];
1242: $isVariadic = false;
1243: $firstOptionalParameterIndex = null;
1244: foreach ($node->params as $i => $param) {
1245: $isOptionalCandidate = $param->default !== null || $param->variadic;
1246:
1247: if ($isOptionalCandidate) {
1248: if ($firstOptionalParameterIndex === null) {
1249: $firstOptionalParameterIndex = $i;
1250: }
1251: } else {
1252: $firstOptionalParameterIndex = null;
1253: }
1254: }
1255:
1256: foreach ($node->params as $i => $param) {
1257: if ($param->variadic) {
1258: $isVariadic = true;
1259: }
1260: if (!$param->var instanceof Variable || !is_string($param->var->name)) {
1261: throw new ShouldNotHappenException();
1262: }
1263: $parameters[] = new NativeParameterReflection(
1264: $param->var->name,
1265: $firstOptionalParameterIndex !== null && $i >= $firstOptionalParameterIndex,
1266: $this->getFunctionType($param->type, $this->isParameterValueNullable($param), false),
1267: $param->byRef
1268: ? PassedByReference::createCreatesNewVariable()
1269: : PassedByReference::createNo(),
1270: $param->variadic,
1271: $param->default !== null ? $this->getType($param->default) : null,
1272: );
1273: }
1274:
1275: $callableParameters = null;
1276: $arrayMapArgs = $node->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME);
1277: if ($arrayMapArgs !== null) {
1278: $callableParameters = [];
1279: foreach ($arrayMapArgs as $funcCallArg) {
1280: $callableParameters[] = new DummyParameter('item', $this->getType($funcCallArg->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null);
1281: }
1282: } else {
1283: $inFunctionCallsStackCount = count($this->inFunctionCallsStack);
1284: if ($inFunctionCallsStackCount > 0) {
1285: [, $inParameter] = $this->inFunctionCallsStack[$inFunctionCallsStackCount - 1];
1286: if ($inParameter !== null) {
1287: $callableParameters = $this->nodeScopeResolver->createCallableParameters($this, $node, null, $inParameter->getType());
1288: }
1289: }
1290: }
1291:
1292: if ($node instanceof Expr\ArrowFunction) {
1293: $arrowScope = $this->enterArrowFunctionWithoutReflection($node, $callableParameters);
1294:
1295: if ($node->expr instanceof Expr\Yield_ || $node->expr instanceof Expr\YieldFrom) {
1296: $yieldNode = $node->expr;
1297:
1298: if ($yieldNode instanceof Expr\Yield_) {
1299: if ($yieldNode->key === null) {
1300: $keyType = new IntegerType();
1301: } else {
1302: $keyType = $arrowScope->getType($yieldNode->key);
1303: }
1304:
1305: if ($yieldNode->value === null) {
1306: $valueType = new NullType();
1307: } else {
1308: $valueType = $arrowScope->getType($yieldNode->value);
1309: }
1310: } else {
1311: $yieldFromType = $arrowScope->getType($yieldNode->expr);
1312: $keyType = $arrowScope->getIterableKeyType($yieldFromType);
1313: $valueType = $arrowScope->getIterableValueType($yieldFromType);
1314: }
1315:
1316: $returnType = new GenericObjectType(Generator::class, [
1317: $keyType,
1318: $valueType,
1319: new MixedType(),
1320: new VoidType(),
1321: ]);
1322: } else {
1323: $returnType = $arrowScope->getKeepVoidType($node->expr);
1324: if ($node->returnType !== null) {
1325: $nativeReturnType = $this->getFunctionType($node->returnType, false, false);
1326: $returnType = self::intersectButNotNever($nativeReturnType, $returnType);
1327: }
1328: }
1329:
1330: $arrowFunctionImpurePoints = [];
1331: $invalidateExpressions = [];
1332: $arrowFunctionExprResult = $this->nodeScopeResolver->processExprNode(
1333: new Node\Stmt\Expression($node->expr),
1334: $node->expr,
1335: $arrowScope,
1336: static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpurePoints, &$invalidateExpressions): void {
1337: if ($scope->getAnonymousFunctionReflection() !== $arrowScope->getAnonymousFunctionReflection()) {
1338: return;
1339: }
1340:
1341: if ($node instanceof InvalidateExprNode) {
1342: $invalidateExpressions[] = $node;
1343: return;
1344: }
1345:
1346: if (!$node instanceof PropertyAssignNode) {
1347: return;
1348: }
1349:
1350: $arrowFunctionImpurePoints[] = new ImpurePoint(
1351: $scope,
1352: $node,
1353: 'propertyAssign',
1354: 'property assignment',
1355: true,
1356: );
1357: },
1358: ExpressionContext::createDeep(),
1359: );
1360: $throwPoints = $arrowFunctionExprResult->getThrowPoints();
1361: $impurePoints = array_merge($arrowFunctionImpurePoints, $arrowFunctionExprResult->getImpurePoints());
1362: $usedVariables = [];
1363: } else {
1364: $cachedTypes = $node->getAttribute('phpstanCachedTypes', []);
1365: $cacheKey = $this->getClosureScopeCacheKey();
1366: if (array_key_exists($cacheKey, $cachedTypes)) {
1367: $cachedClosureData = $cachedTypes[$cacheKey];
1368:
1369: return new ClosureType(
1370: $parameters,
1371: $cachedClosureData['returnType'],
1372: $isVariadic,
1373: TemplateTypeMap::createEmpty(),
1374: TemplateTypeMap::createEmpty(),
1375: TemplateTypeVarianceMap::createEmpty(),
1376: [],
1377: $cachedClosureData['throwPoints'],
1378: $cachedClosureData['impurePoints'],
1379: $cachedClosureData['invalidateExpressions'],
1380: $cachedClosureData['usedVariables'],
1381: TrinaryLogic::createYes(),
1382: );
1383: }
1384: if (self::$resolveClosureTypeDepth >= 2) {
1385: return new ClosureType(
1386: $parameters,
1387: $this->getFunctionType($node->returnType, false, false),
1388: $isVariadic,
1389: );
1390: }
1391:
1392: self::$resolveClosureTypeDepth++;
1393:
1394: $closureScope = $this->enterAnonymousFunctionWithoutReflection($node, $callableParameters);
1395: $closureReturnStatements = [];
1396: $closureYieldStatements = [];
1397: $onlyNeverExecutionEnds = null;
1398: $closureImpurePoints = [];
1399: $invalidateExpressions = [];
1400:
1401: try {
1402: $closureStatementResult = $this->nodeScopeResolver->processStmtNodes($node, $node->stmts, $closureScope, static function (Node $node, Scope $scope) use ($closureScope, &$closureReturnStatements, &$closureYieldStatements, &$onlyNeverExecutionEnds, &$closureImpurePoints, &$invalidateExpressions): void {
1403: if ($scope->getAnonymousFunctionReflection() !== $closureScope->getAnonymousFunctionReflection()) {
1404: return;
1405: }
1406:
1407: if ($node instanceof InvalidateExprNode) {
1408: $invalidateExpressions[] = $node;
1409: return;
1410: }
1411:
1412: if ($node instanceof PropertyAssignNode) {
1413: $closureImpurePoints[] = new ImpurePoint(
1414: $scope,
1415: $node,
1416: 'propertyAssign',
1417: 'property assignment',
1418: true,
1419: );
1420: return;
1421: }
1422:
1423: if ($node instanceof ExecutionEndNode) {
1424: if ($node->getStatementResult()->isAlwaysTerminating()) {
1425: foreach ($node->getStatementResult()->getExitPoints() as $exitPoint) {
1426: if ($exitPoint->getStatement() instanceof Node\Stmt\Return_) {
1427: $onlyNeverExecutionEnds = false;
1428: continue;
1429: }
1430:
1431: if ($onlyNeverExecutionEnds === null) {
1432: $onlyNeverExecutionEnds = true;
1433: }
1434:
1435: break;
1436: }
1437:
1438: if (count($node->getStatementResult()->getExitPoints()) === 0) {
1439: if ($onlyNeverExecutionEnds === null) {
1440: $onlyNeverExecutionEnds = true;
1441: }
1442: }
1443: } else {
1444: $onlyNeverExecutionEnds = false;
1445: }
1446:
1447: return;
1448: }
1449:
1450: if ($node instanceof Node\Stmt\Return_) {
1451: $closureReturnStatements[] = [$node, $scope];
1452: }
1453:
1454: if (!$node instanceof Expr\Yield_ && !$node instanceof Expr\YieldFrom) {
1455: return;
1456: }
1457:
1458: $closureYieldStatements[] = [$node, $scope];
1459: }, StatementContext::createTopLevel());
1460: } finally {
1461: self::$resolveClosureTypeDepth--;
1462: }
1463:
1464: $throwPoints = $closureStatementResult->getThrowPoints();
1465: $impurePoints = array_merge($closureImpurePoints, $closureStatementResult->getImpurePoints());
1466:
1467: $returnTypes = [];
1468: $hasNull = false;
1469: foreach ($closureReturnStatements as [$returnNode, $returnScope]) {
1470: if ($returnNode->expr === null) {
1471: $hasNull = true;
1472: continue;
1473: }
1474:
1475: $returnTypes[] = $returnScope->getType($returnNode->expr);
1476: }
1477:
1478: if (count($returnTypes) === 0) {
1479: if ($onlyNeverExecutionEnds === true && !$hasNull) {
1480: $returnType = new NonAcceptingNeverType();
1481: } else {
1482: $returnType = new VoidType();
1483: }
1484: } else {
1485: if ($onlyNeverExecutionEnds === true) {
1486: $returnTypes[] = new NonAcceptingNeverType();
1487: }
1488: if ($hasNull) {
1489: $returnTypes[] = new NullType();
1490: }
1491: $returnType = TypeCombinator::union(...$returnTypes);
1492: }
1493:
1494: if (count($closureYieldStatements) > 0) {
1495: $keyTypes = [];
1496: $valueTypes = [];
1497: foreach ($closureYieldStatements as [$yieldNode, $yieldScope]) {
1498: if ($yieldNode instanceof Expr\Yield_) {
1499: if ($yieldNode->key === null) {
1500: $keyTypes[] = new IntegerType();
1501: } else {
1502: $keyTypes[] = $yieldScope->getType($yieldNode->key);
1503: }
1504:
1505: if ($yieldNode->value === null) {
1506: $valueTypes[] = new NullType();
1507: } else {
1508: $valueTypes[] = $yieldScope->getType($yieldNode->value);
1509: }
1510:
1511: continue;
1512: }
1513:
1514: $yieldFromType = $yieldScope->getType($yieldNode->expr);
1515: $keyTypes[] = $yieldScope->getIterableKeyType($yieldFromType);
1516: $valueTypes[] = $yieldScope->getIterableValueType($yieldFromType);
1517: }
1518:
1519: $returnType = new GenericObjectType(Generator::class, [
1520: TypeCombinator::union(...$keyTypes),
1521: TypeCombinator::union(...$valueTypes),
1522: new MixedType(),
1523: $returnType,
1524: ]);
1525: } else {
1526: if ($node->returnType !== null) {
1527: $nativeReturnType = $this->getFunctionType($node->returnType, false, false);
1528: $returnType = self::intersectButNotNever($nativeReturnType, $returnType);
1529: }
1530: }
1531:
1532: $usedVariables = [];
1533: foreach ($node->uses as $use) {
1534: if (!is_string($use->var->name)) {
1535: continue;
1536: }
1537:
1538: $usedVariables[] = $use->var->name;
1539: }
1540:
1541: foreach ($node->uses as $use) {
1542: if (!$use->byRef) {
1543: continue;
1544: }
1545:
1546: $impurePoints[] = new ImpurePoint(
1547: $this,
1548: $node,
1549: 'functionCall',
1550: 'call to a Closure with by-ref use',
1551: true,
1552: );
1553: break;
1554: }
1555: }
1556:
1557: foreach ($parameters as $parameter) {
1558: if ($parameter->passedByReference()->no()) {
1559: continue;
1560: }
1561:
1562: $impurePoints[] = new ImpurePoint(
1563: $this,
1564: $node,
1565: 'functionCall',
1566: 'call to a Closure with by-ref parameter',
1567: true,
1568: );
1569: }
1570:
1571: $throwPointsForClosureType = array_map(static fn (ThrowPoint $throwPoint) => $throwPoint->isExplicit() ? SimpleThrowPoint::createExplicit($throwPoint->getType(), $throwPoint->canContainAnyThrowable()) : SimpleThrowPoint::createImplicit(), $throwPoints);
1572: $impurePointsForClosureType = array_map(static fn (ImpurePoint $impurePoint) => new SimpleImpurePoint($impurePoint->getIdentifier(), $impurePoint->getDescription(), $impurePoint->isCertain()), $impurePoints);
1573:
1574: $cachedTypes = $node->getAttribute('phpstanCachedTypes', []);
1575: $cachedTypes[$this->getClosureScopeCacheKey()] = [
1576: 'returnType' => $returnType,
1577: 'throwPoints' => $throwPointsForClosureType,
1578: 'impurePoints' => $impurePointsForClosureType,
1579: 'invalidateExpressions' => $invalidateExpressions,
1580: 'usedVariables' => $usedVariables,
1581: ];
1582: $node->setAttribute('phpstanCachedTypes', $cachedTypes);
1583:
1584: return new ClosureType(
1585: $parameters,
1586: $returnType,
1587: $isVariadic,
1588: TemplateTypeMap::createEmpty(),
1589: TemplateTypeMap::createEmpty(),
1590: TemplateTypeVarianceMap::createEmpty(),
1591: [],
1592: $throwPointsForClosureType,
1593: $impurePointsForClosureType,
1594: $invalidateExpressions,
1595: $usedVariables,
1596: TrinaryLogic::createYes(),
1597: );
1598: } elseif ($node instanceof New_) {
1599: if ($node->class instanceof Name) {
1600: $type = $this->exactInstantiation($node, $node->class->toString());
1601: if ($type !== null) {
1602: return $type;
1603: }
1604:
1605: $lowercasedClassName = strtolower($node->class->toString());
1606: if ($lowercasedClassName === 'static') {
1607: if (!$this->isInClass()) {
1608: return new ErrorType();
1609: }
1610:
1611: return new StaticType($this->getClassReflection());
1612: }
1613: if ($lowercasedClassName === 'parent') {
1614: return new NonexistentParentClassType();
1615: }
1616:
1617: return new ObjectType($node->class->toString());
1618: }
1619: if ($node->class instanceof Node\Stmt\Class_) {
1620: $anonymousClassReflection = $this->reflectionProvider->getAnonymousClassReflection($node->class, $this);
1621:
1622: return new ObjectType($anonymousClassReflection->getName());
1623: }
1624:
1625: $exprType = $this->getType($node->class);
1626: return $exprType->getObjectTypeOrClassStringObjectType();
1627:
1628: } elseif ($node instanceof Array_) {
1629: return $this->initializerExprTypeResolver->getArrayType($node, fn (Expr $expr): Type => $this->getType($expr));
1630: } elseif ($node instanceof Int_) {
1631: return $this->getType($node->expr)->toInteger();
1632: } elseif ($node instanceof Bool_) {
1633: return $this->getType($node->expr)->toBoolean();
1634: } elseif ($node instanceof Double) {
1635: return $this->getType($node->expr)->toFloat();
1636: } elseif ($node instanceof Node\Expr\Cast\String_) {
1637: return $this->getType($node->expr)->toString();
1638: } elseif ($node instanceof Node\Expr\Cast\Array_) {
1639: return $this->getType($node->expr)->toArray();
1640: } elseif ($node instanceof Node\Scalar\MagicConst) {
1641: return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this));
1642: } elseif ($node instanceof Object_) {
1643: $castToObject = static function (Type $type): Type {
1644: $constantArrays = $type->getConstantArrays();
1645: if (count($constantArrays) > 0) {
1646: $objects = [];
1647: foreach ($constantArrays as $constantArray) {
1648: $properties = [];
1649: $optionalProperties = [];
1650: foreach ($constantArray->getKeyTypes() as $i => $keyType) {
1651: if (!$keyType instanceof ConstantStringType) {
1652: // an object with integer properties is >weird<
1653: continue;
1654: }
1655: $valueType = $constantArray->getValueTypes()[$i];
1656: $optional = $constantArray->isOptionalKey($i);
1657: if ($optional) {
1658: $optionalProperties[] = $keyType->getValue();
1659: }
1660: $properties[$keyType->getValue()] = $valueType;
1661: }
1662:
1663: $objects[] = TypeCombinator::intersect(new ObjectShapeType($properties, $optionalProperties), new ObjectType(stdClass::class));
1664: }
1665:
1666: return TypeCombinator::union(...$objects);
1667: }
1668: if ($type->isObject()->yes()) {
1669: return $type;
1670: }
1671:
1672: return new ObjectType('stdClass');
1673: };
1674:
1675: $exprType = $this->getType($node->expr);
1676: if ($exprType instanceof UnionType) {
1677: return TypeCombinator::union(...array_map($castToObject, $exprType->getTypes()));
1678: }
1679:
1680: return $castToObject($exprType);
1681: } elseif ($node instanceof Unset_) {
1682: return new NullType();
1683: } elseif ($node instanceof Expr\PostInc || $node instanceof Expr\PostDec) {
1684: return $this->getType($node->var);
1685: } elseif ($node instanceof Expr\PreInc || $node instanceof Expr\PreDec) {
1686: $varType = $this->getType($node->var);
1687: $varScalars = $varType->getConstantScalarValues();
1688:
1689: if (count($varScalars) > 0) {
1690: $newTypes = [];
1691:
1692: foreach ($varScalars as $varValue) {
1693: if ($node instanceof Expr\PreInc) {
1694: if (!is_bool($varValue)) {
1695: ++$varValue;
1696: }
1697: } elseif (is_numeric($varValue)) {
1698: --$varValue;
1699: }
1700:
1701: $newTypes[] = $this->getTypeFromValue($varValue);
1702: }
1703: return TypeCombinator::union(...$newTypes);
1704: } elseif ($varType->isString()->yes()) {
1705: if ($varType->isLiteralString()->yes()) {
1706: return new IntersectionType([
1707: new StringType(),
1708: new AccessoryLiteralStringType(),
1709: ]);
1710: }
1711:
1712: if ($varType->isNumericString()->yes()) {
1713: return new BenevolentUnionType([
1714: new IntegerType(),
1715: new FloatType(),
1716: ]);
1717: }
1718:
1719: return new BenevolentUnionType([
1720: new StringType(),
1721: new IntegerType(),
1722: new FloatType(),
1723: ]);
1724: }
1725:
1726: if ($node instanceof Expr\PreInc) {
1727: return $this->getType(new BinaryOp\Plus($node->var, new Node\Scalar\Int_(1)));
1728: }
1729:
1730: return $this->getType(new BinaryOp\Minus($node->var, new Node\Scalar\Int_(1)));
1731: } elseif ($node instanceof Expr\Yield_) {
1732: $functionReflection = $this->getFunction();
1733: if ($functionReflection === null) {
1734: return new MixedType();
1735: }
1736:
1737: $returnType = $functionReflection->getReturnType();
1738: $generatorSendType = $returnType->getTemplateType(Generator::class, 'TSend');
1739: if ($generatorSendType instanceof ErrorType) {
1740: return new MixedType();
1741: }
1742:
1743: return $generatorSendType;
1744: } elseif ($node instanceof Expr\YieldFrom) {
1745: $yieldFromType = $this->getType($node->expr);
1746: $generatorReturnType = $yieldFromType->getTemplateType(Generator::class, 'TReturn');
1747: if ($generatorReturnType instanceof ErrorType) {
1748: return new MixedType();
1749: }
1750:
1751: return $generatorReturnType;
1752: } elseif ($node instanceof Expr\Match_) {
1753: $cond = $node->cond;
1754: $condType = $this->getType($cond);
1755: $types = [];
1756:
1757: $matchScope = $this;
1758: $arms = $node->arms;
1759: if ($condType->isEnum()->yes()) {
1760: // enum match analysis would work even without this if branch
1761: // but would be much slower
1762: // this avoids using ObjectType::$subtractedType which is slow for huge enums
1763: // because of repeated union type normalization
1764: $enumCases = $condType->getEnumCases();
1765: if (count($enumCases) > 0) {
1766: $indexedEnumCases = [];
1767: foreach ($enumCases as $enumCase) {
1768: $indexedEnumCases[strtolower($enumCase->getClassName())][$enumCase->getEnumCaseName()] = $enumCase;
1769: }
1770: $unusedIndexedEnumCases = $indexedEnumCases;
1771:
1772: foreach ($arms as $i => $arm) {
1773: if ($arm->conds === null) {
1774: continue;
1775: }
1776:
1777: $conditionCases = [];
1778: foreach ($arm->conds as $armCond) {
1779: if (!$armCond instanceof Expr\ClassConstFetch) {
1780: continue 2;
1781: }
1782: if (!$armCond->class instanceof Name) {
1783: continue 2;
1784: }
1785: if (!$armCond->name instanceof Node\Identifier) {
1786: continue 2;
1787: }
1788: $fetchedClassName = $this->resolveName($armCond->class);
1789: $loweredFetchedClassName = strtolower($fetchedClassName);
1790: if (!array_key_exists($loweredFetchedClassName, $indexedEnumCases)) {
1791: continue 2;
1792: }
1793:
1794: $caseName = $armCond->name->toString();
1795: if (!array_key_exists($caseName, $indexedEnumCases[$loweredFetchedClassName])) {
1796: continue 2;
1797: }
1798:
1799: $conditionCases[] = $indexedEnumCases[$loweredFetchedClassName][$caseName];
1800: unset($unusedIndexedEnumCases[$loweredFetchedClassName][$caseName]);
1801: }
1802:
1803: $conditionCasesCount = count($conditionCases);
1804: if ($conditionCasesCount === 0) {
1805: throw new ShouldNotHappenException();
1806: } elseif ($conditionCasesCount === 1) {
1807: $conditionCaseType = $conditionCases[0];
1808: } else {
1809: $conditionCaseType = new UnionType($conditionCases);
1810: }
1811:
1812: $types[] = $matchScope->addTypeToExpression(
1813: $cond,
1814: $conditionCaseType,
1815: )->getType($arm->body);
1816: unset($arms[$i]);
1817: }
1818:
1819: $remainingCases = [];
1820: foreach ($unusedIndexedEnumCases as $cases) {
1821: foreach ($cases as $case) {
1822: $remainingCases[] = $case;
1823: }
1824: }
1825:
1826: $remainingCasesCount = count($remainingCases);
1827: if ($remainingCasesCount === 0) {
1828: $remainingType = new NeverType();
1829: } elseif ($remainingCasesCount === 1) {
1830: $remainingType = $remainingCases[0];
1831: } else {
1832: $remainingType = new UnionType($remainingCases);
1833: }
1834:
1835: $matchScope = $matchScope->addTypeToExpression($cond, $remainingType);
1836: }
1837: }
1838:
1839: foreach ($arms as $arm) {
1840: if ($arm->conds === null) {
1841: if ($node->hasAttribute(self::KEEP_VOID_ATTRIBUTE_NAME)) {
1842: $arm->body->setAttribute(self::KEEP_VOID_ATTRIBUTE_NAME, $node->getAttribute(self::KEEP_VOID_ATTRIBUTE_NAME));
1843: }
1844: $types[] = $matchScope->getType($arm->body);
1845: continue;
1846: }
1847:
1848: if (count($arm->conds) === 0) {
1849: throw new ShouldNotHappenException();
1850: }
1851:
1852: if (count($arm->conds) === 1) {
1853: $filteringExpr = new BinaryOp\Identical($cond, $arm->conds[0]);
1854: } else {
1855: $items = [];
1856: foreach ($arm->conds as $filteringExpr) {
1857: $items[] = new Node\ArrayItem($filteringExpr);
1858: }
1859: $filteringExpr = new FuncCall(
1860: new Name\FullyQualified('in_array'),
1861: [
1862: new Arg($cond),
1863: new Arg(new Array_($items)),
1864: new Arg(new ConstFetch(new Name\FullyQualified('true'))),
1865: ],
1866: );
1867: }
1868:
1869: $filteringExprType = $matchScope->getType($filteringExpr);
1870:
1871: if (!$filteringExprType->isFalse()->yes()) {
1872: $truthyScope = $matchScope->filterByTruthyValue($filteringExpr);
1873: if ($node->hasAttribute(self::KEEP_VOID_ATTRIBUTE_NAME)) {
1874: $arm->body->setAttribute(self::KEEP_VOID_ATTRIBUTE_NAME, $node->getAttribute(self::KEEP_VOID_ATTRIBUTE_NAME));
1875: }
1876: $types[] = $truthyScope->getType($arm->body);
1877: }
1878:
1879: $matchScope = $matchScope->filterByFalseyValue($filteringExpr);
1880: }
1881:
1882: return TypeCombinator::union(...$types);
1883: }
1884:
1885: if ($node instanceof Expr\Isset_) {
1886: $issetResult = true;
1887: foreach ($node->vars as $var) {
1888: $result = $this->issetCheck($var, static function (Type $type): ?bool {
1889: $isNull = $type->isNull();
1890: if ($isNull->maybe()) {
1891: return null;
1892: }
1893:
1894: return !$isNull->yes();
1895: });
1896: if ($result !== null) {
1897: if (!$result) {
1898: return new ConstantBooleanType($result);
1899: }
1900:
1901: continue;
1902: }
1903:
1904: $issetResult = $result;
1905: }
1906:
1907: if ($issetResult === null) {
1908: return new BooleanType();
1909: }
1910:
1911: return new ConstantBooleanType($issetResult);
1912: }
1913:
1914: if ($node instanceof Expr\AssignOp\Coalesce) {
1915: return $this->getType(new BinaryOp\Coalesce($node->var, $node->expr, $node->getAttributes()));
1916: }
1917:
1918: if ($node instanceof Expr\BinaryOp\Coalesce) {
1919: $issetLeftExpr = new Expr\Isset_([$node->left]);
1920: $leftType = $this->filterByTruthyValue($issetLeftExpr)->getType($node->left);
1921:
1922: $result = $this->issetCheck($node->left, static function (Type $type): ?bool {
1923: $isNull = $type->isNull();
1924: if ($isNull->maybe()) {
1925: return null;
1926: }
1927:
1928: return !$isNull->yes();
1929: });
1930:
1931: if ($result !== null && $result !== false) {
1932: return TypeCombinator::removeNull($leftType);
1933: }
1934:
1935: $rightType = $this->filterByFalseyValue($issetLeftExpr)->getType($node->right);
1936:
1937: if ($result === null) {
1938: return TypeCombinator::union(
1939: TypeCombinator::removeNull($leftType),
1940: $rightType,
1941: );
1942: }
1943:
1944: return $rightType;
1945: }
1946:
1947: if ($node instanceof ConstFetch) {
1948: $constName = (string) $node->name;
1949: $loweredConstName = strtolower($constName);
1950: if ($loweredConstName === 'true') {
1951: return new ConstantBooleanType(true);
1952: } elseif ($loweredConstName === 'false') {
1953: return new ConstantBooleanType(false);
1954: } elseif ($loweredConstName === 'null') {
1955: return new NullType();
1956: }
1957:
1958: $namespacedName = null;
1959: if (!$node->name->isFullyQualified() && $this->getNamespace() !== null) {
1960: $namespacedName = new FullyQualified([$this->getNamespace(), $node->name->toString()]);
1961: }
1962: $globalName = new FullyQualified($node->name->toString());
1963:
1964: foreach ([$namespacedName, $globalName] as $name) {
1965: if ($name === null) {
1966: continue;
1967: }
1968: $constFetch = new ConstFetch($name);
1969: if ($this->hasExpressionType($constFetch)->yes()) {
1970: return $this->constantResolver->resolveConstantType(
1971: $name->toString(),
1972: $this->expressionTypes[$this->getNodeKey($constFetch)]->getType(),
1973: );
1974: }
1975: }
1976:
1977: $constantType = $this->constantResolver->resolveConstant($node->name, $this);
1978: if ($constantType !== null) {
1979: return $constantType;
1980: }
1981:
1982: return new ErrorType();
1983: } elseif ($node instanceof Node\Expr\ClassConstFetch && $node->name instanceof Node\Identifier) {
1984: if ($this->hasExpressionType($node)->yes()) {
1985: return $this->expressionTypes[$exprString]->getType();
1986: }
1987: return $this->initializerExprTypeResolver->getClassConstFetchTypeByReflection(
1988: $node->class,
1989: $node->name->name,
1990: $this->isInClass() ? $this->getClassReflection() : null,
1991: fn (Expr $expr): Type => $this->getType($expr),
1992: );
1993: }
1994:
1995: if ($node instanceof Expr\Ternary) {
1996: $noopCallback = static function (): void {
1997: };
1998: $condResult = $this->nodeScopeResolver->processExprNode(new Node\Stmt\Expression($node->cond), $node->cond, $this, $noopCallback, ExpressionContext::createDeep());
1999: if ($node->if === null) {
2000: $conditionType = $this->getType($node->cond);
2001: $booleanConditionType = $conditionType->toBoolean();
2002: if ($booleanConditionType->isTrue()->yes()) {
2003: return $condResult->getTruthyScope()->getType($node->cond);
2004: }
2005:
2006: if ($booleanConditionType->isFalse()->yes()) {
2007: return $condResult->getFalseyScope()->getType($node->else);
2008: }
2009:
2010: return TypeCombinator::union(
2011: TypeCombinator::removeFalsey($condResult->getTruthyScope()->getType($node->cond)),
2012: $condResult->getFalseyScope()->getType($node->else),
2013: );
2014: }
2015:
2016: $booleanConditionType = $this->getType($node->cond)->toBoolean();
2017: if ($booleanConditionType->isTrue()->yes()) {
2018: return $condResult->getTruthyScope()->getType($node->if);
2019: }
2020:
2021: if ($booleanConditionType->isFalse()->yes()) {
2022: return $condResult->getFalseyScope()->getType($node->else);
2023: }
2024:
2025: return TypeCombinator::union(
2026: $condResult->getTruthyScope()->getType($node->if),
2027: $condResult->getFalseyScope()->getType($node->else),
2028: );
2029: }
2030:
2031: if ($node instanceof Variable && is_string($node->name)) {
2032: if ($this->hasVariableType($node->name)->no()) {
2033: return new ErrorType();
2034: }
2035:
2036: return $this->getVariableType($node->name);
2037: }
2038:
2039: if ($node instanceof Expr\ArrayDimFetch && $node->dim !== null) {
2040: return $this->getNullsafeShortCircuitingType(
2041: $node->var,
2042: $this->getTypeFromArrayDimFetch(
2043: $node,
2044: $this->getType($node->dim),
2045: $this->getType($node->var),
2046: ),
2047: );
2048: }
2049:
2050: if ($node instanceof MethodCall && $node->name instanceof Node\Identifier) {
2051: if ($this->nativeTypesPromoted) {
2052: $typeCallback = function () use ($node): Type {
2053: $methodReflection = $this->getMethodReflection(
2054: $this->getNativeType($node->var),
2055: $node->name->name,
2056: );
2057: if ($methodReflection === null) {
2058: return new ErrorType();
2059: }
2060:
2061: return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType();
2062: };
2063:
2064: return $this->getNullsafeShortCircuitingType($node->var, $typeCallback());
2065: }
2066:
2067: $typeCallback = function () use ($node): Type {
2068: $returnType = $this->methodCallReturnType(
2069: $this->getType($node->var),
2070: $node->name->name,
2071: $node,
2072: );
2073: if ($returnType === null) {
2074: return new ErrorType();
2075: }
2076: return $returnType;
2077: };
2078:
2079: return $this->getNullsafeShortCircuitingType($node->var, $typeCallback());
2080: }
2081:
2082: if ($node instanceof Expr\NullsafeMethodCall) {
2083: $varType = $this->getType($node->var);
2084: if ($varType->isNull()->yes()) {
2085: return new NullType();
2086: }
2087: if (!TypeCombinator::containsNull($varType)) {
2088: return $this->getType(new MethodCall($node->var, $node->name, $node->args));
2089: }
2090:
2091: return TypeCombinator::union(
2092: $this->filterByTruthyValue(new BinaryOp\NotIdentical($node->var, new ConstFetch(new Name('null'))))
2093: ->getType(new MethodCall($node->var, $node->name, $node->args)),
2094: new NullType(),
2095: );
2096: }
2097:
2098: if ($node instanceof Expr\StaticCall && $node->name instanceof Node\Identifier) {
2099: if ($this->nativeTypesPromoted) {
2100: $typeCallback = function () use ($node): Type {
2101: if ($node->class instanceof Name) {
2102: $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name);
2103: } else {
2104: $staticMethodCalledOnType = $this->getNativeType($node->class);
2105: }
2106: $methodReflection = $this->getMethodReflection(
2107: $staticMethodCalledOnType,
2108: $node->name->name,
2109: );
2110: if ($methodReflection === null) {
2111: return new ErrorType();
2112: }
2113:
2114: return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType();
2115: };
2116:
2117: $callType = $typeCallback();
2118: if ($node->class instanceof Expr) {
2119: return $this->getNullsafeShortCircuitingType($node->class, $callType);
2120: }
2121:
2122: return $callType;
2123: }
2124:
2125: $typeCallback = function () use ($node): Type {
2126: if ($node->class instanceof Name) {
2127: $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name);
2128: } else {
2129: $staticMethodCalledOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType();
2130: }
2131:
2132: $returnType = $this->methodCallReturnType(
2133: $staticMethodCalledOnType,
2134: $node->name->toString(),
2135: $node,
2136: );
2137: if ($returnType === null) {
2138: return new ErrorType();
2139: }
2140: return $returnType;
2141: };
2142:
2143: $callType = $typeCallback();
2144: if ($node->class instanceof Expr) {
2145: return $this->getNullsafeShortCircuitingType($node->class, $callType);
2146: }
2147:
2148: return $callType;
2149: }
2150:
2151: if ($node instanceof PropertyFetch && $node->name instanceof Node\Identifier) {
2152: if ($this->nativeTypesPromoted) {
2153: $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $this);
2154: if ($propertyReflection === null) {
2155: return new ErrorType();
2156: }
2157: $nativeType = $propertyReflection->getNativeType();
2158: if ($nativeType === null) {
2159: return new ErrorType();
2160: }
2161:
2162: return $this->getNullsafeShortCircuitingType($node->var, $nativeType);
2163: }
2164:
2165: $typeCallback = function () use ($node): Type {
2166: $returnType = $this->propertyFetchType(
2167: $this->getType($node->var),
2168: $node->name->name,
2169: $node,
2170: );
2171: if ($returnType === null) {
2172: return new ErrorType();
2173: }
2174: return $returnType;
2175: };
2176:
2177: return $this->getNullsafeShortCircuitingType($node->var, $typeCallback());
2178: }
2179:
2180: if ($node instanceof Expr\NullsafePropertyFetch) {
2181: $varType = $this->getType($node->var);
2182: if ($varType->isNull()->yes()) {
2183: return new NullType();
2184: }
2185: if (!TypeCombinator::containsNull($varType)) {
2186: return $this->getType(new PropertyFetch($node->var, $node->name));
2187: }
2188:
2189: return TypeCombinator::union(
2190: $this->filterByTruthyValue(new BinaryOp\NotIdentical($node->var, new ConstFetch(new Name('null'))))
2191: ->getType(new PropertyFetch($node->var, $node->name)),
2192: new NullType(),
2193: );
2194: }
2195:
2196: if (
2197: $node instanceof Expr\StaticPropertyFetch
2198: && $node->name instanceof Node\VarLikeIdentifier
2199: ) {
2200: if ($this->nativeTypesPromoted) {
2201: $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $this);
2202: if ($propertyReflection === null) {
2203: return new ErrorType();
2204: }
2205: $nativeType = $propertyReflection->getNativeType();
2206: if ($nativeType === null) {
2207: return new ErrorType();
2208: }
2209:
2210: if ($node->class instanceof Expr) {
2211: return $this->getNullsafeShortCircuitingType($node->class, $nativeType);
2212: }
2213:
2214: return $nativeType;
2215: }
2216:
2217: $typeCallback = function () use ($node): Type {
2218: if ($node->class instanceof Name) {
2219: $staticPropertyFetchedOnType = $this->resolveTypeByName($node->class);
2220: } else {
2221: $staticPropertyFetchedOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType();
2222: }
2223:
2224: $returnType = $this->propertyFetchType(
2225: $staticPropertyFetchedOnType,
2226: $node->name->toString(),
2227: $node,
2228: );
2229: if ($returnType === null) {
2230: return new ErrorType();
2231: }
2232: return $returnType;
2233: };
2234:
2235: $fetchType = $typeCallback();
2236: if ($node->class instanceof Expr) {
2237: return $this->getNullsafeShortCircuitingType($node->class, $fetchType);
2238: }
2239:
2240: return $fetchType;
2241: }
2242:
2243: if ($node instanceof FuncCall) {
2244: if ($node->name instanceof Expr) {
2245: $calledOnType = $this->getType($node->name);
2246: if ($calledOnType->isCallable()->no()) {
2247: return new ErrorType();
2248: }
2249:
2250: return ParametersAcceptorSelector::selectFromArgs(
2251: $this,
2252: $node->getArgs(),
2253: $calledOnType->getCallableParametersAcceptors($this),
2254: null,
2255: )->getReturnType();
2256: }
2257:
2258: if (!$this->reflectionProvider->hasFunction($node->name, $this)) {
2259: return new ErrorType();
2260: }
2261:
2262: $functionReflection = $this->reflectionProvider->getFunction($node->name, $this);
2263: if ($this->nativeTypesPromoted) {
2264: return ParametersAcceptorSelector::combineAcceptors($functionReflection->getVariants())->getNativeReturnType();
2265: }
2266:
2267: if ($functionReflection->getName() === 'call_user_func') {
2268: $result = ArgumentsNormalizer::reorderCallUserFuncArguments($node, $this);
2269: if ($result !== null) {
2270: [, $innerFuncCall] = $result;
2271:
2272: return $this->getType($innerFuncCall);
2273: }
2274: }
2275:
2276: $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
2277: $this,
2278: $node->getArgs(),
2279: $functionReflection->getVariants(),
2280: $functionReflection->getNamedArgumentsVariants(),
2281: );
2282: $normalizedNode = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node);
2283: if ($normalizedNode !== null) {
2284: foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicFunctionReturnTypeExtensions() as $dynamicFunctionReturnTypeExtension) {
2285: if (!$dynamicFunctionReturnTypeExtension->isFunctionSupported($functionReflection)) {
2286: continue;
2287: }
2288:
2289: $resolvedType = $dynamicFunctionReturnTypeExtension->getTypeFromFunctionCall(
2290: $functionReflection,
2291: $normalizedNode,
2292: $this,
2293: );
2294: if ($resolvedType !== null) {
2295: return $resolvedType;
2296: }
2297: }
2298: }
2299:
2300: return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $node);
2301: }
2302:
2303: return new MixedType();
2304: }
2305:
2306: private function getNullsafeShortCircuitingType(Expr $expr, Type $type): Type
2307: {
2308: if ($expr instanceof Expr\NullsafePropertyFetch || $expr instanceof Expr\NullsafeMethodCall) {
2309: $varType = $this->getType($expr->var);
2310: if (TypeCombinator::containsNull($varType)) {
2311: return TypeCombinator::addNull($type);
2312: }
2313:
2314: return $type;
2315: }
2316:
2317: if ($expr instanceof Expr\ArrayDimFetch) {
2318: return $this->getNullsafeShortCircuitingType($expr->var, $type);
2319: }
2320:
2321: if ($expr instanceof PropertyFetch) {
2322: return $this->getNullsafeShortCircuitingType($expr->var, $type);
2323: }
2324:
2325: if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) {
2326: return $this->getNullsafeShortCircuitingType($expr->class, $type);
2327: }
2328:
2329: if ($expr instanceof MethodCall) {
2330: return $this->getNullsafeShortCircuitingType($expr->var, $type);
2331: }
2332:
2333: if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) {
2334: return $this->getNullsafeShortCircuitingType($expr->class, $type);
2335: }
2336:
2337: return $type;
2338: }
2339:
2340: private function transformVoidToNull(Type $type, Node $node): Type
2341: {
2342: if ($node->getAttribute(self::KEEP_VOID_ATTRIBUTE_NAME) === true) {
2343: return $type;
2344: }
2345:
2346: return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type {
2347: if ($type instanceof UnionType || $type instanceof IntersectionType) {
2348: return $traverse($type);
2349: }
2350:
2351: if ($type->isVoid()->yes()) {
2352: return new NullType();
2353: }
2354:
2355: return $type;
2356: });
2357: }
2358:
2359: /**
2360: * @param callable(Type): ?bool $typeCallback
2361: */
2362: public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = null): ?bool
2363: {
2364: // mirrored in PHPStan\Rules\IssetCheck
2365: if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) {
2366: $hasVariable = $this->hasVariableType($expr->name);
2367: if ($hasVariable->maybe()) {
2368: return null;
2369: }
2370:
2371: if ($result === null) {
2372: if ($hasVariable->yes()) {
2373: if ($expr->name === '_SESSION') {
2374: return null;
2375: }
2376:
2377: return $typeCallback($this->getVariableType($expr->name));
2378: }
2379:
2380: return false;
2381: }
2382:
2383: return $result;
2384: } elseif ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) {
2385: $type = $this->getType($expr->var);
2386: if (!$type->isOffsetAccessible()->yes()) {
2387: return $result ?? $this->issetCheckUndefined($expr->var);
2388: }
2389:
2390: $dimType = $this->getType($expr->dim);
2391: $hasOffsetValue = $type->hasOffsetValueType($dimType);
2392: if ($hasOffsetValue->no()) {
2393: return false;
2394: }
2395:
2396: // If offset cannot be null, store this error message and see if one of the earlier offsets is.
2397: // E.g. $array['a']['b']['c'] ?? null; is a valid coalesce if a OR b or C might be null.
2398: if ($hasOffsetValue->yes()) {
2399: $result = $typeCallback($type->getOffsetValueType($dimType));
2400:
2401: if ($result !== null) {
2402: return $this->issetCheck($expr->var, $typeCallback, $result);
2403: }
2404: }
2405:
2406: // Has offset, it is nullable
2407: return null;
2408:
2409: } elseif ($expr instanceof Node\Expr\PropertyFetch || $expr instanceof Node\Expr\StaticPropertyFetch) {
2410:
2411: $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $this);
2412:
2413: if ($propertyReflection === null) {
2414: if ($expr instanceof Node\Expr\PropertyFetch) {
2415: return $this->issetCheckUndefined($expr->var);
2416: }
2417:
2418: if ($expr->class instanceof Expr) {
2419: return $this->issetCheckUndefined($expr->class);
2420: }
2421:
2422: return null;
2423: }
2424:
2425: if (!$propertyReflection->isNative()) {
2426: if ($expr instanceof Node\Expr\PropertyFetch) {
2427: return $this->issetCheckUndefined($expr->var);
2428: }
2429:
2430: if ($expr->class instanceof Expr) {
2431: return $this->issetCheckUndefined($expr->class);
2432: }
2433:
2434: return null;
2435: }
2436:
2437: $nativeType = $propertyReflection->getNativeType();
2438: if (!$nativeType instanceof MixedType) {
2439: if (!$this->hasExpressionType($expr)->yes()) {
2440: if ($expr instanceof Node\Expr\PropertyFetch) {
2441: return $this->issetCheckUndefined($expr->var);
2442: }
2443:
2444: if ($expr->class instanceof Expr) {
2445: return $this->issetCheckUndefined($expr->class);
2446: }
2447:
2448: return null;
2449: }
2450: }
2451:
2452: if ($result !== null) {
2453: return $result;
2454: }
2455:
2456: $result = $typeCallback($propertyReflection->getWritableType());
2457: if ($result !== null) {
2458: if ($expr instanceof Node\Expr\PropertyFetch) {
2459: return $this->issetCheck($expr->var, $typeCallback, $result);
2460: }
2461:
2462: if ($expr->class instanceof Expr) {
2463: return $this->issetCheck($expr->class, $typeCallback, $result);
2464: }
2465: }
2466:
2467: return $result;
2468: }
2469:
2470: if ($result !== null) {
2471: return $result;
2472: }
2473:
2474: return $typeCallback($this->getType($expr));
2475: }
2476:
2477: private function issetCheckUndefined(Expr $expr): ?bool
2478: {
2479: if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) {
2480: $hasVariable = $this->hasVariableType($expr->name);
2481: if (!$hasVariable->no()) {
2482: return null;
2483: }
2484:
2485: return false;
2486: }
2487:
2488: if ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) {
2489: $type = $this->getType($expr->var);
2490: if (!$type->isOffsetAccessible()->yes()) {
2491: return $this->issetCheckUndefined($expr->var);
2492: }
2493:
2494: $dimType = $this->getType($expr->dim);
2495: $hasOffsetValue = $type->hasOffsetValueType($dimType);
2496:
2497: if (!$hasOffsetValue->no()) {
2498: return $this->issetCheckUndefined($expr->var);
2499: }
2500:
2501: return false;
2502: }
2503:
2504: if ($expr instanceof Expr\PropertyFetch) {
2505: return $this->issetCheckUndefined($expr->var);
2506: }
2507:
2508: if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) {
2509: return $this->issetCheckUndefined($expr->class);
2510: }
2511:
2512: return null;
2513: }
2514:
2515: /**
2516: * @param ParametersAcceptor[] $variants
2517: */
2518: private function createFirstClassCallable(
2519: FunctionReflection|ExtendedMethodReflection|null $function,
2520: array $variants,
2521: ): Type
2522: {
2523: $closureTypes = [];
2524:
2525: foreach ($variants as $variant) {
2526: $returnType = $variant->getReturnType();
2527: if ($variant instanceof ExtendedParametersAcceptor) {
2528: $returnType = $this->nativeTypesPromoted ? $variant->getNativeReturnType() : $returnType;
2529: }
2530:
2531: $templateTags = [];
2532: foreach ($variant->getTemplateTypeMap()->getTypes() as $templateType) {
2533: if (!$templateType instanceof TemplateType) {
2534: continue;
2535: }
2536: $templateTags[$templateType->getName()] = new TemplateTag(
2537: $templateType->getName(),
2538: $templateType->getBound(),
2539: $templateType->getDefault(),
2540: $templateType->getVariance(),
2541: );
2542: }
2543:
2544: $throwPoints = [];
2545: $impurePoints = [];
2546: $acceptsNamedArguments = TrinaryLogic::createYes();
2547: if ($variant instanceof CallableParametersAcceptor) {
2548: $throwPoints = $variant->getThrowPoints();
2549: $impurePoints = $variant->getImpurePoints();
2550: $acceptsNamedArguments = $variant->acceptsNamedArguments();
2551: } elseif ($function !== null) {
2552: $returnTypeForThrow = $variant->getReturnType();
2553: $throwType = $function->getThrowType();
2554: if ($throwType === null) {
2555: if ($returnTypeForThrow instanceof NeverType && $returnTypeForThrow->isExplicit()) {
2556: $throwType = new ObjectType(Throwable::class);
2557: }
2558: }
2559:
2560: if ($throwType !== null) {
2561: if (!$throwType->isVoid()->yes()) {
2562: $throwPoints[] = SimpleThrowPoint::createExplicit($throwType, true);
2563: }
2564: } else {
2565: if (!(new ObjectType(Throwable::class))->isSuperTypeOf($returnTypeForThrow)->yes()) {
2566: $throwPoints[] = SimpleThrowPoint::createImplicit();
2567: }
2568: }
2569:
2570: $impurePoint = SimpleImpurePoint::createFromVariant($function, $variant);
2571: if ($impurePoint !== null) {
2572: $impurePoints[] = $impurePoint;
2573: }
2574:
2575: $acceptsNamedArguments = $function->acceptsNamedArguments();
2576: }
2577:
2578: $parameters = $variant->getParameters();
2579: $closureTypes[] = new ClosureType(
2580: $parameters,
2581: $returnType,
2582: $variant->isVariadic(),
2583: $variant->getTemplateTypeMap(),
2584: $variant->getResolvedTemplateTypeMap(),
2585: $variant instanceof ExtendedParametersAcceptor ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
2586: $templateTags,
2587: $throwPoints,
2588: $impurePoints,
2589: [],
2590: [],
2591: $acceptsNamedArguments,
2592: );
2593: }
2594:
2595: return TypeCombinator::union(...$closureTypes);
2596: }
2597:
2598: /** @api */
2599: public function getNativeType(Expr $expr): Type
2600: {
2601: return $this->promoteNativeTypes()->getType($expr);
2602: }
2603:
2604: public function getKeepVoidType(Expr $node): Type
2605: {
2606: $clonedNode = clone $node;
2607: $clonedNode->setAttribute(self::KEEP_VOID_ATTRIBUTE_NAME, true);
2608:
2609: return $this->getType($clonedNode);
2610: }
2611:
2612: public function doNotTreatPhpDocTypesAsCertain(): Scope
2613: {
2614: return $this->promoteNativeTypes();
2615: }
2616:
2617: private function promoteNativeTypes(): self
2618: {
2619: if ($this->nativeTypesPromoted) {
2620: return $this;
2621: }
2622:
2623: if ($this->scopeWithPromotedNativeTypes !== null) {
2624: return $this->scopeWithPromotedNativeTypes;
2625: }
2626:
2627: return $this->scopeWithPromotedNativeTypes = $this->scopeFactory->create(
2628: $this->context,
2629: $this->declareStrictTypes,
2630: $this->function,
2631: $this->namespace,
2632: $this->nativeExpressionTypes,
2633: [],
2634: [],
2635: $this->inClosureBindScopeClasses,
2636: $this->anonymousFunctionReflection,
2637: $this->inFirstLevelStatement,
2638: $this->currentlyAssignedExpressions,
2639: $this->currentlyAllowedUndefinedExpressions,
2640: $this->inFunctionCallsStack,
2641: $this->afterExtractCall,
2642: $this->parentScope,
2643: true,
2644: );
2645: }
2646:
2647: /**
2648: * @param Node\Expr\PropertyFetch|Node\Expr\StaticPropertyFetch $propertyFetch
2649: */
2650: private function hasPropertyNativeType($propertyFetch): bool
2651: {
2652: $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyFetch, $this);
2653: if ($propertyReflection === null) {
2654: return false;
2655: }
2656:
2657: if (!$propertyReflection->isNative()) {
2658: return false;
2659: }
2660:
2661: return !$propertyReflection->getNativeType() instanceof MixedType;
2662: }
2663:
2664: private function getTypeFromArrayDimFetch(
2665: Expr\ArrayDimFetch $arrayDimFetch,
2666: Type $offsetType,
2667: Type $offsetAccessibleType,
2668: ): Type
2669: {
2670: if ($arrayDimFetch->dim === null) {
2671: throw new ShouldNotHappenException();
2672: }
2673:
2674: if (!$offsetAccessibleType->isArray()->yes() && (new ObjectType(ArrayAccess::class))->isSuperTypeOf($offsetAccessibleType)->yes()) {
2675: return $this->getType(
2676: new MethodCall(
2677: $arrayDimFetch->var,
2678: new Node\Identifier('offsetGet'),
2679: [
2680: new Node\Arg($arrayDimFetch->dim),
2681: ],
2682: ),
2683: );
2684: }
2685:
2686: return $offsetAccessibleType->getOffsetValueType($offsetType);
2687: }
2688:
2689: private function resolveExactName(Name $name): ?string
2690: {
2691: $originalClass = (string) $name;
2692:
2693: switch (strtolower($originalClass)) {
2694: case 'self':
2695: if (!$this->isInClass()) {
2696: return null;
2697: }
2698: return $this->getClassReflection()->getName();
2699: case 'parent':
2700: if (!$this->isInClass()) {
2701: return null;
2702: }
2703: $currentClassReflection = $this->getClassReflection();
2704: if ($currentClassReflection->getParentClass() !== null) {
2705: return $currentClassReflection->getParentClass()->getName();
2706: }
2707: return null;
2708: case 'static':
2709: return null;
2710: }
2711:
2712: return $originalClass;
2713: }
2714:
2715: /** @api */
2716: public function resolveName(Name $name): string
2717: {
2718: $originalClass = (string) $name;
2719: if ($this->isInClass()) {
2720: $lowerClass = strtolower($originalClass);
2721: if (in_array($lowerClass, [
2722: 'self',
2723: 'static',
2724: ], true)) {
2725: if ($this->inClosureBindScopeClasses !== [] && $this->inClosureBindScopeClasses !== ['static']) {
2726: return $this->inClosureBindScopeClasses[0];
2727: }
2728: return $this->getClassReflection()->getName();
2729: } elseif ($lowerClass === 'parent') {
2730: $currentClassReflection = $this->getClassReflection();
2731: if ($currentClassReflection->getParentClass() !== null) {
2732: return $currentClassReflection->getParentClass()->getName();
2733: }
2734: }
2735: }
2736:
2737: return $originalClass;
2738: }
2739:
2740: /** @api */
2741: public function resolveTypeByName(Name $name): TypeWithClassName
2742: {
2743: if ($name->toLowerString() === 'static' && $this->isInClass()) {
2744: if ($this->inClosureBindScopeClasses !== [] && $this->inClosureBindScopeClasses !== ['static']) {
2745: if ($this->reflectionProvider->hasClass($this->inClosureBindScopeClasses[0])) {
2746: return new StaticType($this->reflectionProvider->getClass($this->inClosureBindScopeClasses[0]));
2747: }
2748: }
2749:
2750: return new StaticType($this->getClassReflection());
2751: }
2752:
2753: $originalClass = $this->resolveName($name);
2754: if ($this->isInClass()) {
2755: if ($this->inClosureBindScopeClasses === [$originalClass]) {
2756: if ($this->reflectionProvider->hasClass($originalClass)) {
2757: return new ThisType($this->reflectionProvider->getClass($originalClass));
2758: }
2759: return new ObjectType($originalClass);
2760: }
2761:
2762: $thisType = new ThisType($this->getClassReflection());
2763: $ancestor = $thisType->getAncestorWithClassName($originalClass);
2764: if ($ancestor !== null) {
2765: return $ancestor;
2766: }
2767: }
2768:
2769: return new ObjectType($originalClass);
2770: }
2771:
2772: private function resolveTypeByNameWithLateStaticBinding(Name $class, Node\Identifier $name): TypeWithClassName
2773: {
2774: $classType = $this->resolveTypeByName($class);
2775:
2776: if (
2777: $classType instanceof StaticType
2778: && !in_array($class->toLowerString(), ['self', 'static', 'parent'], true)
2779: ) {
2780: $methodReflectionCandidate = $this->getMethodReflection(
2781: $classType,
2782: $name->name,
2783: );
2784: if ($methodReflectionCandidate !== null && $methodReflectionCandidate->isStatic()) {
2785: $classType = $classType->getStaticObjectType();
2786: }
2787: }
2788:
2789: return $classType;
2790: }
2791:
2792: /**
2793: * @api
2794: * @param mixed $value
2795: */
2796: public function getTypeFromValue($value): Type
2797: {
2798: return ConstantTypeHelper::getTypeFromValue($value);
2799: }
2800:
2801: /** @api */
2802: public function hasExpressionType(Expr $node): TrinaryLogic
2803: {
2804: if ($node instanceof Variable && is_string($node->name)) {
2805: return $this->hasVariableType($node->name);
2806: }
2807:
2808: $exprString = $this->getNodeKey($node);
2809: if (!isset($this->expressionTypes[$exprString])) {
2810: return TrinaryLogic::createNo();
2811: }
2812: return $this->expressionTypes[$exprString]->getCertainty();
2813: }
2814:
2815: /**
2816: * @param MethodReflection|FunctionReflection|null $reflection
2817: */
2818: public function pushInFunctionCall($reflection, ?ParameterReflection $parameter): self
2819: {
2820: $stack = $this->inFunctionCallsStack;
2821: $stack[] = [$reflection, $parameter];
2822:
2823: return $this->scopeFactory->create(
2824: $this->context,
2825: $this->isDeclareStrictTypes(),
2826: $this->getFunction(),
2827: $this->getNamespace(),
2828: $this->expressionTypes,
2829: $this->nativeExpressionTypes,
2830: $this->conditionalExpressions,
2831: $this->inClosureBindScopeClasses,
2832: $this->anonymousFunctionReflection,
2833: $this->isInFirstLevelStatement(),
2834: $this->currentlyAssignedExpressions,
2835: $this->currentlyAllowedUndefinedExpressions,
2836: $stack,
2837: $this->afterExtractCall,
2838: $this->parentScope,
2839: $this->nativeTypesPromoted,
2840: );
2841: }
2842:
2843: public function popInFunctionCall(): self
2844: {
2845: $stack = $this->inFunctionCallsStack;
2846: array_pop($stack);
2847:
2848: return $this->scopeFactory->create(
2849: $this->context,
2850: $this->isDeclareStrictTypes(),
2851: $this->getFunction(),
2852: $this->getNamespace(),
2853: $this->expressionTypes,
2854: $this->nativeExpressionTypes,
2855: $this->conditionalExpressions,
2856: $this->inClosureBindScopeClasses,
2857: $this->anonymousFunctionReflection,
2858: $this->isInFirstLevelStatement(),
2859: $this->currentlyAssignedExpressions,
2860: $this->currentlyAllowedUndefinedExpressions,
2861: $stack,
2862: $this->afterExtractCall,
2863: $this->parentScope,
2864: $this->nativeTypesPromoted,
2865: );
2866: }
2867:
2868: /** @api */
2869: public function isInClassExists(string $className): bool
2870: {
2871: foreach ($this->inFunctionCallsStack as [$inFunctionCall]) {
2872: if (!$inFunctionCall instanceof FunctionReflection) {
2873: continue;
2874: }
2875:
2876: if (in_array($inFunctionCall->getName(), [
2877: 'class_exists',
2878: 'interface_exists',
2879: 'trait_exists',
2880: ], true)) {
2881: return true;
2882: }
2883: }
2884: $expr = new FuncCall(new FullyQualified('class_exists'), [
2885: new Arg(new String_(ltrim($className, '\\'))),
2886: ]);
2887:
2888: return $this->getType($expr)->isTrue()->yes();
2889: }
2890:
2891: public function getFunctionCallStack(): array
2892: {
2893: return array_values(array_filter(
2894: array_map(static fn ($values) => $values[0], $this->inFunctionCallsStack),
2895: static fn (FunctionReflection|MethodReflection|null $reflection) => $reflection !== null,
2896: ));
2897: }
2898:
2899: public function getFunctionCallStackWithParameters(): array
2900: {
2901: return array_values(array_filter(
2902: $this->inFunctionCallsStack,
2903: static fn ($item) => $item[0] !== null,
2904: ));
2905: }
2906:
2907: /** @api */
2908: public function isInFunctionExists(string $functionName): bool
2909: {
2910: $expr = new FuncCall(new FullyQualified('function_exists'), [
2911: new Arg(new String_(ltrim($functionName, '\\'))),
2912: ]);
2913:
2914: return $this->getType($expr)->isTrue()->yes();
2915: }
2916:
2917: /** @api */
2918: public function enterClass(ClassReflection $classReflection): self
2919: {
2920: $thisHolder = ExpressionTypeHolder::createYes(new Variable('this'), new ThisType($classReflection));
2921: $constantTypes = $this->getConstantTypes();
2922: $constantTypes['$this'] = $thisHolder;
2923: $nativeConstantTypes = $this->getNativeConstantTypes();
2924: $nativeConstantTypes['$this'] = $thisHolder;
2925:
2926: return $this->scopeFactory->create(
2927: $this->context->enterClass($classReflection),
2928: $this->isDeclareStrictTypes(),
2929: null,
2930: $this->getNamespace(),
2931: $constantTypes,
2932: $nativeConstantTypes,
2933: [],
2934: [],
2935: null,
2936: true,
2937: [],
2938: [],
2939: [],
2940: false,
2941: $classReflection->isAnonymous() ? $this : null,
2942: );
2943: }
2944:
2945: public function enterTrait(ClassReflection $traitReflection): self
2946: {
2947: $namespace = null;
2948: $traitName = $traitReflection->getName();
2949: $traitNameParts = explode('\\', $traitName);
2950: if (count($traitNameParts) > 1) {
2951: $namespace = implode('\\', array_slice($traitNameParts, 0, -1));
2952: }
2953: return $this->scopeFactory->create(
2954: $this->context->enterTrait($traitReflection),
2955: $this->isDeclareStrictTypes(),
2956: $this->getFunction(),
2957: $namespace,
2958: $this->expressionTypes,
2959: $this->nativeExpressionTypes,
2960: [],
2961: $this->inClosureBindScopeClasses,
2962: $this->anonymousFunctionReflection,
2963: );
2964: }
2965:
2966: /**
2967: * @api
2968: * @param Type[] $phpDocParameterTypes
2969: * @param Type[] $parameterOutTypes
2970: * @param array<string, bool> $immediatelyInvokedCallableParameters
2971: * @param array<string, Type> $phpDocClosureThisTypeParameters
2972: */
2973: public function enterClassMethod(
2974: Node\Stmt\ClassMethod $classMethod,
2975: TemplateTypeMap $templateTypeMap,
2976: array $phpDocParameterTypes,
2977: ?Type $phpDocReturnType,
2978: ?Type $throwType,
2979: ?string $deprecatedDescription,
2980: bool $isDeprecated,
2981: bool $isInternal,
2982: bool $isFinal,
2983: ?bool $isPure = null,
2984: bool $acceptsNamedArguments = true,
2985: ?Assertions $asserts = null,
2986: ?Type $selfOutType = null,
2987: ?string $phpDocComment = null,
2988: array $parameterOutTypes = [],
2989: array $immediatelyInvokedCallableParameters = [],
2990: array $phpDocClosureThisTypeParameters = [],
2991: ): self
2992: {
2993: if (!$this->isInClass()) {
2994: throw new ShouldNotHappenException();
2995: }
2996:
2997: return $this->enterFunctionLike(
2998: new PhpMethodFromParserNodeReflection(
2999: $this->getClassReflection(),
3000: $classMethod,
3001: $this->getFile(),
3002: $templateTypeMap,
3003: $this->getRealParameterTypes($classMethod),
3004: array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocParameterTypes),
3005: $this->getRealParameterDefaultValues($classMethod),
3006: $this->transformStaticType($this->getFunctionType($classMethod->returnType, false, false)),
3007: $phpDocReturnType !== null ? $this->transformStaticType(TemplateTypeHelper::toArgument($phpDocReturnType)) : null,
3008: $throwType,
3009: $deprecatedDescription,
3010: $isDeprecated,
3011: $isInternal,
3012: $isFinal,
3013: $isPure,
3014: $acceptsNamedArguments,
3015: $asserts ?? Assertions::createEmpty(),
3016: $selfOutType,
3017: $phpDocComment,
3018: array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $parameterOutTypes),
3019: $immediatelyInvokedCallableParameters,
3020: array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocClosureThisTypeParameters),
3021: ),
3022: !$classMethod->isStatic(),
3023: );
3024: }
3025:
3026: private function transformStaticType(Type $type): Type
3027: {
3028: return TypeTraverser::map($type, function (Type $type, callable $traverse): Type {
3029: if (!$this->isInClass()) {
3030: return $type;
3031: }
3032: if ($type instanceof StaticType) {
3033: $classReflection = $this->getClassReflection();
3034: $changedType = $type->changeBaseClass($classReflection);
3035: if ($classReflection->isFinal()) {
3036: $changedType = $changedType->getStaticObjectType();
3037: }
3038: return $traverse($changedType);
3039: }
3040:
3041: return $traverse($type);
3042: });
3043: }
3044:
3045: /**
3046: * @return Type[]
3047: */
3048: private function getRealParameterTypes(Node\FunctionLike $functionLike): array
3049: {
3050: $realParameterTypes = [];
3051: foreach ($functionLike->getParams() as $parameter) {
3052: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
3053: throw new ShouldNotHappenException();
3054: }
3055: $realParameterTypes[$parameter->var->name] = $this->getFunctionType(
3056: $parameter->type,
3057: $this->isParameterValueNullable($parameter) && $parameter->flags === 0,
3058: false,
3059: );
3060: }
3061:
3062: return $realParameterTypes;
3063: }
3064:
3065: /**
3066: * @return Type[]
3067: */
3068: private function getRealParameterDefaultValues(Node\FunctionLike $functionLike): array
3069: {
3070: $realParameterDefaultValues = [];
3071: foreach ($functionLike->getParams() as $parameter) {
3072: if ($parameter->default === null) {
3073: continue;
3074: }
3075: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
3076: throw new ShouldNotHappenException();
3077: }
3078: $realParameterDefaultValues[$parameter->var->name] = $this->getType($parameter->default);
3079: }
3080:
3081: return $realParameterDefaultValues;
3082: }
3083:
3084: /**
3085: * @api
3086: * @param Type[] $phpDocParameterTypes
3087: * @param Type[] $parameterOutTypes
3088: * @param array<string, bool> $immediatelyInvokedCallableParameters
3089: * @param array<string, Type> $phpDocClosureThisTypeParameters
3090: */
3091: public function enterFunction(
3092: Node\Stmt\Function_ $function,
3093: TemplateTypeMap $templateTypeMap,
3094: array $phpDocParameterTypes,
3095: ?Type $phpDocReturnType,
3096: ?Type $throwType,
3097: ?string $deprecatedDescription,
3098: bool $isDeprecated,
3099: bool $isInternal,
3100: ?bool $isPure = null,
3101: bool $acceptsNamedArguments = true,
3102: ?Assertions $asserts = null,
3103: ?string $phpDocComment = null,
3104: array $parameterOutTypes = [],
3105: array $immediatelyInvokedCallableParameters = [],
3106: array $phpDocClosureThisTypeParameters = [],
3107: ): self
3108: {
3109: return $this->enterFunctionLike(
3110: new PhpFunctionFromParserNodeReflection(
3111: $function,
3112: $this->getFile(),
3113: $templateTypeMap,
3114: $this->getRealParameterTypes($function),
3115: array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $phpDocParameterTypes),
3116: $this->getRealParameterDefaultValues($function),
3117: $this->getFunctionType($function->returnType, $function->returnType === null, false),
3118: $phpDocReturnType !== null ? TemplateTypeHelper::toArgument($phpDocReturnType) : null,
3119: $throwType,
3120: $deprecatedDescription,
3121: $isDeprecated,
3122: $isInternal,
3123: $isPure,
3124: $acceptsNamedArguments,
3125: $asserts ?? Assertions::createEmpty(),
3126: $phpDocComment,
3127: array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $parameterOutTypes),
3128: $immediatelyInvokedCallableParameters,
3129: $phpDocClosureThisTypeParameters,
3130: ),
3131: false,
3132: );
3133: }
3134:
3135: private function enterFunctionLike(
3136: PhpFunctionFromParserNodeReflection $functionReflection,
3137: bool $preserveThis,
3138: ): self
3139: {
3140: $parametersByName = [];
3141:
3142: foreach ($functionReflection->getParameters() as $parameter) {
3143: $parametersByName[$parameter->getName()] = $parameter;
3144: }
3145:
3146: $expressionTypes = [];
3147: $nativeExpressionTypes = [];
3148: $conditionalTypes = [];
3149: foreach ($functionReflection->getParameters() as $parameter) {
3150: $parameterType = $parameter->getType();
3151:
3152: if ($parameterType instanceof ConditionalTypeForParameter) {
3153: $targetParameterName = substr($parameterType->getParameterName(), 1);
3154: if (array_key_exists($targetParameterName, $parametersByName)) {
3155: $targetParameter = $parametersByName[$targetParameterName];
3156:
3157: $ifType = $parameterType->isNegated() ? $parameterType->getElse() : $parameterType->getIf();
3158: $elseType = $parameterType->isNegated() ? $parameterType->getIf() : $parameterType->getElse();
3159:
3160: $holder = new ConditionalExpressionHolder([
3161: $parameterType->getParameterName() => ExpressionTypeHolder::createYes(new Variable($targetParameterName), TypeCombinator::intersect($targetParameter->getType(), $parameterType->getTarget())),
3162: ], new ExpressionTypeHolder(new Variable($parameter->getName()), $ifType, TrinaryLogic::createYes()));
3163: $conditionalTypes['$' . $parameter->getName()][$holder->getKey()] = $holder;
3164:
3165: $holder = new ConditionalExpressionHolder([
3166: $parameterType->getParameterName() => ExpressionTypeHolder::createYes(new Variable($targetParameterName), TypeCombinator::remove($targetParameter->getType(), $parameterType->getTarget())),
3167: ], new ExpressionTypeHolder(new Variable($parameter->getName()), $elseType, TrinaryLogic::createYes()));
3168: $conditionalTypes['$' . $parameter->getName()][$holder->getKey()] = $holder;
3169: }
3170: }
3171:
3172: $paramExprString = '$' . $parameter->getName();
3173: if ($parameter->isVariadic()) {
3174: if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()->yes()) {
3175: $parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType);
3176: } else {
3177: $parameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $parameterType), new AccessoryArrayListType());
3178: }
3179: }
3180: $parameterNode = new Variable($parameter->getName());
3181: $expressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($parameterNode, $parameterType);
3182:
3183: $parameterOriginalValueExpr = new ParameterVariableOriginalValueExpr($parameter->getName());
3184: $parameterOriginalValueExprString = $this->getNodeKey($parameterOriginalValueExpr);
3185: $expressionTypes[$parameterOriginalValueExprString] = ExpressionTypeHolder::createYes($parameterOriginalValueExpr, $parameterType);
3186:
3187: $nativeParameterType = $parameter->getNativeType();
3188: if ($parameter->isVariadic()) {
3189: if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()->yes()) {
3190: $nativeParameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $nativeParameterType);
3191: } else {
3192: $nativeParameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $nativeParameterType), new AccessoryArrayListType());
3193: }
3194: }
3195: $nativeExpressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($parameterNode, $nativeParameterType);
3196: $nativeExpressionTypes[$parameterOriginalValueExprString] = ExpressionTypeHolder::createYes($parameterOriginalValueExpr, $nativeParameterType);
3197: }
3198:
3199: if ($preserveThis && array_key_exists('$this', $this->expressionTypes)) {
3200: $expressionTypes['$this'] = $this->expressionTypes['$this'];
3201: }
3202: if ($preserveThis && array_key_exists('$this', $this->nativeExpressionTypes)) {
3203: $nativeExpressionTypes['$this'] = $this->nativeExpressionTypes['$this'];
3204: }
3205:
3206: return $this->scopeFactory->create(
3207: $this->context,
3208: $this->isDeclareStrictTypes(),
3209: $functionReflection,
3210: $this->getNamespace(),
3211: array_merge($this->getConstantTypes(), $expressionTypes),
3212: array_merge($this->getNativeConstantTypes(), $nativeExpressionTypes),
3213: $conditionalTypes,
3214: );
3215: }
3216:
3217: /** @api */
3218: public function enterNamespace(string $namespaceName): self
3219: {
3220: return $this->scopeFactory->create(
3221: $this->context->beginFile(),
3222: $this->isDeclareStrictTypes(),
3223: null,
3224: $namespaceName,
3225: );
3226: }
3227:
3228: /**
3229: * @param list<string> $scopeClasses
3230: */
3231: public function enterClosureBind(?Type $thisType, ?Type $nativeThisType, array $scopeClasses): self
3232: {
3233: $expressionTypes = $this->expressionTypes;
3234: if ($thisType !== null) {
3235: $expressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $thisType);
3236: } else {
3237: unset($expressionTypes['$this']);
3238: }
3239:
3240: $nativeExpressionTypes = $this->nativeExpressionTypes;
3241: if ($nativeThisType !== null) {
3242: $nativeExpressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $nativeThisType);
3243: } else {
3244: unset($nativeExpressionTypes['$this']);
3245: }
3246:
3247: if ($scopeClasses === ['static'] && $this->isInClass()) {
3248: $scopeClasses = [$this->getClassReflection()->getName()];
3249: }
3250:
3251: return $this->scopeFactory->create(
3252: $this->context,
3253: $this->isDeclareStrictTypes(),
3254: $this->getFunction(),
3255: $this->getNamespace(),
3256: $expressionTypes,
3257: $nativeExpressionTypes,
3258: $this->conditionalExpressions,
3259: $scopeClasses,
3260: $this->anonymousFunctionReflection,
3261: );
3262: }
3263:
3264: public function restoreOriginalScopeAfterClosureBind(self $originalScope): self
3265: {
3266: $expressionTypes = $this->expressionTypes;
3267: if (isset($originalScope->expressionTypes['$this'])) {
3268: $expressionTypes['$this'] = $originalScope->expressionTypes['$this'];
3269: } else {
3270: unset($expressionTypes['$this']);
3271: }
3272:
3273: $nativeExpressionTypes = $this->nativeExpressionTypes;
3274: if (isset($originalScope->nativeExpressionTypes['$this'])) {
3275: $nativeExpressionTypes['$this'] = $originalScope->nativeExpressionTypes['$this'];
3276: } else {
3277: unset($nativeExpressionTypes['$this']);
3278: }
3279:
3280: return $this->scopeFactory->create(
3281: $this->context,
3282: $this->isDeclareStrictTypes(),
3283: $this->getFunction(),
3284: $this->getNamespace(),
3285: $expressionTypes,
3286: $nativeExpressionTypes,
3287: $this->conditionalExpressions,
3288: $originalScope->inClosureBindScopeClasses,
3289: $this->anonymousFunctionReflection,
3290: );
3291: }
3292:
3293: public function restoreThis(self $restoreThisScope): self
3294: {
3295: $expressionTypes = $this->expressionTypes;
3296: $nativeExpressionTypes = $this->nativeExpressionTypes;
3297:
3298: if ($restoreThisScope->isInClass()) {
3299: $nodeFinder = new NodeFinder();
3300: $cb = static fn ($expr) => $expr instanceof Variable && $expr->name === 'this';
3301: foreach ($restoreThisScope->expressionTypes as $exprString => $expressionTypeHolder) {
3302: $expr = $expressionTypeHolder->getExpr();
3303: $thisExpr = $nodeFinder->findFirst([$expr], $cb);
3304: if ($thisExpr === null) {
3305: continue;
3306: }
3307:
3308: $expressionTypes[$exprString] = $expressionTypeHolder;
3309: }
3310:
3311: foreach ($restoreThisScope->nativeExpressionTypes as $exprString => $expressionTypeHolder) {
3312: $expr = $expressionTypeHolder->getExpr();
3313: $thisExpr = $nodeFinder->findFirst([$expr], $cb);
3314: if ($thisExpr === null) {
3315: continue;
3316: }
3317:
3318: $nativeExpressionTypes[$exprString] = $expressionTypeHolder;
3319: }
3320: } else {
3321: unset($expressionTypes['$this']);
3322: unset($nativeExpressionTypes['$this']);
3323: }
3324:
3325: return $this->scopeFactory->create(
3326: $this->context,
3327: $this->isDeclareStrictTypes(),
3328: $this->getFunction(),
3329: $this->getNamespace(),
3330: $expressionTypes,
3331: $nativeExpressionTypes,
3332: $this->conditionalExpressions,
3333: $this->inClosureBindScopeClasses,
3334: $this->anonymousFunctionReflection,
3335: $this->inFirstLevelStatement,
3336: [],
3337: [],
3338: $this->inFunctionCallsStack,
3339: $this->afterExtractCall,
3340: $this->parentScope,
3341: $this->nativeTypesPromoted,
3342: );
3343: }
3344:
3345: public function enterClosureCall(Type $thisType, Type $nativeThisType): self
3346: {
3347: $expressionTypes = $this->expressionTypes;
3348: $expressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $thisType);
3349:
3350: $nativeExpressionTypes = $this->nativeExpressionTypes;
3351: $nativeExpressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $nativeThisType);
3352:
3353: return $this->scopeFactory->create(
3354: $this->context,
3355: $this->isDeclareStrictTypes(),
3356: $this->getFunction(),
3357: $this->getNamespace(),
3358: $expressionTypes,
3359: $nativeExpressionTypes,
3360: $this->conditionalExpressions,
3361: $thisType->getObjectClassNames(),
3362: $this->anonymousFunctionReflection,
3363: );
3364: }
3365:
3366: /** @api */
3367: public function isInClosureBind(): bool
3368: {
3369: return $this->inClosureBindScopeClasses !== [];
3370: }
3371:
3372: /**
3373: * @api
3374: * @param ParameterReflection[]|null $callableParameters
3375: */
3376: public function enterAnonymousFunction(
3377: Expr\Closure $closure,
3378: ?array $callableParameters,
3379: ): self
3380: {
3381: $anonymousFunctionReflection = $this->getType($closure);
3382: if (!$anonymousFunctionReflection instanceof ClosureType) {
3383: throw new ShouldNotHappenException();
3384: }
3385:
3386: $scope = $this->enterAnonymousFunctionWithoutReflection($closure, $callableParameters);
3387:
3388: return $this->scopeFactory->create(
3389: $scope->context,
3390: $scope->isDeclareStrictTypes(),
3391: $scope->getFunction(),
3392: $scope->getNamespace(),
3393: $scope->expressionTypes,
3394: $scope->nativeExpressionTypes,
3395: [],
3396: $scope->inClosureBindScopeClasses,
3397: $anonymousFunctionReflection,
3398: true,
3399: [],
3400: [],
3401: $this->inFunctionCallsStack,
3402: false,
3403: $this,
3404: $this->nativeTypesPromoted,
3405: );
3406: }
3407:
3408: /**
3409: * @param ParameterReflection[]|null $callableParameters
3410: */
3411: private function enterAnonymousFunctionWithoutReflection(
3412: Expr\Closure $closure,
3413: ?array $callableParameters,
3414: ): self
3415: {
3416: $expressionTypes = [];
3417: $nativeTypes = [];
3418: foreach ($closure->params as $i => $parameter) {
3419: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
3420: throw new ShouldNotHappenException();
3421: }
3422: $paramExprString = sprintf('$%s', $parameter->var->name);
3423: $isNullable = $this->isParameterValueNullable($parameter);
3424: $parameterType = $this->getFunctionType($parameter->type, $isNullable, $parameter->variadic);
3425: if ($callableParameters !== null) {
3426: if (isset($callableParameters[$i])) {
3427: $parameterType = self::intersectButNotNever($parameterType, $callableParameters[$i]->getType());
3428: } elseif (count($callableParameters) > 0) {
3429: $lastParameter = $callableParameters[count($callableParameters) - 1];
3430: if ($lastParameter->isVariadic()) {
3431: $parameterType = self::intersectButNotNever($parameterType, $lastParameter->getType());
3432: } else {
3433: $parameterType = self::intersectButNotNever($parameterType, new MixedType());
3434: }
3435: } else {
3436: $parameterType = self::intersectButNotNever($parameterType, new MixedType());
3437: }
3438: }
3439: $holder = ExpressionTypeHolder::createYes($parameter->var, $parameterType);
3440: $expressionTypes[$paramExprString] = $holder;
3441: $nativeTypes[$paramExprString] = $holder;
3442: }
3443:
3444: $nonRefVariableNames = [];
3445: foreach ($closure->uses as $use) {
3446: if (!is_string($use->var->name)) {
3447: throw new ShouldNotHappenException();
3448: }
3449: $variableName = $use->var->name;
3450: $paramExprString = '$' . $use->var->name;
3451: if ($use->byRef) {
3452: $holder = ExpressionTypeHolder::createYes($use->var, new MixedType());
3453: $expressionTypes[$paramExprString] = $holder;
3454: $nativeTypes[$paramExprString] = $holder;
3455: continue;
3456: }
3457: $nonRefVariableNames[$variableName] = true;
3458: if ($this->hasVariableType($variableName)->no()) {
3459: $variableType = new ErrorType();
3460: $variableNativeType = new ErrorType();
3461: } else {
3462: $variableType = $this->getVariableType($variableName);
3463: $variableNativeType = $this->getNativeType($use->var);
3464: }
3465: $expressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($use->var, $variableType);
3466: $nativeTypes[$paramExprString] = ExpressionTypeHolder::createYes($use->var, $variableNativeType);
3467: }
3468:
3469: foreach ($this->invalidateStaticExpressions($this->expressionTypes) as $exprString => $typeHolder) {
3470: $expr = $typeHolder->getExpr();
3471: if ($expr instanceof Variable) {
3472: continue;
3473: }
3474: $variables = (new NodeFinder())->findInstanceOf([$expr], Variable::class);
3475: if ($variables === [] && !$this->expressionTypeIsUnchangeable($typeHolder)) {
3476: continue;
3477: }
3478: foreach ($variables as $variable) {
3479: if (!is_string($variable->name)) {
3480: continue 2;
3481: }
3482: if (!array_key_exists($variable->name, $nonRefVariableNames)) {
3483: continue 2;
3484: }
3485: }
3486:
3487: $expressionTypes[$exprString] = $typeHolder;
3488: }
3489:
3490: if ($this->hasVariableType('this')->yes() && !$closure->static) {
3491: $node = new Variable('this');
3492: $expressionTypes['$this'] = ExpressionTypeHolder::createYes($node, $this->getType($node));
3493: $nativeTypes['$this'] = ExpressionTypeHolder::createYes($node, $this->getNativeType($node));
3494: }
3495:
3496: return $this->scopeFactory->create(
3497: $this->context,
3498: $this->isDeclareStrictTypes(),
3499: $this->getFunction(),
3500: $this->getNamespace(),
3501: array_merge($this->getConstantTypes(), $expressionTypes),
3502: array_merge($this->getNativeConstantTypes(), $nativeTypes),
3503: [],
3504: $this->inClosureBindScopeClasses,
3505: new TrivialParametersAcceptor(),
3506: true,
3507: [],
3508: [],
3509: [],
3510: false,
3511: $this,
3512: $this->nativeTypesPromoted,
3513: );
3514: }
3515:
3516: private function expressionTypeIsUnchangeable(ExpressionTypeHolder $typeHolder): bool
3517: {
3518: $expr = $typeHolder->getExpr();
3519: $type = $typeHolder->getType();
3520:
3521: return $expr instanceof FuncCall
3522: && !$expr->isFirstClassCallable()
3523: && $expr->name instanceof FullyQualified
3524: && $expr->name->toLowerString() === 'function_exists'
3525: && isset($expr->getArgs()[0])
3526: && count($this->getType($expr->getArgs()[0]->value)->getConstantStrings()) === 1
3527: && $type->isTrue()->yes();
3528: }
3529:
3530: /**
3531: * @param array<string, ExpressionTypeHolder> $expressionTypes
3532: * @return array<string, ExpressionTypeHolder>
3533: */
3534: private function invalidateStaticExpressions(array $expressionTypes): array
3535: {
3536: $filteredExpressionTypes = [];
3537: $nodeFinder = new NodeFinder();
3538: foreach ($expressionTypes as $exprString => $expressionType) {
3539: $staticExpression = $nodeFinder->findFirst(
3540: [$expressionType->getExpr()],
3541: static fn ($node) => $node instanceof Expr\StaticCall || $node instanceof Expr\StaticPropertyFetch,
3542: );
3543: if ($staticExpression !== null) {
3544: continue;
3545: }
3546: $filteredExpressionTypes[$exprString] = $expressionType;
3547: }
3548: return $filteredExpressionTypes;
3549: }
3550:
3551: /**
3552: * @api
3553: * @param ParameterReflection[]|null $callableParameters
3554: */
3555: public function enterArrowFunction(Expr\ArrowFunction $arrowFunction, ?array $callableParameters): self
3556: {
3557: $anonymousFunctionReflection = $this->getType($arrowFunction);
3558: if (!$anonymousFunctionReflection instanceof ClosureType) {
3559: throw new ShouldNotHappenException();
3560: }
3561:
3562: $scope = $this->enterArrowFunctionWithoutReflection($arrowFunction, $callableParameters);
3563:
3564: return $this->scopeFactory->create(
3565: $scope->context,
3566: $scope->isDeclareStrictTypes(),
3567: $scope->getFunction(),
3568: $scope->getNamespace(),
3569: $scope->expressionTypes,
3570: $scope->nativeExpressionTypes,
3571: $scope->conditionalExpressions,
3572: $scope->inClosureBindScopeClasses,
3573: $anonymousFunctionReflection,
3574: true,
3575: [],
3576: [],
3577: $this->inFunctionCallsStack,
3578: $scope->afterExtractCall,
3579: $scope->parentScope,
3580: $this->nativeTypesPromoted,
3581: );
3582: }
3583:
3584: /**
3585: * @param ParameterReflection[]|null $callableParameters
3586: */
3587: private function enterArrowFunctionWithoutReflection(Expr\ArrowFunction $arrowFunction, ?array $callableParameters): self
3588: {
3589: $arrowFunctionScope = $this;
3590: foreach ($arrowFunction->params as $i => $parameter) {
3591: if ($parameter->type === null) {
3592: $parameterType = new MixedType();
3593: } else {
3594: $isNullable = $this->isParameterValueNullable($parameter);
3595: $parameterType = $this->getFunctionType($parameter->type, $isNullable, $parameter->variadic);
3596: }
3597:
3598: if ($callableParameters !== null) {
3599: if (isset($callableParameters[$i])) {
3600: $parameterType = self::intersectButNotNever($parameterType, $callableParameters[$i]->getType());
3601: } elseif (count($callableParameters) > 0) {
3602: $lastParameter = $callableParameters[count($callableParameters) - 1];
3603: if ($lastParameter->isVariadic()) {
3604: $parameterType = self::intersectButNotNever($parameterType, $lastParameter->getType());
3605: } else {
3606: $parameterType = self::intersectButNotNever($parameterType, new MixedType());
3607: }
3608: } else {
3609: $parameterType = self::intersectButNotNever($parameterType, new MixedType());
3610: }
3611: }
3612:
3613: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
3614: throw new ShouldNotHappenException();
3615: }
3616: $arrowFunctionScope = $arrowFunctionScope->assignVariable($parameter->var->name, $parameterType, $parameterType, TrinaryLogic::createYes());
3617: }
3618:
3619: if ($arrowFunction->static) {
3620: $arrowFunctionScope = $arrowFunctionScope->invalidateExpression(new Variable('this'));
3621: }
3622:
3623: return $this->scopeFactory->create(
3624: $arrowFunctionScope->context,
3625: $this->isDeclareStrictTypes(),
3626: $arrowFunctionScope->getFunction(),
3627: $arrowFunctionScope->getNamespace(),
3628: $this->invalidateStaticExpressions($arrowFunctionScope->expressionTypes),
3629: $arrowFunctionScope->nativeExpressionTypes,
3630: $arrowFunctionScope->conditionalExpressions,
3631: $arrowFunctionScope->inClosureBindScopeClasses,
3632: new TrivialParametersAcceptor(),
3633: true,
3634: [],
3635: [],
3636: [],
3637: $arrowFunctionScope->afterExtractCall,
3638: $arrowFunctionScope->parentScope,
3639: $this->nativeTypesPromoted,
3640: );
3641: }
3642:
3643: public function isParameterValueNullable(Node\Param $parameter): bool
3644: {
3645: if ($parameter->default instanceof ConstFetch) {
3646: return strtolower((string) $parameter->default->name) === 'null';
3647: }
3648:
3649: return false;
3650: }
3651:
3652: /**
3653: * @api
3654: * @param Node\Name|Node\Identifier|Node\ComplexType|null $type
3655: */
3656: public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type
3657: {
3658: if ($isNullable) {
3659: return TypeCombinator::addNull(
3660: $this->getFunctionType($type, false, $isVariadic),
3661: );
3662: }
3663: if ($isVariadic) {
3664: if ($this->phpVersion->supportsNamedArguments()) {
3665: return new ArrayType(new UnionType([new IntegerType(), new StringType()]), $this->getFunctionType(
3666: $type,
3667: false,
3668: false,
3669: ));
3670: }
3671:
3672: return TypeCombinator::intersect(new ArrayType(new IntegerType(), $this->getFunctionType(
3673: $type,
3674: false,
3675: false,
3676: )), new AccessoryArrayListType());
3677: }
3678:
3679: if ($type instanceof Name) {
3680: $className = (string) $type;
3681: $lowercasedClassName = strtolower($className);
3682: if ($lowercasedClassName === 'parent') {
3683: if ($this->isInClass() && $this->getClassReflection()->getParentClass() !== null) {
3684: return new ObjectType($this->getClassReflection()->getParentClass()->getName());
3685: }
3686:
3687: return new NonexistentParentClassType();
3688: }
3689: }
3690:
3691: return ParserNodeTypeToPHPStanType::resolve($type, $this->isInClass() ? $this->getClassReflection() : null);
3692: }
3693:
3694: private static function intersectButNotNever(Type $nativeType, Type $inferredType): Type
3695: {
3696: if ($nativeType->isSuperTypeOf($inferredType)->no()) {
3697: return $nativeType;
3698: }
3699:
3700: $result = TypeCombinator::intersect($nativeType, $inferredType);
3701: if (TypeCombinator::containsNull($nativeType)) {
3702: return TypeCombinator::addNull($result);
3703: }
3704:
3705: return $result;
3706: }
3707:
3708: public function enterMatch(Expr\Match_ $expr): self
3709: {
3710: if ($expr->cond instanceof Variable) {
3711: return $this;
3712: }
3713: if ($expr->cond instanceof AlwaysRememberedExpr) {
3714: $cond = $expr->cond->expr;
3715: } else {
3716: $cond = $expr->cond;
3717: }
3718:
3719: $type = $this->getType($cond);
3720: $nativeType = $this->getNativeType($cond);
3721: $condExpr = new AlwaysRememberedExpr($cond, $type, $nativeType);
3722: $expr->cond = $condExpr;
3723:
3724: return $this->assignExpression($condExpr, $type, $nativeType);
3725: }
3726:
3727: public function enterForeach(self $originalScope, Expr $iteratee, string $valueName, ?string $keyName): self
3728: {
3729: $iterateeType = $originalScope->getType($iteratee);
3730: $nativeIterateeType = $originalScope->getNativeType($iteratee);
3731: $scope = $this->assignVariable(
3732: $valueName,
3733: $originalScope->getIterableValueType($iterateeType),
3734: $originalScope->getIterableValueType($nativeIterateeType),
3735: TrinaryLogic::createYes(),
3736: );
3737: if ($keyName !== null) {
3738: $scope = $scope->enterForeachKey($originalScope, $iteratee, $keyName);
3739: }
3740:
3741: return $scope;
3742: }
3743:
3744: public function enterForeachKey(self $originalScope, Expr $iteratee, string $keyName): self
3745: {
3746: $iterateeType = $originalScope->getType($iteratee);
3747: $nativeIterateeType = $originalScope->getNativeType($iteratee);
3748: $scope = $this->assignVariable(
3749: $keyName,
3750: $originalScope->getIterableKeyType($iterateeType),
3751: $originalScope->getIterableKeyType($nativeIterateeType),
3752: TrinaryLogic::createYes(),
3753: );
3754:
3755: if ($iterateeType->isArray()->yes()) {
3756: $scope = $scope->assignExpression(
3757: new Expr\ArrayDimFetch($iteratee, new Variable($keyName)),
3758: $originalScope->getIterableValueType($iterateeType),
3759: $originalScope->getIterableValueType($nativeIterateeType),
3760: );
3761: }
3762:
3763: return $scope;
3764: }
3765:
3766: public function enterCatchType(Type $catchType, ?string $variableName): self
3767: {
3768: if ($variableName === null) {
3769: return $this;
3770: }
3771:
3772: return $this->assignVariable(
3773: $variableName,
3774: TypeCombinator::intersect($catchType, new ObjectType(Throwable::class)),
3775: TypeCombinator::intersect($catchType, new ObjectType(Throwable::class)),
3776: TrinaryLogic::createYes(),
3777: );
3778: }
3779:
3780: public function enterExpressionAssign(Expr $expr): self
3781: {
3782: $exprString = $this->getNodeKey($expr);
3783: $currentlyAssignedExpressions = $this->currentlyAssignedExpressions;
3784: $currentlyAssignedExpressions[$exprString] = true;
3785:
3786: $scope = $this->scopeFactory->create(
3787: $this->context,
3788: $this->isDeclareStrictTypes(),
3789: $this->getFunction(),
3790: $this->getNamespace(),
3791: $this->expressionTypes,
3792: $this->nativeExpressionTypes,
3793: $this->conditionalExpressions,
3794: $this->inClosureBindScopeClasses,
3795: $this->anonymousFunctionReflection,
3796: $this->isInFirstLevelStatement(),
3797: $currentlyAssignedExpressions,
3798: $this->currentlyAllowedUndefinedExpressions,
3799: [],
3800: $this->afterExtractCall,
3801: $this->parentScope,
3802: $this->nativeTypesPromoted,
3803: );
3804: $scope->resolvedTypes = $this->resolvedTypes;
3805: $scope->truthyScopes = $this->truthyScopes;
3806: $scope->falseyScopes = $this->falseyScopes;
3807:
3808: return $scope;
3809: }
3810:
3811: public function exitExpressionAssign(Expr $expr): self
3812: {
3813: $exprString = $this->getNodeKey($expr);
3814: $currentlyAssignedExpressions = $this->currentlyAssignedExpressions;
3815: unset($currentlyAssignedExpressions[$exprString]);
3816:
3817: $scope = $this->scopeFactory->create(
3818: $this->context,
3819: $this->isDeclareStrictTypes(),
3820: $this->getFunction(),
3821: $this->getNamespace(),
3822: $this->expressionTypes,
3823: $this->nativeExpressionTypes,
3824: $this->conditionalExpressions,
3825: $this->inClosureBindScopeClasses,
3826: $this->anonymousFunctionReflection,
3827: $this->isInFirstLevelStatement(),
3828: $currentlyAssignedExpressions,
3829: $this->currentlyAllowedUndefinedExpressions,
3830: [],
3831: $this->afterExtractCall,
3832: $this->parentScope,
3833: $this->nativeTypesPromoted,
3834: );
3835: $scope->resolvedTypes = $this->resolvedTypes;
3836: $scope->truthyScopes = $this->truthyScopes;
3837: $scope->falseyScopes = $this->falseyScopes;
3838:
3839: return $scope;
3840: }
3841:
3842: /** @api */
3843: public function isInExpressionAssign(Expr $expr): bool
3844: {
3845: $exprString = $this->getNodeKey($expr);
3846: return array_key_exists($exprString, $this->currentlyAssignedExpressions);
3847: }
3848:
3849: public function setAllowedUndefinedExpression(Expr $expr): self
3850: {
3851: if ($expr instanceof Expr\StaticPropertyFetch) {
3852: return $this;
3853: }
3854:
3855: $exprString = $this->getNodeKey($expr);
3856: $currentlyAllowedUndefinedExpressions = $this->currentlyAllowedUndefinedExpressions;
3857: $currentlyAllowedUndefinedExpressions[$exprString] = true;
3858:
3859: $scope = $this->scopeFactory->create(
3860: $this->context,
3861: $this->isDeclareStrictTypes(),
3862: $this->getFunction(),
3863: $this->getNamespace(),
3864: $this->expressionTypes,
3865: $this->nativeExpressionTypes,
3866: $this->conditionalExpressions,
3867: $this->inClosureBindScopeClasses,
3868: $this->anonymousFunctionReflection,
3869: $this->isInFirstLevelStatement(),
3870: $this->currentlyAssignedExpressions,
3871: $currentlyAllowedUndefinedExpressions,
3872: [],
3873: $this->afterExtractCall,
3874: $this->parentScope,
3875: $this->nativeTypesPromoted,
3876: );
3877: $scope->resolvedTypes = $this->resolvedTypes;
3878: $scope->truthyScopes = $this->truthyScopes;
3879: $scope->falseyScopes = $this->falseyScopes;
3880:
3881: return $scope;
3882: }
3883:
3884: public function unsetAllowedUndefinedExpression(Expr $expr): self
3885: {
3886: $exprString = $this->getNodeKey($expr);
3887: $currentlyAllowedUndefinedExpressions = $this->currentlyAllowedUndefinedExpressions;
3888: unset($currentlyAllowedUndefinedExpressions[$exprString]);
3889:
3890: $scope = $this->scopeFactory->create(
3891: $this->context,
3892: $this->isDeclareStrictTypes(),
3893: $this->getFunction(),
3894: $this->getNamespace(),
3895: $this->expressionTypes,
3896: $this->nativeExpressionTypes,
3897: $this->conditionalExpressions,
3898: $this->inClosureBindScopeClasses,
3899: $this->anonymousFunctionReflection,
3900: $this->isInFirstLevelStatement(),
3901: $this->currentlyAssignedExpressions,
3902: $currentlyAllowedUndefinedExpressions,
3903: [],
3904: $this->afterExtractCall,
3905: $this->parentScope,
3906: $this->nativeTypesPromoted,
3907: );
3908: $scope->resolvedTypes = $this->resolvedTypes;
3909: $scope->truthyScopes = $this->truthyScopes;
3910: $scope->falseyScopes = $this->falseyScopes;
3911:
3912: return $scope;
3913: }
3914:
3915: /** @api */
3916: public function isUndefinedExpressionAllowed(Expr $expr): bool
3917: {
3918: $exprString = $this->getNodeKey($expr);
3919: return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions);
3920: }
3921:
3922: public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty): self
3923: {
3924: $node = new Variable($variableName);
3925: $scope = $this->assignExpression($node, $type, $nativeType);
3926: if ($certainty->no()) {
3927: throw new ShouldNotHappenException();
3928: } elseif (!$certainty->yes()) {
3929: $exprString = '$' . $variableName;
3930: $scope->expressionTypes[$exprString] = new ExpressionTypeHolder($node, $type, $certainty);
3931: $scope->nativeExpressionTypes[$exprString] = new ExpressionTypeHolder($node, $nativeType, $certainty);
3932: }
3933:
3934: $parameterOriginalValueExprString = $this->getNodeKey(new ParameterVariableOriginalValueExpr($variableName));
3935: unset($scope->expressionTypes[$parameterOriginalValueExprString]);
3936: unset($scope->nativeExpressionTypes[$parameterOriginalValueExprString]);
3937:
3938: return $scope;
3939: }
3940:
3941: public function unsetExpression(Expr $expr): self
3942: {
3943: $scope = $this;
3944: if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
3945: $exprVarType = $scope->getType($expr->var);
3946: $dimType = $scope->getType($expr->dim);
3947: $unsetType = $exprVarType->unsetOffset($dimType);
3948: $exprVarNativeType = $scope->getNativeType($expr->var);
3949: $dimNativeType = $scope->getNativeType($expr->dim);
3950: $unsetNativeType = $exprVarNativeType->unsetOffset($dimNativeType);
3951: $scope = $scope->assignExpression($expr->var, $unsetType, $unsetNativeType)->invalidateExpression(
3952: new FuncCall(new FullyQualified('count'), [new Arg($expr->var)]),
3953: )->invalidateExpression(
3954: new FuncCall(new FullyQualified('sizeof'), [new Arg($expr->var)]),
3955: )->invalidateExpression(
3956: new FuncCall(new Name('count'), [new Arg($expr->var)]),
3957: )->invalidateExpression(
3958: new FuncCall(new Name('sizeof'), [new Arg($expr->var)]),
3959: );
3960:
3961: if ($expr->var instanceof Expr\ArrayDimFetch && $expr->var->dim !== null) {
3962: $scope = $scope->assignExpression(
3963: $expr->var->var,
3964: $this->getType($expr->var->var)->setOffsetValueType(
3965: $scope->getType($expr->var->dim),
3966: $scope->getType($expr->var),
3967: ),
3968: $this->getNativeType($expr->var->var)->setOffsetValueType(
3969: $scope->getNativeType($expr->var->dim),
3970: $scope->getNativeType($expr->var),
3971: ),
3972: );
3973: }
3974: }
3975:
3976: return $scope->invalidateExpression($expr);
3977: }
3978:
3979: public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, TrinaryLogic $certainty): self
3980: {
3981: if ($expr instanceof ConstFetch) {
3982: $loweredConstName = strtolower($expr->name->toString());
3983: if (in_array($loweredConstName, ['true', 'false', 'null'], true)) {
3984: return $this;
3985: }
3986: }
3987:
3988: if ($expr instanceof FuncCall && $expr->name instanceof Name && $type->isFalse()->yes()) {
3989: $functionName = $this->reflectionProvider->resolveFunctionName($expr->name, $this);
3990: if ($functionName !== null && in_array(strtolower($functionName), [
3991: 'is_dir',
3992: 'is_file',
3993: 'file_exists',
3994: ], true)) {
3995: return $this;
3996: }
3997: }
3998:
3999: $scope = $this;
4000: if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
4001: $dimType = $scope->getType($expr->dim)->toArrayKey();
4002: if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) {
4003: $exprVarType = $scope->getType($expr->var);
4004: if (!$exprVarType instanceof MixedType && !$exprVarType->isArray()->no()) {
4005: $types = [
4006: new ArrayType(new MixedType(), new MixedType()),
4007: new ObjectType(ArrayAccess::class),
4008: new NullType(),
4009: ];
4010: if ($dimType instanceof ConstantIntegerType) {
4011: $types[] = new StringType();
4012: }
4013:
4014: $scope = $scope->specifyExpressionType(
4015: $expr->var,
4016: TypeCombinator::intersect(
4017: TypeCombinator::intersect($exprVarType, TypeCombinator::union(...$types)),
4018: new HasOffsetValueType($dimType, $type),
4019: ),
4020: $scope->getNativeType($expr->var),
4021: $certainty,
4022: );
4023: }
4024: }
4025: }
4026:
4027: if ($certainty->no()) {
4028: throw new ShouldNotHappenException();
4029: }
4030:
4031: $exprString = $this->getNodeKey($expr);
4032: $expressionTypes = $scope->expressionTypes;
4033: $expressionTypes[$exprString] = new ExpressionTypeHolder($expr, $type, $certainty);
4034: $nativeTypes = $scope->nativeExpressionTypes;
4035: $nativeTypes[$exprString] = new ExpressionTypeHolder($expr, $nativeType, $certainty);
4036:
4037: $scope = $this->scopeFactory->create(
4038: $this->context,
4039: $this->isDeclareStrictTypes(),
4040: $this->getFunction(),
4041: $this->getNamespace(),
4042: $expressionTypes,
4043: $nativeTypes,
4044: $this->conditionalExpressions,
4045: $this->inClosureBindScopeClasses,
4046: $this->anonymousFunctionReflection,
4047: $this->inFirstLevelStatement,
4048: $this->currentlyAssignedExpressions,
4049: $this->currentlyAllowedUndefinedExpressions,
4050: $this->inFunctionCallsStack,
4051: $this->afterExtractCall,
4052: $this->parentScope,
4053: $this->nativeTypesPromoted,
4054: );
4055:
4056: if ($expr instanceof AlwaysRememberedExpr) {
4057: return $scope->specifyExpressionType($expr->expr, $type, $nativeType, $certainty);
4058: }
4059:
4060: return $scope;
4061: }
4062:
4063: public function assignExpression(Expr $expr, Type $type, Type $nativeType): self
4064: {
4065: $scope = $this;
4066: if ($expr instanceof PropertyFetch) {
4067: $scope = $this->invalidateExpression($expr)
4068: ->invalidateMethodsOnExpression($expr->var);
4069: } elseif ($expr instanceof Expr\StaticPropertyFetch) {
4070: $scope = $this->invalidateExpression($expr);
4071: } elseif ($expr instanceof Variable) {
4072: $scope = $this->invalidateExpression($expr);
4073: }
4074:
4075: return $scope->specifyExpressionType($expr, $type, $nativeType, TrinaryLogic::createYes());
4076: }
4077:
4078: public function assignInitializedProperty(Type $fetchedOnType, string $propertyName): self
4079: {
4080: if (!$this->isInClass()) {
4081: return $this;
4082: }
4083:
4084: if (TypeUtils::findThisType($fetchedOnType) === null) {
4085: return $this;
4086: }
4087:
4088: $propertyReflection = $this->getPropertyReflection($fetchedOnType, $propertyName);
4089: if ($propertyReflection === null) {
4090: return $this;
4091: }
4092: $declaringClass = $propertyReflection->getDeclaringClass();
4093: if ($this->getClassReflection()->getName() !== $declaringClass->getName()) {
4094: return $this;
4095: }
4096: if (!$declaringClass->hasNativeProperty($propertyName)) {
4097: return $this;
4098: }
4099:
4100: return $this->assignExpression(new PropertyInitializationExpr($propertyName), new MixedType(), new MixedType());
4101: }
4102:
4103: public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false): self
4104: {
4105: $expressionTypes = $this->expressionTypes;
4106: $nativeExpressionTypes = $this->nativeExpressionTypes;
4107: $invalidated = false;
4108: $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate);
4109:
4110: foreach ($expressionTypes as $exprString => $exprTypeHolder) {
4111: $exprExpr = $exprTypeHolder->getExpr();
4112: if (!$this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $exprExpr, $requireMoreCharacters)) {
4113: continue;
4114: }
4115:
4116: unset($expressionTypes[$exprString]);
4117: unset($nativeExpressionTypes[$exprString]);
4118: $invalidated = true;
4119: }
4120:
4121: $newConditionalExpressions = [];
4122: foreach ($this->conditionalExpressions as $conditionalExprString => $holders) {
4123: if (count($holders) === 0) {
4124: continue;
4125: }
4126: if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $holders[array_key_first($holders)]->getTypeHolder()->getExpr())) {
4127: $invalidated = true;
4128: continue;
4129: }
4130: foreach ($holders as $holder) {
4131: $conditionalTypeHolders = $holder->getConditionExpressionTypeHolders();
4132: foreach ($conditionalTypeHolders as $conditionalTypeHolder) {
4133: if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $conditionalTypeHolder->getExpr())) {
4134: $invalidated = true;
4135: continue 3;
4136: }
4137: }
4138: }
4139: $newConditionalExpressions[$conditionalExprString] = $holders;
4140: }
4141:
4142: if (!$invalidated) {
4143: return $this;
4144: }
4145:
4146: return $this->scopeFactory->create(
4147: $this->context,
4148: $this->isDeclareStrictTypes(),
4149: $this->getFunction(),
4150: $this->getNamespace(),
4151: $expressionTypes,
4152: $nativeExpressionTypes,
4153: $newConditionalExpressions,
4154: $this->inClosureBindScopeClasses,
4155: $this->anonymousFunctionReflection,
4156: $this->inFirstLevelStatement,
4157: $this->currentlyAssignedExpressions,
4158: $this->currentlyAllowedUndefinedExpressions,
4159: [],
4160: $this->afterExtractCall,
4161: $this->parentScope,
4162: $this->nativeTypesPromoted,
4163: );
4164: }
4165:
4166: private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr $exprToInvalidate, Expr $expr, bool $requireMoreCharacters = false): bool
4167: {
4168: if ($requireMoreCharacters && $exprStringToInvalidate === $this->getNodeKey($expr)) {
4169: return false;
4170: }
4171:
4172: // Variables will not contain traversable expressions. skip the NodeFinder overhead
4173: if ($expr instanceof Variable && is_string($expr->name) && !$requireMoreCharacters) {
4174: return $exprStringToInvalidate === $this->getNodeKey($expr);
4175: }
4176:
4177: $nodeFinder = new NodeFinder();
4178: $expressionToInvalidateClass = get_class($exprToInvalidate);
4179: $found = $nodeFinder->findFirst([$expr], function (Node $node) use ($expressionToInvalidateClass, $exprStringToInvalidate): bool {
4180: if (!$node instanceof $expressionToInvalidateClass) {
4181: return false;
4182: }
4183:
4184: $nodeString = $this->getNodeKey($node);
4185:
4186: return $nodeString === $exprStringToInvalidate;
4187: });
4188:
4189: if ($found === null) {
4190: return false;
4191: }
4192:
4193: if ($this->phpVersion->supportsReadOnlyProperties() && $expr instanceof PropertyFetch && $expr->name instanceof Node\Identifier && $requireMoreCharacters) {
4194: $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $this);
4195: if ($propertyReflection !== null) {
4196: $nativePropertyReflection = $propertyReflection->getNativeReflection();
4197: if ($nativePropertyReflection !== null && $nativePropertyReflection->isReadOnly()) {
4198: return false;
4199: }
4200: }
4201: }
4202:
4203: return true;
4204: }
4205:
4206: private function invalidateMethodsOnExpression(Expr $expressionToInvalidate): self
4207: {
4208: $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate);
4209: $expressionTypes = $this->expressionTypes;
4210: $nativeExpressionTypes = $this->nativeExpressionTypes;
4211: $invalidated = false;
4212: $nodeFinder = new NodeFinder();
4213: foreach ($expressionTypes as $exprString => $exprTypeHolder) {
4214: $expr = $exprTypeHolder->getExpr();
4215: $found = $nodeFinder->findFirst([$expr], function (Node $node) use ($exprStringToInvalidate): bool {
4216: if (!$node instanceof MethodCall) {
4217: return false;
4218: }
4219:
4220: return $this->getNodeKey($node->var) === $exprStringToInvalidate;
4221: });
4222: if ($found === null) {
4223: continue;
4224: }
4225:
4226: unset($expressionTypes[$exprString]);
4227: unset($nativeExpressionTypes[$exprString]);
4228: $invalidated = true;
4229: }
4230:
4231: if (!$invalidated) {
4232: return $this;
4233: }
4234:
4235: return $this->scopeFactory->create(
4236: $this->context,
4237: $this->isDeclareStrictTypes(),
4238: $this->getFunction(),
4239: $this->getNamespace(),
4240: $expressionTypes,
4241: $nativeExpressionTypes,
4242: $this->conditionalExpressions,
4243: $this->inClosureBindScopeClasses,
4244: $this->anonymousFunctionReflection,
4245: $this->inFirstLevelStatement,
4246: $this->currentlyAssignedExpressions,
4247: $this->currentlyAllowedUndefinedExpressions,
4248: [],
4249: $this->afterExtractCall,
4250: $this->parentScope,
4251: $this->nativeTypesPromoted,
4252: );
4253: }
4254:
4255: private function setExpressionCertainty(Expr $expr, TrinaryLogic $certainty): self
4256: {
4257: if ($this->hasExpressionType($expr)->no()) {
4258: throw new ShouldNotHappenException();
4259: }
4260:
4261: $originalExprType = $this->getType($expr);
4262: $nativeType = $this->getNativeType($expr);
4263:
4264: return $this->specifyExpressionType(
4265: $expr,
4266: $originalExprType,
4267: $nativeType,
4268: $certainty,
4269: );
4270: }
4271:
4272: public function addTypeToExpression(Expr $expr, Type $type): self
4273: {
4274: $originalExprType = $this->getType($expr);
4275: $nativeType = $this->getNativeType($expr);
4276:
4277: if ($originalExprType->equals($nativeType)) {
4278: $newType = TypeCombinator::intersect($type, $originalExprType);
4279: return $this->specifyExpressionType($expr, $newType, $newType, TrinaryLogic::createYes());
4280: }
4281:
4282: return $this->specifyExpressionType(
4283: $expr,
4284: TypeCombinator::intersect($type, $originalExprType),
4285: TypeCombinator::intersect($type, $nativeType),
4286: TrinaryLogic::createYes(),
4287: );
4288: }
4289:
4290: public function removeTypeFromExpression(Expr $expr, Type $typeToRemove): self
4291: {
4292: $exprType = $this->getType($expr);
4293: if (
4294: $exprType instanceof NeverType ||
4295: $typeToRemove instanceof NeverType
4296: ) {
4297: return $this;
4298: }
4299: return $this->specifyExpressionType(
4300: $expr,
4301: TypeCombinator::remove($exprType, $typeToRemove),
4302: TypeCombinator::remove($this->getNativeType($expr), $typeToRemove),
4303: TrinaryLogic::createYes(),
4304: );
4305: }
4306:
4307: /**
4308: * @api
4309: * @return MutatingScope
4310: */
4311: public function filterByTruthyValue(Expr $expr): Scope
4312: {
4313: $exprString = $this->getNodeKey($expr);
4314: if (array_key_exists($exprString, $this->truthyScopes)) {
4315: return $this->truthyScopes[$exprString];
4316: }
4317:
4318: $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this, $expr, TypeSpecifierContext::createTruthy());
4319: $scope = $this->filterBySpecifiedTypes($specifiedTypes);
4320: $this->truthyScopes[$exprString] = $scope;
4321:
4322: return $scope;
4323: }
4324:
4325: /**
4326: * @api
4327: * @return MutatingScope
4328: */
4329: public function filterByFalseyValue(Expr $expr): Scope
4330: {
4331: $exprString = $this->getNodeKey($expr);
4332: if (array_key_exists($exprString, $this->falseyScopes)) {
4333: return $this->falseyScopes[$exprString];
4334: }
4335:
4336: $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this, $expr, TypeSpecifierContext::createFalsey());
4337: $scope = $this->filterBySpecifiedTypes($specifiedTypes);
4338: $this->falseyScopes[$exprString] = $scope;
4339:
4340: return $scope;
4341: }
4342:
4343: public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
4344: {
4345: $typeSpecifications = [];
4346: foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $type]) {
4347: if ($expr instanceof Node\Scalar || $expr instanceof Array_ || $expr instanceof Expr\UnaryMinus && $expr->expr instanceof Node\Scalar) {
4348: continue;
4349: }
4350: $typeSpecifications[] = [
4351: 'sure' => true,
4352: 'exprString' => (string) $exprString,
4353: 'expr' => $expr,
4354: 'type' => $type,
4355: ];
4356: }
4357: foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $type]) {
4358: if ($expr instanceof Node\Scalar || $expr instanceof Array_ || $expr instanceof Expr\UnaryMinus && $expr->expr instanceof Node\Scalar) {
4359: continue;
4360: }
4361: $typeSpecifications[] = [
4362: 'sure' => false,
4363: 'exprString' => (string) $exprString,
4364: 'expr' => $expr,
4365: 'type' => $type,
4366: ];
4367: }
4368:
4369: usort($typeSpecifications, static function (array $a, array $b): int {
4370: $length = strlen($a['exprString']) - strlen($b['exprString']);
4371: if ($length !== 0) {
4372: return $length;
4373: }
4374:
4375: return $b['sure'] - $a['sure']; // @phpstan-ignore minus.leftNonNumeric, minus.rightNonNumeric
4376: });
4377:
4378: $scope = $this;
4379: $specifiedExpressions = [];
4380: foreach ($typeSpecifications as $typeSpecification) {
4381: $expr = $typeSpecification['expr'];
4382: $type = $typeSpecification['type'];
4383:
4384: if ($expr instanceof IssetExpr) {
4385: $issetExpr = $expr;
4386: $expr = $issetExpr->getExpr();
4387:
4388: if ($typeSpecification['sure']) {
4389: $scope = $scope->setExpressionCertainty(
4390: $expr,
4391: TrinaryLogic::createMaybe(),
4392: );
4393: } else {
4394: $scope = $scope->unsetExpression($expr);
4395: }
4396:
4397: continue;
4398: }
4399:
4400: if ($typeSpecification['sure']) {
4401: if ($specifiedTypes->shouldOverwrite()) {
4402: $scope = $scope->assignExpression($expr, $type, $type);
4403: } else {
4404: $scope = $scope->addTypeToExpression($expr, $type);
4405: }
4406: } else {
4407: $scope = $scope->removeTypeFromExpression($expr, $type);
4408: }
4409: $specifiedExpressions[$this->getNodeKey($expr)] = ExpressionTypeHolder::createYes($expr, $scope->getType($expr));
4410: }
4411:
4412: $conditions = [];
4413: foreach ($scope->conditionalExpressions as $conditionalExprString => $conditionalExpressions) {
4414: foreach ($conditionalExpressions as $conditionalExpression) {
4415: foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) {
4416: if (!array_key_exists($holderExprString, $specifiedExpressions) || !$specifiedExpressions[$holderExprString]->equals($conditionalTypeHolder)) {
4417: continue 2;
4418: }
4419: }
4420:
4421: $conditions[$conditionalExprString][] = $conditionalExpression;
4422: $specifiedExpressions[$conditionalExprString] = $conditionalExpression->getTypeHolder();
4423: }
4424: }
4425:
4426: foreach ($conditions as $conditionalExprString => $expressions) {
4427: $certainty = TrinaryLogic::lazyExtremeIdentity($expressions, static fn (ConditionalExpressionHolder $holder) => $holder->getTypeHolder()->getCertainty());
4428: if ($certainty->no()) {
4429: unset($scope->expressionTypes[$conditionalExprString]);
4430: } else {
4431: $type = TypeCombinator::intersect(...array_map(static fn (ConditionalExpressionHolder $holder) => $holder->getTypeHolder()->getType(), $expressions));
4432:
4433: $scope->expressionTypes[$conditionalExprString] = array_key_exists($conditionalExprString, $scope->expressionTypes)
4434: ? new ExpressionTypeHolder(
4435: $scope->expressionTypes[$conditionalExprString]->getExpr(),
4436: TypeCombinator::intersect($scope->expressionTypes[$conditionalExprString]->getType(), $type),
4437: TrinaryLogic::maxMin($scope->expressionTypes[$conditionalExprString]->getCertainty(), $certainty),
4438: )
4439: : $expressions[0]->getTypeHolder();
4440: }
4441: }
4442:
4443: return $scope->scopeFactory->create(
4444: $scope->context,
4445: $scope->isDeclareStrictTypes(),
4446: $scope->getFunction(),
4447: $scope->getNamespace(),
4448: $scope->expressionTypes,
4449: $scope->nativeExpressionTypes,
4450: array_merge($specifiedTypes->getNewConditionalExpressionHolders(), $scope->conditionalExpressions),
4451: $scope->inClosureBindScopeClasses,
4452: $scope->anonymousFunctionReflection,
4453: $scope->inFirstLevelStatement,
4454: $scope->currentlyAssignedExpressions,
4455: $scope->currentlyAllowedUndefinedExpressions,
4456: $scope->inFunctionCallsStack,
4457: $scope->afterExtractCall,
4458: $scope->parentScope,
4459: $scope->nativeTypesPromoted,
4460: );
4461: }
4462:
4463: /**
4464: * @param ConditionalExpressionHolder[] $conditionalExpressionHolders
4465: */
4466: public function addConditionalExpressions(string $exprString, array $conditionalExpressionHolders): self
4467: {
4468: $conditionalExpressions = $this->conditionalExpressions;
4469: $conditionalExpressions[$exprString] = $conditionalExpressionHolders;
4470: return $this->scopeFactory->create(
4471: $this->context,
4472: $this->isDeclareStrictTypes(),
4473: $this->getFunction(),
4474: $this->getNamespace(),
4475: $this->expressionTypes,
4476: $this->nativeExpressionTypes,
4477: $conditionalExpressions,
4478: $this->inClosureBindScopeClasses,
4479: $this->anonymousFunctionReflection,
4480: $this->inFirstLevelStatement,
4481: $this->currentlyAssignedExpressions,
4482: $this->currentlyAllowedUndefinedExpressions,
4483: $this->inFunctionCallsStack,
4484: $this->afterExtractCall,
4485: $this->parentScope,
4486: $this->nativeTypesPromoted,
4487: );
4488: }
4489:
4490: public function exitFirstLevelStatements(): self
4491: {
4492: if (!$this->inFirstLevelStatement) {
4493: return $this;
4494: }
4495:
4496: if ($this->scopeOutOfFirstLevelStatement !== null) {
4497: return $this->scopeOutOfFirstLevelStatement;
4498: }
4499:
4500: $scope = $this->scopeFactory->create(
4501: $this->context,
4502: $this->isDeclareStrictTypes(),
4503: $this->getFunction(),
4504: $this->getNamespace(),
4505: $this->expressionTypes,
4506: $this->nativeExpressionTypes,
4507: $this->conditionalExpressions,
4508: $this->inClosureBindScopeClasses,
4509: $this->anonymousFunctionReflection,
4510: false,
4511: $this->currentlyAssignedExpressions,
4512: $this->currentlyAllowedUndefinedExpressions,
4513: $this->inFunctionCallsStack,
4514: $this->afterExtractCall,
4515: $this->parentScope,
4516: $this->nativeTypesPromoted,
4517: );
4518: $scope->resolvedTypes = $this->resolvedTypes;
4519: $scope->truthyScopes = $this->truthyScopes;
4520: $scope->falseyScopes = $this->falseyScopes;
4521: $this->scopeOutOfFirstLevelStatement = $scope;
4522:
4523: return $scope;
4524: }
4525:
4526: /** @api */
4527: public function isInFirstLevelStatement(): bool
4528: {
4529: return $this->inFirstLevelStatement;
4530: }
4531:
4532: public function mergeWith(?self $otherScope): self
4533: {
4534: if ($otherScope === null) {
4535: return $this;
4536: }
4537: $ourExpressionTypes = $this->expressionTypes;
4538: $theirExpressionTypes = $otherScope->expressionTypes;
4539:
4540: $mergedExpressionTypes = $this->mergeVariableHolders($ourExpressionTypes, $theirExpressionTypes);
4541: $conditionalExpressions = $this->intersectConditionalExpressions($otherScope->conditionalExpressions);
4542: $conditionalExpressions = $this->createConditionalExpressions(
4543: $conditionalExpressions,
4544: $ourExpressionTypes,
4545: $theirExpressionTypes,
4546: $mergedExpressionTypes,
4547: );
4548: $conditionalExpressions = $this->createConditionalExpressions(
4549: $conditionalExpressions,
4550: $theirExpressionTypes,
4551: $ourExpressionTypes,
4552: $mergedExpressionTypes,
4553: );
4554: return $this->scopeFactory->create(
4555: $this->context,
4556: $this->isDeclareStrictTypes(),
4557: $this->getFunction(),
4558: $this->getNamespace(),
4559: $mergedExpressionTypes,
4560: $this->mergeVariableHolders($this->nativeExpressionTypes, $otherScope->nativeExpressionTypes),
4561: $conditionalExpressions,
4562: $this->inClosureBindScopeClasses,
4563: $this->anonymousFunctionReflection,
4564: $this->inFirstLevelStatement,
4565: [],
4566: [],
4567: [],
4568: $this->afterExtractCall && $otherScope->afterExtractCall,
4569: $this->parentScope,
4570: $this->nativeTypesPromoted,
4571: );
4572: }
4573:
4574: /**
4575: * @param array<string, ConditionalExpressionHolder[]> $otherConditionalExpressions
4576: * @return array<string, ConditionalExpressionHolder[]>
4577: */
4578: private function intersectConditionalExpressions(array $otherConditionalExpressions): array
4579: {
4580: $newConditionalExpressions = [];
4581: foreach ($this->conditionalExpressions as $exprString => $holders) {
4582: if (!array_key_exists($exprString, $otherConditionalExpressions)) {
4583: continue;
4584: }
4585:
4586: $otherHolders = $otherConditionalExpressions[$exprString];
4587: foreach (array_keys($holders) as $key) {
4588: if (!array_key_exists($key, $otherHolders)) {
4589: continue 2;
4590: }
4591: }
4592:
4593: $newConditionalExpressions[$exprString] = $holders;
4594: }
4595:
4596: return $newConditionalExpressions;
4597: }
4598:
4599: /**
4600: * @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
4601: * @param array<string, ExpressionTypeHolder> $ourExpressionTypes
4602: * @param array<string, ExpressionTypeHolder> $theirExpressionTypes
4603: * @param array<string, ExpressionTypeHolder> $mergedExpressionTypes
4604: * @return array<string, ConditionalExpressionHolder[]>
4605: */
4606: private function createConditionalExpressions(
4607: array $conditionalExpressions,
4608: array $ourExpressionTypes,
4609: array $theirExpressionTypes,
4610: array $mergedExpressionTypes,
4611: ): array
4612: {
4613: $newVariableTypes = $ourExpressionTypes;
4614: foreach ($theirExpressionTypes as $exprString => $holder) {
4615: if (!array_key_exists($exprString, $mergedExpressionTypes)) {
4616: continue;
4617: }
4618:
4619: if (!$mergedExpressionTypes[$exprString]->getType()->equals($holder->getType())) {
4620: continue;
4621: }
4622:
4623: unset($newVariableTypes[$exprString]);
4624: }
4625:
4626: $typeGuards = [];
4627: foreach ($newVariableTypes as $exprString => $holder) {
4628: if (!$holder->getCertainty()->yes()) {
4629: continue;
4630: }
4631: if (!array_key_exists($exprString, $mergedExpressionTypes)) {
4632: continue;
4633: }
4634: if ($mergedExpressionTypes[$exprString]->getType()->equals($holder->getType())) {
4635: continue;
4636: }
4637:
4638: $typeGuards[$exprString] = $holder;
4639: }
4640:
4641: if (count($typeGuards) === 0) {
4642: return $conditionalExpressions;
4643: }
4644:
4645: foreach ($newVariableTypes as $exprString => $holder) {
4646: if (
4647: array_key_exists($exprString, $mergedExpressionTypes)
4648: && $mergedExpressionTypes[$exprString]->equals($holder)
4649: ) {
4650: continue;
4651: }
4652:
4653: $variableTypeGuards = $typeGuards;
4654: unset($variableTypeGuards[$exprString]);
4655:
4656: if (count($variableTypeGuards) === 0) {
4657: continue;
4658: }
4659:
4660: $conditionalExpression = new ConditionalExpressionHolder($variableTypeGuards, $holder);
4661: $conditionalExpressions[$exprString][$conditionalExpression->getKey()] = $conditionalExpression;
4662: }
4663:
4664: foreach ($mergedExpressionTypes as $exprString => $mergedExprTypeHolder) {
4665: if (array_key_exists($exprString, $ourExpressionTypes)) {
4666: continue;
4667: }
4668:
4669: $conditionalExpression = new ConditionalExpressionHolder($typeGuards, new ExpressionTypeHolder($mergedExprTypeHolder->getExpr(), new ErrorType(), TrinaryLogic::createNo()));
4670: $conditionalExpressions[$exprString][$conditionalExpression->getKey()] = $conditionalExpression;
4671: }
4672:
4673: return $conditionalExpressions;
4674: }
4675:
4676: /**
4677: * @param array<string, ExpressionTypeHolder> $ourVariableTypeHolders
4678: * @param array<string, ExpressionTypeHolder> $theirVariableTypeHolders
4679: * @return array<string, ExpressionTypeHolder>
4680: */
4681: private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array
4682: {
4683: $intersectedVariableTypeHolders = [];
4684: foreach ($ourVariableTypeHolders as $exprString => $variableTypeHolder) {
4685: if (isset($theirVariableTypeHolders[$exprString])) {
4686: $intersectedVariableTypeHolders[$exprString] = $variableTypeHolder->and($theirVariableTypeHolders[$exprString]);
4687: } else {
4688: $intersectedVariableTypeHolders[$exprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType());
4689: }
4690: }
4691:
4692: foreach ($theirVariableTypeHolders as $exprString => $variableTypeHolder) {
4693: if (isset($intersectedVariableTypeHolders[$exprString])) {
4694: continue;
4695: }
4696:
4697: $intersectedVariableTypeHolders[$exprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType());
4698: }
4699:
4700: return $intersectedVariableTypeHolders;
4701: }
4702:
4703: public function mergeInitializedProperties(self $calledMethodScope): self
4704: {
4705: $scope = $this;
4706: foreach ($calledMethodScope->expressionTypes as $exprString => $typeHolder) {
4707: $exprString = (string) $exprString;
4708: if (!str_starts_with($exprString, '__phpstanPropertyInitialization(')) {
4709: continue;
4710: }
4711: $propertyName = substr($exprString, strlen('__phpstanPropertyInitialization('), -1);
4712: $propertyExpr = new PropertyInitializationExpr($propertyName);
4713: if (!array_key_exists($exprString, $scope->expressionTypes)) {
4714: $scope = $scope->assignExpression($propertyExpr, new MixedType(), new MixedType());
4715: $scope->expressionTypes[$exprString] = $typeHolder;
4716: continue;
4717: }
4718:
4719: $certainty = $scope->expressionTypes[$exprString]->getCertainty();
4720: $scope = $scope->assignExpression($propertyExpr, new MixedType(), new MixedType());
4721: $scope->expressionTypes[$exprString] = new ExpressionTypeHolder(
4722: $typeHolder->getExpr(),
4723: $typeHolder->getType(),
4724: $typeHolder->getCertainty()->or($certainty),
4725: );
4726: }
4727:
4728: return $scope;
4729: }
4730:
4731: public function processFinallyScope(self $finallyScope, self $originalFinallyScope): self
4732: {
4733: return $this->scopeFactory->create(
4734: $this->context,
4735: $this->isDeclareStrictTypes(),
4736: $this->getFunction(),
4737: $this->getNamespace(),
4738: $this->processFinallyScopeVariableTypeHolders(
4739: $this->expressionTypes,
4740: $finallyScope->expressionTypes,
4741: $originalFinallyScope->expressionTypes,
4742: ),
4743: $this->processFinallyScopeVariableTypeHolders(
4744: $this->nativeExpressionTypes,
4745: $finallyScope->nativeExpressionTypes,
4746: $originalFinallyScope->nativeExpressionTypes,
4747: ),
4748: $this->conditionalExpressions,
4749: $this->inClosureBindScopeClasses,
4750: $this->anonymousFunctionReflection,
4751: $this->inFirstLevelStatement,
4752: [],
4753: [],
4754: [],
4755: $this->afterExtractCall,
4756: $this->parentScope,
4757: $this->nativeTypesPromoted,
4758: );
4759: }
4760:
4761: /**
4762: * @param array<string, ExpressionTypeHolder> $ourVariableTypeHolders
4763: * @param array<string, ExpressionTypeHolder> $finallyVariableTypeHolders
4764: * @param array<string, ExpressionTypeHolder> $originalVariableTypeHolders
4765: * @return array<string, ExpressionTypeHolder>
4766: */
4767: private function processFinallyScopeVariableTypeHolders(
4768: array $ourVariableTypeHolders,
4769: array $finallyVariableTypeHolders,
4770: array $originalVariableTypeHolders,
4771: ): array
4772: {
4773: foreach ($finallyVariableTypeHolders as $exprString => $variableTypeHolder) {
4774: if (
4775: isset($originalVariableTypeHolders[$exprString])
4776: && !$originalVariableTypeHolders[$exprString]->getType()->equals($variableTypeHolder->getType())
4777: ) {
4778: $ourVariableTypeHolders[$exprString] = $variableTypeHolder;
4779: continue;
4780: }
4781:
4782: if (isset($originalVariableTypeHolders[$exprString])) {
4783: continue;
4784: }
4785:
4786: $ourVariableTypeHolders[$exprString] = $variableTypeHolder;
4787: }
4788:
4789: return $ourVariableTypeHolders;
4790: }
4791:
4792: /**
4793: * @param Node\ClosureUse[] $byRefUses
4794: */
4795: public function processClosureScope(
4796: self $closureScope,
4797: ?self $prevScope,
4798: array $byRefUses,
4799: ): self
4800: {
4801: $nativeExpressionTypes = $this->nativeExpressionTypes;
4802: $expressionTypes = $this->expressionTypes;
4803: if (count($byRefUses) === 0) {
4804: return $this;
4805: }
4806:
4807: foreach ($byRefUses as $use) {
4808: if (!is_string($use->var->name)) {
4809: throw new ShouldNotHappenException();
4810: }
4811:
4812: $variableName = $use->var->name;
4813: $variableExprString = '$' . $variableName;
4814:
4815: if (!$closureScope->hasVariableType($variableName)->yes()) {
4816: $holder = ExpressionTypeHolder::createYes($use->var, new NullType());
4817: $expressionTypes[$variableExprString] = $holder;
4818: $nativeExpressionTypes[$variableExprString] = $holder;
4819: continue;
4820: }
4821:
4822: $variableType = $closureScope->getVariableType($variableName);
4823:
4824: if ($prevScope !== null) {
4825: $prevVariableType = $prevScope->getVariableType($variableName);
4826: if (!$variableType->equals($prevVariableType)) {
4827: $variableType = TypeCombinator::union($variableType, $prevVariableType);
4828: $variableType = self::generalizeType($variableType, $prevVariableType, 0);
4829: }
4830: }
4831:
4832: $expressionTypes[$variableExprString] = ExpressionTypeHolder::createYes($use->var, $variableType);
4833: $nativeExpressionTypes[$variableExprString] = ExpressionTypeHolder::createYes($use->var, $variableType);
4834: }
4835:
4836: return $this->scopeFactory->create(
4837: $this->context,
4838: $this->isDeclareStrictTypes(),
4839: $this->getFunction(),
4840: $this->getNamespace(),
4841: $expressionTypes,
4842: $nativeExpressionTypes,
4843: $this->conditionalExpressions,
4844: $this->inClosureBindScopeClasses,
4845: $this->anonymousFunctionReflection,
4846: $this->inFirstLevelStatement,
4847: [],
4848: [],
4849: $this->inFunctionCallsStack,
4850: $this->afterExtractCall,
4851: $this->parentScope,
4852: $this->nativeTypesPromoted,
4853: );
4854: }
4855:
4856: public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope): self
4857: {
4858: $expressionTypes = $this->expressionTypes;
4859: foreach ($finalScope->expressionTypes as $variableExprString => $variableTypeHolder) {
4860: if (!isset($expressionTypes[$variableExprString])) {
4861: $expressionTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType());
4862: continue;
4863: }
4864:
4865: $expressionTypes[$variableExprString] = new ExpressionTypeHolder(
4866: $variableTypeHolder->getExpr(),
4867: $variableTypeHolder->getType(),
4868: $variableTypeHolder->getCertainty()->and($expressionTypes[$variableExprString]->getCertainty()),
4869: );
4870: }
4871: $nativeTypes = $this->nativeExpressionTypes;
4872: foreach ($finalScope->nativeExpressionTypes as $variableExprString => $variableTypeHolder) {
4873: if (!isset($nativeTypes[$variableExprString])) {
4874: $nativeTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType());
4875: continue;
4876: }
4877:
4878: $nativeTypes[$variableExprString] = new ExpressionTypeHolder(
4879: $variableTypeHolder->getExpr(),
4880: $variableTypeHolder->getType(),
4881: $variableTypeHolder->getCertainty()->and($nativeTypes[$variableExprString]->getCertainty()),
4882: );
4883: }
4884:
4885: return $this->scopeFactory->create(
4886: $this->context,
4887: $this->isDeclareStrictTypes(),
4888: $this->getFunction(),
4889: $this->getNamespace(),
4890: $expressionTypes,
4891: $nativeTypes,
4892: $this->conditionalExpressions,
4893: $this->inClosureBindScopeClasses,
4894: $this->anonymousFunctionReflection,
4895: $this->inFirstLevelStatement,
4896: [],
4897: [],
4898: [],
4899: $this->afterExtractCall,
4900: $this->parentScope,
4901: $this->nativeTypesPromoted,
4902: );
4903: }
4904:
4905: public function generalizeWith(self $otherScope): self
4906: {
4907: $variableTypeHolders = $this->generalizeVariableTypeHolders(
4908: $this->expressionTypes,
4909: $otherScope->expressionTypes,
4910: );
4911: $nativeTypes = $this->generalizeVariableTypeHolders(
4912: $this->nativeExpressionTypes,
4913: $otherScope->nativeExpressionTypes,
4914: );
4915:
4916: return $this->scopeFactory->create(
4917: $this->context,
4918: $this->isDeclareStrictTypes(),
4919: $this->getFunction(),
4920: $this->getNamespace(),
4921: $variableTypeHolders,
4922: $nativeTypes,
4923: $this->conditionalExpressions,
4924: $this->inClosureBindScopeClasses,
4925: $this->anonymousFunctionReflection,
4926: $this->inFirstLevelStatement,
4927: [],
4928: [],
4929: [],
4930: $this->afterExtractCall,
4931: $this->parentScope,
4932: $this->nativeTypesPromoted,
4933: );
4934: }
4935:
4936: /**
4937: * @param array<string, ExpressionTypeHolder> $variableTypeHolders
4938: * @param array<string, ExpressionTypeHolder> $otherVariableTypeHolders
4939: * @return array<string, ExpressionTypeHolder>
4940: */
4941: private function generalizeVariableTypeHolders(
4942: array $variableTypeHolders,
4943: array $otherVariableTypeHolders,
4944: ): array
4945: {
4946: foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) {
4947: if (!isset($otherVariableTypeHolders[$variableExprString])) {
4948: continue;
4949: }
4950:
4951: $variableTypeHolders[$variableExprString] = new ExpressionTypeHolder(
4952: $variableTypeHolder->getExpr(),
4953: self::generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType(), 0),
4954: $variableTypeHolder->getCertainty(),
4955: );
4956: }
4957:
4958: return $variableTypeHolders;
4959: }
4960:
4961: private static function generalizeType(Type $a, Type $b, int $depth): Type
4962: {
4963: if ($a->equals($b)) {
4964: return $a;
4965: }
4966:
4967: $constantIntegers = ['a' => [], 'b' => []];
4968: $constantFloats = ['a' => [], 'b' => []];
4969: $constantBooleans = ['a' => [], 'b' => []];
4970: $constantStrings = ['a' => [], 'b' => []];
4971: $constantArrays = ['a' => [], 'b' => []];
4972: $generalArrays = ['a' => [], 'b' => []];
4973: $integerRanges = ['a' => [], 'b' => []];
4974: $otherTypes = [];
4975:
4976: foreach ([
4977: 'a' => TypeUtils::flattenTypes($a),
4978: 'b' => TypeUtils::flattenTypes($b),
4979: ] as $key => $types) {
4980: foreach ($types as $type) {
4981: if ($type instanceof ConstantIntegerType) {
4982: $constantIntegers[$key][] = $type;
4983: continue;
4984: }
4985: if ($type instanceof ConstantFloatType) {
4986: $constantFloats[$key][] = $type;
4987: continue;
4988: }
4989: if ($type instanceof ConstantBooleanType) {
4990: $constantBooleans[$key][] = $type;
4991: continue;
4992: }
4993: if ($type instanceof ConstantStringType) {
4994: $constantStrings[$key][] = $type;
4995: continue;
4996: }
4997: if ($type->isConstantArray()->yes()) {
4998: $constantArrays[$key][] = $type;
4999: continue;
5000: }
5001: if ($type->isArray()->yes()) {
5002: $generalArrays[$key][] = $type;
5003: continue;
5004: }
5005: if ($type instanceof IntegerRangeType) {
5006: $integerRanges[$key][] = $type;
5007: continue;
5008: }
5009:
5010: $otherTypes[] = $type;
5011: }
5012: }
5013:
5014: $resultTypes = [];
5015: foreach ([
5016: $constantFloats,
5017: $constantBooleans,
5018: $constantStrings,
5019: ] as $constantTypes) {
5020: if (count($constantTypes['a']) === 0) {
5021: if (count($constantTypes['b']) > 0) {
5022: $resultTypes[] = TypeCombinator::union(...$constantTypes['b']);
5023: }
5024: continue;
5025: } elseif (count($constantTypes['b']) === 0) {
5026: $resultTypes[] = TypeCombinator::union(...$constantTypes['a']);
5027: continue;
5028: }
5029:
5030: $aTypes = TypeCombinator::union(...$constantTypes['a']);
5031: $bTypes = TypeCombinator::union(...$constantTypes['b']);
5032: if ($aTypes->equals($bTypes)) {
5033: $resultTypes[] = $aTypes;
5034: continue;
5035: }
5036:
5037: $resultTypes[] = TypeCombinator::union(...$constantTypes['a'], ...$constantTypes['b'])->generalize(GeneralizePrecision::moreSpecific());
5038: }
5039:
5040: if (count($constantArrays['a']) > 0) {
5041: if (count($constantArrays['b']) === 0) {
5042: $resultTypes[] = TypeCombinator::union(...$constantArrays['a']);
5043: } else {
5044: $constantArraysA = TypeCombinator::union(...$constantArrays['a']);
5045: $constantArraysB = TypeCombinator::union(...$constantArrays['b']);
5046: if ($constantArraysA->getIterableKeyType()->equals($constantArraysB->getIterableKeyType())) {
5047: $resultArrayBuilder = ConstantArrayTypeBuilder::createEmpty();
5048: foreach (TypeUtils::flattenTypes($constantArraysA->getIterableKeyType()) as $keyType) {
5049: $resultArrayBuilder->setOffsetValueType(
5050: $keyType,
5051: self::generalizeType(
5052: $constantArraysA->getOffsetValueType($keyType),
5053: $constantArraysB->getOffsetValueType($keyType),
5054: $depth + 1,
5055: ),
5056: !$constantArraysA->hasOffsetValueType($keyType)->and($constantArraysB->hasOffsetValueType($keyType))->negate()->no(),
5057: );
5058: }
5059:
5060: $resultTypes[] = $resultArrayBuilder->getArray();
5061: } else {
5062: $resultType = new ArrayType(
5063: TypeCombinator::union(self::generalizeType($constantArraysA->getIterableKeyType(), $constantArraysB->getIterableKeyType(), $depth + 1)),
5064: TypeCombinator::union(self::generalizeType($constantArraysA->getIterableValueType(), $constantArraysB->getIterableValueType(), $depth + 1)),
5065: );
5066: if ($constantArraysA->isIterableAtLeastOnce()->yes() && $constantArraysB->isIterableAtLeastOnce()->yes()) {
5067: $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType());
5068: }
5069: if ($constantArraysA->isList()->yes() && $constantArraysB->isList()->yes()) {
5070: $resultType = TypeCombinator::intersect($resultType, new AccessoryArrayListType());
5071: }
5072: $resultTypes[] = $resultType;
5073: }
5074: }
5075: } elseif (count($constantArrays['b']) > 0) {
5076: $resultTypes[] = TypeCombinator::union(...$constantArrays['b']);
5077: }
5078:
5079: if (count($generalArrays['a']) > 0) {
5080: if (count($generalArrays['b']) === 0) {
5081: $resultTypes[] = TypeCombinator::union(...$generalArrays['a']);
5082: } else {
5083: $generalArraysA = TypeCombinator::union(...$generalArrays['a']);
5084: $generalArraysB = TypeCombinator::union(...$generalArrays['b']);
5085:
5086: $aValueType = $generalArraysA->getIterableValueType();
5087: $bValueType = $generalArraysB->getIterableValueType();
5088: if (
5089: $aValueType->isArray()->yes()
5090: && $aValueType->isConstantArray()->no()
5091: && $bValueType->isArray()->yes()
5092: && $bValueType->isConstantArray()->no()
5093: ) {
5094: $aDepth = self::getArrayDepth($aValueType) + $depth;
5095: $bDepth = self::getArrayDepth($bValueType) + $depth;
5096: if (
5097: ($aDepth > 2 || $bDepth > 2)
5098: && abs($aDepth - $bDepth) > 0
5099: ) {
5100: $aValueType = new MixedType();
5101: $bValueType = new MixedType();
5102: }
5103: }
5104:
5105: $resultType = new ArrayType(
5106: TypeCombinator::union(self::generalizeType($generalArraysA->getIterableKeyType(), $generalArraysB->getIterableKeyType(), $depth + 1)),
5107: TypeCombinator::union(self::generalizeType($aValueType, $bValueType, $depth + 1)),
5108: );
5109: if ($generalArraysA->isIterableAtLeastOnce()->yes() && $generalArraysB->isIterableAtLeastOnce()->yes()) {
5110: $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType());
5111: }
5112: if ($generalArraysA->isList()->yes() && $generalArraysB->isList()->yes()) {
5113: $resultType = TypeCombinator::intersect($resultType, new AccessoryArrayListType());
5114: }
5115: if ($generalArraysA->isOversizedArray()->yes() && $generalArraysB->isOversizedArray()->yes()) {
5116: $resultType = TypeCombinator::intersect($resultType, new OversizedArrayType());
5117: }
5118: $resultTypes[] = $resultType;
5119: }
5120: } elseif (count($generalArrays['b']) > 0) {
5121: $resultTypes[] = TypeCombinator::union(...$generalArrays['b']);
5122: }
5123:
5124: if (count($constantIntegers['a']) > 0) {
5125: if (count($constantIntegers['b']) === 0) {
5126: $resultTypes[] = TypeCombinator::union(...$constantIntegers['a']);
5127: } else {
5128: $constantIntegersA = TypeCombinator::union(...$constantIntegers['a']);
5129: $constantIntegersB = TypeCombinator::union(...$constantIntegers['b']);
5130:
5131: if ($constantIntegersA->equals($constantIntegersB)) {
5132: $resultTypes[] = $constantIntegersA;
5133: } else {
5134: $min = null;
5135: $max = null;
5136: foreach ($constantIntegers['a'] as $int) {
5137: if ($min === null || $int->getValue() < $min) {
5138: $min = $int->getValue();
5139: }
5140: if ($max !== null && $int->getValue() <= $max) {
5141: continue;
5142: }
5143:
5144: $max = $int->getValue();
5145: }
5146:
5147: $gotGreater = false;
5148: $gotSmaller = false;
5149: foreach ($constantIntegers['b'] as $int) {
5150: if ($int->getValue() > $max) {
5151: $gotGreater = true;
5152: }
5153: if ($int->getValue() >= $min) {
5154: continue;
5155: }
5156:
5157: $gotSmaller = true;
5158: }
5159:
5160: if ($gotGreater && $gotSmaller) {
5161: $resultTypes[] = new IntegerType();
5162: } elseif ($gotGreater) {
5163: $resultTypes[] = IntegerRangeType::fromInterval($min, null);
5164: } elseif ($gotSmaller) {
5165: $resultTypes[] = IntegerRangeType::fromInterval(null, $max);
5166: } else {
5167: $resultTypes[] = TypeCombinator::union($constantIntegersA, $constantIntegersB);
5168: }
5169: }
5170: }
5171: } elseif (count($constantIntegers['b']) > 0) {
5172: $resultTypes[] = TypeCombinator::union(...$constantIntegers['b']);
5173: }
5174:
5175: if (count($integerRanges['a']) > 0) {
5176: if (count($integerRanges['b']) === 0) {
5177: $resultTypes[] = TypeCombinator::union(...$integerRanges['a']);
5178: } else {
5179: $integerRangesA = TypeCombinator::union(...$integerRanges['a']);
5180: $integerRangesB = TypeCombinator::union(...$integerRanges['b']);
5181:
5182: if ($integerRangesA->equals($integerRangesB)) {
5183: $resultTypes[] = $integerRangesA;
5184: } else {
5185: $min = null;
5186: $max = null;
5187: foreach ($integerRanges['a'] as $range) {
5188: if ($range->getMin() === null) {
5189: $rangeMin = PHP_INT_MIN;
5190: } else {
5191: $rangeMin = $range->getMin();
5192: }
5193: if ($range->getMax() === null) {
5194: $rangeMax = PHP_INT_MAX;
5195: } else {
5196: $rangeMax = $range->getMax();
5197: }
5198:
5199: if ($min === null || $rangeMin < $min) {
5200: $min = $rangeMin;
5201: }
5202: if ($max !== null && $rangeMax <= $max) {
5203: continue;
5204: }
5205:
5206: $max = $rangeMax;
5207: }
5208:
5209: $gotGreater = false;
5210: $gotSmaller = false;
5211: foreach ($integerRanges['b'] as $range) {
5212: if ($range->getMin() === null) {
5213: $rangeMin = PHP_INT_MIN;
5214: } else {
5215: $rangeMin = $range->getMin();
5216: }
5217: if ($range->getMax() === null) {
5218: $rangeMax = PHP_INT_MAX;
5219: } else {
5220: $rangeMax = $range->getMax();
5221: }
5222:
5223: if ($rangeMax > $max) {
5224: $gotGreater = true;
5225: }
5226: if ($rangeMin >= $min) {
5227: continue;
5228: }
5229:
5230: $gotSmaller = true;
5231: }
5232:
5233: if ($min === PHP_INT_MIN) {
5234: $min = null;
5235: }
5236: if ($max === PHP_INT_MAX) {
5237: $max = null;
5238: }
5239:
5240: if ($gotGreater && $gotSmaller) {
5241: $resultTypes[] = new IntegerType();
5242: } elseif ($gotGreater) {
5243: $resultTypes[] = IntegerRangeType::fromInterval($min, null);
5244: } elseif ($gotSmaller) {
5245: $resultTypes[] = IntegerRangeType::fromInterval(null, $max);
5246: } else {
5247: $resultTypes[] = TypeCombinator::union($integerRangesA, $integerRangesB);
5248: }
5249: }
5250: }
5251: } elseif (count($integerRanges['b']) > 0) {
5252: $resultTypes[] = TypeCombinator::union(...$integerRanges['b']);
5253: }
5254:
5255: $accessoryTypes = array_map(
5256: static fn (Type $type): Type => $type->generalize(GeneralizePrecision::moreSpecific()),
5257: TypeUtils::getAccessoryTypes($a),
5258: );
5259:
5260: return TypeCombinator::union(TypeCombinator::intersect(
5261: TypeCombinator::union(...$resultTypes, ...$otherTypes),
5262: ...$accessoryTypes,
5263: ), ...$otherTypes);
5264: }
5265:
5266: private static function getArrayDepth(Type $type): int
5267: {
5268: $depth = 0;
5269: $arrays = TypeUtils::toBenevolentUnion($type)->getArrays();
5270: while (count($arrays) > 0) {
5271: $temp = $type->getIterableValueType();
5272: $type = $temp;
5273: $arrays = TypeUtils::toBenevolentUnion($type)->getArrays();
5274: $depth++;
5275: }
5276:
5277: return $depth;
5278: }
5279:
5280: public function equals(self $otherScope): bool
5281: {
5282: if (!$this->context->equals($otherScope->context)) {
5283: return false;
5284: }
5285:
5286: if (!$this->compareVariableTypeHolders($this->expressionTypes, $otherScope->expressionTypes)) {
5287: return false;
5288: }
5289: return $this->compareVariableTypeHolders($this->nativeExpressionTypes, $otherScope->nativeExpressionTypes);
5290: }
5291:
5292: /**
5293: * @param array<string, ExpressionTypeHolder> $variableTypeHolders
5294: * @param array<string, ExpressionTypeHolder> $otherVariableTypeHolders
5295: */
5296: private function compareVariableTypeHolders(array $variableTypeHolders, array $otherVariableTypeHolders): bool
5297: {
5298: if (count($variableTypeHolders) !== count($otherVariableTypeHolders)) {
5299: return false;
5300: }
5301: foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) {
5302: if (!isset($otherVariableTypeHolders[$variableExprString])) {
5303: return false;
5304: }
5305:
5306: if (!$variableTypeHolder->getCertainty()->equals($otherVariableTypeHolders[$variableExprString]->getCertainty())) {
5307: return false;
5308: }
5309:
5310: if (!$variableTypeHolder->getType()->equals($otherVariableTypeHolders[$variableExprString]->getType())) {
5311: return false;
5312: }
5313:
5314: unset($otherVariableTypeHolders[$variableExprString]);
5315: }
5316:
5317: return true;
5318: }
5319:
5320: private function getBooleanExpressionDepth(Expr $expr, int $depth = 0): int
5321: {
5322: while (
5323: $expr instanceof BinaryOp\BooleanOr
5324: || $expr instanceof BinaryOp\LogicalOr
5325: || $expr instanceof BinaryOp\BooleanAnd
5326: || $expr instanceof BinaryOp\LogicalAnd
5327: ) {
5328: return $this->getBooleanExpressionDepth($expr->left, $depth + 1);
5329: }
5330:
5331: return $depth;
5332: }
5333:
5334: /** @api */
5335: public function canAccessProperty(PropertyReflection $propertyReflection): bool
5336: {
5337: return $this->canAccessClassMember($propertyReflection);
5338: }
5339:
5340: /** @api */
5341: public function canCallMethod(MethodReflection $methodReflection): bool
5342: {
5343: if ($this->canAccessClassMember($methodReflection)) {
5344: return true;
5345: }
5346:
5347: return $this->canAccessClassMember($methodReflection->getPrototype());
5348: }
5349:
5350: /** @api */
5351: public function canAccessConstant(ClassConstantReflection $constantReflection): bool
5352: {
5353: return $this->canAccessClassMember($constantReflection);
5354: }
5355:
5356: private function canAccessClassMember(ClassMemberReflection $classMemberReflection): bool
5357: {
5358: if ($classMemberReflection->isPublic()) {
5359: return true;
5360: }
5361:
5362: $classReflectionName = $classMemberReflection->getDeclaringClass()->getName();
5363: $canAccessClassMember = static function (ClassReflection $classReflection) use ($classMemberReflection, $classReflectionName) {
5364: if ($classMemberReflection->isPrivate()) {
5365: return $classReflection->getName() === $classReflectionName;
5366: }
5367:
5368: // protected
5369:
5370: if (
5371: $classReflection->getName() === $classReflectionName
5372: || $classReflection->isSubclassOf($classReflectionName)
5373: ) {
5374: return true;
5375: }
5376:
5377: return $classMemberReflection->getDeclaringClass()->isSubclassOf($classReflection->getName());
5378: };
5379:
5380: foreach ($this->inClosureBindScopeClasses as $inClosureBindScopeClass) {
5381: if (!$this->reflectionProvider->hasClass($inClosureBindScopeClass)) {
5382: continue;
5383: }
5384:
5385: if ($canAccessClassMember($this->reflectionProvider->getClass($inClosureBindScopeClass))) {
5386: return true;
5387: }
5388: }
5389:
5390: if ($this->isInClass()) {
5391: return $canAccessClassMember($this->getClassReflection());
5392: }
5393:
5394: return false;
5395: }
5396:
5397: /**
5398: * @return string[]
5399: */
5400: public function debug(): array
5401: {
5402: $descriptions = [];
5403: foreach ($this->expressionTypes as $name => $variableTypeHolder) {
5404: $key = sprintf('%s (%s)', $name, $variableTypeHolder->getCertainty()->describe());
5405: $descriptions[$key] = $variableTypeHolder->getType()->describe(VerbosityLevel::precise());
5406: }
5407: foreach ($this->nativeExpressionTypes as $exprString => $nativeTypeHolder) {
5408: $key = sprintf('native %s (%s)', $exprString, $nativeTypeHolder->getCertainty()->describe());
5409: $descriptions[$key] = $nativeTypeHolder->getType()->describe(VerbosityLevel::precise());
5410: }
5411:
5412: foreach ($this->conditionalExpressions as $exprString => $holders) {
5413: foreach (array_values($holders) as $i => $holder) {
5414: $key = sprintf('condition about %s #%d', $exprString, $i + 1);
5415: $parts = [];
5416: foreach ($holder->getConditionExpressionTypeHolders() as $conditionalExprString => $expressionTypeHolder) {
5417: $parts[] = $conditionalExprString . '=' . $expressionTypeHolder->getType()->describe(VerbosityLevel::precise());
5418: }
5419: $condition = implode(' && ', $parts);
5420: $descriptions[$key] = sprintf(
5421: 'if %s then %s is %s (%s)',
5422: $condition,
5423: $exprString,
5424: $holder->getTypeHolder()->getType()->describe(VerbosityLevel::precise()),
5425: $holder->getTypeHolder()->getCertainty()->describe(),
5426: );
5427: }
5428: }
5429:
5430: return $descriptions;
5431: }
5432:
5433: /**
5434: * @param non-empty-string $className
5435: */
5436: private function exactInstantiation(New_ $node, string $className): ?Type
5437: {
5438: $resolvedClassName = $this->resolveExactName(new Name($className));
5439: if ($resolvedClassName === null) {
5440: return null;
5441: }
5442:
5443: if (!$this->reflectionProvider->hasClass($resolvedClassName)) {
5444: return null;
5445: }
5446:
5447: $classReflection = $this->reflectionProvider->getClass($resolvedClassName);
5448: if ($classReflection->hasConstructor()) {
5449: $constructorMethod = $classReflection->getConstructor();
5450: } else {
5451: $constructorMethod = new DummyConstructorReflection($classReflection);
5452: }
5453:
5454: $resolvedTypes = [];
5455: $methodCall = new Expr\StaticCall(
5456: new Name($resolvedClassName),
5457: new Node\Identifier($constructorMethod->getName()),
5458: $node->getArgs(),
5459: );
5460:
5461: $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
5462: $this,
5463: $methodCall->getArgs(),
5464: $constructorMethod->getVariants(),
5465: $constructorMethod->getNamedArgumentsVariants(),
5466: );
5467: $normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall);
5468:
5469: if ($normalizedMethodCall !== null) {
5470: foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($classReflection->getName()) as $dynamicStaticMethodReturnTypeExtension) {
5471: if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($constructorMethod)) {
5472: continue;
5473: }
5474:
5475: $resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall(
5476: $constructorMethod,
5477: $normalizedMethodCall,
5478: $this,
5479: );
5480: if ($resolvedType === null) {
5481: continue;
5482: }
5483:
5484: $resolvedTypes[] = $resolvedType;
5485: }
5486: }
5487:
5488: if (count($resolvedTypes) > 0) {
5489: return TypeCombinator::union(...$resolvedTypes);
5490: }
5491:
5492: $methodResult = $this->getType($methodCall);
5493: if ($methodResult instanceof NeverType && $methodResult->isExplicit()) {
5494: return $methodResult;
5495: }
5496:
5497: $objectType = new ObjectType($resolvedClassName);
5498: if (!$classReflection->isGeneric()) {
5499: return $objectType;
5500: }
5501:
5502: $assignedToProperty = $node->getAttribute(NewAssignedToPropertyVisitor::ATTRIBUTE_NAME);
5503: if ($assignedToProperty !== null) {
5504: $constructorVariant = $constructorMethod->getOnlyVariant();
5505: $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes();
5506: $originalClassTemplateTypes = $classTemplateTypes;
5507: foreach ($constructorVariant->getParameters() as $parameter) {
5508: TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$classTemplateTypes): Type {
5509: if ($type instanceof TemplateType && array_key_exists($type->getName(), $classTemplateTypes)) {
5510: $classTemplateType = $classTemplateTypes[$type->getName()];
5511: if ($classTemplateType instanceof TemplateType && $classTemplateType->getScope()->equals($type->getScope())) {
5512: unset($classTemplateTypes[$type->getName()]);
5513: }
5514: return $type;
5515: }
5516:
5517: return $traverse($type);
5518: });
5519: }
5520:
5521: if (count($classTemplateTypes) === count($originalClassTemplateTypes)) {
5522: $propertyType = TypeCombinator::removeNull($this->getType($assignedToProperty));
5523: if ($objectType->isSuperTypeOf($propertyType)->yes()) {
5524: return $propertyType;
5525: }
5526: }
5527: }
5528:
5529: if ($constructorMethod instanceof DummyConstructorReflection || $constructorMethod->getDeclaringClass()->getName() !== $classReflection->getName()) {
5530: return new GenericObjectType(
5531: $resolvedClassName,
5532: $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
5533: );
5534: }
5535:
5536: $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
5537: $this,
5538: $methodCall->getArgs(),
5539: $constructorMethod->getVariants(),
5540: $constructorMethod->getNamedArgumentsVariants(),
5541: );
5542:
5543: $resolvedTemplateTypeMap = $parametersAcceptor->getResolvedTemplateTypeMap();
5544: return TypeTraverser::map(new GenericObjectType(
5545: $resolvedClassName,
5546: $classReflection->typeMapToList($classReflection->getTemplateTypeMap()),
5547: ), static function (Type $type, callable $traverse) use ($resolvedTemplateTypeMap): Type {
5548: if ($type instanceof TemplateType && !$type->isArgument()) {
5549: $newType = $resolvedTemplateTypeMap->getType($type->getName());
5550: if ($newType === null || $newType instanceof ErrorType) {
5551: return $type->getDefault() ?? $type->getBound();
5552: }
5553:
5554: return TemplateTypeHelper::generalizeInferredTemplateType($type, $newType);
5555: }
5556:
5557: return $traverse($type);
5558: });
5559: }
5560:
5561: private function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type
5562: {
5563: if ($typeWithMethod instanceof UnionType) {
5564: $newTypes = [];
5565: foreach ($typeWithMethod->getTypes() as $innerType) {
5566: if (!$innerType->hasMethod($methodName)->yes()) {
5567: continue;
5568: }
5569:
5570: $newTypes[] = $innerType;
5571: }
5572: if (count($newTypes) === 0) {
5573: return null;
5574: }
5575: $typeWithMethod = TypeCombinator::union(...$newTypes);
5576: }
5577:
5578: if (!$typeWithMethod->hasMethod($methodName)->yes()) {
5579: return null;
5580: }
5581:
5582: return $typeWithMethod;
5583: }
5584:
5585: /** @api */
5586: public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection
5587: {
5588: $type = $this->filterTypeWithMethod($typeWithMethod, $methodName);
5589: if ($type === null) {
5590: return null;
5591: }
5592:
5593: return $type->getMethod($methodName, $this);
5594: }
5595:
5596: public function getNakedMethod(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection
5597: {
5598: $type = $this->filterTypeWithMethod($typeWithMethod, $methodName);
5599: if ($type === null) {
5600: return null;
5601: }
5602:
5603: return $type->getUnresolvedMethodPrototype($methodName, $this)->getNakedMethod();
5604: }
5605:
5606: /**
5607: * @param MethodCall|Node\Expr\StaticCall $methodCall
5608: */
5609: private function methodCallReturnType(Type $typeWithMethod, string $methodName, Expr $methodCall): ?Type
5610: {
5611: $typeWithMethod = $this->filterTypeWithMethod($typeWithMethod, $methodName);
5612: if ($typeWithMethod === null) {
5613: return null;
5614: }
5615:
5616: $methodReflection = $typeWithMethod->getMethod($methodName, $this);
5617: $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
5618: $this,
5619: $methodCall->getArgs(),
5620: $methodReflection->getVariants(),
5621: $methodReflection->getNamedArgumentsVariants(),
5622: );
5623: if ($methodCall instanceof MethodCall) {
5624: $normalizedMethodCall = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $methodCall);
5625: } else {
5626: $normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall);
5627: }
5628: if ($normalizedMethodCall === null) {
5629: return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $methodCall);
5630: }
5631:
5632: $resolvedTypes = [];
5633: foreach ($typeWithMethod->getObjectClassNames() as $className) {
5634: if ($normalizedMethodCall instanceof MethodCall) {
5635: foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicMethodReturnTypeExtensionsForClass($className) as $dynamicMethodReturnTypeExtension) {
5636: if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) {
5637: continue;
5638: }
5639:
5640: $resolvedType = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $normalizedMethodCall, $this);
5641: if ($resolvedType === null) {
5642: continue;
5643: }
5644:
5645: $resolvedTypes[] = $resolvedType;
5646: }
5647: } else {
5648: foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) {
5649: if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($methodReflection)) {
5650: continue;
5651: }
5652:
5653: $resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall(
5654: $methodReflection,
5655: $normalizedMethodCall,
5656: $this,
5657: );
5658: if ($resolvedType === null) {
5659: continue;
5660: }
5661:
5662: $resolvedTypes[] = $resolvedType;
5663: }
5664: }
5665: }
5666:
5667: if (count($resolvedTypes) > 0) {
5668: return $this->transformVoidToNull(TypeCombinator::union(...$resolvedTypes), $methodCall);
5669: }
5670:
5671: return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $methodCall);
5672: }
5673:
5674: /** @api */
5675: public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection
5676: {
5677: if ($typeWithProperty instanceof UnionType) {
5678: $newTypes = [];
5679: foreach ($typeWithProperty->getTypes() as $innerType) {
5680: if (!$innerType->hasProperty($propertyName)->yes()) {
5681: continue;
5682: }
5683:
5684: $newTypes[] = $innerType;
5685: }
5686: if (count($newTypes) === 0) {
5687: return null;
5688: }
5689: $typeWithProperty = TypeCombinator::union(...$newTypes);
5690: }
5691: if (!$typeWithProperty->hasProperty($propertyName)->yes()) {
5692: return null;
5693: }
5694:
5695: return $typeWithProperty->getProperty($propertyName, $this);
5696: }
5697:
5698: /**
5699: * @param PropertyFetch|Node\Expr\StaticPropertyFetch $propertyFetch
5700: */
5701: private function propertyFetchType(Type $fetchedOnType, string $propertyName, Expr $propertyFetch): ?Type
5702: {
5703: $propertyReflection = $this->getPropertyReflection($fetchedOnType, $propertyName);
5704: if ($propertyReflection === null) {
5705: return null;
5706: }
5707:
5708: if ($this->isInExpressionAssign($propertyFetch)) {
5709: return $propertyReflection->getWritableType();
5710: }
5711:
5712: return $propertyReflection->getReadableType();
5713: }
5714:
5715: public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ClassConstantReflection
5716: {
5717: if ($typeWithConstant instanceof UnionType) {
5718: $newTypes = [];
5719: foreach ($typeWithConstant->getTypes() as $innerType) {
5720: if (!$innerType->hasConstant($constantName)->yes()) {
5721: continue;
5722: }
5723:
5724: $newTypes[] = $innerType;
5725: }
5726: if (count($newTypes) === 0) {
5727: return null;
5728: }
5729: $typeWithConstant = TypeCombinator::union(...$newTypes);
5730: }
5731: if (!$typeWithConstant->hasConstant($constantName)->yes()) {
5732: return null;
5733: }
5734:
5735: return $typeWithConstant->getConstant($constantName);
5736: }
5737:
5738: /**
5739: * @return array<string, ExpressionTypeHolder>
5740: */
5741: private function getConstantTypes(): array
5742: {
5743: $constantTypes = [];
5744: foreach ($this->expressionTypes as $exprString => $typeHolder) {
5745: $expr = $typeHolder->getExpr();
5746: if (!$expr instanceof ConstFetch) {
5747: continue;
5748: }
5749: $constantTypes[$exprString] = $typeHolder;
5750: }
5751: return $constantTypes;
5752: }
5753:
5754: /**
5755: * @return array<string, ExpressionTypeHolder>
5756: */
5757: private function getNativeConstantTypes(): array
5758: {
5759: $constantTypes = [];
5760: foreach ($this->nativeExpressionTypes as $exprString => $typeHolder) {
5761: $expr = $typeHolder->getExpr();
5762: if (!$expr instanceof ConstFetch) {
5763: continue;
5764: }
5765: $constantTypes[$exprString] = $typeHolder;
5766: }
5767: return $constantTypes;
5768: }
5769:
5770: public function getIterableKeyType(Type $iteratee): Type
5771: {
5772: if ($iteratee instanceof UnionType) {
5773: $newTypes = [];
5774: foreach ($iteratee->getTypes() as $innerType) {
5775: if (!$innerType->isIterable()->yes()) {
5776: continue;
5777: }
5778:
5779: $newTypes[] = $innerType;
5780: }
5781: if (count($newTypes) === 0) {
5782: return $iteratee->getIterableKeyType();
5783: }
5784: $iteratee = TypeCombinator::union(...$newTypes);
5785: }
5786:
5787: return $iteratee->getIterableKeyType();
5788: }
5789:
5790: public function getIterableValueType(Type $iteratee): Type
5791: {
5792: if ($iteratee instanceof UnionType) {
5793: $newTypes = [];
5794: foreach ($iteratee->getTypes() as $innerType) {
5795: if (!$innerType->isIterable()->yes()) {
5796: continue;
5797: }
5798:
5799: $newTypes[] = $innerType;
5800: }
5801: if (count($newTypes) === 0) {
5802: return $iteratee->getIterableValueType();
5803: }
5804: $iteratee = TypeCombinator::union(...$newTypes);
5805: }
5806:
5807: return $iteratee->getIterableValueType();
5808: }
5809:
5810: }
5811: