Version: lupa 2.8 (latest release), Python 3.14.2. Reproduces on every backend: lua51, lua52, lua53, lua54, luajit20, luajit21.
When the same Python object is reachable from more than one place in the structure passed to table_from(..., recursive=True), the first occurrence converts correctly and every later occurrence becomes an empty Lua table. No exception is raised, so the data is lost silently.
from lupa.lua54 import LuaRuntime
rt = LuaRuntime()
shared = {"x": 1}
t = rt.table_from({"a": shared, "b": shared}, recursive=True)
count = rt.eval("function(v) local c = 0 for _ in pairs(v) do c = c + 1 end return c end")
print(count(t.a)) # 1
print(count(t.b)) # 0, expected 1
print(rt.eval("rawequal")(t.a, t.b)) # False, expected True
Three references give 1, 0, 0. It also happens when the shared object sits inside lists: {"a": [s], "b": [s]} yields a[1].x == 1 and an empty b[1].
The docstring states that nested structures are converted "handling loops and duplicates via identity de-duplication". Loops do work:
loop = {"x": 1}
loop["self"] = loop
t = rt.table_from(loop, recursive=True)
print(rt.eval("function(t) return rawequal(t, t.self) end")(t)) # True, correct
So the memo is consulted correctly for a self reference, but a sibling duplicate gets a fresh empty table instead of the memoized one.
Expected: t.a and t.b are the same Lua table, which is what identity de-duplication implies.
Impact: silent data loss. A structure built in Python that happens to share a sub-object, which is common when the same row or record is indexed under two keys, arrives in Lua with parts of it empty.
Workaround: recurse in Python and call table_from once per level.
Version: lupa 2.8 (latest release), Python 3.14.2. Reproduces on every backend: lua51, lua52, lua53, lua54, luajit20, luajit21.
When the same Python object is reachable from more than one place in the structure passed to
table_from(..., recursive=True), the first occurrence converts correctly and every later occurrence becomes an empty Lua table. No exception is raised, so the data is lost silently.Three references give 1, 0, 0. It also happens when the shared object sits inside lists:
{"a": [s], "b": [s]}yieldsa[1].x == 1and an emptyb[1].The docstring states that nested structures are converted "handling loops and duplicates via identity de-duplication". Loops do work:
So the memo is consulted correctly for a self reference, but a sibling duplicate gets a fresh empty table instead of the memoized one.
Expected:
t.aandt.bare the same Lua table, which is what identity de-duplication implies.Impact: silent data loss. A structure built in Python that happens to share a sub-object, which is common when the same row or record is indexed under two keys, arrives in Lua with parts of it empty.
Workaround: recurse in Python and call
table_fromonce per level.