1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Analyser\Generator;
4:
5: use ArrayAccess;
6: use Fiber;
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\ConstFetch;
14: use PhpParser\Node\Expr\FuncCall;
15: use PhpParser\Node\Expr\MethodCall;
16: use PhpParser\Node\Expr\PropertyFetch;
17: use PhpParser\Node\Expr\Variable;
18: use PhpParser\Node\Identifier;
19: use PhpParser\Node\Name;
20: use PhpParser\Node\Name\FullyQualified;
21: use PhpParser\Node\PropertyHook;
22: use PhpParser\Node\Scalar\String_;
23: use PhpParser\Node\Stmt\ClassMethod;
24: use PhpParser\Node\Stmt\Function_;
25: use PhpParser\NodeFinder;
26: use PHPStan\Analyser\ConditionalExpressionHolder;
27: use PHPStan\Analyser\ConstantResolver;
28: use PHPStan\Analyser\ExpressionTypeHolder;
29: use PHPStan\Analyser\NodeCallbackInvoker;
30: use PHPStan\Analyser\Scope;
31: use PHPStan\Analyser\ScopeContext;
32: use PHPStan\Analyser\SpecifiedTypes;
33: use PHPStan\Analyser\TypeSpecifier;
34: use PHPStan\Analyser\TypeSpecifierContext;
35: use PHPStan\Analyser\UndefinedVariableException;
36: use PHPStan\DependencyInjection\AutowiredParameter;
37: use PHPStan\DependencyInjection\GenerateFactory;
38: use PHPStan\Node\Expr\AlwaysRememberedExpr;
39: use PHPStan\Node\Expr\IntertwinedVariableByReferenceWithExpr;
40: use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
41: use PHPStan\Node\Expr\PropertyInitializationExpr;
42: use PHPStan\Node\IssetExpr;
43: use PHPStan\Node\Printer\ExprPrinter;
44: use PHPStan\Parser\Parser;
45: use PHPStan\Php\PhpVersion;
46: use PHPStan\Php\PhpVersionFactory;
47: use PHPStan\Php\PhpVersions;
48: use PHPStan\Reflection\Assertions;
49: use PHPStan\Reflection\AttributeReflection;
50: use PHPStan\Reflection\AttributeReflectionFactory;
51: use PHPStan\Reflection\ClassConstantReflection;
52: use PHPStan\Reflection\ClassMemberReflection;
53: use PHPStan\Reflection\ClassReflection;
54: use PHPStan\Reflection\ExtendedMethodReflection;
55: use PHPStan\Reflection\ExtendedPropertyReflection;
56: use PHPStan\Reflection\FunctionReflection;
57: use PHPStan\Reflection\InitializerExprContext;
58: use PHPStan\Reflection\InitializerExprTypeResolver;
59: use PHPStan\Reflection\MethodReflection;
60: use PHPStan\Reflection\ParameterReflection;
61: use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection;
62: use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
63: use PHPStan\Reflection\PropertyReflection;
64: use PHPStan\Reflection\ReflectionProvider;
65: use PHPStan\Rules\Properties\PropertyReflectionFinder;
66: use PHPStan\ShouldNotHappenException;
67: use PHPStan\TrinaryLogic;
68: use PHPStan\Type\Accessory\AccessoryArrayListType;
69: use PHPStan\Type\Accessory\HasOffsetValueType;
70: use PHPStan\Type\Accessory\NonEmptyArrayType;
71: use PHPStan\Type\Accessory\OversizedArrayType;
72: use PHPStan\Type\ArrayType;
73: use PHPStan\Type\BenevolentUnionType;
74: use PHPStan\Type\ClosureType;
75: use PHPStan\Type\ConditionalTypeForParameter;
76: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
77: use PHPStan\Type\Constant\ConstantBooleanType;
78: use PHPStan\Type\Constant\ConstantFloatType;
79: use PHPStan\Type\Constant\ConstantIntegerType;
80: use PHPStan\Type\Constant\ConstantStringType;
81: use PHPStan\Type\ConstantTypeHelper;
82: use PHPStan\Type\ErrorType;
83: use PHPStan\Type\GeneralizePrecision;
84: use PHPStan\Type\Generic\TemplateTypeHelper;
85: use PHPStan\Type\Generic\TemplateTypeMap;
86: use PHPStan\Type\IntegerRangeType;
87: use PHPStan\Type\IntegerType;
88: use PHPStan\Type\MixedType;
89: use PHPStan\Type\NeverType;
90: use PHPStan\Type\NullType;
91: use PHPStan\Type\ObjectType;
92: use PHPStan\Type\StaticType;
93: use PHPStan\Type\StringType;
94: use PHPStan\Type\ThisType;
95: use PHPStan\Type\Type;
96: use PHPStan\Type\TypeCombinator;
97: use PHPStan\Type\TypeTraverser;
98: use PHPStan\Type\TypeUtils;
99: use PHPStan\Type\TypeWithClassName;
100: use PHPStan\Type\UnionType;
101: use PHPStan\Type\VerbosityLevel;
102: use PHPStan\Type\VoidType;
103: use function abs;
104: use function array_filter;
105: use function array_key_exists;
106: use function array_key_first;
107: use function array_keys;
108: use function array_map;
109: use function array_merge;
110: use function array_pop;
111: use function array_slice;
112: use function array_values;
113: use function count;
114: use function explode;
115: use function get_class;
116: use function implode;
117: use function in_array;
118: use function is_array;
119: use function is_string;
120: use function ltrim;
121: use function sprintf;
122: use function str_starts_with;
123: use function strlen;
124: use function strtolower;
125: use function substr;
126: use function uksort;
127: use function usort;
128: use const PHP_INT_MAX;
129: use const PHP_INT_MIN;
130:
131: /**
132: * @phpstan-import-type GeneratorTValueType from GeneratorNodeScopeResolver
133: * @phpstan-import-type GeneratorTSendType from GeneratorNodeScopeResolver
134: */
135: #[GenerateFactory(interface: InternalGeneratorScopeFactory::class)]
136: final class GeneratorScope implements Scope, NodeCallbackInvoker
137: {
138:
139: /** @var non-empty-string|null */
140: private ?string $namespace;
141:
142: /**
143: * @param int|array{min: int, max: int}|null $configPhpVersion
144: * @param array<string, ExpressionTypeHolder> $expressionTypes
145: * @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
146: * @param list<non-empty-string> $inClosureBindScopeClasses
147: * @param array<string, true> $currentlyAssignedExpressions
148: * @param array<string, true> $currentlyAllowedUndefinedExpressions
149: * @param array<string, ExpressionTypeHolder> $nativeExpressionTypes
150: * @param list<array{MethodReflection|FunctionReflection|null, ParameterReflection|null}> $inFunctionCallsStack
151: */
152: public function __construct(
153: private InternalGeneratorScopeFactory $scopeFactory,
154: private ReflectionProvider $reflectionProvider,
155: private InitializerExprTypeResolver $initializerExprTypeResolver,
156: private TypeSpecifier $typeSpecifier,
157: private ExprPrinter $exprPrinter,
158: private PropertyReflectionFinder $propertyReflectionFinder,
159: #[AutowiredParameter(ref: '@currentPhpVersionSimpleParser')]
160: private Parser $parser,
161: private ConstantResolver $constantResolver,
162: private ScopeContext $context,
163: private PhpVersion $phpVersion,
164: private AttributeReflectionFactory $attributeReflectionFactory,
165: #[AutowiredParameter(ref: '%phpVersion%')]
166: private int|array|null $configPhpVersion,
167: private bool $declareStrictTypes = false,
168: private PhpFunctionFromParserNodeReflection|null $function = null,
169: ?string $namespace = null,
170: private array $expressionTypes = [],
171: private array $nativeExpressionTypes = [],
172: private array $conditionalExpressions = [],
173: private array $inClosureBindScopeClasses = [],
174: private ?ClosureType $anonymousFunctionReflection = null,
175: private bool $inFirstLevelStatement = true,
176: private array $currentlyAssignedExpressions = [],
177: private array $currentlyAllowedUndefinedExpressions = [],
178: private array $inFunctionCallsStack = [],
179: private bool $afterExtractCall = false,
180: private ?Scope $parentScope = null,
181: private bool $nativeTypesPromoted = false,
182: )
183: {
184: if ($namespace === '') {
185: $namespace = null;
186: }
187:
188: $this->namespace = $namespace;
189: }
190:
191: /**
192: * This method is meant to be called for expressions for which the type
193: * should be stored in Scope itself.
194: *
195: * This is meant to be used only by handlers in PHPStan\Analyser\Generator namespace.
196: *
197: * All other code should use `getType()` method.
198: */
199: public function getExpressionType(Expr $expr): ?Type
200: {
201: $exprString = $this->getNodeKey($expr);
202: if (array_key_exists($exprString, $this->expressionTypes)) {
203: return $this->expressionTypes[$exprString]->getType();
204: }
205:
206: return null;
207: }
208:
209: public function getNativeExpressionType(Expr $expr): ?Type
210: {
211: $exprString = $this->getNodeKey($expr);
212: if (array_key_exists($exprString, $this->nativeExpressionTypes)) {
213: return $this->nativeExpressionTypes[$exprString]->getType();
214: }
215:
216: return null;
217: }
218:
219: /** @api */
220: public function hasExpressionType(Expr $node): TrinaryLogic
221: {
222: if ($node instanceof Variable && is_string($node->name)) {
223: return $this->hasVariableType($node->name);
224: }
225:
226: $exprString = $this->getNodeKey($node);
227: if (!isset($this->expressionTypes[$exprString])) {
228: return TrinaryLogic::createNo();
229: }
230: return $this->expressionTypes[$exprString]->getCertainty();
231: }
232:
233: private function getNodeKey(Expr $node): string
234: {
235: return $this->exprPrinter->printExpr($node);
236: }
237:
238: /**
239: * @return Generator<int, GeneratorTValueType, GeneratorTSendType, self>
240: */
241: public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty): Generator
242: {
243: $node = new Variable($variableName);
244: $assignExprGen = $this->assignExpression($node, $type, $nativeType);
245: yield from $assignExprGen;
246: $scope = $assignExprGen->getReturn();
247: if ($certainty->no()) {
248: throw new ShouldNotHappenException();
249: } elseif (!$certainty->yes()) {
250: $exprString = '$' . $variableName;
251: $scope->expressionTypes[$exprString] = new ExpressionTypeHolder($node, $type, $certainty);
252: $scope->nativeExpressionTypes[$exprString] = new ExpressionTypeHolder($node, $nativeType, $certainty);
253: }
254:
255: foreach ($scope->expressionTypes as $expressionType) {
256: $intertwinedExpr = $expressionType->getExpr();
257: if (!$intertwinedExpr instanceof IntertwinedVariableByReferenceWithExpr) {
258: continue;
259: }
260: if (!$expressionType->getCertainty()->yes()) {
261: continue;
262: }
263: if ($intertwinedExpr->getVariableName() !== $variableName) {
264: continue;
265: }
266:
267: $dependentExpr = $intertwinedExpr->getExpr();
268: $has = $scope->hasExpressionType($dependentExpr);
269: $assignedExpr = $intertwinedExpr->getAssignedExpr();
270: $assignedExprType = $scope->getExpressionType($assignedExpr);
271: $assignedExprNativeType = $scope->getNativeExpressionType($assignedExpr);
272: if (
273: $assignedExprType === null
274: || $assignedExprNativeType === null
275: ) {
276: continue;
277: }
278:
279: if (
280: $dependentExpr instanceof Variable
281: && is_string($dependentExpr->name)
282: && !$has->no()
283: ) {
284: $assignVarGen = $scope->assignVariable(
285: $dependentExpr->name,
286: $assignedExprType,
287: $assignedExprNativeType,
288: $has,
289: );
290: yield from $assignVarGen;
291: $scope = $assignVarGen->getReturn();
292: } else {
293: $assignExprGen = $scope->assignExpression(
294: $dependentExpr,
295: $assignedExprType,
296: $assignedExprNativeType,
297: );
298: yield from $assignExprGen;
299: $scope = $assignExprGen->getReturn();
300: }
301:
302: }
303:
304: return $scope;
305: }
306:
307: /**
308: * @return Generator<int, GeneratorTValueType, GeneratorTSendType, self>
309: */
310: public function assignExpression(Expr $expr, Type $type, Type $nativeType): Generator
311: {
312: $scope = $this;
313: if ($expr instanceof PropertyFetch) {
314: $scope = $this->invalidateExpression($expr)
315: ->invalidateMethodsOnExpression($expr->var);
316: } elseif ($expr instanceof Expr\StaticPropertyFetch) {
317: $scope = $this->invalidateExpression($expr);
318: } elseif ($expr instanceof Variable) {
319: $scope = $this->invalidateExpression($expr);
320: }
321:
322: $specExprTypeGen = $scope->specifyExpressionType($expr, $type, $nativeType, TrinaryLogic::createYes());
323: yield from $specExprTypeGen;
324: return $specExprTypeGen->getReturn();
325: }
326:
327: /**
328: * @return Generator<int, GeneratorTValueType, GeneratorTSendType, self>
329: */
330: private function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, TrinaryLogic $certainty): Generator
331: {
332: if ($expr instanceof ConstFetch) {
333: $loweredConstName = strtolower($expr->name->toString());
334: if (in_array($loweredConstName, ['true', 'false', 'null'], true)) {
335: return $this;
336: }
337: }
338:
339: if ($expr instanceof FuncCall && $expr->name instanceof Name && $type->isFalse()->yes()) {
340: $functionName = $this->reflectionProvider->resolveFunctionName($expr->name, $this);
341: if ($functionName !== null && in_array(strtolower($functionName), [
342: 'is_dir',
343: 'is_file',
344: 'file_exists',
345: ], true)) {
346: return $this;
347: }
348: }
349:
350: $scope = $this;
351: if (
352: $expr instanceof Expr\ArrayDimFetch
353: && $expr->dim !== null
354: && !$expr->dim instanceof Expr\PreInc
355: && !$expr->dim instanceof Expr\PreDec
356: && !$expr->dim instanceof Expr\PostDec
357: && !$expr->dim instanceof Expr\PostInc
358: ) {
359: $dimType = (yield new TypeExprRequest($expr->dim))->type->toArrayKey();
360: if ($dimType->isInteger()->yes() || $dimType->isString()->yes()) {
361: $exprVarResult = yield new TypeExprRequest($expr->var);
362: $exprVarType = $exprVarResult->type;
363: if (!$exprVarType instanceof MixedType && !$exprVarType->isArray()->no()) {
364: $types = [
365: new ArrayType(new MixedType(), new MixedType()),
366: new ObjectType(ArrayAccess::class),
367: new NullType(),
368: ];
369: if ($dimType->isInteger()->yes()) {
370: $types[] = new StringType();
371: }
372: $offsetValueType = TypeCombinator::intersect($exprVarType, TypeCombinator::union(...$types));
373:
374: if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) {
375: $offsetValueType = TypeCombinator::intersect(
376: $offsetValueType,
377: new HasOffsetValueType($dimType, $type),
378: );
379: }
380:
381: $specifyExprGen = $scope->specifyExpressionType(
382: $expr->var,
383: $offsetValueType,
384: $exprVarResult->nativeType,
385: $certainty,
386: );
387: yield from $specifyExprGen;
388: $scope = $specifyExprGen->getReturn();
389:
390: }
391: }
392: }
393:
394: if ($certainty->no()) {
395: throw new ShouldNotHappenException();
396: }
397:
398: $exprString = $this->getNodeKey($expr);
399: $expressionTypes = $scope->expressionTypes;
400: $expressionTypes[$exprString] = new ExpressionTypeHolder($expr, $type, $certainty);
401: $nativeTypes = $scope->nativeExpressionTypes;
402: $nativeTypes[$exprString] = new ExpressionTypeHolder($expr, $nativeType, $certainty);
403:
404: $scope = $this->scopeFactory->create(
405: $this->context,
406: $this->isDeclareStrictTypes(),
407: $this->getFunction(),
408: $this->getNamespace(),
409: $expressionTypes,
410: $nativeTypes,
411: $this->conditionalExpressions,
412: $this->inClosureBindScopeClasses,
413: $this->anonymousFunctionReflection,
414: $this->inFirstLevelStatement,
415: $this->currentlyAssignedExpressions,
416: $this->currentlyAllowedUndefinedExpressions,
417: $this->inFunctionCallsStack,
418: $this->afterExtractCall,
419: $this->parentScope,
420: $this->nativeTypesPromoted,
421: );
422:
423: if ($expr instanceof AlwaysRememberedExpr) {
424: $alwaysExprGen = $scope->specifyExpressionType($expr->expr, $type, $nativeType, $certainty);
425: yield from $alwaysExprGen;
426: return $alwaysExprGen->getReturn();
427: }
428:
429: return $scope;
430: }
431:
432: private function invalidateMethodsOnExpression(Expr $expressionToInvalidate): self
433: {
434: $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate);
435: $expressionTypes = $this->expressionTypes;
436: $nativeExpressionTypes = $this->nativeExpressionTypes;
437: $invalidated = false;
438: $nodeFinder = new NodeFinder();
439: foreach ($expressionTypes as $exprString => $exprTypeHolder) {
440: $expr = $exprTypeHolder->getExpr();
441: $found = $nodeFinder->findFirst([$expr], function (Node $node) use ($exprStringToInvalidate): bool {
442: if (!$node instanceof MethodCall) {
443: return false;
444: }
445:
446: return $this->getNodeKey($node->var) === $exprStringToInvalidate;
447: });
448: if ($found === null) {
449: continue;
450: }
451:
452: unset($expressionTypes[$exprString]);
453: unset($nativeExpressionTypes[$exprString]);
454: $invalidated = true;
455: }
456:
457: if (!$invalidated) {
458: return $this;
459: }
460:
461: return $this->scopeFactory->create(
462: $this->context,
463: $this->isDeclareStrictTypes(),
464: $this->getFunction(),
465: $this->getNamespace(),
466: $expressionTypes,
467: $nativeExpressionTypes,
468: $this->conditionalExpressions,
469: $this->inClosureBindScopeClasses,
470: $this->anonymousFunctionReflection,
471: $this->inFirstLevelStatement,
472: $this->currentlyAssignedExpressions,
473: $this->currentlyAllowedUndefinedExpressions,
474: [],
475: $this->afterExtractCall,
476: $this->parentScope,
477: $this->nativeTypesPromoted,
478: );
479: }
480:
481: /**
482: * @deprecated
483: */
484: private function assignExpressionInternal(Expr $expr, Type $type, Type $nativeType): self
485: {
486: $scope = $this;
487: if ($expr instanceof PropertyFetch) {
488: $scope = $this->invalidateExpression($expr)
489: ->invalidateMethodsOnExpression($expr->var);
490: } elseif ($expr instanceof Expr\StaticPropertyFetch) {
491: $scope = $this->invalidateExpression($expr);
492: } elseif ($expr instanceof Variable) {
493: $scope = $this->invalidateExpression($expr);
494: }
495:
496: return $scope->specifyExpressionTypeInternal($expr, $type, $nativeType, TrinaryLogic::createYes());
497: }
498:
499: public function assignInitializedProperty(Type $fetchedOnType, string $propertyName): self
500: {
501: if (!$this->isInClass()) {
502: return $this;
503: }
504:
505: if (TypeUtils::findThisType($fetchedOnType) === null) {
506: return $this;
507: }
508:
509: $propertyReflection = $this->getInstancePropertyReflection($fetchedOnType, $propertyName);
510: if ($propertyReflection === null) {
511: return $this;
512: }
513: $declaringClass = $propertyReflection->getDeclaringClass();
514: if ($this->getClassReflection()->getName() !== $declaringClass->getName()) {
515: return $this;
516: }
517: if (!$declaringClass->hasNativeProperty($propertyName)) {
518: return $this;
519: }
520:
521: $expr = new PropertyInitializationExpr($propertyName);
522: $exprHolder = ExpressionTypeHolder::createYes($expr, new MixedType());
523: $exprString = $this->getNodeKey($expr);
524: $expressionTypes = $this->expressionTypes;
525: $expressionTypes[$exprString] = $exprHolder;
526: $nativeTypes = $this->nativeExpressionTypes;
527: $nativeTypes[$exprString] = $exprHolder;
528:
529: return $this->scopeFactory->create(
530: $this->context,
531: $this->isDeclareStrictTypes(),
532: $this->getFunction(),
533: $this->getNamespace(),
534: $expressionTypes,
535: $nativeTypes,
536: $this->conditionalExpressions,
537: $this->inClosureBindScopeClasses,
538: $this->anonymousFunctionReflection,
539: $this->inFirstLevelStatement,
540: $this->currentlyAssignedExpressions,
541: $this->currentlyAllowedUndefinedExpressions,
542: $this->inFunctionCallsStack,
543: $this->afterExtractCall,
544: $this->parentScope,
545: $this->nativeTypesPromoted,
546: );
547: }
548:
549: public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false): self
550: {
551: $expressionTypes = $this->expressionTypes;
552: $nativeExpressionTypes = $this->nativeExpressionTypes;
553: $invalidated = false;
554: $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate);
555:
556: foreach ($expressionTypes as $exprString => $exprTypeHolder) {
557: $exprExpr = $exprTypeHolder->getExpr();
558: if (!$this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $exprExpr, $requireMoreCharacters)) {
559: continue;
560: }
561:
562: unset($expressionTypes[$exprString]);
563: unset($nativeExpressionTypes[$exprString]);
564: $invalidated = true;
565: }
566:
567: $newConditionalExpressions = [];
568: foreach ($this->conditionalExpressions as $conditionalExprString => $holders) {
569: if (count($holders) === 0) {
570: continue;
571: }
572: if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $holders[array_key_first($holders)]->getTypeHolder()->getExpr())) {
573: $invalidated = true;
574: continue;
575: }
576: foreach ($holders as $holder) {
577: $conditionalTypeHolders = $holder->getConditionExpressionTypeHolders();
578: foreach ($conditionalTypeHolders as $conditionalTypeHolder) {
579: if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $conditionalTypeHolder->getExpr())) {
580: $invalidated = true;
581: continue 3;
582: }
583: }
584: }
585: $newConditionalExpressions[$conditionalExprString] = $holders;
586: }
587:
588: if (!$invalidated) {
589: return $this;
590: }
591:
592: return $this->scopeFactory->create(
593: $this->context,
594: $this->isDeclareStrictTypes(),
595: $this->getFunction(),
596: $this->getNamespace(),
597: $expressionTypes,
598: $nativeExpressionTypes,
599: $newConditionalExpressions,
600: $this->inClosureBindScopeClasses,
601: $this->anonymousFunctionReflection,
602: $this->inFirstLevelStatement,
603: $this->currentlyAssignedExpressions,
604: $this->currentlyAllowedUndefinedExpressions,
605: [],
606: $this->afterExtractCall,
607: $this->parentScope,
608: $this->nativeTypesPromoted,
609: );
610: }
611:
612: private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr $exprToInvalidate, Expr $expr, bool $requireMoreCharacters = false): bool
613: {
614: if ($requireMoreCharacters && $exprStringToInvalidate === $this->getNodeKey($expr)) {
615: return false;
616: }
617:
618: // Variables will not contain traversable expressions. skip the NodeFinder overhead
619: if ($expr instanceof Variable && is_string($expr->name) && !$requireMoreCharacters) {
620: return $exprStringToInvalidate === $this->getNodeKey($expr);
621: }
622:
623: $nodeFinder = new NodeFinder();
624: $expressionToInvalidateClass = get_class($exprToInvalidate);
625: $found = $nodeFinder->findFirst([$expr], function (Node $node) use ($expressionToInvalidateClass, $exprStringToInvalidate): bool {
626: if (
627: $exprStringToInvalidate === '$this'
628: && $node instanceof Name
629: && (
630: in_array($node->toLowerString(), ['self', 'static', 'parent'], true)
631: || ($this->getClassReflection() !== null && $this->getClassReflection()->is($this->resolveName($node)))
632: )
633: ) {
634: return true;
635: }
636:
637: if (!$node instanceof $expressionToInvalidateClass) {
638: return false;
639: }
640:
641: $nodeString = $this->getNodeKey($node);
642:
643: return $nodeString === $exprStringToInvalidate;
644: });
645:
646: if ($found === null) {
647: return false;
648: }
649:
650: if (
651: $expr instanceof PropertyFetch
652: && $requireMoreCharacters
653: && $this->isReadonlyPropertyFetch($expr, false)
654: ) {
655: return false;
656: }
657:
658: return true;
659: }
660:
661: private function isReadonlyPropertyFetch(PropertyFetch $expr, bool $allowOnlyOnThis): bool
662: {
663: if (!$this->phpVersion->supportsReadOnlyProperties()) {
664: return false;
665: }
666:
667: while ($expr instanceof PropertyFetch) {
668: if ($expr->var instanceof Variable) {
669: if (
670: $allowOnlyOnThis
671: && (
672: ! $expr->name instanceof Node\Identifier
673: || !is_string($expr->var->name)
674: || $expr->var->name !== 'this'
675: )
676: ) {
677: return false;
678: }
679: } elseif (!$expr->var instanceof PropertyFetch) {
680: return false;
681: }
682:
683: $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $this);
684: if ($propertyReflection === null) {
685: return false;
686: }
687:
688: $nativePropertyReflection = $propertyReflection->getNativeReflection();
689: if ($nativePropertyReflection === null || !$nativePropertyReflection->isReadOnly()) {
690: return false;
691: }
692:
693: $expr = $expr->var;
694: }
695:
696: return true;
697: }
698:
699: /**
700: * @param ConditionalExpressionHolder[] $conditionalExpressionHolders
701: */
702: public function addConditionalExpressions(string $exprString, array $conditionalExpressionHolders): self
703: {
704: $conditionalExpressions = $this->conditionalExpressions;
705: $conditionalExpressions[$exprString] = $conditionalExpressionHolders;
706: return $this->scopeFactory->create(
707: $this->context,
708: $this->isDeclareStrictTypes(),
709: $this->getFunction(),
710: $this->getNamespace(),
711: $this->expressionTypes,
712: $this->nativeExpressionTypes,
713: $conditionalExpressions,
714: $this->inClosureBindScopeClasses,
715: $this->anonymousFunctionReflection,
716: $this->inFirstLevelStatement,
717: $this->currentlyAssignedExpressions,
718: $this->currentlyAllowedUndefinedExpressions,
719: $this->inFunctionCallsStack,
720: $this->afterExtractCall,
721: $this->parentScope,
722: $this->nativeTypesPromoted,
723: );
724: }
725:
726: /** @api */
727: public function enterNamespace(string $namespaceName): self
728: {
729: return $this->scopeFactory->create(
730: $this->context->beginFile(),
731: $this->isDeclareStrictTypes(),
732: null,
733: $namespaceName,
734: );
735: }
736:
737: /** @api */
738: public function isInClass(): bool
739: {
740: return $this->context->getClassReflection() !== null;
741: }
742:
743: /** @api */
744: public function getClassReflection(): ?ClassReflection
745: {
746: return $this->context->getClassReflection();
747: }
748:
749: /** @api */
750: public function enterClass(ClassReflection $classReflection): self
751: {
752: $thisHolder = ExpressionTypeHolder::createYes(new Variable('this'), new ThisType($classReflection));
753: $constantTypes = $this->getConstantTypes();
754: $constantTypes['$this'] = $thisHolder;
755: $nativeConstantTypes = $this->getNativeConstantTypes();
756: $nativeConstantTypes['$this'] = $thisHolder;
757:
758: return $this->scopeFactory->create(
759: $this->context->enterClass($classReflection),
760: $this->isDeclareStrictTypes(),
761: null,
762: $this->getNamespace(),
763: $constantTypes,
764: $nativeConstantTypes,
765: [],
766: [],
767: null,
768: true,
769: [],
770: [],
771: [],
772: false,
773: $classReflection->isAnonymous() ? $this : null,
774: );
775: }
776:
777: public function enterTrait(ClassReflection $traitReflection): self
778: {
779: $namespace = null;
780: $traitName = $traitReflection->getName();
781: $traitNameParts = explode('\\', $traitName);
782: if (count($traitNameParts) > 1) {
783: $namespace = implode('\\', array_slice($traitNameParts, 0, -1));
784: }
785: return $this->scopeFactory->create(
786: $this->context->enterTrait($traitReflection),
787: $this->isDeclareStrictTypes(),
788: $this->getFunction(),
789: $namespace,
790: $this->expressionTypes,
791: $this->nativeExpressionTypes,
792: [],
793: $this->inClosureBindScopeClasses,
794: $this->anonymousFunctionReflection,
795: );
796: }
797:
798: /**
799: * @api
800: * @deprecated Use canReadProperty() or canWriteProperty()
801: */
802: public function canAccessProperty(PropertyReflection $propertyReflection): bool
803: {
804: return $this->canAccessClassMember($propertyReflection);
805: }
806:
807: /** @api */
808: public function canReadProperty(ExtendedPropertyReflection $propertyReflection): bool
809: {
810: return $this->canAccessClassMember($propertyReflection);
811: }
812:
813: /** @api */
814: public function canWriteProperty(ExtendedPropertyReflection $propertyReflection): bool
815: {
816: if (!$propertyReflection->isPrivateSet() && !$propertyReflection->isProtectedSet()) {
817: return $this->canAccessClassMember($propertyReflection);
818: }
819:
820: if (!$this->phpVersion->supportsAsymmetricVisibility()) {
821: return $this->canAccessClassMember($propertyReflection);
822: }
823:
824: $propertyDeclaringClass = $propertyReflection->getDeclaringClass();
825: $canAccessClassMember = static function (ClassReflection $classReflection) use ($propertyReflection, $propertyDeclaringClass) {
826: if ($propertyReflection->isPrivateSet()) {
827: return $classReflection->getName() === $propertyDeclaringClass->getName();
828: }
829:
830: // protected set
831:
832: if (
833: $classReflection->getName() === $propertyDeclaringClass->getName()
834: || $classReflection->isSubclassOfClass($propertyDeclaringClass->removeFinalKeywordOverride())
835: ) {
836: return true;
837: }
838:
839: return $propertyReflection->getDeclaringClass()->isSubclassOfClass($classReflection);
840: };
841:
842: foreach ($this->inClosureBindScopeClasses as $inClosureBindScopeClass) {
843: if (!$this->reflectionProvider->hasClass($inClosureBindScopeClass)) {
844: continue;
845: }
846:
847: if ($canAccessClassMember($this->reflectionProvider->getClass($inClosureBindScopeClass))) {
848: return true;
849: }
850: }
851:
852: if ($this->isInClass()) {
853: return $canAccessClassMember($this->getClassReflection());
854: }
855:
856: return false;
857: }
858:
859: /** @api */
860: public function canCallMethod(MethodReflection $methodReflection): bool
861: {
862: if ($this->canAccessClassMember($methodReflection)) {
863: return true;
864: }
865:
866: return $this->canAccessClassMember($methodReflection->getPrototype());
867: }
868:
869: /** @api */
870: public function canAccessConstant(ClassConstantReflection $constantReflection): bool
871: {
872: return $this->canAccessClassMember($constantReflection);
873: }
874:
875: private function canAccessClassMember(ClassMemberReflection $classMemberReflection): bool
876: {
877: if ($classMemberReflection->isPublic()) {
878: return true;
879: }
880:
881: $classMemberDeclaringClass = $classMemberReflection->getDeclaringClass();
882: $canAccessClassMember = static function (ClassReflection $classReflection) use ($classMemberReflection, $classMemberDeclaringClass) {
883: if ($classMemberReflection->isPrivate()) {
884: return $classReflection->getName() === $classMemberDeclaringClass->getName();
885: }
886:
887: // protected
888:
889: if (
890: $classReflection->getName() === $classMemberDeclaringClass->getName()
891: || $classReflection->isSubclassOfClass($classMemberDeclaringClass->removeFinalKeywordOverride())
892: ) {
893: return true;
894: }
895:
896: return $classMemberReflection->getDeclaringClass()->isSubclassOfClass($classReflection);
897: };
898:
899: foreach ($this->inClosureBindScopeClasses as $inClosureBindScopeClass) {
900: if (!$this->reflectionProvider->hasClass($inClosureBindScopeClass)) {
901: continue;
902: }
903:
904: if ($canAccessClassMember($this->reflectionProvider->getClass($inClosureBindScopeClass))) {
905: return true;
906: }
907: }
908:
909: if ($this->isInClass()) {
910: return $canAccessClassMember($this->getClassReflection());
911: }
912:
913: return false;
914: }
915:
916: public function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type
917: {
918: if ($typeWithMethod instanceof UnionType) {
919: $typeWithMethod = $typeWithMethod->filterTypes(static fn (Type $innerType) => $innerType->hasMethod($methodName)->yes());
920: }
921:
922: if (!$typeWithMethod->hasMethod($methodName)->yes()) {
923: return null;
924: }
925:
926: return $typeWithMethod;
927: }
928:
929: /** @api */
930: public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection
931: {
932: $type = $this->filterTypeWithMethod($typeWithMethod, $methodName);
933: if ($type === null) {
934: return null;
935: }
936:
937: return $type->getMethod($methodName, $this);
938: }
939:
940: public function getNakedMethod(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection
941: {
942: $type = $this->filterTypeWithMethod($typeWithMethod, $methodName);
943: if ($type === null) {
944: return null;
945: }
946:
947: return $type->getUnresolvedMethodPrototype($methodName, $this)->getNakedMethod();
948: }
949:
950: public function getIterableKeyType(Type $iteratee): Type
951: {
952: if ($iteratee instanceof UnionType) {
953: $filtered = $iteratee->filterTypes(static fn (Type $innerType) => $innerType->isIterable()->yes());
954: if (!$filtered instanceof NeverType) {
955: $iteratee = $filtered;
956: }
957: }
958:
959: return $iteratee->getIterableKeyType();
960: }
961:
962: public function getIterableValueType(Type $iteratee): Type
963: {
964: if ($iteratee instanceof UnionType) {
965: $filtered = $iteratee->filterTypes(static fn (Type $innerType) => $innerType->isIterable()->yes());
966: if (!$filtered instanceof NeverType) {
967: $iteratee = $filtered;
968: }
969: }
970:
971: return $iteratee->getIterableValueType();
972: }
973:
974: /**
975: * @return string[]
976: */
977: public function debug(): array
978: {
979: $descriptions = [];
980: foreach ($this->expressionTypes as $name => $variableTypeHolder) {
981: $key = sprintf('%s (%s)', $name, $variableTypeHolder->getCertainty()->describe());
982: $descriptions[$key] = $variableTypeHolder->getType()->describe(VerbosityLevel::precise());
983: }
984: foreach ($this->nativeExpressionTypes as $exprString => $nativeTypeHolder) {
985: $key = sprintf('native %s (%s)', $exprString, $nativeTypeHolder->getCertainty()->describe());
986: $descriptions[$key] = $nativeTypeHolder->getType()->describe(VerbosityLevel::precise());
987: }
988:
989: foreach ($this->conditionalExpressions as $exprString => $holders) {
990: foreach (array_values($holders) as $i => $holder) {
991: $key = sprintf('condition about %s #%d', $exprString, $i + 1);
992: $parts = [];
993: foreach ($holder->getConditionExpressionTypeHolders() as $conditionalExprString => $expressionTypeHolder) {
994: $parts[] = $conditionalExprString . '=' . $expressionTypeHolder->getType()->describe(VerbosityLevel::precise());
995: }
996: $condition = implode(' && ', $parts);
997: $descriptions[$key] = sprintf(
998: 'if %s then %s is %s (%s)',
999: $condition,
1000: $exprString,
1001: $holder->getTypeHolder()->getType()->describe(VerbosityLevel::precise()),
1002: $holder->getTypeHolder()->getCertainty()->describe(),
1003: );
1004: }
1005: }
1006:
1007: return $descriptions;
1008: }
1009:
1010: /** @api */
1011: public function getNamespace(): ?string
1012: {
1013: return $this->namespace;
1014: }
1015:
1016: /** @api */
1017: public function getFile(): string
1018: {
1019: return $this->context->getFile();
1020: }
1021:
1022: public function getFileDescription(): string
1023: {
1024: if ($this->context->getTraitReflection() === null) {
1025: return $this->getFile();
1026: }
1027:
1028: /** @var ClassReflection $classReflection */
1029: $classReflection = $this->context->getClassReflection();
1030:
1031: $className = $classReflection->getDisplayName();
1032: if (!$classReflection->isAnonymous()) {
1033: $className = sprintf('class %s', $className);
1034: }
1035:
1036: $traitReflection = $this->context->getTraitReflection();
1037: if ($traitReflection->getFileName() === null) {
1038: throw new ShouldNotHappenException();
1039: }
1040:
1041: return sprintf(
1042: '%s (in context of %s)',
1043: $traitReflection->getFileName(),
1044: $className,
1045: );
1046: }
1047:
1048: /** @api */
1049: public function isDeclareStrictTypes(): bool
1050: {
1051: return $this->declareStrictTypes;
1052: }
1053:
1054: public function enterDeclareStrictTypes(): self
1055: {
1056: return $this->scopeFactory->create(
1057: $this->context,
1058: true,
1059: null,
1060: null,
1061: $this->expressionTypes,
1062: $this->nativeExpressionTypes,
1063: );
1064: }
1065:
1066: /** @api */
1067: public function isInTrait(): bool
1068: {
1069: return $this->context->getTraitReflection() !== null;
1070: }
1071:
1072: /** @api */
1073: public function getTraitReflection(): ?ClassReflection
1074: {
1075: return $this->context->getTraitReflection();
1076: }
1077:
1078: /** @api */
1079: public function getFunction(): ?PhpFunctionFromParserNodeReflection
1080: {
1081: return $this->function;
1082: }
1083:
1084: /** @api */
1085: public function getFunctionName(): ?string
1086: {
1087: return $this->function !== null ? $this->function->getName() : null;
1088: }
1089:
1090: /**
1091: * @api
1092: * @param Type[] $phpDocParameterTypes
1093: * @param Type[] $parameterOutTypes
1094: * @param array<string, bool> $immediatelyInvokedCallableParameters
1095: * @param array<string, Type> $phpDocClosureThisTypeParameters
1096: */
1097: public function enterFunction(
1098: Node\Stmt\Function_ $function,
1099: TemplateTypeMap $templateTypeMap,
1100: array $phpDocParameterTypes,
1101: ?Type $phpDocReturnType,
1102: ?Type $throwType,
1103: ?string $deprecatedDescription,
1104: bool $isDeprecated,
1105: bool $isInternal,
1106: ?bool $isPure = null,
1107: bool $acceptsNamedArguments = true,
1108: ?Assertions $asserts = null,
1109: ?string $phpDocComment = null,
1110: array $parameterOutTypes = [],
1111: array $immediatelyInvokedCallableParameters = [],
1112: array $phpDocClosureThisTypeParameters = [],
1113: ): self
1114: {
1115: return $this->enterFunctionLike(
1116: new PhpFunctionFromParserNodeReflection(
1117: $function,
1118: $this->getFile(),
1119: $templateTypeMap,
1120: $this->getRealParameterTypes($function),
1121: array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $phpDocParameterTypes),
1122: $this->getRealParameterDefaultValues($function),
1123: $this->getParameterAttributes($function),
1124: $this->getFunctionType($function->returnType, $function->returnType === null, false),
1125: $phpDocReturnType !== null ? TemplateTypeHelper::toArgument($phpDocReturnType) : null,
1126: $throwType,
1127: $deprecatedDescription,
1128: $isDeprecated,
1129: $isInternal,
1130: $isPure,
1131: $acceptsNamedArguments,
1132: $asserts ?? Assertions::createEmpty(),
1133: $phpDocComment,
1134: array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $parameterOutTypes),
1135: $immediatelyInvokedCallableParameters,
1136: $phpDocClosureThisTypeParameters,
1137: $this->attributeReflectionFactory->fromAttrGroups($function->attrGroups, InitializerExprContext::fromStubParameter(null, $this->getFile(), $function)),
1138: ),
1139: false,
1140: );
1141: }
1142:
1143: private function enterFunctionLike(
1144: PhpFunctionFromParserNodeReflection $functionReflection,
1145: bool $preserveConstructorScope,
1146: ): self
1147: {
1148: $parametersByName = [];
1149:
1150: foreach ($functionReflection->getParameters() as $parameter) {
1151: $parametersByName[$parameter->getName()] = $parameter;
1152: }
1153:
1154: $expressionTypes = [];
1155: $nativeExpressionTypes = [];
1156: $conditionalTypes = [];
1157:
1158: if ($preserveConstructorScope) {
1159: $expressionTypes = $this->rememberConstructorExpressions($this->expressionTypes);
1160: $nativeExpressionTypes = $this->rememberConstructorExpressions($this->nativeExpressionTypes);
1161: }
1162:
1163: foreach ($functionReflection->getParameters() as $parameter) {
1164: $parameterType = $parameter->getType();
1165:
1166: if ($parameterType instanceof ConditionalTypeForParameter) {
1167: $targetParameterName = substr($parameterType->getParameterName(), 1);
1168: if (array_key_exists($targetParameterName, $parametersByName)) {
1169: $targetParameter = $parametersByName[$targetParameterName];
1170:
1171: $ifType = $parameterType->isNegated() ? $parameterType->getElse() : $parameterType->getIf();
1172: $elseType = $parameterType->isNegated() ? $parameterType->getIf() : $parameterType->getElse();
1173:
1174: $holder = new ConditionalExpressionHolder([
1175: $parameterType->getParameterName() => ExpressionTypeHolder::createYes(new Variable($targetParameterName), TypeCombinator::intersect($targetParameter->getType(), $parameterType->getTarget())),
1176: ], new ExpressionTypeHolder(new Variable($parameter->getName()), $ifType, TrinaryLogic::createYes()));
1177: $conditionalTypes['$' . $parameter->getName()][$holder->getKey()] = $holder;
1178:
1179: $holder = new ConditionalExpressionHolder([
1180: $parameterType->getParameterName() => ExpressionTypeHolder::createYes(new Variable($targetParameterName), TypeCombinator::remove($targetParameter->getType(), $parameterType->getTarget())),
1181: ], new ExpressionTypeHolder(new Variable($parameter->getName()), $elseType, TrinaryLogic::createYes()));
1182: $conditionalTypes['$' . $parameter->getName()][$holder->getKey()] = $holder;
1183: }
1184: }
1185:
1186: $paramExprString = '$' . $parameter->getName();
1187: if ($parameter->isVariadic()) {
1188: if (!$this->getPhpVersion()->supportsNamedArguments()->no() && $functionReflection->acceptsNamedArguments()->yes()) {
1189: $parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType);
1190: } else {
1191: $parameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $parameterType), new AccessoryArrayListType());
1192: }
1193: }
1194: $parameterNode = new Variable($parameter->getName());
1195: $expressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($parameterNode, $parameterType);
1196:
1197: $parameterOriginalValueExpr = new ParameterVariableOriginalValueExpr($parameter->getName());
1198: $parameterOriginalValueExprString = $this->getNodeKey($parameterOriginalValueExpr);
1199: $expressionTypes[$parameterOriginalValueExprString] = ExpressionTypeHolder::createYes($parameterOriginalValueExpr, $parameterType);
1200:
1201: $nativeParameterType = $parameter->getNativeType();
1202: if ($parameter->isVariadic()) {
1203: if (!$this->getPhpVersion()->supportsNamedArguments()->no() && $functionReflection->acceptsNamedArguments()->yes()) {
1204: $nativeParameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $nativeParameterType);
1205: } else {
1206: $nativeParameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $nativeParameterType), new AccessoryArrayListType());
1207: }
1208: }
1209: $nativeExpressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($parameterNode, $nativeParameterType);
1210: $nativeExpressionTypes[$parameterOriginalValueExprString] = ExpressionTypeHolder::createYes($parameterOriginalValueExpr, $nativeParameterType);
1211: }
1212:
1213: return $this->scopeFactory->create(
1214: $this->context,
1215: $this->isDeclareStrictTypes(),
1216: $functionReflection,
1217: $this->getNamespace(),
1218: array_merge($this->getConstantTypes(), $expressionTypes),
1219: array_merge($this->getNativeConstantTypes(), $nativeExpressionTypes),
1220: $conditionalTypes,
1221: );
1222: }
1223:
1224: /**
1225: * @param array<string, ExpressionTypeHolder> $currentExpressionTypes
1226: * @return array<string, ExpressionTypeHolder>
1227: */
1228: private function rememberConstructorExpressions(array $currentExpressionTypes): array
1229: {
1230: $expressionTypes = [];
1231: foreach ($currentExpressionTypes as $exprString => $expressionTypeHolder) {
1232: $expr = $expressionTypeHolder->getExpr();
1233: if ($expr instanceof FuncCall) {
1234: if (
1235: !$expr->name instanceof Name
1236: || !in_array($expr->name->name, ['class_exists', 'function_exists'], true)
1237: ) {
1238: continue;
1239: }
1240: } elseif ($expr instanceof PropertyFetch) {
1241: if (!$this->isReadonlyPropertyFetch($expr, true)) {
1242: continue;
1243: }
1244: } elseif (!$expr instanceof ConstFetch && !$expr instanceof PropertyInitializationExpr) {
1245: continue;
1246: }
1247:
1248: $expressionTypes[$exprString] = $expressionTypeHolder;
1249: }
1250:
1251: if (array_key_exists('$this', $currentExpressionTypes)) {
1252: $expressionTypes['$this'] = $currentExpressionTypes['$this'];
1253: }
1254:
1255: return $expressionTypes;
1256: }
1257:
1258: public function rememberConstructorScope(): self
1259: {
1260: return $this->scopeFactory->create(
1261: $this->context,
1262: $this->isDeclareStrictTypes(),
1263: null,
1264: $this->getNamespace(),
1265: $this->rememberConstructorExpressions($this->expressionTypes),
1266: $this->rememberConstructorExpressions($this->nativeExpressionTypes),
1267: $this->conditionalExpressions,
1268: $this->inClosureBindScopeClasses,
1269: $this->anonymousFunctionReflection,
1270: $this->inFirstLevelStatement,
1271: [],
1272: [],
1273: $this->inFunctionCallsStack,
1274: $this->afterExtractCall,
1275: $this->parentScope,
1276: $this->nativeTypesPromoted,
1277: );
1278: }
1279:
1280: /**
1281: * @api
1282: * @param Type[] $phpDocParameterTypes
1283: * @param Type[] $parameterOutTypes
1284: * @param array<string, bool> $immediatelyInvokedCallableParameters
1285: * @param array<string, Type> $phpDocClosureThisTypeParameters
1286: */
1287: public function enterClassMethod(
1288: Node\Stmt\ClassMethod $classMethod,
1289: TemplateTypeMap $templateTypeMap,
1290: array $phpDocParameterTypes,
1291: ?Type $phpDocReturnType,
1292: ?Type $throwType,
1293: ?string $deprecatedDescription,
1294: bool $isDeprecated,
1295: bool $isInternal,
1296: bool $isFinal,
1297: ?bool $isPure = null,
1298: bool $acceptsNamedArguments = true,
1299: ?Assertions $asserts = null,
1300: ?Type $selfOutType = null,
1301: ?string $phpDocComment = null,
1302: array $parameterOutTypes = [],
1303: array $immediatelyInvokedCallableParameters = [],
1304: array $phpDocClosureThisTypeParameters = [],
1305: bool $isConstructor = false,
1306: ): self
1307: {
1308: if (!$this->isInClass()) {
1309: throw new ShouldNotHappenException();
1310: }
1311:
1312: return $this->enterFunctionLike(
1313: new PhpMethodFromParserNodeReflection(
1314: $this->getClassReflection(),
1315: $classMethod,
1316: null,
1317: $this->getFile(),
1318: $templateTypeMap,
1319: $this->getRealParameterTypes($classMethod),
1320: array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocParameterTypes),
1321: $this->getRealParameterDefaultValues($classMethod),
1322: $this->getParameterAttributes($classMethod),
1323: $this->transformStaticType($this->getFunctionType($classMethod->returnType, false, false)),
1324: $phpDocReturnType !== null ? $this->transformStaticType(TemplateTypeHelper::toArgument($phpDocReturnType)) : null,
1325: $throwType !== null ? $this->transformStaticType(TemplateTypeHelper::toArgument($throwType)) : null,
1326: $deprecatedDescription,
1327: $isDeprecated,
1328: $isInternal,
1329: $isFinal,
1330: $isPure,
1331: $acceptsNamedArguments,
1332: $asserts ?? Assertions::createEmpty(),
1333: $selfOutType,
1334: $phpDocComment,
1335: array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $parameterOutTypes),
1336: $immediatelyInvokedCallableParameters,
1337: array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocClosureThisTypeParameters),
1338: $isConstructor,
1339: $this->attributeReflectionFactory->fromAttrGroups($classMethod->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $classMethod)),
1340: ),
1341: !$classMethod->isStatic(),
1342: );
1343: }
1344:
1345: /**
1346: * @param Type[] $phpDocParameterTypes
1347: */
1348: public function enterPropertyHook(
1349: Node\PropertyHook $hook,
1350: string $propertyName,
1351: Identifier|Name|ComplexType|null $nativePropertyTypeNode,
1352: ?Type $phpDocPropertyType,
1353: array $phpDocParameterTypes,
1354: ?Type $throwType,
1355: ?string $deprecatedDescription,
1356: bool $isDeprecated,
1357: ?string $phpDocComment,
1358: ): self
1359: {
1360: if (!$this->isInClass()) {
1361: throw new ShouldNotHappenException();
1362: }
1363:
1364: $phpDocParameterTypes = array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocParameterTypes);
1365:
1366: $hookName = $hook->name->toLowerString();
1367: if ($hookName === 'set') {
1368: if ($hook->params === []) {
1369: $hook = clone $hook;
1370: $hook->params = [
1371: new Node\Param(new Variable('value'), type: $nativePropertyTypeNode),
1372: ];
1373: }
1374:
1375: $firstParam = $hook->params[0] ?? null;
1376: if (
1377: $firstParam !== null
1378: && $phpDocPropertyType !== null
1379: && $firstParam->var instanceof Variable
1380: && is_string($firstParam->var->name)
1381: ) {
1382: $valueParamPhpDocType = $phpDocParameterTypes[$firstParam->var->name] ?? null;
1383: if ($valueParamPhpDocType === null) {
1384: $phpDocParameterTypes[$firstParam->var->name] = $this->transformStaticType(TemplateTypeHelper::toArgument($phpDocPropertyType));
1385: }
1386: }
1387:
1388: $realReturnType = new VoidType();
1389: $phpDocReturnType = null;
1390: } elseif ($hookName === 'get') {
1391: $realReturnType = $this->getFunctionType($nativePropertyTypeNode, false, false);
1392: $phpDocReturnType = $phpDocPropertyType !== null ? $this->transformStaticType(TemplateTypeHelper::toArgument($phpDocPropertyType)) : null;
1393: } else {
1394: throw new ShouldNotHappenException();
1395: }
1396:
1397: $realParameterTypes = $this->getRealParameterTypes($hook);
1398:
1399: return $this->enterFunctionLike(
1400: new PhpMethodFromParserNodeReflection(
1401: $this->getClassReflection(),
1402: $hook,
1403: $propertyName,
1404: $this->getFile(),
1405: TemplateTypeMap::createEmpty(),
1406: $realParameterTypes,
1407: $phpDocParameterTypes,
1408: [],
1409: $this->getParameterAttributes($hook),
1410: $realReturnType,
1411: $phpDocReturnType,
1412: $throwType !== null ? $this->transformStaticType(TemplateTypeHelper::toArgument($throwType)) : null,
1413: $deprecatedDescription,
1414: $isDeprecated,
1415: false,
1416: false,
1417: false,
1418: true,
1419: Assertions::createEmpty(),
1420: null,
1421: $phpDocComment,
1422: [],
1423: [],
1424: [],
1425: false,
1426: $this->attributeReflectionFactory->fromAttrGroups($hook->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $hook)),
1427: ),
1428: true,
1429: );
1430: }
1431:
1432: private function transformStaticType(Type $type): Type
1433: {
1434: return TypeTraverser::map($type, function (Type $type, callable $traverse): Type {
1435: if (!$this->isInClass()) {
1436: return $type;
1437: }
1438: if ($type instanceof StaticType) {
1439: $classReflection = $this->getClassReflection();
1440: $changedType = $type->changeBaseClass($classReflection);
1441: if ($classReflection->isFinal() && !$type instanceof ThisType) {
1442: $changedType = $changedType->getStaticObjectType();
1443: }
1444: return $traverse($changedType);
1445: }
1446:
1447: return $traverse($type);
1448: });
1449: }
1450:
1451: /**
1452: * This assumes enterAnonymousFunctionWithoutReflection() has already been called
1453: */
1454: public function enterAnonymousFunction(
1455: ClosureType $anonymousFunctionReflection,
1456: ): self
1457: {
1458: return $this->scopeFactory->create(
1459: $this->context,
1460: $this->isDeclareStrictTypes(),
1461: $this->getFunction(),
1462: $this->getNamespace(),
1463: $this->expressionTypes,
1464: $this->nativeExpressionTypes,
1465: [],
1466: $this->inClosureBindScopeClasses,
1467: $anonymousFunctionReflection,
1468: true,
1469: [],
1470: [],
1471: $this->inFunctionCallsStack,
1472: false,
1473: $this,
1474: $this->nativeTypesPromoted,
1475: );
1476: }
1477:
1478: /**
1479: * @param ParameterReflection[]|null $callableParameters
1480: */
1481: public function enterAnonymousFunctionWithoutReflection(
1482: Expr\Closure $closure,
1483: ?array $callableParameters,
1484: ): self
1485: {
1486: $expressionTypes = [];
1487: $nativeTypes = [];
1488: foreach ($closure->params as $i => $parameter) {
1489: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
1490: throw new ShouldNotHappenException();
1491: }
1492: $paramExprString = sprintf('$%s', $parameter->var->name);
1493: $isNullable = $this->isParameterValueNullable($parameter);
1494: $parameterType = $this->getFunctionType($parameter->type, $isNullable, $parameter->variadic);
1495: if ($callableParameters !== null) {
1496: if (isset($callableParameters[$i])) {
1497: $parameterType = self::intersectButNotNever($parameterType, $callableParameters[$i]->getType());
1498: } elseif (count($callableParameters) > 0) {
1499: $lastParameter = array_last($callableParameters);
1500: if ($lastParameter->isVariadic()) {
1501: $parameterType = self::intersectButNotNever($parameterType, $lastParameter->getType());
1502: } else {
1503: $parameterType = self::intersectButNotNever($parameterType, new MixedType());
1504: }
1505: } else {
1506: $parameterType = self::intersectButNotNever($parameterType, new MixedType());
1507: }
1508: }
1509: $holder = ExpressionTypeHolder::createYes($parameter->var, $parameterType);
1510: $expressionTypes[$paramExprString] = $holder;
1511: $nativeTypes[$paramExprString] = $holder;
1512: }
1513:
1514: $nonRefVariableNames = [];
1515: foreach ($closure->uses as $use) {
1516: if (!is_string($use->var->name)) {
1517: throw new ShouldNotHappenException();
1518: }
1519: $variableName = $use->var->name;
1520: $paramExprString = '$' . $use->var->name;
1521: if ($use->byRef) {
1522: $holder = ExpressionTypeHolder::createYes($use->var, new MixedType());
1523: $expressionTypes[$paramExprString] = $holder;
1524: $nativeTypes[$paramExprString] = $holder;
1525: continue;
1526: }
1527: $nonRefVariableNames[$variableName] = true;
1528: if ($this->hasVariableType($variableName)->no()) {
1529: $variableType = new ErrorType();
1530: $variableNativeType = new ErrorType();
1531: } else {
1532: $variableType = $this->getVariableType($variableName);
1533: $nativeExprHolder = $this->nativeExpressionTypes['$' . $variableName] ?? null;
1534: if ($nativeExprHolder !== null) {
1535: $variableNativeType = $nativeExprHolder->getType();
1536: } else {
1537: $variableNativeType = new ErrorType();
1538: }
1539: }
1540: $expressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($use->var, $variableType);
1541: $nativeTypes[$paramExprString] = ExpressionTypeHolder::createYes($use->var, $variableNativeType);
1542: }
1543:
1544: $nonStaticExpressions = $this->invalidateStaticExpressions($this->expressionTypes);
1545: foreach ($nonStaticExpressions as $exprString => $typeHolder) {
1546: $expr = $typeHolder->getExpr();
1547:
1548: if ($expr instanceof Variable) {
1549: continue;
1550: }
1551:
1552: $variables = (new NodeFinder())->findInstanceOf([$expr], Variable::class);
1553: if ($variables === [] && !$this->expressionTypeIsUnchangeable($typeHolder)) {
1554: continue;
1555: }
1556:
1557: foreach ($variables as $variable) {
1558: if (!is_string($variable->name)) {
1559: continue 2;
1560: }
1561: if (!array_key_exists($variable->name, $nonRefVariableNames)) {
1562: continue 2;
1563: }
1564: }
1565:
1566: $expressionTypes[$exprString] = $typeHolder;
1567: }
1568:
1569: if ($this->hasVariableType('this')->yes() && !$closure->static) {
1570: $node = new Variable('this');
1571: $expressionTypes['$this'] = ExpressionTypeHolder::createYes($node, $this->getVariableType('this'));
1572:
1573: $nativeExprHolder = $this->nativeExpressionTypes['$this'] ?? null;
1574: if ($nativeExprHolder !== null) {
1575: $variableNativeType = $nativeExprHolder->getType();
1576: } else {
1577: $variableNativeType = new ErrorType();
1578: }
1579: $nativeTypes['$this'] = ExpressionTypeHolder::createYes($node, $variableNativeType);
1580:
1581: if ($this->phpVersion->supportsReadOnlyProperties()) {
1582: foreach ($nonStaticExpressions as $exprString => $typeHolder) {
1583: $expr = $typeHolder->getExpr();
1584:
1585: if (!$expr instanceof PropertyFetch) {
1586: continue;
1587: }
1588:
1589: if (!$this->isReadonlyPropertyFetch($expr, true)) {
1590: continue;
1591: }
1592:
1593: $expressionTypes[$exprString] = $typeHolder;
1594: }
1595: }
1596: }
1597:
1598: return $this->scopeFactory->create(
1599: $this->context,
1600: $this->isDeclareStrictTypes(),
1601: $this->getFunction(),
1602: $this->getNamespace(),
1603: array_merge($this->getConstantTypes(), $expressionTypes),
1604: array_merge($this->getNativeConstantTypes(), $nativeTypes),
1605: [],
1606: $this->inClosureBindScopeClasses,
1607: new ClosureType(),
1608: true,
1609: [],
1610: [],
1611: [],
1612: false,
1613: $this,
1614: $this->nativeTypesPromoted,
1615: );
1616: }
1617:
1618: /**
1619: * This assumes enterArrowFunctionWithoutReflection() has already been called
1620: */
1621: public function enterArrowFunction(ClosureType $anonymousFunctionReflection): self
1622: {
1623: return $this->scopeFactory->create(
1624: $this->context,
1625: $this->isDeclareStrictTypes(),
1626: $this->getFunction(),
1627: $this->getNamespace(),
1628: $this->expressionTypes,
1629: $this->nativeExpressionTypes,
1630: $this->conditionalExpressions,
1631: $this->inClosureBindScopeClasses,
1632: $anonymousFunctionReflection,
1633: true,
1634: [],
1635: [],
1636: $this->inFunctionCallsStack,
1637: $this->afterExtractCall,
1638: $this->parentScope,
1639: $this->nativeTypesPromoted,
1640: );
1641: }
1642:
1643: /**
1644: * @param ParameterReflection[]|null $callableParameters
1645: * @return Generator<int, GeneratorTValueType, GeneratorTSendType, self>
1646: */
1647: public function enterArrowFunctionWithoutReflection(Expr\ArrowFunction $arrowFunction, ?array $callableParameters): Generator
1648: {
1649: $arrowFunctionScope = $this;
1650: foreach ($arrowFunction->params as $i => $parameter) {
1651: if ($parameter->type === null) {
1652: $parameterType = new MixedType();
1653: } else {
1654: $isNullable = $this->isParameterValueNullable($parameter);
1655: $parameterType = $this->getFunctionType($parameter->type, $isNullable, $parameter->variadic);
1656: }
1657:
1658: if ($callableParameters !== null) {
1659: if (isset($callableParameters[$i])) {
1660: $parameterType = self::intersectButNotNever($parameterType, $callableParameters[$i]->getType());
1661: } elseif (count($callableParameters) > 0) {
1662: $lastParameter = array_last($callableParameters);
1663: if ($lastParameter->isVariadic()) {
1664: $parameterType = self::intersectButNotNever($parameterType, $lastParameter->getType());
1665: } else {
1666: $parameterType = self::intersectButNotNever($parameterType, new MixedType());
1667: }
1668: } else {
1669: $parameterType = self::intersectButNotNever($parameterType, new MixedType());
1670: }
1671: }
1672:
1673: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
1674: throw new ShouldNotHappenException();
1675: }
1676: $arrowFunctionScopeGen = $arrowFunctionScope->assignVariable($parameter->var->name, $parameterType, $parameterType, TrinaryLogic::createYes());
1677: yield from $arrowFunctionScopeGen;
1678: $arrowFunctionScope = $arrowFunctionScopeGen->getReturn();
1679: }
1680:
1681: if ($arrowFunction->static) {
1682: $arrowFunctionScope = $arrowFunctionScope->invalidateExpression(new Variable('this'));
1683: }
1684:
1685: return $this->scopeFactory->create(
1686: $arrowFunctionScope->context,
1687: $this->isDeclareStrictTypes(),
1688: $arrowFunctionScope->getFunction(),
1689: $arrowFunctionScope->getNamespace(),
1690: $this->invalidateStaticExpressions($arrowFunctionScope->expressionTypes),
1691: $arrowFunctionScope->nativeExpressionTypes,
1692: $arrowFunctionScope->conditionalExpressions,
1693: $arrowFunctionScope->inClosureBindScopeClasses,
1694: new ClosureType(),
1695: true,
1696: [],
1697: [],
1698: [],
1699: $arrowFunctionScope->afterExtractCall,
1700: $arrowFunctionScope->parentScope,
1701: $this->nativeTypesPromoted,
1702: );
1703: }
1704:
1705: public static function intersectButNotNever(Type $nativeType, Type $inferredType): Type
1706: {
1707: if ($nativeType->isSuperTypeOf($inferredType)->no()) {
1708: return $nativeType;
1709: }
1710:
1711: $result = TypeCombinator::intersect($nativeType, $inferredType);
1712: if (TypeCombinator::containsNull($nativeType)) {
1713: return TypeCombinator::addNull($result);
1714: }
1715:
1716: return $result;
1717: }
1718:
1719: /**
1720: * @param array<string, ExpressionTypeHolder> $expressionTypes
1721: * @return array<string, ExpressionTypeHolder>
1722: */
1723: private function invalidateStaticExpressions(array $expressionTypes): array
1724: {
1725: $filteredExpressionTypes = [];
1726: $nodeFinder = new NodeFinder();
1727: foreach ($expressionTypes as $exprString => $expressionType) {
1728: $staticExpression = $nodeFinder->findFirst(
1729: [$expressionType->getExpr()],
1730: static fn ($node) => $node instanceof Expr\StaticCall || $node instanceof Expr\StaticPropertyFetch,
1731: );
1732: if ($staticExpression !== null) {
1733: continue;
1734: }
1735: $filteredExpressionTypes[$exprString] = $expressionType;
1736: }
1737: return $filteredExpressionTypes;
1738: }
1739:
1740: private function expressionTypeIsUnchangeable(ExpressionTypeHolder $typeHolder): bool
1741: {
1742: $expr = $typeHolder->getExpr();
1743: //$type = $typeHolder->getType();
1744:
1745: return $expr instanceof FuncCall
1746: && !$expr->isFirstClassCallable()
1747: && $expr->name instanceof FullyQualified
1748: && $expr->name->toLowerString() === 'function_exists';
1749: /*&& isset($expr->getArgs()[0])
1750: && count($this->getType($expr->getArgs()[0]->value)->getConstantStrings()) === 1
1751: && $type->isTrue()->yes()*/
1752: }
1753:
1754: /**
1755: * @return Type[]
1756: */
1757: private function getRealParameterTypes(Node\FunctionLike $functionLike): array
1758: {
1759: $realParameterTypes = [];
1760: foreach ($functionLike->getParams() as $parameter) {
1761: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
1762: throw new ShouldNotHappenException();
1763: }
1764: $realParameterTypes[$parameter->var->name] = $this->getFunctionType(
1765: $parameter->type,
1766: $this->isParameterValueNullable($parameter) && $parameter->flags === 0,
1767: false,
1768: );
1769: }
1770:
1771: return $realParameterTypes;
1772: }
1773:
1774: /**
1775: * @return Type[]
1776: */
1777: private function getRealParameterDefaultValues(Node\FunctionLike $functionLike): array
1778: {
1779: $realParameterDefaultValues = [];
1780: foreach ($functionLike->getParams() as $parameter) {
1781: if ($parameter->default === null) {
1782: continue;
1783: }
1784: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
1785: throw new ShouldNotHappenException();
1786: }
1787: $realParameterDefaultValues[$parameter->var->name] = $this->initializerExprTypeResolver->getType($parameter->default, InitializerExprContext::fromScope($this));
1788: }
1789:
1790: return $realParameterDefaultValues;
1791: }
1792:
1793: /**
1794: * @return array<string, list<AttributeReflection>>
1795: */
1796: private function getParameterAttributes(ClassMethod|Function_|PropertyHook $functionLike): array
1797: {
1798: $parameterAttributes = [];
1799: $className = null;
1800: if ($this->isInClass()) {
1801: $className = $this->getClassReflection()->getName();
1802: }
1803: foreach ($functionLike->getParams() as $parameter) {
1804: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
1805: throw new ShouldNotHappenException();
1806: }
1807:
1808: $parameterAttributes[$parameter->var->name] = $this->attributeReflectionFactory->fromAttrGroups($parameter->attrGroups, InitializerExprContext::fromStubParameter($className, $this->getFile(), $functionLike));
1809: }
1810:
1811: return $parameterAttributes;
1812: }
1813:
1814: /**
1815: * @param MethodReflection|FunctionReflection|null $reflection
1816: */
1817: public function pushInFunctionCall($reflection, ?ParameterReflection $parameter): self
1818: {
1819: $stack = $this->inFunctionCallsStack;
1820: $stack[] = [$reflection, $parameter];
1821:
1822: return $this->scopeFactory->create(
1823: $this->context,
1824: $this->isDeclareStrictTypes(),
1825: $this->getFunction(),
1826: $this->getNamespace(),
1827: $this->expressionTypes,
1828: $this->nativeExpressionTypes,
1829: $this->conditionalExpressions,
1830: $this->inClosureBindScopeClasses,
1831: $this->anonymousFunctionReflection,
1832: $this->isInFirstLevelStatement(),
1833: $this->currentlyAssignedExpressions,
1834: $this->currentlyAllowedUndefinedExpressions,
1835: $stack,
1836: $this->afterExtractCall,
1837: $this->parentScope,
1838: $this->nativeTypesPromoted,
1839: );
1840: }
1841:
1842: public function popInFunctionCall(): self
1843: {
1844: $stack = $this->inFunctionCallsStack;
1845: array_pop($stack);
1846:
1847: return $this->scopeFactory->create(
1848: $this->context,
1849: $this->isDeclareStrictTypes(),
1850: $this->getFunction(),
1851: $this->getNamespace(),
1852: $this->expressionTypes,
1853: $this->nativeExpressionTypes,
1854: $this->conditionalExpressions,
1855: $this->inClosureBindScopeClasses,
1856: $this->anonymousFunctionReflection,
1857: $this->isInFirstLevelStatement(),
1858: $this->currentlyAssignedExpressions,
1859: $this->currentlyAllowedUndefinedExpressions,
1860: $stack,
1861: $this->afterExtractCall,
1862: $this->parentScope,
1863: $this->nativeTypesPromoted,
1864: );
1865: }
1866:
1867: /**
1868: * @param list<string> $scopeClasses
1869: */
1870: public function enterClosureBind(?Type $thisType, ?Type $nativeThisType, array $scopeClasses): self
1871: {
1872: $expressionTypes = $this->expressionTypes;
1873: if ($thisType !== null) {
1874: $expressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $thisType);
1875: } else {
1876: unset($expressionTypes['$this']);
1877: }
1878:
1879: $nativeExpressionTypes = $this->nativeExpressionTypes;
1880: if ($nativeThisType !== null) {
1881: $nativeExpressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $nativeThisType);
1882: } else {
1883: unset($nativeExpressionTypes['$this']);
1884: }
1885:
1886: if ($scopeClasses === ['static'] && $this->isInClass()) {
1887: $scopeClasses = [$this->getClassReflection()->getName()];
1888: }
1889:
1890: return $this->scopeFactory->create(
1891: $this->context,
1892: $this->isDeclareStrictTypes(),
1893: $this->getFunction(),
1894: $this->getNamespace(),
1895: $expressionTypes,
1896: $nativeExpressionTypes,
1897: $this->conditionalExpressions,
1898: $scopeClasses,
1899: $this->anonymousFunctionReflection,
1900: );
1901: }
1902:
1903: public function restoreOriginalScopeAfterClosureBind(self $originalScope): self
1904: {
1905: $expressionTypes = $this->expressionTypes;
1906: if (isset($originalScope->expressionTypes['$this'])) {
1907: $expressionTypes['$this'] = $originalScope->expressionTypes['$this'];
1908: } else {
1909: unset($expressionTypes['$this']);
1910: }
1911:
1912: $nativeExpressionTypes = $this->nativeExpressionTypes;
1913: if (isset($originalScope->nativeExpressionTypes['$this'])) {
1914: $nativeExpressionTypes['$this'] = $originalScope->nativeExpressionTypes['$this'];
1915: } else {
1916: unset($nativeExpressionTypes['$this']);
1917: }
1918:
1919: return $this->scopeFactory->create(
1920: $this->context,
1921: $this->isDeclareStrictTypes(),
1922: $this->getFunction(),
1923: $this->getNamespace(),
1924: $expressionTypes,
1925: $nativeExpressionTypes,
1926: $this->conditionalExpressions,
1927: $originalScope->inClosureBindScopeClasses,
1928: $this->anonymousFunctionReflection,
1929: );
1930: }
1931:
1932: public function restoreThis(self $restoreThisScope): self
1933: {
1934: $expressionTypes = $this->expressionTypes;
1935: $nativeExpressionTypes = $this->nativeExpressionTypes;
1936:
1937: if ($restoreThisScope->isInClass()) {
1938: $nodeFinder = new NodeFinder();
1939: $cb = static fn ($expr) => $expr instanceof Variable && $expr->name === 'this';
1940: foreach ($restoreThisScope->expressionTypes as $exprString => $expressionTypeHolder) {
1941: $expr = $expressionTypeHolder->getExpr();
1942: $thisExpr = $nodeFinder->findFirst([$expr], $cb);
1943: if ($thisExpr === null) {
1944: continue;
1945: }
1946:
1947: $expressionTypes[$exprString] = $expressionTypeHolder;
1948: }
1949:
1950: foreach ($restoreThisScope->nativeExpressionTypes as $exprString => $expressionTypeHolder) {
1951: $expr = $expressionTypeHolder->getExpr();
1952: $thisExpr = $nodeFinder->findFirst([$expr], $cb);
1953: if ($thisExpr === null) {
1954: continue;
1955: }
1956:
1957: $nativeExpressionTypes[$exprString] = $expressionTypeHolder;
1958: }
1959: } else {
1960: unset($expressionTypes['$this']);
1961: unset($nativeExpressionTypes['$this']);
1962: }
1963:
1964: return $this->scopeFactory->create(
1965: $this->context,
1966: $this->isDeclareStrictTypes(),
1967: $this->getFunction(),
1968: $this->getNamespace(),
1969: $expressionTypes,
1970: $nativeExpressionTypes,
1971: $this->conditionalExpressions,
1972: $this->inClosureBindScopeClasses,
1973: $this->anonymousFunctionReflection,
1974: $this->inFirstLevelStatement,
1975: [],
1976: [],
1977: $this->inFunctionCallsStack,
1978: $this->afterExtractCall,
1979: $this->parentScope,
1980: $this->nativeTypesPromoted,
1981: );
1982: }
1983:
1984: public function enterClosureCall(Type $thisType, Type $nativeThisType): self
1985: {
1986: $expressionTypes = $this->expressionTypes;
1987: $expressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $thisType);
1988:
1989: $nativeExpressionTypes = $this->nativeExpressionTypes;
1990: $nativeExpressionTypes['$this'] = ExpressionTypeHolder::createYes(new Variable('this'), $nativeThisType);
1991:
1992: return $this->scopeFactory->create(
1993: $this->context,
1994: $this->isDeclareStrictTypes(),
1995: $this->getFunction(),
1996: $this->getNamespace(),
1997: $expressionTypes,
1998: $nativeExpressionTypes,
1999: $this->conditionalExpressions,
2000: $thisType->getObjectClassNames(),
2001: $this->anonymousFunctionReflection,
2002: );
2003: }
2004:
2005: /** @api */
2006: public function getParentScope(): ?Scope
2007: {
2008: return $this->parentScope;
2009: }
2010:
2011: /** @api */
2012: public function hasVariableType(string $variableName): TrinaryLogic
2013: {
2014: if ($this->isGlobalVariable($variableName)) {
2015: return TrinaryLogic::createYes();
2016: }
2017:
2018: $varExprString = '$' . $variableName;
2019: if (!isset($this->expressionTypes[$varExprString])) {
2020: if ($this->canAnyVariableExist()) {
2021: return TrinaryLogic::createMaybe();
2022: }
2023:
2024: return TrinaryLogic::createNo();
2025: }
2026:
2027: return $this->expressionTypes[$varExprString]->getCertainty();
2028: }
2029:
2030: /** @api */
2031: public function getVariableType(string $variableName): Type
2032: {
2033: if ($this->hasVariableType($variableName)->maybe()) {
2034: if ($variableName === 'argc') {
2035: return IntegerRangeType::fromInterval(1, null);
2036: }
2037: if ($variableName === 'argv') {
2038: return TypeCombinator::intersect(
2039: new ArrayType(new IntegerType(), new StringType()),
2040: new NonEmptyArrayType(),
2041: new AccessoryArrayListType(),
2042: );
2043: }
2044: if ($this->canAnyVariableExist()) {
2045: return new MixedType();
2046: }
2047: }
2048:
2049: if ($this->hasVariableType($variableName)->no()) {
2050: throw new UndefinedVariableException($this, $variableName);
2051: }
2052:
2053: $varExprString = '$' . $variableName;
2054: if (!array_key_exists($varExprString, $this->expressionTypes)) {
2055: if ($this->isGlobalVariable($variableName)) {
2056: return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
2057: }
2058: return new MixedType();
2059: }
2060:
2061: return TypeUtils::resolveLateResolvableTypes($this->expressionTypes[$varExprString]->getType());
2062: }
2063:
2064: /** @api */
2065: public function canAnyVariableExist(): bool
2066: {
2067: return ($this->function === null && !$this->isInAnonymousFunction()) || $this->afterExtractCall;
2068: }
2069:
2070: public function afterExtractCall(): self
2071: {
2072: return $this->scopeFactory->create(
2073: $this->context,
2074: $this->isDeclareStrictTypes(),
2075: $this->getFunction(),
2076: $this->getNamespace(),
2077: $this->expressionTypes,
2078: $this->nativeExpressionTypes,
2079: [],
2080: $this->inClosureBindScopeClasses,
2081: $this->anonymousFunctionReflection,
2082: $this->isInFirstLevelStatement(),
2083: $this->currentlyAssignedExpressions,
2084: $this->currentlyAllowedUndefinedExpressions,
2085: $this->inFunctionCallsStack,
2086: true,
2087: $this->parentScope,
2088: $this->nativeTypesPromoted,
2089: );
2090: }
2091:
2092: public function afterClearstatcacheCall(): self
2093: {
2094: $expressionTypes = $this->expressionTypes;
2095: $nativeExpressionTypes = $this->nativeExpressionTypes;
2096: foreach (array_keys($expressionTypes) as $exprString) {
2097: // list from https://www.php.net/manual/en/function.clearstatcache.php
2098:
2099: // 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().
2100: foreach ([
2101: 'stat',
2102: 'lstat',
2103: 'file_exists',
2104: 'is_writable',
2105: 'is_writeable',
2106: 'is_readable',
2107: 'is_executable',
2108: 'is_file',
2109: 'is_dir',
2110: 'is_link',
2111: 'filectime',
2112: 'fileatime',
2113: 'filemtime',
2114: 'fileinode',
2115: 'filegroup',
2116: 'fileowner',
2117: 'filesize',
2118: 'filetype',
2119: 'fileperms',
2120: ] as $functionName) {
2121: if (!str_starts_with($exprString, $functionName . '(') && !str_starts_with($exprString, '\\' . $functionName . '(')) {
2122: continue;
2123: }
2124:
2125: unset($expressionTypes[$exprString]);
2126: unset($nativeExpressionTypes[$exprString]);
2127: continue 2;
2128: }
2129: }
2130:
2131: return $this->scopeFactory->create(
2132: $this->context,
2133: $this->isDeclareStrictTypes(),
2134: $this->getFunction(),
2135: $this->getNamespace(),
2136: $expressionTypes,
2137: $nativeExpressionTypes,
2138: $this->conditionalExpressions,
2139: $this->inClosureBindScopeClasses,
2140: $this->anonymousFunctionReflection,
2141: $this->isInFirstLevelStatement(),
2142: $this->currentlyAssignedExpressions,
2143: $this->currentlyAllowedUndefinedExpressions,
2144: $this->inFunctionCallsStack,
2145: $this->afterExtractCall,
2146: $this->parentScope,
2147: $this->nativeTypesPromoted,
2148: );
2149: }
2150:
2151: public function afterOpenSslCall(string $openSslFunctionName): self
2152: {
2153: $expressionTypes = $this->expressionTypes;
2154: $nativeExpressionTypes = $this->nativeExpressionTypes;
2155:
2156: if (in_array($openSslFunctionName, [
2157: 'openssl_cipher_iv_length',
2158: 'openssl_cms_decrypt',
2159: 'openssl_cms_encrypt',
2160: 'openssl_cms_read',
2161: 'openssl_cms_sign',
2162: 'openssl_cms_verify',
2163: 'openssl_csr_export_to_file',
2164: 'openssl_csr_export',
2165: 'openssl_csr_get_public_key',
2166: 'openssl_csr_get_subject',
2167: 'openssl_csr_new',
2168: 'openssl_csr_sign',
2169: 'openssl_decrypt',
2170: 'openssl_dh_compute_key',
2171: 'openssl_digest',
2172: 'openssl_encrypt',
2173: 'openssl_get_curve_names',
2174: 'openssl_get_privatekey',
2175: 'openssl_get_publickey',
2176: 'openssl_open',
2177: 'openssl_pbkdf2',
2178: 'openssl_pkcs12_export_to_file',
2179: 'openssl_pkcs12_export',
2180: 'openssl_pkcs12_read',
2181: 'openssl_pkcs7_decrypt',
2182: 'openssl_pkcs7_encrypt',
2183: 'openssl_pkcs7_read',
2184: 'openssl_pkcs7_sign',
2185: 'openssl_pkcs7_verify',
2186: 'openssl_pkey_derive',
2187: 'openssl_pkey_export_to_file',
2188: 'openssl_pkey_export',
2189: 'openssl_pkey_get_private',
2190: 'openssl_pkey_get_public',
2191: 'openssl_pkey_new',
2192: 'openssl_private_decrypt',
2193: 'openssl_private_encrypt',
2194: 'openssl_public_decrypt',
2195: 'openssl_public_encrypt',
2196: 'openssl_random_pseudo_bytes',
2197: 'openssl_seal',
2198: 'openssl_sign',
2199: 'openssl_spki_export_challenge',
2200: 'openssl_spki_export',
2201: 'openssl_spki_new',
2202: 'openssl_spki_verify',
2203: 'openssl_verify',
2204: 'openssl_x509_checkpurpose',
2205: 'openssl_x509_export_to_file',
2206: 'openssl_x509_export',
2207: 'openssl_x509_fingerprint',
2208: 'openssl_x509_read',
2209: 'openssl_x509_verify',
2210: ], true)) {
2211: unset($expressionTypes['\openssl_error_string()']);
2212: unset($nativeExpressionTypes['\openssl_error_string()']);
2213: }
2214:
2215: return $this->scopeFactory->create(
2216: $this->context,
2217: $this->isDeclareStrictTypes(),
2218: $this->getFunction(),
2219: $this->getNamespace(),
2220: $expressionTypes,
2221: $nativeExpressionTypes,
2222: $this->conditionalExpressions,
2223: $this->inClosureBindScopeClasses,
2224: $this->anonymousFunctionReflection,
2225: $this->isInFirstLevelStatement(),
2226: $this->currentlyAssignedExpressions,
2227: $this->currentlyAllowedUndefinedExpressions,
2228: $this->inFunctionCallsStack,
2229: $this->afterExtractCall,
2230: $this->parentScope,
2231: $this->nativeTypesPromoted,
2232: );
2233: }
2234:
2235: /**
2236: * @api
2237: * @return list<string>
2238: */
2239: public function getDefinedVariables(): array
2240: {
2241: $variables = [];
2242: foreach ($this->expressionTypes as $exprString => $holder) {
2243: if (!$holder->getExpr() instanceof Variable) {
2244: continue;
2245: }
2246: if (!$holder->getCertainty()->yes()) {
2247: continue;
2248: }
2249:
2250: $variables[] = substr($exprString, 1);
2251: }
2252:
2253: return $variables;
2254: }
2255:
2256: /**
2257: * @api
2258: * @return list<string>
2259: */
2260: public function getMaybeDefinedVariables(): array
2261: {
2262: $variables = [];
2263: foreach ($this->expressionTypes as $exprString => $holder) {
2264: if (!$holder->getExpr() instanceof Variable) {
2265: continue;
2266: }
2267: if (!$holder->getCertainty()->maybe()) {
2268: continue;
2269: }
2270:
2271: $variables[] = substr($exprString, 1);
2272: }
2273:
2274: return $variables;
2275: }
2276:
2277: private function isGlobalVariable(string $variableName): bool
2278: {
2279: return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true);
2280: }
2281:
2282: /** @api */
2283: public function hasConstant(Name $name): bool
2284: {
2285: $isCompilerHaltOffset = $name->toString() === '__COMPILER_HALT_OFFSET__';
2286: if ($isCompilerHaltOffset) {
2287: return $this->fileHasCompilerHaltStatementCalls();
2288: }
2289:
2290: if ($this->getGlobalConstantType($name) !== null) {
2291: return true;
2292: }
2293:
2294: return $this->reflectionProvider->hasConstant($name, $this);
2295: }
2296:
2297: private function getGlobalConstantType(Name $name): ?Type
2298: {
2299: $fetches = [];
2300: if (!$name->isFullyQualified() && $this->getNamespace() !== null) {
2301: $fetches[] = new ConstFetch(new FullyQualified([$this->getNamespace(), $name->toString()]));
2302: }
2303:
2304: $fetches[] = new ConstFetch(new FullyQualified($name->toString()));
2305: $fetches[] = new ConstFetch($name);
2306:
2307: foreach ($fetches as $constFetch) {
2308: $constFetchType = $this->getExpressionType($constFetch);
2309: if ($constFetchType === null) {
2310: continue;
2311: }
2312:
2313: return $constFetchType;
2314: }
2315:
2316: return null;
2317: }
2318:
2319: /**
2320: * @return array<string, ExpressionTypeHolder>
2321: */
2322: private function getNativeConstantTypes(): array
2323: {
2324: $constantTypes = [];
2325: foreach ($this->nativeExpressionTypes as $exprString => $typeHolder) {
2326: $expr = $typeHolder->getExpr();
2327: if (!$expr instanceof ConstFetch) {
2328: continue;
2329: }
2330: $constantTypes[$exprString] = $typeHolder;
2331: }
2332: return $constantTypes;
2333: }
2334:
2335: private function fileHasCompilerHaltStatementCalls(): bool
2336: {
2337: $nodes = $this->parser->parseFile($this->getFile());
2338: foreach ($nodes as $node) {
2339: if ($node instanceof Node\Stmt\HaltCompiler) {
2340: return true;
2341: }
2342: }
2343:
2344: return false;
2345: }
2346:
2347: /**
2348: * @api
2349: * @deprecated Use getInstancePropertyReflection or getStaticPropertyReflection instead
2350: */
2351: public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection
2352: {
2353: if ($typeWithProperty instanceof UnionType) {
2354: $typeWithProperty = $typeWithProperty->filterTypes(static fn (Type $innerType) => $innerType->hasProperty($propertyName)->yes());
2355: }
2356: if (!$typeWithProperty->hasProperty($propertyName)->yes()) {
2357: return null;
2358: }
2359:
2360: return $typeWithProperty->getProperty($propertyName, $this);
2361: }
2362:
2363: /** @api */
2364: public function getInstancePropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection
2365: {
2366: if ($typeWithProperty instanceof UnionType) {
2367: $typeWithProperty = $typeWithProperty->filterTypes(static fn (Type $innerType) => $innerType->hasInstanceProperty($propertyName)->yes());
2368: }
2369: if (!$typeWithProperty->hasInstanceProperty($propertyName)->yes()) {
2370: return null;
2371: }
2372:
2373: return $typeWithProperty->getInstanceProperty($propertyName, $this);
2374: }
2375:
2376: /** @api */
2377: public function getStaticPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection
2378: {
2379: if ($typeWithProperty instanceof UnionType) {
2380: $typeWithProperty = $typeWithProperty->filterTypes(static fn (Type $innerType) => $innerType->hasStaticProperty($propertyName)->yes());
2381: }
2382: if (!$typeWithProperty->hasStaticProperty($propertyName)->yes()) {
2383: return null;
2384: }
2385:
2386: return $typeWithProperty->getStaticProperty($propertyName, $this);
2387: }
2388:
2389: public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ClassConstantReflection
2390: {
2391: if ($typeWithConstant instanceof UnionType) {
2392: $typeWithConstant = $typeWithConstant->filterTypes(static fn (Type $innerType) => $innerType->hasConstant($constantName)->yes());
2393: }
2394: if (!$typeWithConstant->hasConstant($constantName)->yes()) {
2395: return null;
2396: }
2397:
2398: return $typeWithConstant->getConstant($constantName);
2399: }
2400:
2401: public function getConstantExplicitTypeFromConfig(string $constantName, Type $constantType): Type
2402: {
2403: return $this->constantResolver->resolveConstantType($constantName, $constantType);
2404: }
2405:
2406: /**
2407: * @return array<string, ExpressionTypeHolder>
2408: */
2409: private function getConstantTypes(): array
2410: {
2411: $constantTypes = [];
2412: foreach ($this->expressionTypes as $exprString => $typeHolder) {
2413: $expr = $typeHolder->getExpr();
2414: if (!$expr instanceof ConstFetch) {
2415: continue;
2416: }
2417: $constantTypes[$exprString] = $typeHolder;
2418: }
2419: return $constantTypes;
2420: }
2421:
2422: /** @api */
2423: public function isInAnonymousFunction(): bool
2424: {
2425: return $this->anonymousFunctionReflection !== null;
2426: }
2427:
2428: /** @api */
2429: public function getAnonymousFunctionReflection(): ?ClosureType
2430: {
2431: return $this->anonymousFunctionReflection;
2432: }
2433:
2434: /** @api */
2435: public function getAnonymousFunctionReturnType(): ?Type
2436: {
2437: if ($this->anonymousFunctionReflection === null) {
2438: return null;
2439: }
2440:
2441: return $this->anonymousFunctionReflection->getReturnType();
2442: }
2443:
2444: /** @api */
2445: public function getType(Expr $node): Type
2446: {
2447: return Fiber::suspend(
2448: ExprAnalysisRequest::createNoopRequest($node, $this),
2449: )->type;
2450: }
2451:
2452: /** @api */
2453: public function getNativeType(Expr $expr): Type
2454: {
2455: return Fiber::suspend(
2456: ExprAnalysisRequest::createNoopRequest($expr, $this),
2457: )->nativeType;
2458: }
2459:
2460: public function doNotTreatPhpDocTypesAsCertain(): Scope
2461: {
2462: return $this->promoteNativeTypes();
2463: }
2464:
2465: private function promoteNativeTypes(): self
2466: {
2467: if ($this->nativeTypesPromoted) {
2468: return $this;
2469: }
2470:
2471: return $this->scopeFactory->create(
2472: $this->context,
2473: $this->declareStrictTypes,
2474: $this->function,
2475: $this->namespace,
2476: $this->nativeExpressionTypes,
2477: [],
2478: [],
2479: $this->inClosureBindScopeClasses,
2480: $this->anonymousFunctionReflection,
2481: $this->inFirstLevelStatement,
2482: $this->currentlyAssignedExpressions,
2483: $this->currentlyAllowedUndefinedExpressions,
2484: $this->inFunctionCallsStack,
2485: $this->afterExtractCall,
2486: $this->parentScope,
2487: true,
2488: );
2489: }
2490:
2491: public function getKeepVoidType(Expr $node): Type
2492: {
2493: return Fiber::suspend(
2494: ExprAnalysisRequest::createNoopRequest($node, $this),
2495: )->keepVoidType;
2496: }
2497:
2498: /**
2499: * @api
2500: * @return non-empty-string
2501: */
2502: public function resolveName(Name $name): string
2503: {
2504: $originalClass = (string) $name;
2505: if ($this->isInClass()) {
2506: $lowerClass = strtolower($originalClass);
2507: if (in_array($lowerClass, [
2508: 'self',
2509: 'static',
2510: ], true)) {
2511: if ($this->inClosureBindScopeClasses !== [] && $this->inClosureBindScopeClasses !== ['static']) {
2512: return $this->inClosureBindScopeClasses[0];
2513: }
2514: return $this->getClassReflection()->getName();
2515: } elseif ($lowerClass === 'parent') {
2516: $currentClassReflection = $this->getClassReflection();
2517: if ($currentClassReflection->getParentClass() !== null) {
2518: return $currentClassReflection->getParentClass()->getName();
2519: }
2520: }
2521: }
2522:
2523: return $originalClass;
2524: }
2525:
2526: /** @api */
2527: public function resolveTypeByName(Name $name): TypeWithClassName
2528: {
2529: if ($name->toLowerString() === 'static' && $this->isInClass()) {
2530: if ($this->inClosureBindScopeClasses !== [] && $this->inClosureBindScopeClasses !== ['static']) {
2531: if ($this->reflectionProvider->hasClass($this->inClosureBindScopeClasses[0])) {
2532: return new StaticType($this->reflectionProvider->getClass($this->inClosureBindScopeClasses[0]));
2533: }
2534: }
2535:
2536: return new StaticType($this->getClassReflection());
2537: }
2538:
2539: $originalClass = $this->resolveName($name);
2540: if ($this->isInClass()) {
2541: if ($this->inClosureBindScopeClasses === [$originalClass]) {
2542: if ($this->reflectionProvider->hasClass($originalClass)) {
2543: return new ThisType($this->reflectionProvider->getClass($originalClass));
2544: }
2545: return new ObjectType($originalClass);
2546: }
2547:
2548: $thisType = new ThisType($this->getClassReflection());
2549: $ancestor = $thisType->getAncestorWithClassName($originalClass);
2550: if ($ancestor !== null) {
2551: return $ancestor;
2552: }
2553: }
2554:
2555: return new ObjectType($originalClass);
2556: }
2557:
2558: /**
2559: * @api
2560: * @param mixed $value
2561: */
2562: public function getTypeFromValue($value): Type
2563: {
2564: return ConstantTypeHelper::getTypeFromValue($value);
2565: }
2566:
2567: /** @api */
2568: public function isInClassExists(string $className): bool
2569: {
2570: foreach ($this->inFunctionCallsStack as [$inFunctionCall]) {
2571: if (!$inFunctionCall instanceof FunctionReflection) {
2572: continue;
2573: }
2574:
2575: if (in_array($inFunctionCall->getName(), [
2576: 'class_exists',
2577: 'interface_exists',
2578: 'trait_exists',
2579: ], true)) {
2580: return true;
2581: }
2582: }
2583: $expr = new FuncCall(new FullyQualified('class_exists'), [
2584: new Arg(new String_(ltrim($className, '\\'))),
2585: ]);
2586:
2587: $type = $this->getExpressionType($expr);
2588: if ($type === null) {
2589: return false;
2590: }
2591:
2592: return $type->isTrue()->yes();
2593: }
2594:
2595: /** @api */
2596: public function isInFunctionExists(string $functionName): bool
2597: {
2598: $expr = new FuncCall(new FullyQualified('function_exists'), [
2599: new Arg(new String_(ltrim($functionName, '\\'))),
2600: ]);
2601: $type = $this->getExpressionType($expr);
2602: if ($type === null) {
2603: return false;
2604: }
2605:
2606: return $type->isTrue()->yes();
2607: }
2608:
2609: /** @api */
2610: public function isInClosureBind(): bool
2611: {
2612: return $this->inClosureBindScopeClasses !== [];
2613: }
2614:
2615: public function getFunctionCallStack(): array
2616: {
2617: return array_values(array_filter(
2618: array_map(static fn ($values) => $values[0], $this->inFunctionCallsStack),
2619: static fn (FunctionReflection|MethodReflection|null $reflection) => $reflection !== null,
2620: ));
2621: }
2622:
2623: public function getFunctionCallStackWithParameters(): array
2624: {
2625: return array_values(array_filter(
2626: $this->inFunctionCallsStack,
2627: static fn ($item) => $item[0] !== null,
2628: ));
2629: }
2630:
2631: public function isParameterValueNullable(Node\Param $parameter): bool
2632: {
2633: if ($parameter->default instanceof ConstFetch) {
2634: return strtolower((string) $parameter->default->name) === 'null';
2635: }
2636:
2637: return false;
2638: }
2639:
2640: /**
2641: * @api
2642: * @param Node\Name|Node\Identifier|Node\ComplexType|null $type
2643: */
2644: public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type
2645: {
2646: if ($isVariadic) {
2647: if (!$this->getPhpVersion()->supportsNamedArguments()->no()) {
2648: return new ArrayType(new UnionType([new IntegerType(), new StringType()]), $this->getFunctionType(
2649: $type,
2650: $isNullable,
2651: false,
2652: ));
2653: }
2654:
2655: return TypeCombinator::intersect(new ArrayType(new IntegerType(), $this->getFunctionType(
2656: $type,
2657: $isNullable,
2658: false,
2659: )), new AccessoryArrayListType());
2660: }
2661: return $this->initializerExprTypeResolver->getFunctionType($type, $isNullable, false, InitializerExprContext::fromScope($this));
2662: }
2663:
2664: public function enterExpressionAssign(Expr $expr): self
2665: {
2666: $exprString = $this->getNodeKey($expr);
2667: $currentlyAssignedExpressions = $this->currentlyAssignedExpressions;
2668: $currentlyAssignedExpressions[$exprString] = true;
2669:
2670: return $this->scopeFactory->create(
2671: $this->context,
2672: $this->isDeclareStrictTypes(),
2673: $this->getFunction(),
2674: $this->getNamespace(),
2675: $this->expressionTypes,
2676: $this->nativeExpressionTypes,
2677: $this->conditionalExpressions,
2678: $this->inClosureBindScopeClasses,
2679: $this->anonymousFunctionReflection,
2680: $this->isInFirstLevelStatement(),
2681: $currentlyAssignedExpressions,
2682: $this->currentlyAllowedUndefinedExpressions,
2683: [],
2684: $this->afterExtractCall,
2685: $this->parentScope,
2686: $this->nativeTypesPromoted,
2687: );
2688: }
2689:
2690: public function exitExpressionAssign(Expr $expr): self
2691: {
2692: $exprString = $this->getNodeKey($expr);
2693: $currentlyAssignedExpressions = $this->currentlyAssignedExpressions;
2694: unset($currentlyAssignedExpressions[$exprString]);
2695:
2696: return $this->scopeFactory->create(
2697: $this->context,
2698: $this->isDeclareStrictTypes(),
2699: $this->getFunction(),
2700: $this->getNamespace(),
2701: $this->expressionTypes,
2702: $this->nativeExpressionTypes,
2703: $this->conditionalExpressions,
2704: $this->inClosureBindScopeClasses,
2705: $this->anonymousFunctionReflection,
2706: $this->isInFirstLevelStatement(),
2707: $currentlyAssignedExpressions,
2708: $this->currentlyAllowedUndefinedExpressions,
2709: [],
2710: $this->afterExtractCall,
2711: $this->parentScope,
2712: $this->nativeTypesPromoted,
2713: );
2714: }
2715:
2716: /** @api */
2717: public function isInExpressionAssign(Expr $expr): bool
2718: {
2719: $exprString = $this->getNodeKey($expr);
2720: return array_key_exists($exprString, $this->currentlyAssignedExpressions);
2721: }
2722:
2723: public function setAllowedUndefinedExpression(Expr $expr): self
2724: {
2725: if ($expr instanceof Expr\StaticPropertyFetch) {
2726: return $this;
2727: }
2728:
2729: $exprString = $this->getNodeKey($expr);
2730: $currentlyAllowedUndefinedExpressions = $this->currentlyAllowedUndefinedExpressions;
2731: $currentlyAllowedUndefinedExpressions[$exprString] = true;
2732:
2733: return $this->scopeFactory->create(
2734: $this->context,
2735: $this->isDeclareStrictTypes(),
2736: $this->getFunction(),
2737: $this->getNamespace(),
2738: $this->expressionTypes,
2739: $this->nativeExpressionTypes,
2740: $this->conditionalExpressions,
2741: $this->inClosureBindScopeClasses,
2742: $this->anonymousFunctionReflection,
2743: $this->isInFirstLevelStatement(),
2744: $this->currentlyAssignedExpressions,
2745: $currentlyAllowedUndefinedExpressions,
2746: [],
2747: $this->afterExtractCall,
2748: $this->parentScope,
2749: $this->nativeTypesPromoted,
2750: );
2751: }
2752:
2753: public function unsetAllowedUndefinedExpression(Expr $expr): self
2754: {
2755: $exprString = $this->getNodeKey($expr);
2756: $currentlyAllowedUndefinedExpressions = $this->currentlyAllowedUndefinedExpressions;
2757: unset($currentlyAllowedUndefinedExpressions[$exprString]);
2758:
2759: return $this->scopeFactory->create(
2760: $this->context,
2761: $this->isDeclareStrictTypes(),
2762: $this->getFunction(),
2763: $this->getNamespace(),
2764: $this->expressionTypes,
2765: $this->nativeExpressionTypes,
2766: $this->conditionalExpressions,
2767: $this->inClosureBindScopeClasses,
2768: $this->anonymousFunctionReflection,
2769: $this->isInFirstLevelStatement(),
2770: $this->currentlyAssignedExpressions,
2771: $currentlyAllowedUndefinedExpressions,
2772: [],
2773: $this->afterExtractCall,
2774: $this->parentScope,
2775: $this->nativeTypesPromoted,
2776: );
2777: }
2778:
2779: /** @api */
2780: public function isUndefinedExpressionAllowed(Expr $expr): bool
2781: {
2782: $exprString = $this->getNodeKey($expr);
2783: return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions);
2784: }
2785:
2786: /**
2787: * @deprecated
2788: */
2789: public function filterByTruthyValue(Expr $expr): self
2790: {
2791: $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this, $expr, TypeSpecifierContext::createTruthy());
2792: return $this->filterBySpecifiedTypes($specifiedTypes);
2793: }
2794:
2795: /**
2796: * @deprecated
2797: */
2798: public function filterByFalseyValue(Expr $expr): self
2799: {
2800: $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($this, $expr, TypeSpecifierContext::createFalsey());
2801: return $this->filterBySpecifiedTypes($specifiedTypes);
2802: }
2803:
2804: /**
2805: * @deprecated
2806: */
2807: private function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
2808: {
2809: $typeSpecifications = [];
2810: foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $type]) {
2811: if ($expr instanceof Node\Scalar || $expr instanceof Array_ || $expr instanceof Expr\UnaryMinus && $expr->expr instanceof Node\Scalar) {
2812: continue;
2813: }
2814: $typeSpecifications[] = [
2815: 'sure' => true,
2816: 'exprString' => (string) $exprString,
2817: 'expr' => $expr,
2818: 'type' => $type,
2819: ];
2820: }
2821: foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $type]) {
2822: if ($expr instanceof Node\Scalar || $expr instanceof Array_ || $expr instanceof Expr\UnaryMinus && $expr->expr instanceof Node\Scalar) {
2823: continue;
2824: }
2825: $typeSpecifications[] = [
2826: 'sure' => false,
2827: 'exprString' => (string) $exprString,
2828: 'expr' => $expr,
2829: 'type' => $type,
2830: ];
2831: }
2832:
2833: usort($typeSpecifications, static function (array $a, array $b): int {
2834: $length = strlen($a['exprString']) - strlen($b['exprString']);
2835: if ($length !== 0) {
2836: return $length;
2837: }
2838:
2839: return $b['sure'] - $a['sure']; // @phpstan-ignore minus.leftNonNumeric, minus.rightNonNumeric
2840: });
2841:
2842: $scope = $this;
2843: $specifiedExpressions = [];
2844: foreach ($typeSpecifications as $typeSpecification) {
2845: $expr = $typeSpecification['expr'];
2846: $type = $typeSpecification['type'];
2847:
2848: if ($expr instanceof IssetExpr) {
2849: $issetExpr = $expr;
2850: $expr = $issetExpr->getExpr();
2851:
2852: if ($typeSpecification['sure']) {
2853: $scope = $scope->setExpressionCertaintyInternal(
2854: $expr,
2855: TrinaryLogic::createMaybe(),
2856: );
2857: } else {
2858: $scope = $scope->unsetExpressionInternal($expr);
2859: }
2860:
2861: continue;
2862: }
2863:
2864: if ($typeSpecification['sure']) {
2865: if ($specifiedTypes->shouldOverwrite()) {
2866: $scope = $scope->assignExpressionInternal($expr, $type, $type);
2867: } else {
2868: $scope = $scope->addTypeToExpressionInternal($expr, $type);
2869: }
2870: } else {
2871: $scope = $scope->removeTypeFromExpressionInternal($expr, $type);
2872: }
2873: $specifiedExpressions[$this->getNodeKey($expr)] = ExpressionTypeHolder::createYes($expr, $scope->getType($expr));
2874: }
2875:
2876: $conditions = [];
2877: foreach ($scope->conditionalExpressions as $conditionalExprString => $conditionalExpressions) {
2878: foreach ($conditionalExpressions as $conditionalExpression) {
2879: foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) {
2880: if (!array_key_exists($holderExprString, $specifiedExpressions) || !$specifiedExpressions[$holderExprString]->equals($conditionalTypeHolder)) {
2881: continue 2;
2882: }
2883: }
2884:
2885: $conditions[$conditionalExprString][] = $conditionalExpression;
2886: $specifiedExpressions[$conditionalExprString] = $conditionalExpression->getTypeHolder();
2887: }
2888: }
2889:
2890: foreach ($conditions as $conditionalExprString => $expressions) {
2891: $certainty = TrinaryLogic::lazyExtremeIdentity($expressions, static fn (ConditionalExpressionHolder $holder) => $holder->getTypeHolder()->getCertainty());
2892: if ($certainty->no()) {
2893: unset($scope->expressionTypes[$conditionalExprString]);
2894: } else {
2895: $type = TypeCombinator::intersect(...array_map(static fn (ConditionalExpressionHolder $holder) => $holder->getTypeHolder()->getType(), $expressions));
2896:
2897: $scope->expressionTypes[$conditionalExprString] = array_key_exists($conditionalExprString, $scope->expressionTypes)
2898: ? new ExpressionTypeHolder(
2899: $scope->expressionTypes[$conditionalExprString]->getExpr(),
2900: TypeCombinator::intersect($scope->expressionTypes[$conditionalExprString]->getType(), $type),
2901: TrinaryLogic::maxMin($scope->expressionTypes[$conditionalExprString]->getCertainty(), $certainty),
2902: )
2903: : $expressions[0]->getTypeHolder();
2904: }
2905: }
2906:
2907: return $scope->scopeFactory->create(
2908: $scope->context,
2909: $scope->isDeclareStrictTypes(),
2910: $scope->getFunction(),
2911: $scope->getNamespace(),
2912: $scope->expressionTypes,
2913: $scope->nativeExpressionTypes,
2914: array_merge($specifiedTypes->getNewConditionalExpressionHolders(), $scope->conditionalExpressions),
2915: $scope->inClosureBindScopeClasses,
2916: $scope->anonymousFunctionReflection,
2917: $scope->inFirstLevelStatement,
2918: $scope->currentlyAssignedExpressions,
2919: $scope->currentlyAllowedUndefinedExpressions,
2920: $scope->inFunctionCallsStack,
2921: $scope->afterExtractCall,
2922: $scope->parentScope,
2923: $scope->nativeTypesPromoted,
2924: );
2925: }
2926:
2927: /**
2928: * @deprecated
2929: */
2930: private function addTypeToExpressionInternal(Expr $expr, Type $type): self
2931: {
2932: $originalExprType = $this->getType($expr);
2933: $nativeType = $this->getNativeType($expr);
2934:
2935: if ($originalExprType->equals($nativeType)) {
2936: $newType = TypeCombinator::intersect($type, $originalExprType);
2937: if ($newType->isConstantScalarValue()->yes() && $newType->equals($originalExprType)) {
2938: // don't add the same type over and over again to improve performance
2939: return $this;
2940: }
2941: return $this->specifyExpressionTypeInternal($expr, $newType, $newType, TrinaryLogic::createYes());
2942: }
2943:
2944: return $this->specifyExpressionTypeInternal(
2945: $expr,
2946: TypeCombinator::intersect($type, $originalExprType),
2947: TypeCombinator::intersect($type, $nativeType),
2948: TrinaryLogic::createYes(),
2949: );
2950: }
2951:
2952: /**
2953: * @deprecated
2954: */
2955: private function removeTypeFromExpressionInternal(Expr $expr, Type $typeToRemove): self
2956: {
2957: $exprType = $this->getType($expr);
2958: if (
2959: $exprType instanceof NeverType ||
2960: $typeToRemove instanceof NeverType
2961: ) {
2962: return $this;
2963: }
2964: return $this->specifyExpressionTypeInternal(
2965: $expr,
2966: TypeCombinator::remove($exprType, $typeToRemove),
2967: TypeCombinator::remove($this->getNativeType($expr), $typeToRemove),
2968: TrinaryLogic::createYes(),
2969: );
2970: }
2971:
2972: /**
2973: * @deprecated
2974: */
2975: private function setExpressionCertaintyInternal(Expr $expr, TrinaryLogic $certainty): self
2976: {
2977: if ($this->hasExpressionType($expr)->no()) {
2978: throw new ShouldNotHappenException();
2979: }
2980:
2981: $originalExprType = $this->getType($expr);
2982: $nativeType = $this->getNativeType($expr);
2983:
2984: return $this->specifyExpressionTypeInternal(
2985: $expr,
2986: $originalExprType,
2987: $nativeType,
2988: $certainty,
2989: );
2990: }
2991:
2992: /**
2993: * @deprecated
2994: */
2995: private function specifyExpressionTypeInternal(Expr $expr, Type $type, Type $nativeType, TrinaryLogic $certainty): self
2996: {
2997: if ($expr instanceof ConstFetch) {
2998: $loweredConstName = strtolower($expr->name->toString());
2999: if (in_array($loweredConstName, ['true', 'false', 'null'], true)) {
3000: return $this;
3001: }
3002: }
3003:
3004: if ($expr instanceof FuncCall && $expr->name instanceof Name && $type->isFalse()->yes()) {
3005: $functionName = $this->reflectionProvider->resolveFunctionName($expr->name, $this);
3006: if ($functionName !== null && in_array(strtolower($functionName), [
3007: 'is_dir',
3008: 'is_file',
3009: 'file_exists',
3010: ], true)) {
3011: return $this;
3012: }
3013: }
3014:
3015: $scope = $this;
3016: if (
3017: $expr instanceof Expr\ArrayDimFetch
3018: && $expr->dim !== null
3019: && !$expr->dim instanceof Expr\PreInc
3020: && !$expr->dim instanceof Expr\PreDec
3021: && !$expr->dim instanceof Expr\PostDec
3022: && !$expr->dim instanceof Expr\PostInc
3023: ) {
3024: $dimType = $scope->getType($expr->dim)->toArrayKey();
3025: if ($dimType->isInteger()->yes() || $dimType->isString()->yes()) {
3026: $exprVarType = $scope->getType($expr->var);
3027: if (!$exprVarType instanceof MixedType && !$exprVarType->isArray()->no()) {
3028: $types = [
3029: new ArrayType(new MixedType(), new MixedType()),
3030: new ObjectType(ArrayAccess::class),
3031: new NullType(),
3032: ];
3033: if ($dimType->isInteger()->yes()) {
3034: $types[] = new StringType();
3035: }
3036: $offsetValueType = TypeCombinator::intersect($exprVarType, TypeCombinator::union(...$types));
3037:
3038: if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) {
3039: $offsetValueType = TypeCombinator::intersect(
3040: $offsetValueType,
3041: new HasOffsetValueType($dimType, $type),
3042: );
3043: }
3044:
3045: $scope = $scope->specifyExpressionTypeInternal(
3046: $expr->var,
3047: $offsetValueType,
3048: $scope->getNativeType($expr->var),
3049: $certainty,
3050: );
3051: }
3052: }
3053: }
3054:
3055: if ($certainty->no()) {
3056: throw new ShouldNotHappenException();
3057: }
3058:
3059: $exprString = $this->getNodeKey($expr);
3060: $expressionTypes = $scope->expressionTypes;
3061: $expressionTypes[$exprString] = new ExpressionTypeHolder($expr, $type, $certainty);
3062: $nativeTypes = $scope->nativeExpressionTypes;
3063: $nativeTypes[$exprString] = new ExpressionTypeHolder($expr, $nativeType, $certainty);
3064:
3065: $scope = $this->scopeFactory->create(
3066: $this->context,
3067: $this->isDeclareStrictTypes(),
3068: $this->getFunction(),
3069: $this->getNamespace(),
3070: $expressionTypes,
3071: $nativeTypes,
3072: $this->conditionalExpressions,
3073: $this->inClosureBindScopeClasses,
3074: $this->anonymousFunctionReflection,
3075: $this->inFirstLevelStatement,
3076: $this->currentlyAssignedExpressions,
3077: $this->currentlyAllowedUndefinedExpressions,
3078: $this->inFunctionCallsStack,
3079: $this->afterExtractCall,
3080: $this->parentScope,
3081: $this->nativeTypesPromoted,
3082: );
3083:
3084: if ($expr instanceof AlwaysRememberedExpr) {
3085: return $scope->specifyExpressionTypeInternal($expr->expr, $type, $nativeType, $certainty);
3086: }
3087:
3088: return $scope;
3089: }
3090:
3091: /**
3092: * @deprecated
3093: */
3094: private function unsetExpressionInternal(Expr $expr): self
3095: {
3096: $scope = $this;
3097: if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
3098: $exprVarType = $scope->getType($expr->var);
3099: $dimType = $scope->getType($expr->dim);
3100: $unsetType = $exprVarType->unsetOffset($dimType);
3101: $exprVarNativeType = $scope->getNativeType($expr->var);
3102: $dimNativeType = $scope->getNativeType($expr->dim);
3103: $unsetNativeType = $exprVarNativeType->unsetOffset($dimNativeType);
3104: $scope = $scope->assignExpressionInternal($expr->var, $unsetType, $unsetNativeType)->invalidateExpression(
3105: new FuncCall(new FullyQualified('count'), [new Arg($expr->var)]),
3106: )->invalidateExpression(
3107: new FuncCall(new FullyQualified('sizeof'), [new Arg($expr->var)]),
3108: )->invalidateExpression(
3109: new FuncCall(new Name('count'), [new Arg($expr->var)]),
3110: )->invalidateExpression(
3111: new FuncCall(new Name('sizeof'), [new Arg($expr->var)]),
3112: );
3113:
3114: if ($expr->var instanceof Expr\ArrayDimFetch && $expr->var->dim !== null) {
3115: $scope = $scope->assignExpressionInternal(
3116: $expr->var->var,
3117: $this->getType($expr->var->var)->setOffsetValueType(
3118: $scope->getType($expr->var->dim),
3119: $scope->getType($expr->var),
3120: ),
3121: $this->getNativeType($expr->var->var)->setOffsetValueType(
3122: $scope->getNativeType($expr->var->dim),
3123: $scope->getNativeType($expr->var),
3124: ),
3125: );
3126: }
3127: }
3128:
3129: return $scope->invalidateExpression($expr);
3130: }
3131:
3132: /**
3133: * @return Generator<int, GeneratorTValueType, GeneratorTSendType, self>
3134: */
3135: public function applySpecifiedTypes(SpecifiedTypes $specifiedTypes): Generator
3136: {
3137: $typeSpecifications = [];
3138: foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $type]) {
3139: if ($expr instanceof Node\Scalar || $expr instanceof Array_ || $expr instanceof Expr\UnaryMinus && $expr->expr instanceof Node\Scalar) {
3140: continue;
3141: }
3142: $typeSpecifications[] = [
3143: 'sure' => true,
3144: 'exprString' => (string) $exprString,
3145: 'expr' => $expr,
3146: 'type' => $type,
3147: ];
3148: }
3149: foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $type]) {
3150: if ($expr instanceof Node\Scalar || $expr instanceof Array_ || $expr instanceof Expr\UnaryMinus && $expr->expr instanceof Node\Scalar) {
3151: continue;
3152: }
3153: $typeSpecifications[] = [
3154: 'sure' => false,
3155: 'exprString' => (string) $exprString,
3156: 'expr' => $expr,
3157: 'type' => $type,
3158: ];
3159: }
3160:
3161: usort($typeSpecifications, static function (array $a, array $b): int {
3162: $length = strlen($a['exprString']) - strlen($b['exprString']);
3163: if ($length !== 0) {
3164: return $length;
3165: }
3166:
3167: return $b['sure'] - $a['sure']; // @phpstan-ignore minus.leftNonNumeric, minus.rightNonNumeric
3168: });
3169:
3170: $scope = $this;
3171: $specifiedExpressions = [];
3172: foreach ($typeSpecifications as $typeSpecification) {
3173: $expr = $typeSpecification['expr'];
3174: $type = $typeSpecification['type'];
3175:
3176: if ($expr instanceof IssetExpr) {
3177: $issetExpr = $expr;
3178: $expr = $issetExpr->getExpr();
3179:
3180: if ($typeSpecification['sure']) {
3181: $gen = $scope->setExpressionCertainty(
3182: $expr,
3183: TrinaryLogic::createMaybe(),
3184: );
3185: yield from $gen;
3186: $scope = $gen->getReturn();
3187: } else {
3188: $gen = $scope->unsetExpression($expr);
3189: yield from $gen;
3190: $scope = $gen->getReturn();
3191: }
3192:
3193: continue;
3194: }
3195:
3196: if ($typeSpecification['sure']) {
3197: if ($specifiedTypes->shouldOverwrite()) {
3198: $gen = $scope->assignExpression($expr, $type, $type);
3199: yield from $gen;
3200: $scope = $gen->getReturn();
3201: } else {
3202: $gen = $scope->addTypeToExpression($expr, $type);
3203: yield from $gen;
3204: $scope = $gen->getReturn();
3205: }
3206: } else {
3207: $gen = $scope->removeTypeFromExpression($expr, $type);
3208: yield from $gen;
3209: $scope = $gen->getReturn();
3210: }
3211:
3212: $exprType = (yield ExprAnalysisRequest::createNoopRequest($expr, $scope))->type;
3213: $specifiedExpressions[$this->getNodeKey($expr)] = ExpressionTypeHolder::createYes($expr, $exprType);
3214: }
3215:
3216: $conditions = [];
3217: foreach ($scope->conditionalExpressions as $conditionalExprString => $conditionalExpressions) {
3218: foreach ($conditionalExpressions as $conditionalExpression) {
3219: foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) {
3220: if (!array_key_exists($holderExprString, $specifiedExpressions) || !$specifiedExpressions[$holderExprString]->equals($conditionalTypeHolder)) {
3221: continue 2;
3222: }
3223: }
3224:
3225: $conditions[$conditionalExprString][] = $conditionalExpression;
3226: $specifiedExpressions[$conditionalExprString] = $conditionalExpression->getTypeHolder();
3227: }
3228: }
3229:
3230: foreach ($conditions as $conditionalExprString => $expressions) {
3231: $certainty = TrinaryLogic::lazyExtremeIdentity($expressions, static fn (ConditionalExpressionHolder $holder) => $holder->getTypeHolder()->getCertainty());
3232: if ($certainty->no()) {
3233: unset($scope->expressionTypes[$conditionalExprString]);
3234: } else {
3235: $type = TypeCombinator::intersect(...array_map(static fn (ConditionalExpressionHolder $holder) => $holder->getTypeHolder()->getType(), $expressions));
3236:
3237: $scope->expressionTypes[$conditionalExprString] = array_key_exists($conditionalExprString, $scope->expressionTypes)
3238: ? new ExpressionTypeHolder(
3239: $scope->expressionTypes[$conditionalExprString]->getExpr(),
3240: TypeCombinator::intersect($scope->expressionTypes[$conditionalExprString]->getType(), $type),
3241: TrinaryLogic::maxMin($scope->expressionTypes[$conditionalExprString]->getCertainty(), $certainty),
3242: )
3243: : $expressions[0]->getTypeHolder();
3244: }
3245: }
3246:
3247: return $scope->scopeFactory->create(
3248: $scope->context,
3249: $scope->isDeclareStrictTypes(),
3250: $scope->getFunction(),
3251: $scope->getNamespace(),
3252: $scope->expressionTypes,
3253: $scope->nativeExpressionTypes,
3254: array_merge($specifiedTypes->getNewConditionalExpressionHolders(), $scope->conditionalExpressions),
3255: $scope->inClosureBindScopeClasses,
3256: $scope->anonymousFunctionReflection,
3257: $scope->inFirstLevelStatement,
3258: $scope->currentlyAssignedExpressions,
3259: $scope->currentlyAllowedUndefinedExpressions,
3260: $scope->inFunctionCallsStack,
3261: $scope->afterExtractCall,
3262: $scope->parentScope,
3263: $scope->nativeTypesPromoted,
3264: );
3265: }
3266:
3267: /**
3268: * @return Generator<int, GeneratorTValueType, GeneratorTSendType, self>
3269: */
3270: private function setExpressionCertainty(Expr $expr, TrinaryLogic $certainty): Generator
3271: {
3272: if ($this->hasExpressionType($expr)->no()) {
3273: throw new ShouldNotHappenException();
3274: }
3275:
3276: $exprResult = (yield ExprAnalysisRequest::createNoopRequest($expr, $this));
3277: $gen = $this->specifyExpressionType(
3278: $expr,
3279: $exprResult->type,
3280: $exprResult->nativeType,
3281: $certainty,
3282: );
3283: yield from $gen;
3284: return $gen->getReturn();
3285: }
3286:
3287: /**
3288: * @return Generator<int, GeneratorTValueType, GeneratorTSendType, self>
3289: */
3290: private function unsetExpression(Expr $expr): Generator
3291: {
3292: $scope = $this;
3293: if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
3294: $exprVarResult = (yield ExprAnalysisRequest::createNoopRequest($expr->var, $scope));
3295: $exprVarType = $exprVarResult->type;
3296:
3297: $dimResult = (yield ExprAnalysisRequest::createNoopRequest($expr->dim, $scope));
3298: $dimType = $dimResult->type;
3299: $unsetType = $exprVarType->unsetOffset($dimType);
3300: $exprVarNativeType = $exprVarResult->nativeType;
3301: $dimNativeType = $dimResult->nativeType;
3302: $unsetNativeType = $exprVarNativeType->unsetOffset($dimNativeType);
3303:
3304: $assignExprGen = $scope->assignExpression($expr->var, $unsetType, $unsetNativeType);
3305: yield from $assignExprGen;
3306: $scope = $assignExprGen->getReturn()->invalidateExpression(
3307: new FuncCall(new FullyQualified('count'), [new Arg($expr->var)]),
3308: )->invalidateExpression(
3309: new FuncCall(new FullyQualified('sizeof'), [new Arg($expr->var)]),
3310: )->invalidateExpression(
3311: new FuncCall(new Name('count'), [new Arg($expr->var)]),
3312: )->invalidateExpression(
3313: new FuncCall(new Name('sizeof'), [new Arg($expr->var)]),
3314: );
3315:
3316: if ($expr->var instanceof Expr\ArrayDimFetch && $expr->var->dim !== null) {
3317: $exprVarVarResult = (yield ExprAnalysisRequest::createNoopRequest($expr->var->var, $scope));
3318: $exprVarDimResult = (yield ExprAnalysisRequest::createNoopRequest($expr->var->dim, $scope));
3319: $assignExprGen = $scope->assignExpression(
3320: $expr->var->var,
3321: $exprVarVarResult->type->setOffsetValueType(
3322: $exprVarDimResult->type,
3323: $exprVarType,
3324: ),
3325: $exprVarVarResult->nativeType->setOffsetValueType(
3326: $exprVarDimResult->nativeType,
3327: $exprVarNativeType,
3328: ),
3329: );
3330: yield from $assignExprGen;
3331: $scope = $assignExprGen->getReturn();
3332: }
3333: }
3334:
3335: return $scope->invalidateExpression($expr);
3336: }
3337:
3338: /**
3339: * @return Generator<int, GeneratorTValueType, GeneratorTSendType, self>
3340: */
3341: private function addTypeToExpression(Expr $expr, Type $type): Generator
3342: {
3343: $exprResult = yield ExprAnalysisRequest::createNoopRequest($expr, $this);
3344: $originalExprType = $exprResult->type;
3345: $nativeType = $exprResult->nativeType;
3346:
3347: if ($originalExprType->equals($nativeType)) {
3348: $newType = TypeCombinator::intersect($type, $originalExprType);
3349: if ($newType->isConstantScalarValue()->yes() && $newType->equals($originalExprType)) {
3350: // don't add the same type over and over again to improve performance
3351: return $this;
3352: }
3353: $gen = $this->specifyExpressionType($expr, $newType, $newType, TrinaryLogic::createYes());
3354: yield from $gen;
3355: return $gen->getReturn();
3356: }
3357:
3358: $gen = $this->specifyExpressionType(
3359: $expr,
3360: TypeCombinator::intersect($type, $originalExprType),
3361: TypeCombinator::intersect($type, $nativeType),
3362: TrinaryLogic::createYes(),
3363: );
3364: yield from $gen;
3365: return $gen->getReturn();
3366: }
3367:
3368: /**
3369: * @return Generator<int, GeneratorTValueType, GeneratorTSendType, self>
3370: */
3371: private function removeTypeFromExpression(Expr $expr, Type $typeToRemove): Generator
3372: {
3373: $exprResult = yield ExprAnalysisRequest::createNoopRequest($expr, $this);
3374: $exprType = $exprResult->type;
3375: if (
3376: $exprType instanceof NeverType ||
3377: $typeToRemove instanceof NeverType
3378: ) {
3379: return $this;
3380: }
3381: $gen = $this->specifyExpressionType(
3382: $expr,
3383: TypeCombinator::remove($exprType, $typeToRemove),
3384: TypeCombinator::remove($exprResult->nativeType, $typeToRemove),
3385: TrinaryLogic::createYes(),
3386: );
3387: yield from $gen;
3388:
3389: return $gen->getReturn();
3390: }
3391:
3392: /** @api */
3393: public function isInFirstLevelStatement(): bool
3394: {
3395: return $this->inFirstLevelStatement;
3396: }
3397:
3398: public function exitFirstLevelStatements(): self
3399: {
3400: if (!$this->inFirstLevelStatement) {
3401: return $this;
3402: }
3403:
3404: return $this->scopeFactory->create(
3405: $this->context,
3406: $this->isDeclareStrictTypes(),
3407: $this->getFunction(),
3408: $this->getNamespace(),
3409: $this->expressionTypes,
3410: $this->nativeExpressionTypes,
3411: $this->conditionalExpressions,
3412: $this->inClosureBindScopeClasses,
3413: $this->anonymousFunctionReflection,
3414: false,
3415: $this->currentlyAssignedExpressions,
3416: $this->currentlyAllowedUndefinedExpressions,
3417: $this->inFunctionCallsStack,
3418: $this->afterExtractCall,
3419: $this->parentScope,
3420: $this->nativeTypesPromoted,
3421: );
3422: }
3423:
3424: public function mergeWith(?self $otherScope): self
3425: {
3426: if ($otherScope === null) {
3427: return $this;
3428: }
3429: $ourExpressionTypes = $this->expressionTypes;
3430: $theirExpressionTypes = $otherScope->expressionTypes;
3431:
3432: $mergedExpressionTypes = $this->mergeVariableHolders($ourExpressionTypes, $theirExpressionTypes);
3433: $conditionalExpressions = $this->intersectConditionalExpressions($otherScope->conditionalExpressions);
3434: $conditionalExpressions = $this->createConditionalExpressions(
3435: $conditionalExpressions,
3436: $ourExpressionTypes,
3437: $theirExpressionTypes,
3438: $mergedExpressionTypes,
3439: );
3440: $conditionalExpressions = $this->createConditionalExpressions(
3441: $conditionalExpressions,
3442: $theirExpressionTypes,
3443: $ourExpressionTypes,
3444: $mergedExpressionTypes,
3445: );
3446: return $this->scopeFactory->create(
3447: $this->context,
3448: $this->isDeclareStrictTypes(),
3449: $this->getFunction(),
3450: $this->getNamespace(),
3451: $mergedExpressionTypes,
3452: $this->mergeVariableHolders($this->nativeExpressionTypes, $otherScope->nativeExpressionTypes),
3453: $conditionalExpressions,
3454: $this->inClosureBindScopeClasses,
3455: $this->anonymousFunctionReflection,
3456: $this->inFirstLevelStatement,
3457: [],
3458: [],
3459: [],
3460: $this->afterExtractCall && $otherScope->afterExtractCall,
3461: $this->parentScope,
3462: $this->nativeTypesPromoted,
3463: );
3464: }
3465:
3466: /**
3467: * @param array<string, ConditionalExpressionHolder[]> $otherConditionalExpressions
3468: * @return array<string, ConditionalExpressionHolder[]>
3469: */
3470: private function intersectConditionalExpressions(array $otherConditionalExpressions): array
3471: {
3472: $newConditionalExpressions = [];
3473: foreach ($this->conditionalExpressions as $exprString => $holders) {
3474: if (!array_key_exists($exprString, $otherConditionalExpressions)) {
3475: continue;
3476: }
3477:
3478: $otherHolders = $otherConditionalExpressions[$exprString];
3479: foreach (array_keys($holders) as $key) {
3480: if (!array_key_exists($key, $otherHolders)) {
3481: continue 2;
3482: }
3483: }
3484:
3485: $newConditionalExpressions[$exprString] = $holders;
3486: }
3487:
3488: return $newConditionalExpressions;
3489: }
3490:
3491: /**
3492: * @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
3493: * @param array<string, ExpressionTypeHolder> $ourExpressionTypes
3494: * @param array<string, ExpressionTypeHolder> $theirExpressionTypes
3495: * @param array<string, ExpressionTypeHolder> $mergedExpressionTypes
3496: * @return array<string, ConditionalExpressionHolder[]>
3497: */
3498: private function createConditionalExpressions(
3499: array $conditionalExpressions,
3500: array $ourExpressionTypes,
3501: array $theirExpressionTypes,
3502: array $mergedExpressionTypes,
3503: ): array
3504: {
3505: $newVariableTypes = $ourExpressionTypes;
3506: foreach ($theirExpressionTypes as $exprString => $holder) {
3507: if (!array_key_exists($exprString, $mergedExpressionTypes)) {
3508: continue;
3509: }
3510:
3511: if (!$mergedExpressionTypes[$exprString]->getType()->equals($holder->getType())) {
3512: continue;
3513: }
3514:
3515: unset($newVariableTypes[$exprString]);
3516: }
3517:
3518: $typeGuards = [];
3519: foreach ($newVariableTypes as $exprString => $holder) {
3520: if (!$holder->getCertainty()->yes()) {
3521: continue;
3522: }
3523: if (!array_key_exists($exprString, $mergedExpressionTypes)) {
3524: continue;
3525: }
3526: if ($mergedExpressionTypes[$exprString]->getType()->equals($holder->getType())) {
3527: continue;
3528: }
3529:
3530: $typeGuards[$exprString] = $holder;
3531: }
3532:
3533: if (count($typeGuards) === 0) {
3534: return $conditionalExpressions;
3535: }
3536:
3537: foreach ($newVariableTypes as $exprString => $holder) {
3538: if (
3539: array_key_exists($exprString, $mergedExpressionTypes)
3540: && $mergedExpressionTypes[$exprString]->equals($holder)
3541: ) {
3542: continue;
3543: }
3544:
3545: $variableTypeGuards = $typeGuards;
3546: unset($variableTypeGuards[$exprString]);
3547:
3548: if (count($variableTypeGuards) === 0) {
3549: continue;
3550: }
3551:
3552: $conditionalExpression = new ConditionalExpressionHolder($variableTypeGuards, $holder);
3553: $conditionalExpressions[$exprString][$conditionalExpression->getKey()] = $conditionalExpression;
3554: }
3555:
3556: foreach ($mergedExpressionTypes as $exprString => $mergedExprTypeHolder) {
3557: if (array_key_exists($exprString, $ourExpressionTypes)) {
3558: continue;
3559: }
3560:
3561: $conditionalExpression = new ConditionalExpressionHolder($typeGuards, new ExpressionTypeHolder($mergedExprTypeHolder->getExpr(), new ErrorType(), TrinaryLogic::createNo()));
3562: $conditionalExpressions[$exprString][$conditionalExpression->getKey()] = $conditionalExpression;
3563: }
3564:
3565: return $conditionalExpressions;
3566: }
3567:
3568: /**
3569: * @param array<string, ExpressionTypeHolder> $ourVariableTypeHolders
3570: * @param array<string, ExpressionTypeHolder> $theirVariableTypeHolders
3571: * @return array<string, ExpressionTypeHolder>
3572: */
3573: private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array
3574: {
3575: $intersectedVariableTypeHolders = [];
3576: $globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isGlobalVariable($node->name);
3577: $nodeFinder = new NodeFinder();
3578: foreach ($ourVariableTypeHolders as $exprString => $variableTypeHolder) {
3579: if (isset($theirVariableTypeHolders[$exprString])) {
3580: if ($variableTypeHolder === $theirVariableTypeHolders[$exprString]) {
3581: $intersectedVariableTypeHolders[$exprString] = $variableTypeHolder;
3582: continue;
3583: }
3584:
3585: $intersectedVariableTypeHolders[$exprString] = $variableTypeHolder->and($theirVariableTypeHolders[$exprString]);
3586: } else {
3587: $expr = $variableTypeHolder->getExpr();
3588: if ($nodeFinder->findFirst($expr, $globalVariableCallback) !== null) {
3589: continue;
3590: }
3591:
3592: $intersectedVariableTypeHolders[$exprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType());
3593: }
3594: }
3595:
3596: foreach ($theirVariableTypeHolders as $exprString => $variableTypeHolder) {
3597: if (isset($intersectedVariableTypeHolders[$exprString])) {
3598: continue;
3599: }
3600:
3601: $expr = $variableTypeHolder->getExpr();
3602: if ($nodeFinder->findFirst($expr, $globalVariableCallback) !== null) {
3603: continue;
3604: }
3605:
3606: $intersectedVariableTypeHolders[$exprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType());
3607: }
3608:
3609: return $intersectedVariableTypeHolders;
3610: }
3611:
3612: public function processFinallyScope(self $finallyScope, self $originalFinallyScope): self
3613: {
3614: return $this->scopeFactory->create(
3615: $this->context,
3616: $this->isDeclareStrictTypes(),
3617: $this->getFunction(),
3618: $this->getNamespace(),
3619: $this->processFinallyScopeVariableTypeHolders(
3620: $this->expressionTypes,
3621: $finallyScope->expressionTypes,
3622: $originalFinallyScope->expressionTypes,
3623: ),
3624: $this->processFinallyScopeVariableTypeHolders(
3625: $this->nativeExpressionTypes,
3626: $finallyScope->nativeExpressionTypes,
3627: $originalFinallyScope->nativeExpressionTypes,
3628: ),
3629: $this->conditionalExpressions,
3630: $this->inClosureBindScopeClasses,
3631: $this->anonymousFunctionReflection,
3632: $this->inFirstLevelStatement,
3633: [],
3634: [],
3635: [],
3636: $this->afterExtractCall,
3637: $this->parentScope,
3638: $this->nativeTypesPromoted,
3639: );
3640: }
3641:
3642: /**
3643: * @param array<string, ExpressionTypeHolder> $ourVariableTypeHolders
3644: * @param array<string, ExpressionTypeHolder> $finallyVariableTypeHolders
3645: * @param array<string, ExpressionTypeHolder> $originalVariableTypeHolders
3646: * @return array<string, ExpressionTypeHolder>
3647: */
3648: private function processFinallyScopeVariableTypeHolders(
3649: array $ourVariableTypeHolders,
3650: array $finallyVariableTypeHolders,
3651: array $originalVariableTypeHolders,
3652: ): array
3653: {
3654: foreach ($finallyVariableTypeHolders as $exprString => $variableTypeHolder) {
3655: if (
3656: isset($originalVariableTypeHolders[$exprString])
3657: && !$originalVariableTypeHolders[$exprString]->getType()->equals($variableTypeHolder->getType())
3658: ) {
3659: $ourVariableTypeHolders[$exprString] = $variableTypeHolder;
3660: continue;
3661: }
3662:
3663: if (isset($originalVariableTypeHolders[$exprString])) {
3664: continue;
3665: }
3666:
3667: $ourVariableTypeHolders[$exprString] = $variableTypeHolder;
3668: }
3669:
3670: return $ourVariableTypeHolders;
3671: }
3672:
3673: /**
3674: * @param Node\ClosureUse[] $byRefUses
3675: */
3676: public function processClosureScope(
3677: self $closureScope,
3678: ?self $prevScope,
3679: array $byRefUses,
3680: ): self
3681: {
3682: $nativeExpressionTypes = $this->nativeExpressionTypes;
3683: $expressionTypes = $this->expressionTypes;
3684: if (count($byRefUses) === 0) {
3685: return $this;
3686: }
3687:
3688: foreach ($byRefUses as $use) {
3689: if (!is_string($use->var->name)) {
3690: throw new ShouldNotHappenException();
3691: }
3692:
3693: $variableName = $use->var->name;
3694: $variableExprString = '$' . $variableName;
3695:
3696: if (!$closureScope->hasVariableType($variableName)->yes()) {
3697: $holder = ExpressionTypeHolder::createYes($use->var, new NullType());
3698: $expressionTypes[$variableExprString] = $holder;
3699: $nativeExpressionTypes[$variableExprString] = $holder;
3700: continue;
3701: }
3702:
3703: $variableType = $closureScope->getVariableType($variableName);
3704:
3705: if ($prevScope !== null) {
3706: $prevVariableType = $prevScope->getVariableType($variableName);
3707: if (!$variableType->equals($prevVariableType)) {
3708: $variableType = TypeCombinator::union($variableType, $prevVariableType);
3709: $variableType = $this->generalizeType($variableType, $prevVariableType, 0);
3710: }
3711: }
3712:
3713: $expressionTypes[$variableExprString] = ExpressionTypeHolder::createYes($use->var, $variableType);
3714: $nativeExpressionTypes[$variableExprString] = ExpressionTypeHolder::createYes($use->var, $variableType);
3715: }
3716:
3717: return $this->scopeFactory->create(
3718: $this->context,
3719: $this->isDeclareStrictTypes(),
3720: $this->getFunction(),
3721: $this->getNamespace(),
3722: $expressionTypes,
3723: $nativeExpressionTypes,
3724: $this->conditionalExpressions,
3725: $this->inClosureBindScopeClasses,
3726: $this->anonymousFunctionReflection,
3727: $this->inFirstLevelStatement,
3728: [],
3729: [],
3730: $this->inFunctionCallsStack,
3731: $this->afterExtractCall,
3732: $this->parentScope,
3733: $this->nativeTypesPromoted,
3734: );
3735: }
3736:
3737: public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope): self
3738: {
3739: $expressionTypes = $this->expressionTypes;
3740: foreach ($finalScope->expressionTypes as $variableExprString => $variableTypeHolder) {
3741: if (!isset($expressionTypes[$variableExprString])) {
3742: $expressionTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType());
3743: continue;
3744: }
3745:
3746: $expressionTypes[$variableExprString] = new ExpressionTypeHolder(
3747: $variableTypeHolder->getExpr(),
3748: $variableTypeHolder->getType(),
3749: $variableTypeHolder->getCertainty()->and($expressionTypes[$variableExprString]->getCertainty()),
3750: );
3751: }
3752: $nativeTypes = $this->nativeExpressionTypes;
3753: foreach ($finalScope->nativeExpressionTypes as $variableExprString => $variableTypeHolder) {
3754: if (!isset($nativeTypes[$variableExprString])) {
3755: $nativeTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType());
3756: continue;
3757: }
3758:
3759: $nativeTypes[$variableExprString] = new ExpressionTypeHolder(
3760: $variableTypeHolder->getExpr(),
3761: $variableTypeHolder->getType(),
3762: $variableTypeHolder->getCertainty()->and($nativeTypes[$variableExprString]->getCertainty()),
3763: );
3764: }
3765:
3766: return $this->scopeFactory->create(
3767: $this->context,
3768: $this->isDeclareStrictTypes(),
3769: $this->getFunction(),
3770: $this->getNamespace(),
3771: $expressionTypes,
3772: $nativeTypes,
3773: $this->conditionalExpressions,
3774: $this->inClosureBindScopeClasses,
3775: $this->anonymousFunctionReflection,
3776: $this->inFirstLevelStatement,
3777: [],
3778: [],
3779: [],
3780: $this->afterExtractCall,
3781: $this->parentScope,
3782: $this->nativeTypesPromoted,
3783: );
3784: }
3785:
3786: public function generalizeWith(self $otherScope): self
3787: {
3788: $variableTypeHolders = $this->generalizeVariableTypeHolders(
3789: $this->expressionTypes,
3790: $otherScope->expressionTypes,
3791: );
3792: $nativeTypes = $this->generalizeVariableTypeHolders(
3793: $this->nativeExpressionTypes,
3794: $otherScope->nativeExpressionTypes,
3795: );
3796:
3797: return $this->scopeFactory->create(
3798: $this->context,
3799: $this->isDeclareStrictTypes(),
3800: $this->getFunction(),
3801: $this->getNamespace(),
3802: $variableTypeHolders,
3803: $nativeTypes,
3804: $this->conditionalExpressions,
3805: $this->inClosureBindScopeClasses,
3806: $this->anonymousFunctionReflection,
3807: $this->inFirstLevelStatement,
3808: [],
3809: [],
3810: [],
3811: $this->afterExtractCall,
3812: $this->parentScope,
3813: $this->nativeTypesPromoted,
3814: );
3815: }
3816:
3817: /**
3818: * @param array<string, ExpressionTypeHolder> $variableTypeHolders
3819: * @param array<string, ExpressionTypeHolder> $otherVariableTypeHolders
3820: * @return array<string, ExpressionTypeHolder>
3821: */
3822: private function generalizeVariableTypeHolders(
3823: array $variableTypeHolders,
3824: array $otherVariableTypeHolders,
3825: ): array
3826: {
3827: uksort($variableTypeHolders, static fn (string $exprA, string $exprB): int => strlen($exprA) <=> strlen($exprB));
3828:
3829: $generalizedExpressions = [];
3830: $newVariableTypeHolders = [];
3831: foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) {
3832: foreach ($generalizedExpressions as $generalizedExprString => $generalizedExpr) {
3833: if (!$this->shouldInvalidateExpression($generalizedExprString, $generalizedExpr, $variableTypeHolder->getExpr())) {
3834: continue;
3835: }
3836:
3837: continue 2;
3838: }
3839: if (!isset($otherVariableTypeHolders[$variableExprString])) {
3840: $newVariableTypeHolders[$variableExprString] = $variableTypeHolder;
3841: continue;
3842: }
3843:
3844: $generalizedType = $this->generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType(), 0);
3845: if (
3846: !$generalizedType->equals($variableTypeHolder->getType())
3847: ) {
3848: $generalizedExpressions[$variableExprString] = $variableTypeHolder->getExpr();
3849: }
3850: $newVariableTypeHolders[$variableExprString] = new ExpressionTypeHolder(
3851: $variableTypeHolder->getExpr(),
3852: $generalizedType,
3853: $variableTypeHolder->getCertainty(),
3854: );
3855: }
3856:
3857: return $newVariableTypeHolders;
3858: }
3859:
3860: private function generalizeType(Type $a, Type $b, int $depth): Type
3861: {
3862: if ($a->equals($b)) {
3863: return $a;
3864: }
3865:
3866: $constantIntegers = ['a' => [], 'b' => []];
3867: $constantFloats = ['a' => [], 'b' => []];
3868: $constantBooleans = ['a' => [], 'b' => []];
3869: $constantStrings = ['a' => [], 'b' => []];
3870: $constantArrays = ['a' => [], 'b' => []];
3871: $generalArrays = ['a' => [], 'b' => []];
3872: $integerRanges = ['a' => [], 'b' => []];
3873: $otherTypes = [];
3874:
3875: foreach ([
3876: 'a' => TypeUtils::flattenTypes($a),
3877: 'b' => TypeUtils::flattenTypes($b),
3878: ] as $key => $types) {
3879: foreach ($types as $type) {
3880: if ($type instanceof ConstantIntegerType) {
3881: $constantIntegers[$key][] = $type;
3882: continue;
3883: }
3884: if ($type instanceof ConstantFloatType) {
3885: $constantFloats[$key][] = $type;
3886: continue;
3887: }
3888: if ($type instanceof ConstantBooleanType) {
3889: $constantBooleans[$key][] = $type;
3890: continue;
3891: }
3892: if ($type instanceof ConstantStringType) {
3893: $constantStrings[$key][] = $type;
3894: continue;
3895: }
3896: if ($type->isConstantArray()->yes()) {
3897: $constantArrays[$key][] = $type;
3898: continue;
3899: }
3900: if ($type->isArray()->yes()) {
3901: $generalArrays[$key][] = $type;
3902: continue;
3903: }
3904: if ($type instanceof IntegerRangeType) {
3905: $integerRanges[$key][] = $type;
3906: continue;
3907: }
3908:
3909: $otherTypes[] = $type;
3910: }
3911: }
3912:
3913: $resultTypes = [];
3914: foreach ([
3915: $constantFloats,
3916: $constantBooleans,
3917: $constantStrings,
3918: ] as $constantTypes) {
3919: if (count($constantTypes['a']) === 0) {
3920: if (count($constantTypes['b']) > 0) {
3921: $resultTypes[] = TypeCombinator::union(...$constantTypes['b']);
3922: }
3923: continue;
3924: } elseif (count($constantTypes['b']) === 0) {
3925: $resultTypes[] = TypeCombinator::union(...$constantTypes['a']);
3926: continue;
3927: }
3928:
3929: $aTypes = TypeCombinator::union(...$constantTypes['a']);
3930: $bTypes = TypeCombinator::union(...$constantTypes['b']);
3931: if ($aTypes->equals($bTypes)) {
3932: $resultTypes[] = $aTypes;
3933: continue;
3934: }
3935:
3936: $resultTypes[] = TypeCombinator::union(...$constantTypes['a'], ...$constantTypes['b'])->generalize(GeneralizePrecision::moreSpecific());
3937: }
3938:
3939: if (count($constantArrays['a']) > 0) {
3940: if (count($constantArrays['b']) === 0) {
3941: $resultTypes[] = TypeCombinator::union(...$constantArrays['a']);
3942: } else {
3943: $constantArraysA = TypeCombinator::union(...$constantArrays['a']);
3944: $constantArraysB = TypeCombinator::union(...$constantArrays['b']);
3945: if (
3946: $constantArraysA->getIterableKeyType()->equals($constantArraysB->getIterableKeyType())
3947: && $constantArraysA->getArraySize()->getGreaterOrEqualType($this->phpVersion)->isSuperTypeOf($constantArraysB->getArraySize())->yes()
3948: ) {
3949: $resultArrayBuilder = ConstantArrayTypeBuilder::createEmpty();
3950: foreach (TypeUtils::flattenTypes($constantArraysA->getIterableKeyType()) as $keyType) {
3951: $resultArrayBuilder->setOffsetValueType(
3952: $keyType,
3953: $this->generalizeType(
3954: $constantArraysA->getOffsetValueType($keyType),
3955: $constantArraysB->getOffsetValueType($keyType),
3956: $depth + 1,
3957: ),
3958: !$constantArraysA->hasOffsetValueType($keyType)->and($constantArraysB->hasOffsetValueType($keyType))->negate()->no(),
3959: );
3960: }
3961:
3962: $resultTypes[] = $resultArrayBuilder->getArray();
3963: } else {
3964: $resultType = new ArrayType(
3965: TypeCombinator::union($this->generalizeType($constantArraysA->getIterableKeyType(), $constantArraysB->getIterableKeyType(), $depth + 1)),
3966: TypeCombinator::union($this->generalizeType($constantArraysA->getIterableValueType(), $constantArraysB->getIterableValueType(), $depth + 1)),
3967: );
3968: if (
3969: $constantArraysA->isIterableAtLeastOnce()->yes()
3970: && $constantArraysB->isIterableAtLeastOnce()->yes()
3971: && $constantArraysA->getArraySize()->getGreaterOrEqualType($this->phpVersion)->isSuperTypeOf($constantArraysB->getArraySize())->yes()
3972: ) {
3973: $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType());
3974: }
3975: if ($constantArraysA->isList()->yes() && $constantArraysB->isList()->yes()) {
3976: $resultType = TypeCombinator::intersect($resultType, new AccessoryArrayListType());
3977: }
3978: $resultTypes[] = $resultType;
3979: }
3980: }
3981: } elseif (count($constantArrays['b']) > 0) {
3982: $resultTypes[] = TypeCombinator::union(...$constantArrays['b']);
3983: }
3984:
3985: if (count($generalArrays['a']) > 0) {
3986: if (count($generalArrays['b']) === 0) {
3987: $resultTypes[] = TypeCombinator::union(...$generalArrays['a']);
3988: } else {
3989: $generalArraysA = TypeCombinator::union(...$generalArrays['a']);
3990: $generalArraysB = TypeCombinator::union(...$generalArrays['b']);
3991:
3992: $aValueType = $generalArraysA->getIterableValueType();
3993: $bValueType = $generalArraysB->getIterableValueType();
3994: if (
3995: $aValueType->isArray()->yes()
3996: && $aValueType->isConstantArray()->no()
3997: && $bValueType->isArray()->yes()
3998: && $bValueType->isConstantArray()->no()
3999: ) {
4000: $aDepth = self::getArrayDepth($aValueType) + $depth;
4001: $bDepth = self::getArrayDepth($bValueType) + $depth;
4002: if (
4003: ($aDepth > 2 || $bDepth > 2)
4004: && abs($aDepth - $bDepth) > 0
4005: ) {
4006: $aValueType = new MixedType();
4007: $bValueType = new MixedType();
4008: }
4009: }
4010:
4011: $resultType = new ArrayType(
4012: TypeCombinator::union($this->generalizeType($generalArraysA->getIterableKeyType(), $generalArraysB->getIterableKeyType(), $depth + 1)),
4013: TypeCombinator::union($this->generalizeType($aValueType, $bValueType, $depth + 1)),
4014: );
4015: if ($generalArraysA->isIterableAtLeastOnce()->yes() && $generalArraysB->isIterableAtLeastOnce()->yes()) {
4016: $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType());
4017: }
4018: if ($generalArraysA->isList()->yes() && $generalArraysB->isList()->yes()) {
4019: $resultType = TypeCombinator::intersect($resultType, new AccessoryArrayListType());
4020: }
4021: if ($generalArraysA->isOversizedArray()->yes() && $generalArraysB->isOversizedArray()->yes()) {
4022: $resultType = TypeCombinator::intersect($resultType, new OversizedArrayType());
4023: }
4024: $resultTypes[] = $resultType;
4025: }
4026: } elseif (count($generalArrays['b']) > 0) {
4027: $resultTypes[] = TypeCombinator::union(...$generalArrays['b']);
4028: }
4029:
4030: if (count($constantIntegers['a']) > 0) {
4031: if (count($constantIntegers['b']) === 0) {
4032: $resultTypes[] = TypeCombinator::union(...$constantIntegers['a']);
4033: } else {
4034: $constantIntegersA = TypeCombinator::union(...$constantIntegers['a']);
4035: $constantIntegersB = TypeCombinator::union(...$constantIntegers['b']);
4036:
4037: if ($constantIntegersA->equals($constantIntegersB)) {
4038: $resultTypes[] = $constantIntegersA;
4039: } else {
4040: $min = null;
4041: $max = null;
4042: foreach ($constantIntegers['a'] as $int) {
4043: if ($min === null || $int->getValue() < $min) {
4044: $min = $int->getValue();
4045: }
4046: if ($max !== null && $int->getValue() <= $max) {
4047: continue;
4048: }
4049:
4050: $max = $int->getValue();
4051: }
4052:
4053: $gotGreater = false;
4054: $gotSmaller = false;
4055: foreach ($constantIntegers['b'] as $int) {
4056: if ($int->getValue() > $max) {
4057: $gotGreater = true;
4058: }
4059: if ($int->getValue() >= $min) {
4060: continue;
4061: }
4062:
4063: $gotSmaller = true;
4064: }
4065:
4066: if ($gotGreater && $gotSmaller) {
4067: $resultTypes[] = new IntegerType();
4068: } elseif ($gotGreater) {
4069: $resultTypes[] = IntegerRangeType::fromInterval($min, null);
4070: } elseif ($gotSmaller) {
4071: $resultTypes[] = IntegerRangeType::fromInterval(null, $max);
4072: } else {
4073: $resultTypes[] = TypeCombinator::union($constantIntegersA, $constantIntegersB);
4074: }
4075: }
4076: }
4077: } elseif (count($constantIntegers['b']) > 0) {
4078: $resultTypes[] = TypeCombinator::union(...$constantIntegers['b']);
4079: }
4080:
4081: if (count($integerRanges['a']) > 0) {
4082: if (count($integerRanges['b']) === 0) {
4083: $resultTypes[] = TypeCombinator::union(...$integerRanges['a']);
4084: } else {
4085: $integerRangesA = TypeCombinator::union(...$integerRanges['a']);
4086: $integerRangesB = TypeCombinator::union(...$integerRanges['b']);
4087:
4088: if ($integerRangesA->equals($integerRangesB)) {
4089: $resultTypes[] = $integerRangesA;
4090: } else {
4091: $min = null;
4092: $max = null;
4093: foreach ($integerRanges['a'] as $range) {
4094: if ($range->getMin() === null) {
4095: $rangeMin = PHP_INT_MIN;
4096: } else {
4097: $rangeMin = $range->getMin();
4098: }
4099: if ($range->getMax() === null) {
4100: $rangeMax = PHP_INT_MAX;
4101: } else {
4102: $rangeMax = $range->getMax();
4103: }
4104:
4105: if ($min === null || $rangeMin < $min) {
4106: $min = $rangeMin;
4107: }
4108: if ($max !== null && $rangeMax <= $max) {
4109: continue;
4110: }
4111:
4112: $max = $rangeMax;
4113: }
4114:
4115: $gotGreater = false;
4116: $gotSmaller = false;
4117: foreach ($integerRanges['b'] as $range) {
4118: if ($range->getMin() === null) {
4119: $rangeMin = PHP_INT_MIN;
4120: } else {
4121: $rangeMin = $range->getMin();
4122: }
4123: if ($range->getMax() === null) {
4124: $rangeMax = PHP_INT_MAX;
4125: } else {
4126: $rangeMax = $range->getMax();
4127: }
4128:
4129: if ($rangeMax > $max) {
4130: $gotGreater = true;
4131: }
4132: if ($rangeMin >= $min) {
4133: continue;
4134: }
4135:
4136: $gotSmaller = true;
4137: }
4138:
4139: if ($min === PHP_INT_MIN) {
4140: $min = null;
4141: }
4142: if ($max === PHP_INT_MAX) {
4143: $max = null;
4144: }
4145:
4146: if ($gotGreater && $gotSmaller) {
4147: $resultTypes[] = new IntegerType();
4148: } elseif ($gotGreater) {
4149: $resultTypes[] = IntegerRangeType::fromInterval($min, null);
4150: } elseif ($gotSmaller) {
4151: $resultTypes[] = IntegerRangeType::fromInterval(null, $max);
4152: } else {
4153: $resultTypes[] = TypeCombinator::union($integerRangesA, $integerRangesB);
4154: }
4155: }
4156: }
4157: } elseif (count($integerRanges['b']) > 0) {
4158: $resultTypes[] = TypeCombinator::union(...$integerRanges['b']);
4159: }
4160:
4161: $accessoryTypes = array_map(
4162: static fn (Type $type): Type => $type->generalize(GeneralizePrecision::moreSpecific()),
4163: TypeUtils::getAccessoryTypes($a),
4164: );
4165:
4166: return TypeCombinator::union(TypeCombinator::intersect(
4167: TypeCombinator::union(...$resultTypes, ...$otherTypes),
4168: ...$accessoryTypes,
4169: ), ...$otherTypes);
4170: }
4171:
4172: private static function getArrayDepth(Type $type): int
4173: {
4174: $depth = 0;
4175: $arrays = TypeUtils::toBenevolentUnion($type)->getArrays();
4176: while (count($arrays) > 0) {
4177: $temp = $type->getIterableValueType();
4178: $type = $temp;
4179: $arrays = TypeUtils::toBenevolentUnion($type)->getArrays();
4180: $depth++;
4181: }
4182:
4183: return $depth;
4184: }
4185:
4186: public function equals(self $otherScope): bool
4187: {
4188: if (!$this->context->equals($otherScope->context)) {
4189: return false;
4190: }
4191:
4192: if (!$this->compareVariableTypeHolders($this->expressionTypes, $otherScope->expressionTypes)) {
4193: return false;
4194: }
4195: return $this->compareVariableTypeHolders($this->nativeExpressionTypes, $otherScope->nativeExpressionTypes);
4196: }
4197:
4198: /**
4199: * @param array<string, ExpressionTypeHolder> $variableTypeHolders
4200: * @param array<string, ExpressionTypeHolder> $otherVariableTypeHolders
4201: */
4202: private function compareVariableTypeHolders(array $variableTypeHolders, array $otherVariableTypeHolders): bool
4203: {
4204: if (count($variableTypeHolders) !== count($otherVariableTypeHolders)) {
4205: return false;
4206: }
4207: foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) {
4208: if (!isset($otherVariableTypeHolders[$variableExprString])) {
4209: return false;
4210: }
4211:
4212: if (!$variableTypeHolder->getCertainty()->equals($otherVariableTypeHolders[$variableExprString]->getCertainty())) {
4213: return false;
4214: }
4215:
4216: if (!$variableTypeHolder->getType()->equals($otherVariableTypeHolders[$variableExprString]->getType())) {
4217: return false;
4218: }
4219:
4220: unset($otherVariableTypeHolders[$variableExprString]);
4221: }
4222:
4223: return true;
4224: }
4225:
4226: public function getPhpVersion(): PhpVersions
4227: {
4228: $constType = $this->getGlobalConstantType(new Name('PHP_VERSION_ID'));
4229:
4230: $isOverallPhpVersionRange = false;
4231: if (
4232: $constType instanceof IntegerRangeType
4233: && $constType->getMin() === ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID
4234: && ($constType->getMax() === null || $constType->getMax() === PhpVersionFactory::MAX_PHP_VERSION)
4235: ) {
4236: $isOverallPhpVersionRange = true;
4237: }
4238:
4239: if ($constType !== null && !$isOverallPhpVersionRange) {
4240: return new PhpVersions($constType);
4241: }
4242:
4243: if (is_array($this->configPhpVersion)) {
4244: return new PhpVersions(IntegerRangeType::fromInterval($this->configPhpVersion['min'], $this->configPhpVersion['max']));
4245: }
4246: return new PhpVersions(new ConstantIntegerType($this->phpVersion->getVersionId()));
4247: }
4248:
4249: public function invokeNodeCallback(Node $node): void
4250: {
4251: Fiber::suspend(new NodeCallbackRequest($node, $this, null));
4252: }
4253:
4254: }
4255: