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