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

  1. Assign a Lua table keyed by entry name: yay.opt.ignored = { foo = {} }
  2. Give each entry sub-table the fields of the struct: yay.opt.ignored = { foo = { reason = "x" } }
  3. 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

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


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)