From b9cfbc90366141604a55cd36188e32198be80dd6 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:12:48 +0000 Subject: [PATCH] Narrow aabb types and give generic aliases their type argument The aabb functions all index the named members, so passing the shorthand [MapPosition, MapPosition] form would error at runtime. The annotations now say so. emmylua requires a type argument on a generic alias, luals did not. Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/module_exports.lua | 12 +++--- .../module/control/fast_deconstruction.lua | 2 +- exp_scenario/module/control/help_bubbles.lua | 4 +- exp_util/module/aabb.lua | 41 +++++++++++-------- exp_util/module/async.lua | 30 +++++++------- exp_util/module/selection.lua | 2 +- 6 files changed, 48 insertions(+), 43 deletions(-) diff --git a/exp_commands/module/module_exports.lua b/exp_commands/module/module_exports.lua index 564312b0..44542a19 100644 --- a/exp_commands/module/module_exports.lua +++ b/exp_commands/module/module_exports.lua @@ -84,7 +84,7 @@ local Commands = { --- Contains the different status values a command can return Commands.status = {} ---- @class (partial) Commands.types: table +--- @class (partial) Commands.types: table | Commands.InputParserFactory> --- Stores all input parsers and validators for different data types Commands.types = {} @@ -103,7 +103,7 @@ end --- @class Commands.Argument --- @field name string The name of the argument --- @field description LocalisedString? The description of the argument ---- @field input_parser Commands.InputParser The input parser for the argument +--- @field input_parser Commands.InputParser The input parser for the argument --- @field optional boolean True when the argument is optional --- @field default any? The default value of the argument @@ -281,7 +281,7 @@ end --- @alias Commands.InputParserFactory fun(...: any): Commands.InputParser --- Add a new input parser to the command library, this method validates that it does not already exist ---- @generic T : Commands.InputParser | Commands.InputParserFactory +--- @generic T : Commands.InputParser | Commands.InputParserFactory --- @param data_type string The name of the data type the input parser reads in and validates, becomes a key of Commands.types --- @param input_parser T The function used to parse and validate the data type --- @return T # The function which was provided as the second argument @@ -295,7 +295,7 @@ function Commands.add_data_type(data_type, input_parser) end --- Remove an input parser for a data type, must be the same string that was passed to add_input_parser ---- @param data_type string | Commands.InputParser | Commands.InputParserFactory The data type or input parser you want to remove the input parser for +--- @param data_type string | Commands.InputParser | Commands.InputParserFactory The data type or input parser you want to remove the input parser for function Commands.remove_data_type(data_type) Commands.types[data_type] = nil for k, v in pairs(Commands.types) do @@ -433,7 +433,7 @@ end --- Add a new required argument to the command of the given data type --- @param name string The name of the argument being added --- @param description LocalisedString? The description of the argument being added ---- @param input_parser Commands.InputParser The input parser to be used for the argument +--- @param input_parser Commands.InputParser The input parser to be used for the argument --- @return ExpCommand function Commands._prototype:argument(name, description, input_parser) assert_command_mutable(self) @@ -454,7 +454,7 @@ end --- Add a new optional argument to the command of the given data type --- @param name string The name of the argument being added --- @param description LocalisedString? The description of the argument being added ---- @param input_parser Commands.InputParser The input parser to be used for the argument +--- @param input_parser Commands.InputParser The input parser to be used for the argument --- @return ExpCommand function Commands._prototype:optional(name, description, input_parser) assert_command_mutable(self) diff --git a/exp_scenario/module/control/fast_deconstruction.lua b/exp_scenario/module/control/fast_deconstruction.lua index 4481f9b4..5470afb6 100644 --- a/exp_scenario/module/control/fast_deconstruction.lua +++ b/exp_scenario/module/control/fast_deconstruction.lua @@ -22,7 +22,7 @@ local min = math.min --- @field trees LuaEntity[] --- @field tree_count number --- @field permission "fast" | "allow" | "disallow" ---- @field task Async.AsyncReturn +--- @field task Async.AsyncReturn local cache --- @type TreeDeconCache? diff --git a/exp_scenario/module/control/help_bubbles.lua b/exp_scenario/module/control/help_bubbles.lua index 6740b93a..b37c143f 100644 --- a/exp_scenario/module/control/help_bubbles.lua +++ b/exp_scenario/module/control/help_bubbles.lua @@ -6,7 +6,7 @@ local Async = require("modules/exp_util/async") local Storage = require("modules/exp_util/storage") local config = require("modules.exp_legacy.config.compilatron") ---- @type table +--- @type table> local persistent_locations = {} Storage.register(persistent_locations, function(tbl) persistent_locations = tbl @@ -55,7 +55,7 @@ local speech_bubble_task = --- @param entity LuaEntity the entity which will have messages spawn from it --- @param messages LocalisedString[] the messages which should be shown --- @param starting_index number? the message index to start at, default 1 ---- @return Async.AsyncReturn +--- @return Async.AsyncReturn local function register_entity(entity, messages, starting_index) return speech_bubble_task{ entity = entity, diff --git a/exp_util/module/aabb.lua b/exp_util/module/aabb.lua index 64c5719d..9ac567d6 100644 --- a/exp_util/module/aabb.lua +++ b/exp_util/module/aabb.lua @@ -7,26 +7,31 @@ local ceil = math.ceil local min = math.min local max = math.max +--- An axis aligned bounding box using named members, the shorthand form is not supported +--- @class ExpUtil_AABB.Box +--- @field left_top MapPosition.struct +--- @field right_bottom MapPosition.struct + --- @class ExpUtil_AABB local AABB = {} --- Check if an area is valid ---- @param aabb BoundingBox +--- @param aabb ExpUtil_AABB.Box --- @return boolean # True if the area is valid function AABB.valid(aabb) return aabb.left_top.x < aabb.right_bottom.x and aabb.left_top.y < aabb.right_bottom.y end --- Returns the size of the area contained within an AABB ---- @param aabb BoundingBox +--- @param aabb ExpUtil_AABB.Box --- @return number function AABB.size(aabb) return (aabb.right_bottom.x - aabb.left_top.x) * (aabb.right_bottom.y - aabb.left_top.y) end --- Clone an area, allows for safe mutation of an input value ---- @param aabb BoundingBox ---- @return BoundingBox +--- @param aabb ExpUtil_AABB.Box +--- @return ExpUtil_AABB.Box function AABB.clone(aabb) return { left_top = { x = aabb.left_top.x, y = aabb.left_top.y }, @@ -35,8 +40,8 @@ function AABB.clone(aabb) end --- Expand an area to be integer aligned, expanding away from 0 ---- @param aabb BoundingBox ---- @return BoundingBox +--- @param aabb ExpUtil_AABB.Box +--- @return ExpUtil_AABB.Box function AABB.expand(aabb) return { left_top = { x = floor(aabb.left_top.x), y = floor(aabb.left_top.y) }, @@ -45,8 +50,8 @@ function AABB.expand(aabb) end --- Contract an area to be integer aligned, contracting towards 0 ---- @param aabb BoundingBox ---- @return BoundingBox +--- @param aabb ExpUtil_AABB.Box +--- @return ExpUtil_AABB.Box function AABB.contract(aabb) return { left_top = { x = ceil(aabb.left_top.x), y = ceil(aabb.left_top.y) }, @@ -55,9 +60,9 @@ function AABB.contract(aabb) end --- Expand an area to include all other areas ---- @param aabb BoundingBox ---- @param ... BoundingBox ---- @return BoundingBox +--- @param aabb ExpUtil_AABB.Box +--- @param ... ExpUtil_AABB.Box +--- @return ExpUtil_AABB.Box function AABB.union(aabb, ...) local rtn = AABB.clone(aabb) for _, next_aabb in ipairs{ ... } do @@ -70,9 +75,9 @@ function AABB.union(aabb, ...) end --- Contract an area to include to the overlap of all areas ---- @param aabb BoundingBox ---- @param ... BoundingBox ---- @return BoundingBox? # Nil if there is no intersection +--- @param aabb ExpUtil_AABB.Box +--- @param ... ExpUtil_AABB.Box +--- @return ExpUtil_AABB.Box? # Nil if there is no intersection function AABB.intersect(aabb, ...) local rtn = AABB.clone(aabb) for _, next_aabb in ipairs{ ... } do @@ -88,8 +93,8 @@ function AABB.intersect(aabb, ...) end --- Check if a point is contained within an area ---- @param aabb BoundingBox ---- @param point MapPosition +--- @param aabb ExpUtil_AABB.Box +--- @param point MapPosition.struct --- @return boolean # True if the point is within or on the edge of the bounding box function AABB.contains_point(aabb, point) return point.x >= aabb.left_top.x and point.y >= aabb.left_top.y @@ -97,8 +102,8 @@ function AABB.contains_point(aabb, point) end --- Check if an area is fulling contained within another area ---- @param aabb BoundingBox ---- @param other BoundingBox +--- @param aabb ExpUtil_AABB.Box +--- @param other ExpUtil_AABB.Box --- @return boolean # True if the point is within or on the edge of the bounding box function AABB.contains_area(aabb, other) return AABB.contains_point(aabb, other.left_top) and AABB.contains_point(aabb, other.right_bottom) diff --git a/exp_util/module/async.lua b/exp_util/module/async.lua index 4056969c..3a837030 100644 --- a/exp_util/module/async.lua +++ b/exp_util/module/async.lua @@ -94,7 +94,7 @@ Async.status = {} --- @class Async.AsyncFunction --- @field id number The id of this async function ---- @operator call: Async.AsyncReturn +--- @operator call: Async.AsyncReturn Async._function_prototype = {} Async._function_metatable = { @@ -123,20 +123,20 @@ script.register_metatable("AsyncReturn", Async._return_metatable) --- Storage Variables -local resolve_next --- @type Async.AsyncReturn[] Stores a queue of async functions to be executed on the next tick -local resolve_queue --- @type Async.AsyncReturn[] Stores a queue of async functions to be executed on a later tick +local resolve_next --- @type Async.AsyncReturn[] Stores a queue of async functions to be executed on the next tick +local resolve_queue --- @type Async.AsyncReturn[] Stores a queue of async functions to be executed on a later tick --- Insert an item into the priority queue ---- @param pending Async.AsyncReturn ---- @return Async.AsyncReturn +--- @param pending Async.AsyncReturn +--- @return Async.AsyncReturn local function add_to_next_tick(pending) resolve_next[#resolve_next + 1] = pending return pending end --- Insert an item into the priority queue ---- @param pending Async.AsyncReturn ---- @return Async.AsyncReturn +--- @param pending Async.AsyncReturn +--- @return Async.AsyncReturn local function add_to_resolve_queue(pending) local tick = pending.tick for index = #resolve_queue, 1, -1 do @@ -191,7 +191,7 @@ end --- Run an async function on the next tick, this is the default and can be used to bypass permission groups --- @param ... any The arguments to call the function with ---- @return Async.AsyncReturn +--- @return Async.AsyncReturn function Async._function_prototype:start_soon(...) assert(Async._registered[self.id], "Async function is not registered") Async._queue_pressure[self.id] = Async._queue_pressure[self.id] + 1 @@ -204,7 +204,7 @@ end --- Run an async function after the given number of ticks --- @param ticks number The number of ticks to call the function after --- @param ... any The arguments to call the function with ---- @return Async.AsyncReturn +--- @return Async.AsyncReturn function Async._function_prototype:start_after(ticks, ...) ExpUtil.assert_argument_type(ticks, "number", 1, "ticks") assert(ticks > 0, "Ticks must be a positive number") @@ -219,7 +219,7 @@ end --- Run an async function on the next tick if the function is not already queued, allows singleton task/thread behaviour --- @param ... any The arguments to call the function with ---- @return Async.AsyncReturn | nil +--- @return Async.AsyncReturn | nil function Async._function_prototype:start_task(...) assert(Async._registered[self.id], "Async function is not registered") if Async._queue_pressure[self.id] > 0 then return end @@ -228,7 +228,7 @@ end --- Run an async function on this tick, then queue it based on its return value --- @param ... any The arguments to call the function with ---- @return Async.AsyncReturn +--- @return Async.AsyncReturn function Async._function_prototype:start_now(...) assert(Async._registered[self.id], "Async function is not registered") local status, rtn1, rtn2 = Async._registered[self.id](...) @@ -293,11 +293,11 @@ end --- Status Returns. ---- @type Async.AsyncReturn[], Async.AsyncReturn[] +--- @type Async.AsyncReturn[], Async.AsyncReturn[] local new_next, new_queue = {}, {} -- File scope to allow for reuse --- Executes an async function and processes the return value ---- @param pending Async.AsyncReturn +--- @param pending Async.AsyncReturn --- @param tick number local function exec(pending, tick) local async_func = Async._registered[pending.func_id] @@ -397,9 +397,9 @@ end --- @package function Async.on_init() if storage.exp_async_next == nil then - --- @type Async.AsyncReturn[] + --- @type Async.AsyncReturn[] storage.exp_async_next = {} - --- @type Async.AsyncReturn[] + --- @type Async.AsyncReturn[] storage.exp_async_queue = {} end Async.on_load() diff --git a/exp_util/module/selection.lua b/exp_util/module/selection.lua index a50a7940..c6695ca7 100644 --- a/exp_util/module/selection.lua +++ b/exp_util/module/selection.lua @@ -23,7 +23,7 @@ local Selection = { --- @field player_index number --- @field selection Selection.Active - --- @type table + --- @type table[] }> _registered = {}, --- @package