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