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