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