Skip to content

Request: Limit "Fields cannot be injected" to exact classes #3361

Open

Description

@idbrii

How are you using the lua-language-server?

NeoVim

Which OS are you using?

Windows

What is the issue affecting?

Annotations

Expected Behaviour

The docs say:

Marking the class as (exact) means fields cannot be injected after the definition.

So when I use @class, I expect to not get any injection warnings unless I use (exact). I see this came up before in #2710, so I'd add: I should be able to get a warning for injections in exact classes while silencing warnings in non-exact classes.

Actual Behaviour

Any time I add a field outside of the constructor (and sometimes even ones that are defined in my class constructor), I get this warning:

Fields cannot be injected into the reference of ClassX for field_y. To do so, use ---@class for instance_z.

The @class annotation makes lua too strict. I'm using lua because I want to be able to inject fields.

Using annotations has multiple effects:

  1. It improves completion.
  2. It improves symbol lookups like goto definition.
  3. It does type checking.

But it's not great that adding annotations to get 1+2 results in tons of warnings from 3.

I understand that people may not want to add (exact) all over their codebase, but I don't want to add ---@field [any] any to all of mine.

I'd propose a change to diagnostic flags to allow independently controlling these two cases:

  • inject-field: suppress inject field diagnostic for non-exact classes.
  • inject-field-exact: suppress inject field diagnostic for exact classes.

Reproduction steps

Using neovim and installed luals with Mason. Version: pkg:github/luals/lua-language-server@3.17.1

-- Stripped-down version of https://github.com/rxi/classic/blob/master/classic.lua with an annotation added to be close to real-world class usage.
--
-- classic
--
-- Copyright (c) 2014, rxi
--
-- This module is free software; you can redistribute it and/or modify it under
-- the terms of the MIT license. See LICENSE for details.

--- @class Object
local Object = {}
Object.__index = Object

function Object:new()
end

function Object:extend()
  local cls = {}
  for k, v in pairs(self) do
    if k:find("__") == 1 then
      cls[k] = v
    end
  end
  cls.__index = cls
  cls.super = self
  setmetatable(cls, self)
  return cls
end

function Object:__call(...)
  local obj = setmetatable({}, self)
  obj:new(...)
  return obj
end

-- /end classic



--- @class Item : Object
local Item = Object:extend()

function Item:new(id, category)
	self.id = id
	self.category = category
	self.count = 0
end

function Item:Print()
	print("Item", self.id, self.category, self.count)
end

