aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/eval/typval.c4
-rw-r--r--test/functional/eval/container_functions_spec.lua24
-rw-r--r--test/unit/eval/helpers.lua2
-rw-r--r--test/unit/eval/typval_spec.lua184
4 files changed, 187 insertions, 27 deletions
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 1cc195ddc4..746cbbfe1c 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -479,11 +479,13 @@ void tv_list_extend(list_T *const l1, list_T *const l2,
FUNC_ATTR_NONNULL_ARG(1, 2)
{
int todo = l2->lv_len;
+ listitem_T *const befbef = (bef == NULL ? NULL : bef->li_prev);
+ listitem_T *const saved_next = (befbef == NULL ? NULL : befbef->li_next);
// We also quit the loop when we have inserted the original item count of
// the list, avoid a hang when we extend a list with itself.
for (listitem_T *item = l2->lv_first
; item != NULL && --todo >= 0
- ; item = item->li_next) {
+ ; item = (item == befbef ? saved_next : item->li_next)) {
tv_list_insert_tv(l1, &item->li_tv, bef);
}
}
diff --git a/test/functional/eval/container_functions_spec.lua b/test/functional/eval/container_functions_spec.lua
new file mode 100644
index 0000000000..04a3248c49
--- /dev/null
+++ b/test/functional/eval/container_functions_spec.lua
@@ -0,0 +1,24 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local eval = helpers.eval
+local meths = helpers.meths
+local clear = helpers.clear
+
+before_each(clear)
+
+describe('extend()', function()
+ it('suceeds to extend list with itself', function()
+ meths.set_var('l', {1, {}})
+ eq({1, {}, 1, {}}, eval('extend(l, l)'))
+ eq({1, {}, 1, {}}, meths.get_var('l'))
+
+ meths.set_var('l', {1, {}})
+ eq({1, {}, 1, {}}, eval('extend(l, l, 0)'))
+ eq({1, {}, 1, {}}, meths.get_var('l'))
+
+ meths.set_var('l', {1, {}})
+ eq({1, 1, {}, {}}, eval('extend(l, l, 1)'))
+ eq({1, 1, {}, {}}, meths.get_var('l'))
+ end)
+end)
diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua
index f4ea6799f5..eea5cc8880 100644
--- a/test/unit/eval/helpers.lua
+++ b/test/unit/eval/helpers.lua
@@ -439,4 +439,6 @@ return {
list_items=list_items,
dict_items=dict_items,
+
+ empty_list = {[type_key]=list_type},
}
diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua
index 2cc22053f3..926bf1c478 100644
--- a/test/unit/eval/typval_spec.lua
+++ b/test/unit/eval/typval_spec.lua
@@ -19,10 +19,9 @@ local type_key = eval_helpers.type_key
local li_alloc = eval_helpers.li_alloc
local int_type = eval_helpers.int_type
local first_di = eval_helpers.first_di
-local dict_type = eval_helpers.dict_type
-local list_type = eval_helpers.list_type
local null_list = eval_helpers.null_list
local null_dict = eval_helpers.null_dict
+local empty_list = eval_helpers.empty_list
local lua2typvalt = eval_helpers.lua2typvalt
local typvalt2lua = eval_helpers.typvalt2lua
local null_string = eval_helpers.null_string
@@ -168,7 +167,7 @@ describe('typval.c', function()
eq(1, l.lv_refcount)
li = li_alloc(true)
- tv = lua2typvalt({[type_key]=dict_type})
+ tv = lua2typvalt({})
tv.vval.v_dict.dv_refcount = 2
li.li_tv = tv
lib.tv_list_item_free(li)
@@ -290,8 +289,8 @@ describe('typval.c', function()
describe('alloc()/free()', function()
itp('recursively frees list with', function()
local l1 = ffi.gc(list(1, 'abc'), nil)
- local l2 = ffi.gc(list({[type_key]=dict_type}), nil)
- local l3 = ffi.gc(list({[type_key]=list_type}), nil)
+ local l2 = ffi.gc(list({}), nil)
+ local l3 = ffi.gc(list(empty_list), nil)
local alloc_rets = {}
alloc_log:check(get_alloc_rets({
a.list(l1),
@@ -325,8 +324,8 @@ describe('typval.c', function()
alloc_rets:freed(8),
})
end)
- itp('frees all containers inside a list', function()
- local l1 = ffi.gc(list('abc', {[type_key]=dict_type}, {[type_key]=list_type}), nil)
+ itp('does not free container items with recurse=false', function()
+ local l1 = ffi.gc(list('abc', {}, empty_list), nil)
local alloc_rets = {}
alloc_log:check(get_alloc_rets({
a.list(l1),
@@ -351,7 +350,7 @@ describe('typval.c', function()
end)
describe('unref()', function()
itp('recursively frees list when reference count goes to 0', function()
- local l = ffi.gc(list({[type_key]=list_type}), nil)
+ local l = ffi.gc(list(empty_list), nil)
local alloc_rets = {}
alloc_log:check(get_alloc_rets({
a.list(l),
@@ -395,7 +394,7 @@ describe('typval.c', function()
eq({lis[4], lis[9], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
lib.tv_list_remove_items(l, lis[4], lis[10])
- eq({[type_key]=list_type}, typvalt2lua(l_tv))
+ eq(empty_list, typvalt2lua(l_tv))
eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil})
alloc_log:check({})
@@ -428,7 +427,7 @@ describe('typval.c', function()
eq({0, 1, 2, 3, 4, 4.5, 5, 6, 7, 100500}, typvalt2lua(l_tv))
end)
itp('works with an empty list', function()
- local l_tv = lua2typvalt({[type_key]=list_type})
+ local l_tv = lua2typvalt(empty_list)
local l = l_tv.vval.v_list
eq(nil, l.lv_first)
@@ -443,10 +442,10 @@ describe('typval.c', function()
end)
describe('tv()', function()
itp('works', function()
- local l_tv = lua2typvalt({[type_key]=list_type})
+ local l_tv = lua2typvalt(empty_list)
local l = l_tv.vval.v_list
- local l_l_tv = lua2typvalt({[type_key]=list_type})
+ local l_l_tv = lua2typvalt(empty_list)
alloc_log:clear()
local l_l = l_l_tv.vval.v_list
eq(1, l_l.lv_refcount)
@@ -467,14 +466,14 @@ describe('typval.c', function()
a.str(l.lv_first.li_tv.vval.v_string, 'test'),
})
- eq({'test', {[type_key]=list_type}}, typvalt2lua(l_tv))
+ eq({'test', empty_list}, typvalt2lua(l_tv))
end)
end)
end)
describe('append', function()
describe('list()', function()
itp('works', function()
- local l_tv = lua2typvalt({[type_key]=list_type})
+ local l_tv = lua2typvalt(empty_list)
local l = l_tv.vval.v_list
local l_l = list(1)
@@ -497,7 +496,7 @@ describe('typval.c', function()
end)
describe('dict()', function()
itp('works', function()
- local l_tv = lua2typvalt({[type_key]=list_type})
+ local l_tv = lua2typvalt(empty_list)
local l = l_tv.vval.v_list
local l_d_tv = lua2typvalt({test=1})
@@ -521,7 +520,7 @@ describe('typval.c', function()
end)
describe('string()', function()
itp('works', function()
- local l_tv = lua2typvalt({[type_key]=list_type})
+ local l_tv = lua2typvalt(empty_list)
local l = l_tv.vval.v_list
alloc_log:clear()
@@ -552,7 +551,7 @@ describe('typval.c', function()
end)
describe('allocated string()', function()
itp('works', function()
- local l_tv = lua2typvalt({[type_key]=list_type})
+ local l_tv = lua2typvalt(empty_list)
local l = l_tv.vval.v_list
local s = lib.xstrdup('test')
@@ -577,7 +576,7 @@ describe('typval.c', function()
end)
describe('number()', function()
itp('works', function()
- local l_tv = lua2typvalt({[type_key]=list_type})
+ local l_tv = lua2typvalt(empty_list)
local l = l_tv.vval.v_list
alloc_log:clear()
@@ -710,7 +709,7 @@ describe('typval.c', function()
})
end)
itp('returns different/same containers with(out) copyID', function()
- local l_inner_tv = lua2typvalt({[type_key]=list_type})
+ local l_inner_tv = lua2typvalt(empty_list)
local l_tv = lua2typvalt({l_inner_tv, l_inner_tv})
eq(3, l_inner_tv.vval.v_list.lv_refcount)
local l = l_tv.vval.v_list
@@ -718,16 +717,16 @@ describe('typval.c', function()
local l_copy1 = tv_list_copy(nil, l, true, 0)
neq(l_copy1.lv_first.li_tv.vval.v_list, l_copy1.lv_last.li_tv.vval.v_list)
- eq({{[type_key]=list_type}, {[type_key]=list_type}}, lst2tbl(l_copy1))
+ eq({empty_list, empty_list}, lst2tbl(l_copy1))
local l_copy2 = tv_list_copy(nil, l, true, 2)
eq(l_copy2.lv_first.li_tv.vval.v_list, l_copy2.lv_last.li_tv.vval.v_list)
- eq({{[type_key]=list_type}, {[type_key]=list_type}}, lst2tbl(l_copy2))
+ eq({empty_list, empty_list}, lst2tbl(l_copy2))
eq(3, l_inner_tv.vval.v_list.lv_refcount)
end)
itp('works with self-referencing list with copyID', function()
- local l_tv = lua2typvalt({[type_key]=list_type})
+ local l_tv = lua2typvalt(empty_list)
local l = l_tv.vval.v_list
eq(1, l.lv_refcount)
lib.tv_list_append_list(l, l)
@@ -748,6 +747,139 @@ describe('typval.c', function()
eq(1, l_copy1.lv_refcount)
end)
end)
+ describe('extend()', function()
+ itp('can extend list with itself', function()
+ local l
+
+ l = list(1, {})
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l, nil)
+ alloc_log:check({
+ a.li(l.lv_last.li_prev),
+ a.li(l.lv_last),
+ })
+ eq(1, l.lv_refcount)
+ eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq({1, {}, 1, {}}, lst2tbl(l))
+
+ l = list(1, {})
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l, l.lv_last)
+ alloc_log:check({
+ a.li(l.lv_last.li_prev.li_prev),
+ a.li(l.lv_last.li_prev),
+ })
+ eq({1, 1, {}, {}}, lst2tbl(l))
+ eq(1, l.lv_refcount)
+ eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ l = list(1, {})
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l, l.lv_first)
+ alloc_log:check({
+ a.li(l.lv_first),
+ a.li(l.lv_first.li_next),
+ })
+ eq({1, {}, 1, {}}, lst2tbl(l))
+ eq(1, l.lv_refcount)
+ eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ end)
+ itp('can extend list with an empty list', function()
+ local l = list(1, {})
+ local el = list()
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, el.lv_refcount)
+
+ lib.tv_list_extend(l, el, nil)
+ alloc_log:check({
+ })
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, el.lv_refcount)
+ eq({1, {}}, lst2tbl(l))
+
+ lib.tv_list_extend(l, el, l.lv_first)
+ alloc_log:check({
+ })
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, el.lv_refcount)
+ eq({1, {}}, lst2tbl(l))
+
+ lib.tv_list_extend(l, el, l.lv_last)
+ alloc_log:check({
+ })
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+ eq(1, el.lv_refcount)
+ eq({1, {}}, lst2tbl(l))
+ end)
+ itp('can extend list with another non-empty list', function()
+ local l
+ local l2 = list(42, empty_list)
+ eq(1, l2.lv_refcount)
+ eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+
+ l = ffi.gc(list(1, {}), nil)
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l2, nil)
+ alloc_log:check({
+ a.li(l.lv_last.li_prev),
+ a.li(l.lv_last),
+ })
+ eq(1, l2.lv_refcount)
+ eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+ eq({1, {}, 42, empty_list}, lst2tbl(l))
+ lib.tv_list_free(l)
+ eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+
+ l = ffi.gc(list(1, {}), nil)
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l2, l.lv_first)
+ alloc_log:check({
+ a.li(l.lv_first),
+ a.li(l.lv_first.li_next),
+ })
+ eq(1, l2.lv_refcount)
+ eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+ eq({42, empty_list, 1, {}}, lst2tbl(l))
+ lib.tv_list_free(l)
+ eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+
+ l = ffi.gc(list(1, {}), nil)
+ alloc_log:clear()
+ eq(1, l.lv_refcount)
+ eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
+
+ lib.tv_list_extend(l, l2, l.lv_last)
+ alloc_log:check({
+ a.li(l.lv_first.li_next),
+ a.li(l.lv_first.li_next.li_next),
+ })
+ eq(1, l2.lv_refcount)
+ eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+ eq({1, 42, empty_list, {}}, lst2tbl(l))
+ lib.tv_list_free(l)
+ eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
+ end)
+ end)
describe('concat()', function()
itp('works with NULL lists', function()
local l = list(1, {})
@@ -789,7 +921,7 @@ describe('typval.c', function()
end)
itp('works with two different lists', function()
local l1 = list(1, {})
- local l2 = list(3, {[type_key]=list_type})
+ local l2 = list(3, empty_list)
eq(1, l1.lv_refcount)
eq(1, l1.lv_last.li_tv.vval.v_dict.dv_refcount)
eq(1, l2.lv_refcount)
@@ -809,7 +941,7 @@ describe('typval.c', function()
a.li(rettv.vval.v_list.lv_last.li_prev),
a.li(rettv.vval.v_list.lv_last),
})
- eq({1, {}, 3, {[type_key]=list_type}}, typvalt2lua(rettv))
+ eq({1, {}, 3, empty_list}, typvalt2lua(rettv))
end)
itp('can concatenate list with itself', function()
local l = list(1, {})
@@ -875,7 +1007,7 @@ describe('typval.c', function()
alloc_log:check({
a.list(rettv3.vval.v_list),
})
- eq({[type_key]=list_type}, typvalt2lua(rettv3))
+ eq(empty_list, typvalt2lua(rettv3))
local rettv4 = typvalt()
eq(OK, lib.tv_list_concat(le, le2, rettv4))
@@ -886,7 +1018,7 @@ describe('typval.c', function()
alloc_log:check({
a.list(rettv4.vval.v_list),
})
- eq({[type_key]=list_type}, typvalt2lua(rettv4))
+ eq(empty_list, typvalt2lua(rettv4))
end)
end)
end)