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