{"record":{"id":"18510099fbe4fe81","repo":"Jguer/yay","slug":"expected-table-got-s","errorCode":null,"errorMessage":"expected table, got %s","messagePattern":"expected table, got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/settings/lua/lua.go","lineNumber":206,"sourceCode":"}\n\n// assignStructSlice fills a []Struct field from a Lua table keyed by name, e.g.\n//\n//\t{ [\"core\"] = { url = \"...\" }, [\"extra\"] = { url = \"...\" } }\n//\n// Each entry becomes one struct: the table key populates the element's\n// lua:\"name\" field and the sub-table populates the remaining fields. Entries\n// are sorted by name so the resulting slice is deterministic despite Lua's\n// unordered table iteration.\nfunc assignStructSlice(field reflect.Value, val lua.LValue) error {\n\telemType := field.Type().Elem()\n\tif elemType.Kind() != reflect.Struct {\n\t\treturn fmt.Errorf(\"unsupported slice element kind %s\", elemType.Kind())\n\t}\n\n\ttbl, ok := val.(*lua.LTable)\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected table, got %s\", val.Type())\n\t}\n\n\t// The name comes from the table key, so a \"name\" key inside the entry table\n\t// would silently override it and let two entries share one name. Drop it\n\t// from the assignable set so it is reported as an unknown key instead.\n\tfieldIndex := luaFieldIndex(elemType)\n\tnameIdx, hasName := fieldIndex[\"name\"]\n\tdelete(fieldIndex, \"name\")\n\n\ttype namedElem struct {\n\t\tname string\n\t\telem reflect.Value\n\t}\n\n\tvar (\n\t\tentries  []namedElem\n\t\tfirstErr error\n\t)","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/Jguer/yay/blob/328f4b4939fb35f38c2f7c7ce3b8638a839bafa5/pkg/settings/lua/lua.go#L188-L224","documentation":"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>: ...","triggerScenarios":"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.","commonSituations":"Attempting to assign a single entry without wrapping it in a table; accidentally assigning nil to reset a list.","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 = {}"],"exampleFix":"// before (init.lua)\nyay.opt.ignored = \"linux-headers\"\n// after\nyay.opt.ignored = { [\"linux-headers\"] = {} }","handlingStrategy":"validation","validationCode":"-- init.lua\nlocal v = yay.opt.ignored\nassert(type(v) == \"table\", \"yay.opt.ignored must be a table keyed by name\")","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"expected table\") {\n    fixOptionToTableInInitLua()\n}","preventionTips":["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"],"tags":["lua","type-mismatch","configuration"],"backgroundTag":"type-mismatch","analyzedSha":"328f4b4939fb35f38c2f7c7ce3b8638a839bafa5","analyzedAt":"2026-09-07T16:45:12.608Z","contentChangedAt":"2026-09-07T16:45:12.608Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}