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