From 1e3a858de6758c37e8a25c83258f439c348edde8 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 20 Nov 2024 16:42:50 +0000 Subject: [PATCH 01/18] Add setUp and tearDown for event unit tests --- tests/phpunit/event/EventTest.php | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/event/EventTest.php b/tests/phpunit/event/EventTest.php index 5d5ace54d4..7410cc3bb0 100644 --- a/tests/phpunit/event/EventTest.php +++ b/tests/phpunit/event/EventTest.php @@ -33,15 +33,26 @@ final class EventTest extends TestCase{ - public function testHandlerInheritance() : void{ + private Plugin $mockPlugin; + private PluginManager $pluginManager; + + protected function setUp() : void{ + HandlerListManager::global()->unregisterAll(); + //TODO: this is a really bad hack and could break any time if PluginManager decides to access its Server field //we really need to make it possible to register events without a Plugin or Server context $mockServer = $this->createMock(Server::class); - $mockPlugin = self::createStub(Plugin::class); - $mockPlugin->method('isEnabled')->willReturn(true); + $this->mockPlugin = self::createStub(Plugin::class); + $this->mockPlugin->method('isEnabled')->willReturn(true); - $pluginManager = new PluginManager($mockServer, null); + $this->pluginManager = new PluginManager($mockServer, null); + } + public static function tearDownAfterClass() : void{ + HandlerListManager::global()->unregisterAll(); + } + + public function testHandlerInheritance() : void{ $expectedOrder = [ TestGrandchildEvent::class, TestChildEvent::class, @@ -50,13 +61,13 @@ public function testHandlerInheritance() : void{ $actualOrder = []; foreach($expectedOrder as $class){ - $pluginManager->registerEvent( + $this->pluginManager->registerEvent( $class, function(TestParentEvent $event) use (&$actualOrder, $class) : void{ $actualOrder[] = $class; }, EventPriority::NORMAL, - $mockPlugin + $this->mockPlugin ); } From 7c2ed7d8845e7d2ce0dd62c77cdac5dd53306b19 Mon Sep 17 00:00:00 2001 From: GameParrot <85067619+GameParrot@users.noreply.github.com> Date: Wed, 20 Nov 2024 21:22:10 -0500 Subject: [PATCH 02/18] Fix insta break check (#6528) --- src/player/Player.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/player/Player.php b/src/player/Player.php index 192e26a5f9..d203fe6a94 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -1764,7 +1764,7 @@ public function attackBlock(Vector3 $pos, int $face) : bool{ return true; } - if(!$this->isCreative() && !$block->getBreakInfo()->breaksInstantly()){ + if(!$this->isCreative() && !$target->getBreakInfo()->breaksInstantly()){ $this->blockBreakHandler = new SurvivalBlockBreakHandler($this, $pos, $target, $face, 16); } From 0070426e97fa1a188e47f552e996cfb3fecc17b7 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sat, 23 Nov 2024 19:07:24 +0000 Subject: [PATCH 03/18] auto-approve: only re-review if previous review was dismissed this avoids unnecessary spam when someone clicks "Update branch" on the PR. --- .github/workflows/team-pr-auto-approve.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/team-pr-auto-approve.yml b/.github/workflows/team-pr-auto-approve.yml index 405aafd8b8..f145812138 100644 --- a/.github/workflows/team-pr-auto-approve.yml +++ b/.github/workflows/team-pr-auto-approve.yml @@ -9,9 +9,10 @@ on: pull_request_target: types: - opened - - synchronize - reopened - ready_for_review + pull_request_review: + types: dismissed jobs: dispatch: @@ -35,4 +36,4 @@ jobs: token: ${{ steps.generate-token.outputs.token }} repository: ${{ github.repository_owner }}/RestrictedActions event-type: auto_approve_collaborator_pr - client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}" }' + client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}", "reviewer_id": "${{ github.event.review.user.id || 0 }}" }' From 5b72f202bfb1d6351d60b8e611245896cb3dff25 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sat, 23 Nov 2024 20:13:46 +0000 Subject: [PATCH 04/18] actions: automatically remove waiting label from PRs on synchronize there are probably other conditions where we'd want to remove this, but this will do for now. --- .github/workflows/pr-remove-waiting-label.yml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/pr-remove-waiting-label.yml diff --git a/.github/workflows/pr-remove-waiting-label.yml b/.github/workflows/pr-remove-waiting-label.yml new file mode 100644 index 0000000000..0e411fe1ae --- /dev/null +++ b/.github/workflows/pr-remove-waiting-label.yml @@ -0,0 +1,24 @@ +name: Remove waiting label from PRs + +on: + pull_request_target: + types: synchronize + +jobs: + delabel: + name: Remove label + runs-on: ubuntu-latest + + steps: + - name: Remove label + uses: actions/github-script@v7 + with: + github-token: ${{ github.token }} + script: | + const [owner, repo] = context.payload.repository.full_name.split('/'); + await github.rest.issues.removeLabel({ + owner: owner, + repo: repo, + issue_number: context.payload.number, + name: "Status: Waiting on Author", + }); From 7460e12b6affaf097a63edf35a77bb0cbee7799f Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sat, 23 Nov 2024 21:48:30 +0000 Subject: [PATCH 05/18] pr-remove-waiting-label: suppress failure on 404 errors this usually means the label wasn't on the PR in the first place --- .github/workflows/pr-remove-waiting-label.yml | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr-remove-waiting-label.yml b/.github/workflows/pr-remove-waiting-label.yml index 0e411fe1ae..eb46043bdd 100644 --- a/.github/workflows/pr-remove-waiting-label.yml +++ b/.github/workflows/pr-remove-waiting-label.yml @@ -16,9 +16,18 @@ jobs: github-token: ${{ github.token }} script: | const [owner, repo] = context.payload.repository.full_name.split('/'); - await github.rest.issues.removeLabel({ - owner: owner, - repo: repo, - issue_number: context.payload.number, - name: "Status: Waiting on Author", - }); + try { + await github.rest.issues.removeLabel({ + owner: owner, + repo: repo, + issue_number: context.payload.number, + name: "Status: Waiting on Author", + }); + } catch (error) { + if (error.status === 404) { + //probably label wasn't set on the issue + console.log('Failed to remove label (probably label isn\'t on the PR): ' + error.message); + } else { + throw error; + } + } From 8338ebaffdc1cb1ed14960ec27df16e14acab709 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Sun, 24 Nov 2024 15:14:34 +0100 Subject: [PATCH 06/18] Add generic types for TaskHandler (#6030) --- src/scheduler/Task.php | 7 ++++ src/scheduler/TaskHandler.php | 9 +++++ src/scheduler/TaskScheduler.php | 44 ++++++++++++++++++++-- src/timings/Timings.php | 5 +++ tests/phpstan/configs/actual-problems.neon | 2 +- 5 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/scheduler/Task.php b/src/scheduler/Task.php index bde405a637..db42b477ad 100644 --- a/src/scheduler/Task.php +++ b/src/scheduler/Task.php @@ -26,8 +26,12 @@ use pocketmine\utils\Utils; abstract class Task{ + /** @phpstan-var TaskHandler|null */ private ?TaskHandler $taskHandler = null; + /** + * @phpstan-return TaskHandler|null + */ final public function getHandler() : ?TaskHandler{ return $this->taskHandler; } @@ -36,6 +40,9 @@ public function getName() : string{ return Utils::getNiceClassName($this); } + /** + * @phpstan-param TaskHandler|null $taskHandler + */ final public function setHandler(?TaskHandler $taskHandler) : void{ if($this->taskHandler === null || $taskHandler === null){ $this->taskHandler = $taskHandler; diff --git a/src/scheduler/TaskHandler.php b/src/scheduler/TaskHandler.php index b90c992d92..985344b649 100644 --- a/src/scheduler/TaskHandler.php +++ b/src/scheduler/TaskHandler.php @@ -26,6 +26,9 @@ use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; +/** + * @template TTask of Task + */ class TaskHandler{ protected int $nextRun; @@ -36,6 +39,9 @@ class TaskHandler{ private string $taskName; private string $ownerName; + /** + * @phpstan-param TTask $task + */ public function __construct( protected Task $task, protected int $delay = -1, @@ -66,6 +72,9 @@ public function setNextRun(int $ticks) : void{ $this->nextRun = $ticks; } + /** + * @phpstan-return TTask + */ public function getTask() : Task{ return $this->task; } diff --git a/src/scheduler/TaskScheduler.php b/src/scheduler/TaskScheduler.php index f2b7c48467..3e2b089c6c 100644 --- a/src/scheduler/TaskScheduler.php +++ b/src/scheduler/TaskScheduler.php @@ -33,12 +33,12 @@ class TaskScheduler{ private bool $enabled = true; - /** @phpstan-var ReversePriorityQueue */ + /** @phpstan-var ReversePriorityQueue> */ protected ReversePriorityQueue $queue; /** * @var ObjectSet|TaskHandler[] - * @phpstan-var ObjectSet + * @phpstan-var ObjectSet> */ protected ObjectSet $tasks; @@ -51,18 +51,42 @@ public function __construct( $this->tasks = new ObjectSet(); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TTask $task + * + * @phpstan-return TaskHandler + */ public function scheduleTask(Task $task) : TaskHandler{ return $this->addTask($task, -1, -1); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TTask $task + * + * @phpstan-return TaskHandler + */ public function scheduleDelayedTask(Task $task, int $delay) : TaskHandler{ return $this->addTask($task, $delay, -1); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TTask $task + * + * @phpstan-return TaskHandler + */ public function scheduleRepeatingTask(Task $task, int $period) : TaskHandler{ return $this->addTask($task, -1, $period); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TTask $task + * + * @phpstan-return TaskHandler + */ public function scheduleDelayedRepeatingTask(Task $task, int $delay, int $period) : TaskHandler{ return $this->addTask($task, $delay, $period); } @@ -77,10 +101,19 @@ public function cancelAllTasks() : void{ } } + /** + * @phpstan-param TaskHandler $task + */ public function isQueued(TaskHandler $task) : bool{ return $this->tasks->contains($task); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TTask $task + * + * @phpstan-return TaskHandler + */ private function addTask(Task $task, int $delay, int $period) : TaskHandler{ if(!$this->enabled){ throw new \LogicException("Tried to schedule task to disabled scheduler"); @@ -99,6 +132,11 @@ private function addTask(Task $task, int $delay, int $period) : TaskHandler{ return $this->handle(new TaskHandler($task, $delay, $period, $this->owner)); } + /** + * @phpstan-template TTask of Task + * @phpstan-param TaskHandler $handler + * @phpstan-return TaskHandler + */ private function handle(TaskHandler $handler) : TaskHandler{ if($handler->isDelayed()){ $nextRun = $this->currentTick + $handler->getDelay(); @@ -128,7 +166,7 @@ public function mainThreadHeartbeat(int $currentTick) : void{ } $this->currentTick = $currentTick; while($this->isReady($this->currentTick)){ - /** @var TaskHandler $task */ + /** @phpstan-var TaskHandler $task */ $task = $this->queue->extract(); if($task->isCancelled()){ $this->tasks->remove($task); diff --git a/src/timings/Timings.php b/src/timings/Timings.php index 563af69bff..77f8efee64 100644 --- a/src/timings/Timings.php +++ b/src/timings/Timings.php @@ -30,6 +30,7 @@ use pocketmine\network\mcpe\protocol\ServerboundPacket; use pocketmine\player\Player; use pocketmine\scheduler\AsyncTask; +use pocketmine\scheduler\Task; use pocketmine\scheduler\TaskHandler; use function get_class; use function str_starts_with; @@ -192,6 +193,10 @@ public static function init() : void{ } + /** + * @template TTask of Task + * @phpstan-param TaskHandler $task + */ public static function getScheduledTaskTimings(TaskHandler $task, int $period) : TimingsHandler{ self::init(); $name = "Task: " . $task->getTaskName(); diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index e778cf0047..f37d238784 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -801,7 +801,7 @@ parameters: path: ../../../src/scheduler/BulkCurlTask.php - - message: "#^Cannot call method getNextRun\\(\\) on array\\\\|int\\|pocketmine\\\\scheduler\\\\TaskHandler\\.$#" + message: "#^Cannot call method getNextRun\\(\\) on array\\\\>\\|int\\|pocketmine\\\\scheduler\\\\TaskHandler\\\\.$#" count: 1 path: ../../../src/scheduler/TaskScheduler.php From a5f607138c491ef55431f5d20baf31d03be59769 Mon Sep 17 00:00:00 2001 From: zSALLAZAR <59490940+zSALLAZAR@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:01:26 +0100 Subject: [PATCH 07/18] Implement Ice Bomb (#5452) Co-authored-by: Dylan K. Taylor --- .../ItemSerializerDeserializerRegistrar.php | 1 + src/entity/EntityFactory.php | 5 ++ src/entity/projectile/IceBomb.php | 86 +++++++++++++++++++ src/item/IceBomb.php | 48 +++++++++++ src/item/ItemTypeIds.php | 3 +- src/item/StringToItemParser.php | 1 + src/item/VanillaItems.php | 2 + src/world/sound/IceBombHitSound.php | 34 ++++++++ 8 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 src/entity/projectile/IceBomb.php create mode 100644 src/item/IceBomb.php create mode 100644 src/world/sound/IceBombHitSound.php diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index 7803cea5c7..8240bf0634 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -266,6 +266,7 @@ private function register1to1ItemMappings() : void{ $this->map1to1Item(Ids::HONEY_BOTTLE, Items::HONEY_BOTTLE()); $this->map1to1Item(Ids::HONEYCOMB, Items::HONEYCOMB()); $this->map1to1Item(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE()); + $this->map1to1Item(Ids::ICE_BOMB, Items::ICE_BOMB()); $this->map1to1Item(Ids::INK_SAC, Items::INK_SAC()); $this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE()); $this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS()); diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php index 3d53233ab7..03d9c03e6b 100644 --- a/src/entity/EntityFactory.php +++ b/src/entity/EntityFactory.php @@ -43,6 +43,7 @@ use pocketmine\entity\projectile\Egg; use pocketmine\entity\projectile\EnderPearl; use pocketmine\entity\projectile\ExperienceBottle; +use pocketmine\entity\projectile\IceBomb; use pocketmine\entity\projectile\Snowball; use pocketmine\entity\projectile\SplashPotion; use pocketmine\item\Item; @@ -120,6 +121,10 @@ public function __construct(){ return new FallingBlock(Helper::parseLocation($nbt, $world), FallingBlock::parseBlockNBT(RuntimeBlockStateRegistry::getInstance(), $nbt), $nbt); }, ['FallingSand', 'minecraft:falling_block']); + $this->register(IceBomb::class, function(World $world, CompoundTag $nbt) : IceBomb{ + return new IceBomb(Helper::parseLocation($nbt, $world), null, $nbt); + }, ['minecraft:ice_bomb']); + $this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{ $itemTag = $nbt->getCompoundTag(ItemEntity::TAG_ITEM); if($itemTag === null){ diff --git a/src/entity/projectile/IceBomb.php b/src/entity/projectile/IceBomb.php new file mode 100644 index 0000000000..69a5d46a2c --- /dev/null +++ b/src/entity/projectile/IceBomb.php @@ -0,0 +1,86 @@ +getTypeId() === BlockTypeIds::WATER){ + $pos = $block->getPosition(); + + return AxisAlignedBB::one()->offset($pos->x, $pos->y, $pos->z)->calculateIntercept($start, $end); + } + + return parent::calculateInterceptWithBlock($block, $start, $end); + } + + protected function onHit(ProjectileHitEvent $event) : void{ + $world = $this->getWorld(); + $pos = $this->location; + + $world->addSound($pos, new IceBombHitSound()); + $itemBreakParticle = new ItemBreakParticle(VanillaItems::ICE_BOMB()); + for($i = 0; $i < 6; ++$i){ + $world->addParticle($pos, $itemBreakParticle); + } + } + + protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{ + parent::onHitBlock($blockHit, $hitResult); + + $pos = $blockHit->getPosition(); + $world = $pos->getWorld(); + $posX = $pos->getFloorX(); + $posY = $pos->getFloorY(); + $posZ = $pos->getFloorZ(); + + $ice = VanillaBlocks::ICE(); + for($x = $posX - 1; $x <= $posX + 1; $x++){ + for($y = $posY - 1; $y <= $posY + 1; $y++){ + for($z = $posZ - 1; $z <= $posZ + 1; $z++){ + if($world->getBlockAt($x, $y, $z)->getTypeId() === BlockTypeIds::WATER){ + $world->setBlockAt($x, $y, $z, $ice); + } + } + } + } + } +} diff --git a/src/item/IceBomb.php b/src/item/IceBomb.php new file mode 100644 index 0000000000..fbc9f24a2f --- /dev/null +++ b/src/item/IceBomb.php @@ -0,0 +1,48 @@ +register("honey_bottle", fn() => Items::HONEY_BOTTLE()); $result->register("host_armor_trim_smithing_template", fn() => Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("honeycomb", fn() => Items::HONEYCOMB()); + $result->register("ice_bomb", fn() => Items::ICE_BOMB()); $result->register("ink_sac", fn() => Items::INK_SAC()); $result->register("iron_axe", fn() => Items::IRON_AXE()); $result->register("iron_boots", fn() => Items::IRON_BOOTS()); diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index 5899b63576..dcf59daf6b 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -192,6 +192,7 @@ * @method static Item HONEYCOMB() * @method static HoneyBottle HONEY_BOTTLE() * @method static Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE() + * @method static IceBomb ICE_BOMB() * @method static Item INK_SAC() * @method static Axe IRON_AXE() * @method static Armor IRON_BOOTS() @@ -504,6 +505,7 @@ protected static function setup() : void{ self::register("heart_of_the_sea", fn(IID $id) => new Item($id, "Heart of the Sea")); self::register("honey_bottle", fn(IID $id) => new HoneyBottle($id, "Honey Bottle")); self::register("honeycomb", fn(IID $id) => new Item($id, "Honeycomb")); + self::register("ice_bomb", fn(IID $id) => new IceBomb($id, "Ice Bomb")); self::register("ink_sac", fn(IID $id) => new Item($id, "Ink Sac")); self::register("iron_ingot", fn(IID $id) => new Item($id, "Iron Ingot")); self::register("iron_nugget", fn(IID $id) => new Item($id, "Iron Nugget")); diff --git a/src/world/sound/IceBombHitSound.php b/src/world/sound/IceBombHitSound.php new file mode 100644 index 0000000000..7dcdeebf67 --- /dev/null +++ b/src/world/sound/IceBombHitSound.php @@ -0,0 +1,34 @@ + Date: Sun, 24 Nov 2024 23:49:21 +0000 Subject: [PATCH 08/18] Candle: fix extinguish logic closes #5983 --- src/block/CakeWithCandle.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/block/CakeWithCandle.php b/src/block/CakeWithCandle.php index 4479faee10..380d080c5d 100644 --- a/src/block/CakeWithCandle.php +++ b/src/block/CakeWithCandle.php @@ -52,6 +52,9 @@ public function getCandle() : Candle{ } public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ + if($this->lit && $face !== Facing::UP){ + return true; + } if($this->onInteractCandle($item, $face, $clickVector, $player, $returnedItems)){ return true; } From aef4fa71747b1773541bc7a294c98b8a40565127 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 23:50:30 +0000 Subject: [PATCH 09/18] Remove unused variable --- src/network/mcpe/InventoryManager.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index e4c303121f..70c427aa17 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -592,7 +592,6 @@ public function syncContents(Inventory $inventory) : void{ $info = $this->trackItemStack($entry, $slot, $itemStack, null); $contents[] = new ItemStackWrapper($info->getStackId(), $itemStack); } - $clearSlotWrapper = new ItemStackWrapper(0, ItemStack::null()); if($entry->complexSlotMap !== null){ foreach($contents as $slotId => $info){ $packetSlot = $entry->complexSlotMap->mapCoreToNet($slotId) ?? null; From 5325ecee373bbba5fb9e4a42ffbe38adb8655e6c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Nov 2024 14:30:58 +0000 Subject: [PATCH 10/18] Deal with a whole lot of PHPStan suppressed key casting errors closes #6534 --- build/generate-biome-ids.php | 2 +- ...enerate-pocketmine-yml-property-consts.php | 2 +- build/generate-registry-annotations.php | 4 ++- src/MemoryManager.php | 10 +++---- src/Server.php | 13 +++++++-- src/block/RuntimeBlockStateRegistry.php | 1 + src/block/inventory/EnchantInventory.php | 5 +++- src/block/utils/SignText.php | 7 ++++- src/command/Command.php | 13 +++++++-- src/command/SimpleCommandMap.php | 11 ++++++-- src/crafting/CraftingManager.php | 6 ++-- .../CraftingManagerFromDataHelper.php | 2 +- src/crafting/CraftingRecipe.php | 2 ++ src/crafting/ShapedRecipe.php | 28 ++++++++++++++----- src/crafting/ShapelessRecipe.php | 17 +++++++---- src/crash/CrashDumpData.php | 10 +++++-- src/data/bedrock/ItemTagToIdMap.php | 2 +- src/data/bedrock/LegacyToStringIdMap.php | 3 +- .../item/upgrade/R12ItemIdToBlockIdMap.php | 2 +- src/entity/Entity.php | 5 +++- src/event/HandlerList.php | 5 +++- src/inventory/BaseInventory.php | 7 +++-- src/inventory/CreativeInventory.php | 6 +++- .../transaction/CraftingTransaction.php | 19 +++++++++++-- .../transaction/InventoryTransaction.php | 23 +++++++++------ src/item/ItemEnchantmentHandlingTrait.php | 6 +++- src/item/LegacyStringToItemParser.php | 3 +- src/lang/Language.php | 4 +-- src/lang/Translatable.php | 4 ++- src/network/NetworkSessionManager.php | 10 +++++-- src/network/mcpe/InventoryManager.php | 1 + src/network/mcpe/NetworkSession.php | 2 +- .../mcpe/StandardPacketBroadcaster.php | 1 - src/network/mcpe/cache/ChunkCache.php | 5 +++- .../convert/BlockStateDictionaryEntry.php | 5 +++- .../ItemTypeDictionaryFromDataHelper.php | 3 +- .../mcpe/handler/InGamePacketHandler.php | 2 +- .../mcpe/handler/ItemStackRequestExecutor.php | 3 +- .../mcpe/handler/LoginPacketHandler.php | 2 +- src/permission/BanList.php | 8 ++++-- src/permission/PermissionAttachment.php | 10 +++++-- src/permission/PermissionManager.php | 13 +++++++-- src/plugin/ApiVersion.php | 4 +-- src/plugin/PluginDescription.php | 5 ++-- src/plugin/PluginGraylist.php | 3 +- src/plugin/PluginManager.php | 13 +++++++-- src/promise/Promise.php | 3 +- src/resourcepacks/ResourcePackManager.php | 3 +- src/utils/Utils.php | 15 +++++++++- src/world/BlockTransaction.php | 5 +++- src/world/WorldManager.php | 6 +++- src/world/format/SubChunk.php | 10 ++++--- src/world/format/io/BaseWorldProvider.php | 2 ++ src/world/format/io/ChunkData.php | 15 ++++++++-- .../format/io/region/RegionGarbageMap.php | 10 +++---- src/world/format/io/region/RegionLoader.php | 8 ++++-- .../format/io/region/RegionWorldProvider.php | 5 +++- src/world/generator/PopulationTask.php | 6 ++-- tests/phpstan/configs/phpstan-bugs.neon | 5 ++++ .../rules/UnsafeForeachArrayOfStringRule.php | 26 ++++++++++++++--- tests/phpunit/block/BlockTest.php | 2 +- tests/phpunit/item/ItemTest.php | 1 - .../utils/CloningRegistryTraitTest.php | 2 +- tests/phpunit/utils/TestCloningRegistry.php | 6 +++- tools/compact-regions.php | 6 ++-- tools/generate-bedrock-data-from-packets.php | 14 +++++----- 66 files changed, 338 insertions(+), 124 deletions(-) diff --git a/build/generate-biome-ids.php b/build/generate-biome-ids.php index f36591fe4d..56f871f431 100644 --- a/build/generate-biome-ids.php +++ b/build/generate-biome-ids.php @@ -122,7 +122,7 @@ private function __construct(){ 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"); } diff --git a/build/generate-pocketmine-yml-property-consts.php b/build/generate-pocketmine-yml-property-consts.php index dcc574f8ab..f90f3fc667 100644 --- a/build/generate-pocketmine-yml-property-consts.php +++ b/build/generate-pocketmine-yml-property-consts.php @@ -42,7 +42,7 @@ * @phpstan-param-out array $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)); diff --git a/build/generate-registry-annotations.php b/build/generate-registry-annotations.php index bcdaee4eba..35d0893954 100644 --- a/build/generate-registry-annotations.php +++ b/build/generate-registry-annotations.php @@ -23,6 +23,7 @@ namespace pocketmine\build\update_registry_annotations; +use pocketmine\utils\Utils; use function basename; use function class_exists; use function count; @@ -48,6 +49,7 @@ /** * @param object[] $members + * @phpstan-param array $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(); diff --git a/src/MemoryManager.php b/src/MemoryManager.php index 96e3bf49cd..4308167d37 100644 --- a/src/MemoryManager.php +++ b/src/MemoryManager.php @@ -332,7 +332,7 @@ public static function dumpMemory(mixed $startingObject, string $outputFolder, i 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 @@ public static function dumpMemory(mixed $startingObject, string $outputFolder, i '_SESSION' => true ]; - foreach($GLOBALS as $varName => $value){ + foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){ if(isset($ignoredGlobals[$varName])){ continue; } @@ -376,7 +376,7 @@ public static function dumpMemory(mixed $startingObject, string $outputFolder, i $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 @@ public static function dumpMemory(mixed $startingObject, string $outputFolder, i $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 @@ private static function continueDump(mixed $from, array &$objects, array &$refCo } $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), diff --git a/src/Server.php b/src/Server.php index a34349bb5c..074088068b 100644 --- a/src/Server.php +++ b/src/Server.php @@ -736,12 +736,15 @@ public function getOps() : Config{ /** * @return string[][] + * @phpstan-return array> */ 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 $commands = []; if(is_array($value)){ $commands = $value; @@ -749,7 +752,7 @@ public function getCommandAliases() : array{ $commands[] = (string) $value; } - $result[$key] = $commands; + $result[(string) $key] = $commands; } } @@ -1094,7 +1097,11 @@ private function startupPrepareWorlds() : bool{ $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)){ diff --git a/src/block/RuntimeBlockStateRegistry.php b/src/block/RuntimeBlockStateRegistry.php index 78be658bc1..a8ece7722c 100644 --- a/src/block/RuntimeBlockStateRegistry.php +++ b/src/block/RuntimeBlockStateRegistry.php @@ -132,6 +132,7 @@ public function fromStateId(int $stateId) : Block{ /** * @return Block[] + * @phpstan-return array */ public function getAllKnownStates() : array{ return $this->fullList; diff --git a/src/block/inventory/EnchantInventory.php b/src/block/inventory/EnchantInventory.php index b726dbedf3..6dffbad32b 100644 --- a/src/block/inventory/EnchantInventory.php +++ b/src/block/inventory/EnchantInventory.php @@ -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 + */ private array $options = []; public function __construct(Position $holder){ diff --git a/src/block/utils/SignText.php b/src/block/utils/SignText.php index 51285d2679..a7e8759b8d 100644 --- a/src/block/utils/SignText.php +++ b/src/block/utils/SignText.php @@ -36,13 +36,17 @@ 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 @@ public static function fromBlob(string $blob, ?Color $baseColor = null, bool $gl * 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; diff --git a/src/command/Command.php b/src/command/Command.php index 02bae60d9e..4c2c6815b2 100644 --- a/src/command/Command.php +++ b/src/command/Command.php @@ -44,10 +44,16 @@ abstract class Command{ private string $nextLabel; private string $label; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $aliases = []; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $activeAliases = []; private ?CommandMap $commandMap = null; @@ -62,6 +68,7 @@ abstract class Command{ /** * @param string[] $aliases + * @phpstan-param list $aliases */ public function __construct(string $name, Translatable|string $description = "", Translatable|string|null $usageMessage = null, array $aliases = []){ $this->name = $name; @@ -182,6 +189,7 @@ public function isRegistered() : bool{ /** * @return string[] + * @phpstan-return list */ public function getAliases() : array{ return $this->activeAliases; @@ -201,6 +209,7 @@ public function getUsage() : Translatable|string{ /** * @param string[] $aliases + * @phpstan-param list $aliases */ public function setAliases(array $aliases) : void{ $this->aliases = $aliases; diff --git a/src/command/SimpleCommandMap.php b/src/command/SimpleCommandMap.php index 1268e715b7..06367994a0 100644 --- a/src/command/SimpleCommandMap.php +++ b/src/command/SimpleCommandMap.php @@ -70,6 +70,7 @@ 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; @@ -80,7 +81,10 @@ class SimpleCommandMap implements CommandMap{ - /** @var Command[] */ + /** + * @var Command[] + * @phpstan-var array + */ protected array $knownCommands = []; public function __construct(private Server $server){ @@ -169,7 +173,7 @@ public function register(string $fallbackPrefix, Command $command, ?string $labe } 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]); } @@ -237,6 +241,7 @@ public function getCommand(string $name) : ?Command{ /** * @return Command[] + * @phpstan-return array */ public function getCommands() : array{ return $this->knownCommands; @@ -245,7 +250,7 @@ public function getCommands() : array{ 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; diff --git a/src/crafting/CraftingManager.php b/src/crafting/CraftingManager.php index c7c0b10c64..ff2be69268 100644 --- a/src/crafting/CraftingManager.php +++ b/src/crafting/CraftingManager.php @@ -110,14 +110,15 @@ public static function sort(Item $i1, Item $i2) : int{ /** * @param Item[] $items + * @phpstan-param list $items * * @return Item[] + * @phpstan-return list */ 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 @@ private static function pack(array $items) : array{ /** * @param Item[] $outputs + * @phpstan-param list $outputs */ private static function hashOutputs(array $outputs) : string{ $outputs = self::pack($outputs); diff --git a/src/crafting/CraftingManagerFromDataHelper.php b/src/crafting/CraftingManagerFromDataHelper.php index 616c2a4bd3..3df6bfb62a 100644 --- a/src/crafting/CraftingManagerFromDataHelper.php +++ b/src/crafting/CraftingManagerFromDataHelper.php @@ -193,7 +193,7 @@ private static function loadJsonObjectIntoModel(\JsonMapper $mapper, string $mod */ 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)); } diff --git a/src/crafting/CraftingRecipe.php b/src/crafting/CraftingRecipe.php index 02e6fce040..d7342aae31 100644 --- a/src/crafting/CraftingRecipe.php +++ b/src/crafting/CraftingRecipe.php @@ -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 */ public function getIngredientList() : array; @@ -37,6 +38,7 @@ public function getIngredientList() : array; * Returns a list of results this recipe will produce when the inputs in the given crafting grid are consumed. * * @return Item[] + * @phpstan-return list */ public function getResultsFor(CraftingGrid $grid) : array; diff --git a/src/crafting/ShapedRecipe.php b/src/crafting/ShapedRecipe.php index f13ba3ae74..4c40eb0f50 100644 --- a/src/crafting/ShapedRecipe.php +++ b/src/crafting/ShapedRecipe.php @@ -32,11 +32,20 @@ use function strlen; class ShapedRecipe implements CraftingRecipe{ - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ private array $shape = []; - /** @var RecipeIngredient[] char => RecipeIngredient map */ + /** + * @var RecipeIngredient[] char => RecipeIngredient map + * @phpstan-var array + */ private array $ingredientList = []; - /** @var Item[] */ + /** + * @var Item[] + * @phpstan-var list + */ 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 $shape + * @phpstan-param array $ingredients + * @phpstan-param list $results */ public function __construct(array $shape, array $ingredients, array $results){ $this->height = count($shape); @@ -84,7 +97,7 @@ public function __construct(array $shape, array $ingredients, array $results){ $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 @@ public function getHeight() : int{ /** * @return Item[] + * @phpstan-return list */ public function getResults() : array{ return Utils::cloneObjectArray($this->results); @@ -112,6 +126,7 @@ public function getResults() : array{ /** * @return Item[] + * @phpstan-return list */ public function getResultsFor(CraftingGrid $grid) : array{ return $this->getResults(); @@ -119,6 +134,7 @@ public function getResultsFor(CraftingGrid $grid) : array{ /** * @return (RecipeIngredient|null)[][] + * @phpstan-return list> */ public function getIngredientMap() : array{ $ingredients = []; @@ -132,9 +148,6 @@ public function getIngredientMap() : array{ return $ingredients; } - /** - * @return RecipeIngredient[] - */ public function getIngredientList() : array{ $ingredients = []; @@ -157,6 +170,7 @@ public function getIngredient(int $x, int $y) : ?RecipeIngredient{ /** * Returns an array of strings containing characters representing the recipe's shape. * @return string[] + * @phpstan-return list */ public function getShape() : array{ return $this->shape; diff --git a/src/crafting/ShapelessRecipe.php b/src/crafting/ShapelessRecipe.php index a9e1f023c5..7a4a22fda7 100644 --- a/src/crafting/ShapelessRecipe.php +++ b/src/crafting/ShapelessRecipe.php @@ -28,15 +28,24 @@ use function count; class ShapelessRecipe implements CraftingRecipe{ - /** @var RecipeIngredient[] */ + /** + * @var RecipeIngredient[] + * @phpstan-var list + */ private array $ingredients = []; - /** @var Item[] */ + /** + * @var Item[] + * @phpstan-var list + */ 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 $ingredients + * @phpstan-param list $results */ public function __construct(array $ingredients, array $results, ShapelessRecipeType $type){ $this->type = $type; @@ -50,6 +59,7 @@ public function __construct(array $ingredients, array $results, ShapelessRecipeT /** * @return Item[] + * @phpstan-return list */ public function getResults() : array{ return Utils::cloneObjectArray($this->results); @@ -63,9 +73,6 @@ public function getType() : ShapelessRecipeType{ return $this->type; } - /** - * @return RecipeIngredient[] - */ public function getIngredientList() : array{ return $this->ingredients; } diff --git a/src/crash/CrashDumpData.php b/src/crash/CrashDumpData.php index b71e6f405b..f8a20abd5b 100644 --- a/src/crash/CrashDumpData.php +++ b/src/crash/CrashDumpData.php @@ -43,7 +43,10 @@ final class CrashDumpData implements \JsonSerializable{ public string $plugin = ""; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var array + */ public array $code = []; /** @var string[] */ @@ -55,7 +58,10 @@ final class CrashDumpData implements \JsonSerializable{ */ public array $plugins = []; - /** @var string[] */ + /** + * @var string[] + * @phpstan-var list + */ public array $parameters = []; public string $serverDotProperties = ""; diff --git a/src/data/bedrock/ItemTagToIdMap.php b/src/data/bedrock/ItemTagToIdMap.php index 84f2ff451f..af9cb3ce55 100644 --- a/src/data/bedrock/ItemTagToIdMap.php +++ b/src/data/bedrock/ItemTagToIdMap.php @@ -48,7 +48,7 @@ private static function make() : self{ 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"); } diff --git a/src/data/bedrock/LegacyToStringIdMap.php b/src/data/bedrock/LegacyToStringIdMap.php index 2b5375db23..1a92f2b6b8 100644 --- a/src/data/bedrock/LegacyToStringIdMap.php +++ b/src/data/bedrock/LegacyToStringIdMap.php @@ -25,6 +25,7 @@ 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 @@ public function __construct(string $file){ 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"); } diff --git a/src/data/bedrock/item/upgrade/R12ItemIdToBlockIdMap.php b/src/data/bedrock/item/upgrade/R12ItemIdToBlockIdMap.php index 2aac8de64e..19cfe6c78d 100644 --- a/src/data/bedrock/item/upgrade/R12ItemIdToBlockIdMap.php +++ b/src/data/bedrock/item/upgrade/R12ItemIdToBlockIdMap.php @@ -56,7 +56,7 @@ private static function make() : self{ } $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"); } diff --git a/src/entity/Entity.php b/src/entity/Entity.php index c55a8716ca..3d791ebca6 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -102,7 +102,10 @@ public static function nextRuntimeId() : int{ return self::$entityCount++; } - /** @var Player[] */ + /** + * @var Player[] + * @phpstan-var array + */ protected array $hasSpawned = []; protected int $id; diff --git a/src/event/HandlerList.php b/src/event/HandlerList.php index 2072cd5226..e7d229a3ff 100644 --- a/src/event/HandlerList.php +++ b/src/event/HandlerList.php @@ -30,7 +30,10 @@ use const SORT_NUMERIC; class HandlerList{ - /** @var RegisteredListener[][] */ + /** + * @var RegisteredListener[][] + * @phpstan-var array> + */ private array $handlerSlots = []; /** @var RegisteredListenerCache[] */ diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 522c827a4b..0d5d1ffe60 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -41,7 +41,10 @@ */ abstract class BaseInventory implements Inventory, SlotValidatedInventory{ protected int $maxStackSize = Inventory::MAX_STACK; - /** @var Player[] */ + /** + * @var Player[] + * @phpstan-var array + */ protected array $viewers = []; /** * @var InventoryListener[]|ObjectSet @@ -286,8 +289,6 @@ public function remove(Item $item) : void{ } public function removeItem(Item ...$slots) : array{ - /** @var Item[] $searchItems */ - /** @var Item[] $slots */ $searchItems = []; foreach($slots as $slot){ if(!$slot->isNull()){ diff --git a/src/inventory/CreativeInventory.php b/src/inventory/CreativeInventory.php index 7502a6785f..57e5cbb4e6 100644 --- a/src/inventory/CreativeInventory.php +++ b/src/inventory/CreativeInventory.php @@ -36,7 +36,10 @@ final class CreativeInventory{ use SingletonTrait; use DestructorCallbackTrait; - /** @var Item[] */ + /** + * @var Item[] + * @phpstan-var array + */ private array $creative = []; /** @phpstan-var ObjectSet<\Closure() : void> */ @@ -69,6 +72,7 @@ public function clear() : void{ /** * @return Item[] + * @phpstan-return array */ public function getAll() : array{ return Utils::cloneObjectArray($this->creative); diff --git a/src/inventory/transaction/CraftingTransaction.php b/src/inventory/transaction/CraftingTransaction.php index 2ae231b6e8..9e3c505520 100644 --- a/src/inventory/transaction/CraftingTransaction.php +++ b/src/inventory/transaction/CraftingTransaction.php @@ -57,9 +57,15 @@ class CraftingTransaction extends InventoryTransaction{ protected ?CraftingRecipe $recipe = null; protected ?int $repetitions = null; - /** @var Item[] */ + /** + * @var Item[] + * @phpstan-var list + */ protected array $inputs = []; - /** @var Item[] */ + /** + * @var Item[] + * @phpstan-var list + */ protected array $outputs = []; private CraftingManager $craftingManager; @@ -74,6 +80,9 @@ public function __construct(Player $source, CraftingManager $craftingManager, ar /** * @param Item[] $providedItems * @return Item[] + * + * @phpstan-param list $providedItems + * @phpstan-return list */ private static function packItems(array $providedItems) : array{ $packedProvidedItems = []; @@ -94,6 +103,9 @@ private static function packItems(array $providedItems) : array{ /** * @param Item[] $providedItems * @param RecipeIngredient[] $recipeIngredients + * + * @phpstan-param list $providedItems + * @phpstan-param list $recipeIngredients */ public static function matchIngredients(array $providedItems, array $recipeIngredients, int $expectedIterations) : void{ if(count($recipeIngredients) === 0){ @@ -172,6 +184,9 @@ public static function matchIngredients(array $providedItems, array $recipeIngre * @param Item[] $txItems * @param Item[] $recipeItems * + * @phpstan-param list $txItems + * @phpstan-param list $recipeItems + * * @throws TransactionValidationException */ protected function matchOutputs(array $txItems, array $recipeItems) : int{ diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php index 2ca00f910c..b3465a8dfb 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -29,6 +29,7 @@ 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 @@ class InventoryTransaction{ protected bool $hasExecuted = false; - /** @var Inventory[] */ + /** + * @var Inventory[] + * @phpstan-var array + */ protected array $inventories = []; - /** @var InventoryAction[] */ + /** + * @var InventoryAction[] + * @phpstan-var array + */ protected array $actions = []; /** @@ -81,6 +88,7 @@ public function getSource() : Player{ /** * @return Inventory[] + * @phpstan-return array */ public function getInventories() : array{ return $this->inventories; @@ -93,6 +101,7 @@ public function getInventories() : array{ * significance and should not be relied on. * * @return InventoryAction[] + * @phpstan-return array */ public function getActions() : array{ return $this->actions; @@ -133,8 +142,8 @@ public function addInventory(Inventory $inventory) : void{ /** * @param Item[] $needItems * @param Item[] $haveItems - * @phpstan-param-out Item[] $needItems - * @phpstan-param-out Item[] $haveItems + * @phpstan-param-out list $needItems + * @phpstan-param-out list $haveItems * * @throws TransactionValidationException */ @@ -188,11 +197,8 @@ protected function matchItems(array &$needItems, array &$haveItems) : void{ * 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 @@ protected function squashDuplicateSlotChanges() : void{ } } - 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 @@ protected function squashDuplicateSlotChanges() : void{ /** * @param SlotChangeAction[] $possibleActions + * @phpstan-param list $possibleActions */ protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{ assert(count($possibleActions) > 0); diff --git a/src/item/ItemEnchantmentHandlingTrait.php b/src/item/ItemEnchantmentHandlingTrait.php index d8302e7b36..10da7dd760 100644 --- a/src/item/ItemEnchantmentHandlingTrait.php +++ b/src/item/ItemEnchantmentHandlingTrait.php @@ -33,7 +33,10 @@ * The primary purpose of this trait is providing scope isolation for the methods it contains. */ trait ItemEnchantmentHandlingTrait{ - /** @var EnchantmentInstance[] */ + /** + * @var EnchantmentInstance[] + * @phpstan-var array + */ protected array $enchantments = []; public function hasEnchantments() : bool{ @@ -79,6 +82,7 @@ public function addEnchantment(EnchantmentInstance $enchantment) : self{ /** * @return EnchantmentInstance[] + * @phpstan-return array */ public function getEnchantments() : array{ return $this->enchantments; diff --git a/src/item/LegacyStringToItemParser.php b/src/item/LegacyStringToItemParser.php index a52b6c716e..6969190d5f 100644 --- a/src/item/LegacyStringToItemParser.php +++ b/src/item/LegacyStringToItemParser.php @@ -29,6 +29,7 @@ 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 @@ private static function make() : self{ $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); } diff --git a/src/lang/Language.php b/src/lang/Language.php index a871c820ae..29f28917d8 100644 --- a/src/lang/Language.php +++ b/src/lang/Language.php @@ -147,7 +147,7 @@ public function translateString(string $str, array $params = [], ?string $onlyPr $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 @@ public function translate(Translatable $c) : string{ $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); } diff --git a/src/lang/Translatable.php b/src/lang/Translatable.php index 827a11657b..8dee8f4773 100644 --- a/src/lang/Translatable.php +++ b/src/lang/Translatable.php @@ -23,6 +23,8 @@ namespace pocketmine\lang; +use pocketmine\utils\Utils; + final class Translatable{ /** @var string[]|Translatable[] $params */ protected array $params = []; @@ -34,7 +36,7 @@ public function __construct( 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{ diff --git a/src/network/NetworkSessionManager.php b/src/network/NetworkSessionManager.php index d8ff7fe038..aecbc646d9 100644 --- a/src/network/NetworkSessionManager.php +++ b/src/network/NetworkSessionManager.php @@ -30,10 +30,16 @@ class NetworkSessionManager{ - /** @var NetworkSession[] */ + /** + * @var NetworkSession[] + * @phpstan-var array + */ private array $sessions = []; - /** @var NetworkSession[] */ + /** + * @var NetworkSession[] + * @phpstan-var array + */ private array $pendingLoginSessions = []; /** diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index 70c427aa17..7df8c734be 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -695,6 +695,7 @@ public function syncCreative() : void{ /** * @param EnchantingOption[] $options + * @phpstan-param list $options */ public function syncEnchantingTableOptions(array $options) : void{ $protocolOptions = []; diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 0eee71e0ee..78b11e27ed 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -1092,7 +1092,7 @@ public function syncAdventureSettings() : void{ 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; } diff --git a/src/network/mcpe/StandardPacketBroadcaster.php b/src/network/mcpe/StandardPacketBroadcaster.php index 32afdeeb7c..7a91b397be 100644 --- a/src/network/mcpe/StandardPacketBroadcaster.php +++ b/src/network/mcpe/StandardPacketBroadcaster.php @@ -53,7 +53,6 @@ public function broadcastPackets(array $recipients, array $packets) : void{ $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 diff --git a/src/network/mcpe/cache/ChunkCache.php b/src/network/mcpe/cache/ChunkCache.php index 6d80200857..12e7697762 100644 --- a/src/network/mcpe/cache/ChunkCache.php +++ b/src/network/mcpe/cache/ChunkCache.php @@ -78,7 +78,10 @@ public static function pruneCaches() : void{ } } - /** @var CompressBatchPromise[] */ + /** + * @var CompressBatchPromise[] + * @phpstan-var array + */ private array $caches = []; private int $hits = 0; diff --git a/src/network/mcpe/convert/BlockStateDictionaryEntry.php b/src/network/mcpe/convert/BlockStateDictionaryEntry.php index 8c6244da58..28cc3960dc 100644 --- a/src/network/mcpe/convert/BlockStateDictionaryEntry.php +++ b/src/network/mcpe/convert/BlockStateDictionaryEntry.php @@ -28,6 +28,7 @@ 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 $stateProperties */ public function __construct( private string $stateName, @@ -79,6 +81,7 @@ public static function decodeStateProperties(string $rawProperties) : array{ /** * @param Tag[] $properties + * @phpstan-param array $properties */ public static function encodeStateProperties(array $properties) : string{ if(count($properties) === 0){ @@ -87,7 +90,7 @@ public static function encodeStateProperties(array $properties) : string{ //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)); diff --git a/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php b/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php index 5d06758ef5..d962063d35 100644 --- a/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php +++ b/src/network/mcpe/convert/ItemTypeDictionaryFromDataHelper.php @@ -26,6 +26,7 @@ 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 @@ public static function loadFromString(string $data) : ItemTypeDictionary{ } $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"); } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index c92db31331..cc1f3eab40 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -251,7 +251,7 @@ public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{ 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); diff --git a/src/network/mcpe/handler/ItemStackRequestExecutor.php b/src/network/mcpe/handler/ItemStackRequestExecutor.php index 54a1925901..6db8f1e12b 100644 --- a/src/network/mcpe/handler/ItemStackRequestExecutor.php +++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php @@ -54,6 +54,7 @@ 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 @@ protected function processItemStackRequestAction(ItemStackRequestAction $action) * @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){ diff --git a/src/network/mcpe/handler/LoginPacketHandler.php b/src/network/mcpe/handler/LoginPacketHandler.php index 2e3a515198..c15753dad3 100644 --- a/src/network/mcpe/handler/LoginPacketHandler.php +++ b/src/network/mcpe/handler/LoginPacketHandler.php @@ -150,7 +150,7 @@ public function handleLogin(LoginPacket $packet) : bool{ 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); diff --git a/src/permission/BanList.php b/src/permission/BanList.php index b09cd98c90..36826ed66f 100644 --- a/src/permission/BanList.php +++ b/src/permission/BanList.php @@ -23,6 +23,7 @@ namespace pocketmine\permission; +use pocketmine\utils\Utils; use function fclose; use function fgets; use function fopen; @@ -32,7 +33,10 @@ use function trim; class BanList{ - /** @var BanEntry[] */ + /** + * @var BanEntry[] + * @phpstan-var array + */ private array $list = []; private bool $enabled = true; @@ -101,7 +105,7 @@ public function remove(string $name) : void{ } 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]); } diff --git a/src/permission/PermissionAttachment.php b/src/permission/PermissionAttachment.php index b01fba3070..79c66ba8a1 100644 --- a/src/permission/PermissionAttachment.php +++ b/src/permission/PermissionAttachment.php @@ -25,10 +25,14 @@ 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 + */ private array $permissions = []; /** @@ -60,6 +64,7 @@ public function getSubscribers() : array{ return $this->subscribers; } /** * @return bool[] + * @phpstan-return array */ public function getPermissions() : array{ return $this->permissions; @@ -78,9 +83,10 @@ public function clearPermissions() : void{ /** * @param bool[] $permissions + * @phpstan-param array $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(); diff --git a/src/permission/PermissionManager.php b/src/permission/PermissionManager.php index 1291ba86bf..f2b02e8e5e 100644 --- a/src/permission/PermissionManager.php +++ b/src/permission/PermissionManager.php @@ -23,6 +23,7 @@ namespace pocketmine\permission; +use pocketmine\utils\Utils; use function count; use function spl_object_id; @@ -37,9 +38,15 @@ public static function getInstance() : PermissionManager{ return self::$instance; } - /** @var Permission[] */ + /** + * @var Permission[] + * @phpstan-var array + */ protected array $permissions = []; - /** @var PermissibleInternal[][] */ + /** + * @var PermissibleInternal[][] + * @phpstan-var array> + */ protected array $permSubs = []; public function getPermission(string $name) : ?Permission{ @@ -82,7 +89,7 @@ public function unsubscribeFromPermission(string $permission, PermissibleInterna } 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{ diff --git a/src/plugin/ApiVersion.php b/src/plugin/ApiVersion.php index bcf7e59a04..d95b66576c 100644 --- a/src/plugin/ApiVersion.php +++ b/src/plugin/ApiVersion.php @@ -70,7 +70,6 @@ public static function isCompatible(string $myVersionStr, array $wantVersionsStr * @return string[] */ public static function checkAmbiguousVersions(array $versions) : array{ - /** @var VersionString[][] $indexedVersions */ $indexedVersions = []; foreach($versions as $str){ @@ -85,9 +84,8 @@ public static function checkAmbiguousVersions(array $versions) : array{ } } - /** @var VersionString[] $result */ $result = []; - foreach($indexedVersions as $major => $list){ + foreach($indexedVersions as $list){ if(count($list) > 1){ array_push($result, ...$list); } diff --git a/src/plugin/PluginDescription.php b/src/plugin/PluginDescription.php index 72f0add7fd..35ae2ba329 100644 --- a/src/plugin/PluginDescription.php +++ b/src/plugin/PluginDescription.php @@ -26,6 +26,7 @@ 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 @@ private function loadMap(array $plugin) : void{ $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 @@ private function loadMap(array $plugin) : void{ 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 = "*"; diff --git a/src/plugin/PluginGraylist.php b/src/plugin/PluginGraylist.php index 84aa3f7924..ff9d718322 100644 --- a/src/plugin/PluginGraylist.php +++ b/src/plugin/PluginGraylist.php @@ -23,6 +23,7 @@ namespace pocketmine\plugin; +use pocketmine\utils\Utils; use function array_flip; use function is_array; use function is_float; @@ -77,7 +78,7 @@ public static function fromArray(array $array) : 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"); } diff --git a/src/plugin/PluginManager.php b/src/plugin/PluginManager.php index 198e4e893b..f84698aa08 100644 --- a/src/plugin/PluginManager.php +++ b/src/plugin/PluginManager.php @@ -69,10 +69,16 @@ * Manages all the plugins */ class PluginManager{ - /** @var Plugin[] */ + /** + * @var Plugin[] + * @phpstan-var array + */ protected array $plugins = []; - /** @var Plugin[] */ + /** + * @var Plugin[] + * @phpstan-var array + */ protected array $enabledPlugins = []; /** @var array> */ @@ -114,6 +120,7 @@ public function registerInterface(PluginLoader $loader) : void{ /** * @return Plugin[] + * @phpstan-return array */ public function getPlugins() : array{ return $this->plugins; @@ -526,7 +533,7 @@ public function disablePlugin(Plugin $plugin) : void{ } 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 diff --git a/src/promise/Promise.php b/src/promise/Promise.php index 0def7e6052..cd8fcc4f1c 100644 --- a/src/promise/Promise.php +++ b/src/promise/Promise.php @@ -23,6 +23,7 @@ namespace pocketmine\promise; +use pocketmine\utils\Utils; use function count; use function spl_object_id; @@ -83,7 +84,7 @@ public static function all(array $promises) : 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; diff --git a/src/resourcepacks/ResourcePackManager.php b/src/resourcepacks/ResourcePackManager.php index 2df6750def..baf16ca207 100644 --- a/src/resourcepacks/ResourcePackManager.php +++ b/src/resourcepacks/ResourcePackManager.php @@ -25,6 +25,7 @@ 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 @@ public function __construct(string $path, \Logger $logger){ 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; diff --git a/src/utils/Utils.php b/src/utils/Utils.php index ef3f2d2499..551e6ae44f 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -445,6 +445,7 @@ private static function stringifyValueForTrace(mixed $value, int $maxStringLengt * @phpstan-param list> $trace * * @return string[] + * @phpstan-return list */ public static function printableTrace(array $trace, int $maxStringLength = 80) : array{ $messages = []; @@ -456,7 +457,7 @@ public static function printableTrace(array $trace, int $maxStringLength = 80) : }else{ $args = $trace[$i]["params"]; } - /** @var mixed[] $args */ + /** @phpstan-var array $args */ $paramsList = []; $offset = 0; @@ -608,6 +609,18 @@ public static function stringifyKeys(array $array) : \Generator{ } } + /** + * 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 $array + * @phpstan-return array + */ + 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"); diff --git a/src/world/BlockTransaction.php b/src/world/BlockTransaction.php index 5f7e9f9faa..46cbc7903f 100644 --- a/src/world/BlockTransaction.php +++ b/src/world/BlockTransaction.php @@ -28,7 +28,10 @@ use pocketmine\utils\Utils; class BlockTransaction{ - /** @var Block[][][] */ + /** + * @var Block[][][] + * @phpstan-var array>> + */ private array $blocks = []; /** diff --git a/src/world/WorldManager.php b/src/world/WorldManager.php index ff603a2dfc..7ff4ed6e02 100644 --- a/src/world/WorldManager.php +++ b/src/world/WorldManager.php @@ -56,7 +56,10 @@ class WorldManager{ public const TICKS_PER_AUTOSAVE = 300 * Server::TARGET_TICKS_PER_SECOND; - /** @var World[] */ + /** + * @var World[] + * @phpstan-var array + */ private array $worlds = []; private ?World $defaultWorld = null; @@ -76,6 +79,7 @@ public function getProviderManager() : WorldProviderManager{ /** * @return World[] + * @phpstan-return array */ public function getWorlds() : array{ return $this->worlds; diff --git a/src/world/format/SubChunk.php b/src/world/format/SubChunk.php index 3f7e943e31..d8546e7e92 100644 --- a/src/world/format/SubChunk.php +++ b/src/world/format/SubChunk.php @@ -24,7 +24,6 @@ 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 $blockLayers */ public function __construct( private int $emptyBlockId, @@ -85,6 +85,7 @@ public function setBlockStateId(int $x, int $y, int $z, int $block) : void{ /** * @return PalettedBlockArray[] + * @phpstan-return list */ public function getBlockLayers() : array{ return $this->blockLayers; @@ -129,17 +130,18 @@ public function __debugInfo() : array{ } 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)){ diff --git a/src/world/format/io/BaseWorldProvider.php b/src/world/format/io/BaseWorldProvider.php index f863fdf747..79f6875a4b 100644 --- a/src/world/format/io/BaseWorldProvider.php +++ b/src/world/format/io/BaseWorldProvider.php @@ -65,6 +65,8 @@ public function __construct( abstract protected function loadLevelData() : WorldData; private function translatePalette(PalettedBlockArray $blockArray, \Logger $logger) : PalettedBlockArray{ + //TODO: missing type info in stubs + /** @phpstan-var list $palette */ $palette = $blockArray->getPalette(); $newPalette = []; diff --git a/src/world/format/io/ChunkData.php b/src/world/format/io/ChunkData.php index 458e001962..235ac07bfc 100644 --- a/src/world/format/io/ChunkData.php +++ b/src/world/format/io/ChunkData.php @@ -32,6 +32,10 @@ final class ChunkData{ * @param SubChunk[] $subChunks * @param CompoundTag[] $entityNBT * @param CompoundTag[] $tileNBT + * + * @phpstan-param array $subChunks + * @phpstan-param list $entityNBT + * @phpstan-param list $tileNBT */ public function __construct( private array $subChunks, @@ -42,14 +46,21 @@ public function __construct( /** * @return SubChunk[] + * @phpstan-return array */ public function getSubChunks() : array{ return $this->subChunks; } public function isPopulated() : bool{ return $this->populated; } - /** @return CompoundTag[] */ + /** + * @return CompoundTag[] + * @phpstan-return list + */ public function getEntityNBT() : array{ return $this->entityNBT; } - /** @return CompoundTag[] */ + /** + * @return CompoundTag[] + * @phpstan-return list + */ public function getTileNBT() : array{ return $this->tileNBT; } } diff --git a/src/world/format/io/region/RegionGarbageMap.php b/src/world/format/io/region/RegionGarbageMap.php index d1e9504524..9e4e232ee0 100644 --- a/src/world/format/io/region/RegionGarbageMap.php +++ b/src/world/format/io/region/RegionGarbageMap.php @@ -31,7 +31,10 @@ final class RegionGarbageMap{ - /** @var RegionLocationTableEntry[] */ + /** + * @var RegionLocationTableEntry[] + * @phpstan-var array + */ private array $entries = []; private bool $clean = false; @@ -48,7 +51,6 @@ public function __construct(array $entries){ * @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 @@ public static function buildFromLocationTable(array $locationTable) : self{ 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){ diff --git a/src/world/format/io/region/RegionLoader.php b/src/world/format/io/region/RegionLoader.php index f040b8a472..b65f5346cc 100644 --- a/src/world/format/io/region/RegionLoader.php +++ b/src/world/format/io/region/RegionLoader.php @@ -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 + */ protected array $locationTable = []; protected RegionGarbageMap $garbageTable; public int $lastUsed; @@ -327,7 +330,6 @@ protected function loadLocationTable() : void{ * @throws CorruptedRegionException */ private function checkLocationTableValidity() : void{ - /** @var int[] $usedOffsets */ $usedOffsets = []; $fileSize = filesize($this->filePath); @@ -355,7 +357,7 @@ private function checkLocationTableValidity() : void{ } ksort($usedOffsets, SORT_NUMERIC); $prevLocationIndex = null; - foreach($usedOffsets as $startOffset => $locationTableIndex){ + foreach($usedOffsets as $locationTableIndex){ if($this->locationTable[$locationTableIndex] === null){ continue; } diff --git a/src/world/format/io/region/RegionWorldProvider.php b/src/world/format/io/region/RegionWorldProvider.php index 1feca61be1..75fcfd0832 100644 --- a/src/world/format/io/region/RegionWorldProvider.php +++ b/src/world/format/io/region/RegionWorldProvider.php @@ -72,7 +72,10 @@ public static function isValid(string $path) : bool{ return false; } - /** @var RegionLoader[] */ + /** + * @var RegionLoader[] + * @phpstan-var array + */ protected array $regions = []; protected function loadLevelData() : WorldData{ diff --git a/src/world/generator/PopulationTask.php b/src/world/generator/PopulationTask.php index 8479a79fa7..bad1343240 100644 --- a/src/world/generator/PopulationTask.php +++ b/src/world/generator/PopulationTask.php @@ -76,7 +76,10 @@ public function onRun() : void{ $chunk = $this->chunk !== null ? FastChunkSerializer::deserializeTerrain($this->chunk) : null; - /** @var string[] $serialChunks */ + /** + * @var string[] $serialChunks + * @phpstan-var array $serialChunks + */ $serialChunks = igbinary_unserialize($this->adjacentChunks); $chunks = array_map( function(?string $serialized) : ?Chunk{ @@ -92,7 +95,6 @@ function(?string $serialized) : ?Chunk{ 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); diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index 0fc3defda7..02c94d2d68 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -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 + diff --git a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php b/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php index e42d329275..745cf2109d 100644 --- a/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php +++ b/tests/phpstan/rules/UnsafeForeachArrayOfStringRule.php @@ -28,6 +28,7 @@ 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 @@ public function processNode(Node $node, Scope $scope) : array{ $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 @@ public function processNode(Node $node, Scope $scope) : array{ 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 []; diff --git a/tests/phpunit/block/BlockTest.php b/tests/phpunit/block/BlockTest.php index 6ade2bffe2..841917787a 100644 --- a/tests/phpunit/block/BlockTest.php +++ b/tests/phpunit/block/BlockTest.php @@ -135,7 +135,7 @@ public static function computeConsistencyCheckDiff(string $expectedFile, array $ } $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"); } diff --git a/tests/phpunit/item/ItemTest.php b/tests/phpunit/item/ItemTest.php index 05cb48b30f..36ca5c5ffc 100644 --- a/tests/phpunit/item/ItemTest.php +++ b/tests/phpunit/item/ItemTest.php @@ -89,7 +89,6 @@ public function testGetEnchantmentLevel() : void{ } public function testGetEnchantments() : void{ - /** @var EnchantmentInstance[] $enchantments */ $enchantments = [ new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 5), new EnchantmentInstance(VanillaEnchantments::SHARPNESS(), 1) diff --git a/tests/phpunit/utils/CloningRegistryTraitTest.php b/tests/phpunit/utils/CloningRegistryTraitTest.php index 7f8298ff98..e3b53ecb56 100644 --- a/tests/phpunit/utils/CloningRegistryTraitTest.php +++ b/tests/phpunit/utils/CloningRegistryTraitTest.php @@ -47,7 +47,7 @@ public function testEachMemberClone(\Closure $provider) : void{ 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"); } } diff --git a/tests/phpunit/utils/TestCloningRegistry.php b/tests/phpunit/utils/TestCloningRegistry.php index ade94d461a..d65b8abaac 100644 --- a/tests/phpunit/utils/TestCloningRegistry.php +++ b/tests/phpunit/utils/TestCloningRegistry.php @@ -37,9 +37,13 @@ final class TestCloningRegistry{ /** * @return \stdClass[] + * @phpstan-return array */ public static function getAll() : array{ - /** @var \stdClass[] $result */ + /** + * @var \stdClass[] $result + * @phpstan-var array $result + */ $result = self::_registryGetAll(); return $result; } diff --git a/tools/compact-regions.php b/tools/compact-regions.php index 6959c82fe2..04ac3f0c94 100644 --- a/tools/compact-regions.php +++ b/tools/compact-regions.php @@ -23,6 +23,7 @@ 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 @@ /** * @param int[] $files * @phpstan-param array $files + * @phpstan-param-out array $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; diff --git a/tools/generate-bedrock-data-from-packets.php b/tools/generate-bedrock-data-from-packets.php index 0cb5ac3667..2c20e6099d 100644 --- a/tools/generate-bedrock-data-from-packets.php +++ b/tools/generate-bedrock-data-from-packets.php @@ -198,12 +198,12 @@ private static function objectToOrderedArray(object $object) : array{ $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 @@ private static function sort(mixed $object) : mixed{ } 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 @@ public function handleStartGame(StartGamePacket $packet) : bool{ 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 @@ private function shapedRecipeToJson(ShapedRecipe $entry) : ShapedRecipeData{ $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 @@ private function shapedRecipeToJson(ShapedRecipe $entry) : ShapedRecipeData{ } $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(), From a9787f0d993ab2f9e6bac29649f732fe933099ce Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Nov 2024 14:32:17 +0000 Subject: [PATCH 11/18] Fix PHPStan error --- src/utils/Utils.php | 2 +- tests/phpstan/configs/actual-problems.neon | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 551e6ae44f..38f523ad4d 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -403,7 +403,7 @@ private static function printableExceptionMessage(\Throwable $e) : string{ } /** - * @param mixed[] $trace + * @param mixed[][] $trace * @return string[] */ public static function printableExceptionInfo(\Throwable $e, $trace = null) : array{ diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index e778cf0047..54028f0dd1 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -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\\\\>, 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 From 8cdc7d7ee16482d630e49af2765dc958062b8693 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Mon, 25 Nov 2024 20:43:59 +0000 Subject: [PATCH 12/18] auto-approve: drop pull_request_review trigger this doesn't work for PRs from forks, since fork PRs don't have access to repo secrets. we'll need some more advanced mechanism to avoid redundant reviews, but that's a job for another time. --- .github/workflows/team-pr-auto-approve.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/team-pr-auto-approve.yml b/.github/workflows/team-pr-auto-approve.yml index f145812138..0c2fdd81c0 100644 --- a/.github/workflows/team-pr-auto-approve.yml +++ b/.github/workflows/team-pr-auto-approve.yml @@ -11,8 +11,7 @@ on: - opened - reopened - ready_for_review - pull_request_review: - types: dismissed + - synchronize jobs: dispatch: @@ -36,4 +35,4 @@ jobs: token: ${{ steps.generate-token.outputs.token }} repository: ${{ github.repository_owner }}/RestrictedActions event-type: auto_approve_collaborator_pr - client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}", "reviewer_id": "${{ github.event.review.user.id || 0 }}" }' + client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}", "reviewer_id": "0" }' From 52fe2cb97fa0cee5e4d72fa8e4fd298b4697385f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Nov 2024 21:29:26 +0000 Subject: [PATCH 13/18] PermissionManager: deprecate permission subscription system this is no longer used by the core, and as far as I can tell no plugin uses it either. it was used in the past for chat broadcast channels, but not anymore. --- src/permission/PermissionManager.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/permission/PermissionManager.php b/src/permission/PermissionManager.php index f2b02e8e5e..c9e37f5e94 100644 --- a/src/permission/PermissionManager.php +++ b/src/permission/PermissionManager.php @@ -23,6 +23,7 @@ namespace pocketmine\permission; +use pocketmine\Server; use pocketmine\utils\Utils; use function count; use function spl_object_id; @@ -71,6 +72,10 @@ public function removePermission(Permission|string $permission) : void{ } } + /** + * @deprecated Superseded by server chat broadcast channels + * @see Server::subscribeToBroadcastChannel() + */ public function subscribeToPermission(string $permission, PermissibleInternal $permissible) : void{ if(!isset($this->permSubs[$permission])){ $this->permSubs[$permission] = []; @@ -78,6 +83,10 @@ public function subscribeToPermission(string $permission, PermissibleInternal $p $this->permSubs[$permission][spl_object_id($permissible)] = $permissible; } + /** + * @deprecated Superseded by server chat broadcast channels + * @see Server::unsubscribeFromBroadcastChannel() + */ public function unsubscribeFromPermission(string $permission, PermissibleInternal $permissible) : void{ if(isset($this->permSubs[$permission][spl_object_id($permissible)])){ if(count($this->permSubs[$permission]) === 1){ @@ -88,6 +97,10 @@ public function unsubscribeFromPermission(string $permission, PermissibleInterna } } + /** + * @deprecated Superseded by server chat broadcast channels + * @see Server::unsubscribeFromAllBroadcastChannels() + */ public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{ foreach(Utils::promoteKeys($this->permSubs) as $permission => $subs){ if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){ @@ -99,6 +112,8 @@ public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) } /** + * @deprecated Superseded by server chat broadcast channels + * @see Server::getBroadcastChannelSubscribers() * @return PermissibleInternal[] */ public function getPermissionSubscriptions(string $permission) : array{ From 905a10e980cb6552bab0bad59cc593852409033d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Nov 2024 21:39:35 +0000 Subject: [PATCH 14/18] Deprecate InventoryAction->onAddToTransaction() this never made any sense --- src/inventory/transaction/InventoryTransaction.php | 13 +++---------- .../transaction/action/InventoryAction.php | 1 + .../transaction/action/SlotChangeAction.php | 8 -------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php index b3465a8dfb..47290e4015 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -111,6 +111,9 @@ public function addAction(InventoryAction $action) : void{ if(!isset($this->actions[$hash = spl_object_id($action)])){ $this->actions[$hash] = $action; $action->onAddToTransaction($this); + if($action instanceof SlotChangeAction && !isset($this->inventories[$inventoryId = spl_object_id($action->getInventory())])){ + $this->inventories[$inventoryId] = $action->getInventory(); + } }else{ throw new \InvalidArgumentException("Tried to add the same action to a transaction twice"); } @@ -129,16 +132,6 @@ private function shuffleActions() : void{ $this->actions = $actions; } - /** - * @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions - * involving inventories. - */ - public function addInventory(Inventory $inventory) : void{ - if(!isset($this->inventories[$hash = spl_object_id($inventory)])){ - $this->inventories[$hash] = $inventory; - } - } - /** * @param Item[] $needItems * @param Item[] $haveItems diff --git a/src/inventory/transaction/action/InventoryAction.php b/src/inventory/transaction/action/InventoryAction.php index fff3d22b80..2f0db083c9 100644 --- a/src/inventory/transaction/action/InventoryAction.php +++ b/src/inventory/transaction/action/InventoryAction.php @@ -60,6 +60,7 @@ abstract public function validate(Player $source) : void; /** * Called when the action is added to the specified InventoryTransaction. + * @deprecated */ public function onAddToTransaction(InventoryTransaction $transaction) : void{ diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 68c3dba1b3..3c9b8e5cf7 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -25,7 +25,6 @@ use pocketmine\inventory\Inventory; use pocketmine\inventory\SlotValidatedInventory; -use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; use pocketmine\player\Player; @@ -85,13 +84,6 @@ public function validate(Player $source) : void{ } } - /** - * Adds this action's target inventory to the transaction's inventory list. - */ - public function onAddToTransaction(InventoryTransaction $transaction) : void{ - $transaction->addInventory($this->inventory); - } - /** * Sets the item into the target inventory. */ From 269effcecf2398046c22b399773f63c1d4fca1fe Mon Sep 17 00:00:00 2001 From: Akmal Fairuz <35138228+AkmalFairuz@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:33:29 +0700 Subject: [PATCH 15/18] Introduce Utils::getRandomFloat() (#6532) Drop-in replacement for lcg_value() for PHP 8.4 --- src/block/Anvil.php | 4 ++-- src/block/Farmland.php | 4 ++-- src/block/ItemFrame.php | 6 +++--- src/block/Liquid.php | 4 ++-- src/entity/Entity.php | 3 +-- src/entity/Living.php | 6 +++--- src/item/Armor.php | 4 ++-- src/item/Durable.php | 4 ++-- src/item/RottenFlesh.php | 4 ++-- src/item/SpawnEgg.php | 4 ++-- src/utils/Utils.php | 10 ++++++++++ src/world/World.php | 10 +++++----- 12 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/block/Anvil.php b/src/block/Anvil.php index 4b4afef615..0a1a470700 100644 --- a/src/block/Anvil.php +++ b/src/block/Anvil.php @@ -35,10 +35,10 @@ use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\Utils; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\AnvilFallSound; use pocketmine\world\sound\Sound; -use function lcg_value; use function round; class Anvil extends Transparent implements Fallable{ @@ -97,7 +97,7 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo } public function onHitGround(FallingBlock $blockEntity) : bool{ - if(lcg_value() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){ + if(Utils::getRandomFloat() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){ if($this->damage !== self::VERY_DAMAGED){ $this->damage = $this->damage + 1; }else{ diff --git a/src/block/Farmland.php b/src/block/Farmland.php index a17a220f0b..b7a2500a8b 100644 --- a/src/block/Farmland.php +++ b/src/block/Farmland.php @@ -31,8 +31,8 @@ use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; +use pocketmine\utils\Utils; use function intdiv; -use function lcg_value; class Farmland extends Transparent{ public const MAX_WETNESS = 7; @@ -148,7 +148,7 @@ public function onRandomTick() : void{ } public function onEntityLand(Entity $entity) : ?float{ - if($entity instanceof Living && lcg_value() < $entity->getFallDistance() - 0.5){ + if($entity instanceof Living && Utils::getRandomFloat() < $entity->getFallDistance() - 0.5){ $ev = new EntityTrampleFarmlandEvent($entity, $this); $ev->call(); if(!$ev->isCancelled()){ diff --git a/src/block/ItemFrame.php b/src/block/ItemFrame.php index b5b6093c49..c03806a3b5 100644 --- a/src/block/ItemFrame.php +++ b/src/block/ItemFrame.php @@ -31,13 +31,13 @@ use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\Utils; use pocketmine\world\BlockTransaction; use pocketmine\world\sound\ItemFrameAddItemSound; use pocketmine\world\sound\ItemFrameRemoveItemSound; use pocketmine\world\sound\ItemFrameRotateItemSound; use function is_infinite; use function is_nan; -use function lcg_value; class ItemFrame extends Flowable{ use AnyFacingTrait; @@ -154,7 +154,7 @@ public function onAttack(Item $item, int $face, ?Player $player = null) : bool{ return false; } $world = $this->position->getWorld(); - if(lcg_value() <= $this->itemDropChance){ + if(Utils::getRandomFloat() <= $this->itemDropChance){ $world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem); $world->addSound($this->position, new ItemFrameRemoveItemSound()); } @@ -185,7 +185,7 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo public function getDropsForCompatibleTool(Item $item) : array{ $drops = parent::getDropsForCompatibleTool($item); - if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){ + if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){ $drops[] = clone $this->framedItem; } diff --git a/src/block/Liquid.php b/src/block/Liquid.php index 6404cf9081..a37019d650 100644 --- a/src/block/Liquid.php +++ b/src/block/Liquid.php @@ -33,9 +33,9 @@ use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; +use pocketmine\utils\Utils; use pocketmine\world\sound\FizzSound; use pocketmine\world\sound\Sound; -use function lcg_value; abstract class Liquid extends Transparent{ public const MAX_DECAY = 7; @@ -368,7 +368,7 @@ protected function checkForHarden() : bool{ protected function liquidCollide(Block $cause, Block $result) : bool{ if(BlockEventHelper::form($this, $result, $cause)){ - $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8)); + $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8)); } return true; } diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 0b690b0d97..9bd0de9eac 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -75,7 +75,6 @@ use function floor; use function fmod; use function get_class; -use function lcg_value; use function sin; use function spl_object_id; use const M_PI_2; @@ -910,7 +909,7 @@ protected function checkObstruction(float $x, float $y, float $z) : bool{ return false; } - $force = lcg_value() * 0.2 + 0.1; + $force = Utils::getRandomFloat() * 0.2 + 0.1; $this->motion = match($direction){ Facing::WEST => $this->motion->withComponents(-$force, null, null), diff --git a/src/entity/Living.php b/src/entity/Living.php index 81f46424f1..1f27a5cacf 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -58,6 +58,7 @@ use pocketmine\player\Player; use pocketmine\timings\Timings; use pocketmine\utils\Binary; +use pocketmine\utils\Utils; use pocketmine\world\sound\BurpSound; use pocketmine\world\sound\EntityLandSound; use pocketmine\world\sound\EntityLongFallSound; @@ -69,7 +70,6 @@ use function count; use function floor; use function ksort; -use function lcg_value; use function max; use function min; use function mt_getrandmax; @@ -490,7 +490,7 @@ protected function applyPostDamageEffects(EntityDamageEvent $source) : void{ $helmet = $this->armorInventory->getHelmet(); if($helmet instanceof Armor){ $finalDamage = $source->getFinalDamage(); - $this->damageItem($helmet, (int) round($finalDamage * 4 + lcg_value() * $finalDamage * 2)); + $this->damageItem($helmet, (int) round($finalDamage * 4 + Utils::getRandomFloat() * $finalDamage * 2)); $this->armorInventory->setHelmet($helmet); } } @@ -697,7 +697,7 @@ protected function doAirSupplyTick(int $tickDiff) : bool{ $this->setBreathing(false); if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 || - lcg_value() <= (1 / ($respirationLevel + 1)) + Utils::getRandomFloat() <= (1 / ($respirationLevel + 1)) ){ $ticks -= $tickDiff; if($ticks <= -20){ diff --git a/src/item/Armor.php b/src/item/Armor.php index 417c57f75c..63a8003adc 100644 --- a/src/item/Armor.php +++ b/src/item/Armor.php @@ -33,7 +33,7 @@ use pocketmine\nbt\tag\IntTag; use pocketmine\player\Player; use pocketmine\utils\Binary; -use function lcg_value; +use pocketmine\utils\Utils; use function mt_rand; class Armor extends Durable{ @@ -129,7 +129,7 @@ protected function getUnbreakingDamageReduction(int $amount) : int{ $chance = 1 / ($unbreakingLevel + 1); for($i = 0; $i < $amount; ++$i){ - if(mt_rand(1, 100) > 60 && lcg_value() > $chance){ //unbreaking only applies to armor 40% of the time at best + if(mt_rand(1, 100) > 60 && Utils::getRandomFloat() > $chance){ //unbreaking only applies to armor 40% of the time at best $negated++; } } diff --git a/src/item/Durable.php b/src/item/Durable.php index f110f6ea51..069a01202e 100644 --- a/src/item/Durable.php +++ b/src/item/Durable.php @@ -25,7 +25,7 @@ use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\nbt\tag\CompoundTag; -use function lcg_value; +use pocketmine\utils\Utils; use function min; abstract class Durable extends Item{ @@ -87,7 +87,7 @@ protected function getUnbreakingDamageReduction(int $amount) : int{ $chance = 1 / ($unbreakingLevel + 1); for($i = 0; $i < $amount; ++$i){ - if(lcg_value() > $chance){ + if(Utils::getRandomFloat() > $chance){ $negated++; } } diff --git a/src/item/RottenFlesh.php b/src/item/RottenFlesh.php index 4cecc67fcd..2ea3ee9559 100644 --- a/src/item/RottenFlesh.php +++ b/src/item/RottenFlesh.php @@ -25,7 +25,7 @@ use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\VanillaEffects; -use function lcg_value; +use pocketmine\utils\Utils; class RottenFlesh extends Food{ @@ -38,7 +38,7 @@ public function getSaturationRestore() : float{ } public function getAdditionalEffects() : array{ - if(lcg_value() <= 0.8){ + if(Utils::getRandomFloat() <= 0.8){ return [ new EffectInstance(VanillaEffects::HUNGER(), 600) ]; diff --git a/src/item/SpawnEgg.php b/src/item/SpawnEgg.php index 51dcceebd2..ab4f0e1498 100644 --- a/src/item/SpawnEgg.php +++ b/src/item/SpawnEgg.php @@ -27,15 +27,15 @@ use pocketmine\entity\Entity; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\utils\Utils; use pocketmine\world\World; -use function lcg_value; abstract class SpawnEgg extends Item{ abstract protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity; public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{ - $entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), lcg_value() * 360, 0); + $entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), Utils::getRandomFloat() * 360, 0); if($this->hasCustomName()){ $entity->setNameTag($this->getCustomName()); diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 38f523ad4d..c8be174d6a 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -67,6 +67,8 @@ use function is_object; use function is_string; use function mb_check_encoding; +use function mt_getrandmax; +use function mt_rand; use function ob_end_clean; use function ob_get_contents; use function ob_start; @@ -688,4 +690,12 @@ function_exists('opcache_get_status') && //jit not available return null; } + + /** + * Returns a random float between 0.0 and 1.0 + * Drop-in replacement for lcg_value() + */ + public static function getRandomFloat() : float{ + return mt_rand() / mt_getrandmax(); + } } diff --git a/src/world/World.php b/src/world/World.php index a8e624dd5c..ff65377c09 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -83,6 +83,7 @@ use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Limits; use pocketmine\utils\ReversePriorityQueue; +use pocketmine\utils\Utils; use pocketmine\world\biome\Biome; use pocketmine\world\biome\BiomeRegistry; use pocketmine\world\format\Chunk; @@ -120,7 +121,6 @@ use function gettype; use function is_a; use function is_object; -use function lcg_value; use function max; use function microtime; use function min; @@ -1998,10 +1998,10 @@ public function dropItem(Vector3 $source, Item $item, ?Vector3 $motion = null, i return null; } - $itemEntity = new ItemEntity(Location::fromObject($source, $this, lcg_value() * 360, 0), $item); + $itemEntity = new ItemEntity(Location::fromObject($source, $this, Utils::getRandomFloat() * 360, 0), $item); $itemEntity->setPickupDelay($delay); - $itemEntity->setMotion($motion ?? new Vector3(lcg_value() * 0.2 - 0.1, 0.2, lcg_value() * 0.2 - 0.1)); + $itemEntity->setMotion($motion ?? new Vector3(Utils::getRandomFloat() * 0.2 - 0.1, 0.2, Utils::getRandomFloat() * 0.2 - 0.1)); $itemEntity->spawnToAll(); return $itemEntity; @@ -2018,9 +2018,9 @@ public function dropExperience(Vector3 $pos, int $amount) : array{ $orbs = []; foreach(ExperienceOrb::splitIntoOrbSizes($amount) as $split){ - $orb = new ExperienceOrb(Location::fromObject($pos, $this, lcg_value() * 360, 0), $split); + $orb = new ExperienceOrb(Location::fromObject($pos, $this, Utils::getRandomFloat() * 360, 0), $split); - $orb->setMotion(new Vector3((lcg_value() * 0.2 - 0.1) * 2, lcg_value() * 0.4, (lcg_value() * 0.2 - 0.1) * 2)); + $orb->setMotion(new Vector3((Utils::getRandomFloat() * 0.2 - 0.1) * 2, Utils::getRandomFloat() * 0.4, (Utils::getRandomFloat() * 0.2 - 0.1) * 2)); $orb->spawnToAll(); $orbs[] = $orb; From daacc8eddb6555e7a9015ebabda6a1fe75a92b82 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 27 Nov 2024 17:54:59 +0000 Subject: [PATCH 16/18] Updated setup-php-action to 3.2.0 --- .github/workflows/main-php-matrix.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main-php-matrix.yml b/.github/workflows/main-php-matrix.yml index 6d71a0e70c..e26f7c3187 100644 --- a/.github/workflows/main-php-matrix.yml +++ b/.github/workflows/main-php-matrix.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" @@ -62,7 +62,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" @@ -96,7 +96,7 @@ jobs: submodules: true - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" @@ -128,7 +128,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup PHP - uses: pmmp/setup-php-action@3.1.0 + uses: pmmp/setup-php-action@3.2.0 with: php-version: ${{ inputs.php }} install-path: "./bin" From 0543bf301ee783536ba1015183fd69ba258177dd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 27 Nov 2024 19:19:51 +0000 Subject: [PATCH 17/18] draft-release: updated php_download_url --- .github/workflows/draft-release.yml | 3 ++- build/dump-version-info.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 0a07a738bb..cd1841e4f1 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -64,6 +64,7 @@ jobs: id: get-pm-version run: | echo PM_VERSION=$(php build/dump-version-info.php base_version) >> $GITHUB_OUTPUT + echo PM_MAJOR=$(php build/dump-version-info.php major_version) >> $GITHUB_OUTPUT echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT @@ -72,7 +73,7 @@ jobs: - name: Generate PHP binary download URL id: php-binary-url run: | - echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT + echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT - name: Generate build info run: | diff --git a/build/dump-version-info.php b/build/dump-version-info.php index 8898d7cabf..166264d980 100644 --- a/build/dump-version-info.php +++ b/build/dump-version-info.php @@ -36,6 +36,7 @@ */ $options = [ "base_version" => VersionInfo::BASE_VERSION, + "major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0], "mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK, "is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD, "changelog_file_name" => function() : string{ From d666f17ec63bb0db4c7e19cea29c2be67026e3c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:46:30 +0000 Subject: [PATCH 18/18] Bump build/php from `a51259d` to `8a396c6` (#6540) --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index a51259d7a6..8a396c63fc 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit a51259d7a6ea649d64f409fc0276baa59cf4f19a +Subproject commit 8a396c63fc5e79ea2849bfca100ea21a49ba2933