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