mirror of
https://github.com/neovim/neovim
synced 2025-07-18 02:01:46 +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
|
• https://www.lua.org/pil/20.2.html
|
||||||
|
|
||||||
vim.validate({opt}) *vim.validate()*
|
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.
|
evaluated in alphanumeric order, until the first failure.
|
||||||
|
|
||||||
Usage example: >lua
|
Usage example: >lua
|
||||||
|
@ -214,7 +214,7 @@ end
|
|||||||
---@param t table<T, any> (table) Table
|
---@param t table<T, any> (table) Table
|
||||||
---@return T[] : List of keys
|
---@return T[] : List of keys
|
||||||
function vim.tbl_keys(t)
|
function vim.tbl_keys(t)
|
||||||
vim.validate({ t = { t, 't' } })
|
vim.validate('t', t, 'table')
|
||||||
--- @cast t table<any,any>
|
--- @cast t table<any,any>
|
||||||
|
|
||||||
local keys = {}
|
local keys = {}
|
||||||
@ -231,7 +231,7 @@ end
|
|||||||
---@param t table<any, T> (table) Table
|
---@param t table<any, T> (table) Table
|
||||||
---@return T[] : List of values
|
---@return T[] : List of values
|
||||||
function vim.tbl_values(t)
|
function vim.tbl_values(t)
|
||||||
vim.validate({ t = { t, 't' } })
|
vim.validate('t', t, 'table')
|
||||||
|
|
||||||
local values = {}
|
local values = {}
|
||||||
for _, v in
|
for _, v in
|
||||||
@ -332,7 +332,7 @@ end
|
|||||||
---@param value any Value to compare
|
---@param value any Value to compare
|
||||||
---@return boolean `true` if `t` contains `value`
|
---@return boolean `true` if `t` contains `value`
|
||||||
function vim.list_contains(t, value)
|
function vim.list_contains(t, value)
|
||||||
vim.validate({ t = { t, 't' } })
|
vim.validate('t', t, 'table')
|
||||||
--- @cast t table<any,any>
|
--- @cast t table<any,any>
|
||||||
|
|
||||||
for _, v in ipairs(t) do
|
for _, v in ipairs(t) do
|
||||||
@ -350,7 +350,7 @@ end
|
|||||||
---@param t table Table to check
|
---@param t table Table to check
|
||||||
---@return boolean `true` if `t` is empty
|
---@return boolean `true` if `t` is empty
|
||||||
function vim.tbl_isempty(t)
|
function vim.tbl_isempty(t)
|
||||||
vim.validate({ t = { t, 't' } })
|
vim.validate('t', t, 'table')
|
||||||
return next(t) == nil
|
return next(t) == nil
|
||||||
end
|
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 fun(table: table<K, V>, index?: K):K, V # |for-in| iterator over sorted keys and their values
|
||||||
---@return T
|
---@return T
|
||||||
function vim.spairs(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>
|
--- @cast t table<any,any>
|
||||||
|
|
||||||
-- collect the keys
|
-- collect the keys
|
||||||
@ -691,7 +691,7 @@ end
|
|||||||
---@param t table Table
|
---@param t table Table
|
||||||
---@return integer : Number of non-nil values in table
|
---@return integer : Number of non-nil values in table
|
||||||
function vim.tbl_count(t)
|
function vim.tbl_count(t)
|
||||||
vim.validate({ t = { t, 't' } })
|
vim.validate('t', t, 'table')
|
||||||
--- @cast t table<any,any>
|
--- @cast t table<any,any>
|
||||||
|
|
||||||
local count = 0
|
local count = 0
|
||||||
@ -723,7 +723,7 @@ end
|
|||||||
---@param s string String to trim
|
---@param s string String to trim
|
||||||
---@return string String with whitespace removed from its beginning and end
|
---@return string String with whitespace removed from its beginning and end
|
||||||
function vim.trim(s)
|
function vim.trim(s)
|
||||||
vim.validate({ s = { s, 's' } })
|
vim.validate('s', s, 'string')
|
||||||
return s:match('^%s*(.*%S)') or ''
|
return s:match('^%s*(.*%S)') or ''
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -733,7 +733,7 @@ end
|
|||||||
---@param s string String to escape
|
---@param s string String to escape
|
||||||
---@return string %-escaped pattern string
|
---@return string %-escaped pattern string
|
||||||
function vim.pesc(s)
|
function vim.pesc(s)
|
||||||
vim.validate({ s = { s, 's' } })
|
vim.validate('s', s, 'string')
|
||||||
return (s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1'))
|
return (s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1'))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -743,7 +743,8 @@ end
|
|||||||
---@param prefix string Prefix to match
|
---@param prefix string Prefix to match
|
||||||
---@return boolean `true` if `prefix` is a prefix of `s`
|
---@return boolean `true` if `prefix` is a prefix of `s`
|
||||||
function vim.startswith(s, prefix)
|
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
|
return s:sub(1, #prefix) == prefix
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -753,7 +754,8 @@ end
|
|||||||
---@param suffix string Suffix to match
|
---@param suffix string Suffix to match
|
||||||
---@return boolean `true` if `suffix` is a suffix of `s`
|
---@return boolean `true` if `suffix` is a suffix of `s`
|
||||||
function vim.endswith(s, suffix)
|
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
|
return #suffix == 0 or s:sub(-#suffix) == suffix
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -877,8 +879,30 @@ do
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Validates a parameter specification (types and values). Specs are evaluated in alphanumeric
|
--- Validate function arguments.
|
||||||
--- order, until the first failure.
|
---
|
||||||
|
--- 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:
|
--- Usage example:
|
||||||
---
|
---
|
||||||
@ -930,8 +954,32 @@ do
|
|||||||
--- only if the argument is valid. Can optionally return an additional
|
--- only if the argument is valid. Can optionally return an additional
|
||||||
--- informative error message as the second returned value.
|
--- informative error message as the second returned value.
|
||||||
--- - msg: (optional) error string if validation fails
|
--- - msg: (optional) error string if validation fails
|
||||||
function vim.validate(opt)
|
--- @overload fun(name: string, val: any, expected: string, optional?: boolean)
|
||||||
local ok, err_msg = is_valid(opt)
|
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
|
if not ok then
|
||||||
error(err_msg, 2)
|
error(err_msg, 2)
|
||||||
end
|
end
|
||||||
|
@ -1408,7 +1408,25 @@ describe('lua stdlib', function()
|
|||||||
exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}")
|
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={2, function(a) return (a % 2) == 0 end, 'even number' }}")
|
||||||
exec_lua("vim.validate{arg1={5, {'n', 's'} }, arg2={ 'foo', {'n', 's'} }}")
|
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('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: x', pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}"))
|
||||||
matches('invalid type name: 1', pcall_err(exec_lua, 'vim.validate{ arg1={ 1, 1 }}'))
|
matches('invalid type name: 1', pcall_err(exec_lua, 'vim.validate{ arg1={ 1, 1 }}'))
|
||||||
|
Reference in New Issue
Block a user