From fc6d4caa8ea3c28579e28cf2a2e86fa446a32f21 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 Aug 2023 20:18:22 -0400 Subject: [PATCH] Dynamically generate field info in __index Constructing the complete tables when the types were initialized made it impossible to populate the "type" field, because not all types had been added to the global type tables yet. --- library/LuaTypes.cpp | 58 +++++++++++++++++++++++++++++-- test/structures/struct_fields.lua | 12 +++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 0d81886db..4507cb718 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1367,6 +1367,11 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, in static void PushFieldInfoSubTable(lua_State *state, const struct_field_info *field) { + if (!field) { + lua_pushnil(state); + return; + } + lua_newtable(state); // new field info Lua::TableInsert(state, "mode", field->mode); Lua::TableInsert(state, "name", field->name); @@ -1401,6 +1406,51 @@ static void PushFieldInfoSubTable(lua_State *state, const struct_field_info *fie // freeze_table(state); // TODO: make pairs() work } +/** + * Metamethod: __index for struct._fields + * + * upvalue 1: name -> struct_field_info* table + */ +static int meta_fieldinfo_index(lua_State *state) +{ + luaL_checktype(state, -1, LUA_TSTRING); + + lua_gettable(state, lua_upvalueindex(1)); + auto field = static_cast(lua_touserdata(state, -1)); + lua_pop(state, 1); + PushFieldInfoSubTable(state, field); + + return 1; +} + +/** + * Metamethod: iterator for struct._fields + * + * upvalue 1: name -> struct_field_info* table + * upvalue 3: field table (int <-> name) + */ +static int meta_fieldinfo_next(lua_State *state) +{ + if (lua_gettop(state) < 2) lua_pushnil(state); + + int len = lua_rawlen(state, UPVAL_FIELDTABLE); + int idx = cur_iter_index(state, len+1, 2, 0); + if (idx == len) + return 0; + + lua_rawgeti(state, UPVAL_FIELDTABLE, idx+1); + + // modified from meta_struct_next: + // retrieve the struct_field_info* from the table and convert it + lua_dup(state); + lua_gettable(state, lua_upvalueindex(1)); + auto field = static_cast(lua_touserdata(state, -1)); + lua_pop(state, 1); + PushFieldInfoSubTable(state, field); + + return 2; +} + static void AddFieldInfoTable(lua_State *state, int ftable_idx, struct_identity *pstruct) { Lua::StackUnwinder base{state}; @@ -1418,7 +1468,10 @@ static void AddFieldInfoTable(lua_State *state, int ftable_idx, struct_identity int ix_fielditer = lua_gettop(state); IndexFields(state, base, pstruct, IndexFieldsFlags::RAW); - PushStructMethod(state, ix_meta, ix_fielditer, meta_struct_next); + PushStructMethod(state, ix_meta, ix_fielditer, meta_fieldinfo_next); + // change upvalue 1 to the field info table since we don't need the original + lua_pushvalue(state, ix_fieldinfo); + lua_setupvalue(state, -2, 1); SetPairsMethod(state, ix_meta, "__pairs"); // field table (name -> table representation of struct_field_info) @@ -1432,7 +1485,8 @@ static void AddFieldInfoTable(lua_State *state, int ftable_idx, struct_identity // set up metatable for the wrapper // use field table for __index lua_pushstring(state, "__index"); - lua_pushvalue(state, ix_fields); + lua_pushvalue(state, ix_fieldinfo); + lua_pushcclosure(state, meta_fieldinfo_index, 1); lua_settable(state, ix_meta); // use change_error() for __newindex diff --git a/test/structures/struct_fields.lua b/test/structures/struct_fields.lua index 5ad513dcc..f9b3c41fc 100644 --- a/test/structures/struct_fields.lua +++ b/test/structures/struct_fields.lua @@ -60,3 +60,15 @@ function test.readonly() expect.eq(df.coord._fields.x.name, 'x') expect.nil_(df.coord._fields.nonexistent) end + +function test.circular_refs_init() + expect.eq(df.job._fields.list_link.type, df.job_list_link) + expect.eq(df.job_list_link._fields.item.type, df.job) +end + +function test.subclass_match() + for f, parent in pairs(df.viewscreen._fields) do + local child = df.viewscreen_titlest._fields[f] + expect.table_eq(parent, child, f) + end +end