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