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