Jguer/yay · error
expected table, got
Error message
expected table, got %s
What it means
After confirming the slice element is a struct, assignStructSlice requires the Lua value itself to be an *lua.LTable, since entries are read as name-keyed sub-tables. Any other Lua value (string, number, nil) yields 'expected table, got <ltype>', wrapped by the caller as yay.opt.<key>: ...
Solutions
- Assign a Lua table keyed by entry name: yay.opt.ignored = { foo = {} }
- Give each entry sub-table the fields of the struct: yay.opt.ignored = { foo = { reason = "x" } }
- Clear the list by assigning an empty table instead of nil: yay.opt.ignored = {}
Example fix
// before (init.lua)
yay.opt.ignored = "linux-headers"
// after
yay.opt.ignored = { ["linux-headers"] = {} } Defensive patterns
Strategy: validation
Validate before calling
-- init.lua local v = yay.opt.ignored assert(type(v) == "table", "yay.opt.ignored must be a table keyed by name")
Try / catch
if err != nil && strings.Contains(err.Error(), "expected table") {
fixOptionToTableInInitLua()
} Prevention
- Always assign a table to struct-slice options, even to clear it ({} )
- Never assign nil or scalars to list options
- Document each option's expected Lua shape next to its declaration
When it happens
Trigger: An init.lua option bound to a []Struct field is assigned a scalar instead of a table, e.g. yay.opt.ignored = "foo" or yay.opt.ignored = nil.
Common situations: Attempting to assign a single entry without wrapping it in a table; accidentally assigning nil to reset a list.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- yay.opt.
- expected string, got
- expected boolean, got
- expected number, got
- entry keys must be strings, got
AI-assisted analysis of Jguer/yay@328f4b4939 (2026-09-07).
Data as JSON: /api/errors/18510099fbe4fe81.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/settings/lua/lua.go:206
}
// assignStructSlice fills a []Struct field from a Lua table keyed by name, e.g.
//
// { ["core"] = { url = "..." }, ["extra"] = { url = "..." } }
//
// Each entry becomes one struct: the table key populates the element's
// lua:"name" field and the sub-table populates the remaining fields. Entries
// are sorted by name so the resulting slice is deterministic despite Lua's
// unordered table iteration.
func assignStructSlice(field reflect.Value, val lua.LValue) error {
elemType := field.Type().Elem()
if elemType.Kind() != reflect.Struct {
return fmt.Errorf("unsupported slice element kind %s", elemType.Kind())
}
tbl, ok := val.(*lua.LTable)
if !ok {
return fmt.Errorf("expected table, got %s", val.Type())
}
// The name comes from the table key, so a "name" key inside the entry table
// would silently override it and let two entries share one name. Drop it
// from the assignable set so it is reported as an unknown key instead.
fieldIndex := luaFieldIndex(elemType)
nameIdx, hasName := fieldIndex["name"]
delete(fieldIndex, "name")
type namedElem struct {
name string
elem reflect.Value
}
var (
entries []namedElem
firstErr error
)View on GitHub (pinned to 328f4b4939)