Skip to content

Commit a3f2eea

Browse files
ChouUnclaude
andauthored
fix debug.traceback failing on anonymous function (#1665) (#1666)
* fix `debug.traceback` failing on anonymous function (#1665) * test: add regression test for anonymous function traceback mapping (#1665) Nested IIFEs produce <file.lua:N> 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 <noreply@anthropic.com> * test: address review feedback for anonymous function traceback test (#1665) Use distinct sourcemap values and explicit <main.ts:N> assertions per reviewer request. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: extract setLuaHeader string to variable for prettier Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9bdb41d commit a3f2eea

File tree

2 files changed

+56
-1
lines changed

2 files changed

+56
-1
lines changed

src/lualib/SourceMapTraceBack.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ export function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap
4848
return `${file}:${line}`;
4949
};
5050

51-
let [result] = string.gsub(trace, "(%S+)%.lua:(%d+)", (file, line) =>
51+
// Rewrite stack trace frames from "{PATH}.lua:{LINE}" to "{PATH}.ts:{ORIGINAL_LINE}".
52+
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1665
53+
// Avoid matching anonymous function stack entries like `in function <...>`
54+
// by excluding `<` before the file path.
55+
// TODO: This will still fail for paths containing spaces.
56+
let [result] = string.gsub(trace, "([^%s<]+)%.lua:(%d+)", (file, line) =>
5257
replacer(`${file}.lua`, `${file}.ts`, line)
5358
);
5459

test/unit/error.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,56 @@ test("still works without debug module", () => {
345345
});
346346
});
347347

348+
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1665
349+
test("sourceMapTraceback maps anonymous function locations in .lua files (#1665)", () => {
350+
// Nested IIFEs produce <file.lua:N> anonymous function notation in traceback.
351+
// Old pattern (%S+)%.lua:(%d+) captures "<main" from "<main.lua:4>",
352+
// failing the sourcemap lookup. Fix: ([^%s<]+) excludes "<".
353+
354+
// mapping is copied from the emitted Lua, not invented.
355+
const mapping = `{["5"] = 1,["6"] = 2,["7"] = 3,["8"] = 4,["9"] = 3,["10"] = 2,["11"] = 1}`;
356+
357+
// Test harness executes via luaL_dostring (chunk names are [string "..."]), so we mock a file-based traceback.
358+
const fakeTraceback = [
359+
"stack traceback:",
360+
"\tmain.lua:8: in function <main.lua:7>",
361+
"\tmain.lua:7: in function <main.lua:6>",
362+
"\t[C]: in ?",
363+
].join("\n");
364+
365+
// Inject sourcemap for "main.lua" and mock debug.traceback to return file-based frames.
366+
const header = `
367+
__TS__sourcemap = { ["main.lua"] = ${mapping} };
368+
local __real_tb = debug.traceback
369+
debug.traceback = function() return ${JSON.stringify(fakeTraceback)} end
370+
`;
371+
372+
const builder = util.testFunction`
373+
return (() => {
374+
return (() => {
375+
return (debug.traceback as (this: void) => string)();
376+
})();
377+
})();
378+
`
379+
.setLuaHeader(header)
380+
.setOptions({ sourceMapTraceback: true });
381+
382+
const lua = builder.getMainLuaCodeChunk();
383+
// Sanity check: emitted code registers the same mapping literal we inject above.
384+
expect(lua).toContain(`__TS__SourceMapTraceBack(debug.getinfo(1).short_src, ${mapping});`);
385+
386+
const result = builder.getLuaExecutionResult();
387+
expect(result).toEqual(expect.any(String));
388+
// Both `main.lua:N` and `<main.lua:N>` frames should be rewritten using the sourcemap.
389+
expect(result).not.toContain("main.lua");
390+
// Regular line references
391+
expect(result).toContain("\tmain.ts:4:");
392+
expect(result).toContain("\tmain.ts:3:");
393+
// Anonymous function references must keep <> format
394+
expect(result).toContain("<main.ts:3>");
395+
expect(result).toContain("<main.ts:2>");
396+
});
397+
348398
util.testEachVersion(
349399
"error stacktrace omits constructor and __TS_New",
350400
() => util.testFunction`

0 commit comments

Comments
 (0)