mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	perf: add fast path to vim.validate (#28977)
For many small/simple functions (like those found in shared.lua), the runtime of vim.validate can far exceed the runtime of the function itself. Add an "overload" to vim.validate that uses a simple assertion pattern, rather than parsing a full "validation spec".
This commit is contained in:
		| @@ -2366,7 +2366,26 @@ vim.trim({s})                                                     *vim.trim()* | ||||
|       • https://www.lua.org/pil/20.2.html | ||||
|  | ||||
| vim.validate({opt})                                           *vim.validate()* | ||||
|     Validates a parameter specification (types and values). Specs are | ||||
|     Validate function arguments. | ||||
|  | ||||
|     This function has two valid forms: | ||||
|     1. vim.validate(name: str, value: any, type: string, optional?: bool) | ||||
|     2. vim.validate(spec: table) | ||||
|  | ||||
|     Form 1 validates that argument {name} with value {value} has the type | ||||
|     {type}. {type} must be a value returned by |lua-type()|. If {optional} is | ||||
|     true, then {value} may be null. This form is significantly faster and | ||||
|     should be preferred for simple cases. | ||||
|  | ||||
|     Example: >lua | ||||
|         function vim.startswith(s, prefix) | ||||
|           vim.validate('s', s, 'string') | ||||
|           vim.validate('prefix', prefix, 'string') | ||||
|           ... | ||||
|         end | ||||
| < | ||||
|  | ||||
|     Form 2 validates a parameter specification (types and values). Specs are | ||||
|     evaluated in alphanumeric order, until the first failure. | ||||
|  | ||||
|     Usage example: >lua | ||||
|   | ||||
| @@ -214,7 +214,7 @@ end | ||||
| ---@param t table<T, any> (table) Table | ||||
| ---@return T[] : List of keys | ||||
| function vim.tbl_keys(t) | ||||
|   vim.validate({ t = { t, 't' } }) | ||||
|   vim.validate('t', t, 'table') | ||||
|   --- @cast t table<any,any> | ||||
|  | ||||
|   local keys = {} | ||||
| @@ -231,7 +231,7 @@ end | ||||
| ---@param t table<any, T> (table) Table | ||||
| ---@return T[] : List of values | ||||
| function vim.tbl_values(t) | ||||
|   vim.validate({ t = { t, 't' } }) | ||||
|   vim.validate('t', t, 'table') | ||||
|  | ||||
|   local values = {} | ||||
|   for _, v in | ||||
| @@ -332,7 +332,7 @@ end | ||||
| ---@param value any Value to compare | ||||
| ---@return boolean `true` if `t` contains `value` | ||||
| function vim.list_contains(t, value) | ||||
|   vim.validate({ t = { t, 't' } }) | ||||
|   vim.validate('t', t, 'table') | ||||
|   --- @cast t table<any,any> | ||||
|  | ||||
|   for _, v in ipairs(t) do | ||||
| @@ -350,7 +350,7 @@ end | ||||
| ---@param t table Table to check | ||||
| ---@return boolean `true` if `t` is empty | ||||
| function vim.tbl_isempty(t) | ||||
|   vim.validate({ t = { t, 't' } }) | ||||
|   vim.validate('t', t, 'table') | ||||
|   return next(t) == nil | ||||
| end | ||||
|  | ||||
| @@ -580,7 +580,7 @@ end | ||||
| ---@return fun(table: table<K, V>, index?: K):K, V # |for-in| iterator over sorted keys and their values | ||||
| ---@return T | ||||
| function vim.spairs(t) | ||||
|   assert(type(t) == 'table', ('expected table, got %s'):format(type(t))) | ||||
|   vim.validate('t', t, 'table') | ||||
|   --- @cast t table<any,any> | ||||
|  | ||||
|   -- collect the keys | ||||
| @@ -691,7 +691,7 @@ end | ||||
| ---@param t table Table | ||||
| ---@return integer : Number of non-nil values in table | ||||
| function vim.tbl_count(t) | ||||
|   vim.validate({ t = { t, 't' } }) | ||||
|   vim.validate('t', t, 'table') | ||||
|   --- @cast t table<any,any> | ||||
|  | ||||
|   local count = 0 | ||||
| @@ -723,7 +723,7 @@ end | ||||
| ---@param s string String to trim | ||||
| ---@return string String with whitespace removed from its beginning and end | ||||
| function vim.trim(s) | ||||
|   vim.validate({ s = { s, 's' } }) | ||||
|   vim.validate('s', s, 'string') | ||||
|   return s:match('^%s*(.*%S)') or '' | ||||
| end | ||||
|  | ||||
| @@ -733,7 +733,7 @@ end | ||||
| ---@param s string String to escape | ||||
| ---@return string %-escaped pattern string | ||||
| function vim.pesc(s) | ||||
|   vim.validate({ s = { s, 's' } }) | ||||
|   vim.validate('s', s, 'string') | ||||
|   return (s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')) | ||||
| end | ||||
|  | ||||
| @@ -743,7 +743,8 @@ end | ||||
| ---@param prefix string Prefix to match | ||||
| ---@return boolean `true` if `prefix` is a prefix of `s` | ||||
| function vim.startswith(s, prefix) | ||||
|   vim.validate({ s = { s, 's' }, prefix = { prefix, 's' } }) | ||||
|   vim.validate('s', s, 'string') | ||||
|   vim.validate('prefix', prefix, 'string') | ||||
|   return s:sub(1, #prefix) == prefix | ||||
| end | ||||
|  | ||||
| @@ -753,7 +754,8 @@ end | ||||
| ---@param suffix string Suffix to match | ||||
| ---@return boolean `true` if `suffix` is a suffix of `s` | ||||
| function vim.endswith(s, suffix) | ||||
|   vim.validate({ s = { s, 's' }, suffix = { suffix, 's' } }) | ||||
|   vim.validate('s', s, 'string') | ||||
|   vim.validate('suffix', suffix, 'string') | ||||
|   return #suffix == 0 or s:sub(-#suffix) == suffix | ||||
| end | ||||
|  | ||||
| @@ -877,8 +879,30 @@ do | ||||
|     return true | ||||
|   end | ||||
|  | ||||
|   --- Validates a parameter specification (types and values). Specs are evaluated in alphanumeric | ||||
|   --- order, until the first failure. | ||||
|   --- Validate function arguments. | ||||
|   --- | ||||
|   --- This function has two valid forms: | ||||
|   --- | ||||
|   --- 1. vim.validate(name: str, value: any, type: string, optional?: bool) | ||||
|   --- 2. vim.validate(spec: table) | ||||
|   --- | ||||
|   --- Form 1 validates that argument {name} with value {value} has the type | ||||
|   --- {type}. {type} must be a value returned by |lua-type()|. If {optional} is | ||||
|   --- true, then {value} may be null. This form is significantly faster and | ||||
|   --- should be preferred for simple cases. | ||||
|   --- | ||||
|   --- Example: | ||||
|   --- | ||||
|   --- ```lua | ||||
|   --- function vim.startswith(s, prefix) | ||||
|   ---   vim.validate('s', s, 'string') | ||||
|   ---   vim.validate('prefix', prefix, 'string') | ||||
|   ---   ... | ||||
|   --- end | ||||
|   --- ``` | ||||
|   --- | ||||
|   --- Form 2 validates a parameter specification (types and values). Specs are | ||||
|   --- evaluated in alphanumeric order, until the first failure. | ||||
|   --- | ||||
|   --- Usage example: | ||||
|   --- | ||||
| @@ -930,8 +954,32 @@ do | ||||
|   ---               only if the argument is valid. Can optionally return an additional | ||||
|   ---               informative error message as the second returned value. | ||||
|   ---             - msg: (optional) error string if validation fails | ||||
|   function vim.validate(opt) | ||||
|     local ok, err_msg = is_valid(opt) | ||||
|   --- @overload fun(name: string, val: any, expected: string, optional?: boolean) | ||||
|   function vim.validate(opt, ...) | ||||
|     local ok = false | ||||
|     local err_msg ---@type string? | ||||
|     local narg = select('#', ...) | ||||
|     if narg == 0 then | ||||
|       ok, err_msg = is_valid(opt) | ||||
|     elseif narg >= 2 then | ||||
|       -- Overloaded signature for fast/simple cases | ||||
|       local name = opt --[[@as string]] | ||||
|       local v, expected, optional = ... ---@type string, string, boolean? | ||||
|       local actual = type(v) | ||||
|  | ||||
|       ok = (actual == expected) or (v == nil and optional == true) | ||||
|       if not ok then | ||||
|         err_msg = ('%s: expected %s, got %s%s'):format( | ||||
|           name, | ||||
|           expected, | ||||
|           actual, | ||||
|           v and (' (%s)'):format(v) or '' | ||||
|         ) | ||||
|       end | ||||
|     else | ||||
|       error('invalid arguments') | ||||
|     end | ||||
|  | ||||
|     if not ok then | ||||
|       error(err_msg, 2) | ||||
|     end | ||||
|   | ||||
| @@ -1408,7 +1408,25 @@ describe('lua stdlib', function() | ||||
|     exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}") | ||||
|     exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0  end, 'even number' }}") | ||||
|     exec_lua("vim.validate{arg1={5, {'n', 's'} }, arg2={ 'foo', {'n', 's'} }}") | ||||
|     vim.validate('arg1', 5, 'number') | ||||
|     vim.validate('arg1', '5', 'string') | ||||
|     vim.validate('arg1', { 5 }, 'table') | ||||
|     vim.validate('arg1', function() | ||||
|       return 5 | ||||
|     end, 'function') | ||||
|     vim.validate('arg1', nil, 'number', true) | ||||
|     vim.validate('arg1', nil, 'string', true) | ||||
|     vim.validate('arg1', nil, 'table', true) | ||||
|     vim.validate('arg1', nil, 'function', true) | ||||
|  | ||||
|     matches('arg1: expected number, got nil', pcall_err(vim.validate, 'arg1', nil, 'number')) | ||||
|     matches('arg1: expected string, got nil', pcall_err(vim.validate, 'arg1', nil, 'string')) | ||||
|     matches('arg1: expected table, got nil', pcall_err(vim.validate, 'arg1', nil, 'table')) | ||||
|     matches('arg1: expected function, got nil', pcall_err(vim.validate, 'arg1', nil, 'function')) | ||||
|     matches('arg1: expected string, got number', pcall_err(vim.validate, 'arg1', 5, 'string')) | ||||
|     matches('arg1: expected table, got number', pcall_err(vim.validate, 'arg1', 5, 'table')) | ||||
|     matches('arg1: expected function, got number', pcall_err(vim.validate, 'arg1', 5, 'function')) | ||||
|     matches('arg1: expected number, got string', pcall_err(vim.validate, 'arg1', '5', 'number')) | ||||
|     matches('expected table, got number', pcall_err(exec_lua, "vim.validate{ 1, 'x' }")) | ||||
|     matches('invalid type name: x', pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}")) | ||||
|     matches('invalid type name: 1', pcall_err(exec_lua, 'vim.validate{ arg1={ 1, 1 }}')) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Gregory Anders
					Gregory Anders