|    1:  | <?php declare(strict_types = 1); | 
|    2:  |  | 
|    3:  | namespace PHPStan\Analyser; | 
|    4:  |  | 
|    5:  | use ArrayAccess; | 
|    6:  | use Closure; | 
|    7:  | use DivisionByZeroError; | 
|    8:  | use Override; | 
|    9:  | use PhpParser\Comment\Doc; | 
|   10:  | use PhpParser\Modifiers; | 
|   11:  | use PhpParser\Node; | 
|   12:  | use PhpParser\Node\Arg; | 
|   13:  | use PhpParser\Node\AttributeGroup; | 
|   14:  | use PhpParser\Node\ComplexType; | 
|   15:  | use PhpParser\Node\Expr; | 
|   16:  | use PhpParser\Node\Expr\Array_; | 
|   17:  | use PhpParser\Node\Expr\ArrayDimFetch; | 
|   18:  | use PhpParser\Node\Expr\Assign; | 
|   19:  | use PhpParser\Node\Expr\AssignRef; | 
|   20:  | use PhpParser\Node\Expr\BinaryOp; | 
|   21:  | use PhpParser\Node\Expr\BinaryOp\BooleanAnd; | 
|   22:  | use PhpParser\Node\Expr\BinaryOp\BooleanOr; | 
|   23:  | use PhpParser\Node\Expr\BinaryOp\Coalesce; | 
|   24:  | use PhpParser\Node\Expr\BooleanNot; | 
|   25:  | use PhpParser\Node\Expr\CallLike; | 
|   26:  | use PhpParser\Node\Expr\Cast; | 
|   27:  | use PhpParser\Node\Expr\ConstFetch; | 
|   28:  | use PhpParser\Node\Expr\ErrorSuppress; | 
|   29:  | use PhpParser\Node\Expr\Exit_; | 
|   30:  | use PhpParser\Node\Expr\FuncCall; | 
|   31:  | use PhpParser\Node\Expr\Instanceof_; | 
|   32:  | use PhpParser\Node\Expr\List_; | 
|   33:  | use PhpParser\Node\Expr\MethodCall; | 
|   34:  | use PhpParser\Node\Expr\New_; | 
|   35:  | use PhpParser\Node\Expr\PropertyFetch; | 
|   36:  | use PhpParser\Node\Expr\StaticCall; | 
|   37:  | use PhpParser\Node\Expr\StaticPropertyFetch; | 
|   38:  | use PhpParser\Node\Expr\Ternary; | 
|   39:  | use PhpParser\Node\Expr\Variable; | 
|   40:  | use PhpParser\Node\Identifier; | 
|   41:  | use PhpParser\Node\Name; | 
|   42:  | use PhpParser\Node\Stmt\Break_; | 
|   43:  | use PhpParser\Node\Stmt\Class_; | 
|   44:  | use PhpParser\Node\Stmt\Continue_; | 
|   45:  | use PhpParser\Node\Stmt\Do_; | 
|   46:  | use PhpParser\Node\Stmt\Echo_; | 
|   47:  | use PhpParser\Node\Stmt\For_; | 
|   48:  | use PhpParser\Node\Stmt\Foreach_; | 
|   49:  | use PhpParser\Node\Stmt\If_; | 
|   50:  | use PhpParser\Node\Stmt\InlineHTML; | 
|   51:  | use PhpParser\Node\Stmt\Return_; | 
|   52:  | use PhpParser\Node\Stmt\Static_; | 
|   53:  | use PhpParser\Node\Stmt\Switch_; | 
|   54:  | use PhpParser\Node\Stmt\TryCatch; | 
|   55:  | use PhpParser\Node\Stmt\Unset_; | 
|   56:  | use PhpParser\Node\Stmt\While_; | 
|   57:  | use PhpParser\NodeFinder; | 
|   58:  | use PhpParser\NodeTraverser; | 
|   59:  | use PhpParser\NodeVisitor\CloningVisitor; | 
|   60:  | use PhpParser\NodeVisitorAbstract; | 
|   61:  | use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass; | 
|   62:  | use PHPStan\BetterReflection\Reflection\ReflectionEnum; | 
|   63:  | use PHPStan\BetterReflection\Reflector\Reflector; | 
|   64:  | use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection; | 
|   65:  | use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource; | 
|   66:  | use PHPStan\DependencyInjection\AutowiredParameter; | 
|   67:  | use PHPStan\DependencyInjection\AutowiredService; | 
|   68:  | use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; | 
|   69:  | use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; | 
|   70:  | use PHPStan\DependencyInjection\Type\ParameterClosureThisExtensionProvider; | 
|   71:  | use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; | 
|   72:  | use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; | 
|   73:  | use PHPStan\File\FileHelper; | 
|   74:  | use PHPStan\File\FileReader; | 
|   75:  | use PHPStan\Node\BooleanAndNode; | 
|   76:  | use PHPStan\Node\BooleanOrNode; | 
|   77:  | use PHPStan\Node\BreaklessWhileLoopNode; | 
|   78:  | use PHPStan\Node\CatchWithUnthrownExceptionNode; | 
|   79:  | use PHPStan\Node\ClassConstantsNode; | 
|   80:  | use PHPStan\Node\ClassMethodsNode; | 
|   81:  | use PHPStan\Node\ClassPropertiesNode; | 
|   82:  | use PHPStan\Node\ClassPropertyNode; | 
|   83:  | use PHPStan\Node\ClassStatementsGatherer; | 
|   84:  | use PHPStan\Node\ClosureReturnStatementsNode; | 
|   85:  | use PHPStan\Node\DoWhileLoopConditionNode; | 
|   86:  | use PHPStan\Node\ExecutionEndNode; | 
|   87:  | use PHPStan\Node\Expr\AlwaysRememberedExpr; | 
|   88:  | use PHPStan\Node\Expr\ExistingArrayDimFetch; | 
|   89:  | use PHPStan\Node\Expr\ForeachValueByRefExpr; | 
|   90:  | use PHPStan\Node\Expr\GetIterableKeyTypeExpr; | 
|   91:  | use PHPStan\Node\Expr\GetIterableValueTypeExpr; | 
|   92:  | use PHPStan\Node\Expr\GetOffsetValueTypeExpr; | 
|   93:  | use PHPStan\Node\Expr\NativeTypeExpr; | 
|   94:  | use PHPStan\Node\Expr\OriginalPropertyTypeExpr; | 
|   95:  | use PHPStan\Node\Expr\PropertyInitializationExpr; | 
|   96:  | use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr; | 
|   97:  | use PHPStan\Node\Expr\SetOffsetValueTypeExpr; | 
|   98:  | use PHPStan\Node\Expr\TypeExpr; | 
|   99:  | use PHPStan\Node\Expr\UnsetOffsetExpr; | 
|  100:  | use PHPStan\Node\FinallyExitPointsNode; | 
|  101:  | use PHPStan\Node\FunctionCallableNode; | 
|  102:  | use PHPStan\Node\FunctionReturnStatementsNode; | 
|  103:  | use PHPStan\Node\InArrowFunctionNode; | 
|  104:  | use PHPStan\Node\InClassMethodNode; | 
|  105:  | use PHPStan\Node\InClassNode; | 
|  106:  | use PHPStan\Node\InClosureNode; | 
|  107:  | use PHPStan\Node\InForeachNode; | 
|  108:  | use PHPStan\Node\InFunctionNode; | 
|  109:  | use PHPStan\Node\InPropertyHookNode; | 
|  110:  | use PHPStan\Node\InstantiationCallableNode; | 
|  111:  | use PHPStan\Node\InTraitNode; | 
|  112:  | use PHPStan\Node\InvalidateExprNode; | 
|  113:  | use PHPStan\Node\LiteralArrayItem; | 
|  114:  | use PHPStan\Node\LiteralArrayNode; | 
|  115:  | use PHPStan\Node\MatchExpressionArm; | 
|  116:  | use PHPStan\Node\MatchExpressionArmBody; | 
|  117:  | use PHPStan\Node\MatchExpressionArmCondition; | 
|  118:  | use PHPStan\Node\MatchExpressionNode; | 
|  119:  | use PHPStan\Node\MethodCallableNode; | 
|  120:  | use PHPStan\Node\MethodReturnStatementsNode; | 
|  121:  | use PHPStan\Node\NoopExpressionNode; | 
|  122:  | use PHPStan\Node\PropertyAssignNode; | 
|  123:  | use PHPStan\Node\PropertyHookReturnStatementsNode; | 
|  124:  | use PHPStan\Node\PropertyHookStatementNode; | 
|  125:  | use PHPStan\Node\ReturnStatement; | 
|  126:  | use PHPStan\Node\StaticMethodCallableNode; | 
|  127:  | use PHPStan\Node\UnreachableStatementNode; | 
|  128:  | use PHPStan\Node\VariableAssignNode; | 
|  129:  | use PHPStan\Node\VarTagChangedExpressionTypeNode; | 
|  130:  | use PHPStan\Parser\ArrowFunctionArgVisitor; | 
|  131:  | use PHPStan\Parser\ClosureArgVisitor; | 
|  132:  | use PHPStan\Parser\ImmediatelyInvokedClosureVisitor; | 
|  133:  | use PHPStan\Parser\LineAttributesVisitor; | 
|  134:  | use PHPStan\Parser\Parser; | 
|  135:  | use PHPStan\Parser\ReversePipeTransformerVisitor; | 
|  136:  | use PHPStan\Php\PhpVersion; | 
|  137:  | use PHPStan\PhpDoc\PhpDocInheritanceResolver; | 
|  138:  | use PHPStan\PhpDoc\ResolvedPhpDocBlock; | 
|  139:  | use PHPStan\PhpDoc\StubPhpDocProvider; | 
|  140:  | use PHPStan\PhpDoc\Tag\VarTag; | 
|  141:  | use PHPStan\Reflection\Assertions; | 
|  142:  | use PHPStan\Reflection\AttributeReflectionFactory; | 
|  143:  | use PHPStan\Reflection\Callables\CallableParametersAcceptor; | 
|  144:  | use PHPStan\Reflection\Callables\SimpleImpurePoint; | 
|  145:  | use PHPStan\Reflection\Callables\SimpleThrowPoint; | 
|  146:  | use PHPStan\Reflection\ClassReflection; | 
|  147:  | use PHPStan\Reflection\Deprecation\DeprecationProvider; | 
|  148:  | use PHPStan\Reflection\ExtendedMethodReflection; | 
|  149:  | use PHPStan\Reflection\ExtendedParameterReflection; | 
|  150:  | use PHPStan\Reflection\ExtendedParametersAcceptor; | 
|  151:  | use PHPStan\Reflection\FunctionReflection; | 
|  152:  | use PHPStan\Reflection\InitializerExprContext; | 
|  153:  | use PHPStan\Reflection\InitializerExprTypeResolver; | 
|  154:  | use PHPStan\Reflection\MethodReflection; | 
|  155:  | use PHPStan\Reflection\Native\NativeMethodReflection; | 
|  156:  | use PHPStan\Reflection\Native\NativeParameterReflection; | 
|  157:  | use PHPStan\Reflection\ParameterReflection; | 
|  158:  | use PHPStan\Reflection\ParametersAcceptor; | 
|  159:  | use PHPStan\Reflection\ParametersAcceptorSelector; | 
|  160:  | use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; | 
|  161:  | use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; | 
|  162:  | use PHPStan\Reflection\Php\PhpMethodReflection; | 
|  163:  | use PHPStan\Reflection\Php\PhpPropertyReflection; | 
|  164:  | use PHPStan\Reflection\ReflectionProvider; | 
|  165:  | use PHPStan\Reflection\SignatureMap\SignatureMapProvider; | 
|  166:  | use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; | 
|  167:  | use PHPStan\ShouldNotHappenException; | 
|  168:  | use PHPStan\TrinaryLogic; | 
|  169:  | use PHPStan\Type\Accessory\AccessoryArrayListType; | 
|  170:  | use PHPStan\Type\Accessory\HasOffsetValueType; | 
|  171:  | use PHPStan\Type\Accessory\NonEmptyArrayType; | 
|  172:  | use PHPStan\Type\ArrayType; | 
|  173:  | use PHPStan\Type\ClosureType; | 
|  174:  | use PHPStan\Type\Constant\ConstantArrayType; | 
|  175:  | use PHPStan\Type\Constant\ConstantArrayTypeBuilder; | 
|  176:  | use PHPStan\Type\Constant\ConstantIntegerType; | 
|  177:  | use PHPStan\Type\Constant\ConstantStringType; | 
|  178:  | use PHPStan\Type\FileTypeMapper; | 
|  179:  | use PHPStan\Type\GeneralizePrecision; | 
|  180:  | use PHPStan\Type\Generic\TemplateTypeHelper; | 
|  181:  | use PHPStan\Type\Generic\TemplateTypeMap; | 
|  182:  | use PHPStan\Type\Generic\TemplateTypeVariance; | 
|  183:  | use PHPStan\Type\Generic\TemplateTypeVarianceMap; | 
|  184:  | use PHPStan\Type\IntegerType; | 
|  185:  | use PHPStan\Type\IntersectionType; | 
|  186:  | use PHPStan\Type\MixedType; | 
|  187:  | use PHPStan\Type\NeverType; | 
|  188:  | use PHPStan\Type\NullType; | 
|  189:  | use PHPStan\Type\ObjectType; | 
|  190:  | use PHPStan\Type\ObjectWithoutClassType; | 
|  191:  | use PHPStan\Type\ParserNodeTypeToPHPStanType; | 
|  192:  | use PHPStan\Type\ResourceType; | 
|  193:  | use PHPStan\Type\StaticType; | 
|  194:  | use PHPStan\Type\StaticTypeFactory; | 
|  195:  | use PHPStan\Type\StringType; | 
|  196:  | use PHPStan\Type\ThisType; | 
|  197:  | use PHPStan\Type\Type; | 
|  198:  | use PHPStan\Type\TypeCombinator; | 
|  199:  | use PHPStan\Type\TypeTraverser; | 
|  200:  | use PHPStan\Type\TypeUtils; | 
|  201:  | use PHPStan\Type\UnionType; | 
|  202:  | use ReflectionProperty; | 
|  203:  | use Throwable; | 
|  204:  | use Traversable; | 
|  205:  | use TypeError; | 
|  206:  | use UnhandledMatchError; | 
|  207:  | use function array_fill_keys; | 
|  208:  | use function array_filter; | 
|  209:  | use function array_key_exists; | 
|  210:  | use function array_key_last; | 
|  211:  | use function array_keys; | 
|  212:  | use function array_map; | 
|  213:  | use function array_merge; | 
|  214:  | use function array_pop; | 
|  215:  | use function array_reverse; | 
|  216:  | use function array_slice; | 
|  217:  | use function array_values; | 
|  218:  | use function base64_decode; | 
|  219:  | use function count; | 
|  220:  | use function in_array; | 
|  221:  | use function is_array; | 
|  222:  | use function is_int; | 
|  223:  | use function is_string; | 
|  224:  | use function ksort; | 
|  225:  | use function sprintf; | 
|  226:  | use function str_starts_with; | 
|  227:  | use function strtolower; | 
|  228:  | use function trim; | 
|  229:  | use function usort; | 
|  230:  | use const PHP_VERSION_ID; | 
|  231:  | use const SORT_NUMERIC; | 
|  232:  |  | 
|  233:  | #[AutowiredService] | 
|  234:  | final class NodeScopeResolver | 
|  235:  | { | 
|  236:  |  | 
|  237:  | 	private const LOOP_SCOPE_ITERATIONS = 3; | 
|  238:  | 	private const GENERALIZE_AFTER_ITERATION = 1; | 
|  239:  |  | 
|  240:  | 	 | 
|  241:  | 	private array $analysedFiles = []; | 
|  242:  |  | 
|  243:  | 	 | 
|  244:  | 	private array $earlyTerminatingMethodNames; | 
|  245:  |  | 
|  246:  | 	 | 
|  247:  | 	private array $calledMethodStack = []; | 
|  248:  |  | 
|  249:  | 	 | 
|  250:  | 	private array $calledMethodResults = []; | 
|  251:  |  | 
|  252:  | 	 | 
|  253:  |  | 
|  254:  |  | 
|  255:  |  | 
|  256:  |  | 
|  257:  | 	public function __construct( | 
|  258:  | 		private readonly ReflectionProvider $reflectionProvider, | 
|  259:  | 		private readonly InitializerExprTypeResolver $initializerExprTypeResolver, | 
|  260:  | 		#[AutowiredParameter(ref: '@nodeScopeResolverReflector')] | 
|  261:  | 		private readonly Reflector $reflector, | 
|  262:  | 		private readonly ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, | 
|  263:  | 		private readonly ParameterOutTypeExtensionProvider $parameterOutTypeExtensionProvider, | 
|  264:  | 		#[AutowiredParameter(ref: '@defaultAnalysisParser')] | 
|  265:  | 		private readonly Parser $parser, | 
|  266:  | 		private readonly FileTypeMapper $fileTypeMapper, | 
|  267:  | 		private readonly StubPhpDocProvider $stubPhpDocProvider, | 
|  268:  | 		private readonly PhpVersion $phpVersion, | 
|  269:  | 		private readonly SignatureMapProvider $signatureMapProvider, | 
|  270:  | 		private readonly DeprecationProvider $deprecationProvider, | 
|  271:  | 		private readonly AttributeReflectionFactory $attributeReflectionFactory, | 
|  272:  | 		private readonly PhpDocInheritanceResolver $phpDocInheritanceResolver, | 
|  273:  | 		private readonly FileHelper $fileHelper, | 
|  274:  | 		private readonly TypeSpecifier $typeSpecifier, | 
|  275:  | 		private readonly DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, | 
|  276:  | 		private readonly ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider, | 
|  277:  | 		private readonly ParameterClosureThisExtensionProvider $parameterClosureThisExtensionProvider, | 
|  278:  | 		private readonly ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider, | 
|  279:  | 		private readonly ScopeFactory $scopeFactory, | 
|  280:  | 		#[AutowiredParameter] | 
|  281:  | 		private readonly bool $polluteScopeWithLoopInitialAssignments, | 
|  282:  | 		#[AutowiredParameter] | 
|  283:  | 		private readonly bool $polluteScopeWithAlwaysIterableForeach, | 
|  284:  | 		#[AutowiredParameter] | 
|  285:  | 		private readonly bool $polluteScopeWithBlock, | 
|  286:  | 		#[AutowiredParameter] | 
|  287:  | 		private readonly array $earlyTerminatingMethodCalls, | 
|  288:  | 		#[AutowiredParameter] | 
|  289:  | 		private readonly array $earlyTerminatingFunctionCalls, | 
|  290:  | 		#[AutowiredParameter] | 
|  291:  | 		private readonly array $universalObjectCratesClasses, | 
|  292:  | 		#[AutowiredParameter(ref: '%exceptions.implicitThrows%')] | 
|  293:  | 		private readonly bool $implicitThrows, | 
|  294:  | 		#[AutowiredParameter] | 
|  295:  | 		private readonly bool $treatPhpDocTypesAsCertain, | 
|  296:  | 		private readonly bool $narrowMethodScopeFromConstructor = true, | 
|  297:  | 	) | 
|  298:  | 	{ | 
|  299:  | 		$earlyTerminatingMethodNames = []; | 
|  300:  | 		foreach ($this->earlyTerminatingMethodCalls as $methodNames) { | 
|  301:  | 			foreach ($methodNames as $methodName) { | 
|  302:  | 				$earlyTerminatingMethodNames[strtolower($methodName)] = true; | 
|  303:  | 			} | 
|  304:  | 		} | 
|  305:  | 		$this->earlyTerminatingMethodNames = $earlyTerminatingMethodNames; | 
|  306:  | 	} | 
|  307:  |  | 
|  308:  | 	 | 
|  309:  |  | 
|  310:  |  | 
|  311:  |  | 
|  312:  | 	public function setAnalysedFiles(array $files): void | 
|  313:  | 	{ | 
|  314:  | 		$this->analysedFiles = array_fill_keys($files, true); | 
|  315:  | 	} | 
|  316:  |  | 
|  317:  | 	 | 
|  318:  |  | 
|  319:  |  | 
|  320:  |  | 
|  321:  |  | 
|  322:  | 	public function processNodes( | 
|  323:  | 		array $nodes, | 
|  324:  | 		MutatingScope $scope, | 
|  325:  | 		callable $nodeCallback, | 
|  326:  | 	): void | 
|  327:  | 	{ | 
|  328:  | 		$alreadyTerminated = false; | 
|  329:  | 		foreach ($nodes as $i => $node) { | 
|  330:  | 			if ( | 
|  331:  | 				!$node instanceof Node\Stmt | 
|  332:  | 				|| ($alreadyTerminated && !($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike)) | 
|  333:  | 			) { | 
|  334:  | 				continue; | 
|  335:  | 			} | 
|  336:  |  | 
|  337:  | 			$statementResult = $this->processStmtNode($node, $scope, $nodeCallback, StatementContext::createTopLevel()); | 
|  338:  | 			$scope = $statementResult->getScope(); | 
|  339:  | 			if ($alreadyTerminated || !$statementResult->isAlwaysTerminating()) { | 
|  340:  | 				continue; | 
|  341:  | 			} | 
|  342:  |  | 
|  343:  | 			$alreadyTerminated = true; | 
|  344:  | 			$nextStmts = $this->getNextUnreachableStatements(array_slice($nodes, $i + 1), true); | 
|  345:  | 			$this->processUnreachableStatement($nextStmts, $scope, $nodeCallback); | 
|  346:  | 		} | 
|  347:  | 	} | 
|  348:  |  | 
|  349:  | 	 | 
|  350:  |  | 
|  351:  |  | 
|  352:  |  | 
|  353:  | 	private function processUnreachableStatement(array $nextStmts, MutatingScope $scope, callable $nodeCallback): void | 
|  354:  | 	{ | 
|  355:  | 		if ($nextStmts === []) { | 
|  356:  | 			return; | 
|  357:  | 		} | 
|  358:  |  | 
|  359:  | 		$unreachableStatement = null; | 
|  360:  | 		$nextStatements = []; | 
|  361:  |  | 
|  362:  | 		foreach ($nextStmts as $key => $nextStmt) { | 
|  363:  | 			if ($key === 0) { | 
|  364:  | 				$unreachableStatement = $nextStmt; | 
|  365:  | 				continue; | 
|  366:  | 			} | 
|  367:  |  | 
|  368:  | 			$nextStatements[] = $nextStmt; | 
|  369:  | 		} | 
|  370:  |  | 
|  371:  | 		if (!$unreachableStatement instanceof Node\Stmt) { | 
|  372:  | 			return; | 
|  373:  | 		} | 
|  374:  |  | 
|  375:  | 		$nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); | 
|  376:  | 	} | 
|  377:  |  | 
|  378:  | 	 | 
|  379:  |  | 
|  380:  |  | 
|  381:  |  | 
|  382:  |  | 
|  383:  | 	public function processStmtNodes( | 
|  384:  | 		Node $parentNode, | 
|  385:  | 		array $stmts, | 
|  386:  | 		MutatingScope $scope, | 
|  387:  | 		callable $nodeCallback, | 
|  388:  | 		StatementContext $context, | 
|  389:  | 	): StatementResult | 
|  390:  | 	{ | 
|  391:  | 		$exitPoints = []; | 
|  392:  | 		$throwPoints = []; | 
|  393:  | 		$impurePoints = []; | 
|  394:  | 		$alreadyTerminated = false; | 
|  395:  | 		$hasYield = false; | 
|  396:  | 		$stmtCount = count($stmts); | 
|  397:  | 		$shouldCheckLastStatement = $parentNode instanceof Node\Stmt\Function_ | 
|  398:  | 			|| $parentNode instanceof Node\Stmt\ClassMethod | 
|  399:  | 			|| $parentNode instanceof PropertyHookStatementNode | 
|  400:  | 			|| $parentNode instanceof Expr\Closure; | 
|  401:  | 		foreach ($stmts as $i => $stmt) { | 
|  402:  | 			if ($alreadyTerminated && !($stmt instanceof Node\Stmt\Function_ || $stmt instanceof Node\Stmt\ClassLike)) { | 
|  403:  | 				continue; | 
|  404:  | 			} | 
|  405:  |  | 
|  406:  | 			$isLast = $i === $stmtCount - 1; | 
|  407:  | 			$statementResult = $this->processStmtNode( | 
|  408:  | 				$stmt, | 
|  409:  | 				$scope, | 
|  410:  | 				$nodeCallback, | 
|  411:  | 				$context, | 
|  412:  | 			); | 
|  413:  | 			$scope = $statementResult->getScope(); | 
|  414:  | 			$hasYield = $hasYield || $statementResult->hasYield(); | 
|  415:  |  | 
|  416:  | 			if ($shouldCheckLastStatement && $isLast) { | 
|  417:  | 				$endStatements = $statementResult->getEndStatements(); | 
|  418:  | 				if (count($endStatements) > 0) { | 
|  419:  | 					foreach ($endStatements as $endStatement) { | 
|  420:  | 						$endStatementResult = $endStatement->getResult(); | 
|  421:  | 						$nodeCallback(new ExecutionEndNode( | 
|  422:  | 							$endStatement->getStatement(), | 
|  423:  | 							new StatementResult( | 
|  424:  | 								$endStatementResult->getScope(), | 
|  425:  | 								$hasYield, | 
|  426:  | 								$endStatementResult->isAlwaysTerminating(), | 
|  427:  | 								$endStatementResult->getExitPoints(), | 
|  428:  | 								$endStatementResult->getThrowPoints(), | 
|  429:  | 								$endStatementResult->getImpurePoints(), | 
|  430:  | 							), | 
|  431:  | 							$parentNode->getReturnType() !== null, | 
|  432:  | 						), $endStatementResult->getScope()); | 
|  433:  | 					} | 
|  434:  | 				} else { | 
|  435:  | 					$nodeCallback(new ExecutionEndNode( | 
|  436:  | 						$stmt, | 
|  437:  | 						new StatementResult( | 
|  438:  | 							$scope, | 
|  439:  | 							$hasYield, | 
|  440:  | 							$statementResult->isAlwaysTerminating(), | 
|  441:  | 							$statementResult->getExitPoints(), | 
|  442:  | 							$statementResult->getThrowPoints(), | 
|  443:  | 							$statementResult->getImpurePoints(), | 
|  444:  | 						), | 
|  445:  | 						$parentNode->getReturnType() !== null, | 
|  446:  | 					), $scope); | 
|  447:  | 				} | 
|  448:  | 			} | 
|  449:  |  | 
|  450:  | 			$exitPoints = array_merge($exitPoints, $statementResult->getExitPoints()); | 
|  451:  | 			$throwPoints = array_merge($throwPoints, $statementResult->getThrowPoints()); | 
|  452:  | 			$impurePoints = array_merge($impurePoints, $statementResult->getImpurePoints()); | 
|  453:  |  | 
|  454:  | 			if ($alreadyTerminated || !$statementResult->isAlwaysTerminating()) { | 
|  455:  | 				continue; | 
|  456:  | 			} | 
|  457:  |  | 
|  458:  | 			$alreadyTerminated = true; | 
|  459:  | 			$nextStmts = $this->getNextUnreachableStatements(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_); | 
|  460:  | 			$this->processUnreachableStatement($nextStmts, $scope, $nodeCallback); | 
|  461:  | 		} | 
|  462:  |  | 
|  463:  | 		$statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints); | 
|  464:  | 		if ($stmtCount === 0 && $shouldCheckLastStatement) { | 
|  465:  | 			$returnTypeNode = $parentNode->getReturnType(); | 
|  466:  | 			if ($parentNode instanceof Expr\Closure) { | 
|  467:  | 				$parentNode = new Node\Stmt\Expression($parentNode, $parentNode->getAttributes()); | 
|  468:  | 			} | 
|  469:  | 			$nodeCallback(new ExecutionEndNode( | 
|  470:  | 				$parentNode, | 
|  471:  | 				$statementResult, | 
|  472:  | 				$returnTypeNode !== null, | 
|  473:  | 			), $scope); | 
|  474:  | 		} | 
|  475:  |  | 
|  476:  | 		return $statementResult; | 
|  477:  | 	} | 
|  478:  |  | 
|  479:  | 	 | 
|  480:  |  | 
|  481:  |  | 
|  482:  | 	private function processStmtNode( | 
|  483:  | 		Node\Stmt $stmt, | 
|  484:  | 		MutatingScope $scope, | 
|  485:  | 		callable $nodeCallback, | 
|  486:  | 		StatementContext $context, | 
|  487:  | 	): StatementResult | 
|  488:  | 	{ | 
|  489:  | 		if ( | 
|  490:  | 			!$stmt instanceof Static_ | 
|  491:  | 			&& !$stmt instanceof Foreach_ | 
|  492:  | 			&& !$stmt instanceof Node\Stmt\Global_ | 
|  493:  | 			&& !$stmt instanceof Node\Stmt\Property | 
|  494:  | 			&& !$stmt instanceof Node\Stmt\ClassConst | 
|  495:  | 			&& !$stmt instanceof Node\Stmt\Const_ | 
|  496:  | 		) { | 
|  497:  | 			$scope = $this->processStmtVarAnnotation($scope, $stmt, null, $nodeCallback); | 
|  498:  | 		} | 
|  499:  |  | 
|  500:  | 		if ($stmt instanceof Node\Stmt\ClassMethod) { | 
|  501:  | 			if (!$scope->isInClass()) { | 
|  502:  | 				throw new ShouldNotHappenException(); | 
|  503:  | 			} | 
|  504:  | 			if ( | 
|  505:  | 				$scope->isInTrait() | 
|  506:  | 				&& $scope->getClassReflection()->hasNativeMethod($stmt->name->toString()) | 
|  507:  | 			) { | 
|  508:  | 				$methodReflection = $scope->getClassReflection()->getNativeMethod($stmt->name->toString()); | 
|  509:  | 				if ($methodReflection instanceof NativeMethodReflection) { | 
|  510:  | 					return new StatementResult($scope, false, false, [], [], []); | 
|  511:  | 				} | 
|  512:  | 				if ($methodReflection instanceof PhpMethodReflection) { | 
|  513:  | 					$declaringTrait = $methodReflection->getDeclaringTrait(); | 
|  514:  | 					if ($declaringTrait === null || $declaringTrait->getName() !== $scope->getTraitReflection()->getName()) { | 
|  515:  | 						return new StatementResult($scope, false, false, [], [], []); | 
|  516:  | 					} | 
|  517:  | 				} | 
|  518:  | 			} | 
|  519:  | 		} | 
|  520:  |  | 
|  521:  | 		$stmtScope = $scope; | 
|  522:  | 		if ($stmt instanceof Node\Stmt\Expression && $stmt->expr instanceof Expr\Throw_) { | 
|  523:  | 			$stmtScope = $this->processStmtVarAnnotation($scope, $stmt, $stmt->expr->expr, $nodeCallback); | 
|  524:  | 		} | 
|  525:  | 		if ($stmt instanceof Return_) { | 
|  526:  | 			$stmtScope = $this->processStmtVarAnnotation($scope, $stmt, $stmt->expr, $nodeCallback); | 
|  527:  | 		} | 
|  528:  |  | 
|  529:  | 		$nodeCallback($stmt, $stmtScope); | 
|  530:  |  | 
|  531:  | 		$overridingThrowPoints = $this->getOverridingThrowPoints($stmt, $scope); | 
|  532:  |  | 
|  533:  | 		if ($stmt instanceof Node\Stmt\Declare_) { | 
|  534:  | 			$hasYield = false; | 
|  535:  | 			$throwPoints = []; | 
|  536:  | 			$impurePoints = []; | 
|  537:  | 			$alwaysTerminating = false; | 
|  538:  | 			$exitPoints = []; | 
|  539:  | 			foreach ($stmt->declares as $declare) { | 
|  540:  | 				$nodeCallback($declare, $scope); | 
|  541:  | 				$nodeCallback($declare->value, $scope); | 
|  542:  | 				if ( | 
|  543:  | 					$declare->key->name !== 'strict_types' | 
|  544:  | 					|| !($declare->value instanceof Node\Scalar\Int_) | 
|  545:  | 					|| $declare->value->value !== 1 | 
|  546:  | 				) { | 
|  547:  | 					continue; | 
|  548:  | 				} | 
|  549:  |  | 
|  550:  | 				$scope = $scope->enterDeclareStrictTypes(); | 
|  551:  | 			} | 
|  552:  |  | 
|  553:  | 			if ($stmt->stmts !== null) { | 
|  554:  | 				$result = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback, $context); | 
|  555:  | 				$scope = $result->getScope(); | 
|  556:  | 				$hasYield = $result->hasYield(); | 
|  557:  | 				$throwPoints = $result->getThrowPoints(); | 
|  558:  | 				$impurePoints = $result->getImpurePoints(); | 
|  559:  | 				$alwaysTerminating = $result->isAlwaysTerminating(); | 
|  560:  | 				$exitPoints = $result->getExitPoints(); | 
|  561:  | 			} | 
|  562:  |  | 
|  563:  | 			return new StatementResult($scope, $hasYield, $alwaysTerminating, $exitPoints, $throwPoints, $impurePoints); | 
|  564:  | 		} elseif ($stmt instanceof Node\Stmt\Function_) { | 
|  565:  | 			$hasYield = false; | 
|  566:  | 			$throwPoints = []; | 
|  567:  | 			$impurePoints = []; | 
|  568:  | 			$this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); | 
|  569:  | 			[$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, , $isPure, $acceptsNamedArguments, , $phpDocComment, $asserts,, $phpDocParameterOutTypes] = $this->getPhpDocs($scope, $stmt); | 
|  570:  |  | 
|  571:  | 			foreach ($stmt->params as $param) { | 
|  572:  | 				$this->processParamNode($stmt, $param, $scope, $nodeCallback); | 
|  573:  | 			} | 
|  574:  |  | 
|  575:  | 			if ($stmt->returnType !== null) { | 
|  576:  | 				$nodeCallback($stmt->returnType, $scope); | 
|  577:  | 			} | 
|  578:  |  | 
|  579:  | 			if (!$isDeprecated) { | 
|  580:  | 				[$isDeprecated, $deprecatedDescription] = $this->getDeprecatedAttribute($scope, $stmt); | 
|  581:  | 			} | 
|  582:  |  | 
|  583:  | 			$functionScope = $scope->enterFunction( | 
|  584:  | 				$stmt, | 
|  585:  | 				$templateTypeMap, | 
|  586:  | 				$phpDocParameterTypes, | 
|  587:  | 				$phpDocReturnType, | 
|  588:  | 				$phpDocThrowType, | 
|  589:  | 				$deprecatedDescription, | 
|  590:  | 				$isDeprecated, | 
|  591:  | 				$isInternal, | 
|  592:  | 				$isPure, | 
|  593:  | 				$acceptsNamedArguments, | 
|  594:  | 				$asserts, | 
|  595:  | 				$phpDocComment, | 
|  596:  | 				$phpDocParameterOutTypes, | 
|  597:  | 				$phpDocImmediatelyInvokedCallableParameters, | 
|  598:  | 				$phpDocClosureThisTypeParameters, | 
|  599:  | 			); | 
|  600:  | 			$functionReflection = $functionScope->getFunction(); | 
|  601:  | 			if (!$functionReflection instanceof PhpFunctionFromParserNodeReflection) { | 
|  602:  | 				throw new ShouldNotHappenException(); | 
|  603:  | 			} | 
|  604:  |  | 
|  605:  | 			$nodeCallback(new InFunctionNode($functionReflection, $stmt), $functionScope); | 
|  606:  |  | 
|  607:  | 			$gatheredReturnStatements = []; | 
|  608:  | 			$gatheredYieldStatements = []; | 
|  609:  | 			$executionEnds = []; | 
|  610:  | 			$functionImpurePoints = []; | 
|  611:  | 			$statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $functionScope, static function (Node $node, Scope $scope) use ($nodeCallback, $functionScope, &$gatheredReturnStatements, &$gatheredYieldStatements, &$executionEnds, &$functionImpurePoints): void { | 
|  612:  | 				$nodeCallback($node, $scope); | 
|  613:  | 				if ($scope->getFunction() !== $functionScope->getFunction()) { | 
|  614:  | 					return; | 
|  615:  | 				} | 
|  616:  | 				if ($scope->isInAnonymousFunction()) { | 
|  617:  | 					return; | 
|  618:  | 				} | 
|  619:  | 				if ($node instanceof PropertyAssignNode) { | 
|  620:  | 					$functionImpurePoints[] = new ImpurePoint( | 
|  621:  | 						$scope, | 
|  622:  | 						$node, | 
|  623:  | 						'propertyAssign', | 
|  624:  | 						'property assignment', | 
|  625:  | 						true, | 
|  626:  | 					); | 
|  627:  | 					return; | 
|  628:  | 				} | 
|  629:  | 				if ($node instanceof ExecutionEndNode) { | 
|  630:  | 					$executionEnds[] = $node; | 
|  631:  | 					return; | 
|  632:  | 				} | 
|  633:  | 				if ($node instanceof Expr\Yield_ || $node instanceof Expr\YieldFrom) { | 
|  634:  | 					$gatheredYieldStatements[] = $node; | 
|  635:  | 				} | 
|  636:  | 				if (!$node instanceof Return_) { | 
|  637:  | 					return; | 
|  638:  | 				} | 
|  639:  |  | 
|  640:  | 				$gatheredReturnStatements[] = new ReturnStatement($scope, $node); | 
|  641:  | 			}, StatementContext::createTopLevel()); | 
|  642:  |  | 
|  643:  | 			$nodeCallback(new FunctionReturnStatementsNode( | 
|  644:  | 				$stmt, | 
|  645:  | 				$gatheredReturnStatements, | 
|  646:  | 				$gatheredYieldStatements, | 
|  647:  | 				$statementResult, | 
|  648:  | 				$executionEnds, | 
|  649:  | 				array_merge($statementResult->getImpurePoints(), $functionImpurePoints), | 
|  650:  | 				$functionReflection, | 
|  651:  | 			), $functionScope); | 
|  652:  | 		} elseif ($stmt instanceof Node\Stmt\ClassMethod) { | 
|  653:  | 			$hasYield = false; | 
|  654:  | 			$throwPoints = []; | 
|  655:  | 			$impurePoints = []; | 
|  656:  | 			$this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); | 
|  657:  | 			[$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $phpDocComment, $asserts, $selfOutType, $phpDocParameterOutTypes] = $this->getPhpDocs($scope, $stmt); | 
|  658:  |  | 
|  659:  | 			foreach ($stmt->params as $param) { | 
|  660:  | 				$this->processParamNode($stmt, $param, $scope, $nodeCallback); | 
|  661:  | 			} | 
|  662:  |  | 
|  663:  | 			if ($stmt->returnType !== null) { | 
|  664:  | 				$nodeCallback($stmt->returnType, $scope); | 
|  665:  | 			} | 
|  666:  |  | 
|  667:  | 			if (!$isDeprecated) { | 
|  668:  | 				[$isDeprecated, $deprecatedDescription] = $this->getDeprecatedAttribute($scope, $stmt); | 
|  669:  | 			} | 
|  670:  |  | 
|  671:  | 			$isFromTrait = $stmt->getAttribute('originalTraitMethodName') === '__construct'; | 
|  672:  | 			$isConstructor = $isFromTrait || $stmt->name->toLowerString() === '__construct'; | 
|  673:  |  | 
|  674:  | 			$methodScope = $scope->enterClassMethod( | 
|  675:  | 				$stmt, | 
|  676:  | 				$templateTypeMap, | 
|  677:  | 				$phpDocParameterTypes, | 
|  678:  | 				$phpDocReturnType, | 
|  679:  | 				$phpDocThrowType, | 
|  680:  | 				$deprecatedDescription, | 
|  681:  | 				$isDeprecated, | 
|  682:  | 				$isInternal, | 
|  683:  | 				$isFinal, | 
|  684:  | 				$isPure, | 
|  685:  | 				$acceptsNamedArguments, | 
|  686:  | 				$asserts, | 
|  687:  | 				$selfOutType, | 
|  688:  | 				$phpDocComment, | 
|  689:  | 				$phpDocParameterOutTypes, | 
|  690:  | 				$phpDocImmediatelyInvokedCallableParameters, | 
|  691:  | 				$phpDocClosureThisTypeParameters, | 
|  692:  | 				$isConstructor, | 
|  693:  | 			); | 
|  694:  |  | 
|  695:  | 			if (!$scope->isInClass()) { | 
|  696:  | 				throw new ShouldNotHappenException(); | 
|  697:  | 			} | 
|  698:  |  | 
|  699:  | 			$classReflection = $scope->getClassReflection(); | 
|  700:  |  | 
|  701:  | 			if ($isConstructor) { | 
|  702:  | 				foreach ($stmt->params as $param) { | 
|  703:  | 					if ($param->flags === 0 && $param->hooks === []) { | 
|  704:  | 						continue; | 
|  705:  | 					} | 
|  706:  |  | 
|  707:  | 					if (!$param->var instanceof Variable || !is_string($param->var->name) || $param->var->name === '') { | 
|  708:  | 						throw new ShouldNotHappenException(); | 
|  709:  | 					} | 
|  710:  | 					$phpDoc = null; | 
|  711:  | 					if ($param->getDocComment() !== null) { | 
|  712:  | 						$phpDoc = $param->getDocComment()->getText(); | 
|  713:  | 					} | 
|  714:  | 					$nodeCallback(new ClassPropertyNode( | 
|  715:  | 						$param->var->name, | 
|  716:  | 						$param->flags, | 
|  717:  | 						$param->type !== null ? ParserNodeTypeToPHPStanType::resolve($param->type, $classReflection) : null, | 
|  718:  | 						null, | 
|  719:  | 						$phpDoc, | 
|  720:  | 						$phpDocParameterTypes[$param->var->name] ?? null, | 
|  721:  | 						true, | 
|  722:  | 						$isFromTrait, | 
|  723:  | 						$param, | 
|  724:  | 						$isReadOnly, | 
|  725:  | 						$scope->isInTrait(), | 
|  726:  | 						$classReflection->isReadOnly(), | 
|  727:  | 						false, | 
|  728:  | 						$classReflection, | 
|  729:  | 					), $methodScope); | 
|  730:  | 					$this->processPropertyHooks( | 
|  731:  | 						$stmt, | 
|  732:  | 						$param->type, | 
|  733:  | 						$phpDocParameterTypes[$param->var->name] ?? null, | 
|  734:  | 						$param->var->name, | 
|  735:  | 						$param->hooks, | 
|  736:  | 						$scope, | 
|  737:  | 						$nodeCallback, | 
|  738:  | 					); | 
|  739:  | 					$methodScope = $methodScope->assignExpression(new PropertyInitializationExpr($param->var->name), new MixedType(), new MixedType()); | 
|  740:  | 				} | 
|  741:  | 			} | 
|  742:  |  | 
|  743:  | 			if ($stmt->getAttribute('virtual', false) === false) { | 
|  744:  | 				$methodReflection = $methodScope->getFunction(); | 
|  745:  | 				if (!$methodReflection instanceof PhpMethodFromParserNodeReflection) { | 
|  746:  | 					throw new ShouldNotHappenException(); | 
|  747:  | 				} | 
|  748:  | 				$nodeCallback(new InClassMethodNode($classReflection, $methodReflection, $stmt), $methodScope); | 
|  749:  | 			} | 
|  750:  |  | 
|  751:  | 			if ($stmt->stmts !== null) { | 
|  752:  | 				$gatheredReturnStatements = []; | 
|  753:  | 				$gatheredYieldStatements = []; | 
|  754:  | 				$executionEnds = []; | 
|  755:  | 				$methodImpurePoints = []; | 
|  756:  | 				$statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $methodScope, static function (Node $node, Scope $scope) use ($nodeCallback, $methodScope, &$gatheredReturnStatements, &$gatheredYieldStatements, &$executionEnds, &$methodImpurePoints): void { | 
|  757:  | 					$nodeCallback($node, $scope); | 
|  758:  | 					if ($scope->getFunction() !== $methodScope->getFunction()) { | 
|  759:  | 						return; | 
|  760:  | 					} | 
|  761:  | 					if ($scope->isInAnonymousFunction()) { | 
|  762:  | 						return; | 
|  763:  | 					} | 
|  764:  | 					if ($node instanceof PropertyAssignNode) { | 
|  765:  | 						if ( | 
|  766:  | 							$node->getPropertyFetch() instanceof Expr\PropertyFetch | 
|  767:  | 							&& $scope->getFunction() instanceof PhpMethodFromParserNodeReflection | 
|  768:  | 							&& $scope->getFunction()->getDeclaringClass()->hasConstructor() | 
|  769:  | 							&& $scope->getFunction()->getDeclaringClass()->getConstructor()->getName() === $scope->getFunction()->getName() | 
|  770:  | 							&& TypeUtils::findThisType($scope->getType($node->getPropertyFetch()->var)) !== null | 
|  771:  | 						) { | 
|  772:  | 							return; | 
|  773:  | 						} | 
|  774:  | 						$methodImpurePoints[] = new ImpurePoint( | 
|  775:  | 							$scope, | 
|  776:  | 							$node, | 
|  777:  | 							'propertyAssign', | 
|  778:  | 							'property assignment', | 
|  779:  | 							true, | 
|  780:  | 						); | 
|  781:  | 						return; | 
|  782:  | 					} | 
|  783:  | 					if ($node instanceof ExecutionEndNode) { | 
|  784:  | 						$executionEnds[] = $node; | 
|  785:  | 						return; | 
|  786:  | 					} | 
|  787:  | 					if ($node instanceof Expr\Yield_ || $node instanceof Expr\YieldFrom) { | 
|  788:  | 						$gatheredYieldStatements[] = $node; | 
|  789:  | 					} | 
|  790:  | 					if (!$node instanceof Return_) { | 
|  791:  | 						return; | 
|  792:  | 					} | 
|  793:  |  | 
|  794:  | 					$gatheredReturnStatements[] = new ReturnStatement($scope, $node); | 
|  795:  | 				}, StatementContext::createTopLevel()); | 
|  796:  |  | 
|  797:  | 				$methodReflection = $methodScope->getFunction(); | 
|  798:  | 				if (!$methodReflection instanceof PhpMethodFromParserNodeReflection) { | 
|  799:  | 					throw new ShouldNotHappenException(); | 
|  800:  | 				} | 
|  801:  |  | 
|  802:  | 				$nodeCallback(new MethodReturnStatementsNode( | 
|  803:  | 					$stmt, | 
|  804:  | 					$gatheredReturnStatements, | 
|  805:  | 					$gatheredYieldStatements, | 
|  806:  | 					$statementResult, | 
|  807:  | 					$executionEnds, | 
|  808:  | 					array_merge($statementResult->getImpurePoints(), $methodImpurePoints), | 
|  809:  | 					$classReflection, | 
|  810:  | 					$methodReflection, | 
|  811:  | 				), $methodScope); | 
|  812:  |  | 
|  813:  | 				if ($isConstructor && $this->narrowMethodScopeFromConstructor) { | 
|  814:  | 					$finalScope = null; | 
|  815:  |  | 
|  816:  | 					foreach ($executionEnds as $executionEnd) { | 
|  817:  | 						if ($executionEnd->getStatementResult()->isAlwaysTerminating()) { | 
|  818:  | 							continue; | 
|  819:  | 						} | 
|  820:  |  | 
|  821:  | 						$endScope = $executionEnd->getStatementResult()->getScope(); | 
|  822:  | 						if ($finalScope === null) { | 
|  823:  | 							$finalScope = $endScope; | 
|  824:  | 							continue; | 
|  825:  | 						} | 
|  826:  |  | 
|  827:  | 						$finalScope = $finalScope->mergeWith($endScope); | 
|  828:  | 					} | 
|  829:  |  | 
|  830:  | 					foreach ($gatheredReturnStatements as $statement) { | 
|  831:  | 						if ($finalScope === null) { | 
|  832:  | 							$finalScope = $statement->getScope(); | 
|  833:  | 							continue; | 
|  834:  | 						} | 
|  835:  |  | 
|  836:  | 						$finalScope = $finalScope->mergeWith($statement->getScope()); | 
|  837:  | 					} | 
|  838:  |  | 
|  839:  | 					if ($finalScope !== null) { | 
|  840:  | 						$scope = $finalScope->rememberConstructorScope(); | 
|  841:  | 					} | 
|  842:  |  | 
|  843:  | 				} | 
|  844:  | 			} | 
|  845:  | 		} elseif ($stmt instanceof Echo_) { | 
|  846:  | 			$hasYield = false; | 
|  847:  | 			$throwPoints = []; | 
|  848:  | 			$isAlwaysTerminating = false; | 
|  849:  | 			foreach ($stmt->exprs as $echoExpr) { | 
|  850:  | 				$result = $this->processExprNode($stmt, $echoExpr, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
|  851:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
|  852:  | 				$scope = $result->getScope(); | 
|  853:  | 				$hasYield = $hasYield || $result->hasYield(); | 
|  854:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
|  855:  | 			} | 
|  856:  |  | 
|  857:  | 			$throwPoints = $overridingThrowPoints ?? $throwPoints; | 
|  858:  | 			$impurePoints = [ | 
|  859:  | 				new ImpurePoint($scope, $stmt, 'echo', 'echo', true), | 
|  860:  | 			]; | 
|  861:  | 			return new StatementResult($scope, $hasYield, $isAlwaysTerminating, [], $throwPoints, $impurePoints); | 
|  862:  | 		} elseif ($stmt instanceof Return_) { | 
|  863:  | 			if ($stmt->expr !== null) { | 
|  864:  | 				$result = $this->processExprNode($stmt, $stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
|  865:  | 				$throwPoints = $result->getThrowPoints(); | 
|  866:  | 				$impurePoints = $result->getImpurePoints(); | 
|  867:  | 				$scope = $result->getScope(); | 
|  868:  | 				$hasYield = $result->hasYield(); | 
|  869:  | 			} else { | 
|  870:  | 				$hasYield = false; | 
|  871:  | 				$throwPoints = []; | 
|  872:  | 				$impurePoints = []; | 
|  873:  | 			} | 
|  874:  |  | 
|  875:  | 			return new StatementResult($scope, $hasYield, true, [ | 
|  876:  | 				new StatementExitPoint($stmt, $scope), | 
|  877:  | 			], $overridingThrowPoints ?? $throwPoints, $impurePoints); | 
|  878:  | 		} elseif ($stmt instanceof Continue_ || $stmt instanceof Break_) { | 
|  879:  | 			if ($stmt->num !== null) { | 
|  880:  | 				$result = $this->processExprNode($stmt, $stmt->num, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
|  881:  | 				$scope = $result->getScope(); | 
|  882:  | 				$hasYield = $result->hasYield(); | 
|  883:  | 				$throwPoints = $result->getThrowPoints(); | 
|  884:  | 				$impurePoints = $result->getImpurePoints(); | 
|  885:  | 			} else { | 
|  886:  | 				$hasYield = false; | 
|  887:  | 				$throwPoints = []; | 
|  888:  | 				$impurePoints = []; | 
|  889:  | 			} | 
|  890:  |  | 
|  891:  | 			return new StatementResult($scope, $hasYield, true, [ | 
|  892:  | 				new StatementExitPoint($stmt, $scope), | 
|  893:  | 			], $overridingThrowPoints ?? $throwPoints, $impurePoints); | 
|  894:  | 		} elseif ($stmt instanceof Node\Stmt\Expression) { | 
|  895:  | 			if ($stmt->expr instanceof Expr\Throw_) { | 
|  896:  | 				$scope = $stmtScope; | 
|  897:  | 			} | 
|  898:  | 			$earlyTerminationExpr = $this->findEarlyTerminatingExpr($stmt->expr, $scope); | 
|  899:  | 			$hasAssign = false; | 
|  900:  | 			$currentScope = $scope; | 
|  901:  | 			$result = $this->processExprNode($stmt, $stmt->expr, $scope, static function (Node $node, Scope $scope) use ($nodeCallback, $currentScope, &$hasAssign): void { | 
|  902:  | 				$nodeCallback($node, $scope); | 
|  903:  | 				if ($scope->getAnonymousFunctionReflection() !== $currentScope->getAnonymousFunctionReflection()) { | 
|  904:  | 					return; | 
|  905:  | 				} | 
|  906:  | 				if ($scope->getFunction() !== $currentScope->getFunction()) { | 
|  907:  | 					return; | 
|  908:  | 				} | 
|  909:  | 				if (!$node instanceof VariableAssignNode && !$node instanceof PropertyAssignNode) { | 
|  910:  | 					return; | 
|  911:  | 				} | 
|  912:  |  | 
|  913:  | 				$hasAssign = true; | 
|  914:  | 			}, ExpressionContext::createTopLevel()); | 
|  915:  | 			$throwPoints = array_filter($result->getThrowPoints(), static fn ($throwPoint) => $throwPoint->isExplicit()); | 
|  916:  | 			if ( | 
|  917:  | 				count($result->getImpurePoints()) === 0 | 
|  918:  | 				&& count($throwPoints) === 0 | 
|  919:  | 				&& !$stmt->expr instanceof Expr\PostInc | 
|  920:  | 				&& !$stmt->expr instanceof Expr\PreInc | 
|  921:  | 				&& !$stmt->expr instanceof Expr\PostDec | 
|  922:  | 				&& !$stmt->expr instanceof Expr\PreDec | 
|  923:  | 			) { | 
|  924:  | 				$nodeCallback(new NoopExpressionNode($stmt->expr, $hasAssign), $scope); | 
|  925:  | 			} | 
|  926:  | 			$scope = $result->getScope(); | 
|  927:  | 			$scope = $scope->filterBySpecifiedTypes($this->typeSpecifier->specifyTypesInCondition( | 
|  928:  | 				$scope, | 
|  929:  | 				$stmt->expr, | 
|  930:  | 				TypeSpecifierContext::createNull(), | 
|  931:  | 			)); | 
|  932:  | 			$hasYield = $result->hasYield(); | 
|  933:  | 			$throwPoints = $result->getThrowPoints(); | 
|  934:  | 			$impurePoints = $result->getImpurePoints(); | 
|  935:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
|  936:  |  | 
|  937:  | 			if ($earlyTerminationExpr !== null) { | 
|  938:  | 				return new StatementResult($scope, $hasYield, true, [ | 
|  939:  | 					new StatementExitPoint($stmt, $scope), | 
|  940:  | 				], $overridingThrowPoints ?? $throwPoints, $impurePoints); | 
|  941:  | 			} | 
|  942:  | 			return new StatementResult($scope, $hasYield, $isAlwaysTerminating, [], $overridingThrowPoints ?? $throwPoints, $impurePoints); | 
|  943:  | 		} elseif ($stmt instanceof Node\Stmt\Namespace_) { | 
|  944:  | 			if ($stmt->name !== null) { | 
|  945:  | 				$scope = $scope->enterNamespace($stmt->name->toString()); | 
|  946:  | 			} | 
|  947:  |  | 
|  948:  | 			$scope = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback, $context)->getScope(); | 
|  949:  | 			$hasYield = false; | 
|  950:  | 			$throwPoints = []; | 
|  951:  | 			$impurePoints = []; | 
|  952:  | 		} elseif ($stmt instanceof Node\Stmt\Trait_) { | 
|  953:  | 			return new StatementResult($scope, false, false, [], [], []); | 
|  954:  | 		} elseif ($stmt instanceof Node\Stmt\ClassLike) { | 
|  955:  | 			if (!$context->isTopLevel()) { | 
|  956:  | 				return new StatementResult($scope, false, false, [], [], []); | 
|  957:  | 			} | 
|  958:  | 			$hasYield = false; | 
|  959:  | 			$throwPoints = []; | 
|  960:  | 			$impurePoints = []; | 
|  961:  | 			if (isset($stmt->namespacedName)) { | 
|  962:  | 				$classReflection = $this->getCurrentClassReflection($stmt, $stmt->namespacedName->toString(), $scope); | 
|  963:  | 				$classScope = $scope->enterClass($classReflection); | 
|  964:  | 				$nodeCallback(new InClassNode($stmt, $classReflection), $classScope); | 
|  965:  | 			} elseif ($stmt instanceof Class_) { | 
|  966:  | 				if ($stmt->name === null) { | 
|  967:  | 					throw new ShouldNotHappenException(); | 
|  968:  | 				} | 
|  969:  | 				if (!$stmt->isAnonymous()) { | 
|  970:  | 					$classReflection = $this->reflectionProvider->getClass($stmt->name->toString()); | 
|  971:  | 				} else { | 
|  972:  | 					$classReflection = $this->reflectionProvider->getAnonymousClassReflection($stmt, $scope); | 
|  973:  | 				} | 
|  974:  | 				$classScope = $scope->enterClass($classReflection); | 
|  975:  | 				$nodeCallback(new InClassNode($stmt, $classReflection), $classScope); | 
|  976:  | 			} else { | 
|  977:  | 				throw new ShouldNotHappenException(); | 
|  978:  | 			} | 
|  979:  |  | 
|  980:  | 			$classStatementsGatherer = new ClassStatementsGatherer($classReflection, $nodeCallback); | 
|  981:  | 			$this->processAttributeGroups($stmt, $stmt->attrGroups, $classScope, $classStatementsGatherer); | 
|  982:  |  | 
|  983:  | 			$classLikeStatements = $stmt->stmts; | 
|  984:  | 			if ($this->narrowMethodScopeFromConstructor) { | 
|  985:  | 				 | 
|  986:  | 				usort($classLikeStatements, static function ($a, $b) { | 
|  987:  | 					if ($a instanceof Node\Stmt\Property) { | 
|  988:  | 						return 1; | 
|  989:  | 					} | 
|  990:  | 					if ($b instanceof Node\Stmt\Property) { | 
|  991:  | 						return -1; | 
|  992:  | 					} | 
|  993:  |  | 
|  994:  | 					if (!$a instanceof Node\Stmt\ClassMethod || !$b instanceof Node\Stmt\ClassMethod) { | 
|  995:  | 						return 0; | 
|  996:  | 					} | 
|  997:  |  | 
|  998:  | 					return [!$a->isStatic(), $a->name->toLowerString() !== '__construct'] <=> [!$b->isStatic(), $b->name->toLowerString() !== '__construct']; | 
|  999:  | 				}); | 
| 1000:  | 			} | 
| 1001:  |  | 
| 1002:  | 			$this->processStmtNodes($stmt, $classLikeStatements, $classScope, $classStatementsGatherer, $context); | 
| 1003:  | 			$nodeCallback(new ClassPropertiesNode($stmt, $this->readWritePropertiesExtensionProvider, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls(), $classStatementsGatherer->getReturnStatementsNodes(), $classStatementsGatherer->getPropertyAssigns(), $classReflection), $classScope); | 
| 1004:  | 			$nodeCallback(new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls(), $classReflection), $classScope); | 
| 1005:  | 			$nodeCallback(new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches(), $classReflection), $classScope); | 
| 1006:  | 			$classReflection->evictPrivateSymbols(); | 
| 1007:  | 			$this->calledMethodResults = []; | 
| 1008:  | 		} elseif ($stmt instanceof Node\Stmt\Property) { | 
| 1009:  | 			$hasYield = false; | 
| 1010:  | 			$throwPoints = []; | 
| 1011:  | 			$impurePoints = []; | 
| 1012:  | 			$this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); | 
| 1013:  |  | 
| 1014:  | 			$nativePropertyType = $stmt->type !== null ? ParserNodeTypeToPHPStanType::resolve($stmt->type, $scope->getClassReflection()) : null; | 
| 1015:  |  | 
| 1016:  | 			[,,,,,,,,,,,,$isReadOnly, $docComment, ,,,$varTags, $isAllowedPrivateMutation] = $this->getPhpDocs($scope, $stmt); | 
| 1017:  | 			$phpDocType = null; | 
| 1018:  | 			if (isset($varTags[0]) && count($varTags) === 1) { | 
| 1019:  | 				$phpDocType = $varTags[0]->getType(); | 
| 1020:  | 			} | 
| 1021:  |  | 
| 1022:  | 			foreach ($stmt->props as $prop) { | 
| 1023:  | 				$nodeCallback($prop, $scope); | 
| 1024:  | 				if ($prop->default !== null) { | 
| 1025:  | 					$this->processExprNode($stmt, $prop->default, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1026:  | 				} | 
| 1027:  |  | 
| 1028:  | 				if (!$scope->isInClass()) { | 
| 1029:  | 					throw new ShouldNotHappenException(); | 
| 1030:  | 				} | 
| 1031:  | 				$propertyName = $prop->name->toString(); | 
| 1032:  |  | 
| 1033:  | 				if ($phpDocType === null) { | 
| 1034:  | 					if (isset($varTags[$propertyName])) { | 
| 1035:  | 						$phpDocType = $varTags[$propertyName]->getType(); | 
| 1036:  | 					} | 
| 1037:  | 				} | 
| 1038:  |  | 
| 1039:  | 				$propStmt = clone $stmt; | 
| 1040:  | 				$propStmt->setAttributes($prop->getAttributes()); | 
| 1041:  | 				$propStmt->setAttribute('originalPropertyStmt', $stmt); | 
| 1042:  | 				$nodeCallback( | 
| 1043:  | 					new ClassPropertyNode( | 
| 1044:  | 						$propertyName, | 
| 1045:  | 						$stmt->flags, | 
| 1046:  | 						$nativePropertyType, | 
| 1047:  | 						$prop->default, | 
| 1048:  | 						$docComment, | 
| 1049:  | 						$phpDocType, | 
| 1050:  | 						false, | 
| 1051:  | 						false, | 
| 1052:  | 						$propStmt, | 
| 1053:  | 						$isReadOnly, | 
| 1054:  | 						$scope->isInTrait(), | 
| 1055:  | 						$scope->getClassReflection()->isReadOnly(), | 
| 1056:  | 						$isAllowedPrivateMutation, | 
| 1057:  | 						$scope->getClassReflection(), | 
| 1058:  | 					), | 
| 1059:  | 					$scope, | 
| 1060:  | 				); | 
| 1061:  | 			} | 
| 1062:  |  | 
| 1063:  | 			if (count($stmt->hooks) > 0) { | 
| 1064:  | 				if (!isset($propertyName)) { | 
| 1065:  | 					throw new ShouldNotHappenException('Property name should be known when analysing hooks.'); | 
| 1066:  | 				} | 
| 1067:  | 				$this->processPropertyHooks( | 
| 1068:  | 					$stmt, | 
| 1069:  | 					$stmt->type, | 
| 1070:  | 					$phpDocType, | 
| 1071:  | 					$propertyName, | 
| 1072:  | 					$stmt->hooks, | 
| 1073:  | 					$scope, | 
| 1074:  | 					$nodeCallback, | 
| 1075:  | 				); | 
| 1076:  | 			} | 
| 1077:  |  | 
| 1078:  | 			if ($stmt->type !== null) { | 
| 1079:  | 				$nodeCallback($stmt->type, $scope); | 
| 1080:  | 			} | 
| 1081:  | 		} elseif ($stmt instanceof If_) { | 
| 1082:  | 			$conditionType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean(); | 
| 1083:  | 			$ifAlwaysTrue = $conditionType->isTrue()->yes(); | 
| 1084:  | 			$condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1085:  | 			$exitPoints = []; | 
| 1086:  | 			$throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); | 
| 1087:  | 			$impurePoints = $condResult->getImpurePoints(); | 
| 1088:  | 			$endStatements = []; | 
| 1089:  | 			$finalScope = null; | 
| 1090:  | 			$alwaysTerminating = true; | 
| 1091:  | 			$hasYield = $condResult->hasYield(); | 
| 1092:  |  | 
| 1093:  | 			$branchScopeStatementResult = $this->processStmtNodes($stmt, $stmt->stmts, $condResult->getTruthyScope(), $nodeCallback, $context); | 
| 1094:  |  | 
| 1095:  | 			if (!$conditionType->isTrue()->no()) { | 
| 1096:  | 				$exitPoints = $branchScopeStatementResult->getExitPoints(); | 
| 1097:  | 				$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); | 
| 1098:  | 				$impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints()); | 
| 1099:  | 				$branchScope = $branchScopeStatementResult->getScope(); | 
| 1100:  | 				$finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? null : $branchScope; | 
| 1101:  | 				$alwaysTerminating = $branchScopeStatementResult->isAlwaysTerminating(); | 
| 1102:  | 				if (count($branchScopeStatementResult->getEndStatements()) > 0) { | 
| 1103:  | 					$endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements()); | 
| 1104:  | 				} elseif (count($stmt->stmts) > 0) { | 
| 1105:  | 					$endStatements[] = new EndStatementResult($stmt->stmts[count($stmt->stmts) - 1], $branchScopeStatementResult); | 
| 1106:  | 				} else { | 
| 1107:  | 					$endStatements[] = new EndStatementResult($stmt, $branchScopeStatementResult); | 
| 1108:  | 				} | 
| 1109:  | 				$hasYield = $branchScopeStatementResult->hasYield() || $hasYield; | 
| 1110:  | 			} | 
| 1111:  |  | 
| 1112:  | 			$scope = $condResult->getFalseyScope(); | 
| 1113:  | 			$lastElseIfConditionIsTrue = false; | 
| 1114:  |  | 
| 1115:  | 			$condScope = $scope; | 
| 1116:  | 			foreach ($stmt->elseifs as $elseif) { | 
| 1117:  | 				$nodeCallback($elseif, $scope); | 
| 1118:  | 				$elseIfConditionType = ($this->treatPhpDocTypesAsCertain ? $condScope->getType($elseif->cond) : $scope->getNativeType($elseif->cond))->toBoolean(); | 
| 1119:  | 				$condResult = $this->processExprNode($stmt, $elseif->cond, $condScope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1120:  | 				$throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); | 
| 1121:  | 				$impurePoints = array_merge($impurePoints, $condResult->getImpurePoints()); | 
| 1122:  | 				$condScope = $condResult->getScope(); | 
| 1123:  | 				$branchScopeStatementResult = $this->processStmtNodes($elseif, $elseif->stmts, $condResult->getTruthyScope(), $nodeCallback, $context); | 
| 1124:  |  | 
| 1125:  | 				if ( | 
| 1126:  | 					!$ifAlwaysTrue | 
| 1127:  | 					&& !$lastElseIfConditionIsTrue | 
| 1128:  | 					&& !$elseIfConditionType->isTrue()->no() | 
| 1129:  | 				) { | 
| 1130:  | 					$exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); | 
| 1131:  | 					$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); | 
| 1132:  | 					$impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints()); | 
| 1133:  | 					$branchScope = $branchScopeStatementResult->getScope(); | 
| 1134:  | 					$finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); | 
| 1135:  | 					$alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); | 
| 1136:  | 					if (count($branchScopeStatementResult->getEndStatements()) > 0) { | 
| 1137:  | 						$endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements()); | 
| 1138:  | 					} elseif (count($elseif->stmts) > 0) { | 
| 1139:  | 						$endStatements[] = new EndStatementResult($elseif->stmts[count($elseif->stmts) - 1], $branchScopeStatementResult); | 
| 1140:  | 					} else { | 
| 1141:  | 						$endStatements[] = new EndStatementResult($elseif, $branchScopeStatementResult); | 
| 1142:  | 					} | 
| 1143:  | 					$hasYield = $hasYield || $branchScopeStatementResult->hasYield(); | 
| 1144:  | 				} | 
| 1145:  |  | 
| 1146:  | 				if ( | 
| 1147:  | 					$elseIfConditionType->isTrue()->yes() | 
| 1148:  | 				) { | 
| 1149:  | 					$lastElseIfConditionIsTrue = true; | 
| 1150:  | 				} | 
| 1151:  |  | 
| 1152:  | 				$condScope = $condScope->filterByFalseyValue($elseif->cond); | 
| 1153:  | 				$scope = $condScope; | 
| 1154:  | 			} | 
| 1155:  |  | 
| 1156:  | 			if ($stmt->else === null) { | 
| 1157:  | 				if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { | 
| 1158:  | 					$finalScope = $scope->mergeWith($finalScope); | 
| 1159:  | 					$alwaysTerminating = false; | 
| 1160:  | 				} | 
| 1161:  | 			} else { | 
| 1162:  | 				$nodeCallback($stmt->else, $scope); | 
| 1163:  | 				$branchScopeStatementResult = $this->processStmtNodes($stmt->else, $stmt->else->stmts, $scope, $nodeCallback, $context); | 
| 1164:  |  | 
| 1165:  | 				if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { | 
| 1166:  | 					$exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); | 
| 1167:  | 					$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); | 
| 1168:  | 					$impurePoints = array_merge($impurePoints, $branchScopeStatementResult->getImpurePoints()); | 
| 1169:  | 					$branchScope = $branchScopeStatementResult->getScope(); | 
| 1170:  | 					$finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); | 
| 1171:  | 					$alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); | 
| 1172:  | 					if (count($branchScopeStatementResult->getEndStatements()) > 0) { | 
| 1173:  | 						$endStatements = array_merge($endStatements, $branchScopeStatementResult->getEndStatements()); | 
| 1174:  | 					} elseif (count($stmt->else->stmts) > 0) { | 
| 1175:  | 						$endStatements[] = new EndStatementResult($stmt->else->stmts[count($stmt->else->stmts) - 1], $branchScopeStatementResult); | 
| 1176:  | 					} else { | 
| 1177:  | 						$endStatements[] = new EndStatementResult($stmt->else, $branchScopeStatementResult); | 
| 1178:  | 					} | 
| 1179:  | 					$hasYield = $hasYield || $branchScopeStatementResult->hasYield(); | 
| 1180:  | 				} | 
| 1181:  | 			} | 
| 1182:  |  | 
| 1183:  | 			if ($finalScope === null) { | 
| 1184:  | 				$finalScope = $scope; | 
| 1185:  | 			} | 
| 1186:  |  | 
| 1187:  | 			if ($stmt->else === null && !$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { | 
| 1188:  | 				$endStatements[] = new EndStatementResult($stmt, new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, $throwPoints, $impurePoints)); | 
| 1189:  | 			} | 
| 1190:  |  | 
| 1191:  | 			return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, $throwPoints, $impurePoints, $endStatements); | 
| 1192:  | 		} elseif ($stmt instanceof Node\Stmt\TraitUse) { | 
| 1193:  | 			$hasYield = false; | 
| 1194:  | 			$throwPoints = []; | 
| 1195:  | 			$impurePoints = []; | 
| 1196:  | 			$this->processTraitUse($stmt, $scope, $nodeCallback); | 
| 1197:  | 		} elseif ($stmt instanceof Foreach_) { | 
| 1198:  | 			$condResult = $this->processExprNode($stmt, $stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1199:  | 			$throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); | 
| 1200:  | 			$impurePoints = $condResult->getImpurePoints(); | 
| 1201:  | 			$scope = $condResult->getScope(); | 
| 1202:  | 			$arrayComparisonExpr = new BinaryOp\NotIdentical( | 
| 1203:  | 				$stmt->expr, | 
| 1204:  | 				new Array_([]), | 
| 1205:  | 			); | 
| 1206:  | 			if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) { | 
| 1207:  | 				$scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt); | 
| 1208:  | 			} | 
| 1209:  | 			$nodeCallback(new InForeachNode($stmt), $scope); | 
| 1210:  | 			$originalScope = $scope; | 
| 1211:  | 			$bodyScope = $scope; | 
| 1212:  |  | 
| 1213:  | 			if ($stmt->keyVar instanceof Variable) { | 
| 1214:  | 				$nodeCallback(new VariableAssignNode($stmt->keyVar, new GetIterableKeyTypeExpr($stmt->expr)), $originalScope); | 
| 1215:  | 			} | 
| 1216:  |  | 
| 1217:  | 			if ($stmt->valueVar instanceof Variable) { | 
| 1218:  | 				$nodeCallback(new VariableAssignNode($stmt->valueVar, new GetIterableValueTypeExpr($stmt->expr)), $originalScope); | 
| 1219:  | 			} | 
| 1220:  |  | 
| 1221:  | 			if ($context->isTopLevel()) { | 
| 1222:  | 				$originalScope = $this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope; | 
| 1223:  | 				$bodyScope = $this->enterForeach($originalScope, $originalScope, $stmt, $nodeCallback); | 
| 1224:  | 				$count = 0; | 
| 1225:  | 				do { | 
| 1226:  | 					$prevScope = $bodyScope; | 
| 1227:  | 					$bodyScope = $bodyScope->mergeWith($this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope); | 
| 1228:  | 					$bodyScope = $this->enterForeach($bodyScope, $originalScope, $stmt, $nodeCallback); | 
| 1229:  | 					$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { | 
| 1230:  | 					}, $context->enterDeep())->filterOutLoopExitPoints(); | 
| 1231:  | 					$bodyScope = $bodyScopeResult->getScope(); | 
| 1232:  | 					foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1233:  | 						$bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); | 
| 1234:  | 					} | 
| 1235:  | 					if ($bodyScope->equals($prevScope)) { | 
| 1236:  | 						break; | 
| 1237:  | 					} | 
| 1238:  |  | 
| 1239:  | 					if ($count >= self::GENERALIZE_AFTER_ITERATION) { | 
| 1240:  | 						$bodyScope = $prevScope->generalizeWith($bodyScope); | 
| 1241:  | 					} | 
| 1242:  | 					$count++; | 
| 1243:  | 				} while ($count < self::LOOP_SCOPE_ITERATIONS); | 
| 1244:  | 			} | 
| 1245:  |  | 
| 1246:  | 			$bodyScope = $bodyScope->mergeWith($this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope); | 
| 1247:  | 			$bodyScope = $this->enterForeach($bodyScope, $originalScope, $stmt, $nodeCallback); | 
| 1248:  | 			$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); | 
| 1249:  | 			$finalScope = $finalScopeResult->getScope(); | 
| 1250:  | 			foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1251:  | 				$finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); | 
| 1252:  | 			} | 
| 1253:  | 			foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { | 
| 1254:  | 				$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); | 
| 1255:  | 			} | 
| 1256:  |  | 
| 1257:  | 			$exprType = $scope->getType($stmt->expr); | 
| 1258:  | 			$isIterableAtLeastOnce = $exprType->isIterableAtLeastOnce(); | 
| 1259:  | 			if ($exprType->isIterable()->no() || $isIterableAtLeastOnce->maybe()) { | 
| 1260:  | 				$finalScope = $finalScope->mergeWith($scope->filterByTruthyValue(new BooleanOr( | 
| 1261:  | 					new BinaryOp\Identical( | 
| 1262:  | 						$stmt->expr, | 
| 1263:  | 						new Array_([]), | 
| 1264:  | 					), | 
| 1265:  | 					new FuncCall(new Name\FullyQualified('is_object'), [ | 
| 1266:  | 						new Arg($stmt->expr), | 
| 1267:  | 					]), | 
| 1268:  | 				))); | 
| 1269:  | 			} elseif ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) { | 
| 1270:  | 				$finalScope = $scope; | 
| 1271:  | 			} elseif (!$this->polluteScopeWithAlwaysIterableForeach) { | 
| 1272:  | 				$finalScope = $scope->processAlwaysIterableForeachScopeWithoutPollute($finalScope); | 
| 1273:  | 				 | 
| 1274:  | 			} | 
| 1275:  |  | 
| 1276:  | 			if (!$isIterableAtLeastOnce->no()) { | 
| 1277:  | 				$throwPoints = array_merge($throwPoints, $finalScopeResult->getThrowPoints()); | 
| 1278:  | 				$impurePoints = array_merge($impurePoints, $finalScopeResult->getImpurePoints()); | 
| 1279:  | 			} | 
| 1280:  | 			if (!(new ObjectType(Traversable::class))->isSuperTypeOf($scope->getType($stmt->expr))->no()) { | 
| 1281:  | 				$throwPoints[] = ThrowPoint::createImplicit($scope, $stmt->expr); | 
| 1282:  | 			} | 
| 1283:  | 			if ($context->isTopLevel() && $stmt->byRef) { | 
| 1284:  | 				$finalScope = $finalScope->assignExpression(new ForeachValueByRefExpr($stmt->valueVar), new MixedType(), new MixedType()); | 
| 1285:  | 			} | 
| 1286:  |  | 
| 1287:  | 			return new StatementResult( | 
| 1288:  | 				$finalScope, | 
| 1289:  | 				$finalScopeResult->hasYield() || $condResult->hasYield(), | 
| 1290:  | 				$isIterableAtLeastOnce->yes() && $finalScopeResult->isAlwaysTerminating(), | 
| 1291:  | 				$finalScopeResult->getExitPointsForOuterLoop(), | 
| 1292:  | 				$throwPoints, | 
| 1293:  | 				$impurePoints, | 
| 1294:  | 			); | 
| 1295:  | 		} elseif ($stmt instanceof While_) { | 
| 1296:  | 			$condResult = $this->processExprNode($stmt, $stmt->cond, $scope, static function (): void { | 
| 1297:  | 			}, ExpressionContext::createDeep()); | 
| 1298:  | 			$beforeCondBooleanType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean(); | 
| 1299:  | 			$condScope = $condResult->getFalseyScope(); | 
| 1300:  | 			if (!$context->isTopLevel() && $beforeCondBooleanType->isFalse()->yes()) { | 
| 1301:  | 				if (!$this->polluteScopeWithLoopInitialAssignments) { | 
| 1302:  | 					$scope = $condScope->mergeWith($scope); | 
| 1303:  | 				} | 
| 1304:  |  | 
| 1305:  | 				return new StatementResult( | 
| 1306:  | 					$scope, | 
| 1307:  | 					$condResult->hasYield(), | 
| 1308:  | 					false, | 
| 1309:  | 					[], | 
| 1310:  | 					$condResult->getThrowPoints(), | 
| 1311:  | 					$condResult->getImpurePoints(), | 
| 1312:  | 				); | 
| 1313:  | 			} | 
| 1314:  | 			$bodyScope = $condResult->getTruthyScope(); | 
| 1315:  |  | 
| 1316:  | 			if ($context->isTopLevel()) { | 
| 1317:  | 				$count = 0; | 
| 1318:  | 				do { | 
| 1319:  | 					$prevScope = $bodyScope; | 
| 1320:  | 					$bodyScope = $bodyScope->mergeWith($scope); | 
| 1321:  | 					$bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, static function (): void { | 
| 1322:  | 					}, ExpressionContext::createDeep())->getTruthyScope(); | 
| 1323:  | 					$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { | 
| 1324:  | 					}, $context->enterDeep())->filterOutLoopExitPoints(); | 
| 1325:  | 					$bodyScope = $bodyScopeResult->getScope(); | 
| 1326:  | 					foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1327:  | 						$bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); | 
| 1328:  | 					} | 
| 1329:  | 					if ($bodyScope->equals($prevScope)) { | 
| 1330:  | 						break; | 
| 1331:  | 					} | 
| 1332:  |  | 
| 1333:  | 					if ($count >= self::GENERALIZE_AFTER_ITERATION) { | 
| 1334:  | 						$bodyScope = $prevScope->generalizeWith($bodyScope); | 
| 1335:  | 					} | 
| 1336:  | 					$count++; | 
| 1337:  | 				} while ($count < self::LOOP_SCOPE_ITERATIONS); | 
| 1338:  | 			} | 
| 1339:  |  | 
| 1340:  | 			$bodyScope = $bodyScope->mergeWith($scope); | 
| 1341:  | 			$bodyScopeMaybeRan = $bodyScope; | 
| 1342:  | 			$bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); | 
| 1343:  | 			$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); | 
| 1344:  | 			$finalScope = $finalScopeResult->getScope()->filterByFalseyValue($stmt->cond); | 
| 1345:  |  | 
| 1346:  | 			$condBooleanType = ($this->treatPhpDocTypesAsCertain ? $bodyScopeMaybeRan->getType($stmt->cond) : $bodyScopeMaybeRan->getNativeType($stmt->cond))->toBoolean(); | 
| 1347:  | 			$alwaysIterates = $condBooleanType->isTrue()->yes() && $context->isTopLevel(); | 
| 1348:  | 			$neverIterates = $condBooleanType->isFalse()->yes() && $context->isTopLevel(); | 
| 1349:  | 			if (!$alwaysIterates) { | 
| 1350:  | 				foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1351:  | 					$finalScope = $finalScope->mergeWith($continueExitPoint->getScope()); | 
| 1352:  | 				} | 
| 1353:  | 			} | 
| 1354:  |  | 
| 1355:  | 			$breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class); | 
| 1356:  | 			foreach ($breakExitPoints as $breakExitPoint) { | 
| 1357:  | 				$finalScope = $finalScope->mergeWith($breakExitPoint->getScope()); | 
| 1358:  | 			} | 
| 1359:  |  | 
| 1360:  | 			$isIterableAtLeastOnce = $beforeCondBooleanType->isTrue()->yes(); | 
| 1361:  | 			$nodeCallback(new BreaklessWhileLoopNode($stmt, $finalScopeResult->getExitPoints()), $bodyScopeMaybeRan); | 
| 1362:  |  | 
| 1363:  | 			if ($alwaysIterates) { | 
| 1364:  | 				$isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0; | 
| 1365:  | 			} elseif ($isIterableAtLeastOnce) { | 
| 1366:  | 				$isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating(); | 
| 1367:  | 			} else { | 
| 1368:  | 				$isAlwaysTerminating = false; | 
| 1369:  | 			} | 
| 1370:  | 			if (!$isIterableAtLeastOnce) { | 
| 1371:  | 				if (!$this->polluteScopeWithLoopInitialAssignments) { | 
| 1372:  | 					$condScope = $condScope->mergeWith($scope); | 
| 1373:  | 				} | 
| 1374:  | 				$finalScope = $finalScope->mergeWith($condScope); | 
| 1375:  | 			} | 
| 1376:  |  | 
| 1377:  | 			$throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); | 
| 1378:  | 			$impurePoints = $condResult->getImpurePoints(); | 
| 1379:  | 			if (!$neverIterates) { | 
| 1380:  | 				$throwPoints = array_merge($throwPoints, $finalScopeResult->getThrowPoints()); | 
| 1381:  | 				$impurePoints = array_merge($impurePoints, $finalScopeResult->getImpurePoints()); | 
| 1382:  | 			} | 
| 1383:  |  | 
| 1384:  | 			return new StatementResult( | 
| 1385:  | 				$finalScope, | 
| 1386:  | 				$finalScopeResult->hasYield() || $condResult->hasYield(), | 
| 1387:  | 				$isAlwaysTerminating, | 
| 1388:  | 				$finalScopeResult->getExitPointsForOuterLoop(), | 
| 1389:  | 				$throwPoints, | 
| 1390:  | 				$impurePoints, | 
| 1391:  | 			); | 
| 1392:  | 		} elseif ($stmt instanceof Do_) { | 
| 1393:  | 			$finalScope = null; | 
| 1394:  | 			$bodyScope = $scope; | 
| 1395:  | 			$count = 0; | 
| 1396:  | 			$hasYield = false; | 
| 1397:  | 			$throwPoints = []; | 
| 1398:  | 			$impurePoints = []; | 
| 1399:  |  | 
| 1400:  | 			if ($context->isTopLevel()) { | 
| 1401:  | 				do { | 
| 1402:  | 					$prevScope = $bodyScope; | 
| 1403:  | 					$bodyScope = $bodyScope->mergeWith($scope); | 
| 1404:  | 					$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { | 
| 1405:  | 					}, $context->enterDeep())->filterOutLoopExitPoints(); | 
| 1406:  | 					$alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); | 
| 1407:  | 					$bodyScope = $bodyScopeResult->getScope(); | 
| 1408:  | 					foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1409:  | 						$bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); | 
| 1410:  | 					} | 
| 1411:  | 					$finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); | 
| 1412:  | 					foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { | 
| 1413:  | 						$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); | 
| 1414:  | 					} | 
| 1415:  | 					$bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, static function (): void { | 
| 1416:  | 					}, ExpressionContext::createDeep())->getTruthyScope(); | 
| 1417:  | 					if ($bodyScope->equals($prevScope)) { | 
| 1418:  | 						break; | 
| 1419:  | 					} | 
| 1420:  |  | 
| 1421:  | 					if ($count >= self::GENERALIZE_AFTER_ITERATION) { | 
| 1422:  | 						$bodyScope = $prevScope->generalizeWith($bodyScope); | 
| 1423:  | 					} | 
| 1424:  | 					$count++; | 
| 1425:  | 				} while ($count < self::LOOP_SCOPE_ITERATIONS); | 
| 1426:  |  | 
| 1427:  | 				$bodyScope = $bodyScope->mergeWith($scope); | 
| 1428:  | 			} | 
| 1429:  |  | 
| 1430:  | 			$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); | 
| 1431:  | 			$bodyScope = $bodyScopeResult->getScope(); | 
| 1432:  | 			foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1433:  | 				$bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); | 
| 1434:  | 			} | 
| 1435:  | 			$condBooleanType = ($this->treatPhpDocTypesAsCertain ? $bodyScope->getType($stmt->cond) : $bodyScope->getNativeType($stmt->cond))->toBoolean(); | 
| 1436:  | 			$alwaysIterates = $condBooleanType->isTrue()->yes() && $context->isTopLevel(); | 
| 1437:  |  | 
| 1438:  | 			$nodeCallback(new DoWhileLoopConditionNode($stmt->cond, $bodyScopeResult->getExitPoints()), $bodyScope); | 
| 1439:  |  | 
| 1440:  | 			if ($alwaysIterates) { | 
| 1441:  | 				$alwaysTerminating = count($bodyScopeResult->getExitPointsByType(Break_::class)) === 0; | 
| 1442:  | 			} else { | 
| 1443:  | 				$alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); | 
| 1444:  | 			} | 
| 1445:  | 			$finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); | 
| 1446:  | 			if ($finalScope === null) { | 
| 1447:  | 				$finalScope = $scope; | 
| 1448:  | 			} | 
| 1449:  | 			if (!$alwaysTerminating) { | 
| 1450:  | 				$condResult = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1451:  | 				$hasYield = $condResult->hasYield(); | 
| 1452:  | 				$throwPoints = $condResult->getThrowPoints(); | 
| 1453:  | 				$impurePoints = $condResult->getImpurePoints(); | 
| 1454:  | 				$finalScope = $condResult->getFalseyScope(); | 
| 1455:  | 			} else { | 
| 1456:  | 				$this->processExprNode($stmt, $stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1457:  | 			} | 
| 1458:  | 			foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { | 
| 1459:  | 				$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); | 
| 1460:  | 			} | 
| 1461:  |  | 
| 1462:  | 			return new StatementResult( | 
| 1463:  | 				$finalScope, | 
| 1464:  | 				$bodyScopeResult->hasYield() || $hasYield, | 
| 1465:  | 				$alwaysTerminating, | 
| 1466:  | 				$bodyScopeResult->getExitPointsForOuterLoop(), | 
| 1467:  | 				array_merge($throwPoints, $bodyScopeResult->getThrowPoints()), | 
| 1468:  | 				array_merge($impurePoints, $bodyScopeResult->getImpurePoints()), | 
| 1469:  | 			); | 
| 1470:  | 		} elseif ($stmt instanceof For_) { | 
| 1471:  | 			$initScope = $scope; | 
| 1472:  | 			$hasYield = false; | 
| 1473:  | 			$throwPoints = []; | 
| 1474:  | 			$impurePoints = []; | 
| 1475:  | 			foreach ($stmt->init as $initExpr) { | 
| 1476:  | 				$initResult = $this->processExprNode($stmt, $initExpr, $initScope, $nodeCallback, ExpressionContext::createTopLevel()); | 
| 1477:  | 				$initScope = $initResult->getScope(); | 
| 1478:  | 				$hasYield = $hasYield || $initResult->hasYield(); | 
| 1479:  | 				$throwPoints = array_merge($throwPoints, $initResult->getThrowPoints()); | 
| 1480:  | 				$impurePoints = array_merge($impurePoints, $initResult->getImpurePoints()); | 
| 1481:  | 			} | 
| 1482:  |  | 
| 1483:  | 			$bodyScope = $initScope; | 
| 1484:  | 			$isIterableAtLeastOnce = TrinaryLogic::createYes(); | 
| 1485:  | 			$lastCondExpr = array_last($stmt->cond) ?? null; | 
| 1486:  | 			foreach ($stmt->cond as $condExpr) { | 
| 1487:  | 				$condResult = $this->processExprNode($stmt, $condExpr, $bodyScope, static function (): void { | 
| 1488:  | 				}, ExpressionContext::createDeep()); | 
| 1489:  | 				$initScope = $condResult->getScope(); | 
| 1490:  | 				$condResultScope = $condResult->getScope(); | 
| 1491:  |  | 
| 1492:  | 				 | 
| 1493:  | 				 | 
| 1494:  | 				if ($condExpr === $lastCondExpr) { | 
| 1495:  | 					$condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResultScope->getType($condExpr) : $condResultScope->getNativeType($condExpr))->toBoolean(); | 
| 1496:  | 					$isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthiness->isTrue()); | 
| 1497:  | 				} | 
| 1498:  |  | 
| 1499:  | 				$hasYield = $hasYield || $condResult->hasYield(); | 
| 1500:  | 				$throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); | 
| 1501:  | 				$impurePoints = array_merge($impurePoints, $condResult->getImpurePoints()); | 
| 1502:  | 				$bodyScope = $condResult->getTruthyScope(); | 
| 1503:  | 			} | 
| 1504:  |  | 
| 1505:  | 			if ($context->isTopLevel()) { | 
| 1506:  | 				$count = 0; | 
| 1507:  | 				do { | 
| 1508:  | 					$prevScope = $bodyScope; | 
| 1509:  | 					$bodyScope = $bodyScope->mergeWith($initScope); | 
| 1510:  | 					if ($lastCondExpr !== null) { | 
| 1511:  | 						$bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, static function (): void { | 
| 1512:  | 						}, ExpressionContext::createDeep())->getTruthyScope(); | 
| 1513:  | 					} | 
| 1514:  | 					$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { | 
| 1515:  | 					}, $context->enterDeep())->filterOutLoopExitPoints(); | 
| 1516:  | 					$bodyScope = $bodyScopeResult->getScope(); | 
| 1517:  | 					foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1518:  | 						$bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); | 
| 1519:  | 					} | 
| 1520:  |  | 
| 1521:  | 					foreach ($stmt->loop as $loopExpr) { | 
| 1522:  | 						$exprResult = $this->processExprNode($stmt, $loopExpr, $bodyScope, static function (): void { | 
| 1523:  | 						}, ExpressionContext::createTopLevel()); | 
| 1524:  | 						$bodyScope = $exprResult->getScope(); | 
| 1525:  | 						$hasYield = $hasYield || $exprResult->hasYield(); | 
| 1526:  | 						$throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); | 
| 1527:  | 						$impurePoints = array_merge($impurePoints, $exprResult->getImpurePoints()); | 
| 1528:  | 					} | 
| 1529:  |  | 
| 1530:  | 					if ($bodyScope->equals($prevScope)) { | 
| 1531:  | 						break; | 
| 1532:  | 					} | 
| 1533:  |  | 
| 1534:  | 					if ($count >= self::GENERALIZE_AFTER_ITERATION) { | 
| 1535:  | 						$bodyScope = $prevScope->generalizeWith($bodyScope); | 
| 1536:  | 					} | 
| 1537:  | 					$count++; | 
| 1538:  | 				} while ($count < self::LOOP_SCOPE_ITERATIONS); | 
| 1539:  | 			} | 
| 1540:  |  | 
| 1541:  | 			$bodyScope = $bodyScope->mergeWith($initScope); | 
| 1542:  |  | 
| 1543:  | 			$alwaysIterates = TrinaryLogic::createFromBoolean($context->isTopLevel()); | 
| 1544:  | 			if ($lastCondExpr !== null) { | 
| 1545:  | 				$alwaysIterates = $alwaysIterates->and($bodyScope->getType($lastCondExpr)->toBoolean()->isTrue()); | 
| 1546:  | 				$bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); | 
| 1547:  | 				$bodyScope = $this->inferForLoopExpressions($stmt, $lastCondExpr, $bodyScope); | 
| 1548:  | 			} | 
| 1549:  |  | 
| 1550:  | 			$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); | 
| 1551:  | 			$finalScope = $finalScopeResult->getScope(); | 
| 1552:  | 			foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1553:  | 				$finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); | 
| 1554:  | 			} | 
| 1555:  |  | 
| 1556:  | 			$loopScope = $finalScope; | 
| 1557:  | 			foreach ($stmt->loop as $loopExpr) { | 
| 1558:  | 				$loopScope = $this->processExprNode($stmt, $loopExpr, $loopScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); | 
| 1559:  | 			} | 
| 1560:  | 			$finalScope = $finalScope->generalizeWith($loopScope); | 
| 1561:  |  | 
| 1562:  | 			if ($lastCondExpr !== null) { | 
| 1563:  | 				$finalScope = $finalScope->filterByFalseyValue($lastCondExpr); | 
| 1564:  | 			} | 
| 1565:  |  | 
| 1566:  | 			foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { | 
| 1567:  | 				$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); | 
| 1568:  | 			} | 
| 1569:  |  | 
| 1570:  | 			if ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) { | 
| 1571:  | 				if ($this->polluteScopeWithLoopInitialAssignments) { | 
| 1572:  | 					$finalScope = $initScope; | 
| 1573:  | 				} else { | 
| 1574:  | 					$finalScope = $scope; | 
| 1575:  | 				} | 
| 1576:  |  | 
| 1577:  | 			} elseif ($isIterableAtLeastOnce->maybe()) { | 
| 1578:  | 				if ($this->polluteScopeWithLoopInitialAssignments) { | 
| 1579:  | 					$finalScope = $finalScope->mergeWith($initScope); | 
| 1580:  | 				} else { | 
| 1581:  | 					$finalScope = $finalScope->mergeWith($scope); | 
| 1582:  | 				} | 
| 1583:  | 			} else { | 
| 1584:  | 				if (!$this->polluteScopeWithLoopInitialAssignments) { | 
| 1585:  | 					$finalScope = $finalScope->mergeWith($scope); | 
| 1586:  | 				} | 
| 1587:  | 			} | 
| 1588:  |  | 
| 1589:  | 			if ($alwaysIterates->yes()) { | 
| 1590:  | 				$isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0; | 
| 1591:  | 			} elseif ($isIterableAtLeastOnce->yes()) { | 
| 1592:  | 				$isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating(); | 
| 1593:  | 			} else { | 
| 1594:  | 				$isAlwaysTerminating = false; | 
| 1595:  | 			} | 
| 1596:  |  | 
| 1597:  | 			return new StatementResult( | 
| 1598:  | 				$finalScope, | 
| 1599:  | 				$finalScopeResult->hasYield() || $hasYield, | 
| 1600:  | 				$isAlwaysTerminating, | 
| 1601:  | 				$finalScopeResult->getExitPointsForOuterLoop(), | 
| 1602:  | 				array_merge($throwPoints, $finalScopeResult->getThrowPoints()), | 
| 1603:  | 				array_merge($impurePoints, $finalScopeResult->getImpurePoints()), | 
| 1604:  | 			); | 
| 1605:  | 		} elseif ($stmt instanceof Switch_) { | 
| 1606:  | 			$condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1607:  | 			$scope = $condResult->getScope(); | 
| 1608:  | 			$scopeForBranches = $scope; | 
| 1609:  | 			$finalScope = null; | 
| 1610:  | 			$prevScope = null; | 
| 1611:  | 			$hasDefaultCase = false; | 
| 1612:  | 			$alwaysTerminating = true; | 
| 1613:  | 			$hasYield = $condResult->hasYield(); | 
| 1614:  | 			$exitPointsForOuterLoop = []; | 
| 1615:  | 			$throwPoints = $condResult->getThrowPoints(); | 
| 1616:  | 			$impurePoints = $condResult->getImpurePoints(); | 
| 1617:  | 			$fullCondExpr = null; | 
| 1618:  | 			foreach ($stmt->cases as $caseNode) { | 
| 1619:  | 				if ($caseNode->cond !== null) { | 
| 1620:  | 					$condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond); | 
| 1621:  | 					$fullCondExpr = $fullCondExpr === null ? $condExpr : new BooleanOr($fullCondExpr, $condExpr); | 
| 1622:  | 					$caseResult = $this->processExprNode($stmt, $caseNode->cond, $scopeForBranches, $nodeCallback, ExpressionContext::createDeep()); | 
| 1623:  | 					$scopeForBranches = $caseResult->getScope(); | 
| 1624:  | 					$hasYield = $hasYield || $caseResult->hasYield(); | 
| 1625:  | 					$throwPoints = array_merge($throwPoints, $caseResult->getThrowPoints()); | 
| 1626:  | 					$impurePoints = array_merge($impurePoints, $caseResult->getImpurePoints()); | 
| 1627:  | 					$branchScope = $caseResult->getTruthyScope()->filterByTruthyValue($condExpr); | 
| 1628:  | 				} else { | 
| 1629:  | 					$hasDefaultCase = true; | 
| 1630:  | 					$fullCondExpr = null; | 
| 1631:  | 					$branchScope = $scopeForBranches; | 
| 1632:  | 				} | 
| 1633:  |  | 
| 1634:  | 				$branchScope = $branchScope->mergeWith($prevScope); | 
| 1635:  | 				$branchScopeResult = $this->processStmtNodes($caseNode, $caseNode->stmts, $branchScope, $nodeCallback, $context); | 
| 1636:  | 				$branchScope = $branchScopeResult->getScope(); | 
| 1637:  | 				$branchFinalScopeResult = $branchScopeResult->filterOutLoopExitPoints(); | 
| 1638:  | 				$hasYield = $hasYield || $branchFinalScopeResult->hasYield(); | 
| 1639:  | 				foreach ($branchScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { | 
| 1640:  | 					$alwaysTerminating = false; | 
| 1641:  | 					$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); | 
| 1642:  | 				} | 
| 1643:  | 				foreach ($branchScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1644:  | 					$finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); | 
| 1645:  | 				} | 
| 1646:  | 				$exitPointsForOuterLoop = array_merge($exitPointsForOuterLoop, $branchFinalScopeResult->getExitPointsForOuterLoop()); | 
| 1647:  | 				$throwPoints = array_merge($throwPoints, $branchFinalScopeResult->getThrowPoints()); | 
| 1648:  | 				$impurePoints = array_merge($impurePoints, $branchFinalScopeResult->getImpurePoints()); | 
| 1649:  | 				if ($branchScopeResult->isAlwaysTerminating()) { | 
| 1650:  | 					$alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating(); | 
| 1651:  | 					$prevScope = null; | 
| 1652:  | 					if (isset($fullCondExpr)) { | 
| 1653:  | 						$scopeForBranches = $scopeForBranches->filterByFalseyValue($fullCondExpr); | 
| 1654:  | 						$fullCondExpr = null; | 
| 1655:  | 					} | 
| 1656:  | 					if (!$branchFinalScopeResult->isAlwaysTerminating()) { | 
| 1657:  | 						$finalScope = $branchScope->mergeWith($finalScope); | 
| 1658:  | 					} | 
| 1659:  | 				} else { | 
| 1660:  | 					$prevScope = $branchScope; | 
| 1661:  | 				} | 
| 1662:  | 			} | 
| 1663:  |  | 
| 1664:  | 			$exhaustive = $scopeForBranches->getType($stmt->cond) instanceof NeverType; | 
| 1665:  |  | 
| 1666:  | 			if (!$hasDefaultCase && !$exhaustive) { | 
| 1667:  | 				$alwaysTerminating = false; | 
| 1668:  | 			} | 
| 1669:  |  | 
| 1670:  | 			if ($prevScope !== null && isset($branchFinalScopeResult)) { | 
| 1671:  | 				$finalScope = $prevScope->mergeWith($finalScope); | 
| 1672:  | 				$alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating(); | 
| 1673:  | 			} | 
| 1674:  |  | 
| 1675:  | 			if ((!$hasDefaultCase && !$exhaustive) || $finalScope === null) { | 
| 1676:  | 				$finalScope = $scope->mergeWith($finalScope); | 
| 1677:  | 			} | 
| 1678:  |  | 
| 1679:  | 			return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPointsForOuterLoop, $throwPoints, $impurePoints); | 
| 1680:  | 		} elseif ($stmt instanceof TryCatch) { | 
| 1681:  | 			$branchScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback, $context); | 
| 1682:  | 			$branchScope = $branchScopeResult->getScope(); | 
| 1683:  | 			$finalScope = $branchScopeResult->isAlwaysTerminating() ? null : $branchScope; | 
| 1684:  |  | 
| 1685:  | 			$exitPoints = []; | 
| 1686:  | 			$finallyExitPoints = []; | 
| 1687:  | 			$alwaysTerminating = $branchScopeResult->isAlwaysTerminating(); | 
| 1688:  | 			$hasYield = $branchScopeResult->hasYield(); | 
| 1689:  |  | 
| 1690:  | 			if ($stmt->finally !== null) { | 
| 1691:  | 				$finallyScope = $branchScope; | 
| 1692:  | 			} else { | 
| 1693:  | 				$finallyScope = null; | 
| 1694:  | 			} | 
| 1695:  | 			foreach ($branchScopeResult->getExitPoints() as $exitPoint) { | 
| 1696:  | 				$finallyExitPoints[] = $exitPoint; | 
| 1697:  | 				if ($exitPoint->getStatement() instanceof Node\Stmt\Expression && $exitPoint->getStatement()->expr instanceof Expr\Throw_) { | 
| 1698:  | 					continue; | 
| 1699:  | 				} | 
| 1700:  | 				if ($finallyScope !== null) { | 
| 1701:  | 					$finallyScope = $finallyScope->mergeWith($exitPoint->getScope()); | 
| 1702:  | 				} | 
| 1703:  | 				$exitPoints[] = $exitPoint; | 
| 1704:  | 			} | 
| 1705:  |  | 
| 1706:  | 			$throwPoints = $branchScopeResult->getThrowPoints(); | 
| 1707:  | 			$impurePoints = $branchScopeResult->getImpurePoints(); | 
| 1708:  | 			$throwPointsForLater = []; | 
| 1709:  | 			$pastCatchTypes = new NeverType(); | 
| 1710:  |  | 
| 1711:  | 			foreach ($stmt->catches as $catchNode) { | 
| 1712:  | 				$nodeCallback($catchNode, $scope); | 
| 1713:  |  | 
| 1714:  | 				$originalCatchTypes = array_map(static fn (Name $name): Type => new ObjectType($name->toString()), $catchNode->types); | 
| 1715:  | 				$catchTypes = array_map(static fn (Type $type): Type => TypeCombinator::remove($type, $pastCatchTypes), $originalCatchTypes); | 
| 1716:  |  | 
| 1717:  | 				$originalCatchType = TypeCombinator::union(...$originalCatchTypes); | 
| 1718:  | 				$catchType = TypeCombinator::union(...$catchTypes); | 
| 1719:  | 				$pastCatchTypes = TypeCombinator::union($pastCatchTypes, $originalCatchType); | 
| 1720:  |  | 
| 1721:  | 				$matchingThrowPoints = []; | 
| 1722:  | 				$matchingCatchTypes = array_fill_keys(array_keys($originalCatchTypes), false); | 
| 1723:  |  | 
| 1724:  | 				 | 
| 1725:  | 				foreach ($originalCatchTypes as $catchTypeIndex => $catchTypeItem) { | 
| 1726:  | 					if (!$catchTypeItem->isSuperTypeOf(new ObjectType(Throwable::class))->yes()) { | 
| 1727:  | 						continue; | 
| 1728:  | 					} | 
| 1729:  |  | 
| 1730:  | 					foreach ($throwPoints as $throwPointIndex => $throwPoint) { | 
| 1731:  | 						$matchingThrowPoints[$throwPointIndex] = $throwPoint; | 
| 1732:  | 						$matchingCatchTypes[$catchTypeIndex] = true; | 
| 1733:  | 					} | 
| 1734:  | 				} | 
| 1735:  |  | 
| 1736:  | 				 | 
| 1737:  | 				$onlyExplicitIsThrow = true; | 
| 1738:  | 				if (count($matchingThrowPoints) === 0) { | 
| 1739:  | 					foreach ($throwPoints as $throwPointIndex => $throwPoint) { | 
| 1740:  | 						foreach ($catchTypes as $catchTypeIndex => $catchTypeItem) { | 
| 1741:  | 							if ($catchTypeItem->isSuperTypeOf($throwPoint->getType())->no()) { | 
| 1742:  | 								continue; | 
| 1743:  | 							} | 
| 1744:  |  | 
| 1745:  | 							$matchingCatchTypes[$catchTypeIndex] = true; | 
| 1746:  | 							if (!$throwPoint->isExplicit()) { | 
| 1747:  | 								continue; | 
| 1748:  | 							} | 
| 1749:  | 							$throwNode = $throwPoint->getNode(); | 
| 1750:  | 							if ( | 
| 1751:  | 								!$throwNode instanceof Expr\Throw_ | 
| 1752:  | 								&& !($throwNode instanceof Node\Stmt\Expression && $throwNode->expr instanceof Expr\Throw_) | 
| 1753:  | 							) { | 
| 1754:  | 								$onlyExplicitIsThrow = false; | 
| 1755:  | 							} | 
| 1756:  | 							$matchingThrowPoints[$throwPointIndex] = $throwPoint; | 
| 1757:  | 						} | 
| 1758:  | 					} | 
| 1759:  | 				} | 
| 1760:  |  | 
| 1761:  | 				 | 
| 1762:  | 				if (count($matchingThrowPoints) === 0 || $onlyExplicitIsThrow) { | 
| 1763:  | 					foreach ($throwPoints as $throwPointIndex => $throwPoint) { | 
| 1764:  | 						if ($throwPoint->isExplicit()) { | 
| 1765:  | 							continue; | 
| 1766:  | 						} | 
| 1767:  |  | 
| 1768:  | 						foreach ($catchTypes as $catchTypeIndex => $catchTypeItem) { | 
| 1769:  | 							if ($catchTypeItem->isSuperTypeOf($throwPoint->getType())->no()) { | 
| 1770:  | 								continue; | 
| 1771:  | 							} | 
| 1772:  |  | 
| 1773:  | 							$matchingThrowPoints[$throwPointIndex] = $throwPoint; | 
| 1774:  | 						} | 
| 1775:  | 					} | 
| 1776:  | 				} | 
| 1777:  |  | 
| 1778:  | 				 | 
| 1779:  | 				if (count($matchingThrowPoints) === 0) { | 
| 1780:  | 					if ($originalCatchType->isSuperTypeOf(new ObjectType(Throwable::class))->yes()) { | 
| 1781:  | 						foreach ($branchScopeResult->getThrowPoints() as $originalThrowPoint) { | 
| 1782:  | 							if (!$originalThrowPoint->canContainAnyThrowable()) { | 
| 1783:  | 								continue; | 
| 1784:  | 							} | 
| 1785:  |  | 
| 1786:  | 							$matchingThrowPoints[] = $originalThrowPoint; | 
| 1787:  | 							$matchingCatchTypes = array_fill_keys(array_keys($originalCatchTypes), true); | 
| 1788:  | 						} | 
| 1789:  | 					} | 
| 1790:  | 				} | 
| 1791:  |  | 
| 1792:  | 				 | 
| 1793:  | 				foreach ($matchingCatchTypes as $catchTypeIndex => $matched) { | 
| 1794:  | 					if ($matched) { | 
| 1795:  | 						continue; | 
| 1796:  | 					} | 
| 1797:  | 					$nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchTypes[$catchTypeIndex], $originalCatchTypes[$catchTypeIndex]), $scope); | 
| 1798:  | 				} | 
| 1799:  |  | 
| 1800:  | 				if (count($matchingThrowPoints) === 0) { | 
| 1801:  | 					continue; | 
| 1802:  | 				} | 
| 1803:  |  | 
| 1804:  | 				 | 
| 1805:  | 				$newThrowPoints = []; | 
| 1806:  | 				foreach ($throwPoints as $throwPoint) { | 
| 1807:  | 					$newThrowPoint = $throwPoint->subtractCatchType($originalCatchType); | 
| 1808:  |  | 
| 1809:  | 					if ($newThrowPoint->getType() instanceof NeverType) { | 
| 1810:  | 						continue; | 
| 1811:  | 					} | 
| 1812:  |  | 
| 1813:  | 					$newThrowPoints[] = $newThrowPoint; | 
| 1814:  | 				} | 
| 1815:  | 				$throwPoints = $newThrowPoints; | 
| 1816:  |  | 
| 1817:  | 				$catchScope = null; | 
| 1818:  | 				foreach ($matchingThrowPoints as $matchingThrowPoint) { | 
| 1819:  | 					if ($catchScope === null) { | 
| 1820:  | 						$catchScope = $matchingThrowPoint->getScope(); | 
| 1821:  | 					} else { | 
| 1822:  | 						$catchScope = $catchScope->mergeWith($matchingThrowPoint->getScope()); | 
| 1823:  | 					} | 
| 1824:  | 				} | 
| 1825:  |  | 
| 1826:  | 				$variableName = null; | 
| 1827:  | 				if ($catchNode->var !== null) { | 
| 1828:  | 					if (!is_string($catchNode->var->name)) { | 
| 1829:  | 						throw new ShouldNotHappenException(); | 
| 1830:  | 					} | 
| 1831:  |  | 
| 1832:  | 					$variableName = $catchNode->var->name; | 
| 1833:  | 				} | 
| 1834:  |  | 
| 1835:  | 				$catchScopeResult = $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $nodeCallback, $context); | 
| 1836:  | 				$catchScopeForFinally = $catchScopeResult->getScope(); | 
| 1837:  |  | 
| 1838:  | 				$finalScope = $catchScopeResult->isAlwaysTerminating() ? $finalScope : $catchScopeResult->getScope()->mergeWith($finalScope); | 
| 1839:  | 				$alwaysTerminating = $alwaysTerminating && $catchScopeResult->isAlwaysTerminating(); | 
| 1840:  | 				$hasYield = $hasYield || $catchScopeResult->hasYield(); | 
| 1841:  | 				$catchThrowPoints = $catchScopeResult->getThrowPoints(); | 
| 1842:  | 				$impurePoints = array_merge($impurePoints, $catchScopeResult->getImpurePoints()); | 
| 1843:  | 				$throwPointsForLater = array_merge($throwPointsForLater, $catchThrowPoints); | 
| 1844:  |  | 
| 1845:  | 				if ($finallyScope !== null) { | 
| 1846:  | 					$finallyScope = $finallyScope->mergeWith($catchScopeForFinally); | 
| 1847:  | 				} | 
| 1848:  | 				foreach ($catchScopeResult->getExitPoints() as $exitPoint) { | 
| 1849:  | 					$finallyExitPoints[] = $exitPoint; | 
| 1850:  | 					if ($exitPoint->getStatement() instanceof Node\Stmt\Expression && $exitPoint->getStatement()->expr instanceof Expr\Throw_) { | 
| 1851:  | 						continue; | 
| 1852:  | 					} | 
| 1853:  | 					if ($finallyScope !== null) { | 
| 1854:  | 						$finallyScope = $finallyScope->mergeWith($exitPoint->getScope()); | 
| 1855:  | 					} | 
| 1856:  | 					$exitPoints[] = $exitPoint; | 
| 1857:  | 				} | 
| 1858:  |  | 
| 1859:  | 				foreach ($catchThrowPoints as $catchThrowPoint) { | 
| 1860:  | 					if ($finallyScope === null) { | 
| 1861:  | 						continue; | 
| 1862:  | 					} | 
| 1863:  | 					$finallyScope = $finallyScope->mergeWith($catchThrowPoint->getScope()); | 
| 1864:  | 				} | 
| 1865:  | 			} | 
| 1866:  |  | 
| 1867:  | 			if ($finalScope === null) { | 
| 1868:  | 				$finalScope = $scope; | 
| 1869:  | 			} | 
| 1870:  |  | 
| 1871:  | 			foreach ($throwPoints as $throwPoint) { | 
| 1872:  | 				if ($finallyScope === null) { | 
| 1873:  | 					continue; | 
| 1874:  | 				} | 
| 1875:  | 				$finallyScope = $finallyScope->mergeWith($throwPoint->getScope()); | 
| 1876:  | 			} | 
| 1877:  |  | 
| 1878:  | 			if ($finallyScope !== null) { | 
| 1879:  | 				$originalFinallyScope = $finallyScope; | 
| 1880:  | 				$finallyResult = $this->processStmtNodes($stmt->finally, $stmt->finally->stmts, $finallyScope, $nodeCallback, $context); | 
| 1881:  | 				$alwaysTerminating = $alwaysTerminating || $finallyResult->isAlwaysTerminating(); | 
| 1882:  | 				$hasYield = $hasYield || $finallyResult->hasYield(); | 
| 1883:  | 				$throwPointsForLater = array_merge($throwPointsForLater, $finallyResult->getThrowPoints()); | 
| 1884:  | 				$impurePoints = array_merge($impurePoints, $finallyResult->getImpurePoints()); | 
| 1885:  | 				$finallyScope = $finallyResult->getScope(); | 
| 1886:  | 				$finalScope = $finallyResult->isAlwaysTerminating() ? $finalScope : $finalScope->processFinallyScope($finallyScope, $originalFinallyScope); | 
| 1887:  | 				if (count($finallyResult->getExitPoints()) > 0) { | 
| 1888:  | 					$nodeCallback(new FinallyExitPointsNode( | 
| 1889:  | 						$finallyResult->getExitPoints(), | 
| 1890:  | 						$finallyExitPoints, | 
| 1891:  | 					), $scope); | 
| 1892:  | 				} | 
| 1893:  | 				$exitPoints = array_merge($exitPoints, $finallyResult->getExitPoints()); | 
| 1894:  | 			} | 
| 1895:  |  | 
| 1896:  | 			return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, array_merge($throwPoints, $throwPointsForLater), $impurePoints); | 
| 1897:  | 		} elseif ($stmt instanceof Unset_) { | 
| 1898:  | 			$hasYield = false; | 
| 1899:  | 			$throwPoints = []; | 
| 1900:  | 			$impurePoints = []; | 
| 1901:  | 			foreach ($stmt->vars as $var) { | 
| 1902:  | 				$scope = $this->lookForSetAllowedUndefinedExpressions($scope, $var); | 
| 1903:  | 				$exprResult = $this->processExprNode($stmt, $var, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1904:  | 				$scope = $exprResult->getScope(); | 
| 1905:  | 				$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $var); | 
| 1906:  | 				$hasYield = $hasYield || $exprResult->hasYield(); | 
| 1907:  | 				$throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); | 
| 1908:  | 				$impurePoints = array_merge($impurePoints, $exprResult->getImpurePoints()); | 
| 1909:  | 				if ($var instanceof ArrayDimFetch && $var->dim !== null) { | 
| 1910:  | 					$cloningTraverser = new NodeTraverser(); | 
| 1911:  | 					$cloningTraverser->addVisitor(new CloningVisitor()); | 
| 1912:  |  | 
| 1913:  | 					 | 
| 1914:  | 					[$clonedVar] = $cloningTraverser->traverse([$var->var]); | 
| 1915:  |  | 
| 1916:  | 					$traverser = new NodeTraverser(); | 
| 1917:  | 					$traverser->addVisitor(new class () extends NodeVisitorAbstract { | 
| 1918:  |  | 
| 1919:  | 						#[Override] | 
| 1920:  | 						public function leaveNode(Node $node): ?ExistingArrayDimFetch | 
| 1921:  | 						{ | 
| 1922:  | 							if (!$node instanceof ArrayDimFetch || $node->dim === null) { | 
| 1923:  | 								return null; | 
| 1924:  | 							} | 
| 1925:  |  | 
| 1926:  | 							return new ExistingArrayDimFetch($node->var, $node->dim); | 
| 1927:  | 						} | 
| 1928:  |  | 
| 1929:  | 					}); | 
| 1930:  |  | 
| 1931:  | 					 | 
| 1932:  | 					[$clonedVar] = $traverser->traverse([$clonedVar]); | 
| 1933:  | 					$scope = $this->processVirtualAssign($scope, $stmt, $clonedVar, new UnsetOffsetExpr($var->var, $var->dim), $nodeCallback)->getScope(); | 
| 1934:  | 				} elseif ($var instanceof PropertyFetch) { | 
| 1935:  | 					$scope = $scope->invalidateExpression($var); | 
| 1936:  | 					$impurePoints[] = new ImpurePoint( | 
| 1937:  | 						$scope, | 
| 1938:  | 						$var, | 
| 1939:  | 						'propertyUnset', | 
| 1940:  | 						'property unset', | 
| 1941:  | 						true, | 
| 1942:  | 					); | 
| 1943:  | 				} else { | 
| 1944:  | 					$scope = $scope->invalidateExpression($var); | 
| 1945:  | 				} | 
| 1946:  |  | 
| 1947:  | 				$scope = $scope->invalidateExpression(new ForeachValueByRefExpr($var)); | 
| 1948:  | 			} | 
| 1949:  | 		} elseif ($stmt instanceof Node\Stmt\Use_) { | 
| 1950:  | 			$hasYield = false; | 
| 1951:  | 			$throwPoints = []; | 
| 1952:  | 			$impurePoints = []; | 
| 1953:  | 			foreach ($stmt->uses as $use) { | 
| 1954:  | 				$nodeCallback($use, $scope); | 
| 1955:  | 			} | 
| 1956:  | 		} elseif ($stmt instanceof Node\Stmt\Global_) { | 
| 1957:  | 			$hasYield = false; | 
| 1958:  | 			$throwPoints = []; | 
| 1959:  | 			$impurePoints = [ | 
| 1960:  | 				new ImpurePoint( | 
| 1961:  | 					$scope, | 
| 1962:  | 					$stmt, | 
| 1963:  | 					'global', | 
| 1964:  | 					'global variable', | 
| 1965:  | 					true, | 
| 1966:  | 				), | 
| 1967:  | 			]; | 
| 1968:  | 			$vars = []; | 
| 1969:  | 			foreach ($stmt->vars as $var) { | 
| 1970:  | 				if (!$var instanceof Variable) { | 
| 1971:  | 					throw new ShouldNotHappenException(); | 
| 1972:  | 				} | 
| 1973:  | 				$scope = $this->lookForSetAllowedUndefinedExpressions($scope, $var); | 
| 1974:  | 				$varResult = $this->processExprNode($stmt, $var, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1975:  | 				$impurePoints = array_merge($impurePoints, $varResult->getImpurePoints()); | 
| 1976:  | 				$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $var); | 
| 1977:  |  | 
| 1978:  | 				if (!is_string($var->name)) { | 
| 1979:  | 					continue; | 
| 1980:  | 				} | 
| 1981:  |  | 
| 1982:  | 				$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes()); | 
| 1983:  | 				$vars[] = $var->name; | 
| 1984:  | 			} | 
| 1985:  | 			$scope = $this->processVarAnnotation($scope, $vars, $stmt); | 
| 1986:  | 		} elseif ($stmt instanceof Static_) { | 
| 1987:  | 			$hasYield = false; | 
| 1988:  | 			$throwPoints = []; | 
| 1989:  | 			$impurePoints = [ | 
| 1990:  | 				new ImpurePoint( | 
| 1991:  | 					$scope, | 
| 1992:  | 					$stmt, | 
| 1993:  | 					'static', | 
| 1994:  | 					'static variable', | 
| 1995:  | 					true, | 
| 1996:  | 				), | 
| 1997:  | 			]; | 
| 1998:  |  | 
| 1999:  | 			$vars = []; | 
| 2000:  | 			foreach ($stmt->vars as $var) { | 
| 2001:  | 				if (!is_string($var->var->name)) { | 
| 2002:  | 					throw new ShouldNotHappenException(); | 
| 2003:  | 				} | 
| 2004:  |  | 
| 2005:  | 				if ($var->default !== null) { | 
| 2006:  | 					$defaultExprResult = $this->processExprNode($stmt, $var->default, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2007:  | 					$impurePoints = array_merge($impurePoints, $defaultExprResult->getImpurePoints()); | 
| 2008:  | 				} | 
| 2009:  |  | 
| 2010:  | 				$scope = $scope->enterExpressionAssign($var->var); | 
| 2011:  | 				$varResult = $this->processExprNode($stmt, $var->var, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2012:  | 				$impurePoints = array_merge($impurePoints, $varResult->getImpurePoints()); | 
| 2013:  | 				$scope = $scope->exitExpressionAssign($var->var); | 
| 2014:  |  | 
| 2015:  | 				$scope = $scope->assignVariable($var->var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes()); | 
| 2016:  | 				$vars[] = $var->var->name; | 
| 2017:  | 			} | 
| 2018:  |  | 
| 2019:  | 			$scope = $this->processVarAnnotation($scope, $vars, $stmt); | 
| 2020:  | 		} elseif ($stmt instanceof Node\Stmt\Const_) { | 
| 2021:  | 			$hasYield = false; | 
| 2022:  | 			$throwPoints = []; | 
| 2023:  | 			$impurePoints = []; | 
| 2024:  | 			foreach ($stmt->consts as $const) { | 
| 2025:  | 				$nodeCallback($const, $scope); | 
| 2026:  | 				$constResult = $this->processExprNode($stmt, $const->value, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2027:  | 				$impurePoints = array_merge($impurePoints, $constResult->getImpurePoints()); | 
| 2028:  | 				if ($const->namespacedName !== null) { | 
| 2029:  | 					$constantName = new Name\FullyQualified($const->namespacedName->toString()); | 
| 2030:  | 				} else { | 
| 2031:  | 					$constantName = new Name\FullyQualified($const->name->toString()); | 
| 2032:  | 				} | 
| 2033:  | 				$scope = $scope->assignExpression(new ConstFetch($constantName), $scope->getType($const->value), $scope->getNativeType($const->value)); | 
| 2034:  | 			} | 
| 2035:  | 		} elseif ($stmt instanceof Node\Stmt\ClassConst) { | 
| 2036:  | 			$hasYield = false; | 
| 2037:  | 			$throwPoints = []; | 
| 2038:  | 			$impurePoints = []; | 
| 2039:  | 			$this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); | 
| 2040:  | 			foreach ($stmt->consts as $const) { | 
| 2041:  | 				$nodeCallback($const, $scope); | 
| 2042:  | 				$constResult = $this->processExprNode($stmt, $const->value, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2043:  | 				$impurePoints = array_merge($impurePoints, $constResult->getImpurePoints()); | 
| 2044:  | 				if ($scope->getClassReflection() === null) { | 
| 2045:  | 					throw new ShouldNotHappenException(); | 
| 2046:  | 				} | 
| 2047:  | 				$scope = $scope->assignExpression( | 
| 2048:  | 					new Expr\ClassConstFetch(new Name\FullyQualified($scope->getClassReflection()->getName()), $const->name), | 
| 2049:  | 					$scope->getType($const->value), | 
| 2050:  | 					$scope->getNativeType($const->value), | 
| 2051:  | 				); | 
| 2052:  | 			} | 
| 2053:  | 		} elseif ($stmt instanceof Node\Stmt\EnumCase) { | 
| 2054:  | 			$hasYield = false; | 
| 2055:  | 			$throwPoints = []; | 
| 2056:  | 			$this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); | 
| 2057:  | 			$impurePoints = []; | 
| 2058:  | 			if ($stmt->expr !== null) { | 
| 2059:  | 				$exprResult = $this->processExprNode($stmt, $stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2060:  | 				$impurePoints = $exprResult->getImpurePoints(); | 
| 2061:  | 			} | 
| 2062:  | 		} elseif ($stmt instanceof InlineHTML) { | 
| 2063:  | 			$hasYield = false; | 
| 2064:  | 			$throwPoints = []; | 
| 2065:  | 			$impurePoints = [ | 
| 2066:  | 				new ImpurePoint($scope, $stmt, 'betweenPhpTags', 'output between PHP opening and closing tags', true), | 
| 2067:  | 			]; | 
| 2068:  | 		} elseif ($stmt instanceof Node\Stmt\Block) { | 
| 2069:  | 			$result = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback, $context); | 
| 2070:  | 			if ($this->polluteScopeWithBlock) { | 
| 2071:  | 				return $result; | 
| 2072:  | 			} | 
| 2073:  |  | 
| 2074:  | 			return new StatementResult( | 
| 2075:  | 				$scope->mergeWith($result->getScope()), | 
| 2076:  | 				$result->hasYield(), | 
| 2077:  | 				$result->isAlwaysTerminating(), | 
| 2078:  | 				$result->getExitPoints(), | 
| 2079:  | 				$result->getThrowPoints(), | 
| 2080:  | 				$result->getImpurePoints(), | 
| 2081:  | 				$result->getEndStatements(), | 
| 2082:  | 			); | 
| 2083:  | 		} elseif ($stmt instanceof Node\Stmt\Nop) { | 
| 2084:  | 			$hasYield = false; | 
| 2085:  | 			$throwPoints = $overridingThrowPoints ?? []; | 
| 2086:  | 			$impurePoints = []; | 
| 2087:  | 		} elseif ($stmt instanceof Node\Stmt\GroupUse) { | 
| 2088:  | 			$hasYield = false; | 
| 2089:  | 			$throwPoints = []; | 
| 2090:  | 			foreach ($stmt->uses as $use) { | 
| 2091:  | 				$nodeCallback($use, $scope); | 
| 2092:  | 			} | 
| 2093:  | 			$impurePoints = []; | 
| 2094:  | 		} else { | 
| 2095:  | 			$hasYield = false; | 
| 2096:  | 			$throwPoints = $overridingThrowPoints ?? []; | 
| 2097:  | 			$impurePoints = []; | 
| 2098:  | 		} | 
| 2099:  |  | 
| 2100:  | 		return new StatementResult($scope, $hasYield, false, [], $throwPoints, $impurePoints); | 
| 2101:  | 	} | 
| 2102:  |  | 
| 2103:  | 	 | 
| 2104:  |  | 
| 2105:  |  | 
| 2106:  | 	private function getDeprecatedAttribute(Scope $scope, Node\Stmt\Function_|Node\Stmt\ClassMethod|Node\PropertyHook $stmt): array | 
| 2107:  | 	{ | 
| 2108:  | 		$initializerExprContext = InitializerExprContext::fromStubParameter( | 
| 2109:  | 			$scope->isInClass() ? $scope->getClassReflection()->getName() : null, | 
| 2110:  | 			$scope->getFile(), | 
| 2111:  | 			$stmt, | 
| 2112:  | 		); | 
| 2113:  | 		$isDeprecated = false; | 
| 2114:  | 		$deprecatedDescription = null; | 
| 2115:  | 		$deprecatedDescriptionType = null; | 
| 2116:  | 		foreach ($stmt->attrGroups as $attrGroup) { | 
| 2117:  | 			foreach ($attrGroup->attrs as $attr) { | 
| 2118:  | 				if ($attr->name->toString() !== 'Deprecated') { | 
| 2119:  | 					continue; | 
| 2120:  | 				} | 
| 2121:  | 				$isDeprecated = true; | 
| 2122:  | 				$arguments = $attr->args; | 
| 2123:  | 				foreach ($arguments as $i => $arg) { | 
| 2124:  | 					$argName = $arg->name; | 
| 2125:  | 					if ($argName === null) { | 
| 2126:  | 						if ($i !== 0) { | 
| 2127:  | 							continue; | 
| 2128:  | 						} | 
| 2129:  |  | 
| 2130:  | 						$deprecatedDescriptionType = $this->initializerExprTypeResolver->getType($arg->value, $initializerExprContext); | 
| 2131:  | 						break; | 
| 2132:  | 					} | 
| 2133:  |  | 
| 2134:  | 					if ($argName->toString() !== 'message') { | 
| 2135:  | 						continue; | 
| 2136:  | 					} | 
| 2137:  |  | 
| 2138:  | 					$deprecatedDescriptionType = $this->initializerExprTypeResolver->getType($arg->value, $initializerExprContext); | 
| 2139:  | 					break; | 
| 2140:  | 				} | 
| 2141:  | 			} | 
| 2142:  | 		} | 
| 2143:  |  | 
| 2144:  | 		if ($deprecatedDescriptionType !== null) { | 
| 2145:  | 			$constantStrings = $deprecatedDescriptionType->getConstantStrings(); | 
| 2146:  | 			if (count($constantStrings) === 1) { | 
| 2147:  | 				$deprecatedDescription = $constantStrings[0]->getValue(); | 
| 2148:  | 			} | 
| 2149:  | 		} | 
| 2150:  |  | 
| 2151:  | 		return [$isDeprecated, $deprecatedDescription]; | 
| 2152:  | 	} | 
| 2153:  |  | 
| 2154:  | 	 | 
| 2155:  |  | 
| 2156:  |  | 
| 2157:  | 	private function getOverridingThrowPoints(Node\Stmt $statement, MutatingScope $scope): ?array | 
| 2158:  | 	{ | 
| 2159:  | 		foreach ($statement->getComments() as $comment) { | 
| 2160:  | 			if (!$comment instanceof Doc) { | 
| 2161:  | 				continue; | 
| 2162:  | 			} | 
| 2163:  |  | 
| 2164:  | 			$function = $scope->getFunction(); | 
| 2165:  | 			$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | 
| 2166:  | 				$scope->getFile(), | 
| 2167:  | 				$scope->isInClass() ? $scope->getClassReflection()->getName() : null, | 
| 2168:  | 				$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, | 
| 2169:  | 				$function !== null ? $function->getName() : null, | 
| 2170:  | 				$comment->getText(), | 
| 2171:  | 			); | 
| 2172:  |  | 
| 2173:  | 			$throwsTag = $resolvedPhpDoc->getThrowsTag(); | 
| 2174:  | 			if ($throwsTag !== null) { | 
| 2175:  | 				$throwsType = $throwsTag->getType(); | 
| 2176:  | 				if ($throwsType->isVoid()->yes()) { | 
| 2177:  | 					return []; | 
| 2178:  | 				} | 
| 2179:  |  | 
| 2180:  | 				return [ThrowPoint::createExplicit($scope, $throwsType, $statement, false)]; | 
| 2181:  | 			} | 
| 2182:  | 		} | 
| 2183:  |  | 
| 2184:  | 		return null; | 
| 2185:  | 	} | 
| 2186:  |  | 
| 2187:  | 	private function getCurrentClassReflection(Node\Stmt\ClassLike $stmt, string $className, Scope $scope): ClassReflection | 
| 2188:  | 	{ | 
| 2189:  | 		if (!$this->reflectionProvider->hasClass($className)) { | 
| 2190:  | 			return $this->createAstClassReflection($stmt, $className, $scope); | 
| 2191:  | 		} | 
| 2192:  |  | 
| 2193:  | 		$defaultClassReflection = $this->reflectionProvider->getClass($className); | 
| 2194:  | 		if ($defaultClassReflection->getFileName() !== $scope->getFile()) { | 
| 2195:  | 			return $this->createAstClassReflection($stmt, $className, $scope); | 
| 2196:  | 		} | 
| 2197:  |  | 
| 2198:  | 		$startLine = $defaultClassReflection->getNativeReflection()->getStartLine(); | 
| 2199:  | 		if ($startLine !== $stmt->getStartLine()) { | 
| 2200:  | 			return $this->createAstClassReflection($stmt, $className, $scope); | 
| 2201:  | 		} | 
| 2202:  |  | 
| 2203:  | 		return $defaultClassReflection; | 
| 2204:  | 	} | 
| 2205:  |  | 
| 2206:  | 	private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $className, Scope $scope): ClassReflection | 
| 2207:  | 	{ | 
| 2208:  | 		$nodeToReflection = new NodeToReflection(); | 
| 2209:  | 		$betterReflectionClass = $nodeToReflection->__invoke( | 
| 2210:  | 			$this->reflector, | 
| 2211:  | 			$stmt, | 
| 2212:  | 			new LocatedSource(FileReader::read($scope->getFile()), $className, $scope->getFile()), | 
| 2213:  | 			$scope->getNamespace() !== null ? new Node\Stmt\Namespace_(new Name($scope->getNamespace())) : null, | 
| 2214:  | 		); | 
| 2215:  | 		if (!$betterReflectionClass instanceof \PHPStan\BetterReflection\Reflection\ReflectionClass) { | 
| 2216:  | 			throw new ShouldNotHappenException(); | 
| 2217:  | 		} | 
| 2218:  |  | 
| 2219:  | 		$enumAdapter = base64_decode('UEhQU3RhblxCZXR0ZXJSZWZsZWN0aW9uXFJlZmxlY3Rpb25cQWRhcHRlclxSZWZsZWN0aW9uRW51bQ==', true); | 
| 2220:  |  | 
| 2221:  | 		return new ClassReflection( | 
| 2222:  | 			$this->reflectionProvider, | 
| 2223:  | 			$this->initializerExprTypeResolver, | 
| 2224:  | 			$this->fileTypeMapper, | 
| 2225:  | 			$this->stubPhpDocProvider, | 
| 2226:  | 			$this->phpDocInheritanceResolver, | 
| 2227:  | 			$this->phpVersion, | 
| 2228:  | 			$this->signatureMapProvider, | 
| 2229:  | 			$this->deprecationProvider, | 
| 2230:  | 			$this->attributeReflectionFactory, | 
| 2231:  | 			$this->classReflectionExtensionRegistryProvider->getRegistry()->getPhpClassReflectionExtension(), | 
| 2232:  | 			$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), | 
| 2233:  | 			$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), | 
| 2234:  | 			$this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(), | 
| 2235:  | 			$this->classReflectionExtensionRegistryProvider->getRegistry()->getRequireExtendsPropertyClassReflectionExtension(), | 
| 2236:  | 			$this->classReflectionExtensionRegistryProvider->getRegistry()->getRequireExtendsMethodsClassReflectionExtension(), | 
| 2237:  | 			$betterReflectionClass->getName(), | 
| 2238:  | 			$betterReflectionClass instanceof ReflectionEnum && PHP_VERSION_ID >= 80000 ? new $enumAdapter($betterReflectionClass) : new ReflectionClass($betterReflectionClass), | 
| 2239:  | 			null, | 
| 2240:  | 			null, | 
| 2241:  | 			null, | 
| 2242:  | 			$this->universalObjectCratesClasses, | 
| 2243:  | 			sprintf('%s:%d', $scope->getFile(), $stmt->getStartLine()), | 
| 2244:  | 		); | 
| 2245:  | 	} | 
| 2246:  |  | 
| 2247:  | 	private function lookForSetAllowedUndefinedExpressions(MutatingScope $scope, Expr $expr): MutatingScope | 
| 2248:  | 	{ | 
| 2249:  | 		return $this->lookForExpressionCallback($scope, $expr, static fn (MutatingScope $scope, Expr $expr): MutatingScope => $scope->setAllowedUndefinedExpression($expr)); | 
| 2250:  | 	} | 
| 2251:  |  | 
| 2252:  | 	private function lookForUnsetAllowedUndefinedExpressions(MutatingScope $scope, Expr $expr): MutatingScope | 
| 2253:  | 	{ | 
| 2254:  | 		return $this->lookForExpressionCallback($scope, $expr, static fn (MutatingScope $scope, Expr $expr): MutatingScope => $scope->unsetAllowedUndefinedExpression($expr)); | 
| 2255:  | 	} | 
| 2256:  |  | 
| 2257:  | 	 | 
| 2258:  |  | 
| 2259:  |  | 
| 2260:  | 	private function lookForExpressionCallback(MutatingScope $scope, Expr $expr, Closure $callback): MutatingScope | 
| 2261:  | 	{ | 
| 2262:  | 		if (!$expr instanceof ArrayDimFetch || $expr->dim !== null) { | 
| 2263:  | 			$scope = $callback($scope, $expr); | 
| 2264:  | 		} | 
| 2265:  |  | 
| 2266:  | 		if ($expr instanceof ArrayDimFetch) { | 
| 2267:  | 			$scope = $this->lookForExpressionCallback($scope, $expr->var, $callback); | 
| 2268:  | 		} elseif ($expr instanceof PropertyFetch || $expr instanceof Expr\NullsafePropertyFetch) { | 
| 2269:  | 			$scope = $this->lookForExpressionCallback($scope, $expr->var, $callback); | 
| 2270:  | 		} elseif ($expr instanceof StaticPropertyFetch && $expr->class instanceof Expr) { | 
| 2271:  | 			$scope = $this->lookForExpressionCallback($scope, $expr->class, $callback); | 
| 2272:  | 		} elseif ($expr instanceof List_) { | 
| 2273:  | 			foreach ($expr->items as $item) { | 
| 2274:  | 				if ($item === null) { | 
| 2275:  | 					continue; | 
| 2276:  | 				} | 
| 2277:  |  | 
| 2278:  | 				$scope = $this->lookForExpressionCallback($scope, $item->value, $callback); | 
| 2279:  | 			} | 
| 2280:  | 		} | 
| 2281:  |  | 
| 2282:  | 		return $scope; | 
| 2283:  | 	} | 
| 2284:  |  | 
| 2285:  | 	private function ensureShallowNonNullability(MutatingScope $scope, Scope $originalScope, Expr $exprToSpecify): EnsuredNonNullabilityResult | 
| 2286:  | 	{ | 
| 2287:  | 		$exprType = $scope->getType($exprToSpecify); | 
| 2288:  | 		$isNull = $exprType->isNull(); | 
| 2289:  | 		if ($isNull->yes()) { | 
| 2290:  | 			return new EnsuredNonNullabilityResult($scope, []); | 
| 2291:  | 		} | 
| 2292:  |  | 
| 2293:  | 		 | 
| 2294:  | 		$certainty = TrinaryLogic::createYes(); | 
| 2295:  | 		$hasExpressionType = $originalScope->hasExpressionType($exprToSpecify); | 
| 2296:  | 		if (!$hasExpressionType->no()) { | 
| 2297:  | 			$certainty = $hasExpressionType; | 
| 2298:  | 		} | 
| 2299:  |  | 
| 2300:  | 		$exprTypeWithoutNull = TypeCombinator::removeNull($exprType); | 
| 2301:  | 		if ($exprType->equals($exprTypeWithoutNull)) { | 
| 2302:  | 			$originalExprType = $originalScope->getType($exprToSpecify); | 
| 2303:  | 			if (!$originalExprType->equals($exprTypeWithoutNull)) { | 
| 2304:  | 				$originalNativeType = $originalScope->getNativeType($exprToSpecify); | 
| 2305:  |  | 
| 2306:  | 				return new EnsuredNonNullabilityResult($scope, [ | 
| 2307:  | 					new EnsuredNonNullabilityResultExpression($exprToSpecify, $originalExprType, $originalNativeType, $certainty), | 
| 2308:  | 				]); | 
| 2309:  | 			} | 
| 2310:  | 			return new EnsuredNonNullabilityResult($scope, []); | 
| 2311:  | 		} | 
| 2312:  |  | 
| 2313:  | 		$nativeType = $scope->getNativeType($exprToSpecify); | 
| 2314:  | 		$scope = $scope->specifyExpressionType( | 
| 2315:  | 			$exprToSpecify, | 
| 2316:  | 			$exprTypeWithoutNull, | 
| 2317:  | 			TypeCombinator::removeNull($nativeType), | 
| 2318:  | 			TrinaryLogic::createYes(), | 
| 2319:  | 		); | 
| 2320:  |  | 
| 2321:  | 		return new EnsuredNonNullabilityResult( | 
| 2322:  | 			$scope, | 
| 2323:  | 			[ | 
| 2324:  | 				new EnsuredNonNullabilityResultExpression($exprToSpecify, $exprType, $nativeType, $certainty), | 
| 2325:  | 			], | 
| 2326:  | 		); | 
| 2327:  | 	} | 
| 2328:  |  | 
| 2329:  | 	private function ensureNonNullability(MutatingScope $scope, Expr $expr): EnsuredNonNullabilityResult | 
| 2330:  | 	{ | 
| 2331:  | 		$specifiedExpressions = []; | 
| 2332:  | 		$originalScope = $scope; | 
| 2333:  | 		$scope = $this->lookForExpressionCallback($scope, $expr, function ($scope, $expr) use (&$specifiedExpressions, $originalScope) { | 
| 2334:  | 			$result = $this->ensureShallowNonNullability($scope, $originalScope, $expr); | 
| 2335:  | 			foreach ($result->getSpecifiedExpressions() as $specifiedExpression) { | 
| 2336:  | 				$specifiedExpressions[] = $specifiedExpression; | 
| 2337:  | 			} | 
| 2338:  | 			return $result->getScope(); | 
| 2339:  | 		}); | 
| 2340:  |  | 
| 2341:  | 		return new EnsuredNonNullabilityResult($scope, $specifiedExpressions); | 
| 2342:  | 	} | 
| 2343:  |  | 
| 2344:  | 	 | 
| 2345:  |  | 
| 2346:  |  | 
| 2347:  | 	private function revertNonNullability(MutatingScope $scope, array $specifiedExpressions): MutatingScope | 
| 2348:  | 	{ | 
| 2349:  | 		foreach ($specifiedExpressions as $specifiedExpressionResult) { | 
| 2350:  | 			$scope = $scope->specifyExpressionType( | 
| 2351:  | 				$specifiedExpressionResult->getExpression(), | 
| 2352:  | 				$specifiedExpressionResult->getOriginalType(), | 
| 2353:  | 				$specifiedExpressionResult->getOriginalNativeType(), | 
| 2354:  | 				$specifiedExpressionResult->getCertainty(), | 
| 2355:  | 			); | 
| 2356:  | 		} | 
| 2357:  |  | 
| 2358:  | 		return $scope; | 
| 2359:  | 	} | 
| 2360:  |  | 
| 2361:  | 	private function findEarlyTerminatingExpr(Expr $expr, Scope $scope): ?Expr | 
| 2362:  | 	{ | 
| 2363:  | 		if (($expr instanceof MethodCall || $expr instanceof Expr\StaticCall) && $expr->name instanceof Node\Identifier) { | 
| 2364:  | 			if (array_key_exists($expr->name->toLowerString(), $this->earlyTerminatingMethodNames)) { | 
| 2365:  | 				if ($expr instanceof MethodCall) { | 
| 2366:  | 					$methodCalledOnType = $scope->getType($expr->var); | 
| 2367:  | 				} else { | 
| 2368:  | 					if ($expr->class instanceof Name) { | 
| 2369:  | 						$methodCalledOnType = $scope->resolveTypeByName($expr->class); | 
| 2370:  | 					} else { | 
| 2371:  | 						$methodCalledOnType = $scope->getType($expr->class); | 
| 2372:  | 					} | 
| 2373:  | 				} | 
| 2374:  |  | 
| 2375:  | 				foreach ($methodCalledOnType->getObjectClassNames() as $referencedClass) { | 
| 2376:  | 					if (!$this->reflectionProvider->hasClass($referencedClass)) { | 
| 2377:  | 						continue; | 
| 2378:  | 					} | 
| 2379:  |  | 
| 2380:  | 					$classReflection = $this->reflectionProvider->getClass($referencedClass); | 
| 2381:  | 					foreach (array_merge([$referencedClass], $classReflection->getParentClassesNames(), $classReflection->getNativeReflection()->getInterfaceNames()) as $className) { | 
| 2382:  | 						if (!isset($this->earlyTerminatingMethodCalls[$className])) { | 
| 2383:  | 							continue; | 
| 2384:  | 						} | 
| 2385:  |  | 
| 2386:  | 						if (in_array((string) $expr->name, $this->earlyTerminatingMethodCalls[$className], true)) { | 
| 2387:  | 							return $expr; | 
| 2388:  | 						} | 
| 2389:  | 					} | 
| 2390:  | 				} | 
| 2391:  | 			} | 
| 2392:  | 		} | 
| 2393:  |  | 
| 2394:  | 		if ($expr instanceof FuncCall && $expr->name instanceof Name) { | 
| 2395:  | 			if (in_array((string) $expr->name, $this->earlyTerminatingFunctionCalls, true)) { | 
| 2396:  | 				return $expr; | 
| 2397:  | 			} | 
| 2398:  | 		} | 
| 2399:  |  | 
| 2400:  | 		if ($expr instanceof Expr\Exit_ || $expr instanceof Expr\Throw_) { | 
| 2401:  | 			return $expr; | 
| 2402:  | 		} | 
| 2403:  |  | 
| 2404:  | 		$exprType = $scope->getType($expr); | 
| 2405:  | 		if ($exprType instanceof NeverType && $exprType->isExplicit()) { | 
| 2406:  | 			return $expr; | 
| 2407:  | 		} | 
| 2408:  |  | 
| 2409:  | 		return null; | 
| 2410:  | 	} | 
| 2411:  |  | 
| 2412:  | 	 | 
| 2413:  |  | 
| 2414:  |  | 
| 2415:  | 	public function processExprNode(Node\Stmt $stmt, Expr $expr, MutatingScope $scope, callable $nodeCallback, ExpressionContext $context): ExpressionResult | 
| 2416:  | 	{ | 
| 2417:  | 		if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) { | 
| 2418:  | 			if ($expr instanceof FuncCall) { | 
| 2419:  | 				$newExpr = new FunctionCallableNode($expr->name, $expr); | 
| 2420:  | 			} elseif ($expr instanceof MethodCall) { | 
| 2421:  | 				$newExpr = new MethodCallableNode($expr->var, $expr->name, $expr); | 
| 2422:  | 			} elseif ($expr instanceof StaticCall) { | 
| 2423:  | 				$newExpr = new StaticMethodCallableNode($expr->class, $expr->name, $expr); | 
| 2424:  | 			} elseif ($expr instanceof New_ && !$expr->class instanceof Class_) { | 
| 2425:  | 				$newExpr = new InstantiationCallableNode($expr->class, $expr); | 
| 2426:  | 			} else { | 
| 2427:  | 				throw new ShouldNotHappenException(); | 
| 2428:  | 			} | 
| 2429:  |  | 
| 2430:  | 			return $this->processExprNode($stmt, $newExpr, $scope, $nodeCallback, $context); | 
| 2431:  | 		} | 
| 2432:  |  | 
| 2433:  | 		$this->callNodeCallbackWithExpression($nodeCallback, $expr, $scope, $context); | 
| 2434:  |  | 
| 2435:  | 		if ($expr instanceof Variable) { | 
| 2436:  | 			$hasYield = false; | 
| 2437:  | 			$throwPoints = []; | 
| 2438:  | 			$impurePoints = []; | 
| 2439:  | 			$isAlwaysTerminating = false; | 
| 2440:  | 			if ($expr->name instanceof Expr) { | 
| 2441:  | 				return $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 2442:  | 			} elseif (in_array($expr->name, Scope::SUPERGLOBAL_VARIABLES, true)) { | 
| 2443:  | 				$impurePoints[] = new ImpurePoint($scope, $expr, 'superglobal', 'access to superglobal variable', true); | 
| 2444:  | 			} | 
| 2445:  | 		} elseif ($expr instanceof Assign || $expr instanceof AssignRef) { | 
| 2446:  | 			$result = $this->processAssignVar( | 
| 2447:  | 				$scope, | 
| 2448:  | 				$stmt, | 
| 2449:  | 				$expr->var, | 
| 2450:  | 				$expr->expr, | 
| 2451:  | 				$nodeCallback, | 
| 2452:  | 				$context, | 
| 2453:  | 				function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): ExpressionResult { | 
| 2454:  | 					$impurePoints = []; | 
| 2455:  | 					if ($expr instanceof AssignRef) { | 
| 2456:  | 						$referencedExpr = $expr->expr; | 
| 2457:  | 						while ($referencedExpr instanceof ArrayDimFetch) { | 
| 2458:  | 							$referencedExpr = $referencedExpr->var; | 
| 2459:  | 						} | 
| 2460:  |  | 
| 2461:  | 						if ($referencedExpr instanceof PropertyFetch || $referencedExpr instanceof StaticPropertyFetch) { | 
| 2462:  | 							$impurePoints[] = new ImpurePoint( | 
| 2463:  | 								$scope, | 
| 2464:  | 								$expr, | 
| 2465:  | 								'propertyAssignByRef', | 
| 2466:  | 								'property assignment by reference', | 
| 2467:  | 								false, | 
| 2468:  | 							); | 
| 2469:  | 						} | 
| 2470:  |  | 
| 2471:  | 						$scope = $scope->enterExpressionAssign($expr->expr); | 
| 2472:  | 					} | 
| 2473:  |  | 
| 2474:  | 					if ($expr->var instanceof Variable && is_string($expr->var->name)) { | 
| 2475:  | 						$context = $context->enterRightSideAssign( | 
| 2476:  | 							$expr->var->name, | 
| 2477:  | 							$scope->getType($expr->expr), | 
| 2478:  | 							$scope->getNativeType($expr->expr), | 
| 2479:  | 						); | 
| 2480:  | 					} | 
| 2481:  |  | 
| 2482:  | 					$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 2483:  | 					$hasYield = $result->hasYield(); | 
| 2484:  | 					$throwPoints = $result->getThrowPoints(); | 
| 2485:  | 					$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 2486:  | 					$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 2487:  | 					$scope = $result->getScope(); | 
| 2488:  |  | 
| 2489:  | 					if ($expr instanceof AssignRef) { | 
| 2490:  | 						$scope = $scope->exitExpressionAssign($expr->expr); | 
| 2491:  | 					} | 
| 2492:  |  | 
| 2493:  | 					return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); | 
| 2494:  | 				}, | 
| 2495:  | 				true, | 
| 2496:  | 			); | 
| 2497:  | 			$scope = $result->getScope(); | 
| 2498:  | 			$hasYield = $result->hasYield(); | 
| 2499:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2500:  | 			$impurePoints = $result->getImpurePoints(); | 
| 2501:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 2502:  | 			$vars = $this->getAssignedVariables($expr->var); | 
| 2503:  | 			if (count($vars) > 0) { | 
| 2504:  | 				$varChangedScope = false; | 
| 2505:  | 				$scope = $this->processVarAnnotation($scope, $vars, $stmt, $varChangedScope); | 
| 2506:  | 				if (!$varChangedScope) { | 
| 2507:  | 					$scope = $this->processStmtVarAnnotation($scope, $stmt, null, $nodeCallback); | 
| 2508:  | 				} | 
| 2509:  | 			} | 
| 2510:  | 		} elseif ($expr instanceof Expr\AssignOp) { | 
| 2511:  | 			$result = $this->processAssignVar( | 
| 2512:  | 				$scope, | 
| 2513:  | 				$stmt, | 
| 2514:  | 				$expr->var, | 
| 2515:  | 				$expr, | 
| 2516:  | 				$nodeCallback, | 
| 2517:  | 				$context, | 
| 2518:  | 				function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): ExpressionResult { | 
| 2519:  | 					$originalScope = $scope; | 
| 2520:  | 					if ($expr instanceof Expr\AssignOp\Coalesce) { | 
| 2521:  | 						$scope = $scope->filterByFalseyValue( | 
| 2522:  | 							new BinaryOp\NotIdentical($expr->var, new ConstFetch(new Name('null'))), | 
| 2523:  | 						); | 
| 2524:  | 					} | 
| 2525:  |  | 
| 2526:  | 					$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 2527:  | 					if ($expr instanceof Expr\AssignOp\Coalesce) { | 
| 2528:  | 						return new ExpressionResult( | 
| 2529:  | 							$result->getScope()->mergeWith($originalScope), | 
| 2530:  | 							$result->hasYield(), | 
| 2531:  | 							$result->isAlwaysTerminating(), | 
| 2532:  | 							$result->getThrowPoints(), | 
| 2533:  | 							$result->getImpurePoints(), | 
| 2534:  | 						); | 
| 2535:  | 					} | 
| 2536:  |  | 
| 2537:  | 					return $result; | 
| 2538:  | 				}, | 
| 2539:  | 				$expr instanceof Expr\AssignOp\Coalesce, | 
| 2540:  | 			); | 
| 2541:  | 			$scope = $result->getScope(); | 
| 2542:  | 			$hasYield = $result->hasYield(); | 
| 2543:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2544:  | 			$impurePoints = $result->getImpurePoints(); | 
| 2545:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 2546:  | 			if ( | 
| 2547:  | 				($expr instanceof Expr\AssignOp\Div || $expr instanceof Expr\AssignOp\Mod) && | 
| 2548:  | 				!$scope->getType($expr->expr)->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no() | 
| 2549:  | 			) { | 
| 2550:  | 				$throwPoints[] = ThrowPoint::createExplicit($scope, new ObjectType(DivisionByZeroError::class), $expr, false); | 
| 2551:  | 			} | 
| 2552:  | 		} elseif ($expr instanceof FuncCall) { | 
| 2553:  | 			$parametersAcceptor = null; | 
| 2554:  | 			$functionReflection = null; | 
| 2555:  | 			$throwPoints = []; | 
| 2556:  | 			$impurePoints = []; | 
| 2557:  | 			$isAlwaysTerminating = false; | 
| 2558:  | 			if ($expr->name instanceof Expr) { | 
| 2559:  | 				$nameType = $scope->getType($expr->name); | 
| 2560:  | 				if (!$nameType->isCallable()->no()) { | 
| 2561:  | 					$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 2562:  | 						$scope, | 
| 2563:  | 						$expr->getArgs(), | 
| 2564:  | 						$nameType->getCallableParametersAcceptors($scope), | 
| 2565:  | 						null, | 
| 2566:  | 					); | 
| 2567:  | 				} | 
| 2568:  |  | 
| 2569:  | 				$nameResult = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 2570:  | 				$scope = $nameResult->getScope(); | 
| 2571:  | 				$throwPoints = $nameResult->getThrowPoints(); | 
| 2572:  | 				$impurePoints = $nameResult->getImpurePoints(); | 
| 2573:  | 				$isAlwaysTerminating = $nameResult->isAlwaysTerminating(); | 
| 2574:  | 				if ( | 
| 2575:  | 					$nameType->isObject()->yes() | 
| 2576:  | 					&& $nameType->isCallable()->yes() | 
| 2577:  | 					&& (new ObjectType(Closure::class))->isSuperTypeOf($nameType)->no() | 
| 2578:  | 				) { | 
| 2579:  | 					$invokeResult = $this->processExprNode( | 
| 2580:  | 						$stmt, | 
| 2581:  | 						new MethodCall($expr->name, '__invoke', $expr->getArgs(), $expr->getAttributes()), | 
| 2582:  | 						$scope, | 
| 2583:  | 						static function (): void { | 
| 2584:  | 						}, | 
| 2585:  | 						$context->enterDeep(), | 
| 2586:  | 					); | 
| 2587:  | 					$throwPoints = array_merge($throwPoints, $invokeResult->getThrowPoints()); | 
| 2588:  | 					$impurePoints = array_merge($impurePoints, $invokeResult->getImpurePoints()); | 
| 2589:  | 					$isAlwaysTerminating = $invokeResult->isAlwaysTerminating(); | 
| 2590:  | 				} elseif ($parametersAcceptor instanceof CallableParametersAcceptor) { | 
| 2591:  | 					$callableThrowPoints = array_map(static fn (SimpleThrowPoint $throwPoint) => $throwPoint->isExplicit() ? ThrowPoint::createExplicit($scope, $throwPoint->getType(), $expr, $throwPoint->canContainAnyThrowable()) : ThrowPoint::createImplicit($scope, $expr), $parametersAcceptor->getThrowPoints()); | 
| 2592:  | 					if (!$this->implicitThrows) { | 
| 2593:  | 						$callableThrowPoints = array_values(array_filter($callableThrowPoints, static fn (ThrowPoint $throwPoint) => $throwPoint->isExplicit())); | 
| 2594:  | 					} | 
| 2595:  | 					$throwPoints = array_merge($throwPoints, $callableThrowPoints); | 
| 2596:  | 					$impurePoints = array_merge($impurePoints, array_map(static fn (SimpleImpurePoint $impurePoint) => new ImpurePoint($scope, $expr, $impurePoint->getIdentifier(), $impurePoint->getDescription(), $impurePoint->isCertain()), $parametersAcceptor->getImpurePoints())); | 
| 2597:  |  | 
| 2598:  | 					$scope = $this->processImmediatelyCalledCallable($scope, $parametersAcceptor->getInvalidateExpressions(), $parametersAcceptor->getUsedVariables()); | 
| 2599:  | 				} | 
| 2600:  | 			} elseif ($this->reflectionProvider->hasFunction($expr->name, $scope)) { | 
| 2601:  | 				$functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); | 
| 2602:  | 				$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 2603:  | 					$scope, | 
| 2604:  | 					$expr->getArgs(), | 
| 2605:  | 					$functionReflection->getVariants(), | 
| 2606:  | 					$functionReflection->getNamedArgumentsVariants(), | 
| 2607:  | 				); | 
| 2608:  | 				$impurePoint = SimpleImpurePoint::createFromVariant($functionReflection, $parametersAcceptor, $scope, $expr->getArgs()); | 
| 2609:  | 				if ($impurePoint !== null) { | 
| 2610:  | 					$impurePoints[] = new ImpurePoint($scope, $expr, $impurePoint->getIdentifier(), $impurePoint->getDescription(), $impurePoint->isCertain()); | 
| 2611:  | 				} | 
| 2612:  | 			} else { | 
| 2613:  | 				$impurePoints[] = new ImpurePoint( | 
| 2614:  | 					$scope, | 
| 2615:  | 					$expr, | 
| 2616:  | 					'functionCall', | 
| 2617:  | 					'call to unknown function', | 
| 2618:  | 					false, | 
| 2619:  | 				); | 
| 2620:  | 			} | 
| 2621:  |  | 
| 2622:  | 			if ($parametersAcceptor !== null) { | 
| 2623:  | 				$expr = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $expr) ?? $expr; | 
| 2624:  | 				$returnType = $parametersAcceptor->getReturnType(); | 
| 2625:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $returnType instanceof NeverType && $returnType->isExplicit(); | 
| 2626:  | 			} | 
| 2627:  |  | 
| 2628:  | 			if ( | 
| 2629:  | 				$expr->name instanceof Name | 
| 2630:  | 				&& $functionReflection !== null | 
| 2631:  | 				&& $functionReflection->getName() === 'clone' | 
| 2632:  | 				&& count($expr->getArgs()) === 2 | 
| 2633:  | 			) { | 
| 2634:  | 				$clonePropertiesArgType = $scope->getType($expr->getArgs()[1]->value); | 
| 2635:  | 				$cloneExpr = new TypeExpr($scope->getType(new Expr\Clone_($expr->getArgs()[0]->value))); | 
| 2636:  | 				$clonePropertiesArgTypeConstantArrays = $clonePropertiesArgType->getConstantArrays(); | 
| 2637:  | 				foreach ($clonePropertiesArgTypeConstantArrays as $clonePropertiesArgTypeConstantArray) { | 
| 2638:  | 					foreach ($clonePropertiesArgTypeConstantArray->getKeyTypes() as $i => $clonePropertyKeyType) { | 
| 2639:  | 						$clonePropertyKeyTypeScalars = $clonePropertyKeyType->getConstantScalarValues(); | 
| 2640:  | 						$propertyAttributes = $expr->getAttributes(); | 
| 2641:  | 						$propertyAttributes['inCloneWith'] = true; | 
| 2642:  | 						if (count($clonePropertyKeyTypeScalars) === 1) { | 
| 2643:  | 							$this->processVirtualAssign( | 
| 2644:  | 								$scope, | 
| 2645:  | 								$stmt, | 
| 2646:  | 								new PropertyFetch($cloneExpr, (string) $clonePropertyKeyTypeScalars[0], $propertyAttributes), | 
| 2647:  | 								new TypeExpr($clonePropertiesArgTypeConstantArray->getValueTypes()[$i]), | 
| 2648:  | 								$nodeCallback, | 
| 2649:  | 							); | 
| 2650:  | 							continue; | 
| 2651:  | 						} | 
| 2652:  |  | 
| 2653:  | 						$this->processVirtualAssign( | 
| 2654:  | 							$scope, | 
| 2655:  | 							$stmt, | 
| 2656:  | 							new PropertyFetch($cloneExpr, new TypeExpr($clonePropertyKeyType), $propertyAttributes), | 
| 2657:  | 							new TypeExpr($clonePropertiesArgTypeConstantArray->getValueTypes()[$i]), | 
| 2658:  | 							$nodeCallback, | 
| 2659:  | 						); | 
| 2660:  | 					} | 
| 2661:  | 				} | 
| 2662:  | 			} | 
| 2663:  |  | 
| 2664:  | 			$result = $this->processArgs($stmt, $functionReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context); | 
| 2665:  | 			$scope = $result->getScope(); | 
| 2666:  | 			$hasYield = $result->hasYield(); | 
| 2667:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2668:  | 			$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 2669:  | 			$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 2670:  |  | 
| 2671:  | 			if ($functionReflection !== null) { | 
| 2672:  | 				$functionThrowPoint = $this->getFunctionThrowPoint($functionReflection, $parametersAcceptor, $expr, $scope); | 
| 2673:  | 				if ($functionThrowPoint !== null) { | 
| 2674:  | 					$throwPoints[] = $functionThrowPoint; | 
| 2675:  | 				} | 
| 2676:  | 			} else { | 
| 2677:  | 				$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 2678:  | 			} | 
| 2679:  |  | 
| 2680:  | 			if ( | 
| 2681:  | 				$parametersAcceptor instanceof ClosureType && count($parametersAcceptor->getImpurePoints()) > 0 | 
| 2682:  | 				&& $scope->isInClass() | 
| 2683:  | 			) { | 
| 2684:  | 				$scope = $scope->invalidateExpression(new Variable('this'), true); | 
| 2685:  | 			} | 
| 2686:  |  | 
| 2687:  | 			if ( | 
| 2688:  | 				$functionReflection !== null | 
| 2689:  | 				&& in_array($functionReflection->getName(), ['json_encode', 'json_decode'], true) | 
| 2690:  | 			) { | 
| 2691:  | 				$scope = $scope->invalidateExpression(new FuncCall(new Name('json_last_error'), [])) | 
| 2692:  | 					->invalidateExpression(new FuncCall(new Name\FullyQualified('json_last_error'), [])) | 
| 2693:  | 					->invalidateExpression(new FuncCall(new Name('json_last_error_msg'), [])) | 
| 2694:  | 					->invalidateExpression(new FuncCall(new Name\FullyQualified('json_last_error_msg'), [])); | 
| 2695:  | 			} | 
| 2696:  |  | 
| 2697:  | 			if ( | 
| 2698:  | 				$functionReflection !== null | 
| 2699:  | 				&& $functionReflection->getName() === 'file_put_contents' | 
| 2700:  | 				&& count($expr->getArgs()) > 0 | 
| 2701:  | 			) { | 
| 2702:  | 				$scope = $scope->invalidateExpression(new FuncCall(new Name('file_get_contents'), [$expr->getArgs()[0]])) | 
| 2703:  | 					->invalidateExpression(new FuncCall(new Name\FullyQualified('file_get_contents'), [$expr->getArgs()[0]])); | 
| 2704:  | 			} | 
| 2705:  |  | 
| 2706:  | 			if ( | 
| 2707:  | 				$functionReflection !== null | 
| 2708:  | 				&& in_array($functionReflection->getName(), ['array_pop', 'array_shift'], true) | 
| 2709:  | 				&& count($expr->getArgs()) >= 1 | 
| 2710:  | 			) { | 
| 2711:  | 				$arrayArg = $expr->getArgs()[0]->value; | 
| 2712:  |  | 
| 2713:  | 				$arrayArgType = $scope->getType($arrayArg); | 
| 2714:  | 				$arrayArgNativeType = $scope->getNativeType($arrayArg); | 
| 2715:  | 				$isArrayPop = $functionReflection->getName() === 'array_pop'; | 
| 2716:  |  | 
| 2717:  | 				$scope = $this->processVirtualAssign( | 
| 2718:  | 					$scope, | 
| 2719:  | 					$stmt, | 
| 2720:  | 					$arrayArg, | 
| 2721:  | 					new NativeTypeExpr( | 
| 2722:  | 						$isArrayPop ? $arrayArgType->popArray() : $arrayArgType->shiftArray(), | 
| 2723:  | 						$isArrayPop ? $arrayArgNativeType->popArray() : $arrayArgNativeType->shiftArray(), | 
| 2724:  | 					), | 
| 2725:  | 					$nodeCallback, | 
| 2726:  | 				)->getScope(); | 
| 2727:  | 			} | 
| 2728:  |  | 
| 2729:  | 			if ( | 
| 2730:  | 				$functionReflection !== null | 
| 2731:  | 				&& in_array($functionReflection->getName(), ['array_push', 'array_unshift'], true) | 
| 2732:  | 				&& count($expr->getArgs()) >= 2 | 
| 2733:  | 			) { | 
| 2734:  | 				$arrayArg = $expr->getArgs()[0]->value; | 
| 2735:  |  | 
| 2736:  | 				$scope = $this->processVirtualAssign( | 
| 2737:  | 					$scope, | 
| 2738:  | 					$stmt, | 
| 2739:  | 					$arrayArg, | 
| 2740:  | 					new NativeTypeExpr( | 
| 2741:  | 						$this->getArrayFunctionAppendingType($functionReflection, $scope, $expr), | 
| 2742:  | 						$this->getArrayFunctionAppendingType($functionReflection, $scope->doNotTreatPhpDocTypesAsCertain(), $expr), | 
| 2743:  | 					), | 
| 2744:  | 					$nodeCallback, | 
| 2745:  | 				)->getScope(); | 
| 2746:  | 			} | 
| 2747:  |  | 
| 2748:  | 			if ( | 
| 2749:  | 				$functionReflection !== null | 
| 2750:  | 				&& in_array($functionReflection->getName(), ['fopen', 'file_get_contents'], true) | 
| 2751:  | 			) { | 
| 2752:  | 				$scope = $scope->assignVariable('http_response_header', TypeCombinator::intersect(new ArrayType(new IntegerType(), new StringType()), new AccessoryArrayListType()), new ArrayType(new IntegerType(), new StringType()), TrinaryLogic::createYes()); | 
| 2753:  | 			} | 
| 2754:  |  | 
| 2755:  | 			if ( | 
| 2756:  | 				$functionReflection !== null | 
| 2757:  | 				&& $functionReflection->getName() === 'shuffle' | 
| 2758:  | 			) { | 
| 2759:  | 				$arrayArg = $expr->getArgs()[0]->value; | 
| 2760:  |  | 
| 2761:  | 				$scope = $this->processVirtualAssign( | 
| 2762:  | 					$scope, | 
| 2763:  | 					$stmt, | 
| 2764:  | 					$arrayArg, | 
| 2765:  | 					new NativeTypeExpr($scope->getType($arrayArg)->shuffleArray(), $scope->getNativeType($arrayArg)->shuffleArray()), | 
| 2766:  | 					$nodeCallback, | 
| 2767:  | 				)->getScope(); | 
| 2768:  | 			} | 
| 2769:  |  | 
| 2770:  | 			if ( | 
| 2771:  | 				$functionReflection !== null | 
| 2772:  | 				&& $functionReflection->getName() === 'array_splice' | 
| 2773:  | 				&& count($expr->getArgs()) >= 2 | 
| 2774:  | 			) { | 
| 2775:  | 				$arrayArg = $expr->getArgs()[0]->value; | 
| 2776:  | 				$arrayArgType = $scope->getType($arrayArg); | 
| 2777:  | 				$arrayArgNativeType = $scope->getNativeType($arrayArg); | 
| 2778:  |  | 
| 2779:  | 				$offsetType = $scope->getType($expr->getArgs()[1]->value); | 
| 2780:  | 				$lengthType = isset($expr->getArgs()[2]) ? $scope->getType($expr->getArgs()[2]->value) : new NullType(); | 
| 2781:  | 				$replacementType = isset($expr->getArgs()[3]) ? $scope->getType($expr->getArgs()[3]->value) : new ConstantArrayType([], []); | 
| 2782:  |  | 
| 2783:  | 				$scope = $this->processVirtualAssign( | 
| 2784:  | 					$scope, | 
| 2785:  | 					$stmt, | 
| 2786:  | 					$arrayArg, | 
| 2787:  | 					new NativeTypeExpr( | 
| 2788:  | 						$arrayArgType->spliceArray($offsetType, $lengthType, $replacementType), | 
| 2789:  | 						$arrayArgNativeType->spliceArray($offsetType, $lengthType, $replacementType), | 
| 2790:  | 					), | 
| 2791:  | 					$nodeCallback, | 
| 2792:  | 				)->getScope(); | 
| 2793:  | 			} | 
| 2794:  |  | 
| 2795:  | 			if ( | 
| 2796:  | 				$functionReflection !== null | 
| 2797:  | 				&& in_array($functionReflection->getName(), ['sort', 'rsort', 'usort'], true) | 
| 2798:  | 				&& count($expr->getArgs()) >= 1 | 
| 2799:  | 			) { | 
| 2800:  | 				$arrayArg = $expr->getArgs()[0]->value; | 
| 2801:  |  | 
| 2802:  | 				$scope = $this->processVirtualAssign( | 
| 2803:  | 					$scope, | 
| 2804:  | 					$stmt, | 
| 2805:  | 					$arrayArg, | 
| 2806:  | 					new NativeTypeExpr($this->getArraySortPreserveListFunctionType($scope->getType($arrayArg)), $this->getArraySortPreserveListFunctionType($scope->getNativeType($arrayArg))), | 
| 2807:  | 					$nodeCallback, | 
| 2808:  | 				)->getScope(); | 
| 2809:  | 			} | 
| 2810:  |  | 
| 2811:  | 			if ( | 
| 2812:  | 				$functionReflection !== null | 
| 2813:  | 				&& in_array($functionReflection->getName(), ['natcasesort', 'natsort', 'arsort', 'asort', 'ksort', 'krsort', 'uasort', 'uksort'], true) | 
| 2814:  | 				&& count($expr->getArgs()) >= 1 | 
| 2815:  | 			) { | 
| 2816:  | 				$arrayArg = $expr->getArgs()[0]->value; | 
| 2817:  |  | 
| 2818:  | 				$scope = $this->processVirtualAssign( | 
| 2819:  | 					$scope, | 
| 2820:  | 					$stmt, | 
| 2821:  | 					$arrayArg, | 
| 2822:  | 					new NativeTypeExpr($this->getArraySortDoNotPreserveListFunctionType($scope->getType($arrayArg)), $this->getArraySortDoNotPreserveListFunctionType($scope->getNativeType($arrayArg))), | 
| 2823:  | 					$nodeCallback, | 
| 2824:  | 				)->getScope(); | 
| 2825:  | 			} | 
| 2826:  |  | 
| 2827:  | 			if ( | 
| 2828:  | 				$functionReflection !== null | 
| 2829:  | 				&& $functionReflection->getName() === 'extract' | 
| 2830:  | 			) { | 
| 2831:  | 				$extractedArg = $expr->getArgs()[0]->value; | 
| 2832:  | 				$extractedType = $scope->getType($extractedArg); | 
| 2833:  | 				$constantArrays = $extractedType->getConstantArrays(); | 
| 2834:  | 				if (count($constantArrays) > 0) { | 
| 2835:  | 					$properties = []; | 
| 2836:  | 					$optionalProperties = []; | 
| 2837:  | 					$refCount = []; | 
| 2838:  | 					foreach ($constantArrays as $constantArray) { | 
| 2839:  | 						foreach ($constantArray->getKeyTypes() as $i => $keyType) { | 
| 2840:  | 							if ($keyType->isString()->no()) { | 
| 2841:  | 								 | 
| 2842:  | 								continue; | 
| 2843:  | 							} | 
| 2844:  | 							$key = (string) $keyType->getValue(); | 
| 2845:  | 							$valueType = $constantArray->getValueTypes()[$i]; | 
| 2846:  | 							$optional = $constantArray->isOptionalKey($i); | 
| 2847:  | 							if ($optional) { | 
| 2848:  | 								$optionalProperties[] = $key; | 
| 2849:  | 							} | 
| 2850:  | 							if (isset($properties[$key])) { | 
| 2851:  | 								$properties[$key] = TypeCombinator::union($properties[$key], $valueType); | 
| 2852:  | 								$refCount[$key]++; | 
| 2853:  | 							} else { | 
| 2854:  | 								$properties[$key] = $valueType; | 
| 2855:  | 								$refCount[$key] = 1; | 
| 2856:  | 							} | 
| 2857:  | 						} | 
| 2858:  | 					} | 
| 2859:  | 					foreach ($properties as $name => $type) { | 
| 2860:  | 						$optional = in_array($name, $optionalProperties, true) || $refCount[$name] < count($constantArrays); | 
| 2861:  | 						$scope = $scope->assignVariable($name, $type, $type, $optional ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes()); | 
| 2862:  | 					} | 
| 2863:  | 				} else { | 
| 2864:  | 					$scope = $scope->afterExtractCall(); | 
| 2865:  | 				} | 
| 2866:  | 			} | 
| 2867:  |  | 
| 2868:  | 			if ( | 
| 2869:  | 				$functionReflection !== null | 
| 2870:  | 				&& in_array($functionReflection->getName(), ['clearstatcache', 'unlink'], true) | 
| 2871:  | 			) { | 
| 2872:  | 				$scope = $scope->afterClearstatcacheCall(); | 
| 2873:  | 			} | 
| 2874:  |  | 
| 2875:  | 			if ( | 
| 2876:  | 				$functionReflection !== null | 
| 2877:  | 				&& str_starts_with($functionReflection->getName(), 'openssl') | 
| 2878:  | 			) { | 
| 2879:  | 				$scope = $scope->afterOpenSslCall($functionReflection->getName()); | 
| 2880:  | 			} | 
| 2881:  |  | 
| 2882:  | 		} elseif ($expr instanceof MethodCall) { | 
| 2883:  | 			$originalScope = $scope; | 
| 2884:  | 			if ( | 
| 2885:  | 				($expr->var instanceof Expr\Closure || $expr->var instanceof Expr\ArrowFunction) | 
| 2886:  | 				&& $expr->name instanceof Node\Identifier | 
| 2887:  | 				&& strtolower($expr->name->name) === 'call' | 
| 2888:  | 				&& isset($expr->getArgs()[0]) | 
| 2889:  | 			) { | 
| 2890:  | 				$closureCallScope = $scope->enterClosureCall( | 
| 2891:  | 					$scope->getType($expr->getArgs()[0]->value), | 
| 2892:  | 					$scope->getNativeType($expr->getArgs()[0]->value), | 
| 2893:  | 				); | 
| 2894:  | 			} | 
| 2895:  |  | 
| 2896:  | 			$result = $this->processExprNode($stmt, $expr->var, $closureCallScope ?? $scope, $nodeCallback, $context->enterDeep()); | 
| 2897:  | 			$hasYield = $result->hasYield(); | 
| 2898:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2899:  | 			$impurePoints = $result->getImpurePoints(); | 
| 2900:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 2901:  | 			$scope = $result->getScope(); | 
| 2902:  | 			if (isset($closureCallScope)) { | 
| 2903:  | 				$scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope); | 
| 2904:  | 			} | 
| 2905:  | 			$parametersAcceptor = null; | 
| 2906:  | 			$methodReflection = null; | 
| 2907:  | 			$calledOnType = $scope->getType($expr->var); | 
| 2908:  | 			if ($expr->name instanceof Expr) { | 
| 2909:  | 				$methodNameResult = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 2910:  | 				$throwPoints = array_merge($throwPoints, $methodNameResult->getThrowPoints()); | 
| 2911:  | 				$scope = $methodNameResult->getScope(); | 
| 2912:  | 			} else { | 
| 2913:  | 				$methodName = $expr->name->name; | 
| 2914:  | 				$methodReflection = $scope->getMethodReflection($calledOnType, $methodName); | 
| 2915:  | 				if ($methodReflection !== null) { | 
| 2916:  | 					$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 2917:  | 						$scope, | 
| 2918:  | 						$expr->getArgs(), | 
| 2919:  | 						$methodReflection->getVariants(), | 
| 2920:  | 						$methodReflection->getNamedArgumentsVariants(), | 
| 2921:  | 					); | 
| 2922:  |  | 
| 2923:  | 					$methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope); | 
| 2924:  | 					if ($methodThrowPoint !== null) { | 
| 2925:  | 						$throwPoints[] = $methodThrowPoint; | 
| 2926:  | 					} | 
| 2927:  | 				} | 
| 2928:  | 			} | 
| 2929:  |  | 
| 2930:  | 			if ($methodReflection !== null) { | 
| 2931:  | 				$impurePoint = SimpleImpurePoint::createFromVariant($methodReflection, $parametersAcceptor, $scope, $expr->getArgs()); | 
| 2932:  | 				if ($impurePoint !== null) { | 
| 2933:  | 					$impurePoints[] = new ImpurePoint($scope, $expr, $impurePoint->getIdentifier(), $impurePoint->getDescription(), $impurePoint->isCertain()); | 
| 2934:  | 				} | 
| 2935:  | 			} else { | 
| 2936:  | 				$impurePoints[] = new ImpurePoint( | 
| 2937:  | 					$scope, | 
| 2938:  | 					$expr, | 
| 2939:  | 					'methodCall', | 
| 2940:  | 					'call to unknown method', | 
| 2941:  | 					false, | 
| 2942:  | 				); | 
| 2943:  | 			} | 
| 2944:  |  | 
| 2945:  | 			if ($parametersAcceptor !== null) { | 
| 2946:  | 				$expr = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $expr) ?? $expr; | 
| 2947:  | 				$returnType = $parametersAcceptor->getReturnType(); | 
| 2948:  | 				$isAlwaysTerminating = $returnType instanceof NeverType && $returnType->isExplicit(); | 
| 2949:  | 			} | 
| 2950:  |  | 
| 2951:  | 			$result = $this->processArgs( | 
| 2952:  | 				$stmt, | 
| 2953:  | 				$methodReflection, | 
| 2954:  | 				$methodReflection !== null ? $scope->getNakedMethod($calledOnType, $methodReflection->getName()) : null, | 
| 2955:  | 				$parametersAcceptor, | 
| 2956:  | 				$expr, | 
| 2957:  | 				$scope, | 
| 2958:  | 				$nodeCallback, | 
| 2959:  | 				$context, | 
| 2960:  | 			); | 
| 2961:  | 			$scope = $result->getScope(); | 
| 2962:  |  | 
| 2963:  | 			if ($methodReflection !== null) { | 
| 2964:  | 				$hasSideEffects = $methodReflection->hasSideEffects(); | 
| 2965:  | 				if ($hasSideEffects->yes() || $methodReflection->getName() === '__construct') { | 
| 2966:  | 					$nodeCallback(new InvalidateExprNode($expr->var), $scope); | 
| 2967:  | 					$scope = $scope->invalidateExpression($expr->var, true); | 
| 2968:  | 				} | 
| 2969:  | 				if ($parametersAcceptor !== null && !$methodReflection->isStatic()) { | 
| 2970:  | 					$selfOutType = $methodReflection->getSelfOutType(); | 
| 2971:  | 					if ($selfOutType !== null) { | 
| 2972:  | 						$scope = $scope->assignExpression( | 
| 2973:  | 							$expr->var, | 
| 2974:  | 							TemplateTypeHelper::resolveTemplateTypes( | 
| 2975:  | 								$selfOutType, | 
| 2976:  | 								$parametersAcceptor->getResolvedTemplateTypeMap(), | 
| 2977:  | 								$parametersAcceptor instanceof ExtendedParametersAcceptor ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), | 
| 2978:  | 								TemplateTypeVariance::createCovariant(), | 
| 2979:  | 							), | 
| 2980:  | 							$scope->getNativeType($expr->var), | 
| 2981:  | 						); | 
| 2982:  | 					} | 
| 2983:  | 				} | 
| 2984:  |  | 
| 2985:  | 				if ( | 
| 2986:  | 					$scope->isInClass() | 
| 2987:  | 					&& $scope->getClassReflection()->getName() === $methodReflection->getDeclaringClass()->getName() | 
| 2988:  | 					 | 
| 2989:  |  | 
| 2990:  |  | 
| 2991:  |  | 
| 2992:  |  | 
| 2993:  |  | 
| 2994:  | 					&& TypeUtils::findThisType($calledOnType) !== null | 
| 2995:  | 				) { | 
| 2996:  | 					$calledMethodScope = $this->processCalledMethod($methodReflection); | 
| 2997:  | 					if ($calledMethodScope !== null) { | 
| 2998:  | 						$scope = $scope->mergeInitializedProperties($calledMethodScope); | 
| 2999:  | 					} | 
| 3000:  | 				} | 
| 3001:  | 			} else { | 
| 3002:  | 				$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 3003:  | 			} | 
| 3004:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 3005:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3006:  | 			$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3007:  | 			$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3008:  | 		} elseif ($expr instanceof Expr\NullsafeMethodCall) { | 
| 3009:  | 			$nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var); | 
| 3010:  | 			$exprResult = $this->processExprNode($stmt, new MethodCall($expr->var, $expr->name, $expr->args, array_merge($expr->getAttributes(), ['virtualNullsafeMethodCall' => true])), $nonNullabilityResult->getScope(), $nodeCallback, $context); | 
| 3011:  | 			$scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); | 
| 3012:  |  | 
| 3013:  | 			return new ExpressionResult( | 
| 3014:  | 				$scope, | 
| 3015:  | 				$exprResult->hasYield(), | 
| 3016:  | 				false, | 
| 3017:  | 				$exprResult->getThrowPoints(), | 
| 3018:  | 				$exprResult->getImpurePoints(), | 
| 3019:  | 				static fn (): MutatingScope => $scope->filterByTruthyValue($expr), | 
| 3020:  | 				static fn (): MutatingScope => $scope->filterByFalseyValue($expr), | 
| 3021:  | 			); | 
| 3022:  | 		} elseif ($expr instanceof StaticCall) { | 
| 3023:  | 			$hasYield = false; | 
| 3024:  | 			$throwPoints = []; | 
| 3025:  | 			$impurePoints = []; | 
| 3026:  | 			$isAlwaysTerminating = false; | 
| 3027:  | 			if ($expr->class instanceof Expr) { | 
| 3028:  | 				$objectClasses = $scope->getType($expr->class)->getObjectClassNames(); | 
| 3029:  | 				if (count($objectClasses) !== 1) { | 
| 3030:  | 					$objectClasses = $scope->getType(new New_($expr->class))->getObjectClassNames(); | 
| 3031:  | 				} | 
| 3032:  | 				if (count($objectClasses) === 1) { | 
| 3033:  | 					$objectExprResult = $this->processExprNode($stmt, new StaticCall(new Name($objectClasses[0]), $expr->name, []), $scope, static function (): void { | 
| 3034:  | 					}, $context->enterDeep()); | 
| 3035:  | 					$additionalThrowPoints = $objectExprResult->getThrowPoints(); | 
| 3036:  | 				} else { | 
| 3037:  | 					$additionalThrowPoints = [ThrowPoint::createImplicit($scope, $expr)]; | 
| 3038:  | 				} | 
| 3039:  | 				$classResult = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); | 
| 3040:  | 				$hasYield = $classResult->hasYield(); | 
| 3041:  | 				$throwPoints = array_merge($throwPoints, $classResult->getThrowPoints()); | 
| 3042:  | 				$impurePoints = array_merge($impurePoints, $classResult->getImpurePoints()); | 
| 3043:  | 				$isAlwaysTerminating = $classResult->isAlwaysTerminating(); | 
| 3044:  | 				foreach ($additionalThrowPoints as $throwPoint) { | 
| 3045:  | 					$throwPoints[] = $throwPoint; | 
| 3046:  | 				} | 
| 3047:  | 				$scope = $classResult->getScope(); | 
| 3048:  | 			} | 
| 3049:  |  | 
| 3050:  | 			$parametersAcceptor = null; | 
| 3051:  | 			$methodReflection = null; | 
| 3052:  | 			if ($expr->name instanceof Expr) { | 
| 3053:  | 				$result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 3054:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 3055:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3056:  | 				$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3057:  | 				$scope = $result->getScope(); | 
| 3058:  | 			} elseif ($expr->class instanceof Name) { | 
| 3059:  | 				$classType = $scope->resolveTypeByName($expr->class); | 
| 3060:  | 				$methodName = $expr->name->name; | 
| 3061:  | 				if ($classType->hasMethod($methodName)->yes()) { | 
| 3062:  | 					$methodReflection = $classType->getMethod($methodName, $scope); | 
| 3063:  | 					$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 3064:  | 						$scope, | 
| 3065:  | 						$expr->getArgs(), | 
| 3066:  | 						$methodReflection->getVariants(), | 
| 3067:  | 						$methodReflection->getNamedArgumentsVariants(), | 
| 3068:  | 					); | 
| 3069:  |  | 
| 3070:  | 					$methodThrowPoint = $this->getStaticMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope); | 
| 3071:  | 					if ($methodThrowPoint !== null) { | 
| 3072:  | 						$throwPoints[] = $methodThrowPoint; | 
| 3073:  | 					} | 
| 3074:  |  | 
| 3075:  | 					$declaringClass = $methodReflection->getDeclaringClass(); | 
| 3076:  | 					if ( | 
| 3077:  | 						$declaringClass->getName() === 'Closure' | 
| 3078:  | 						&& strtolower($methodName) === 'bind' | 
| 3079:  | 					) { | 
| 3080:  | 						$thisType = null; | 
| 3081:  | 						$nativeThisType = null; | 
| 3082:  | 						if (isset($expr->getArgs()[1])) { | 
| 3083:  | 							$argType = $scope->getType($expr->getArgs()[1]->value); | 
| 3084:  | 							if ($argType->isNull()->yes()) { | 
| 3085:  | 								$thisType = null; | 
| 3086:  | 							} else { | 
| 3087:  | 								$thisType = $argType; | 
| 3088:  | 							} | 
| 3089:  |  | 
| 3090:  | 							$nativeArgType = $scope->getNativeType($expr->getArgs()[1]->value); | 
| 3091:  | 							if ($nativeArgType->isNull()->yes()) { | 
| 3092:  | 								$nativeThisType = null; | 
| 3093:  | 							} else { | 
| 3094:  | 								$nativeThisType = $nativeArgType; | 
| 3095:  | 							} | 
| 3096:  | 						} | 
| 3097:  | 						$scopeClasses = ['static']; | 
| 3098:  | 						if (isset($expr->getArgs()[2])) { | 
| 3099:  | 							$argValue = $expr->getArgs()[2]->value; | 
| 3100:  | 							$argValueType = $scope->getType($argValue); | 
| 3101:  |  | 
| 3102:  | 							$directClassNames = $argValueType->getObjectClassNames(); | 
| 3103:  | 							if (count($directClassNames) > 0) { | 
| 3104:  | 								$scopeClasses = $directClassNames; | 
| 3105:  | 								$thisTypes = []; | 
| 3106:  | 								foreach ($directClassNames as $directClassName) { | 
| 3107:  | 									$thisTypes[] = new ObjectType($directClassName); | 
| 3108:  | 								} | 
| 3109:  | 								$thisType = TypeCombinator::union(...$thisTypes); | 
| 3110:  | 							} else { | 
| 3111:  | 								$thisType = $argValueType->getClassStringObjectType(); | 
| 3112:  | 								$scopeClasses = $thisType->getObjectClassNames(); | 
| 3113:  | 							} | 
| 3114:  | 						} | 
| 3115:  | 						$closureBindScope = $scope->enterClosureBind($thisType, $nativeThisType, $scopeClasses); | 
| 3116:  | 					} | 
| 3117:  | 				} else { | 
| 3118:  | 					$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 3119:  | 				} | 
| 3120:  | 			} | 
| 3121:  |  | 
| 3122:  | 			if ($methodReflection !== null) { | 
| 3123:  | 				$impurePoint = SimpleImpurePoint::createFromVariant($methodReflection, $parametersAcceptor, $scope, $expr->getArgs()); | 
| 3124:  | 				if ($impurePoint !== null) { | 
| 3125:  | 					$impurePoints[] = new ImpurePoint($scope, $expr, $impurePoint->getIdentifier(), $impurePoint->getDescription(), $impurePoint->isCertain()); | 
| 3126:  | 				} | 
| 3127:  | 			} else { | 
| 3128:  | 				$impurePoints[] = new ImpurePoint( | 
| 3129:  | 					$scope, | 
| 3130:  | 					$expr, | 
| 3131:  | 					'methodCall', | 
| 3132:  | 					'call to unknown method', | 
| 3133:  | 					false, | 
| 3134:  | 				); | 
| 3135:  | 			} | 
| 3136:  |  | 
| 3137:  | 			if ($parametersAcceptor !== null) { | 
| 3138:  | 				$expr = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $expr) ?? $expr; | 
| 3139:  | 				$returnType = $parametersAcceptor->getReturnType(); | 
| 3140:  | 				$isAlwaysTerminating = $returnType instanceof NeverType && $returnType->isExplicit(); | 
| 3141:  | 			} | 
| 3142:  | 			$result = $this->processArgs($stmt, $methodReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context, $closureBindScope ?? null); | 
| 3143:  | 			$scope = $result->getScope(); | 
| 3144:  | 			$scopeFunction = $scope->getFunction(); | 
| 3145:  |  | 
| 3146:  | 			if ( | 
| 3147:  | 				$methodReflection !== null | 
| 3148:  | 				&& ( | 
| 3149:  | 					$methodReflection->hasSideEffects()->yes() | 
| 3150:  | 					|| ( | 
| 3151:  | 						!$methodReflection->isStatic() | 
| 3152:  | 						&& $methodReflection->getName() === '__construct' | 
| 3153:  | 					) | 
| 3154:  | 				) | 
| 3155:  | 				&& $scope->isInClass() | 
| 3156:  | 				&& $scope->getClassReflection()->is($methodReflection->getDeclaringClass()->getName()) | 
| 3157:  | 			) { | 
| 3158:  | 				$scope = $scope->invalidateExpression(new Variable('this'), true); | 
| 3159:  | 			} | 
| 3160:  |  | 
| 3161:  | 			if ( | 
| 3162:  | 				$methodReflection !== null | 
| 3163:  | 				&& !$methodReflection->isStatic() | 
| 3164:  | 				&& $methodReflection->getName() === '__construct' | 
| 3165:  | 				&& $scopeFunction instanceof MethodReflection | 
| 3166:  | 				&& !$scopeFunction->isStatic() | 
| 3167:  | 				&& $scope->isInClass() | 
| 3168:  | 				&& $scope->getClassReflection()->isSubclassOfClass($methodReflection->getDeclaringClass()) | 
| 3169:  | 			) { | 
| 3170:  | 				$thisType = $scope->getType(new Variable('this')); | 
| 3171:  | 				$methodClassReflection = $methodReflection->getDeclaringClass(); | 
| 3172:  | 				foreach ($methodClassReflection->getNativeReflection()->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED) as $property) { | 
| 3173:  | 					if (!$property->isPromoted() || $property->getDeclaringClass()->getName() !== $methodClassReflection->getName()) { | 
| 3174:  | 						continue; | 
| 3175:  | 					} | 
| 3176:  |  | 
| 3177:  | 					$scope = $scope->assignInitializedProperty($thisType, $property->getName()); | 
| 3178:  | 				} | 
| 3179:  | 			} | 
| 3180:  |  | 
| 3181:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 3182:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3183:  | 			$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3184:  | 			$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3185:  | 		} elseif ($expr instanceof PropertyFetch) { | 
| 3186:  | 			$scopeBeforeVar = $scope; | 
| 3187:  | 			$result = $this->processExprNode($stmt, $expr->var, $scope, $nodeCallback, $context->enterDeep()); | 
| 3188:  | 			$hasYield = $result->hasYield(); | 
| 3189:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3190:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3191:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3192:  | 			$scope = $result->getScope(); | 
| 3193:  | 			if ($expr->name instanceof Expr) { | 
| 3194:  | 				$result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 3195:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 3196:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3197:  | 				$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3198:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3199:  | 				$scope = $result->getScope(); | 
| 3200:  | 				if ($this->phpVersion->supportsPropertyHooks()) { | 
| 3201:  | 					$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 3202:  | 				} | 
| 3203:  | 			} else { | 
| 3204:  | 				$propertyName = $expr->name->toString(); | 
| 3205:  | 				$propertyHolderType = $scopeBeforeVar->getType($expr->var); | 
| 3206:  | 				$propertyReflection = $scopeBeforeVar->getInstancePropertyReflection($propertyHolderType, $propertyName); | 
| 3207:  | 				if ($propertyReflection !== null && $this->phpVersion->supportsPropertyHooks()) { | 
| 3208:  | 					$propertyDeclaringClass = $propertyReflection->getDeclaringClass(); | 
| 3209:  | 					if ($propertyDeclaringClass->hasNativeProperty($propertyName)) { | 
| 3210:  | 						$nativeProperty = $propertyDeclaringClass->getNativeProperty($propertyName); | 
| 3211:  | 						$throwPoints = array_merge($throwPoints, $this->getPropertyReadThrowPointsFromGetHook($scopeBeforeVar, $expr, $nativeProperty)); | 
| 3212:  | 					} | 
| 3213:  | 				} | 
| 3214:  | 			} | 
| 3215:  | 		} elseif ($expr instanceof Expr\NullsafePropertyFetch) { | 
| 3216:  | 			$nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var); | 
| 3217:  | 			$exprResult = $this->processExprNode($stmt, new PropertyFetch($expr->var, $expr->name, array_merge($expr->getAttributes(), ['virtualNullsafePropertyFetch' => true])), $nonNullabilityResult->getScope(), $nodeCallback, $context); | 
| 3218:  | 			$scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); | 
| 3219:  |  | 
| 3220:  | 			return new ExpressionResult( | 
| 3221:  | 				$scope, | 
| 3222:  | 				$exprResult->hasYield(), | 
| 3223:  | 				false, | 
| 3224:  | 				$exprResult->getThrowPoints(), | 
| 3225:  | 				$exprResult->getImpurePoints(), | 
| 3226:  | 				static fn (): MutatingScope => $scope->filterByTruthyValue($expr), | 
| 3227:  | 				static fn (): MutatingScope => $scope->filterByFalseyValue($expr), | 
| 3228:  | 			); | 
| 3229:  | 		} elseif ($expr instanceof StaticPropertyFetch) { | 
| 3230:  | 			$hasYield = false; | 
| 3231:  | 			$throwPoints = []; | 
| 3232:  | 			$impurePoints = [ | 
| 3233:  | 				new ImpurePoint( | 
| 3234:  | 					$scope, | 
| 3235:  | 					$expr, | 
| 3236:  | 					'staticPropertyAccess', | 
| 3237:  | 					'static property access', | 
| 3238:  | 					true, | 
| 3239:  | 				), | 
| 3240:  | 			]; | 
| 3241:  | 			$isAlwaysTerminating = false; | 
| 3242:  | 			if ($expr->class instanceof Expr) { | 
| 3243:  | 				$result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); | 
| 3244:  | 				$hasYield = $result->hasYield(); | 
| 3245:  | 				$throwPoints = $result->getThrowPoints(); | 
| 3246:  | 				$impurePoints = $result->getImpurePoints(); | 
| 3247:  | 				$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3248:  | 				$scope = $result->getScope(); | 
| 3249:  | 			} | 
| 3250:  | 			if ($expr->name instanceof Expr) { | 
| 3251:  | 				$result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 3252:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 3253:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3254:  | 				$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3255:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3256:  | 				$scope = $result->getScope(); | 
| 3257:  | 			} | 
| 3258:  | 		} elseif ($expr instanceof Expr\Closure) { | 
| 3259:  | 			$processClosureResult = $this->processClosureNode($stmt, $expr, $scope, $nodeCallback, $context, null); | 
| 3260:  |  | 
| 3261:  | 			return new ExpressionResult( | 
| 3262:  | 				$processClosureResult->getScope(), | 
| 3263:  | 				false, | 
| 3264:  | 				false, | 
| 3265:  | 				[], | 
| 3266:  | 				[], | 
| 3267:  | 			); | 
| 3268:  | 		} elseif ($expr instanceof Expr\ArrowFunction) { | 
| 3269:  | 			$result = $this->processArrowFunctionNode($stmt, $expr, $scope, $nodeCallback, null); | 
| 3270:  | 			return new ExpressionResult( | 
| 3271:  | 				$result->getScope(), | 
| 3272:  | 				$result->hasYield(), | 
| 3273:  | 				false, | 
| 3274:  | 				[], | 
| 3275:  | 				[], | 
| 3276:  | 			); | 
| 3277:  | 		} elseif ($expr instanceof ErrorSuppress) { | 
| 3278:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context); | 
| 3279:  | 			$hasYield = $result->hasYield(); | 
| 3280:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3281:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3282:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3283:  | 			$scope = $result->getScope(); | 
| 3284:  | 		} elseif ($expr instanceof Exit_) { | 
| 3285:  | 			$hasYield = false; | 
| 3286:  | 			$throwPoints = []; | 
| 3287:  | 			$kind = $expr->getAttribute('kind', Exit_::KIND_EXIT); | 
| 3288:  | 			$identifier = $kind === Exit_::KIND_DIE ? 'die' : 'exit'; | 
| 3289:  | 			$impurePoints = [ | 
| 3290:  | 				new ImpurePoint($scope, $expr, $identifier, $identifier, true), | 
| 3291:  | 			]; | 
| 3292:  | 			$isAlwaysTerminating = true; | 
| 3293:  | 			if ($expr->expr !== null) { | 
| 3294:  | 				$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3295:  | 				$hasYield = $result->hasYield(); | 
| 3296:  | 				$throwPoints = $result->getThrowPoints(); | 
| 3297:  | 				$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3298:  | 				$scope = $result->getScope(); | 
| 3299:  | 			} | 
| 3300:  | 		} elseif ($expr instanceof Node\Scalar\InterpolatedString) { | 
| 3301:  | 			$hasYield = false; | 
| 3302:  | 			$throwPoints = []; | 
| 3303:  | 			$impurePoints = []; | 
| 3304:  | 			$isAlwaysTerminating = false; | 
| 3305:  | 			foreach ($expr->parts as $part) { | 
| 3306:  | 				if (!$part instanceof Expr) { | 
| 3307:  | 					continue; | 
| 3308:  | 				} | 
| 3309:  | 				$result = $this->processExprNode($stmt, $part, $scope, $nodeCallback, $context->enterDeep()); | 
| 3310:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 3311:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3312:  | 				$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3313:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3314:  | 				$scope = $result->getScope(); | 
| 3315:  | 			} | 
| 3316:  | 		} elseif ($expr instanceof ArrayDimFetch) { | 
| 3317:  | 			$hasYield = false; | 
| 3318:  | 			$throwPoints = []; | 
| 3319:  | 			$impurePoints = []; | 
| 3320:  | 			$isAlwaysTerminating = false; | 
| 3321:  | 			if ($expr->dim !== null) { | 
| 3322:  | 				$result = $this->processExprNode($stmt, $expr->dim, $scope, $nodeCallback, $context->enterDeep()); | 
| 3323:  | 				$hasYield = $result->hasYield(); | 
| 3324:  | 				$throwPoints = $result->getThrowPoints(); | 
| 3325:  | 				$impurePoints = $result->getImpurePoints(); | 
| 3326:  | 				$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3327:  | 				$scope = $result->getScope(); | 
| 3328:  | 			} | 
| 3329:  |  | 
| 3330:  | 			$result = $this->processExprNode($stmt, $expr->var, $scope, $nodeCallback, $context->enterDeep()); | 
| 3331:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 3332:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3333:  | 			$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3334:  | 			$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3335:  | 			$scope = $result->getScope(); | 
| 3336:  | 		} elseif ($expr instanceof Array_) { | 
| 3337:  | 			$itemNodes = []; | 
| 3338:  | 			$hasYield = false; | 
| 3339:  | 			$throwPoints = []; | 
| 3340:  | 			$impurePoints = []; | 
| 3341:  | 			$isAlwaysTerminating = false; | 
| 3342:  | 			foreach ($expr->items as $arrayItem) { | 
| 3343:  | 				$itemNodes[] = new LiteralArrayItem($scope, $arrayItem); | 
| 3344:  | 				$nodeCallback($arrayItem, $scope); | 
| 3345:  | 				if ($arrayItem->key !== null) { | 
| 3346:  | 					$keyResult = $this->processExprNode($stmt, $arrayItem->key, $scope, $nodeCallback, $context->enterDeep()); | 
| 3347:  | 					$hasYield = $hasYield || $keyResult->hasYield(); | 
| 3348:  | 					$throwPoints = array_merge($throwPoints, $keyResult->getThrowPoints()); | 
| 3349:  | 					$impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints()); | 
| 3350:  | 					$isAlwaysTerminating = $isAlwaysTerminating || $keyResult->isAlwaysTerminating(); | 
| 3351:  | 					$scope = $keyResult->getScope(); | 
| 3352:  | 				} | 
| 3353:  |  | 
| 3354:  | 				$valueResult = $this->processExprNode($stmt, $arrayItem->value, $scope, $nodeCallback, $context->enterDeep()); | 
| 3355:  | 				$hasYield = $hasYield || $valueResult->hasYield(); | 
| 3356:  | 				$throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); | 
| 3357:  | 				$impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints()); | 
| 3358:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $valueResult->isAlwaysTerminating(); | 
| 3359:  | 				$scope = $valueResult->getScope(); | 
| 3360:  | 			} | 
| 3361:  | 			$nodeCallback(new LiteralArrayNode($expr, $itemNodes), $scope); | 
| 3362:  | 		} elseif ($expr instanceof BooleanAnd || $expr instanceof BinaryOp\LogicalAnd) { | 
| 3363:  | 			$leftResult = $this->processExprNode($stmt, $expr->left, $scope, $nodeCallback, $context->enterDeep()); | 
| 3364:  | 			$rightResult = $this->processExprNode($stmt, $expr->right, $leftResult->getTruthyScope(), $nodeCallback, $context); | 
| 3365:  | 			$rightExprType = $rightResult->getScope()->getType($expr->right); | 
| 3366:  | 			if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { | 
| 3367:  | 				$leftMergedWithRightScope = $leftResult->getFalseyScope(); | 
| 3368:  | 			} else { | 
| 3369:  | 				$leftMergedWithRightScope = $leftResult->getScope()->mergeWith($rightResult->getScope()); | 
| 3370:  | 			} | 
| 3371:  |  | 
| 3372:  | 			$this->callNodeCallbackWithExpression($nodeCallback, new BooleanAndNode($expr, $leftResult->getTruthyScope()), $scope, $context); | 
| 3373:  |  | 
| 3374:  | 			return new ExpressionResult( | 
| 3375:  | 				$leftMergedWithRightScope, | 
| 3376:  | 				$leftResult->hasYield() || $rightResult->hasYield(), | 
| 3377:  | 				$leftResult->isAlwaysTerminating(), | 
| 3378:  | 				array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), | 
| 3379:  | 				array_merge($leftResult->getImpurePoints(), $rightResult->getImpurePoints()), | 
| 3380:  | 				static fn (): MutatingScope => $rightResult->getScope()->filterByTruthyValue($expr), | 
| 3381:  | 				static fn (): MutatingScope => $leftMergedWithRightScope->filterByFalseyValue($expr), | 
| 3382:  | 			); | 
| 3383:  | 		} elseif ($expr instanceof BooleanOr || $expr instanceof BinaryOp\LogicalOr) { | 
| 3384:  | 			$leftResult = $this->processExprNode($stmt, $expr->left, $scope, $nodeCallback, $context->enterDeep()); | 
| 3385:  | 			$rightResult = $this->processExprNode($stmt, $expr->right, $leftResult->getFalseyScope(), $nodeCallback, $context); | 
| 3386:  | 			$rightExprType = $rightResult->getScope()->getType($expr->right); | 
| 3387:  | 			if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { | 
| 3388:  | 				$leftMergedWithRightScope = $leftResult->getTruthyScope(); | 
| 3389:  | 			} else { | 
| 3390:  | 				$leftMergedWithRightScope = $leftResult->getScope()->mergeWith($rightResult->getScope()); | 
| 3391:  | 			} | 
| 3392:  |  | 
| 3393:  | 			$this->callNodeCallbackWithExpression($nodeCallback, new BooleanOrNode($expr, $leftResult->getFalseyScope()), $scope, $context); | 
| 3394:  |  | 
| 3395:  | 			return new ExpressionResult( | 
| 3396:  | 				$leftMergedWithRightScope, | 
| 3397:  | 				$leftResult->hasYield() || $rightResult->hasYield(), | 
| 3398:  | 				$leftResult->isAlwaysTerminating(), | 
| 3399:  | 				array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), | 
| 3400:  | 				array_merge($leftResult->getImpurePoints(), $rightResult->getImpurePoints()), | 
| 3401:  | 				static fn (): MutatingScope => $leftMergedWithRightScope->filterByTruthyValue($expr), | 
| 3402:  | 				static fn (): MutatingScope => $rightResult->getScope()->filterByFalseyValue($expr), | 
| 3403:  | 			); | 
| 3404:  | 		} elseif ($expr instanceof Coalesce) { | 
| 3405:  | 			$nonNullabilityResult = $this->ensureNonNullability($scope, $expr->left); | 
| 3406:  | 			$condScope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->left); | 
| 3407:  | 			$condResult = $this->processExprNode($stmt, $expr->left, $condScope, $nodeCallback, $context->enterDeep()); | 
| 3408:  | 			$scope = $this->revertNonNullability($condResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); | 
| 3409:  | 			$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $expr->left); | 
| 3410:  |  | 
| 3411:  | 			$rightScope = $scope->filterByFalseyValue($expr); | 
| 3412:  | 			$rightResult = $this->processExprNode($stmt, $expr->right, $rightScope, $nodeCallback, $context->enterDeep()); | 
| 3413:  | 			$rightExprType = $scope->getType($expr->right); | 
| 3414:  | 			if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { | 
| 3415:  | 				$scope = $scope->filterByTruthyValue(new Expr\Isset_([$expr->left])); | 
| 3416:  | 			} else { | 
| 3417:  | 				$scope = $scope->filterByTruthyValue(new Expr\Isset_([$expr->left]))->mergeWith($rightResult->getScope()); | 
| 3418:  | 			} | 
| 3419:  |  | 
| 3420:  | 			$hasYield = $condResult->hasYield() || $rightResult->hasYield(); | 
| 3421:  | 			$throwPoints = array_merge($condResult->getThrowPoints(), $rightResult->getThrowPoints()); | 
| 3422:  | 			$impurePoints = array_merge($condResult->getImpurePoints(), $rightResult->getImpurePoints()); | 
| 3423:  | 			$isAlwaysTerminating = $condResult->isAlwaysTerminating(); | 
| 3424:  | 		} elseif ($expr instanceof BinaryOp\Pipe) { | 
| 3425:  | 			if ($expr->right instanceof FuncCall && $expr->right->isFirstClassCallable()) { | 
| 3426:  | 				$exprResult = $this->processExprNode($stmt, new FuncCall($expr->right->name, [ | 
| 3427:  | 					new Arg($expr->left, attributes: $expr->getAttribute(ReversePipeTransformerVisitor::ARG_ATTRIBUTES_NAME, [])), | 
| 3428:  | 				], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $nodeCallback, $context); | 
| 3429:  | 				$scope = $exprResult->getScope(); | 
| 3430:  | 				$hasYield = $exprResult->hasYield(); | 
| 3431:  | 				$throwPoints = $exprResult->getThrowPoints(); | 
| 3432:  | 				$impurePoints = $exprResult->getImpurePoints(); | 
| 3433:  | 				$isAlwaysTerminating = $exprResult->isAlwaysTerminating(); | 
| 3434:  | 			} elseif ($expr->right instanceof MethodCall && $expr->right->isFirstClassCallable()) { | 
| 3435:  | 				$exprResult = $this->processExprNode($stmt, new MethodCall($expr->right->var, $expr->right->name, [ | 
| 3436:  | 					new Arg($expr->left, attributes: $expr->getAttribute(ReversePipeTransformerVisitor::ARG_ATTRIBUTES_NAME, [])), | 
| 3437:  | 				], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $nodeCallback, $context); | 
| 3438:  | 				$scope = $exprResult->getScope(); | 
| 3439:  | 				$hasYield = $exprResult->hasYield(); | 
| 3440:  | 				$throwPoints = $exprResult->getThrowPoints(); | 
| 3441:  | 				$impurePoints = $exprResult->getImpurePoints(); | 
| 3442:  | 				$isAlwaysTerminating = $exprResult->isAlwaysTerminating(); | 
| 3443:  | 			} elseif ($expr->right instanceof StaticCall && $expr->right->isFirstClassCallable()) { | 
| 3444:  | 				$exprResult = $this->processExprNode($stmt, new StaticCall($expr->right->class, $expr->right->name, [ | 
| 3445:  | 					new Arg($expr->left, attributes: $expr->getAttribute(ReversePipeTransformerVisitor::ARG_ATTRIBUTES_NAME, [])), | 
| 3446:  | 				], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $nodeCallback, $context); | 
| 3447:  | 				$scope = $exprResult->getScope(); | 
| 3448:  | 				$hasYield = $exprResult->hasYield(); | 
| 3449:  | 				$throwPoints = $exprResult->getThrowPoints(); | 
| 3450:  | 				$impurePoints = $exprResult->getImpurePoints(); | 
| 3451:  | 				$isAlwaysTerminating = $exprResult->isAlwaysTerminating(); | 
| 3452:  | 			} else { | 
| 3453:  | 				$exprResult = $this->processExprNode($stmt, new FuncCall($expr->right, [ | 
| 3454:  | 					new Arg($expr->left, attributes: $expr->getAttribute(ReversePipeTransformerVisitor::ARG_ATTRIBUTES_NAME, [])), | 
| 3455:  | 				], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $nodeCallback, $context); | 
| 3456:  | 				$scope = $exprResult->getScope(); | 
| 3457:  | 				$hasYield = $exprResult->hasYield(); | 
| 3458:  | 				$throwPoints = $exprResult->getThrowPoints(); | 
| 3459:  | 				$impurePoints = $exprResult->getImpurePoints(); | 
| 3460:  | 				$isAlwaysTerminating = $exprResult->isAlwaysTerminating(); | 
| 3461:  | 			} | 
| 3462:  | 		} elseif ($expr instanceof BinaryOp) { | 
| 3463:  | 			$result = $this->processExprNode($stmt, $expr->left, $scope, $nodeCallback, $context->enterDeep()); | 
| 3464:  | 			$scope = $result->getScope(); | 
| 3465:  | 			$hasYield = $result->hasYield(); | 
| 3466:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3467:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3468:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3469:  | 			$result = $this->processExprNode($stmt, $expr->right, $scope, $nodeCallback, $context->enterDeep()); | 
| 3470:  | 			if ( | 
| 3471:  | 				($expr instanceof BinaryOp\Div || $expr instanceof BinaryOp\Mod) && | 
| 3472:  | 				!$scope->getType($expr->right)->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no() | 
| 3473:  | 			) { | 
| 3474:  | 				$throwPoints[] = ThrowPoint::createExplicit($scope, new ObjectType(DivisionByZeroError::class), $expr, false); | 
| 3475:  | 			} | 
| 3476:  | 			$scope = $result->getScope(); | 
| 3477:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 3478:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3479:  | 			$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3480:  | 			$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3481:  | 		} elseif ($expr instanceof Expr\Include_) { | 
| 3482:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3483:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3484:  | 			$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 3485:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3486:  | 			$impurePoints[] = new ImpurePoint( | 
| 3487:  | 				$scope, | 
| 3488:  | 				$expr, | 
| 3489:  | 				in_array($expr->type, [Expr\Include_::TYPE_INCLUDE, Expr\Include_::TYPE_INCLUDE_ONCE], true) ? 'include' : 'require', | 
| 3490:  | 				in_array($expr->type, [Expr\Include_::TYPE_INCLUDE, Expr\Include_::TYPE_INCLUDE_ONCE], true) ? 'include' : 'require', | 
| 3491:  | 				true, | 
| 3492:  | 			); | 
| 3493:  | 			$hasYield = $result->hasYield(); | 
| 3494:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3495:  | 			$scope = $result->getScope()->afterExtractCall(); | 
| 3496:  | 		} elseif ($expr instanceof Expr\Print_) { | 
| 3497:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3498:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3499:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3500:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3501:  | 			$impurePoints[] = new ImpurePoint($scope, $expr, 'print', 'print', true); | 
| 3502:  | 			$hasYield = $result->hasYield(); | 
| 3503:  |  | 
| 3504:  | 			$scope = $result->getScope(); | 
| 3505:  | 		} elseif ($expr instanceof Cast\String_) { | 
| 3506:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3507:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3508:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3509:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3510:  | 			$hasYield = $result->hasYield(); | 
| 3511:  |  | 
| 3512:  | 			$exprType = $scope->getType($expr->expr); | 
| 3513:  | 			$toStringMethod = $scope->getMethodReflection($exprType, '__toString'); | 
| 3514:  | 			if ($toStringMethod !== null) { | 
| 3515:  | 				if (!$toStringMethod->hasSideEffects()->no()) { | 
| 3516:  | 					$impurePoints[] = new ImpurePoint( | 
| 3517:  | 						$scope, | 
| 3518:  | 						$expr, | 
| 3519:  | 						'methodCall', | 
| 3520:  | 						sprintf('call to method %s::%s()', $toStringMethod->getDeclaringClass()->getDisplayName(), $toStringMethod->getName()), | 
| 3521:  | 						$toStringMethod->isPure()->no(), | 
| 3522:  | 					); | 
| 3523:  | 				} | 
| 3524:  | 			} | 
| 3525:  |  | 
| 3526:  | 			$scope = $result->getScope(); | 
| 3527:  | 		} elseif ( | 
| 3528:  | 			$expr instanceof Expr\BitwiseNot | 
| 3529:  | 			|| $expr instanceof Cast | 
| 3530:  | 			|| $expr instanceof Expr\Clone_ | 
| 3531:  | 			|| $expr instanceof Expr\UnaryMinus | 
| 3532:  | 			|| $expr instanceof Expr\UnaryPlus | 
| 3533:  | 		) { | 
| 3534:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3535:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3536:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3537:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3538:  | 			$hasYield = $result->hasYield(); | 
| 3539:  |  | 
| 3540:  | 			$scope = $result->getScope(); | 
| 3541:  | 		} elseif ($expr instanceof Expr\Eval_) { | 
| 3542:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3543:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3544:  | 			$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 3545:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3546:  | 			$impurePoints[] = new ImpurePoint($scope, $expr, 'eval', 'eval', true); | 
| 3547:  | 			$hasYield = $result->hasYield(); | 
| 3548:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3549:  |  | 
| 3550:  | 			$scope = $result->getScope(); | 
| 3551:  | 		} elseif ($expr instanceof Expr\YieldFrom) { | 
| 3552:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3553:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3554:  | 			$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 3555:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3556:  | 			$impurePoints[] = new ImpurePoint( | 
| 3557:  | 				$scope, | 
| 3558:  | 				$expr, | 
| 3559:  | 				'yieldFrom', | 
| 3560:  | 				'yield from', | 
| 3561:  | 				true, | 
| 3562:  | 			); | 
| 3563:  | 			$hasYield = true; | 
| 3564:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3565:  |  | 
| 3566:  | 			$scope = $result->getScope(); | 
| 3567:  | 		} elseif ($expr instanceof BooleanNot) { | 
| 3568:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3569:  | 			$scope = $result->getScope(); | 
| 3570:  | 			$hasYield = $result->hasYield(); | 
| 3571:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3572:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3573:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3574:  | 		} elseif ($expr instanceof Expr\ClassConstFetch) { | 
| 3575:  | 			$isAlwaysTerminating = false; | 
| 3576:  |  | 
| 3577:  | 			if ($expr->class instanceof Expr) { | 
| 3578:  | 				$result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); | 
| 3579:  | 				$scope = $result->getScope(); | 
| 3580:  | 				$hasYield = $result->hasYield(); | 
| 3581:  | 				$throwPoints = $result->getThrowPoints(); | 
| 3582:  | 				$impurePoints = $result->getImpurePoints(); | 
| 3583:  | 				$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3584:  | 			} else { | 
| 3585:  | 				$hasYield = false; | 
| 3586:  | 				$throwPoints = []; | 
| 3587:  | 				$impurePoints = []; | 
| 3588:  | 				$nodeCallback($expr->class, $scope); | 
| 3589:  | 			} | 
| 3590:  |  | 
| 3591:  | 			if ($expr->name instanceof Expr) { | 
| 3592:  | 				$result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 3593:  | 				$scope = $result->getScope(); | 
| 3594:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 3595:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3596:  | 				$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3597:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3598:  | 			} else { | 
| 3599:  | 				$nodeCallback($expr->name, $scope); | 
| 3600:  | 			} | 
| 3601:  | 		} elseif ($expr instanceof Expr\Empty_) { | 
| 3602:  | 			$nonNullabilityResult = $this->ensureNonNullability($scope, $expr->expr); | 
| 3603:  | 			$scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->expr); | 
| 3604:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3605:  | 			$scope = $result->getScope(); | 
| 3606:  | 			$hasYield = $result->hasYield(); | 
| 3607:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3608:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3609:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3610:  | 			$scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); | 
| 3611:  | 			$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $expr->expr); | 
| 3612:  | 		} elseif ($expr instanceof Expr\Isset_) { | 
| 3613:  | 			$hasYield = false; | 
| 3614:  | 			$throwPoints = []; | 
| 3615:  | 			$impurePoints = []; | 
| 3616:  | 			$nonNullabilityResults = []; | 
| 3617:  | 			$isAlwaysTerminating = false; | 
| 3618:  | 			foreach ($expr->vars as $var) { | 
| 3619:  | 				$nonNullabilityResult = $this->ensureNonNullability($scope, $var); | 
| 3620:  | 				$scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $var); | 
| 3621:  | 				$result = $this->processExprNode($stmt, $var, $scope, $nodeCallback, $context->enterDeep()); | 
| 3622:  | 				$scope = $result->getScope(); | 
| 3623:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 3624:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3625:  | 				$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3626:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3627:  | 				$nonNullabilityResults[] = $nonNullabilityResult; | 
| 3628:  | 			} | 
| 3629:  | 			foreach (array_reverse($expr->vars) as $var) { | 
| 3630:  | 				$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $var); | 
| 3631:  | 			} | 
| 3632:  | 			foreach (array_reverse($nonNullabilityResults) as $nonNullabilityResult) { | 
| 3633:  | 				$scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); | 
| 3634:  | 			} | 
| 3635:  | 		} elseif ($expr instanceof Instanceof_) { | 
| 3636:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3637:  | 			$scope = $result->getScope(); | 
| 3638:  | 			$hasYield = $result->hasYield(); | 
| 3639:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3640:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3641:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3642:  | 			if ($expr->class instanceof Expr) { | 
| 3643:  | 				$result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); | 
| 3644:  | 				$scope = $result->getScope(); | 
| 3645:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 3646:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3647:  | 				$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3648:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3649:  | 			} | 
| 3650:  | 		} elseif ($expr instanceof List_) { | 
| 3651:  | 			 | 
| 3652:  | 			return new ExpressionResult($scope, false, false, [], []); | 
| 3653:  | 		} elseif ($expr instanceof New_) { | 
| 3654:  | 			$parametersAcceptor = null; | 
| 3655:  | 			$constructorReflection = null; | 
| 3656:  | 			$hasYield = false; | 
| 3657:  | 			$throwPoints = []; | 
| 3658:  | 			$impurePoints = []; | 
| 3659:  | 			$isAlwaysTerminating = false; | 
| 3660:  | 			$className = null; | 
| 3661:  | 			if ($expr->class instanceof Expr || $expr->class instanceof Name) { | 
| 3662:  | 				if ($expr->class instanceof Expr) { | 
| 3663:  | 					$objectClasses = $scope->getType($expr)->getObjectClassNames(); | 
| 3664:  | 					if (count($objectClasses) === 1) { | 
| 3665:  | 						$objectExprResult = $this->processExprNode($stmt, new New_(new Name($objectClasses[0])), $scope, static function (): void { | 
| 3666:  | 						}, $context->enterDeep()); | 
| 3667:  | 						$className = $objectClasses[0]; | 
| 3668:  | 						$additionalThrowPoints = $objectExprResult->getThrowPoints(); | 
| 3669:  | 					} else { | 
| 3670:  | 						$additionalThrowPoints = [ThrowPoint::createImplicit($scope, $expr)]; | 
| 3671:  | 					} | 
| 3672:  |  | 
| 3673:  | 					$result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); | 
| 3674:  | 					$scope = $result->getScope(); | 
| 3675:  | 					$hasYield = $result->hasYield(); | 
| 3676:  | 					$throwPoints = $result->getThrowPoints(); | 
| 3677:  | 					$impurePoints = $result->getImpurePoints(); | 
| 3678:  | 					$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3679:  | 					foreach ($additionalThrowPoints as $throwPoint) { | 
| 3680:  | 						$throwPoints[] = $throwPoint; | 
| 3681:  | 					} | 
| 3682:  | 				} else { | 
| 3683:  | 					$className = $scope->resolveName($expr->class); | 
| 3684:  | 				} | 
| 3685:  |  | 
| 3686:  | 				$classReflection = null; | 
| 3687:  | 				if ($className !== null && $this->reflectionProvider->hasClass($className)) { | 
| 3688:  | 					$classReflection = $this->reflectionProvider->getClass($className); | 
| 3689:  | 					if ($classReflection->hasConstructor()) { | 
| 3690:  | 						$constructorReflection = $classReflection->getConstructor(); | 
| 3691:  | 						$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 3692:  | 							$scope, | 
| 3693:  | 							$expr->getArgs(), | 
| 3694:  | 							$constructorReflection->getVariants(), | 
| 3695:  | 							$constructorReflection->getNamedArgumentsVariants(), | 
| 3696:  | 						); | 
| 3697:  | 						$constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $parametersAcceptor, $classReflection, $expr, new Name\FullyQualified($className), $expr->getArgs(), $scope); | 
| 3698:  | 						if ($constructorThrowPoint !== null) { | 
| 3699:  | 							$throwPoints[] = $constructorThrowPoint; | 
| 3700:  | 						} | 
| 3701:  | 					} | 
| 3702:  | 				} else { | 
| 3703:  | 					$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 3704:  | 				} | 
| 3705:  |  | 
| 3706:  | 				if ($constructorReflection !== null) { | 
| 3707:  | 					if (!$constructorReflection->hasSideEffects()->no()) { | 
| 3708:  | 						$certain = $constructorReflection->isPure()->no(); | 
| 3709:  | 						$impurePoints[] = new ImpurePoint( | 
| 3710:  | 							$scope, | 
| 3711:  | 							$expr, | 
| 3712:  | 							'new', | 
| 3713:  | 							sprintf('instantiation of class %s', $constructorReflection->getDeclaringClass()->getDisplayName()), | 
| 3714:  | 							$certain, | 
| 3715:  | 						); | 
| 3716:  | 					} | 
| 3717:  | 				} elseif ($classReflection === null) { | 
| 3718:  | 					$impurePoints[] = new ImpurePoint( | 
| 3719:  | 						$scope, | 
| 3720:  | 						$expr, | 
| 3721:  | 						'new', | 
| 3722:  | 						'instantiation of unknown class', | 
| 3723:  | 						false, | 
| 3724:  | 					); | 
| 3725:  | 				} | 
| 3726:  |  | 
| 3727:  | 				if ($parametersAcceptor !== null) { | 
| 3728:  | 					$expr = ArgumentsNormalizer::reorderNewArguments($parametersAcceptor, $expr) ?? $expr; | 
| 3729:  | 				} | 
| 3730:  |  | 
| 3731:  | 			} else { | 
| 3732:  | 				$classReflection = $this->reflectionProvider->getAnonymousClassReflection($expr->class, $scope);  | 
| 3733:  | 				if ($classReflection->hasConstructor()) { | 
| 3734:  | 					$constructorReflection = $classReflection->getConstructor(); | 
| 3735:  | 					$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 3736:  | 						$scope, | 
| 3737:  | 						$expr->getArgs(), | 
| 3738:  | 						$constructorReflection->getVariants(), | 
| 3739:  | 						$constructorReflection->getNamedArgumentsVariants(), | 
| 3740:  | 					); | 
| 3741:  |  | 
| 3742:  | 					if ($constructorReflection->getDeclaringClass()->getName() === $classReflection->getName()) { | 
| 3743:  | 						$constructorResult = null; | 
| 3744:  | 						$this->processStmtNode($expr->class, $scope, static function (Node $node, Scope $scope) use ($nodeCallback, $classReflection, &$constructorResult): void { | 
| 3745:  | 							$nodeCallback($node, $scope); | 
| 3746:  | 							if (!$node instanceof MethodReturnStatementsNode) { | 
| 3747:  | 								return; | 
| 3748:  | 							} | 
| 3749:  | 							if ($constructorResult !== null) { | 
| 3750:  | 								return; | 
| 3751:  | 							} | 
| 3752:  | 							$currentClassReflection = $node->getClassReflection(); | 
| 3753:  | 							if ($currentClassReflection->getName() !== $classReflection->getName()) { | 
| 3754:  | 								return; | 
| 3755:  | 							} | 
| 3756:  | 							if (!$currentClassReflection->hasConstructor()) { | 
| 3757:  | 								return; | 
| 3758:  | 							} | 
| 3759:  | 							if ($currentClassReflection->getConstructor()->getName() !== $node->getMethodReflection()->getName()) { | 
| 3760:  | 								return; | 
| 3761:  | 							} | 
| 3762:  | 							$constructorResult = $node; | 
| 3763:  | 						}, StatementContext::createTopLevel()); | 
| 3764:  | 						if ($constructorResult !== null) { | 
| 3765:  | 							$throwPoints = $constructorResult->getStatementResult()->getThrowPoints(); | 
| 3766:  | 							$impurePoints = $constructorResult->getImpurePoints(); | 
| 3767:  | 						} | 
| 3768:  | 					} else { | 
| 3769:  | 						$this->processStmtNode($expr->class, $scope, $nodeCallback, StatementContext::createTopLevel()); | 
| 3770:  | 						$declaringClass = $constructorReflection->getDeclaringClass(); | 
| 3771:  | 						$constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $parametersAcceptor, $classReflection, $expr, new Name\FullyQualified($declaringClass->getName()), $expr->getArgs(), $scope); | 
| 3772:  | 						if ($constructorThrowPoint !== null) { | 
| 3773:  | 							$throwPoints[] = $constructorThrowPoint; | 
| 3774:  | 						} | 
| 3775:  |  | 
| 3776:  | 						if (!$constructorReflection->hasSideEffects()->no()) { | 
| 3777:  | 							$certain = $constructorReflection->isPure()->no(); | 
| 3778:  | 							$impurePoints[] = new ImpurePoint( | 
| 3779:  | 								$scope, | 
| 3780:  | 								$expr, | 
| 3781:  | 								'new', | 
| 3782:  | 								sprintf('instantiation of class %s', $declaringClass->getDisplayName()), | 
| 3783:  | 								$certain, | 
| 3784:  | 							); | 
| 3785:  | 						} | 
| 3786:  | 					} | 
| 3787:  | 				} else { | 
| 3788:  | 					$this->processStmtNode($expr->class, $scope, $nodeCallback, StatementContext::createTopLevel()); | 
| 3789:  | 				} | 
| 3790:  | 			} | 
| 3791:  |  | 
| 3792:  | 			$result = $this->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context); | 
| 3793:  | 			$scope = $result->getScope(); | 
| 3794:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 3795:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3796:  | 			$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 3797:  | 			$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 3798:  | 		} elseif ( | 
| 3799:  | 			$expr instanceof Expr\PreInc | 
| 3800:  | 			|| $expr instanceof Expr\PostInc | 
| 3801:  | 			|| $expr instanceof Expr\PreDec | 
| 3802:  | 			|| $expr instanceof Expr\PostDec | 
| 3803:  | 		) { | 
| 3804:  | 			$result = $this->processExprNode($stmt, $expr->var, $scope, $nodeCallback, $context->enterDeep()); | 
| 3805:  | 			$scope = $result->getScope(); | 
| 3806:  | 			$hasYield = $result->hasYield(); | 
| 3807:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3808:  | 			$impurePoints = $result->getImpurePoints(); | 
| 3809:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 3810:  |  | 
| 3811:  | 			$newExpr = $expr; | 
| 3812:  | 			if ($expr instanceof Expr\PostInc) { | 
| 3813:  | 				$newExpr = new Expr\PreInc($expr->var); | 
| 3814:  | 			} elseif ($expr instanceof Expr\PostDec) { | 
| 3815:  | 				$newExpr = new Expr\PreDec($expr->var); | 
| 3816:  | 			} | 
| 3817:  |  | 
| 3818:  | 			$scope = $this->processVirtualAssign( | 
| 3819:  | 				$scope, | 
| 3820:  | 				$stmt, | 
| 3821:  | 				$expr->var, | 
| 3822:  | 				$newExpr, | 
| 3823:  | 				$nodeCallback, | 
| 3824:  | 			)->getScope(); | 
| 3825:  | 		} elseif ($expr instanceof Ternary) { | 
| 3826:  | 			$ternaryCondResult = $this->processExprNode($stmt, $expr->cond, $scope, $nodeCallback, $context->enterDeep()); | 
| 3827:  | 			$throwPoints = $ternaryCondResult->getThrowPoints(); | 
| 3828:  | 			$impurePoints = $ternaryCondResult->getImpurePoints(); | 
| 3829:  | 			$isAlwaysTerminating = $ternaryCondResult->isAlwaysTerminating(); | 
| 3830:  | 			$ifTrueScope = $ternaryCondResult->getTruthyScope(); | 
| 3831:  | 			$ifFalseScope = $ternaryCondResult->getFalseyScope(); | 
| 3832:  | 			$ifTrueType = null; | 
| 3833:  | 			if ($expr->if !== null) { | 
| 3834:  | 				$ifResult = $this->processExprNode($stmt, $expr->if, $ifTrueScope, $nodeCallback, $context); | 
| 3835:  | 				$throwPoints = array_merge($throwPoints, $ifResult->getThrowPoints()); | 
| 3836:  | 				$impurePoints = array_merge($impurePoints, $ifResult->getImpurePoints()); | 
| 3837:  | 				$ifTrueScope = $ifResult->getScope(); | 
| 3838:  | 				$ifTrueType = $ifTrueScope->getType($expr->if); | 
| 3839:  | 			} | 
| 3840:  |  | 
| 3841:  | 			$elseResult = $this->processExprNode($stmt, $expr->else, $ifFalseScope, $nodeCallback, $context); | 
| 3842:  | 			$throwPoints = array_merge($throwPoints, $elseResult->getThrowPoints()); | 
| 3843:  | 			$impurePoints = array_merge($impurePoints, $elseResult->getImpurePoints()); | 
| 3844:  | 			$ifFalseScope = $elseResult->getScope(); | 
| 3845:  |  | 
| 3846:  | 			$condType = $scope->getType($expr->cond); | 
| 3847:  | 			if ($condType->isTrue()->yes()) { | 
| 3848:  | 				$finalScope = $ifTrueScope; | 
| 3849:  | 			} elseif ($condType->isFalse()->yes()) { | 
| 3850:  | 				$finalScope = $ifFalseScope; | 
| 3851:  | 			} else { | 
| 3852:  | 				if ($ifTrueType instanceof NeverType && $ifTrueType->isExplicit()) { | 
| 3853:  | 					$finalScope = $ifFalseScope; | 
| 3854:  | 				} else { | 
| 3855:  | 					$ifFalseType = $ifFalseScope->getType($expr->else); | 
| 3856:  |  | 
| 3857:  | 					if ($ifFalseType instanceof NeverType && $ifFalseType->isExplicit()) { | 
| 3858:  | 						$finalScope = $ifTrueScope; | 
| 3859:  | 					} else { | 
| 3860:  | 						$finalScope = $ifTrueScope->mergeWith($ifFalseScope); | 
| 3861:  | 					} | 
| 3862:  | 				} | 
| 3863:  | 			} | 
| 3864:  |  | 
| 3865:  | 			return new ExpressionResult( | 
| 3866:  | 				$finalScope, | 
| 3867:  | 				$ternaryCondResult->hasYield(), | 
| 3868:  | 				$isAlwaysTerminating, | 
| 3869:  | 				$throwPoints, | 
| 3870:  | 				$impurePoints, | 
| 3871:  | 				static fn (): MutatingScope => $finalScope->filterByTruthyValue($expr), | 
| 3872:  | 				static fn (): MutatingScope => $finalScope->filterByFalseyValue($expr), | 
| 3873:  | 			); | 
| 3874:  |  | 
| 3875:  | 		} elseif ($expr instanceof Expr\Yield_) { | 
| 3876:  | 			$throwPoints = [ | 
| 3877:  | 				ThrowPoint::createImplicit($scope, $expr), | 
| 3878:  | 			]; | 
| 3879:  | 			$impurePoints = [ | 
| 3880:  | 				new ImpurePoint( | 
| 3881:  | 					$scope, | 
| 3882:  | 					$expr, | 
| 3883:  | 					'yield', | 
| 3884:  | 					'yield', | 
| 3885:  | 					true, | 
| 3886:  | 				), | 
| 3887:  | 			]; | 
| 3888:  | 			$isAlwaysTerminating = false; | 
| 3889:  | 			if ($expr->key !== null) { | 
| 3890:  | 				$keyResult = $this->processExprNode($stmt, $expr->key, $scope, $nodeCallback, $context->enterDeep()); | 
| 3891:  | 				$scope = $keyResult->getScope(); | 
| 3892:  | 				$throwPoints = $keyResult->getThrowPoints(); | 
| 3893:  | 				$impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints()); | 
| 3894:  | 				$isAlwaysTerminating = $keyResult->isAlwaysTerminating(); | 
| 3895:  | 			} | 
| 3896:  | 			if ($expr->value !== null) { | 
| 3897:  | 				$valueResult = $this->processExprNode($stmt, $expr->value, $scope, $nodeCallback, $context->enterDeep()); | 
| 3898:  | 				$scope = $valueResult->getScope(); | 
| 3899:  | 				$throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); | 
| 3900:  | 				$impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints()); | 
| 3901:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $valueResult->isAlwaysTerminating(); | 
| 3902:  | 			} | 
| 3903:  | 			$hasYield = true; | 
| 3904:  | 		} elseif ($expr instanceof Expr\Match_) { | 
| 3905:  | 			$deepContext = $context->enterDeep(); | 
| 3906:  | 			$condType = $scope->getType($expr->cond); | 
| 3907:  | 			$condResult = $this->processExprNode($stmt, $expr->cond, $scope, $nodeCallback, $deepContext); | 
| 3908:  | 			$scope = $condResult->getScope(); | 
| 3909:  | 			$hasYield = $condResult->hasYield(); | 
| 3910:  | 			$throwPoints = $condResult->getThrowPoints(); | 
| 3911:  | 			$impurePoints = $condResult->getImpurePoints(); | 
| 3912:  | 			$isAlwaysTerminating = $condResult->isAlwaysTerminating(); | 
| 3913:  | 			$matchScope = $scope->enterMatch($expr); | 
| 3914:  | 			$armNodes = []; | 
| 3915:  | 			$hasDefaultCond = false; | 
| 3916:  | 			$hasAlwaysTrueCond = false; | 
| 3917:  | 			$arms = $expr->arms; | 
| 3918:  | 			if ($condType->isEnum()->yes()) { | 
| 3919:  | 				 | 
| 3920:  | 				 | 
| 3921:  | 				 | 
| 3922:  | 				 | 
| 3923:  | 				$enumCases = $condType->getEnumCases(); | 
| 3924:  | 				if (count($enumCases) > 0) { | 
| 3925:  | 					$indexedEnumCases = []; | 
| 3926:  | 					foreach ($enumCases as $enumCase) { | 
| 3927:  | 						$indexedEnumCases[strtolower($enumCase->getClassName())][$enumCase->getEnumCaseName()] = $enumCase; | 
| 3928:  | 					} | 
| 3929:  | 					$unusedIndexedEnumCases = $indexedEnumCases; | 
| 3930:  | 					foreach ($arms as $i => $arm) { | 
| 3931:  | 						if ($arm->conds === null) { | 
| 3932:  | 							continue; | 
| 3933:  | 						} | 
| 3934:  |  | 
| 3935:  | 						$condNodes = []; | 
| 3936:  | 						$conditionCases = []; | 
| 3937:  | 						$conditionExprs = []; | 
| 3938:  | 						foreach ($arm->conds as $cond) { | 
| 3939:  | 							if (!$cond instanceof Expr\ClassConstFetch) { | 
| 3940:  | 								continue 2; | 
| 3941:  | 							} | 
| 3942:  | 							if (!$cond->class instanceof Name) { | 
| 3943:  | 								continue 2; | 
| 3944:  | 							} | 
| 3945:  | 							if (!$cond->name instanceof Node\Identifier) { | 
| 3946:  | 								continue 2; | 
| 3947:  | 							} | 
| 3948:  | 							$fetchedClassName = $scope->resolveName($cond->class); | 
| 3949:  | 							$loweredFetchedClassName = strtolower($fetchedClassName); | 
| 3950:  | 							if (!array_key_exists($loweredFetchedClassName, $indexedEnumCases)) { | 
| 3951:  | 								continue 2; | 
| 3952:  | 							} | 
| 3953:  |  | 
| 3954:  | 							if (!array_key_exists($loweredFetchedClassName, $unusedIndexedEnumCases)) { | 
| 3955:  | 								throw new ShouldNotHappenException(); | 
| 3956:  | 							} | 
| 3957:  |  | 
| 3958:  | 							$caseName = $cond->name->toString(); | 
| 3959:  | 							if (!array_key_exists($caseName, $indexedEnumCases[$loweredFetchedClassName])) { | 
| 3960:  | 								continue 2; | 
| 3961:  | 							} | 
| 3962:  |  | 
| 3963:  | 							$enumCase = $indexedEnumCases[$loweredFetchedClassName][$caseName]; | 
| 3964:  | 							$conditionCases[] = $enumCase; | 
| 3965:  | 							$armConditionScope = $matchScope; | 
| 3966:  | 							if (!array_key_exists($caseName, $unusedIndexedEnumCases[$loweredFetchedClassName])) { | 
| 3967:  | 								 | 
| 3968:  | 								$armConditionScope = $armConditionScope->removeTypeFromExpression( | 
| 3969:  | 									$expr->cond, | 
| 3970:  | 									$enumCase, | 
| 3971:  | 								); | 
| 3972:  | 							} else { | 
| 3973:  | 								$unusedCasesCount = 0; | 
| 3974:  | 								foreach ($unusedIndexedEnumCases as $cases) { | 
| 3975:  | 									$unusedCasesCount += count($cases); | 
| 3976:  | 								} | 
| 3977:  | 								if ($unusedCasesCount === 1) { | 
| 3978:  | 									$hasAlwaysTrueCond = true; | 
| 3979:  |  | 
| 3980:  | 									 | 
| 3981:  | 									$armConditionScope = $armConditionScope->addTypeToExpression( | 
| 3982:  | 										$expr->cond, | 
| 3983:  | 										$enumCase, | 
| 3984:  | 									); | 
| 3985:  | 								} | 
| 3986:  | 							} | 
| 3987:  |  | 
| 3988:  | 							$this->processExprNode($stmt, $cond, $armConditionScope, $nodeCallback, $deepContext); | 
| 3989:  |  | 
| 3990:  | 							$condNodes[] = new MatchExpressionArmCondition( | 
| 3991:  | 								$cond, | 
| 3992:  | 								$armConditionScope, | 
| 3993:  | 								$cond->getStartLine(), | 
| 3994:  | 							); | 
| 3995:  | 							$conditionExprs[] = $cond; | 
| 3996:  |  | 
| 3997:  | 							unset($unusedIndexedEnumCases[$loweredFetchedClassName][$caseName]); | 
| 3998:  | 						} | 
| 3999:  |  | 
| 4000:  | 						$conditionCasesCount = count($conditionCases); | 
| 4001:  | 						if ($conditionCasesCount === 0) { | 
| 4002:  | 							throw new ShouldNotHappenException(); | 
| 4003:  | 						} elseif ($conditionCasesCount === 1) { | 
| 4004:  | 							$conditionCaseType = $conditionCases[0]; | 
| 4005:  | 						} else { | 
| 4006:  | 							$conditionCaseType = new UnionType($conditionCases); | 
| 4007:  | 						} | 
| 4008:  |  | 
| 4009:  | 						$filteringExpr = $this->getFilteringExprForMatchArm($expr, $conditionExprs); | 
| 4010:  | 						$matchArmBodyScope = $matchScope->addTypeToExpression( | 
| 4011:  | 							$expr->cond, | 
| 4012:  | 							$conditionCaseType, | 
| 4013:  | 						)->filterByTruthyValue($filteringExpr); | 
| 4014:  | 						$matchArmBody = new MatchExpressionArmBody($matchArmBodyScope, $arm->body); | 
| 4015:  | 						$armNodes[$i] = new MatchExpressionArm($matchArmBody, $condNodes, $arm->getStartLine()); | 
| 4016:  |  | 
| 4017:  | 						$armResult = $this->processExprNode( | 
| 4018:  | 							$stmt, | 
| 4019:  | 							$arm->body, | 
| 4020:  | 							$matchArmBodyScope, | 
| 4021:  | 							$nodeCallback, | 
| 4022:  | 							ExpressionContext::createTopLevel(), | 
| 4023:  | 						); | 
| 4024:  | 						$armScope = $armResult->getScope(); | 
| 4025:  | 						$scope = $scope->mergeWith($armScope); | 
| 4026:  | 						$hasYield = $hasYield || $armResult->hasYield(); | 
| 4027:  | 						$throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); | 
| 4028:  | 						$impurePoints = array_merge($impurePoints, $armResult->getImpurePoints()); | 
| 4029:  |  | 
| 4030:  | 						unset($arms[$i]); | 
| 4031:  | 					} | 
| 4032:  |  | 
| 4033:  | 					$remainingCases = []; | 
| 4034:  | 					foreach ($unusedIndexedEnumCases as $cases) { | 
| 4035:  | 						foreach ($cases as $case) { | 
| 4036:  | 							$remainingCases[] = $case; | 
| 4037:  | 						} | 
| 4038:  | 					} | 
| 4039:  |  | 
| 4040:  | 					$remainingCasesCount = count($remainingCases); | 
| 4041:  | 					if ($remainingCasesCount === 0) { | 
| 4042:  | 						$remainingType = new NeverType(); | 
| 4043:  | 					} elseif ($remainingCasesCount === 1) { | 
| 4044:  | 						$remainingType = $remainingCases[0]; | 
| 4045:  | 					} else { | 
| 4046:  | 						$remainingType = new UnionType($remainingCases); | 
| 4047:  | 					} | 
| 4048:  |  | 
| 4049:  | 					$matchScope = $matchScope->addTypeToExpression($expr->cond, $remainingType); | 
| 4050:  | 				} | 
| 4051:  | 			} | 
| 4052:  | 			foreach ($arms as $i => $arm) { | 
| 4053:  | 				if ($arm->conds === null) { | 
| 4054:  | 					$hasDefaultCond = true; | 
| 4055:  | 					$matchArmBody = new MatchExpressionArmBody($matchScope, $arm->body); | 
| 4056:  | 					$armNodes[$i] = new MatchExpressionArm($matchArmBody, [], $arm->getStartLine()); | 
| 4057:  | 					$armResult = $this->processExprNode($stmt, $arm->body, $matchScope, $nodeCallback, ExpressionContext::createTopLevel()); | 
| 4058:  | 					$matchScope = $armResult->getScope(); | 
| 4059:  | 					$hasYield = $hasYield || $armResult->hasYield(); | 
| 4060:  | 					$throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); | 
| 4061:  | 					$impurePoints = array_merge($impurePoints, $armResult->getImpurePoints()); | 
| 4062:  | 					$scope = $scope->mergeWith($matchScope); | 
| 4063:  | 					continue; | 
| 4064:  | 				} | 
| 4065:  |  | 
| 4066:  | 				if (count($arm->conds) === 0) { | 
| 4067:  | 					throw new ShouldNotHappenException(); | 
| 4068:  | 				} | 
| 4069:  |  | 
| 4070:  | 				$filteringExprs = []; | 
| 4071:  | 				$armCondScope = $matchScope; | 
| 4072:  | 				$condNodes = []; | 
| 4073:  | 				foreach ($arm->conds as $armCond) { | 
| 4074:  | 					$condNodes[] = new MatchExpressionArmCondition($armCond, $armCondScope, $armCond->getStartLine()); | 
| 4075:  | 					$armCondResult = $this->processExprNode($stmt, $armCond, $armCondScope, $nodeCallback, $deepContext); | 
| 4076:  | 					$hasYield = $hasYield || $armCondResult->hasYield(); | 
| 4077:  | 					$throwPoints = array_merge($throwPoints, $armCondResult->getThrowPoints()); | 
| 4078:  | 					$impurePoints = array_merge($impurePoints, $armCondResult->getImpurePoints()); | 
| 4079:  | 					$armCondExpr = new BinaryOp\Identical($expr->cond, $armCond); | 
| 4080:  | 					$armCondResultScope = $armCondResult->getScope(); | 
| 4081:  | 					$armCondType = $this->treatPhpDocTypesAsCertain ? $armCondResultScope->getType($armCondExpr) : $armCondResultScope->getNativeType($armCondExpr); | 
| 4082:  | 					if ($armCondType->isTrue()->yes()) { | 
| 4083:  | 						$hasAlwaysTrueCond = true; | 
| 4084:  | 					} | 
| 4085:  | 					$armCondScope = $armCondResult->getScope()->filterByFalseyValue($armCondExpr); | 
| 4086:  | 					$filteringExprs[] = $armCond; | 
| 4087:  | 				} | 
| 4088:  |  | 
| 4089:  | 				$filteringExpr = $this->getFilteringExprForMatchArm($expr, $filteringExprs); | 
| 4090:  |  | 
| 4091:  | 				$bodyScope = $this->processExprNode($stmt, $filteringExpr, $matchScope, static function (): void { | 
| 4092:  | 				}, $deepContext)->getTruthyScope(); | 
| 4093:  | 				$matchArmBody = new MatchExpressionArmBody($bodyScope, $arm->body); | 
| 4094:  | 				$armNodes[$i] = new MatchExpressionArm($matchArmBody, $condNodes, $arm->getStartLine()); | 
| 4095:  |  | 
| 4096:  | 				$armResult = $this->processExprNode( | 
| 4097:  | 					$stmt, | 
| 4098:  | 					$arm->body, | 
| 4099:  | 					$bodyScope, | 
| 4100:  | 					$nodeCallback, | 
| 4101:  | 					ExpressionContext::createTopLevel(), | 
| 4102:  | 				); | 
| 4103:  | 				$armScope = $armResult->getScope(); | 
| 4104:  | 				$scope = $scope->mergeWith($armScope); | 
| 4105:  | 				$hasYield = $hasYield || $armResult->hasYield(); | 
| 4106:  | 				$throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); | 
| 4107:  | 				$impurePoints = array_merge($impurePoints, $armResult->getImpurePoints()); | 
| 4108:  | 				$matchScope = $matchScope->filterByFalseyValue($filteringExpr); | 
| 4109:  | 			} | 
| 4110:  |  | 
| 4111:  | 			$remainingType = $matchScope->getType($expr->cond); | 
| 4112:  | 			if (!$hasDefaultCond && !$hasAlwaysTrueCond && !$remainingType instanceof NeverType) { | 
| 4113:  | 				$throwPoints[] = ThrowPoint::createExplicit($scope, new ObjectType(UnhandledMatchError::class), $expr, false); | 
| 4114:  | 			} | 
| 4115:  |  | 
| 4116:  | 			ksort($armNodes, SORT_NUMERIC); | 
| 4117:  |  | 
| 4118:  | 			$nodeCallback(new MatchExpressionNode($expr->cond, array_values($armNodes), $expr, $matchScope), $scope); | 
| 4119:  | 		} elseif ($expr instanceof AlwaysRememberedExpr) { | 
| 4120:  | 			$result = $this->processExprNode($stmt, $expr->getExpr(), $scope, $nodeCallback, $context); | 
| 4121:  | 			$hasYield = $result->hasYield(); | 
| 4122:  | 			$throwPoints = $result->getThrowPoints(); | 
| 4123:  | 			$impurePoints = $result->getImpurePoints(); | 
| 4124:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 4125:  | 			$scope = $result->getScope(); | 
| 4126:  | 		} elseif ($expr instanceof Expr\Throw_) { | 
| 4127:  | 			$hasYield = false; | 
| 4128:  | 			$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 4129:  | 			$throwPoints = $result->getThrowPoints(); | 
| 4130:  | 			$impurePoints = $result->getImpurePoints(); | 
| 4131:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 4132:  | 			$throwPoints[] = ThrowPoint::createExplicit($scope, $scope->getType($expr->expr), $expr, false); | 
| 4133:  | 		} elseif ($expr instanceof FunctionCallableNode) { | 
| 4134:  | 			$throwPoints = []; | 
| 4135:  | 			$impurePoints = []; | 
| 4136:  | 			$hasYield = false; | 
| 4137:  | 			$isAlwaysTerminating = false; | 
| 4138:  | 			if ($expr->getName() instanceof Expr) { | 
| 4139:  | 				$result = $this->processExprNode($stmt, $expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 4140:  | 				$scope = $result->getScope(); | 
| 4141:  | 				$hasYield = $result->hasYield(); | 
| 4142:  | 				$throwPoints = $result->getThrowPoints(); | 
| 4143:  | 				$impurePoints = $result->getImpurePoints(); | 
| 4144:  | 				$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 4145:  | 			} | 
| 4146:  | 		} elseif ($expr instanceof MethodCallableNode) { | 
| 4147:  | 			$result = $this->processExprNode($stmt, $expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 4148:  | 			$scope = $result->getScope(); | 
| 4149:  | 			$hasYield = $result->hasYield(); | 
| 4150:  | 			$throwPoints = $result->getThrowPoints(); | 
| 4151:  | 			$impurePoints = $result->getImpurePoints(); | 
| 4152:  | 			$isAlwaysTerminating = false; | 
| 4153:  | 			if ($expr->getName() instanceof Expr) { | 
| 4154:  | 				$nameResult = $this->processExprNode($stmt, $expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 4155:  | 				$scope = $nameResult->getScope(); | 
| 4156:  | 				$hasYield = $hasYield || $nameResult->hasYield(); | 
| 4157:  | 				$throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints()); | 
| 4158:  | 				$impurePoints = array_merge($impurePoints, $nameResult->getImpurePoints()); | 
| 4159:  | 				$isAlwaysTerminating = $nameResult->isAlwaysTerminating(); | 
| 4160:  | 			} | 
| 4161:  | 		} elseif ($expr instanceof StaticMethodCallableNode) { | 
| 4162:  | 			$throwPoints = []; | 
| 4163:  | 			$impurePoints = []; | 
| 4164:  | 			$hasYield = false; | 
| 4165:  | 			$isAlwaysTerminating = false; | 
| 4166:  | 			if ($expr->getClass() instanceof Expr) { | 
| 4167:  | 				$classResult = $this->processExprNode($stmt, $expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 4168:  | 				$scope = $classResult->getScope(); | 
| 4169:  | 				$hasYield = $classResult->hasYield(); | 
| 4170:  | 				$throwPoints = $classResult->getThrowPoints(); | 
| 4171:  | 				$impurePoints = $classResult->getImpurePoints(); | 
| 4172:  | 				$isAlwaysTerminating = $classResult->isAlwaysTerminating(); | 
| 4173:  | 			} | 
| 4174:  | 			if ($expr->getName() instanceof Expr) { | 
| 4175:  | 				$nameResult = $this->processExprNode($stmt, $expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 4176:  | 				$scope = $nameResult->getScope(); | 
| 4177:  | 				$hasYield = $hasYield || $nameResult->hasYield(); | 
| 4178:  | 				$throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints()); | 
| 4179:  | 				$impurePoints = array_merge($impurePoints, $nameResult->getImpurePoints()); | 
| 4180:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $nameResult->isAlwaysTerminating(); | 
| 4181:  | 			} | 
| 4182:  | 		} elseif ($expr instanceof InstantiationCallableNode) { | 
| 4183:  | 			$throwPoints = []; | 
| 4184:  | 			$impurePoints = []; | 
| 4185:  | 			$hasYield = false; | 
| 4186:  | 			$isAlwaysTerminating = false; | 
| 4187:  | 			if ($expr->getClass() instanceof Expr) { | 
| 4188:  | 				$classResult = $this->processExprNode($stmt, $expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 4189:  | 				$scope = $classResult->getScope(); | 
| 4190:  | 				$hasYield = $classResult->hasYield(); | 
| 4191:  | 				$throwPoints = $classResult->getThrowPoints(); | 
| 4192:  | 				$impurePoints = $classResult->getImpurePoints(); | 
| 4193:  | 				$isAlwaysTerminating = $classResult->isAlwaysTerminating(); | 
| 4194:  | 			} | 
| 4195:  | 		} elseif ($expr instanceof Node\Scalar) { | 
| 4196:  | 			$hasYield = false; | 
| 4197:  | 			$throwPoints = []; | 
| 4198:  | 			$impurePoints = []; | 
| 4199:  | 			$isAlwaysTerminating = false; | 
| 4200:  | 		} elseif ($expr instanceof ConstFetch) { | 
| 4201:  | 			$hasYield = false; | 
| 4202:  | 			$throwPoints = []; | 
| 4203:  | 			$impurePoints = []; | 
| 4204:  | 			$isAlwaysTerminating = false; | 
| 4205:  | 			$nodeCallback($expr->name, $scope); | 
| 4206:  | 		} else { | 
| 4207:  | 			$hasYield = false; | 
| 4208:  | 			$throwPoints = []; | 
| 4209:  | 			$impurePoints = []; | 
| 4210:  | 			$isAlwaysTerminating = false; | 
| 4211:  | 		} | 
| 4212:  |  | 
| 4213:  | 		return new ExpressionResult( | 
| 4214:  | 			$scope, | 
| 4215:  | 			$hasYield, | 
| 4216:  | 			$isAlwaysTerminating, | 
| 4217:  | 			$throwPoints, | 
| 4218:  | 			$impurePoints, | 
| 4219:  | 			static fn (): MutatingScope => $scope->filterByTruthyValue($expr), | 
| 4220:  | 			static fn (): MutatingScope => $scope->filterByFalseyValue($expr), | 
| 4221:  | 		); | 
| 4222:  | 	} | 
| 4223:  |  | 
| 4224:  | 	private function getArrayFunctionAppendingType(FunctionReflection $functionReflection, Scope $scope, FuncCall $expr): Type | 
| 4225:  | 	{ | 
| 4226:  | 		$arrayArg = $expr->getArgs()[0]->value; | 
| 4227:  | 		$arrayType = $scope->getType($arrayArg); | 
| 4228:  | 		$callArgs = array_slice($expr->getArgs(), 1); | 
| 4229:  |  | 
| 4230:  | 		 | 
| 4231:  |  | 
| 4232:  |  | 
| 4233:  |  | 
| 4234:  | 		$setOffsetValueTypes = static function (Scope $scope, array $callArgs, callable $setOffsetValueType, ?bool &$nonConstantArrayWasUnpacked = null): void { | 
| 4235:  | 			foreach ($callArgs as $callArg) { | 
| 4236:  | 				$callArgType = $scope->getType($callArg->value); | 
| 4237:  | 				if ($callArg->unpack) { | 
| 4238:  | 					$constantArrays = $callArgType->getConstantArrays(); | 
| 4239:  | 					if (count($constantArrays) === 1) { | 
| 4240:  | 						$iterableValueTypes = $constantArrays[0]->getValueTypes(); | 
| 4241:  | 					} else { | 
| 4242:  | 						$iterableValueTypes = [$callArgType->getIterableValueType()]; | 
| 4243:  | 						$nonConstantArrayWasUnpacked = true; | 
| 4244:  | 					} | 
| 4245:  |  | 
| 4246:  | 					$isOptional = !$callArgType->isIterableAtLeastOnce()->yes(); | 
| 4247:  | 					foreach ($iterableValueTypes as $iterableValueType) { | 
| 4248:  | 						if ($iterableValueType instanceof UnionType) { | 
| 4249:  | 							foreach ($iterableValueType->getTypes() as $innerType) { | 
| 4250:  | 								$setOffsetValueType(null, $innerType, $isOptional); | 
| 4251:  | 							} | 
| 4252:  | 						} else { | 
| 4253:  | 							$setOffsetValueType(null, $iterableValueType, $isOptional); | 
| 4254:  | 						} | 
| 4255:  | 					} | 
| 4256:  | 					continue; | 
| 4257:  | 				} | 
| 4258:  | 				$setOffsetValueType(null, $callArgType, false); | 
| 4259:  | 			} | 
| 4260:  | 		}; | 
| 4261:  |  | 
| 4262:  | 		$constantArrays = $arrayType->getConstantArrays(); | 
| 4263:  | 		if (count($constantArrays) > 0) { | 
| 4264:  | 			$newArrayTypes = []; | 
| 4265:  | 			$prepend = $functionReflection->getName() === 'array_unshift'; | 
| 4266:  | 			foreach ($constantArrays as $constantArray) { | 
| 4267:  | 				$arrayTypeBuilder = $prepend ? ConstantArrayTypeBuilder::createEmpty() : ConstantArrayTypeBuilder::createFromConstantArray($constantArray); | 
| 4268:  |  | 
| 4269:  | 				$setOffsetValueTypes( | 
| 4270:  | 					$scope, | 
| 4271:  | 					$callArgs, | 
| 4272:  | 					static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arrayTypeBuilder): void { | 
| 4273:  | 						$arrayTypeBuilder->setOffsetValueType($offsetType, $valueType, $optional); | 
| 4274:  | 					}, | 
| 4275:  | 					$nonConstantArrayWasUnpacked, | 
| 4276:  | 				); | 
| 4277:  |  | 
| 4278:  | 				if ($prepend) { | 
| 4279:  | 					$keyTypes = $constantArray->getKeyTypes(); | 
| 4280:  | 					$valueTypes = $constantArray->getValueTypes(); | 
| 4281:  | 					foreach ($keyTypes as $k => $keyType) { | 
| 4282:  | 						$arrayTypeBuilder->setOffsetValueType( | 
| 4283:  | 							count($keyType->getConstantStrings()) === 1 ? $keyType->getConstantStrings()[0] : null, | 
| 4284:  | 							$valueTypes[$k], | 
| 4285:  | 							$constantArray->isOptionalKey($k), | 
| 4286:  | 						); | 
| 4287:  | 					} | 
| 4288:  | 				} | 
| 4289:  |  | 
| 4290:  | 				$constantArray = $arrayTypeBuilder->getArray(); | 
| 4291:  |  | 
| 4292:  | 				if ($constantArray->isConstantArray()->yes() && $nonConstantArrayWasUnpacked) { | 
| 4293:  | 					$array = new ArrayType($constantArray->generalize(GeneralizePrecision::lessSpecific())->getIterableKeyType(), $constantArray->getIterableValueType()); | 
| 4294:  | 					$isList = $constantArray->isList()->yes(); | 
| 4295:  | 					$constantArray = $constantArray->isIterableAtLeastOnce()->yes() | 
| 4296:  | 						? TypeCombinator::intersect($array, new NonEmptyArrayType()) | 
| 4297:  | 						: $array; | 
| 4298:  | 					$constantArray = $isList | 
| 4299:  | 						? TypeCombinator::intersect($constantArray, new AccessoryArrayListType()) | 
| 4300:  | 						: $constantArray; | 
| 4301:  | 				} | 
| 4302:  |  | 
| 4303:  | 				$newArrayTypes[] = $constantArray; | 
| 4304:  | 			} | 
| 4305:  |  | 
| 4306:  | 			return TypeCombinator::union(...$newArrayTypes); | 
| 4307:  | 		} | 
| 4308:  |  | 
| 4309:  | 		$setOffsetValueTypes( | 
| 4310:  | 			$scope, | 
| 4311:  | 			$callArgs, | 
| 4312:  | 			static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arrayType): void { | 
| 4313:  | 				$isIterableAtLeastOnce = $arrayType->isIterableAtLeastOnce()->yes() || !$optional; | 
| 4314:  | 				$arrayType = $arrayType->setOffsetValueType($offsetType, $valueType); | 
| 4315:  | 				if ($isIterableAtLeastOnce) { | 
| 4316:  | 					return; | 
| 4317:  | 				} | 
| 4318:  |  | 
| 4319:  | 				$arrayType = TypeCombinator::union($arrayType, new ConstantArrayType([], [])); | 
| 4320:  | 			}, | 
| 4321:  | 		); | 
| 4322:  |  | 
| 4323:  | 		return $arrayType; | 
| 4324:  | 	} | 
| 4325:  |  | 
| 4326:  | 	private function getArraySortPreserveListFunctionType(Type $type): Type | 
| 4327:  | 	{ | 
| 4328:  | 		$isIterableAtLeastOnce = $type->isIterableAtLeastOnce(); | 
| 4329:  | 		if ($isIterableAtLeastOnce->no()) { | 
| 4330:  | 			return $type; | 
| 4331:  | 		} | 
| 4332:  |  | 
| 4333:  | 		return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($isIterableAtLeastOnce): Type { | 
| 4334:  | 			if ($type instanceof UnionType || $type instanceof IntersectionType) { | 
| 4335:  | 				return $traverse($type); | 
| 4336:  | 			} | 
| 4337:  |  | 
| 4338:  | 			if (!$type instanceof ArrayType && !$type instanceof ConstantArrayType) { | 
| 4339:  | 				return $type; | 
| 4340:  | 			} | 
| 4341:  |  | 
| 4342:  | 			$newArrayType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $type->getIterableValueType()), new AccessoryArrayListType()); | 
| 4343:  | 			if ($isIterableAtLeastOnce->yes()) { | 
| 4344:  | 				$newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType()); | 
| 4345:  | 			} | 
| 4346:  |  | 
| 4347:  | 			return $newArrayType; | 
| 4348:  | 		}); | 
| 4349:  | 	} | 
| 4350:  |  | 
| 4351:  | 	private function getArraySortDoNotPreserveListFunctionType(Type $type): Type | 
| 4352:  | 	{ | 
| 4353:  | 		$isIterableAtLeastOnce = $type->isIterableAtLeastOnce(); | 
| 4354:  | 		if ($isIterableAtLeastOnce->no()) { | 
| 4355:  | 			return $type; | 
| 4356:  | 		} | 
| 4357:  |  | 
| 4358:  | 		return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($isIterableAtLeastOnce): Type { | 
| 4359:  | 			if ($type instanceof UnionType) { | 
| 4360:  | 				return $traverse($type); | 
| 4361:  | 			} | 
| 4362:  |  | 
| 4363:  | 			$constantArrays = $type->getConstantArrays(); | 
| 4364:  | 			if (count($constantArrays) > 0) { | 
| 4365:  | 				$types = []; | 
| 4366:  | 				foreach ($constantArrays as $constantArray) { | 
| 4367:  | 					$types[] = new ConstantArrayType( | 
| 4368:  | 						$constantArray->getKeyTypes(), | 
| 4369:  | 						$constantArray->getValueTypes(), | 
| 4370:  | 						$constantArray->getNextAutoIndexes(), | 
| 4371:  | 						$constantArray->getOptionalKeys(), | 
| 4372:  | 						$constantArray->isList()->and(TrinaryLogic::createMaybe()), | 
| 4373:  | 					); | 
| 4374:  | 				} | 
| 4375:  |  | 
| 4376:  | 				return TypeCombinator::union(...$types); | 
| 4377:  | 			} | 
| 4378:  |  | 
| 4379:  | 			$newArrayType = new ArrayType($type->getIterableKeyType(), $type->getIterableValueType()); | 
| 4380:  | 			if ($isIterableAtLeastOnce->yes()) { | 
| 4381:  | 				$newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType()); | 
| 4382:  | 			} | 
| 4383:  |  | 
| 4384:  | 			return $newArrayType; | 
| 4385:  | 		}); | 
| 4386:  | 	} | 
| 4387:  |  | 
| 4388:  | 	private function getFunctionThrowPoint( | 
| 4389:  | 		FunctionReflection $functionReflection, | 
| 4390:  | 		?ParametersAcceptor $parametersAcceptor, | 
| 4391:  | 		FuncCall $funcCall, | 
| 4392:  | 		MutatingScope $scope, | 
| 4393:  | 	): ?ThrowPoint | 
| 4394:  | 	{ | 
| 4395:  | 		$normalizedFuncCall = $funcCall; | 
| 4396:  | 		if ($parametersAcceptor !== null) { | 
| 4397:  | 			$normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $funcCall); | 
| 4398:  | 		} | 
| 4399:  |  | 
| 4400:  | 		if ($normalizedFuncCall !== null) { | 
| 4401:  | 			foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicFunctionThrowTypeExtensions() as $extension) { | 
| 4402:  | 				if (!$extension->isFunctionSupported($functionReflection)) { | 
| 4403:  | 					continue; | 
| 4404:  | 				} | 
| 4405:  |  | 
| 4406:  | 				$throwType = $extension->getThrowTypeFromFunctionCall($functionReflection, $normalizedFuncCall, $scope); | 
| 4407:  | 				if ($throwType === null) { | 
| 4408:  | 					return null; | 
| 4409:  | 				} | 
| 4410:  |  | 
| 4411:  | 				return ThrowPoint::createExplicit($scope, $throwType, $funcCall, false); | 
| 4412:  | 			} | 
| 4413:  | 		} | 
| 4414:  |  | 
| 4415:  | 		$throwType = $functionReflection->getThrowType(); | 
| 4416:  | 		if ($throwType === null && $parametersAcceptor !== null) { | 
| 4417:  | 			$returnType = $parametersAcceptor->getReturnType(); | 
| 4418:  | 			if ($returnType instanceof NeverType && $returnType->isExplicit()) { | 
| 4419:  | 				$throwType = new ObjectType(Throwable::class); | 
| 4420:  | 			} | 
| 4421:  | 		} | 
| 4422:  |  | 
| 4423:  | 		if ($throwType !== null) { | 
| 4424:  | 			if (!$throwType->isVoid()->yes()) { | 
| 4425:  | 				return ThrowPoint::createExplicit($scope, $throwType, $funcCall, true); | 
| 4426:  | 			} | 
| 4427:  | 		} elseif ($this->implicitThrows) { | 
| 4428:  | 			$requiredParameters = null; | 
| 4429:  | 			if ($parametersAcceptor !== null) { | 
| 4430:  | 				$requiredParameters = 0; | 
| 4431:  | 				foreach ($parametersAcceptor->getParameters() as $parameter) { | 
| 4432:  | 					if ($parameter->isOptional()) { | 
| 4433:  | 						continue; | 
| 4434:  | 					} | 
| 4435:  |  | 
| 4436:  | 					$requiredParameters++; | 
| 4437:  | 				} | 
| 4438:  | 			} | 
| 4439:  | 			if ( | 
| 4440:  | 				!$functionReflection->isBuiltin() | 
| 4441:  | 				|| $requiredParameters === null | 
| 4442:  | 				|| $requiredParameters > 0 | 
| 4443:  | 				|| count($funcCall->getArgs()) > 0 | 
| 4444:  | 			) { | 
| 4445:  | 				$functionReturnedType = $scope->getType($funcCall); | 
| 4446:  | 				if (!(new ObjectType(Throwable::class))->isSuperTypeOf($functionReturnedType)->yes()) { | 
| 4447:  | 					return ThrowPoint::createImplicit($scope, $funcCall); | 
| 4448:  | 				} | 
| 4449:  | 			} | 
| 4450:  | 		} | 
| 4451:  |  | 
| 4452:  | 		return null; | 
| 4453:  | 	} | 
| 4454:  |  | 
| 4455:  | 	private function getMethodThrowPoint(MethodReflection $methodReflection, ParametersAcceptor $parametersAcceptor, MethodCall $methodCall, MutatingScope $scope): ?ThrowPoint | 
| 4456:  | 	{ | 
| 4457:  | 		$normalizedMethodCall = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $methodCall); | 
| 4458:  | 		if ($normalizedMethodCall !== null) { | 
| 4459:  | 			foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicMethodThrowTypeExtensions() as $extension) { | 
| 4460:  | 				if (!$extension->isMethodSupported($methodReflection)) { | 
| 4461:  | 					continue; | 
| 4462:  | 				} | 
| 4463:  |  | 
| 4464:  | 				$throwType = $extension->getThrowTypeFromMethodCall($methodReflection, $normalizedMethodCall, $scope); | 
| 4465:  | 				if ($throwType === null) { | 
| 4466:  | 					return null; | 
| 4467:  | 				} | 
| 4468:  |  | 
| 4469:  | 				return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); | 
| 4470:  | 			} | 
| 4471:  | 		} | 
| 4472:  |  | 
| 4473:  | 		$throwType = $methodReflection->getThrowType(); | 
| 4474:  | 		if ($throwType === null) { | 
| 4475:  | 			$returnType = $parametersAcceptor->getReturnType(); | 
| 4476:  | 			if ($returnType instanceof NeverType && $returnType->isExplicit()) { | 
| 4477:  | 				$throwType = new ObjectType(Throwable::class); | 
| 4478:  | 			} | 
| 4479:  | 		} | 
| 4480:  |  | 
| 4481:  | 		if ($throwType !== null) { | 
| 4482:  | 			if (!$throwType->isVoid()->yes()) { | 
| 4483:  | 				return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true); | 
| 4484:  | 			} | 
| 4485:  | 		} elseif ($this->implicitThrows) { | 
| 4486:  | 			$methodReturnedType = $scope->getType($methodCall); | 
| 4487:  | 			if (!(new ObjectType(Throwable::class))->isSuperTypeOf($methodReturnedType)->yes()) { | 
| 4488:  | 				return ThrowPoint::createImplicit($scope, $methodCall); | 
| 4489:  | 			} | 
| 4490:  | 		} | 
| 4491:  |  | 
| 4492:  | 		return null; | 
| 4493:  | 	} | 
| 4494:  |  | 
| 4495:  | 	 | 
| 4496:  |  | 
| 4497:  |  | 
| 4498:  | 	private function getConstructorThrowPoint(MethodReflection $constructorReflection, ParametersAcceptor $parametersAcceptor, ClassReflection $classReflection, New_ $new, Name $className, array $args, MutatingScope $scope): ?ThrowPoint | 
| 4499:  | 	{ | 
| 4500:  | 		$methodCall = new StaticCall($className, $constructorReflection->getName(), $args); | 
| 4501:  | 		$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall); | 
| 4502:  | 		if ($normalizedMethodCall !== null) { | 
| 4503:  | 			foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicStaticMethodThrowTypeExtensions() as $extension) { | 
| 4504:  | 				if (!$extension->isStaticMethodSupported($constructorReflection)) { | 
| 4505:  | 					continue; | 
| 4506:  | 				} | 
| 4507:  |  | 
| 4508:  | 				$throwType = $extension->getThrowTypeFromStaticMethodCall($constructorReflection, $normalizedMethodCall, $scope); | 
| 4509:  | 				if ($throwType === null) { | 
| 4510:  | 					return null; | 
| 4511:  | 				} | 
| 4512:  |  | 
| 4513:  | 				return ThrowPoint::createExplicit($scope, $throwType, $new, false); | 
| 4514:  | 			} | 
| 4515:  | 		} | 
| 4516:  |  | 
| 4517:  | 		if ($constructorReflection->getThrowType() !== null) { | 
| 4518:  | 			$throwType = $constructorReflection->getThrowType(); | 
| 4519:  | 			if (!$throwType->isVoid()->yes()) { | 
| 4520:  | 				return ThrowPoint::createExplicit($scope, $throwType, $new, true); | 
| 4521:  | 			} | 
| 4522:  | 		} elseif ($this->implicitThrows) { | 
| 4523:  | 			if (!$classReflection->is(Throwable::class)) { | 
| 4524:  | 				return ThrowPoint::createImplicit($scope, $methodCall); | 
| 4525:  | 			} | 
| 4526:  | 		} | 
| 4527:  |  | 
| 4528:  | 		return null; | 
| 4529:  | 	} | 
| 4530:  |  | 
| 4531:  | 	private function getStaticMethodThrowPoint(MethodReflection $methodReflection, ParametersAcceptor $parametersAcceptor, StaticCall $methodCall, MutatingScope $scope): ?ThrowPoint | 
| 4532:  | 	{ | 
| 4533:  | 		$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall); | 
| 4534:  | 		if ($normalizedMethodCall !== null) { | 
| 4535:  | 			foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicStaticMethodThrowTypeExtensions() as $extension) { | 
| 4536:  | 				if (!$extension->isStaticMethodSupported($methodReflection)) { | 
| 4537:  | 					continue; | 
| 4538:  | 				} | 
| 4539:  |  | 
| 4540:  | 				$throwType = $extension->getThrowTypeFromStaticMethodCall($methodReflection, $normalizedMethodCall, $scope); | 
| 4541:  | 				if ($throwType === null) { | 
| 4542:  | 					return null; | 
| 4543:  | 				} | 
| 4544:  |  | 
| 4545:  | 				return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); | 
| 4546:  | 			} | 
| 4547:  | 		} | 
| 4548:  |  | 
| 4549:  | 		if ($methodReflection->getThrowType() !== null) { | 
| 4550:  | 			$throwType = $methodReflection->getThrowType(); | 
| 4551:  | 			if (!$throwType->isVoid()->yes()) { | 
| 4552:  | 				return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true); | 
| 4553:  | 			} | 
| 4554:  | 		} elseif ($this->implicitThrows) { | 
| 4555:  | 			$methodReturnedType = $scope->getType($methodCall); | 
| 4556:  | 			if (!(new ObjectType(Throwable::class))->isSuperTypeOf($methodReturnedType)->yes()) { | 
| 4557:  | 				return ThrowPoint::createImplicit($scope, $methodCall); | 
| 4558:  | 			} | 
| 4559:  | 		} | 
| 4560:  |  | 
| 4561:  | 		return null; | 
| 4562:  | 	} | 
| 4563:  |  | 
| 4564:  | 	 | 
| 4565:  |  | 
| 4566:  |  | 
| 4567:  | 	private function getPropertyReadThrowPointsFromGetHook( | 
| 4568:  | 		MutatingScope $scope, | 
| 4569:  | 		PropertyFetch $propertyFetch, | 
| 4570:  | 		PhpPropertyReflection $propertyReflection, | 
| 4571:  | 	): array | 
| 4572:  | 	{ | 
| 4573:  | 		return $this->getThrowPointsFromPropertyHook($scope, $propertyFetch, $propertyReflection, 'get'); | 
| 4574:  | 	} | 
| 4575:  |  | 
| 4576:  | 	 | 
| 4577:  |  | 
| 4578:  |  | 
| 4579:  | 	private function getPropertyAssignThrowPointsFromSetHook( | 
| 4580:  | 		MutatingScope $scope, | 
| 4581:  | 		PropertyFetch $propertyFetch, | 
| 4582:  | 		PhpPropertyReflection $propertyReflection, | 
| 4583:  | 	): array | 
| 4584:  | 	{ | 
| 4585:  | 		return $this->getThrowPointsFromPropertyHook($scope, $propertyFetch, $propertyReflection, 'set'); | 
| 4586:  | 	} | 
| 4587:  |  | 
| 4588:  | 	 | 
| 4589:  |  | 
| 4590:  |  | 
| 4591:  |  | 
| 4592:  | 	private function getThrowPointsFromPropertyHook( | 
| 4593:  | 		MutatingScope $scope, | 
| 4594:  | 		PropertyFetch $propertyFetch, | 
| 4595:  | 		PhpPropertyReflection $propertyReflection, | 
| 4596:  | 		string $hookName, | 
| 4597:  | 	): array | 
| 4598:  | 	{ | 
| 4599:  | 		$scopeFunction = $scope->getFunction(); | 
| 4600:  | 		if ( | 
| 4601:  | 			$scopeFunction instanceof PhpMethodFromParserNodeReflection | 
| 4602:  | 			&& $scopeFunction->isPropertyHook() | 
| 4603:  | 			&& $propertyFetch->var instanceof Variable | 
| 4604:  | 			&& $propertyFetch->var->name === 'this' | 
| 4605:  | 			&& $propertyFetch->name instanceof Identifier | 
| 4606:  | 			&& $propertyFetch->name->toString() === $scopeFunction->getHookedPropertyName() | 
| 4607:  | 		) { | 
| 4608:  | 			return []; | 
| 4609:  | 		} | 
| 4610:  | 		$declaringClass = $propertyReflection->getDeclaringClass(); | 
| 4611:  | 		if (!$propertyReflection->hasHook($hookName)) { | 
| 4612:  | 			if ( | 
| 4613:  | 				$propertyReflection->isPrivate() | 
| 4614:  | 				|| $propertyReflection->isFinal()->yes() | 
| 4615:  | 				|| $declaringClass->isFinal() | 
| 4616:  | 			) { | 
| 4617:  | 				return []; | 
| 4618:  | 			} | 
| 4619:  |  | 
| 4620:  | 			if ($this->implicitThrows) { | 
| 4621:  | 				return [ThrowPoint::createImplicit($scope, $propertyFetch)]; | 
| 4622:  | 			} | 
| 4623:  |  | 
| 4624:  | 			return []; | 
| 4625:  | 		} | 
| 4626:  |  | 
| 4627:  | 		$getHook = $propertyReflection->getHook($hookName); | 
| 4628:  | 		$throwType = $getHook->getThrowType(); | 
| 4629:  |  | 
| 4630:  | 		if ($throwType !== null) { | 
| 4631:  | 			if (!$throwType->isVoid()->yes()) { | 
| 4632:  | 				return [ThrowPoint::createExplicit($scope, $throwType, $propertyFetch, true)]; | 
| 4633:  | 			} | 
| 4634:  | 		} elseif ($this->implicitThrows) { | 
| 4635:  | 			return [ThrowPoint::createImplicit($scope, $propertyFetch)]; | 
| 4636:  | 		} | 
| 4637:  |  | 
| 4638:  | 		return []; | 
| 4639:  | 	} | 
| 4640:  |  | 
| 4641:  | 	 | 
| 4642:  |  | 
| 4643:  |  | 
| 4644:  | 	private function getAssignedVariables(Expr $expr): array | 
| 4645:  | 	{ | 
| 4646:  | 		if ($expr instanceof Expr\Variable) { | 
| 4647:  | 			if (is_string($expr->name)) { | 
| 4648:  | 				return [$expr->name]; | 
| 4649:  | 			} | 
| 4650:  |  | 
| 4651:  | 			return []; | 
| 4652:  | 		} | 
| 4653:  |  | 
| 4654:  | 		if ($expr instanceof Expr\List_) { | 
| 4655:  | 			$names = []; | 
| 4656:  | 			foreach ($expr->items as $item) { | 
| 4657:  | 				if ($item === null) { | 
| 4658:  | 					continue; | 
| 4659:  | 				} | 
| 4660:  |  | 
| 4661:  | 				$names = array_merge($names, $this->getAssignedVariables($item->value)); | 
| 4662:  | 			} | 
| 4663:  |  | 
| 4664:  | 			return $names; | 
| 4665:  | 		} | 
| 4666:  |  | 
| 4667:  | 		if ($expr instanceof ArrayDimFetch) { | 
| 4668:  | 			return $this->getAssignedVariables($expr->var); | 
| 4669:  | 		} | 
| 4670:  |  | 
| 4671:  | 		return []; | 
| 4672:  | 	} | 
| 4673:  |  | 
| 4674:  | 	 | 
| 4675:  |  | 
| 4676:  |  | 
| 4677:  | 	private function callNodeCallbackWithExpression( | 
| 4678:  | 		callable $nodeCallback, | 
| 4679:  | 		Expr $expr, | 
| 4680:  | 		MutatingScope $scope, | 
| 4681:  | 		ExpressionContext $context, | 
| 4682:  | 	): void | 
| 4683:  | 	{ | 
| 4684:  | 		if ($context->isDeep()) { | 
| 4685:  | 			$scope = $scope->exitFirstLevelStatements(); | 
| 4686:  | 		} | 
| 4687:  | 		$nodeCallback($expr, $scope); | 
| 4688:  | 	} | 
| 4689:  |  | 
| 4690:  | 	 | 
| 4691:  |  | 
| 4692:  |  | 
| 4693:  | 	private function processClosureNode( | 
| 4694:  | 		Node\Stmt $stmt, | 
| 4695:  | 		Expr\Closure $expr, | 
| 4696:  | 		MutatingScope $scope, | 
| 4697:  | 		callable $nodeCallback, | 
| 4698:  | 		ExpressionContext $context, | 
| 4699:  | 		?Type $passedToType, | 
| 4700:  | 	): ProcessClosureResult | 
| 4701:  | 	{ | 
| 4702:  | 		foreach ($expr->params as $param) { | 
| 4703:  | 			$this->processParamNode($stmt, $param, $scope, $nodeCallback); | 
| 4704:  | 		} | 
| 4705:  |  | 
| 4706:  | 		$byRefUses = []; | 
| 4707:  |  | 
| 4708:  | 		$closureCallArgs = $expr->getAttribute(ClosureArgVisitor::ATTRIBUTE_NAME); | 
| 4709:  | 		$callableParameters = $this->createCallableParameters( | 
| 4710:  | 			$scope, | 
| 4711:  | 			$expr, | 
| 4712:  | 			$closureCallArgs, | 
| 4713:  | 			$passedToType, | 
| 4714:  | 		); | 
| 4715:  |  | 
| 4716:  | 		$useScope = $scope; | 
| 4717:  | 		foreach ($expr->uses as $use) { | 
| 4718:  | 			if ($use->byRef) { | 
| 4719:  | 				$byRefUses[] = $use; | 
| 4720:  | 				$useScope = $useScope->enterExpressionAssign($use->var); | 
| 4721:  |  | 
| 4722:  | 				$inAssignRightSideVariableName = $context->getInAssignRightSideVariableName(); | 
| 4723:  | 				$inAssignRightSideType = $context->getInAssignRightSideType(); | 
| 4724:  | 				$inAssignRightSideNativeType = $context->getInAssignRightSideNativeType(); | 
| 4725:  | 				if ( | 
| 4726:  | 					$inAssignRightSideVariableName === $use->var->name | 
| 4727:  | 					&& $inAssignRightSideType !== null | 
| 4728:  | 					&& $inAssignRightSideNativeType !== null | 
| 4729:  | 				) { | 
| 4730:  | 					if ($inAssignRightSideType instanceof ClosureType) { | 
| 4731:  | 						$variableType = $inAssignRightSideType; | 
| 4732:  | 					} else { | 
| 4733:  | 						$alreadyHasVariableType = $scope->hasVariableType($inAssignRightSideVariableName); | 
| 4734:  | 						if ($alreadyHasVariableType->no()) { | 
| 4735:  | 							$variableType = TypeCombinator::union(new NullType(), $inAssignRightSideType); | 
| 4736:  | 						} else { | 
| 4737:  | 							$variableType = TypeCombinator::union($scope->getVariableType($inAssignRightSideVariableName), $inAssignRightSideType); | 
| 4738:  | 						} | 
| 4739:  | 					} | 
| 4740:  | 					if ($inAssignRightSideNativeType instanceof ClosureType) { | 
| 4741:  | 						$variableNativeType = $inAssignRightSideNativeType; | 
| 4742:  | 					} else { | 
| 4743:  | 						$alreadyHasVariableType = $scope->hasVariableType($inAssignRightSideVariableName); | 
| 4744:  | 						if ($alreadyHasVariableType->no()) { | 
| 4745:  | 							$variableNativeType = TypeCombinator::union(new NullType(), $inAssignRightSideNativeType); | 
| 4746:  | 						} else { | 
| 4747:  | 							$variableNativeType = TypeCombinator::union($scope->getVariableType($inAssignRightSideVariableName), $inAssignRightSideNativeType); | 
| 4748:  | 						} | 
| 4749:  | 					} | 
| 4750:  | 					$scope = $scope->assignVariable($inAssignRightSideVariableName, $variableType, $variableNativeType, TrinaryLogic::createYes()); | 
| 4751:  | 				} | 
| 4752:  | 			} | 
| 4753:  | 			$this->processExprNode($stmt, $use->var, $useScope, $nodeCallback, $context); | 
| 4754:  | 			if (!$use->byRef) { | 
| 4755:  | 				continue; | 
| 4756:  | 			} | 
| 4757:  |  | 
| 4758:  | 			$useScope = $useScope->exitExpressionAssign($use->var); | 
| 4759:  | 		} | 
| 4760:  |  | 
| 4761:  | 		if ($expr->returnType !== null) { | 
| 4762:  | 			$nodeCallback($expr->returnType, $scope); | 
| 4763:  | 		} | 
| 4764:  |  | 
| 4765:  | 		$closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); | 
| 4766:  | 		$closureScope = $closureScope->processClosureScope($scope, null, $byRefUses); | 
| 4767:  | 		$closureType = $closureScope->getAnonymousFunctionReflection(); | 
| 4768:  | 		if (!$closureType instanceof ClosureType) { | 
| 4769:  | 			throw new ShouldNotHappenException(); | 
| 4770:  | 		} | 
| 4771:  |  | 
| 4772:  | 		$returnType = $closureType->getReturnType(); | 
| 4773:  | 		$isAlwaysTerminating = ($returnType instanceof NeverType && $returnType->isExplicit()); | 
| 4774:  |  | 
| 4775:  | 		$nodeCallback(new InClosureNode($closureType, $expr), $closureScope); | 
| 4776:  |  | 
| 4777:  | 		$executionEnds = []; | 
| 4778:  | 		$gatheredReturnStatements = []; | 
| 4779:  | 		$gatheredYieldStatements = []; | 
| 4780:  | 		$closureImpurePoints = []; | 
| 4781:  | 		$invalidateExpressions = []; | 
| 4782:  | 		$closureStmtsCallback = static function (Node $node, Scope $scope) use ($nodeCallback, &$executionEnds, &$gatheredReturnStatements, &$gatheredYieldStatements, &$closureScope, &$closureImpurePoints, &$invalidateExpressions): void { | 
| 4783:  | 			$nodeCallback($node, $scope); | 
| 4784:  | 			if ($scope->getAnonymousFunctionReflection() !== $closureScope->getAnonymousFunctionReflection()) { | 
| 4785:  | 				return; | 
| 4786:  | 			} | 
| 4787:  | 			if ($node instanceof PropertyAssignNode) { | 
| 4788:  | 				$closureImpurePoints[] = new ImpurePoint( | 
| 4789:  | 					$scope, | 
| 4790:  | 					$node, | 
| 4791:  | 					'propertyAssign', | 
| 4792:  | 					'property assignment', | 
| 4793:  | 					true, | 
| 4794:  | 				); | 
| 4795:  | 				return; | 
| 4796:  | 			} | 
| 4797:  | 			if ($node instanceof ExecutionEndNode) { | 
| 4798:  | 				$executionEnds[] = $node; | 
| 4799:  | 				return; | 
| 4800:  | 			} | 
| 4801:  | 			if ($node instanceof InvalidateExprNode) { | 
| 4802:  | 				$invalidateExpressions[] = $node; | 
| 4803:  | 				return; | 
| 4804:  | 			} | 
| 4805:  | 			if ($node instanceof Expr\Yield_ || $node instanceof Expr\YieldFrom) { | 
| 4806:  | 				$gatheredYieldStatements[] = $node; | 
| 4807:  | 			} | 
| 4808:  | 			if (!$node instanceof Return_) { | 
| 4809:  | 				return; | 
| 4810:  | 			} | 
| 4811:  |  | 
| 4812:  | 			$gatheredReturnStatements[] = new ReturnStatement($scope, $node); | 
| 4813:  | 		}; | 
| 4814:  |  | 
| 4815:  | 		if (count($byRefUses) === 0) { | 
| 4816:  | 			$statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback, StatementContext::createTopLevel()); | 
| 4817:  | 			$nodeCallback(new ClosureReturnStatementsNode( | 
| 4818:  | 				$expr, | 
| 4819:  | 				$gatheredReturnStatements, | 
| 4820:  | 				$gatheredYieldStatements, | 
| 4821:  | 				$statementResult, | 
| 4822:  | 				$executionEnds, | 
| 4823:  | 				array_merge($statementResult->getImpurePoints(), $closureImpurePoints), | 
| 4824:  | 			), $closureScope); | 
| 4825:  |  | 
| 4826:  | 			return new ProcessClosureResult($scope, $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions, $isAlwaysTerminating); | 
| 4827:  | 		} | 
| 4828:  |  | 
| 4829:  | 		$count = 0; | 
| 4830:  | 		$closureResultScope = null; | 
| 4831:  | 		do { | 
| 4832:  | 			$prevScope = $closureScope; | 
| 4833:  |  | 
| 4834:  | 			$intermediaryClosureScopeResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, static function (): void { | 
| 4835:  | 			}, StatementContext::createTopLevel()); | 
| 4836:  | 			$intermediaryClosureScope = $intermediaryClosureScopeResult->getScope(); | 
| 4837:  | 			foreach ($intermediaryClosureScopeResult->getExitPoints() as $exitPoint) { | 
| 4838:  | 				$intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope()); | 
| 4839:  | 			} | 
| 4840:  |  | 
| 4841:  | 			if ($expr->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) === true) { | 
| 4842:  | 				$closureResultScope = $intermediaryClosureScope; | 
| 4843:  | 				break; | 
| 4844:  | 			} | 
| 4845:  |  | 
| 4846:  | 			$closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); | 
| 4847:  | 			$closureScope = $closureScope->processClosureScope($intermediaryClosureScope, $prevScope, $byRefUses); | 
| 4848:  |  | 
| 4849:  | 			if ($closureScope->equals($prevScope)) { | 
| 4850:  | 				break; | 
| 4851:  | 			} | 
| 4852:  | 			if ($count >= self::GENERALIZE_AFTER_ITERATION) { | 
| 4853:  | 				$closureScope = $prevScope->generalizeWith($closureScope); | 
| 4854:  | 			} | 
| 4855:  | 			$count++; | 
| 4856:  | 		} while ($count < self::LOOP_SCOPE_ITERATIONS); | 
| 4857:  |  | 
| 4858:  | 		if ($closureResultScope === null) { | 
| 4859:  | 			$closureResultScope = $closureScope; | 
| 4860:  | 		} | 
| 4861:  |  | 
| 4862:  | 		$statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback, StatementContext::createTopLevel()); | 
| 4863:  | 		$nodeCallback(new ClosureReturnStatementsNode( | 
| 4864:  | 			$expr, | 
| 4865:  | 			$gatheredReturnStatements, | 
| 4866:  | 			$gatheredYieldStatements, | 
| 4867:  | 			$statementResult, | 
| 4868:  | 			$executionEnds, | 
| 4869:  | 			array_merge($statementResult->getImpurePoints(), $closureImpurePoints), | 
| 4870:  | 		), $closureScope); | 
| 4871:  |  | 
| 4872:  | 		return new ProcessClosureResult($scope->processClosureScope($closureResultScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions, $isAlwaysTerminating); | 
| 4873:  | 	} | 
| 4874:  |  | 
| 4875:  | 	 | 
| 4876:  |  | 
| 4877:  |  | 
| 4878:  |  | 
| 4879:  | 	private function processImmediatelyCalledCallable(MutatingScope $scope, array $invalidatedExpressions, array $uses): MutatingScope | 
| 4880:  | 	{ | 
| 4881:  | 		if ($scope->isInClass()) { | 
| 4882:  | 			$uses[] = 'this'; | 
| 4883:  | 		} | 
| 4884:  |  | 
| 4885:  | 		$finder = new NodeFinder(); | 
| 4886:  | 		foreach ($invalidatedExpressions as $invalidateExpression) { | 
| 4887:  | 			$found = false; | 
| 4888:  | 			foreach ($uses as $use) { | 
| 4889:  | 				$result = $finder->findFirst([$invalidateExpression->getExpr()], static fn ($node) => $node instanceof Variable && $node->name === $use); | 
| 4890:  | 				if ($result === null) { | 
| 4891:  | 					continue; | 
| 4892:  | 				} | 
| 4893:  |  | 
| 4894:  | 				$found = true; | 
| 4895:  | 				break; | 
| 4896:  | 			} | 
| 4897:  |  | 
| 4898:  | 			if (!$found) { | 
| 4899:  | 				continue; | 
| 4900:  | 			} | 
| 4901:  |  | 
| 4902:  | 			$scope = $scope->invalidateExpression($invalidateExpression->getExpr(), true); | 
| 4903:  | 		} | 
| 4904:  |  | 
| 4905:  | 		return $scope; | 
| 4906:  | 	} | 
| 4907:  |  | 
| 4908:  | 	 | 
| 4909:  |  | 
| 4910:  |  | 
| 4911:  | 	private function processArrowFunctionNode( | 
| 4912:  | 		Node\Stmt $stmt, | 
| 4913:  | 		Expr\ArrowFunction $expr, | 
| 4914:  | 		MutatingScope $scope, | 
| 4915:  | 		callable $nodeCallback, | 
| 4916:  | 		?Type $passedToType, | 
| 4917:  | 	): ExpressionResult | 
| 4918:  | 	{ | 
| 4919:  | 		foreach ($expr->params as $param) { | 
| 4920:  | 			$this->processParamNode($stmt, $param, $scope, $nodeCallback); | 
| 4921:  | 		} | 
| 4922:  | 		if ($expr->returnType !== null) { | 
| 4923:  | 			$nodeCallback($expr->returnType, $scope); | 
| 4924:  | 		} | 
| 4925:  |  | 
| 4926:  | 		$arrowFunctionCallArgs = $expr->getAttribute(ArrowFunctionArgVisitor::ATTRIBUTE_NAME); | 
| 4927:  | 		$arrowFunctionScope = $scope->enterArrowFunction($expr, $this->createCallableParameters( | 
| 4928:  | 			$scope, | 
| 4929:  | 			$expr, | 
| 4930:  | 			$arrowFunctionCallArgs, | 
| 4931:  | 			$passedToType, | 
| 4932:  | 		)); | 
| 4933:  | 		$arrowFunctionType = $arrowFunctionScope->getAnonymousFunctionReflection(); | 
| 4934:  | 		if ($arrowFunctionType === null) { | 
| 4935:  | 			throw new ShouldNotHappenException(); | 
| 4936:  | 		} | 
| 4937:  | 		$nodeCallback(new InArrowFunctionNode($arrowFunctionType, $expr), $arrowFunctionScope); | 
| 4938:  | 		$exprResult = $this->processExprNode($stmt, $expr->expr, $arrowFunctionScope, $nodeCallback, ExpressionContext::createTopLevel()); | 
| 4939:  |  | 
| 4940:  | 		return new ExpressionResult($scope, false, $exprResult->isAlwaysTerminating(), $exprResult->getThrowPoints(), $exprResult->getImpurePoints()); | 
| 4941:  | 	} | 
| 4942:  |  | 
| 4943:  | 	 | 
| 4944:  |  | 
| 4945:  |  | 
| 4946:  |  | 
| 4947:  | 	public function createCallableParameters(Scope $scope, Expr $closureExpr, ?array $args, ?Type $passedToType): ?array | 
| 4948:  | 	{ | 
| 4949:  | 		$callableParameters = null; | 
| 4950:  | 		if ($args !== null) { | 
| 4951:  | 			$closureType = $scope->getType($closureExpr); | 
| 4952:  |  | 
| 4953:  | 			if ($closureType->isCallable()->no()) { | 
| 4954:  | 				return null; | 
| 4955:  | 			} | 
| 4956:  |  | 
| 4957:  | 			$acceptors = $closureType->getCallableParametersAcceptors($scope); | 
| 4958:  | 			if (count($acceptors) === 1) { | 
| 4959:  | 				$callableParameters = $acceptors[0]->getParameters(); | 
| 4960:  |  | 
| 4961:  | 				foreach ($callableParameters as $index => $callableParameter) { | 
| 4962:  | 					if (!isset($args[$index])) { | 
| 4963:  | 						continue; | 
| 4964:  | 					} | 
| 4965:  |  | 
| 4966:  | 					$type = $scope->getType($args[$index]->value); | 
| 4967:  | 					$callableParameters[$index] = new NativeParameterReflection( | 
| 4968:  | 						$callableParameter->getName(), | 
| 4969:  | 						$callableParameter->isOptional(), | 
| 4970:  | 						$type, | 
| 4971:  | 						$callableParameter->passedByReference(), | 
| 4972:  | 						$callableParameter->isVariadic(), | 
| 4973:  | 						$callableParameter->getDefaultValue(), | 
| 4974:  | 					); | 
| 4975:  | 				} | 
| 4976:  | 			} | 
| 4977:  | 		} elseif ($passedToType !== null && !$passedToType->isCallable()->no()) { | 
| 4978:  | 			if ($passedToType instanceof UnionType) { | 
| 4979:  | 				$passedToType = $passedToType->filterTypes(static fn (Type $innerType) => $innerType->isCallable()->yes()); | 
| 4980:  |  | 
| 4981:  | 				if ($passedToType->isCallable()->no()) { | 
| 4982:  | 					return null; | 
| 4983:  | 				} | 
| 4984:  | 			} | 
| 4985:  |  | 
| 4986:  | 			$acceptors = $passedToType->getCallableParametersAcceptors($scope); | 
| 4987:  | 			if (count($acceptors) > 0) { | 
| 4988:  | 				foreach ($acceptors as $acceptor) { | 
| 4989:  | 					if ($callableParameters === null) { | 
| 4990:  | 						$callableParameters = array_map(static fn (ParameterReflection $callableParameter) => new NativeParameterReflection( | 
| 4991:  | 							$callableParameter->getName(), | 
| 4992:  | 							$callableParameter->isOptional(), | 
| 4993:  | 							$callableParameter->getType(), | 
| 4994:  | 							$callableParameter->passedByReference(), | 
| 4995:  | 							$callableParameter->isVariadic(), | 
| 4996:  | 							$callableParameter->getDefaultValue(), | 
| 4997:  | 						), $acceptor->getParameters()); | 
| 4998:  | 						continue; | 
| 4999:  | 					} | 
| 5000:  |  | 
| 5001:  | 					$newParameters = []; | 
| 5002:  | 					foreach ($acceptor->getParameters() as $i => $callableParameter) { | 
| 5003:  | 						if (!array_key_exists($i, $callableParameters)) { | 
| 5004:  | 							$newParameters[] = $callableParameter; | 
| 5005:  | 							continue; | 
| 5006:  | 						} | 
| 5007:  |  | 
| 5008:  | 						$newParameters[] = $callableParameters[$i]->union(new NativeParameterReflection( | 
| 5009:  | 							$callableParameter->getName(), | 
| 5010:  | 							$callableParameter->isOptional(), | 
| 5011:  | 							$callableParameter->getType(), | 
| 5012:  | 							$callableParameter->passedByReference(), | 
| 5013:  | 							$callableParameter->isVariadic(), | 
| 5014:  | 							$callableParameter->getDefaultValue(), | 
| 5015:  | 						)); | 
| 5016:  | 					} | 
| 5017:  |  | 
| 5018:  | 					$callableParameters = $newParameters; | 
| 5019:  | 				} | 
| 5020:  | 			} | 
| 5021:  | 		} | 
| 5022:  |  | 
| 5023:  | 		return $callableParameters; | 
| 5024:  | 	} | 
| 5025:  |  | 
| 5026:  | 	 | 
| 5027:  |  | 
| 5028:  |  | 
| 5029:  | 	private function processParamNode( | 
| 5030:  | 		Node\Stmt $stmt, | 
| 5031:  | 		Node\Param $param, | 
| 5032:  | 		MutatingScope $scope, | 
| 5033:  | 		callable $nodeCallback, | 
| 5034:  | 	): void | 
| 5035:  | 	{ | 
| 5036:  | 		$this->processAttributeGroups($stmt, $param->attrGroups, $scope, $nodeCallback); | 
| 5037:  | 		$nodeCallback($param, $scope); | 
| 5038:  | 		if ($param->type !== null) { | 
| 5039:  | 			$nodeCallback($param->type, $scope); | 
| 5040:  | 		} | 
| 5041:  | 		if ($param->default === null) { | 
| 5042:  | 			return; | 
| 5043:  | 		} | 
| 5044:  |  | 
| 5045:  | 		$this->processExprNode($stmt, $param->default, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 5046:  | 	} | 
| 5047:  |  | 
| 5048:  | 	 | 
| 5049:  |  | 
| 5050:  |  | 
| 5051:  |  | 
| 5052:  | 	private function processAttributeGroups( | 
| 5053:  | 		Node\Stmt $stmt, | 
| 5054:  | 		array $attrGroups, | 
| 5055:  | 		MutatingScope $scope, | 
| 5056:  | 		callable $nodeCallback, | 
| 5057:  | 	): void | 
| 5058:  | 	{ | 
| 5059:  | 		foreach ($attrGroups as $attrGroup) { | 
| 5060:  | 			foreach ($attrGroup->attrs as $attr) { | 
| 5061:  | 				foreach ($attr->args as $arg) { | 
| 5062:  | 					$this->processExprNode($stmt, $arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 5063:  | 					$nodeCallback($arg, $scope); | 
| 5064:  | 				} | 
| 5065:  | 				$nodeCallback($attr, $scope); | 
| 5066:  | 			} | 
| 5067:  | 			$nodeCallback($attrGroup, $scope); | 
| 5068:  | 		} | 
| 5069:  | 	} | 
| 5070:  |  | 
| 5071:  | 	 | 
| 5072:  |  | 
| 5073:  |  | 
| 5074:  |  | 
| 5075:  | 	private function processPropertyHooks( | 
| 5076:  | 		Node\Stmt $stmt, | 
| 5077:  | 		Identifier|Name|ComplexType|null $nativeTypeNode, | 
| 5078:  | 		?Type $phpDocType, | 
| 5079:  | 		string $propertyName, | 
| 5080:  | 		array $hooks, | 
| 5081:  | 		MutatingScope $scope, | 
| 5082:  | 		callable $nodeCallback, | 
| 5083:  | 	): void | 
| 5084:  | 	{ | 
| 5085:  | 		if (!$scope->isInClass()) { | 
| 5086:  | 			throw new ShouldNotHappenException(); | 
| 5087:  | 		} | 
| 5088:  |  | 
| 5089:  | 		$classReflection = $scope->getClassReflection(); | 
| 5090:  |  | 
| 5091:  | 		foreach ($hooks as $hook) { | 
| 5092:  | 			$nodeCallback($hook, $scope); | 
| 5093:  | 			$this->processAttributeGroups($stmt, $hook->attrGroups, $scope, $nodeCallback); | 
| 5094:  |  | 
| 5095:  | 			[, $phpDocParameterTypes,,,, $phpDocThrowType,,,,,,,, $phpDocComment] = $this->getPhpDocs($scope, $hook); | 
| 5096:  |  | 
| 5097:  | 			foreach ($hook->params as $param) { | 
| 5098:  | 				$this->processParamNode($stmt, $param, $scope, $nodeCallback); | 
| 5099:  | 			} | 
| 5100:  |  | 
| 5101:  | 			[$isDeprecated, $deprecatedDescription] = $this->getDeprecatedAttribute($scope, $hook); | 
| 5102:  |  | 
| 5103:  | 			$hookScope = $scope->enterPropertyHook( | 
| 5104:  | 				$hook, | 
| 5105:  | 				$propertyName, | 
| 5106:  | 				$nativeTypeNode, | 
| 5107:  | 				$phpDocType, | 
| 5108:  | 				$phpDocParameterTypes, | 
| 5109:  | 				$phpDocThrowType, | 
| 5110:  | 				$deprecatedDescription, | 
| 5111:  | 				$isDeprecated, | 
| 5112:  | 				$phpDocComment, | 
| 5113:  | 			); | 
| 5114:  | 			$hookReflection = $hookScope->getFunction(); | 
| 5115:  | 			if (!$hookReflection instanceof PhpMethodFromParserNodeReflection) { | 
| 5116:  | 				throw new ShouldNotHappenException(); | 
| 5117:  | 			} | 
| 5118:  |  | 
| 5119:  | 			if (!$classReflection->hasNativeProperty($propertyName)) { | 
| 5120:  | 				throw new ShouldNotHappenException(); | 
| 5121:  | 			} | 
| 5122:  |  | 
| 5123:  | 			$propertyReflection = $classReflection->getNativeProperty($propertyName); | 
| 5124:  |  | 
| 5125:  | 			$nodeCallback(new InPropertyHookNode( | 
| 5126:  | 				$classReflection, | 
| 5127:  | 				$hookReflection, | 
| 5128:  | 				$propertyReflection, | 
| 5129:  | 				$hook, | 
| 5130:  | 			), $hookScope); | 
| 5131:  |  | 
| 5132:  | 			$stmts = $hook->getStmts(); | 
| 5133:  | 			if ($stmts === null) { | 
| 5134:  | 				return; | 
| 5135:  | 			} | 
| 5136:  |  | 
| 5137:  | 			if ($hook->body instanceof Expr) { | 
| 5138:  | 				 | 
| 5139:  | 				$traverser = new NodeTraverser( | 
| 5140:  | 					new LineAttributesVisitor($hook->body->getStartLine(), $hook->body->getEndLine()), | 
| 5141:  | 				); | 
| 5142:  | 				$traverser->traverse($stmts); | 
| 5143:  | 			} | 
| 5144:  |  | 
| 5145:  | 			$gatheredReturnStatements = []; | 
| 5146:  | 			$executionEnds = []; | 
| 5147:  | 			$methodImpurePoints = []; | 
| 5148:  | 			$statementResult = $this->processStmtNodes(new PropertyHookStatementNode($hook), $stmts, $hookScope, static function (Node $node, Scope $scope) use ($nodeCallback, $hookScope, &$gatheredReturnStatements, &$executionEnds, &$hookImpurePoints): void { | 
| 5149:  | 				$nodeCallback($node, $scope); | 
| 5150:  | 				if ($scope->getFunction() !== $hookScope->getFunction()) { | 
| 5151:  | 					return; | 
| 5152:  | 				} | 
| 5153:  | 				if ($scope->isInAnonymousFunction()) { | 
| 5154:  | 					return; | 
| 5155:  | 				} | 
| 5156:  | 				if ($node instanceof PropertyAssignNode) { | 
| 5157:  | 					$hookImpurePoints[] = new ImpurePoint( | 
| 5158:  | 						$scope, | 
| 5159:  | 						$node, | 
| 5160:  | 						'propertyAssign', | 
| 5161:  | 						'property assignment', | 
| 5162:  | 						true, | 
| 5163:  | 					); | 
| 5164:  | 					return; | 
| 5165:  | 				} | 
| 5166:  | 				if ($node instanceof ExecutionEndNode) { | 
| 5167:  | 					$executionEnds[] = $node; | 
| 5168:  | 					return; | 
| 5169:  | 				} | 
| 5170:  | 				if (!$node instanceof Return_) { | 
| 5171:  | 					return; | 
| 5172:  | 				} | 
| 5173:  |  | 
| 5174:  | 				$gatheredReturnStatements[] = new ReturnStatement($scope, $node); | 
| 5175:  | 			}, StatementContext::createTopLevel()); | 
| 5176:  |  | 
| 5177:  | 			$nodeCallback(new PropertyHookReturnStatementsNode( | 
| 5178:  | 				$hook, | 
| 5179:  | 				$gatheredReturnStatements, | 
| 5180:  | 				$statementResult, | 
| 5181:  | 				$executionEnds, | 
| 5182:  | 				array_merge($statementResult->getImpurePoints(), $methodImpurePoints), | 
| 5183:  | 				$classReflection, | 
| 5184:  | 				$hookReflection, | 
| 5185:  | 				$propertyReflection, | 
| 5186:  | 			), $hookScope); | 
| 5187:  | 		} | 
| 5188:  | 	} | 
| 5189:  |  | 
| 5190:  | 	 | 
| 5191:  |  | 
| 5192:  |  | 
| 5193:  | 	private function resolveClosureThisType( | 
| 5194:  | 		?CallLike $call, | 
| 5195:  | 		$calleeReflection, | 
| 5196:  | 		ParameterReflection $parameter, | 
| 5197:  | 		MutatingScope $scope, | 
| 5198:  | 	): ?Type | 
| 5199:  | 	{ | 
| 5200:  | 		if ($call instanceof FuncCall && $calleeReflection instanceof FunctionReflection) { | 
| 5201:  | 			foreach ($this->parameterClosureThisExtensionProvider->getFunctionParameterClosureThisExtensions() as $extension) { | 
| 5202:  | 				if (! $extension->isFunctionSupported($calleeReflection, $parameter)) { | 
| 5203:  | 					continue; | 
| 5204:  | 				} | 
| 5205:  | 				$type = $extension->getClosureThisTypeFromFunctionCall($calleeReflection, $call, $parameter, $scope); | 
| 5206:  | 				if ($type !== null) { | 
| 5207:  | 					return $type; | 
| 5208:  | 				} | 
| 5209:  | 			} | 
| 5210:  | 		} elseif ($call instanceof StaticCall && $calleeReflection instanceof MethodReflection) { | 
| 5211:  | 			foreach ($this->parameterClosureThisExtensionProvider->getStaticMethodParameterClosureThisExtensions() as $extension) { | 
| 5212:  | 				if (! $extension->isStaticMethodSupported($calleeReflection, $parameter)) { | 
| 5213:  | 					continue; | 
| 5214:  | 				} | 
| 5215:  | 				$type = $extension->getClosureThisTypeFromStaticMethodCall($calleeReflection, $call, $parameter, $scope); | 
| 5216:  | 				if ($type !== null) { | 
| 5217:  | 					return $type; | 
| 5218:  | 				} | 
| 5219:  | 			} | 
| 5220:  | 		} elseif ($call instanceof MethodCall && $calleeReflection instanceof MethodReflection) { | 
| 5221:  | 			foreach ($this->parameterClosureThisExtensionProvider->getMethodParameterClosureThisExtensions() as $extension) { | 
| 5222:  | 				if (! $extension->isMethodSupported($calleeReflection, $parameter)) { | 
| 5223:  | 					continue; | 
| 5224:  | 				} | 
| 5225:  | 				$type = $extension->getClosureThisTypeFromMethodCall($calleeReflection, $call, $parameter, $scope); | 
| 5226:  | 				if ($type !== null) { | 
| 5227:  | 					return $type; | 
| 5228:  | 				} | 
| 5229:  | 			} | 
| 5230:  | 		} | 
| 5231:  |  | 
| 5232:  | 		if ($parameter instanceof ExtendedParameterReflection) { | 
| 5233:  | 			return $parameter->getClosureThisType(); | 
| 5234:  | 		} | 
| 5235:  |  | 
| 5236:  | 		return null; | 
| 5237:  | 	} | 
| 5238:  |  | 
| 5239:  | 	 | 
| 5240:  |  | 
| 5241:  |  | 
| 5242:  |  | 
| 5243:  | 	private function processArgs( | 
| 5244:  | 		Node\Stmt $stmt, | 
| 5245:  | 		$calleeReflection, | 
| 5246:  | 		?ExtendedMethodReflection $nakedMethodReflection, | 
| 5247:  | 		?ParametersAcceptor $parametersAcceptor, | 
| 5248:  | 		CallLike $callLike, | 
| 5249:  | 		MutatingScope $scope, | 
| 5250:  | 		callable $nodeCallback, | 
| 5251:  | 		ExpressionContext $context, | 
| 5252:  | 		?MutatingScope $closureBindScope = null, | 
| 5253:  | 	): ExpressionResult | 
| 5254:  | 	{ | 
| 5255:  | 		$args = $callLike->getArgs(); | 
| 5256:  |  | 
| 5257:  | 		if ($parametersAcceptor !== null) { | 
| 5258:  | 			$parameters = $parametersAcceptor->getParameters(); | 
| 5259:  | 		} | 
| 5260:  |  | 
| 5261:  | 		$hasYield = false; | 
| 5262:  | 		$throwPoints = []; | 
| 5263:  | 		$impurePoints = []; | 
| 5264:  | 		$isAlwaysTerminating = false; | 
| 5265:  | 		foreach ($args as $i => $arg) { | 
| 5266:  | 			$assignByReference = false; | 
| 5267:  | 			$parameter = null; | 
| 5268:  | 			$parameterType = null; | 
| 5269:  | 			$parameterNativeType = null; | 
| 5270:  | 			if (isset($parameters) && $parametersAcceptor !== null) { | 
| 5271:  | 				if (isset($parameters[$i])) { | 
| 5272:  | 					$assignByReference = $parameters[$i]->passedByReference()->createsNewVariable(); | 
| 5273:  | 					$parameterType = $parameters[$i]->getType(); | 
| 5274:  |  | 
| 5275:  | 					if ($parameters[$i] instanceof ExtendedParameterReflection) { | 
| 5276:  | 						$parameterNativeType = $parameters[$i]->getNativeType(); | 
| 5277:  | 					} | 
| 5278:  | 					$parameter = $parameters[$i]; | 
| 5279:  | 				} elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) { | 
| 5280:  | 					$lastParameter = array_last($parameters); | 
| 5281:  | 					$assignByReference = $lastParameter->passedByReference()->createsNewVariable(); | 
| 5282:  | 					$parameterType = $lastParameter->getType(); | 
| 5283:  |  | 
| 5284:  | 					if ($lastParameter instanceof ExtendedParameterReflection) { | 
| 5285:  | 						$parameterNativeType = $lastParameter->getNativeType(); | 
| 5286:  | 					} | 
| 5287:  | 					$parameter = $lastParameter; | 
| 5288:  | 				} | 
| 5289:  | 			} | 
| 5290:  |  | 
| 5291:  | 			$lookForUnset = false; | 
| 5292:  | 			if ($assignByReference) { | 
| 5293:  | 				$isBuiltin = false; | 
| 5294:  | 				if ($calleeReflection instanceof FunctionReflection && $calleeReflection->isBuiltin()) { | 
| 5295:  | 					$isBuiltin = true; | 
| 5296:  | 				} elseif ($calleeReflection instanceof ExtendedMethodReflection && $calleeReflection->getDeclaringClass()->isBuiltin()) { | 
| 5297:  | 					$isBuiltin = true; | 
| 5298:  | 				} | 
| 5299:  | 				if ( | 
| 5300:  | 					$isBuiltin | 
| 5301:  | 					|| ($parameterNativeType === null || !$parameterNativeType->isNull()->no()) | 
| 5302:  | 				) { | 
| 5303:  | 					$scope = $this->lookForSetAllowedUndefinedExpressions($scope, $arg->value); | 
| 5304:  | 					$lookForUnset = true; | 
| 5305:  | 				} | 
| 5306:  | 			} | 
| 5307:  |  | 
| 5308:  | 			if ($calleeReflection !== null) { | 
| 5309:  | 				$scope = $scope->pushInFunctionCall($calleeReflection, $parameter); | 
| 5310:  | 			} | 
| 5311:  |  | 
| 5312:  | 			$originalArg = $arg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE) ?? $arg; | 
| 5313:  | 			$nodeCallback($originalArg, $scope); | 
| 5314:  |  | 
| 5315:  | 			$originalScope = $scope; | 
| 5316:  | 			$scopeToPass = $scope; | 
| 5317:  | 			if ($i === 0 && $closureBindScope !== null) { | 
| 5318:  | 				$scopeToPass = $closureBindScope; | 
| 5319:  | 			} | 
| 5320:  |  | 
| 5321:  | 			$parameterCallableType = null; | 
| 5322:  | 			if ($parameterType !== null) { | 
| 5323:  | 				$parameterCallableType = TypeUtils::findCallableType($parameterType); | 
| 5324:  | 			} | 
| 5325:  |  | 
| 5326:  | 			if ($parameter instanceof ExtendedParameterReflection) { | 
| 5327:  | 				$parameterCallImmediately = $parameter->isImmediatelyInvokedCallable(); | 
| 5328:  | 				if ($parameterCallImmediately->maybe()) { | 
| 5329:  | 					$callCallbackImmediately = $parameterCallableType !== null && $calleeReflection instanceof FunctionReflection; | 
| 5330:  | 				} else { | 
| 5331:  | 					$callCallbackImmediately = $parameterCallImmediately->yes(); | 
| 5332:  | 				} | 
| 5333:  | 			} else { | 
| 5334:  | 				$callCallbackImmediately = $parameterCallableType !== null && $calleeReflection instanceof FunctionReflection; | 
| 5335:  | 			} | 
| 5336:  |  | 
| 5337:  | 			if ($arg->value instanceof Expr\Closure) { | 
| 5338:  | 				$restoreThisScope = null; | 
| 5339:  | 				if ( | 
| 5340:  | 					$closureBindScope === null | 
| 5341:  | 					&& $parameter instanceof ExtendedParameterReflection | 
| 5342:  | 					&& !$arg->value->static | 
| 5343:  | 				) { | 
| 5344:  | 					$closureThisType = $this->resolveClosureThisType($callLike, $calleeReflection, $parameter, $scopeToPass); | 
| 5345:  | 					if ($closureThisType !== null) { | 
| 5346:  | 						$restoreThisScope = $scopeToPass; | 
| 5347:  | 						$scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes()); | 
| 5348:  | 					} | 
| 5349:  | 				} | 
| 5350:  |  | 
| 5351:  | 				if ($parameter !== null) { | 
| 5352:  | 					$overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); | 
| 5353:  |  | 
| 5354:  | 					if ($overwritingParameterType !== null) { | 
| 5355:  | 						$parameterType = $overwritingParameterType; | 
| 5356:  | 					} | 
| 5357:  | 				} | 
| 5358:  |  | 
| 5359:  | 				$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context); | 
| 5360:  | 				$closureResult = $this->processClosureNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $context, $parameterType ?? null); | 
| 5361:  | 				if ($callCallbackImmediately) { | 
| 5362:  | 					$throwPoints = array_merge($throwPoints, array_map(static fn (ThrowPoint $throwPoint) => $throwPoint->isExplicit() ? ThrowPoint::createExplicit($scope, $throwPoint->getType(), $arg->value, $throwPoint->canContainAnyThrowable()) : ThrowPoint::createImplicit($scope, $arg->value), $closureResult->getThrowPoints())); | 
| 5363:  | 					$impurePoints = array_merge($impurePoints, $closureResult->getImpurePoints()); | 
| 5364:  | 					$isAlwaysTerminating = $isAlwaysTerminating || $closureResult->isAlwaysTerminating(); | 
| 5365:  | 				} | 
| 5366:  |  | 
| 5367:  | 				$uses = []; | 
| 5368:  | 				foreach ($arg->value->uses as $use) { | 
| 5369:  | 					if (!is_string($use->var->name)) { | 
| 5370:  | 						continue; | 
| 5371:  | 					} | 
| 5372:  |  | 
| 5373:  | 					$uses[] = $use->var->name; | 
| 5374:  | 				} | 
| 5375:  |  | 
| 5376:  | 				$scope = $closureResult->getScope(); | 
| 5377:  | 				$invalidateExpressions = $closureResult->getInvalidateExpressions(); | 
| 5378:  | 				if ($restoreThisScope !== null) { | 
| 5379:  | 					$nodeFinder = new NodeFinder(); | 
| 5380:  | 					$cb = static fn ($expr) => $expr instanceof Variable && $expr->name === 'this'; | 
| 5381:  | 					foreach ($invalidateExpressions as $j => $invalidateExprNode) { | 
| 5382:  | 						$foundThis = $nodeFinder->findFirst([$invalidateExprNode->getExpr()], $cb); | 
| 5383:  | 						if ($foundThis === null) { | 
| 5384:  | 							continue; | 
| 5385:  | 						} | 
| 5386:  |  | 
| 5387:  | 						unset($invalidateExpressions[$j]); | 
| 5388:  | 					} | 
| 5389:  | 					$invalidateExpressions = array_values($invalidateExpressions); | 
| 5390:  | 					$scope = $scope->restoreThis($restoreThisScope); | 
| 5391:  | 				} | 
| 5392:  |  | 
| 5393:  | 				$scope = $this->processImmediatelyCalledCallable($scope, $invalidateExpressions, $uses); | 
| 5394:  | 			} elseif ($arg->value instanceof Expr\ArrowFunction) { | 
| 5395:  | 				if ( | 
| 5396:  | 					$closureBindScope === null | 
| 5397:  | 					&& $parameter instanceof ExtendedParameterReflection | 
| 5398:  | 					&& !$arg->value->static | 
| 5399:  | 				) { | 
| 5400:  | 					$closureThisType = $this->resolveClosureThisType($callLike, $calleeReflection, $parameter, $scopeToPass); | 
| 5401:  | 					if ($closureThisType !== null) { | 
| 5402:  | 						$scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes()); | 
| 5403:  | 					} | 
| 5404:  | 				} | 
| 5405:  |  | 
| 5406:  | 				if ($parameter !== null) { | 
| 5407:  | 					$overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass); | 
| 5408:  |  | 
| 5409:  | 					if ($overwritingParameterType !== null) { | 
| 5410:  | 						$parameterType = $overwritingParameterType; | 
| 5411:  | 					} | 
| 5412:  | 				} | 
| 5413:  |  | 
| 5414:  | 				$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context); | 
| 5415:  | 				$arrowFunctionResult = $this->processArrowFunctionNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $parameterType ?? null); | 
| 5416:  | 				if ($callCallbackImmediately) { | 
| 5417:  | 					$throwPoints = array_merge($throwPoints, array_map(static fn (ThrowPoint $throwPoint) => $throwPoint->isExplicit() ? ThrowPoint::createExplicit($scope, $throwPoint->getType(), $arg->value, $throwPoint->canContainAnyThrowable()) : ThrowPoint::createImplicit($scope, $arg->value), $arrowFunctionResult->getThrowPoints())); | 
| 5418:  | 					$impurePoints = array_merge($impurePoints, $arrowFunctionResult->getImpurePoints()); | 
| 5419:  | 					$isAlwaysTerminating = $isAlwaysTerminating || $arrowFunctionResult->isAlwaysTerminating(); | 
| 5420:  | 				} | 
| 5421:  | 			} else { | 
| 5422:  | 				$exprType = $scope->getType($arg->value); | 
| 5423:  | 				$exprResult = $this->processExprNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $context->enterDeep()); | 
| 5424:  | 				$throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); | 
| 5425:  | 				$impurePoints = array_merge($impurePoints, $exprResult->getImpurePoints()); | 
| 5426:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $exprResult->isAlwaysTerminating(); | 
| 5427:  | 				$scope = $exprResult->getScope(); | 
| 5428:  | 				$hasYield = $hasYield || $exprResult->hasYield(); | 
| 5429:  |  | 
| 5430:  | 				if ($exprType->isCallable()->yes()) { | 
| 5431:  | 					$acceptors = $exprType->getCallableParametersAcceptors($scope); | 
| 5432:  | 					if (count($acceptors) === 1) { | 
| 5433:  | 						$scope = $this->processImmediatelyCalledCallable($scope, $acceptors[0]->getInvalidateExpressions(), $acceptors[0]->getUsedVariables()); | 
| 5434:  | 						if ($callCallbackImmediately) { | 
| 5435:  | 							$callableThrowPoints = array_map(static fn (SimpleThrowPoint $throwPoint) => $throwPoint->isExplicit() ? ThrowPoint::createExplicit($scope, $throwPoint->getType(), $arg->value, $throwPoint->canContainAnyThrowable()) : ThrowPoint::createImplicit($scope, $arg->value), $acceptors[0]->getThrowPoints()); | 
| 5436:  | 							if (!$this->implicitThrows) { | 
| 5437:  | 								$callableThrowPoints = array_values(array_filter($callableThrowPoints, static fn (ThrowPoint $throwPoint) => $throwPoint->isExplicit())); | 
| 5438:  | 							} | 
| 5439:  | 							$throwPoints = array_merge($throwPoints, $callableThrowPoints); | 
| 5440:  | 							$impurePoints = array_merge($impurePoints, array_map(static fn (SimpleImpurePoint $impurePoint) => new ImpurePoint($scope, $arg->value, $impurePoint->getIdentifier(), $impurePoint->getDescription(), $impurePoint->isCertain()), $acceptors[0]->getImpurePoints())); | 
| 5441:  | 							$returnType = $acceptors[0]->getReturnType(); | 
| 5442:  | 							$isAlwaysTerminating = $isAlwaysTerminating || ($returnType instanceof NeverType && $returnType->isExplicit()); | 
| 5443:  | 						} | 
| 5444:  | 					} | 
| 5445:  | 				} | 
| 5446:  | 			} | 
| 5447:  |  | 
| 5448:  | 			if ($assignByReference && $lookForUnset) { | 
| 5449:  | 				$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $arg->value); | 
| 5450:  | 			} | 
| 5451:  |  | 
| 5452:  | 			if ($calleeReflection !== null) { | 
| 5453:  | 				$scope = $scope->popInFunctionCall(); | 
| 5454:  | 			} | 
| 5455:  |  | 
| 5456:  | 			if ($i !== 0 || $closureBindScope === null) { | 
| 5457:  | 				continue; | 
| 5458:  | 			} | 
| 5459:  |  | 
| 5460:  | 			$scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope); | 
| 5461:  | 		} | 
| 5462:  | 		foreach ($args as $i => $arg) { | 
| 5463:  | 			if (!isset($parameters) || $parametersAcceptor === null) { | 
| 5464:  | 				continue; | 
| 5465:  | 			} | 
| 5466:  |  | 
| 5467:  | 			$byRefType = new MixedType(); | 
| 5468:  | 			$assignByReference = false; | 
| 5469:  | 			$currentParameter = null; | 
| 5470:  | 			if (isset($parameters[$i])) { | 
| 5471:  | 				$currentParameter = $parameters[$i]; | 
| 5472:  | 			} elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) { | 
| 5473:  | 				$currentParameter = array_last($parameters); | 
| 5474:  | 			} | 
| 5475:  |  | 
| 5476:  | 			if ($currentParameter !== null) { | 
| 5477:  | 				$assignByReference = $currentParameter->passedByReference()->createsNewVariable(); | 
| 5478:  | 				if ($assignByReference) { | 
| 5479:  | 					if ($currentParameter instanceof ExtendedParameterReflection && $currentParameter->getOutType() !== null) { | 
| 5480:  | 						$byRefType = $currentParameter->getOutType(); | 
| 5481:  | 					} elseif ( | 
| 5482:  | 						$calleeReflection instanceof MethodReflection | 
| 5483:  | 						&& !$calleeReflection->getDeclaringClass()->isBuiltin() | 
| 5484:  | 					) { | 
| 5485:  | 						$byRefType = $currentParameter->getType(); | 
| 5486:  | 					} elseif ( | 
| 5487:  | 						$calleeReflection instanceof FunctionReflection | 
| 5488:  | 						&& !$calleeReflection->isBuiltin() | 
| 5489:  | 					) { | 
| 5490:  | 						$byRefType = $currentParameter->getType(); | 
| 5491:  | 					} | 
| 5492:  | 				} | 
| 5493:  | 			} | 
| 5494:  |  | 
| 5495:  | 			if ($assignByReference) { | 
| 5496:  | 				if ($currentParameter === null) { | 
| 5497:  | 					throw new ShouldNotHappenException(); | 
| 5498:  | 				} | 
| 5499:  |  | 
| 5500:  | 				$argValue = $arg->value; | 
| 5501:  | 				if (!$argValue instanceof Variable || $argValue->name !== 'this') { | 
| 5502:  | 					$paramOutType = $this->getParameterOutExtensionsType($callLike, $calleeReflection, $currentParameter, $scope); | 
| 5503:  | 					if ($paramOutType !== null) { | 
| 5504:  | 						$byRefType = $paramOutType; | 
| 5505:  | 					} | 
| 5506:  |  | 
| 5507:  | 					$scope = $this->processVirtualAssign( | 
| 5508:  | 						$scope, | 
| 5509:  | 						$stmt, | 
| 5510:  | 						$argValue, | 
| 5511:  | 						new TypeExpr($byRefType), | 
| 5512:  | 						$nodeCallback, | 
| 5513:  | 					)->getScope(); | 
| 5514:  | 				} | 
| 5515:  | 			} elseif ($calleeReflection !== null && $calleeReflection->hasSideEffects()->yes()) { | 
| 5516:  | 				$argType = $scope->getType($arg->value); | 
| 5517:  | 				if (!$argType->isObject()->no()) { | 
| 5518:  | 					$nakedReturnType = null; | 
| 5519:  | 					if ($nakedMethodReflection !== null) { | 
| 5520:  | 						$nakedParametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 5521:  | 							$scope, | 
| 5522:  | 							$args, | 
| 5523:  | 							$nakedMethodReflection->getVariants(), | 
| 5524:  | 							$nakedMethodReflection->getNamedArgumentsVariants(), | 
| 5525:  | 						); | 
| 5526:  | 						$nakedReturnType = $nakedParametersAcceptor->getReturnType(); | 
| 5527:  | 					} | 
| 5528:  | 					if ( | 
| 5529:  | 						$nakedReturnType === null | 
| 5530:  | 						|| !(new ThisType($nakedMethodReflection->getDeclaringClass()))->isSuperTypeOf($nakedReturnType)->yes() | 
| 5531:  | 						|| $nakedMethodReflection->isPure()->no() | 
| 5532:  | 					) { | 
| 5533:  | 						$nodeCallback(new InvalidateExprNode($arg->value), $scope); | 
| 5534:  | 						$scope = $scope->invalidateExpression($arg->value, true); | 
| 5535:  | 					} | 
| 5536:  | 				} elseif (!(new ResourceType())->isSuperTypeOf($argType)->no()) { | 
| 5537:  | 					$nodeCallback(new InvalidateExprNode($arg->value), $scope); | 
| 5538:  | 					$scope = $scope->invalidateExpression($arg->value, true); | 
| 5539:  | 				} | 
| 5540:  | 			} | 
| 5541:  | 		} | 
| 5542:  |  | 
| 5543:  | 		return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); | 
| 5544:  | 	} | 
| 5545:  |  | 
| 5546:  | 	 | 
| 5547:  |  | 
| 5548:  |  | 
| 5549:  | 	private function getParameterTypeFromParameterClosureTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, MutatingScope $scope): ?Type | 
| 5550:  | 	{ | 
| 5551:  | 		if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) { | 
| 5552:  | 			foreach ($this->parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) { | 
| 5553:  | 				if ($functionParameterClosureTypeExtension->isFunctionSupported($calleeReflection, $parameter)) { | 
| 5554:  | 					return $functionParameterClosureTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope); | 
| 5555:  | 				} | 
| 5556:  | 			} | 
| 5557:  | 		} elseif ($calleeReflection instanceof MethodReflection) { | 
| 5558:  | 			if ($callLike instanceof StaticCall) { | 
| 5559:  | 				foreach ($this->parameterClosureTypeExtensionProvider->getStaticMethodParameterClosureTypeExtensions() as $staticMethodParameterClosureTypeExtension) { | 
| 5560:  | 					if ($staticMethodParameterClosureTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) { | 
| 5561:  | 						return $staticMethodParameterClosureTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope); | 
| 5562:  | 					} | 
| 5563:  | 				} | 
| 5564:  | 			} elseif ($callLike instanceof MethodCall) { | 
| 5565:  | 				foreach ($this->parameterClosureTypeExtensionProvider->getMethodParameterClosureTypeExtensions() as $methodParameterClosureTypeExtension) { | 
| 5566:  | 					if ($methodParameterClosureTypeExtension->isMethodSupported($calleeReflection, $parameter)) { | 
| 5567:  | 						return $methodParameterClosureTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope); | 
| 5568:  | 					} | 
| 5569:  | 				} | 
| 5570:  | 			} | 
| 5571:  | 		} | 
| 5572:  |  | 
| 5573:  | 		return null; | 
| 5574:  | 	} | 
| 5575:  |  | 
| 5576:  | 	 | 
| 5577:  |  | 
| 5578:  |  | 
| 5579:  | 	private function getParameterOutExtensionsType(CallLike $callLike, $calleeReflection, ParameterReflection $currentParameter, MutatingScope $scope): ?Type | 
| 5580:  | 	{ | 
| 5581:  | 		$paramOutTypes = []; | 
| 5582:  | 		if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) { | 
| 5583:  | 			foreach ($this->parameterOutTypeExtensionProvider->getFunctionParameterOutTypeExtensions() as $functionParameterOutTypeExtension) { | 
| 5584:  | 				if (!$functionParameterOutTypeExtension->isFunctionSupported($calleeReflection, $currentParameter)) { | 
| 5585:  | 					continue; | 
| 5586:  | 				} | 
| 5587:  |  | 
| 5588:  | 				$resolvedType = $functionParameterOutTypeExtension->getParameterOutTypeFromFunctionCall($calleeReflection, $callLike, $currentParameter, $scope); | 
| 5589:  | 				if ($resolvedType === null) { | 
| 5590:  | 					continue; | 
| 5591:  | 				} | 
| 5592:  | 				$paramOutTypes[] = $resolvedType; | 
| 5593:  | 			} | 
| 5594:  | 		} elseif ($callLike instanceof MethodCall && $calleeReflection instanceof MethodReflection) { | 
| 5595:  | 			foreach ($this->parameterOutTypeExtensionProvider->getMethodParameterOutTypeExtensions() as $methodParameterOutTypeExtension) { | 
| 5596:  | 				if (!$methodParameterOutTypeExtension->isMethodSupported($calleeReflection, $currentParameter)) { | 
| 5597:  | 					continue; | 
| 5598:  | 				} | 
| 5599:  |  | 
| 5600:  | 				$resolvedType = $methodParameterOutTypeExtension->getParameterOutTypeFromMethodCall($calleeReflection, $callLike, $currentParameter, $scope); | 
| 5601:  | 				if ($resolvedType === null) { | 
| 5602:  | 					continue; | 
| 5603:  | 				} | 
| 5604:  | 				$paramOutTypes[] = $resolvedType; | 
| 5605:  | 			} | 
| 5606:  | 		} elseif ($callLike instanceof StaticCall && $calleeReflection instanceof MethodReflection) { | 
| 5607:  | 			foreach ($this->parameterOutTypeExtensionProvider->getStaticMethodParameterOutTypeExtensions() as $staticMethodParameterOutTypeExtension) { | 
| 5608:  | 				if (!$staticMethodParameterOutTypeExtension->isStaticMethodSupported($calleeReflection, $currentParameter)) { | 
| 5609:  | 					continue; | 
| 5610:  | 				} | 
| 5611:  |  | 
| 5612:  | 				$resolvedType = $staticMethodParameterOutTypeExtension->getParameterOutTypeFromStaticMethodCall($calleeReflection, $callLike, $currentParameter, $scope); | 
| 5613:  | 				if ($resolvedType === null) { | 
| 5614:  | 					continue; | 
| 5615:  | 				} | 
| 5616:  | 				$paramOutTypes[] = $resolvedType; | 
| 5617:  | 			} | 
| 5618:  | 		} | 
| 5619:  |  | 
| 5620:  | 		if (count($paramOutTypes) === 1) { | 
| 5621:  | 			return $paramOutTypes[0]; | 
| 5622:  | 		} | 
| 5623:  |  | 
| 5624:  | 		if (count($paramOutTypes) > 1) { | 
| 5625:  | 			return TypeCombinator::union(...$paramOutTypes); | 
| 5626:  | 		} | 
| 5627:  |  | 
| 5628:  | 		return null; | 
| 5629:  | 	} | 
| 5630:  |  | 
| 5631:  | 	 | 
| 5632:  |  | 
| 5633:  |  | 
| 5634:  |  | 
| 5635:  | 	private function processAssignVar( | 
| 5636:  | 		MutatingScope $scope, | 
| 5637:  | 		Node\Stmt $stmt, | 
| 5638:  | 		Expr $var, | 
| 5639:  | 		Expr $assignedExpr, | 
| 5640:  | 		callable $nodeCallback, | 
| 5641:  | 		ExpressionContext $context, | 
| 5642:  | 		Closure $processExprCallback, | 
| 5643:  | 		bool $enterExpressionAssign, | 
| 5644:  | 	): ExpressionResult | 
| 5645:  | 	{ | 
| 5646:  | 		$nodeCallback($var, $enterExpressionAssign ? $scope->enterExpressionAssign($var) : $scope); | 
| 5647:  | 		$hasYield = false; | 
| 5648:  | 		$throwPoints = []; | 
| 5649:  | 		$impurePoints = []; | 
| 5650:  | 		$isAlwaysTerminating = false; | 
| 5651:  | 		$isAssignOp = $assignedExpr instanceof Expr\AssignOp && !$enterExpressionAssign; | 
| 5652:  | 		if ($var instanceof Variable && is_string($var->name)) { | 
| 5653:  | 			$result = $processExprCallback($scope); | 
| 5654:  | 			$hasYield = $result->hasYield(); | 
| 5655:  | 			$throwPoints = $result->getThrowPoints(); | 
| 5656:  | 			$impurePoints = $result->getImpurePoints(); | 
| 5657:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 5658:  | 			if (in_array($var->name, Scope::SUPERGLOBAL_VARIABLES, true)) { | 
| 5659:  | 				$impurePoints[] = new ImpurePoint($scope, $var, 'superglobal', 'assign to superglobal variable', true); | 
| 5660:  | 			} | 
| 5661:  | 			$assignedExpr = $this->unwrapAssign($assignedExpr); | 
| 5662:  | 			$type = $scope->getType($assignedExpr); | 
| 5663:  |  | 
| 5664:  | 			$conditionalExpressions = []; | 
| 5665:  | 			if ($assignedExpr instanceof Ternary) { | 
| 5666:  | 				$if = $assignedExpr->if; | 
| 5667:  | 				if ($if === null) { | 
| 5668:  | 					$if = $assignedExpr->cond; | 
| 5669:  | 				} | 
| 5670:  | 				$condScope = $this->processExprNode($stmt, $assignedExpr->cond, $scope, static function (): void { | 
| 5671:  | 				}, ExpressionContext::createDeep())->getScope(); | 
| 5672:  | 				$truthySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($condScope, $assignedExpr->cond, TypeSpecifierContext::createTruthy()); | 
| 5673:  | 				$falseySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($condScope, $assignedExpr->cond, TypeSpecifierContext::createFalsey()); | 
| 5674:  | 				$truthyScope = $condScope->filterBySpecifiedTypes($truthySpecifiedTypes); | 
| 5675:  | 				$falsyScope = $condScope->filterBySpecifiedTypes($falseySpecifiedTypes); | 
| 5676:  | 				$truthyType = $truthyScope->getType($if); | 
| 5677:  | 				$falseyType = $falsyScope->getType($assignedExpr->else); | 
| 5678:  |  | 
| 5679:  | 				if ( | 
| 5680:  | 					$truthyType->isSuperTypeOf($falseyType)->no() | 
| 5681:  | 					&& $falseyType->isSuperTypeOf($truthyType)->no() | 
| 5682:  | 				) { | 
| 5683:  | 					$conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($condScope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType); | 
| 5684:  | 					$conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($condScope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType); | 
| 5685:  | 					$conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($condScope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); | 
| 5686:  | 					$conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($condScope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); | 
| 5687:  | 				} | 
| 5688:  | 			} | 
| 5689:  |  | 
| 5690:  | 			$scopeBeforeAssignEval = $scope; | 
| 5691:  | 			$scope = $result->getScope(); | 
| 5692:  | 			$truthySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createTruthy()); | 
| 5693:  | 			$falseySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createFalsey()); | 
| 5694:  |  | 
| 5695:  | 			$truthyType = TypeCombinator::removeFalsey($type); | 
| 5696:  | 			$falseyType = TypeCombinator::intersect($type, StaticTypeFactory::falsey()); | 
| 5697:  |  | 
| 5698:  | 			$conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType); | 
| 5699:  | 			$conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType); | 
| 5700:  | 			$conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); | 
| 5701:  | 			$conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); | 
| 5702:  |  | 
| 5703:  | 			$nodeCallback(new VariableAssignNode($var, $assignedExpr), $scopeBeforeAssignEval); | 
| 5704:  | 			$scope = $scope->assignVariable($var->name, $type, $scope->getNativeType($assignedExpr), TrinaryLogic::createYes()); | 
| 5705:  | 			foreach ($conditionalExpressions as $exprString => $holders) { | 
| 5706:  | 				$scope = $scope->addConditionalExpressions($exprString, $holders); | 
| 5707:  | 			} | 
| 5708:  | 		} elseif ($var instanceof ArrayDimFetch) { | 
| 5709:  | 			$dimFetchStack = []; | 
| 5710:  | 			$originalVar = $var; | 
| 5711:  | 			$assignedPropertyExpr = $assignedExpr; | 
| 5712:  | 			while ($var instanceof ArrayDimFetch) { | 
| 5713:  | 				$varForSetOffsetValue = $var->var; | 
| 5714:  | 				if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) { | 
| 5715:  | 					$varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue); | 
| 5716:  | 				} | 
| 5717:  |  | 
| 5718:  | 				if ( | 
| 5719:  | 					$var === $originalVar | 
| 5720:  | 					&& $var->dim !== null | 
| 5721:  | 					&& $scope->hasExpressionType($var)->yes() | 
| 5722:  | 				) { | 
| 5723:  | 					$assignedPropertyExpr = new SetExistingOffsetValueTypeExpr( | 
| 5724:  | 						$varForSetOffsetValue, | 
| 5725:  | 						$var->dim, | 
| 5726:  | 						$assignedPropertyExpr, | 
| 5727:  | 					); | 
| 5728:  | 				} else { | 
| 5729:  | 					$assignedPropertyExpr = new SetOffsetValueTypeExpr( | 
| 5730:  | 						$varForSetOffsetValue, | 
| 5731:  | 						$var->dim, | 
| 5732:  | 						$assignedPropertyExpr, | 
| 5733:  | 					); | 
| 5734:  | 				} | 
| 5735:  | 				$dimFetchStack[] = $var; | 
| 5736:  | 				$var = $var->var; | 
| 5737:  | 			} | 
| 5738:  |  | 
| 5739:  | 			 | 
| 5740:  | 			if ($enterExpressionAssign) { | 
| 5741:  | 				$scope = $scope->enterExpressionAssign($var); | 
| 5742:  | 			} | 
| 5743:  | 			$result = $this->processExprNode($stmt, $var, $scope, $nodeCallback, $context->enterDeep()); | 
| 5744:  | 			$hasYield = $result->hasYield(); | 
| 5745:  | 			$throwPoints = $result->getThrowPoints(); | 
| 5746:  | 			$impurePoints = $result->getImpurePoints(); | 
| 5747:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 5748:  | 			$scope = $result->getScope(); | 
| 5749:  | 			if ($enterExpressionAssign) { | 
| 5750:  | 				$scope = $scope->exitExpressionAssign($var); | 
| 5751:  | 			} | 
| 5752:  |  | 
| 5753:  | 			 | 
| 5754:  | 			$offsetTypes = []; | 
| 5755:  | 			$offsetNativeTypes = []; | 
| 5756:  | 			$dimFetchStack = array_reverse($dimFetchStack); | 
| 5757:  | 			$lastDimKey = array_key_last($dimFetchStack); | 
| 5758:  | 			foreach ($dimFetchStack as $key => $dimFetch) { | 
| 5759:  | 				$dimExpr = $dimFetch->dim; | 
| 5760:  |  | 
| 5761:  | 				 | 
| 5762:  | 				if ($key !== $lastDimKey) { | 
| 5763:  | 					$nodeCallback($dimFetch, $enterExpressionAssign ? $scope->enterExpressionAssign($dimFetch) : $scope); | 
| 5764:  | 				} | 
| 5765:  |  | 
| 5766:  | 				if ($dimExpr === null) { | 
| 5767:  | 					$offsetTypes[] = [null, $dimFetch]; | 
| 5768:  | 					$offsetNativeTypes[] = [null, $dimFetch]; | 
| 5769:  |  | 
| 5770:  | 				} else { | 
| 5771:  | 					$offsetTypes[] = [$scope->getType($dimExpr), $dimFetch]; | 
| 5772:  | 					$offsetNativeTypes[] = [$scope->getNativeType($dimExpr), $dimFetch]; | 
| 5773:  |  | 
| 5774:  | 					if ($enterExpressionAssign) { | 
| 5775:  | 						$scope->enterExpressionAssign($dimExpr); | 
| 5776:  | 					} | 
| 5777:  | 					$result = $this->processExprNode($stmt, $dimExpr, $scope, $nodeCallback, $context->enterDeep()); | 
| 5778:  | 					$hasYield = $hasYield || $result->hasYield(); | 
| 5779:  | 					$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 5780:  | 					$scope = $result->getScope(); | 
| 5781:  |  | 
| 5782:  | 					if ($enterExpressionAssign) { | 
| 5783:  | 						$scope = $scope->exitExpressionAssign($dimExpr); | 
| 5784:  | 					} | 
| 5785:  | 				} | 
| 5786:  | 			} | 
| 5787:  |  | 
| 5788:  | 			$valueToWrite = $scope->getType($assignedExpr); | 
| 5789:  | 			$nativeValueToWrite = $scope->getNativeType($assignedExpr); | 
| 5790:  | 			$originalValueToWrite = $valueToWrite; | 
| 5791:  | 			$originalNativeValueToWrite = $nativeValueToWrite; | 
| 5792:  | 			$scopeBeforeAssignEval = $scope; | 
| 5793:  |  | 
| 5794:  | 			 | 
| 5795:  | 			$result = $processExprCallback($scope); | 
| 5796:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 5797:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 5798:  | 			$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 5799:  | 			$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 5800:  | 			$scope = $result->getScope(); | 
| 5801:  |  | 
| 5802:  | 			$varType = $scope->getType($var); | 
| 5803:  | 			$varNativeType = $scope->getNativeType($var); | 
| 5804:  |  | 
| 5805:  | 			 | 
| 5806:  | 			$isImplicitArrayCreation = $this->isImplicitArrayCreation($dimFetchStack, $scope); | 
| 5807:  | 			if ($isImplicitArrayCreation->yes()) { | 
| 5808:  | 				$varType = new ConstantArrayType([], []); | 
| 5809:  | 				$varNativeType = new ConstantArrayType([], []); | 
| 5810:  | 			} | 
| 5811:  | 			$offsetValueType = $varType; | 
| 5812:  | 			$offsetNativeValueType = $varNativeType; | 
| 5813:  |  | 
| 5814:  | 			[$valueToWrite, $additionalExpressions] = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetTypes, $offsetValueType, $valueToWrite, $scope); | 
| 5815:  |  | 
| 5816:  | 			if (!$offsetValueType->equals($offsetNativeValueType) || !$valueToWrite->equals($nativeValueToWrite)) { | 
| 5817:  | 				[$nativeValueToWrite, $additionalNativeExpressions] = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite, $scope); | 
| 5818:  | 			} else { | 
| 5819:  | 				$rewritten = false; | 
| 5820:  | 				foreach ($offsetTypes as $i => [$offsetType]) { | 
| 5821:  | 					[$offsetNativeType] = $offsetNativeTypes[$i]; | 
| 5822:  |  | 
| 5823:  | 					if ($offsetType === null) { | 
| 5824:  | 						if ($offsetNativeType !== null) { | 
| 5825:  | 							throw new ShouldNotHappenException(); | 
| 5826:  | 						} | 
| 5827:  |  | 
| 5828:  | 						continue; | 
| 5829:  | 					} elseif ($offsetNativeType === null) { | 
| 5830:  | 						throw new ShouldNotHappenException(); | 
| 5831:  | 					} | 
| 5832:  | 					if ($offsetType->equals($offsetNativeType)) { | 
| 5833:  | 						continue; | 
| 5834:  | 					} | 
| 5835:  |  | 
| 5836:  | 					[$nativeValueToWrite] = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite, $scope); | 
| 5837:  | 					$rewritten = true; | 
| 5838:  | 					break; | 
| 5839:  | 				} | 
| 5840:  |  | 
| 5841:  | 				if (!$rewritten) { | 
| 5842:  | 					$nativeValueToWrite = $valueToWrite; | 
| 5843:  | 				} | 
| 5844:  | 			} | 
| 5845:  |  | 
| 5846:  | 			if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { | 
| 5847:  | 				if ($var instanceof Variable && is_string($var->name)) { | 
| 5848:  | 					$nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr), $scopeBeforeAssignEval); | 
| 5849:  | 					$scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite, TrinaryLogic::createYes()); | 
| 5850:  | 				} else { | 
| 5851:  | 					if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { | 
| 5852:  | 						$nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scopeBeforeAssignEval); | 
| 5853:  | 						if ($var instanceof PropertyFetch && $var->name instanceof Node\Identifier && !$isAssignOp) { | 
| 5854:  | 							$scope = $scope->assignInitializedProperty($scope->getType($var->var), $var->name->toString()); | 
| 5855:  | 						} | 
| 5856:  | 					} | 
| 5857:  | 					$scope = $scope->assignExpression( | 
| 5858:  | 						$var, | 
| 5859:  | 						$valueToWrite, | 
| 5860:  | 						$nativeValueToWrite, | 
| 5861:  | 					); | 
| 5862:  | 				} | 
| 5863:  |  | 
| 5864:  | 				if ($originalVar->dim instanceof Variable || $originalVar->dim instanceof Node\Scalar) { | 
| 5865:  | 					$currentVarType = $scope->getType($originalVar); | 
| 5866:  | 					$currentVarNativeType = $scope->getNativeType($originalVar); | 
| 5867:  | 					if ( | 
| 5868:  | 						!$originalValueToWrite->isSuperTypeOf($currentVarType)->yes() | 
| 5869:  | 						|| !$originalNativeValueToWrite->isSuperTypeOf($currentVarNativeType)->yes() | 
| 5870:  | 					) { | 
| 5871:  | 						$scope = $scope->assignExpression( | 
| 5872:  | 							$originalVar, | 
| 5873:  | 							$originalValueToWrite, | 
| 5874:  | 							$originalNativeValueToWrite, | 
| 5875:  | 						); | 
| 5876:  | 					} | 
| 5877:  | 				} | 
| 5878:  | 			} else { | 
| 5879:  | 				if ($var instanceof Variable) { | 
| 5880:  | 					$nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr), $scopeBeforeAssignEval); | 
| 5881:  | 				} elseif ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { | 
| 5882:  | 					$nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scopeBeforeAssignEval); | 
| 5883:  | 					if ($var instanceof PropertyFetch && $var->name instanceof Node\Identifier && !$isAssignOp) { | 
| 5884:  | 						$scope = $scope->assignInitializedProperty($scope->getType($var->var), $var->name->toString()); | 
| 5885:  | 					} | 
| 5886:  | 				} | 
| 5887:  | 			} | 
| 5888:  |  | 
| 5889:  | 			foreach ($additionalExpressions as $k => $additionalExpression) { | 
| 5890:  | 				[$expr, $type] = $additionalExpression; | 
| 5891:  | 				$nativeType = $type; | 
| 5892:  | 				if (isset($additionalNativeExpressions[$k])) { | 
| 5893:  | 					[, $nativeType] = $additionalNativeExpressions[$k]; | 
| 5894:  | 				} | 
| 5895:  |  | 
| 5896:  | 				$scope = $scope->assignExpression($expr, $type, $nativeType); | 
| 5897:  | 			} | 
| 5898:  |  | 
| 5899:  | 			if (!$varType->isArray()->yes() && !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->no()) { | 
| 5900:  | 				$throwPoints = array_merge($throwPoints, $this->processExprNode( | 
| 5901:  | 					$stmt, | 
| 5902:  | 					new MethodCall($var, 'offsetSet'), | 
| 5903:  | 					$scope, | 
| 5904:  | 					static function (): void { | 
| 5905:  | 					}, | 
| 5906:  | 					$context, | 
| 5907:  | 				)->getThrowPoints()); | 
| 5908:  | 			} | 
| 5909:  | 		} elseif ($var instanceof PropertyFetch) { | 
| 5910:  | 			$objectResult = $this->processExprNode($stmt, $var->var, $scope, $nodeCallback, $context); | 
| 5911:  | 			$hasYield = $objectResult->hasYield(); | 
| 5912:  | 			$throwPoints = $objectResult->getThrowPoints(); | 
| 5913:  | 			$impurePoints = $objectResult->getImpurePoints(); | 
| 5914:  | 			$isAlwaysTerminating = $objectResult->isAlwaysTerminating(); | 
| 5915:  | 			$scope = $objectResult->getScope(); | 
| 5916:  |  | 
| 5917:  | 			$propertyName = null; | 
| 5918:  | 			if ($var->name instanceof Node\Identifier) { | 
| 5919:  | 				$propertyName = $var->name->name; | 
| 5920:  | 			} else { | 
| 5921:  | 				$propertyNameResult = $this->processExprNode($stmt, $var->name, $scope, $nodeCallback, $context); | 
| 5922:  | 				$hasYield = $hasYield || $propertyNameResult->hasYield(); | 
| 5923:  | 				$throwPoints = array_merge($throwPoints, $propertyNameResult->getThrowPoints()); | 
| 5924:  | 				$impurePoints = array_merge($impurePoints, $propertyNameResult->getImpurePoints()); | 
| 5925:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $propertyNameResult->isAlwaysTerminating(); | 
| 5926:  | 				$scope = $propertyNameResult->getScope(); | 
| 5927:  | 			} | 
| 5928:  |  | 
| 5929:  | 			$scopeBeforeAssignEval = $scope; | 
| 5930:  | 			$result = $processExprCallback($scope); | 
| 5931:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 5932:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 5933:  | 			$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 5934:  | 			$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 5935:  | 			$scope = $result->getScope(); | 
| 5936:  |  | 
| 5937:  | 			if ($var->name instanceof Expr && $this->phpVersion->supportsPropertyHooks()) { | 
| 5938:  | 				$throwPoints[] = ThrowPoint::createImplicit($scope, $var); | 
| 5939:  | 			} | 
| 5940:  |  | 
| 5941:  | 			$propertyHolderType = $scope->getType($var->var); | 
| 5942:  | 			if ($propertyName !== null && $propertyHolderType->hasInstanceProperty($propertyName)->yes()) { | 
| 5943:  | 				$propertyReflection = $propertyHolderType->getInstanceProperty($propertyName, $scope); | 
| 5944:  | 				$assignedExprType = $scope->getType($assignedExpr); | 
| 5945:  | 				$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scopeBeforeAssignEval); | 
| 5946:  | 				if ($propertyReflection->canChangeTypeAfterAssignment()) { | 
| 5947:  | 					if ($propertyReflection->hasNativeType()) { | 
| 5948:  | 						$propertyNativeType = $propertyReflection->getNativeType(); | 
| 5949:  |  | 
| 5950:  | 						$assignedTypeIsCompatible = $propertyNativeType->isSuperTypeOf($assignedExprType)->yes(); | 
| 5951:  | 						if (!$assignedTypeIsCompatible) { | 
| 5952:  | 							foreach (TypeUtils::flattenTypes($propertyNativeType) as $type) { | 
| 5953:  | 								if ($type->isSuperTypeOf($assignedExprType)->yes()) { | 
| 5954:  | 									$assignedTypeIsCompatible = true; | 
| 5955:  | 									break; | 
| 5956:  | 								} | 
| 5957:  | 							} | 
| 5958:  | 						} | 
| 5959:  |  | 
| 5960:  | 						if ($assignedTypeIsCompatible) { | 
| 5961:  | 							$scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); | 
| 5962:  | 						} else { | 
| 5963:  | 							$scope = $scope->assignExpression( | 
| 5964:  | 								$var, | 
| 5965:  | 								TypeCombinator::intersect($assignedExprType->toCoercedArgumentType($scope->isDeclareStrictTypes()), $propertyNativeType), | 
| 5966:  | 								TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType($scope->isDeclareStrictTypes()), $propertyNativeType), | 
| 5967:  | 							); | 
| 5968:  | 						} | 
| 5969:  | 					} else { | 
| 5970:  | 						$scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); | 
| 5971:  | 					} | 
| 5972:  | 				} | 
| 5973:  | 				$declaringClass = $propertyReflection->getDeclaringClass(); | 
| 5974:  | 				if ($declaringClass->hasNativeProperty($propertyName)) { | 
| 5975:  | 					$nativeProperty = $declaringClass->getNativeProperty($propertyName); | 
| 5976:  | 					if ( | 
| 5977:  | 						!$nativeProperty->getNativeType()->accepts($assignedExprType, true)->yes() | 
| 5978:  | 					) { | 
| 5979:  | 						$throwPoints[] = ThrowPoint::createExplicit($scope, new ObjectType(TypeError::class), $assignedExpr, false); | 
| 5980:  | 					} | 
| 5981:  | 					if ($this->phpVersion->supportsPropertyHooks()) { | 
| 5982:  | 						$throwPoints = array_merge($throwPoints, $this->getPropertyAssignThrowPointsFromSetHook($scope, $var, $nativeProperty)); | 
| 5983:  | 					} | 
| 5984:  | 					if ($enterExpressionAssign) { | 
| 5985:  | 						$scope = $scope->assignInitializedProperty($propertyHolderType, $propertyName); | 
| 5986:  | 					} | 
| 5987:  | 				} | 
| 5988:  | 			} else { | 
| 5989:  | 				 | 
| 5990:  | 				$assignedExprType = $scope->getType($assignedExpr); | 
| 5991:  | 				$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scopeBeforeAssignEval); | 
| 5992:  | 				$scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); | 
| 5993:  | 				 | 
| 5994:  | 				if (!$propertyHolderType->hasMethod('__set')->no()) { | 
| 5995:  | 					$throwPoints = array_merge($throwPoints, $this->processExprNode( | 
| 5996:  | 						$stmt, | 
| 5997:  | 						new MethodCall($var->var, '__set'), | 
| 5998:  | 						$scope, | 
| 5999:  | 						static function (): void { | 
| 6000:  | 						}, | 
| 6001:  | 						$context, | 
| 6002:  | 					)->getThrowPoints()); | 
| 6003:  | 				} | 
| 6004:  | 			} | 
| 6005:  |  | 
| 6006:  | 		} elseif ($var instanceof Expr\StaticPropertyFetch) { | 
| 6007:  | 			if ($var->class instanceof Node\Name) { | 
| 6008:  | 				$propertyHolderType = $scope->resolveTypeByName($var->class); | 
| 6009:  | 			} else { | 
| 6010:  | 				$this->processExprNode($stmt, $var->class, $scope, $nodeCallback, $context); | 
| 6011:  | 				$propertyHolderType = $scope->getType($var->class); | 
| 6012:  | 			} | 
| 6013:  |  | 
| 6014:  | 			$propertyName = null; | 
| 6015:  | 			if ($var->name instanceof Node\Identifier) { | 
| 6016:  | 				$propertyName = $var->name->name; | 
| 6017:  | 			} else { | 
| 6018:  | 				$propertyNameResult = $this->processExprNode($stmt, $var->name, $scope, $nodeCallback, $context); | 
| 6019:  | 				$hasYield = $propertyNameResult->hasYield(); | 
| 6020:  | 				$throwPoints = $propertyNameResult->getThrowPoints(); | 
| 6021:  | 				$impurePoints = $propertyNameResult->getImpurePoints(); | 
| 6022:  | 				$isAlwaysTerminating = $propertyNameResult->isAlwaysTerminating(); | 
| 6023:  | 				$scope = $propertyNameResult->getScope(); | 
| 6024:  | 			} | 
| 6025:  |  | 
| 6026:  | 			$scopeBeforeAssignEval = $scope; | 
| 6027:  | 			$result = $processExprCallback($scope); | 
| 6028:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 6029:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 6030:  | 			$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 6031:  | 			$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 6032:  | 			$scope = $result->getScope(); | 
| 6033:  |  | 
| 6034:  | 			if ($propertyName !== null) { | 
| 6035:  | 				$propertyReflection = $scope->getStaticPropertyReflection($propertyHolderType, $propertyName); | 
| 6036:  | 				$assignedExprType = $scope->getType($assignedExpr); | 
| 6037:  | 				$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scopeBeforeAssignEval); | 
| 6038:  | 				if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { | 
| 6039:  | 					if ($propertyReflection->hasNativeType()) { | 
| 6040:  | 						$propertyNativeType = $propertyReflection->getNativeType(); | 
| 6041:  | 						$assignedTypeIsCompatible = $propertyNativeType->isSuperTypeOf($assignedExprType)->yes(); | 
| 6042:  |  | 
| 6043:  | 						if (!$assignedTypeIsCompatible) { | 
| 6044:  | 							foreach (TypeUtils::flattenTypes($propertyNativeType) as $type) { | 
| 6045:  | 								if ($type->isSuperTypeOf($assignedExprType)->yes()) { | 
| 6046:  | 									$assignedTypeIsCompatible = true; | 
| 6047:  | 									break; | 
| 6048:  | 								} | 
| 6049:  | 							} | 
| 6050:  | 						} | 
| 6051:  |  | 
| 6052:  | 						if ($assignedTypeIsCompatible) { | 
| 6053:  | 							$scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); | 
| 6054:  | 						} else { | 
| 6055:  | 							$scope = $scope->assignExpression( | 
| 6056:  | 								$var, | 
| 6057:  | 								TypeCombinator::intersect($assignedExprType->toCoercedArgumentType($scope->isDeclareStrictTypes()), $propertyNativeType), | 
| 6058:  | 								TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType($scope->isDeclareStrictTypes()), $propertyNativeType), | 
| 6059:  | 							); | 
| 6060:  | 						} | 
| 6061:  | 					} else { | 
| 6062:  | 						$scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); | 
| 6063:  | 					} | 
| 6064:  | 				} | 
| 6065:  | 			} else { | 
| 6066:  | 				 | 
| 6067:  | 				$assignedExprType = $scope->getType($assignedExpr); | 
| 6068:  | 				$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scopeBeforeAssignEval); | 
| 6069:  | 				$scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); | 
| 6070:  | 			} | 
| 6071:  | 		} elseif ($var instanceof List_) { | 
| 6072:  | 			$result = $processExprCallback($scope); | 
| 6073:  | 			$hasYield = $result->hasYield(); | 
| 6074:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 6075:  | 			$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 6076:  | 			$isAlwaysTerminating = $result->isAlwaysTerminating(); | 
| 6077:  | 			$scope = $result->getScope(); | 
| 6078:  | 			foreach ($var->items as $i => $arrayItem) { | 
| 6079:  | 				if ($arrayItem === null) { | 
| 6080:  | 					continue; | 
| 6081:  | 				} | 
| 6082:  |  | 
| 6083:  | 				$itemScope = $scope; | 
| 6084:  | 				if ($enterExpressionAssign) { | 
| 6085:  | 					$itemScope = $itemScope->enterExpressionAssign($arrayItem->value); | 
| 6086:  | 				} | 
| 6087:  | 				$itemScope = $this->lookForSetAllowedUndefinedExpressions($itemScope, $arrayItem->value); | 
| 6088:  | 				$nodeCallback($arrayItem, $itemScope); | 
| 6089:  | 				if ($arrayItem->key !== null) { | 
| 6090:  | 					$keyResult = $this->processExprNode($stmt, $arrayItem->key, $itemScope, $nodeCallback, $context->enterDeep()); | 
| 6091:  | 					$hasYield = $hasYield || $keyResult->hasYield(); | 
| 6092:  | 					$throwPoints = array_merge($throwPoints, $keyResult->getThrowPoints()); | 
| 6093:  | 					$impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints()); | 
| 6094:  | 					$isAlwaysTerminating = $isAlwaysTerminating || $keyResult->isAlwaysTerminating(); | 
| 6095:  | 					$itemScope = $keyResult->getScope(); | 
| 6096:  | 				} | 
| 6097:  |  | 
| 6098:  | 				$valueResult = $this->processExprNode($stmt, $arrayItem->value, $itemScope, $nodeCallback, $context->enterDeep()); | 
| 6099:  | 				$hasYield = $hasYield || $valueResult->hasYield(); | 
| 6100:  | 				$throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); | 
| 6101:  | 				$impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints()); | 
| 6102:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $valueResult->isAlwaysTerminating(); | 
| 6103:  |  | 
| 6104:  | 				if ($arrayItem->key === null) { | 
| 6105:  | 					$dimExpr = new Node\Scalar\Int_($i); | 
| 6106:  | 				} else { | 
| 6107:  | 					$dimExpr = $arrayItem->key; | 
| 6108:  | 				} | 
| 6109:  | 				$result = $this->processAssignVar( | 
| 6110:  | 					$scope, | 
| 6111:  | 					$stmt, | 
| 6112:  | 					$arrayItem->value, | 
| 6113:  | 					new GetOffsetValueTypeExpr($assignedExpr, $dimExpr), | 
| 6114:  | 					$nodeCallback, | 
| 6115:  | 					$context, | 
| 6116:  | 					static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, false, [], []), | 
| 6117:  | 					$enterExpressionAssign, | 
| 6118:  | 				); | 
| 6119:  | 				$scope = $result->getScope(); | 
| 6120:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 6121:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 6122:  | 				$impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | 
| 6123:  | 				$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | 
| 6124:  | 			} | 
| 6125:  | 		} elseif ($var instanceof ExistingArrayDimFetch) { | 
| 6126:  | 			$dimFetchStack = []; | 
| 6127:  | 			$assignedPropertyExpr = $assignedExpr; | 
| 6128:  | 			while ($var instanceof ExistingArrayDimFetch) { | 
| 6129:  | 				$varForSetOffsetValue = $var->getVar(); | 
| 6130:  | 				if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) { | 
| 6131:  | 					$varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue); | 
| 6132:  | 				} | 
| 6133:  | 				$assignedPropertyExpr = new SetExistingOffsetValueTypeExpr( | 
| 6134:  | 					$varForSetOffsetValue, | 
| 6135:  | 					$var->getDim(), | 
| 6136:  | 					$assignedPropertyExpr, | 
| 6137:  | 				); | 
| 6138:  | 				$dimFetchStack[] = $var; | 
| 6139:  | 				$var = $var->getVar(); | 
| 6140:  | 			} | 
| 6141:  |  | 
| 6142:  | 			$offsetTypes = []; | 
| 6143:  | 			$offsetNativeTypes = []; | 
| 6144:  | 			foreach (array_reverse($dimFetchStack) as $dimFetch) { | 
| 6145:  | 				$dimExpr = $dimFetch->getDim(); | 
| 6146:  | 				$offsetTypes[] = [$scope->getType($dimExpr), $dimFetch]; | 
| 6147:  | 				$offsetNativeTypes[] = [$scope->getNativeType($dimExpr), $dimFetch]; | 
| 6148:  | 			} | 
| 6149:  |  | 
| 6150:  | 			$valueToWrite = $scope->getType($assignedExpr); | 
| 6151:  | 			$nativeValueToWrite = $scope->getNativeType($assignedExpr); | 
| 6152:  | 			$varType = $scope->getType($var); | 
| 6153:  | 			$varNativeType = $scope->getNativeType($var); | 
| 6154:  |  | 
| 6155:  | 			$offsetValueType = $varType; | 
| 6156:  | 			$offsetNativeValueType = $varNativeType; | 
| 6157:  | 			$offsetValueTypeStack = [$offsetValueType]; | 
| 6158:  | 			$offsetValueNativeTypeStack = [$offsetNativeValueType]; | 
| 6159:  | 			foreach (array_slice($offsetTypes, 0, -1) as [$offsetType]) { | 
| 6160:  | 				$offsetValueType = $offsetValueType->getOffsetValueType($offsetType); | 
| 6161:  | 				$offsetValueTypeStack[] = $offsetValueType; | 
| 6162:  | 			} | 
| 6163:  | 			foreach (array_slice($offsetNativeTypes, 0, -1) as [$offsetNativeType]) { | 
| 6164:  | 				$offsetNativeValueType = $offsetNativeValueType->getOffsetValueType($offsetNativeType); | 
| 6165:  | 				$offsetValueNativeTypeStack[] = $offsetNativeValueType; | 
| 6166:  | 			} | 
| 6167:  |  | 
| 6168:  | 			foreach (array_reverse($offsetTypes) as [$offsetType]) { | 
| 6169:  | 				 | 
| 6170:  | 				$offsetValueType = array_pop($offsetValueTypeStack); | 
| 6171:  | 				$valueToWrite = $offsetValueType->setExistingOffsetValueType($offsetType, $valueToWrite); | 
| 6172:  | 			} | 
| 6173:  | 			foreach (array_reverse($offsetNativeTypes) as [$offsetNativeType]) { | 
| 6174:  | 				 | 
| 6175:  | 				$offsetNativeValueType = array_pop($offsetValueNativeTypeStack); | 
| 6176:  | 				$nativeValueToWrite = $offsetNativeValueType->setExistingOffsetValueType($offsetNativeType, $nativeValueToWrite); | 
| 6177:  | 			} | 
| 6178:  |  | 
| 6179:  | 			if ($var instanceof Variable && is_string($var->name)) { | 
| 6180:  | 				$nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr), $scope); | 
| 6181:  | 				$scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite, TrinaryLogic::createYes()); | 
| 6182:  | 			} else { | 
| 6183:  | 				if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { | 
| 6184:  | 					$nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); | 
| 6185:  | 				} | 
| 6186:  | 				$scope = $scope->assignExpression( | 
| 6187:  | 					$var, | 
| 6188:  | 					$valueToWrite, | 
| 6189:  | 					$nativeValueToWrite, | 
| 6190:  | 				); | 
| 6191:  | 			} | 
| 6192:  | 		} | 
| 6193:  |  | 
| 6194:  | 		return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); | 
| 6195:  | 	} | 
| 6196:  |  | 
| 6197:  | 	 | 
| 6198:  |  | 
| 6199:  |  | 
| 6200:  | 	private function processVirtualAssign(MutatingScope $scope, Node\Stmt $stmt, Expr $var, Expr $assignedExpr, callable $nodeCallback): ExpressionResult | 
| 6201:  | 	{ | 
| 6202:  | 		return $this->processAssignVar( | 
| 6203:  | 			$scope, | 
| 6204:  | 			$stmt, | 
| 6205:  | 			$var, | 
| 6206:  | 			$assignedExpr, | 
| 6207:  | 			static function (Node $node, Scope $scope) use ($nodeCallback): void { | 
| 6208:  | 				if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) { | 
| 6209:  | 					return; | 
| 6210:  | 				} | 
| 6211:  |  | 
| 6212:  | 				$nodeCallback($node, $scope); | 
| 6213:  | 			}, | 
| 6214:  | 			ExpressionContext::createDeep(), | 
| 6215:  | 			static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, false, [], []), | 
| 6216:  | 			false, | 
| 6217:  | 		); | 
| 6218:  | 	} | 
| 6219:  |  | 
| 6220:  | 	 | 
| 6221:  |  | 
| 6222:  |  | 
| 6223:  | 	private function isImplicitArrayCreation(array $dimFetchStack, Scope $scope): TrinaryLogic | 
| 6224:  | 	{ | 
| 6225:  | 		if (count($dimFetchStack) === 0) { | 
| 6226:  | 			return TrinaryLogic::createNo(); | 
| 6227:  | 		} | 
| 6228:  |  | 
| 6229:  | 		$varNode = $dimFetchStack[0]->var; | 
| 6230:  | 		if (!$varNode instanceof Variable) { | 
| 6231:  | 			return TrinaryLogic::createNo(); | 
| 6232:  | 		} | 
| 6233:  |  | 
| 6234:  | 		if (!is_string($varNode->name)) { | 
| 6235:  | 			return TrinaryLogic::createNo(); | 
| 6236:  | 		} | 
| 6237:  |  | 
| 6238:  | 		return $scope->hasVariableType($varNode->name)->negate(); | 
| 6239:  | 	} | 
| 6240:  |  | 
| 6241:  | 	 | 
| 6242:  |  | 
| 6243:  |  | 
| 6244:  |  | 
| 6245:  |  | 
| 6246:  |  | 
| 6247:  | 	private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, array $offsetTypes, Type $offsetValueType, Type $valueToWrite, Scope $scope): array | 
| 6248:  | 	{ | 
| 6249:  | 		$originalValueToWrite = $valueToWrite; | 
| 6250:  |  | 
| 6251:  | 		$offsetValueTypeStack = [$offsetValueType]; | 
| 6252:  | 		foreach (array_slice($offsetTypes, 0, -1) as [$offsetType, $dimFetch]) { | 
| 6253:  | 			if ($offsetType === null) { | 
| 6254:  | 				$offsetValueType = new ConstantArrayType([], []); | 
| 6255:  |  | 
| 6256:  | 			} else { | 
| 6257:  | 				$has = $offsetValueType->hasOffsetValueType($offsetType); | 
| 6258:  | 				if ($has->yes()) { | 
| 6259:  | 					$offsetValueType = $offsetValueType->getOffsetValueType($offsetType); | 
| 6260:  | 				} elseif ($has->maybe()) { | 
| 6261:  | 					if (!$scope->hasExpressionType($dimFetch)->yes()) { | 
| 6262:  | 						$offsetValueType = TypeCombinator::union($offsetValueType->getOffsetValueType($offsetType), new ConstantArrayType([], [])); | 
| 6263:  | 					} else { | 
| 6264:  | 						$offsetValueType = $offsetValueType->getOffsetValueType($offsetType); | 
| 6265:  | 					} | 
| 6266:  | 				} else { | 
| 6267:  | 					$offsetValueType = new ConstantArrayType([], []); | 
| 6268:  | 				} | 
| 6269:  | 			} | 
| 6270:  |  | 
| 6271:  | 			$offsetValueTypeStack[] = $offsetValueType; | 
| 6272:  | 		} | 
| 6273:  |  | 
| 6274:  | 		foreach (array_reverse($offsetTypes) as $i => [$offsetType]) { | 
| 6275:  | 			 | 
| 6276:  | 			$offsetValueType = array_pop($offsetValueTypeStack); | 
| 6277:  | 			if ( | 
| 6278:  | 				!$offsetValueType instanceof MixedType | 
| 6279:  | 				&& !$offsetValueType->isConstantArray()->yes() | 
| 6280:  | 			) { | 
| 6281:  | 				$types = [ | 
| 6282:  | 					new ArrayType(new MixedType(), new MixedType()), | 
| 6283:  | 					new ObjectType(ArrayAccess::class), | 
| 6284:  | 					new NullType(), | 
| 6285:  | 				]; | 
| 6286:  | 				if ($offsetType !== null && $offsetType->isInteger()->yes()) { | 
| 6287:  | 					$types[] = new StringType(); | 
| 6288:  | 				} | 
| 6289:  | 				$offsetValueType = TypeCombinator::intersect($offsetValueType, TypeCombinator::union(...$types)); | 
| 6290:  | 			} | 
| 6291:  |  | 
| 6292:  | 			$arrayDimFetch = $dimFetchStack[$i] ?? null; | 
| 6293:  | 			if ( | 
| 6294:  | 				$offsetType !== null | 
| 6295:  | 				&& $arrayDimFetch !== null | 
| 6296:  | 				&& $scope->hasExpressionType($arrayDimFetch)->yes() | 
| 6297:  | 			) { | 
| 6298:  | 				$hasOffsetType = null; | 
| 6299:  | 				if ($offsetType instanceof ConstantStringType || $offsetType instanceof ConstantIntegerType) { | 
| 6300:  | 					$hasOffsetType = new HasOffsetValueType($offsetType, $valueToWrite); | 
| 6301:  | 				} | 
| 6302:  | 				$valueToWrite = $offsetValueType->setExistingOffsetValueType($offsetType, $valueToWrite); | 
| 6303:  |  | 
| 6304:  | 				if ($valueToWrite->isArray()->yes()) { | 
| 6305:  | 					if ($hasOffsetType !== null) { | 
| 6306:  | 						$valueToWrite = TypeCombinator::intersect( | 
| 6307:  | 							$valueToWrite, | 
| 6308:  | 							$hasOffsetType, | 
| 6309:  | 						); | 
| 6310:  | 					} else { | 
| 6311:  | 						$valueToWrite = TypeCombinator::intersect( | 
| 6312:  | 							$valueToWrite, | 
| 6313:  | 							new NonEmptyArrayType(), | 
| 6314:  | 						); | 
| 6315:  | 					} | 
| 6316:  | 				} | 
| 6317:  |  | 
| 6318:  | 			} else { | 
| 6319:  | 				$valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0); | 
| 6320:  | 			} | 
| 6321:  |  | 
| 6322:  | 			if ($arrayDimFetch === null || !$offsetValueType->isList()->yes()) { | 
| 6323:  | 				continue; | 
| 6324:  | 			} | 
| 6325:  |  | 
| 6326:  | 			if (!$arrayDimFetch->dim instanceof BinaryOp\Plus) { | 
| 6327:  | 				continue; | 
| 6328:  | 			} | 
| 6329:  |  | 
| 6330:  | 			if (  | 
| 6331:  | 				$arrayDimFetch->dim->right instanceof Variable | 
| 6332:  | 				&& $arrayDimFetch->dim->left instanceof Node\Scalar\Int_ | 
| 6333:  | 				&& $arrayDimFetch->dim->left->value === 1 | 
| 6334:  | 				&& $scope->hasExpressionType(new ArrayDimFetch($arrayDimFetch->var, $arrayDimFetch->dim->right))->yes() | 
| 6335:  | 			) { | 
| 6336:  | 				$valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType()); | 
| 6337:  | 			} elseif (  | 
| 6338:  | 				$arrayDimFetch->dim->left instanceof Variable | 
| 6339:  | 				&& $arrayDimFetch->dim->right instanceof Node\Scalar\Int_ | 
| 6340:  | 				&& $arrayDimFetch->dim->right->value === 1 | 
| 6341:  | 				&& $scope->hasExpressionType(new ArrayDimFetch($arrayDimFetch->var, $arrayDimFetch->dim->left))->yes() | 
| 6342:  | 			) { | 
| 6343:  | 				$valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType()); | 
| 6344:  | 			} | 
| 6345:  | 		} | 
| 6346:  |  | 
| 6347:  | 		$additionalExpressions = []; | 
| 6348:  | 		$offsetValueType = $valueToWrite; | 
| 6349:  | 		$lastDimKey = array_key_last($dimFetchStack); | 
| 6350:  | 		foreach ($dimFetchStack as $key => $dimFetch) { | 
| 6351:  | 			if ($dimFetch->dim === null) { | 
| 6352:  | 				continue; | 
| 6353:  | 			} | 
| 6354:  |  | 
| 6355:  | 			if ($key === $lastDimKey) { | 
| 6356:  | 				$offsetValueType = $originalValueToWrite; | 
| 6357:  | 			} else { | 
| 6358:  | 				$offsetType = $scope->getType($dimFetch->dim); | 
| 6359:  | 				$offsetValueType = $offsetValueType->getOffsetValueType($offsetType); | 
| 6360:  | 			} | 
| 6361:  |  | 
| 6362:  | 			$additionalExpressions[] = [$dimFetch, $offsetValueType]; | 
| 6363:  | 		} | 
| 6364:  |  | 
| 6365:  | 		return [$valueToWrite, $additionalExpressions]; | 
| 6366:  | 	} | 
| 6367:  |  | 
| 6368:  | 	private function unwrapAssign(Expr $expr): Expr | 
| 6369:  | 	{ | 
| 6370:  | 		if ($expr instanceof Assign) { | 
| 6371:  | 			return $this->unwrapAssign($expr->expr); | 
| 6372:  | 		} | 
| 6373:  |  | 
| 6374:  | 		return $expr; | 
| 6375:  | 	} | 
| 6376:  |  | 
| 6377:  | 	 | 
| 6378:  |  | 
| 6379:  |  | 
| 6380:  |  | 
| 6381:  | 	private function processSureTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array | 
| 6382:  | 	{ | 
| 6383:  | 		foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $exprType]) { | 
| 6384:  | 			if (!$expr instanceof Variable) { | 
| 6385:  | 				continue; | 
| 6386:  | 			} | 
| 6387:  | 			if (!is_string($expr->name)) { | 
| 6388:  | 				continue; | 
| 6389:  | 			} | 
| 6390:  |  | 
| 6391:  | 			if ($expr->name === $variableName) { | 
| 6392:  | 				continue; | 
| 6393:  | 			} | 
| 6394:  |  | 
| 6395:  | 			if (!isset($conditionalExpressions[$exprString])) { | 
| 6396:  | 				$conditionalExpressions[$exprString] = []; | 
| 6397:  | 			} | 
| 6398:  |  | 
| 6399:  | 			$holder = new ConditionalExpressionHolder([ | 
| 6400:  | 				'$' . $variableName => ExpressionTypeHolder::createYes(new Variable($variableName), $variableType), | 
| 6401:  | 			], ExpressionTypeHolder::createYes( | 
| 6402:  | 				$expr, | 
| 6403:  | 				TypeCombinator::intersect($scope->getType($expr), $exprType), | 
| 6404:  | 			)); | 
| 6405:  | 			$conditionalExpressions[$exprString][$holder->getKey()] = $holder; | 
| 6406:  | 		} | 
| 6407:  |  | 
| 6408:  | 		return $conditionalExpressions; | 
| 6409:  | 	} | 
| 6410:  |  | 
| 6411:  | 	 | 
| 6412:  |  | 
| 6413:  |  | 
| 6414:  |  | 
| 6415:  | 	private function processSureNotTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array | 
| 6416:  | 	{ | 
| 6417:  | 		foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $exprType]) { | 
| 6418:  | 			if (!$expr instanceof Variable) { | 
| 6419:  | 				continue; | 
| 6420:  | 			} | 
| 6421:  | 			if (!is_string($expr->name)) { | 
| 6422:  | 				continue; | 
| 6423:  | 			} | 
| 6424:  |  | 
| 6425:  | 			if ($expr->name === $variableName) { | 
| 6426:  | 				continue; | 
| 6427:  | 			} | 
| 6428:  |  | 
| 6429:  | 			if (!isset($conditionalExpressions[$exprString])) { | 
| 6430:  | 				$conditionalExpressions[$exprString] = []; | 
| 6431:  | 			} | 
| 6432:  |  | 
| 6433:  | 			$holder = new ConditionalExpressionHolder([ | 
| 6434:  | 				'$' . $variableName => ExpressionTypeHolder::createYes(new Variable($variableName), $variableType), | 
| 6435:  | 			], ExpressionTypeHolder::createYes( | 
| 6436:  | 				$expr, | 
| 6437:  | 				TypeCombinator::remove($scope->getType($expr), $exprType), | 
| 6438:  | 			)); | 
| 6439:  | 			$conditionalExpressions[$exprString][$holder->getKey()] = $holder; | 
| 6440:  | 		} | 
| 6441:  |  | 
| 6442:  | 		return $conditionalExpressions; | 
| 6443:  | 	} | 
| 6444:  |  | 
| 6445:  | 	 | 
| 6446:  |  | 
| 6447:  |  | 
| 6448:  | 	private function processStmtVarAnnotation(MutatingScope $scope, Node\Stmt $stmt, ?Expr $defaultExpr, callable $nodeCallback): MutatingScope | 
| 6449:  | 	{ | 
| 6450:  | 		$function = $scope->getFunction(); | 
| 6451:  | 		$variableLessTags = []; | 
| 6452:  |  | 
| 6453:  | 		foreach ($stmt->getComments() as $comment) { | 
| 6454:  | 			if (!$comment instanceof Doc) { | 
| 6455:  | 				continue; | 
| 6456:  | 			} | 
| 6457:  |  | 
| 6458:  | 			$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | 
| 6459:  | 				$scope->getFile(), | 
| 6460:  | 				$scope->isInClass() ? $scope->getClassReflection()->getName() : null, | 
| 6461:  | 				$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, | 
| 6462:  | 				$function !== null ? $function->getName() : null, | 
| 6463:  | 				$comment->getText(), | 
| 6464:  | 			); | 
| 6465:  |  | 
| 6466:  | 			$assignedVariable = null; | 
| 6467:  | 			if ( | 
| 6468:  | 				$stmt instanceof Node\Stmt\Expression | 
| 6469:  | 				&& ($stmt->expr instanceof Assign || $stmt->expr instanceof AssignRef) | 
| 6470:  | 				&& $stmt->expr->var instanceof Variable | 
| 6471:  | 				&& is_string($stmt->expr->var->name) | 
| 6472:  | 			) { | 
| 6473:  | 				$assignedVariable = $stmt->expr->var->name; | 
| 6474:  | 			} | 
| 6475:  |  | 
| 6476:  | 			foreach ($resolvedPhpDoc->getVarTags() as $name => $varTag) { | 
| 6477:  | 				if (is_int($name)) { | 
| 6478:  | 					$variableLessTags[] = $varTag; | 
| 6479:  | 					continue; | 
| 6480:  | 				} | 
| 6481:  |  | 
| 6482:  | 				if ($name === $assignedVariable) { | 
| 6483:  | 					continue; | 
| 6484:  | 				} | 
| 6485:  |  | 
| 6486:  | 				$certainty = $scope->hasVariableType($name); | 
| 6487:  | 				if ($certainty->no()) { | 
| 6488:  | 					continue; | 
| 6489:  | 				} | 
| 6490:  |  | 
| 6491:  | 				if ($scope->isInClass() && $scope->getFunction() === null) { | 
| 6492:  | 					continue; | 
| 6493:  | 				} | 
| 6494:  |  | 
| 6495:  | 				if ($scope->canAnyVariableExist()) { | 
| 6496:  | 					$certainty = TrinaryLogic::createYes(); | 
| 6497:  | 				} | 
| 6498:  |  | 
| 6499:  | 				$variableNode = new Variable($name, $stmt->getAttributes()); | 
| 6500:  | 				$originalType = $scope->getVariableType($name); | 
| 6501:  | 				if (!$originalType->equals($varTag->getType())) { | 
| 6502:  | 					$nodeCallback(new VarTagChangedExpressionTypeNode($varTag, $variableNode), $scope); | 
| 6503:  | 				} | 
| 6504:  |  | 
| 6505:  | 				$scope = $scope->assignVariable( | 
| 6506:  | 					$name, | 
| 6507:  | 					$varTag->getType(), | 
| 6508:  | 					$scope->getNativeType($variableNode), | 
| 6509:  | 					$certainty, | 
| 6510:  | 				); | 
| 6511:  | 			} | 
| 6512:  | 		} | 
| 6513:  |  | 
| 6514:  | 		if (count($variableLessTags) === 1 && $defaultExpr !== null) { | 
| 6515:  | 			$originalType = $scope->getType($defaultExpr); | 
| 6516:  | 			$varTag = $variableLessTags[0]; | 
| 6517:  | 			if (!$originalType->equals($varTag->getType())) { | 
| 6518:  | 				$nodeCallback(new VarTagChangedExpressionTypeNode($varTag, $defaultExpr), $scope); | 
| 6519:  | 			} | 
| 6520:  | 			$scope = $scope->assignExpression($defaultExpr, $varTag->getType(), new MixedType()); | 
| 6521:  | 		} | 
| 6522:  |  | 
| 6523:  | 		return $scope; | 
| 6524:  | 	} | 
| 6525:  |  | 
| 6526:  | 	 | 
| 6527:  |  | 
| 6528:  |  | 
| 6529:  | 	private function processVarAnnotation(MutatingScope $scope, array $variableNames, Node\Stmt $node, bool &$changed = false): MutatingScope | 
| 6530:  | 	{ | 
| 6531:  | 		$function = $scope->getFunction(); | 
| 6532:  | 		$varTags = []; | 
| 6533:  | 		foreach ($node->getComments() as $comment) { | 
| 6534:  | 			if (!$comment instanceof Doc) { | 
| 6535:  | 				continue; | 
| 6536:  | 			} | 
| 6537:  |  | 
| 6538:  | 			$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | 
| 6539:  | 				$scope->getFile(), | 
| 6540:  | 				$scope->isInClass() ? $scope->getClassReflection()->getName() : null, | 
| 6541:  | 				$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, | 
| 6542:  | 				$function !== null ? $function->getName() : null, | 
| 6543:  | 				$comment->getText(), | 
| 6544:  | 			); | 
| 6545:  | 			foreach ($resolvedPhpDoc->getVarTags() as $key => $varTag) { | 
| 6546:  | 				$varTags[$key] = $varTag; | 
| 6547:  | 			} | 
| 6548:  | 		} | 
| 6549:  |  | 
| 6550:  | 		if (count($varTags) === 0) { | 
| 6551:  | 			return $scope; | 
| 6552:  | 		} | 
| 6553:  |  | 
| 6554:  | 		foreach ($variableNames as $variableName) { | 
| 6555:  | 			if (!isset($varTags[$variableName])) { | 
| 6556:  | 				continue; | 
| 6557:  | 			} | 
| 6558:  |  | 
| 6559:  | 			$variableType = $varTags[$variableName]->getType(); | 
| 6560:  | 			$changed = true; | 
| 6561:  | 			$scope = $scope->assignVariable($variableName, $variableType, new MixedType(), TrinaryLogic::createYes()); | 
| 6562:  | 		} | 
| 6563:  |  | 
| 6564:  | 		if (count($variableNames) === 1 && count($varTags) === 1 && isset($varTags[0])) { | 
| 6565:  | 			$variableType = $varTags[0]->getType(); | 
| 6566:  | 			$changed = true; | 
| 6567:  | 			$scope = $scope->assignVariable($variableNames[0], $variableType, new MixedType(), TrinaryLogic::createYes()); | 
| 6568:  | 		} | 
| 6569:  |  | 
| 6570:  | 		return $scope; | 
| 6571:  | 	} | 
| 6572:  |  | 
| 6573:  | 	 | 
| 6574:  |  | 
| 6575:  |  | 
| 6576:  | 	private function enterForeach(MutatingScope $scope, MutatingScope $originalScope, Foreach_ $stmt, callable $nodeCallback): MutatingScope | 
| 6577:  | 	{ | 
| 6578:  | 		if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) { | 
| 6579:  | 			$scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt); | 
| 6580:  | 		} | 
| 6581:  |  | 
| 6582:  | 		$iterateeType = $originalScope->getType($stmt->expr); | 
| 6583:  | 		if ( | 
| 6584:  | 			($stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name)) | 
| 6585:  | 			&& ($stmt->keyVar === null || ($stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name))) | 
| 6586:  | 		) { | 
| 6587:  | 			$keyVarName = null; | 
| 6588:  | 			if ($stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name)) { | 
| 6589:  | 				$keyVarName = $stmt->keyVar->name; | 
| 6590:  | 			} | 
| 6591:  | 			$scope = $scope->enterForeach( | 
| 6592:  | 				$originalScope, | 
| 6593:  | 				$stmt->expr, | 
| 6594:  | 				$stmt->valueVar->name, | 
| 6595:  | 				$keyVarName, | 
| 6596:  | 			); | 
| 6597:  | 			$vars = [$stmt->valueVar->name]; | 
| 6598:  | 			if ($keyVarName !== null) { | 
| 6599:  | 				$vars[] = $keyVarName; | 
| 6600:  | 			} | 
| 6601:  | 		} else { | 
| 6602:  | 			$scope = $this->processVirtualAssign( | 
| 6603:  | 				$scope, | 
| 6604:  | 				$stmt, | 
| 6605:  | 				$stmt->valueVar, | 
| 6606:  | 				new GetIterableValueTypeExpr($stmt->expr), | 
| 6607:  | 				$nodeCallback, | 
| 6608:  | 			)->getScope(); | 
| 6609:  | 			$vars = $this->getAssignedVariables($stmt->valueVar); | 
| 6610:  | 			if ( | 
| 6611:  | 				$stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name) | 
| 6612:  | 			) { | 
| 6613:  | 				$scope = $scope->enterForeachKey($originalScope, $stmt->expr, $stmt->keyVar->name); | 
| 6614:  | 				$vars[] = $stmt->keyVar->name; | 
| 6615:  | 			} elseif ($stmt->keyVar !== null) { | 
| 6616:  | 				$scope = $this->processVirtualAssign( | 
| 6617:  | 					$scope, | 
| 6618:  | 					$stmt, | 
| 6619:  | 					$stmt->keyVar, | 
| 6620:  | 					new GetIterableKeyTypeExpr($stmt->expr), | 
| 6621:  | 					$nodeCallback, | 
| 6622:  | 				)->getScope(); | 
| 6623:  | 				$vars = array_merge($vars, $this->getAssignedVariables($stmt->keyVar)); | 
| 6624:  | 			} | 
| 6625:  | 		} | 
| 6626:  |  | 
| 6627:  | 		$constantArrays = $iterateeType->getConstantArrays(); | 
| 6628:  | 		if ( | 
| 6629:  | 			$stmt->getDocComment() === null | 
| 6630:  | 			&& $iterateeType->isConstantArray()->yes() | 
| 6631:  | 			&& count($constantArrays) === 1 | 
| 6632:  | 			&& $stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name) | 
| 6633:  | 			&& $stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name) | 
| 6634:  | 		) { | 
| 6635:  | 			$valueConditionalHolders = []; | 
| 6636:  | 			$arrayDimFetchConditionalHolders = []; | 
| 6637:  | 			foreach ($constantArrays[0]->getKeyTypes() as $i => $keyType) { | 
| 6638:  | 				$valueType = $constantArrays[0]->getValueTypes()[$i]; | 
| 6639:  | 				$holder = new ConditionalExpressionHolder([ | 
| 6640:  | 					'$' . $stmt->keyVar->name => ExpressionTypeHolder::createYes(new Variable($stmt->keyVar->name), $keyType), | 
| 6641:  | 				], new ExpressionTypeHolder($stmt->valueVar, $valueType, TrinaryLogic::createYes())); | 
| 6642:  | 				$valueConditionalHolders[$holder->getKey()] = $holder; | 
| 6643:  | 				$arrayDimFetchHolder = new ConditionalExpressionHolder([ | 
| 6644:  | 					'$' . $stmt->keyVar->name => ExpressionTypeHolder::createYes(new Variable($stmt->keyVar->name), $keyType), | 
| 6645:  | 				], new ExpressionTypeHolder(new ArrayDimFetch($stmt->expr, $stmt->keyVar), $valueType, TrinaryLogic::createYes())); | 
| 6646:  | 				$arrayDimFetchConditionalHolders[$arrayDimFetchHolder->getKey()] = $arrayDimFetchHolder; | 
| 6647:  | 			} | 
| 6648:  |  | 
| 6649:  | 			$scope = $scope->addConditionalExpressions( | 
| 6650:  | 				'$' . $stmt->valueVar->name, | 
| 6651:  | 				$valueConditionalHolders, | 
| 6652:  | 			); | 
| 6653:  | 			if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) { | 
| 6654:  | 				$scope = $scope->addConditionalExpressions( | 
| 6655:  | 					sprintf('$%s[$%s]', $stmt->expr->name, $stmt->keyVar->name), | 
| 6656:  | 					$arrayDimFetchConditionalHolders, | 
| 6657:  | 				); | 
| 6658:  | 			} | 
| 6659:  | 		} | 
| 6660:  |  | 
| 6661:  | 		if ( | 
| 6662:  | 			$stmt->expr instanceof FuncCall | 
| 6663:  | 			&& $stmt->expr->name instanceof Name | 
| 6664:  | 			&& $stmt->expr->name->toLowerString() === 'array_keys' | 
| 6665:  | 			&& $stmt->valueVar instanceof Variable | 
| 6666:  | 		) { | 
| 6667:  | 			$args = $stmt->expr->getArgs(); | 
| 6668:  | 			if (count($args) >= 1) { | 
| 6669:  | 				$arrayArg = $args[0]->value; | 
| 6670:  | 				$scope = $scope->assignExpression( | 
| 6671:  | 					new ArrayDimFetch($arrayArg, $stmt->valueVar), | 
| 6672:  | 					$scope->getType($arrayArg)->getIterableValueType(), | 
| 6673:  | 					$scope->getNativeType($arrayArg)->getIterableValueType(), | 
| 6674:  | 				); | 
| 6675:  | 			} | 
| 6676:  | 		} | 
| 6677:  |  | 
| 6678:  | 		return $this->processVarAnnotation($scope, $vars, $stmt); | 
| 6679:  | 	} | 
| 6680:  |  | 
| 6681:  | 	 | 
| 6682:  |  | 
| 6683:  |  | 
| 6684:  | 	private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classScope, callable $nodeCallback): void | 
| 6685:  | 	{ | 
| 6686:  | 		$parentTraitNames = []; | 
| 6687:  | 		$parent = $classScope->getParentScope(); | 
| 6688:  | 		while ($parent !== null) { | 
| 6689:  | 			if ($parent->isInTrait()) { | 
| 6690:  | 				$parentTraitNames[] = $parent->getTraitReflection()->getName(); | 
| 6691:  | 			} | 
| 6692:  | 			$parent = $parent->getParentScope(); | 
| 6693:  | 		} | 
| 6694:  |  | 
| 6695:  | 		foreach ($node->traits as $trait) { | 
| 6696:  | 			$traitName = (string) $trait; | 
| 6697:  | 			if (in_array($traitName, $parentTraitNames, true)) { | 
| 6698:  | 				continue; | 
| 6699:  | 			} | 
| 6700:  | 			if (!$this->reflectionProvider->hasClass($traitName)) { | 
| 6701:  | 				continue; | 
| 6702:  | 			} | 
| 6703:  | 			$traitReflection = $this->reflectionProvider->getClass($traitName); | 
| 6704:  | 			$traitFileName = $traitReflection->getFileName(); | 
| 6705:  | 			if ($traitFileName === null) { | 
| 6706:  | 				continue;  | 
| 6707:  | 			} | 
| 6708:  | 			$fileName = $this->fileHelper->normalizePath($traitFileName); | 
| 6709:  | 			if (!isset($this->analysedFiles[$fileName])) { | 
| 6710:  | 				continue; | 
| 6711:  | 			} | 
| 6712:  | 			$adaptations = []; | 
| 6713:  | 			foreach ($node->adaptations as $adaptation) { | 
| 6714:  | 				if ($adaptation->trait === null) { | 
| 6715:  | 					$adaptations[] = $adaptation; | 
| 6716:  | 					continue; | 
| 6717:  | 				} | 
| 6718:  | 				if ($adaptation->trait->toLowerString() !== $trait->toLowerString()) { | 
| 6719:  | 					continue; | 
| 6720:  | 				} | 
| 6721:  |  | 
| 6722:  | 				$adaptations[] = $adaptation; | 
| 6723:  | 			} | 
| 6724:  | 			$parserNodes = $this->parser->parseFile($fileName); | 
| 6725:  | 			$this->processNodesForTraitUse($parserNodes, $traitReflection, $classScope, $adaptations, $nodeCallback); | 
| 6726:  | 		} | 
| 6727:  | 	} | 
| 6728:  |  | 
| 6729:  | 	 | 
| 6730:  |  | 
| 6731:  |  | 
| 6732:  |  | 
| 6733:  |  | 
| 6734:  | 	private function processNodesForTraitUse($node, ClassReflection $traitReflection, MutatingScope $scope, array $adaptations, callable $nodeCallback): void | 
| 6735:  | 	{ | 
| 6736:  | 		if ($node instanceof Node) { | 
| 6737:  | 			if ($node instanceof Node\Stmt\Trait_ && $traitReflection->getName() === (string) $node->namespacedName && $traitReflection->getNativeReflection()->getStartLine() === $node->getStartLine()) { | 
| 6738:  | 				$methodModifiers = []; | 
| 6739:  | 				$methodNames = []; | 
| 6740:  | 				foreach ($adaptations as $adaptation) { | 
| 6741:  | 					if (!$adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { | 
| 6742:  | 						continue; | 
| 6743:  | 					} | 
| 6744:  |  | 
| 6745:  | 					$methodName = $adaptation->method->toLowerString(); | 
| 6746:  | 					if ($adaptation->newModifier !== null) { | 
| 6747:  | 						$methodModifiers[$methodName] = $adaptation->newModifier; | 
| 6748:  | 					} | 
| 6749:  |  | 
| 6750:  | 					if ($adaptation->newName === null) { | 
| 6751:  | 						continue; | 
| 6752:  | 					} | 
| 6753:  |  | 
| 6754:  | 					$methodNames[$methodName] = $adaptation->newName; | 
| 6755:  | 				} | 
| 6756:  |  | 
| 6757:  | 				$stmts = $node->stmts; | 
| 6758:  | 				foreach ($stmts as $i => $stmt) { | 
| 6759:  | 					if (!$stmt instanceof Node\Stmt\ClassMethod) { | 
| 6760:  | 						continue; | 
| 6761:  | 					} | 
| 6762:  | 					$methodName = $stmt->name->toLowerString(); | 
| 6763:  | 					$methodAst = clone $stmt; | 
| 6764:  | 					$stmts[$i] = $methodAst; | 
| 6765:  | 					if (array_key_exists($methodName, $methodModifiers)) { | 
| 6766:  | 						$methodAst->flags = ($methodAst->flags & ~ Modifiers::VISIBILITY_MASK) | $methodModifiers[$methodName]; | 
| 6767:  | 					} | 
| 6768:  |  | 
| 6769:  | 					if (!array_key_exists($methodName, $methodNames)) { | 
| 6770:  | 						continue; | 
| 6771:  | 					} | 
| 6772:  |  | 
| 6773:  | 					$methodAst->setAttribute('originalTraitMethodName', $methodAst->name->toLowerString()); | 
| 6774:  | 					$methodAst->name = $methodNames[$methodName]; | 
| 6775:  | 				} | 
| 6776:  |  | 
| 6777:  | 				if (!$scope->isInClass()) { | 
| 6778:  | 					throw new ShouldNotHappenException(); | 
| 6779:  | 				} | 
| 6780:  | 				$traitScope = $scope->enterTrait($traitReflection); | 
| 6781:  | 				$nodeCallback(new InTraitNode($node, $traitReflection, $scope->getClassReflection()), $traitScope); | 
| 6782:  | 				$this->processStmtNodes($node, $stmts, $traitScope, $nodeCallback, StatementContext::createTopLevel()); | 
| 6783:  | 				return; | 
| 6784:  | 			} | 
| 6785:  | 			if ($node instanceof Node\Stmt\ClassLike) { | 
| 6786:  | 				return; | 
| 6787:  | 			} | 
| 6788:  | 			if ($node instanceof Node\FunctionLike) { | 
| 6789:  | 				return; | 
| 6790:  | 			} | 
| 6791:  | 			foreach ($node->getSubNodeNames() as $subNodeName) { | 
| 6792:  | 				$subNode = $node->{$subNodeName}; | 
| 6793:  | 				$this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); | 
| 6794:  | 			} | 
| 6795:  | 		} elseif (is_array($node)) { | 
| 6796:  | 			foreach ($node as $subNode) { | 
| 6797:  | 				$this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); | 
| 6798:  | 			} | 
| 6799:  | 		} | 
| 6800:  | 	} | 
| 6801:  |  | 
| 6802:  | 	private function processCalledMethod(MethodReflection $methodReflection): ?MutatingScope | 
| 6803:  | 	{ | 
| 6804:  | 		$declaringClass = $methodReflection->getDeclaringClass(); | 
| 6805:  | 		if ($declaringClass->isAnonymous()) { | 
| 6806:  | 			return null; | 
| 6807:  | 		} | 
| 6808:  | 		if ($declaringClass->getFileName() === null) { | 
| 6809:  | 			return null; | 
| 6810:  | 		} | 
| 6811:  |  | 
| 6812:  | 		$stackName = sprintf('%s::%s', $declaringClass->getName(), $methodReflection->getName()); | 
| 6813:  | 		if (array_key_exists($stackName, $this->calledMethodResults)) { | 
| 6814:  | 			return $this->calledMethodResults[$stackName]; | 
| 6815:  | 		} | 
| 6816:  |  | 
| 6817:  | 		if (array_key_exists($stackName, $this->calledMethodStack)) { | 
| 6818:  | 			return null; | 
| 6819:  | 		} | 
| 6820:  |  | 
| 6821:  | 		if (count($this->calledMethodStack) > 0) { | 
| 6822:  | 			return null; | 
| 6823:  | 		} | 
| 6824:  |  | 
| 6825:  | 		$this->calledMethodStack[$stackName] = true; | 
| 6826:  |  | 
| 6827:  | 		$fileName = $this->fileHelper->normalizePath($declaringClass->getFileName()); | 
| 6828:  | 		if (!isset($this->analysedFiles[$fileName])) { | 
| 6829:  | 			return null; | 
| 6830:  | 		} | 
| 6831:  | 		$parserNodes = $this->parser->parseFile($fileName); | 
| 6832:  |  | 
| 6833:  | 		$returnStatement = null; | 
| 6834:  | 		$this->processNodesForCalledMethod($parserNodes, $fileName, $methodReflection, static function (Node $node, Scope $scope) use ($methodReflection, &$returnStatement): void { | 
| 6835:  | 			if (!$node instanceof MethodReturnStatementsNode) { | 
| 6836:  | 				return; | 
| 6837:  | 			} | 
| 6838:  |  | 
| 6839:  | 			if ($node->getClassReflection()->getName() !== $methodReflection->getDeclaringClass()->getName()) { | 
| 6840:  | 				return; | 
| 6841:  | 			} | 
| 6842:  |  | 
| 6843:  | 			if ($returnStatement !== null) { | 
| 6844:  | 				return; | 
| 6845:  | 			} | 
| 6846:  |  | 
| 6847:  | 			$returnStatement = $node; | 
| 6848:  | 		}); | 
| 6849:  |  | 
| 6850:  | 		$calledMethodEndScope = null; | 
| 6851:  | 		if ($returnStatement !== null) { | 
| 6852:  | 			foreach ($returnStatement->getExecutionEnds() as $executionEnd) { | 
| 6853:  | 				$statementResult = $executionEnd->getStatementResult(); | 
| 6854:  | 				$endNode = $executionEnd->getNode(); | 
| 6855:  | 				if ($endNode instanceof Node\Stmt\Expression) { | 
| 6856:  | 					$exprType = $statementResult->getScope()->getType($endNode->expr); | 
| 6857:  | 					if ($exprType instanceof NeverType && $exprType->isExplicit()) { | 
| 6858:  | 						continue; | 
| 6859:  | 					} | 
| 6860:  | 				} | 
| 6861:  | 				if ($calledMethodEndScope === null) { | 
| 6862:  | 					$calledMethodEndScope = $statementResult->getScope(); | 
| 6863:  | 					continue; | 
| 6864:  | 				} | 
| 6865:  |  | 
| 6866:  | 				$calledMethodEndScope = $calledMethodEndScope->mergeWith($statementResult->getScope()); | 
| 6867:  | 			} | 
| 6868:  | 			foreach ($returnStatement->getReturnStatements() as $statement) { | 
| 6869:  | 				if ($calledMethodEndScope === null) { | 
| 6870:  | 					$calledMethodEndScope = $statement->getScope(); | 
| 6871:  | 					continue; | 
| 6872:  | 				} | 
| 6873:  |  | 
| 6874:  | 				$calledMethodEndScope = $calledMethodEndScope->mergeWith($statement->getScope()); | 
| 6875:  | 			} | 
| 6876:  | 		} | 
| 6877:  |  | 
| 6878:  | 		unset($this->calledMethodStack[$stackName]); | 
| 6879:  |  | 
| 6880:  | 		$this->calledMethodResults[$stackName] = $calledMethodEndScope; | 
| 6881:  |  | 
| 6882:  | 		return $calledMethodEndScope; | 
| 6883:  | 	} | 
| 6884:  |  | 
| 6885:  | 	 | 
| 6886:  |  | 
| 6887:  |  | 
| 6888:  |  | 
| 6889:  | 	private function processNodesForCalledMethod($node, string $fileName, MethodReflection $methodReflection, callable $nodeCallback): void | 
| 6890:  | 	{ | 
| 6891:  | 		if ($node instanceof Node) { | 
| 6892:  | 			$declaringClass = $methodReflection->getDeclaringClass(); | 
| 6893:  | 			if ( | 
| 6894:  | 				$node instanceof Node\Stmt\Class_ | 
| 6895:  | 				&& isset($node->namespacedName) | 
| 6896:  | 				&& $declaringClass->getName() === (string) $node->namespacedName | 
| 6897:  | 				&& $declaringClass->getNativeReflection()->getStartLine() === $node->getStartLine() | 
| 6898:  | 			) { | 
| 6899:  |  | 
| 6900:  | 				$stmts = $node->stmts; | 
| 6901:  | 				foreach ($stmts as $stmt) { | 
| 6902:  | 					if (!$stmt instanceof Node\Stmt\ClassMethod) { | 
| 6903:  | 						continue; | 
| 6904:  | 					} | 
| 6905:  |  | 
| 6906:  | 					if ($stmt->name->toString() !== $methodReflection->getName()) { | 
| 6907:  | 						continue; | 
| 6908:  | 					} | 
| 6909:  |  | 
| 6910:  | 					if ($stmt->getEndLine() - $stmt->getStartLine() > 50) { | 
| 6911:  | 						continue; | 
| 6912:  | 					} | 
| 6913:  |  | 
| 6914:  | 					$scope = $this->scopeFactory->create(ScopeContext::create($fileName))->enterClass($declaringClass); | 
| 6915:  | 					$this->processStmtNode($stmt, $scope, $nodeCallback, StatementContext::createTopLevel()); | 
| 6916:  | 				} | 
| 6917:  | 				return; | 
| 6918:  | 			} | 
| 6919:  | 			if ($node instanceof Node\Stmt\ClassLike) { | 
| 6920:  | 				return; | 
| 6921:  | 			} | 
| 6922:  | 			if ($node instanceof Node\FunctionLike) { | 
| 6923:  | 				return; | 
| 6924:  | 			} | 
| 6925:  | 			foreach ($node->getSubNodeNames() as $subNodeName) { | 
| 6926:  | 				$subNode = $node->{$subNodeName}; | 
| 6927:  | 				$this->processNodesForCalledMethod($subNode, $fileName, $methodReflection, $nodeCallback); | 
| 6928:  | 			} | 
| 6929:  | 		} elseif (is_array($node)) { | 
| 6930:  | 			foreach ($node as $subNode) { | 
| 6931:  | 				$this->processNodesForCalledMethod($subNode, $fileName, $methodReflection, $nodeCallback); | 
| 6932:  | 			} | 
| 6933:  | 		} | 
| 6934:  | 	} | 
| 6935:  |  | 
| 6936:  | 	 | 
| 6937:  |  | 
| 6938:  |  | 
| 6939:  | 	public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $node): array | 
| 6940:  | 	{ | 
| 6941:  | 		$templateTypeMap = TemplateTypeMap::createEmpty(); | 
| 6942:  | 		$phpDocParameterTypes = []; | 
| 6943:  | 		$phpDocImmediatelyInvokedCallableParameters = []; | 
| 6944:  | 		$phpDocClosureThisTypeParameters = []; | 
| 6945:  | 		$phpDocReturnType = null; | 
| 6946:  | 		$phpDocThrowType = null; | 
| 6947:  | 		$deprecatedDescription = null; | 
| 6948:  | 		$isDeprecated = false; | 
| 6949:  | 		$isInternal = false; | 
| 6950:  | 		$isFinal = false; | 
| 6951:  | 		$isPure = null; | 
| 6952:  | 		$isAllowedPrivateMutation = false; | 
| 6953:  | 		$acceptsNamedArguments = true; | 
| 6954:  | 		$isReadOnly = $scope->isInClass() && $scope->getClassReflection()->isImmutable(); | 
| 6955:  | 		$asserts = Assertions::createEmpty(); | 
| 6956:  | 		$selfOutType = null; | 
| 6957:  | 		$docComment = $node->getDocComment() !== null | 
| 6958:  | 			? $node->getDocComment()->getText() | 
| 6959:  | 			: null; | 
| 6960:  |  | 
| 6961:  | 		$file = $scope->getFile(); | 
| 6962:  | 		$class = $scope->isInClass() ? $scope->getClassReflection()->getName() : null; | 
| 6963:  | 		$trait = $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null; | 
| 6964:  | 		$resolvedPhpDoc = null; | 
| 6965:  | 		$functionName = null; | 
| 6966:  | 		$phpDocParameterOutTypes = []; | 
| 6967:  |  | 
| 6968:  | 		if ($node instanceof Node\Stmt\ClassMethod) { | 
| 6969:  | 			if (!$scope->isInClass()) { | 
| 6970:  | 				throw new ShouldNotHappenException(); | 
| 6971:  | 			} | 
| 6972:  | 			$functionName = $node->name->name; | 
| 6973:  | 			$positionalParameterNames = array_map(static function (Node\Param $param): string { | 
| 6974:  | 				if (!$param->var instanceof Variable || !is_string($param->var->name)) { | 
| 6975:  | 					throw new ShouldNotHappenException(); | 
| 6976:  | 				} | 
| 6977:  |  | 
| 6978:  | 				return $param->var->name; | 
| 6979:  | 			}, $node->getParams()); | 
| 6980:  | 			$resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( | 
| 6981:  | 				$docComment, | 
| 6982:  | 				$file, | 
| 6983:  | 				$scope->getClassReflection(), | 
| 6984:  | 				$trait, | 
| 6985:  | 				$node->name->name, | 
| 6986:  | 				$positionalParameterNames, | 
| 6987:  | 			); | 
| 6988:  |  | 
| 6989:  | 			if ($node->name->toLowerString() === '__construct') { | 
| 6990:  | 				foreach ($node->params as $param) { | 
| 6991:  | 					if ($param->flags === 0) { | 
| 6992:  | 						continue; | 
| 6993:  | 					} | 
| 6994:  |  | 
| 6995:  | 					if ($param->getDocComment() === null) { | 
| 6996:  | 						continue; | 
| 6997:  | 					} | 
| 6998:  |  | 
| 6999:  | 					if ( | 
| 7000:  | 						!$param->var instanceof Variable | 
| 7001:  | 						|| !is_string($param->var->name) | 
| 7002:  | 					) { | 
| 7003:  | 						throw new ShouldNotHappenException(); | 
| 7004:  | 					} | 
| 7005:  |  | 
| 7006:  | 					$paramPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | 
| 7007:  | 						$file, | 
| 7008:  | 						$class, | 
| 7009:  | 						$trait, | 
| 7010:  | 						'__construct', | 
| 7011:  | 						$param->getDocComment()->getText(), | 
| 7012:  | 					); | 
| 7013:  | 					$varTags = $paramPhpDoc->getVarTags(); | 
| 7014:  | 					if (isset($varTags[0]) && count($varTags) === 1) { | 
| 7015:  | 						$phpDocType = $varTags[0]->getType(); | 
| 7016:  | 					} elseif (isset($varTags[$param->var->name])) { | 
| 7017:  | 						$phpDocType = $varTags[$param->var->name]->getType(); | 
| 7018:  | 					} else { | 
| 7019:  | 						continue; | 
| 7020:  | 					} | 
| 7021:  |  | 
| 7022:  | 					$phpDocParameterTypes[$param->var->name] = $phpDocType; | 
| 7023:  | 				} | 
| 7024:  | 			} | 
| 7025:  | 		} elseif ($node instanceof Node\Stmt\Function_) { | 
| 7026:  | 			$functionName = trim($scope->getNamespace() . '\\' . $node->name->name, '\\'); | 
| 7027:  | 		} elseif ($node instanceof Node\PropertyHook) { | 
| 7028:  | 			$propertyName = $node->getAttribute('propertyName'); | 
| 7029:  | 			if ($propertyName !== null) { | 
| 7030:  | 				$functionName = sprintf('$%s::%s', $propertyName, $node->name->toString()); | 
| 7031:  | 			} | 
| 7032:  | 		} | 
| 7033:  |  | 
| 7034:  | 		if ($docComment !== null && $resolvedPhpDoc === null) { | 
| 7035:  | 			$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | 
| 7036:  | 				$file, | 
| 7037:  | 				$class, | 
| 7038:  | 				$trait, | 
| 7039:  | 				$functionName, | 
| 7040:  | 				$docComment, | 
| 7041:  | 			); | 
| 7042:  | 		} | 
| 7043:  |  | 
| 7044:  | 		$varTags = []; | 
| 7045:  | 		if ($resolvedPhpDoc !== null) { | 
| 7046:  | 			$templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); | 
| 7047:  | 			$phpDocImmediatelyInvokedCallableParameters = $resolvedPhpDoc->getParamsImmediatelyInvokedCallable(); | 
| 7048:  | 			foreach ($resolvedPhpDoc->getParamTags() as $paramName => $paramTag) { | 
| 7049:  | 				if (array_key_exists($paramName, $phpDocParameterTypes)) { | 
| 7050:  | 					continue; | 
| 7051:  | 				} | 
| 7052:  | 				$paramType = $paramTag->getType(); | 
| 7053:  | 				if ($scope->isInClass()) { | 
| 7054:  | 					$paramType = $this->transformStaticType($scope->getClassReflection(), $paramType); | 
| 7055:  | 				} | 
| 7056:  | 				$phpDocParameterTypes[$paramName] = $paramType; | 
| 7057:  | 			} | 
| 7058:  | 			foreach ($resolvedPhpDoc->getParamClosureThisTags() as $paramName => $paramClosureThisTag) { | 
| 7059:  | 				if (array_key_exists($paramName, $phpDocClosureThisTypeParameters)) { | 
| 7060:  | 					continue; | 
| 7061:  | 				} | 
| 7062:  | 				$paramClosureThisType = $paramClosureThisTag->getType(); | 
| 7063:  | 				if ($scope->isInClass()) { | 
| 7064:  | 					$paramClosureThisType = $this->transformStaticType($scope->getClassReflection(), $paramClosureThisType); | 
| 7065:  | 				} | 
| 7066:  | 				$phpDocClosureThisTypeParameters[$paramName] = $paramClosureThisType; | 
| 7067:  | 			} | 
| 7068:  |  | 
| 7069:  | 			foreach ($resolvedPhpDoc->getParamOutTags() as $paramName => $paramOutTag) { | 
| 7070:  | 				$phpDocParameterOutTypes[$paramName] = $paramOutTag->getType(); | 
| 7071:  | 			} | 
| 7072:  | 			if ($node instanceof Node\FunctionLike) { | 
| 7073:  | 				$nativeReturnType = $scope->getFunctionType($node->getReturnType(), false, false); | 
| 7074:  | 				$phpDocReturnType = $this->getPhpDocReturnType($resolvedPhpDoc, $nativeReturnType); | 
| 7075:  | 				if ($phpDocReturnType !== null && $scope->isInClass()) { | 
| 7076:  | 					$phpDocReturnType = $this->transformStaticType($scope->getClassReflection(), $phpDocReturnType); | 
| 7077:  | 				} | 
| 7078:  | 			} | 
| 7079:  | 			$phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null; | 
| 7080:  | 			$deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; | 
| 7081:  | 			$isDeprecated = $resolvedPhpDoc->isDeprecated(); | 
| 7082:  | 			$isInternal = $resolvedPhpDoc->isInternal(); | 
| 7083:  | 			$isFinal = $resolvedPhpDoc->isFinal(); | 
| 7084:  | 			$isPure = $resolvedPhpDoc->isPure(); | 
| 7085:  | 			$isAllowedPrivateMutation = $resolvedPhpDoc->isAllowedPrivateMutation(); | 
| 7086:  | 			$acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments(); | 
| 7087:  | 			if ($acceptsNamedArguments && $scope->isInClass()) { | 
| 7088:  | 				$acceptsNamedArguments = $scope->getClassReflection()->acceptsNamedArguments(); | 
| 7089:  | 			} | 
| 7090:  | 			$isReadOnly = $isReadOnly || $resolvedPhpDoc->isReadOnly(); | 
| 7091:  | 			$asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc); | 
| 7092:  | 			$selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null; | 
| 7093:  | 			$varTags = $resolvedPhpDoc->getVarTags(); | 
| 7094:  | 		} | 
| 7095:  |  | 
| 7096:  | 		return [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes, $varTags, $isAllowedPrivateMutation]; | 
| 7097:  | 	} | 
| 7098:  |  | 
| 7099:  | 	private function transformStaticType(ClassReflection $declaringClass, Type $type): Type | 
| 7100:  | 	{ | 
| 7101:  | 		return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($declaringClass): Type { | 
| 7102:  | 			if ($type instanceof StaticType) { | 
| 7103:  | 				$changedType = $type->changeBaseClass($declaringClass); | 
| 7104:  | 				if ($declaringClass->isFinal() && !$type instanceof ThisType) { | 
| 7105:  | 					$changedType = $changedType->getStaticObjectType(); | 
| 7106:  | 				} | 
| 7107:  | 				return $traverse($changedType); | 
| 7108:  | 			} | 
| 7109:  |  | 
| 7110:  | 			return $traverse($type); | 
| 7111:  | 		}); | 
| 7112:  | 	} | 
| 7113:  |  | 
| 7114:  | 	private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $nativeReturnType): ?Type | 
| 7115:  | 	{ | 
| 7116:  | 		$returnTag = $resolvedPhpDoc->getReturnTag(); | 
| 7117:  |  | 
| 7118:  | 		if ($returnTag === null) { | 
| 7119:  | 			return null; | 
| 7120:  | 		} | 
| 7121:  |  | 
| 7122:  | 		$phpDocReturnType = $returnTag->getType(); | 
| 7123:  |  | 
| 7124:  | 		if ($returnTag->isExplicit()) { | 
| 7125:  | 			return $phpDocReturnType; | 
| 7126:  | 		} | 
| 7127:  |  | 
| 7128:  | 		if ($nativeReturnType->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocReturnType))->yes()) { | 
| 7129:  | 			return $phpDocReturnType; | 
| 7130:  | 		} | 
| 7131:  |  | 
| 7132:  | 		if ($phpDocReturnType instanceof UnionType) { | 
| 7133:  | 			$types = []; | 
| 7134:  | 			foreach ($phpDocReturnType->getTypes() as $innerType) { | 
| 7135:  | 				if (!$nativeReturnType->isSuperTypeOf($innerType)->yes()) { | 
| 7136:  | 					continue; | 
| 7137:  | 				} | 
| 7138:  |  | 
| 7139:  | 				$types[] = $innerType; | 
| 7140:  | 			} | 
| 7141:  |  | 
| 7142:  | 			if (count($types) === 0) { | 
| 7143:  | 				return null; | 
| 7144:  | 			} | 
| 7145:  |  | 
| 7146:  | 			return TypeCombinator::union(...$types); | 
| 7147:  | 		} | 
| 7148:  |  | 
| 7149:  | 		return null; | 
| 7150:  | 	} | 
| 7151:  |  | 
| 7152:  | 	 | 
| 7153:  |  | 
| 7154:  |  | 
| 7155:  |  | 
| 7156:  | 	private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): array | 
| 7157:  | 	{ | 
| 7158:  | 		$stmts = []; | 
| 7159:  | 		$isPassedUnreachableStatement = false; | 
| 7160:  | 		foreach ($nodes as $node) { | 
| 7161:  | 			if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) { | 
| 7162:  | 				continue; | 
| 7163:  | 			} | 
| 7164:  | 			if ($isPassedUnreachableStatement && $node instanceof Node\Stmt) { | 
| 7165:  | 				$stmts[] = $node; | 
| 7166:  | 				continue; | 
| 7167:  | 			} | 
| 7168:  | 			if ($node instanceof Node\Stmt\Nop || $node instanceof Node\Stmt\InlineHTML) { | 
| 7169:  | 				continue; | 
| 7170:  | 			} | 
| 7171:  | 			if (!$node instanceof Node\Stmt) { | 
| 7172:  | 				continue; | 
| 7173:  | 			} | 
| 7174:  | 			$stmts[] = $node; | 
| 7175:  | 			$isPassedUnreachableStatement = true; | 
| 7176:  | 		} | 
| 7177:  | 		return $stmts; | 
| 7178:  | 	} | 
| 7179:  |  | 
| 7180:  | 	 | 
| 7181:  |  | 
| 7182:  |  | 
| 7183:  | 	private function getFilteringExprForMatchArm(Expr\Match_ $expr, array $conditions): BinaryOp\Identical|FuncCall | 
| 7184:  | 	{ | 
| 7185:  | 		if (count($conditions) === 1) { | 
| 7186:  | 			return new BinaryOp\Identical($expr->cond, $conditions[0]); | 
| 7187:  | 		} | 
| 7188:  |  | 
| 7189:  | 		$items = []; | 
| 7190:  | 		foreach ($conditions as $filteringExpr) { | 
| 7191:  | 			$items[] = new Node\ArrayItem($filteringExpr); | 
| 7192:  | 		} | 
| 7193:  |  | 
| 7194:  | 		return new FuncCall( | 
| 7195:  | 			new Name\FullyQualified('in_array'), | 
| 7196:  | 			[ | 
| 7197:  | 				new Arg($expr->cond), | 
| 7198:  | 				new Arg(new Array_($items)), | 
| 7199:  | 				new Arg(new ConstFetch(new Name\FullyQualified('true'))), | 
| 7200:  | 			], | 
| 7201:  | 		); | 
| 7202:  | 	} | 
| 7203:  |  | 
| 7204:  | 	private function inferForLoopExpressions(For_ $stmt, Expr $lastCondExpr, MutatingScope $bodyScope): MutatingScope | 
| 7205:  | 	{ | 
| 7206:  | 		 | 
| 7207:  |  | 
| 7208:  | 		if ( | 
| 7209:  | 			 | 
| 7210:  | 			count($stmt->init) === 1 | 
| 7211:  | 			&& $stmt->init[0] instanceof Assign | 
| 7212:  | 			&& $stmt->init[0]->var instanceof Variable | 
| 7213:  | 			&& $stmt->init[0]->expr instanceof Node\Scalar\Int_ | 
| 7214:  | 			&& $stmt->init[0]->expr->value === 0 | 
| 7215:  | 			 | 
| 7216:  | 			&& count($stmt->loop) === 1 | 
| 7217:  | 			&& ($stmt->loop[0] instanceof Expr\PreInc || $stmt->loop[0] instanceof Expr\PostInc) | 
| 7218:  | 			&& $stmt->loop[0]->var instanceof Variable | 
| 7219:  | 		) { | 
| 7220:  | 			 | 
| 7221:  | 			if ( | 
| 7222:  | 				$lastCondExpr instanceof BinaryOp\Smaller | 
| 7223:  | 				&& $lastCondExpr->left instanceof Variable | 
| 7224:  | 				&& $lastCondExpr->right instanceof FuncCall | 
| 7225:  | 				&& $lastCondExpr->right->name instanceof Name | 
| 7226:  | 				&& $lastCondExpr->right->name->toLowerString() === 'count' | 
| 7227:  | 				&& count($lastCondExpr->right->getArgs()) > 0 | 
| 7228:  | 				&& $lastCondExpr->right->getArgs()[0]->value instanceof Variable | 
| 7229:  | 				&& is_string($stmt->init[0]->var->name) | 
| 7230:  | 				&& $stmt->init[0]->var->name === $stmt->loop[0]->var->name | 
| 7231:  | 				&& $stmt->init[0]->var->name === $lastCondExpr->left->name | 
| 7232:  | 			) { | 
| 7233:  | 				$arrayArg = $lastCondExpr->right->getArgs()[0]->value; | 
| 7234:  | 				$bodyScope = $bodyScope->assignExpression( | 
| 7235:  | 					new ArrayDimFetch($lastCondExpr->right->getArgs()[0]->value, $lastCondExpr->left), | 
| 7236:  | 					$bodyScope->getType($arrayArg)->getIterableValueType(), | 
| 7237:  | 					$bodyScope->getNativeType($arrayArg)->getIterableValueType(), | 
| 7238:  | 				); | 
| 7239:  | 			} | 
| 7240:  |  | 
| 7241:  | 			 | 
| 7242:  | 			if ( | 
| 7243:  | 				$lastCondExpr instanceof BinaryOp\Greater | 
| 7244:  | 				&& $lastCondExpr->right instanceof Variable | 
| 7245:  | 				&& $lastCondExpr->left instanceof FuncCall | 
| 7246:  | 				&& $lastCondExpr->left->name instanceof Name | 
| 7247:  | 				&& $lastCondExpr->left->name->toLowerString() === 'count' | 
| 7248:  | 				&& count($lastCondExpr->left->getArgs()) > 0 | 
| 7249:  | 				&& $lastCondExpr->left->getArgs()[0]->value instanceof Variable | 
| 7250:  | 				&& is_string($stmt->init[0]->var->name) | 
| 7251:  | 				&& $stmt->init[0]->var->name === $stmt->loop[0]->var->name | 
| 7252:  | 				&& $stmt->init[0]->var->name === $lastCondExpr->right->name | 
| 7253:  | 			) { | 
| 7254:  | 				$arrayArg = $lastCondExpr->left->getArgs()[0]->value; | 
| 7255:  | 				$bodyScope = $bodyScope->assignExpression( | 
| 7256:  | 					new ArrayDimFetch($lastCondExpr->left->getArgs()[0]->value, $lastCondExpr->right), | 
| 7257:  | 					$bodyScope->getType($arrayArg)->getIterableValueType(), | 
| 7258:  | 					$bodyScope->getNativeType($arrayArg)->getIterableValueType(), | 
| 7259:  | 				); | 
| 7260:  | 			} | 
| 7261:  | 		} | 
| 7262:  |  | 
| 7263:  | 		return $bodyScope; | 
| 7264:  | 	} | 
| 7265:  |  | 
| 7266:  | } | 
| 7267:  |  |