More info, and more general.
This commit is contained in:
parent
dcee4f6f95
commit
e11ec95fb5
|
@ -39,6 +39,41 @@ local function get_value_token (token)
|
|||
assert(false)
|
||||
end
|
||||
|
||||
local function get_variable (var_name, info)
|
||||
--
|
||||
assert(type(var_name) == 'string')
|
||||
assert(type(info) == 'table')
|
||||
|
||||
-- Local
|
||||
if info.locals[var_name] then
|
||||
local var_info = info.locals[var_name]
|
||||
return var_info[1], var_info[2] and ('argument #'..var_info[3]) or 'local', info.name or var_info[2] and ''
|
||||
end
|
||||
|
||||
-- Up-value
|
||||
local index = 0
|
||||
repeat
|
||||
index = index + 1
|
||||
local name, val = debug.getupvalue(info.func, index)
|
||||
if name == var_name then return val, 'upvalue' end
|
||||
until not name
|
||||
|
||||
-- Global
|
||||
return getfenv(info.func)[var_name], 'global'
|
||||
end
|
||||
|
||||
local function get_value_from_lvalue (lvalue, info)
|
||||
assert(type(lvalue) == 'table')
|
||||
assert(type(info) == 'table')
|
||||
|
||||
-- Base value
|
||||
local value, var_scope, in_func = get_variable(lvalue[1], info)
|
||||
-- Sub value
|
||||
for i = 2, #lvalue do value = value[lvalue[i].value] end
|
||||
--
|
||||
return value, var_scope, in_func
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Parsing
|
||||
|
||||
|
@ -51,15 +86,15 @@ local function parse (tokens)
|
|||
return {
|
||||
exp = 'COMPARE',
|
||||
binop = 'EQ',
|
||||
left = { exp = 'CALL', callee = get_value_token(tokens[1]), get_value_token(tokens[3]) },
|
||||
right = get_value_token(tokens[6]),
|
||||
[1] = { exp = 'CALL', get_value_token(tokens[1]), get_value_token(tokens[3]) },
|
||||
[2] = get_value_token(tokens[6]),
|
||||
}
|
||||
elseif #tokens == 3 and VALUE_TOKEN[tokens[1].token] and COMPARE_BINOP[tokens[2].token] and VALUE_TOKEN[tokens[3].token] then
|
||||
return {
|
||||
exp = 'COMPARE',
|
||||
binop = tokens[2].token,
|
||||
left = get_value_token(tokens[1]),
|
||||
right = get_value_token(tokens[3])
|
||||
[1] = get_value_token(tokens[1]),
|
||||
[2] = get_value_token(tokens[3])
|
||||
}
|
||||
elseif #tokens == 3 and tokens[1].token == 'IDENTIFIER' and tokens[2].token == 'DOT' and tokens[3].token == 'IDENTIFIER' then
|
||||
return { exp = 'LVALUE', tokens[1].text, { exp = 'STRING', value = tokens[3].text } }
|
||||
|
@ -72,8 +107,13 @@ local function parse (tokens)
|
|||
end
|
||||
end
|
||||
|
||||
local function populate_ast_with_semantics (node, env)
|
||||
|
||||
local function populate_ast_with_semantics (node, info)
|
||||
if type(node) ~= 'table' then return end
|
||||
for i = 1, #node do populate_ast_with_semantics(node[i], info) end
|
||||
--
|
||||
if node.exp == 'LVALUE' then
|
||||
node.value = get_value_from_lvalue(node, info)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
@ -112,29 +152,6 @@ local function get_assert_body (call_info)
|
|||
return lexer:lex(text), text
|
||||
end
|
||||
|
||||
local function get_variable (var_name, info)
|
||||
--
|
||||
assert(type(var_name) == 'string')
|
||||
assert(type(info) == 'table')
|
||||
|
||||
-- Local
|
||||
if info.locals[var_name] then
|
||||
local var_info = info.locals[var_name]
|
||||
return var_info[1], var_info[2] and ('argument #'..var_info[3]) or 'local', info.name or var_info[2] and ''
|
||||
end
|
||||
|
||||
-- Up-value
|
||||
local index = 0
|
||||
repeat
|
||||
index = index + 1
|
||||
local name, val = debug.getupvalue(info.func, index)
|
||||
if name == var_name then return val, 'upvalue' end
|
||||
until not name
|
||||
|
||||
-- Global
|
||||
return getfenv(info.func)[var_name], 'global'
|
||||
end
|
||||
|
||||
local function get_function_name (call_info)
|
||||
--
|
||||
if call_info.name then return string.format('\'%s\'', call_info.name) end
|
||||
|
@ -170,10 +187,7 @@ end
|
|||
local function get_variable_and_prefix (lvalue, call_info)
|
||||
assert(type(lvalue) == 'table' and lvalue.exp == 'LVALUE')
|
||||
--
|
||||
local base_value, var_scope, in_func = get_variable(lvalue[1], call_info)
|
||||
-- Determine value of variable
|
||||
local value = base_value
|
||||
for i = 2, #lvalue do value = value[lvalue[i].value] end
|
||||
local value, var_scope, in_func = get_value_from_lvalue(lvalue, call_info)
|
||||
--
|
||||
local func_name = in_func and (' to '..get_function_name(call_info)) or ''
|
||||
return value, ('bad %s%s'):format(fmt_lvalue(lvalue, var_scope), func_name)
|
||||
|
@ -189,9 +203,10 @@ local COMPLEX_TYPES = {
|
|||
['table'] = true,
|
||||
['userdata'] = true,
|
||||
['cdata'] = true,
|
||||
['function'] = true,
|
||||
}
|
||||
|
||||
local function fmt_with_type (val)
|
||||
local function fmt_val_with_type (val)
|
||||
-- Primitive values ARE their type, and don't need the annotation.
|
||||
if PRIMITIVE_VALUES[type(val)] then return tostring(val) end
|
||||
-- Complex types are already formatted with some type information.
|
||||
|
@ -205,25 +220,26 @@ end
|
|||
local function determine_error_message (call_info, msg, condition)
|
||||
local tokens, body_text = get_assert_body(call_info)
|
||||
local ast = parse(tokens)
|
||||
populate_ast_with_semantics(ast, call_info)
|
||||
|
||||
msg[1] = ('expression `%s` evaluated to %s'):format(body_text, condition)
|
||||
local var_prefix = function(token) return get_variable_and_prefix(token, call_info) end
|
||||
|
||||
if not ast then return nil
|
||||
elseif ast.exp == 'COMPARE' and ast.binop == 'EQ' and ast.left.exp == 'CALL' and ast.left.callee.exp == 'LVALUE' and ast.left.callee[1] == 'type' then
|
||||
local gotten_val, prefix = var_prefix(ast.left[1])
|
||||
msg[1] = ('%s (%s expected, but got %s: %s)'):format(prefix, ast.right.value, type(gotten_val), fmt_val(gotten_val))
|
||||
elseif ast.exp == 'COMPARE' and ast.binop == 'EQ' and ast[1].exp == 'CALL' and ast[1][1].exp == 'LVALUE' and ast[1][1][1] == 'type' then
|
||||
local gotten_val, prefix = var_prefix(ast[1][2])
|
||||
msg[1] = ('%s (%s expected, but got %s)'):format(prefix, ast[2].value, fmt_val_with_type(gotten_val))
|
||||
|
||||
elseif ast.exp == 'COMPARE' and ast.binop == 'EQ' then
|
||||
local gotten_value, prefix = var_prefix(ast.left)
|
||||
local expected_value = ast.right.value
|
||||
local type_annotation = (type(expected_value) == type(gotten_value)) and '' or (' '..type(gotten_value))
|
||||
msg[1] = ('%s (%s expected, but got%s: %s)'):format(prefix, fmt_with_type(expected_value), type_annotation, fmt_val(gotten_value))
|
||||
local gotten_value, prefix = var_prefix(ast[1])
|
||||
local expected_value = ast[2].value
|
||||
local fmt_gotten = (type(expected_value) == type(gotten_value)) and fmt_val or fmt_val_with_type
|
||||
msg[1] = ('%s (%s expected, but got %s)'):format(prefix, fmt_val_with_type(expected_value), fmt_gotten(gotten_value))
|
||||
|
||||
elseif ast.exp == 'COMPARE' and ast.binop == 'NEQ' then
|
||||
local gotten_val, prefix = var_prefix(ast.left)
|
||||
local expected_value = ast.right.value
|
||||
msg[1] = ('%s (expected anything other than %s, but got %s)'):format(prefix, fmt_with_type(expected_value), fmt_val(gotten_val))
|
||||
local gotten_val, prefix = var_prefix(ast[1])
|
||||
local expected_value = ast[2].value
|
||||
msg[1] = ('%s (expected anything other than %s, but got %s)'):format(prefix, fmt_val_with_type(expected_value), fmt_val(gotten_val))
|
||||
|
||||
elseif ast.exp == 'LVALUE' then
|
||||
local gotten_val, prefix = var_prefix(ast)
|
||||
|
|
|
@ -11,7 +11,7 @@ SUITE:addTest('local variable', function ()
|
|||
local a = 2
|
||||
assert(type(a) == 'string')
|
||||
end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (string expected, but got number: 2)', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (string expected, but got number 2)', msg)
|
||||
end)
|
||||
|
||||
SUITE:addTest('upvalue variable', function ()
|
||||
|
@ -19,7 +19,7 @@ SUITE:addTest('upvalue variable', function ()
|
|||
local _, msg = pcall(function ()
|
||||
assert(type(a) == 'string')
|
||||
end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad upvalue \'a\' (string expected, but got number: 2)', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad upvalue \'a\' (string expected, but got number 2)', msg)
|
||||
end)
|
||||
|
||||
SUITE:addTest('global variable', function ()
|
||||
|
@ -27,7 +27,7 @@ SUITE:addTest('global variable', function ()
|
|||
local _, msg = pcall(function ()
|
||||
assert(type(a) == 'string')
|
||||
end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad global \'a\' (string expected, but got number: 2)', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad global \'a\' (string expected, but got number 2)', msg)
|
||||
end)
|
||||
|
||||
SUITE:addTest('argument to anon function', function ()
|
||||
|
@ -36,7 +36,7 @@ SUITE:addTest('argument to anon function', function ()
|
|||
assert(type(a) == 'string')
|
||||
end)(2)
|
||||
end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-3)..': '..'assertion failed! bad argument #1 \'a\' to the anonymous function at ./test/test_assert-gooder.lua:'..curline(-4)..' (string expected, but got number: 2)', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-3)..': '..'assertion failed! bad argument #1 \'a\' to the anonymous function at ./test/test_assert-gooder.lua:'..curline(-4)..' (string expected, but got number 2)', msg)
|
||||
end)
|
||||
|
||||
SUITE:addTest('argument to named function', function ()
|
||||
|
@ -44,7 +44,7 @@ SUITE:addTest('argument to named function', function ()
|
|||
assert(type(a) == 'string')
|
||||
end
|
||||
local _, msg = pcall(function () f(2) end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-3)..': '..'assertion failed! bad argument #1 \'a\' to \'f\' (string expected, but got number: 2)', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-3)..': '..'assertion failed! bad argument #1 \'a\' to \'f\' (string expected, but got number 2)', msg)
|
||||
end)
|
||||
|
||||
|
||||
|
@ -53,7 +53,7 @@ SUITE:addTest('indexing', function ()
|
|||
local a = { b = 39 }
|
||||
assert(type(a.b) == 'string')
|
||||
end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad key "b" in local \'a\' (string expected, but got number: 39)', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad key "b" in local \'a\' (string expected, but got number 39)', msg)
|
||||
end)
|
||||
|
||||
SUITE:addTest('subscript constant', function ()
|
||||
|
@ -61,7 +61,7 @@ SUITE:addTest('subscript constant', function ()
|
|||
local a = { 4, 2, 3, 6 }
|
||||
assert(type(a.b) == 'string')
|
||||
end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad key 2 in local \'a\' (string expected, but got number: 2)', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad key 2 in local \'a\' (string expected, but got number 2)', msg)
|
||||
end)
|
||||
|
||||
SUITE:addTest('subscript variable', function ()
|
||||
|
@ -69,7 +69,7 @@ SUITE:addTest('subscript variable', function ()
|
|||
local a, i = { 4, 2, 3, 6 }, 2
|
||||
assert(type(a.b) == 'string')
|
||||
end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad key 2 in local \'a\' (string expected, but got number: 2)', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad key 2 in local \'a\' (string expected, but got number 2)', msg)
|
||||
end)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
@ -77,7 +77,7 @@ end)
|
|||
SUITE:addTest('can improve asserts in loaded strings too', function ()
|
||||
local func = loadstring "return function(a) assert(type(a) == 'string') end" ()
|
||||
local _, msg = pcall(setfenv(func, getfenv()), 42)
|
||||
assert_equal('[string "return function(a) assert(type(a) == \'string\'..."]:1: '..'assertion failed! bad argument #1 \'a\' to the anonymous function from loaded string (string expected, but got number: 42)', msg)
|
||||
assert_equal('[string "return function(a) assert(type(a) == \'string\'..."]:1: '..'assertion failed! bad argument #1 \'a\' to the anonymous function from loaded string (string expected, but got number 42)', msg)
|
||||
end)
|
||||
|
||||
SUITE:addTest('really complicated expression', function ()
|
||||
|
@ -96,7 +96,7 @@ SUITE:addTest('compare values', function ()
|
|||
local a = 2
|
||||
assert(a == 4)
|
||||
end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (number 4 expected, but got: 2)', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (number 4 expected, but got 2)', msg)
|
||||
end)
|
||||
|
||||
SUITE:addTest('compare values across types', function ()
|
||||
|
@ -104,7 +104,7 @@ SUITE:addTest('compare values across types', function ()
|
|||
local a = "hi"
|
||||
assert(a == 4)
|
||||
end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (number 4 expected, but got string: "hi")', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (number 4 expected, but got string "hi")', msg)
|
||||
end)
|
||||
|
||||
SUITE:addTest('is nil', function ()
|
||||
|
@ -112,7 +112,7 @@ SUITE:addTest('is nil', function ()
|
|||
local a = "hi"
|
||||
assert(a == nil)
|
||||
end)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (nil expected, but got string: "hi")', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'a\' (nil expected, but got string "hi")', msg)
|
||||
end)
|
||||
|
||||
SUITE:addTest('truthy', function ()
|
||||
|
@ -175,12 +175,11 @@ SUITE:addTest('constant nil', function ()
|
|||
end)
|
||||
|
||||
SUITE:addTest('function as type argument', function ()
|
||||
local f = function() end
|
||||
local _, msg = pcall(function ()
|
||||
local f = function() end
|
||||
assert(type(f) == 'string')
|
||||
end)
|
||||
-- TODO: How do we test this?
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad local \'f\' (string expected, but got function)', msg)
|
||||
assert_equal('./test/test_assert-gooder.lua:'..curline(-2)..': '..'assertion failed! bad upvalue \'f\' (string expected, but got '..tostring(f)..')', msg)
|
||||
end)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in New Issue
Block a user