diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index c3f3bf66d3..92c8fe0e26 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -311,6 +311,28 @@ local get_option_type = function(name, info) end +-- Check whether the OptionTypes is allowed for vim.opt +-- If it does not match, throw an error which indicates which option causes the error. +local function assert_valid_value(name, value, types) + local type_of_value = type(value) + for _, valid_type in ipairs(types) do + if valid_type == type_of_value then + return + end + end + + error(string.format("Invalid option type '%s' for '%s', should be %s", type_of_value, name, table.concat(types, " or "))) +end + +local valid_types = { + [OptionTypes.BOOLEAN] = { "boolean" }, + [OptionTypes.NUMBER] = { "number" }, + [OptionTypes.STRING] = { "string" }, + [OptionTypes.SET] = { "string", "table" }, + [OptionTypes.ARRAY] = { "string", "table" }, + [OptionTypes.MAP] = { "string", "table" }, +} + --- Convert a lua value to a vimoption_T value local convert_value_to_vim = (function() -- Map of functions to take a Lua style value and convert to vimoption_T style value. @@ -340,7 +362,6 @@ local convert_value_to_vim = (function() [OptionTypes.MAP] = function(_, value) if type(value) == "string" then return value end - if type(value) == "function" then error(debug.traceback("asdf")) end local result = {} for opt_key, opt_value in pairs(value) do @@ -353,7 +374,10 @@ local convert_value_to_vim = (function() } return function(name, info, value) - return to_vim_value[get_option_type(name, info)](info, value) + local option_type = get_option_type(name, info) + assert_valid_value(name, value, valid_types[option_type]) + + return to_vim_value[option_type](info, value) end end)() diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 890f6427bb..e3d19008cc 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -1592,6 +1592,128 @@ describe('lua stdlib', function() eq(wildmode, 'full,list,full') end) + + describe('option types', function() + it('should allow to set option with numeric value', function() + eq(4, exec_lua [[ + vim.opt.tabstop = 4 + return vim.bo.tabstop + ]]) + + matches("Invalid option type 'string' for 'tabstop'", pcall_err(exec_lua, [[ + vim.opt.tabstop = '4' + ]])) + matches("Invalid option type 'boolean' for 'tabstop'", pcall_err(exec_lua, [[ + vim.opt.tabstop = true + ]])) + matches("Invalid option type 'table' for 'tabstop'", pcall_err(exec_lua, [[ + vim.opt.tabstop = {4, 2} + ]])) + matches("Invalid option type 'function' for 'tabstop'", pcall_err(exec_lua, [[ + vim.opt.tabstop = function() + return 4 + end + ]])) + end) + + it('should allow to set option with boolean value', function() + eq(true, exec_lua [[ + vim.opt.undofile = true + return vim.bo.undofile + ]]) + + matches("Invalid option type 'number' for 'undofile'", pcall_err(exec_lua, [[ + vim.opt.undofile = 0 + ]])) + matches("Invalid option type 'table' for 'undofile'", pcall_err(exec_lua, [[ + vim.opt.undofile = {true} + ]])) + matches("Invalid option type 'string' for 'undofile'", pcall_err(exec_lua, [[ + vim.opt.undofile = 'true' + ]])) + matches("Invalid option type 'function' for 'undofile'", pcall_err(exec_lua, [[ + vim.opt.undofile = function() + return true + end + ]])) + end) + + it('should allow to set option with array or string value', function() + eq('indent,eol,start', exec_lua [[ + vim.opt.backspace = {'indent','eol','start'} + return vim.go.backspace + ]]) + eq('indent,eol,start', exec_lua [[ + vim.opt.backspace = 'indent,eol,start' + return vim.go.backspace + ]]) + + matches("Invalid option type 'boolean' for 'backspace'", pcall_err(exec_lua, [[ + vim.opt.backspace = true + ]])) + matches("Invalid option type 'number' for 'backspace'", pcall_err(exec_lua, [[ + vim.opt.backspace = 2 + ]])) + matches("Invalid option type 'function' for 'backspace'", pcall_err(exec_lua, [[ + vim.opt.backspace = function() + return 'indent,eol,start' + end + ]])) + end) + + it('should allow set option with map or string value', function() + eq("eol:~,space:.", exec_lua [[ + vim.opt.listchars = { + eol = "~", + space = ".", + } + return vim.o.listchars + ]]) + eq("eol:~,space:.,tab:>~", exec_lua [[ + vim.opt.listchars = "eol:~,space:.,tab:>~" + return vim.o.listchars + ]]) + + matches("Invalid option type 'boolean' for 'listchars'", pcall_err(exec_lua, [[ + vim.opt.listchars = true + ]])) + matches("Invalid option type 'number' for 'listchars'", pcall_err(exec_lua, [[ + vim.opt.listchars = 2 + ]])) + matches("Invalid option type 'function' for 'listchars'", pcall_err(exec_lua, [[ + vim.opt.listchars = function() + return "eol:~,space:.,tab:>~" + end + ]])) + end) + + it('should allow set option with set or string value', function() + local ww = exec_lua [[ + vim.opt.whichwrap = { + b = true, + s = 1, + } + return vim.go.whichwrap + ]] + eq(true, ww == "bs" or ww == "sb") + eq("b,s,<,>,[,]", exec_lua [[ + vim.opt.whichwrap = "b,s,<,>,[,]" + return vim.go.whichwrap + ]]) + + matches("Invalid option type 'boolean' for 'whichwrap'", pcall_err(exec_lua, [[ + vim.opt.whichwrap = true + ]])) + matches("Invalid option type 'number' for 'whichwrap'", pcall_err(exec_lua, [[ + vim.opt.whichwrap = 2 + ]])) + matches("Invalid option type 'function' for 'whichwrap'", pcall_err(exec_lua, [[ + vim.opt.whichwrap = function() + return "b,s,<,>,[,]" + end + ]])) + end) + end) end) -- vim.opt it('vim.cmd', function()