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