Skip to content

Commit

Permalink
Try improve error reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
centau committed Dec 27, 2024
1 parent 3b22f6c commit 58a31a1
Show file tree
Hide file tree
Showing 16 changed files with 164 additions and 55 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
for this.
- Implicit effects to set children now unparent all children when the effect is
destroyed.
- Error reporting should be improved with better formatting when effects invoke
other effects and no more loss of stack traces.

### Removed

Expand Down
5 changes: 2 additions & 3 deletions src/apply.luau
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
local typeof = game and typeof or require "../test/mock".typeof :: never

local flags = require "./flags"
local throw = require "./throw"
local implicit_effect = require "./implicit_effect"
local _, is_action = require "./action"()
local graph = require "./graph"
Expand Down Expand Up @@ -67,7 +66,7 @@ local function process_properties(properties: Map<unknown, unknown>, instance: I
if type(property) == "string" then
if flags.strict then -- check for duplicate property assignment at nesting depth
if cache.nested_debug[depth][property] then
throw(`duplicate property {property} at depth {depth}`)
error(`duplicate property {property} at depth {depth}`, 0)
end
cache.nested_debug[depth][property] = true
end
Expand Down Expand Up @@ -104,7 +103,7 @@ end
-- applies table of nested properties to an instance using full vide semantics
local function apply<T>(instance: T & Instance, properties: { [unknown]: unknown }): T
if not properties then
throw("attempt to call a constructor returned by create() with no properties")
error "attempt to call a constructor returned by create() with no properties"
end

-- queue parent assignment if any for last
Expand Down
5 changes: 2 additions & 3 deletions src/batch.luau
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
local flags = require "./flags"
local throw = require "./throw"
local graph = require "./graph"

local function batch(setter: () -> ())
Expand All @@ -11,14 +10,14 @@ local function batch(setter: () -> ())
from = graph.get_update_queue_length()
end

local ok, err: string? = pcall(setter)
local ok, err: string? = xpcall(setter, debug.traceback)

if not already_batching then
flags.batch = false
graph.flush_update_queue(from)
end

if not ok then throw(`error occured while batching updates: {err}`) end
if not ok then error(`error occured while batching updates: {err}`, 0) end
end

return batch
5 changes: 2 additions & 3 deletions src/cleanup.luau
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
local typeof = game and typeof or require "../test/mock".typeof :: never

local throw = require "./throw"
local graph = require "./graph"
local get_scope = graph.get_scope
local push_cleanup = graph.push_cleanup
Expand All @@ -14,14 +13,14 @@ local function helper(obj: any)
elseif obj.disconnect then function() obj:disconnect() end
elseif obj.Destroy then function() obj:Destroy() end
elseif obj.Disconnect then function() obj:Disconnect() end
else throw("cannot cleanup given object")
else error "cannot cleanup given object"
end

local function cleanup(value: unknown)
local scope = get_scope()

if not scope then
throw "cannot cleanup outside a stable or reactive scope"
error "cannot cleanup outside a stable or reactive scope"
end; assert(scope)

if type(value) == "function" then
Expand Down
7 changes: 3 additions & 4 deletions src/context.luau
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local throw = require "./throw"
local graph = require "./graph"
type Node<T> = graph.Node<T>
local create_node = graph.create_node
Expand Down Expand Up @@ -44,10 +43,10 @@ local function context<T>(...: T): Context<T>
if has_default ~= nil then
return default_value
else
throw("attempt to get context when no context is set and no default context is set")
error("attempt to get context when no context is set and no default context is set", 0)
end
else -- set
if not scope then return throw("attempt to set context outside of a vide scope") end
if not scope then return error("attempt to set context outside of a vide scope", 0) end

local value, component = ...

Expand All @@ -62,7 +61,7 @@ local function context<T>(...: T): Context<T>
pop_scope()

if not ok then
throw(`error while running context:\n\n{result}`)
error(`error while running context:\n\n{result}`, 0)
end

return result
Expand Down
7 changes: 3 additions & 4 deletions src/create.luau
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
local typeof = game and typeof or require "../test/mock".typeof :: never
local Instance = game and Instance or require "../test/mock".Instance :: never

local throw = require "./throw"
local defaults = require "./defaults"
local apply = require "./apply"

Expand All @@ -10,7 +9,7 @@ local ctor_cache = {} :: { [string]: () -> Instance }
setmetatable(ctor_cache :: any, {
__index = function(self, class)
local ok, instance: Instance = pcall(Instance.new, class :: any)
if not ok then throw(`invalid class name, could not create instance of class { class }`) end
if not ok then error(`invalid class name, could not create instance of class { class }`, 0) end

local default: { [string]: unknown }? = defaults[class]
if default then
Expand All @@ -35,7 +34,7 @@ end
local function clone_instance(instance: Instance)
return function(properties: Props): Instance
local clone = instance:Clone()
if not clone then throw "attempt to clone a non-archivable instance" end
if not clone then error "attempt to clone a non-archivable instance" end
return apply(clone, properties)
end
end
Expand All @@ -47,7 +46,7 @@ local function create(class_or_instance: string | Instance, props: Props?): ((Pr
elseif typeof(class_or_instance) == "Instance" then
result = clone_instance(class_or_instance)
else
throw("bad argument #1, expected string or instance, got " .. typeof(class_or_instance))
error("bad argument #1, expected string or instance, got " .. typeof(class_or_instance), 0)
return nil :: never
end
if props then
Expand Down
31 changes: 22 additions & 9 deletions src/graph.luau
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local throw = require "./throw"
local flags = require "./flags"

export type SourceNode<T> = {
Expand All @@ -22,9 +21,23 @@ export type Node<T> = {

local scopes = { n = 0 } :: { [number]: Node<any>, n: number } -- scopes stack

local function efn(err: string)
local trace = debug.traceback(err, 2)

if string.find(err, "^effect error stacktrace") then -- if effect error is nested
trace = string.gsub(" " .. trace, "\n", function() -- indent entire error
return "\n "
end)
end

trace ..= "\nsource update stacktrace:"
return trace
end

local function ycall<T, U>(fn: (T) -> U, arg: T): (boolean, string|U)

local thread = coroutine.create(xpcall)
local function efn(err: string) return debug.traceback(err, 3) end
--local function efn(err: string) return debug.traceback(err, 3) end
local resume_ok, run_ok, result = coroutine.resume(thread, fn, efn, arg)

assert(resume_ok)
Expand All @@ -45,9 +58,9 @@ local function assert_stable_scope(): Node<unknown>

if not scope then
local caller_name = debug.info(2, "n")
return throw(`cannot use {caller_name}() outside a stable or reactive scope`)
return error(`cannot use {caller_name}() outside a stable or reactive scope`, 0)
elseif scope.effect then
throw("cannot create a new reactive scope inside another reactive scope")
error("cannot create a new reactive scope inside another reactive scope", 0)
end

return scope
Expand Down Expand Up @@ -81,8 +94,8 @@ end
local function flush_cleanups<T>(node: Node<T>)
if node.cleanups then
for _, fn in next, node.cleanups do
local ok, err: string? = pcall(fn)
if not ok then throw(`cleanup error: {err}`) end
local ok, err: string? = xpcall(fn, debug.traceback)
if not ok then error(`cleanup error: {err}`, 0) end
end

table.clear(node.cleanups)
Expand All @@ -107,7 +120,7 @@ end

local function destroy<T>(node: Node<T>)
if flags.strict and table.find(scopes, node) then
throw("attempt to destroy an active scope")
error("attempt to destroy an active scope", 0)
end

flush_cleanups(node)
Expand Down Expand Up @@ -150,7 +163,7 @@ local function evaluate_node<T>(node: Node<T>)
if not ok then
table.clear(update_queue)
update_queue.n = 0
throw(`effect stacktrace:\n{new_value :: string}`)
error(`effect error stacktrace\n{new_value :: string}`, 0)
end

node.cache = new_value :: T
Expand All @@ -170,7 +183,7 @@ local function evaluate_node<T>(node: Node<T>)
if not ok then
table.clear(update_queue)
update_queue.n = 0
throw(`effect stacktrace:\n{new_value}\n`)
error(`effect error:\n{new_value}\n`, 0)
end

node.cache = new_value
Expand Down
5 changes: 2 additions & 3 deletions src/lib.luau
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ local indexes, values = require "./maps"()
local spring, update_springs = require "./spring"()
local action = require "./action"()
local changed = require "./changed"
local throw = require "./throw"
local flags = require "./flags"

export type Source<T> = source.Source<T>
Expand Down Expand Up @@ -98,15 +97,15 @@ local vide = {
setmetatable(vide :: any, {
__index = function(_, index: unknown): ()
if flags[index] == nil then
throw(`{tostring(index)} is not a valid member of vide`)
error(`{tostring(index)} is not a valid member of vide`, 0)
else
return flags[index]
end
end,

__newindex = function(_, index: unknown, value: unknown)
if flags[index] == nil then
throw(`{tostring(index)} is not a valid member of vide`)
error(`{tostring(index)} is not a valid member of vide, 0`)
else
flags[index] = value
end
Expand Down
9 changes: 4 additions & 5 deletions src/maps.luau
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local throw = require "./throw"
local flags = require "./flags"
local graph = require "./graph"
type Node<T> = graph.Node<T>
Expand All @@ -20,7 +19,7 @@ local function check_primitives(t: {})

for _, v in next, t do
if type(v) == "table" or type(v) == "userdata" or type(v) == "function" then continue end
throw("table source map cannot return primitives")
error("table source map cannot return primitives", 0)
end
end

Expand Down Expand Up @@ -71,7 +70,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,

push_scope(scope)

local ok, result = pcall(transform, function()
local ok, result = xpcall(transform, debug.traceback, function()
push_child_to_scope(node)
return node.cache
end, i)
Expand Down Expand Up @@ -132,7 +131,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
local cache = {}
for _, v in next, data do
if cache[v] ~= nil then
throw "duplicate table value detected"
error "duplicate table value detected"
end
cache[v] = true
end
Expand All @@ -154,7 +153,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->

push_scope(scope)

local ok, result = pcall(transform, v, function()
local ok, result = xpcall(transform, debug.traceback, v, function()
push_child_to_scope(node)
return node.cache
end)
Expand Down
8 changes: 3 additions & 5 deletions src/root.luau
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local throw = require "./throw"
local graph = require "./graph"
type Node<T> = graph.Node<T>
local create_node = graph.create_node
Expand All @@ -14,21 +13,20 @@ local function root<T...>(fn: (destroy: () -> ()) -> T...): (() -> (), T...)
refs[node] = true -- prevent gc of root node

local destroy = function()
if not refs[node] then throw "root already destroyed" end
if not refs[node] then error "root already destroyed" end
refs[node] = nil
destroy(node)
end

push_scope(node)

local function efn(err: string) return debug.traceback(err, 3) end
local result = { xpcall(fn, efn, destroy) }
local result = { xpcall(fn, debug.traceback, destroy) }

pop_scope()

if not result[1] then
destroy()
throw(`error while running root():\n\n{result[2]}`)
error(`error while running root():\n\n{result[2]}`, 0)
end

return destroy, unpack(result :: any, 2)
Expand Down
4 changes: 3 additions & 1 deletion src/source.luau
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type Source<T> = (() -> T) & ((value: T) -> T)
local function source<T>(initial_value: T): Source<T>
local node = create_source_node(initial_value)

return function(...): T
local function update_source(...): T
if select("#", ...) == 0 then -- no args were given
push_child_to_scope(node)
return node.cache
Expand All @@ -24,6 +24,8 @@ local function source<T>(initial_value: T): Source<T>
update_descendants(node)
return v
end

return update_source
end

return source :: (<T>(initial_value: T) -> Source<T>) & (<T>() -> Source<T>)
7 changes: 2 additions & 5 deletions src/spring.luau
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local throw = require "./throw"
local graph = require "./graph"
type Node<T> = graph.Node<T>
type SourceNode<T> = graph.SourceNode<T>
Expand Down Expand Up @@ -114,7 +113,7 @@ local vec6_to_type = {

local invalid_type = {
__index = function(_, t: string)
throw(`cannot spring type {t}`)
error(`cannot spring type {t}`, 0)
end
}

Expand All @@ -141,7 +140,7 @@ local function spring<T>(source: () -> T, period: number?, damping_ratio: number
-- todo: is there a solution other than reducing step size?
-- todo: this does not catch all solver exploding cases
if c > UPDATE_RATE*2 then -- solver will explode if this is true
throw("spring damping too high, consider reducing damping or increasing period")
error("spring damping too high, consider reducing damping or increasing period", 0)
end

local data: SpringState<T> = {
Expand Down Expand Up @@ -263,8 +262,6 @@ local function step_springs(dt: number)
end
end

local remove_queue = {}

local function update_spring_sources()
for data, output in springs do
local x0_123, x1_123, v_123,
Expand Down
5 changes: 2 additions & 3 deletions src/switch.luau
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local throw = require "./throw"
local graph = require "./graph"
type Node<T> = graph.Node<T>
type SourceNode<T> = graph.SourceNode<T>
Expand Down Expand Up @@ -32,15 +31,15 @@ local function switch<T, U>(source: () -> T): (map: Map<T, ((() -> U)?)>) -> ()
if component == nil then return nil end

if type(component) ~= "function" then
throw "map must map a value to a function"
error "map must map a value to a function"
end

local new_scope = create_node(owner, false, false)
last_scope = new_scope :: Node<any>

push_scope(new_scope)

local ok, result = pcall(component)
local ok, result = xpcall(component, debug.traceback)

pop_scope()

Expand Down
5 changes: 0 additions & 5 deletions src/throw.luau

This file was deleted.

Loading

0 comments on commit 58a31a1

Please sign in to comment.