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