Skip to content

lua_ls workspace.library: vim.tbl_extend overwrites two runtime paths instead of appending #2138

Description

@4rc0s

Before Reporting an Issue

  • I have read the kickstart.nvim README.md.

  • I have read the appropriate plugin's documentation.

  • I have searched that this issue has not been reported before.

  • I have ran :checkhealth and so no obvious issue.

  • By checking this, I confirm that the above steps are completed. I understand leaving this unchecked will result in this report being closed immediately.

Describe the bug

vim.tbl_extend is used to append the two ${3rd} paths to the lua_ls
workspace library, but both arguments are list tables. vim.tbl_extend
merges by key, so ${3rd}/luv/library and ${3rd}/busted/library land on
indices 1 and 2 and overwrite the first two runtime directories instead
of being appended.

init.lua:728 (current master, b01d052):

library = vim.tbl_extend('force', vim.api.nvim_get_runtime_file('', true), {
  '${3rd}/luv/library',
  '${3rd}/busted/library',
}),

The list never grows: it comes back the same length it went in, minus the two
runtime paths that were silently replaced. On my machine those two are
~/.config/nvim and ~/.local/share/nvim/site.

vim.list_extend is the one that appends.

Why this may be load-bearing, and why the obvious fix is a regression

The two entries that get overwritten are the first two of
nvim_get_runtime_file('', true), and the first is the user's own config
directory. That is exactly what the linked
nvim-lspconfig#3189
referenced in the comment two lines above this one — says to remove, because
including the config dir in workspace.library produces spurious
duplicate-doc-field warnings when editing your own config.

So the bug currently implements that issue's workaround by accident. Changing
tbl_extend to list_extend on its own will reintroduce the
duplicate-doc-field warnings #3189 describes.
That seemed worth flagging
before someone applies the one-word fix.

To Reproduce

  1. Save as repro.lua:
local rt = vim.api.nvim_get_runtime_file('', true)
local merged = vim.tbl_extend('force', rt, { '${3rd}/luv/library', '${3rd}/busted/library' })
print('runtime dirs:        ' .. #rt)
print('after tbl_extend:    ' .. #merged .. '   (expected ' .. (#rt + 2) .. ')')
print('rt[1] was:           ' .. rt[1])
print('merged[1] is now:    ' .. merged[1])
print('config dir survived: ' .. tostring(vim.tbl_contains(merged, rt[1])))
local fixed = vim.list_extend(vim.deepcopy(rt), { '${3rd}/luv/library', '${3rd}/busted/library' })
print('after list_extend:   ' .. #fixed)
  1. nvim --headless --clean -c 'luafile repro.lua' -c qa

Output (--clean, so only 5 runtime dirs; a real config has many more):

runtime dirs:        5
after tbl_extend:    5   (expected 7)
rt[1] was:           /usr/share/nvim/site
merged[1] is now:    ${3rd}/luv/library
config dir survived: false
after list_extend:   7

Potential fixes

Depending on what the intended behaviour is:

A. Append, and filter the config dir explicitly — fixes the merge and keeps
#3189 handled deliberately rather than accidentally:

local library = vim.tbl_filter(function(p)
  return p ~= vim.fn.stdpath 'config' and p ~= vim.fn.stdpath 'config' .. '/after'
end, vim.api.nvim_get_runtime_file('', true))
library = vim.list_extend(library, { '${3rd}/luv/library', '${3rd}/busted/library' })

B. Drop the sweep. The comment above already concedes this path "is a lot
slower and will cause issues when working on your own configuration."
lazydev.nvim resolves libraries from
the require statements actually present in the buffer. On my config that took
LuaLS from 32 runtime directories (3,331 files, ~650k lines) down to 7 resolved
paths, and the pause on first opening a Lua file went away. This is what LazyVim
does. I understand kickstart may not want the extra dependency — mentioning it
because the trade-off is already acknowledged in the comment.

C. Minimal. Just list_extend, accepting the #3189 warnings come back.

Happy to send a PR for whichever direction you prefer.

Unrelated, but adjacent — b01d052

The fix for #2133 kept the --[[@as lspconfig.settings.lua_ls]] cast but
dropped the or {} the reporter had suggested:

local current_settings = client.config.settings --[[@as lspconfig.settings.lua_ls]]
client.config.settings.Lua = vim.tbl_deep_extend('force', current_settings.Lua, {

client.config.settings.Lua or {} silences the same diagnostic without
depending on nvim-lspconfig's type definitions resolving in the workspace, and
also guards tbl_deep_extend against a nil first argument. Not a bug today
since settings is defined a few lines below with a Lua key — just noting it
since the cast is the only thing making those type definitions load-bearing.

Desktop

  • OS: Fedora Linux 44 (Workstation Edition)
  • Terminal: tmux (tmux-256color)

Neovim Version

NVIM v0.12.4
Build type: RelWithDebInfo
LuaJIT 2.1.1767980792

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions