Skip to content

Dict / Table optional not typing properly. #3360

Closed

Description

@Safebox36

How are you using the lua-language-server?

Visual Studio Code Extension (sumneko.lua)

Which OS are you using?

Windows

What is the issue affecting?

Type Checking, Annotations

Expected Behaviour

The fields of children in @field properties table<string, PropsClass>? when non-nil should have a tooltip with their associated field typings and comments.

Actual Behaviour

The fields of children in @field properties table<string, PropsClass>? when non-nil do not have a tooltip with their associated field typings and comments. Instead displaying as ambiguous and no highlighting to indicate a warning or error:

Image Image Image

Reproduction steps

See [Actual Behaviour].

Additional Notes

No response

Log File

No response

Activity

  1. carsakiller commented on Feb 23, 2026

    @carsakiller
    Collaborator

    I am unable to replicate. Can you provide your full code snippet and annotations?

    Image

    My best guess is your did variable is not a string and thus does not satisfy table<string, BazaarDenominationsData>, resulting in denominations being nil, as defined in your annotation:

    Image
    My Code
    ---@class BazaarCurrencyProperties
    ---@field denominations table<string, BazaarDenominationsData>?
    ---@field pluralName string?
    
    ---@class BazaarDenominationsData
    ---@field value number
    ---@field properties BazaarDenominationProperties
    
    ---@class BazaarDenominationProperties
    ---@field tradeOnly boolean
    ---@field name string
    
    ---@type BazaarCurrencyProperties
    local bcp
    
    local var = "foo"
    
    local isTradeOnly = bcp.denominations[var].properties.tradeOnly
  2. Safebox36 commented on Feb 23, 2026

    @Safebox36
    Author

    It looks like you have the same scenario I got in your second screenshot, where it returns unknown as the "type".

    The function looks like this if you want to try and replicate it more accurately:

    Spoiler warning
    local interop =
    {
        currencies = {}
    }
    
    --- @class BazaarCurrencyData
    --- @field symbolPattern string The string pattern used to represent the currency using `%d`. For example: "%d gp", "gold worth %d".
    --- @field properties BazaarCurrencyProperties?
    
    --- @class BazaarCurrencyProperties
    --- @field denominations table<string, BazaarDenominationData>? Alternate object IDs for denominations of the currency. Note: If the value is less than the base, it will only be converted to the base upon reaching a whole value.
    --- @field pluralName string? The plural form of the currency's name.
    --- @field soundId string? The sound effect played when the currency is exchanged.
    --- @field tradeOnly boolean? Hide the currency in the merchant's barter menu.
    
    local function filterBarterMenuCallback(e)
        local merchantData = interop.getMerchant(tes3ui.getServiceActor().reference.baseObject.id)
        if (merchantData) then
            for _, cid in ipairs(merchantData.acceptedCurrencyIds) do
                print("id 1: " .. cid)
                if (e.item.id == cid and e.tile.isBartered == false) then
                    e.filter = not interop.getCurrency(cid).properties.tradeOnly
                    return
                else
                    for did, _ in ipairs(interop.getCurrency(cid).properties.denominations) do
                        if (e.item.id == did and e.tile.isBartered == false) then
                            e.filter = not interop.getCurrency(cid).properties.denominations[did].properties.tradeOnly
                            return
                        end
                    end
                end
            end
        else
            for func, merchantData in pairs(interop.complexMerchants) do
                if (func(tes3ui.getServiceActor().reference)) then
                    for _, cid in ipairs(merchantData.acceptedCurrencyIds) do
                        print("id 2: " .. cid)
                        if (e.item.id == cid and e.tile.isBartered == false) then
                            e.filter = not interop.getCurrency(cid).properties.tradeOnly
                            return
                        else
                            for did, _ in ipairs(interop.getCurrency(cid).properties.denominations) do
                                if (e.item.id == did and e.tile.isBartered == false) then
                                    e.filter = not interop.getCurrency(cid).properties.denominations[did].properties.tradeOnly
                                    return
                                end
                            end
                        end
                    end
                    break
                end
            end
        end
    end
  3. tomlau10 commented on Feb 24, 2026

    @tomlau10
    Contributor

    This is exactly what @carsakiller trying to illustrate:

    My best guess is your did variable is not a string and thus does not satisfy table<string, BazaarDenominationsData>, resulting in denominations being nil, as defined in your annotation:

    • your did var is integer type, as it is declared as the 1st iter var in ipairs
    for did, _ in ipairs(interop.getCurrency(cid).properties.denominations) do
    -- ^^^^ `did` is `integer` here
        if (e.item.id == did and e.tile.isBartered == false) then
            e.filter = not interop.getCurrency(cid).properties.denominations[did].properties.tradeOnly
            return
        end
    end
    • this doesn't match your @field denominations table<string, BazaarDenominationData>? which requires a string key
    • denominations[<string>] => BazaarDenominationData
      but denominations[<integer>] => unknown

    Maybe you want to write the annotation as @field denominations table<integer, BazaarDenominationData>?
    or even @field denominations BazaarDenominationData[]? because it is actually an array of BazaarDenominationData ? 🤔

    Furthermore if it is an array, you can access the array element directly as a loop variable:

    for did, denomination in ipairs(interop.getCurrency(cid).properties.denominations) do
        if (e.item.id == did and e.tile.isBartered == false) then
            e.filter = not denomination.properties.tradeOnly
            return
        end
    end
  4. Safebox36 commented on Feb 26, 2026

    @Safebox36
    Author

    Oh I see what I've done now. My mistake.

  5. carsakiller commented on Feb 26, 2026

    @carsakiller
    Collaborator

    Good luck with your project!

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

    feat/LuaCats AnnotationsRelated to Lua Language Server Annotations (LuaCats)feat/type checkRelated to the type checking feature

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions