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