Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions src/std/result/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@ local ResultPrototype = table.freeze({
__index = Result;
})

export type Ok<T> = setmetatable<{
read _ok: true;
export type Result<T, E> = setmetatable<{
read _ok: true | false;
read _value: T;
}, typeof(ResultPrototype)>

export type Err<E> = setmetatable<{
read _ok: false;
read _err: E;
}, typeof(ResultPrototype)>

export type Result<T, E> = Ok<T> | Err<E>
export type Ok<T> = Result<T, never>
export type Err<E> = Result<never, E>

--- Checks if the given value is a Result.
local function is(value: unknown): boolean
Expand All @@ -25,13 +22,15 @@ local function ok<T>(value: T): Ok<T>
return table.freeze(setmetatable({
_ok = true :: true;
_value = value;
_err = nil :: never;
}, ResultPrototype))
end

--- Constructs a new `Err` Result.
local function err<E>(err: E): Err<E>
return table.freeze(setmetatable({
_ok = false :: false;
_value = nil :: never;
_err = err;
}, ResultPrototype))
end
Expand Down Expand Up @@ -227,13 +226,13 @@ end
--- Returns the contained `Ok` value, consuming the `self` value, without checking that the value is not an `Err`.
--- Calling this method on an `Err` is _undefined behavior_.
function Result.unwrap_unchecked<T, E>(self: Result<T, E>): T
return (self :: Ok<T>)._value
return self._value
end

--- Returns the contained `Err` value, consuming the `self` value, without checking the value is not an `Ok`.
--- calling this method on an `Ok`. is _undefined behavior_.
function Result.unwrap_err_unchecked<T, E>(self: Result<T, E>): E
return (self :: Err<E>)._err
return self._err
end

--- Class Result
Expand Down
46 changes: 23 additions & 23 deletions tests/std/result/.spec.luau
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ test.suite("std.result", function(suite)

asserts.eq(Result.ok(2):is_ok_and(even), true)
asserts.eq(Result.ok(3):is_ok_and(even), false)
asserts.eq((Result.err("e") :: any):is_ok_and(even), false)
asserts.eq((Result.err("e")):is_ok_and(even), false)
end)

suite:case(":is_err_and", function(asserts)
local isNotFound = function(e: string) return e == "not_found" end

asserts.eq(Result.err("not_found"):is_err_and(isNotFound), true)
asserts.eq(Result.err("other"):is_err_and(isNotFound), false)
asserts.eq((Result.ok(1) :: Result.Result<number, string>):is_err_and(isNotFound), false)
asserts.eq((Result.ok(1)):is_err_and(isNotFound), false)
end)

suite:case(":map on Ok applies the function", function(asserts)
Expand All @@ -52,7 +52,7 @@ test.suite("std.result", function(suite)
end)

suite:case(":map on Err passes through", function(asserts)
local res = (Result.err("e") :: any):map(function(n: number) return n * 10 end)
local res = Result.err("e"):map(function(n: number) return n * 10 end)

asserts.eq(res:is_err(), true)
asserts.eq(res:unwrap_err(), "e")
Expand All @@ -62,19 +62,19 @@ test.suite("std.result", function(suite)
local double = function(n: number) return n * 2 end

asserts.eq(Result.ok(5):map_or(0, double), 10)
asserts.eq((Result.err("e") :: any):map_or(0, double), 0)
asserts.eq(Result.err("e"):map_or(0, double), 0)
end)

suite:case(":map_or_else", function(asserts)
local fallback = function(e: string) return #e end
local double = function(n: number) return n * 2 end

asserts.eq(Result.ok(5):map_or_else(fallback, double), 10)
asserts.eq((Result.err("oops") :: any):map_or_else(fallback, double), 4)
asserts.eq(Result.err("oops"):map_or_else(fallback, double), 4)
end)

suite:case(":map_err on Err applies the function", function(asserts)
local res = (Result.err("bad") :: Result.Result<number, string>):map_err(function(e) return `wrapped:{e}` end)
local res = Result.err("bad"):map_err(function(e) return `wrapped:{e}` end)

asserts.eq(res:is_err(), true)
asserts.eq(res:unwrap_err(), "wrapped:bad")
Expand All @@ -96,7 +96,7 @@ test.suite("std.result", function(suite)
asserts.eq(returned, original)

seen = nil
local errOriginal = Result.err("e") :: Result.Result<number, string>
local errOriginal = Result.err("e")
local errReturned = errOriginal:inspect(function(v) seen = v end)

asserts.eq(seen, nil)
Expand All @@ -105,14 +105,14 @@ test.suite("std.result", function(suite)

suite:case(":inspect_err runs the closure only on Err and returns self", function(asserts)
local seen: string? = nil
local original = Result.err("boom") :: Result.Result<number, string>
local original = Result.err("boom")
local returned = original:inspect_err(function(e) seen = e end)

asserts.eq(seen, "boom")
asserts.eq(returned, original)

seen = nil
local okOriginal = Result.ok(1) :: Result.Result<number, string>
local okOriginal = Result.ok(1)
local okReturned = okOriginal:inspect_err(function(e) seen = e end)

asserts.eq(seen, nil)
Expand All @@ -125,7 +125,7 @@ test.suite("std.result", function(suite)

suite:case(":expect throws with the given message on Err", function(asserts)
asserts.throwsWith("boom", function()
(Result.err("inner") :: Result.Result<number, string>):expect("boom")
Result.err("inner"):expect("boom")
end)
end)

Expand All @@ -135,7 +135,7 @@ test.suite("std.result", function(suite)

suite:case(":unwrap throws with the inner error on Err", function(asserts)
asserts.throwsWith("inner error", function()
(Result.err("inner error") :: Result.Result<number, string>):unwrap()
Result.err("inner error"):unwrap()
end)
end)

Expand All @@ -145,7 +145,7 @@ test.suite("std.result", function(suite)

suite:case(":expect_err throws with the given message on Ok", function(asserts)
asserts.throwsWith("expected err", function()
(Result.ok(1) :: Result.Result<number, string>):expect_err("expected err")
Result.ok(1):expect_err("expected err")
end)
end)

Expand All @@ -155,16 +155,16 @@ test.suite("std.result", function(suite)

suite:case(":unwrap_err throws with the inner value on Ok", function(asserts)
asserts.throwsWith("inner value", function()
(Result.ok("inner value") :: Result.Result<string, string>):unwrap_err()
Result.ok("inner value"):unwrap_err()
end)
end)

suite:case(":and_res", function(asserts)
local later = Result.ok(99) :: Result.Result<number, string>
local later = Result.ok(99)

asserts.eq(Result.ok(1):and_res(later):unwrap(), 99)

local errSelf = Result.err("first") :: Result.Result<number, string>
local errSelf = Result.err("first")
local andResult = errSelf:and_res(later)
asserts.eq(andResult:is_err(), true)
asserts.eq(andResult:unwrap_err(), "first")
Expand All @@ -182,16 +182,16 @@ test.suite("std.result", function(suite)
local errChain = Result.ok(-1):and_then(sqrtOrErr)
asserts.eq(errChain:unwrap_err(), "negative")

local passthrough = (Result.err("first") :: any):and_then(sqrtOrErr)
local passthrough = Result.err("first"):and_then(sqrtOrErr)
asserts.eq(passthrough:unwrap_err(), "first")
end)

suite:case(":or_res", function(asserts)
local fallback = Result.ok(99) :: Result.Result<number, string>
local fallback = Result.ok(99)

asserts.eq(Result.ok(1):or_res(fallback):unwrap(), 1)

local errSelf = Result.err("first") :: Result.Result<number, string>
local errSelf = Result.err("first")
asserts.eq(errSelf:or_res(fallback):unwrap(), 99)
end)

Expand All @@ -201,10 +201,10 @@ test.suite("std.result", function(suite)
return Result.err(`fatal:{e}`)
end

local errChain = (Result.err("recoverable") :: Result.Result<number, string>):or_else(recover)
local errChain = (Result.err("recoverable")):or_else(recover)
asserts.eq(errChain:unwrap(), 0)

local errChain2 = (Result.err("bad") :: Result.Result<number, string>):or_else(recover)
local errChain2 = (Result.err("bad")):or_else(recover)
asserts.eq(errChain2:unwrap_err(), "fatal:bad")

local passthrough = Result.ok(7):or_else(recover)
Expand All @@ -213,14 +213,14 @@ test.suite("std.result", function(suite)

suite:case(":unwrap_or", function(asserts)
asserts.eq(Result.ok(1):unwrap_or(99), 1)
asserts.eq((Result.err("e") :: Result.Result<number, string>):unwrap_or(99), 99)
asserts.eq((Result.err("e")):unwrap_or(99), 99)
end)

suite:case(":unwrap_or_else", function(asserts)
local recover = function(e: string) return #e end

asserts.eq(Result.ok(1):unwrap_or_else(recover), 1)
asserts.eq((Result.err("hello") :: Result.Result<number, string>):unwrap_or_else(recover), 5)
asserts.eq((Result.err("hello")):unwrap_or_else(recover), 5)
end)

suite:case(":unwrap_unchecked returns the inner value on Ok", function(asserts)
Expand All @@ -233,7 +233,7 @@ test.suite("std.result", function(suite)

suite:case("instances are frozen", function(asserts)
local res = Result.ok(1)
asserts.eq(table.isfrozen(res :: any), true)
asserts.eq(table.isfrozen(res), true)
end)
end)

Expand Down