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