diff --git a/CHANGELOG.md b/CHANGELOG.md index a246c2a84..45f7e4d86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `@stdlib/stoppable` now imports `@stdlib/ownable` so the programmer does not have to do it separately: PR [#193](https://github.com/tact-lang/tact/pull/193) - Support escape sequences for strings (`\\`, `\"`, `\n`, `\r`, `\t`, `\v`, `\b`, `\f`, `\u{0}` through `\u{FFFFFF}`, `\u0000` through `\uFFFF`, `\x00` through `\xFF`): PR [#192](https://github.com/tact-lang/tact/pull/192) - `newAddress` function now evaluates to a constant value if possible: PR [#237](https://github.com/tact-lang/tact/pull/237) +- The `dump()` and `dumpStack()` functions now print the file path, line number, and column number in addition to the data: PR [#271](https://github.com/tact-lang/tact/pull/271). - `pow` function is now in the standard library, allowing its use at runtime. If constant arguments are used, the result is evaluated at compile-time: PR [#267](https://github.com/tact-lang/tact/pull/267) ### Fixed @@ -37,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow chaining method calls with `!!`, for instance, `map.asCell()!!.hash()` is grammatically correct now: PR [#257](ttps://github.com/tact-lang/tact/pull/257) - Operation precendence for bitwise operators, equality and comparisons now matches common languages, like JavaScript: PR [#265](https://github.com/tact-lang/tact/pull/265) - Incorrect variable scoping in `repeat`, `while` and `until` loops: PR [#269](https://github.com/tact-lang/tact/pull/269) +- FunC compilation errors when trying to `dump()` such types as `Cell`, `Slice`, `Builder` and `StringBuilder`: PR [#271](https://github.com/tact-lang/tact/pull/271) ## [1.2.0] - 2024-02-29 diff --git a/src/abi/global.ts b/src/abi/global.ts index 40deddef4..ea81ba497 100644 --- a/src/abi/global.ts +++ b/src/abi/global.ts @@ -7,6 +7,8 @@ import { resolveConstantValue } from "../types/resolveConstantValue"; import { getErrorId } from "../types/resolveErrors"; import { AbiFunction } from "./AbiFunction"; import { sha256_sync } from "@ton/crypto"; +import path from "path"; +import { cwd } from "process"; export const GlobalFunctions: Map = new Map([ [ @@ -174,32 +176,40 @@ export const GlobalFunctions: Map = new Map([ return `${ctx.used("__tact_nop")}()`; } const arg = args[0]; + + const filePath = ref.file + ? path.relative(cwd(), ref.file!) + : "unknown"; + const lineCol = ref.interval.getLineAndColumn(); + const debugPrint = `[DEBUG] File ${filePath}:${lineCol.lineNum}:${lineCol.colNum}`; + if (arg.kind === "map") { const exp = writeExpression(resolved[0], ctx); - return `${ctx.used(`__tact_debug`)}(${exp})`; + return `${ctx.used(`__tact_debug`)}(${exp}, "${debugPrint}")`; } else if (arg.kind === "null") { - return `${ctx.used(`__tact_debug_str`)}("null")`; + return `${ctx.used(`__tact_debug_str`)}("null", "${debugPrint}")`; } else if (arg.kind === "void") { - return `${ctx.used(`__tact_debug_str`)}("void")`; + return `${ctx.used(`__tact_debug_str`)}("void", "${debugPrint}")`; } else if (arg.kind === "ref") { - if ( - arg.name === "Int" || - arg.name === "Builder" || - arg.name === "Slice" || - arg.name === "Cell" || - arg.name === "StringBuilder" - ) { + if (arg.name === "Int") { const exp = writeExpression(resolved[0], ctx); - return `${ctx.used(`__tact_debug_str`)}(${ctx.used(`__tact_int_to_string`)}(${exp}))`; + return `${ctx.used(`__tact_debug_str`)}(${ctx.used(`__tact_int_to_string`)}(${exp}), "${debugPrint}")`; } else if (arg.name === "Bool") { const exp = writeExpression(resolved[0], ctx); - return `${ctx.used(`__tact_debug_bool`)}(${exp})`; + return `${ctx.used(`__tact_debug_bool`)}(${exp}, "${debugPrint}")`; } else if (arg.name === "String") { const exp = writeExpression(resolved[0], ctx); - return `${ctx.used(`__tact_debug_str`)}(${exp})`; + return `${ctx.used(`__tact_debug_str`)}(${exp}, "${debugPrint}")`; } else if (arg.name === "Address") { const exp = writeExpression(resolved[0], ctx); - return `${ctx.used(`__tact_debug_address`)}(${exp})`; + return `${ctx.used(`__tact_debug_address`)}(${exp}, "${debugPrint}")`; + } else if ( + arg.name === "Builder" || + arg.name === "Slice" || + arg.name === "Cell" + ) { + const exp = writeExpression(resolved[0], ctx); + return `${ctx.used(`__tact_debug`)}(${exp}, "${debugPrint}")`; } throwError( "dump() not supported for type: " + arg.name, @@ -211,6 +221,29 @@ export const GlobalFunctions: Map = new Map([ }, }, ], + [ + "dumpStack", + { + name: "dumpStack", + resolve: (_ctx, args, ref) => { + if (args.length !== 0) { + throwError("dumpStack expects no arguments", ref); + } + return { kind: "void" }; + }, + generate: (ctx, _args, _resolved, ref) => { + if (!enabledDebug(ctx.ctx)) { + return `${ctx.used("__tact_nop")}()`; + } + const filePath = ref.file + ? path.relative(cwd(), ref.file!) + : "unknown"; + const lineCol = ref.interval.getLineAndColumn(); + const debugPrint = `[DEBUG] File ${filePath}:${lineCol.lineNum}:${lineCol.colNum}`; + return `${ctx.used(`__tact_debug_stack`)}("${debugPrint}")`; + }, + }, + ], [ "emptyMap", { diff --git a/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap b/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap index 96fdc8c76..a736d34a2 100644 --- a/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap +++ b/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap @@ -273,7 +273,7 @@ return __tact_create_address(chain, hash);", }, { "code": { - "code": "asm "s0 DUMP" "DROP"", + "code": "asm "STRDUMP" "DROP" "s0 DUMP" "DROP"", "kind": "asm", }, "comment": null, @@ -283,11 +283,11 @@ return __tact_create_address(chain, hash);", "impure", }, "name": "__tact_debug", - "signature": "forall X -> () __tact_debug(X value)", + "signature": "forall X -> () __tact_debug(X value, slice debug_print)", }, { "code": { - "code": "asm "STRDUMP" "DROP"", + "code": "asm "STRDUMP" "DROP" "STRDUMP" "DROP"", "kind": "asm", }, "comment": null, @@ -297,14 +297,14 @@ return __tact_create_address(chain, hash);", "impure", }, "name": "__tact_debug_str", - "signature": "() __tact_debug_str(slice value)", + "signature": "() __tact_debug_str(slice value, slice debug_print)", }, { "code": { "code": "if (value) { - __tact_debug_str("true"); + __tact_debug_str("true", debug_print); } else { - __tact_debug_str("false"); + __tact_debug_str("false", debug_print); }", "kind": "generic", }, @@ -317,7 +317,7 @@ return __tact_create_address(chain, hash);", "impure", }, "name": "__tact_debug_bool", - "signature": "() __tact_debug_bool(int value)", + "signature": "() __tact_debug_bool(int value, slice debug_print)", }, { "code": { @@ -431,7 +431,7 @@ return __tact_base64_encode(user_friendly_address_with_checksum);", }, { "code": { - "code": "__tact_debug_str(__tact_address_to_userfriendly(address));", + "code": "__tact_debug_str(__tact_address_to_userfriendly(address), debug_print);", "kind": "generic", }, "comment": null, @@ -444,7 +444,21 @@ return __tact_base64_encode(user_friendly_address_with_checksum);", "impure", }, "name": "__tact_debug_address", - "signature": "() __tact_debug_address(slice address)", + "signature": "() __tact_debug_address(slice address, slice debug_print)", + }, + { + "code": { + "code": "asm "STRDUMP" "DROP" "DUMPSTK"", + "kind": "asm", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug_stack", + "signature": "() __tact_debug_stack(slice debug_print)", }, { "code": { @@ -4299,7 +4313,7 @@ return __tact_create_address(chain, hash);", }, { "code": { - "code": "asm "s0 DUMP" "DROP"", + "code": "asm "STRDUMP" "DROP" "s0 DUMP" "DROP"", "kind": "asm", }, "comment": null, @@ -4309,11 +4323,11 @@ return __tact_create_address(chain, hash);", "impure", }, "name": "__tact_debug", - "signature": "forall X -> () __tact_debug(X value)", + "signature": "forall X -> () __tact_debug(X value, slice debug_print)", }, { "code": { - "code": "asm "STRDUMP" "DROP"", + "code": "asm "STRDUMP" "DROP" "STRDUMP" "DROP"", "kind": "asm", }, "comment": null, @@ -4323,14 +4337,14 @@ return __tact_create_address(chain, hash);", "impure", }, "name": "__tact_debug_str", - "signature": "() __tact_debug_str(slice value)", + "signature": "() __tact_debug_str(slice value, slice debug_print)", }, { "code": { "code": "if (value) { - __tact_debug_str("true"); + __tact_debug_str("true", debug_print); } else { - __tact_debug_str("false"); + __tact_debug_str("false", debug_print); }", "kind": "generic", }, @@ -4343,7 +4357,7 @@ return __tact_create_address(chain, hash);", "impure", }, "name": "__tact_debug_bool", - "signature": "() __tact_debug_bool(int value)", + "signature": "() __tact_debug_bool(int value, slice debug_print)", }, { "code": { @@ -4457,7 +4471,7 @@ return __tact_base64_encode(user_friendly_address_with_checksum);", }, { "code": { - "code": "__tact_debug_str(__tact_address_to_userfriendly(address));", + "code": "__tact_debug_str(__tact_address_to_userfriendly(address), debug_print);", "kind": "generic", }, "comment": null, @@ -4470,7 +4484,21 @@ return __tact_base64_encode(user_friendly_address_with_checksum);", "impure", }, "name": "__tact_debug_address", - "signature": "() __tact_debug_address(slice address)", + "signature": "() __tact_debug_address(slice address, slice debug_print)", + }, + { + "code": { + "code": "asm "STRDUMP" "DROP" "DUMPSTK"", + "kind": "asm", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug_stack", + "signature": "() __tact_debug_stack(slice debug_print)", }, { "code": { @@ -8325,7 +8353,7 @@ return __tact_create_address(chain, hash);", }, { "code": { - "code": "asm "s0 DUMP" "DROP"", + "code": "asm "STRDUMP" "DROP" "s0 DUMP" "DROP"", "kind": "asm", }, "comment": null, @@ -8335,11 +8363,11 @@ return __tact_create_address(chain, hash);", "impure", }, "name": "__tact_debug", - "signature": "forall X -> () __tact_debug(X value)", + "signature": "forall X -> () __tact_debug(X value, slice debug_print)", }, { "code": { - "code": "asm "STRDUMP" "DROP"", + "code": "asm "STRDUMP" "DROP" "STRDUMP" "DROP"", "kind": "asm", }, "comment": null, @@ -8349,14 +8377,14 @@ return __tact_create_address(chain, hash);", "impure", }, "name": "__tact_debug_str", - "signature": "() __tact_debug_str(slice value)", + "signature": "() __tact_debug_str(slice value, slice debug_print)", }, { "code": { "code": "if (value) { - __tact_debug_str("true"); + __tact_debug_str("true", debug_print); } else { - __tact_debug_str("false"); + __tact_debug_str("false", debug_print); }", "kind": "generic", }, @@ -8369,7 +8397,7 @@ return __tact_create_address(chain, hash);", "impure", }, "name": "__tact_debug_bool", - "signature": "() __tact_debug_bool(int value)", + "signature": "() __tact_debug_bool(int value, slice debug_print)", }, { "code": { @@ -8483,7 +8511,7 @@ return __tact_base64_encode(user_friendly_address_with_checksum);", }, { "code": { - "code": "__tact_debug_str(__tact_address_to_userfriendly(address));", + "code": "__tact_debug_str(__tact_address_to_userfriendly(address), debug_print);", "kind": "generic", }, "comment": null, @@ -8496,7 +8524,21 @@ return __tact_base64_encode(user_friendly_address_with_checksum);", "impure", }, "name": "__tact_debug_address", - "signature": "() __tact_debug_address(slice address)", + "signature": "() __tact_debug_address(slice address, slice debug_print)", + }, + { + "code": { + "code": "asm "STRDUMP" "DROP" "DUMPSTK"", + "kind": "asm", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug_stack", + "signature": "() __tact_debug_stack(slice debug_print)", }, { "code": { diff --git a/src/generator/writers/writeStdlib.ts b/src/generator/writers/writeStdlib.ts index ba1b618e2..89d04d1bb 100644 --- a/src/generator/writers/writeStdlib.ts +++ b/src/generator/writers/writeStdlib.ts @@ -192,29 +192,31 @@ export function writeStdlib(ctx: WriterContext) { }); ctx.fun("__tact_debug", () => { - ctx.signature(`forall X -> () __tact_debug(X value)`); + ctx.signature( + `forall X -> () __tact_debug(X value, slice debug_print)`, + ); ctx.flag("impure"); ctx.context("stdlib"); - ctx.asm(`asm "s0 DUMP" "DROP"`); + ctx.asm(`asm "STRDUMP" "DROP" "s0 DUMP" "DROP"`); }); ctx.fun("__tact_debug_str", () => { - ctx.signature(`() __tact_debug_str(slice value)`); + ctx.signature(`() __tact_debug_str(slice value, slice debug_print)`); ctx.flag("impure"); ctx.context("stdlib"); - ctx.asm(`asm "STRDUMP" "DROP"`); + ctx.asm(`asm "STRDUMP" "DROP" "STRDUMP" "DROP"`); }); ctx.fun("__tact_debug_bool", () => { - ctx.signature(`() __tact_debug_bool(int value)`); + ctx.signature(`() __tact_debug_bool(int value, slice debug_print)`); ctx.flag("impure"); ctx.context("stdlib"); ctx.body(() => { ctx.write(` if (value) { - ${ctx.used("__tact_debug_str")}("true"); + ${ctx.used("__tact_debug_str")}("true", debug_print); } else { - ${ctx.used("__tact_debug_str")}("false"); + ${ctx.used("__tact_debug_str")}("false", debug_print); } `); }); @@ -314,16 +316,25 @@ export function writeStdlib(ctx: WriterContext) { }); ctx.fun("__tact_debug_address", () => { - ctx.signature(`() __tact_debug_address(slice address)`); + ctx.signature( + `() __tact_debug_address(slice address, slice debug_print)`, + ); ctx.flag("impure"); ctx.context("stdlib"); ctx.body(() => { ctx.write(` - ${ctx.used("__tact_debug_str")}(${ctx.used("__tact_address_to_userfriendly")}(address)); + ${ctx.used("__tact_debug_str")}(${ctx.used("__tact_address_to_userfriendly")}(address), debug_print); `); }); }); + ctx.fun("__tact_debug_stack", () => { + ctx.signature(`() __tact_debug_stack(slice debug_print)`); + ctx.flag("impure"); + ctx.context("stdlib"); + ctx.asm(`asm "STRDUMP" "DROP" "DUMPSTK"`); + }); + ctx.fun("__tact_context_get", () => { ctx.signature(`(int, slice, int, slice) __tact_context_get()`); ctx.flag("inline"); diff --git a/src/imports/stdlib.ts b/src/imports/stdlib.ts index b4ade9759..484d33038 100644 --- a/src/imports/stdlib.ts +++ b/src/imports/stdlib.ts @@ -176,7 +176,7 @@ files['std/crypto.tact'] = files['std/debug.tact'] = 'QG5hbWUodGhyb3cpCm5hdGl2ZSBuYXRpdmVUaHJvdyhjb2RlOiBJbnQpOwoKQG5hbWUodGhyb3dfd2hlbikKbmF0aXZlIG5hdGl2ZVRocm93V2hlbihjb2RlOiBJbnQs' + 'IGNvbmRpdGlvbjogQm9vbCk7CgpAbmFtZSh0aHJvdykKbmF0aXZlIHRocm93KGNvZGU6IEludCk7CgpAbmFtZSh0aHJvd191bmxlc3MpCm5hdGl2ZSBuYXRpdmVUaHJv' + - 'd1VubGVzcyhjb2RlOiBJbnQsIGNvbmRpdGlvbjogQm9vbCk7CgpAbmFtZShkdW1wX3N0YWNrKQpuYXRpdmUgZHVtcFN0YWNrKCk7'; + 'd1VubGVzcyhjb2RlOiBJbnQsIGNvbmRpdGlvbjogQm9vbCk7'; files['std/math.tact'] = 'Ly8gUHJlcGFyZSByYW5kb20KCkBuYW1lKHJhbmRvbWl6ZSkKbmF0aXZlIG5hdGl2ZVJhbmRvbWl6ZSh4OiBJbnQpOwoKQG5hbWUocmFuZG9taXplX2x0KQpuYXRpdmUg' + 'bmF0aXZlUmFuZG9taXplTHQoKTsKCkBuYW1lKF9fdGFjdF9wcmVwYXJlX3JhbmRvbSkKbmF0aXZlIG5hdGl2ZVByZXBhcmVSYW5kb20oKTsKCi8vIFJhbmRvbQoKQG5h' + diff --git a/src/test/__snapshots__/bugs.spec.ts.snap b/src/test/__snapshots__/bugs.spec.ts.snap index e9e1f7db2..6c3fdb485 100644 --- a/src/test/__snapshots__/bugs.spec.ts.snap +++ b/src/test/__snapshots__/bugs.spec.ts.snap @@ -12,19 +12,19 @@ exports[`bugs should deploy contract correctly 1`] = ` "$type": "received", "message": { "body": { - "cell": "x{178D45190000000000000000502540BE400800A8651ACEAB81220DBBF8AA566E0CBE7FC77A30EADF32B457F1CF4DE9575E5A210016E3A425A4E75B646191AC9A34FE5D050BD101A5C490F87D01C66D885D09BC1082_}", + "cell": "x{178D45190000000000000000502540BE40080129FFBF06CE484F4D3B323979CEDFD65654BF66C54070193CB1B2A1F8F06E245F0016E3A425A4E75B646191AC9A34FE5D050BD101A5C490F87D01C66D885D09BC1082_}", "type": "cell", }, "bounce": false, - "from": "kQBUMo1nVcCRBt38VSs3Bl8_470YdW-ZWiv456b0q68tEE5x", - "to": "kQCzGu8bropdv4KCT6jpaDeRXU1coEBo8pjeCjMAt-xYDAXm", + "from": "kQCU_9-DZyQnpp2ZHLznb-srKl-zYqA4DJ5Y2VD8eDcSLynd", + "to": "kQAHONfaUkZLyQhTV1s8zW9FcSeUrB7MQEkpOf3J7hsxPHcc", "type": "internal", - "value": "9.95885", + "value": "9.959886", }, }, { "$type": "processed", - "gasUsed": 16070n, + "gasUsed": 12277n, }, { "$type": "sent", @@ -38,10 +38,10 @@ exports[`bugs should deploy contract correctly 1`] = ` }, }, "bounce": false, - "from": "kQCzGu8bropdv4KCT6jpaDeRXU1coEBo8pjeCjMAt-xYDAXm", + "from": "kQAHONfaUkZLyQhTV1s8zW9FcSeUrB7MQEkpOf3J7hsxPHcc", "to": "@treasure(treasure)", "type": "internal", - "value": "9.914852826", + "value": "9.916924834", }, ], }, diff --git a/src/test/bugs/issue43.tact b/src/test/bugs/issue43.tact index 278f1248c..02b3f7b46 100644 --- a/src/test/bugs/issue43.tact +++ b/src/test/bugs/issue43.tact @@ -260,7 +260,6 @@ contract JettonDefaultWallet { let msgValue: Int = self.msgValue(ctx.value); // Get value for gas let fwdFee: Int = ctx.readForwardFee(); - dump(fwdFee); msgValue = msgValue - msg.forward_ton_amount - fwdFee; // msgValue = msgValue - msg.forward_ton_amount - min(fwdFee, ton("0.01")); diff --git a/src/test/feature-debug.spec.ts b/src/test/feature-debug.spec.ts index 1d85243c9..aff048c68 100644 --- a/src/test/feature-debug.spec.ts +++ b/src/test/feature-debug.spec.ts @@ -2,6 +2,7 @@ import { Address, toNano } from "@ton/core"; import { ContractSystem } from "@tact-lang/emulator"; import { __DANGER_resetNodeId } from "../grammar/ast"; import { Debug } from "./features/output/debug_Debug"; +import path from "path"; describe("feature-debug", () => { beforeEach(() => { @@ -29,13 +30,24 @@ describe("feature-debug", () => { res.indexOf("=== DEBUG LOGS ===") + 19, res.indexOf("=== VM LOGS ===") - 2, ); - expect(debugLogs).toStrictEqual(`stack(2 values) : 10000000000 () + + const filePath = path.normalize("src/test/features/debug.tact"); + + expect(debugLogs).toStrictEqual(`[DEBUG] File ${filePath}:10:9 +stack(2 values) : 10000000000 () +[DEBUG] File ${filePath}:11:9 Hello world! +[DEBUG] File ${filePath}:12:9 123 +[DEBUG] File ${filePath}:13:9 true +[DEBUG] File ${filePath}:14:9 false +[DEBUG] File ${filePath}:15:9 null +[DEBUG] File ${filePath}:16:9 ${contract.address.toString({ bounceable: true })} +[DEBUG] File ${filePath}:17:9 ${Address.parseRaw( "0:83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8", )}`); diff --git a/stdlib/std/debug.tact b/stdlib/std/debug.tact index c1fd13ea7..0ab3cb4b8 100644 --- a/stdlib/std/debug.tact +++ b/stdlib/std/debug.tact @@ -8,7 +8,4 @@ native nativeThrowWhen(code: Int, condition: Bool); native throw(code: Int); @name(throw_unless) -native nativeThrowUnless(code: Int, condition: Bool); - -@name(dump_stack) -native dumpStack(); \ No newline at end of file +native nativeThrowUnless(code: Int, condition: Bool); \ No newline at end of file