diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7d7690cfa6..206a16c8f5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -109,7 +109,7 @@ jobs: uses: "shivammathur/setup-php@v2" with: coverage: "none" - php-version: "8.3" + php-version: "8.5" - uses: "ramsey/composer-install@v3" diff --git a/build/composer-dependency-analyser.php b/build/composer-dependency-analyser.php index 9819b8d3df..10e19107dd 100644 --- a/build/composer-dependency-analyser.php +++ b/build/composer-dependency-analyser.php @@ -12,6 +12,8 @@ 'symfony/polyfill-php80', 'symfony/polyfill-php81', 'symfony/polyfill-php83', + 'symfony/polyfill-php84', + 'symfony/polyfill-php85', ]; $pinnedToSupportPhp72 = [ diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index 98138a3bca..64829f3b81 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -16,9 +16,12 @@ '../../stubs', '../../vendor/jetbrains/phpstorm-stubs', '../../vendor/phpstan/php-8-stubs/stubs', + // when adding new polyfills, don't forget to also append them in 'exclude-namespaces' below '../../vendor/symfony/polyfill-php80', '../../vendor/symfony/polyfill-php81', '../../vendor/symfony/polyfill-php83', + '../../vendor/symfony/polyfill-php84', + '../../vendor/symfony/polyfill-php85', '../../vendor/symfony/polyfill-mbstring', '../../vendor/symfony/polyfill-intl-normalizer', '../../vendor/symfony/polyfill-intl-grapheme', @@ -246,6 +249,8 @@ function (string $filePath, string $prefix, string $content): string { 'Symfony\Polyfill\Php80', 'Symfony\Polyfill\Php81', 'Symfony\Polyfill\Php83', + 'Symfony\Polyfill\Php84', + 'Symfony\Polyfill\Php85', 'Symfony\Polyfill\Mbstring', 'Symfony\Polyfill\Intl\Normalizer', 'Symfony\Polyfill\Intl\Grapheme', diff --git a/composer.json b/composer.json index ceac308215..2df3f6a6a2 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,8 @@ "symfony/polyfill-php80": "^1.23", "symfony/polyfill-php81": "^1.27", "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33", "symfony/process": "^5.4.3", "symfony/service-contracts": "^2.5.0", "symfony/string": "^5.4.3" diff --git a/composer.lock b/composer.lock index 95462736f0..a8e2e75d1d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e7c4dc1df6f5ed59acc039a10295f2ac", + "content-hash": "27d4ef9cc36afbb320cdd1b9464db788", "packages": [ { "name": "clue/ndjson-react", @@ -4142,6 +4142,166 @@ ], "time": "2025-07-08T02:45:35+00:00" }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, { "name": "symfony/process", "version": "v5.4.47", diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 88ff702a1e..9eaff99386 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3779,7 +3779,7 @@ private function enterAnonymousFunctionWithoutReflection( if (isset($callableParameters[$i])) { $parameterType = self::intersectButNotNever($parameterType, $callableParameters[$i]->getType()); } elseif (count($callableParameters) > 0) { - $lastParameter = $callableParameters[count($callableParameters) - 1]; + $lastParameter = array_last($callableParameters); if ($lastParameter->isVariadic()) { $parameterType = self::intersectButNotNever($parameterType, $lastParameter->getType()); } else { @@ -3972,7 +3972,7 @@ private function enterArrowFunctionWithoutReflection(Expr\ArrowFunction $arrowFu if (isset($callableParameters[$i])) { $parameterType = self::intersectButNotNever($parameterType, $callableParameters[$i]->getType()); } elseif (count($callableParameters) > 0) { - $lastParameter = $callableParameters[count($callableParameters) - 1]; + $lastParameter = array_last($callableParameters); if ($lastParameter->isVariadic()) { $parameterType = self::intersectButNotNever($parameterType, $lastParameter->getType()); } else { diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 1d833752b0..c91365fcf1 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1481,7 +1481,7 @@ private function processStmtNode( $bodyScope = $initScope; $isIterableAtLeastOnce = TrinaryLogic::createYes(); - $lastCondExpr = $stmt->cond[count($stmt->cond) - 1] ?? null; + $lastCondExpr = array_last($stmt->cond) ?? null; foreach ($stmt->cond as $condExpr) { $condResult = $this->processExprNode($stmt, $condExpr, $bodyScope, static function (): void { }, ExpressionContext::createDeep()); @@ -5239,7 +5239,7 @@ private function processArgs( } $parameter = $parameters[$i]; } elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) { - $lastParameter = $parameters[count($parameters) - 1]; + $lastParameter = array_last($parameters); $assignByReference = $lastParameter->passedByReference()->createsNewVariable(); $parameterType = $lastParameter->getType(); @@ -5432,7 +5432,7 @@ private function processArgs( if (isset($parameters[$i])) { $currentParameter = $parameters[$i]; } elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) { - $currentParameter = $parameters[count($parameters) - 1]; + $currentParameter = array_last($parameters); } if ($currentParameter !== null) { diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 1e22d530a4..6698a8a800 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1517,7 +1517,7 @@ private function specifyTypesFromAsserts(TypeSpecifierContext $context, Expr\Cal } elseif (isset($parameters[$i])) { $paramName = $parameters[$i]->getName(); } elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) { - $lastParameter = $parameters[count($parameters) - 1]; + $lastParameter = array_last($parameters); $paramName = $lastParameter->getName(); } else { continue; diff --git a/src/Fixable/PhpPrinter.php b/src/Fixable/PhpPrinter.php index 4407c15d75..5bee2a187c 100644 --- a/src/Fixable/PhpPrinter.php +++ b/src/Fixable/PhpPrinter.php @@ -24,7 +24,7 @@ protected function pCommaSeparated(array $nodes): string if (count($nodes) === 0) { return $result; } - $last = $nodes[count($nodes) - 1]; + $last = array_last($nodes); $trailingComma = $last->getAttribute(self::FUNC_ARGS_TRAILING_COMMA_ATTRIBUTE); if ($trailingComma === false) { diff --git a/src/Parser/LastConditionVisitor.php b/src/Parser/LastConditionVisitor.php index f122206e0b..9338df9b60 100644 --- a/src/Parser/LastConditionVisitor.php +++ b/src/Parser/LastConditionVisitor.php @@ -87,7 +87,7 @@ public function enterNode(Node $node): ?Node } $if = $statements[$statementCount - 2]; - $cond = count($if->elseifs) > 0 ? $if->elseifs[count($if->elseifs) - 1]->cond : $if->cond; + $cond = count($if->elseifs) > 0 ? array_last($if->elseifs)->cond : $if->cond; $cond->setAttribute(self::ATTRIBUTE_NAME, true); } diff --git a/src/Parser/TryCatchTypeVisitor.php b/src/Parser/TryCatchTypeVisitor.php index df2c8f4210..c6323c09c1 100644 --- a/src/Parser/TryCatchTypeVisitor.php +++ b/src/Parser/TryCatchTypeVisitor.php @@ -31,7 +31,7 @@ public function enterNode(Node $node): ?Node { if ($node instanceof Node\Stmt || $node instanceof Node\Expr\Match_) { if (count($this->typeStack) > 0) { - $node->setAttribute(self::ATTRIBUTE_NAME, $this->typeStack[count($this->typeStack) - 1]); + $node->setAttribute(self::ATTRIBUTE_NAME, array_last($this->typeStack)); } } diff --git a/src/Parser/VariadicMethodsVisitor.php b/src/Parser/VariadicMethodsVisitor.php index e82e6dfb6b..945c929a8f 100644 --- a/src/Parser/VariadicMethodsVisitor.php +++ b/src/Parser/VariadicMethodsVisitor.php @@ -11,7 +11,6 @@ use PHPStan\Reflection\ParametersAcceptor; use function array_key_exists; use function array_pop; -use function count; use function in_array; use function sprintf; @@ -74,7 +73,7 @@ public function enterNode(Node $node): ?Node $this->inMethodStack[] = $node->name->name; } - $lastMethod = $this->inMethodStack[count($this->inMethodStack) - 1] ?? null; + $lastMethod = array_last($this->inMethodStack) ?? null; if ( $lastMethod !== null @@ -82,7 +81,7 @@ public function enterNode(Node $node): ?Node && $node->name instanceof Name && in_array((string) $node->name, ParametersAcceptor::VARIADIC_FUNCTIONS, true) ) { - $lastClass = $this->classStack[count($this->classStack) - 1] ?? null; + $lastClass = array_last($this->classStack) ?? null; if ($lastClass !== null) { if ( !array_key_exists($lastClass, $this->variadicMethods) @@ -101,8 +100,8 @@ public function enterNode(Node $node): ?Node public function leaveNode(Node $node): ?Node { if ($node instanceof ClassMethod) { - $lastClass = $this->classStack[count($this->classStack) - 1] ?? null; - $lastMethod = $this->inMethodStack[count($this->inMethodStack) - 1] ?? null; + $lastClass = array_last($this->classStack) ?? null; + $lastMethod = array_last($this->inMethodStack) ?? null; if ($lastClass !== null && $lastMethod !== null) { $this->variadicMethods[$lastClass][$lastMethod] ??= false; } diff --git a/src/Reflection/AttributeReflectionFactory.php b/src/Reflection/AttributeReflectionFactory.php index 5cd5cd9cb9..dbecc921c6 100644 --- a/src/Reflection/AttributeReflectionFactory.php +++ b/src/Reflection/AttributeReflectionFactory.php @@ -107,7 +107,7 @@ private function fromNameAndArgumentExpressions(string $name, array $arguments, continue; } if (count($parameters) > 0) { - $lastParameter = $parameters[count($parameters) - 1]; + $lastParameter = array_last($parameters); if ($lastParameter->isVariadic()) { $parameterName = $lastParameter->getName(); if (array_key_exists($parameterName, $namedArgTypes)) { diff --git a/src/Reflection/GenericParametersAcceptorResolver.php b/src/Reflection/GenericParametersAcceptorResolver.php index 784b1427d1..375a3db71a 100644 --- a/src/Reflection/GenericParametersAcceptorResolver.php +++ b/src/Reflection/GenericParametersAcceptorResolver.php @@ -39,7 +39,7 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc continue; } if (count($parameters) > 0) { - $lastParameter = $parameters[count($parameters) - 1]; + $lastParameter = array_last($parameters); if ($lastParameter->isVariadic()) { $parameterName = $lastParameter->getName(); if (array_key_exists($parameterName, $namedArgTypes)) { diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index f6c3058cc2..3ae3dbf9d3 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -490,7 +490,7 @@ public static function selectFromArgs( if (isset($parameters[$i])) { $parameter = $parameters[$i]; } elseif (count($parameters) > 0 && $singleParametersAcceptor->isVariadic()) { - $parameter = $parameters[count($parameters) - 1]; + $parameter = array_last($parameters); } } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 5c3821aa4f..a133df2060 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -586,8 +586,8 @@ private function processArguments( break; } - $parameter = $parameters[count($parameters) - 1]; - $originalParameter = $originalParameters[count($originalParameters) - 1]; + $parameter = array_last($parameters); + $originalParameter = array_last($originalParameters); if (!$parameter->isVariadic()) { $newArguments[$i] = [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, null, null]; break; // func_get_args diff --git a/src/Rules/Whitespace/FileWhitespaceRule.php b/src/Rules/Whitespace/FileWhitespaceRule.php index ee067b1ceb..c718256ea3 100644 --- a/src/Rules/Whitespace/FileWhitespaceRule.php +++ b/src/Rules/Whitespace/FileWhitespaceRule.php @@ -56,13 +56,13 @@ public function enterNode(Node $node) { if ($node instanceof Node\Stmt\Declare_) { if ($node->stmts !== null && count($node->stmts) > 0) { - $this->lastNodes[] = $node->stmts[count($node->stmts) - 1]; + $this->lastNodes[] = array_last($node->stmts); } return null; } if ($node instanceof Node\Stmt\Namespace_) { if (count($node->stmts) > 0) { - $this->lastNodes[] = $node->stmts[count($node->stmts) - 1]; + $this->lastNodes[] = array_last($node->stmts); } return null; } @@ -82,7 +82,7 @@ public function getLastNodes(): array $nodeTraverser->traverse($nodes); $lastNodes = $visitor->getLastNodes(); - $lastNodes[] = $nodes[count($nodes) - 1]; + $lastNodes[] = array_last($nodes); foreach ($lastNodes as $lastNode) { if (!$lastNode instanceof Node\Stmt\InlineHTML || Strings::match($lastNode->value, '#^(\s+)$#') === null) { continue; diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 2ce66f1e1a..c22758d359 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -309,8 +309,8 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA } } - $className = $classStack[count($classStack) - 1] ?? null; - $functionName = $functionStack[count($functionStack) - 1] ?? null; + $className = array_last($classStack) ?? null; + $functionName = array_last($functionStack) ?? null; if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) { $docComment = GetLastDocComment::forNode($node); @@ -378,7 +378,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA continue; } - $className = $classStack[count($classStack) - 1] ?? null; + $className = array_last($classStack) ?? null; if ($className === null) { throw new ShouldNotHappenException(); } @@ -531,8 +531,8 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun } } - $className = $classStack[count($classStack) - 1] ?? null; - $functionName = $functionStack[count($functionStack) - 1] ?? null; + $className = array_last($classStack) ?? null; + $functionName = array_last($functionStack) ?? null; $nameScopeKey = $this->getNameScopeKey($originalClassFileName, $className, $lookForTrait, $functionName); if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) { @@ -540,9 +540,9 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun if (array_key_exists($nameScopeKey, $phpDocNodeMap)) { $phpDocNode = $phpDocNodeMap[$nameScopeKey]; $typeMapStack[] = function () use ($namespace, $uses, $className, $lookForTrait, $functionName, $phpDocNode, $typeMapStack, $typeAliasStack, $constUses): TemplateTypeMap { - $typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null; + $typeMapCb = array_last($typeMapStack) ?? null; $currentTypeMap = $typeMapCb !== null ? $typeMapCb() : null; - $typeAliasesMap = $typeAliasStack[count($typeAliasStack) - 1] ?? []; + $typeAliasesMap = array_last($typeAliasStack) ?? []; $nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap, $typeAliasesMap, constUses: $constUses, typeAliasClassName: $lookForTrait); $templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope); $templateTypeScope = $nameScope->getTemplateTypeScope(); @@ -562,8 +562,8 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun } } - $typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null; - $typeAliasesMap = $typeAliasStack[count($typeAliasStack) - 1] ?? []; + $typeMapCb = array_last($typeMapStack) ?? null; + $typeAliasesMap = array_last($typeAliasStack) ?? []; if ( ( @@ -671,7 +671,7 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun continue; } - $className = $classStack[count($classStack) - 1] ?? null; + $className = array_last($classStack) ?? null; if ($className === null) { throw new ShouldNotHappenException(); } diff --git a/tests/PHPStan/Composer/AutoloadFilesTest.php b/tests/PHPStan/Composer/AutoloadFilesTest.php index 7b87a7896a..4af7c6b0bf 100644 --- a/tests/PHPStan/Composer/AutoloadFilesTest.php +++ b/tests/PHPStan/Composer/AutoloadFilesTest.php @@ -69,6 +69,8 @@ public function testExpectedFiles(): void 'symfony/polyfill-php80/bootstrap.php', // afaik polyfills aren't necessary 'symfony/polyfill-php81/bootstrap.php', // afaik polyfills aren't necessary 'symfony/polyfill-php83/bootstrap.php', // afaik polyfills aren't necessary + 'symfony/polyfill-php84/bootstrap.php', // afaik polyfills aren't necessary + 'symfony/polyfill-php85/bootstrap.php', // afaik polyfills aren't necessary 'symfony/string/Resources/functions.php', // afaik polyfills aren't necessary ];