Skip to content

feature request: attach a reason parameter to @nodiscard #3173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
thacuber2a03 opened this issue May 4, 2025 · 4 comments
Open

feature request: attach a reason parameter to @nodiscard #3173

thacuber2a03 opened this issue May 4, 2025 · 4 comments

Comments

@thacuber2a03
Copy link

Note

I couldn't find an issue for this. sorry if there's a duplicate.

I propose updating the @nodiscard annotation to add a "reason" as to why not discard the value:

---@nodiscard [<reason>]

this would mirror Rust's #[must_use = "reason"] annotation.

example:

---Creates a new class that inherits from this one.
---@return Class
---@nodiscard This method returns the new class; it's pointless to immediately discard it.
function Object:extend()
  -- ...
end

discarding the output would then display the warning:

* [warning][Lua Diagnostics.][discard-returns]
  The return values of this function cannot be discarded:
  This method returns the new class; it's pointless to immediately discard it.

note the colon at the end of "discarded".
if not given a reason, it would fall-back to the usual message, with "discarded" ending with a dot and no further explanation
(yes, I'm aware that reason in the Object:extend example isn't exactly... professional 😅)

@thacuber2a03
Copy link
Author

thacuber2a03 commented May 4, 2025

I'm up for implementing this feature, it would be cool to try haha
nevermind, I'm not used to the code

@tomlau10
Copy link
Contributor

tomlau10 commented May 5, 2025

I'm up for implementing this feature, it would be cool to try haha

Actually this might be a good first issue 😂

There is an almost identical feature, but for @deprecated [reason].
Basically:

  • the reason is stored in the comment field of a doc.deprecated parser.object
  • and then vm.getDeprecated() is designed to return the binded doc.deprecated object of any parse.object (if there is any)
    local function getDeprecated(value)
    if not value.bindDocs then
    return nil
    end
    if value._deprecated ~= nil then
    return value._deprecated or nil
    end
    for _, doc in ipairs(value.bindDocs) do
    if doc.type == 'doc.deprecated' then
    value._deprecated = doc
    return doc
    elseif doc.type == 'doc.version' then
    local valids = vm.getValidVersions(doc)
    if valids and not valids[config.get(guide.getUri(value), 'Lua.runtime.version')] then
    value._deprecated = doc
    return doc
    end
    end
    end
    if value.type == 'function' then
    local doc = getDeprecated(value.parent)
    if doc then
    value._deprecated = doc
    return doc
    end
    end
    value._deprecated = false
    return nil
    end
    ---@param value parser.object
    ---@param deep boolean?
    ---@return parser.object?
    function vm.getDeprecated(value, deep)
    if deep then
    local defs = vm.getDefs(value)
    if #defs == 0 then
    return nil
    end
    local deprecated
    for _, def in ipairs(defs) do
    if def.type == 'setglobal'
    or def.type == 'setfield'
    or def.type == 'setmethod'
    or def.type == 'setindex'
    or def.type == 'tablefield'
    or def.type == 'tableindex' then
    deprecated = getDeprecated(def)
    if not deprecated then
    return nil
    end
    end
    end
    return deprecated
    else
    return getDeprecated(value)
    end
    end
  • finally this doc.deprecated is used in script/core/diagnostics/deprecated.lua
    if deprecated.comment then
    message = ('%s(%s)'):format(message, util.trim(deprecated.comment.text))
    end

I believe for @nodicard (doc.nodiscard), there is already a comment field parsed as well.
So the only thing left is to refactor the vm.isNoDiscard() => vm.getNoDiscard():

  • when doing memoization, cache the actual doc.nodiscard object into value._nodiscard
  • and then return it directly if it exists, just like the local function getDeprecated() and vm.getDeprecated() in the above
    ---@param value parser.object
    ---@return boolean
    local function isNoDiscard(value)
    if value.type == 'function' then
    if not value.bindDocs then
    return false
    end
    if value._nodiscard ~= nil then
    return value._nodiscard
    end
    for _, doc in ipairs(value.bindDocs) do
    if doc.type == 'doc.nodiscard' then
    value._nodiscard = true
    return true
    end
    end
    value._nodiscard = false
    return false
    end
    return false
    end
    ---@param value parser.object
    ---@param deep boolean?
    ---@return boolean
    function vm.isNoDiscard(value, deep)
    if isNoDiscard(value) then
    return true
    end
    if deep then
    local defs = vm.getDefs(value)
    if #defs == 0 then
    return false
    end
    for _, def in ipairs(defs) do
    if isNoDiscard(def) then
    return true
    end
    end
    end
    return false
    end
  • finally use its comment field (if any) to format the message in the script/core/diagnostics/discard-returns.lua
    if vm.isNoDiscard(source.node, true) then
    callback {
    start = source.start,
    finish = source.finish,
    message = lang.script('DIAG_DISCARD_RETURNS'),

@CppCXY
Copy link
Collaborator

CppCXY commented May 6, 2025

Image
or you can choose the emmylua_ls(dev version support)

@thacuber2a03
Copy link
Author

thacuber2a03 commented May 9, 2025

not sure... some annotations here aren't compatible with EmmyLua, and I don't quite want to have two language servers installed at a time, plus, I'm used to this one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants