refactor(lib): use extension pattern for result<->option

Formerly, we used metatables to get custom `Option` and `Result` objects
which were difficult to type properly, leading to a lot of `unknown` and
`any` casts.

This refactor fixes it by making extensions opt-in, where we import the
extension methods separately from the original implementations, thereby
allowing us to not have to typecast things everywhere.
This commit is contained in:
Erica Marigold 2024-12-13 14:33:05 +00:00
parent 45627ea4a9
commit ead60c003e
Signed by: DevComp
GPG key ID: 429EF1C337871656
15 changed files with 96 additions and 86 deletions

View file

@ -7,11 +7,11 @@ local pathfs = require("../lune_packages/pathfs")
local base64 = require("../lune_packages/base64")
local manifestTypes = require("../toolchainlib/src/manifest")
local types = require("../toolchainlib/src/utils/result_option_conv")
local Option = types.Option
type Option<T> = types.Option<T>
local Result = types.Result
type Result<T, E> = types.Result<T, E>
local Result = require("../lune_packages/result")
local Option = require("../lune_packages/option")
type Result<T, E> = Result.Result<T, E>
type Option<T> = Option.Option<T>
local Github = require("../toolchainlib/src/github")
type GithubContents = {

View file

@ -65,6 +65,24 @@ index_url = "https://github.com/daimond113/pesde-index"
environment = "lune"
lib = "lib/init.luau"
[graph."lukadev_0/result"."1.2.0 lune"]
direct = ["result", { name = "lukadev_0/result", version = "^1.2.0" }, "dev"]
resolved_ty = "dev"
[graph."lukadev_0/result"."1.2.0 lune".target]
environment = "lune"
lib = "lib/init.luau"
[graph."lukadev_0/result"."1.2.0 lune".pkg_ref]
ref_ty = "pesde"
name = "lukadev_0/result"
version = "1.2.0"
index_url = "https://github.com/daimond113/pesde-index"
[graph."lukadev_0/result"."1.2.0 lune".pkg_ref.target]
environment = "lune"
lib = "lib/init.luau"
[graph."synpixel/base64"."3.0.1 lune"]
direct = ["base64", { name = "synpixel/base64", version = "^3.0.1" }, "dev"]
resolved_ty = "dev"

View file

@ -9,6 +9,7 @@ environment = "lune"
[dev_dependencies]
option = { name = "lukadev_0/option", version = "^1.2.0" }
result = { name = "lukadev_0/result", version = "^1.2.0" }
pathfs = { name = "jiwonz/pathfs", version = "^0.1.0" }
base64 = { name = "synpixel/base64", version = "^3.0.1" }

View file

@ -4,13 +4,13 @@ local process = require("@lune/process")
local dirs = require("../lune_packages/dirs")
local pathfs = require("../lune_packages/pathfs")
local Result = require("../lune_packages/result")
local Option = require("../lune_packages/option")
type Result<T, E> = Result.Result<T, E>
type Option<T> = Option.Option<T>
local revTable = require("./utils/rev_table")
local CommandBuilder = require("./utils/exec")
local types = require("./utils/result_option_conv")
local Option = types.Option
type Option<T> = types.Option<T>
local Result = types.Result
type Result<T, E> = types.Result<T, E>
export type CompressionFormat = "TarGz" | "TarXz" | "Zip"

View file

@ -1,11 +1,11 @@
local net = require("@lune/net")
local Result = require("../lune_packages/result")
local Option = require("../lune_packages/option")
type Result<T, E> = Result.Result<T, E>
type Option<T> = Option.Option<T>
local copy = require("./utils/copy")
local types = require("./utils/result_option_conv")
local Option = types.Option
local Result = types.Result
type Option<T> = types.Option<T>
type Result<T, E> = types.Result<T, E>
local Github = {}
export type Github = typeof(setmetatable(Github :: GithubFields, { __index = Github }))

View file

@ -15,9 +15,11 @@ local serde = require("@lune/serde")
local pathfs = require("../lune_packages/pathfs")
local dirs = require("../lune_packages/dirs")
local types = require("./utils/result_option_conv")
local Option = types.Option
type Option<T> = types.Option<T>
local Result = require("../lune_packages/result")
local ResultExt = require("./utils/ext/result")
local Option = require("../lune_packages/option")
type Result<T, E> = Result.Result<T, E>
type Option<T> = Option.Option<T>
local Github = require("./github")
local PlatformDescriptor = require("./platform/descriptor")
@ -54,7 +56,7 @@ local function downloadAndDecompress(asset: {
return error(`Failed to download asset {asset.name}: HTTP Code {contentsResp.statusCode}`)
end
return compression.decompress[format](buffer.fromstring(contentsResp.body)):ok() :: Option<pathfs.Path>
return ResultExt.ok(compression.decompress[format](buffer.fromstring(contentsResp.body)))
end) :: Option<pathfs.Path>
end
@ -89,7 +91,9 @@ function runTool(tool: ToolId | pathfs.Path): number
-- FIXME: `process.spawn` has a bug where interactive features don't
-- forward properly
local toolId = tool :: ToolId
local path = if toolId.alias ~= nil then LINK_INSTALL_DIR:join(toolAliasOrDefault(toolId)) else tool :: pathfs.Path
local path = if (toolId :: any).alias ~= nil
then LINK_INSTALL_DIR:join(toolAliasOrDefault(toolId))
else tool :: pathfs.Path
return process.spawn(path:toString(), process.args, {
cwd = process.cwd,

View file

@ -4,9 +4,8 @@
local process = require("@lune/process")
local detection = require("./detection")
local types = require("../utils/result_option_conv")
local Option = types.Option
type Option<T> = types.Option<T>
local Option = require("../../lune_packages/option")
type Option<T> = Option.Option<T>
export type Arch = process.Arch | "arm" | "x86"

View file

@ -7,11 +7,11 @@ local toolchain = require("./toolchain")
local result = require("./result")
local detectFromExecutable = require("./detection/executable")
local types = require("../utils/result_option_conv")
local Option = types.Option
local Result = types.Result
type Option<T> = types.Option<T>
type Result<T, E> = types.Result<T, E>
local Result = require("../../lune_packages/result")
local Option = require("../../lune_packages/option")
local OptionExt = require("../utils/ext/option")
type Result<T, E> = Result.Result<T, E>
type Option<T> = Option.Option<T>
local PlatformDescriptor = {}
export type PlatformDescriptor = {
@ -63,7 +63,8 @@ function PlatformDescriptor.fromExecutable(path: string): result.PlatformResult<
}
end) :: Option<PlatformDescriptor>
return platformDesc:okOr(
return OptionExt.okOr(
platformDesc,
"NoExecutableDetected" :: result.PlatformError
) :: result.PlatformResult<PlatformDescriptor>
end

View file

@ -1,8 +1,7 @@
local String = require("../../utils/string")
local types = require("../../utils/result_option_conv")
local Option = types.Option
type Option<T> = types.Option<T>
local Option = require("../../../lune_packages/option")
type Option<T> = Option.Option<T>
local function charWordSep(char: string)
return char == " " or char == "-" or char == "_"

View file

@ -1,5 +1,5 @@
local types = require("../utils/result_option_conv")
type Result<T, E> = types.Result<T, E>
local Result = require("../../lune_packages/result")
type Result<T, E> = Result.Result<T, E>
export type PlatformError = "NoPatternDetected" | "NoExecutableDetected" | "UnknownExecutableField"
export type PlatformResult<T> = Result<T, PlatformError>

View file

@ -1,6 +1,5 @@
local types = require("../utils/result_option_conv")
local Option = types.Option
type Option<T> = types.Option<T>
local Option = require("../../lune_packages/option")
type Option<T> = Option.Option<T>
local TOOLCHAINS: { Toolchain } = { "msvc", "gnu", "musl" }
export type Toolchain = "msvc" | "gnu" | "musl"

View file

@ -3,9 +3,8 @@
local process = require("@lune/process")
local task = require("@lune/task")
local types = require("../utils/result_option_conv")
local Option = types.Option
type Option<T> = types.Option<T>
local Option = require("../../lune_packages/option")
type Option<T> = Option.Option<T>
local CommandBuilder = {}
type CommandBuilderFields = {
@ -34,10 +33,9 @@ export type ChildStatus = { ok: boolean, code: number, io: {
stderr: string,
} }
-- FIXME: remove unknown usage
local DEFAULT_STDIO_STRATEGY: IoStrategyMapping = {
stdout = Option.Some("pipe" :: StdioStrategy) :: Option<unknown>,
stderr = Option.Some("pipe" :: StdioStrategy) :: Option<unknown>,
stdout = Option.Some("pipe" :: StdioStrategy),
stderr = Option.Some("pipe" :: StdioStrategy),
}
local DEFAULT_RETRIES = 0
local DEFAULT_IGNORE_ERRORS = false
@ -84,11 +82,10 @@ function CommandBuilder.withStdioStrategy(
self: CommandBuilder,
strategy: StdioStrategy | IoStrategyMapping
): CommandBuilder
-- FIXME: remove unknown usage
self.stdioStrategy = Option.Some(if typeof(strategy) == "string"
then {
stdout = Option.Some(strategy) :: Option<unknown>,
stderr = Option.Some(strategy) :: Option<unknown>,
stdout = Option.Some(strategy),
stderr = Option.Some(strategy),
}
else strategy) :: Option<IoStrategyMapping>
return self
@ -124,12 +121,8 @@ function CommandBuilder.intoChildProcess(self: CommandBuilder): ChildProcess
else `{self.program} {argsList} & echo $!`,
{},
{
stdio = self
.stdioStrategy
-- FIXME: remove unknown usage
:orOpt(
Option.Some(DEFAULT_STDIO_STRATEGY) :: Option<unknown>
)
stdio = self.stdioStrategy
:orOpt(Option.Some(DEFAULT_STDIO_STRATEGY))
:map(function(mappings: IoStrategyMapping)
local translatedMappings: process.SpawnOptionsStdio = {}
for field, value in mappings do

View file

@ -0,0 +1,16 @@
--> Non-exhaustive set of extensions for the `Option<T>` type
local Option = require("../../../lune_packages/option")
local Result = require("../../../lune_packages/result")
local OptionExt = {}
function OptionExt.okOr<T, E>(self: Option.Option<T>, err: E): Result.Result<T, E>
return self:mapOrElse(function()
return Result.Err(err)
end, function(val)
return Result.Ok(val)
end) :: Result.Result<T, E>
end
return OptionExt

View file

@ -0,0 +1,14 @@
--> Non-exhaustive set of extensions for the `Result<T, E>` type
local Option = require("../../../lune_packages/option")
local Result = require("../../../lune_packages/result")
local ResultExt = {}
function ResultExt.ok<T, E>(self: Result.Result<T, E>): Option.Option<T>
return self:mapOr(Option.None, function(val: T)
return Option.Some(val)
end) :: Option.Option<T>
end
return ResultExt

View file

@ -1,34 +0,0 @@
--> Non-exhaustive set of extensions for Option<->Result conversion
local OptionImpl = require("../../lune_packages/option")
local ResultImpl = require("../../lune_packages/result")
local Option = {}
local Result = {}
export type Option<T> = OptionImpl.Option<T> & typeof(Option)
export type Result<T, E> = ResultImpl.Result<T, E> & typeof(Result)
function Option.okOr<T, E>(self: Option<T>, err: E): Result<T, E>
return self:mapOrElse(function()
return ResultImpl.Err(err)
end, function(val)
return ResultImpl.Ok(val)
end) :: Result<T, E>
end
function Result.ok<T, E>(self: Result<T, E>): Option<T>
return self:mapOr(OptionImpl.None, function(val: T)
return OptionImpl.Some(val)
end) :: Option<T>
end
return {
Option = setmetatable(OptionImpl, {
__index = Option,
}),
Result = setmetatable(ResultImpl, {
__index = Result,
}),
}