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