feat(lua)!: vim.isnil, vim.nonnil, deprecate vim.F #39495

This commit is contained in:
Olivia Kinnear
2026-05-06 07:15:00 -05:00
committed by GitHub
parent f562204a5c
commit fcd1d97265
22 changed files with 197 additions and 58 deletions
+7
View File
@@ -27,6 +27,13 @@ API
EVENTS
• *BufModifiedSet* Use |OptionSet| with pattern "modified" instead.
LUA
• vim.F.if_nil() Renamed to |vim.nonnil()|
• vim.F.ok_or_nil()
• vim.F.npcall() Renamed to |vim.npcall()|
• vim.F.nil_wrap() Use |vim.npcall()| instead
------------------------------------------------------------------------------
DEPRECATED IN 0.12 *deprecated-0.12*
+48
View File
@@ -1768,6 +1768,18 @@ vim.islist({t}) *vim.islist()*
See also: ~
• |vim.isarray()|
vim.isnil({t}) *vim.isnil()*
Tests if `t` is `nil` or |vim.NIL|.
Attributes: ~
Since: 0.13.0
Parameters: ~
• {t} (`any?`)
Return: ~
(`boolean`) `true` if `nil` or |vim.NIL|, else `false`.
vim.list.bisect({t}, {val}, {opts}) *vim.list.bisect()*
Search for a position in a sorted |lua-list| {t} where {val} can be
inserted while keeping the list sorted.
@@ -1906,6 +1918,42 @@ vim.list_slice({list}, {start}, {finish}) *vim.list_slice()*
Return: ~
(`any[]`) Copy of table sliced from start to finish (inclusive)
vim.nonnil({...}) *vim.nonnil()*
Returns the first argument which is not nil.
If all arguments are nil, returns nil.
Example: >lua
local a = nil
local b = nil
local c = 42
local d = true
assert(vim.nonnil(a, b, c, d) == 42)
<
Attributes: ~
Since: 0.13.0
Parameters: ~
• {...} (`any`)
Return: ~
(`any`)
vim.npcall({fn}, {...}) *vim.npcall()*
Calls the function `fn` in `protected mode` like |pcall()|, but returns
`nil` on error.
Attributes: ~
Since: 0.13.0
Parameters: ~
• {fn} (`fun(...):T`)
• {...} (`any?`)
Return: ~
(`any`) ...
vim.pesc({s}) *vim.pesc()*
Escapes magic chars in |lua-pattern|s.
+4
View File
@@ -176,6 +176,10 @@ LUA
available.
• |vim.list.unique()| and |vim.list.bisect()| now support passing a string
as a shorthand of a `key`
• |vim.isnil()| tests if a value is `nil` or |vim.NIL|.
• |vim.nonnil()| returns the first argument which is not nil.
• |vim.npcall()| calls the function `fn` in protected-mode like |pcall()|,
but returns `nil` on error.
OPTIONS
+2 -2
View File
@@ -498,8 +498,8 @@ local function get_paths(name, sect)
-- - does not work on MacOS 14 and later.
-- - only returns '/usr/bin/man' on MacOS 13 and earlier.
--- @type string?
local mandirs_raw = vim.F.npcall(system, { 'manpath', '-q' })
or vim.F.npcall(system, { 'man', '-w' })
local mandirs_raw = vim.npcall(system, { 'manpath', '-q' })
or vim.npcall(system, { 'man', '-w' })
or vim.env.MANPATH
if not mandirs_raw then
+16 -13
View File
@@ -14,22 +14,19 @@ local F = {}
--- assert(vim.F.if_nil(a, b, c, d) == 42)
--- ```
---
---@generic T
---@param ... T
---@return T
--- @deprecated
--- @generic T
--- @param ... T
--- @return T
function F.if_nil(...)
local nargs = select('#', ...)
for i = 1, nargs do
local v = select(i, ...)
if v ~= nil then
return v
end
end
return nil
vim.deprecate('vim.F.if_nil', 'vim.nonnil', '0.14')
return vim.nonnil(...)
end
-- Use in combination with pcall
--- @deprecated
function F.ok_or_nil(status, ...)
vim.deprecate('vim.F.ok_or_nil', 'actual error handling', '0.14')
if not status then
return
end
@@ -37,21 +34,27 @@ function F.ok_or_nil(status, ...)
end
-- Nil pcall.
--- @deprecated
--- @generic T
--- @param fn fun(...):T
--- @param ... T?
--- @return T
function F.npcall(fn, ...)
return F.ok_or_nil(pcall(fn, ...))
vim.deprecate('vim.F.npcall', 'vim.npcall', '0.14')
return vim.npcall(fn, ...)
end
--- Wrap a function to return nil if it fails, otherwise the value
--- @deprecated
function F.nil_wrap(fn)
vim.deprecate('vim.F.nil_wrap', 'vim.npcall', '0.14')
return function(...)
return F.npcall(fn, ...)
return vim.npcall(fn, ...)
end
end
-- TODO: deprecate `F.pack_len` and `F.unpack_len`
--- like {...} except preserve the length explicitly
function F.pack_len(...)
return { n = select('#', ...), ... }
+55 -1
View File
@@ -1006,6 +1006,15 @@ function vim.islist(t)
return true
end
--- Tests if `t` is `nil` or |vim.NIL|.
---
--- @since 15
--- @param t? any
--- @return boolean `true` if `nil` or |vim.NIL|, else `false`.
function vim.isnil(t)
return t == nil or t == vim.NIL
end
--- Counts the number of non-nil values in table `t`.
---
--- ```lua
@@ -1582,7 +1591,7 @@ local get_context_state = function(context)
-- Do not override already set state and fall back to `vim.NIL` for
-- state `nil` values (which still needs restoring later)
res[sc][name] = vim.F.if_nil(res[sc][name], vim[sc][name], vim.NIL)
res[sc][name] = vim.nonnil(res[sc][name], vim[sc][name], vim.NIL)
-- Always track global option value to properly restore later.
-- This matters for at least `o` and `wo` (which might set either/both
@@ -1758,4 +1767,49 @@ end
-- Use max 32-bit signed int value to avoid overflow on 32-bit systems. #31633
vim._maxint = 2 ^ 32 - 1
--- Returns the first argument which is not nil.
---
--- If all arguments are nil, returns nil.
---
--- Example:
---
--- ```lua
--- local a = nil
--- local b = nil
--- local c = 42
--- local d = true
--- assert(vim.nonnil(a, b, c, d) == 42)
--- ```
---
--- @since 15
--- @generic T
--- @param ... T
--- @return T
function vim.nonnil(...)
local nargs = select('#', ...)
for i = 1, nargs do
local v = select(i, ...)
if v ~= nil then
return v
end
end
return nil
end
--- Calls the function `fn` in `protected mode` like |pcall()|, but returns
--- `nil` on error.
---
--- @since 15
--- @generic T
--- @param fn fun(...):T
--- @param ... any?
--- @return T ...
function vim.npcall(fn, ...)
return (function(success, ...)
if success then
return ...
end
end)(pcall(fn, ...))
end
return vim
-7
View File
@@ -166,11 +166,4 @@ function M.get_forge_url(repo, target, target_type)
return ('%s/%s/%s'):format(repo, middle, target)
end
--- Check if value is `nil` or `vim.NIL`
---
--- @return boolean
function M.isnil(value)
return value == nil or value == vim.NIL
end
return M
+1 -1
View File
@@ -799,7 +799,7 @@ end
--- @param opts? vim.diagnostic.setqflist.Opts|vim.diagnostic.setloclist.Opts
local function set_list(loclist, opts)
opts = opts or {}
local open = vim.F.if_nil(opts.open, true)
local open = vim.nonnil(opts.open, true)
local title = opts.title or 'Diagnostics'
local winnr = opts.winnr or 0
local bufnr --- @type integer?
+3 -3
View File
@@ -1,4 +1,4 @@
local api, if_nil = vim.api, vim.F.if_nil
local api, nonnil = vim.api, vim.nonnil
local shared = require('vim.diagnostic._shared')
local store = require('vim.diagnostic._store')
@@ -99,7 +99,7 @@ function M.open(opts, ...)
return
end
local severity_sort = if_nil(opts.severity_sort, global_opts.severity_sort)
local severity_sort = nonnil(opts.severity_sort, global_opts.severity_sort)
if severity_sort then
if type(severity_sort) == 'table' and severity_sort.reverse then
table.sort(diagnostics, function(a, b)
@@ -114,7 +114,7 @@ function M.open(opts, ...)
local lines = {} --- @type string[]
local highlights = {} --- @type { hlname: string, prefix?: { length: integer, hlname: string? }, suffix?: { length: integer, hlname: string? } }[]
local header = if_nil(opts.header, 'Diagnostics:')
local header = nonnil(opts.header, 'Diagnostics:')
if header then
vim.validate('header', header, { 'string', 'table' }, "'string' or 'table'")
if type(header) == 'table' then
+4 -4
View File
@@ -1,4 +1,4 @@
local api, if_nil = vim.api, vim.F.if_nil
local api, nonnil = vim.api, vim.nonnil
local shared = require('vim.diagnostic._shared')
local store = require('vim.diagnostic._store')
@@ -60,7 +60,7 @@ local function next_diagnostic(search_forward, opts, use_logical_pos)
-- Adjust row to be 0-indexed
position[1] = position[1] - 1
local wrap = if_nil(opts.wrap, true)
local wrap = nonnil(opts.wrap, true)
local diagnostics = store.get_diagnostics(bufnr, opts, true)
if opts._highest then
@@ -197,7 +197,7 @@ end
function M.goto_prev(opts)
vim.deprecate('vim.diagnostic.goto_prev()', 'vim.diagnostic.jump()', '0.13')
opts = opts or {}
opts.float = if_nil(opts.float, true) --- @diagnostic disable-line
opts.float = nonnil(opts.float, true) --- @diagnostic disable-line
goto_diagnostic(M.get_prev(opts), opts)
end
@@ -282,7 +282,7 @@ end
function M.goto_next(opts)
vim.deprecate('vim.diagnostic.goto_next()', 'vim.diagnostic.jump()', '0.13')
opts = opts or {}
opts.float = if_nil(opts.float, true) --- @diagnostic disable-line
opts.float = nonnil(opts.float, true) --- @diagnostic disable-line
goto_diagnostic(M.get_next(opts), opts)
end
+2 -2
View File
@@ -208,8 +208,8 @@ function M.is_enabled(name, filter)
-- As a fallback when not explicitly enabled or disabled:
-- Clients are treated as "enabled" since their capabilities can control behavior.
-- Buffers are treated as "disabled" to allow users to enable them as needed.
return vim.F.if_nil(client and client._enabled_capabilities[name], vim.g[var], true)
and vim.F.if_nil(bufnr and vim.b[bufnr][var], vim.g[var], false)
return vim.nonnil(client and client._enabled_capabilities[name], vim.g[var], true)
and vim.nonnil(bufnr and vim.b[bufnr][var], vim.g[var], false)
end
M.all = all_capabilities
+1 -1
View File
@@ -64,7 +64,7 @@ local state_by_group = setmetatable({}, {
---@param client vim.lsp.Client
---@return vim.lsp.CTGroup
local function get_group(client)
local allow_inc_sync = vim.F.if_nil(client.flags.allow_incremental_sync, true)
local allow_inc_sync = vim.nonnil(client.flags.allow_incremental_sync, true)
local change_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
local sync_kind = change_capability or protocol.TextDocumentSyncKind.None
if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then
+1 -1
View File
@@ -5,7 +5,7 @@ local api = vim.api
local lsp = vim.lsp
local validate = vim.validate
local util = require('vim.lsp.util')
local npcall = vim.F.npcall
local npcall = vim.npcall
local M = {}
+1 -2
View File
@@ -36,7 +36,6 @@ local M = {}
local api = vim.api
local lsp = vim.lsp
local protocol = lsp.protocol
local isnil = require('vim._core.util').isnil
local rtt_ms = 50.0
local ns_to_ms = 0.000001
@@ -1011,7 +1010,7 @@ local function trigger(bufnr, clients, ctx)
client and client.name or 'UNKNOWN'
)
)
elseif not isnil(result) and #(result.items or result) > 0 then
elseif not vim.isnil(result) and #(result.items or result) > 0 then
Context.isIncomplete = Context.isIncomplete or result.isIncomplete
local encoding = client and client.offset_encoding or 'utf-16'
local client_matches, tmp_server_start_boundary
+7 -5
View File
@@ -3,7 +3,6 @@ local validate = vim.validate
local api = vim.api
local list_extend = vim.list_extend
local uv = vim.uv
local isnil = require('vim._core.util').isnil
local M = {}
@@ -1030,7 +1029,7 @@ function M.show_document(location, position_encoding, opts)
local bufnr = vim.uri_to_bufnr(uri)
opts = opts or {}
local focus = vim.F.if_nil(opts.focus, true)
local focus = vim.nonnil(opts.focus, true)
if focus then
-- Save position in jumplist
vim.cmd("normal! m'")
@@ -1982,13 +1981,16 @@ function M.symbols_to_items(symbols, bufnr, position_encoding)
local end_lnum = range['end'].line + 1
local end_col = get_line_byte_from_position(bufnr, range['end'], position_encoding) + 1
local is_deprecated = not isnil(symbol.deprecated or nil)
or (not isnil(symbol.tags) and vim.tbl_contains(symbol.tags, protocol.SymbolTag.Deprecated))
local is_deprecated = not vim.isnil(symbol.deprecated or nil)
or (
not vim.isnil(symbol.tags)
and vim.tbl_contains(symbol.tags, protocol.SymbolTag.Deprecated)
)
local text = string.format(
'[%s] %s%s%s',
kind,
symbol.name,
not isnil(symbol.containerName) and ' in ' .. symbol.containerName or '',
not vim.isnil(symbol.containerName) and ' in ' .. symbol.containerName or '',
is_deprecated and ' (deprecated)' or ''
)
+2 -2
View File
@@ -435,7 +435,7 @@ local function normalize_plugs(plugs)
local p_data = plug_map[p.path]
-- TODO(echasnovski): if both versions are `vim.VersionRange`, collect as
-- their intersection. Needs `vim.version.intersect`.
p_data.plug.spec.version = vim.F.if_nil(p_data.plug.spec.version, p.spec.version)
p_data.plug.spec.version = vim.nonnil(p_data.plug.spec.version, p.spec.version)
-- Ensure no conflicts
local spec_ref = p_data.plug.spec
@@ -964,7 +964,7 @@ local function lock_read(confirm, specs)
plugin_lock = { plugins = {} }
end
lock_sync(vim.F.if_nil(confirm, true), vim.F.if_nil(specs, {}))
lock_sync(vim.nonnil(confirm, true), vim.nonnil(specs, {}))
end
--- @class vim.pack.keyset.add
+1 -1
View File
@@ -77,7 +77,7 @@ local function system(cmd, args)
vim.fn.chansend(jobid, stdin)
end
local res = vim.fn.jobwait({ jobid }, vim.F.if_nil(args.timeout, 30) * 1000)
local res = vim.fn.jobwait({ jobid }, vim.nonnil(args.timeout, 30) * 1000)
if res[1] == -1 then
error('Command timed out: ' .. shellify(cmd))
vim.fn.jobstop(jobid)
+1 -1
View File
@@ -97,7 +97,7 @@ function M.get_parser(buf, lang, opts)
if not api.nvim_buf_is_loaded(buf) then
return nil, string.format('Buffer %s must be loaded to create parser', buf)
end
local parser = vim.F.npcall(M._create_parser, buf, lang, opts)
local parser = vim.npcall(M._create_parser, buf, lang, opts)
if not parser then
return nil,
string.format('Parser could not be created for buffer %s and language "%s"', buf, lang)
+1 -1
View File
@@ -173,7 +173,7 @@ function M.lint(buf, opts)
local lang = opts.langs[i]
--- @type (table|nil)
local parser_info = vim.F.npcall(vim.treesitter.language.inspect, lang)
local parser_info = vim.npcall(vim.treesitter.language.inspect, lang)
local lang_context = {
lang = lang,
parser_info = parser_info,
+1 -1
View File
@@ -1415,7 +1415,7 @@ end
---@return TSTree?
function LanguageTree:tree_for_range(range, opts)
opts = opts or {}
local ignore = vim.F.if_nil(opts.ignore_injections, true)
local ignore = vim.nonnil(opts.ignore_injections, true)
if not ignore then
for _, child in pairs(self._children) do
+1 -1
View File
@@ -3,7 +3,7 @@ vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead', 'BufFilePost' }, {
group = group,
callback = function(ev)
-- Buffer-local enable has higher priority
local enable = vim.F.if_nil(vim.b.editorconfig, vim.g.editorconfig, true)
local enable = vim.nonnil(vim.b.editorconfig, vim.g.editorconfig, true)
if not enable then
return
end
+38 -9
View File
@@ -982,6 +982,14 @@ describe('lua stdlib', function()
)
end)
it('vim.isnil', function()
eq(true, exec_lua('return vim.isnil(nil)'))
eq(true, exec_lua('return vim.isnil(vim.NIL)'))
eq(false, exec_lua('return vim.isnil(true)'))
eq(false, exec_lua('return vim.isnil(false)'))
eq(false, exec_lua('return vim.isnil({})'))
end)
it('vim.tbl_isempty', function()
eq(true, exec_lua('return vim.tbl_isempty({})'))
eq(false, exec_lua('return vim.tbl_isempty({ 1, 2, 3 })'))
@@ -2976,8 +2984,8 @@ describe('lua stdlib', function()
)
end)
it('vim.F.if_nil', function()
local function if_nil(...)
it('vim.nonnil', function()
local function nonnil(...)
return exec_lua(
[[
local args = {...}
@@ -2987,7 +2995,7 @@ describe('lua stdlib', function()
args[i] = nil
end
end
return vim.F.if_nil(unpack(args, 1, nargs))
return vim.nonnil(unpack(args, 1, nargs))
]],
...
)
@@ -2997,12 +3005,33 @@ describe('lua stdlib', function()
local b = NIL
local c = 42
local d = false
eq(42, if_nil(a, c))
eq(false, if_nil(d, b))
eq(42, if_nil(a, b, c, d))
eq(false, if_nil(d))
eq(false, if_nil(d, c))
eq(NIL, if_nil(a))
eq(42, nonnil(a, c))
eq(false, nonnil(d, b))
eq(42, nonnil(a, b, c, d))
eq(false, nonnil(d))
eq(false, nonnil(d, c))
eq(NIL, nonnil(a))
end)
it('vim.npcall', function()
-- No error
eq(
{ '123', 'test' },
exec_lua(function()
local function swap_args(a, b)
return b, a
end
return { vim.npcall(swap_args, 'test', '123') }
end)
)
-- Error
eq(
nil,
exec_lua(function()
return vim.npcall(error, 'error')
end)
)
end)
it('lpeg', function()