Deal with a whole lot of PHPStan suppressed key casting errors

closes #6534
This commit is contained in:
Dylan K. Taylor
2024-11-25 14:30:58 +00:00
parent aef4fa7174
commit 5325ecee37
66 changed files with 338 additions and 124 deletions

View File

@ -60,3 +60,8 @@ parameters:
count: 2
path: ../../phpunit/promise/PromiseTest.php
-
message: "#^Strict comparison using \\=\\=\\= between 0 and 0 will always evaluate to true\\.$#"
count: 1
path: ../rules/UnsafeForeachArrayOfStringRule.php

View File

@ -28,6 +28,7 @@ use PhpParser\Node\Stmt\Foreach_;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\ClassStringType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\StringType;
@ -62,8 +63,17 @@ final class UnsafeForeachArrayOfStringRule implements Rule{
$hasCastableKeyTypes = false;
$expectsIntKeyTypes = false;
TypeTraverser::map($iterableType->getIterableKeyType(), function(Type $type, callable $traverse) use (&$hasCastableKeyTypes, &$expectsIntKeyTypes) : Type{
if($type instanceof IntegerType){
$implicitType = false;
$benevolentUnionDepth = 0;
TypeTraverser::map($iterableType->getIterableKeyType(), function(Type $type, callable $traverse) use (&$hasCastableKeyTypes, &$expectsIntKeyTypes, &$benevolentUnionDepth, &$implicitType) : Type{
if($type instanceof BenevolentUnionType){
$implicitType = true;
$benevolentUnionDepth++;
$result = $traverse($type);
$benevolentUnionDepth--;
return $result;
}
if($type instanceof IntegerType && $benevolentUnionDepth === 0){
$expectsIntKeyTypes = true;
return $type;
}
@ -78,12 +88,20 @@ final class UnsafeForeachArrayOfStringRule implements Rule{
return $type;
});
if($hasCastableKeyTypes && !$expectsIntKeyTypes){
$func = Utils::stringifyKeys(...);
$tip = $implicitType ?
sprintf(
"Declare a key type using @phpstan-var or @phpstan-param, or use %s() to promote the key type to get proper error reporting",
Utils::getNiceClosureName(Utils::promoteKeys(...))
) :
sprintf(
"Use %s() to get a \Generator that will force the keys to string",
Utils::getNiceClosureName(Utils::stringifyKeys(...)),
);
return [
RuleErrorBuilder::message(sprintf(
"Unsafe foreach on array with key type %s (they might be casted to int).",
$iterableType->getIterableKeyType()->describe(VerbosityLevel::value())
))->tip(sprintf("Use %s() for a safe Generator-based iterator.", Utils::getNiceClosureName($func)))->build()
))->tip($tip)->build()
];
}
return [];

View File

@ -135,7 +135,7 @@ class BlockTest extends TestCase{
}
$errors = [];
foreach($expected as $typeName => $numStates){
foreach(Utils::promoteKeys($expected) as $typeName => $numStates){
if(!is_string($typeName) || !is_int($numStates)){
throw new AssumptionFailedError("Old table should be array<string, int>");
}

View File

@ -89,7 +89,6 @@ class ItemTest extends TestCase{
}
public function testGetEnchantments() : void{
/** @var EnchantmentInstance[] $enchantments */
$enchantments = [
new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 5),
new EnchantmentInstance(VanillaEnchantments::SHARPNESS(), 1)

View File

@ -47,7 +47,7 @@ final class CloningRegistryTraitTest extends TestCase{
public function testGetAllClone() : void{
$list1 = TestCloningRegistry::getAll();
$list2 = TestCloningRegistry::getAll();
foreach($list1 as $k => $member){
foreach(Utils::promoteKeys($list1) as $k => $member){
self::assertNotSame($member, $list2[$k], "VanillaBlocks ought to clone its members");
}
}

View File

@ -37,9 +37,13 @@ final class TestCloningRegistry{
/**
* @return \stdClass[]
* @phpstan-return array<string, \stdClass>
*/
public static function getAll() : array{
/** @var \stdClass[] $result */
/**
* @var \stdClass[] $result
* @phpstan-var array<string, \stdClass> $result
*/
$result = self::_registryGetAll();
return $result;
}