Merge branch 'stable' into minor-next

This commit is contained in:
Dylan K. Taylor 2024-11-25 14:32:30 +00:00
commit 12ae8dc03b
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
67 changed files with 339 additions and 130 deletions

View File

@ -122,7 +122,7 @@ if(!is_array($ids)){
throw new \RuntimeException("Invalid biome ID map, expected array for root JSON object");
}
$cleanedIds = [];
foreach($ids as $name => $id){
foreach(Utils::promoteKeys($ids) as $name => $id){
if(!is_string($name) || !is_int($id)){
throw new \RuntimeException("Invalid biome ID map, expected string => int map");
}

View File

@ -42,7 +42,7 @@ $constants = [];
* @phpstan-param-out array<string, string> $constants
*/
function collectProperties(string $prefix, array $properties, array &$constants) : void{
foreach($properties as $propertyName => $property){
foreach(Utils::promoteKeys($properties) as $propertyName => $property){
$fullPropertyName = ($prefix !== "" ? $prefix . "." : "") . $propertyName;
$constName = str_replace([".", "-"], "_", strtoupper($fullPropertyName));

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\build\update_registry_annotations;
use pocketmine\utils\Utils;
use function basename;
use function class_exists;
use function count;
@ -48,6 +49,7 @@ if(count($argv) !== 2){
/**
* @param object[] $members
* @phpstan-param array<string, object> $members
*/
function generateMethodAnnotations(string $namespaceName, array $members) : string{
$selfName = basename(__FILE__);
@ -60,7 +62,7 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri
static $lineTmpl = " * @method static %2\$s %s()";
$memberLines = [];
foreach($members as $name => $member){
foreach(Utils::stringifyKeys($members) as $name => $member){
$reflect = new \ReflectionClass($member);
while($reflect !== false && $reflect->isAnonymous()){
$reflect = $reflect->getParentClass();

View File

@ -332,7 +332,7 @@ class MemoryManager{
continue;
}
$methodStatics = [];
foreach($method->getStaticVariables() as $name => $variable){
foreach(Utils::promoteKeys($method->getStaticVariables()) as $name => $variable){
$methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($methodStatics) > 0){
@ -360,7 +360,7 @@ class MemoryManager{
'_SESSION' => true
];
foreach($GLOBALS as $varName => $value){
foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){
if(isset($ignoredGlobals[$varName])){
continue;
}
@ -376,7 +376,7 @@ class MemoryManager{
$reflect = new \ReflectionFunction($function);
$vars = [];
foreach($reflect->getStaticVariables() as $varName => $variable){
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $varName => $variable){
$vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($vars) > 0){
@ -416,7 +416,7 @@ class MemoryManager{
$info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
foreach($reflect->getStaticVariables() as $name => $variable){
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $name => $variable){
$info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
}else{
@ -499,7 +499,7 @@ class MemoryManager{
}
$data = [];
$numeric = 0;
foreach($from as $key => $value){
foreach(Utils::promoteKeys($from) as $key => $value){
$data[$numeric] = [
"k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
"v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),

View File

@ -736,12 +736,15 @@ class Server{
/**
* @return string[][]
* @phpstan-return array<string, list<string>>
*/
public function getCommandAliases() : array{
$section = $this->configGroup->getProperty(Yml::ALIASES);
$result = [];
if(is_array($section)){
foreach($section as $key => $value){
foreach(Utils::promoteKeys($section) as $key => $value){
//TODO: more validation needed here
//key might not be a string, value might not be list<string>
$commands = [];
if(is_array($value)){
$commands = $value;
@ -749,7 +752,7 @@ class Server{
$commands[] = (string) $value;
}
$result[$key] = $commands;
$result[(string) $key] = $commands;
}
}
@ -1094,7 +1097,11 @@ class Server{
$anyWorldFailedToLoad = false;
foreach((array) $this->configGroup->getProperty(Yml::WORLDS, []) as $name => $options){
foreach(Utils::promoteKeys((array) $this->configGroup->getProperty(Yml::WORLDS, [])) as $name => $options){
if(!is_string($name)){
//TODO: this probably should be an error
continue;
}
if($options === null){
$options = [];
}elseif(!is_array($options)){

View File

@ -132,6 +132,7 @@ class RuntimeBlockStateRegistry{
/**
* @return Block[]
* @phpstan-return array<int, Block>
*/
public function getAllKnownStates() : array{
return $this->fullList;

View File

@ -39,7 +39,10 @@ class EnchantInventory extends SimpleInventory implements BlockInventory, Tempor
public const SLOT_INPUT = 0;
public const SLOT_LAPIS = 1;
/** @var EnchantingOption[] $options */
/**
* @var EnchantingOption[] $options
* @phpstan-var list<EnchantingOption>
*/
private array $options = [];
public function __construct(Position $holder){

View File

@ -36,13 +36,17 @@ use function str_contains;
class SignText{
public const LINE_COUNT = 4;
/** @var string[] */
/**
* @var string[]
* @phpstan-var array{0: string, 1: string, 2: string, 3: string}
*/
private array $lines;
private Color $baseColor;
private bool $glowing;
/**
* @param string[]|null $lines index-sensitive; keys 0-3 will be used, regardless of array order
* @phpstan-param array{0?: string, 1?: string, 2?: string, 3?: string}|null $lines
*
* @throws \InvalidArgumentException if the array size is greater than 4
* @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array
@ -82,6 +86,7 @@ class SignText{
* Returns an array of lines currently on the sign.
*
* @return string[]
* @phpstan-return array{0: string, 1: string, 2: string, 3: string}
*/
public function getLines() : array{
return $this->lines;

View File

@ -44,10 +44,16 @@ abstract class Command{
private string $nextLabel;
private string $label;
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $aliases = [];
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $activeAliases = [];
private ?CommandMap $commandMap = null;
@ -62,6 +68,7 @@ abstract class Command{
/**
* @param string[] $aliases
* @phpstan-param list<string> $aliases
*/
public function __construct(string $name, Translatable|string $description = "", Translatable|string|null $usageMessage = null, array $aliases = []){
$this->name = $name;
@ -182,6 +189,7 @@ abstract class Command{
/**
* @return string[]
* @phpstan-return list<string>
*/
public function getAliases() : array{
return $this->activeAliases;
@ -201,6 +209,7 @@ abstract class Command{
/**
* @param string[] $aliases
* @phpstan-param list<string> $aliases
*/
public function setAliases(array $aliases) : void{
$this->aliases = $aliases;

View File

@ -71,6 +71,7 @@ use pocketmine\lang\KnownTranslationFactory;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
use function array_shift;
use function count;
use function implode;
@ -81,7 +82,10 @@ use function trim;
class SimpleCommandMap implements CommandMap{
/** @var Command[] */
/**
* @var Command[]
* @phpstan-var array<string, Command>
*/
protected array $knownCommands = [];
public function __construct(private Server $server){
@ -171,7 +175,7 @@ class SimpleCommandMap implements CommandMap{
}
public function unregister(Command $command) : bool{
foreach($this->knownCommands as $lbl => $cmd){
foreach(Utils::promoteKeys($this->knownCommands) as $lbl => $cmd){
if($cmd === $command){
unset($this->knownCommands[$lbl]);
}
@ -239,6 +243,7 @@ class SimpleCommandMap implements CommandMap{
/**
* @return Command[]
* @phpstan-return array<string, Command>
*/
public function getCommands() : array{
return $this->knownCommands;
@ -247,7 +252,7 @@ class SimpleCommandMap implements CommandMap{
public function registerServerAliases() : void{
$values = $this->server->getCommandAliases();
foreach($values as $alias => $commandStrings){
foreach(Utils::stringifyKeys($values) as $alias => $commandStrings){
if(str_contains($alias, ":")){
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_illegal($alias)));
continue;

View File

@ -110,14 +110,15 @@ class CraftingManager{
/**
* @param Item[] $items
* @phpstan-param list<Item> $items
*
* @return Item[]
* @phpstan-return list<Item>
*/
private static function pack(array $items) : array{
/** @var Item[] $result */
$result = [];
foreach($items as $i => $item){
foreach($items as $item){
foreach($result as $otherItem){
if($item->canStackWith($otherItem)){
$otherItem->setCount($otherItem->getCount() + $item->getCount());
@ -134,6 +135,7 @@ class CraftingManager{
/**
* @param Item[] $outputs
* @phpstan-param list<Item> $outputs
*/
private static function hashOutputs(array $outputs) : string{
$outputs = self::pack($outputs);

View File

@ -193,7 +193,7 @@ final class CraftingManagerFromDataHelper{
*/
private static function loadJsonObjectListIntoModel(\JsonMapper $mapper, string $modelClass, array $data) : array{
$result = [];
foreach($data as $i => $item){
foreach(Utils::promoteKeys($data) as $i => $item){
if(!is_object($item)){
throw new SavedDataLoadingException("Invalid entry at index $i: expected object, got " . get_debug_type($item));
}

View File

@ -30,6 +30,7 @@ interface CraftingRecipe{
* Returns a list of items needed to craft this recipe. This MUST NOT include Air items or items with a zero count.
*
* @return RecipeIngredient[]
* @phpstan-return list<RecipeIngredient>
*/
public function getIngredientList() : array;
@ -37,6 +38,7 @@ interface CraftingRecipe{
* Returns a list of results this recipe will produce when the inputs in the given crafting grid are consumed.
*
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResultsFor(CraftingGrid $grid) : array;

View File

@ -32,11 +32,20 @@ use function str_contains;
use function strlen;
class ShapedRecipe implements CraftingRecipe{
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $shape = [];
/** @var RecipeIngredient[] char => RecipeIngredient map */
/**
* @var RecipeIngredient[] char => RecipeIngredient map
* @phpstan-var array<string, RecipeIngredient>
*/
private array $ingredientList = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
private array $results = [];
private int $height;
@ -56,6 +65,10 @@ class ShapedRecipe implements CraftingRecipe{
* @param Item[] $results List of items that this recipe produces when crafted.
*
* Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns.
*
* @phpstan-param list<string> $shape
* @phpstan-param array<string, RecipeIngredient> $ingredients
* @phpstan-param list<Item> $results
*/
public function __construct(array $shape, array $ingredients, array $results){
$this->height = count($shape);
@ -84,7 +97,7 @@ class ShapedRecipe implements CraftingRecipe{
$this->shape = $shape;
foreach($ingredients as $char => $i){
foreach(Utils::stringifyKeys($ingredients) as $char => $i){
if(!str_contains(implode($this->shape), $char)){
throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape");
}
@ -105,6 +118,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResults() : array{
return Utils::cloneObjectArray($this->results);
@ -112,6 +126,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResultsFor(CraftingGrid $grid) : array{
return $this->getResults();
@ -119,6 +134,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return (RecipeIngredient|null)[][]
* @phpstan-return list<list<RecipeIngredient|null>>
*/
public function getIngredientMap() : array{
$ingredients = [];
@ -132,9 +148,6 @@ class ShapedRecipe implements CraftingRecipe{
return $ingredients;
}
/**
* @return RecipeIngredient[]
*/
public function getIngredientList() : array{
$ingredients = [];
@ -157,6 +170,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* Returns an array of strings containing characters representing the recipe's shape.
* @return string[]
* @phpstan-return list<string>
*/
public function getShape() : array{
return $this->shape;

View File

@ -28,15 +28,24 @@ use pocketmine\utils\Utils;
use function count;
class ShapelessRecipe implements CraftingRecipe{
/** @var RecipeIngredient[] */
/**
* @var RecipeIngredient[]
* @phpstan-var list<RecipeIngredient>
*/
private array $ingredients = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
private array $results;
private ShapelessRecipeType $type;
/**
* @param RecipeIngredient[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array.
* @param Item[] $results List of result items created by this recipe.
*
* @phpstan-param list<RecipeIngredient> $ingredients
* @phpstan-param list<Item> $results
*/
public function __construct(array $ingredients, array $results, ShapelessRecipeType $type){
$this->type = $type;
@ -50,6 +59,7 @@ class ShapelessRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResults() : array{
return Utils::cloneObjectArray($this->results);
@ -63,9 +73,6 @@ class ShapelessRecipe implements CraftingRecipe{
return $this->type;
}
/**
* @return RecipeIngredient[]
*/
public function getIngredientList() : array{
return $this->ingredients;
}

View File

@ -43,7 +43,10 @@ final class CrashDumpData implements \JsonSerializable{
public string $plugin = "";
/** @var string[] */
/**
* @var string[]
* @phpstan-var array<int, string>
*/
public array $code = [];
/** @var string[] */
@ -55,7 +58,10 @@ final class CrashDumpData implements \JsonSerializable{
*/
public array $plugins = [];
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
public array $parameters = [];
public string $serverDotProperties = "";

View File

@ -48,7 +48,7 @@ final class ItemTagToIdMap{
throw new AssumptionFailedError("Invalid item tag map, expected array");
}
$cleanMap = [];
foreach($map as $tagName => $ids){
foreach(Utils::promoteKeys($map) as $tagName => $ids){
if(!is_string($tagName)){
throw new AssumptionFailedError("Invalid item tag name $tagName, expected string as key");
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\data\bedrock;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use function is_array;
use function is_int;
use function is_string;
@ -43,7 +44,7 @@ abstract class LegacyToStringIdMap{
if(!is_array($stringToLegacyId)){
throw new AssumptionFailedError("Invalid format of ID map");
}
foreach($stringToLegacyId as $stringId => $legacyId){
foreach(Utils::promoteKeys($stringToLegacyId) as $stringId => $legacyId){
if(!is_string($stringId) || !is_int($legacyId)){
throw new AssumptionFailedError("ID map should have string keys and int values");
}

View File

@ -56,7 +56,7 @@ final class R12ItemIdToBlockIdMap{
}
$builtMap = [];
foreach($map as $itemId => $blockId){
foreach(Utils::promoteKeys($map) as $itemId => $blockId){
if(!is_string($itemId)){
throw new AssumptionFailedError("Invalid blockitem ID mapping table, expected string as key");
}

View File

@ -103,7 +103,10 @@ abstract class Entity{
return self::$entityCount++;
}
/** @var Player[] */
/**
* @var Player[]
* @phpstan-var array<int, Player>
*/
protected array $hasSpawned = [];
protected int $id;

View File

@ -30,7 +30,10 @@ use function spl_object_id;
use const SORT_NUMERIC;
class HandlerList{
/** @var RegisteredListener[][] */
/**
* @var RegisteredListener[][]
* @phpstan-var array<int, array<int, RegisteredListener>>
*/
private array $handlerSlots = [];
/** @var RegisteredListenerCache[] */

View File

@ -41,7 +41,10 @@ use function spl_object_id;
*/
abstract class BaseInventory implements Inventory, SlotValidatedInventory{
protected int $maxStackSize = Inventory::MAX_STACK;
/** @var Player[] */
/**
* @var Player[]
* @phpstan-var array<int, Player>
*/
protected array $viewers = [];
/**
* @var InventoryListener[]|ObjectSet
@ -286,8 +289,6 @@ abstract class BaseInventory implements Inventory, SlotValidatedInventory{
}
public function removeItem(Item ...$slots) : array{
/** @var Item[] $searchItems */
/** @var Item[] $slots */
$searchItems = [];
foreach($slots as $slot){
if(!$slot->isNull()){

View File

@ -36,7 +36,10 @@ final class CreativeInventory{
use SingletonTrait;
use DestructorCallbackTrait;
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var array<int, Item>
*/
private array $creative = [];
/** @phpstan-var ObjectSet<\Closure() : void> */
@ -69,6 +72,7 @@ final class CreativeInventory{
/**
* @return Item[]
* @phpstan-return array<int, Item>
*/
public function getAll() : array{
return Utils::cloneObjectArray($this->creative);

View File

@ -57,9 +57,15 @@ class CraftingTransaction extends InventoryTransaction{
protected ?CraftingRecipe $recipe = null;
protected ?int $repetitions = null;
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
protected array $inputs = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
protected array $outputs = [];
private CraftingManager $craftingManager;
@ -74,6 +80,9 @@ class CraftingTransaction extends InventoryTransaction{
/**
* @param Item[] $providedItems
* @return Item[]
*
* @phpstan-param list<Item> $providedItems
* @phpstan-return list<Item>
*/
private static function packItems(array $providedItems) : array{
$packedProvidedItems = [];
@ -94,6 +103,9 @@ class CraftingTransaction extends InventoryTransaction{
/**
* @param Item[] $providedItems
* @param RecipeIngredient[] $recipeIngredients
*
* @phpstan-param list<Item> $providedItems
* @phpstan-param list<RecipeIngredient> $recipeIngredients
*/
public static function matchIngredients(array $providedItems, array $recipeIngredients, int $expectedIterations) : void{
if(count($recipeIngredients) === 0){
@ -172,6 +184,9 @@ class CraftingTransaction extends InventoryTransaction{
* @param Item[] $txItems
* @param Item[] $recipeItems
*
* @phpstan-param list<Item> $txItems
* @phpstan-param list<Item> $recipeItems
*
* @throws TransactionValidationException
*/
protected function matchOutputs(array $txItems, array $recipeItems) : int{

View File

@ -29,6 +29,7 @@ use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use function array_keys;
use function array_values;
use function assert;
@ -57,10 +58,16 @@ use function spl_object_id;
class InventoryTransaction{
protected bool $hasExecuted = false;
/** @var Inventory[] */
/**
* @var Inventory[]
* @phpstan-var array<int, Inventory>
*/
protected array $inventories = [];
/** @var InventoryAction[] */
/**
* @var InventoryAction[]
* @phpstan-var array<int, InventoryAction>
*/
protected array $actions = [];
/**
@ -81,6 +88,7 @@ class InventoryTransaction{
/**
* @return Inventory[]
* @phpstan-return array<int, Inventory>
*/
public function getInventories() : array{
return $this->inventories;
@ -93,6 +101,7 @@ class InventoryTransaction{
* significance and should not be relied on.
*
* @return InventoryAction[]
* @phpstan-return array<int, InventoryAction>
*/
public function getActions() : array{
return $this->actions;
@ -133,8 +142,8 @@ class InventoryTransaction{
/**
* @param Item[] $needItems
* @param Item[] $haveItems
* @phpstan-param-out Item[] $needItems
* @phpstan-param-out Item[] $haveItems
* @phpstan-param-out list<Item> $needItems
* @phpstan-param-out list<Item> $haveItems
*
* @throws TransactionValidationException
*/
@ -188,11 +197,8 @@ class InventoryTransaction{
* wrong order), so this method also tries to chain them into order.
*/
protected function squashDuplicateSlotChanges() : void{
/** @var SlotChangeAction[][] $slotChanges */
$slotChanges = [];
/** @var Inventory[] $inventories */
$inventories = [];
/** @var int[] $slots */
$slots = [];
foreach($this->actions as $key => $action){
@ -203,7 +209,7 @@ class InventoryTransaction{
}
}
foreach($slotChanges as $hash => $list){
foreach(Utils::stringifyKeys($slotChanges) as $hash => $list){
if(count($list) === 1){ //No need to compact slot changes if there is only one on this slot
continue;
}
@ -233,6 +239,7 @@ class InventoryTransaction{
/**
* @param SlotChangeAction[] $possibleActions
* @phpstan-param list<SlotChangeAction> $possibleActions
*/
protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{
assert(count($possibleActions) > 0);

View File

@ -33,7 +33,10 @@ use function spl_object_id;
* The primary purpose of this trait is providing scope isolation for the methods it contains.
*/
trait ItemEnchantmentHandlingTrait{
/** @var EnchantmentInstance[] */
/**
* @var EnchantmentInstance[]
* @phpstan-var array<int, EnchantmentInstance>
*/
protected array $enchantments = [];
public function hasEnchantments() : bool{
@ -79,6 +82,7 @@ trait ItemEnchantmentHandlingTrait{
/**
* @return EnchantmentInstance[]
* @phpstan-return array<int, EnchantmentInstance>
*/
public function getEnchantments() : array{
return $this->enchantments;

View File

@ -29,6 +29,7 @@ use pocketmine\data\bedrock\item\upgrade\ItemDataUpgrader;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use pocketmine\world\format\io\GlobalItemDataHandlers;
use Symfony\Component\Filesystem\Path;
use function explode;
@ -67,7 +68,7 @@ final class LegacyStringToItemParser{
$mappings = json_decode($mappingsRaw, true);
if(!is_array($mappings)) throw new AssumptionFailedError("Invalid mappings format, expected array");
foreach($mappings as $name => $id){
foreach(Utils::promoteKeys($mappings) as $name => $id){
if(!is_string($id)) throw new AssumptionFailedError("Invalid mappings format, expected string values");
$result->addMapping((string) $name, $id);
}

View File

@ -147,7 +147,7 @@ class Language{
$baseText = $this->parseTranslation($str, $onlyPrefix);
}
foreach($params as $i => $p){
foreach(Utils::promoteKeys($params) as $i => $p){
$replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p;
$baseText = str_replace("{%$i}", $replacement, $baseText);
}
@ -161,7 +161,7 @@ class Language{
$baseText = $this->parseTranslation($c->getText());
}
foreach($c->getParameters() as $i => $p){
foreach(Utils::promoteKeys($c->getParameters()) as $i => $p){
$replacement = $p instanceof Translatable ? $this->translate($p) : $p;
$baseText = str_replace("{%$i}", $replacement, $baseText);
}

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\lang;
use pocketmine\utils\Utils;
final class Translatable{
/** @var string[]|Translatable[] $params */
protected array $params = [];
@ -34,7 +36,7 @@ final class Translatable{
protected string $text,
array $params = []
){
foreach($params as $k => $param){
foreach(Utils::promoteKeys($params) as $k => $param){
if(!($param instanceof Translatable)){
$this->params[$k] = (string) $param;
}else{

View File

@ -30,10 +30,16 @@ use function spl_object_id;
class NetworkSessionManager{
/** @var NetworkSession[] */
/**
* @var NetworkSession[]
* @phpstan-var array<int, NetworkSession>
*/
private array $sessions = [];
/** @var NetworkSession[] */
/**
* @var NetworkSession[]
* @phpstan-var array<int, NetworkSession>
*/
private array $pendingLoginSessions = [];
/**

View File

@ -695,6 +695,7 @@ class InventoryManager{
/**
* @param EnchantingOption[] $options
* @phpstan-param list<EnchantingOption> $options
*/
public function syncEnchantingTableOptions(array $options) : void{
$protocolOptions = [];

View File

@ -1092,7 +1092,7 @@ class NetworkSession{
public function syncAvailableCommands() : void{
$commandData = [];
foreach($this->server->getCommandMap()->getCommands() as $name => $command){
foreach($this->server->getCommandMap()->getCommands() as $command){
if(isset($commandData[$command->getLabel()]) || $command->getLabel() === "help" || !$command->testPermissionSilent($this->player)){
continue;
}

View File

@ -53,7 +53,6 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
$compressors = [];
/** @var NetworkSession[][] $targetsByCompressor */
$targetsByCompressor = [];
foreach($recipients as $recipient){
//TODO: different compressors might be compatible, it might not be necessary to split them up by object

View File

@ -78,7 +78,10 @@ class ChunkCache implements ChunkListener{
}
}
/** @var CompressBatchPromise[] */
/**
* @var CompressBatchPromise[]
* @phpstan-var array<int, CompressBatchPromise>
*/
private array $caches = [];
private int $hits = 0;

View File

@ -28,6 +28,7 @@ use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\Tag;
use pocketmine\nbt\TreeRoot;
use pocketmine\utils\Utils;
use function count;
use function ksort;
use const SORT_STRING;
@ -43,6 +44,7 @@ final class BlockStateDictionaryEntry{
/**
* @param Tag[] $stateProperties
* @phpstan-param array<string, Tag> $stateProperties
*/
public function __construct(
private string $stateName,
@ -79,6 +81,7 @@ final class BlockStateDictionaryEntry{
/**
* @param Tag[] $properties
* @phpstan-param array<string, Tag> $properties
*/
public static function encodeStateProperties(array $properties) : string{
if(count($properties) === 0){
@ -87,7 +90,7 @@ final class BlockStateDictionaryEntry{
//TODO: make a more efficient encoding - NBT will do for now, but it's not very compact
ksort($properties, SORT_STRING);
$tag = new CompoundTag();
foreach($properties as $k => $v){
foreach(Utils::stringifyKeys($properties) as $k => $v){
$tag->setTag($k, $v);
}
return (new LittleEndianNbtSerializer())->write(new TreeRoot($tag));

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\convert;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use function is_array;
use function is_bool;
use function is_int;
@ -41,7 +42,7 @@ final class ItemTypeDictionaryFromDataHelper{
}
$params = [];
foreach($table as $name => $entry){
foreach(Utils::promoteKeys($table) as $name => $entry){
if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"])){
throw new AssumptionFailedError("Invalid item list format");
}

View File

@ -251,7 +251,7 @@ class InGamePacketHandler extends PacketHandler{
if(count($blockActions) > 100){
throw new PacketHandlingException("Too many block actions in PlayerAuthInputPacket");
}
foreach($blockActions as $k => $blockAction){
foreach(Utils::promoteKeys($blockActions) as $k => $blockAction){
$actionHandled = false;
if($blockAction instanceof PlayerBlockActionStopBreak){
$actionHandled = $this->handlePlayerActionFromData($blockAction->getActionType(), new BlockPosition(0, 0, 0), Facing::DOWN);

View File

@ -54,6 +54,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResp
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use function array_key_first;
use function count;
use function spl_object_id;
@ -370,7 +371,7 @@ class ItemStackRequestExecutor{
* @throws ItemStackRequestProcessException
*/
public function generateInventoryTransaction() : InventoryTransaction{
foreach($this->request->getActions() as $k => $action){
foreach(Utils::promoteKeys($this->request->getActions()) as $k => $action){
try{
$this->processItemStackRequestAction($action);
}catch(ItemStackRequestProcessException $e){

View File

@ -150,7 +150,7 @@ class LoginPacketHandler extends PacketHandler{
protected function fetchAuthData(JwtChain $chain) : AuthenticationData{
/** @var AuthenticationData|null $extraData */
$extraData = null;
foreach($chain->chain as $k => $jwt){
foreach($chain->chain as $jwt){
//validate every chain element
try{
[, $claims, ] = JwtUtils::parse($jwt);

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\permission;
use pocketmine\utils\Utils;
use function fclose;
use function fgets;
use function fopen;
@ -32,7 +33,10 @@ use function strtolower;
use function trim;
class BanList{
/** @var BanEntry[] */
/**
* @var BanEntry[]
* @phpstan-var array<string, BanEntry>
*/
private array $list = [];
private bool $enabled = true;
@ -101,7 +105,7 @@ class BanList{
}
public function removeExpired() : void{
foreach($this->list as $name => $entry){
foreach(Utils::promoteKeys($this->list) as $name => $entry){
if($entry->hasExpired()){
unset($this->list[$name]);
}

View File

@ -25,10 +25,14 @@ namespace pocketmine\permission;
use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginException;
use pocketmine\utils\Utils;
use function spl_object_id;
class PermissionAttachment{
/** @var bool[] */
/**
* @var bool[]
* @phpstan-var array<string, bool>
*/
private array $permissions = [];
/**
@ -60,6 +64,7 @@ class PermissionAttachment{
/**
* @return bool[]
* @phpstan-return array<string, bool>
*/
public function getPermissions() : array{
return $this->permissions;
@ -78,9 +83,10 @@ class PermissionAttachment{
/**
* @param bool[] $permissions
* @phpstan-param array<string, bool> $permissions
*/
public function setPermissions(array $permissions) : void{
foreach($permissions as $key => $value){
foreach(Utils::stringifyKeys($permissions) as $key => $value){
$this->permissions[$key] = $value;
}
$this->recalculatePermissibles();

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\permission;
use pocketmine\utils\Utils;
use function count;
use function spl_object_id;
@ -37,9 +38,15 @@ class PermissionManager{
return self::$instance;
}
/** @var Permission[] */
/**
* @var Permission[]
* @phpstan-var array<string, Permission>
*/
protected array $permissions = [];
/** @var PermissibleInternal[][] */
/**
* @var PermissibleInternal[][]
* @phpstan-var array<string, array<int, PermissibleInternal>>
*/
protected array $permSubs = [];
public function getPermission(string $name) : ?Permission{
@ -82,7 +89,7 @@ class PermissionManager{
}
public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{
foreach($this->permSubs as $permission => $subs){
foreach(Utils::promoteKeys($this->permSubs) as $permission => $subs){
if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){
unset($this->permSubs[$permission]);
}else{

View File

@ -70,7 +70,6 @@ final class ApiVersion{
* @return string[]
*/
public static function checkAmbiguousVersions(array $versions) : array{
/** @var VersionString[][] $indexedVersions */
$indexedVersions = [];
foreach($versions as $str){
@ -85,9 +84,8 @@ final class ApiVersion{
}
}
/** @var VersionString[] $result */
$result = [];
foreach($indexedVersions as $major => $list){
foreach($indexedVersions as $list){
if(count($list) > 1){
array_push($result, ...$list);
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\plugin;
use pocketmine\permission\Permission;
use pocketmine\permission\PermissionParser;
use pocketmine\permission\PermissionParserException;
use pocketmine\utils\Utils;
use function array_map;
use function array_values;
use function get_debug_type;
@ -151,7 +152,7 @@ class PluginDescription{
$this->compatibleOperatingSystems = array_map("\strval", (array) ($plugin[self::KEY_OS] ?? []));
if(isset($plugin[self::KEY_COMMANDS]) && is_array($plugin[self::KEY_COMMANDS])){
foreach($plugin[self::KEY_COMMANDS] as $commandName => $commandData){
foreach(Utils::promoteKeys($plugin[self::KEY_COMMANDS]) as $commandName => $commandData){
if(!is_string($commandName)){
throw new PluginDescriptionParseException("Invalid Plugin commands, key must be the name of the command");
}
@ -177,7 +178,7 @@ class PluginDescription{
if(isset($plugin[self::KEY_EXTENSIONS])){
$extensions = (array) $plugin[self::KEY_EXTENSIONS];
$isLinear = $extensions === array_values($extensions);
foreach($extensions as $k => $v){
foreach(Utils::promoteKeys($extensions) as $k => $v){
if($isLinear){
$k = $v;
$v = "*";

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\plugin;
use pocketmine\utils\Utils;
use function array_flip;
use function is_array;
use function is_float;
@ -77,7 +78,7 @@ class PluginGraylist{
if(!is_array($array["plugins"])){
throw new \InvalidArgumentException("\"plugins\" must be an array");
}
foreach($array["plugins"] as $k => $v){
foreach(Utils::promoteKeys($array["plugins"]) as $k => $v){
if(!is_string($v) && !is_int($v) && !is_float($v)){
throw new \InvalidArgumentException("\"plugins\" contains invalid element at position $k");
}

View File

@ -69,10 +69,16 @@ use function strtolower;
* Manages all the plugins
*/
class PluginManager{
/** @var Plugin[] */
/**
* @var Plugin[]
* @phpstan-var array<string, Plugin>
*/
protected array $plugins = [];
/** @var Plugin[] */
/**
* @var Plugin[]
* @phpstan-var array<string, Plugin>
*/
protected array $enabledPlugins = [];
/** @var array<string, array<string, true>> */
@ -114,6 +120,7 @@ class PluginManager{
/**
* @return Plugin[]
* @phpstan-return array<string, Plugin>
*/
public function getPlugins() : array{
return $this->plugins;
@ -526,7 +533,7 @@ class PluginManager{
}
public function tickSchedulers(int $currentTick) : void{
foreach($this->enabledPlugins as $pluginName => $p){
foreach(Utils::promoteKeys($this->enabledPlugins) as $pluginName => $p){
if(isset($this->enabledPlugins[$pluginName])){
//the plugin may have been disabled as a result of updating other plugins' schedulers, and therefore
//removed from enabledPlugins; however, foreach will still see it due to copy-on-write

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\promise;
use pocketmine\utils\Utils;
use function count;
use function spl_object_id;
@ -84,7 +85,7 @@ final class Promise{
$toResolve = count($promises);
$continue = true;
foreach($promises as $key => $promise){
foreach(Utils::promoteKeys($promises) as $key => $promise){
$promise->onCompletion(
function(mixed $value) use ($resolver, $key, $toResolve, &$values) : void{
$values[$key] = $value;

View File

@ -25,6 +25,7 @@ namespace pocketmine\resourcepacks;
use pocketmine\utils\Config;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use Symfony\Component\Filesystem\Path;
use function array_keys;
use function copy;
@ -87,7 +88,7 @@ class ResourcePackManager{
throw new \InvalidArgumentException("\"resource_stack\" key should contain a list of pack names");
}
foreach($resourceStack as $pos => $pack){
foreach(Utils::promoteKeys($resourceStack) as $pos => $pack){
if(!is_string($pack) && !is_int($pack) && !is_float($pack)){
$logger->critical("Found invalid entry in resource pack list at offset $pos of type " . gettype($pack));
continue;

View File

@ -403,7 +403,7 @@ final class Utils{
}
/**
* @param mixed[] $trace
* @param mixed[][] $trace
* @return string[]
*/
public static function printableExceptionInfo(\Throwable $e, $trace = null) : array{
@ -445,6 +445,7 @@ final class Utils{
* @phpstan-param list<array<string, mixed>> $trace
*
* @return string[]
* @phpstan-return list<string>
*/
public static function printableTrace(array $trace, int $maxStringLength = 80) : array{
$messages = [];
@ -456,7 +457,7 @@ final class Utils{
}else{
$args = $trace[$i]["params"];
}
/** @var mixed[] $args */
/** @phpstan-var array<int, mixed> $args */
$paramsList = [];
$offset = 0;
@ -608,6 +609,18 @@ final class Utils{
}
}
/**
* Gets rid of PHPStan BenevolentUnionType on array keys, so that wrong type errors get reported properly
* Use this if you don't care what the key type is and just want proper PHPStan error reporting
*
* @phpstan-template TValueType
* @phpstan-param array<TValueType> $array
* @phpstan-return array<int|string, TValueType>
*/
public static function promoteKeys(array $array) : array{
return $array;
}
public static function checkUTF8(string $string) : void{
if(!mb_check_encoding($string, 'UTF-8')){
throw new \InvalidArgumentException("Text must be valid UTF-8");

View File

@ -28,7 +28,10 @@ use pocketmine\math\Vector3;
use pocketmine\utils\Utils;
class BlockTransaction{
/** @var Block[][][] */
/**
* @var Block[][][]
* @phpstan-var array<int, array<int, array<int, Block>>>
*/
private array $blocks = [];
/**

View File

@ -56,7 +56,10 @@ use function trim;
class WorldManager{
public const TICKS_PER_AUTOSAVE = 300 * Server::TARGET_TICKS_PER_SECOND;
/** @var World[] */
/**
* @var World[]
* @phpstan-var array<int, World>
*/
private array $worlds = [];
private ?World $defaultWorld = null;
@ -76,6 +79,7 @@ class WorldManager{
/**
* @return World[]
* @phpstan-return array<int, World>
*/
public function getWorlds() : array{
return $this->worlds;

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\world\format;
use function array_map;
use function array_values;
use function count;
class SubChunk{
@ -36,6 +35,7 @@ class SubChunk{
* SubChunk constructor.
*
* @param PalettedBlockArray[] $blockLayers
* @phpstan-param list<PalettedBlockArray> $blockLayers
*/
public function __construct(
private int $emptyBlockId,
@ -85,6 +85,7 @@ class SubChunk{
/**
* @return PalettedBlockArray[]
* @phpstan-return list<PalettedBlockArray>
*/
public function getBlockLayers() : array{
return $this->blockLayers;
@ -129,17 +130,18 @@ class SubChunk{
}
public function collectGarbage() : void{
foreach($this->blockLayers as $k => $layer){
$cleanedLayers = [];
foreach($this->blockLayers as $layer){
$layer->collectGarbage();
foreach($layer->getPalette() as $p){
if($p !== $this->emptyBlockId){
$cleanedLayers[] = $layer;
continue 2;
}
}
unset($this->blockLayers[$k]);
}
$this->blockLayers = array_values($this->blockLayers);
$this->blockLayers = $cleanedLayers;
$this->biomes->collectGarbage();
if($this->skyLight !== null && $this->skyLight->isUniform(0)){

View File

@ -65,6 +65,8 @@ abstract class BaseWorldProvider implements WorldProvider{
abstract protected function loadLevelData() : WorldData;
private function translatePalette(PalettedBlockArray $blockArray, \Logger $logger) : PalettedBlockArray{
//TODO: missing type info in stubs
/** @phpstan-var list<int> $palette */
$palette = $blockArray->getPalette();
$newPalette = [];

View File

@ -32,6 +32,10 @@ final class ChunkData{
* @param SubChunk[] $subChunks
* @param CompoundTag[] $entityNBT
* @param CompoundTag[] $tileNBT
*
* @phpstan-param array<int, SubChunk> $subChunks
* @phpstan-param list<CompoundTag> $entityNBT
* @phpstan-param list<CompoundTag> $tileNBT
*/
public function __construct(
private array $subChunks,
@ -42,14 +46,21 @@ final class ChunkData{
/**
* @return SubChunk[]
* @phpstan-return array<int, SubChunk>
*/
public function getSubChunks() : array{ return $this->subChunks; }
public function isPopulated() : bool{ return $this->populated; }
/** @return CompoundTag[] */
/**
* @return CompoundTag[]
* @phpstan-return list<CompoundTag>
*/
public function getEntityNBT() : array{ return $this->entityNBT; }
/** @return CompoundTag[] */
/**
* @return CompoundTag[]
* @phpstan-return list<CompoundTag>
*/
public function getTileNBT() : array{ return $this->tileNBT; }
}

View File

@ -31,7 +31,10 @@ use const SORT_NUMERIC;
final class RegionGarbageMap{
/** @var RegionLocationTableEntry[] */
/**
* @var RegionLocationTableEntry[]
* @phpstan-var array<int, RegionLocationTableEntry>
*/
private array $entries = [];
private bool $clean = false;
@ -48,7 +51,6 @@ final class RegionGarbageMap{
* @param RegionLocationTableEntry[]|null[] $locationTable
*/
public static function buildFromLocationTable(array $locationTable) : self{
/** @var RegionLocationTableEntry[] $usedMap */
$usedMap = [];
foreach($locationTable as $entry){
if($entry === null){
@ -62,12 +64,10 @@ final class RegionGarbageMap{
ksort($usedMap, SORT_NUMERIC);
/** @var RegionLocationTableEntry[] $garbageMap */
$garbageMap = [];
/** @var RegionLocationTableEntry|null $prevEntry */
$prevEntry = null;
foreach($usedMap as $firstSector => $entry){
foreach($usedMap as $entry){
$prevEndPlusOne = ($prevEntry !== null ? $prevEntry->getLastSector() + 1 : RegionLoader::FIRST_SECTOR);
$currentStart = $entry->getFirstSector();
if($prevEndPlusOne < $currentStart){

View File

@ -67,7 +67,10 @@ class RegionLoader{
/** @var resource */
protected $filePointer;
protected int $nextSector = self::FIRST_SECTOR;
/** @var RegionLocationTableEntry[]|null[] */
/**
* @var RegionLocationTableEntry[]|null[]
* @phpstan-var list<RegionLocationTableEntry|null>
*/
protected array $locationTable = [];
protected RegionGarbageMap $garbageTable;
public int $lastUsed;
@ -327,7 +330,6 @@ class RegionLoader{
* @throws CorruptedRegionException
*/
private function checkLocationTableValidity() : void{
/** @var int[] $usedOffsets */
$usedOffsets = [];
$fileSize = filesize($this->filePath);
@ -355,7 +357,7 @@ class RegionLoader{
}
ksort($usedOffsets, SORT_NUMERIC);
$prevLocationIndex = null;
foreach($usedOffsets as $startOffset => $locationTableIndex){
foreach($usedOffsets as $locationTableIndex){
if($this->locationTable[$locationTableIndex] === null){
continue;
}

View File

@ -72,7 +72,10 @@ abstract class RegionWorldProvider extends BaseWorldProvider{
return false;
}
/** @var RegionLoader[] */
/**
* @var RegionLoader[]
* @phpstan-var array<int, RegionLoader>
*/
protected array $regions = [];
protected function loadLevelData() : WorldData{

View File

@ -76,7 +76,10 @@ class PopulationTask extends AsyncTask{
$chunk = $this->chunk !== null ? FastChunkSerializer::deserializeTerrain($this->chunk) : null;
/** @var string[] $serialChunks */
/**
* @var string[] $serialChunks
* @phpstan-var array<int, string|null> $serialChunks
*/
$serialChunks = igbinary_unserialize($this->adjacentChunks);
$chunks = array_map(
function(?string $serialized) : ?Chunk{
@ -92,7 +95,6 @@ class PopulationTask extends AsyncTask{
self::setOrGenerateChunk($manager, $generator, $this->chunkX, $this->chunkZ, $chunk);
/** @var Chunk[] $resultChunks */
$resultChunks = []; //this is just to keep phpstan's type inference happy
foreach($chunks as $relativeChunkHash => $c){
World::getXZ($relativeChunkHash, $relativeX, $relativeZ);

View File

@ -840,11 +840,6 @@ parameters:
count: 1
path: ../../../src/utils/Utils.php
-
message: "#^Parameter \\#1 \\$trace of static method pocketmine\\\\utils\\\\Utils\\:\\:printableTrace\\(\\) expects array\\<int, array\\<string, mixed\\>\\>, array given\\.$#"
count: 1
path: ../../../src/utils/Utils.php
-
message: "#^Parameter \\#2 \\$file of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects string\\|null, mixed given\\.$#"
count: 1

View File

@ -110,3 +110,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;
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\tools\compact_regions;
use pocketmine\utils\Utils;
use pocketmine\world\format\io\exception\CorruptedChunkException;
use pocketmine\world\format\io\region\CorruptedRegionException;
use pocketmine\world\format\io\region\RegionLoader;
@ -59,6 +60,7 @@ const SUPPORTED_EXTENSIONS = [
/**
* @param int[] $files
* @phpstan-param array<string, int> $files
* @phpstan-param-out array<string, int> $files
*/
function find_regions_recursive(string $dir, array &$files) : void{
$dirFiles = scandir($dir, SCANDIR_SORT_NONE);
@ -112,7 +114,7 @@ function main(array $argv) : int{
$corruptedFiles = [];
$doneCount = 0;
$totalCount = count($files);
foreach($files as $file => $size){
foreach(Utils::stringifyKeys($files) as $file => $size){
try{
$oldRegion = RegionLoader::loadExisting($file);
}catch(CorruptedRegionException $e){
@ -162,7 +164,7 @@ function main(array $argv) : int{
clearstatcache();
$newSize = 0;
foreach($files as $file => $oldSize){
foreach(Utils::stringifyKeys($files) as $file => $oldSize){
$newSize += file_exists($file) ? filesize($file) : 0;
}
$diff = $currentSize - $newSize;

View File

@ -198,12 +198,12 @@ class ParserPacketHandler extends PacketHandler{
$result = (array) ($object instanceof \JsonSerializable ? $object->jsonSerialize() : $object);
ksort($result, SORT_STRING);
foreach($result as $property => $value){
foreach(Utils::promoteKeys($result) as $property => $value){
if(is_object($value)){
$result[$property] = self::objectToOrderedArray($value);
}elseif(is_array($value)){
$array = [];
foreach($value as $k => $v){
foreach(Utils::promoteKeys($value) as $k => $v){
if(is_object($v)){
$array[$k] = self::objectToOrderedArray($v);
}else{
@ -224,7 +224,7 @@ class ParserPacketHandler extends PacketHandler{
}
if(is_array($object)){
$result = [];
foreach($object as $k => $v){
foreach(Utils::promoteKeys($object) as $k => $v){
$result[$k] = self::sort($v);
}
return $result;
@ -247,7 +247,7 @@ class ParserPacketHandler extends PacketHandler{
ksort($table, SORT_STRING);
file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n");
foreach($packet->levelSettings->experiments->getExperiments() as $name => $experiment){
foreach(Utils::promoteKeys($packet->levelSettings->experiments->getExperiments()) as $name => $experiment){
echo "Experiment \"$name\" is " . ($experiment ? "" : "not ") . "active\n";
}
return true;
@ -317,8 +317,8 @@ class ParserPacketHandler extends PacketHandler{
$char = ord("A");
$outputsByKey = [];
foreach($entry->getInput() as $x => $row){
foreach($row as $y => $ingredient){
foreach(Utils::promoteKeys($entry->getInput()) as $x => $row){
foreach(Utils::promoteKeys($row) as $y => $ingredient){
if($ingredient->getDescriptor() === null){
$shape[$x][$y] = " ";
}else{
@ -337,7 +337,7 @@ class ParserPacketHandler extends PacketHandler{
}
$unlockingIngredients = $entry->getUnlockingRequirement()->getUnlockingIngredients();
return new ShapedRecipeData(
array_map(fn(array $array) => implode('', $array), $shape),
array_map(fn(array $array) => implode('', array_values($array)), array_values($shape)),
$outputsByKey,
array_map(fn(ItemStack $output) => $this->itemStackToJson($output), $entry->getOutput()),
$entry->getBlockName(),