--- @param item Item
function FlagDebugItem(item)
	item:Print()  -- with the annotation, this completes. Yay!
	item.is_cheater = true  -- Warning :( "Fields cannot be injected into the reference of `Item` for `is_cheater`."
end

If I configure luals to add inject-field to the Lua.diagnostics.disable list, then the warning goes away. But the warning doesn't come back when I make Item an exact class with this annotation: --- @class (exact) Item : Object

Workaround: Add a fake field of any type:

--- @class Item : Object
--- @field [any] any
local Item = Object:extend()

Additional Notes

No response

Log File

[WARN][2026-02-23 11:41:41] ...lsp/handlers.lua:564	"More than 100000 files have been scanned. The current scanned directory is `D:\\code\\project\\br`. Please see the [FAQ](https://luals.github.io/wiki/faq/#how-can-i-improve-startup-speeds) to see how you can include fewer files. It is also possible that your [configuration is incorrect](https://luals.github.io/wiki/faq#why-is-the-server-scanning-the-wrong-folder)."
[WARN][2026-02-23 14:32:31] .../lua/vim/lsp.lua:52	"method textDocument/typeDefinition is not supported by any of the servers registered for the current buffer"
[START][2026-02-23 15:29:24] LSP logging initiated
[ERROR][2026-02-23 15:29:24] ...m/lsp/client.lua:620	"LSP[lua_ls]"	"Cannot find request with id 125 whilst attempting to cancel"

Activity

  1. tomlau10 commented on Feb 24, 2026

    @tomlau10
    Contributor

    Although I am not maintainer (just a user/contributor), I don't think this would work based on my understanding of how @class works in this language server 😕.

    my understanding

    The inject-field warning is saying that the server cannot allow some code to just modify the fields of a class, as otherwise it cannot do the undefined-field check properly:

    • it must have a way for user to define valid fields
    • and now it is done by @field or actual field/method injection on a variable marked by @class
    ---@class A
    ---@field a string # allowed
    local A = {}
    
    A.x = 1 -- allowed
    function A.y() end -- allowed
    function A:z() -- allowed
        self.field_defined_inside_method = 1 -- allowed, auto injected as `self` is treated as `@class A`
    end
    
    ---@type A
    local a = {}
    a.nope = 2 --> inject-field error, because `local a` is NOT marked by `@class`
    -- its more like an instance of class A only
    print(a.not_define) --> undefined field error, you are accessing a field of class A not registered by the server
    • now think it in another way, if all field injections are allowed, then there is no way to detect a undefined field
      => because ... um all field injections are just allowed 😅
    • as for the (exact) class, it simply limits the class definition to only @field / method definition, but disallow injecting fields inside methods
    ---@class (exact) A
    ---@field a string # allowed
    local A = {}
    
    A.x = 1 -- error
    function A.y() end -- error
    function A:z() -- allowed
        self.field_defined_inside_method = 1 -- error, same case as `A.x = 1` disallowed
    end
    
    ---@type A
    local a = {}
    a.nope = 2 --> error
    print(a.not_define) --> undefined field error

    in reply to the snippet in Reproduction steps

    The problematic code lies in the last part:

    --- @param item Item
    function FlagDebugItem(item)
    	item:Print()  -- with the annotation, this completes. Yay!
    	item.is_cheater = true  -- Warning :( "Fields cannot be injected into the reference of `Item` for `is_cheater`."
    end
    • here item is just an instance variable of Item class
    • so the language server disallows you to inject a field to it
    • even if you "silence" this error, the server actually doesn't inject the is_cheater field to Item class
      => such that you will not see is_cheater in auto complete for another Item object
    -- add the following after your snippet
    ---@type Item
    local a
    a.is_ --> no auto complete for `is_cheater`
    • to property let the server inject is_cheater field, you need to mark the variable with @class
    --- @param item Item
    function FlagDebugItem(item)
    	---@class Item
    	item = item -- now `item` is marked with `@class`, it is treated as a "class" variable and allowed for injection
    	item:Print()  -- with the annotation, this completes. Yay!
    	item.is_cheater = true  -- now this is properly injected
    end
    
    ---@type Item
    local a
    a.is_ --> now you can see `is_cheater` as completion
    • i know this ---@class Item; item = item line is a bit dumb 🤦‍♂️
    • but thats the workaround that I used 😂 to let the server properly inject a field in these situation

    I understand that people may not want to add (exact) all over their codebase

    As explained from above, adding (exact) is totally a different thing 🙅‍♂️
    Because it will disallow fields that are defined just inside method, like in you case:

    function Item:new(id, category)
    	self.id = id
    	self.category = category
    	self.count = 0
    end

    => if you add (exact) then these fields will not be injected
    => you will then have to write them using @field before you can use these fields
    => which loses all the benefits of auto injection IMO

  2. idbrii commented on Mar 7, 2026

    @idbrii
    Author

    The inject-field warning is saying that the server cannot allow some code to just modify the fields of a class, as otherwise it cannot do the undefined-field check properly

    That seems like an understanding based on how it behaves and not the code? If it knows when fields are injected into a class (because it warns), then it could add those fields to the class definition and have a list of fields that can be accessed.

    as for the (exact) class, it simply limits the class definition to only @field / method definition, but disallow injecting fields inside methods

    If that's true, the docs should make that clearer. (The definition sounds like definition of the Lua object, not the annotation.) Regardless, I think we should preserve the ability to warn about an exact class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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