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