Skip to content

Fields added after a forward table assignment are not inferred #1272

Description

@oriori1703

Description

A table assigned after a forward local declaration does not gain fields assigned later. This produces undefined-field at the assignment and causes exported module fields to be considered nullable.

The equivalent code with declaration and table initialization in one statement works without diagnostics.

This affects LuaSnip: luasnip/config.lua forward-declares c, assigns its table later, and then aliases c.setup = c.set_config. As a result, normal user configuration gets function require('luasnip').setup may be nil [need-check-nil].

Minimal reproduction

.emmyrc.json:

{}

split/config.lua:

local config

config = {
  set_config = function(user_config)
    return user_config or {}
  end,
}

config.setup = config.set_config

return config

split/api.lua:

local API = {}

API.setup = require('split.config').setup

return API

client.lua:

require('split.api').setup {}

Run emmylua_check ..

Actual behavior

split/config.lua:9:8: Undefined field `setup`. [undefined-field]
split/api.lua:3:37: Undefined field `setup`. [undefined-field]
client.lua:1:1: function require('split.api').setup may be nil [need-check-nil]

Expected behavior

No diagnostics. config is definitely assigned the table before config.setup is added, and setup is definitely assigned before the module is returned.

Changing only the first two declarations to initialize the local directly makes the diagnostics disappear:

local config = {
  set_config = function(user_config)
    return user_config or {}
  end,
}

Environment

Activity

  1. added theissue type on Oct 3, 2026
  2. oriori1703 commented on Oct 3, 2026

    @oriori1703
    Author

    I opened L3MON4D3/LuaSnip#1454 with a narrow annotation workaround. It explicitly types LuaSnip's forward-declared config table, which removes the false setup may be nil diagnostic in both the real plugin consumer and a minimal test.

    This issue should remain open because the annotation only works around the underlying inference problem.

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions