1
0

More info, and more general.

This commit is contained in:
Jon Michael Aanes 2017-11-07 11:28:41 +01:00
parent dcee4f6f95
commit e11ec95fb5
2 changed files with 74 additions and 59 deletions

View File

@ -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)

View File

@ -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)
--------------------------------------------------------------------------------