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