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