patch 9.1.1239: if_python: no tuple data type support
Problem: if_python: no tuple data type support (after v9.1.1232)
Solution: Add support for using Vim tuple in the python interface
(Yegappan Lakshmanan)
closes: #16964
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/testdir/test_python2.vim b/src/testdir/test_python2.vim
index 4ba0f8e..cafba3d 100644
--- a/src/testdir/test_python2.vim
+++ b/src/testdir/test_python2.vim
@@ -406,6 +406,107 @@
\ 'Vim(python):TypeError: index must be int or slice, not dict')
endfunc
+" Test for the python Tuple object
+func Test_python_tuple()
+ " Try to convert a null tuple
+ call AssertException(["py l = vim.eval('test_null_tuple()')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to convert a Tuple with a null Tuple item
+ call AssertException(["py t = vim.eval('(test_null_tuple(),)')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to convert a List with a null Tuple item
+ call AssertException(["py t = vim.eval('[test_null_tuple()]')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to convert a Tuple with a null List item
+ call AssertException(["py t = vim.eval('(test_null_list(),)')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to bind a null Tuple variable (works because an empty tuple is used)
+ let cmds =<< trim END
+ let t = test_null_tuple()
+ py tt = vim.bindeval('t')
+ END
+ call AssertException(cmds, '')
+
+ " Creating a tuple using different iterators
+ py t1 = vim.Tuple(['abc', 20, 1.2, (4, 5)])
+ call assert_equal(('abc', 20, 1.2, (4, 5)), pyeval('t1'))
+ py t2 = vim.Tuple('abc')
+ call assert_equal(('a', 'b', 'c'), pyeval('t2'))
+ py t3 = vim.Tuple({'color': 'red', 'model': 'ford'})
+ call assert_equal(('color', 'model'), pyeval('t3'))
+ py t4 = vim.Tuple()
+ call assert_equal((), pyeval('t4'))
+ py t5 = vim.Tuple(x**2 for x in range(5))
+ call assert_equal((0, 1, 4, 9, 16), pyeval('t5'))
+ py t6 = vim.Tuple(('abc', 20, 1.2, (4, 5)))
+ call assert_equal(('abc', 20, 1.2, (4, 5)), pyeval('t6'))
+
+ " Convert between Vim tuple/list and python tuple/list
+ py t = vim.Tuple(vim.bindeval("('a', ('b',), ['c'], {'s': 'd'})"))
+ call assert_equal(('a', ('b',), ['c'], {'s': 'd'}), pyeval('t'))
+ call assert_equal(['a', ('b',), ['c'], {'s': 'd'}], pyeval('list(t)'))
+ call assert_equal(('a', ('b',), ['c'], {'s': 'd'}), pyeval('tuple(t)'))
+
+ py l = vim.List(vim.bindeval("['e', ('f',), ['g'], {'s': 'h'}]"))
+ call assert_equal(('e', ('f',), ['g'], {'s': 'h'}), pyeval('tuple(l)'))
+
+ " Tuple assignment
+ py tt = vim.bindeval('("a", "b")')
+ call AssertException(['py tt[0] = 10'],
+ \ "Vim(python):TypeError: 'vim.tuple' object does not support item assignment")
+ py tt = vim.bindeval('("a", "b")')
+ call AssertException(['py tt[0:1] = (10, 20)'],
+ \ "Vim(python):TypeError: 'vim.tuple' object does not support item assignment")
+
+ " iterating over tuple from Python
+ py print([x for x in vim.bindeval("('a', 'b')")])
+
+ " modifying a list item within a tuple
+ let t = ('a', ['b', 'c'], 'd')
+ py vim.bindeval('t')[1][1] = 'x'
+ call assert_equal(('a', ['b', 'x'], 'd'), t)
+
+ " length of a tuple
+ let t = ()
+ py p_t = vim.bindeval('t')
+ call assert_equal(0, pyeval('len(p_t)'))
+ let t = ('a', )
+ py p_t = vim.bindeval('t')
+ call assert_equal(1, pyeval('len(p_t)'))
+ let t = ('a', 'b', 'c')
+ py p_t = vim.bindeval('t')
+ call assert_equal(3, pyeval('len(p_t)'))
+
+ " membership test
+ let t = ('a', 'b', 'c')
+ py p_t = vim.bindeval('t')
+ call assert_true(pyeval("b'c' in p_t"))
+ call assert_true(pyeval("b'd' not in p_t"))
+
+ py x = vim.eval('("a", (2), [3], {})')
+ call assert_equal(('a', '2', ['3'], {}), pyeval('x'))
+
+ " Using a keyword argument for a tuple
+ call AssertException(['py x = vim.Tuple(a=1)'],
+ \ 'Vim(python):TypeError: tuple constructor does not accept keyword arguments')
+
+ " Using dict as an index
+ call AssertException(['py x = tt[{}]'],
+ \ 'Vim(python):TypeError: index must be int or slice, not dict')
+ call AssertException(['py x = tt["abc"]'],
+ \ 'Vim(python):TypeError: index must be int or slice, not str')
+
+ call AssertException(['py del tt.locked'],
+ \ 'Vim(python):AttributeError: cannot delete vim.Tuple attributes')
+
+ call AssertException(['py tt.foobar = 1'],
+ \ 'Vim(python):AttributeError: cannot set attribute foobar')
+endfunc
+
" Test for the python Dict object
func Test_python_dict()
let d = {}
@@ -787,11 +888,24 @@
\ 'Vim(python):TypeError: cannot modify fixed list')
endfunc
+" Test for locking/unlocking a tuple
+func Test_tuple_lock()
+ let t = (1, 2, 3)
+ py t = vim.bindeval('t')
+ py t.locked = True
+ call assert_equal(1, islocked('t'))
+ py t.locked = False
+ call assert_equal(0, islocked('t'))
+endfunc
+
" Test for pyeval()
func Test_python_pyeval()
let l = pyeval('range(3)')
call assert_equal([0, 1, 2], l)
+ let t = pyeval('("a", "b", "c")')
+ call assert_equal(("a", "b", "c"), t)
+
let d = pyeval('{"a": "b", "c": 1, "d": ["e"]}')
call assert_equal([['a', 'b'], ['c', 1], ['d', ['e']]], sort(items(d)))
@@ -812,18 +926,20 @@
call AssertException(['let v = pyeval("vim")'], 'E859:')
endfunc
-" Test for py3eval with locals
+" Test for pyeval with locals
func Test_python_pyeval_locals()
let str = 'a string'
let num = 0xbadb33f
let d = {'a': 1, 'b': 2, 'c': str}
let l = [ str, num, d ]
+ let t = ( str, num, d )
let locals = #{
\ s: str,
\ n: num,
\ d: d,
\ l: l,
+ \ t: t,
\ }
" check basics
@@ -831,6 +947,8 @@
call assert_equal(0xbadb33f, pyeval('n', locals))
call assert_equal(d, pyeval('d', locals))
call assert_equal(l, pyeval('l', locals))
+ call assert_equal(t, pyeval('t', locals))
+ call assert_equal('a-b-c', 'b"-".join(t)'->pyeval({'t': ('a', 'b', 'c')}))
py << trim EOF
def __UpdateDict(d, upd):
@@ -997,6 +1115,92 @@
\ 'Vim(python):SystemError: error return without exception set')
endfunc
+" Slice
+func Test_python_tuple_slice()
+ py tt = vim.bindeval('(0, 1, 2, 3, 4, 5)')
+ py t = tt[:4]
+ call assert_equal((0, 1, 2, 3), pyeval('t'))
+ py t = tt[2:]
+ call assert_equal((2, 3, 4, 5), pyeval('t'))
+ py t = tt[:-4]
+ call assert_equal((0, 1), pyeval('t'))
+ py t = tt[-2:]
+ call assert_equal((4, 5), pyeval('t'))
+ py t = tt[2:4]
+ call assert_equal((2, 3), pyeval('t'))
+ py t = tt[4:2]
+ call assert_equal((), pyeval('t'))
+ py t = tt[-4:-2]
+ call assert_equal((2, 3), pyeval('t'))
+ py t = tt[-2:-4]
+ call assert_equal((), pyeval('t'))
+ py t = tt[:]
+ call assert_equal((0, 1, 2, 3, 4, 5), pyeval('t'))
+ py t = tt[0:6]
+ call assert_equal((0, 1, 2, 3, 4, 5), pyeval('t'))
+ py t = tt[-10:10]
+ call assert_equal((0, 1, 2, 3, 4, 5), pyeval('t'))
+ py t = tt[4:2:-1]
+ call assert_equal((4, 3), pyeval('t'))
+ py t = tt[::2]
+ call assert_equal((0, 2, 4), pyeval('t'))
+ py t = tt[4:2:1]
+ call assert_equal((), pyeval('t'))
+
+ " Error case: Use an invalid index
+ call AssertException(['py x = tt[-10]'], 'Vim(python):IndexError: tuple index out of range')
+
+ " Use a step value of 0
+ call AssertException(['py x = tt[0:3:0]'],
+ \ 'Vim(python):ValueError: slice step cannot be zero')
+
+ " Error case: Invalid slice type
+ call AssertException(["py x = tt['abc']"],
+ \ "Vim(python):TypeError: index must be int or slice, not str")
+
+ " Error case: List with a null tuple item
+ let t = (test_null_tuple(),)
+ py tt = vim.bindeval('t')
+ call AssertException(["py x = tt[:]"], 'Vim(python):SystemError: error return without exception set')
+endfunc
+
+func Test_python_pytuple_to_vimtuple()
+ let t = pyeval("('a', 'b')")
+ call assert_equal(('a', 'b'), t)
+ let t = pyeval("()")
+ call assert_equal((), t)
+ let t = pyeval("('x',)")
+ call assert_equal(('x',), t)
+ let t = pyeval("((1, 2), (), (3, 4))")
+ call assert_equal(((1, 2), (), (3, 4)), t)
+ let t = pyeval("((1, 2), {'a': 10}, [5, 6])")
+ call assert_equal(((1, 2), {'a': 10}, [5, 6]), t)
+
+ " Invalid python tuple
+ py << trim END
+ class FailingIter(object):
+ def __iter__(self):
+ raise NotImplementedError('iter')
+ END
+ call assert_fails('call pyeval("(1, FailingIter, 2)")',
+ \ 'E859: Failed to convert returned python object to a Vim value')
+
+ py del FailingIter
+endfunc
+
+" Test for tuple garbage collection
+func Test_python_tuple_garbage_collect()
+ let t = (1, (2, 3), [4, 5], {'a': 6})
+ py py_t = vim.bindeval('t')
+ let save_testing = v:testing
+ let v:testing = 1
+ call test_garbagecollect_now()
+ let v:testing = save_testing
+
+ let new_t = pyeval('py_t')
+ call assert_equal((1, (2, 3), [4, 5], {'a': 6}), new_t)
+endfunc
+
" Vars
func Test_python_vars()
let g:foo = 'bac'
@@ -1976,6 +2180,7 @@
('range', vim.current.range),
('dictionary', vim.bindeval('{}')),
('list', vim.bindeval('[]')),
+ ('tuple', vim.bindeval('()')),
('function', vim.bindeval('function("tr")')),
('output', sys.stdout),
):
@@ -1991,6 +2196,7 @@
range:__dir__,__members__,append,end,start
dictionary:__dir__,__members__,get,has_key,items,keys,locked,pop,popitem,scope,update,values
list:__dir__,__members__,extend,locked
+ tuple:__dir__,__members__,locked
function:__dir__,__members__,args,auto_rebind,self,softspace
output:__dir__,__members__,close,closed,flush,isatty,readable,seekable,softspace,writable,write,writelines
END
@@ -2003,7 +2209,9 @@
call assert_equal({'a': 1}, pyeval('vim.Dictionary(a=1)'))
call assert_equal({'a': 1}, pyeval('vim.Dictionary(((''a'', 1),))'))
call assert_equal([], pyeval('vim.List()'))
+ call assert_equal((), pyeval('vim.Tuple()'))
call assert_equal(['a', 'b', 'c', '7'], pyeval('vim.List(iter(''abc7''))'))
+ call assert_equal(('a', 'b', 'c', '7'), pyeval('vim.Tuple(iter(''abc7''))'))
call assert_equal(function('tr'), pyeval('vim.Function(''tr'')'))
call assert_equal(function('tr', [123, 3, 4]),
\ pyeval('vim.Function(''tr'', args=[123, 3, 4])'))