From 4bc32b7b88dc3674216485a7b17a11e48bf05a21 Mon Sep 17 00:00:00 2001 From: ChouUn Date: Wed, 29 Oct 2025 07:00:23 +0800 Subject: [PATCH 1/4] fix `debug.traceback` failing on anonymous function (#1665) --- src/lualib/SourceMapTraceBack.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts index 47c2c8739..777c220c1 100644 --- a/src/lualib/SourceMapTraceBack.ts +++ b/src/lualib/SourceMapTraceBack.ts @@ -49,7 +49,12 @@ export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap return `${file}:${line}`; }; - let [result] = string.gsub(trace, "(%S+)%.lua:(%d+)", (file, line) => + // Rewrite stack trace frames from "{PATH}.lua:{LINE}" to "{PATH}.ts:{ORIGINAL_LINE}". + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1665 + // Avoid matching anonymous function stack entries like `in function <...>` + // by excluding `<` before the file path. + // TODO: This will still fail for paths containing spaces. + let [result] = string.gsub(trace, "([^%s<]+)%.lua:(%d+)", (file, line) => replacer(`${file}.lua`, `${file}.ts`, line) ); From b45f93ddb58bbc1cf6d2efa92801dbefaedb820e Mon Sep 17 00:00:00 2001 From: ChouUn Date: Sat, 28 Feb 2026 01:59:02 +0800 Subject: [PATCH 2/4] test: add regression test for anonymous function traceback mapping (#1665) Nested IIFEs produce notation in debug.traceback(). The old pattern (%S+) captured the "<" prefix, causing sourcemap lookup to fail for anonymous function definition locations. Co-Authored-By: Claude Opus 4.6 --- test/unit/error.spec.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 05399d492..37230ff50 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -345,6 +345,47 @@ test("still works without debug module", () => { }); }); +// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1665 +test("sourceMapTraceback maps anonymous function locations in .lua files (#1665)", () => { + // Nested IIFEs produce anonymous function notation in traceback. + // Old pattern (%S+)%.lua:(%d+) captures "", + // failing the sourcemap lookup. Fix: ([^%s<]+) excludes "<". + // + // Compiled Lua for the nested IIFEs below: + // line 3: (function() + // line 4: (function() + // line 5: print(debug.traceback()) + // line 6: end)(nil) + // line 7: end)(nil) + const fakeTraceback = [ + "stack traceback:", + "\tmain.lua:5: in function ", + "\tmain.lua:6: in function ", + "\t[C]: in ?", + ].join("\n"); + + const result = util.testFunction` + return (() => { + return (() => { + return debug.traceback(); + })(); + })(); + ` + .setLuaHeader( + `__TS__sourcemap = { ["main.lua"] = {["3"] = 7, ["4"] = 8, ["5"] = 9, ["6"] = 8} }\n` + + `local __real_tb = debug.traceback\n` + + `debug.traceback = function() return ${JSON.stringify(fakeTraceback)} end` + ) + .setOptions({ sourceMapTraceback: true }) + .getLuaExecutionResult(); + + // Regular line references should be mapped + expect(result).toContain("main.ts:9"); + expect(result).toContain("main.ts:8"); + // Anonymous function definitions and should also be mapped + expect(result).not.toContain("main.lua"); +}); + util.testEachVersion( "error stacktrace omits constructor and __TS_New", () => util.testFunction` From d61f863c0b9217ae501bfba291764c1c40c64a4d Mon Sep 17 00:00:00 2001 From: ChouUn Date: Sun, 1 Mar 2026 06:41:40 +0800 Subject: [PATCH 3/4] test: address review feedback for anonymous function traceback test (#1665) Use distinct sourcemap values and explicit assertions per reviewer request. Co-Authored-By: Claude Opus 4.6 --- test/unit/error.spec.ts | 53 +++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 37230ff50..ad6183111 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -350,40 +350,47 @@ test("sourceMapTraceback maps anonymous function locations in .lua files (#1665) // Nested IIFEs produce anonymous function notation in traceback. // Old pattern (%S+)%.lua:(%d+) captures "", // failing the sourcemap lookup. Fix: ([^%s<]+) excludes "<". - // - // Compiled Lua for the nested IIFEs below: - // line 3: (function() - // line 4: (function() - // line 5: print(debug.traceback()) - // line 6: end)(nil) - // line 7: end)(nil) + + // mapping is copied from the emitted Lua, not invented. + const mapping = `{["5"] = 1,["6"] = 2,["7"] = 3,["8"] = 4,["9"] = 3,["10"] = 2,["11"] = 1}`; + + // Test harness executes via luaL_dostring (chunk names are [string "..."]), so we mock a file-based traceback. const fakeTraceback = [ "stack traceback:", - "\tmain.lua:5: in function ", - "\tmain.lua:6: in function ", + "\tmain.lua:8: in function ", + "\tmain.lua:7: in function ", "\t[C]: in ?", ].join("\n"); - const result = util.testFunction` + const builder = util.testFunction` return (() => { return (() => { - return debug.traceback(); + return (debug.traceback as (this: void) => string)(); })(); })(); ` - .setLuaHeader( - `__TS__sourcemap = { ["main.lua"] = {["3"] = 7, ["4"] = 8, ["5"] = 9, ["6"] = 8} }\n` + - `local __real_tb = debug.traceback\n` + - `debug.traceback = function() return ${JSON.stringify(fakeTraceback)} end` - ) - .setOptions({ sourceMapTraceback: true }) - .getLuaExecutionResult(); - - // Regular line references should be mapped - expect(result).toContain("main.ts:9"); - expect(result).toContain("main.ts:8"); - // Anonymous function definitions and should also be mapped + // Inject sourcemap for "main.lua" and mock debug.traceback to return file-based frames. + .setLuaHeader(` + __TS__sourcemap = { ["main.lua"] = ${mapping} }; + local __real_tb = debug.traceback + debug.traceback = function() return ${JSON.stringify(fakeTraceback)} end + `) + .setOptions({ sourceMapTraceback: true }); + + const lua = builder.getMainLuaCodeChunk(); + // Sanity check: emitted code registers the same mapping literal we inject above. + expect(lua).toContain(`__TS__SourceMapTraceBack(debug.getinfo(1).short_src, ${mapping});`); + + const result = builder.getLuaExecutionResult(); + expect(result).toEqual(expect.any(String)); + // Both `main.lua:N` and `` frames should be rewritten using the sourcemap. expect(result).not.toContain("main.lua"); + // Regular line references + expect(result).toContain("\tmain.ts:4:"); + expect(result).toContain("\tmain.ts:3:"); + // Anonymous function references must keep <> format + expect(result).toContain(""); + expect(result).toContain(""); }); util.testEachVersion( From fdbd0b2e65a246d3b52a799dd51b15229bb9827a Mon Sep 17 00:00:00 2001 From: ChouUn Date: Sun, 1 Mar 2026 06:55:10 +0800 Subject: [PATCH 4/4] style: extract setLuaHeader string to variable for prettier Co-Authored-By: Claude Opus 4.6 --- test/unit/error.spec.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index ad6183111..af69d7e1d 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -362,6 +362,13 @@ test("sourceMapTraceback maps anonymous function locations in .lua files (#1665) "\t[C]: in ?", ].join("\n"); + // Inject sourcemap for "main.lua" and mock debug.traceback to return file-based frames. + const header = ` + __TS__sourcemap = { ["main.lua"] = ${mapping} }; + local __real_tb = debug.traceback + debug.traceback = function() return ${JSON.stringify(fakeTraceback)} end + `; + const builder = util.testFunction` return (() => { return (() => { @@ -369,12 +376,7 @@ test("sourceMapTraceback maps anonymous function locations in .lua files (#1665) })(); })(); ` - // Inject sourcemap for "main.lua" and mock debug.traceback to return file-based frames. - .setLuaHeader(` - __TS__sourcemap = { ["main.lua"] = ${mapping} }; - local __real_tb = debug.traceback - debug.traceback = function() return ${JSON.stringify(fakeTraceback)} end - `) + .setLuaHeader(header) .setOptions({ sourceMapTraceback: true }); const lua = builder.getMainLuaCodeChunk();