|    1:  | <?php declare(strict_types = 1); | 
|    2:  |  | 
|    3:  | namespace PHPStan\Analyser; | 
|    4:  |  | 
|    5:  | use ArrayAccess; | 
|    6:  | use Closure; | 
|    7:  | use DivisionByZeroError; | 
|    8:  | use PhpParser\Comment\Doc; | 
|    9:  | use PhpParser\Node; | 
|   10:  | use PhpParser\Node\Arg; | 
|   11:  | use PhpParser\Node\AttributeGroup; | 
|   12:  | use PhpParser\Node\Expr; | 
|   13:  | use PhpParser\Node\Expr\Array_; | 
|   14:  | use PhpParser\Node\Expr\ArrayDimFetch; | 
|   15:  | use PhpParser\Node\Expr\ArrayItem; | 
|   16:  | use PhpParser\Node\Expr\Assign; | 
|   17:  | use PhpParser\Node\Expr\AssignRef; | 
|   18:  | use PhpParser\Node\Expr\BinaryOp; | 
|   19:  | use PhpParser\Node\Expr\BinaryOp\BooleanAnd; | 
|   20:  | use PhpParser\Node\Expr\BinaryOp\BooleanOr; | 
|   21:  | use PhpParser\Node\Expr\BinaryOp\Coalesce; | 
|   22:  | use PhpParser\Node\Expr\BooleanNot; | 
|   23:  | use PhpParser\Node\Expr\Cast; | 
|   24:  | use PhpParser\Node\Expr\ConstFetch; | 
|   25:  | use PhpParser\Node\Expr\ErrorSuppress; | 
|   26:  | use PhpParser\Node\Expr\Exit_; | 
|   27:  | use PhpParser\Node\Expr\FuncCall; | 
|   28:  | use PhpParser\Node\Expr\Instanceof_; | 
|   29:  | use PhpParser\Node\Expr\List_; | 
|   30:  | use PhpParser\Node\Expr\MethodCall; | 
|   31:  | use PhpParser\Node\Expr\New_; | 
|   32:  | use PhpParser\Node\Expr\PropertyFetch; | 
|   33:  | use PhpParser\Node\Expr\StaticCall; | 
|   34:  | use PhpParser\Node\Expr\StaticPropertyFetch; | 
|   35:  | use PhpParser\Node\Expr\Ternary; | 
|   36:  | use PhpParser\Node\Expr\Variable; | 
|   37:  | use PhpParser\Node\Name; | 
|   38:  | use PhpParser\Node\Stmt\Break_; | 
|   39:  | use PhpParser\Node\Stmt\Class_; | 
|   40:  | use PhpParser\Node\Stmt\Continue_; | 
|   41:  | use PhpParser\Node\Stmt\Do_; | 
|   42:  | use PhpParser\Node\Stmt\Echo_; | 
|   43:  | use PhpParser\Node\Stmt\For_; | 
|   44:  | use PhpParser\Node\Stmt\Foreach_; | 
|   45:  | use PhpParser\Node\Stmt\If_; | 
|   46:  | use PhpParser\Node\Stmt\Return_; | 
|   47:  | use PhpParser\Node\Stmt\Static_; | 
|   48:  | use PhpParser\Node\Stmt\StaticVar; | 
|   49:  | use PhpParser\Node\Stmt\Switch_; | 
|   50:  | use PhpParser\Node\Stmt\Throw_; | 
|   51:  | use PhpParser\Node\Stmt\TryCatch; | 
|   52:  | use PhpParser\Node\Stmt\Unset_; | 
|   53:  | use PhpParser\Node\Stmt\While_; | 
|   54:  | use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass; | 
|   55:  | use PHPStan\BetterReflection\Reflection\ReflectionEnum; | 
|   56:  | use PHPStan\BetterReflection\Reflector\Reflector; | 
|   57:  | use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection; | 
|   58:  | use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource; | 
|   59:  | use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; | 
|   60:  | use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; | 
|   61:  | use PHPStan\File\FileHelper; | 
|   62:  | use PHPStan\File\FileReader; | 
|   63:  | use PHPStan\Node\BooleanAndNode; | 
|   64:  | use PHPStan\Node\BooleanOrNode; | 
|   65:  | use PHPStan\Node\BreaklessWhileLoopNode; | 
|   66:  | use PHPStan\Node\CatchWithUnthrownExceptionNode; | 
|   67:  | use PHPStan\Node\ClassConstantsNode; | 
|   68:  | use PHPStan\Node\ClassMethodsNode; | 
|   69:  | use PHPStan\Node\ClassPropertiesNode; | 
|   70:  | use PHPStan\Node\ClassPropertyNode; | 
|   71:  | use PHPStan\Node\ClassStatementsGatherer; | 
|   72:  | use PHPStan\Node\ClosureReturnStatementsNode; | 
|   73:  | use PHPStan\Node\DoWhileLoopConditionNode; | 
|   74:  | use PHPStan\Node\ExecutionEndNode; | 
|   75:  | use PHPStan\Node\Expr\GetIterableKeyTypeExpr; | 
|   76:  | use PHPStan\Node\Expr\GetIterableValueTypeExpr; | 
|   77:  | use PHPStan\Node\Expr\GetOffsetValueTypeExpr; | 
|   78:  | use PHPStan\Node\Expr\OriginalPropertyTypeExpr; | 
|   79:  | use PHPStan\Node\Expr\SetOffsetValueTypeExpr; | 
|   80:  | use PHPStan\Node\FinallyExitPointsNode; | 
|   81:  | use PHPStan\Node\FunctionCallableNode; | 
|   82:  | use PHPStan\Node\FunctionReturnStatementsNode; | 
|   83:  | use PHPStan\Node\InArrowFunctionNode; | 
|   84:  | use PHPStan\Node\InClassMethodNode; | 
|   85:  | use PHPStan\Node\InClassNode; | 
|   86:  | use PHPStan\Node\InClosureNode; | 
|   87:  | use PHPStan\Node\InForeachNode; | 
|   88:  | use PHPStan\Node\InFunctionNode; | 
|   89:  | use PHPStan\Node\InstantiationCallableNode; | 
|   90:  | use PHPStan\Node\LiteralArrayItem; | 
|   91:  | use PHPStan\Node\LiteralArrayNode; | 
|   92:  | use PHPStan\Node\MatchExpressionArm; | 
|   93:  | use PHPStan\Node\MatchExpressionArmCondition; | 
|   94:  | use PHPStan\Node\MatchExpressionNode; | 
|   95:  | use PHPStan\Node\MethodCallableNode; | 
|   96:  | use PHPStan\Node\MethodReturnStatementsNode; | 
|   97:  | use PHPStan\Node\PropertyAssignNode; | 
|   98:  | use PHPStan\Node\ReturnStatement; | 
|   99:  | use PHPStan\Node\StaticMethodCallableNode; | 
|  100:  | use PHPStan\Node\UnreachableStatementNode; | 
|  101:  | use PHPStan\Parser\ArrowFunctionArgVisitor; | 
|  102:  | use PHPStan\Parser\ClosureArgVisitor; | 
|  103:  | use PHPStan\Parser\Parser; | 
|  104:  | use PHPStan\Php\PhpVersion; | 
|  105:  | use PHPStan\PhpDoc\PhpDocInheritanceResolver; | 
|  106:  | use PHPStan\PhpDoc\ResolvedPhpDocBlock; | 
|  107:  | use PHPStan\PhpDoc\StubPhpDocProvider; | 
|  108:  | use PHPStan\Reflection\ClassReflection; | 
|  109:  | use PHPStan\Reflection\FunctionReflection; | 
|  110:  | use PHPStan\Reflection\InitializerExprTypeResolver; | 
|  111:  | use PHPStan\Reflection\MethodReflection; | 
|  112:  | use PHPStan\Reflection\Native\NativeMethodReflection; | 
|  113:  | use PHPStan\Reflection\Native\NativeParameterReflection; | 
|  114:  | use PHPStan\Reflection\ParametersAcceptor; | 
|  115:  | use PHPStan\Reflection\ParametersAcceptorSelector; | 
|  116:  | use PHPStan\Reflection\Php\PhpMethodReflection; | 
|  117:  | use PHPStan\Reflection\ReflectionProvider; | 
|  118:  | use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; | 
|  119:  | use PHPStan\ShouldNotHappenException; | 
|  120:  | use PHPStan\TrinaryLogic; | 
|  121:  | use PHPStan\Type\Accessory\NonEmptyArrayType; | 
|  122:  | use PHPStan\Type\ArrayType; | 
|  123:  | use PHPStan\Type\ClosureType; | 
|  124:  | use PHPStan\Type\Constant\ConstantArrayType; | 
|  125:  | use PHPStan\Type\Constant\ConstantArrayTypeBuilder; | 
|  126:  | use PHPStan\Type\Constant\ConstantBooleanType; | 
|  127:  | use PHPStan\Type\Constant\ConstantIntegerType; | 
|  128:  | use PHPStan\Type\Constant\ConstantStringType; | 
|  129:  | use PHPStan\Type\ErrorType; | 
|  130:  | use PHPStan\Type\FileTypeMapper; | 
|  131:  | use PHPStan\Type\Generic\GenericClassStringType; | 
|  132:  | use PHPStan\Type\Generic\TemplateTypeHelper; | 
|  133:  | use PHPStan\Type\Generic\TemplateTypeMap; | 
|  134:  | use PHPStan\Type\IntegerType; | 
|  135:  | use PHPStan\Type\IntersectionType; | 
|  136:  | use PHPStan\Type\MixedType; | 
|  137:  | use PHPStan\Type\NeverType; | 
|  138:  | use PHPStan\Type\NullType; | 
|  139:  | use PHPStan\Type\ObjectType; | 
|  140:  | use PHPStan\Type\StaticType; | 
|  141:  | use PHPStan\Type\StaticTypeFactory; | 
|  142:  | use PHPStan\Type\StringType; | 
|  143:  | use PHPStan\Type\Type; | 
|  144:  | use PHPStan\Type\TypeCombinator; | 
|  145:  | use PHPStan\Type\TypeTraverser; | 
|  146:  | use PHPStan\Type\TypeUtils; | 
|  147:  | use PHPStan\Type\TypeWithClassName; | 
|  148:  | use PHPStan\Type\UnionType; | 
|  149:  | use PHPStan\Type\VoidType; | 
|  150:  | use Throwable; | 
|  151:  | use Traversable; | 
|  152:  | use TypeError; | 
|  153:  | use UnhandledMatchError; | 
|  154:  | use function array_fill_keys; | 
|  155:  | use function array_filter; | 
|  156:  | use function array_key_exists; | 
|  157:  | use function array_map; | 
|  158:  | use function array_merge; | 
|  159:  | use function array_pop; | 
|  160:  | use function array_reverse; | 
|  161:  | use function array_slice; | 
|  162:  | use function base64_decode; | 
|  163:  | use function count; | 
|  164:  | use function in_array; | 
|  165:  | use function is_array; | 
|  166:  | use function is_int; | 
|  167:  | use function is_string; | 
|  168:  | use function sprintf; | 
|  169:  | use function str_starts_with; | 
|  170:  | use function strtolower; | 
|  171:  | use function trim; | 
|  172:  | use const PHP_VERSION_ID; | 
|  173:  |  | 
|  174:  | class NodeScopeResolver | 
|  175:  | { | 
|  176:  |  | 
|  177:  | 	private const LOOP_SCOPE_ITERATIONS = 3; | 
|  178:  | 	private const GENERALIZE_AFTER_ITERATION = 1; | 
|  179:  |  | 
|  180:  | 	 | 
|  181:  | 	private array $analysedFiles = []; | 
|  182:  |  | 
|  183:  | 	 | 
|  184:  | 	private array $earlyTerminatingMethodNames = []; | 
|  185:  |  | 
|  186:  | 	 | 
|  187:  |  | 
|  188:  |  | 
|  189:  |  | 
|  190:  | 	public function __construct( | 
|  191:  | 		private readonly ReflectionProvider $reflectionProvider, | 
|  192:  | 		private readonly InitializerExprTypeResolver $initializerExprTypeResolver, | 
|  193:  | 		private readonly Reflector $reflector, | 
|  194:  | 		private readonly ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, | 
|  195:  | 		private readonly Parser $parser, | 
|  196:  | 		private readonly FileTypeMapper $fileTypeMapper, | 
|  197:  | 		private readonly StubPhpDocProvider $stubPhpDocProvider, | 
|  198:  | 		private readonly PhpVersion $phpVersion, | 
|  199:  | 		private readonly PhpDocInheritanceResolver $phpDocInheritanceResolver, | 
|  200:  | 		private readonly FileHelper $fileHelper, | 
|  201:  | 		private readonly TypeSpecifier $typeSpecifier, | 
|  202:  | 		private readonly DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, | 
|  203:  | 		private readonly ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider, | 
|  204:  | 		private readonly bool $polluteScopeWithLoopInitialAssignments, | 
|  205:  | 		private readonly bool $polluteScopeWithAlwaysIterableForeach, | 
|  206:  | 		private readonly array $earlyTerminatingMethodCalls, | 
|  207:  | 		private readonly array $earlyTerminatingFunctionCalls, | 
|  208:  | 		private readonly bool $implicitThrows, | 
|  209:  | 	) | 
|  210:  | 	{ | 
|  211:  | 		$earlyTerminatingMethodNames = []; | 
|  212:  | 		foreach ($this->earlyTerminatingMethodCalls as $methodNames) { | 
|  213:  | 			foreach ($methodNames as $methodName) { | 
|  214:  | 				$earlyTerminatingMethodNames[strtolower($methodName)] = true; | 
|  215:  | 			} | 
|  216:  | 		} | 
|  217:  | 		$this->earlyTerminatingMethodNames = $earlyTerminatingMethodNames; | 
|  218:  | 	} | 
|  219:  |  | 
|  220:  | 	 | 
|  221:  |  | 
|  222:  |  | 
|  223:  |  | 
|  224:  | 	public function setAnalysedFiles(array $files): void | 
|  225:  | 	{ | 
|  226:  | 		$this->analysedFiles = array_fill_keys($files, true); | 
|  227:  | 	} | 
|  228:  |  | 
|  229:  | 	 | 
|  230:  |  | 
|  231:  |  | 
|  232:  |  | 
|  233:  |  | 
|  234:  | 	public function processNodes( | 
|  235:  | 		array $nodes, | 
|  236:  | 		MutatingScope $scope, | 
|  237:  | 		callable $nodeCallback, | 
|  238:  | 	): void | 
|  239:  | 	{ | 
|  240:  | 		$nodesCount = count($nodes); | 
|  241:  | 		foreach ($nodes as $i => $node) { | 
|  242:  | 			if (!$node instanceof Node\Stmt) { | 
|  243:  | 				continue; | 
|  244:  | 			} | 
|  245:  |  | 
|  246:  | 			$statementResult = $this->processStmtNode($node, $scope, $nodeCallback); | 
|  247:  | 			$scope = $statementResult->getScope(); | 
|  248:  | 			if (!$statementResult->isAlwaysTerminating()) { | 
|  249:  | 				continue; | 
|  250:  | 			} | 
|  251:  |  | 
|  252:  | 			if ($i < $nodesCount - 1) { | 
|  253:  | 				$nextStmt = $nodes[$i + 1]; | 
|  254:  | 				if (!$nextStmt instanceof Node\Stmt) { | 
|  255:  | 					continue; | 
|  256:  | 				} | 
|  257:  |  | 
|  258:  | 				$nodeCallback(new UnreachableStatementNode($nextStmt), $scope); | 
|  259:  | 			} | 
|  260:  | 			break; | 
|  261:  | 		} | 
|  262:  | 	} | 
|  263:  |  | 
|  264:  | 	 | 
|  265:  |  | 
|  266:  |  | 
|  267:  |  | 
|  268:  |  | 
|  269:  | 	public function processStmtNodes( | 
|  270:  | 		Node $parentNode, | 
|  271:  | 		array $stmts, | 
|  272:  | 		MutatingScope $scope, | 
|  273:  | 		callable $nodeCallback, | 
|  274:  | 	): StatementResult | 
|  275:  | 	{ | 
|  276:  | 		$exitPoints = []; | 
|  277:  | 		$throwPoints = []; | 
|  278:  | 		$alreadyTerminated = false; | 
|  279:  | 		$hasYield = false; | 
|  280:  | 		$stmtCount = count($stmts); | 
|  281:  | 		$shouldCheckLastStatement = $parentNode instanceof Node\Stmt\Function_ | 
|  282:  | 			|| $parentNode instanceof Node\Stmt\ClassMethod | 
|  283:  | 			|| $parentNode instanceof Expr\Closure; | 
|  284:  | 		foreach ($stmts as $i => $stmt) { | 
|  285:  | 			$isLast = $i === $stmtCount - 1; | 
|  286:  | 			$statementResult = $this->processStmtNode( | 
|  287:  | 				$stmt, | 
|  288:  | 				$scope, | 
|  289:  | 				$nodeCallback, | 
|  290:  | 			); | 
|  291:  | 			$scope = $statementResult->getScope(); | 
|  292:  | 			$hasYield = $hasYield || $statementResult->hasYield(); | 
|  293:  |  | 
|  294:  | 			if ($shouldCheckLastStatement && $isLast) { | 
|  295:  | 				 | 
|  296:  | 				$parentNode = $parentNode; | 
|  297:  | 				$nodeCallback(new ExecutionEndNode( | 
|  298:  | 					$stmt, | 
|  299:  | 					new StatementResult( | 
|  300:  | 						$scope, | 
|  301:  | 						$hasYield, | 
|  302:  | 						$statementResult->isAlwaysTerminating(), | 
|  303:  | 						$statementResult->getExitPoints(), | 
|  304:  | 						$statementResult->getThrowPoints(), | 
|  305:  | 					), | 
|  306:  | 					$parentNode->returnType !== null, | 
|  307:  | 				), $scope); | 
|  308:  | 			} | 
|  309:  |  | 
|  310:  | 			$exitPoints = array_merge($exitPoints, $statementResult->getExitPoints()); | 
|  311:  | 			$throwPoints = array_merge($throwPoints, $statementResult->getThrowPoints()); | 
|  312:  |  | 
|  313:  | 			if (!$statementResult->isAlwaysTerminating()) { | 
|  314:  | 				continue; | 
|  315:  | 			} | 
|  316:  |  | 
|  317:  | 			$alreadyTerminated = true; | 
|  318:  | 			if ($i < $stmtCount - 1) { | 
|  319:  | 				$nextStmt = $stmts[$i + 1]; | 
|  320:  | 				$nodeCallback(new UnreachableStatementNode($nextStmt), $scope); | 
|  321:  | 			} | 
|  322:  | 			break; | 
|  323:  | 		} | 
|  324:  |  | 
|  325:  | 		$statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints); | 
|  326:  | 		if ($stmtCount === 0 && $shouldCheckLastStatement) { | 
|  327:  | 			 | 
|  328:  | 			$parentNode = $parentNode; | 
|  329:  | 			$nodeCallback(new ExecutionEndNode( | 
|  330:  | 				$parentNode, | 
|  331:  | 				$statementResult, | 
|  332:  | 				$parentNode->returnType !== null, | 
|  333:  | 			), $scope); | 
|  334:  | 		} | 
|  335:  |  | 
|  336:  | 		return $statementResult; | 
|  337:  | 	} | 
|  338:  |  | 
|  339:  | 	 | 
|  340:  |  | 
|  341:  |  | 
|  342:  | 	private function processStmtNode( | 
|  343:  | 		Node\Stmt $stmt, | 
|  344:  | 		MutatingScope $scope, | 
|  345:  | 		callable $nodeCallback, | 
|  346:  | 	): StatementResult | 
|  347:  | 	{ | 
|  348:  | 		if ( | 
|  349:  | 			$stmt instanceof Throw_ | 
|  350:  | 			|| $stmt instanceof Return_ | 
|  351:  | 		) { | 
|  352:  | 			$scope = $this->processStmtVarAnnotation($scope, $stmt, $stmt->expr); | 
|  353:  | 		} elseif ( | 
|  354:  | 			!$stmt instanceof Static_ | 
|  355:  | 			&& !$stmt instanceof Foreach_ | 
|  356:  | 			&& !$stmt instanceof Node\Stmt\Global_ | 
|  357:  | 			&& !$stmt instanceof Node\Stmt\Property | 
|  358:  | 			&& !$stmt instanceof Node\Stmt\PropertyProperty | 
|  359:  | 		) { | 
|  360:  | 			$scope = $this->processStmtVarAnnotation($scope, $stmt, null); | 
|  361:  | 		} | 
|  362:  |  | 
|  363:  | 		if ($stmt instanceof Node\Stmt\ClassMethod) { | 
|  364:  | 			if (!$scope->isInClass()) { | 
|  365:  | 				throw new ShouldNotHappenException(); | 
|  366:  | 			} | 
|  367:  | 			if ( | 
|  368:  | 				$scope->isInTrait() | 
|  369:  | 				&& $scope->getClassReflection()->hasNativeMethod($stmt->name->toString()) | 
|  370:  | 			) { | 
|  371:  | 				$methodReflection = $scope->getClassReflection()->getNativeMethod($stmt->name->toString()); | 
|  372:  | 				if ($methodReflection instanceof NativeMethodReflection) { | 
|  373:  | 					return new StatementResult($scope, false, false, [], []); | 
|  374:  | 				} | 
|  375:  | 				if ($methodReflection instanceof PhpMethodReflection) { | 
|  376:  | 					$declaringTrait = $methodReflection->getDeclaringTrait(); | 
|  377:  | 					if ($declaringTrait === null || $declaringTrait->getName() !== $scope->getTraitReflection()->getName()) { | 
|  378:  | 						return new StatementResult($scope, false, false, [], []); | 
|  379:  | 					} | 
|  380:  | 				} | 
|  381:  | 			} | 
|  382:  | 		} | 
|  383:  |  | 
|  384:  | 		$nodeCallback($stmt, $scope); | 
|  385:  |  | 
|  386:  | 		$overridingThrowPoints = $this->getOverridingThrowPoints($stmt, $scope); | 
|  387:  |  | 
|  388:  | 		if ($stmt instanceof Node\Stmt\Declare_) { | 
|  389:  | 			$hasYield = false; | 
|  390:  | 			$throwPoints = []; | 
|  391:  | 			foreach ($stmt->declares as $declare) { | 
|  392:  | 				$nodeCallback($declare, $scope); | 
|  393:  | 				$nodeCallback($declare->value, $scope); | 
|  394:  | 				if ( | 
|  395:  | 					$declare->key->name !== 'strict_types' | 
|  396:  | 					|| !($declare->value instanceof Node\Scalar\LNumber) | 
|  397:  | 					|| $declare->value->value !== 1 | 
|  398:  | 				) { | 
|  399:  | 					continue; | 
|  400:  | 				} | 
|  401:  |  | 
|  402:  | 				$scope = $scope->enterDeclareStrictTypes(); | 
|  403:  | 			} | 
|  404:  | 		} elseif ($stmt instanceof Node\Stmt\Function_) { | 
|  405:  | 			$hasYield = false; | 
|  406:  | 			$throwPoints = []; | 
|  407:  | 			$this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback); | 
|  408:  | 			[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments] = $this->getPhpDocs($scope, $stmt); | 
|  409:  |  | 
|  410:  | 			foreach ($stmt->params as $param) { | 
|  411:  | 				$this->processParamNode($param, $scope, $nodeCallback); | 
|  412:  | 			} | 
|  413:  |  | 
|  414:  | 			if ($stmt->returnType !== null) { | 
|  415:  | 				$nodeCallback($stmt->returnType, $scope); | 
|  416:  | 			} | 
|  417:  |  | 
|  418:  | 			$functionScope = $scope->enterFunction( | 
|  419:  | 				$stmt, | 
|  420:  | 				$templateTypeMap, | 
|  421:  | 				$phpDocParameterTypes, | 
|  422:  | 				$phpDocReturnType, | 
|  423:  | 				$phpDocThrowType, | 
|  424:  | 				$deprecatedDescription, | 
|  425:  | 				$isDeprecated, | 
|  426:  | 				$isInternal, | 
|  427:  | 				$isFinal, | 
|  428:  | 				$isPure, | 
|  429:  | 				$acceptsNamedArguments, | 
|  430:  | 			); | 
|  431:  | 			$functionReflection = $functionScope->getFunction(); | 
|  432:  | 			if (!$functionReflection instanceof FunctionReflection) { | 
|  433:  | 				throw new ShouldNotHappenException(); | 
|  434:  | 			} | 
|  435:  |  | 
|  436:  | 			$nodeCallback(new InFunctionNode($functionReflection, $stmt), $functionScope); | 
|  437:  |  | 
|  438:  | 			$gatheredReturnStatements = []; | 
|  439:  | 			$executionEnds = []; | 
|  440:  | 			$statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $functionScope, static function (Node $node, Scope $scope) use ($nodeCallback, $functionScope, &$gatheredReturnStatements, &$executionEnds): void { | 
|  441:  | 				$nodeCallback($node, $scope); | 
|  442:  | 				if ($scope->getFunction() !== $functionScope->getFunction()) { | 
|  443:  | 					return; | 
|  444:  | 				} | 
|  445:  | 				if ($scope->isInAnonymousFunction()) { | 
|  446:  | 					return; | 
|  447:  | 				} | 
|  448:  | 				if ($node instanceof ExecutionEndNode) { | 
|  449:  | 					$executionEnds[] = $node; | 
|  450:  | 					return; | 
|  451:  | 				} | 
|  452:  | 				if (!$node instanceof Return_) { | 
|  453:  | 					return; | 
|  454:  | 				} | 
|  455:  |  | 
|  456:  | 				$gatheredReturnStatements[] = new ReturnStatement($scope, $node); | 
|  457:  | 			}); | 
|  458:  |  | 
|  459:  | 			$nodeCallback(new FunctionReturnStatementsNode( | 
|  460:  | 				$stmt, | 
|  461:  | 				$gatheredReturnStatements, | 
|  462:  | 				$statementResult, | 
|  463:  | 				$executionEnds, | 
|  464:  | 			), $functionScope); | 
|  465:  | 		} elseif ($stmt instanceof Node\Stmt\ClassMethod) { | 
|  466:  | 			$hasYield = false; | 
|  467:  | 			$throwPoints = []; | 
|  468:  | 			$this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback); | 
|  469:  | 			[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments] = $this->getPhpDocs($scope, $stmt); | 
|  470:  |  | 
|  471:  | 			foreach ($stmt->params as $param) { | 
|  472:  | 				$this->processParamNode($param, $scope, $nodeCallback); | 
|  473:  | 			} | 
|  474:  |  | 
|  475:  | 			if ($stmt->returnType !== null) { | 
|  476:  | 				$nodeCallback($stmt->returnType, $scope); | 
|  477:  | 			} | 
|  478:  |  | 
|  479:  | 			$methodScope = $scope->enterClassMethod( | 
|  480:  | 				$stmt, | 
|  481:  | 				$templateTypeMap, | 
|  482:  | 				$phpDocParameterTypes, | 
|  483:  | 				$phpDocReturnType, | 
|  484:  | 				$phpDocThrowType, | 
|  485:  | 				$deprecatedDescription, | 
|  486:  | 				$isDeprecated, | 
|  487:  | 				$isInternal, | 
|  488:  | 				$isFinal, | 
|  489:  | 				$isPure, | 
|  490:  | 				$acceptsNamedArguments, | 
|  491:  | 			); | 
|  492:  |  | 
|  493:  | 			if ($stmt->name->toLowerString() === '__construct') { | 
|  494:  | 				foreach ($stmt->params as $param) { | 
|  495:  | 					if ($param->flags === 0) { | 
|  496:  | 						continue; | 
|  497:  | 					} | 
|  498:  |  | 
|  499:  | 					if (!$param->var instanceof Variable || !is_string($param->var->name)) { | 
|  500:  | 						throw new ShouldNotHappenException(); | 
|  501:  | 					} | 
|  502:  | 					$phpDoc = null; | 
|  503:  | 					if ($param->getDocComment() !== null) { | 
|  504:  | 						$phpDoc = $param->getDocComment()->getText(); | 
|  505:  | 					} | 
|  506:  | 					if (!$scope->isInClass()) { | 
|  507:  | 						throw new ShouldNotHappenException(); | 
|  508:  | 					} | 
|  509:  | 					$nodeCallback(new ClassPropertyNode( | 
|  510:  | 						$param->var->name, | 
|  511:  | 						$param->flags, | 
|  512:  | 						$param->type, | 
|  513:  | 						null, | 
|  514:  | 						$phpDoc, | 
|  515:  | 						true, | 
|  516:  | 						$param, | 
|  517:  | 						false, | 
|  518:  | 						$scope->isInTrait(), | 
|  519:  | 						$scope->getClassReflection()->isReadOnly(), | 
|  520:  | 					), $methodScope); | 
|  521:  | 				} | 
|  522:  | 			} | 
|  523:  |  | 
|  524:  | 			if ($stmt->getAttribute('virtual', false) === false) { | 
|  525:  | 				$methodReflection = $methodScope->getFunction(); | 
|  526:  | 				if (!$methodReflection instanceof MethodReflection) { | 
|  527:  | 					throw new ShouldNotHappenException(); | 
|  528:  | 				} | 
|  529:  | 				$nodeCallback(new InClassMethodNode($methodReflection, $stmt), $methodScope); | 
|  530:  | 			} | 
|  531:  |  | 
|  532:  | 			if ($stmt->stmts !== null) { | 
|  533:  | 				$gatheredReturnStatements = []; | 
|  534:  | 				$executionEnds = []; | 
|  535:  | 				$statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $methodScope, static function (Node $node, Scope $scope) use ($nodeCallback, $methodScope, &$gatheredReturnStatements, &$executionEnds): void { | 
|  536:  | 					$nodeCallback($node, $scope); | 
|  537:  | 					if ($scope->getFunction() !== $methodScope->getFunction()) { | 
|  538:  | 						return; | 
|  539:  | 					} | 
|  540:  | 					if ($scope->isInAnonymousFunction()) { | 
|  541:  | 						return; | 
|  542:  | 					} | 
|  543:  | 					if ($node instanceof ExecutionEndNode) { | 
|  544:  | 						$executionEnds[] = $node; | 
|  545:  | 						return; | 
|  546:  | 					} | 
|  547:  | 					if (!$node instanceof Return_) { | 
|  548:  | 						return; | 
|  549:  | 					} | 
|  550:  |  | 
|  551:  | 					$gatheredReturnStatements[] = new ReturnStatement($scope, $node); | 
|  552:  | 				}); | 
|  553:  | 				$nodeCallback(new MethodReturnStatementsNode( | 
|  554:  | 					$stmt, | 
|  555:  | 					$gatheredReturnStatements, | 
|  556:  | 					$statementResult, | 
|  557:  | 					$executionEnds, | 
|  558:  | 				), $methodScope); | 
|  559:  | 			} | 
|  560:  | 		} elseif ($stmt instanceof Echo_) { | 
|  561:  | 			$hasYield = false; | 
|  562:  | 			$throwPoints = []; | 
|  563:  | 			foreach ($stmt->exprs as $echoExpr) { | 
|  564:  | 				$result = $this->processExprNode($echoExpr, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
|  565:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
|  566:  | 				$scope = $result->getScope(); | 
|  567:  | 				$hasYield = $hasYield || $result->hasYield(); | 
|  568:  | 			} | 
|  569:  |  | 
|  570:  | 			$throwPoints = $overridingThrowPoints ?? $throwPoints; | 
|  571:  | 		} elseif ($stmt instanceof Return_) { | 
|  572:  | 			if ($stmt->expr !== null) { | 
|  573:  | 				$result = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
|  574:  | 				$throwPoints = $result->getThrowPoints(); | 
|  575:  | 				$scope = $result->getScope(); | 
|  576:  | 				$hasYield = $result->hasYield(); | 
|  577:  | 			} else { | 
|  578:  | 				$hasYield = false; | 
|  579:  | 				$throwPoints = []; | 
|  580:  | 			} | 
|  581:  |  | 
|  582:  | 			return new StatementResult($scope, $hasYield, true, [ | 
|  583:  | 				new StatementExitPoint($stmt, $scope), | 
|  584:  | 			], $overridingThrowPoints ?? $throwPoints); | 
|  585:  | 		} elseif ($stmt instanceof Continue_ || $stmt instanceof Break_) { | 
|  586:  | 			if ($stmt->num !== null) { | 
|  587:  | 				$result = $this->processExprNode($stmt->num, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
|  588:  | 				$scope = $result->getScope(); | 
|  589:  | 				$hasYield = $result->hasYield(); | 
|  590:  | 				$throwPoints = $result->getThrowPoints(); | 
|  591:  | 			} else { | 
|  592:  | 				$hasYield = false; | 
|  593:  | 				$throwPoints = []; | 
|  594:  | 			} | 
|  595:  |  | 
|  596:  | 			return new StatementResult($scope, $hasYield, true, [ | 
|  597:  | 				new StatementExitPoint($stmt, $scope), | 
|  598:  | 			], $overridingThrowPoints ?? $throwPoints); | 
|  599:  | 		} elseif ($stmt instanceof Node\Stmt\Expression) { | 
|  600:  | 			$earlyTerminationExpr = $this->findEarlyTerminatingExpr($stmt->expr, $scope); | 
|  601:  | 			$result = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createTopLevel()); | 
|  602:  | 			$scope = $result->getScope(); | 
|  603:  | 			$scope = $scope->filterBySpecifiedTypes($this->typeSpecifier->specifyTypesInCondition( | 
|  604:  | 				$scope, | 
|  605:  | 				$stmt->expr, | 
|  606:  | 				TypeSpecifierContext::createNull(), | 
|  607:  | 			)); | 
|  608:  | 			$hasYield = $result->hasYield(); | 
|  609:  | 			$throwPoints = $result->getThrowPoints(); | 
|  610:  | 			if ($earlyTerminationExpr !== null) { | 
|  611:  | 				return new StatementResult($scope, $hasYield, true, [ | 
|  612:  | 					new StatementExitPoint($stmt, $scope), | 
|  613:  | 				], $overridingThrowPoints ?? $throwPoints); | 
|  614:  | 			} | 
|  615:  | 			return new StatementResult($scope, $hasYield, false, [], $overridingThrowPoints ?? $throwPoints); | 
|  616:  | 		} elseif ($stmt instanceof Node\Stmt\Namespace_) { | 
|  617:  | 			if ($stmt->name !== null) { | 
|  618:  | 				$scope = $scope->enterNamespace($stmt->name->toString()); | 
|  619:  | 			} | 
|  620:  |  | 
|  621:  | 			$scope = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback)->getScope(); | 
|  622:  | 			$hasYield = false; | 
|  623:  | 			$throwPoints = []; | 
|  624:  | 		} elseif ($stmt instanceof Node\Stmt\Trait_) { | 
|  625:  | 			return new StatementResult($scope, false, false, [], []); | 
|  626:  | 		} elseif ($stmt instanceof Node\Stmt\ClassLike) { | 
|  627:  | 			$hasYield = false; | 
|  628:  | 			$throwPoints = []; | 
|  629:  | 			if (isset($stmt->namespacedName)) { | 
|  630:  | 				$classReflection = $this->getCurrentClassReflection($stmt, $stmt->namespacedName->toString(), $scope); | 
|  631:  | 				$classScope = $scope->enterClass($classReflection); | 
|  632:  | 				$nodeCallback(new InClassNode($stmt, $classReflection), $classScope); | 
|  633:  | 			} elseif ($stmt instanceof Class_) { | 
|  634:  | 				if ($stmt->name === null) { | 
|  635:  | 					throw new ShouldNotHappenException(); | 
|  636:  | 				} | 
|  637:  | 				if ($stmt->getAttribute('anonymousClass', false) === false) { | 
|  638:  | 					$classReflection = $this->reflectionProvider->getClass($stmt->name->toString()); | 
|  639:  | 				} else { | 
|  640:  | 					$classReflection = $this->reflectionProvider->getAnonymousClassReflection($stmt, $scope); | 
|  641:  | 				} | 
|  642:  | 				$classScope = $scope->enterClass($classReflection); | 
|  643:  | 				$nodeCallback(new InClassNode($stmt, $classReflection), $classScope); | 
|  644:  | 			} else { | 
|  645:  | 				throw new ShouldNotHappenException(); | 
|  646:  | 			} | 
|  647:  |  | 
|  648:  | 			$classStatementsGatherer = new ClassStatementsGatherer($classReflection, $nodeCallback); | 
|  649:  | 			$this->processAttributeGroups($stmt->attrGroups, $classScope, $classStatementsGatherer); | 
|  650:  |  | 
|  651:  | 			$this->processStmtNodes($stmt, $stmt->stmts, $classScope, $classStatementsGatherer); | 
|  652:  | 			$nodeCallback(new ClassPropertiesNode($stmt, $this->readWritePropertiesExtensionProvider, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls()), $classScope); | 
|  653:  | 			$nodeCallback(new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls()), $classScope); | 
|  654:  | 			$nodeCallback(new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches()), $classScope); | 
|  655:  | 			$classReflection->evictPrivateSymbols(); | 
|  656:  | 		} elseif ($stmt instanceof Node\Stmt\Property) { | 
|  657:  | 			$hasYield = false; | 
|  658:  | 			$throwPoints = []; | 
|  659:  | 			$this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback); | 
|  660:  |  | 
|  661:  | 			foreach ($stmt->props as $prop) { | 
|  662:  | 				$this->processStmtNode($prop, $scope, $nodeCallback); | 
|  663:  | 				[,,,,,,,,,,$isReadOnly, $docComment] = $this->getPhpDocs($scope, $stmt); | 
|  664:  | 				if (!$scope->isInClass()) { | 
|  665:  | 					throw new ShouldNotHappenException(); | 
|  666:  | 				} | 
|  667:  | 				$nodeCallback( | 
|  668:  | 					new ClassPropertyNode( | 
|  669:  | 						$prop->name->toString(), | 
|  670:  | 						$stmt->flags, | 
|  671:  | 						$stmt->type, | 
|  672:  | 						$prop->default, | 
|  673:  | 						$docComment, | 
|  674:  | 						false, | 
|  675:  | 						$prop, | 
|  676:  | 						$isReadOnly, | 
|  677:  | 						$scope->isInTrait(), | 
|  678:  | 						$scope->getClassReflection()->isReadOnly(), | 
|  679:  | 					), | 
|  680:  | 					$scope, | 
|  681:  | 				); | 
|  682:  | 			} | 
|  683:  |  | 
|  684:  | 			if ($stmt->type !== null) { | 
|  685:  | 				$nodeCallback($stmt->type, $scope); | 
|  686:  | 			} | 
|  687:  | 		} elseif ($stmt instanceof Node\Stmt\PropertyProperty) { | 
|  688:  | 			$hasYield = false; | 
|  689:  | 			$throwPoints = []; | 
|  690:  | 			if ($stmt->default !== null) { | 
|  691:  | 				$this->processExprNode($stmt->default, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
|  692:  | 			} | 
|  693:  | 		} elseif ($stmt instanceof Throw_) { | 
|  694:  | 			$result = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
|  695:  | 			$throwPoints = $result->getThrowPoints(); | 
|  696:  | 			$throwPoints[] = ThrowPoint::createExplicit($result->getScope(), $scope->getType($stmt->expr), $stmt, false); | 
|  697:  | 			return new StatementResult($result->getScope(), $result->hasYield(), true, [ | 
|  698:  | 				new StatementExitPoint($stmt, $scope), | 
|  699:  | 			], $throwPoints); | 
|  700:  | 		} elseif ($stmt instanceof If_) { | 
|  701:  | 			$conditionType = $scope->getType($stmt->cond)->toBoolean(); | 
|  702:  | 			$ifAlwaysTrue = $conditionType instanceof ConstantBooleanType && $conditionType->getValue(); | 
|  703:  | 			$condResult = $this->processExprNode($stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
|  704:  | 			$exitPoints = []; | 
|  705:  | 			$throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); | 
|  706:  | 			$finalScope = null; | 
|  707:  | 			$alwaysTerminating = true; | 
|  708:  | 			$hasYield = $condResult->hasYield(); | 
|  709:  |  | 
|  710:  | 			$branchScopeStatementResult = $this->processStmtNodes($stmt, $stmt->stmts, $condResult->getTruthyScope(), $nodeCallback); | 
|  711:  |  | 
|  712:  | 			if (!$conditionType instanceof ConstantBooleanType || $conditionType->getValue()) { | 
|  713:  | 				$exitPoints = $branchScopeStatementResult->getExitPoints(); | 
|  714:  | 				$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); | 
|  715:  | 				$branchScope = $branchScopeStatementResult->getScope(); | 
|  716:  | 				$finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? null : $branchScope; | 
|  717:  | 				$alwaysTerminating = $branchScopeStatementResult->isAlwaysTerminating(); | 
|  718:  | 				$hasYield = $branchScopeStatementResult->hasYield() || $hasYield; | 
|  719:  | 			} | 
|  720:  |  | 
|  721:  | 			$scope = $condResult->getFalseyScope(); | 
|  722:  | 			$lastElseIfConditionIsTrue = false; | 
|  723:  |  | 
|  724:  | 			$condScope = $scope; | 
|  725:  | 			foreach ($stmt->elseifs as $elseif) { | 
|  726:  | 				$nodeCallback($elseif, $scope); | 
|  727:  | 				$elseIfConditionType = $condScope->getType($elseif->cond)->toBoolean(); | 
|  728:  | 				$condResult = $this->processExprNode($elseif->cond, $condScope, $nodeCallback, ExpressionContext::createDeep()); | 
|  729:  | 				$throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); | 
|  730:  | 				$condScope = $condResult->getScope(); | 
|  731:  | 				$branchScopeStatementResult = $this->processStmtNodes($elseif, $elseif->stmts, $condResult->getTruthyScope(), $nodeCallback); | 
|  732:  |  | 
|  733:  | 				if ( | 
|  734:  | 					!$ifAlwaysTrue | 
|  735:  | 					&& ( | 
|  736:  | 						!$lastElseIfConditionIsTrue | 
|  737:  | 						&& ( | 
|  738:  | 							!$elseIfConditionType instanceof ConstantBooleanType | 
|  739:  | 							|| $elseIfConditionType->getValue() | 
|  740:  | 						) | 
|  741:  | 					) | 
|  742:  | 				) { | 
|  743:  | 					$exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); | 
|  744:  | 					$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); | 
|  745:  | 					$branchScope = $branchScopeStatementResult->getScope(); | 
|  746:  | 					$finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); | 
|  747:  | 					$alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); | 
|  748:  | 					$hasYield = $hasYield || $branchScopeStatementResult->hasYield(); | 
|  749:  | 				} | 
|  750:  |  | 
|  751:  | 				if ( | 
|  752:  | 					$elseIfConditionType instanceof ConstantBooleanType | 
|  753:  | 					&& $elseIfConditionType->getValue() | 
|  754:  | 				) { | 
|  755:  | 					$lastElseIfConditionIsTrue = true; | 
|  756:  | 				} | 
|  757:  |  | 
|  758:  | 				$condScope = $condScope->filterByFalseyValue($elseif->cond); | 
|  759:  | 				$scope = $condScope; | 
|  760:  | 			} | 
|  761:  |  | 
|  762:  | 			if ($stmt->else === null) { | 
|  763:  | 				if (!$ifAlwaysTrue) { | 
|  764:  | 					$finalScope = $scope->mergeWith($finalScope); | 
|  765:  | 					$alwaysTerminating = false; | 
|  766:  | 				} | 
|  767:  | 			} else { | 
|  768:  | 				$nodeCallback($stmt->else, $scope); | 
|  769:  | 				$branchScopeStatementResult = $this->processStmtNodes($stmt->else, $stmt->else->stmts, $scope, $nodeCallback); | 
|  770:  |  | 
|  771:  | 				if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { | 
|  772:  | 					$exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); | 
|  773:  | 					$throwPoints = array_merge($throwPoints, $branchScopeStatementResult->getThrowPoints()); | 
|  774:  | 					$branchScope = $branchScopeStatementResult->getScope(); | 
|  775:  | 					$finalScope = $branchScopeStatementResult->isAlwaysTerminating() ? $finalScope : $branchScope->mergeWith($finalScope); | 
|  776:  | 					$alwaysTerminating = $alwaysTerminating && $branchScopeStatementResult->isAlwaysTerminating(); | 
|  777:  | 					$hasYield = $hasYield || $branchScopeStatementResult->hasYield(); | 
|  778:  | 				} | 
|  779:  | 			} | 
|  780:  |  | 
|  781:  | 			if ($finalScope === null) { | 
|  782:  | 				$finalScope = $scope; | 
|  783:  | 			} | 
|  784:  |  | 
|  785:  | 			return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, $throwPoints); | 
|  786:  | 		} elseif ($stmt instanceof Node\Stmt\TraitUse) { | 
|  787:  | 			$hasYield = false; | 
|  788:  | 			$throwPoints = []; | 
|  789:  | 			$this->processTraitUse($stmt, $scope, $nodeCallback); | 
|  790:  | 		} elseif ($stmt instanceof Foreach_) { | 
|  791:  | 			$condResult = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
|  792:  | 			$throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); | 
|  793:  | 			$scope = $condResult->getScope(); | 
|  794:  | 			$arrayComparisonExpr = new BinaryOp\NotIdentical( | 
|  795:  | 				$stmt->expr, | 
|  796:  | 				new Array_([]), | 
|  797:  | 			); | 
|  798:  | 			$inForeachScope = $scope; | 
|  799:  | 			if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) { | 
|  800:  | 				$inForeachScope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt); | 
|  801:  | 			} | 
|  802:  | 			$nodeCallback(new InForeachNode($stmt), $inForeachScope); | 
|  803:  | 			$bodyScope = $this->enterForeach($scope->filterByTruthyValue($arrayComparisonExpr), $stmt); | 
|  804:  | 			$count = 0; | 
|  805:  | 			do { | 
|  806:  | 				$prevScope = $bodyScope; | 
|  807:  | 				$bodyScope = $bodyScope->mergeWith($scope->filterByTruthyValue($arrayComparisonExpr)); | 
|  808:  | 				$bodyScope = $this->enterForeach($bodyScope, $stmt); | 
|  809:  | 				$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { | 
|  810:  | 				})->filterOutLoopExitPoints(); | 
|  811:  | 				$alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); | 
|  812:  | 				$bodyScope = $bodyScopeResult->getScope(); | 
|  813:  | 				foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
|  814:  | 					$bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); | 
|  815:  | 				} | 
|  816:  | 				if ($bodyScope->equals($prevScope)) { | 
|  817:  | 					break; | 
|  818:  | 				} | 
|  819:  |  | 
|  820:  | 				if ($count >= self::GENERALIZE_AFTER_ITERATION) { | 
|  821:  | 					$bodyScope = $prevScope->generalizeWith($bodyScope); | 
|  822:  | 				} | 
|  823:  | 				$count++; | 
|  824:  | 			} while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); | 
|  825:  |  | 
|  826:  | 			$bodyScope = $bodyScope->mergeWith($scope->filterByTruthyValue($arrayComparisonExpr)); | 
|  827:  | 			$bodyScope = $this->enterForeach($bodyScope, $stmt); | 
|  828:  | 			$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); | 
|  829:  | 			$finalScope = $finalScopeResult->getScope(); | 
|  830:  | 			foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
|  831:  | 				$finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); | 
|  832:  | 			} | 
|  833:  | 			foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { | 
|  834:  | 				$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); | 
|  835:  | 			} | 
|  836:  |  | 
|  837:  | 			$isIterableAtLeastOnce = $scope->getType($stmt->expr)->isIterableAtLeastOnce(); | 
|  838:  | 			if ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) { | 
|  839:  | 				$finalScope = $scope; | 
|  840:  | 			} elseif ($isIterableAtLeastOnce->maybe()) { | 
|  841:  | 				if ($this->polluteScopeWithAlwaysIterableForeach) { | 
|  842:  | 					$finalScope = $finalScope->mergeWith($scope->filterByFalseyValue($arrayComparisonExpr)); | 
|  843:  | 				} else { | 
|  844:  | 					$finalScope = $finalScope->mergeWith($scope); | 
|  845:  | 				} | 
|  846:  | 			} elseif (!$this->polluteScopeWithAlwaysIterableForeach) { | 
|  847:  | 				$finalScope = $scope->processAlwaysIterableForeachScopeWithoutPollute($finalScope); | 
|  848:  | 				 | 
|  849:  | 			} | 
|  850:  |  | 
|  851:  | 			if (!$isIterableAtLeastOnce->no()) { | 
|  852:  | 				$throwPoints = array_merge($throwPoints, $finalScopeResult->getThrowPoints()); | 
|  853:  | 			} | 
|  854:  | 			if (!(new ObjectType(Traversable::class))->isSuperTypeOf($scope->getType($stmt->expr))->no()) { | 
|  855:  | 				$throwPoints[] = ThrowPoint::createImplicit($scope, $stmt->expr); | 
|  856:  | 			} | 
|  857:  |  | 
|  858:  | 			return new StatementResult( | 
|  859:  | 				$finalScope, | 
|  860:  | 				$finalScopeResult->hasYield() || $condResult->hasYield(), | 
|  861:  | 				$isIterableAtLeastOnce->yes() && $finalScopeResult->isAlwaysTerminating(), | 
|  862:  | 				$finalScopeResult->getExitPointsForOuterLoop(), | 
|  863:  | 				$throwPoints, | 
|  864:  | 			); | 
|  865:  | 		} elseif ($stmt instanceof While_) { | 
|  866:  | 			$condResult = $this->processExprNode($stmt->cond, $scope, static function (): void { | 
|  867:  | 			}, ExpressionContext::createDeep()); | 
|  868:  | 			$bodyScope = $condResult->getTruthyScope(); | 
|  869:  | 			$count = 0; | 
|  870:  | 			do { | 
|  871:  | 				$prevScope = $bodyScope; | 
|  872:  | 				$bodyScope = $bodyScope->mergeWith($scope); | 
|  873:  | 				$bodyScope = $this->processExprNode($stmt->cond, $bodyScope, static function (): void { | 
|  874:  | 				}, ExpressionContext::createDeep())->getTruthyScope(); | 
|  875:  | 				$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { | 
|  876:  | 				})->filterOutLoopExitPoints(); | 
|  877:  | 				$alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); | 
|  878:  | 				$bodyScope = $bodyScopeResult->getScope(); | 
|  879:  | 				foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
|  880:  | 					$bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); | 
|  881:  | 				} | 
|  882:  | 				if ($bodyScope->equals($prevScope)) { | 
|  883:  | 					break; | 
|  884:  | 				} | 
|  885:  |  | 
|  886:  | 				if ($count >= self::GENERALIZE_AFTER_ITERATION) { | 
|  887:  | 					$bodyScope = $prevScope->generalizeWith($bodyScope); | 
|  888:  | 				} | 
|  889:  | 				$count++; | 
|  890:  | 			} while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); | 
|  891:  |  | 
|  892:  | 			$bodyScope = $bodyScope->mergeWith($scope); | 
|  893:  | 			$bodyScopeMaybeRan = $bodyScope; | 
|  894:  | 			$bodyScope = $this->processExprNode($stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); | 
|  895:  | 			$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); | 
|  896:  | 			$finalScope = $finalScopeResult->getScope()->filterByFalseyValue($stmt->cond); | 
|  897:  | 			foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
|  898:  | 				$finalScope = $finalScope->mergeWith($continueExitPoint->getScope()); | 
|  899:  | 			} | 
|  900:  | 			$breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class); | 
|  901:  | 			foreach ($breakExitPoints as $breakExitPoint) { | 
|  902:  | 				$finalScope = $finalScope->mergeWith($breakExitPoint->getScope()); | 
|  903:  | 			} | 
|  904:  |  | 
|  905:  | 			$beforeCondBooleanType = $scope->getType($stmt->cond)->toBoolean(); | 
|  906:  | 			$condBooleanType = $bodyScopeMaybeRan->getType($stmt->cond)->toBoolean(); | 
|  907:  | 			$isIterableAtLeastOnce = $beforeCondBooleanType instanceof ConstantBooleanType && $beforeCondBooleanType->getValue(); | 
|  908:  | 			$alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); | 
|  909:  | 			$neverIterates = $condBooleanType instanceof ConstantBooleanType && !$condBooleanType->getValue(); | 
|  910:  | 			$nodeCallback(new BreaklessWhileLoopNode($stmt, $finalScopeResult->getExitPoints()), $bodyScopeMaybeRan); | 
|  911:  |  | 
|  912:  | 			if ($alwaysIterates) { | 
|  913:  | 				$isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0; | 
|  914:  | 			} elseif ($isIterableAtLeastOnce) { | 
|  915:  | 				$isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating(); | 
|  916:  | 			} else { | 
|  917:  | 				$isAlwaysTerminating = false; | 
|  918:  | 			} | 
|  919:  | 			$condScope = $condResult->getFalseyScope(); | 
|  920:  | 			if (!$isIterableAtLeastOnce) { | 
|  921:  | 				if (!$this->polluteScopeWithLoopInitialAssignments) { | 
|  922:  | 					$condScope = $condScope->mergeWith($scope); | 
|  923:  | 				} | 
|  924:  | 				$finalScope = $finalScope->mergeWith($condScope); | 
|  925:  | 			} | 
|  926:  |  | 
|  927:  | 			$throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); | 
|  928:  | 			if (!$neverIterates) { | 
|  929:  | 				$throwPoints = array_merge($throwPoints, $finalScopeResult->getThrowPoints()); | 
|  930:  | 			} | 
|  931:  |  | 
|  932:  | 			return new StatementResult( | 
|  933:  | 				$finalScope, | 
|  934:  | 				$finalScopeResult->hasYield() || $condResult->hasYield(), | 
|  935:  | 				$isAlwaysTerminating, | 
|  936:  | 				$finalScopeResult->getExitPointsForOuterLoop(), | 
|  937:  | 				$throwPoints, | 
|  938:  | 			); | 
|  939:  | 		} elseif ($stmt instanceof Do_) { | 
|  940:  | 			$finalScope = null; | 
|  941:  | 			$bodyScope = $scope; | 
|  942:  | 			$count = 0; | 
|  943:  | 			$hasYield = false; | 
|  944:  | 			$throwPoints = []; | 
|  945:  |  | 
|  946:  | 			do { | 
|  947:  | 				$prevScope = $bodyScope; | 
|  948:  | 				$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { | 
|  949:  | 				})->filterOutLoopExitPoints(); | 
|  950:  | 				$alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); | 
|  951:  | 				$bodyScope = $bodyScopeResult->getScope(); | 
|  952:  | 				foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
|  953:  | 					$bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); | 
|  954:  | 				} | 
|  955:  | 				$finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); | 
|  956:  | 				foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { | 
|  957:  | 					$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); | 
|  958:  | 				} | 
|  959:  | 				$bodyScope = $this->processExprNode($stmt->cond, $bodyScope, static function (): void { | 
|  960:  | 				}, ExpressionContext::createDeep())->getTruthyScope(); | 
|  961:  | 				if ($bodyScope->equals($prevScope)) { | 
|  962:  | 					break; | 
|  963:  | 				} | 
|  964:  |  | 
|  965:  | 				if ($count >= self::GENERALIZE_AFTER_ITERATION) { | 
|  966:  | 					$bodyScope = $prevScope->generalizeWith($bodyScope); | 
|  967:  | 				} | 
|  968:  | 				$count++; | 
|  969:  | 			} while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); | 
|  970:  |  | 
|  971:  | 			$bodyScope = $bodyScope->mergeWith($scope); | 
|  972:  |  | 
|  973:  | 			$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); | 
|  974:  | 			$bodyScope = $bodyScopeResult->getScope(); | 
|  975:  | 			foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
|  976:  | 				$bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); | 
|  977:  | 			} | 
|  978:  | 			$condBooleanType = $bodyScope->getType($stmt->cond)->toBoolean(); | 
|  979:  | 			$alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); | 
|  980:  |  | 
|  981:  | 			$nodeCallback(new DoWhileLoopConditionNode($stmt->cond, $bodyScopeResult->getExitPoints()), $bodyScope); | 
|  982:  |  | 
|  983:  | 			if ($alwaysIterates) { | 
|  984:  | 				$alwaysTerminating = count($bodyScopeResult->getExitPointsByType(Break_::class)) === 0; | 
|  985:  | 			} else { | 
|  986:  | 				$alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); | 
|  987:  | 			} | 
|  988:  | 			$finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); | 
|  989:  | 			if ($finalScope === null) { | 
|  990:  | 				$finalScope = $scope; | 
|  991:  | 			} | 
|  992:  | 			if (!$alwaysTerminating) { | 
|  993:  | 				$condResult = $this->processExprNode($stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep()); | 
|  994:  | 				$hasYield = $condResult->hasYield(); | 
|  995:  | 				$throwPoints = $condResult->getThrowPoints(); | 
|  996:  | 				$finalScope = $condResult->getFalseyScope(); | 
|  997:  | 			} else { | 
|  998:  | 				$this->processExprNode($stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep()); | 
|  999:  | 			} | 
| 1000:  | 			foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { | 
| 1001:  | 				$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); | 
| 1002:  | 			} | 
| 1003:  |  | 
| 1004:  | 			return new StatementResult( | 
| 1005:  | 				$finalScope, | 
| 1006:  | 				$bodyScopeResult->hasYield() || $hasYield, | 
| 1007:  | 				$alwaysTerminating, | 
| 1008:  | 				$bodyScopeResult->getExitPointsForOuterLoop(), | 
| 1009:  | 				array_merge($throwPoints, $bodyScopeResult->getThrowPoints()), | 
| 1010:  | 			); | 
| 1011:  | 		} elseif ($stmt instanceof For_) { | 
| 1012:  | 			$initScope = $scope; | 
| 1013:  | 			$hasYield = false; | 
| 1014:  | 			$throwPoints = []; | 
| 1015:  | 			foreach ($stmt->init as $initExpr) { | 
| 1016:  | 				$initResult = $this->processExprNode($initExpr, $initScope, $nodeCallback, ExpressionContext::createTopLevel()); | 
| 1017:  | 				$initScope = $initResult->getScope(); | 
| 1018:  | 				$hasYield = $hasYield || $initResult->hasYield(); | 
| 1019:  | 				$throwPoints = array_merge($throwPoints, $initResult->getThrowPoints()); | 
| 1020:  | 			} | 
| 1021:  |  | 
| 1022:  | 			$bodyScope = $initScope; | 
| 1023:  | 			$isIterableAtLeastOnce = TrinaryLogic::createYes(); | 
| 1024:  | 			foreach ($stmt->cond as $condExpr) { | 
| 1025:  | 				$condResult = $this->processExprNode($condExpr, $bodyScope, static function (): void { | 
| 1026:  | 				}, ExpressionContext::createDeep()); | 
| 1027:  | 				$initScope = $condResult->getScope(); | 
| 1028:  | 				$condTruthiness = $condResult->getScope()->getType($condExpr)->toBoolean(); | 
| 1029:  | 				if ($condTruthiness instanceof ConstantBooleanType) { | 
| 1030:  | 					$condTruthinessTrinary = TrinaryLogic::createFromBoolean($condTruthiness->getValue()); | 
| 1031:  | 				} else { | 
| 1032:  | 					$condTruthinessTrinary = TrinaryLogic::createMaybe(); | 
| 1033:  | 				} | 
| 1034:  | 				$isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthinessTrinary); | 
| 1035:  | 				$hasYield = $hasYield || $condResult->hasYield(); | 
| 1036:  | 				$throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); | 
| 1037:  | 				$bodyScope = $condResult->getTruthyScope(); | 
| 1038:  | 			} | 
| 1039:  |  | 
| 1040:  | 			$count = 0; | 
| 1041:  | 			do { | 
| 1042:  | 				$prevScope = $bodyScope; | 
| 1043:  | 				$bodyScope = $bodyScope->mergeWith($initScope); | 
| 1044:  | 				foreach ($stmt->cond as $condExpr) { | 
| 1045:  | 					$bodyScope = $this->processExprNode($condExpr, $bodyScope, static function (): void { | 
| 1046:  | 					}, ExpressionContext::createDeep())->getTruthyScope(); | 
| 1047:  | 				} | 
| 1048:  | 				$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { | 
| 1049:  | 				})->filterOutLoopExitPoints(); | 
| 1050:  | 				$alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); | 
| 1051:  | 				$bodyScope = $bodyScopeResult->getScope(); | 
| 1052:  | 				foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1053:  | 					$bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); | 
| 1054:  | 				} | 
| 1055:  | 				foreach ($stmt->loop as $loopExpr) { | 
| 1056:  | 					$exprResult = $this->processExprNode($loopExpr, $bodyScope, static function (): void { | 
| 1057:  | 					}, ExpressionContext::createTopLevel()); | 
| 1058:  | 					$bodyScope = $exprResult->getScope(); | 
| 1059:  | 					$hasYield = $hasYield || $exprResult->hasYield(); | 
| 1060:  | 					$throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); | 
| 1061:  | 				} | 
| 1062:  |  | 
| 1063:  | 				if ($bodyScope->equals($prevScope)) { | 
| 1064:  | 					break; | 
| 1065:  | 				} | 
| 1066:  |  | 
| 1067:  | 				if ($count >= self::GENERALIZE_AFTER_ITERATION) { | 
| 1068:  | 					$bodyScope = $prevScope->generalizeWith($bodyScope); | 
| 1069:  | 				} | 
| 1070:  | 				$count++; | 
| 1071:  | 			} while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS); | 
| 1072:  |  | 
| 1073:  | 			$bodyScope = $bodyScope->mergeWith($initScope); | 
| 1074:  | 			foreach ($stmt->cond as $condExpr) { | 
| 1075:  | 				$bodyScope = $this->processExprNode($condExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); | 
| 1076:  | 			} | 
| 1077:  |  | 
| 1078:  | 			$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); | 
| 1079:  | 			$finalScope = $finalScopeResult->getScope(); | 
| 1080:  | 			foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1081:  | 				$finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); | 
| 1082:  | 			} | 
| 1083:  |  | 
| 1084:  | 			$loopScope = $finalScope; | 
| 1085:  | 			foreach ($stmt->loop as $loopExpr) { | 
| 1086:  | 				$loopScope = $this->processExprNode($loopExpr, $loopScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); | 
| 1087:  | 			} | 
| 1088:  | 			$finalScope = $finalScope->generalizeWith($loopScope); | 
| 1089:  | 			foreach ($stmt->cond as $condExpr) { | 
| 1090:  | 				$finalScope = $finalScope->filterByFalseyValue($condExpr); | 
| 1091:  | 			} | 
| 1092:  |  | 
| 1093:  | 			foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { | 
| 1094:  | 				$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); | 
| 1095:  | 			} | 
| 1096:  |  | 
| 1097:  | 			if ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) { | 
| 1098:  | 				if ($this->polluteScopeWithLoopInitialAssignments) { | 
| 1099:  | 					$finalScope = $initScope; | 
| 1100:  | 				} else { | 
| 1101:  | 					$finalScope = $scope; | 
| 1102:  | 				} | 
| 1103:  |  | 
| 1104:  | 			} elseif ($isIterableAtLeastOnce->maybe()) { | 
| 1105:  | 				if ($this->polluteScopeWithLoopInitialAssignments) { | 
| 1106:  | 					$finalScope = $finalScope->mergeWith($initScope); | 
| 1107:  | 				} else { | 
| 1108:  | 					$finalScope = $finalScope->mergeWith($scope); | 
| 1109:  | 				} | 
| 1110:  | 			} else { | 
| 1111:  | 				if (!$this->polluteScopeWithLoopInitialAssignments) { | 
| 1112:  | 					$finalScope = $finalScope->mergeWith($scope); | 
| 1113:  | 				} | 
| 1114:  | 			} | 
| 1115:  |  | 
| 1116:  | 			return new StatementResult( | 
| 1117:  | 				$finalScope, | 
| 1118:  | 				$finalScopeResult->hasYield() || $hasYield, | 
| 1119:  | 				false, | 
| 1120:  | 				$finalScopeResult->getExitPointsForOuterLoop(), | 
| 1121:  | 				array_merge($throwPoints, $finalScopeResult->getThrowPoints()), | 
| 1122:  | 			); | 
| 1123:  | 		} elseif ($stmt instanceof Switch_) { | 
| 1124:  | 			$condResult = $this->processExprNode($stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1125:  | 			$scope = $condResult->getScope(); | 
| 1126:  | 			$scopeForBranches = $scope; | 
| 1127:  | 			$finalScope = null; | 
| 1128:  | 			$prevScope = null; | 
| 1129:  | 			$hasDefaultCase = false; | 
| 1130:  | 			$alwaysTerminating = true; | 
| 1131:  | 			$hasYield = $condResult->hasYield(); | 
| 1132:  | 			$exitPointsForOuterLoop = []; | 
| 1133:  | 			$throwPoints = $condResult->getThrowPoints(); | 
| 1134:  | 			foreach ($stmt->cases as $caseNode) { | 
| 1135:  | 				if ($caseNode->cond !== null) { | 
| 1136:  | 					$condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond); | 
| 1137:  | 					$caseResult = $this->processExprNode($caseNode->cond, $scopeForBranches, $nodeCallback, ExpressionContext::createDeep()); | 
| 1138:  | 					$scopeForBranches = $caseResult->getScope(); | 
| 1139:  | 					$hasYield = $hasYield || $caseResult->hasYield(); | 
| 1140:  | 					$throwPoints = array_merge($throwPoints, $caseResult->getThrowPoints()); | 
| 1141:  | 					$branchScope = $scopeForBranches->filterByTruthyValue($condExpr); | 
| 1142:  | 				} else { | 
| 1143:  | 					$hasDefaultCase = true; | 
| 1144:  | 					$branchScope = $scopeForBranches; | 
| 1145:  | 				} | 
| 1146:  |  | 
| 1147:  | 				$branchScope = $branchScope->mergeWith($prevScope); | 
| 1148:  | 				$branchScopeResult = $this->processStmtNodes($caseNode, $caseNode->stmts, $branchScope, $nodeCallback); | 
| 1149:  | 				$branchScope = $branchScopeResult->getScope(); | 
| 1150:  | 				$branchFinalScopeResult = $branchScopeResult->filterOutLoopExitPoints(); | 
| 1151:  | 				$hasYield = $hasYield || $branchFinalScopeResult->hasYield(); | 
| 1152:  | 				foreach ($branchScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { | 
| 1153:  | 					$alwaysTerminating = false; | 
| 1154:  | 					$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); | 
| 1155:  | 				} | 
| 1156:  | 				foreach ($branchScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { | 
| 1157:  | 					$finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); | 
| 1158:  | 				} | 
| 1159:  | 				$exitPointsForOuterLoop = array_merge($exitPointsForOuterLoop, $branchFinalScopeResult->getExitPointsForOuterLoop()); | 
| 1160:  | 				$throwPoints = array_merge($throwPoints, $branchFinalScopeResult->getThrowPoints()); | 
| 1161:  | 				if ($branchScopeResult->isAlwaysTerminating()) { | 
| 1162:  | 					$alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating(); | 
| 1163:  | 					$prevScope = null; | 
| 1164:  | 					if (isset($condExpr)) { | 
| 1165:  | 						$scopeForBranches = $scopeForBranches->filterByFalseyValue($condExpr); | 
| 1166:  | 					} | 
| 1167:  | 					if (!$branchFinalScopeResult->isAlwaysTerminating()) { | 
| 1168:  | 						$finalScope = $branchScope->mergeWith($finalScope); | 
| 1169:  | 					} | 
| 1170:  | 				} else { | 
| 1171:  | 					$prevScope = $branchScope; | 
| 1172:  | 				} | 
| 1173:  | 			} | 
| 1174:  |  | 
| 1175:  | 			$exhaustive = $scopeForBranches->getType($stmt->cond) instanceof NeverType; | 
| 1176:  |  | 
| 1177:  | 			if (!$hasDefaultCase && !$exhaustive) { | 
| 1178:  | 				$alwaysTerminating = false; | 
| 1179:  | 			} | 
| 1180:  |  | 
| 1181:  | 			if ($prevScope !== null && isset($branchFinalScopeResult)) { | 
| 1182:  | 				$finalScope = $prevScope->mergeWith($finalScope); | 
| 1183:  | 				$alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating(); | 
| 1184:  | 			} | 
| 1185:  |  | 
| 1186:  | 			if ((!$hasDefaultCase && !$exhaustive) || $finalScope === null) { | 
| 1187:  | 				$finalScope = $scope->mergeWith($finalScope); | 
| 1188:  | 			} | 
| 1189:  |  | 
| 1190:  | 			return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPointsForOuterLoop, $throwPoints); | 
| 1191:  | 		} elseif ($stmt instanceof TryCatch) { | 
| 1192:  | 			$branchScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback); | 
| 1193:  | 			$branchScope = $branchScopeResult->getScope(); | 
| 1194:  | 			$finalScope = $branchScopeResult->isAlwaysTerminating() ? null : $branchScope; | 
| 1195:  |  | 
| 1196:  | 			$exitPoints = []; | 
| 1197:  | 			$finallyExitPoints = []; | 
| 1198:  | 			$alwaysTerminating = $branchScopeResult->isAlwaysTerminating(); | 
| 1199:  | 			$hasYield = $branchScopeResult->hasYield(); | 
| 1200:  |  | 
| 1201:  | 			if ($stmt->finally !== null) { | 
| 1202:  | 				$finallyScope = $branchScope; | 
| 1203:  | 			} else { | 
| 1204:  | 				$finallyScope = null; | 
| 1205:  | 			} | 
| 1206:  | 			foreach ($branchScopeResult->getExitPoints() as $exitPoint) { | 
| 1207:  | 				$finallyExitPoints[] = $exitPoint; | 
| 1208:  | 				if ($exitPoint->getStatement() instanceof Throw_) { | 
| 1209:  | 					continue; | 
| 1210:  | 				} | 
| 1211:  | 				if ($finallyScope !== null) { | 
| 1212:  | 					$finallyScope = $finallyScope->mergeWith($exitPoint->getScope()); | 
| 1213:  | 				} | 
| 1214:  | 				$exitPoints[] = $exitPoint; | 
| 1215:  | 			} | 
| 1216:  |  | 
| 1217:  | 			$throwPoints = $branchScopeResult->getThrowPoints(); | 
| 1218:  | 			$throwPointsForLater = []; | 
| 1219:  | 			$pastCatchTypes = new NeverType(); | 
| 1220:  |  | 
| 1221:  | 			foreach ($stmt->catches as $catchNode) { | 
| 1222:  | 				$nodeCallback($catchNode, $scope); | 
| 1223:  |  | 
| 1224:  | 				$catchType = TypeCombinator::union(...array_map(static fn (Name $name): Type => new ObjectType($name->toString()), $catchNode->types)); | 
| 1225:  | 				$originalCatchType = $catchType; | 
| 1226:  | 				$isThrowable = $originalCatchType instanceof TypeWithClassName && strtolower($originalCatchType->getClassName()) === 'throwable'; | 
| 1227:  | 				$catchType = TypeCombinator::remove($catchType, $pastCatchTypes); | 
| 1228:  | 				$pastCatchTypes = TypeCombinator::union($pastCatchTypes, $originalCatchType); | 
| 1229:  | 				$matchingThrowPoints = []; | 
| 1230:  | 				$newThrowPoints = []; | 
| 1231:  | 				foreach ($throwPoints as $throwPoint) { | 
| 1232:  | 					if (!$throwPoint->isExplicit() && !$catchType->isSuperTypeOf(new ObjectType(Throwable::class))->yes()) { | 
| 1233:  | 						continue; | 
| 1234:  | 					} | 
| 1235:  | 					$isSuperType = $catchType->isSuperTypeOf($throwPoint->getType()); | 
| 1236:  | 					if ($isSuperType->no()) { | 
| 1237:  | 						continue; | 
| 1238:  | 					} | 
| 1239:  | 					$matchingThrowPoints[] = $throwPoint; | 
| 1240:  | 				} | 
| 1241:  | 				$hasExplicit = count($matchingThrowPoints) > 0; | 
| 1242:  | 				foreach ($throwPoints as $throwPoint) { | 
| 1243:  | 					$isSuperType = $catchType->isSuperTypeOf($throwPoint->getType()); | 
| 1244:  | 					if (!$hasExplicit && !$isSuperType->no()) { | 
| 1245:  | 						$matchingThrowPoints[] = $throwPoint; | 
| 1246:  | 					} | 
| 1247:  | 					if ($isSuperType->yes()) { | 
| 1248:  | 						continue; | 
| 1249:  | 					} | 
| 1250:  | 					if ($isThrowable) { | 
| 1251:  | 						continue; | 
| 1252:  | 					} | 
| 1253:  | 					$newThrowPoints[] = $throwPoint->subtractCatchType($catchType); | 
| 1254:  | 				} | 
| 1255:  | 				$throwPoints = $newThrowPoints; | 
| 1256:  |  | 
| 1257:  | 				if (count($matchingThrowPoints) === 0) { | 
| 1258:  | 					$throwableThrowPoints = []; | 
| 1259:  | 					if ($originalCatchType->isSuperTypeOf(new ObjectType(Throwable::class))->yes()) { | 
| 1260:  | 						foreach ($branchScopeResult->getThrowPoints() as $originalThrowPoint) { | 
| 1261:  | 							if (!$originalThrowPoint->canContainAnyThrowable()) { | 
| 1262:  | 								continue; | 
| 1263:  | 							} | 
| 1264:  |  | 
| 1265:  | 							$throwableThrowPoints[] = $originalThrowPoint; | 
| 1266:  | 						} | 
| 1267:  | 					} | 
| 1268:  |  | 
| 1269:  | 					if (count($throwableThrowPoints) === 0) { | 
| 1270:  | 						$nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchType, $originalCatchType), $scope); | 
| 1271:  | 						continue; | 
| 1272:  | 					} | 
| 1273:  |  | 
| 1274:  | 					$matchingThrowPoints = $throwableThrowPoints; | 
| 1275:  | 				} | 
| 1276:  |  | 
| 1277:  | 				$catchScope = null; | 
| 1278:  | 				foreach ($matchingThrowPoints as $matchingThrowPoint) { | 
| 1279:  | 					if ($catchScope === null) { | 
| 1280:  | 						$catchScope = $matchingThrowPoint->getScope(); | 
| 1281:  | 					} else { | 
| 1282:  | 						$catchScope = $catchScope->mergeWith($matchingThrowPoint->getScope()); | 
| 1283:  | 					} | 
| 1284:  | 				} | 
| 1285:  |  | 
| 1286:  | 				$variableName = null; | 
| 1287:  | 				if ($catchNode->var !== null) { | 
| 1288:  | 					if (!is_string($catchNode->var->name)) { | 
| 1289:  | 						throw new ShouldNotHappenException(); | 
| 1290:  | 					} | 
| 1291:  |  | 
| 1292:  | 					$variableName = $catchNode->var->name; | 
| 1293:  | 				} | 
| 1294:  |  | 
| 1295:  | 				$catchScopeResult = $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $nodeCallback); | 
| 1296:  | 				$catchScopeForFinally = $catchScopeResult->getScope(); | 
| 1297:  |  | 
| 1298:  | 				$finalScope = $catchScopeResult->isAlwaysTerminating() ? $finalScope : $catchScopeResult->getScope()->mergeWith($finalScope); | 
| 1299:  | 				$alwaysTerminating = $alwaysTerminating && $catchScopeResult->isAlwaysTerminating(); | 
| 1300:  | 				$hasYield = $hasYield || $catchScopeResult->hasYield(); | 
| 1301:  | 				$catchThrowPoints = $catchScopeResult->getThrowPoints(); | 
| 1302:  | 				$throwPointsForLater = array_merge($throwPointsForLater, $catchThrowPoints); | 
| 1303:  |  | 
| 1304:  | 				if ($finallyScope !== null) { | 
| 1305:  | 					$finallyScope = $finallyScope->mergeWith($catchScopeForFinally); | 
| 1306:  | 				} | 
| 1307:  | 				foreach ($catchScopeResult->getExitPoints() as $exitPoint) { | 
| 1308:  | 					$finallyExitPoints[] = $exitPoint; | 
| 1309:  | 					if ($exitPoint->getStatement() instanceof Throw_) { | 
| 1310:  | 						continue; | 
| 1311:  | 					} | 
| 1312:  | 					if ($finallyScope !== null) { | 
| 1313:  | 						$finallyScope = $finallyScope->mergeWith($exitPoint->getScope()); | 
| 1314:  | 					} | 
| 1315:  | 					$exitPoints[] = $exitPoint; | 
| 1316:  | 				} | 
| 1317:  |  | 
| 1318:  | 				foreach ($catchThrowPoints as $catchThrowPoint) { | 
| 1319:  | 					if ($finallyScope === null) { | 
| 1320:  | 						continue; | 
| 1321:  | 					} | 
| 1322:  | 					$finallyScope = $finallyScope->mergeWith($catchThrowPoint->getScope()); | 
| 1323:  | 				} | 
| 1324:  | 			} | 
| 1325:  |  | 
| 1326:  | 			if ($finalScope === null) { | 
| 1327:  | 				$finalScope = $scope; | 
| 1328:  | 			} | 
| 1329:  |  | 
| 1330:  | 			foreach ($throwPoints as $throwPoint) { | 
| 1331:  | 				if ($finallyScope === null) { | 
| 1332:  | 					continue; | 
| 1333:  | 				} | 
| 1334:  | 				$finallyScope = $finallyScope->mergeWith($throwPoint->getScope()); | 
| 1335:  | 			} | 
| 1336:  |  | 
| 1337:  | 			if ($finallyScope !== null && $stmt->finally !== null) { | 
| 1338:  | 				$originalFinallyScope = $finallyScope; | 
| 1339:  | 				$finallyResult = $this->processStmtNodes($stmt->finally, $stmt->finally->stmts, $finallyScope, $nodeCallback); | 
| 1340:  | 				$alwaysTerminating = $alwaysTerminating || $finallyResult->isAlwaysTerminating(); | 
| 1341:  | 				$hasYield = $hasYield || $finallyResult->hasYield(); | 
| 1342:  | 				$throwPointsForLater = array_merge($throwPointsForLater, $finallyResult->getThrowPoints()); | 
| 1343:  | 				$finallyScope = $finallyResult->getScope(); | 
| 1344:  | 				$finalScope = $finallyResult->isAlwaysTerminating() ? $finalScope : $finalScope->processFinallyScope($finallyScope, $originalFinallyScope); | 
| 1345:  | 				if (count($finallyResult->getExitPoints()) > 0) { | 
| 1346:  | 					$nodeCallback(new FinallyExitPointsNode( | 
| 1347:  | 						$finallyResult->getExitPoints(), | 
| 1348:  | 						$finallyExitPoints, | 
| 1349:  | 					), $scope); | 
| 1350:  | 				} | 
| 1351:  | 				$exitPoints = array_merge($exitPoints, $finallyResult->getExitPoints()); | 
| 1352:  | 			} | 
| 1353:  |  | 
| 1354:  | 			return new StatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPoints, array_merge($throwPoints, $throwPointsForLater)); | 
| 1355:  | 		} elseif ($stmt instanceof Unset_) { | 
| 1356:  | 			$hasYield = false; | 
| 1357:  | 			$throwPoints = []; | 
| 1358:  | 			foreach ($stmt->vars as $var) { | 
| 1359:  | 				$scope = $this->lookForSetAllowedUndefinedExpressions($scope, $var); | 
| 1360:  | 				$scope = $this->processExprNode($var, $scope, $nodeCallback, ExpressionContext::createDeep())->getScope(); | 
| 1361:  | 				$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $var); | 
| 1362:  | 				$scope = $scope->unsetExpression($var); | 
| 1363:  | 			} | 
| 1364:  | 		} elseif ($stmt instanceof Node\Stmt\Use_) { | 
| 1365:  | 			$hasYield = false; | 
| 1366:  | 			$throwPoints = []; | 
| 1367:  | 			foreach ($stmt->uses as $use) { | 
| 1368:  | 				$this->processStmtNode($use, $scope, $nodeCallback); | 
| 1369:  | 			} | 
| 1370:  | 		} elseif ($stmt instanceof Node\Stmt\Global_) { | 
| 1371:  | 			$hasYield = false; | 
| 1372:  | 			$throwPoints = []; | 
| 1373:  | 			$vars = []; | 
| 1374:  | 			foreach ($stmt->vars as $var) { | 
| 1375:  | 				if (!$var instanceof Variable) { | 
| 1376:  | 					throw new ShouldNotHappenException(); | 
| 1377:  | 				} | 
| 1378:  | 				$scope = $this->lookForSetAllowedUndefinedExpressions($scope, $var); | 
| 1379:  | 				$this->processExprNode($var, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1380:  | 				$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $var); | 
| 1381:  |  | 
| 1382:  | 				if (!is_string($var->name)) { | 
| 1383:  | 					continue; | 
| 1384:  | 				} | 
| 1385:  |  | 
| 1386:  | 				$scope = $scope->assignVariable($var->name, new MixedType()); | 
| 1387:  | 				$vars[] = $var->name; | 
| 1388:  | 			} | 
| 1389:  | 			$scope = $this->processVarAnnotation($scope, $vars, $stmt); | 
| 1390:  | 		} elseif ($stmt instanceof Static_) { | 
| 1391:  | 			$hasYield = false; | 
| 1392:  | 			$throwPoints = []; | 
| 1393:  |  | 
| 1394:  | 			$vars = []; | 
| 1395:  | 			foreach ($stmt->vars as $var) { | 
| 1396:  | 				$scope = $this->processStmtNode($var, $scope, $nodeCallback)->getScope(); | 
| 1397:  | 				if (!is_string($var->var->name)) { | 
| 1398:  | 					continue; | 
| 1399:  | 				} | 
| 1400:  |  | 
| 1401:  | 				$vars[] = $var->var->name; | 
| 1402:  | 			} | 
| 1403:  |  | 
| 1404:  | 			$scope = $this->processVarAnnotation($scope, $vars, $stmt); | 
| 1405:  | 		} elseif ($stmt instanceof StaticVar) { | 
| 1406:  | 			$hasYield = false; | 
| 1407:  | 			$throwPoints = []; | 
| 1408:  | 			if (!is_string($stmt->var->name)) { | 
| 1409:  | 				throw new ShouldNotHappenException(); | 
| 1410:  | 			} | 
| 1411:  | 			if ($stmt->default !== null) { | 
| 1412:  | 				$this->processExprNode($stmt->default, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1413:  | 			} | 
| 1414:  | 			$scope = $scope->enterExpressionAssign($stmt->var); | 
| 1415:  | 			$this->processExprNode($stmt->var, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1416:  | 			$scope = $scope->exitExpressionAssign($stmt->var); | 
| 1417:  | 			$scope = $scope->assignVariable($stmt->var->name, new MixedType()); | 
| 1418:  | 		} elseif ($stmt instanceof Node\Stmt\Const_ || $stmt instanceof Node\Stmt\ClassConst) { | 
| 1419:  | 			$hasYield = false; | 
| 1420:  | 			$throwPoints = []; | 
| 1421:  | 			if ($stmt instanceof Node\Stmt\ClassConst) { | 
| 1422:  | 				$this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback); | 
| 1423:  | 			} | 
| 1424:  | 			foreach ($stmt->consts as $const) { | 
| 1425:  | 				$nodeCallback($const, $scope); | 
| 1426:  | 				$this->processExprNode($const->value, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 1427:  | 				if ($scope->getNamespace() !== null) { | 
| 1428:  | 					$constName = [$scope->getNamespace(), $const->name->toString()]; | 
| 1429:  | 				} else { | 
| 1430:  | 					$constName = $const->name->toString(); | 
| 1431:  | 				} | 
| 1432:  | 				$scope = $scope->assignExpression(new ConstFetch(new Name\FullyQualified($constName)), $scope->getType($const->value)); | 
| 1433:  | 			} | 
| 1434:  | 		} elseif ($stmt instanceof Node\Stmt\Nop) { | 
| 1435:  | 			$scope = $this->processStmtVarAnnotation($scope, $stmt, null); | 
| 1436:  | 			$hasYield = false; | 
| 1437:  | 			$throwPoints = $overridingThrowPoints ?? []; | 
| 1438:  | 		} else { | 
| 1439:  | 			$hasYield = false; | 
| 1440:  | 			$throwPoints = $overridingThrowPoints ?? []; | 
| 1441:  | 		} | 
| 1442:  |  | 
| 1443:  | 		return new StatementResult($scope, $hasYield, false, [], $throwPoints); | 
| 1444:  | 	} | 
| 1445:  |  | 
| 1446:  | 	 | 
| 1447:  |  | 
| 1448:  |  | 
| 1449:  | 	private function getOverridingThrowPoints(Node\Stmt $statement, MutatingScope $scope): ?array | 
| 1450:  | 	{ | 
| 1451:  | 		foreach ($statement->getComments() as $comment) { | 
| 1452:  | 			if (!$comment instanceof Doc) { | 
| 1453:  | 				continue; | 
| 1454:  | 			} | 
| 1455:  |  | 
| 1456:  | 			$function = $scope->getFunction(); | 
| 1457:  | 			$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | 
| 1458:  | 				$scope->getFile(), | 
| 1459:  | 				$scope->isInClass() ? $scope->getClassReflection()->getName() : null, | 
| 1460:  | 				$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, | 
| 1461:  | 				$function !== null ? $function->getName() : null, | 
| 1462:  | 				$comment->getText(), | 
| 1463:  | 			); | 
| 1464:  |  | 
| 1465:  | 			$throwsTag = $resolvedPhpDoc->getThrowsTag(); | 
| 1466:  | 			if ($throwsTag !== null) { | 
| 1467:  | 				$throwsType = $throwsTag->getType(); | 
| 1468:  | 				if ($throwsType instanceof VoidType) { | 
| 1469:  | 					return []; | 
| 1470:  | 				} | 
| 1471:  |  | 
| 1472:  | 				return [ThrowPoint::createExplicit($scope, $throwsType, $statement, false)]; | 
| 1473:  | 			} | 
| 1474:  | 		} | 
| 1475:  |  | 
| 1476:  | 		return null; | 
| 1477:  | 	} | 
| 1478:  |  | 
| 1479:  | 	private function getCurrentClassReflection(Node\Stmt\ClassLike $stmt, string $className, Scope $scope): ClassReflection | 
| 1480:  | 	{ | 
| 1481:  | 		if (!$this->reflectionProvider->hasClass($className)) { | 
| 1482:  | 			return $this->createAstClassReflection($stmt, $className, $scope); | 
| 1483:  | 		} | 
| 1484:  |  | 
| 1485:  | 		$defaultClassReflection = $this->reflectionProvider->getClass($className); | 
| 1486:  | 		if ($defaultClassReflection->getFileName() !== $scope->getFile()) { | 
| 1487:  | 			return $this->createAstClassReflection($stmt, $className, $scope); | 
| 1488:  | 		} | 
| 1489:  |  | 
| 1490:  | 		$startLine = $defaultClassReflection->getNativeReflection()->getStartLine(); | 
| 1491:  | 		if ($startLine !== $stmt->getStartLine()) { | 
| 1492:  | 			return $this->createAstClassReflection($stmt, $className, $scope); | 
| 1493:  | 		} | 
| 1494:  |  | 
| 1495:  | 		return $defaultClassReflection; | 
| 1496:  | 	} | 
| 1497:  |  | 
| 1498:  | 	private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $className, Scope $scope): ClassReflection | 
| 1499:  | 	{ | 
| 1500:  | 		$nodeToReflection = new NodeToReflection(); | 
| 1501:  | 		$betterReflectionClass = $nodeToReflection->__invoke( | 
| 1502:  | 			$this->reflector, | 
| 1503:  | 			$stmt, | 
| 1504:  | 			new LocatedSource(FileReader::read($scope->getFile()), $className, $scope->getFile()), | 
| 1505:  | 			$scope->getNamespace() !== null ? new Node\Stmt\Namespace_(new Name($scope->getNamespace())) : null, | 
| 1506:  | 		); | 
| 1507:  | 		if (!$betterReflectionClass instanceof \PHPStan\BetterReflection\Reflection\ReflectionClass) { | 
| 1508:  | 			throw new ShouldNotHappenException(); | 
| 1509:  | 		} | 
| 1510:  |  | 
| 1511:  | 		$enumAdapter = base64_decode('UEhQU3RhblxCZXR0ZXJSZWZsZWN0aW9uXFJlZmxlY3Rpb25cQWRhcHRlclxSZWZsZWN0aW9uRW51bQ==', true); | 
| 1512:  |  | 
| 1513:  | 		return new ClassReflection( | 
| 1514:  | 			$this->reflectionProvider, | 
| 1515:  | 			$this->initializerExprTypeResolver, | 
| 1516:  | 			$this->fileTypeMapper, | 
| 1517:  | 			$this->stubPhpDocProvider, | 
| 1518:  | 			$this->phpDocInheritanceResolver, | 
| 1519:  | 			$this->phpVersion, | 
| 1520:  | 			$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), | 
| 1521:  | 			$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), | 
| 1522:  | 			$betterReflectionClass->getName(), | 
| 1523:  | 			$betterReflectionClass instanceof ReflectionEnum && PHP_VERSION_ID >= 80000 ? new $enumAdapter($betterReflectionClass) : new ReflectionClass($betterReflectionClass), | 
| 1524:  | 			null, | 
| 1525:  | 			null, | 
| 1526:  | 			null, | 
| 1527:  | 			sprintf('%s:%d', $scope->getFile(), $stmt->getStartLine()), | 
| 1528:  | 		); | 
| 1529:  | 	} | 
| 1530:  |  | 
| 1531:  | 	private function lookForSetAllowedUndefinedExpressions(MutatingScope $scope, Expr $expr): MutatingScope | 
| 1532:  | 	{ | 
| 1533:  | 		return $this->lookForExpressionCallback($scope, $expr, static fn (MutatingScope $scope, Expr $expr): MutatingScope => $scope->setAllowedUndefinedExpression($expr)); | 
| 1534:  | 	} | 
| 1535:  |  | 
| 1536:  | 	private function lookForUnsetAllowedUndefinedExpressions(MutatingScope $scope, Expr $expr): MutatingScope | 
| 1537:  | 	{ | 
| 1538:  | 		return $this->lookForExpressionCallback($scope, $expr, static fn (MutatingScope $scope, Expr $expr): MutatingScope => $scope->unsetAllowedUndefinedExpression($expr)); | 
| 1539:  | 	} | 
| 1540:  |  | 
| 1541:  | 	 | 
| 1542:  |  | 
| 1543:  |  | 
| 1544:  | 	private function lookForExpressionCallback(MutatingScope $scope, Expr $expr, Closure $callback): MutatingScope | 
| 1545:  | 	{ | 
| 1546:  | 		if (!$expr instanceof ArrayDimFetch || $expr->dim !== null) { | 
| 1547:  | 			$scope = $callback($scope, $expr); | 
| 1548:  | 		} | 
| 1549:  |  | 
| 1550:  | 		if ($expr instanceof ArrayDimFetch) { | 
| 1551:  | 			$scope = $this->lookForExpressionCallback($scope, $expr->var, $callback); | 
| 1552:  | 		} elseif ($expr instanceof PropertyFetch || $expr instanceof Expr\NullsafePropertyFetch) { | 
| 1553:  | 			$scope = $this->lookForExpressionCallback($scope, $expr->var, $callback); | 
| 1554:  | 		} elseif ($expr instanceof StaticPropertyFetch && $expr->class instanceof Expr) { | 
| 1555:  | 			$scope = $this->lookForExpressionCallback($scope, $expr->class, $callback); | 
| 1556:  | 		} elseif ($expr instanceof Array_ || $expr instanceof List_) { | 
| 1557:  | 			foreach ($expr->items as $item) { | 
| 1558:  | 				if ($item === null) { | 
| 1559:  | 					continue; | 
| 1560:  | 				} | 
| 1561:  |  | 
| 1562:  | 				$scope = $this->lookForExpressionCallback($scope, $item->value, $callback); | 
| 1563:  | 			} | 
| 1564:  | 		} | 
| 1565:  |  | 
| 1566:  | 		return $scope; | 
| 1567:  | 	} | 
| 1568:  |  | 
| 1569:  | 	private function ensureShallowNonNullability(MutatingScope $scope, Scope $originalScope, Expr $exprToSpecify): EnsuredNonNullabilityResult | 
| 1570:  | 	{ | 
| 1571:  | 		$exprType = $scope->getType($exprToSpecify); | 
| 1572:  | 		$exprTypeWithoutNull = TypeCombinator::removeNull($exprType); | 
| 1573:  | 		if ($exprType->equals($exprTypeWithoutNull)) { | 
| 1574:  | 			$originalExprType = $originalScope->getType($exprToSpecify); | 
| 1575:  | 			$originalNativeType = $originalScope->getNativeType($exprToSpecify); | 
| 1576:  | 			if (!$originalExprType->equals($exprTypeWithoutNull)) { | 
| 1577:  | 				return new EnsuredNonNullabilityResult($scope, [ | 
| 1578:  | 					new EnsuredNonNullabilityResultExpression($exprToSpecify, $originalExprType, $originalNativeType), | 
| 1579:  | 				]); | 
| 1580:  | 			} | 
| 1581:  | 			return new EnsuredNonNullabilityResult($scope, []); | 
| 1582:  | 		} | 
| 1583:  |  | 
| 1584:  | 		$nativeType = $scope->getNativeType($exprToSpecify); | 
| 1585:  | 		$scope = $scope->assignExpression( | 
| 1586:  | 			$exprToSpecify, | 
| 1587:  | 			$exprTypeWithoutNull, | 
| 1588:  | 			TypeCombinator::removeNull($nativeType), | 
| 1589:  | 		); | 
| 1590:  |  | 
| 1591:  | 		return new EnsuredNonNullabilityResult( | 
| 1592:  | 			$scope, | 
| 1593:  | 			[ | 
| 1594:  | 				new EnsuredNonNullabilityResultExpression($exprToSpecify, $exprType, $nativeType), | 
| 1595:  | 			], | 
| 1596:  | 		); | 
| 1597:  | 	} | 
| 1598:  |  | 
| 1599:  | 	private function ensureNonNullability(MutatingScope $scope, Expr $expr): EnsuredNonNullabilityResult | 
| 1600:  | 	{ | 
| 1601:  | 		$specifiedExpressions = []; | 
| 1602:  | 		$originalScope = $scope; | 
| 1603:  | 		$scope = $this->lookForExpressionCallback($scope, $expr, function ($scope, $expr) use (&$specifiedExpressions, $originalScope) { | 
| 1604:  | 			$result = $this->ensureShallowNonNullability($scope, $originalScope, $expr); | 
| 1605:  | 			foreach ($result->getSpecifiedExpressions() as $specifiedExpression) { | 
| 1606:  | 				$specifiedExpressions[] = $specifiedExpression; | 
| 1607:  | 			} | 
| 1608:  | 			return $result->getScope(); | 
| 1609:  | 		}); | 
| 1610:  |  | 
| 1611:  | 		return new EnsuredNonNullabilityResult($scope, $specifiedExpressions); | 
| 1612:  | 	} | 
| 1613:  |  | 
| 1614:  | 	 | 
| 1615:  |  | 
| 1616:  |  | 
| 1617:  | 	private function revertNonNullability(MutatingScope $scope, array $specifiedExpressions): MutatingScope | 
| 1618:  | 	{ | 
| 1619:  | 		foreach ($specifiedExpressions as $specifiedExpressionResult) { | 
| 1620:  | 			$scope = $scope->assignExpression( | 
| 1621:  | 				$specifiedExpressionResult->getExpression(), | 
| 1622:  | 				$specifiedExpressionResult->getOriginalType(), | 
| 1623:  | 				$specifiedExpressionResult->getOriginalNativeType(), | 
| 1624:  | 			); | 
| 1625:  | 		} | 
| 1626:  |  | 
| 1627:  | 		return $scope; | 
| 1628:  | 	} | 
| 1629:  |  | 
| 1630:  | 	private function findEarlyTerminatingExpr(Expr $expr, Scope $scope): ?Expr | 
| 1631:  | 	{ | 
| 1632:  | 		if (($expr instanceof MethodCall || $expr instanceof Expr\StaticCall) && $expr->name instanceof Node\Identifier) { | 
| 1633:  | 			if (array_key_exists($expr->name->toLowerString(), $this->earlyTerminatingMethodNames)) { | 
| 1634:  | 				if ($expr instanceof MethodCall) { | 
| 1635:  | 					$methodCalledOnType = $scope->getType($expr->var); | 
| 1636:  | 				} else { | 
| 1637:  | 					if ($expr->class instanceof Name) { | 
| 1638:  | 						$methodCalledOnType = $scope->resolveTypeByName($expr->class); | 
| 1639:  | 					} else { | 
| 1640:  | 						$methodCalledOnType = $scope->getType($expr->class); | 
| 1641:  | 					} | 
| 1642:  | 				} | 
| 1643:  |  | 
| 1644:  | 				$directClassNames = TypeUtils::getDirectClassNames($methodCalledOnType); | 
| 1645:  | 				foreach ($directClassNames as $referencedClass) { | 
| 1646:  | 					if (!$this->reflectionProvider->hasClass($referencedClass)) { | 
| 1647:  | 						continue; | 
| 1648:  | 					} | 
| 1649:  |  | 
| 1650:  | 					$classReflection = $this->reflectionProvider->getClass($referencedClass); | 
| 1651:  | 					foreach (array_merge([$referencedClass], $classReflection->getParentClassesNames(), $classReflection->getNativeReflection()->getInterfaceNames()) as $className) { | 
| 1652:  | 						if (!isset($this->earlyTerminatingMethodCalls[$className])) { | 
| 1653:  | 							continue; | 
| 1654:  | 						} | 
| 1655:  |  | 
| 1656:  | 						if (in_array((string) $expr->name, $this->earlyTerminatingMethodCalls[$className], true)) { | 
| 1657:  | 							return $expr; | 
| 1658:  | 						} | 
| 1659:  | 					} | 
| 1660:  | 				} | 
| 1661:  | 			} | 
| 1662:  | 		} | 
| 1663:  |  | 
| 1664:  | 		if ($expr instanceof FuncCall && $expr->name instanceof Name) { | 
| 1665:  | 			if (in_array((string) $expr->name, $this->earlyTerminatingFunctionCalls, true)) { | 
| 1666:  | 				return $expr; | 
| 1667:  | 			} | 
| 1668:  | 		} | 
| 1669:  |  | 
| 1670:  | 		if ($expr instanceof Expr\Exit_ || $expr instanceof Expr\Throw_) { | 
| 1671:  | 			return $expr; | 
| 1672:  | 		} | 
| 1673:  |  | 
| 1674:  | 		$exprType = $scope->getType($expr); | 
| 1675:  | 		if ($exprType instanceof NeverType && $exprType->isExplicit()) { | 
| 1676:  | 			return $expr; | 
| 1677:  | 		} | 
| 1678:  |  | 
| 1679:  | 		return null; | 
| 1680:  | 	} | 
| 1681:  |  | 
| 1682:  | 	 | 
| 1683:  |  | 
| 1684:  |  | 
| 1685:  | 	private function processExprNode(Expr $expr, MutatingScope $scope, callable $nodeCallback, ExpressionContext $context): ExpressionResult | 
| 1686:  | 	{ | 
| 1687:  | 		if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) { | 
| 1688:  | 			if ($expr instanceof FuncCall) { | 
| 1689:  | 				$newExpr = new FunctionCallableNode($expr->name, $expr); | 
| 1690:  | 			} elseif ($expr instanceof MethodCall) { | 
| 1691:  | 				$newExpr = new MethodCallableNode($expr->var, $expr->name, $expr); | 
| 1692:  | 			} elseif ($expr instanceof StaticCall) { | 
| 1693:  | 				$newExpr = new StaticMethodCallableNode($expr->class, $expr->name, $expr); | 
| 1694:  | 			} elseif ($expr instanceof New_ && !$expr->class instanceof Class_) { | 
| 1695:  | 				$newExpr = new InstantiationCallableNode($expr->class, $expr); | 
| 1696:  | 			} else { | 
| 1697:  | 				throw new ShouldNotHappenException(); | 
| 1698:  | 			} | 
| 1699:  |  | 
| 1700:  | 			return $this->processExprNode($newExpr, $scope, $nodeCallback, $context); | 
| 1701:  | 		} | 
| 1702:  |  | 
| 1703:  | 		$this->callNodeCallbackWithExpression($nodeCallback, $expr, $scope, $context); | 
| 1704:  |  | 
| 1705:  | 		if ($expr instanceof Variable) { | 
| 1706:  | 			$hasYield = false; | 
| 1707:  | 			$throwPoints = []; | 
| 1708:  | 			if ($expr->name instanceof Expr) { | 
| 1709:  | 				return $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 1710:  | 			} | 
| 1711:  | 		} elseif ($expr instanceof Assign || $expr instanceof AssignRef) { | 
| 1712:  | 			$result = $this->processAssignVar( | 
| 1713:  | 				$scope, | 
| 1714:  | 				$expr->var, | 
| 1715:  | 				$expr->expr, | 
| 1716:  | 				$nodeCallback, | 
| 1717:  | 				$context, | 
| 1718:  | 				function (MutatingScope $scope) use ($expr, $nodeCallback, $context): ExpressionResult { | 
| 1719:  | 					if ($expr instanceof AssignRef) { | 
| 1720:  | 						$scope = $scope->enterExpressionAssign($expr->expr); | 
| 1721:  | 					} | 
| 1722:  |  | 
| 1723:  | 					if ($expr->var instanceof Variable && is_string($expr->var->name)) { | 
| 1724:  | 						$context = $context->enterRightSideAssign( | 
| 1725:  | 							$expr->var->name, | 
| 1726:  | 							$scope->getType($expr->expr), | 
| 1727:  | 						); | 
| 1728:  | 					} | 
| 1729:  |  | 
| 1730:  | 					$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 1731:  | 					$hasYield = $result->hasYield(); | 
| 1732:  | 					$throwPoints = $result->getThrowPoints(); | 
| 1733:  | 					$scope = $result->getScope(); | 
| 1734:  |  | 
| 1735:  | 					if ($expr instanceof AssignRef) { | 
| 1736:  | 						$scope = $scope->exitExpressionAssign($expr->expr); | 
| 1737:  | 					} | 
| 1738:  |  | 
| 1739:  | 					return new ExpressionResult($scope, $hasYield, $throwPoints); | 
| 1740:  | 				}, | 
| 1741:  | 				true, | 
| 1742:  | 			); | 
| 1743:  | 			$scope = $result->getScope(); | 
| 1744:  | 			$hasYield = $result->hasYield(); | 
| 1745:  | 			$throwPoints = $result->getThrowPoints(); | 
| 1746:  | 			$vars = $this->getAssignedVariables($expr->var); | 
| 1747:  | 			if (count($vars) > 0) { | 
| 1748:  | 				$varChangedScope = false; | 
| 1749:  | 				$scope = $this->processVarAnnotation($scope, $vars, $expr, $varChangedScope); | 
| 1750:  | 				if (!$varChangedScope) { | 
| 1751:  | 					$scope = $this->processStmtVarAnnotation($scope, new Node\Stmt\Expression($expr, [ | 
| 1752:  | 						'comments' => $expr->getAttribute('comments'), | 
| 1753:  | 					]), null); | 
| 1754:  | 				} | 
| 1755:  | 			} | 
| 1756:  | 		} elseif ($expr instanceof Expr\AssignOp) { | 
| 1757:  | 			$result = $this->processAssignVar( | 
| 1758:  | 				$scope, | 
| 1759:  | 				$expr->var, | 
| 1760:  | 				$expr, | 
| 1761:  | 				$nodeCallback, | 
| 1762:  | 				$context, | 
| 1763:  | 				fn (MutatingScope $scope): ExpressionResult => $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()), | 
| 1764:  | 				$expr instanceof Expr\AssignOp\Coalesce, | 
| 1765:  | 			); | 
| 1766:  | 			$scope = $result->getScope(); | 
| 1767:  | 			$hasYield = $result->hasYield(); | 
| 1768:  | 			$throwPoints = $result->getThrowPoints(); | 
| 1769:  | 			if ( | 
| 1770:  | 				($expr instanceof Expr\AssignOp\Div || $expr instanceof Expr\AssignOp\Mod) && | 
| 1771:  | 				!$scope->getType($expr->expr)->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no() | 
| 1772:  | 			) { | 
| 1773:  | 				$throwPoints[] = ThrowPoint::createExplicit($scope, new ObjectType(DivisionByZeroError::class), $expr, false); | 
| 1774:  | 			} | 
| 1775:  | 		} elseif ($expr instanceof FuncCall) { | 
| 1776:  | 			$parametersAcceptor = null; | 
| 1777:  | 			$functionReflection = null; | 
| 1778:  | 			$throwPoints = []; | 
| 1779:  | 			if ($expr->name instanceof Expr) { | 
| 1780:  | 				$nameType = $scope->getType($expr->name); | 
| 1781:  | 				if ($nameType->isCallable()->yes()) { | 
| 1782:  | 					$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 1783:  | 						$scope, | 
| 1784:  | 						$expr->getArgs(), | 
| 1785:  | 						$nameType->getCallableParametersAcceptors($scope), | 
| 1786:  | 					); | 
| 1787:  | 				} | 
| 1788:  | 				$nameResult = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 1789:  | 				$throwPoints = $nameResult->getThrowPoints(); | 
| 1790:  | 				$scope = $nameResult->getScope(); | 
| 1791:  | 			} elseif ($this->reflectionProvider->hasFunction($expr->name, $scope)) { | 
| 1792:  | 				$functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); | 
| 1793:  | 				$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 1794:  | 					$scope, | 
| 1795:  | 					$expr->getArgs(), | 
| 1796:  | 					$functionReflection->getVariants(), | 
| 1797:  | 				); | 
| 1798:  | 			} | 
| 1799:  | 			$result = $this->processArgs($functionReflection, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context); | 
| 1800:  | 			$scope = $result->getScope(); | 
| 1801:  | 			$hasYield = $result->hasYield(); | 
| 1802:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 1803:  |  | 
| 1804:  | 			if (isset($functionReflection)) { | 
| 1805:  | 				$functionThrowPoint = $this->getFunctionThrowPoint($functionReflection, $parametersAcceptor, $expr, $scope); | 
| 1806:  | 				if ($functionThrowPoint !== null) { | 
| 1807:  | 					$throwPoints[] = $functionThrowPoint; | 
| 1808:  | 				} | 
| 1809:  | 			} else { | 
| 1810:  | 				$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 1811:  | 			} | 
| 1812:  |  | 
| 1813:  | 			if ( | 
| 1814:  | 				isset($functionReflection) | 
| 1815:  | 				&& in_array($functionReflection->getName(), ['json_encode', 'json_decode'], true) | 
| 1816:  | 			) { | 
| 1817:  | 				$scope = $scope->invalidateExpression(new FuncCall(new Name('json_last_error'), [])) | 
| 1818:  | 					->invalidateExpression(new FuncCall(new Name\FullyQualified('json_last_error'), [])) | 
| 1819:  | 					->invalidateExpression(new FuncCall(new Name('json_last_error_msg'), [])) | 
| 1820:  | 					->invalidateExpression(new FuncCall(new Name\FullyQualified('json_last_error_msg'), [])); | 
| 1821:  | 			} | 
| 1822:  |  | 
| 1823:  | 			if ( | 
| 1824:  | 				isset($functionReflection) | 
| 1825:  | 				&& in_array($functionReflection->getName(), ['array_pop', 'array_shift'], true) | 
| 1826:  | 				&& count($expr->getArgs()) >= 1 | 
| 1827:  | 			) { | 
| 1828:  | 				$arrayArg = $expr->getArgs()[0]->value; | 
| 1829:  | 				$arrayArgType = $scope->getType($arrayArg); | 
| 1830:  | 				$scope = $scope->invalidateExpression($arrayArg); | 
| 1831:  |  | 
| 1832:  | 				$functionName = $functionReflection->getName(); | 
| 1833:  | 				$arrayArgType = TypeTraverser::map($arrayArgType, static function (Type $type, callable $traverse) use ($functionName): Type { | 
| 1834:  | 					if ($type instanceof UnionType || $type instanceof IntersectionType) { | 
| 1835:  | 						return $traverse($type); | 
| 1836:  | 					} | 
| 1837:  | 					if ($type instanceof ConstantArrayType) { | 
| 1838:  | 						return $functionName === 'array_pop' ? $type->removeLast() : $type->removeFirst(); | 
| 1839:  | 					} | 
| 1840:  | 					if ($type->isIterableAtLeastOnce()->yes()) { | 
| 1841:  | 						return $type->toArray(); | 
| 1842:  | 					} | 
| 1843:  | 					return $type; | 
| 1844:  | 				}); | 
| 1845:  |  | 
| 1846:  | 				$scope = $scope->assignExpression( | 
| 1847:  | 					$arrayArg, | 
| 1848:  | 					$arrayArgType, | 
| 1849:  | 					$arrayArgType, | 
| 1850:  | 				); | 
| 1851:  | 			} | 
| 1852:  |  | 
| 1853:  | 			if ( | 
| 1854:  | 				isset($functionReflection) | 
| 1855:  | 				&& in_array($functionReflection->getName(), ['array_push', 'array_unshift'], true) | 
| 1856:  | 				&& count($expr->getArgs()) >= 2 | 
| 1857:  | 			) { | 
| 1858:  | 				$arrayArg = $expr->getArgs()[0]->value; | 
| 1859:  | 				$arrayType = $scope->getType($arrayArg); | 
| 1860:  | 				$callArgs = array_slice($expr->getArgs(), 1); | 
| 1861:  |  | 
| 1862:  | 				 | 
| 1863:  |  | 
| 1864:  |  | 
| 1865:  |  | 
| 1866:  | 				$setOffsetValueTypes = static function (Scope $scope, array $callArgs, callable $setOffsetValueType, ?bool &$nonConstantArrayWasUnpacked = null): void { | 
| 1867:  | 					foreach ($callArgs as $callArg) { | 
| 1868:  | 						$callArgType = $scope->getType($callArg->value); | 
| 1869:  | 						if ($callArg->unpack) { | 
| 1870:  | 							if ($callArgType instanceof ConstantArrayType) { | 
| 1871:  | 								$iterableValueTypes = $callArgType->getValueTypes(); | 
| 1872:  | 							} else { | 
| 1873:  | 								$iterableValueTypes = [$callArgType->getIterableValueType()]; | 
| 1874:  | 								$nonConstantArrayWasUnpacked = true; | 
| 1875:  | 							} | 
| 1876:  |  | 
| 1877:  | 							$isOptional = !$callArgType->isIterableAtLeastOnce()->yes(); | 
| 1878:  | 							foreach ($iterableValueTypes as $iterableValueType) { | 
| 1879:  | 								if ($iterableValueType instanceof UnionType) { | 
| 1880:  | 									foreach ($iterableValueType->getTypes() as $innerType) { | 
| 1881:  | 										$setOffsetValueType(null, $innerType, $isOptional); | 
| 1882:  | 									} | 
| 1883:  | 								} else { | 
| 1884:  | 									$setOffsetValueType(null, $iterableValueType, $isOptional); | 
| 1885:  | 								} | 
| 1886:  | 							} | 
| 1887:  | 							continue; | 
| 1888:  | 						} | 
| 1889:  | 						$setOffsetValueType(null, $callArgType, false); | 
| 1890:  | 					} | 
| 1891:  | 				}; | 
| 1892:  |  | 
| 1893:  | 				$constantArrays = TypeUtils::getOldConstantArrays($arrayType); | 
| 1894:  | 				if (count($constantArrays) > 0) { | 
| 1895:  | 					$newArrayTypes = []; | 
| 1896:  | 					$prepend = $functionReflection->getName() === 'array_unshift'; | 
| 1897:  | 					foreach ($constantArrays as $constantArray) { | 
| 1898:  | 						$arrayTypeBuilder = $prepend ? ConstantArrayTypeBuilder::createEmpty() : ConstantArrayTypeBuilder::createFromConstantArray($constantArray); | 
| 1899:  |  | 
| 1900:  | 						$setOffsetValueTypes( | 
| 1901:  | 							$scope, | 
| 1902:  | 							$callArgs, | 
| 1903:  | 							static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arrayTypeBuilder): void { | 
| 1904:  | 								$arrayTypeBuilder->setOffsetValueType($offsetType, $valueType, $optional); | 
| 1905:  | 							}, | 
| 1906:  | 							$nonConstantArrayWasUnpacked, | 
| 1907:  | 						); | 
| 1908:  |  | 
| 1909:  | 						if ($prepend) { | 
| 1910:  | 							$keyTypes = $constantArray->getKeyTypes(); | 
| 1911:  | 							$valueTypes = $constantArray->getValueTypes(); | 
| 1912:  | 							foreach ($keyTypes as $k => $keyType) { | 
| 1913:  | 								$arrayTypeBuilder->setOffsetValueType( | 
| 1914:  | 									$keyType instanceof ConstantStringType ? $keyType : null, | 
| 1915:  | 									$valueTypes[$k], | 
| 1916:  | 									$constantArray->isOptionalKey($k), | 
| 1917:  | 								); | 
| 1918:  | 							} | 
| 1919:  | 						} | 
| 1920:  |  | 
| 1921:  | 						$constantArray = $arrayTypeBuilder->getArray(); | 
| 1922:  |  | 
| 1923:  | 						if ($constantArray instanceof ConstantArrayType && $nonConstantArrayWasUnpacked) { | 
| 1924:  | 							$constantArray = $constantArray->isIterableAtLeastOnce()->yes() | 
| 1925:  | 								? TypeCombinator::intersect($constantArray->generalizeKeys(), new NonEmptyArrayType()) | 
| 1926:  | 								: $constantArray->generalizeKeys(); | 
| 1927:  | 						} | 
| 1928:  |  | 
| 1929:  | 						$newArrayTypes[] = $constantArray; | 
| 1930:  | 					} | 
| 1931:  |  | 
| 1932:  | 					$arrayType = TypeCombinator::union(...$newArrayTypes); | 
| 1933:  | 				} else { | 
| 1934:  | 					$setOffsetValueTypes( | 
| 1935:  | 						$scope, | 
| 1936:  | 						$callArgs, | 
| 1937:  | 						static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arrayType): void { | 
| 1938:  | 							$isIterableAtLeastOnce = $arrayType->isIterableAtLeastOnce()->yes() || !$optional; | 
| 1939:  | 							$arrayType = $arrayType->setOffsetValueType($offsetType, $valueType); | 
| 1940:  | 							if ($isIterableAtLeastOnce) { | 
| 1941:  | 								return; | 
| 1942:  | 							} | 
| 1943:  |  | 
| 1944:  | 							$arrayType = new ArrayType($arrayType->getIterableKeyType(), $arrayType->getIterableValueType()); | 
| 1945:  | 						}, | 
| 1946:  | 					); | 
| 1947:  | 				} | 
| 1948:  |  | 
| 1949:  | 				$scope = $scope->invalidateExpression($arrayArg)->assignExpression($arrayArg, $arrayType, $arrayType); | 
| 1950:  | 			} | 
| 1951:  |  | 
| 1952:  | 			if ( | 
| 1953:  | 				isset($functionReflection) | 
| 1954:  | 				&& in_array($functionReflection->getName(), ['fopen', 'file_get_contents'], true) | 
| 1955:  | 			) { | 
| 1956:  | 				$scope = $scope->assignVariable('http_response_header', new ArrayType(new IntegerType(), new StringType())); | 
| 1957:  | 			} | 
| 1958:  |  | 
| 1959:  | 			if (isset($functionReflection) && $functionReflection->getName() === 'shuffle') { | 
| 1960:  | 				$arrayArg = $expr->getArgs()[0]->value; | 
| 1961:  | 				$arrayArgType = $scope->getType($arrayArg); | 
| 1962:  |  | 
| 1963:  | 				if ($arrayArgType instanceof ConstantArrayType) { | 
| 1964:  | 					$arrayArgType = $arrayArgType->getValuesArray()->generalizeToArray(); | 
| 1965:  | 				} | 
| 1966:  |  | 
| 1967:  | 				$scope = $scope->assignExpression($arrayArg, $arrayArgType, $arrayArgType); | 
| 1968:  | 			} | 
| 1969:  |  | 
| 1970:  | 			if ( | 
| 1971:  | 				isset($functionReflection) | 
| 1972:  | 				&& $functionReflection->getName() === 'array_splice' | 
| 1973:  | 				&& count($expr->getArgs()) >= 1 | 
| 1974:  | 			) { | 
| 1975:  | 				$arrayArg = $expr->getArgs()[0]->value; | 
| 1976:  | 				$arrayArgType = $scope->getType($arrayArg); | 
| 1977:  | 				$valueType = $arrayArgType->getIterableValueType(); | 
| 1978:  | 				if (count($expr->getArgs()) >= 4) { | 
| 1979:  | 					$valueType = TypeCombinator::union($valueType, $scope->getType($expr->getArgs()[3]->value)->getIterableValueType()); | 
| 1980:  | 				} | 
| 1981:  | 				$scope = $scope->invalidateExpression($arrayArg)->assignExpression( | 
| 1982:  | 					$arrayArg, | 
| 1983:  | 					new ArrayType($arrayArgType->getIterableKeyType(), $valueType), | 
| 1984:  | 					new ArrayType($arrayArgType->getIterableKeyType(), $valueType), | 
| 1985:  | 				); | 
| 1986:  | 			} | 
| 1987:  |  | 
| 1988:  | 			if (isset($functionReflection) && $functionReflection->getName() === 'extract') { | 
| 1989:  | 				$scope = $scope->afterExtractCall(); | 
| 1990:  | 			} | 
| 1991:  |  | 
| 1992:  | 			if (isset($functionReflection) && ($functionReflection->getName() === 'clearstatcache' || $functionReflection->getName() === 'unlink')) { | 
| 1993:  | 				$scope = $scope->afterClearstatcacheCall(); | 
| 1994:  | 			} | 
| 1995:  |  | 
| 1996:  | 			if (isset($functionReflection) && str_starts_with($functionReflection->getName(), 'openssl')) { | 
| 1997:  | 				$scope = $scope->afterOpenSslCall($functionReflection->getName()); | 
| 1998:  | 			} | 
| 1999:  |  | 
| 2000:  | 			if (isset($functionReflection) && $functionReflection->hasSideEffects()->yes()) { | 
| 2001:  | 				foreach ($expr->getArgs() as $arg) { | 
| 2002:  | 					$scope = $scope->invalidateExpression($arg->value, true); | 
| 2003:  | 				} | 
| 2004:  | 			} | 
| 2005:  |  | 
| 2006:  | 		} elseif ($expr instanceof MethodCall) { | 
| 2007:  | 			$originalScope = $scope; | 
| 2008:  | 			if ( | 
| 2009:  | 				($expr->var instanceof Expr\Closure || $expr->var instanceof Expr\ArrowFunction) | 
| 2010:  | 				&& $expr->name instanceof Node\Identifier | 
| 2011:  | 				&& strtolower($expr->name->name) === 'call' | 
| 2012:  | 				&& isset($expr->getArgs()[0]) | 
| 2013:  | 			) { | 
| 2014:  | 				$closureCallScope = $scope->enterClosureCall($scope->getType($expr->getArgs()[0]->value)); | 
| 2015:  | 			} | 
| 2016:  |  | 
| 2017:  | 			$result = $this->processExprNode($expr->var, $closureCallScope ?? $scope, $nodeCallback, $context->enterDeep()); | 
| 2018:  | 			$hasYield = $result->hasYield(); | 
| 2019:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2020:  | 			$scope = $result->getScope(); | 
| 2021:  | 			if (isset($closureCallScope)) { | 
| 2022:  | 				$scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope); | 
| 2023:  | 			} | 
| 2024:  | 			$parametersAcceptor = null; | 
| 2025:  | 			$methodReflection = null; | 
| 2026:  | 			if ($expr->name instanceof Expr) { | 
| 2027:  | 				$methodNameResult = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 2028:  | 				$throwPoints = array_merge($throwPoints, $methodNameResult->getThrowPoints()); | 
| 2029:  | 				$scope = $methodNameResult->getScope(); | 
| 2030:  | 			} else { | 
| 2031:  | 				$calledOnType = $scope->getType($expr->var); | 
| 2032:  | 				$methodName = $expr->name->name; | 
| 2033:  | 				$methodReflection = $scope->getMethodReflection($calledOnType, $methodName); | 
| 2034:  | 				if ($methodReflection !== null) { | 
| 2035:  | 					$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 2036:  | 						$scope, | 
| 2037:  | 						$expr->getArgs(), | 
| 2038:  | 						$methodReflection->getVariants(), | 
| 2039:  | 					); | 
| 2040:  |  | 
| 2041:  | 					$methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope); | 
| 2042:  | 					if ($methodThrowPoint !== null) { | 
| 2043:  | 						$throwPoints[] = $methodThrowPoint; | 
| 2044:  | 					} | 
| 2045:  | 				} | 
| 2046:  | 			} | 
| 2047:  | 			$result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context); | 
| 2048:  | 			$scope = $result->getScope(); | 
| 2049:  | 			if ($methodReflection !== null) { | 
| 2050:  | 				$hasSideEffects = $methodReflection->hasSideEffects(); | 
| 2051:  | 				if ($hasSideEffects->yes() || $methodReflection->getName() === '__construct') { | 
| 2052:  | 					$scope = $scope->invalidateExpression($expr->var, true); | 
| 2053:  | 					foreach ($expr->getArgs() as $arg) { | 
| 2054:  | 						$scope = $scope->invalidateExpression($arg->value, true); | 
| 2055:  | 					} | 
| 2056:  | 				} | 
| 2057:  | 			} else { | 
| 2058:  | 				$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 2059:  | 			} | 
| 2060:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 2061:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2062:  | 		} elseif ($expr instanceof Expr\NullsafeMethodCall) { | 
| 2063:  | 			$nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var); | 
| 2064:  | 			$exprResult = $this->processExprNode(new MethodCall($expr->var, $expr->name, $expr->args, $expr->getAttributes()), $nonNullabilityResult->getScope(), $nodeCallback, $context); | 
| 2065:  | 			$scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); | 
| 2066:  |  | 
| 2067:  | 			return new ExpressionResult( | 
| 2068:  | 				$scope, | 
| 2069:  | 				$exprResult->hasYield(), | 
| 2070:  | 				$exprResult->getThrowPoints(), | 
| 2071:  | 				static fn (): MutatingScope => $scope->filterByTruthyValue($expr), | 
| 2072:  | 				static fn (): MutatingScope => $scope->filterByFalseyValue($expr), | 
| 2073:  | 			); | 
| 2074:  | 		} elseif ($expr instanceof StaticCall) { | 
| 2075:  | 			$hasYield = false; | 
| 2076:  | 			$throwPoints = []; | 
| 2077:  | 			if ($expr->class instanceof Expr) { | 
| 2078:  | 				$objectClasses = TypeUtils::getDirectClassNames($scope->getType($expr->class)); | 
| 2079:  | 				if (count($objectClasses) !== 1) { | 
| 2080:  | 					$objectClasses = TypeUtils::getDirectClassNames($scope->getType(new New_($expr->class))); | 
| 2081:  | 				} | 
| 2082:  | 				if (count($objectClasses) === 1) { | 
| 2083:  | 					$objectExprResult = $this->processExprNode(new StaticCall(new Name($objectClasses[0]), $expr->name, []), $scope, static function (): void { | 
| 2084:  | 					}, $context->enterDeep()); | 
| 2085:  | 					$additionalThrowPoints = $objectExprResult->getThrowPoints(); | 
| 2086:  | 				} else { | 
| 2087:  | 					$additionalThrowPoints = [ThrowPoint::createImplicit($scope, $expr)]; | 
| 2088:  | 				} | 
| 2089:  | 				$classResult = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); | 
| 2090:  | 				$hasYield = $classResult->hasYield(); | 
| 2091:  | 				$throwPoints = array_merge($throwPoints, $classResult->getThrowPoints()); | 
| 2092:  | 				foreach ($additionalThrowPoints as $throwPoint) { | 
| 2093:  | 					$throwPoints[] = $throwPoint; | 
| 2094:  | 				} | 
| 2095:  | 				$scope = $classResult->getScope(); | 
| 2096:  | 			} | 
| 2097:  |  | 
| 2098:  | 			$parametersAcceptor = null; | 
| 2099:  | 			$methodReflection = null; | 
| 2100:  | 			if ($expr->name instanceof Expr) { | 
| 2101:  | 				$result = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 2102:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 2103:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2104:  | 				$scope = $result->getScope(); | 
| 2105:  | 			} elseif ($expr->class instanceof Name) { | 
| 2106:  | 				$className = $scope->resolveName($expr->class); | 
| 2107:  | 				if ($this->reflectionProvider->hasClass($className)) { | 
| 2108:  | 					$classReflection = $this->reflectionProvider->getClass($className); | 
| 2109:  | 					if (is_string($expr->name)) { | 
| 2110:  | 						$methodName = $expr->name; | 
| 2111:  | 					} else { | 
| 2112:  | 						$methodName = $expr->name->name; | 
| 2113:  | 					} | 
| 2114:  | 					if ($classReflection->hasMethod($methodName)) { | 
| 2115:  | 						$methodReflection = $classReflection->getMethod($methodName, $scope); | 
| 2116:  | 						$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 2117:  | 							$scope, | 
| 2118:  | 							$expr->getArgs(), | 
| 2119:  | 							$methodReflection->getVariants(), | 
| 2120:  | 						); | 
| 2121:  |  | 
| 2122:  | 						$methodThrowPoint = $this->getStaticMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope); | 
| 2123:  | 						if ($methodThrowPoint !== null) { | 
| 2124:  | 							$throwPoints[] = $methodThrowPoint; | 
| 2125:  | 						} | 
| 2126:  | 						if ( | 
| 2127:  | 							$classReflection->getName() === 'Closure' | 
| 2128:  | 							&& strtolower($methodName) === 'bind' | 
| 2129:  | 						) { | 
| 2130:  | 							$thisType = null; | 
| 2131:  | 							if (isset($expr->getArgs()[1])) { | 
| 2132:  | 								$argType = $scope->getType($expr->getArgs()[1]->value); | 
| 2133:  | 								if ($argType instanceof NullType) { | 
| 2134:  | 									$thisType = null; | 
| 2135:  | 								} else { | 
| 2136:  | 									$thisType = $argType; | 
| 2137:  | 								} | 
| 2138:  | 							} | 
| 2139:  | 							$scopeClass = 'static'; | 
| 2140:  | 							if (isset($expr->getArgs()[2])) { | 
| 2141:  | 								$argValue = $expr->getArgs()[2]->value; | 
| 2142:  | 								$argValueType = $scope->getType($argValue); | 
| 2143:  |  | 
| 2144:  | 								$directClassNames = TypeUtils::getDirectClassNames($argValueType); | 
| 2145:  | 								if (count($directClassNames) === 1) { | 
| 2146:  | 									$scopeClass = $directClassNames[0]; | 
| 2147:  | 									$thisType = new ObjectType($scopeClass); | 
| 2148:  | 								} elseif ($argValueType instanceof ConstantStringType) { | 
| 2149:  | 									$scopeClass = $argValueType->getValue(); | 
| 2150:  | 									$thisType = new ObjectType($scopeClass); | 
| 2151:  | 								} elseif ( | 
| 2152:  | 									$argValueType instanceof GenericClassStringType | 
| 2153:  | 									&& $argValueType->getGenericType() instanceof TypeWithClassName | 
| 2154:  | 								) { | 
| 2155:  | 									$scopeClass = $argValueType->getGenericType()->getClassName(); | 
| 2156:  | 									$thisType = $argValueType->getGenericType(); | 
| 2157:  | 								} | 
| 2158:  | 							} | 
| 2159:  | 							$closureBindScope = $scope->enterClosureBind($thisType, $scopeClass); | 
| 2160:  | 						} | 
| 2161:  | 					} else { | 
| 2162:  | 						$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 2163:  | 					} | 
| 2164:  | 				} else { | 
| 2165:  | 					$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 2166:  | 				} | 
| 2167:  | 			} | 
| 2168:  | 			$result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context, $closureBindScope ?? null); | 
| 2169:  | 			$scope = $result->getScope(); | 
| 2170:  | 			$scopeFunction = $scope->getFunction(); | 
| 2171:  | 			if ( | 
| 2172:  | 				$methodReflection !== null | 
| 2173:  | 				&& !$methodReflection->isStatic() | 
| 2174:  | 				&& ( | 
| 2175:  | 					$methodReflection->hasSideEffects()->yes() | 
| 2176:  | 					|| $methodReflection->getName() === '__construct' | 
| 2177:  | 				) | 
| 2178:  | 				&& $scopeFunction instanceof MethodReflection | 
| 2179:  | 				&& !$scopeFunction->isStatic() | 
| 2180:  | 				&& $scope->isInClass() | 
| 2181:  | 				&& ( | 
| 2182:  | 					$scope->getClassReflection()->getName() === $methodReflection->getDeclaringClass()->getName() | 
| 2183:  | 					|| $scope->getClassReflection()->isSubclassOf($methodReflection->getDeclaringClass()->getName()) | 
| 2184:  | 				) | 
| 2185:  | 			) { | 
| 2186:  | 				$scope = $scope->invalidateExpression(new Variable('this'), true); | 
| 2187:  | 			} | 
| 2188:  |  | 
| 2189:  | 			if ($methodReflection !== null) { | 
| 2190:  | 				if ($methodReflection->hasSideEffects()->yes() || $methodReflection->getName() === '__construct') { | 
| 2191:  | 					foreach ($expr->getArgs() as $arg) { | 
| 2192:  | 						$scope = $scope->invalidateExpression($arg->value, true); | 
| 2193:  | 					} | 
| 2194:  | 				} | 
| 2195:  | 			} | 
| 2196:  |  | 
| 2197:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 2198:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2199:  | 		} elseif ($expr instanceof PropertyFetch) { | 
| 2200:  | 			$result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep()); | 
| 2201:  | 			$hasYield = $result->hasYield(); | 
| 2202:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2203:  | 			$scope = $result->getScope(); | 
| 2204:  | 			if ($expr->name instanceof Expr) { | 
| 2205:  | 				$result = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 2206:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 2207:  | 				$scope = $result->getScope(); | 
| 2208:  | 			} | 
| 2209:  | 		} elseif ($expr instanceof Expr\NullsafePropertyFetch) { | 
| 2210:  | 			$nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var); | 
| 2211:  | 			$exprResult = $this->processExprNode(new PropertyFetch($expr->var, $expr->name, $expr->getAttributes()), $nonNullabilityResult->getScope(), $nodeCallback, $context); | 
| 2212:  | 			$scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); | 
| 2213:  |  | 
| 2214:  | 			return new ExpressionResult( | 
| 2215:  | 				$scope, | 
| 2216:  | 				$exprResult->hasYield(), | 
| 2217:  | 				$exprResult->getThrowPoints(), | 
| 2218:  | 				static fn (): MutatingScope => $scope->filterByTruthyValue($expr), | 
| 2219:  | 				static fn (): MutatingScope => $scope->filterByFalseyValue($expr), | 
| 2220:  | 			); | 
| 2221:  | 		} elseif ($expr instanceof StaticPropertyFetch) { | 
| 2222:  | 			$hasYield = false; | 
| 2223:  | 			$throwPoints = []; | 
| 2224:  | 			if ($expr->class instanceof Expr) { | 
| 2225:  | 				$result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); | 
| 2226:  | 				$hasYield = $result->hasYield(); | 
| 2227:  | 				$throwPoints = $result->getThrowPoints(); | 
| 2228:  | 				$scope = $result->getScope(); | 
| 2229:  | 			} | 
| 2230:  | 			if ($expr->name instanceof Expr) { | 
| 2231:  | 				$result = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); | 
| 2232:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 2233:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2234:  | 				$scope = $result->getScope(); | 
| 2235:  | 			} | 
| 2236:  | 		} elseif ($expr instanceof Expr\Closure) { | 
| 2237:  | 			return $this->processClosureNode($expr, $scope, $nodeCallback, $context, null); | 
| 2238:  | 		} elseif ($expr instanceof Expr\ClosureUse) { | 
| 2239:  | 			$this->processExprNode($expr->var, $scope, $nodeCallback, $context); | 
| 2240:  | 			$hasYield = false; | 
| 2241:  | 			$throwPoints = []; | 
| 2242:  | 		} elseif ($expr instanceof Expr\ArrowFunction) { | 
| 2243:  | 			return $this->processArrowFunctionNode($expr, $scope, $nodeCallback, $context, null); | 
| 2244:  | 		} elseif ($expr instanceof ErrorSuppress) { | 
| 2245:  | 			$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context); | 
| 2246:  | 			$hasYield = $result->hasYield(); | 
| 2247:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2248:  | 			$scope = $result->getScope(); | 
| 2249:  | 		} elseif ($expr instanceof Exit_) { | 
| 2250:  | 			$hasYield = false; | 
| 2251:  | 			$throwPoints = []; | 
| 2252:  | 			if ($expr->expr !== null) { | 
| 2253:  | 				$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 2254:  | 				$hasYield = $result->hasYield(); | 
| 2255:  | 				$throwPoints = $result->getThrowPoints(); | 
| 2256:  | 				$scope = $result->getScope(); | 
| 2257:  | 			} | 
| 2258:  | 		} elseif ($expr instanceof Node\Scalar\Encapsed) { | 
| 2259:  | 			$hasYield = false; | 
| 2260:  | 			$throwPoints = []; | 
| 2261:  | 			foreach ($expr->parts as $part) { | 
| 2262:  | 				$result = $this->processExprNode($part, $scope, $nodeCallback, $context->enterDeep()); | 
| 2263:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 2264:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2265:  | 				$scope = $result->getScope(); | 
| 2266:  | 			} | 
| 2267:  | 		} elseif ($expr instanceof ArrayDimFetch) { | 
| 2268:  | 			$hasYield = false; | 
| 2269:  | 			$throwPoints = []; | 
| 2270:  | 			if ($expr->dim !== null) { | 
| 2271:  | 				$result = $this->processExprNode($expr->dim, $scope, $nodeCallback, $context->enterDeep()); | 
| 2272:  | 				$hasYield = $result->hasYield(); | 
| 2273:  | 				$throwPoints = $result->getThrowPoints(); | 
| 2274:  | 				$scope = $result->getScope(); | 
| 2275:  | 			} | 
| 2276:  |  | 
| 2277:  | 			$result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep()); | 
| 2278:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 2279:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2280:  | 			$scope = $result->getScope(); | 
| 2281:  | 		} elseif ($expr instanceof Array_) { | 
| 2282:  | 			$itemNodes = []; | 
| 2283:  | 			$hasYield = false; | 
| 2284:  | 			$throwPoints = []; | 
| 2285:  | 			foreach ($expr->items as $arrayItem) { | 
| 2286:  | 				$itemNodes[] = new LiteralArrayItem($scope, $arrayItem); | 
| 2287:  | 				if ($arrayItem === null) { | 
| 2288:  | 					continue; | 
| 2289:  | 				} | 
| 2290:  | 				$result = $this->processExprNode($arrayItem, $scope, $nodeCallback, $context->enterDeep()); | 
| 2291:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 2292:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2293:  | 				$scope = $result->getScope(); | 
| 2294:  | 			} | 
| 2295:  | 			$nodeCallback(new LiteralArrayNode($expr, $itemNodes), $scope); | 
| 2296:  | 		} elseif ($expr instanceof ArrayItem) { | 
| 2297:  | 			$hasYield = false; | 
| 2298:  | 			$throwPoints = []; | 
| 2299:  | 			if ($expr->key !== null) { | 
| 2300:  | 				$result = $this->processExprNode($expr->key, $scope, $nodeCallback, $context->enterDeep()); | 
| 2301:  | 				$hasYield = $result->hasYield(); | 
| 2302:  | 				$throwPoints = $result->getThrowPoints(); | 
| 2303:  | 				$scope = $result->getScope(); | 
| 2304:  | 			} | 
| 2305:  | 			$result = $this->processExprNode($expr->value, $scope, $nodeCallback, $context->enterDeep()); | 
| 2306:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 2307:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2308:  | 			$scope = $result->getScope(); | 
| 2309:  | 		} elseif ($expr instanceof BooleanAnd || $expr instanceof BinaryOp\LogicalAnd) { | 
| 2310:  | 			$leftResult = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); | 
| 2311:  | 			$rightResult = $this->processExprNode($expr->right, $leftResult->getTruthyScope(), $nodeCallback, $context); | 
| 2312:  | 			$rightExprType = $rightResult->getScope()->getType($expr->right); | 
| 2313:  | 			if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { | 
| 2314:  | 				$leftMergedWithRightScope = $leftResult->getFalseyScope(); | 
| 2315:  | 			} else { | 
| 2316:  | 				$leftMergedWithRightScope = $leftResult->getScope()->mergeWith($rightResult->getScope()); | 
| 2317:  | 			} | 
| 2318:  |  | 
| 2319:  | 			$this->callNodeCallbackWithExpression($nodeCallback, new BooleanAndNode($expr, $leftResult->getTruthyScope()), $scope, $context); | 
| 2320:  |  | 
| 2321:  | 			return new ExpressionResult( | 
| 2322:  | 				$leftMergedWithRightScope, | 
| 2323:  | 				$leftResult->hasYield() || $rightResult->hasYield(), | 
| 2324:  | 				array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), | 
| 2325:  | 				static fn (): MutatingScope => $rightResult->getScope()->filterByTruthyValue($expr), | 
| 2326:  | 				static fn (): MutatingScope => $leftMergedWithRightScope->filterByFalseyValue($expr), | 
| 2327:  | 			); | 
| 2328:  | 		} elseif ($expr instanceof BooleanOr || $expr instanceof BinaryOp\LogicalOr) { | 
| 2329:  | 			$leftResult = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); | 
| 2330:  | 			$rightResult = $this->processExprNode($expr->right, $leftResult->getFalseyScope(), $nodeCallback, $context); | 
| 2331:  | 			$rightExprType = $rightResult->getScope()->getType($expr->right); | 
| 2332:  | 			if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { | 
| 2333:  | 				$leftMergedWithRightScope = $leftResult->getTruthyScope(); | 
| 2334:  | 			} else { | 
| 2335:  | 				$leftMergedWithRightScope = $leftResult->getScope()->mergeWith($rightResult->getScope()); | 
| 2336:  | 			} | 
| 2337:  |  | 
| 2338:  | 			$this->callNodeCallbackWithExpression($nodeCallback, new BooleanOrNode($expr, $leftResult->getFalseyScope()), $scope, $context); | 
| 2339:  |  | 
| 2340:  | 			return new ExpressionResult( | 
| 2341:  | 				$leftMergedWithRightScope, | 
| 2342:  | 				$leftResult->hasYield() || $rightResult->hasYield(), | 
| 2343:  | 				array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), | 
| 2344:  | 				static fn (): MutatingScope => $leftMergedWithRightScope->filterByTruthyValue($expr), | 
| 2345:  | 				static fn (): MutatingScope => $rightResult->getScope()->filterByFalseyValue($expr), | 
| 2346:  | 			); | 
| 2347:  | 		} elseif ($expr instanceof Coalesce) { | 
| 2348:  | 			$nonNullabilityResult = $this->ensureNonNullability($scope, $expr->left); | 
| 2349:  | 			$condScope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->left); | 
| 2350:  | 			$condResult = $this->processExprNode($expr->left, $condScope, $nodeCallback, $context->enterDeep()); | 
| 2351:  | 			$scope = $this->revertNonNullability($condResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); | 
| 2352:  | 			$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $expr->left); | 
| 2353:  |  | 
| 2354:  | 			$rightScope = $scope->filterByFalseyValue(new Expr\Isset_([$expr->left])); | 
| 2355:  | 			$rightResult = $this->processExprNode($expr->right, $rightScope, $nodeCallback, $context->enterDeep()); | 
| 2356:  | 			$rightExprType = $scope->getType($expr->right); | 
| 2357:  | 			if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { | 
| 2358:  | 				$scope = $scope->filterByTruthyValue(new Expr\Isset_([$expr->left])); | 
| 2359:  | 			} else { | 
| 2360:  | 				$scope = $scope->filterByTruthyValue(new Expr\Isset_([$expr->left]))->mergeWith($rightResult->getScope()); | 
| 2361:  | 			} | 
| 2362:  |  | 
| 2363:  | 			$hasYield = $condResult->hasYield() || $rightResult->hasYield(); | 
| 2364:  | 			$throwPoints = array_merge($condResult->getThrowPoints(), $rightResult->getThrowPoints()); | 
| 2365:  | 		} elseif ($expr instanceof BinaryOp) { | 
| 2366:  | 			$result = $this->processExprNode($expr->left, $scope, $nodeCallback, $context->enterDeep()); | 
| 2367:  | 			$scope = $result->getScope(); | 
| 2368:  | 			$hasYield = $result->hasYield(); | 
| 2369:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2370:  | 			$result = $this->processExprNode($expr->right, $scope, $nodeCallback, $context->enterDeep()); | 
| 2371:  | 			if ( | 
| 2372:  | 				($expr instanceof BinaryOp\Div || $expr instanceof BinaryOp\Mod) && | 
| 2373:  | 				!$scope->getType($expr->right)->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no() | 
| 2374:  | 			) { | 
| 2375:  | 				$throwPoints[] = ThrowPoint::createExplicit($scope, new ObjectType(DivisionByZeroError::class), $expr, false); | 
| 2376:  | 			} | 
| 2377:  | 			$scope = $result->getScope(); | 
| 2378:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 2379:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2380:  | 		} elseif ($expr instanceof Expr\Include_) { | 
| 2381:  | 			$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 2382:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2383:  | 			$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 2384:  | 			$hasYield = $result->hasYield(); | 
| 2385:  | 			$scope = $result->getScope(); | 
| 2386:  | 		} elseif ( | 
| 2387:  | 			$expr instanceof Expr\BitwiseNot | 
| 2388:  | 			|| $expr instanceof Cast | 
| 2389:  | 			|| $expr instanceof Expr\Clone_ | 
| 2390:  | 			|| $expr instanceof Expr\Print_ | 
| 2391:  | 			|| $expr instanceof Expr\UnaryMinus | 
| 2392:  | 			|| $expr instanceof Expr\UnaryPlus | 
| 2393:  | 		) { | 
| 2394:  | 			$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 2395:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2396:  | 			$hasYield = $result->hasYield(); | 
| 2397:  |  | 
| 2398:  | 			$scope = $result->getScope(); | 
| 2399:  | 		} elseif ($expr instanceof Expr\Eval_) { | 
| 2400:  | 			$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 2401:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2402:  | 			$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 2403:  | 			$hasYield = $result->hasYield(); | 
| 2404:  |  | 
| 2405:  | 			$scope = $result->getScope(); | 
| 2406:  | 		} elseif ($expr instanceof Expr\YieldFrom) { | 
| 2407:  | 			$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 2408:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2409:  | 			$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 2410:  | 			$hasYield = true; | 
| 2411:  |  | 
| 2412:  | 			$scope = $result->getScope(); | 
| 2413:  | 		} elseif ($expr instanceof BooleanNot) { | 
| 2414:  | 			$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 2415:  | 			$scope = $result->getScope(); | 
| 2416:  | 			$hasYield = $result->hasYield(); | 
| 2417:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2418:  | 		} elseif ($expr instanceof Expr\ClassConstFetch) { | 
| 2419:  | 			$hasYield = false; | 
| 2420:  | 			$throwPoints = []; | 
| 2421:  | 			if ($expr->class instanceof Expr) { | 
| 2422:  | 				$result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); | 
| 2423:  | 				$scope = $result->getScope(); | 
| 2424:  | 				$hasYield = $result->hasYield(); | 
| 2425:  | 				$throwPoints = $result->getThrowPoints(); | 
| 2426:  | 			} | 
| 2427:  | 		} elseif ($expr instanceof Expr\Empty_) { | 
| 2428:  | 			$nonNullabilityResult = $this->ensureNonNullability($scope, $expr->expr); | 
| 2429:  | 			$scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->expr); | 
| 2430:  | 			$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 2431:  | 			$scope = $result->getScope(); | 
| 2432:  | 			$hasYield = $result->hasYield(); | 
| 2433:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2434:  | 			$scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); | 
| 2435:  | 			$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $expr->expr); | 
| 2436:  | 		} elseif ($expr instanceof Expr\Isset_) { | 
| 2437:  | 			$hasYield = false; | 
| 2438:  | 			$throwPoints = []; | 
| 2439:  | 			$nonNullabilityResults = []; | 
| 2440:  | 			foreach ($expr->vars as $var) { | 
| 2441:  | 				$nonNullabilityResult = $this->ensureNonNullability($scope, $var); | 
| 2442:  | 				$scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $var); | 
| 2443:  | 				$result = $this->processExprNode($var, $scope, $nodeCallback, $context->enterDeep()); | 
| 2444:  | 				$scope = $result->getScope(); | 
| 2445:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 2446:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2447:  | 				$nonNullabilityResults[] = $nonNullabilityResult; | 
| 2448:  | 			} | 
| 2449:  | 			foreach (array_reverse($expr->vars) as $var) { | 
| 2450:  | 				$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $var); | 
| 2451:  | 			} | 
| 2452:  | 			foreach (array_reverse($nonNullabilityResults) as $nonNullabilityResult) { | 
| 2453:  | 				$scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); | 
| 2454:  | 			} | 
| 2455:  | 		} elseif ($expr instanceof Instanceof_) { | 
| 2456:  | 			$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); | 
| 2457:  | 			$scope = $result->getScope(); | 
| 2458:  | 			$hasYield = $result->hasYield(); | 
| 2459:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2460:  | 			if ($expr->class instanceof Expr) { | 
| 2461:  | 				$result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); | 
| 2462:  | 				$scope = $result->getScope(); | 
| 2463:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 2464:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2465:  | 			} | 
| 2466:  | 		} elseif ($expr instanceof List_) { | 
| 2467:  | 			 | 
| 2468:  | 			return new ExpressionResult($scope, false, []); | 
| 2469:  | 		} elseif ($expr instanceof New_) { | 
| 2470:  | 			$parametersAcceptor = null; | 
| 2471:  | 			$constructorReflection = null; | 
| 2472:  | 			$hasYield = false; | 
| 2473:  | 			$throwPoints = []; | 
| 2474:  | 			if ($expr->class instanceof Expr) { | 
| 2475:  | 				$objectClasses = TypeUtils::getDirectClassNames($scope->getType($expr)); | 
| 2476:  | 				if (count($objectClasses) === 1) { | 
| 2477:  | 					$objectExprResult = $this->processExprNode(new New_(new Name($objectClasses[0])), $scope, static function (): void { | 
| 2478:  | 					}, $context->enterDeep()); | 
| 2479:  | 					$additionalThrowPoints = $objectExprResult->getThrowPoints(); | 
| 2480:  | 				} else { | 
| 2481:  | 					$additionalThrowPoints = [ThrowPoint::createImplicit($scope, $expr)]; | 
| 2482:  | 				} | 
| 2483:  |  | 
| 2484:  | 				$result = $this->processExprNode($expr->class, $scope, $nodeCallback, $context->enterDeep()); | 
| 2485:  | 				$scope = $result->getScope(); | 
| 2486:  | 				$hasYield = $result->hasYield(); | 
| 2487:  | 				$throwPoints = $result->getThrowPoints(); | 
| 2488:  | 				foreach ($additionalThrowPoints as $throwPoint) { | 
| 2489:  | 					$throwPoints[] = $throwPoint; | 
| 2490:  | 				} | 
| 2491:  | 			} elseif ($expr->class instanceof Class_) { | 
| 2492:  | 				$this->reflectionProvider->getAnonymousClassReflection($expr->class, $scope);  | 
| 2493:  | 				$this->processStmtNode($expr->class, $scope, $nodeCallback); | 
| 2494:  | 			} else { | 
| 2495:  | 				$className = $scope->resolveName($expr->class); | 
| 2496:  | 				if ($this->reflectionProvider->hasClass($className)) { | 
| 2497:  | 					$classReflection = $this->reflectionProvider->getClass($className); | 
| 2498:  | 					if ($classReflection->hasConstructor()) { | 
| 2499:  | 						$constructorReflection = $classReflection->getConstructor(); | 
| 2500:  | 						$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( | 
| 2501:  | 							$scope, | 
| 2502:  | 							$expr->getArgs(), | 
| 2503:  | 							$constructorReflection->getVariants(), | 
| 2504:  | 						); | 
| 2505:  | 						$hasSideEffects = $constructorReflection->hasSideEffects(); | 
| 2506:  | 						if ($hasSideEffects->yes()) { | 
| 2507:  | 							foreach ($expr->getArgs() as $arg) { | 
| 2508:  | 								$scope = $scope->invalidateExpression($arg->value, true); | 
| 2509:  | 							} | 
| 2510:  | 						} | 
| 2511:  | 						$constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $parametersAcceptor, $classReflection, $expr, $expr->class, $expr->getArgs(), $scope); | 
| 2512:  | 						if ($constructorThrowPoint !== null) { | 
| 2513:  | 							$throwPoints[] = $constructorThrowPoint; | 
| 2514:  | 						} | 
| 2515:  | 					} | 
| 2516:  | 				} else { | 
| 2517:  | 					$throwPoints[] = ThrowPoint::createImplicit($scope, $expr); | 
| 2518:  | 				} | 
| 2519:  | 			} | 
| 2520:  | 			$result = $this->processArgs($constructorReflection, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context); | 
| 2521:  | 			$scope = $result->getScope(); | 
| 2522:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 2523:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 2524:  | 		} elseif ( | 
| 2525:  | 			$expr instanceof Expr\PreInc | 
| 2526:  | 			|| $expr instanceof Expr\PostInc | 
| 2527:  | 			|| $expr instanceof Expr\PreDec | 
| 2528:  | 			|| $expr instanceof Expr\PostDec | 
| 2529:  | 		) { | 
| 2530:  | 			$result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep()); | 
| 2531:  | 			$scope = $result->getScope(); | 
| 2532:  | 			$hasYield = $result->hasYield(); | 
| 2533:  | 			$throwPoints = []; | 
| 2534:  |  | 
| 2535:  | 			$newExpr = $expr; | 
| 2536:  | 			if ($expr instanceof Expr\PostInc) { | 
| 2537:  | 				$newExpr = new Expr\PreInc($expr->var); | 
| 2538:  | 			} elseif ($expr instanceof Expr\PostDec) { | 
| 2539:  | 				$newExpr = new Expr\PreDec($expr->var); | 
| 2540:  | 			} | 
| 2541:  |  | 
| 2542:  | 			$scope = $this->processAssignVar( | 
| 2543:  | 				$scope, | 
| 2544:  | 				$expr->var, | 
| 2545:  | 				$newExpr, | 
| 2546:  | 				static function (Node $node, Scope $scope) use ($nodeCallback): void { | 
| 2547:  | 					if (!$node instanceof PropertyAssignNode) { | 
| 2548:  | 						return; | 
| 2549:  | 					} | 
| 2550:  |  | 
| 2551:  | 					$nodeCallback($node, $scope); | 
| 2552:  | 				}, | 
| 2553:  | 				$context, | 
| 2554:  | 				static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, []), | 
| 2555:  | 				false, | 
| 2556:  | 			)->getScope(); | 
| 2557:  | 		} elseif ($expr instanceof Ternary) { | 
| 2558:  | 			$ternaryCondResult = $this->processExprNode($expr->cond, $scope, $nodeCallback, $context->enterDeep()); | 
| 2559:  | 			$throwPoints = $ternaryCondResult->getThrowPoints(); | 
| 2560:  | 			$ifTrueScope = $ternaryCondResult->getTruthyScope(); | 
| 2561:  | 			$ifFalseScope = $ternaryCondResult->getFalseyScope(); | 
| 2562:  | 			$ifTrueType = null; | 
| 2563:  | 			if ($expr->if !== null) { | 
| 2564:  | 				$ifResult = $this->processExprNode($expr->if, $ifTrueScope, $nodeCallback, $context); | 
| 2565:  | 				$throwPoints = array_merge($throwPoints, $ifResult->getThrowPoints()); | 
| 2566:  | 				$ifTrueScope = $ifResult->getScope(); | 
| 2567:  | 				$ifTrueType = $ifTrueScope->getType($expr->if); | 
| 2568:  | 			} | 
| 2569:  |  | 
| 2570:  | 			$elseResult = $this->processExprNode($expr->else, $ifFalseScope, $nodeCallback, $context); | 
| 2571:  | 			$throwPoints = array_merge($throwPoints, $elseResult->getThrowPoints()); | 
| 2572:  | 			$ifFalseScope = $elseResult->getScope(); | 
| 2573:  | 			$ifFalseType = $ifFalseScope->getType($expr->else); | 
| 2574:  |  | 
| 2575:  | 			if ($ifTrueType instanceof NeverType && $ifTrueType->isExplicit()) { | 
| 2576:  | 				$finalScope = $ifFalseScope; | 
| 2577:  | 			} elseif ($ifFalseType instanceof NeverType && $ifFalseType->isExplicit()) { | 
| 2578:  | 				$finalScope = $ifTrueScope; | 
| 2579:  | 			} else { | 
| 2580:  | 				$finalScope = $ifTrueScope->mergeWith($ifFalseScope); | 
| 2581:  | 			} | 
| 2582:  |  | 
| 2583:  | 			return new ExpressionResult( | 
| 2584:  | 				$finalScope, | 
| 2585:  | 				$ternaryCondResult->hasYield(), | 
| 2586:  | 				$throwPoints, | 
| 2587:  | 				static fn (): MutatingScope => $finalScope->filterByTruthyValue($expr), | 
| 2588:  | 				static fn (): MutatingScope => $finalScope->filterByFalseyValue($expr), | 
| 2589:  | 			); | 
| 2590:  |  | 
| 2591:  | 		} elseif ($expr instanceof Expr\Yield_) { | 
| 2592:  | 			$throwPoints = [ | 
| 2593:  | 				ThrowPoint::createImplicit($scope, $expr), | 
| 2594:  | 			]; | 
| 2595:  | 			if ($expr->key !== null) { | 
| 2596:  | 				$keyResult = $this->processExprNode($expr->key, $scope, $nodeCallback, $context->enterDeep()); | 
| 2597:  | 				$scope = $keyResult->getScope(); | 
| 2598:  | 				$throwPoints = $keyResult->getThrowPoints(); | 
| 2599:  | 			} | 
| 2600:  | 			if ($expr->value !== null) { | 
| 2601:  | 				$valueResult = $this->processExprNode($expr->value, $scope, $nodeCallback, $context->enterDeep()); | 
| 2602:  | 				$scope = $valueResult->getScope(); | 
| 2603:  | 				$throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); | 
| 2604:  | 			} | 
| 2605:  | 			$hasYield = true; | 
| 2606:  | 		} elseif ($expr instanceof Expr\Match_) { | 
| 2607:  | 			$deepContext = $context->enterDeep(); | 
| 2608:  | 			$condResult = $this->processExprNode($expr->cond, $scope, $nodeCallback, $deepContext); | 
| 2609:  | 			$scope = $condResult->getScope(); | 
| 2610:  | 			$hasYield = $condResult->hasYield(); | 
| 2611:  | 			$throwPoints = $condResult->getThrowPoints(); | 
| 2612:  | 			$matchScope = $scope; | 
| 2613:  | 			$armNodes = []; | 
| 2614:  | 			$hasDefaultCond = false; | 
| 2615:  | 			$hasAlwaysTrueCond = false; | 
| 2616:  | 			foreach ($expr->arms as $arm) { | 
| 2617:  | 				if ($arm->conds === null) { | 
| 2618:  | 					$hasDefaultCond = true; | 
| 2619:  | 					$armResult = $this->processExprNode($arm->body, $matchScope, $nodeCallback, ExpressionContext::createTopLevel()); | 
| 2620:  | 					$matchScope = $armResult->getScope(); | 
| 2621:  | 					$hasYield = $hasYield || $armResult->hasYield(); | 
| 2622:  | 					$throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); | 
| 2623:  | 					$scope = $scope->mergeWith($matchScope); | 
| 2624:  | 					$armNodes[] = new MatchExpressionArm([], $arm->getLine()); | 
| 2625:  | 					continue; | 
| 2626:  | 				} | 
| 2627:  |  | 
| 2628:  | 				if (count($arm->conds) === 0) { | 
| 2629:  | 					throw new ShouldNotHappenException(); | 
| 2630:  | 				} | 
| 2631:  |  | 
| 2632:  | 				$filteringExpr = null; | 
| 2633:  | 				$armCondScope = $matchScope; | 
| 2634:  | 				$condNodes = []; | 
| 2635:  | 				foreach ($arm->conds as $armCond) { | 
| 2636:  | 					$condNodes[] = new MatchExpressionArmCondition($armCond, $armCondScope, $armCond->getLine()); | 
| 2637:  | 					$armCondResult = $this->processExprNode($armCond, $armCondScope, $nodeCallback, $deepContext); | 
| 2638:  | 					$hasYield = $hasYield || $armCondResult->hasYield(); | 
| 2639:  | 					$throwPoints = array_merge($throwPoints, $armCondResult->getThrowPoints()); | 
| 2640:  | 					$armCondExpr = new BinaryOp\Identical($expr->cond, $armCond); | 
| 2641:  | 					$armCondType = $armCondResult->getScope()->getType($armCondExpr); | 
| 2642:  | 					if ($armCondType instanceof ConstantBooleanType && $armCondType->getValue()) { | 
| 2643:  | 						$hasAlwaysTrueCond = true; | 
| 2644:  | 					} | 
| 2645:  | 					$armCondScope = $armCondResult->getScope()->filterByFalseyValue($armCondExpr); | 
| 2646:  | 					if ($filteringExpr === null) { | 
| 2647:  | 						$filteringExpr = $armCondExpr; | 
| 2648:  | 						continue; | 
| 2649:  | 					} | 
| 2650:  |  | 
| 2651:  | 					$filteringExpr = new BinaryOp\BooleanOr($filteringExpr, $armCondExpr); | 
| 2652:  | 				} | 
| 2653:  |  | 
| 2654:  | 				$armNodes[] = new MatchExpressionArm($condNodes, $arm->getLine()); | 
| 2655:  |  | 
| 2656:  | 				$armResult = $this->processExprNode( | 
| 2657:  | 					$arm->body, | 
| 2658:  | 					$matchScope->filterByTruthyValue($filteringExpr), | 
| 2659:  | 					$nodeCallback, | 
| 2660:  | 					ExpressionContext::createTopLevel(), | 
| 2661:  | 				); | 
| 2662:  | 				$armScope = $armResult->getScope(); | 
| 2663:  | 				$scope = $scope->mergeWith($armScope); | 
| 2664:  | 				$hasYield = $hasYield || $armResult->hasYield(); | 
| 2665:  | 				$throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); | 
| 2666:  | 				$matchScope = $matchScope->filterByFalseyValue($filteringExpr); | 
| 2667:  | 			} | 
| 2668:  |  | 
| 2669:  | 			$remainingType = $matchScope->getType($expr->cond); | 
| 2670:  | 			if (!$hasDefaultCond && !$hasAlwaysTrueCond && !$remainingType instanceof NeverType) { | 
| 2671:  | 				$throwPoints[] = ThrowPoint::createExplicit($scope, new ObjectType(UnhandledMatchError::class), $expr, false); | 
| 2672:  | 			} | 
| 2673:  |  | 
| 2674:  | 			$nodeCallback(new MatchExpressionNode($expr->cond, $armNodes, $expr, $matchScope), $scope); | 
| 2675:  | 		} elseif ($expr instanceof Expr\Throw_) { | 
| 2676:  | 			$hasYield = false; | 
| 2677:  | 			$result = $this->processExprNode($expr->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2678:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2679:  | 			$throwPoints[] = ThrowPoint::createExplicit($scope, $scope->getType($expr->expr), $expr, false); | 
| 2680:  | 		} elseif ($expr instanceof FunctionCallableNode) { | 
| 2681:  | 			$throwPoints = []; | 
| 2682:  | 			$hasYield = false; | 
| 2683:  | 			if ($expr->getName() instanceof Expr) { | 
| 2684:  | 				$result = $this->processExprNode($expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2685:  | 				$scope = $result->getScope(); | 
| 2686:  | 				$hasYield = $result->hasYield(); | 
| 2687:  | 				$throwPoints = $result->getThrowPoints(); | 
| 2688:  | 			} | 
| 2689:  | 		} elseif ($expr instanceof MethodCallableNode) { | 
| 2690:  | 			$result = $this->processExprNode($expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2691:  | 			$scope = $result->getScope(); | 
| 2692:  | 			$hasYield = $result->hasYield(); | 
| 2693:  | 			$throwPoints = $result->getThrowPoints(); | 
| 2694:  | 			if ($expr->getName() instanceof Expr) { | 
| 2695:  | 				$nameResult = $this->processExprNode($expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2696:  | 				$scope = $nameResult->getScope(); | 
| 2697:  | 				$hasYield = $hasYield || $nameResult->hasYield(); | 
| 2698:  | 				$throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints()); | 
| 2699:  | 			} | 
| 2700:  | 		} elseif ($expr instanceof StaticMethodCallableNode) { | 
| 2701:  | 			$throwPoints = []; | 
| 2702:  | 			$hasYield = false; | 
| 2703:  | 			if ($expr->getClass() instanceof Expr) { | 
| 2704:  | 				$classResult = $this->processExprNode($expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2705:  | 				$scope = $classResult->getScope(); | 
| 2706:  | 				$hasYield = $classResult->hasYield(); | 
| 2707:  | 				$throwPoints = $classResult->getThrowPoints(); | 
| 2708:  | 			} | 
| 2709:  | 			if ($expr->getName() instanceof Expr) { | 
| 2710:  | 				$nameResult = $this->processExprNode($expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2711:  | 				$scope = $nameResult->getScope(); | 
| 2712:  | 				$hasYield = $hasYield || $nameResult->hasYield(); | 
| 2713:  | 				$throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints()); | 
| 2714:  | 			} | 
| 2715:  | 		} elseif ($expr instanceof InstantiationCallableNode) { | 
| 2716:  | 			$throwPoints = []; | 
| 2717:  | 			$hasYield = false; | 
| 2718:  | 			if ($expr->getClass() instanceof Expr) { | 
| 2719:  | 				$classResult = $this->processExprNode($expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 2720:  | 				$scope = $classResult->getScope(); | 
| 2721:  | 				$hasYield = $classResult->hasYield(); | 
| 2722:  | 				$throwPoints = $classResult->getThrowPoints(); | 
| 2723:  | 			} | 
| 2724:  | 		} else { | 
| 2725:  | 			$hasYield = false; | 
| 2726:  | 			$throwPoints = []; | 
| 2727:  | 		} | 
| 2728:  |  | 
| 2729:  | 		return new ExpressionResult( | 
| 2730:  | 			$scope, | 
| 2731:  | 			$hasYield, | 
| 2732:  | 			$throwPoints, | 
| 2733:  | 			static fn (): MutatingScope => $scope->filterByTruthyValue($expr), | 
| 2734:  | 			static fn (): MutatingScope => $scope->filterByFalseyValue($expr), | 
| 2735:  | 		); | 
| 2736:  | 	} | 
| 2737:  |  | 
| 2738:  | 	private function getFunctionThrowPoint( | 
| 2739:  | 		FunctionReflection $functionReflection, | 
| 2740:  | 		?ParametersAcceptor $parametersAcceptor, | 
| 2741:  | 		FuncCall $funcCall, | 
| 2742:  | 		MutatingScope $scope, | 
| 2743:  | 	): ?ThrowPoint | 
| 2744:  | 	{ | 
| 2745:  | 		$normalizedFuncCall = $funcCall; | 
| 2746:  | 		if ($parametersAcceptor !== null) { | 
| 2747:  | 			$normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $funcCall); | 
| 2748:  | 		} | 
| 2749:  |  | 
| 2750:  | 		if ($normalizedFuncCall !== null) { | 
| 2751:  | 			foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicFunctionThrowTypeExtensions() as $extension) { | 
| 2752:  | 				if (!$extension->isFunctionSupported($functionReflection)) { | 
| 2753:  | 					continue; | 
| 2754:  | 				} | 
| 2755:  |  | 
| 2756:  | 				$throwType = $extension->getThrowTypeFromFunctionCall($functionReflection, $normalizedFuncCall, $scope); | 
| 2757:  | 				if ($throwType === null) { | 
| 2758:  | 					return null; | 
| 2759:  | 				} | 
| 2760:  |  | 
| 2761:  | 				return ThrowPoint::createExplicit($scope, $throwType, $funcCall, false); | 
| 2762:  | 			} | 
| 2763:  | 		} | 
| 2764:  |  | 
| 2765:  | 		$throwType = $functionReflection->getThrowType(); | 
| 2766:  | 		if ($throwType === null && $parametersAcceptor !== null) { | 
| 2767:  | 			$returnType = $parametersAcceptor->getReturnType(); | 
| 2768:  | 			if ($returnType instanceof NeverType && $returnType->isExplicit()) { | 
| 2769:  | 				$throwType = new ObjectType(Throwable::class); | 
| 2770:  | 			} | 
| 2771:  | 		} | 
| 2772:  |  | 
| 2773:  | 		if ($throwType !== null) { | 
| 2774:  | 			if (!$throwType instanceof VoidType) { | 
| 2775:  | 				return ThrowPoint::createExplicit($scope, $throwType, $funcCall, true); | 
| 2776:  | 			} | 
| 2777:  | 		} elseif ($this->implicitThrows) { | 
| 2778:  | 			$requiredParameters = null; | 
| 2779:  | 			if ($parametersAcceptor !== null) { | 
| 2780:  | 				$requiredParameters = 0; | 
| 2781:  | 				foreach ($parametersAcceptor->getParameters() as $parameter) { | 
| 2782:  | 					if ($parameter->isOptional()) { | 
| 2783:  | 						continue; | 
| 2784:  | 					} | 
| 2785:  |  | 
| 2786:  | 					$requiredParameters++; | 
| 2787:  | 				} | 
| 2788:  | 			} | 
| 2789:  | 			if ( | 
| 2790:  | 				!$functionReflection->isBuiltin() | 
| 2791:  | 				|| $requiredParameters === null | 
| 2792:  | 				|| $requiredParameters > 0 | 
| 2793:  | 				|| count($funcCall->getArgs()) > 0 | 
| 2794:  | 			) { | 
| 2795:  | 				$functionReturnedType = $scope->getType($funcCall); | 
| 2796:  | 				if (!(new ObjectType(Throwable::class))->isSuperTypeOf($functionReturnedType)->yes()) { | 
| 2797:  | 					return ThrowPoint::createImplicit($scope, $funcCall); | 
| 2798:  | 				} | 
| 2799:  | 			} | 
| 2800:  | 		} | 
| 2801:  |  | 
| 2802:  | 		return null; | 
| 2803:  | 	} | 
| 2804:  |  | 
| 2805:  | 	private function getMethodThrowPoint(MethodReflection $methodReflection, ParametersAcceptor $parametersAcceptor, MethodCall $methodCall, MutatingScope $scope): ?ThrowPoint | 
| 2806:  | 	{ | 
| 2807:  | 		$normalizedMethodCall = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $methodCall); | 
| 2808:  | 		if ($normalizedMethodCall !== null) { | 
| 2809:  | 			foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicMethodThrowTypeExtensions() as $extension) { | 
| 2810:  | 				if (!$extension->isMethodSupported($methodReflection)) { | 
| 2811:  | 					continue; | 
| 2812:  | 				} | 
| 2813:  |  | 
| 2814:  | 				$throwType = $extension->getThrowTypeFromMethodCall($methodReflection, $normalizedMethodCall, $scope); | 
| 2815:  | 				if ($throwType === null) { | 
| 2816:  | 					return null; | 
| 2817:  | 				} | 
| 2818:  |  | 
| 2819:  | 				return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); | 
| 2820:  | 			} | 
| 2821:  | 		} | 
| 2822:  |  | 
| 2823:  | 		$throwType = $methodReflection->getThrowType(); | 
| 2824:  | 		if ($throwType === null) { | 
| 2825:  | 			$returnType = $parametersAcceptor->getReturnType(); | 
| 2826:  | 			if ($returnType instanceof NeverType && $returnType->isExplicit()) { | 
| 2827:  | 				$throwType = new ObjectType(Throwable::class); | 
| 2828:  | 			} | 
| 2829:  | 		} | 
| 2830:  |  | 
| 2831:  | 		if ($throwType !== null) { | 
| 2832:  | 			if (!$throwType instanceof VoidType) { | 
| 2833:  | 				return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true); | 
| 2834:  | 			} | 
| 2835:  | 		} elseif ($this->implicitThrows) { | 
| 2836:  | 			$methodReturnedType = $scope->getType($methodCall); | 
| 2837:  | 			if (!(new ObjectType(Throwable::class))->isSuperTypeOf($methodReturnedType)->yes()) { | 
| 2838:  | 				return ThrowPoint::createImplicit($scope, $methodCall); | 
| 2839:  | 			} | 
| 2840:  | 		} | 
| 2841:  |  | 
| 2842:  | 		return null; | 
| 2843:  | 	} | 
| 2844:  |  | 
| 2845:  | 	 | 
| 2846:  |  | 
| 2847:  |  | 
| 2848:  | 	private function getConstructorThrowPoint(MethodReflection $constructorReflection, ParametersAcceptor $parametersAcceptor, ClassReflection $classReflection, New_ $new, Name $className, array $args, MutatingScope $scope): ?ThrowPoint | 
| 2849:  | 	{ | 
| 2850:  | 		$methodCall = new StaticCall($className, $constructorReflection->getName(), $args); | 
| 2851:  | 		$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall); | 
| 2852:  | 		if ($normalizedMethodCall !== null) { | 
| 2853:  | 			foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicStaticMethodThrowTypeExtensions() as $extension) { | 
| 2854:  | 				if (!$extension->isStaticMethodSupported($constructorReflection)) { | 
| 2855:  | 					continue; | 
| 2856:  | 				} | 
| 2857:  |  | 
| 2858:  | 				$throwType = $extension->getThrowTypeFromStaticMethodCall($constructorReflection, $normalizedMethodCall, $scope); | 
| 2859:  | 				if ($throwType === null) { | 
| 2860:  | 					return null; | 
| 2861:  | 				} | 
| 2862:  |  | 
| 2863:  | 				return ThrowPoint::createExplicit($scope, $throwType, $new, false); | 
| 2864:  | 			} | 
| 2865:  | 		} | 
| 2866:  |  | 
| 2867:  | 		if ($constructorReflection->getThrowType() !== null) { | 
| 2868:  | 			$throwType = $constructorReflection->getThrowType(); | 
| 2869:  | 			if (!$throwType instanceof VoidType) { | 
| 2870:  | 				return ThrowPoint::createExplicit($scope, $throwType, $new, true); | 
| 2871:  | 			} | 
| 2872:  | 		} elseif ($this->implicitThrows) { | 
| 2873:  | 			if ($classReflection->getName() !== Throwable::class && !$classReflection->isSubclassOf(Throwable::class)) { | 
| 2874:  | 				return ThrowPoint::createImplicit($scope, $methodCall); | 
| 2875:  | 			} | 
| 2876:  | 		} | 
| 2877:  |  | 
| 2878:  | 		return null; | 
| 2879:  | 	} | 
| 2880:  |  | 
| 2881:  | 	private function getStaticMethodThrowPoint(MethodReflection $methodReflection, ParametersAcceptor $parametersAcceptor, StaticCall $methodCall, MutatingScope $scope): ?ThrowPoint | 
| 2882:  | 	{ | 
| 2883:  | 		$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall); | 
| 2884:  | 		if ($normalizedMethodCall !== null) { | 
| 2885:  | 			foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicStaticMethodThrowTypeExtensions() as $extension) { | 
| 2886:  | 				if (!$extension->isStaticMethodSupported($methodReflection)) { | 
| 2887:  | 					continue; | 
| 2888:  | 				} | 
| 2889:  |  | 
| 2890:  | 				$throwType = $extension->getThrowTypeFromStaticMethodCall($methodReflection, $normalizedMethodCall, $scope); | 
| 2891:  | 				if ($throwType === null) { | 
| 2892:  | 					return null; | 
| 2893:  | 				} | 
| 2894:  |  | 
| 2895:  | 				return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); | 
| 2896:  | 			} | 
| 2897:  | 		} | 
| 2898:  |  | 
| 2899:  | 		if ($methodReflection->getThrowType() !== null) { | 
| 2900:  | 			$throwType = $methodReflection->getThrowType(); | 
| 2901:  | 			if (!$throwType instanceof VoidType) { | 
| 2902:  | 				return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true); | 
| 2903:  | 			} | 
| 2904:  | 		} elseif ($this->implicitThrows) { | 
| 2905:  | 			$methodReturnedType = $scope->getType($methodCall); | 
| 2906:  | 			if (!(new ObjectType(Throwable::class))->isSuperTypeOf($methodReturnedType)->yes()) { | 
| 2907:  | 				return ThrowPoint::createImplicit($scope, $methodCall); | 
| 2908:  | 			} | 
| 2909:  | 		} | 
| 2910:  |  | 
| 2911:  | 		return null; | 
| 2912:  | 	} | 
| 2913:  |  | 
| 2914:  | 	 | 
| 2915:  |  | 
| 2916:  |  | 
| 2917:  | 	private function getAssignedVariables(Expr $expr): array | 
| 2918:  | 	{ | 
| 2919:  | 		if ($expr instanceof Expr\Variable) { | 
| 2920:  | 			if (is_string($expr->name)) { | 
| 2921:  | 				return [$expr->name]; | 
| 2922:  | 			} | 
| 2923:  |  | 
| 2924:  | 			return []; | 
| 2925:  | 		} | 
| 2926:  |  | 
| 2927:  | 		if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { | 
| 2928:  | 			$names = []; | 
| 2929:  | 			foreach ($expr->items as $item) { | 
| 2930:  | 				if ($item === null) { | 
| 2931:  | 					continue; | 
| 2932:  | 				} | 
| 2933:  |  | 
| 2934:  | 				$names = array_merge($names, $this->getAssignedVariables($item->value)); | 
| 2935:  | 			} | 
| 2936:  |  | 
| 2937:  | 			return $names; | 
| 2938:  | 		} | 
| 2939:  |  | 
| 2940:  | 		if ($expr instanceof ArrayDimFetch) { | 
| 2941:  | 			return $this->getAssignedVariables($expr->var); | 
| 2942:  | 		} | 
| 2943:  |  | 
| 2944:  | 		return []; | 
| 2945:  | 	} | 
| 2946:  |  | 
| 2947:  | 	 | 
| 2948:  |  | 
| 2949:  |  | 
| 2950:  | 	private function callNodeCallbackWithExpression( | 
| 2951:  | 		callable $nodeCallback, | 
| 2952:  | 		Expr $expr, | 
| 2953:  | 		MutatingScope $scope, | 
| 2954:  | 		ExpressionContext $context, | 
| 2955:  | 	): void | 
| 2956:  | 	{ | 
| 2957:  | 		if ($context->isDeep()) { | 
| 2958:  | 			$scope = $scope->exitFirstLevelStatements(); | 
| 2959:  | 		} | 
| 2960:  | 		$nodeCallback($expr, $scope); | 
| 2961:  | 	} | 
| 2962:  |  | 
| 2963:  | 	 | 
| 2964:  |  | 
| 2965:  |  | 
| 2966:  | 	private function processClosureNode( | 
| 2967:  | 		Expr\Closure $expr, | 
| 2968:  | 		MutatingScope $scope, | 
| 2969:  | 		callable $nodeCallback, | 
| 2970:  | 		ExpressionContext $context, | 
| 2971:  | 		?Type $passedToType, | 
| 2972:  | 	): ExpressionResult | 
| 2973:  | 	{ | 
| 2974:  | 		foreach ($expr->params as $param) { | 
| 2975:  | 			$this->processParamNode($param, $scope, $nodeCallback); | 
| 2976:  | 		} | 
| 2977:  |  | 
| 2978:  | 		$byRefUses = []; | 
| 2979:  |  | 
| 2980:  | 		$callableParameters = null; | 
| 2981:  | 		$closureCallArgs = $expr->getAttribute(ClosureArgVisitor::ATTRIBUTE_NAME); | 
| 2982:  |  | 
| 2983:  | 		if ($closureCallArgs !== null) { | 
| 2984:  | 			$acceptors = $scope->getType($expr)->getCallableParametersAcceptors($scope); | 
| 2985:  | 			if (count($acceptors) === 1) { | 
| 2986:  | 				$callableParameters = $acceptors[0]->getParameters(); | 
| 2987:  |  | 
| 2988:  | 				foreach ($callableParameters as $index => $callableParameter) { | 
| 2989:  | 					if (!isset($closureCallArgs[$index])) { | 
| 2990:  | 						continue; | 
| 2991:  | 					} | 
| 2992:  |  | 
| 2993:  | 					$type = $scope->getType($closureCallArgs[$index]->value); | 
| 2994:  | 					$callableParameters[$index] = new NativeParameterReflection( | 
| 2995:  | 						$callableParameter->getName(), | 
| 2996:  | 						$callableParameter->isOptional(), | 
| 2997:  | 						$type, | 
| 2998:  | 						$callableParameter->passedByReference(), | 
| 2999:  | 						$callableParameter->isVariadic(), | 
| 3000:  | 						$callableParameter->getDefaultValue(), | 
| 3001:  | 					); | 
| 3002:  | 				} | 
| 3003:  | 			} | 
| 3004:  | 		} elseif ($passedToType !== null && !$passedToType->isCallable()->no()) { | 
| 3005:  | 			if ($passedToType instanceof UnionType) { | 
| 3006:  | 				$passedToType = TypeCombinator::union(...array_filter( | 
| 3007:  | 					$passedToType->getTypes(), | 
| 3008:  | 					static fn (Type $type) => $type->isCallable()->yes(), | 
| 3009:  | 				)); | 
| 3010:  | 			} | 
| 3011:  |  | 
| 3012:  | 			$acceptors = $passedToType->getCallableParametersAcceptors($scope); | 
| 3013:  | 			if (count($acceptors) === 1) { | 
| 3014:  | 				$callableParameters = $acceptors[0]->getParameters(); | 
| 3015:  | 			} | 
| 3016:  | 		} | 
| 3017:  |  | 
| 3018:  | 		$useScope = $scope; | 
| 3019:  | 		foreach ($expr->uses as $use) { | 
| 3020:  | 			if ($use->byRef) { | 
| 3021:  | 				$byRefUses[] = $use; | 
| 3022:  | 				$useScope = $useScope->enterExpressionAssign($use->var); | 
| 3023:  |  | 
| 3024:  | 				$inAssignRightSideVariableName = $context->getInAssignRightSideVariableName(); | 
| 3025:  | 				$inAssignRightSideType = $context->getInAssignRightSideType(); | 
| 3026:  | 				if ( | 
| 3027:  | 					$inAssignRightSideVariableName === $use->var->name | 
| 3028:  | 					&& $inAssignRightSideType !== null | 
| 3029:  | 				) { | 
| 3030:  | 					if ($inAssignRightSideType instanceof ClosureType) { | 
| 3031:  | 						$variableType = $inAssignRightSideType; | 
| 3032:  | 					} else { | 
| 3033:  | 						$alreadyHasVariableType = $scope->hasVariableType($inAssignRightSideVariableName); | 
| 3034:  | 						if ($alreadyHasVariableType->no()) { | 
| 3035:  | 							$variableType = TypeCombinator::union(new NullType(), $inAssignRightSideType); | 
| 3036:  | 						} else { | 
| 3037:  | 							$variableType = TypeCombinator::union($scope->getVariableType($inAssignRightSideVariableName), $inAssignRightSideType); | 
| 3038:  | 						} | 
| 3039:  | 					} | 
| 3040:  | 					$scope = $scope->assignVariable($inAssignRightSideVariableName, $variableType); | 
| 3041:  | 				} | 
| 3042:  | 			} | 
| 3043:  | 			$this->processExprNode($use, $useScope, $nodeCallback, $context); | 
| 3044:  | 			if (!$use->byRef) { | 
| 3045:  | 				continue; | 
| 3046:  | 			} | 
| 3047:  |  | 
| 3048:  | 			$useScope = $useScope->exitExpressionAssign($use->var); | 
| 3049:  | 		} | 
| 3050:  |  | 
| 3051:  | 		if ($expr->returnType !== null) { | 
| 3052:  | 			$nodeCallback($expr->returnType, $scope); | 
| 3053:  | 		} | 
| 3054:  |  | 
| 3055:  | 		$closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); | 
| 3056:  | 		$closureScope = $closureScope->processClosureScope($scope, null, $byRefUses); | 
| 3057:  | 		$closureType = $closureScope->getAnonymousFunctionReflection(); | 
| 3058:  | 		if (!$closureType instanceof ClosureType) { | 
| 3059:  | 			throw new ShouldNotHappenException(); | 
| 3060:  | 		} | 
| 3061:  |  | 
| 3062:  | 		$nodeCallback(new InClosureNode($closureType, $expr), $closureScope); | 
| 3063:  |  | 
| 3064:  | 		$gatheredReturnStatements = []; | 
| 3065:  | 		$gatheredYieldStatements = []; | 
| 3066:  | 		$closureStmtsCallback = static function (Node $node, Scope $scope) use ($nodeCallback, &$gatheredReturnStatements, &$gatheredYieldStatements, &$closureScope): void { | 
| 3067:  | 			$nodeCallback($node, $scope); | 
| 3068:  | 			if ($scope->getAnonymousFunctionReflection() !== $closureScope->getAnonymousFunctionReflection()) { | 
| 3069:  | 				return; | 
| 3070:  | 			} | 
| 3071:  | 			if ($node instanceof Expr\Yield_ || $node instanceof Expr\YieldFrom) { | 
| 3072:  | 				$gatheredYieldStatements[] = $node; | 
| 3073:  | 			} | 
| 3074:  | 			if (!$node instanceof Return_) { | 
| 3075:  | 				return; | 
| 3076:  | 			} | 
| 3077:  |  | 
| 3078:  | 			$gatheredReturnStatements[] = new ReturnStatement($scope, $node); | 
| 3079:  | 		}; | 
| 3080:  | 		if (count($byRefUses) === 0) { | 
| 3081:  | 			$statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback); | 
| 3082:  | 			$nodeCallback(new ClosureReturnStatementsNode( | 
| 3083:  | 				$expr, | 
| 3084:  | 				$gatheredReturnStatements, | 
| 3085:  | 				$gatheredYieldStatements, | 
| 3086:  | 				$statementResult, | 
| 3087:  | 			), $closureScope); | 
| 3088:  |  | 
| 3089:  | 			return new ExpressionResult($scope, false, []); | 
| 3090:  | 		} | 
| 3091:  |  | 
| 3092:  | 		$count = 0; | 
| 3093:  | 		do { | 
| 3094:  | 			$prevScope = $closureScope; | 
| 3095:  |  | 
| 3096:  | 			$intermediaryClosureScopeResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, static function (): void { | 
| 3097:  | 			}); | 
| 3098:  | 			$intermediaryClosureScope = $intermediaryClosureScopeResult->getScope(); | 
| 3099:  | 			foreach ($intermediaryClosureScopeResult->getExitPoints() as $exitPoint) { | 
| 3100:  | 				$intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope()); | 
| 3101:  | 			} | 
| 3102:  | 			$closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); | 
| 3103:  | 			$closureScope = $closureScope->processClosureScope($intermediaryClosureScope, $prevScope, $byRefUses); | 
| 3104:  | 			if ($closureScope->equals($prevScope)) { | 
| 3105:  | 				break; | 
| 3106:  | 			} | 
| 3107:  | 			if ($count >= self::GENERALIZE_AFTER_ITERATION) { | 
| 3108:  | 				$closureScope = $prevScope->generalizeWith($closureScope); | 
| 3109:  | 			} | 
| 3110:  | 			$count++; | 
| 3111:  | 		} while ($count < self::LOOP_SCOPE_ITERATIONS); | 
| 3112:  |  | 
| 3113:  | 		$statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback); | 
| 3114:  | 		$nodeCallback(new ClosureReturnStatementsNode( | 
| 3115:  | 			$expr, | 
| 3116:  | 			$gatheredReturnStatements, | 
| 3117:  | 			$gatheredYieldStatements, | 
| 3118:  | 			$statementResult, | 
| 3119:  | 		), $closureScope); | 
| 3120:  |  | 
| 3121:  | 		return new ExpressionResult($scope->processClosureScope($closureScope, null, $byRefUses), false, []); | 
| 3122:  | 	} | 
| 3123:  |  | 
| 3124:  | 	 | 
| 3125:  |  | 
| 3126:  |  | 
| 3127:  | 	private function processArrowFunctionNode( | 
| 3128:  | 		Expr\ArrowFunction $expr, | 
| 3129:  | 		MutatingScope $scope, | 
| 3130:  | 		callable $nodeCallback, | 
| 3131:  | 		ExpressionContext $context, | 
| 3132:  | 		?Type $passedToType, | 
| 3133:  | 	): ExpressionResult | 
| 3134:  | 	{ | 
| 3135:  | 		foreach ($expr->params as $param) { | 
| 3136:  | 			$this->processParamNode($param, $scope, $nodeCallback); | 
| 3137:  | 		} | 
| 3138:  | 		if ($expr->returnType !== null) { | 
| 3139:  | 			$nodeCallback($expr->returnType, $scope); | 
| 3140:  | 		} | 
| 3141:  |  | 
| 3142:  | 		$callableParameters = null; | 
| 3143:  | 		$arrowFunctionCallArgs = $expr->getAttribute(ArrowFunctionArgVisitor::ATTRIBUTE_NAME); | 
| 3144:  |  | 
| 3145:  | 		if ($arrowFunctionCallArgs !== null) { | 
| 3146:  | 			$acceptors = $scope->getType($expr)->getCallableParametersAcceptors($scope); | 
| 3147:  | 			if (count($acceptors) === 1) { | 
| 3148:  | 				$callableParameters = $acceptors[0]->getParameters(); | 
| 3149:  |  | 
| 3150:  | 				foreach ($callableParameters as $index => $callableParameter) { | 
| 3151:  | 					if (!isset($arrowFunctionCallArgs[$index])) { | 
| 3152:  | 						continue; | 
| 3153:  | 					} | 
| 3154:  |  | 
| 3155:  | 					$type = $scope->getType($arrowFunctionCallArgs[$index]->value); | 
| 3156:  | 					$callableParameters[$index] = new NativeParameterReflection( | 
| 3157:  | 						$callableParameter->getName(), | 
| 3158:  | 						$callableParameter->isOptional(), | 
| 3159:  | 						$type, | 
| 3160:  | 						$callableParameter->passedByReference(), | 
| 3161:  | 						$callableParameter->isVariadic(), | 
| 3162:  | 						$callableParameter->getDefaultValue(), | 
| 3163:  | 					); | 
| 3164:  | 				} | 
| 3165:  | 			} | 
| 3166:  | 		} elseif ($passedToType !== null && !$passedToType->isCallable()->no()) { | 
| 3167:  | 			if ($passedToType instanceof UnionType) { | 
| 3168:  | 				$passedToType = TypeCombinator::union(...array_filter( | 
| 3169:  | 					$passedToType->getTypes(), | 
| 3170:  | 					static fn (Type $type) => $type->isCallable()->yes(), | 
| 3171:  | 				)); | 
| 3172:  | 			} | 
| 3173:  |  | 
| 3174:  | 			$acceptors = $passedToType->getCallableParametersAcceptors($scope); | 
| 3175:  | 			if (count($acceptors) === 1) { | 
| 3176:  | 				$callableParameters = $acceptors[0]->getParameters(); | 
| 3177:  | 			} | 
| 3178:  | 		} | 
| 3179:  |  | 
| 3180:  | 		$arrowFunctionScope = $scope->enterArrowFunction($expr, $callableParameters); | 
| 3181:  | 		$nodeCallback(new InArrowFunctionNode($expr), $arrowFunctionScope); | 
| 3182:  | 		$this->processExprNode($expr->expr, $arrowFunctionScope, $nodeCallback, ExpressionContext::createTopLevel()); | 
| 3183:  |  | 
| 3184:  | 		return new ExpressionResult($scope, false, []); | 
| 3185:  | 	} | 
| 3186:  |  | 
| 3187:  | 	 | 
| 3188:  |  | 
| 3189:  |  | 
| 3190:  | 	private function processParamNode( | 
| 3191:  | 		Node\Param $param, | 
| 3192:  | 		MutatingScope $scope, | 
| 3193:  | 		callable $nodeCallback, | 
| 3194:  | 	): void | 
| 3195:  | 	{ | 
| 3196:  | 		$this->processAttributeGroups($param->attrGroups, $scope, $nodeCallback); | 
| 3197:  | 		$nodeCallback($param, $scope); | 
| 3198:  | 		if ($param->type !== null) { | 
| 3199:  | 			$nodeCallback($param->type, $scope); | 
| 3200:  | 		} | 
| 3201:  | 		if ($param->default === null) { | 
| 3202:  | 			return; | 
| 3203:  | 		} | 
| 3204:  |  | 
| 3205:  | 		$this->processExprNode($param->default, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 3206:  | 	} | 
| 3207:  |  | 
| 3208:  | 	 | 
| 3209:  |  | 
| 3210:  |  | 
| 3211:  |  | 
| 3212:  | 	private function processAttributeGroups( | 
| 3213:  | 		array $attrGroups, | 
| 3214:  | 		MutatingScope $scope, | 
| 3215:  | 		callable $nodeCallback, | 
| 3216:  | 	): void | 
| 3217:  | 	{ | 
| 3218:  | 		foreach ($attrGroups as $attrGroup) { | 
| 3219:  | 			foreach ($attrGroup->attrs as $attr) { | 
| 3220:  | 				foreach ($attr->args as $arg) { | 
| 3221:  | 					$this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); | 
| 3222:  | 					$nodeCallback($arg, $scope); | 
| 3223:  | 				} | 
| 3224:  | 				$nodeCallback($attr, $scope); | 
| 3225:  | 			} | 
| 3226:  | 			$nodeCallback($attrGroup, $scope); | 
| 3227:  | 		} | 
| 3228:  | 	} | 
| 3229:  |  | 
| 3230:  | 	 | 
| 3231:  |  | 
| 3232:  |  | 
| 3233:  |  | 
| 3234:  |  | 
| 3235:  | 	private function processArgs( | 
| 3236:  | 		$calleeReflection, | 
| 3237:  | 		?ParametersAcceptor $parametersAcceptor, | 
| 3238:  | 		array $args, | 
| 3239:  | 		MutatingScope $scope, | 
| 3240:  | 		callable $nodeCallback, | 
| 3241:  | 		ExpressionContext $context, | 
| 3242:  | 		?MutatingScope $closureBindScope = null, | 
| 3243:  | 	): ExpressionResult | 
| 3244:  | 	{ | 
| 3245:  | 		if ($parametersAcceptor !== null) { | 
| 3246:  | 			$parameters = $parametersAcceptor->getParameters(); | 
| 3247:  | 		} | 
| 3248:  |  | 
| 3249:  | 		if ($calleeReflection !== null) { | 
| 3250:  | 			$scope = $scope->pushInFunctionCall($calleeReflection); | 
| 3251:  | 		} | 
| 3252:  |  | 
| 3253:  | 		$hasYield = false; | 
| 3254:  | 		$throwPoints = []; | 
| 3255:  | 		foreach ($args as $i => $arg) { | 
| 3256:  | 			$nodeCallback($arg, $scope); | 
| 3257:  | 			if (isset($parameters) && $parametersAcceptor !== null) { | 
| 3258:  | 				$assignByReference = false; | 
| 3259:  | 				if (isset($parameters[$i])) { | 
| 3260:  | 					$assignByReference = $parameters[$i]->passedByReference()->createsNewVariable(); | 
| 3261:  | 					$parameterType = $parameters[$i]->getType(); | 
| 3262:  | 				} elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) { | 
| 3263:  | 					$lastParameter = $parameters[count($parameters) - 1]; | 
| 3264:  | 					$assignByReference = $lastParameter->passedByReference()->createsNewVariable(); | 
| 3265:  | 					$parameterType = $lastParameter->getType(); | 
| 3266:  | 				} | 
| 3267:  |  | 
| 3268:  | 				if ($assignByReference) { | 
| 3269:  | 					$argValue = $arg->value; | 
| 3270:  | 					if ($argValue instanceof Variable && is_string($argValue->name)) { | 
| 3271:  | 						$scope = $scope->assignVariable($argValue->name, new MixedType()); | 
| 3272:  | 					} | 
| 3273:  | 				} | 
| 3274:  | 			} | 
| 3275:  |  | 
| 3276:  | 			$originalScope = $scope; | 
| 3277:  | 			$scopeToPass = $scope; | 
| 3278:  | 			if ($i === 0 && $closureBindScope !== null) { | 
| 3279:  | 				$scopeToPass = $closureBindScope; | 
| 3280:  | 			} | 
| 3281:  |  | 
| 3282:  | 			if ($arg->value instanceof Expr\Closure) { | 
| 3283:  | 				$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context); | 
| 3284:  | 				$result = $this->processClosureNode($arg->value, $scopeToPass, $nodeCallback, $context, $parameterType ?? null); | 
| 3285:  | 			} elseif ($arg->value instanceof Expr\ArrowFunction) { | 
| 3286:  | 				$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context); | 
| 3287:  | 				$result = $this->processArrowFunctionNode($arg->value, $scopeToPass, $nodeCallback, $context, $parameterType ?? null); | 
| 3288:  | 			} else { | 
| 3289:  | 				$result = $this->processExprNode($arg->value, $scopeToPass, $nodeCallback, $context->enterDeep()); | 
| 3290:  | 			} | 
| 3291:  | 			$scope = $result->getScope(); | 
| 3292:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 3293:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3294:  | 			if ($i !== 0 || $closureBindScope === null) { | 
| 3295:  | 				continue; | 
| 3296:  | 			} | 
| 3297:  |  | 
| 3298:  | 			$scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope); | 
| 3299:  | 		} | 
| 3300:  |  | 
| 3301:  | 		if ($calleeReflection !== null) { | 
| 3302:  | 			$scope = $scope->popInFunctionCall(); | 
| 3303:  | 		} | 
| 3304:  |  | 
| 3305:  | 		return new ExpressionResult($scope, $hasYield, $throwPoints); | 
| 3306:  | 	} | 
| 3307:  |  | 
| 3308:  | 	 | 
| 3309:  |  | 
| 3310:  |  | 
| 3311:  |  | 
| 3312:  | 	private function processAssignVar( | 
| 3313:  | 		MutatingScope $scope, | 
| 3314:  | 		Expr $var, | 
| 3315:  | 		Expr $assignedExpr, | 
| 3316:  | 		callable $nodeCallback, | 
| 3317:  | 		ExpressionContext $context, | 
| 3318:  | 		Closure $processExprCallback, | 
| 3319:  | 		bool $enterExpressionAssign, | 
| 3320:  | 	): ExpressionResult | 
| 3321:  | 	{ | 
| 3322:  | 		$nodeCallback($var, $enterExpressionAssign ? $scope->enterExpressionAssign($var) : $scope); | 
| 3323:  | 		$hasYield = false; | 
| 3324:  | 		$throwPoints = []; | 
| 3325:  | 		$isAssignOp = $assignedExpr instanceof Expr\AssignOp && !$enterExpressionAssign; | 
| 3326:  | 		if ($var instanceof Variable && is_string($var->name)) { | 
| 3327:  | 			$result = $processExprCallback($scope); | 
| 3328:  | 			$hasYield = $result->hasYield(); | 
| 3329:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3330:  | 			$assignedExpr = $this->unwrapAssign($assignedExpr); | 
| 3331:  | 			$type = $scope->getType($assignedExpr); | 
| 3332:  | 			$truthySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createTruthy()); | 
| 3333:  | 			$falseySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createFalsey()); | 
| 3334:  |  | 
| 3335:  | 			$conditionalExpressions = []; | 
| 3336:  |  | 
| 3337:  | 			$truthyType = TypeCombinator::remove($type, StaticTypeFactory::falsey()); | 
| 3338:  | 			$falseyType = TypeCombinator::intersect($type, StaticTypeFactory::falsey()); | 
| 3339:  |  | 
| 3340:  | 			$conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType); | 
| 3341:  | 			$conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $truthySpecifiedTypes, $truthyType); | 
| 3342:  | 			$conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); | 
| 3343:  | 			$conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); | 
| 3344:  |  | 
| 3345:  | 			$scope = $result->getScope()->assignVariable($var->name, $type); | 
| 3346:  | 			foreach ($conditionalExpressions as $exprString => $holders) { | 
| 3347:  | 				$scope = $scope->addConditionalExpressions($exprString, $holders); | 
| 3348:  | 			} | 
| 3349:  | 		} elseif ($var instanceof ArrayDimFetch) { | 
| 3350:  | 			$dimExprStack = []; | 
| 3351:  | 			$originalVar = $var; | 
| 3352:  | 			$assignedPropertyExpr = $assignedExpr; | 
| 3353:  | 			while ($var instanceof ArrayDimFetch) { | 
| 3354:  | 				$varForSetOffsetValue = $var->var; | 
| 3355:  | 				if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) { | 
| 3356:  | 					$varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue); | 
| 3357:  | 				} | 
| 3358:  | 				$assignedPropertyExpr = new SetOffsetValueTypeExpr( | 
| 3359:  | 					$varForSetOffsetValue, | 
| 3360:  | 					$var->dim, | 
| 3361:  | 					$assignedPropertyExpr, | 
| 3362:  | 				); | 
| 3363:  | 				$dimExprStack[] = $var->dim; | 
| 3364:  | 				$var = $var->var; | 
| 3365:  | 			} | 
| 3366:  |  | 
| 3367:  | 			 | 
| 3368:  | 			if ($enterExpressionAssign) { | 
| 3369:  | 				$scope = $scope->enterExpressionAssign($var); | 
| 3370:  | 			} | 
| 3371:  | 			$result = $this->processExprNode($var, $scope, $nodeCallback, $context->enterDeep()); | 
| 3372:  | 			$hasYield = $result->hasYield(); | 
| 3373:  | 			$throwPoints = $result->getThrowPoints(); | 
| 3374:  | 			$scope = $result->getScope(); | 
| 3375:  | 			if ($enterExpressionAssign) { | 
| 3376:  | 				$scope = $scope->exitExpressionAssign($var); | 
| 3377:  | 			} | 
| 3378:  |  | 
| 3379:  | 			 | 
| 3380:  | 			$offsetTypes = []; | 
| 3381:  | 			foreach (array_reverse($dimExprStack) as $dimExpr) { | 
| 3382:  | 				if ($dimExpr === null) { | 
| 3383:  | 					$offsetTypes[] = null; | 
| 3384:  |  | 
| 3385:  | 				} else { | 
| 3386:  | 					$offsetTypes[] = $scope->getType($dimExpr); | 
| 3387:  |  | 
| 3388:  | 					if ($enterExpressionAssign) { | 
| 3389:  | 						$scope->enterExpressionAssign($dimExpr); | 
| 3390:  | 					} | 
| 3391:  | 					$result = $this->processExprNode($dimExpr, $scope, $nodeCallback, $context->enterDeep()); | 
| 3392:  | 					$hasYield = $hasYield || $result->hasYield(); | 
| 3393:  | 					$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3394:  | 					$scope = $result->getScope(); | 
| 3395:  |  | 
| 3396:  | 					if ($enterExpressionAssign) { | 
| 3397:  | 						$scope = $scope->exitExpressionAssign($dimExpr); | 
| 3398:  | 					} | 
| 3399:  | 				} | 
| 3400:  | 			} | 
| 3401:  |  | 
| 3402:  | 			$valueToWrite = $scope->getType($assignedExpr); | 
| 3403:  | 			$originalValueToWrite = $valueToWrite; | 
| 3404:  |  | 
| 3405:  | 			 | 
| 3406:  | 			$result = $processExprCallback($scope); | 
| 3407:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 3408:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3409:  | 			$scope = $result->getScope(); | 
| 3410:  |  | 
| 3411:  | 			$varType = $scope->getType($var); | 
| 3412:  |  | 
| 3413:  | 			 | 
| 3414:  | 			if ($varType instanceof ErrorType) { | 
| 3415:  | 				$varType = new ConstantArrayType([], []); | 
| 3416:  | 			} | 
| 3417:  | 			$offsetValueType = $varType; | 
| 3418:  | 			$offsetValueTypeStack = [$offsetValueType]; | 
| 3419:  | 			foreach (array_slice($offsetTypes, 0, -1) as $offsetType) { | 
| 3420:  | 				if ($offsetType === null) { | 
| 3421:  | 					$offsetValueType = new ConstantArrayType([], []); | 
| 3422:  |  | 
| 3423:  | 				} else { | 
| 3424:  | 					$offsetValueType = $offsetValueType->getOffsetValueType($offsetType); | 
| 3425:  | 					if ($offsetValueType instanceof ErrorType) { | 
| 3426:  | 						$offsetValueType = new ConstantArrayType([], []); | 
| 3427:  | 					} | 
| 3428:  | 				} | 
| 3429:  |  | 
| 3430:  | 				$offsetValueTypeStack[] = $offsetValueType; | 
| 3431:  | 			} | 
| 3432:  |  | 
| 3433:  | 			foreach (array_reverse($offsetTypes) as $i => $offsetType) { | 
| 3434:  | 				 | 
| 3435:  | 				$offsetValueType = array_pop($offsetValueTypeStack); | 
| 3436:  | 				if (!$offsetValueType instanceof MixedType) { | 
| 3437:  | 					$types = [ | 
| 3438:  | 						new ArrayType(new MixedType(), new MixedType()), | 
| 3439:  | 						new ObjectType(ArrayAccess::class), | 
| 3440:  | 						new NullType(), | 
| 3441:  | 					]; | 
| 3442:  | 					if ($offsetType !== null && (new IntegerType())->isSuperTypeOf($offsetType)->yes()) { | 
| 3443:  | 						$types[] = new StringType(); | 
| 3444:  | 					} | 
| 3445:  | 					$offsetValueType = TypeCombinator::intersect($offsetValueType, TypeCombinator::union(...$types)); | 
| 3446:  | 				} | 
| 3447:  | 				$valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0); | 
| 3448:  | 			} | 
| 3449:  |  | 
| 3450:  | 			if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { | 
| 3451:  | 				if ($var instanceof Variable && is_string($var->name)) { | 
| 3452:  | 					$scope = $scope->assignVariable($var->name, $valueToWrite); | 
| 3453:  | 				} else { | 
| 3454:  | 					if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { | 
| 3455:  | 						$nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); | 
| 3456:  | 					} | 
| 3457:  | 					$scope = $scope->assignExpression( | 
| 3458:  | 						$var, | 
| 3459:  | 						$valueToWrite, | 
| 3460:  | 					); | 
| 3461:  | 				} | 
| 3462:  |  | 
| 3463:  | 				if ($originalVar->dim instanceof Variable || $originalVar->dim instanceof Node\Scalar) { | 
| 3464:  | 					$currentVarType = $scope->getType($originalVar); | 
| 3465:  | 					if (!$originalValueToWrite->isSuperTypeOf($currentVarType)->yes()) { | 
| 3466:  | 						$scope = $scope->assignExpression( | 
| 3467:  | 							$originalVar, | 
| 3468:  | 							$originalValueToWrite, | 
| 3469:  | 						); | 
| 3470:  | 					} | 
| 3471:  | 				} | 
| 3472:  | 			} else { | 
| 3473:  | 				if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { | 
| 3474:  | 					$nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); | 
| 3475:  | 				} | 
| 3476:  | 			} | 
| 3477:  |  | 
| 3478:  | 			if (!$varType->isArray()->yes() && !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->no()) { | 
| 3479:  | 				$throwPoints = array_merge($throwPoints, $this->processExprNode( | 
| 3480:  | 					new MethodCall($var, 'offsetSet'), | 
| 3481:  | 					$scope, | 
| 3482:  | 					static function (): void { | 
| 3483:  | 					}, | 
| 3484:  | 					$context, | 
| 3485:  | 				)->getThrowPoints()); | 
| 3486:  | 			} | 
| 3487:  | 		} elseif ($var instanceof PropertyFetch) { | 
| 3488:  | 			$objectResult = $this->processExprNode($var->var, $scope, $nodeCallback, $context); | 
| 3489:  | 			$hasYield = $objectResult->hasYield(); | 
| 3490:  | 			$throwPoints = $objectResult->getThrowPoints(); | 
| 3491:  | 			$scope = $objectResult->getScope(); | 
| 3492:  |  | 
| 3493:  | 			$propertyName = null; | 
| 3494:  | 			if ($var->name instanceof Node\Identifier) { | 
| 3495:  | 				$propertyName = $var->name->name; | 
| 3496:  | 			} else { | 
| 3497:  | 				$propertyNameResult = $this->processExprNode($var->name, $scope, $nodeCallback, $context); | 
| 3498:  | 				$hasYield = $hasYield || $propertyNameResult->hasYield(); | 
| 3499:  | 				$throwPoints = array_merge($throwPoints, $propertyNameResult->getThrowPoints()); | 
| 3500:  | 				$scope = $propertyNameResult->getScope(); | 
| 3501:  | 			} | 
| 3502:  |  | 
| 3503:  | 			$result = $processExprCallback($scope); | 
| 3504:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 3505:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3506:  | 			$scope = $result->getScope(); | 
| 3507:  |  | 
| 3508:  | 			$propertyHolderType = $scope->getType($var->var); | 
| 3509:  | 			if ($propertyName !== null && $propertyHolderType->hasProperty($propertyName)->yes()) { | 
| 3510:  | 				$propertyReflection = $propertyHolderType->getProperty($propertyName, $scope); | 
| 3511:  | 				$assignedExprType = $scope->getType($assignedExpr); | 
| 3512:  | 				$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); | 
| 3513:  | 				if ($propertyReflection->canChangeTypeAfterAssignment()) { | 
| 3514:  | 					$scope = $scope->assignExpression($var, $assignedExprType); | 
| 3515:  | 				} | 
| 3516:  | 				$declaringClass = $propertyReflection->getDeclaringClass(); | 
| 3517:  | 				if ( | 
| 3518:  | 					$declaringClass->hasNativeProperty($propertyName) | 
| 3519:  | 					&& !$declaringClass->getNativeProperty($propertyName)->getNativeType()->accepts($assignedExprType, true)->yes() | 
| 3520:  | 				) { | 
| 3521:  | 					$throwPoints[] = ThrowPoint::createExplicit($scope, new ObjectType(TypeError::class), $assignedExpr, false); | 
| 3522:  | 				} | 
| 3523:  | 			} else { | 
| 3524:  | 				 | 
| 3525:  | 				$assignedExprType = $scope->getType($assignedExpr); | 
| 3526:  | 				$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); | 
| 3527:  | 				$scope = $scope->assignExpression($var, $assignedExprType); | 
| 3528:  | 				 | 
| 3529:  | 				if (!$propertyHolderType->hasMethod('__set')->no()) { | 
| 3530:  | 					$throwPoints = array_merge($throwPoints, $this->processExprNode( | 
| 3531:  | 						new MethodCall($var->var, '__set'), | 
| 3532:  | 						$scope, | 
| 3533:  | 						static function (): void { | 
| 3534:  | 						}, | 
| 3535:  | 						$context, | 
| 3536:  | 					)->getThrowPoints()); | 
| 3537:  | 				} | 
| 3538:  | 			} | 
| 3539:  |  | 
| 3540:  | 		} elseif ($var instanceof Expr\StaticPropertyFetch) { | 
| 3541:  | 			if ($var->class instanceof Node\Name) { | 
| 3542:  | 				$propertyHolderType = $scope->resolveTypeByName($var->class); | 
| 3543:  | 			} else { | 
| 3544:  | 				$this->processExprNode($var->class, $scope, $nodeCallback, $context); | 
| 3545:  | 				$propertyHolderType = $scope->getType($var->class); | 
| 3546:  | 			} | 
| 3547:  |  | 
| 3548:  | 			$propertyName = null; | 
| 3549:  | 			if ($var->name instanceof Node\Identifier) { | 
| 3550:  | 				$propertyName = $var->name->name; | 
| 3551:  | 				$hasYield = false; | 
| 3552:  | 				$throwPoints = []; | 
| 3553:  | 			} else { | 
| 3554:  | 				$propertyNameResult = $this->processExprNode($var->name, $scope, $nodeCallback, $context); | 
| 3555:  | 				$hasYield = $propertyNameResult->hasYield(); | 
| 3556:  | 				$throwPoints = $propertyNameResult->getThrowPoints(); | 
| 3557:  | 				$scope = $propertyNameResult->getScope(); | 
| 3558:  | 			} | 
| 3559:  |  | 
| 3560:  | 			$result = $processExprCallback($scope); | 
| 3561:  | 			$hasYield = $hasYield || $result->hasYield(); | 
| 3562:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3563:  | 			$scope = $result->getScope(); | 
| 3564:  |  | 
| 3565:  | 			if ($propertyName !== null) { | 
| 3566:  | 				$propertyReflection = $scope->getPropertyReflection($propertyHolderType, $propertyName); | 
| 3567:  | 				$assignedExprType = $scope->getType($assignedExpr); | 
| 3568:  | 				$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); | 
| 3569:  | 				if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { | 
| 3570:  | 					$scope = $scope->assignExpression($var, $assignedExprType); | 
| 3571:  | 				} | 
| 3572:  | 			} else { | 
| 3573:  | 				 | 
| 3574:  | 				$assignedExprType = $scope->getType($assignedExpr); | 
| 3575:  | 				$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); | 
| 3576:  | 				$scope = $scope->assignExpression($var, $assignedExprType); | 
| 3577:  | 			} | 
| 3578:  | 		} elseif ($var instanceof List_ || $var instanceof Array_) { | 
| 3579:  | 			$result = $processExprCallback($scope); | 
| 3580:  | 			$hasYield = $result->hasYield(); | 
| 3581:  | 			$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3582:  | 			$scope = $result->getScope(); | 
| 3583:  | 			foreach ($var->items as $i => $arrayItem) { | 
| 3584:  | 				if ($arrayItem === null) { | 
| 3585:  | 					continue; | 
| 3586:  | 				} | 
| 3587:  |  | 
| 3588:  | 				$itemScope = $scope; | 
| 3589:  | 				if ($enterExpressionAssign) { | 
| 3590:  | 					$itemScope = $itemScope->enterExpressionAssign($arrayItem->value); | 
| 3591:  | 				} | 
| 3592:  | 				$itemScope = $this->lookForSetAllowedUndefinedExpressions($itemScope, $arrayItem->value); | 
| 3593:  | 				$itemResult = $this->processExprNode($arrayItem, $itemScope, $nodeCallback, $context->enterDeep()); | 
| 3594:  | 				$hasYield = $hasYield || $itemResult->hasYield(); | 
| 3595:  | 				$throwPoints = array_merge($throwPoints, $itemResult->getThrowPoints()); | 
| 3596:  |  | 
| 3597:  | 				if ($arrayItem->key === null) { | 
| 3598:  | 					$dimExpr = new Node\Scalar\LNumber($i); | 
| 3599:  | 				} else { | 
| 3600:  | 					$dimExpr = $arrayItem->key; | 
| 3601:  | 				} | 
| 3602:  | 				$result = $this->processAssignVar( | 
| 3603:  | 					$scope, | 
| 3604:  | 					$arrayItem->value, | 
| 3605:  | 					new GetOffsetValueTypeExpr($assignedExpr, $dimExpr), | 
| 3606:  | 					$nodeCallback, | 
| 3607:  | 					$context, | 
| 3608:  | 					static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, []), | 
| 3609:  | 					$enterExpressionAssign, | 
| 3610:  | 				); | 
| 3611:  | 				$scope = $result->getScope(); | 
| 3612:  | 				$hasYield = $hasYield || $result->hasYield(); | 
| 3613:  | 				$throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | 
| 3614:  | 			} | 
| 3615:  | 		} | 
| 3616:  |  | 
| 3617:  | 		return new ExpressionResult($scope, $hasYield, $throwPoints); | 
| 3618:  | 	} | 
| 3619:  |  | 
| 3620:  | 	private function unwrapAssign(Expr $expr): Expr | 
| 3621:  | 	{ | 
| 3622:  | 		if ($expr instanceof Assign) { | 
| 3623:  | 			return $this->unwrapAssign($expr->expr); | 
| 3624:  | 		} | 
| 3625:  |  | 
| 3626:  | 		return $expr; | 
| 3627:  | 	} | 
| 3628:  |  | 
| 3629:  | 	 | 
| 3630:  |  | 
| 3631:  |  | 
| 3632:  |  | 
| 3633:  | 	private function processSureTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array | 
| 3634:  | 	{ | 
| 3635:  | 		foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $exprType]) { | 
| 3636:  | 			if (!$expr instanceof Variable) { | 
| 3637:  | 				continue; | 
| 3638:  | 			} | 
| 3639:  | 			if (!is_string($expr->name)) { | 
| 3640:  | 				continue; | 
| 3641:  | 			} | 
| 3642:  |  | 
| 3643:  | 			if (!isset($conditionalExpressions[$exprString])) { | 
| 3644:  | 				$conditionalExpressions[$exprString] = []; | 
| 3645:  | 			} | 
| 3646:  |  | 
| 3647:  | 			$holder = new ConditionalExpressionHolder([ | 
| 3648:  | 				'$' . $variableName => $variableType, | 
| 3649:  | 			], VariableTypeHolder::createYes( | 
| 3650:  | 				TypeCombinator::intersect($scope->getType($expr), $exprType), | 
| 3651:  | 			)); | 
| 3652:  | 			$conditionalExpressions[$exprString][$holder->getKey()] = $holder; | 
| 3653:  | 		} | 
| 3654:  |  | 
| 3655:  | 		return $conditionalExpressions; | 
| 3656:  | 	} | 
| 3657:  |  | 
| 3658:  | 	 | 
| 3659:  |  | 
| 3660:  |  | 
| 3661:  |  | 
| 3662:  | 	private function processSureNotTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array | 
| 3663:  | 	{ | 
| 3664:  | 		foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $exprType]) { | 
| 3665:  | 			if (!$expr instanceof Variable) { | 
| 3666:  | 				continue; | 
| 3667:  | 			} | 
| 3668:  | 			if (!is_string($expr->name)) { | 
| 3669:  | 				continue; | 
| 3670:  | 			} | 
| 3671:  |  | 
| 3672:  | 			if (!isset($conditionalExpressions[$exprString])) { | 
| 3673:  | 				$conditionalExpressions[$exprString] = []; | 
| 3674:  | 			} | 
| 3675:  |  | 
| 3676:  | 			$holder = new ConditionalExpressionHolder([ | 
| 3677:  | 				'$' . $variableName => $variableType, | 
| 3678:  | 			], VariableTypeHolder::createYes( | 
| 3679:  | 				TypeCombinator::remove($scope->getType($expr), $exprType), | 
| 3680:  | 			)); | 
| 3681:  | 			$conditionalExpressions[$exprString][$holder->getKey()] = $holder; | 
| 3682:  | 		} | 
| 3683:  |  | 
| 3684:  | 		return $conditionalExpressions; | 
| 3685:  | 	} | 
| 3686:  |  | 
| 3687:  | 	private function processStmtVarAnnotation(MutatingScope $scope, Node\Stmt $stmt, ?Expr $defaultExpr): MutatingScope | 
| 3688:  | 	{ | 
| 3689:  | 		$function = $scope->getFunction(); | 
| 3690:  | 		$variableLessTags = []; | 
| 3691:  |  | 
| 3692:  | 		foreach ($stmt->getComments() as $comment) { | 
| 3693:  | 			if (!$comment instanceof Doc) { | 
| 3694:  | 				continue; | 
| 3695:  | 			} | 
| 3696:  |  | 
| 3697:  | 			$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | 
| 3698:  | 				$scope->getFile(), | 
| 3699:  | 				$scope->isInClass() ? $scope->getClassReflection()->getName() : null, | 
| 3700:  | 				$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, | 
| 3701:  | 				$function !== null ? $function->getName() : null, | 
| 3702:  | 				$comment->getText(), | 
| 3703:  | 			); | 
| 3704:  |  | 
| 3705:  | 			$assignedVariable = null; | 
| 3706:  | 			if ( | 
| 3707:  | 				$stmt instanceof Node\Stmt\Expression | 
| 3708:  | 				&& ($stmt->expr instanceof Assign || $stmt->expr instanceof AssignRef) | 
| 3709:  | 				&& $stmt->expr->var instanceof Variable | 
| 3710:  | 				&& is_string($stmt->expr->var->name) | 
| 3711:  | 			) { | 
| 3712:  | 				$assignedVariable = $stmt->expr->var->name; | 
| 3713:  | 			} | 
| 3714:  |  | 
| 3715:  | 			foreach ($resolvedPhpDoc->getVarTags() as $name => $varTag) { | 
| 3716:  | 				if (is_int($name)) { | 
| 3717:  | 					$variableLessTags[] = $varTag; | 
| 3718:  | 					continue; | 
| 3719:  | 				} | 
| 3720:  |  | 
| 3721:  | 				if ($name === $assignedVariable) { | 
| 3722:  | 					continue; | 
| 3723:  | 				} | 
| 3724:  |  | 
| 3725:  | 				$certainty = $scope->hasVariableType($name); | 
| 3726:  | 				if ($certainty->no()) { | 
| 3727:  | 					continue; | 
| 3728:  | 				} | 
| 3729:  |  | 
| 3730:  | 				if ($scope->isInClass() && $scope->getFunction() === null) { | 
| 3731:  | 					continue; | 
| 3732:  | 				} | 
| 3733:  |  | 
| 3734:  | 				if ($scope->canAnyVariableExist()) { | 
| 3735:  | 					$certainty = TrinaryLogic::createYes(); | 
| 3736:  | 				} | 
| 3737:  |  | 
| 3738:  | 				$scope = $scope->assignVariable($name, $varTag->getType(), $certainty); | 
| 3739:  | 			} | 
| 3740:  | 		} | 
| 3741:  |  | 
| 3742:  | 		if (count($variableLessTags) === 1 && $defaultExpr !== null) { | 
| 3743:  | 			$scope = $scope->assignExpression($defaultExpr, $variableLessTags[0]->getType()); | 
| 3744:  | 		} | 
| 3745:  |  | 
| 3746:  | 		return $scope; | 
| 3747:  | 	} | 
| 3748:  |  | 
| 3749:  | 	 | 
| 3750:  |  | 
| 3751:  |  | 
| 3752:  | 	private function processVarAnnotation(MutatingScope $scope, array $variableNames, Node $node, bool &$changed = false): MutatingScope | 
| 3753:  | 	{ | 
| 3754:  | 		$function = $scope->getFunction(); | 
| 3755:  | 		$varTags = []; | 
| 3756:  | 		foreach ($node->getComments() as $comment) { | 
| 3757:  | 			if (!$comment instanceof Doc) { | 
| 3758:  | 				continue; | 
| 3759:  | 			} | 
| 3760:  |  | 
| 3761:  | 			$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | 
| 3762:  | 				$scope->getFile(), | 
| 3763:  | 				$scope->isInClass() ? $scope->getClassReflection()->getName() : null, | 
| 3764:  | 				$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, | 
| 3765:  | 				$function !== null ? $function->getName() : null, | 
| 3766:  | 				$comment->getText(), | 
| 3767:  | 			); | 
| 3768:  | 			foreach ($resolvedPhpDoc->getVarTags() as $key => $varTag) { | 
| 3769:  | 				$varTags[$key] = $varTag; | 
| 3770:  | 			} | 
| 3771:  | 		} | 
| 3772:  |  | 
| 3773:  | 		if (count($varTags) === 0) { | 
| 3774:  | 			return $scope; | 
| 3775:  | 		} | 
| 3776:  |  | 
| 3777:  | 		foreach ($variableNames as $variableName) { | 
| 3778:  | 			if (!isset($varTags[$variableName])) { | 
| 3779:  | 				continue; | 
| 3780:  | 			} | 
| 3781:  |  | 
| 3782:  | 			$variableType = $varTags[$variableName]->getType(); | 
| 3783:  | 			$changed = true; | 
| 3784:  | 			$scope = $scope->assignVariable($variableName, $variableType); | 
| 3785:  | 		} | 
| 3786:  |  | 
| 3787:  | 		if (count($variableNames) === 1 && count($varTags) === 1 && isset($varTags[0])) { | 
| 3788:  | 			$variableType = $varTags[0]->getType(); | 
| 3789:  | 			$changed = true; | 
| 3790:  | 			$scope = $scope->assignVariable($variableNames[0], $variableType); | 
| 3791:  | 		} | 
| 3792:  |  | 
| 3793:  | 		return $scope; | 
| 3794:  | 	} | 
| 3795:  |  | 
| 3796:  | 	private function enterForeach(MutatingScope $scope, Foreach_ $stmt): MutatingScope | 
| 3797:  | 	{ | 
| 3798:  | 		if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) { | 
| 3799:  | 			$scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt); | 
| 3800:  | 		} | 
| 3801:  | 		$iterateeType = $scope->getType($stmt->expr); | 
| 3802:  | 		if ( | 
| 3803:  | 			($stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name)) | 
| 3804:  | 			&& ($stmt->keyVar === null || ($stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name))) | 
| 3805:  | 		) { | 
| 3806:  | 			$keyVarName = null; | 
| 3807:  | 			if ($stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name)) { | 
| 3808:  | 				$keyVarName = $stmt->keyVar->name; | 
| 3809:  | 			} | 
| 3810:  | 			$scope = $scope->enterForeach( | 
| 3811:  | 				$stmt->expr, | 
| 3812:  | 				$stmt->valueVar->name, | 
| 3813:  | 				$keyVarName, | 
| 3814:  | 			); | 
| 3815:  | 			$vars = [$stmt->valueVar->name]; | 
| 3816:  | 			if ($keyVarName !== null) { | 
| 3817:  | 				$vars[] = $keyVarName; | 
| 3818:  | 			} | 
| 3819:  | 		} else { | 
| 3820:  | 			$scope = $this->processAssignVar( | 
| 3821:  | 				$scope, | 
| 3822:  | 				$stmt->valueVar, | 
| 3823:  | 				new GetIterableValueTypeExpr($stmt->expr), | 
| 3824:  | 				static function (): void { | 
| 3825:  | 				}, | 
| 3826:  | 				ExpressionContext::createDeep(), | 
| 3827:  | 				static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, []), | 
| 3828:  | 				true, | 
| 3829:  | 			)->getScope(); | 
| 3830:  | 			$vars = $this->getAssignedVariables($stmt->valueVar); | 
| 3831:  | 			if ( | 
| 3832:  | 				$stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name) | 
| 3833:  | 			) { | 
| 3834:  | 				$scope = $scope->enterForeachKey($stmt->expr, $stmt->keyVar->name); | 
| 3835:  | 				$vars[] = $stmt->keyVar->name; | 
| 3836:  | 			} elseif ($stmt->keyVar !== null) { | 
| 3837:  | 				$scope = $this->processAssignVar( | 
| 3838:  | 					$scope, | 
| 3839:  | 					$stmt->keyVar, | 
| 3840:  | 					new GetIterableKeyTypeExpr($stmt->expr), | 
| 3841:  | 					static function (): void { | 
| 3842:  | 					}, | 
| 3843:  | 					ExpressionContext::createDeep(), | 
| 3844:  | 					static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, []), | 
| 3845:  | 					true, | 
| 3846:  | 				)->getScope(); | 
| 3847:  | 				$vars = array_merge($vars, $this->getAssignedVariables($stmt->keyVar)); | 
| 3848:  | 			} | 
| 3849:  | 		} | 
| 3850:  |  | 
| 3851:  | 		if ( | 
| 3852:  | 			$stmt->getDocComment() === null | 
| 3853:  | 			&& $iterateeType instanceof ConstantArrayType | 
| 3854:  | 			&& $stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name) | 
| 3855:  | 			&& $stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name) | 
| 3856:  | 		) { | 
| 3857:  | 			$conditionalHolders = []; | 
| 3858:  | 			foreach ($iterateeType->getKeyTypes() as $i => $keyType) { | 
| 3859:  | 				$valueType = $iterateeType->getValueTypes()[$i]; | 
| 3860:  | 				$holder = new ConditionalExpressionHolder([ | 
| 3861:  | 					'$' . $stmt->keyVar->name => $keyType, | 
| 3862:  | 				], new VariableTypeHolder($valueType, TrinaryLogic::createYes())); | 
| 3863:  | 				$conditionalHolders[$holder->getKey()] = $holder; | 
| 3864:  | 			} | 
| 3865:  |  | 
| 3866:  | 			$scope = $scope->addConditionalExpressions( | 
| 3867:  | 				'$' . $stmt->valueVar->name, | 
| 3868:  | 				$conditionalHolders, | 
| 3869:  | 			); | 
| 3870:  | 		} | 
| 3871:  |  | 
| 3872:  | 		return $this->processVarAnnotation($scope, $vars, $stmt); | 
| 3873:  | 	} | 
| 3874:  |  | 
| 3875:  | 	 | 
| 3876:  |  | 
| 3877:  |  | 
| 3878:  | 	private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classScope, callable $nodeCallback): void | 
| 3879:  | 	{ | 
| 3880:  | 		$parentTraitNames = []; | 
| 3881:  | 		$parent = $classScope->getParentScope(); | 
| 3882:  | 		while ($parent !== null) { | 
| 3883:  | 			if ($parent->isInTrait()) { | 
| 3884:  | 				$parentTraitNames[] = $parent->getTraitReflection()->getName(); | 
| 3885:  | 			} | 
| 3886:  | 			$parent = $parent->getParentScope(); | 
| 3887:  | 		} | 
| 3888:  |  | 
| 3889:  | 		foreach ($node->traits as $trait) { | 
| 3890:  | 			$traitName = (string) $trait; | 
| 3891:  | 			if (in_array($traitName, $parentTraitNames, true)) { | 
| 3892:  | 				continue; | 
| 3893:  | 			} | 
| 3894:  | 			if (!$this->reflectionProvider->hasClass($traitName)) { | 
| 3895:  | 				continue; | 
| 3896:  | 			} | 
| 3897:  | 			$traitReflection = $this->reflectionProvider->getClass($traitName); | 
| 3898:  | 			$traitFileName = $traitReflection->getFileName(); | 
| 3899:  | 			if ($traitFileName === null) { | 
| 3900:  | 				continue;  | 
| 3901:  | 			} | 
| 3902:  | 			$fileName = $this->fileHelper->normalizePath($traitFileName); | 
| 3903:  | 			if (!isset($this->analysedFiles[$fileName])) { | 
| 3904:  | 				continue; | 
| 3905:  | 			} | 
| 3906:  | 			$parserNodes = $this->parser->parseFile($fileName); | 
| 3907:  | 			$this->processNodesForTraitUse($parserNodes, $traitReflection, $classScope, $node->adaptations, $nodeCallback); | 
| 3908:  | 		} | 
| 3909:  | 	} | 
| 3910:  |  | 
| 3911:  | 	 | 
| 3912:  |  | 
| 3913:  |  | 
| 3914:  |  | 
| 3915:  |  | 
| 3916:  | 	private function processNodesForTraitUse($node, ClassReflection $traitReflection, MutatingScope $scope, array $adaptations, callable $nodeCallback): void | 
| 3917:  | 	{ | 
| 3918:  | 		if ($node instanceof Node) { | 
| 3919:  | 			if ($node instanceof Node\Stmt\Trait_ && $traitReflection->getName() === (string) $node->namespacedName && $traitReflection->getNativeReflection()->getStartLine() === $node->getStartLine()) { | 
| 3920:  | 				$methodModifiers = []; | 
| 3921:  | 				foreach ($adaptations as $adaptation) { | 
| 3922:  | 					if (!$adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { | 
| 3923:  | 						continue; | 
| 3924:  | 					} | 
| 3925:  |  | 
| 3926:  | 					if ($adaptation->newModifier === null) { | 
| 3927:  | 						continue; | 
| 3928:  | 					} | 
| 3929:  |  | 
| 3930:  | 					$methodModifiers[$adaptation->method->toLowerString()] = $adaptation->newModifier; | 
| 3931:  | 				} | 
| 3932:  |  | 
| 3933:  | 				$stmts = $node->stmts; | 
| 3934:  | 				foreach ($stmts as $i => $stmt) { | 
| 3935:  | 					if (!$stmt instanceof Node\Stmt\ClassMethod) { | 
| 3936:  | 						continue; | 
| 3937:  | 					} | 
| 3938:  | 					$methodName = $stmt->name->toLowerString(); | 
| 3939:  | 					if (!array_key_exists($methodName, $methodModifiers)) { | 
| 3940:  | 						continue; | 
| 3941:  | 					} | 
| 3942:  |  | 
| 3943:  | 					$methodAst = clone $stmt; | 
| 3944:  | 					$methodAst->flags = ($methodAst->flags & ~ Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK) | $methodModifiers[$methodName]; | 
| 3945:  | 					$stmts[$i] = $methodAst; | 
| 3946:  | 				} | 
| 3947:  | 				$this->processStmtNodes($node, $stmts, $scope->enterTrait($traitReflection), $nodeCallback); | 
| 3948:  | 				return; | 
| 3949:  | 			} | 
| 3950:  | 			if ($node instanceof Node\Stmt\ClassLike) { | 
| 3951:  | 				return; | 
| 3952:  | 			} | 
| 3953:  | 			if ($node instanceof Node\FunctionLike) { | 
| 3954:  | 				return; | 
| 3955:  | 			} | 
| 3956:  | 			foreach ($node->getSubNodeNames() as $subNodeName) { | 
| 3957:  | 				$subNode = $node->{$subNodeName}; | 
| 3958:  | 				$this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); | 
| 3959:  | 			} | 
| 3960:  | 		} elseif (is_array($node)) { | 
| 3961:  | 			foreach ($node as $subNode) { | 
| 3962:  | 				$this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); | 
| 3963:  | 			} | 
| 3964:  | 		} | 
| 3965:  | 	} | 
| 3966:  |  | 
| 3967:  | 	 | 
| 3968:  |  | 
| 3969:  |  | 
| 3970:  | 	public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $node): array | 
| 3971:  | 	{ | 
| 3972:  | 		$templateTypeMap = TemplateTypeMap::createEmpty(); | 
| 3973:  | 		$phpDocParameterTypes = []; | 
| 3974:  | 		$phpDocReturnType = null; | 
| 3975:  | 		$phpDocThrowType = null; | 
| 3976:  | 		$deprecatedDescription = null; | 
| 3977:  | 		$isDeprecated = false; | 
| 3978:  | 		$isInternal = false; | 
| 3979:  | 		$isFinal = false; | 
| 3980:  | 		$isPure = false; | 
| 3981:  | 		$acceptsNamedArguments = true; | 
| 3982:  | 		$isReadOnly = $scope->isInClass() && $scope->getClassReflection()->isImmutable(); | 
| 3983:  | 		$docComment = $node->getDocComment() !== null | 
| 3984:  | 			? $node->getDocComment()->getText() | 
| 3985:  | 			: null; | 
| 3986:  |  | 
| 3987:  | 		$file = $scope->getFile(); | 
| 3988:  | 		$class = $scope->isInClass() ? $scope->getClassReflection()->getName() : null; | 
| 3989:  | 		$trait = $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null; | 
| 3990:  | 		$resolvedPhpDoc = null; | 
| 3991:  | 		$functionName = null; | 
| 3992:  |  | 
| 3993:  | 		if ($node instanceof Node\Stmt\ClassMethod) { | 
| 3994:  | 			if (!$scope->isInClass()) { | 
| 3995:  | 				throw new ShouldNotHappenException(); | 
| 3996:  | 			} | 
| 3997:  | 			$functionName = $node->name->name; | 
| 3998:  | 			$positionalParameterNames = array_map(static function (Node\Param $param): string { | 
| 3999:  | 				if (!$param->var instanceof Variable || !is_string($param->var->name)) { | 
| 4000:  | 					throw new ShouldNotHappenException(); | 
| 4001:  | 				} | 
| 4002:  |  | 
| 4003:  | 				return $param->var->name; | 
| 4004:  | 			}, $node->getParams()); | 
| 4005:  | 			$resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( | 
| 4006:  | 				$docComment, | 
| 4007:  | 				$file, | 
| 4008:  | 				$scope->getClassReflection(), | 
| 4009:  | 				$trait, | 
| 4010:  | 				$node->name->name, | 
| 4011:  | 				$positionalParameterNames, | 
| 4012:  | 			); | 
| 4013:  |  | 
| 4014:  | 			if ($node->name->toLowerString() === '__construct') { | 
| 4015:  | 				foreach ($node->params as $param) { | 
| 4016:  | 					if ($param->flags === 0) { | 
| 4017:  | 						continue; | 
| 4018:  | 					} | 
| 4019:  |  | 
| 4020:  | 					if ($param->getDocComment() === null) { | 
| 4021:  | 						continue; | 
| 4022:  | 					} | 
| 4023:  |  | 
| 4024:  | 					if ( | 
| 4025:  | 						!$param->var instanceof Variable | 
| 4026:  | 						|| !is_string($param->var->name) | 
| 4027:  | 					) { | 
| 4028:  | 						throw new ShouldNotHappenException(); | 
| 4029:  | 					} | 
| 4030:  |  | 
| 4031:  | 					$paramPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | 
| 4032:  | 						$file, | 
| 4033:  | 						$class, | 
| 4034:  | 						$trait, | 
| 4035:  | 						'__construct', | 
| 4036:  | 						$param->getDocComment()->getText(), | 
| 4037:  | 					); | 
| 4038:  | 					$varTags = $paramPhpDoc->getVarTags(); | 
| 4039:  | 					if (isset($varTags[0]) && count($varTags) === 1) { | 
| 4040:  | 						$phpDocType = $varTags[0]->getType(); | 
| 4041:  | 					} elseif (isset($varTags[$param->var->name])) { | 
| 4042:  | 						$phpDocType = $varTags[$param->var->name]->getType(); | 
| 4043:  | 					} else { | 
| 4044:  | 						continue; | 
| 4045:  | 					} | 
| 4046:  |  | 
| 4047:  | 					$phpDocParameterTypes[$param->var->name] = $phpDocType; | 
| 4048:  | 				} | 
| 4049:  | 			} | 
| 4050:  | 		} elseif ($node instanceof Node\Stmt\Function_) { | 
| 4051:  | 			$functionName = trim($scope->getNamespace() . '\\' . $node->name->name, '\\'); | 
| 4052:  | 		} | 
| 4053:  |  | 
| 4054:  | 		if ($docComment !== null && $resolvedPhpDoc === null) { | 
| 4055:  | 			$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | 
| 4056:  | 				$file, | 
| 4057:  | 				$class, | 
| 4058:  | 				$trait, | 
| 4059:  | 				$functionName, | 
| 4060:  | 				$docComment, | 
| 4061:  | 			); | 
| 4062:  | 		} | 
| 4063:  |  | 
| 4064:  | 		if ($resolvedPhpDoc !== null) { | 
| 4065:  | 			$templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); | 
| 4066:  | 			foreach ($resolvedPhpDoc->getParamTags() as $paramName => $paramTag) { | 
| 4067:  | 				if (array_key_exists($paramName, $phpDocParameterTypes)) { | 
| 4068:  | 					continue; | 
| 4069:  | 				} | 
| 4070:  | 				$paramType = $paramTag->getType(); | 
| 4071:  | 				if ($scope->isInClass()) { | 
| 4072:  | 					$paramType = $this->transformStaticType($scope->getClassReflection(), $paramType); | 
| 4073:  | 				} | 
| 4074:  | 				$phpDocParameterTypes[$paramName] = $paramType; | 
| 4075:  | 			} | 
| 4076:  | 			if ($node instanceof Node\FunctionLike) { | 
| 4077:  | 				$nativeReturnType = $scope->getFunctionType($node->getReturnType(), false, false); | 
| 4078:  | 				$phpDocReturnType = $this->getPhpDocReturnType($resolvedPhpDoc, $nativeReturnType); | 
| 4079:  | 				if ($phpDocReturnType !== null && $scope->isInClass()) { | 
| 4080:  | 					$phpDocReturnType = $this->transformStaticType($scope->getClassReflection(), $phpDocReturnType); | 
| 4081:  | 				} | 
| 4082:  | 			} | 
| 4083:  | 			$phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null; | 
| 4084:  | 			$deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; | 
| 4085:  | 			$isDeprecated = $resolvedPhpDoc->isDeprecated(); | 
| 4086:  | 			$isInternal = $resolvedPhpDoc->isInternal(); | 
| 4087:  | 			$isFinal = $resolvedPhpDoc->isFinal(); | 
| 4088:  | 			$isPure = $resolvedPhpDoc->isPure(); | 
| 4089:  | 			$acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments(); | 
| 4090:  | 			$isReadOnly = $isReadOnly || $resolvedPhpDoc->isReadOnly(); | 
| 4091:  | 		} | 
| 4092:  |  | 
| 4093:  | 		return [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment]; | 
| 4094:  | 	} | 
| 4095:  |  | 
| 4096:  | 	private function transformStaticType(ClassReflection $declaringClass, Type $type): Type | 
| 4097:  | 	{ | 
| 4098:  | 		return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($declaringClass): Type { | 
| 4099:  | 			if ($type instanceof StaticType) { | 
| 4100:  | 				$changedType = $type->changeBaseClass($declaringClass); | 
| 4101:  | 				if ($declaringClass->isFinal()) { | 
| 4102:  | 					$changedType = $changedType->getStaticObjectType(); | 
| 4103:  | 				} | 
| 4104:  | 				return $traverse($changedType); | 
| 4105:  | 			} | 
| 4106:  |  | 
| 4107:  | 			return $traverse($type); | 
| 4108:  | 		}); | 
| 4109:  | 	} | 
| 4110:  |  | 
| 4111:  | 	private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $nativeReturnType): ?Type | 
| 4112:  | 	{ | 
| 4113:  | 		$returnTag = $resolvedPhpDoc->getReturnTag(); | 
| 4114:  |  | 
| 4115:  | 		if ($returnTag === null) { | 
| 4116:  | 			return null; | 
| 4117:  | 		} | 
| 4118:  |  | 
| 4119:  | 		$phpDocReturnType = $returnTag->getType(); | 
| 4120:  |  | 
| 4121:  | 		if ($returnTag->isExplicit()) { | 
| 4122:  | 			return $phpDocReturnType; | 
| 4123:  | 		} | 
| 4124:  |  | 
| 4125:  | 		if ($nativeReturnType->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocReturnType))->yes()) { | 
| 4126:  | 			return $phpDocReturnType; | 
| 4127:  | 		} | 
| 4128:  |  | 
| 4129:  | 		return null; | 
| 4130:  | 	} | 
| 4131:  |  | 
| 4132:  | } | 
| 4133:  |  |