1

When writing Lua code, I felt that the built-in methods for string were a bit lacking. So I added this function to the string class.

---@param self string
function string:firstToUpper() -- setting undefined field 'firstToUpper' of global 'string'string
    return (self:gsub("^%l", string.upper))
end

However, when running luacheck, it reported warning: (W142) setting undefined field firstToUpper of global string. I don't want to add an ignore rule in my .luacheckrc file. What changes should I make to my code to prevent this error?

I have try to write in this way

local mt = getmetatable("")
mt.__index.firstToUpper = function(self)
    return (self:gsub("^%l", string.upper))
end

it does not report this warning, but I have not seen this kind of writing before, my question is:

is "" a table when handle it in Lua interpreter?

and how can I improve my code

1 Answers1

1

is "" a table when handle it in Lua interpreter?

No, it's a string, just like you would expect. Every value in Lua can have a metatable. This includes strings, hence why getmetatable() will work on them. Types other than tables and full userdata have one metatable per type. This also includes strings, that's why you can modify metatable of "" and it propagates to all strings.

See 2.4 - Metatables and Metamethods for more detailed explanation.

and how can I improve my code

Do you need to? Linters are just tools. You don't need to listen to it. Of course, modifying built-in modules is usually considered bad practice in any language, but does it matter in your case?

If you want to listen to it, you could create another module that "inherits" from built-in string module and then use debug.setmetatable() to modify metatable of strings:

local ext = {
    firstToUpper = function (str)
        return (str:gsub("^%l", string.upper))
    end,
}
setmetatable(ext, {__index=string})
debug.setmetatable("", {__index=ext})
print(("hello"):firstToUpper())  --> Hello

This way you end up using debug module and you still modify part of built-in state. Is it any better in your case? If not you can use ext.firstToUpper("hello") but this way you lose convenience.

Alternatively, you can define read_globals in configuration file:

read_globals = {
    string = {
        fields = {
            firstToUpper = {
                read_only=false,
            },
        },
    },
}

But at this point I'd consider it being a very targeted ignore which you are trying to avoid. Remember that this would disable detection of any unintentional changes to firstToUpper (or anything else defined similarly).

For me, currently, the best option would be to modify and define all needed globals in a file(s) that wouldn't be checked or would be checked with loose rules and then define strict luacheck configuration file using globals and read_globals for all other files that would use the modified global configuration.

Aki
  • 2,818
  • 1
  • 15
  • 23
  • The proper way would be to add `globals = {string = {fields = {"firstToUpper"}}}` to your `.luacheckrc` to declare that you're not "accidentally" tampering with global variables here; alternatively, an ignore rule in the code could be used. – Luatic Apr 20 '23 at 15:21
  • @Luatic Or that, but in the end we're still modifying built-in module. Actually let me check the intent of that check after work, maybe I'm just bringing C++ things in here. – Aki Apr 20 '23 at 15:30
  • @Luatic I disagree that it's a proper way. It is not defined in documentation as such and it breaks read-only attribute of `string`. If you try to tweak that part it reports the change of newly introduced function: https://pastebin.com/fq685Pqr, but you can do it via `read_globals`, see updated answer. – Aki Apr 20 '23 at 16:59
  • You're correct, my proposed solution is overly permissive. Your stricter one is better; if I were to tamper with Lua's builtin globals, I'd probably use it. Either one is better than abusing the debug API to trick luacheck, though. – Luatic Apr 21 '23 at 20:54