From f46f138fb6882ad98ed66fc6d4fa24fd6a97aeae Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 4 May 2018 08:23:37 +0200 Subject: [PATCH 1/5] test: nvim_call_function: verify "too many arguments" error --- test/functional/api/vim_spec.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 718294d941..77cc7e6a05 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -158,13 +158,23 @@ describe('api', function() eq(17, nvim('call_function', 'eval', {17})) eq('foo', nvim('call_function', 'simplify', {'this/./is//redundant/../../../foo'})) end) - it("VimL error: fails (generic error), does NOT update v:errmsg", function() local status, rv = pcall(nvim, "call_function", "bogus function", {"arg1"}) eq(false, status) -- nvim_call_function() failed. ok(nil ~= string.find(rv, "Error calling function")) eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. end) + it('validates args', function() + local too_many_args = { 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x' } + source([[ + function! Foo(...) abort + echo a:000 + endfunction + ]]) + -- E740 + expect_err('Function called with too many arguments', request, + 'nvim_call_function', 'Foo', too_many_args) + end) end) describe('nvim_execute_lua', function() From 124275dd58ae998decbc8891f9feb7e239a8a7e1 Mon Sep 17 00:00:00 2001 From: Sebastian Witte Date: Wed, 2 May 2018 23:38:18 +0200 Subject: [PATCH 2/5] API: nvim_call_dict_function #3032 --- src/nvim/api/vim.c | 106 ++++++++++++++++++++++++++++++- test/functional/api/vim_spec.lua | 38 +++++++++-- 2 files changed, 137 insertions(+), 7 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index af3d379870..dabbfe1621 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -297,6 +297,18 @@ Object nvim_eval(String expr, Error *err) /// @return Result of the function call Object nvim_call_function(String fname, Array args, Error *err) FUNC_API_SINCE(1) +{ + return call_function(fname, args, NULL, err); +} + +/// Call an internal or user defined function. +/// +/// @param fname Function name +/// @param args Function arguments +/// @param self `self` dict (only required for dict functions) +/// @param[out] err Details of an error that may have occurred +/// @return Result of the function call +static Object call_function(String fname, Array args, dict_T *self, Error *err) { Object rv = OBJECT_INIT; if (args.size > MAX_FUNC_ARGS) { @@ -321,7 +333,7 @@ Object nvim_call_function(String fname, Array args, Error *err) int r = call_func((char_u *)fname.data, (int)fname.size, &rettv, (int)args.size, vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, - true, NULL, NULL); + true, NULL, self); if (r == FAIL) { api_set_error(err, kErrorTypeException, "Error calling function."); } @@ -356,6 +368,98 @@ Object nvim_execute_lua(String code, Array args, Error *err) return executor_exec_lua_api(code, args, err); } +/// Call the given dict function with the given arguments stored in an array. +/// +/// @param self |self| dict or string expression evaluating to a dict +/// @param internal true if the function is stored in the self-dict +/// @param fnname Function to call +/// @param args Functions arguments packed in an Array +/// @param[out] err Details of an error that may have occurred +/// @return Result of the function call +Object nvim_call_dict_function(Object self, Boolean internal, String fnname, + Array args, Error *err) + FUNC_API_SINCE(4) +{ + Object rv = OBJECT_INIT; + + typval_T rettv; + bool mustfree = false; + switch (self.type) { + case kObjectTypeString: { + try_start(); + if (eval0((char_u *)self.data.string.data, &rettv, NULL, true) == FAIL) { + api_set_error(err, kErrorTypeException, + "Failed to evaluate self expression"); + } + if (try_end(err)) { + return rv; + } + // Evaluation of the string arg created a new dict or increased the + // refcount of a dict. Not necessary for a dict inside a RPC Object. + mustfree = true; + break; + } + case kObjectTypeDictionary: { + if (internal) { + api_set_error(err, kErrorTypeValidation, + "Funcrefs are not supported for RPC dicts"); + return rv; + } else if (!object_to_vim(self, &rettv, err)) { + tv_clear(&rettv); + return rv; + } + break; + } + default: { + api_set_error(err, kErrorTypeValidation, + "self argument must be String or Dictionary"); + return rv; + } + } + dict_T *self_dict = rettv.vval.v_dict; + if (rettv.v_type != VAR_DICT || !self_dict) { + api_set_error(err, kErrorTypeValidation, + "Referenced self-dict does not exist"); + goto end; + } + + // Set the function to call + String func = STRING_INIT; + if (internal /* && self.type == kObjectTypeString */) { + dictitem_T *const di = tv_dict_find(self_dict, fnname.data, + (ptrdiff_t)fnname.size); + if (di == NULL) { + api_set_error(err, kErrorTypeValidation, + "Function not found in self-dict"); + goto end; + } + if (di->di_tv.v_type != VAR_STRING) { + api_set_error(err, kErrorTypeValidation, + "Value inside self-dict is not a valid function name"); + goto end; + } + func.data = (char *)di->di_tv.vval.v_string; + func.size = strlen(func.data); + } else { + func.data = fnname.data; + func.size = fnname.size; + } + if (!func.data || func.size < 1) { + api_set_error(err, kErrorTypeValidation, + "Invalid (empty) function name"); + goto end; + } + + // Finally try to call the function + rv = call_function(func, args, self_dict, err); +end: + if (mustfree) { + tv_clear(&rettv); + } + + return rv; +} + /// Calculates the number of display cells occupied by `text`. /// counts as one cell. /// diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 77cc7e6a05..67e49ff46d 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -4,17 +4,19 @@ local global_helpers = require('test.helpers') local NIL = helpers.NIL local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq +local command = helpers.command +local funcs = helpers.funcs +local iswin = helpers.iswin +local meth_pcall = helpers.meth_pcall +local meths = helpers.meths local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed local os_name = helpers.os_name -local meths = helpers.meths -local funcs = helpers.funcs local request = helpers.request -local meth_pcall = helpers.meth_pcall -local command = helpers.command -local iswin = helpers.iswin +local source = helpers.source -local intchar2lua = global_helpers.intchar2lua +local expect_err = global_helpers.expect_err local format_string = global_helpers.format_string +local intchar2lua = global_helpers.intchar2lua local mergedicts_copy = global_helpers.mergedicts_copy describe('api', function() @@ -177,6 +179,30 @@ describe('api', function() end) end) + describe('nvim_call_dict_function', function() + it('invokes VimL dict', function() + source('function! F(name) dict\n return self.greeting . ", " . a:name . "!"\nendfunction') + nvim('set_var', 'dict_function_dict', { greeting = 'Hello', F = 'function("F")' }) + eq('Hello, World!', nvim('call_dict_function', 'g:dict_function_dict', false, 'F', {'World'})) + eq({ greeting = 'Hello', F = 'function("F")' }, nvim('get_var', 'dict_function_dict')) + nvim('set_var', 'dict_function_dict_i', { greeting = 'Hi', F = "F" }) + eq('Hi, Moon!', nvim('call_dict_function', 'g:dict_function_dict_i', true, 'F', {'Moon'})) + eq({ greeting = 'Hi', F = "F" }, nvim('get_var', 'dict_function_dict_i')) + end) + it('invokes RPC dict', function() + source('function! G() dict\n return self.result\nendfunction') + eq('self', nvim('call_dict_function', { result = 'self', G = 'G'}, false, 'G', {})) + end) + it('fails for a RPC dictionary and internal set to true', function() + expect_err('Funcrefs are not supported for RPC dicts', request, + 'nvim_call_dict_function', { f = '' }, true, 'f', {1,2}) + end) + it('fails with empty function name', function() + expect_err('Invalid %(empty%) function name', request, + 'nvim_call_dict_function', "{ 'f': '' }", true, 'f', {1,2}) + end) + end) + describe('nvim_execute_lua', function() it('works', function() meths.execute_lua('vim.api.nvim_set_var("test", 3)', {}) From 19c2ce1901b08aa493ad3b12514d681cb22160f6 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 3 May 2018 00:20:14 +0200 Subject: [PATCH 3/5] refactor: nvim_call_dict_function - Add test coverage for errors. - Rename, rearrange. --- src/nvim/api/vim.c | 119 ++++++++++++++----------------- test/functional/api/vim_spec.lua | 29 +++++--- 2 files changed, 75 insertions(+), 73 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index dabbfe1621..05f8afefbe 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -287,28 +287,32 @@ Object nvim_eval(String expr, Error *err) return rv; } -/// Calls a VimL function with the given arguments +/// Execute lua code. Parameters (if any) are available as `...` inside the +/// chunk. The chunk can return a value. /// -/// On VimL error: Returns a generic error; v:errmsg is not updated. +/// Only statements are executed. To evaluate an expression, prefix it +/// with `return`: return my_function(...) /// -/// @param fname Function to call -/// @param args Function arguments packed in an Array -/// @param[out] err Error details, if any -/// @return Result of the function call -Object nvim_call_function(String fname, Array args, Error *err) - FUNC_API_SINCE(1) +/// @param code lua code to execute +/// @param args Arguments to the code +/// @param[out] err Details of an error encountered while parsing +/// or executing the lua code. +/// +/// @return Return value of lua code if present or NIL. +Object nvim_execute_lua(String code, Array args, Error *err) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY { - return call_function(fname, args, NULL, err); + return executor_exec_lua_api(code, args, err); } -/// Call an internal or user defined function. +/// Calls a VimL function. /// -/// @param fname Function name +/// @param fn Function name /// @param args Function arguments -/// @param self `self` dict (only required for dict functions) -/// @param[out] err Details of an error that may have occurred +/// @param self `self` dict, or NULL for non-dict functions +/// @param[out] err Error details, if any /// @return Result of the function call -static Object call_function(String fname, Array args, dict_T *self, Error *err) +static Object _call_function(String fn, Array args, dict_T *self, Error *err) { Object rv = OBJECT_INIT; if (args.size > MAX_FUNC_ARGS) { @@ -330,10 +334,9 @@ static Object call_function(String fname, Array args, dict_T *self, Error *err) // Call the function typval_T rettv; int dummy; - int r = call_func((char_u *)fname.data, (int)fname.size, - &rettv, (int)args.size, vim_args, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, - true, NULL, self); + int r = call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size, + vim_args, NULL, curwin->w_cursor.lnum, + curwin->w_cursor.lnum, &dummy, true, NULL, self); if (r == FAIL) { api_set_error(err, kErrorTypeException, "Error calling function."); } @@ -350,33 +353,29 @@ free_vim_args: return rv; } -/// Execute lua code. Parameters (if any) are available as `...` inside the -/// chunk. The chunk can return a value. +/// Calls a VimL function with the given arguments. /// -/// Only statements are executed. To evaluate an expression, prefix it -/// with `return`: return my_function(...) +/// On VimL error: Returns a generic error; v:errmsg is not updated. /// -/// @param code lua code to execute -/// @param args Arguments to the code -/// @param[out] err Details of an error encountered while parsing -/// or executing the lua code. -/// -/// @return Return value of lua code if present or NIL. -Object nvim_execute_lua(String code, Array args, Error *err) - FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY +/// @param fn Function to call +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any +/// @return Result of the function call +Object nvim_call_function(String fn, Array args, Error *err) + FUNC_API_SINCE(1) { - return executor_exec_lua_api(code, args, err); + return _call_function(fn, args, NULL, err); } -/// Call the given dict function with the given arguments stored in an array. +/// Calls a VimL |Dictionary-function| with the given arguments. /// -/// @param self |self| dict or string expression evaluating to a dict -/// @param internal true if the function is stored in the self-dict -/// @param fnname Function to call +/// @param dict Dictionary, or String evaluating to a VimL |self| dict +/// @param fn Function to call +/// @param internal true if the function is stored on the dict /// @param args Functions arguments packed in an Array -/// @param[out] err Details of an error that may have occurred +/// @param[out] err Error details, if any /// @return Result of the function call -Object nvim_call_dict_function(Object self, Boolean internal, String fnname, +Object nvim_call_dict_function(Object dict, String fn, Boolean internal, Array args, Error *err) FUNC_API_SINCE(4) { @@ -384,27 +383,27 @@ Object nvim_call_dict_function(Object self, Boolean internal, String fnname, typval_T rettv; bool mustfree = false; - switch (self.type) { + switch (dict.type) { case kObjectTypeString: { try_start(); - if (eval0((char_u *)self.data.string.data, &rettv, NULL, true) == FAIL) { + if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) { api_set_error(err, kErrorTypeException, - "Failed to evaluate self expression"); + "Failed to evaluate dict expression"); } if (try_end(err)) { return rv; } // Evaluation of the string arg created a new dict or increased the - // refcount of a dict. Not necessary for a dict inside a RPC Object. + // refcount of a dict. Not necessary for a RPC dict. mustfree = true; break; } case kObjectTypeDictionary: { if (internal) { api_set_error(err, kErrorTypeValidation, - "Funcrefs are not supported for RPC dicts"); + "Cannot invoke RPC dict as a VimL reference"); return rv; - } else if (!object_to_vim(self, &rettv, err)) { + } else if (!object_to_vim(dict, &rettv, err)) { tv_clear(&rettv); return rv; } @@ -412,46 +411,38 @@ Object nvim_call_dict_function(Object self, Boolean internal, String fnname, } default: { api_set_error(err, kErrorTypeValidation, - "self argument must be String or Dictionary"); + "dict argument type must be String or Dictionary"); return rv; } } dict_T *self_dict = rettv.vval.v_dict; if (rettv.v_type != VAR_DICT || !self_dict) { - api_set_error(err, kErrorTypeValidation, - "Referenced self-dict does not exist"); + api_set_error(err, kErrorTypeValidation, "Referenced dict does not exist"); goto end; } - // Set the function to call - String func = STRING_INIT; - if (internal /* && self.type == kObjectTypeString */) { - dictitem_T *const di = tv_dict_find(self_dict, fnname.data, - (ptrdiff_t)fnname.size); + if (internal) { + dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size); if (di == NULL) { - api_set_error(err, kErrorTypeValidation, - "Function not found in self-dict"); + api_set_error(err, kErrorTypeValidation, "Function not found in dict"); goto end; } if (di->di_tv.v_type != VAR_STRING) { api_set_error(err, kErrorTypeValidation, - "Value inside self-dict is not a valid function name"); + "Value found in dict is not a valid function"); goto end; } - func.data = (char *)di->di_tv.vval.v_string; - func.size = strlen(func.data); - } else { - func.data = fnname.data; - func.size = fnname.size; + fn = (String) { + .data = (char *)di->di_tv.vval.v_string, + .size = strlen((char *)di->di_tv.vval.v_string), + }; } - if (!func.data || func.size < 1) { - api_set_error(err, kErrorTypeValidation, - "Invalid (empty) function name"); + if (!fn.data || fn.size < 1) { + api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name"); goto end; } - // Finally try to call the function - rv = call_function(func, args, self_dict, err); + rv = _call_function(fn, args, self_dict, err); end: if (mustfree) { tv_clear(&rettv); diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 67e49ff46d..f49469d201 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -183,23 +183,34 @@ describe('api', function() it('invokes VimL dict', function() source('function! F(name) dict\n return self.greeting . ", " . a:name . "!"\nendfunction') nvim('set_var', 'dict_function_dict', { greeting = 'Hello', F = 'function("F")' }) - eq('Hello, World!', nvim('call_dict_function', 'g:dict_function_dict', false, 'F', {'World'})) + eq('Hello, World!', nvim('call_dict_function', 'g:dict_function_dict', 'F', false, {'World'})) eq({ greeting = 'Hello', F = 'function("F")' }, nvim('get_var', 'dict_function_dict')) nvim('set_var', 'dict_function_dict_i', { greeting = 'Hi', F = "F" }) - eq('Hi, Moon!', nvim('call_dict_function', 'g:dict_function_dict_i', true, 'F', {'Moon'})) + eq('Hi, Moon!', nvim('call_dict_function', 'g:dict_function_dict_i', 'F', true, {'Moon'})) eq({ greeting = 'Hi', F = "F" }, nvim('get_var', 'dict_function_dict_i')) end) it('invokes RPC dict', function() source('function! G() dict\n return self.result\nendfunction') - eq('self', nvim('call_dict_function', { result = 'self', G = 'G'}, false, 'G', {})) + eq('self', nvim('call_dict_function', { result = 'self', G = 'G'}, 'G', false, {})) end) - it('fails for a RPC dictionary and internal set to true', function() - expect_err('Funcrefs are not supported for RPC dicts', request, - 'nvim_call_dict_function', { f = '' }, true, 'f', {1,2}) - end) - it('fails with empty function name', function() + it('validates args', function() + command('let g:d={"baz":"zub","meep":[]}') + expect_err('Function not found in dict', request, + 'nvim_call_dict_function', 'g:d', 'bogus', true, {1,2}) + expect_err('Error calling function.', request, + 'nvim_call_dict_function', 'g:d', 'baz', true, {1,2}) + expect_err('Value found in dict is not a valid function', request, + 'nvim_call_dict_function', 'g:d', 'meep', true, {1,2}) + expect_err('Cannot invoke RPC dict as a VimL reference', request, + 'nvim_call_dict_function', { f = '' }, 'f', true, {1,2}) expect_err('Invalid %(empty%) function name', request, - 'nvim_call_dict_function', "{ 'f': '' }", true, 'f', {1,2}) + 'nvim_call_dict_function', "{ 'f': '' }", 'f', true, {1,2}) + expect_err('dict argument type must be String or Dictionary', request, + 'nvim_call_dict_function', 42, 'f', true, {1,2}) + expect_err('Failed to evaluate dict expression', request, + 'nvim_call_dict_function', 'foo', 'f', true, {1,2}) + expect_err('Referenced dict does not exist', request, + 'nvim_call_dict_function', '42', 'f', true, {1,2}) end) end) From fe7ab60af7c8fbc2b1830dcfbcff75e39f5c4bbe Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 4 May 2018 08:20:37 +0200 Subject: [PATCH 4/5] API: nvim_call_dict_function: eliminate `internal` param The `internal` param is difficult to explain, and will rarely be anything but `true`. To avoid it, use a hack: check if the resolved dict value starts with "function(". --- src/nvim/api/vim.c | 46 +++++++++++++++----------------- test/functional/api/vim_spec.lua | 40 ++++++++++++++++----------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 05f8afefbe..79b61b2942 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -317,7 +317,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) Object rv = OBJECT_INIT; if (args.size > MAX_FUNC_ARGS) { api_set_error(err, kErrorTypeValidation, - "Function called with too many arguments."); + "Function called with too many arguments"); return rv; } @@ -338,7 +338,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, NULL, self); if (r == FAIL) { - api_set_error(err, kErrorTypeException, "Error calling function."); + api_set_error(err, kErrorTypeException, "Error calling function"); } if (!try_end(err)) { rv = vim_to_object(&rettv); @@ -371,12 +371,10 @@ Object nvim_call_function(String fn, Array args, Error *err) /// /// @param dict Dictionary, or String evaluating to a VimL |self| dict /// @param fn Function to call -/// @param internal true if the function is stored on the dict /// @param args Functions arguments packed in an Array /// @param[out] err Error details, if any /// @return Result of the function call -Object nvim_call_dict_function(Object dict, String fn, Boolean internal, - Array args, Error *err) +Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) FUNC_API_SINCE(4) { Object rv = OBJECT_INIT; @@ -399,13 +397,8 @@ Object nvim_call_dict_function(Object dict, String fn, Boolean internal, break; } case kObjectTypeDictionary: { - if (internal) { - api_set_error(err, kErrorTypeValidation, - "Cannot invoke RPC dict as a VimL reference"); - return rv; - } else if (!object_to_vim(dict, &rettv, err)) { - tv_clear(&rettv); - return rv; + if (!object_to_vim(dict, &rettv, err)) { + goto end; } break; } @@ -421,22 +414,25 @@ Object nvim_call_dict_function(Object dict, String fn, Boolean internal, goto end; } - if (internal) { + if (dict.type != kObjectTypeDictionary) { dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size); - if (di == NULL) { - api_set_error(err, kErrorTypeValidation, "Function not found in dict"); - goto end; + if (di != NULL) { + if (di->di_tv.v_type != VAR_STRING) { + api_set_error(err, kErrorTypeValidation, + "Value found in dict is not a valid function"); + goto end; + } + // XXX: Hack to guess if function is "internal". + bool internal = (0 != STRNCMP(di->di_tv.vval.v_string, "function(", 9)); + if (internal) { + fn = (String) { + .data = (char *)di->di_tv.vval.v_string, + .size = strlen((char *)di->di_tv.vval.v_string), + }; + } } - if (di->di_tv.v_type != VAR_STRING) { - api_set_error(err, kErrorTypeValidation, - "Value found in dict is not a valid function"); - goto end; - } - fn = (String) { - .data = (char *)di->di_tv.vval.v_string, - .size = strlen((char *)di->di_tv.vval.v_string), - }; } + if (!fn.data || fn.size < 1) { api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name"); goto end; diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index f49469d201..250c2e58ac 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -180,37 +180,45 @@ describe('api', function() end) describe('nvim_call_dict_function', function() - it('invokes VimL dict', function() - source('function! F(name) dict\n return self.greeting . ", " . a:name . "!"\nendfunction') + it('invokes VimL dict function', function() + source([[ + function! F(name) dict + return self.greeting . ", " . a:name . "!" + endfunction + ]]) + + -- function() ("non-internal") function nvim('set_var', 'dict_function_dict', { greeting = 'Hello', F = 'function("F")' }) - eq('Hello, World!', nvim('call_dict_function', 'g:dict_function_dict', 'F', false, {'World'})) + eq('Hello, World!', nvim('call_dict_function', 'g:dict_function_dict', 'F', {'World'})) eq({ greeting = 'Hello', F = 'function("F")' }, nvim('get_var', 'dict_function_dict')) + + -- "internal" function nvim('set_var', 'dict_function_dict_i', { greeting = 'Hi', F = "F" }) - eq('Hi, Moon!', nvim('call_dict_function', 'g:dict_function_dict_i', 'F', true, {'Moon'})) + eq('Hi, Moon!', nvim('call_dict_function', 'g:dict_function_dict_i', 'F', {'Moon'})) eq({ greeting = 'Hi', F = "F" }, nvim('get_var', 'dict_function_dict_i')) end) it('invokes RPC dict', function() source('function! G() dict\n return self.result\nendfunction') - eq('self', nvim('call_dict_function', { result = 'self', G = 'G'}, 'G', false, {})) + eq('it works', nvim('call_dict_function', { result = 'it works', G = 'G'}, 'G', {})) end) it('validates args', function() command('let g:d={"baz":"zub","meep":[]}') - expect_err('Function not found in dict', request, - 'nvim_call_dict_function', 'g:d', 'bogus', true, {1,2}) - expect_err('Error calling function.', request, - 'nvim_call_dict_function', 'g:d', 'baz', true, {1,2}) + expect_err('Error calling function', request, + 'nvim_call_dict_function', 'g:d', 'bogus', {1,2}) + expect_err('Error calling function', request, + 'nvim_call_dict_function', 'g:d', 'baz', {1,2}) expect_err('Value found in dict is not a valid function', request, - 'nvim_call_dict_function', 'g:d', 'meep', true, {1,2}) - expect_err('Cannot invoke RPC dict as a VimL reference', request, - 'nvim_call_dict_function', { f = '' }, 'f', true, {1,2}) + 'nvim_call_dict_function', 'g:d', 'meep', {1,2}) + expect_err('Error calling function', request, + 'nvim_call_dict_function', { f = '' }, 'f', {1,2}) expect_err('Invalid %(empty%) function name', request, - 'nvim_call_dict_function', "{ 'f': '' }", 'f', true, {1,2}) + 'nvim_call_dict_function', "{ 'f': '' }", 'f', {1,2}) expect_err('dict argument type must be String or Dictionary', request, - 'nvim_call_dict_function', 42, 'f', true, {1,2}) + 'nvim_call_dict_function', 42, 'f', {1,2}) expect_err('Failed to evaluate dict expression', request, - 'nvim_call_dict_function', 'foo', 'f', true, {1,2}) + 'nvim_call_dict_function', 'foo', 'f', {1,2}) expect_err('Referenced dict does not exist', request, - 'nvim_call_dict_function', '42', 'f', true, {1,2}) + 'nvim_call_dict_function', '42', 'f', {1,2}) end) end) From cabffb0182097f0d6d5c22682c32282c459b5543 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 6 May 2018 14:37:51 +0200 Subject: [PATCH 5/5] API: nvim_call_dict_function: expect actual function, not name --- src/nvim/api/vim.c | 38 ++++++++++++++------------- test/functional/api/vim_spec.lua | 45 +++++++++++++++++++------------- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 79b61b2942..3679d1e569 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -370,8 +370,8 @@ Object nvim_call_function(String fn, Array args, Error *err) /// Calls a VimL |Dictionary-function| with the given arguments. /// /// @param dict Dictionary, or String evaluating to a VimL |self| dict -/// @param fn Function to call -/// @param args Functions arguments packed in an Array +/// @param fn Name of the function defined on the VimL dict +/// @param args Function arguments packed in an Array /// @param[out] err Error details, if any /// @return Result of the function call Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) @@ -410,27 +410,29 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) } dict_T *self_dict = rettv.vval.v_dict; if (rettv.v_type != VAR_DICT || !self_dict) { - api_set_error(err, kErrorTypeValidation, "Referenced dict does not exist"); + api_set_error(err, kErrorTypeValidation, "dict not found"); goto end; } - if (dict.type != kObjectTypeDictionary) { + if (fn.data && fn.size > 0 && dict.type != kObjectTypeDictionary) { dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size); - if (di != NULL) { - if (di->di_tv.v_type != VAR_STRING) { - api_set_error(err, kErrorTypeValidation, - "Value found in dict is not a valid function"); - goto end; - } - // XXX: Hack to guess if function is "internal". - bool internal = (0 != STRNCMP(di->di_tv.vval.v_string, "function(", 9)); - if (internal) { - fn = (String) { - .data = (char *)di->di_tv.vval.v_string, - .size = strlen((char *)di->di_tv.vval.v_string), - }; - } + if (di == NULL) { + api_set_error(err, kErrorTypeValidation, "Not found: %s", fn.data); + goto end; } + if (di->di_tv.v_type == VAR_PARTIAL) { + api_set_error(err, kErrorTypeValidation, + "partial function not supported"); + goto end; + } + if (di->di_tv.v_type != VAR_FUNC) { + api_set_error(err, kErrorTypeValidation, "Not a function: %s", fn.data); + goto end; + } + fn = (String) { + .data = (char *)di->di_tv.vval.v_string, + .size = strlen((char *)di->di_tv.vval.v_string), + }; } if (!fn.data || fn.size < 1) { diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 250c2e58ac..0ff755b320 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -183,42 +183,51 @@ describe('api', function() it('invokes VimL dict function', function() source([[ function! F(name) dict - return self.greeting . ", " . a:name . "!" + return self.greeting.', '.a:name.'!' + endfunction + let g:test_dict_fn = { 'greeting':'Hello', 'F':function('F') } + + let g:test_dict_fn2 = { 'greeting':'Hi' } + function g:test_dict_fn2.F2(name) + return self.greeting.', '.a:name.' ...' endfunction ]]) - -- function() ("non-internal") function - nvim('set_var', 'dict_function_dict', { greeting = 'Hello', F = 'function("F")' }) - eq('Hello, World!', nvim('call_dict_function', 'g:dict_function_dict', 'F', {'World'})) - eq({ greeting = 'Hello', F = 'function("F")' }, nvim('get_var', 'dict_function_dict')) + -- :help Dictionary-function + eq('Hello, World!', nvim('call_dict_function', 'g:test_dict_fn', 'F', {'World'})) + -- Funcref is sent as NIL over RPC. + eq({ greeting = 'Hello', F = NIL }, nvim('get_var', 'test_dict_fn')) - -- "internal" function - nvim('set_var', 'dict_function_dict_i', { greeting = 'Hi', F = "F" }) - eq('Hi, Moon!', nvim('call_dict_function', 'g:dict_function_dict_i', 'F', {'Moon'})) - eq({ greeting = 'Hi', F = "F" }, nvim('get_var', 'dict_function_dict_i')) - end) - it('invokes RPC dict', function() - source('function! G() dict\n return self.result\nendfunction') - eq('it works', nvim('call_dict_function', { result = 'it works', G = 'G'}, 'G', {})) + -- :help numbered-function + eq('Hi, Moon ...', nvim('call_dict_function', 'g:test_dict_fn2', 'F2', {'Moon'})) + -- Funcref is sent as NIL over RPC. + eq({ greeting = 'Hi', F2 = NIL }, nvim('get_var', 'test_dict_fn2')) + + -- Function specified via RPC dict. + source('function! G() dict\n return "@".(self.result)."@"\nendfunction') + eq('@it works@', nvim('call_dict_function', { result = 'it works', G = 'G'}, 'G', {})) end) + it('validates args', function() command('let g:d={"baz":"zub","meep":[]}') - expect_err('Error calling function', request, + expect_err('Not found: bogus', request, 'nvim_call_dict_function', 'g:d', 'bogus', {1,2}) - expect_err('Error calling function', request, + expect_err('Not a function: baz', request, 'nvim_call_dict_function', 'g:d', 'baz', {1,2}) - expect_err('Value found in dict is not a valid function', request, + expect_err('Not a function: meep', request, 'nvim_call_dict_function', 'g:d', 'meep', {1,2}) expect_err('Error calling function', request, 'nvim_call_dict_function', { f = '' }, 'f', {1,2}) - expect_err('Invalid %(empty%) function name', request, + expect_err('Not a function: f', request, 'nvim_call_dict_function', "{ 'f': '' }", 'f', {1,2}) expect_err('dict argument type must be String or Dictionary', request, 'nvim_call_dict_function', 42, 'f', {1,2}) expect_err('Failed to evaluate dict expression', request, 'nvim_call_dict_function', 'foo', 'f', {1,2}) - expect_err('Referenced dict does not exist', request, + expect_err('dict not found', request, 'nvim_call_dict_function', '42', 'f', {1,2}) + expect_err('Invalid %(empty%) function name', request, + 'nvim_call_dict_function', "{ 'f': '' }", '', {1,2}) end) end)