diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 2c8be9770..e8b118a98 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -32,5 +32,10 @@ module.exports = { "@typescript-eslint/no-duplicate-type-constituents": "error", "@typescript-eslint/restrict-plus-operands": "error", "@typescript-eslint/no-base-to-string": "error", + "@typescript-eslint/restrict-template-expressions": "error", + "@typescript-eslint/no-useless-template-literals": "error", + "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", + "@typescript-eslint/no-unnecessary-condition": "error", + "@typescript-eslint/switch-exhaustiveness-check": "error", }, }; diff --git a/src/abi/global.ts b/src/abi/global.ts index 14ed5413c..7ed03154c 100644 --- a/src/abi/global.ts +++ b/src/abi/global.ts @@ -23,13 +23,14 @@ export const GlobalFunctions: Map = new Map([ ref, ); } - if (args[0].kind !== "ref") { + const arg0 = args[0]!; + if (arg0.kind !== "ref") { throwCompilationError( "ton() expects single string argument", ref, ); } - if (args[0].name !== "String") { + if (arg0.name !== "String") { throwCompilationError( "ton() expects single string argument", ref, @@ -45,7 +46,7 @@ export const GlobalFunctions: Map = new Map([ ); } const str = evalConstantExpression( - resolved[0], + resolved[0]!, ctx.ctx, ) as string; return toNano(str).toString(10); @@ -63,25 +64,27 @@ export const GlobalFunctions: Map = new Map([ ref, ); } - if (args[0].kind !== "ref") { + const arg0 = args[0]!; + const arg1 = args[1]!; + if (arg0.kind !== "ref") { throwCompilationError( "require() expects first Bool argument", ref, ); } - if (args[0].name !== "Bool") { + if (arg0.name !== "Bool") { throwCompilationError( "require() expects first Bool argument", ref, ); } - if (args[1].kind !== "ref") { + if (arg1.kind !== "ref") { throwCompilationError( "require() expects second string argument", ref, ); } - if (args[1].name !== "String") { + if (arg1.name !== "String") { throwCompilationError( "require() expects second string argument", ref, @@ -97,10 +100,10 @@ export const GlobalFunctions: Map = new Map([ ); } const str = evalConstantExpression( - resolved[1], + resolved[1]!, ctx.ctx, ) as string; - return `throw_unless(${getErrorId(str, ctx.ctx)}, ${writeExpression(resolved[0], ctx)})`; + return `throw_unless(${getErrorId(str, ctx.ctx)}, ${writeExpression(resolved[0]!, ctx)})`; }, }, ], @@ -115,13 +118,14 @@ export const GlobalFunctions: Map = new Map([ ref, ); } - if (args[0].kind !== "ref") { + const arg0 = args[0]!; + if (arg0.kind !== "ref") { throwCompilationError( "address() expects string argument", ref, ); } - if (args[0].name !== "String") { + if (arg0.name !== "String") { throwCompilationError( "address() expects string argument", ref, @@ -137,7 +141,7 @@ export const GlobalFunctions: Map = new Map([ ); } const str = evalConstantExpression( - resolved[0], + resolved[0]!, ctx.ctx, ) as string; let address: Address; @@ -176,13 +180,14 @@ export const GlobalFunctions: Map = new Map([ if (args.length !== 1) { throwCompilationError("cell() expects one argument", ref); } - if (args[0].kind !== "ref") { + const arg0 = args[0]!; + if (arg0.kind !== "ref") { throwCompilationError( "cell() expects string argument", ref, ); } - if (args[0].name !== "String") { + if (arg0.name !== "String") { throwCompilationError( "cell() expects string argument", ref, @@ -197,7 +202,7 @@ export const GlobalFunctions: Map = new Map([ // Load cell data const str = evalConstantExpression( - resolved[0], + resolved[0]!, ctx.ctx, ) as string; let c: Cell; @@ -228,7 +233,7 @@ export const GlobalFunctions: Map = new Map([ if (!enabledDebug(ctx.ctx)) { return `${ctx.used("__tact_nop")}()`; } - const arg = args[0]; + const arg0 = args[0]!; const filePath = ref.file ? posixNormalize(path.relative(cwd(), ref.file!)) @@ -236,36 +241,36 @@ export const GlobalFunctions: Map = new Map([ const lineCol = ref.interval.getLineAndColumn(); const debugPrint = `File ${filePath}:${lineCol.lineNum}:${lineCol.colNum}`; - if (arg.kind === "map") { - const exp = writeExpression(resolved[0], ctx); + if (arg0.kind === "map") { + const exp = writeExpression(resolved[0]!, ctx); return `${ctx.used(`__tact_debug`)}(${exp}, "${debugPrint}")`; - } else if (arg.kind === "null") { + } else if (arg0.kind === "null") { return `${ctx.used(`__tact_debug_str`)}("null", "${debugPrint}")`; - } else if (arg.kind === "void") { + } else if (arg0.kind === "void") { return `${ctx.used(`__tact_debug_str`)}("void", "${debugPrint}")`; - } else if (arg.kind === "ref") { - if (arg.name === "Int") { - const exp = writeExpression(resolved[0], ctx); + } else if (arg0.kind === "ref") { + if (arg0.name === "Int") { + const exp = writeExpression(resolved[0]!, ctx); 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); + } else if (arg0.name === "Bool") { + const exp = writeExpression(resolved[0]!, ctx); return `${ctx.used(`__tact_debug_bool`)}(${exp}, "${debugPrint}")`; - } else if (arg.name === "String") { - const exp = writeExpression(resolved[0], ctx); + } else if (arg0.name === "String") { + const exp = writeExpression(resolved[0]!, ctx); return `${ctx.used(`__tact_debug_str`)}(${exp}, "${debugPrint}")`; - } else if (arg.name === "Address") { - const exp = writeExpression(resolved[0], ctx); + } else if (arg0.name === "Address") { + const exp = writeExpression(resolved[0]!, ctx); return `${ctx.used(`__tact_debug_address`)}(${exp}, "${debugPrint}")`; } else if ( - arg.name === "Builder" || - arg.name === "Slice" || - arg.name === "Cell" + arg0.name === "Builder" || + arg0.name === "Slice" || + arg0.name === "Cell" ) { - const exp = writeExpression(resolved[0], ctx); + const exp = writeExpression(resolved[0]!, ctx); return `${ctx.used(`__tact_debug`)}(${exp}, "${debugPrint}")`; } throwCompilationError( - "dump() not supported for type: " + arg.name, + "dump() not supported for type: " + arg0.name, ref, ); } else { @@ -326,13 +331,14 @@ export const GlobalFunctions: Map = new Map([ if (args.length !== 1) { throwCompilationError("sha256 expects 1 argument", ref); } - if (args[0].kind !== "ref") { + const arg0 = args[0]!; + if (arg0.kind !== "ref") { throwCompilationError( "sha256 expects string argument", ref, ); } - if (args[0].name !== "String" && args[0].name !== "Slice") { + if (arg0.name !== "String" && arg0.name !== "Slice") { throwCompilationError( "sha256 expects string or slice argument", ref, @@ -344,7 +350,8 @@ export const GlobalFunctions: Map = new Map([ if (args.length !== 1) { throwCompilationError("sha256 expects 1 argument", ref); } - if (args[0].kind !== "ref") { + const arg0 = args[0]!; + if (arg0.kind !== "ref") { throwCompilationError( "sha256 expects string argument", ref, @@ -352,10 +359,10 @@ export const GlobalFunctions: Map = new Map([ } // String case - if (args[0].name === "String") { + if (arg0.name === "String") { try { const str = evalConstantExpression( - resolved[0], + resolved[0]!, ctx.ctx, ) as string; if (Buffer.from(str).length > 128) { @@ -370,13 +377,13 @@ export const GlobalFunctions: Map = new Map([ } catch (e) { // Not a constant } - const exp = writeExpression(resolved[0], ctx); + const exp = writeExpression(resolved[0]!, ctx); return `string_hash(${exp})`; } // Slice case - if (args[0].name === "Slice") { - const exp = writeExpression(resolved[0], ctx); + if (arg0.name === "Slice") { + const exp = writeExpression(resolved[0]!, ctx); return `string_hash(${exp})`; } diff --git a/src/abi/map.ts b/src/abi/map.ts index 94764964c..371234e01 100644 --- a/src/abi/map.ts +++ b/src/abi/map.ts @@ -14,8 +14,10 @@ export const MapFunctions: Map = new Map([ if (args.length !== 3) { throwCompilationError("set expects two arguments", ref); // Should not happen } - const self = args[0]; - if (!self || self.kind !== "map") { + const self = args[0]!; + const key = args[1]!; + const value = args[2]!; + if (self.kind !== "map") { throwCompilationError( "set expects a map as self argument", ref, @@ -31,13 +33,13 @@ export const MapFunctions: Map = new Map([ } // Check key type - if (args[1].kind !== "ref" || args[1].optional) { + if (key.kind !== "ref" || key.optional) { throwCompilationError( "set expects a direct type as first argument", ref, ); } - if (args[1].name !== self.key) { + if (key.name !== self.key) { throwCompilationError( `set expects a "${self.key}" as first argument`, ref, @@ -45,13 +47,13 @@ export const MapFunctions: Map = new Map([ } // Check value type - if (args[2].kind !== "null" && args[2].kind !== "ref") { + if (value.kind !== "null" && value.kind !== "ref") { throwCompilationError( "set expects a direct type as second argument", ref, ); } - if (args[2].kind !== "null" && args[2].name !== self.value) { + if (value.kind !== "null" && value.name !== self.value) { throwCompilationError( `set expects a "${self.value}" as second argument`, ref, @@ -66,8 +68,9 @@ export const MapFunctions: Map = new Map([ if (args.length !== 3) { throwCompilationError("set expects two arguments", ref); // Ignore self argument } - const self = args[0]; - if (!self || self.kind !== "map") { + const self = args[0]!; + const value = args[2]!; + if (self.kind !== "map") { throwCompilationError( "set expects a map as self argument", ref, @@ -126,7 +129,7 @@ export const MapFunctions: Map = new Map([ } if (t.kind === "struct") { ctx.used(`__tact_dict_set_${kind}_cell`); - if (args[2].kind === "ref" && !args[2].optional) { + if (value.kind === "ref" && !value.optional) { return `${resolved[0]}~__tact_dict_set_${kind}_cell(${bits}, ${resolved[1]}, ${ops.writerCell(t.name, ctx)}(${resolved[2]}))`; } else { return `${resolved[0]}~__tact_dict_set_${kind}_cell(${bits}, ${resolved[1]}, ${ops.writerCellOpt(t.name, ctx)}(${resolved[2]}))`; @@ -181,7 +184,7 @@ export const MapFunctions: Map = new Map([ } if (t.kind === "struct") { ctx.used(`__tact_dict_set_slice_cell`); - if (args[2].kind === "ref" && !args[2].optional) { + if (value.kind === "ref" && !value.optional) { return `${resolved[0]}~__tact_dict_set_slice_cell(267, ${resolved[1]}, ${ops.writerCell(t.name, ctx)}(${resolved[2]}))`; } else { return `${resolved[0]}~__tact_dict_set_slice_cell(267, ${resolved[1]}, ${ops.writerCellOpt(t.name, ctx)}(${resolved[2]}))`; @@ -211,8 +214,9 @@ export const MapFunctions: Map = new Map([ if (args.length !== 2) { throwCompilationError("set expects one argument", ref); // Ignore self argument } - const self = args[0]; - if (!self || self.kind !== "map") { + const self = args[0]!; + const key = args[1]!; + if (self.kind !== "map") { throwCompilationError( "set expects a map as self argument", ref, @@ -220,13 +224,13 @@ export const MapFunctions: Map = new Map([ } // Check key type - if (args[1].kind !== "ref" || args[1].optional) { + if (key.kind !== "ref" || key.optional) { throwCompilationError( "set expects a direct type as first argument", ref, ); } - if (args[1].name !== self.key) { + if (key.name !== self.key) { throwCompilationError( `set expects a "${self.key}" as first argument`, ref, @@ -239,8 +243,8 @@ export const MapFunctions: Map = new Map([ if (args.length !== 2) { throwCompilationError("set expects one argument", ref); // Ignore self argument } - const self = args[0]; - if (!self || self.kind !== "map") { + const self = args[0]!; + if (self.kind !== "map") { throwCompilationError( "set expects a map as self argument", ref, @@ -373,8 +377,9 @@ export const MapFunctions: Map = new Map([ if (args.length !== 2) { throwCompilationError("del expects one argument", ref); // Ignore self argument } - const self = args[0]; - if (!self || self.kind !== "map") { + const self = args[0]!; + const key = args[1]!; + if (self.kind !== "map") { throwCompilationError( "del expects a map as self argument", ref, @@ -382,13 +387,13 @@ export const MapFunctions: Map = new Map([ } // Check key type - if (args[1].kind !== "ref" || args[1].optional) { + if (key.kind !== "ref" || key.optional) { throwCompilationError( "del expects a direct type as first argument", ref, ); } - if (args[1].name !== self.key) { + if (key.name !== self.key) { throwCompilationError( `del expects a "${self.key}" as first argument`, ref, @@ -402,8 +407,8 @@ export const MapFunctions: Map = new Map([ if (args.length !== 2) { throwCompilationError("del expects one argument", ref); // Ignore self argument } - const self = args[0]; - if (!self || self.kind !== "map") { + const self = args[0]!; + if (self.kind !== "map") { throwCompilationError( "del expects a map as self argument", ref, @@ -446,8 +451,8 @@ export const MapFunctions: Map = new Map([ if (args.length !== 1) { throwCompilationError("asCell expects one argument", ref); // Ignore self argument } - const self = args[0]; - if (!self || self.kind !== "map") { + const self = args[0]!; + if (self.kind !== "map") { throwCompilationError( "asCell expects a map as self argument", ref, @@ -460,15 +465,15 @@ export const MapFunctions: Map = new Map([ if (args.length !== 1) { throwCompilationError("asCell expects one argument", ref); // Ignore self argument } - const self = args[0]; - if (!self || self.kind !== "map") { + const self = args[0]!; + if (self.kind !== "map") { throwCompilationError( "asCell expects a map as self argument", ref, ); // Should not happen } - return writeExpression(exprs[0], ctx); + return writeExpression(exprs[0]!, ctx); }, }, ], @@ -481,8 +486,8 @@ export const MapFunctions: Map = new Map([ if (args.length !== 1) { throwCompilationError("isEmpty expects one argument", ref); // Ignore self argument } - const self = args[0]; - if (!self || self.kind !== "map") { + const self = args[0]!; + if (self.kind !== "map") { throwCompilationError( "isEmpty expects a map as self argument", ref, @@ -495,15 +500,15 @@ export const MapFunctions: Map = new Map([ if (args.length !== 1) { throwCompilationError("isEmpty expects one argument", ref); // Ignore self argument } - const self = args[0]; - if (!self || self.kind !== "map") { + const self = args[0]!; + if (self.kind !== "map") { throwCompilationError( "isEmpty expects a map as self argument", ref, ); // Should not happen } - return `null?(${writeExpression(exprs[0], ctx)})`; + return `null?(${writeExpression(exprs[0]!, ctx)})`; }, }, ], diff --git a/src/abi/struct.ts b/src/abi/struct.ts index 4a2e5040c..a6dc4b4d2 100644 --- a/src/abi/struct.ts +++ b/src/abi/struct.ts @@ -13,13 +13,14 @@ export const StructFunctions: Map = new Map([ if (args.length !== 1) { throwCompilationError("toCell() expects no arguments", ref); } - if (args[0].kind !== "ref") { + const arg = args[0]!; + if (arg.kind !== "ref") { throwCompilationError( "toCell() is implemented only a struct type", ref, ); } - const tp = getType(ctx, args[0].name); + const tp = getType(ctx, arg.name); if (tp.kind !== "struct") { throwCompilationError( "toCell() is implemented only a struct type", @@ -32,13 +33,14 @@ export const StructFunctions: Map = new Map([ if (resolved.length !== 1) { throwCompilationError("toCell() expects no arguments", ref); } - if (args[0].kind !== "ref") { + const arg = args[0]!; + if (arg.kind !== "ref") { throwCompilationError( "toCell() is implemented only a struct type", ref, ); } - return `${ops.writerCell(args[0].name, ctx)}(${resolved.map((v) => writeExpression(v, ctx)).join(", ")})`; + return `${ops.writerCell(arg.name, ctx)}(${resolved.map((v) => writeExpression(v, ctx)).join(", ")})`; }, }, ], @@ -53,26 +55,28 @@ export const StructFunctions: Map = new Map([ ref, ); } - if (args[0].kind !== "ref") { + const arg0 = args[0]!; + const arg1 = args[1]!; + if (arg0.kind !== "ref") { throwCompilationError( "fromCell() is implemented only for struct types", ref, ); } - const tp = getType(ctx, args[0].name); + const tp = getType(ctx, arg0.name); if (tp.kind !== "struct") { throwCompilationError( "fromCell() is implemented only for struct types", ref, ); } - if (args[1].kind !== "ref" || args[1].name !== "Cell") { + if (arg1.kind !== "ref" || arg1.name !== "Cell") { throwCompilationError( "fromCell() expects a Cell as an argument", ref, ); } - return { kind: "ref", name: args[0].name, optional: false }; + return { kind: "ref", name: arg0.name, optional: false }; }, generate: (ctx, args, resolved, ref) => { if (resolved.length !== 2) { @@ -81,19 +85,21 @@ export const StructFunctions: Map = new Map([ ref, ); } - if (args[0].kind !== "ref") { + const arg0 = args[0]!; + const arg1 = args[1]!; + if (arg0.kind !== "ref") { throwCompilationError( "fromCell() is implemented only for struct types", ref, ); } - if (args[1].kind !== "ref" || args[1].name !== "Cell") { + if (arg1.kind !== "ref" || arg1.name !== "Cell") { throwCompilationError( "fromCell() expects a Cell as an argument", ref, ); } - return `${ops.readerNonModifying(args[0].name, ctx)}(${writeExpression(resolved[1], ctx)}.begin_parse())`; + return `${ops.readerNonModifying(arg0.name, ctx)}(${writeExpression(resolved[1]!, ctx)}.begin_parse())`; }, }, ], @@ -108,26 +114,28 @@ export const StructFunctions: Map = new Map([ ref, ); } - if (args[0].kind !== "ref") { + const arg0 = args[0]!; + const arg1 = args[1]!; + if (arg0.kind !== "ref") { throwCompilationError( "fromSlice() is implemented only for struct types", ref, ); } - const tp = getType(ctx, args[0].name); + const tp = getType(ctx, arg0.name); if (tp.kind !== "struct") { throwCompilationError( "fromSlice() is implemented only for struct types", ref, ); } - if (args[1].kind !== "ref" || args[1].name !== "Slice") { + if (arg1.kind !== "ref" || arg1.name !== "Slice") { throwCompilationError( "fromSlice() expects a Slice as an argument", ref, ); } - return { kind: "ref", name: args[0].name, optional: false }; + return { kind: "ref", name: arg0.name, optional: false }; }, generate: (ctx, args, resolved, ref) => { if (resolved.length !== 2) { @@ -136,19 +144,21 @@ export const StructFunctions: Map = new Map([ ref, ); } - if (args[0].kind !== "ref") { + const arg0 = args[0]!; + const arg1 = args[1]!; + if (arg0.kind !== "ref") { throwCompilationError( "fromSlice() is implemented only for struct types", ref, ); } - if (args[1].kind !== "ref" || args[1].name !== "Slice") { + if (arg1.kind !== "ref" || arg1.name !== "Slice") { throwCompilationError( "fromSlice() expects a Slice as an argument", ref, ); } - return `${ops.readerNonModifying(args[0].name, ctx)}(${writeExpression(resolved[1], ctx)})`; + return `${ops.readerNonModifying(arg0.name, ctx)}(${writeExpression(resolved[1]!, ctx)})`; }, }, ], diff --git a/src/bindings/typescript/serializers.ts b/src/bindings/typescript/serializers.ts index e7c3deeac..81f6a8d90 100644 --- a/src/bindings/typescript/serializers.ts +++ b/src/bindings/typescript/serializers.ts @@ -598,47 +598,54 @@ type MapSerializerDescr = { value: MapSerializerDescrValue; }; function getKeyParser(src: MapSerializerDescrKey) { - if (src.kind === "int") { - if (src.bits <= 32) { - return `Dictionary.Keys.Int(${src.bits})`; - } else { - return `Dictionary.Keys.BigInt(${src.bits})`; + switch (src.kind) { + case "int": { + if (src.bits <= 32) { + return `Dictionary.Keys.Int(${src.bits})`; + } else { + return `Dictionary.Keys.BigInt(${src.bits})`; + } } - } else if (src.kind === "uint") { - if (src.bits <= 32) { - return `Dictionary.Keys.Uint(${src.bits})`; - } else { - return `Dictionary.Keys.BigUint(${src.bits})`; + case "uint": { + if (src.bits <= 32) { + return `Dictionary.Keys.Uint(${src.bits})`; + } else { + return `Dictionary.Keys.BigUint(${src.bits})`; + } + } + case "address": { + return "Dictionary.Keys.Address()"; } - } else if (src.kind === "address") { - return "Dictionary.Keys.Address()"; - } else { - throw Error("Unreachable"); } } function getValueParser(src: MapSerializerDescrValue) { - if (src.kind === "int") { - if (src.bits <= 32) { - return `Dictionary.Values.Int(${src.bits})`; - } else { - return `Dictionary.Values.BigInt(${src.bits})`; - } - } else if (src.kind === "uint") { - if (src.bits <= 32) { - return `Dictionary.Values.Uint(${src.bits})`; - } else { - return `Dictionary.Values.BigUint(${src.bits})`; - } - } else if (src.kind === "address") { - return "Dictionary.Values.Address()"; - } else if (src.kind === "cell") { - return "Dictionary.Values.Cell()"; - } else if (src.kind === "boolean") { - return "Dictionary.Values.Bool()"; - } else if (src.kind === "struct") { - return `dictValueParser${src.type}()`; - } else { - throw Error("Unreachable"); + switch (src.kind) { + case "int": { + if (src.bits <= 32) { + return `Dictionary.Values.Int(${src.bits})`; + } else { + return `Dictionary.Values.BigInt(${src.bits})`; + } + } + case "uint": { + if (src.bits <= 32) { + return `Dictionary.Values.Uint(${src.bits})`; + } else { + return `Dictionary.Values.BigUint(${src.bits})`; + } + } + case "address": { + return "Dictionary.Values.Address()"; + } + case "cell": { + return "Dictionary.Values.Cell()"; + } + case "boolean": { + return "Dictionary.Values.Bool()"; + } + case "struct": { + return `dictValueParser${src.type}()`; + } } } @@ -740,36 +747,55 @@ const map: Serializer = { tsType(v) { // Resolve key type let keyT: string; - if (v.key.kind === "int" || v.key.kind === "uint") { - if (v.key.bits <= 32) { - keyT = `number`; - } else { - keyT = `bigint`; + switch (v.key.kind) { + case "int": + case "uint": + { + if (v.key.bits <= 32) { + keyT = `number`; + } else { + keyT = `bigint`; + } + } + break; + case "address": { + keyT = `Address`; } - } else if (v.key.kind === "address") { - keyT = `Address`; - } else { - throw Error("Unexpected key type"); } // Resolve value type let valueT: string; - if (v.value.kind === "int" || v.value.kind === "uint") { - if (v.value.bits <= 32) { - valueT = `number`; - } else { - valueT = `bigint`; - } - } else if (v.value.kind === "boolean") { - valueT = `boolean`; - } else if (v.value.kind === "address") { - valueT = `Address`; - } else if (v.value.kind === "cell") { - valueT = `Cell`; - } else if (v.value.kind === "struct") { - valueT = v.value.type; - } else { - throw Error("Unexpected key type"); + switch (v.value.kind) { + case "int": + case "uint": + { + if (v.value.bits <= 32) { + valueT = `number`; + } else { + valueT = `bigint`; + } + } + break; + case "boolean": + { + valueT = `boolean`; + } + break; + case "address": + { + valueT = `Address`; + } + break; + case "cell": + { + valueT = `Cell`; + } + break; + case "struct": + { + valueT = v.value.type; + } + break; } return `Dictionary<${keyT}, ${valueT}>`; diff --git a/src/bindings/writeTypescript.ts b/src/bindings/writeTypescript.ts index aff20cb16..7e8534582 100644 --- a/src/bindings/writeTypescript.ts +++ b/src/bindings/writeTypescript.ts @@ -112,7 +112,7 @@ export function writeTypescript( type: v.type, op: getAllocationOperationFromField( v.type, - (s) => allocations[s].size, + (s) => allocations[s]!.size, ), })); const headerBits = f.header ? 32 : 0; @@ -131,8 +131,8 @@ export function writeTypescript( for (const s of abi.types) { writeStruct(s.name, s.fields, true, w); - writeSerializer(s, allocations[s.name].root, w); - writeParser(s, allocations[s.name].root, w); + writeSerializer(s, allocations[s.name]!.root, w); + writeParser(s, allocations[s.name]!.root, w); writeTupleParser(s, w); writeTupleSerializer(s, w); writeDictParser(s, w); @@ -148,7 +148,7 @@ export function writeTypescript( type: v.type, op: getAllocationOperationFromField( v.type, - (s) => allocations[s].size, + (s) => allocations[s]!.size, ), })); const allocation = allocate({ @@ -191,13 +191,11 @@ export function writeTypescript( ); w.inIndent(() => { if (abi.errors) { - for (const k in abi.errors) { + Object.entries(abi.errors).forEach(([k, abiError]) => w.append( - `${k}: { message: \`${abi.errors[ - parseInt(k, 10) - ].message.replaceAll("`", "\\`")}\` },`, - ); - } + `${k}: { message: \`${abiError.message.replaceAll("`", "\\`")}\` },`, + ), + ); } }); w.append(`}`); @@ -309,21 +307,34 @@ export function writeTypescript( if (r.receiver !== "internal") { continue; } - if (r.message.kind === "empty") { - receivers.push(`null`); - } else if (r.message.kind === "typed") { - receivers.push(`${r.message.type}`); - } else if (r.message.kind === "text") { - if ( - r.message.text !== null && - r.message.text !== undefined - ) { - receivers.push(`'${r.message.text}'`); - } else { - receivers.push(`string`); - } - } else if (r.message.kind === "any") { - receivers.push(`Slice`); + switch (r.message.kind) { + case "empty": + { + receivers.push(`null`); + } + break; + case "typed": + { + receivers.push(r.message.type); + } + break; + case "text": + { + if ( + r.message.text !== null && + r.message.text !== undefined + ) { + receivers.push(`'${r.message.text}'`); + } else { + receivers.push(`string`); + } + } + break; + case "any": + { + receivers.push(`Slice`); + } + break; } } @@ -341,48 +352,66 @@ export function writeTypescript( continue; } const msg = r.message; - if (msg.kind === "typed") { - w.append( - `if (message && typeof message === 'object' && !(message instanceof Slice) && message.$$type === '${msg.type}') {`, - ); - w.inIndent(() => { - w.append( - `body = beginCell().store(store${msg.type}(message)).endCell();`, - ); - }); - w.append(`}`); - } else if (msg.kind === "empty") { - w.append(`if (message === null) {`); - w.inIndent(() => { - w.append(`body = new Cell();`); - }); - w.append(`}`); - } else if (msg.kind === "text") { - if (msg.text === null || msg.text === undefined) { - w.append(`if (typeof message === 'string') {`); - w.inIndent(() => { + switch (msg.kind) { + case "typed": + { w.append( - `body = beginCell().storeUint(0, 32).storeStringTail(message).endCell();`, + `if (message && typeof message === 'object' && !(message instanceof Slice) && message.$$type === '${msg.type}') {`, ); - }); - w.append(`}`); - } else { - w.append(`if (message === '${msg.text}') {`); + w.inIndent(() => { + w.append( + `body = beginCell().store(store${msg.type}(message)).endCell();`, + ); + }); + w.append(`}`); + } + break; + case "empty": + { + w.append(`if (message === null) {`); + w.inIndent(() => { + w.append(`body = new Cell();`); + }); + w.append(`}`); + } + break; + case "text": + { + if ( + msg.text === null || + msg.text === undefined + ) { + w.append( + `if (typeof message === 'string') {`, + ); + w.inIndent(() => { + w.append( + `body = beginCell().storeUint(0, 32).storeStringTail(message).endCell();`, + ); + }); + w.append(`}`); + } else { + w.append( + `if (message === '${msg.text}') {`, + ); + w.inIndent(() => { + w.append( + `body = beginCell().storeUint(0, 32).storeStringTail(message).endCell();`, + ); + }); + w.append(`}`); + } + } + break; + case "any": { + w.append( + `if (message && typeof message === 'object' && message instanceof Slice) {`, + ); w.inIndent(() => { - w.append( - `body = beginCell().storeUint(0, 32).storeStringTail(message).endCell();`, - ); + w.append(`body = message.asCell();`); }); w.append(`}`); } - } else if (msg.kind === "any") { - w.append( - `if (message && typeof message === 'object' && message instanceof Slice) {`, - ); - w.inIndent(() => { - w.append(`body = message.asCell();`); - }); - w.append(`}`); } } w.append( @@ -410,21 +439,34 @@ export function writeTypescript( if (r.receiver !== "external") { continue; } - if (r.message.kind === "empty") { - receivers.push(`null`); - } else if (r.message.kind === "typed") { - receivers.push(`${r.message.type}`); - } else if (r.message.kind === "text") { - if ( - r.message.text !== null && - r.message.text !== undefined - ) { - receivers.push(`'${r.message.text}'`); - } else { - receivers.push(`string`); - } - } else if (r.message.kind === "any") { - receivers.push(`Slice`); + switch (r.message.kind) { + case "empty": + { + receivers.push(`null`); + } + break; + case "typed": + { + receivers.push(r.message.type); + } + break; + case "text": + { + if ( + r.message.text !== null && + r.message.text !== undefined + ) { + receivers.push(`'${r.message.text}'`); + } else { + receivers.push(`string`); + } + } + break; + case "any": + { + receivers.push(`Slice`); + } + break; } } @@ -442,48 +484,66 @@ export function writeTypescript( continue; } const msg = r.message; - if (msg.kind === "typed") { - w.append( - `if (message && typeof message === 'object' && !(message instanceof Slice) && message.$$type === '${msg.type}') {`, - ); - w.inIndent(() => { - w.append( - `body = beginCell().store(store${msg.type}(message)).endCell();`, - ); - }); - w.append(`}`); - } else if (msg.kind === "empty") { - w.append(`if (message === null) {`); - w.inIndent(() => { - w.append(`body = new Cell();`); - }); - w.append(`}`); - } else if (msg.kind === "text") { - if (msg.text === null || msg.text === undefined) { - w.append(`if (typeof message === 'string') {`); - w.inIndent(() => { + switch (msg.kind) { + case "typed": + { w.append( - `body = beginCell().storeUint(0, 32).storeStringTail(message).endCell();`, + `if (message && typeof message === 'object' && !(message instanceof Slice) && message.$$type === '${msg.type}') {`, ); - }); - w.append(`}`); - } else { - w.append(`if (message === '${msg.text}') {`); + w.inIndent(() => { + w.append( + `body = beginCell().store(store${msg.type}(message)).endCell();`, + ); + }); + w.append(`}`); + } + break; + case "empty": + { + w.append(`if (message === null) {`); + w.inIndent(() => { + w.append(`body = new Cell();`); + }); + w.append(`}`); + } + break; + case "text": + { + if ( + msg.text === null || + msg.text === undefined + ) { + w.append( + `if (typeof message === 'string') {`, + ); + w.inIndent(() => { + w.append( + `body = beginCell().storeUint(0, 32).storeStringTail(message).endCell();`, + ); + }); + w.append(`}`); + } else { + w.append( + `if (message === '${msg.text}') {`, + ); + w.inIndent(() => { + w.append( + `body = beginCell().storeUint(0, 32).storeStringTail(message).endCell();`, + ); + }); + w.append(`}`); + } + } + break; + case "any": { + w.append( + `if (message && typeof message === 'object' && message instanceof Slice) {`, + ); w.inIndent(() => { - w.append( - `body = beginCell().storeUint(0, 32).storeStringTail(message).endCell();`, - ); + w.append(`body = message.asCell();`); }); w.append(`}`); } - } else if (msg.kind === "any") { - w.append( - `if (message && typeof message === 'object' && message instanceof Slice) {`, - ); - w.inIndent(() => { - w.append(`body = message.asCell();`); - }); - w.append(`}`); } } w.append( diff --git a/src/constEval.ts b/src/constEval.ts index adefc6b7b..b01580f81 100644 --- a/src/constEval.ts +++ b/src/constEval.ts @@ -13,7 +13,7 @@ import { idText, } from "./grammar/ast"; import { idTextErr, throwConstEvalError } from "./errors"; -import { CommentValue, StructValue, Value } from "./types/types"; +import { CommentValue, showValue, StructValue, Value } from "./types/types"; import { sha256_sync } from "@ton/crypto"; import { getStaticConstant, @@ -49,13 +49,16 @@ function throwErrorConstEval(msg: string, source: SrcInfo): never { function ensureInt(val: Value, source: SrcInfo): bigint { if (typeof val !== "bigint") { - throwErrorConstEval(`integer expected, but got '${val}'`, source); + throwErrorConstEval( + `integer expected, but got '${showValue(val)}'`, + source, + ); } if (minTvmInt <= val && val <= maxTvmInt) { return val; } else { throwErrorConstEval( - `integer '${val}' does not fit into TVM Int type`, + `integer '${showValue(val)}' does not fit into TVM Int type`, source, ); } @@ -63,14 +66,20 @@ function ensureInt(val: Value, source: SrcInfo): bigint { function ensureBoolean(val: Value, source: SrcInfo): boolean { if (typeof val !== "boolean") { - throwErrorConstEval(`boolean expected, but got '${val}'`, source); + throwErrorConstEval( + `boolean expected, but got '${showValue(val)}'`, + source, + ); } return val; } function ensureString(val: Value, source: SrcInfo): string { if (typeof val !== "string") { - throwErrorConstEval(`string expected, but got '${val}'`, source); + throwErrorConstEval( + `string expected, but got '${showValue(val)}'`, + source, + ); } return val; } @@ -375,12 +384,12 @@ function evalFieldAccess( !("$tactStruct" in valStruct) ) { throwErrorConstEval( - `constant struct expected, but got ${valStruct}`, + `constant struct expected, but got ${showValue(valStruct)}`, structExpr.loc, ); } - if (fieldId.text in valStruct) { - return valStruct[fieldId.text]; + if (idText(fieldId) in valStruct) { + return valStruct[idText(fieldId)]!; } else { // this cannot happen in a well-typed program throwErrorConstEval( @@ -424,8 +433,8 @@ function evalBuiltins( case "ton": { ensureFunArity(1, args, source); const tons = ensureString( - evalConstantExpression(args[0], ctx), - args[0].loc, + evalConstantExpression(args[0]!, ctx), + args[0]!.loc, ); try { return ensureInt(BigInt(toNano(tons).toString(10)), source); @@ -442,12 +451,12 @@ function evalBuiltins( case "pow": { ensureFunArity(2, args, source); const valBase = ensureInt( - evalConstantExpression(args[0], ctx), - args[0].loc, + evalConstantExpression(args[0]!, ctx), + args[0]!.loc, ); const valExp = ensureInt( - evalConstantExpression(args[1], ctx), - args[1].loc, + evalConstantExpression(args[1]!, ctx), + args[1]!.loc, ); if (valExp < 0n) { throwErrorConstEval( @@ -471,8 +480,8 @@ function evalBuiltins( case "pow2": { ensureFunArity(1, args, source); const valExponent = ensureInt( - evalConstantExpression(args[0], ctx), - args[0].loc, + evalConstantExpression(args[0]!, ctx), + args[0]!.loc, ); if (valExponent < 0n) { throwErrorConstEval( @@ -496,8 +505,8 @@ function evalBuiltins( case "sha256": { ensureFunArity(1, args, source); const str = ensureString( - evalConstantExpression(args[0], ctx), - args[0].loc, + evalConstantExpression(args[0]!, ctx), + args[0]!.loc, ); const dataSize = Buffer.from(str).length; if (dataSize > 128) { @@ -516,8 +525,8 @@ function evalBuiltins( { ensureFunArity(1, args, source); const str = ensureString( - evalConstantExpression(args[0], ctx), - args[0].loc, + evalConstantExpression(args[0]!, ctx), + args[0]!.loc, ); try { return Cell.fromBase64(str); @@ -533,8 +542,8 @@ function evalBuiltins( { ensureFunArity(1, args, source); const str = ensureString( - evalConstantExpression(args[0], ctx), - args[0].loc, + evalConstantExpression(args[0]!, ctx), + args[0]!.loc, ); try { const address = Address.parse(str); @@ -562,11 +571,11 @@ function evalBuiltins( case "newAddress": { ensureFunArity(2, args, source); const wc = ensureInt( - evalConstantExpression(args[0], ctx), - args[0].loc, + evalConstantExpression(args[0]!, ctx), + args[0]!.loc, ); const addr = Buffer.from( - ensureInt(evalConstantExpression(args[1], ctx), args[1].loc) + ensureInt(evalConstantExpression(args[1]!, ctx), args[1]!.loc) .toString(16) .padStart(64, "0"), "hex", @@ -579,7 +588,7 @@ function evalBuiltins( } if (!enabledMasterchain(ctx) && wc !== 0n) { throwErrorConstEval( - `${wc}:${addr} address is from masterchain which is not enabled for this contract`, + `${wc}:${addr.toString("hex")} address is from masterchain which is not enabled for this contract`, source, ); } diff --git a/src/context.ts b/src/context.ts index 736729104..5fab12885 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,7 +1,11 @@ export class CompilerContext { - readonly shared: { [key: symbol]: object } = {}; + readonly shared: { [key: symbol]: object | undefined } = {}; - constructor(args: { shared: { [key: symbol]: object } } = { shared: {} }) { + constructor( + args: { shared: { [key: symbol]: object | undefined } } = { + shared: {}, + }, + ) { this.shared = args.shared; Object.freeze(this.shared); Object.freeze(this); diff --git a/src/func/funcCompile.ts b/src/func/funcCompile.ts index f0809d6b7..222593155 100644 --- a/src/func/funcCompile.ts +++ b/src/func/funcCompile.ts @@ -152,32 +152,33 @@ export async function funcCompile(args: { const msg = logs.join("\n"); - if (result.status === "error") { - return { - ok: false, - log: - logs.length > 0 - ? msg - : result.message - ? result.message - : "Unknown error", - fift: null, - output: null, - }; - } else if (result.status === "ok") { - return { - ok: true, - log: - logs.length > 0 - ? msg - : result.warnings - ? result.warnings - : "", - fift: cutFirstLine(result.fiftCode.replaceAll("\\n", "\n")), - output: Buffer.from(result.codeBoc, "base64"), - }; - } else { - throw Error("Unexpected compiler response"); + switch (result.status) { + case "error": { + return { + ok: false, + log: + logs.length > 0 + ? msg + : result.message + ? result.message + : "Unknown error", + fift: null, + output: null, + }; + } + case "ok": { + return { + ok: true, + log: + logs.length > 0 + ? msg + : result.warnings + ? result.warnings + : "", + fift: cutFirstLine(result.fiftCode.replaceAll("\\n", "\n")), + output: Buffer.from(result.codeBoc, "base64"), + }; + } } } catch (e) { args.logger.error(errorToString(e)); @@ -190,4 +191,5 @@ export async function funcCompile(args: { mod._free(i); } } + throw Error("Unexpected compiler response"); } diff --git a/src/generator/Writer.ts b/src/generator/Writer.ts index f44827301..5f964b26a 100644 --- a/src/generator/Writer.ts +++ b/src/generator/Writer.ts @@ -191,6 +191,7 @@ export class WriterContext { if (!signature && name !== "$main") { throw new Error(`Function "${name}" signature not set`); } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!code) { throw new Error(`Function "${name}" body not set`); } diff --git a/src/generator/createABI.ts b/src/generator/createABI.ts index 53cc9f3f8..2383c47b3 100644 --- a/src/generator/createABI.ts +++ b/src/generator/createABI.ts @@ -11,7 +11,7 @@ export function createABI(ctx: CompilerContext, name: string): ContractABI { const allTypes = Object.values(getAllTypes(ctx)); // Contract - const contract = allTypes.find((v) => v.name === name)!; + const contract = allTypes.find((v) => v.name === name); if (!contract) { throw Error(`Contract "${name}" not found`); } diff --git a/src/generator/writeProgram.ts b/src/generator/writeProgram.ts index 6e2dbe849..81f4584bd 100644 --- a/src/generator/writeProgram.ts +++ b/src/generator/writeProgram.ts @@ -378,10 +378,7 @@ function writeAll( // Static functions const sf = getAllStaticFunctions(ctx); - for (const k in sf) { - const f = sf[k]; - writeFunction(f, wCtx); - } + Object.values(sf).forEach((f) => writeFunction(f, wCtx)); // Extensions for (const c of allTypes) { diff --git a/src/generator/writeReport.ts b/src/generator/writeReport.ts index 6b1307392..8674328f6 100644 --- a/src/generator/writeReport.ts +++ b/src/generator/writeReport.ts @@ -40,9 +40,9 @@ export function writeReport(ctx: CompilerContext, pkg: PackageFileFormat) { // Error Codes w.write(`# Error Codes`); - for (const t in abi.errors!) { - w.write(`${t}: ${abi.errors![parseInt(t, 10)].message}`); - } + Object.entries(abi.errors!).forEach(([t, abiError]) => + w.write(`${t}: ${abiError.message}`), + ); return w.end(); } diff --git a/src/generator/writers/resolveFuncTypeFromAbi.ts b/src/generator/writers/resolveFuncTypeFromAbi.ts index 9d4dd4277..7ee9634a1 100644 --- a/src/generator/writers/resolveFuncTypeFromAbi.ts +++ b/src/generator/writers/resolveFuncTypeFromAbi.ts @@ -11,37 +11,44 @@ export function resolveFuncTypeFromAbi( } const res: string[] = []; for (const f of fields) { - if (f.kind === "dict") { - res.push("cell"); - } else if (f.kind === "simple") { - if (f.type === "int" || f.type === "uint" || f.type === "bool") { - res.push("int"); - } else if (f.type === "cell") { - res.push("cell"); - } else if (f.type === "slice") { - res.push("slice"); - } else if (f.type === "builder") { - res.push("builder"); - } else if (f.type === "address") { - res.push("slice"); - } else if (f.type === "fixed-bytes") { - res.push("slice"); - } else if (f.type === "string") { - res.push("slice"); - } else { - const t = getType(ctx.ctx, f.type); - if (t.kind !== "struct") { - throw Error("Unsupported type: " + t.kind); + switch (f.kind) { + case "dict": + { + res.push("cell"); } - if (f.optional || t.fields.length === 0) { - res.push("tuple"); + break; + case "simple": { + if ( + f.type === "int" || + f.type === "uint" || + f.type === "bool" + ) { + res.push("int"); + } else if (f.type === "cell") { + res.push("cell"); + } else if (f.type === "slice") { + res.push("slice"); + } else if (f.type === "builder") { + res.push("builder"); + } else if (f.type === "address") { + res.push("slice"); + } else if (f.type === "fixed-bytes") { + res.push("slice"); + } else if (f.type === "string") { + res.push("slice"); } else { - const loaded = t.fields.map((v) => v.abi.type); - res.push(resolveFuncTypeFromAbi(loaded, ctx)); + const t = getType(ctx.ctx, f.type); + if (t.kind !== "struct") { + throw Error("Unsupported type: " + t.kind); + } + if (f.optional || t.fields.length === 0) { + res.push("tuple"); + } else { + const loaded = t.fields.map((v) => v.abi.type); + res.push(resolveFuncTypeFromAbi(loaded, ctx)); + } } } - } else { - throw Error("Unsupported type"); } } return `(${res.join(", ")})`; diff --git a/src/generator/writers/resolveFuncTypeFromAbiUnpack.ts b/src/generator/writers/resolveFuncTypeFromAbiUnpack.ts index e50498fa6..aacb66038 100644 --- a/src/generator/writers/resolveFuncTypeFromAbiUnpack.ts +++ b/src/generator/writers/resolveFuncTypeFromAbiUnpack.ts @@ -8,48 +8,53 @@ export function resolveFuncTypeFromAbiUnpack( ctx: WriterContext, ): string { if (fields.length === 0) { - return `${name}`; + return name; } const res: string[] = []; for (const f of fields) { - if (f.type.kind === "dict") { - res.push(`${name}'${f.name}`); - } else if (f.type.kind === "simple") { - if ( - f.type.type === "int" || - f.type.type === "uint" || - f.type.type === "bool" - ) { - res.push(`${name}'${f.name}`); - } else if (f.type.type === "cell") { - res.push(`${name}'${f.name}`); - } else if (f.type.type === "slice") { - res.push(`${name}'${f.name}`); - } else if (f.type.type === "builder") { - res.push(`${name}'${f.name}`); - } else if (f.type.type === "address") { - res.push(`${name}'${f.name}`); - } else if (f.type.type === "fixed-bytes") { - res.push(`${name}'${f.name}`); - } else if (f.type.type === "string") { - res.push(`${name}'${f.name}`); - } else { - const t = getType(ctx.ctx, f.type.type); - if (f.type.optional || t.fields.length === 0) { + switch (f.type.kind) { + case "dict": + { res.push(`${name}'${f.name}`); - } else { - const loaded = t.fields.map((v) => v.abi); - res.push( - resolveFuncTypeFromAbiUnpack( - `${name}'${f.name}`, - loaded, - ctx, - ), - ); } - } - } else { - throw Error("Unsupported type"); + break; + case "simple": + { + if ( + f.type.type === "int" || + f.type.type === "uint" || + f.type.type === "bool" + ) { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "cell") { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "slice") { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "builder") { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "address") { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "fixed-bytes") { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "string") { + res.push(`${name}'${f.name}`); + } else { + const t = getType(ctx.ctx, f.type.type); + if (f.type.optional || t.fields.length === 0) { + res.push(`${name}'${f.name}`); + } else { + const loaded = t.fields.map((v) => v.abi); + res.push( + resolveFuncTypeFromAbiUnpack( + `${name}'${f.name}`, + loaded, + ctx, + ), + ); + } + } + } + break; } } return `(${res.join(", ")})`; diff --git a/src/generator/writers/writeConstant.ts b/src/generator/writers/writeConstant.ts index 85712d31a..6e2f8138d 100644 --- a/src/generator/writers/writeConstant.ts +++ b/src/generator/writers/writeConstant.ts @@ -49,7 +49,7 @@ function writeRawSlice( ctx.markRendered(k); ctx.fun(`__gen_slice_${prefix}_${h}`, () => { ctx.signature(`slice __gen_slice_${prefix}_${h}()`); - ctx.comment(`${comment}`); + ctx.comment(comment); ctx.context("constants"); ctx.asm(`asm "B{${t}} B>boc { ctx.signature(`cell __gen_cell_${prefix}_${h}()`); - ctx.comment(`${comment}`); + ctx.comment(comment); ctx.context("constants"); ctx.asm(`asm "B{${t}} B>boc PUSHREF"`); }); diff --git a/src/generator/writers/writeContract.ts b/src/generator/writers/writeContract.ts index 9433ef1f5..e45cb0722 100644 --- a/src/generator/writers/writeContract.ts +++ b/src/generator/writers/writeContract.ts @@ -122,13 +122,13 @@ export function writeInit( // Generate self initial tensor const initValues: string[] = []; - for (let i = 0; i < t.fields.length; i++) { + t.fields.forEach((tField) => { let init = "null()"; - if (t.fields[i].default !== undefined) { - init = writeValue(t.fields[i].default!, ctx); + if (tField.default !== undefined) { + init = writeValue(tField.default!, ctx); } initValues.push(init); - } + }); if (initValues.length > 0) { // Special case for empty contracts ctx.append( @@ -147,7 +147,7 @@ export function writeInit( // Return result if ( init.ast.statements.length === 0 || - init.ast.statements[init.ast.statements.length - 1].kind !== + init.ast.statements[init.ast.statements.length - 1]!.kind !== "statement_return" ) { ctx.append(`return ${returns};`); diff --git a/src/generator/writers/writeExpression.spec.ts b/src/generator/writers/writeExpression.spec.ts index d3de50c0b..19b9bd17f 100644 --- a/src/generator/writers/writeExpression.spec.ts +++ b/src/generator/writers/writeExpression.spec.ts @@ -91,7 +91,9 @@ describe("writeExpression", () => { const wCtx = new WriterContext(ctx, "Contract1"); wCtx.fun("$main", () => { wCtx.body(() => { - expect(writeExpression(s.expression, wCtx)).toBe(golden[i]); + expect(writeExpression(s.expression, wCtx)).toBe( + golden[i]!, + ); }); }); i++; diff --git a/src/generator/writers/writeExpression.ts b/src/generator/writers/writeExpression.ts index 6e5cf7cb2..7d959afe7 100644 --- a/src/generator/writers/writeExpression.ts +++ b/src/generator/writers/writeExpression.ts @@ -126,7 +126,7 @@ export function writeValue(val: Value, wCtx: WriterContext): string { wCtx.used(id); return `${id}()`; } - if (typeof val === "object" && val !== null && "$tactStruct" in val) { + if (typeof val === "object" && "$tactStruct" in val) { // this is a struct value const structDescription = getType( wCtx.ctx, @@ -137,7 +137,7 @@ export function writeValue(val: Value, wCtx: WriterContext): string { wCtx.used(id); const fieldValues = structDescription.fields.map((field) => { if (field.name in val) { - return writeValue(val[field.name], wCtx); + return writeValue(val[field.name]!, wCtx); } else { throw Error( `Struct value is missing a field: ${field.name}`, @@ -151,9 +151,7 @@ export function writeValue(val: Value, wCtx: WriterContext): string { } export function writePathExpression(path: AstId[]): string { - return [funcIdOf(path[0].text), ...path.slice(1).map((id) => id.text)].join( - `'`, - ); + return [funcIdOf(idText(path[0]!)), ...path.slice(1).map(idText)].join(`'`); } export function writeExpression(f: AstExpression, wCtx: WriterContext): string { @@ -360,43 +358,11 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string { } // Other ops - let op: string; - if (f.op === "*") { - op = "*"; - } else if (f.op === "/") { - op = "/"; - } else if (f.op === "%") { - op = "%"; - } else if (f.op === "+") { - op = "+"; - } else if (f.op === "-") { - op = "-"; - } else if (f.op === "<") { - op = "<"; - } else if (f.op === "<=") { - op = "<="; - } else if (f.op === ">") { - op = ">"; - } else if (f.op === ">=") { - op = ">="; - } else if (f.op === "<<") { - op = "<<"; - } else if (f.op === ">>") { - op = ">>"; - } else if (f.op === "&") { - op = "&"; - } else if (f.op === "|") { - op = "|"; - } else if (f.op === "^") { - op = "^"; - } else { - throwCompilationError(`Unknown binary operator: ${f.op}`, f.loc); - } return ( "(" + writeExpression(f.left, wCtx) + " " + - op + + f.op + " " + writeExpression(f.right, wCtx) + ")" @@ -410,37 +376,37 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string { if (f.kind === "op_unary") { // NOTE: Logical not is written as a bitwise not - if (f.op === "!") { - return "(~ " + writeExpression(f.operand, wCtx) + ")"; - } + switch (f.op) { + case "!": { + return "(~ " + writeExpression(f.operand, wCtx) + ")"; + } - if (f.op === "~") { - return "(~ " + writeExpression(f.operand, wCtx) + ")"; - } + case "~": { + return "(~ " + writeExpression(f.operand, wCtx) + ")"; + } - if (f.op === "-") { - return "(- " + writeExpression(f.operand, wCtx) + ")"; - } + case "-": { + return "(- " + writeExpression(f.operand, wCtx) + ")"; + } - if (f.op === "+") { - return "(+ " + writeExpression(f.operand, wCtx) + ")"; - } + case "+": { + return "(+ " + writeExpression(f.operand, wCtx) + ")"; + } - // NOTE: Assert function that ensures that the value is not null - if (f.op === "!!") { - const t = getExpType(wCtx.ctx, f.operand); - if (t.kind === "ref") { - const tt = getType(wCtx.ctx, t.name); - if (tt.kind === "struct") { - return `${ops.typeNotNull(tt.name, wCtx)}(${writeExpression(f.operand, wCtx)})`; + // NOTE: Assert function that ensures that the value is not null + case "!!": { + const t = getExpType(wCtx.ctx, f.operand); + if (t.kind === "ref") { + const tt = getType(wCtx.ctx, t.name); + if (tt.kind === "struct") { + return `${ops.typeNotNull(tt.name, wCtx)}(${writeExpression(f.operand, wCtx)})`; + } } - } - wCtx.used("__tact_not_null"); - return `${wCtx.used("__tact_not_null")}(${writeExpression(f.operand, wCtx)})`; + wCtx.used("__tact_not_null"); + return `${wCtx.used("__tact_not_null")}(${writeExpression(f.operand, wCtx)})`; + } } - - throwCompilationError(`Unknown unary operator: ${f.op}`, f.loc); } // @@ -452,8 +418,8 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string { // Resolve the type of the expression const src = getExpType(wCtx.ctx, f.aggregate); if ( - src === null || - ((src.kind !== "ref" || src.optional) && src.kind !== "ref_bounced") + (src.kind !== "ref" || src.optional) && + src.kind !== "ref_bounced" ) { throwCompilationError( `Cannot access field of non-struct type: "${printTypeRef(src)}"`, @@ -470,8 +436,8 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string { fields = fields.slice(0, srcT.partialFieldCount); } - const field = fields.find((v) => eqNames(v.name, f.field))!; - const cst = srcT.constants.find((v) => eqNames(v.name, f.field))!; + const field = fields.find((v) => eqNames(v.name, f.field)); + const cst = srcT.constants.find((v) => eqNames(v.name, f.field)); if (!field && !cst) { throwCompilationError( `Cannot find field ${idTextErr(f.field)} in struct ${idTextErr(srcT.name)}`, @@ -500,7 +466,7 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string { // Getter instead of direct field access return `${ops.typeField(srcT.name, field.name, wCtx)}(${writeExpression(f.aggregate, wCtx)})`; } else { - return writeValue(cst.value!, wCtx); + return writeValue(cst!.value!, wCtx); } } @@ -534,7 +500,7 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string { "(" + f.args .map((a, i) => - writeCastedExpression(a, sf.params[i].type, wCtx), + writeCastedExpression(a, sf.params[i]!.type, wCtx), ) .join(", ") + ")" @@ -576,12 +542,6 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string { if (f.kind === "method_call") { // Resolve source type const src = getExpType(wCtx.ctx, f.self); - if (src === null) { - throwCompilationError( - `Cannot call function of non - direct type: "${printTypeRef(src)}"`, - f.loc, - ); - } // Reference type if (src.kind === "ref") { @@ -625,20 +585,20 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string { // Render arguments let renderedArguments = f.args.map((a, i) => - writeCastedExpression(a, ff.params[i].type, wCtx), + writeCastedExpression(a, ff.params[i]!.type, wCtx), ); // Hack to replace a single struct argument to a tensor wrapper since otherwise // func would convert (int) type to just int and break mutating functions if (ff.isMutating) { if (f.args.length === 1) { - const t = getExpType(wCtx.ctx, f.args[0]); + const t = getExpType(wCtx.ctx, f.args[0]!); if (t.kind === "ref") { const tt = getType(wCtx.ctx, t.name); if ( (tt.kind === "contract" || tt.kind === "struct") && - ff.params[0].type.kind === "ref" && - !ff.params[0].type.optional + ff.params[0]!.type.kind === "ref" && + !ff.params[0]!.type.optional ) { renderedArguments = [ `${ops.typeTensorCast(tt.name, wCtx)}(${renderedArguments[0]})`, @@ -694,7 +654,7 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string { if (f.kind === "init_of") { const type = getType(wCtx.ctx, f.contract); - return `${ops.contractInitChild(idText(f.contract), wCtx)}(${["__tact_context_sys", ...f.args.map((a, i) => writeCastedExpression(a, type.init!.params[i].type, wCtx))].join(", ")})`; + return `${ops.contractInitChild(idText(f.contract), wCtx)}(${["__tact_context_sys", ...f.args.map((a, i) => writeCastedExpression(a, type.init!.params[i]!.type, wCtx))].join(", ")})`; } // diff --git a/src/generator/writers/writeFunction.ts b/src/generator/writers/writeFunction.ts index 5a9013f31..8597f33df 100644 --- a/src/generator/writers/writeFunction.ts +++ b/src/generator/writers/writeFunction.ts @@ -74,369 +74,388 @@ export function writeStatement( returns: TypeRef | null, ctx: WriterContext, ) { - if (f.kind === "statement_return") { - if (f.expression) { - // Format expression - const result = writeCastedExpression(f.expression, returns!, ctx); + switch (f.kind) { + case "statement_return": { + if (f.expression) { + // Format expression + const result = writeCastedExpression( + f.expression, + returns!, + ctx, + ); - // Return - if (self) { - ctx.append(`return (${self}, ${result});`); - } else { - ctx.append(`return ${result};`); - } - } else { - if (self) { - ctx.append(`return (${self}, ());`); + // Return + if (self) { + ctx.append(`return (${self}, ${result});`); + } else { + ctx.append(`return ${result};`); + } } else { - ctx.append(`return ();`); + if (self) { + ctx.append(`return (${self}, ());`); + } else { + ctx.append(`return ();`); + } } - } - return; - } else if (f.kind === "statement_let") { - // Underscore name case - if (isWildcard(f.name)) { - ctx.append(`${writeExpression(f.expression, ctx)};`); return; } + case "statement_let": { + // Underscore name case + if (isWildcard(f.name)) { + ctx.append(`${writeExpression(f.expression, ctx)};`); + return; + } - // Contract/struct case - const t = - f.type === null - ? getExpType(ctx.ctx, f.expression) - : resolveTypeRef(ctx.ctx, f.type); + // Contract/struct case + const t = + f.type === null + ? getExpType(ctx.ctx, f.expression) + : resolveTypeRef(ctx.ctx, f.type); + + if (t.kind === "ref") { + const tt = getType(ctx.ctx, t.name); + if (tt.kind === "contract" || tt.kind === "struct") { + if (t.optional) { + ctx.append( + `tuple ${funcIdOf(f.name)} = ${writeCastedExpression(f.expression, t, ctx)};`, + ); + } else { + ctx.append( + `var ${resolveFuncTypeUnpack(t, funcIdOf(f.name), ctx)} = ${writeCastedExpression(f.expression, t, ctx)};`, + ); + } + return; + } + } - if (t.kind === "ref") { - const tt = getType(ctx.ctx, t.name); - if (tt.kind === "contract" || tt.kind === "struct") { - if (t.optional) { - ctx.append( - `tuple ${funcIdOf(f.name)} = ${writeCastedExpression(f.expression, t, ctx)};`, - ); - } else { + ctx.append( + `${resolveFuncType(t, ctx)} ${funcIdOf(f.name)} = ${writeCastedExpression(f.expression, t, ctx)};`, + ); + return; + } + case "statement_assign": { + // Prepare lvalue + const lvaluePath = tryExtractPath(f.path); + if (lvaluePath === null) { + // typechecker is supposed to catch this + throwInternalCompilerError( + `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + f.path.loc, + ); + } + const path = writePathExpression(lvaluePath); + + // Contract/struct case + const t = getExpType(ctx.ctx, f.path); + if (t.kind === "ref") { + const tt = getType(ctx.ctx, t.name); + if (tt.kind === "contract" || tt.kind === "struct") { ctx.append( - `var ${resolveFuncTypeUnpack(t, funcIdOf(f.name), ctx)} = ${writeCastedExpression(f.expression, t, ctx)};`, + `${resolveFuncTypeUnpack(t, path, ctx)} = ${writeCastedExpression(f.expression, t, ctx)};`, ); + return; } - return; } - } - ctx.append( - `${resolveFuncType(t, ctx)} ${funcIdOf(f.name)} = ${writeCastedExpression(f.expression, t, ctx)};`, - ); - return; - } else if (f.kind === "statement_assign") { - // Prepare lvalue - const lvaluePath = tryExtractPath(f.path); - if (lvaluePath === null) { - // typechecker is supposed to catch this - throwInternalCompilerError( - `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, - f.path.loc, + ctx.append( + `${path} = ${writeCastedExpression(f.expression, t, ctx)};`, ); + return; } - const path = writePathExpression(lvaluePath); - - // Contract/struct case - const t = getExpType(ctx.ctx, f.path); - if (t.kind === "ref") { - const tt = getType(ctx.ctx, t.name); - if (tt.kind === "contract" || tt.kind === "struct") { - ctx.append( - `${resolveFuncTypeUnpack(t, `${path}`, ctx)} = ${writeCastedExpression(f.expression, t, ctx)};`, + case "statement_augmentedassign": { + const lvaluePath = tryExtractPath(f.path); + if (lvaluePath === null) { + // typechecker is supposed to catch this + throwInternalCompilerError( + `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + f.path.loc, ); - return; } - } - - ctx.append(`${path} = ${writeCastedExpression(f.expression, t, ctx)};`); - return; - } else if (f.kind === "statement_augmentedassign") { - const lvaluePath = tryExtractPath(f.path); - if (lvaluePath === null) { - // typechecker is supposed to catch this - throwInternalCompilerError( - `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, - f.path.loc, + const path = writePathExpression(lvaluePath); + const t = getExpType(ctx.ctx, f.path); + ctx.append( + `${path} = ${cast(t, t, `${path} ${f.op} ${writeExpression(f.expression, ctx)}`, ctx)};`, ); + return; } - const path = writePathExpression(lvaluePath); - const t = getExpType(ctx.ctx, f.path); - ctx.append( - `${path} = ${cast(t, t, `${path} ${f.op} ${writeExpression(f.expression, ctx)}`, ctx)};`, - ); - return; - } else if (f.kind === "statement_condition") { - writeCondition(f, self, false, returns, ctx); - return; - } else if (f.kind === "statement_expression") { - const exp = writeExpression(f.expression, ctx); - ctx.append(`${exp};`); - return; - } else if (f.kind === "statement_while") { - ctx.append(`while (${writeExpression(f.condition, ctx)}) {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } - }); - ctx.append(`}`); - return; - } else if (f.kind === "statement_until") { - ctx.append(`do {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } - }); - ctx.append(`} until (${writeExpression(f.condition, ctx)});`); - return; - } else if (f.kind === "statement_repeat") { - ctx.append(`repeat (${writeExpression(f.iterations, ctx)}) {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } - }); - ctx.append(`}`); - return; - } else if (f.kind === "statement_try") { - ctx.append(`try {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } - }); - ctx.append("} catch (_) { }"); - return; - } else if (f.kind === "statement_try_catch") { - ctx.append(`try {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } - }); - if (isWildcard(f.catchName)) { - ctx.append(`} catch (_) {`); - } else { - ctx.append(`} catch (_, ${funcIdOf(f.catchName)}) {`); + case "statement_condition": { + writeCondition(f, self, false, returns, ctx); + return; } - ctx.inIndent(() => { - for (const s of f.catchStatements) { - writeStatement(s, self, returns, ctx); - } - }); - ctx.append(`}`); - return; - } else if (f.kind === "statement_foreach") { - const mapPath = tryExtractPath(f.map); - if (mapPath === null) { - // typechecker is supposed to catch this - throwInternalCompilerError( - `foreach is only allowed over maps that are path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, - f.map.loc, - ); + case "statement_expression": { + const exp = writeExpression(f.expression, ctx); + ctx.append(`${exp};`); + return; } - const path = writePathExpression(mapPath); - - const t = getExpType(ctx.ctx, f.map); - if (t.kind !== "map") { - throw Error("Unknown map type"); + case "statement_while": { + ctx.append(`while (${writeExpression(f.condition, ctx)}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + }); + ctx.append(`}`); + return; } - - const flag = freshIdentifier("flag"); - const key = isWildcard(f.keyName) - ? freshIdentifier("underscore") - : funcIdOf(f.keyName); - const value = isWildcard(f.valueName) - ? freshIdentifier("underscore") - : funcIdOf(f.valueName); - - // Handle Int key - if (t.key === "Int") { - let bits = 257; - let kind = "int"; - if (t.keyAs && t.keyAs.startsWith("int")) { - bits = parseInt(t.keyAs.slice(3), 10); - } else if (t.keyAs && t.keyAs.startsWith("uint")) { - bits = parseInt(t.keyAs.slice(4), 10); - kind = "uint"; + case "statement_until": { + ctx.append(`do {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + }); + ctx.append(`} until (${writeExpression(f.condition, ctx)});`); + return; + } + case "statement_repeat": { + ctx.append(`repeat (${writeExpression(f.iterations, ctx)}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + }); + ctx.append(`}`); + return; + } + case "statement_try": { + ctx.append(`try {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + }); + ctx.append("} catch (_) { }"); + return; + } + case "statement_try_catch": { + ctx.append(`try {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + }); + if (isWildcard(f.catchName)) { + ctx.append(`} catch (_) {`); + } else { + ctx.append(`} catch (_, ${funcIdOf(f.catchName)}) {`); } - if (t.value === "Int") { - let vBits = 257; - let vKind = "int"; - if (t.valueAs && t.valueAs.startsWith("int")) { - vBits = parseInt(t.valueAs.slice(3), 10); - } else if (t.valueAs && t.valueAs.startsWith("uint")) { - vBits = parseInt(t.valueAs.slice(4), 10); - vKind = "uint"; + ctx.inIndent(() => { + for (const s of f.catchStatements) { + writeStatement(s, self, returns, ctx); } - - ctx.append( - `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_${vKind}`)}(${path}, ${bits}, ${vBits});`, + }); + ctx.append(`}`); + return; + } + case "statement_foreach": { + const mapPath = tryExtractPath(f.map); + if (mapPath === null) { + // typechecker is supposed to catch this + throwInternalCompilerError( + `foreach is only allowed over maps that are path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + f.map.loc, ); - ctx.append(`while (${flag}) {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); + } + const path = writePathExpression(mapPath); + + const t = getExpType(ctx.ctx, f.map); + if (t.kind !== "map") { + throw Error("Unknown map type"); + } + + const flag = freshIdentifier("flag"); + const key = isWildcard(f.keyName) + ? freshIdentifier("underscore") + : funcIdOf(f.keyName); + const value = isWildcard(f.valueName) + ? freshIdentifier("underscore") + : funcIdOf(f.valueName); + + // Handle Int key + if (t.key === "Int") { + let bits = 257; + let kind = "int"; + if (t.keyAs && t.keyAs.startsWith("int")) { + bits = parseInt(t.keyAs.slice(3), 10); + } else if (t.keyAs && t.keyAs.startsWith("uint")) { + bits = parseInt(t.keyAs.slice(4), 10); + kind = "uint"; + } + if (t.value === "Int") { + let vBits = 257; + let vKind = "int"; + if (t.valueAs && t.valueAs.startsWith("int")) { + vBits = parseInt(t.valueAs.slice(3), 10); + } else if (t.valueAs && t.valueAs.startsWith("uint")) { + vBits = parseInt(t.valueAs.slice(4), 10); + vKind = "uint"; } + ctx.append( - `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_${vKind}`)}(${path}, ${bits}, ${key}, ${vBits});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_${vKind}`)}(${path}, ${bits}, ${vBits});`, ); - }); - ctx.append(`}`); - } else if (t.value === "Bool") { - ctx.append( - `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_int`)}(${path}, ${bits}, 1);`, - ); - ctx.append(`while (${flag}) {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_${vKind}`)}(${path}, ${bits}, ${key}, ${vBits});`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Bool") { ctx.append( - `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_int`)}(${path}, ${bits}, ${key}, 1);`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_int`)}(${path}, ${bits}, 1);`, ); - }); - ctx.append(`}`); - } else if (t.value === "Cell") { - ctx.append( - `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, - ); - ctx.append(`while (${flag}) {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_int`)}(${path}, ${bits}, ${key}, 1);`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Cell") { ctx.append( - `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${key});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, ); - }); - ctx.append(`}`); - } else if (t.value === "Address") { - ctx.append( - `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_slice`)}(${path}, ${bits});`, - ); - ctx.append(`while (${flag}) {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${key});`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Address") { ctx.append( - `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_slice`)}(${path}, ${bits}, ${key});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_slice`)}(${path}, ${bits});`, ); - }); - ctx.append(`}`); - } else { - // value is struct - ctx.append( - `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, - ); - ctx.append(`while (${flag}) {`); - ctx.inIndent(() => { - ctx.append( - `var ${resolveFuncTypeUnpack(t.value, funcIdOf(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${value}));`, - ); - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_slice`)}(${path}, ${bits}, ${key});`, + ); + }); + ctx.append(`}`); + } else { + // value is struct ctx.append( - `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${key});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, ); - }); - ctx.append(`}`); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + ctx.append( + `var ${resolveFuncTypeUnpack(t.value, funcIdOf(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${value}));`, + ); + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${key});`, + ); + }); + ctx.append(`}`); + } } - } - // Handle address key - if (t.key === "Address") { - if (t.value === "Int") { - let vBits = 257; - let vKind = "int"; - if (t.valueAs && t.valueAs.startsWith("int")) { - vBits = parseInt(t.valueAs.slice(3), 10); - } else if (t.valueAs && t.valueAs.startsWith("uint")) { - vBits = parseInt(t.valueAs.slice(4), 10); - vKind = "uint"; - } - ctx.append( - `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_${vKind}`)}(${path}, 267, ${vBits});`, - ); - ctx.append(`while (${flag}) {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } - ctx.append( - `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_${vKind}`)}(${path}, 267, ${key}, ${vBits});`, - ); - }); - ctx.append(`}`); - } else if (t.value === "Bool") { - ctx.append( - `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_int`)}(${path}, 267, 1);`, - ); - ctx.append(`while (${flag}) {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); + // Handle address key + if (t.key === "Address") { + if (t.value === "Int") { + let vBits = 257; + let vKind = "int"; + if (t.valueAs && t.valueAs.startsWith("int")) { + vBits = parseInt(t.valueAs.slice(3), 10); + } else if (t.valueAs && t.valueAs.startsWith("uint")) { + vBits = parseInt(t.valueAs.slice(4), 10); + vKind = "uint"; } ctx.append( - `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_int`)}(${path}, 267, ${key}, 1);`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_${vKind}`)}(${path}, 267, ${vBits});`, ); - }); - ctx.append(`}`); - } else if (t.value === "Cell") { - ctx.append( - `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, - ); - ctx.append(`while (${flag}) {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_${vKind}`)}(${path}, 267, ${key}, ${vBits});`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Bool") { ctx.append( - `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${key});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_int`)}(${path}, 267, 1);`, ); - }); - ctx.append(`}`); - } else if (t.value === "Address") { - ctx.append( - `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_slice`)}(${path}, 267);`, - ); - ctx.append(`while (${flag}) {`); - ctx.inIndent(() => { - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_int`)}(${path}, 267, ${key}, 1);`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Cell") { ctx.append( - `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_slice`)}(${path}, 267, ${key});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, ); - }); - ctx.append(`}`); - } else { - // value is struct - ctx.append( - `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, - ); - ctx.append(`while (${flag}) {`); - ctx.inIndent(() => { + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${key});`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Address") { ctx.append( - `var ${resolveFuncTypeUnpack(t.value, funcIdOf(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${value}));`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_slice`)}(${path}, 267);`, ); - for (const s of f.statements) { - writeStatement(s, self, returns, ctx); - } + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_slice`)}(${path}, 267, ${key});`, + ); + }); + ctx.append(`}`); + } else { + // value is struct ctx.append( - `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${key});`, + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, ); - }); - ctx.append(`}`); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + ctx.append( + `var ${resolveFuncTypeUnpack(t.value, funcIdOf(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${value}));`, + ); + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${key});`, + ); + }); + ctx.append(`}`); + } } - } - return; + return; + } } throw Error("Unknown statement kind"); @@ -572,7 +591,7 @@ export function writeFunction(f: FunctionDescription, ctx: WriterContext) { if (f.self && f.returns.kind === "void" && f.isMutating) { if ( fd.statements.length === 0 || - fd.statements[fd.statements.length - 1].kind !== + fd.statements[fd.statements.length - 1]!.kind !== "statement_return" ) { ctx.append(`return (${returnsStr}, ());`); diff --git a/src/generator/writers/writeRouter.ts b/src/generator/writers/writeRouter.ts index 668754ae8..396ae6194 100644 --- a/src/generator/writers/writeRouter.ts +++ b/src/generator/writers/writeRouter.ts @@ -69,9 +69,6 @@ export function writeRouter( if (selector.kind !== "bounce-binary") throw Error("Invalid selector type: " + selector.kind); // Should not happen const allocation = getType(ctx.ctx, selector.type); - if (!allocation) - throw Error("Invalid allocation: " + selector.type); // Should not happen - ctx.append( `;; Bounced handler for ${selector.type} message`, ); @@ -309,7 +306,7 @@ export function writeReceiver( if ( f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1].kind !== + f.ast.statements[f.ast.statements.length - 1]!.kind !== "statement_return" ) { ctx.append(`return (${selfRes}, ());`); @@ -337,7 +334,7 @@ export function writeReceiver( if ( f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1].kind !== + f.ast.statements[f.ast.statements.length - 1]!.kind !== "statement_return" ) { ctx.append(`return (${selfRes}, ());`); @@ -366,7 +363,7 @@ export function writeReceiver( if ( f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1].kind !== + f.ast.statements[f.ast.statements.length - 1]!.kind !== "statement_return" ) { ctx.append(`return (${selfRes}, ());`); @@ -394,7 +391,7 @@ export function writeReceiver( if ( f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1].kind !== + f.ast.statements[f.ast.statements.length - 1]!.kind !== "statement_return" ) { ctx.append(`return (${selfRes}, ());`); @@ -408,7 +405,7 @@ export function writeReceiver( // Fallback if (selector.kind === "internal-fallback") { ctx.append( - `(${selfType}, ()) ${ops.receiveAny(self.name, selector.kind === "internal-fallback" ? "internal" : "external")}(${selfType} ${funcIdOf("self")}, slice ${funcIdOf(selector.name)}) impure inline {`, + `(${selfType}, ()) ${ops.receiveAny(self.name, "internal")}(${selfType} ${funcIdOf("self")}, slice ${funcIdOf(selector.name)}) impure inline {`, ); ctx.inIndent(() => { ctx.append(selfUnpack); @@ -419,7 +416,7 @@ export function writeReceiver( if ( f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1].kind !== + f.ast.statements[f.ast.statements.length - 1]!.kind !== "statement_return" ) { ctx.append(`return (${selfRes}, ());`); @@ -444,7 +441,7 @@ export function writeReceiver( if ( f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1].kind !== + f.ast.statements[f.ast.statements.length - 1]!.kind !== "statement_return" ) { ctx.append(`return (${selfRes}, ());`); @@ -477,7 +474,7 @@ export function writeReceiver( if ( f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1].kind !== + f.ast.statements[f.ast.statements.length - 1]!.kind !== "statement_return" ) { ctx.append(`return (${selfRes}, ());`); diff --git a/src/generator/writers/writeSerialization.ts b/src/generator/writers/writeSerialization.ts index de5853233..aca186622 100644 --- a/src/generator/writers/writeSerialization.ts +++ b/src/generator/writers/writeSerialization.ts @@ -1,5 +1,6 @@ import { contractErrors } from "../../abi/errors"; -import { ItemOrigin } from "../../grammar/grammar"; +import { throwInternalCompilerError } from "../../errors"; +import { dummySrcInfo, ItemOrigin } from "../../grammar/grammar"; import { AllocationCell, AllocationOperation } from "../../storage/operation"; import { StorageAllocation } from "../../storage/StorageAllocation"; import { getType } from "../../types/resolveDescriptors"; @@ -117,183 +118,196 @@ function writeSerializerField( const fieldName = `v'${f.name}`; const op = f.op; - if (op.kind === "int") { - if (op.optional) { - ctx.append( - `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_int(${fieldName}, ${op.bits}) : build_${gen}.store_int(false, 1);`, - ); - } else { - ctx.append( - `build_${gen} = build_${gen}.store_int(${fieldName}, ${op.bits});`, - ); - } - return; - } - if (op.kind === "uint") { - if (op.optional) { - ctx.append( - `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_uint(${fieldName}, ${op.bits}) : build_${gen}.store_int(false, 1);`, - ); - } else { - ctx.append( - `build_${gen} = build_${gen}.store_uint(${fieldName}, ${op.bits});`, - ); - } - return; - } - if (op.kind === "coins") { - if (op.optional) { - ctx.append( - `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_coins(${fieldName}) : build_${gen}.store_int(false, 1);`, - ); - } else { - ctx.append( - `build_${gen} = build_${gen}.store_coins(${fieldName});`, - ); - } - return; - } - if (op.kind === "boolean") { - if (op.optional) { - ctx.append( - `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_int(${fieldName}, 1) : build_${gen}.store_int(false, 1);`, - ); - } else { - ctx.append( - `build_${gen} = build_${gen}.store_int(${fieldName}, 1);`, - ); - } - return; - } - if (op.kind === "address") { - if (op.optional) { - ctx.used(`__tact_store_address_opt`); - ctx.append( - `build_${gen} = __tact_store_address_opt(build_${gen}, ${fieldName});`, - ); - } else { - ctx.used(`__tact_store_address`); - ctx.append( - `build_${gen} = __tact_store_address(build_${gen}, ${fieldName});`, - ); - } - return; - } - if (op.kind === "cell") { - if (op.format === "default") { + switch (op.kind) { + case "int": { if (op.optional) { ctx.append( - `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(${fieldName}) : build_${gen}.store_int(false, 1);`, + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_int(${fieldName}, ${op.bits}) : build_${gen}.store_int(false, 1);`, ); } else { ctx.append( - `build_${gen} = build_${gen}.store_ref(${fieldName});`, + `build_${gen} = build_${gen}.store_int(${fieldName}, ${op.bits});`, ); } - } else if (op.format === "remainder") { + return; + } + case "uint": { if (op.optional) { - throw Error("Impossible"); + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_uint(${fieldName}, ${op.bits}) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_uint(${fieldName}, ${op.bits});`, + ); } - ctx.append( - `build_${gen} = build_${gen}.store_slice(${fieldName}.begin_parse());`, - ); - } else { - throw Error("Impossible"); + return; } - return; - } - if (op.kind === "slice") { - if (op.format === "default") { + case "coins": { if (op.optional) { ctx.append( - `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(begin_cell().store_slice(${fieldName}).end_cell()) : build_${gen}.store_int(false, 1);`, + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_coins(${fieldName}) : build_${gen}.store_int(false, 1);`, ); } else { ctx.append( - `build_${gen} = build_${gen}.store_ref(begin_cell().store_slice(${fieldName}).end_cell());`, + `build_${gen} = build_${gen}.store_coins(${fieldName});`, ); } - } else if (op.format === "remainder") { + return; + } + case "boolean": { if (op.optional) { - throw Error("Impossible"); + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_int(${fieldName}, 1) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_int(${fieldName}, 1);`, + ); } - ctx.append( - `build_${gen} = build_${gen}.store_slice(${fieldName});`, - ); - } else { - throw Error("Impossible"); + return; } - return; - } - if (op.kind === "builder") { - if (op.format === "default") { + case "address": { if (op.optional) { + ctx.used(`__tact_store_address_opt`); ctx.append( - `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(begin_cell().store_slice(${fieldName}.end_cell().begin_parse()).end_cell()) : build_${gen}.store_int(false, 1);`, + `build_${gen} = __tact_store_address_opt(build_${gen}, ${fieldName});`, ); } else { + ctx.used(`__tact_store_address`); ctx.append( - `build_${gen} = build_${gen}.store_ref(begin_cell().store_slice(${fieldName}.end_cell().begin_parse()).end_cell());`, + `build_${gen} = __tact_store_address(build_${gen}, ${fieldName});`, ); } - } else if (op.format === "remainder") { - if (op.optional) { - throw Error("Impossible"); + return; + } + case "cell": { + switch (op.format) { + case "default": + { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(${fieldName}) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_ref(${fieldName});`, + ); + } + } + break; + case "remainder": + { + if (op.optional) { + throw Error("Impossible"); + } + ctx.append( + `build_${gen} = build_${gen}.store_slice(${fieldName}.begin_parse());`, + ); + } + break; } - ctx.append( - `build_${gen} = build_${gen}.store_slice(${fieldName}.end_cell().begin_parse());`, - ); - } else { - throw Error("Impossible"); + return; } - return; - } - if (op.kind === "string") { - if (op.optional) { - ctx.append( - `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(begin_cell().store_slice(${fieldName}).end_cell()) : build_${gen}.store_int(false, 1);`, - ); - } else { - ctx.append( - `build_${gen} = build_${gen}.store_ref(begin_cell().store_slice(${fieldName}).end_cell());`, - ); + case "slice": { + switch (op.format) { + case "default": + { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(begin_cell().store_slice(${fieldName}).end_cell()) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_ref(begin_cell().store_slice(${fieldName}).end_cell());`, + ); + } + } + break; + case "remainder": { + if (op.optional) { + throw Error("Impossible"); + } + ctx.append( + `build_${gen} = build_${gen}.store_slice(${fieldName});`, + ); + } + } + return; } - return; - } - if (op.kind === "fixed-bytes") { - if (op.optional) { - ctx.append( - `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_slice(${fieldName}) : build_${gen}.store_int(false, 1);`, - ); - } else { - ctx.append( - `build_${gen} = build_${gen}.store_slice(${fieldName});`, - ); + case "builder": { + switch (op.format) { + case "default": + { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(begin_cell().store_slice(${fieldName}.end_cell().begin_parse()).end_cell()) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_ref(begin_cell().store_slice(${fieldName}.end_cell().begin_parse()).end_cell());`, + ); + } + } + break; + case "remainder": { + if (op.optional) { + throw Error("Impossible"); + } + ctx.append( + `build_${gen} = build_${gen}.store_slice(${fieldName}.end_cell().begin_parse());`, + ); + } + } + return; } - return; - } - if (op.kind === "map") { - ctx.append(`build_${gen} = build_${gen}.store_dict(${fieldName});`); - return; - } - if (op.kind === "struct") { - if (op.ref) { - throw Error("Not implemented"); + case "string": { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(begin_cell().store_slice(${fieldName}).end_cell()) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_ref(begin_cell().store_slice(${fieldName}).end_cell());`, + ); + } + return; } - if (op.optional) { - ctx.append( - `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).${ops.writer(op.type, ctx)}(${ops.typeNotNull(op.type, ctx)}(${fieldName})) : build_${gen}.store_int(false, 1);`, - ); - } else { - const ff = getType(ctx.ctx, op.type).fields.map((f) => f.abi); - ctx.append( - `build_${gen} = ${ops.writer(op.type, ctx)}(build_${gen}, ${resolveFuncTypeFromAbiUnpack(fieldName, ff, ctx)});`, - ); + case "fixed-bytes": { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_slice(${fieldName}) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_slice(${fieldName});`, + ); + } + return; + } + case "map": { + ctx.append(`build_${gen} = build_${gen}.store_dict(${fieldName});`); + return; + } + case "struct": { + if (op.ref) { + throw Error("Not implemented"); + } + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).${ops.writer(op.type, ctx)}(${ops.typeNotNull(op.type, ctx)}(${fieldName})) : build_${gen}.store_int(false, 1);`, + ); + } else { + const ff = getType(ctx.ctx, op.type).fields.map((f) => f.abi); + ctx.append( + `build_${gen} = ${ops.writer(op.type, ctx)}(build_${gen}, ${resolveFuncTypeFromAbiUnpack(fieldName, ff, ctx)});`, + ); + } + return; } - return; } - throw Error(`Unsupported field kind: ${op.kind}`); + throwInternalCompilerError(`Unsupported field kind`, dummySrcInfo); } // @@ -454,162 +468,180 @@ function writeFieldParser( const op = f.op; const varName = `var v'${f.name}`; - // Handle int - if (op.kind === "int") { - if (op.optional) { - ctx.append( - `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_int(${op.bits}) : null();`, - ); - } else { - ctx.append(`${varName} = sc_${gen}~load_int(${op.bits});`); - } - return; - } - if (op.kind === "uint") { - if (op.optional) { - ctx.append( - `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_uint(${op.bits}) : null();`, - ); - } else { - ctx.append(`${varName} = sc_${gen}~load_uint(${op.bits});`); - } - return; - } - if (op.kind === "coins") { - if (op.optional) { - ctx.append( - `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_coins() : null();`, - ); - } else { - ctx.append(`${varName} = sc_${gen}~load_coins();`); - } - return; - } - if (op.kind === "boolean") { - if (op.optional) { - ctx.append( - `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_int(1) : null();`, - ); - } else { - ctx.append(`${varName} = sc_${gen}~load_int(1);`); - } - return; - } - if (op.kind === "address") { - if (op.optional) { - ctx.used(`__tact_load_address_opt`); - ctx.append(`${varName} = sc_${gen}~__tact_load_address_opt();`); - } else { - ctx.used(`__tact_load_address`); - ctx.append(`${varName} = sc_${gen}~__tact_load_address();`); - } - return; - } - if (op.kind === "cell") { - if (op.optional) { - if (op.format !== "default") { - throw new Error(`Impossible`); + switch (op.kind) { + case "int": { + if (op.optional) { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_int(${op.bits}) : null();`, + ); + } else { + ctx.append(`${varName} = sc_${gen}~load_int(${op.bits});`); } - ctx.append( - `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_ref() : null();`, - ); - } else { - if (op.format === "default") { - ctx.append(`${varName} = sc_${gen}~load_ref();`); - } else if (op.format === "remainder") { + return; + } + case "uint": { + if (op.optional) { ctx.append( - `${varName} = begin_cell().store_slice(sc_${gen}).end_cell();`, + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_uint(${op.bits}) : null();`, ); } else { - throw new Error(`Impossible`); + ctx.append(`${varName} = sc_${gen}~load_uint(${op.bits});`); } + return; } - return; - } - if (op.kind === "slice") { - if (op.optional) { - if (op.format !== "default") { - throw new Error(`Impossible`); + case "coins": { + if (op.optional) { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_coins() : null();`, + ); + } else { + ctx.append(`${varName} = sc_${gen}~load_coins();`); } - ctx.append( - `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_ref().begin_parse() : null();`, - ); - } else { - if (op.format === "default") { - ctx.append(`${varName} = sc_${gen}~load_ref().begin_parse();`); - } else if (op.format === "remainder") { - ctx.append(`${varName} = sc_${gen};`); + return; + } + case "boolean": { + if (op.optional) { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_int(1) : null();`, + ); } else { - throw new Error(`Impossible`); + ctx.append(`${varName} = sc_${gen}~load_int(1);`); } + return; } - return; - } - if (op.kind === "builder") { - if (op.optional) { - if (op.format !== "default") { - throw new Error(`Impossible`); + case "address": { + if (op.optional) { + ctx.used(`__tact_load_address_opt`); + ctx.append(`${varName} = sc_${gen}~__tact_load_address_opt();`); + } else { + ctx.used(`__tact_load_address`); + ctx.append(`${varName} = sc_${gen}~__tact_load_address();`); } - ctx.append( - `${varName} = sc_${gen}~load_int(1) ? begin_cell().store_slice(sc_${gen}~load_ref().begin_parse()) : null();`, - ); - } else { - if (op.format === "default") { + return; + } + case "cell": { + if (op.optional) { + if (op.format !== "default") { + throw new Error(`Impossible`); + } ctx.append( - `${varName} = begin_cell().store_slice(sc_${gen}~load_ref().begin_parse());`, + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_ref() : null();`, ); - } else if (op.format === "remainder") { - ctx.append(`${varName} = begin_cell().store_slice(sc_${gen});`); } else { - throw new Error(`Impossible`); + switch (op.format) { + case "default": + { + ctx.append(`${varName} = sc_${gen}~load_ref();`); + } + break; + case "remainder": { + ctx.append( + `${varName} = begin_cell().store_slice(sc_${gen}).end_cell();`, + ); + } + } } + return; } - return; - } - if (op.kind === "string") { - if (op.optional) { - ctx.append( - `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_ref().begin_parse() : null();`, - ); - } else { - ctx.append(`${varName} = sc_${gen}~load_ref().begin_parse();`); - } - return; - } - if (op.kind === "fixed-bytes") { - if (op.optional) { - ctx.append( - `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_bits(${op.bytes * 8}) : null();`, - ); - } else { - ctx.append(`${varName} = sc_${gen}~load_bits(${op.bytes * 8});`); + case "slice": { + if (op.optional) { + if (op.format !== "default") { + throw new Error(`Impossible`); + } + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_ref().begin_parse() : null();`, + ); + } else { + switch (op.format) { + case "default": + { + ctx.append( + `${varName} = sc_${gen}~load_ref().begin_parse();`, + ); + } + break; + case "remainder": + { + ctx.append(`${varName} = sc_${gen};`); + } + break; + } + } + return; } - return; - } - if (op.kind === "map") { - ctx.append(`${varName} = sc_${gen}~load_dict();`); - return; - } - if (op.kind === "struct") { - if (op.optional) { - if (op.ref) { - throw Error("Not implemented"); + case "builder": { + if (op.optional) { + if (op.format !== "default") { + throw new Error(`Impossible`); + } + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? begin_cell().store_slice(sc_${gen}~load_ref().begin_parse()) : null();`, + ); } else { + switch (op.format) { + case "default": + { + ctx.append( + `${varName} = begin_cell().store_slice(sc_${gen}~load_ref().begin_parse());`, + ); + } + break; + case "remainder": + { + ctx.append( + `${varName} = begin_cell().store_slice(sc_${gen});`, + ); + } + break; + } + } + return; + } + case "string": { + if (op.optional) { ctx.append( - `${varName} = sc_${gen}~load_int(1) ? ${ops.typeAsOptional(op.type, ctx)}(sc_${gen}~${ops.reader(op.type, ctx)}()) : null();`, + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_ref().begin_parse() : null();`, ); + } else { + ctx.append(`${varName} = sc_${gen}~load_ref().begin_parse();`); } - } else { - if (op.ref) { - throw Error("Not implemented"); + return; + } + case "fixed-bytes": { + if (op.optional) { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_bits(${op.bytes * 8}) : null();`, + ); } else { ctx.append( - `${varName} = sc_${gen}~${ops.reader(op.type, ctx)}();`, + `${varName} = sc_${gen}~load_bits(${op.bytes * 8});`, ); } + return; + } + case "map": { + ctx.append(`${varName} = sc_${gen}~load_dict();`); + return; + } + case "struct": { + if (op.optional) { + if (op.ref) { + throw Error("Not implemented"); + } else { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? ${ops.typeAsOptional(op.type, ctx)}(sc_${gen}~${ops.reader(op.type, ctx)}()) : null();`, + ); + } + } else { + if (op.ref) { + throw Error("Not implemented"); + } else { + ctx.append( + `${varName} = sc_${gen}~${ops.reader(op.type, ctx)}();`, + ); + } + } + return; } - return; } - - throw Error(`Unsupported field kind: ${op.kind}`); } diff --git a/src/grammar/grammar.spec.ts b/src/grammar/grammar.spec.ts index 31b2d4b87..47616705c 100644 --- a/src/grammar/grammar.spec.ts +++ b/src/grammar/grammar.spec.ts @@ -4,7 +4,7 @@ import { loadCases } from "../utils/loadCases"; expect.addSnapshotSerializer({ test: (src) => src instanceof SrcInfo, - print: (src) => `${(src as SrcInfo).contents}`, + print: (src) => (src as SrcInfo).contents, }); describe("grammar", () => { diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts index dd4b25361..424bd4eb9 100644 --- a/src/grammar/grammar.ts +++ b/src/grammar/grammar.ts @@ -89,8 +89,8 @@ function unwrapOptNode( optional: IterationNode, f: (n: Node) => T, ): T | null { - const optNode = optional.children[0]; - return optNode ? f(optNode) : null; + const optNode = optional.children[0] as Node | undefined; + return optNode !== undefined ? f(optNode) : null; } const semantics = tactGrammar.createSemantics(); @@ -421,7 +421,7 @@ semantics.addOperation("astOfItem", { receiverBody, _rbrace, ) { - const optParam = optParameter.children[0]; + const optParam = optParameter.children[0] as Node | undefined; const selector: AstReceiverKind = optParam ? { kind: "external-simple", diff --git a/src/grammar/iterators.ts b/src/grammar/iterators.ts index 5f1c5f363..b7a5f5076 100644 --- a/src/grammar/iterators.ts +++ b/src/grammar/iterators.ts @@ -109,9 +109,7 @@ export function forEachExpression( case "function_def": case "contract_init": case "receiver": - if (node.statements) { - node.statements.forEach(traverseStatement); - } + node.statements.forEach(traverseStatement); break; case "contract": case "trait": @@ -319,11 +317,9 @@ export function foldExpressions( case "function_def": case "contract_init": case "receiver": - if (node.statements) { - node.statements.forEach((stmt) => { - acc = traverseStatement(acc, stmt); - }); - } + node.statements.forEach((stmt) => { + acc = traverseStatement(acc, stmt); + }); break; case "contract": case "trait": @@ -439,7 +435,7 @@ export function forEachStatement( case "function_def": case "contract_init": case "receiver": - if (node.statements) node.statements.forEach(traverseStatement); + node.statements.forEach(traverseStatement); break; case "contract": case "trait": @@ -561,11 +557,9 @@ export function foldStatements( case "function_def": case "contract_init": case "receiver": - if (node.statements) { - node.statements.forEach((stmt) => { - acc = traverseStatement(acc, stmt); - }); - } + node.statements.forEach((stmt) => { + acc = traverseStatement(acc, stmt); + }); break; case "contract": case "trait": diff --git a/src/grammar/store.ts b/src/grammar/store.ts index 1f5faf1bb..861e124be 100644 --- a/src/grammar/store.ts +++ b/src/grammar/store.ts @@ -67,21 +67,27 @@ export function openContext( const constants: AstConstantDef[] = []; for (const program of programs) { for (const item of program.items) { - if ( - item.kind === "struct_decl" || - item.kind === "message_decl" || - item.kind === "contract" || - item.kind === "trait" || - item.kind === "primitive_type_decl" - ) { - types.push(item); - } else if ( - item.kind === "function_def" || - item.kind === "native_function_decl" - ) { - functions.push(item); - } else if (item.kind === "constant_def") { - constants.push(item); + switch (item.kind) { + case "struct_decl": + case "message_decl": + case "contract": + case "trait": + case "primitive_type_decl": + { + types.push(item); + } + break; + case "function_def": + case "native_function_decl": + { + functions.push(item); + } + break; + case "constant_def": + { + constants.push(item); + } + break; } } } diff --git a/src/pipeline/build.ts b/src/pipeline/build.ts index ae1a9f8b2..b49182bfa 100644 --- a/src/pipeline/build.ts +++ b/src/pipeline/build.ts @@ -81,13 +81,12 @@ export async function build(args: { // Compile contracts let ok = true; const built: { - [key: string]: { - codeBoc: Buffer; - // codeFunc: string, - // codeFift: string, - // codeFiftDecompiled: string, - abi: string; - }; + [key: string]: + | { + codeBoc: Buffer; + abi: string; + } + | undefined; } = {}; for (const contract of getContracts(ctx)) { const pathAbi = project.resolve( @@ -234,14 +233,14 @@ export async function build(args: { Dictionary.Values.Cell(), ); const ct = getType(ctx, contract); - depends.set(ct.uid, Cell.fromBoc(built[ct.name].codeBoc)[0]); // Mine + depends.set(ct.uid, Cell.fromBoc(built[ct.name]!.codeBoc)[0]!); // Mine for (const c of ct.dependsOn) { const cd = built[c.name]; if (!cd) { - logger.error(` > ${cd}: no artifacts found`); + logger.error(` > ${c.name}: no artifacts found`); return false; } - depends.set(c.uid, Cell.fromBoc(cd.codeBoc)[0]); + depends.set(c.uid, Cell.fromBoc(cd.codeBoc)[0]!); } const systemCell = beginCell().storeDict(depends).endCell(); diff --git a/src/storage/allocator.ts b/src/storage/allocator.ts index fde6c2dcf..7e1213ad9 100644 --- a/src/storage/allocator.ts +++ b/src/storage/allocator.ts @@ -12,59 +12,59 @@ export function getOperationSize(src: AllocationOperationType): { bits: number; refs: number; } { - if (src.kind === "int" || src.kind === "uint") { - return { bits: src.bits + (src.optional ? 1 : 0), refs: 0 }; - } else if (src.kind === "coins") { - return { bits: 124 + (src.optional ? 1 : 0), refs: 0 }; - } else if (src.kind === "boolean") { - return { bits: 1 + (src.optional ? 1 : 0), refs: 0 }; - } else if (src.kind === "address") { - return { bits: 267, refs: 0 }; - } else if ( - src.kind === "cell" || - src.kind === "slice" || - src.kind === "builder" - ) { - if (src.format === "default") { - if (src.optional) { - return { bits: 1, refs: 1 }; - } else { - return { bits: 0, refs: 1 }; - } - } else if (src.format === "remainder") { - if (src.optional) { - throw new Error("Remainder cell cannot be optional"); + switch (src.kind) { + case "int": + case "uint": { + return { bits: src.bits + (src.optional ? 1 : 0), refs: 0 }; + } + case "coins": { + return { bits: 124 + (src.optional ? 1 : 0), refs: 0 }; + } + case "boolean": { + return { bits: 1 + (src.optional ? 1 : 0), refs: 0 }; + } + case "address": { + return { bits: 267, refs: 0 }; + } + case "cell": + case "slice": + case "builder": + { + switch (src.format) { + case "default": { + return { bits: src.optional ? 1 : 0, refs: 1 }; + } + case "remainder": { + if (src.optional) { + throw new Error( + "Remainder cell cannot be optional", + ); + } + return { bits: 0, refs: 0 }; + } + } } - return { bits: 0, refs: 0 }; - } else { - throw new Error("Unsupported format"); + break; + case "string": { + return { bits: src.optional ? 1 : 0, refs: 1 }; } - } else if (src.kind === "string") { - if (src.optional) { + case "map": { return { bits: 1, refs: 1 }; - } else { - return { bits: 0, refs: 1 }; } - } else if (src.kind === "map") { - return { bits: 1, refs: 1 }; - } else if (src.kind === "struct") { - if (src.ref) { - if (src.optional) { - return { bits: 1, refs: 1 }; - } else { - return { bits: 0, refs: 1 }; - } - } else { - if (src.optional) { - return { bits: src.size.bits + 1, refs: src.size.refs }; + case "struct": { + if (src.ref) { + return { bits: src.optional ? 1 : 0, refs: 1 }; } else { - return { bits: src.size.bits, refs: src.size.refs }; + return { + bits: src.size.bits + (src.optional ? 1 : 0), + refs: src.size.refs, + }; } } - } else if (src.kind === "fixed-bytes") { - return { bits: src.bytes * 8 + (src.optional ? 1 : 0), refs: 0 }; + case "fixed-bytes": { + return { bits: src.bytes * 8 + (src.optional ? 1 : 0), refs: 0 }; + } } - throw new Error("Unsupported operation"); } export function getAllocationOperationFromField( @@ -72,230 +72,224 @@ export function getAllocationOperationFromField( structLoader: (name: string) => { bits: number; refs: number }, ): AllocationOperationType { // Reference types - if (src.kind === "simple") { - // let td = getType(ctx, src.type); - - // Handle primitive types - // if (td.kind === 'primitive') { - if (src.type === "int") { - if (src.format === 8) { - return { - kind: "int", - bits: 8, - optional: src.optional ? src.optional : false, - }; - } else if (src.format === 16) { - return { - kind: "int", - bits: 16, - optional: src.optional ? src.optional : false, - }; - } else if (src.format === 32) { - return { - kind: "int", - bits: 32, - optional: src.optional ? src.optional : false, - }; - } else if (src.format === 64) { - return { - kind: "int", - bits: 64, - optional: src.optional ? src.optional : false, - }; - } else if (src.format === 128) { - return { - kind: "int", - bits: 128, - optional: src.optional ? src.optional : false, - }; - } else if (src.format === 256) { - return { - kind: "int", - bits: 256, - optional: src.optional ? src.optional : false, - }; - } else if (src.format === 257) { + switch (src.kind) { + case "simple": { + if (src.type === "int") { + if (src.format === 8) { + return { + kind: "int", + bits: 8, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 16) { + return { + kind: "int", + bits: 16, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 32) { + return { + kind: "int", + bits: 32, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 64) { + return { + kind: "int", + bits: 64, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 128) { + return { + kind: "int", + bits: 128, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 256) { + return { + kind: "int", + bits: 256, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 257) { + return { + kind: "int", + bits: 257, + optional: src.optional ? src.optional : false, + }; + } else if (src.format !== null && src.format !== undefined) { + throw Error("Unsupported int format " + src.format); + } return { kind: "int", bits: 257, optional: src.optional ? src.optional : false, }; - } else if (src.format !== null && src.format !== undefined) { - throw Error("Unsupported int format " + src.format); } - return { - kind: "int", - bits: 257, - optional: src.optional ? src.optional : false, - }; - } - if (src.type === "uint") { - if (src.format === 8) { + if (src.type === "uint") { + if (src.format === 8) { + return { + kind: "uint", + bits: 8, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 16) { + return { + kind: "uint", + bits: 16, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 32) { + return { + kind: "uint", + bits: 32, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 64) { + return { + kind: "uint", + bits: 64, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 128) { + return { + kind: "uint", + bits: 128, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === 256) { + return { + kind: "uint", + bits: 256, + optional: src.optional ? src.optional : false, + }; + } else if (src.format === "coins") { + return { + kind: "coins", + optional: src.optional ? src.optional : false, + }; + } else if (src.format !== null && src.format !== undefined) { + throw Error("Unsupported int format " + src.format); + } return { kind: "uint", - bits: 8, - optional: src.optional ? src.optional : false, - }; - } else if (src.format === 16) { - return { - kind: "uint", - bits: 16, - optional: src.optional ? src.optional : false, - }; - } else if (src.format === 32) { - return { - kind: "uint", - bits: 32, + bits: 256, optional: src.optional ? src.optional : false, }; - } else if (src.format === 64) { + } + if (src.type === "bool") { + if (src.format !== null && src.format !== undefined) { + throw Error("Unsupported bool format " + src.format); + } return { - kind: "uint", - bits: 64, + kind: "boolean", optional: src.optional ? src.optional : false, }; - } else if (src.format === 128) { + } + if (src.type === "cell") { + if (src.format === "remainder") { + return { + kind: "cell", + optional: src.optional ? src.optional : false, + format: "remainder", + }; + } else if (src.format !== null && src.format !== undefined) { + throw Error("Unsupported cell format " + src.format); + } return { - kind: "uint", - bits: 128, + kind: "cell", optional: src.optional ? src.optional : false, + format: "default", }; - } else if (src.format === 256) { + } + if (src.type === "slice") { + if (src.format === "remainder") { + return { + kind: "slice", + optional: src.optional ? src.optional : false, + format: "remainder", + }; + } else if (src.format !== null && src.format !== undefined) { + throw Error("Unsupported slice format " + src.format); + } return { - kind: "uint", - bits: 256, + kind: "slice", optional: src.optional ? src.optional : false, + format: "default", }; - } else if (src.format === "coins") { + } + if (src.type === "builder") { + if (src.format === "remainder") { + return { + kind: "builder", + optional: src.optional ? src.optional : false, + format: "remainder", + }; + } else if (src.format !== null && src.format !== undefined) { + throw Error("Unsupported slice format " + src.format); + } return { - kind: "coins", + kind: "builder", optional: src.optional ? src.optional : false, + format: "default", }; - } else if (src.format !== null && src.format !== undefined) { - throw Error("Unsupported int format " + src.format); - } - return { - kind: "uint", - bits: 256, - optional: src.optional ? src.optional : false, - }; - } - if (src.type === "bool") { - if (src.format !== null && src.format !== undefined) { - throw Error("Unsupported bool format " + src.format); } - return { - kind: "boolean", - optional: src.optional ? src.optional : false, - }; - } - if (src.type === "cell") { - if (src.format === "remainder") { + if (src.type === "address") { return { - kind: "cell", + kind: "address", optional: src.optional ? src.optional : false, - format: "remainder", }; - } else if (src.format !== null && src.format !== undefined) { - throw Error("Unsupported cell format " + src.format); } - return { - kind: "cell", - optional: src.optional ? src.optional : false, - format: "default", - }; - } - if (src.type === "slice") { - if (src.format === "remainder") { + if (src.type === "fixed-bytes") { + if (src.format === 32 || src.format === 64) { + return { + kind: "fixed-bytes", + bytes: src.format, + optional: src.optional ? src.optional : false, + }; + } else { + throw Error("Unsupported fixed-bytes format " + src.format); + } + } + if (src.type === "string") { + if (src.format !== null && src.format !== undefined) { + throw Error("Unsupported string format " + src.format); + } return { - kind: "slice", + kind: "string", optional: src.optional ? src.optional : false, - format: "remainder", }; - } else if (src.format !== null && src.format !== undefined) { - throw Error("Unsupported slice format " + src.format); } - return { - kind: "slice", - optional: src.optional ? src.optional : false, - format: "default", - }; - } - if (src.type === "builder") { - if (src.format === "remainder") { + + // Struct types + const size = structLoader(src.type); + if (src.format === "ref") { return { - kind: "builder", + kind: "struct", + type: src.type, + ref: true, optional: src.optional ? src.optional : false, - format: "remainder", + size, }; - } else if (src.format !== null && src.format !== undefined) { - throw Error("Unsupported slice format " + src.format); - } - return { - kind: "builder", - optional: src.optional ? src.optional : false, - format: "default", - }; - } - if (src.type === "address") { - return { - kind: "address", - optional: src.optional ? src.optional : false, - }; - } - if (src.type === "fixed-bytes") { - if (src.format === 32 || src.format === 64) { + } else if (src.format !== undefined && src.format !== null) { + throw Error("Unsupported struct format " + src.format); + } else { return { - kind: "fixed-bytes", - bytes: src.format, + kind: "struct", + type: src.type, + ref: false, optional: src.optional ? src.optional : false, + size, }; - } else { - throw Error("Unsupported fixed-bytes format " + src.format); } } - if (src.type === "string") { + case "dict": { if (src.format !== null && src.format !== undefined) { - throw Error("Unsupported string format " + src.format); + throw Error("Unsupported map format " + src.format); } - return { - kind: "string", - optional: src.optional ? src.optional : false, - }; - } - - // Struct types - const size = structLoader(src.type); - if (src.format === "ref") { - return { - kind: "struct", - type: src.type, - ref: true, - optional: src.optional ? src.optional : false, - size, - }; - } else if (src.format !== undefined && src.format !== null) { - throw Error("Unsupported struct format " + src.format); - } else { - return { - kind: "struct", - type: src.type, - ref: false, - optional: src.optional ? src.optional : false, - size, - }; + return { kind: "map" }; } } - - // Map - if (src.kind === "dict") { - if (src.format !== null && src.format !== undefined) { - throw Error("Unsupported map format " + src.format); - } - return { kind: "map" }; - } - - throw new Error("Unsupported operation"); } function allocateSegment( @@ -307,8 +301,7 @@ function allocateSegment( let next: AllocationCell | null = null; const used: { bits: number; refs: number } = { bits: 0, refs: 0 }; - for (let i = 0; i < ops.length; i++) { - const op = ops[i]; + for (const [i, op] of ops.entries()) { const size = getOperationSize(op.op); // Check if we can fit this operation diff --git a/src/test/compilation-failed/util.ts b/src/test/compilation-failed/util.ts index 9c91d5067..48eb88ac1 100644 --- a/src/test/compilation-failed/util.ts +++ b/src/test/compilation-failed/util.ts @@ -9,7 +9,7 @@ export function itShouldNotCompile(params: { it(`should not compile ${params.testName}`, async () => { const result = await run({ configPath: __dirname + "/tact.config.json", - projectNames: [`${params.testName}`], + projectNames: [params.testName], }); expect(result).toBe(false); expect((consoleLogger.error as jest.Mock).mock.lastCall[0]).toContain( diff --git a/src/test/e2e-emulated/debug.spec.ts b/src/test/e2e-emulated/debug.spec.ts index 1ac4ed498..50230d527 100644 --- a/src/test/e2e-emulated/debug.spec.ts +++ b/src/test/e2e-emulated/debug.spec.ts @@ -52,6 +52,6 @@ ${contract.address.toString({ bounceable: true })} File ${filePath}:17:9 ${Address.parseRaw( "0:83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8", -)}`); +).toString()}`); }); }); diff --git a/src/test/e2e-emulated/dns.spec.ts b/src/test/e2e-emulated/dns.spec.ts index 171dd8838..b3c5dae07 100644 --- a/src/test/e2e-emulated/dns.spec.ts +++ b/src/test/e2e-emulated/dns.spec.ts @@ -1,3 +1,4 @@ +// eslint-disable rule-name import { OpenedContract, beginCell, toNano } from "@ton/core"; import { ContractSystem } from "@tact-lang/emulator"; import { __DANGER_resetNodeId } from "../../grammar/ast"; @@ -13,7 +14,7 @@ function convertToInternal(src: string) { if (i > 0) { res = Buffer.concat([res, Buffer.from([0])]); } - res = Buffer.concat([res, parts[parts.length - i - 1]]); + res = Buffer.concat([res, parts[parts.length - i - 1]!]); } res = Buffer.concat([res, Buffer.from([0])]); return res; @@ -81,11 +82,11 @@ describe("dns", () => { ]; for (let i = 0; i < invalidNames.length; i++) { - it(`should fail on invalid name: ${invalidNames[i]}`, async () => { - expect(await contract.getStringToInternal(invalidNames[i])).toBe( + it(`should fail on invalid name: ${invalidNames[i]!}`, async () => { + expect(await contract.getStringToInternal(invalidNames[i]!)).toBe( null, ); - const internalAddress = convertToInternal(invalidNames[i]); + const internalAddress = convertToInternal(invalidNames[i]!); expect( await contract.getDnsInternalVerify( beginCell().storeBuffer(internalAddress).endCell(), @@ -95,27 +96,27 @@ describe("dns", () => { } for (let i = 0; i < validNames.length; i++) { - it(`should convert valid name: ${validNames[i]}`, async () => { - const data = (await contract.getStringToInternal(validNames[i]))!; + it(`should convert valid name: ${validNames[i]!}`, async () => { + const data = (await contract.getStringToInternal(validNames[i]!))!; const received = data .beginParse() .loadBuffer(data.bits.length / 8) .toString("hex"); expect(received).toBe( convertToInternal( - validNames[i].endsWith(".") && validNames[i] !== "." - ? validNames[i].slice(0, validNames[i].length - 1) - : validNames[i], + validNames[i]!.endsWith(".") && validNames[i]! !== "." + ? validNames[i]!.slice(0, validNames[i]!.length - 1) + : validNames[i]!, ).toString("hex"), ); }); } for (let i = 0; i < validNames.length; i++) { - if (validNames[i] !== ".") { - it(`should convert valid name: ${validNames[i]}`, async () => { + if (validNames[i]! !== ".") { + it(`should convert valid name: ${validNames[i]!}`, async () => { const data = (await contract.getStringToInternal( - validNames[i], + validNames[i]!, ))!; expect(await contract.getDnsInternalVerify(data)).toBe(true); }); @@ -123,9 +124,9 @@ describe("dns", () => { } for (let i = 0; i < equalNormalized.length; i++) { - it(`should convert equal normalized names: ${equalNormalized[i][0]} ${equalNormalized[i][1]}`, async () => { + it(`should convert equal normalized names: ${equalNormalized[i]![0]!} ${equalNormalized[i]![1]!}`, async () => { let data1 = (await contract.getStringToInternal( - equalNormalized[i][0], + equalNormalized[i]![0]!, ))!; data1 = await contract.getInternalNormalize(data1); const received1 = data1 @@ -133,7 +134,7 @@ describe("dns", () => { .loadBuffer(data1.bits.length / 8) .toString("hex"); let data2 = (await contract.getStringToInternal( - equalNormalized[i][1], + equalNormalized[i]![1]!, ))!; data2 = await contract.getInternalNormalize(data2); const received2 = data2 @@ -145,9 +146,9 @@ describe("dns", () => { }); } for (let i = 0; i < notEqualNormalized.length; i++) { - it(`should convert not equal normalized names: ${notEqualNormalized[i][0]} ${notEqualNormalized[i][1]}`, async () => { + it(`should convert not equal normalized names: ${notEqualNormalized[i]![0]!} ${notEqualNormalized[i]![1]!}`, async () => { let data1 = (await contract.getStringToInternal( - notEqualNormalized[i][0], + notEqualNormalized[i]![0]!, ))!; data1 = await contract.getInternalNormalize(data1); const received1 = data1 @@ -155,7 +156,7 @@ describe("dns", () => { .loadBuffer(data1.bits.length / 8) .toString("hex"); let data2 = (await contract.getStringToInternal( - notEqualNormalized[i][1], + notEqualNormalized[i]![1]!, ))!; data2 = await contract.getInternalNormalize(data2); const received2 = data2 @@ -168,17 +169,17 @@ describe("dns", () => { } for (let i = 0; i < validNames.length; i++) { - it("should resolve name " + validNames[i], async () => { - const internalAddress = convertToInternal(validNames[i]); + it("should resolve name " + validNames[i]!, async () => { + const internalAddress = convertToInternal(validNames[i]!); const resolved = (await contract.getDnsresolve( beginCell().storeBuffer(internalAddress).endCell(), 1n, ))!; expect(resolved.prefix).toBe(BigInt(internalAddress.length * 8)); - if (validNames[i] === ".") { + if (validNames[i]! === ".") { expect(resolved.record!.bits.length).toBe(0); expect(resolved.record!.refs.length).toBe(0); - } else if (validNames[i].endsWith(".")) { + } else if (validNames[i]!.endsWith(".")) { expect( resolved .record!.beginParse() @@ -197,8 +198,8 @@ describe("dns", () => { } for (let i = 0; i < invalidNames.length; i++) { - it("should not resolve name " + invalidNames[i], async () => { - const internalAddress = convertToInternal(invalidNames[i]); + it("should not resolve name " + invalidNames[i]!, async () => { + const internalAddress = convertToInternal(invalidNames[i]!); await expect( contract.getDnsresolve( beginCell().storeBuffer(internalAddress).endCell(), @@ -209,13 +210,13 @@ describe("dns", () => { } for (let i = 0; i < validNames.length; i++) { - if (validNames[i].endsWith(".")) { + if (validNames[i]!.endsWith(".")) { continue; } it( - "should resolve name with leading zero " + validNames[i], + "should resolve name with leading zero " + validNames[i]!, async () => { - const internalAddress = convertToInternal(validNames[i]); + const internalAddress = convertToInternal(validNames[i]!); const resolved = (await contract.getDnsresolve( beginCell() .storeBuffer( @@ -230,7 +231,7 @@ describe("dns", () => { expect(resolved.prefix).toBe( BigInt(internalAddress.length * 8 + 8), ); - if (validNames[i] === ".") { + if (validNames[i]! === ".") { expect(resolved.record!.bits.length).toBe(0); expect(resolved.record!.refs.length).toBe(0); } else { diff --git a/src/test/e2e-emulated/optionals.spec.ts b/src/test/e2e-emulated/optionals.spec.ts index 9ddd6832a..f3a0e8605 100644 --- a/src/test/e2e-emulated/optionals.spec.ts +++ b/src/test/e2e-emulated/optionals.spec.ts @@ -172,7 +172,7 @@ describe("features", () => { for (let i = 0; i < cases.length; i++) { it("should handle case #" + i, async () => { - const cs = cases[i]; + const cs = cases[i]!; // Init contract const system = await ContractSystem.create(); diff --git a/src/test/e2e-emulated/serialization.spec.ts b/src/test/e2e-emulated/serialization.spec.ts index de6e9360c..26749ef70 100644 --- a/src/test/e2e-emulated/serialization.spec.ts +++ b/src/test/e2e-emulated/serialization.spec.ts @@ -38,7 +38,7 @@ describe("serialization", () => { for (let i = 0; i < cases.length; i++) { it("should handle case #" + i, async () => { - const cs = cases[i]; + const cs = cases[i]!; // Init contract const system = await ContractSystem.create(); @@ -116,7 +116,7 @@ describe("serialization", () => { for (let i = 0; i < cases.length; i++) { it("should handle case-2 #" + i, async () => { - const cs = cases[i]; + const cs = cases[i]!; // Init contract const system = await ContractSystem.create(); diff --git a/src/test/e2e-emulated/strings.spec.ts b/src/test/e2e-emulated/strings.spec.ts index 4adfc1b90..016efd0b4 100644 --- a/src/test/e2e-emulated/strings.spec.ts +++ b/src/test/e2e-emulated/strings.spec.ts @@ -15,7 +15,7 @@ describe("strings", () => { await contract.send(treasure, { value: toNano("10") }, null); await system.run(); - expect(contract.abi.errors!["31733"].message).toStrictEqual( + expect(contract.abi.errors!["31733"]!.message).toStrictEqual( "condition can`t be...", ); diff --git a/src/types/createTLBType.ts b/src/types/createTLBType.ts index 8cdd45473..ec039a26b 100644 --- a/src/types/createTLBType.ts +++ b/src/types/createTLBType.ts @@ -70,33 +70,32 @@ function createTypeFormat( } function createTLBField(src: ABIField) { - if (src.type.kind === "simple") { - let base = createTypeFormat( - src.type.type, - src.type.format ? src.type.format : null, - ); - if (src.type.optional) { - base = "Maybe " + base; + switch (src.type.kind) { + case "simple": { + let base = createTypeFormat( + src.type.type, + src.type.format ? src.type.format : null, + ); + if (src.type.optional) { + base = "Maybe " + base; + } + return src.name + ":" + base; } - return src.name + ":" + base; - } - - if (src.type.kind === "dict") { - if (src.type.format !== null && src.type.format !== undefined) { - throw Error("Unsupported map format " + src.type.format); + case "dict": { + if (src.type.format !== null && src.type.format !== undefined) { + throw Error("Unsupported map format " + src.type.format); + } + const key = createTypeFormat( + src.type.key, + src.type.keyFormat ? src.type.keyFormat : null, + ); + const value = createTypeFormat( + src.type.value, + src.type.valueFormat ? src.type.valueFormat : null, + ); + return src.name + ":dict<" + key + ", " + value + ">"; } - const key = createTypeFormat( - src.type.key, - src.type.keyFormat ? src.type.keyFormat : null, - ); - const value = createTypeFormat( - src.type.value, - src.type.valueFormat ? src.type.valueFormat : null, - ); - return src.name + ":dict<" + key + ", " + value + ">"; } - - throw Error("Unsupported ABI field"); } export function createTLBType( diff --git a/src/types/getSupportedInterfaces.ts b/src/types/getSupportedInterfaces.ts index 90821af2c..0f5cd639e 100644 --- a/src/types/getSupportedInterfaces.ts +++ b/src/types/getSupportedInterfaces.ts @@ -17,8 +17,6 @@ export function getSupportedInterfaces( } else { interfaces.push("org.ton.chain.any.v0"); } - for (let i = 0; i < type.interfaces.length; i++) { - interfaces.push(type.interfaces[i]); - } + type.interfaces.forEach((iface) => interfaces.push(iface)); return interfaces; } diff --git a/src/types/resolveABITypeRef.ts b/src/types/resolveABITypeRef.ts index 1d6124baf..9554b2ad8 100644 --- a/src/types/resolveABITypeRef.ts +++ b/src/types/resolveABITypeRef.ts @@ -21,7 +21,9 @@ import { } from "../errors"; import { TypeRef } from "./types"; -type FormatDef = { [key: string]: { type: string; format: string | number } }; +type FormatDef = { + [key: string]: { type: string; format: string | number } | undefined; +}; const intFormats: FormatDef = { int8: { type: "int", format: 8 }, @@ -88,8 +90,7 @@ export function resolveABIType(src: AstFieldDecl): ABITypeRef { const typeId: AstTypeId = src.type.kind === "type_id" ? src.type - : src.type.kind === "optional_type" && - src.type.typeArg.kind == "type_id" + : src.type.typeArg.kind === "type_id" ? src.type.typeArg : throwInternalCompilerError( "Only optional type identifiers are supported now", @@ -156,21 +157,19 @@ export function resolveABIType(src: AstFieldDecl): ABITypeRef { } if (isSlice(typeId)) { if (src.as) { - if (src.as) { - const fmt = sliceFormats[idText(src.as)]; - if (!fmt) { - throwCompilationError( - `Unsupported format ${idTextErr(src.as)}`, - src.loc, - ); - } - return { - kind: "simple", - type: fmt.type, - optional: src.type.kind === "optional_type", - format: fmt.format, - }; + const fmt = sliceFormats[idText(src.as)]; + if (!fmt) { + throwCompilationError( + `Unsupported format ${idTextErr(src.as)}`, + src.loc, + ); } + return { + kind: "simple", + type: fmt.type, + optional: src.type.kind === "optional_type", + format: fmt.format, + }; } return { kind: "simple", @@ -180,21 +179,19 @@ export function resolveABIType(src: AstFieldDecl): ABITypeRef { } if (isBuilder(typeId)) { if (src.as) { - if (src.as) { - const fmt = builderFormats[idText(src.as)]; - if (!fmt) { - throwCompilationError( - `Unsupported format ${idTextErr(src.as)}`, - src.loc, - ); - } - return { - kind: "simple", - type: fmt.type, - optional: src.type.kind === "optional_type", - format: fmt.format, - }; + const fmt = builderFormats[idText(src.as)]; + if (!fmt) { + throwCompilationError( + `Unsupported format ${idTextErr(src.as)}`, + src.loc, + ); } + return { + kind: "simple", + type: fmt.type, + optional: src.type.kind === "optional_type", + format: fmt.format, + }; } return { kind: "simple", diff --git a/src/types/resolveDescriptors.spec.ts b/src/types/resolveDescriptors.spec.ts index 7b7df2828..923fbf57f 100644 --- a/src/types/resolveDescriptors.spec.ts +++ b/src/types/resolveDescriptors.spec.ts @@ -12,7 +12,7 @@ import { featureEnable } from "../config/features"; expect.addSnapshotSerializer({ test: (src) => src instanceof SrcInfo, - print: (src) => `${(src as SrcInfo).contents}`, + print: (src) => (src as SrcInfo).contents, }); describe("resolveDescriptors", () => { diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts index de8c2ba9c..829c051fb 100644 --- a/src/types/resolveDescriptors.ts +++ b/src/types/resolveDescriptors.ts @@ -121,120 +121,134 @@ function verifyMapType( export const toBounced = (type: string) => `${type}%%BOUNCED%%`; export function resolveTypeRef(ctx: CompilerContext, src: AstType): TypeRef { - if (src.kind === "type_id") { - const t = getType(ctx, idText(src)); - return { - kind: "ref", - name: t.name, - optional: false, - }; - } - if (src.kind === "optional_type") { - if (src.typeArg.kind !== "type_id") { - throwInternalCompilerError( - "Only optional type identifiers are supported now", - src.typeArg.loc, + switch (src.kind) { + case "type_id": { + const t = getType(ctx, idText(src)); + return { + kind: "ref", + name: t.name, + optional: false, + }; + } + case "optional_type": { + if (src.typeArg.kind !== "type_id") { + throwInternalCompilerError( + "Only optional type identifiers are supported now", + src.typeArg.loc, + ); + } + const t = getType(ctx, idText(src.typeArg)); + return { + kind: "ref", + name: t.name, + optional: true, + }; + } + case "map_type": { + const k = getType(ctx, idText(src.keyType)).name; + const v = getType(ctx, idText(src.valueType)).name; + verifyMapType( + k, + src.keyStorageType, + v, + src.valueStorageType, + src.loc, ); + return { + kind: "map", + key: k, + keyAs: + src.keyStorageType !== null + ? idText(src.keyStorageType) + : null, + value: v, + valueAs: + src.valueStorageType !== null + ? idText(src.valueStorageType) + : null, + }; + } + case "bounced_message_type": { + const t = getType(ctx, idText(src.messageType)); + return { + kind: "ref_bounced", + name: t.name, + }; } - const t = getType(ctx, idText(src.typeArg)); - return { - kind: "ref", - name: t.name, - optional: true, - }; - } - if (src.kind === "map_type") { - const k = getType(ctx, idText(src.keyType)).name; - const v = getType(ctx, idText(src.valueType)).name; - verifyMapType(k, src.keyStorageType, v, src.valueStorageType, src.loc); - return { - kind: "map", - key: k, - keyAs: - src.keyStorageType !== null ? idText(src.keyStorageType) : null, - value: v, - valueAs: - src.valueStorageType !== null - ? idText(src.valueStorageType) - : null, - }; - } - if (src.kind === "bounced_message_type") { - const t = getType(ctx, idText(src.messageType)); - return { - kind: "ref_bounced", - name: t.name, - }; } - throw Error("Invalid type ref"); } function buildTypeRef( src: AstType, types: Map, ): TypeRef { - if (src.kind === "type_id") { - if (!types.has(idText(src))) { - throwCompilationError(`Type ${idTextErr(src)} not found`, src.loc); - } - return { - kind: "ref", - name: idText(src), - optional: false, - }; - } - if (src.kind === "optional_type") { - if (src.typeArg.kind !== "type_id") { - throwInternalCompilerError( - "Only optional type identifiers are supported now", - src.typeArg.loc, - ); + switch (src.kind) { + case "type_id": { + if (!types.has(idText(src))) { + throwCompilationError( + `Type ${idTextErr(src)} not found`, + src.loc, + ); + } + return { + kind: "ref", + name: idText(src), + optional: false, + }; } - if (!types.has(idText(src.typeArg))) { - throwCompilationError( - `Type ${idTextErr(src.typeArg)} not found`, - src.loc, - ); + case "optional_type": { + if (src.typeArg.kind !== "type_id") { + throwInternalCompilerError( + "Only optional type identifiers are supported now", + src.typeArg.loc, + ); + } + if (!types.has(idText(src.typeArg))) { + throwCompilationError( + `Type ${idTextErr(src.typeArg)} not found`, + src.loc, + ); + } + return { + kind: "ref", + name: idText(src.typeArg), + optional: true, + }; } - return { - kind: "ref", - name: idText(src.typeArg), - optional: true, - }; - } - if (src.kind === "map_type") { - if (!types.has(idText(src.keyType))) { - throwCompilationError( - `Type ${idTextErr(src.keyType)} not found`, - src.loc, - ); + case "map_type": { + if (!types.has(idText(src.keyType))) { + throwCompilationError( + `Type ${idTextErr(src.keyType)} not found`, + src.loc, + ); + } + if (!types.has(idText(src.valueType))) { + throwCompilationError( + `Type ${idTextErr(src.valueType)} not found`, + src.loc, + ); + } + return { + kind: "map", + key: idText(src.keyType), + keyAs: + src.keyStorageType !== null + ? idText(src.keyStorageType) + : null, + value: idText(src.valueType), + valueAs: + src.valueStorageType !== null + ? idText(src.valueStorageType) + : null, + }; } - if (!types.has(idText(src.valueType))) { - throwCompilationError( - `Type ${idTextErr(src.valueType)} not found`, - src.loc, - ); + case "bounced_message_type": { + return { + kind: "ref_bounced", + name: idText(src.messageType), + }; } - return { - kind: "map", - key: idText(src.keyType), - keyAs: - src.keyStorageType !== null ? idText(src.keyStorageType) : null, - value: idText(src.valueType), - valueAs: - src.valueStorageType !== null - ? idText(src.valueStorageType) - : null, - }; } - if (src.kind === "bounced_message_type") { - return { - kind: "ref_bounced", - name: idText(src.messageType), - }; - } - - throw Error("Unknown type ref"); } function uidForName(name: string, types: Map) { @@ -266,90 +280,98 @@ export function resolveDescriptors(ctx: CompilerContext) { const uid = uidForName(idText(a.name), types); - if (a.kind === "primitive_type_decl") { - types.set(idText(a.name), { - kind: "primitive_type_decl", - origin: a.loc.origin, - name: idText(a.name), - uid, - fields: [], - traits: [], - header: null, - tlb: null, - signature: null, - functions: new Map(), - receivers: [], - dependsOn: [], - init: null, - ast: a, - interfaces: [], - constants: [], - partialFieldCount: 0, - }); - } else if (a.kind === "contract") { - types.set(idText(a.name), { - kind: "contract", - origin: a.loc.origin, - name: idText(a.name), - uid, - header: null, - tlb: null, - fields: [], - traits: [], - signature: null, - functions: new Map(), - receivers: [], - dependsOn: [], - init: null, - ast: a, - interfaces: a.attributes - .filter((v) => v.type === "interface") - .map((v) => v.name.value), - constants: [], - partialFieldCount: 0, - }); - } else if (a.kind === "struct_decl" || a.kind === "message_decl") { - types.set(idText(a.name), { - kind: "struct", - origin: a.loc.origin, - name: idText(a.name), - uid, - header: null, - tlb: null, - signature: null, - fields: [], - traits: [], - functions: new Map(), - receivers: [], - dependsOn: [], - init: null, - ast: a, - interfaces: [], - constants: [], - partialFieldCount: 0, - }); - } else if (a.kind === "trait") { - types.set(idText(a.name), { - kind: "trait", - origin: a.loc.origin, - name: idText(a.name), - uid, - header: null, - tlb: null, - signature: null, - fields: [], - traits: [], - functions: new Map(), - receivers: [], - dependsOn: [], - init: null, - ast: a, - interfaces: a.attributes - .filter((v) => v.type === "interface") - .map((v) => v.name.value), - constants: [], - partialFieldCount: 0, - }); + switch (a.kind) { + case "primitive_type_decl": + { + types.set(idText(a.name), { + kind: "primitive_type_decl", + origin: a.loc.origin, + name: idText(a.name), + uid, + fields: [], + traits: [], + header: null, + tlb: null, + signature: null, + functions: new Map(), + receivers: [], + dependsOn: [], + init: null, + ast: a, + interfaces: [], + constants: [], + partialFieldCount: 0, + }); + } + break; + case "contract": + { + types.set(idText(a.name), { + kind: "contract", + origin: a.loc.origin, + name: idText(a.name), + uid, + header: null, + tlb: null, + fields: [], + traits: [], + signature: null, + functions: new Map(), + receivers: [], + dependsOn: [], + init: null, + ast: a, + interfaces: a.attributes.map((v) => v.name.value), + constants: [], + partialFieldCount: 0, + }); + } + break; + case "struct_decl": + case "message_decl": + { + types.set(idText(a.name), { + kind: "struct", + origin: a.loc.origin, + name: idText(a.name), + uid, + header: null, + tlb: null, + signature: null, + fields: [], + traits: [], + functions: new Map(), + receivers: [], + dependsOn: [], + init: null, + ast: a, + interfaces: [], + constants: [], + partialFieldCount: 0, + }); + } + break; + case "trait": { + types.set(idText(a.name), { + kind: "trait", + origin: a.loc.origin, + name: idText(a.name), + uid, + header: null, + tlb: null, + signature: null, + fields: [], + traits: [], + functions: new Map(), + receivers: [], + dependsOn: [], + init: null, + ast: a, + interfaces: a.attributes.map((v) => v.name.value), + constants: [], + partialFieldCount: 0, + }); + } } } @@ -760,33 +782,34 @@ export function resolveDescriptors(ctx: CompilerContext) { isExtends.loc, ); } - if (!isSelfId(params[0].name)) { + const firstParam = params[0]!; + if (!isSelfId(firstParam.name)) { throwCompilationError( 'Extend function must have first parameter named "self"', - params[0].loc, + firstParam.loc, ); } - if (params[0].type.kind !== "ref") { + if (firstParam.type.kind !== "ref") { throwCompilationError( "Extend functions must have a reference type as the first parameter", - params[0].loc, + firstParam.loc, ); } - if (params[0].type.optional) { + if (firstParam.type.optional) { throwCompilationError( "Extend functions must have a non-optional type as the first parameter", - params[0].loc, + firstParam.loc, ); } - if (!types.has(params[0].type.name)) { + if (!types.has(firstParam.type.name)) { throwCompilationError( - "Type " + params[0].type.name + " not found", - params[0].loc, + "Type " + firstParam.type.name + " not found", + firstParam.loc, ); } // Update self and remove first parameter - self = params[0].type.name; + self = firstParam.type.name; params = params.slice(1); } @@ -919,238 +942,290 @@ export function resolveDescriptors(ctx: CompilerContext) { ); } - if ( - d.selector.kind === "internal-simple" || - d.selector.kind === "external-simple" - ) { - const param = d.selector.param; - const internal = d.selector.kind === "internal-simple"; + switch (d.selector.kind) { + case "internal-simple": + case "external-simple": + { + const param = d.selector.param; + const internal = + d.selector.kind === "internal-simple"; - if (param.type.kind !== "type_id") { - throwCompilationError( - "Receive function can only accept non-optional message types", - d.loc, - ); - } - const t = types.get(idText(param.type)); - if (!t) { - throwCompilationError( - `Type ${idTextErr(param.type)} not found`, - d.loc, - ); - } + if (param.type.kind !== "type_id") { + throwCompilationError( + "Receive function can only accept non-optional message types", + d.loc, + ); + } + const t = types.get(idText(param.type)); + if (!t) { + throwCompilationError( + `Type ${idTextErr(param.type)} not found`, + d.loc, + ); + } - // Raw receiver - if (t.kind === "primitive_type_decl") { - if (t.name === "Slice") { - // Check for existing receiver + // Raw receiver + if (t.kind === "primitive_type_decl") { + if (t.name === "Slice") { + // Check for existing receiver + if ( + s.receivers.find( + (v) => + v.selector.kind === + (internal + ? "internal-fallback" + : "external-fallback"), + ) + ) { + throwCompilationError( + `Fallback receive function already exists`, + d.loc, + ); + } + + // Persist receiver + s.receivers.push({ + selector: { + kind: internal + ? "internal-fallback" + : "external-fallback", + name: param.name, + }, + ast: d, + }); + } else if (t.name === "String") { + // Check for existing receiver + if ( + s.receivers.find( + (v) => + v.selector.kind === + (internal + ? "internal-comment-fallback" + : "external-comment-fallback"), + ) + ) { + throwCompilationError( + "Comment fallback receive function already exists", + d.loc, + ); + } + + // Persist receiver + s.receivers.push({ + selector: { + kind: internal + ? "internal-comment-fallback" + : "external-comment-fallback", + name: param.name, + }, + ast: d, + }); + } else { + throwCompilationError( + "Receive function can only accept message, Slice or String", + d.loc, + ); + } + } else { + // Check type + if (t.kind !== "struct") { + throwCompilationError( + "Receive function can only accept message", + d.loc, + ); + } + if (t.ast.kind !== "message_decl") { + throwCompilationError( + "Receive function can only accept message", + d.loc, + ); + } + + // Check for duplicate + const n = idText(param.type); + if ( + s.receivers.find( + (v) => + v.selector.kind === + (internal + ? "internal-binary" + : "external-binary") && + eqNames(v.selector.type, n), + ) + ) { + throwCompilationError( + `Receive function for ${idTextErr(param.type)} already exists`, + param.loc, + ); + } + + // Persist receiver + s.receivers.push({ + selector: { + kind: internal + ? "internal-binary" + : "external-binary", + name: param.name, + type: idText(param.type), + }, + ast: d, + }); + } + } + break; + case "internal-comment": + case "external-comment": + { + const internal = + d.selector.kind === "internal-comment"; + if (d.selector.comment.value === "") { + throwCompilationError( + "To use empty comment receiver, just remove parameter instead of passing empty string", + d.loc, + ); + } + const c = d.selector.comment.value; if ( s.receivers.find( (v) => v.selector.kind === - (internal - ? "internal-fallback" - : "external-fallback"), + (internal + ? "internal-comment" + : "external-comment") && + v.selector.comment === c, ) ) { throwCompilationError( - `Fallback receive function already exists`, + `Receive function for ${idTextErr(c)} already exists`, d.loc, ); } - - // Persist receiver s.receivers.push({ selector: { kind: internal - ? "internal-fallback" - : "external-fallback", - name: param.name, + ? "internal-comment" + : "external-comment", + comment: c, }, ast: d, }); - } else if (t.name === "String") { - // Check for existing receiver + } + break; + case "internal-fallback": + case "external-fallback": + { + const internal = + d.selector.kind === "internal-fallback"; + // Handle empty if ( s.receivers.find( (v) => v.selector.kind === (internal - ? "internal-comment-fallback" - : "external-comment-fallback"), + ? "internal-empty" + : "external-empty"), ) ) { throwCompilationError( - "Comment fallback receive function already exists", + "Empty receive function already exists", d.loc, ); } - - // Persist receiver s.receivers.push({ selector: { kind: internal - ? "internal-comment-fallback" - : "external-comment-fallback", - name: param.name, + ? "internal-empty" + : "external-empty", }, ast: d, }); - } else { - throwCompilationError( - "Receive function can only accept message, Slice or String", - d.loc, - ); - } - } else { - // Check type - if (t.kind !== "struct") { - throwCompilationError( - "Receive function can only accept message", - d.loc, - ); } - if (t.ast.kind !== "message_decl") { + break; + case "bounce": { + const param = d.selector.param; + + if (param.type.kind === "type_id") { + if (isSlice(param.type)) { + if ( + s.receivers.find( + (v) => + v.selector.kind === + "bounce-fallback", + ) + ) { + throwCompilationError( + `Fallback bounce receive function already exists`, + d.loc, + ); + } + + s.receivers.push({ + selector: { + kind: "bounce-fallback", + name: param.name, + }, + ast: d, + }); + } else { + const type = types.get(idText(param.type))!; + if (type.ast.kind !== "message_decl") { + throwCompilationError( + "Bounce receive function can only accept bounced message, message or Slice", + d.loc, + ); + } + if ( + type.fields.length !== + type.partialFieldCount + ) { + throwCompilationError( + `This message is too big for bounce receiver, you need to wrap it to a bounced<${idTextErr(param.type)}>.`, + d.loc, + ); + } + if ( + s.receivers.find( + (v) => + v.selector.kind === + "bounce-binary" && + v.selector.type === type.name, + ) + ) { + throwCompilationError( + `Bounce receive function for ${idTextErr(param.type)} already exists`, + param.loc, + ); + } + s.receivers.push({ + selector: { + kind: "bounce-binary", + name: param.name, + type: idText(param.type), + bounced: false, + }, + ast: d, + }); + } + } else if (param.type.kind === "optional_type") { throwCompilationError( - "Receive function can only accept message", + "Bounce receive function cannot have optional parameter", d.loc, ); - } - - // Check for duplicate - const n = idText(param.type); - if ( - s.receivers.find( - (v) => - v.selector.kind === - (internal - ? "internal-binary" - : "external-binary") && - eqNames(v.selector.type, n), - ) + } else if ( + param.type.kind === "bounced_message_type" ) { - throwCompilationError( - `Receive function for ${idTextErr(param.type)} already exists`, - param.loc, - ); - } - - // Persist receiver - s.receivers.push({ - selector: { - kind: internal - ? "internal-binary" - : "external-binary", - name: param.name, - type: idText(param.type), - }, - ast: d, - }); - } - } else if ( - d.selector.kind === "internal-comment" || - d.selector.kind === "external-comment" - ) { - const internal = d.selector.kind === "internal-comment"; - if (d.selector.comment.value === "") { - throwCompilationError( - "To use empty comment receiver, just remove parameter instead of passing empty string", - d.loc, - ); - } - const c = d.selector.comment.value; - if ( - s.receivers.find( - (v) => - v.selector.kind === - (internal - ? "internal-comment" - : "external-comment") && - v.selector.comment === c, - ) - ) { - throwCompilationError( - `Receive function for ${idTextErr(c)} already exists`, - d.loc, - ); - } - s.receivers.push({ - selector: { - kind: internal - ? "internal-comment" - : "external-comment", - comment: c, - }, - ast: d, - }); - } else if ( - d.selector.kind === "internal-fallback" || - d.selector.kind === "external-fallback" - ) { - const internal = - d.selector.kind === "internal-fallback"; - // Handle empty - if ( - s.receivers.find( - (v) => - v.selector.kind === - (internal - ? "internal-empty" - : "external-empty"), - ) - ) { - throwCompilationError( - "Empty receive function already exists", - d.loc, - ); - } - s.receivers.push({ - selector: { - kind: internal - ? "internal-empty" - : "external-empty", - }, - ast: d, - }); - } else if (d.selector.kind === "bounce") { - const param = d.selector.param; - - if (param.type.kind === "type_id") { - if (isSlice(param.type)) { - if ( - s.receivers.find( - (v) => - v.selector.kind === - "bounce-fallback", - ) - ) { + const t = types.get( + idText(param.type.messageType), + )!; + if (t.kind !== "struct") { throwCompilationError( - `Fallback bounce receive function already exists`, + "Bounce receive function can only accept bounced struct types", d.loc, ); } - - s.receivers.push({ - selector: { - kind: "bounce-fallback", - name: param.name, - }, - ast: d, - }); - } else { - const type = types.get(idText(param.type))!; - if (type.ast.kind !== "message_decl") { + if (t.ast.kind !== "message_decl") { throwCompilationError( - "Bounce receive function can only accept bounced message, message or Slice", - d.loc, - ); - } - if ( - type.fields.length !== - type.partialFieldCount - ) { - throwCompilationError( - `This message is too big for bounce receiver, you need to wrap it to a bounced<${idTextErr(param.type)}>.`, + "Bounce receive function can only accept bounced message types", d.loc, ); } @@ -1159,83 +1234,36 @@ export function resolveDescriptors(ctx: CompilerContext) { (v) => v.selector.kind === "bounce-binary" && - v.selector.type === type.name, + v.selector.type === t.name, ) ) { throwCompilationError( - `Bounce receive function for ${idTextErr(param.type)} already exists`, - param.loc, + `Bounce receive function for ${idTextErr(t.name)} already exists`, + d.loc, + ); + } + if (t.fields.length === t.partialFieldCount) { + throwCompilationError( + "This message is small enough for bounce receiver, you need to remove bounced modifier.", + d.loc, ); } s.receivers.push({ selector: { kind: "bounce-binary", name: param.name, - type: idText(param.type), - bounced: false, + type: idText(param.type.messageType), + bounced: true, }, ast: d, }); - } - } else if (param.type.kind === "optional_type") { - throwCompilationError( - "Bounce receive function cannot have optional parameter", - d.loc, - ); - } else if (param.type.kind === "bounced_message_type") { - const t = types.get( - idText(param.type.messageType), - )!; - if (t.kind !== "struct") { - throwCompilationError( - "Bounce receive function can only accept bounced struct types", - d.loc, - ); - } - if (t.ast.kind !== "message_decl") { - throwCompilationError( - "Bounce receive function can only accept bounced message types", - d.loc, - ); - } - if ( - s.receivers.find( - (v) => - v.selector.kind === "bounce-binary" && - v.selector.type === t.name, - ) - ) { - throwCompilationError( - `Bounce receive function for ${idTextErr(t.name)} already exists`, - d.loc, - ); - } - if (t.fields.length === t.partialFieldCount) { + } else { throwCompilationError( - "This message is small enough for bounce receiver, you need to remove bounced modifier.", + "Bounce receive function can only accept bounced struct parameters or Slice", d.loc, ); } - s.receivers.push({ - selector: { - kind: "bounce-binary", - name: param.name, - type: idText(param.type.messageType), - bounced: true, - }, - ast: d, - }); - } else { - throwCompilationError( - "Bounce receive function can only accept bounced struct parameters or Slice", - d.loc, - ); } - } else { - throwCompilationError( - "Invalid receive function selector", - d.loc, - ); } } } @@ -1422,8 +1450,8 @@ export function resolveDescriptors(ctx: CompilerContext) { ); } for (let i = 0; i < traitFunction.params.length; i++) { - const a = funInContractOrTrait.params[i]; - const b = traitFunction.params[i]; + const a = funInContractOrTrait.params[i]!; + const b = traitFunction.params[i]!; if (!typeRefEquals(a.type, b.type)) { throwCompilationError( `Overridden function "${traitFunction.name}" should have same parameter types`, diff --git a/src/types/resolveErrors.ts b/src/types/resolveErrors.ts index 38de9d823..b56cd7773 100644 --- a/src/types/resolveErrors.ts +++ b/src/types/resolveErrors.ts @@ -25,7 +25,7 @@ function resolveStringsInAST(ast: AstNode, ctx: CompilerContext) { return; } const resolved = evalConstantExpression( - node.args[1], + node.args[1]!, ctx, ) as string; if (!exceptions.get(ctx, resolved)) { diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index 5f055f483..b5c02fe62 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -189,126 +189,136 @@ function resolveBinaryOp( // Check operands let resolved: TypeRef; - if ( - exp.op === "-" || - exp.op === "+" || - exp.op === "*" || - exp.op === "/" || - exp.op === "%" || - exp.op === ">>" || - exp.op === "<<" || - exp.op === "&" || - exp.op === "|" || - exp.op === "^" - ) { - if (le.kind !== "ref" || le.optional || le.name !== "Int") { - throwCompilationError( - `Invalid type "${printTypeRef(le)}" for binary operator "${exp.op}"`, - exp.loc, - ); - } - if (re.kind !== "ref" || re.optional || re.name !== "Int") { - throwCompilationError( - `Invalid type "${printTypeRef(re)}" for binary operator "${exp.op}"`, - exp.loc, - ); - } - resolved = { kind: "ref", name: "Int", optional: false }; - } else if ( - exp.op === "<" || - exp.op === "<=" || - exp.op === ">" || - exp.op === ">=" - ) { - if (le.kind !== "ref" || le.optional || le.name !== "Int") { - throwCompilationError( - `Invalid type "${printTypeRef(le)}" for binary operator "${exp.op}"`, - exp.loc, - ); - } - if (re.kind !== "ref" || re.optional || re.name !== "Int") { - throwCompilationError( - `Invalid type "${printTypeRef(re)}" for binary operator "${exp.op}"`, - exp.loc, - ); - } - resolved = { kind: "ref", name: "Bool", optional: false }; - } else if (exp.op === "==" || exp.op === "!=") { - // Check if types are compatible - if (le.kind !== "null" && re.kind !== "null") { - const l = le; - const r = re; - - if (l.kind === "map" && r.kind === "map") { - if ( - l.key !== r.key || - l.value !== r.value || - l.keyAs !== r.keyAs || - l.valueAs !== r.valueAs - ) { + switch (exp.op) { + case "-": + case "+": + case "*": + case "/": + case "%": + case ">>": + case "<<": + case "&": + case "|": + case "^": + { + if (le.kind !== "ref" || le.optional || le.name !== "Int") { throwCompilationError( - `Incompatible types "${printTypeRef(le)}" and "${printTypeRef(re)}" for binary operator "${exp.op}"`, + `Invalid type "${printTypeRef(le)}" for binary operator "${exp.op}"`, exp.loc, ); } - } else { - if (l.kind === "ref_bounced" || r.kind === "ref_bounced") { + if (re.kind !== "ref" || re.optional || re.name !== "Int") { throwCompilationError( - "Bounced types are not supported in binary operators", + `Invalid type "${printTypeRef(re)}" for binary operator "${exp.op}"`, exp.loc, ); } - if (l.kind == "void" || r.kind == "void") { - throwCompilationError( - `Expressions of "" type cannot be used for (non)equality operator "${exp.op}"`, - exp.loc, - ); - } - if (l.kind !== "ref" || r.kind !== "ref") { - throwCompilationError( - `Incompatible types "${printTypeRef(le)}" and "${printTypeRef(re)}" for binary operator "${exp.op}"`, - exp.loc, - ); - } - if (l.name !== r.name) { + resolved = { kind: "ref", name: "Int", optional: false }; + } + break; + case "<": + case "<=": + case ">": + case ">=": + { + if (le.kind !== "ref" || le.optional || le.name !== "Int") { throwCompilationError( - `Incompatible types "${printTypeRef(le)}" and "${printTypeRef(re)}" for binary operator "${exp.op}"`, + `Invalid type "${printTypeRef(le)}" for binary operator "${exp.op}"`, exp.loc, ); } - if ( - r.name !== "Int" && - r.name !== "Bool" && - r.name !== "Address" && - r.name !== "Cell" && - r.name !== "Slice" && - r.name !== "String" - ) { + if (re.kind !== "ref" || re.optional || re.name !== "Int") { throwCompilationError( - `Invalid type "${r.name}" for binary operator "${exp.op}"`, + `Invalid type "${printTypeRef(re)}" for binary operator "${exp.op}"`, exp.loc, ); } + resolved = { kind: "ref", name: "Bool", optional: false }; } - } + break; + case "==": + case "!=": + { + // Check if types are compatible + if (le.kind !== "null" && re.kind !== "null") { + const l = le; + const r = re; + + if (l.kind === "map" && r.kind === "map") { + if ( + l.key !== r.key || + l.value !== r.value || + l.keyAs !== r.keyAs || + l.valueAs !== r.valueAs + ) { + throwCompilationError( + `Incompatible types "${printTypeRef(le)}" and "${printTypeRef(re)}" for binary operator "${exp.op}"`, + exp.loc, + ); + } + } else { + if ( + l.kind === "ref_bounced" || + r.kind === "ref_bounced" + ) { + throwCompilationError( + "Bounced types are not supported in binary operators", + exp.loc, + ); + } + if (l.kind == "void" || r.kind == "void") { + throwCompilationError( + `Expressions of "" type cannot be used for (non)equality operator "${exp.op}"`, + exp.loc, + ); + } + if (l.kind !== "ref" || r.kind !== "ref") { + throwCompilationError( + `Incompatible types "${printTypeRef(le)}" and "${printTypeRef(re)}" for binary operator "${exp.op}"`, + exp.loc, + ); + } + if (l.name !== r.name) { + throwCompilationError( + `Incompatible types "${printTypeRef(le)}" and "${printTypeRef(re)}" for binary operator "${exp.op}"`, + exp.loc, + ); + } + if ( + r.name !== "Int" && + r.name !== "Bool" && + r.name !== "Address" && + r.name !== "Cell" && + r.name !== "Slice" && + r.name !== "String" + ) { + throwCompilationError( + `Invalid type "${r.name}" for binary operator "${exp.op}"`, + exp.loc, + ); + } + } + } - resolved = { kind: "ref", name: "Bool", optional: false }; - } else if (exp.op === "&&" || exp.op === "||") { - if (le.kind !== "ref" || le.optional || le.name !== "Bool") { - throwCompilationError( - `Invalid type "${printTypeRef(le)}" for binary operator "${exp.op}"`, - exp.loc, - ); - } - if (re.kind !== "ref" || re.optional || re.name !== "Bool") { - throwCompilationError( - `Invalid type "${printTypeRef(re)}" for binary operator "${exp.op}"`, - exp.loc, - ); + resolved = { kind: "ref", name: "Bool", optional: false }; + } + break; + case "&&": + case "||": { + if (le.kind !== "ref" || le.optional || le.name !== "Bool") { + throwCompilationError( + `Invalid type "${printTypeRef(le)}" for binary operator "${exp.op}"`, + exp.loc, + ); + } + if (re.kind !== "ref" || re.optional || re.name !== "Bool") { + throwCompilationError( + `Invalid type "${printTypeRef(re)}" for binary operator "${exp.op}"`, + exp.loc, + ); + } + resolved = { kind: "ref", name: "Bool", optional: false }; } - resolved = { kind: "ref", name: "Bool", optional: false }; - } else { - throw Error(`Unsupported operator: ${exp.op}`); } // Register result @@ -325,42 +335,50 @@ function resolveUnaryOp( // Check right type dependent on operator let resolvedType = getExpType(ctx, exp.operand); - if (exp.op === "-" || exp.op === "+" || exp.op === "~") { - if ( - resolvedType.kind !== "ref" || - resolvedType.optional || - resolvedType.name !== "Int" - ) { - throwCompilationError( - `Invalid type "${printTypeRef(resolvedType)}" for unary operator "${exp.op}"`, - exp.loc, - ); - } - } else if (exp.op === "!") { - if ( - resolvedType.kind !== "ref" || - resolvedType.optional || - resolvedType.name !== "Bool" - ) { - throwCompilationError( - `Invalid type "${printTypeRef(resolvedType)}" for unary operator "${exp.op}"`, - exp.loc, - ); - } - } else if (exp.op === "!!") { - if (resolvedType.kind !== "ref" || !resolvedType.optional) { - throwCompilationError( - `Type "${printTypeRef(resolvedType)}" is not optional`, - exp.loc, - ); + switch (exp.op) { + case "-": + case "+": + case "~": + { + if ( + resolvedType.kind !== "ref" || + resolvedType.optional || + resolvedType.name !== "Int" + ) { + throwCompilationError( + `Invalid type "${printTypeRef(resolvedType)}" for unary operator "${exp.op}"`, + exp.loc, + ); + } + } + break; + case "!": + { + if ( + resolvedType.kind !== "ref" || + resolvedType.optional || + resolvedType.name !== "Bool" + ) { + throwCompilationError( + `Invalid type "${printTypeRef(resolvedType)}" for unary operator "${exp.op}"`, + exp.loc, + ); + } + } + break; + case "!!": { + if (resolvedType.kind !== "ref" || !resolvedType.optional) { + throwCompilationError( + `Type "${printTypeRef(resolvedType)}" is not optional`, + exp.loc, + ); + } + resolvedType = { + kind: "ref", + name: resolvedType.name, + optional: false, + }; } - resolvedType = { - kind: "ref", - name: resolvedType.name, - optional: false, - }; - } else { - throwCompilationError(`Unknown operator: ${exp.op}`, exp.loc); } // Register result @@ -378,10 +396,7 @@ function resolveFieldAccess( // Find target type and check for type const src = getExpType(ctx, exp.aggregate); - if ( - src === null || - ((src.kind !== "ref" || src.optional) && src.kind !== "ref_bounced") - ) { + if ((src.kind !== "ref" || src.optional) && src.kind !== "ref_bounced") { throwCompilationError( `Invalid type "${printTypeRef(src)}" for field access`, exp.loc, @@ -484,9 +499,8 @@ function resolveStaticCall( exp.loc, ); } - for (let i = 0; i < f.params.length; i++) { - const a = f.params[i]; - const e = exp.args[i]; + for (const [i, a] of f.params.entries()) { + const e = exp.args[i]!; const t = getExpType(ctx, e); if (!isAssignable(t, a.type)) { throwCompilationError( @@ -524,12 +538,6 @@ function resolveCall( // Resolve return value const src = getExpType(ctx, exp.self); - if (src === null) { - throwCompilationError( - `Invalid type "${printTypeRef(src)}" for function call`, - exp.loc, - ); - } // Handle ref if (src.kind === "ref") { @@ -556,7 +564,7 @@ function resolveCall( } } - const f = srcT.functions.get(idText(exp.method))!; + const f = srcT.functions.get(idText(exp.method)); if (!f) { throwCompilationError( `Type "${src.name}" does not have a function named ${idTextErr(exp.method)}`, @@ -571,9 +579,8 @@ function resolveCall( exp.loc, ); } - for (let i = 0; i < f.params.length; i++) { - const a = f.params[i]; - const e = exp.args[i]; + for (const [i, a] of f.params.entries()) { + const e = exp.args[i]!; const t = getExpType(ctx, e); if (!isAssignable(t, a.type)) { throwCompilationError( @@ -645,9 +652,8 @@ export function resolveInitOf( ast.loc, ); } - for (let i = 0; i < type.init.params.length; i++) { - const a = type.init.params[i]; - const e = ast.args[i]; + for (const [i, a] of type.init.params.entries()) { + const e = ast.args[i]!; const t = getExpType(ctx, e); if (!isAssignable(t, a.type)) { throwCompilationError( @@ -710,117 +716,87 @@ export function resolveExpression( sctx: StatementContext, ctx: CompilerContext, ) { - // - // Literals - // - - if (exp.kind === "boolean") { - return resolveBooleanLiteral(exp, sctx, ctx); - } - if (exp.kind === "number") { - return resolveIntLiteral(exp, sctx, ctx); - } - if (exp.kind === "null") { - return resolveNullLiteral(exp, sctx, ctx); - } - if (exp.kind === "string") { - return resolveStringLiteral(exp, sctx, ctx); - } - - // - // Constructors - // - - if (exp.kind === "struct_instance") { - return resolveStructNew(exp, sctx, ctx); - } - - // - // Binary, unary and suffix operations - // - - if (exp.kind === "op_binary") { - return resolveBinaryOp(exp, sctx, ctx); - } - - if (exp.kind === "op_unary") { - return resolveUnaryOp(exp, sctx, ctx); - } - - // - // References - // + switch (exp.kind) { + case "boolean": { + return resolveBooleanLiteral(exp, sctx, ctx); + } + case "number": { + return resolveIntLiteral(exp, sctx, ctx); + } + case "null": { + return resolveNullLiteral(exp, sctx, ctx); + } + case "string": { + return resolveStringLiteral(exp, sctx, ctx); + } + case "struct_instance": { + return resolveStructNew(exp, sctx, ctx); + } + case "op_binary": { + return resolveBinaryOp(exp, sctx, ctx); + } + case "op_unary": { + return resolveUnaryOp(exp, sctx, ctx); + } + case "id": { + // Find variable + const v = sctx.vars.get(exp.text); + if (!v) { + if (!hasStaticConstant(ctx, exp.text)) { + if (isWildcard(exp)) { + throwCompilationError( + "Wildcard variable name '_' cannot be accessed", + exp.loc, + ); + } + // Handle static struct method calls + try { + const t = getType(ctx, exp.text); + if (t.kind === "struct") { + return registerExpType(ctx, exp, { + kind: "ref", + name: t.name, + optional: false, + }); + } + } catch { + // Ignore + } - if (exp.kind === "id") { - // Find variable - const v = sctx.vars.get(exp.text); - if (!v) { - if (!hasStaticConstant(ctx, exp.text)) { - if (isWildcard(exp)) { throwCompilationError( - "Wildcard variable name '_' cannot be accessed", + "Unable to resolve id " + exp.text, exp.loc, ); + } else { + const cc = getStaticConstant(ctx, exp.text); + return registerExpType(ctx, exp, cc.type); } - // Handle static struct method calls - try { - const t = getType(ctx, exp.text); - if (t.kind === "struct") { - return registerExpType(ctx, exp, { - kind: "ref", - name: t.name, - optional: false, - }); - } - } catch { - // Ignore - } - - throwCompilationError( - "Unable to resolve id " + exp.text, - exp.loc, - ); - } else { - const cc = getStaticConstant(ctx, exp.text); - return registerExpType(ctx, exp, cc.type); } - } - - return registerExpType(ctx, exp, v); - } - - if (exp.kind === "field_access") { - return resolveFieldAccess(exp, sctx, ctx); - } - - // - // Function calls - // - if (exp.kind === "static_call") { - return resolveStaticCall(exp, sctx, ctx); - } - - if (exp.kind === "method_call") { - return resolveCall(exp, sctx, ctx); - } - - if (exp.kind === "init_of") { - return resolveInitOf(exp, sctx, ctx); - } - - if (exp.kind === "conditional") { - return resolveConditional(exp, sctx, ctx); + return registerExpType(ctx, exp, v); + } + case "field_access": { + return resolveFieldAccess(exp, sctx, ctx); + } + case "static_call": { + return resolveStaticCall(exp, sctx, ctx); + } + case "method_call": { + return resolveCall(exp, sctx, ctx); + } + case "init_of": { + return resolveInitOf(exp, sctx, ctx); + } + case "conditional": { + return resolveConditional(exp, sctx, ctx); + } } - - throw Error("Unknown expression"); // Unreachable } export function getAllExpressionTypes(ctx: CompilerContext) { const res: [string, string][] = []; - const a = store.all(ctx); - for (const e in a) { - res.push([a[e].ast.loc.contents, printTypeRef(a[e].description)]); - } + Object.values(store.all(ctx)).forEach((val) => { + res.push([val.ast.loc.contents, printTypeRef(val.description)]); + }); return res; } diff --git a/src/types/resolveSignatures.ts b/src/types/resolveSignatures.ts index b93d52db0..2f4e9566c 100644 --- a/src/types/resolveSignatures.ts +++ b/src/types/resolveSignatures.ts @@ -102,37 +102,36 @@ export function resolveSignatures(ctx: CompilerContext) { } else if (format !== null) { throw Error("Unsupported struct format " + format); } - return `${s.signature}`; + return s.signature; } function createTLBField(src: ABIField) { - if (src.type.kind === "simple") { - let base = createTypeFormat( - src.type.type, - src.type.format ? src.type.format : null, - ); - if (src.type.optional) { - base = "Maybe " + base; + switch (src.type.kind) { + case "simple": { + let base = createTypeFormat( + src.type.type, + src.type.format ? src.type.format : null, + ); + if (src.type.optional) { + base = "Maybe " + base; + } + return src.name + ":" + base; } - return src.name + ":" + base; - } - - if (src.type.kind === "dict") { - if (src.type.format !== null && src.type.format !== undefined) { - throw Error("Unsupported map format " + src.type.format); + case "dict": { + if (src.type.format !== null && src.type.format !== undefined) { + throw Error("Unsupported map format " + src.type.format); + } + const key = createTypeFormat( + src.type.key, + src.type.keyFormat ? src.type.keyFormat : null, + ); + const value = createTypeFormat( + src.type.value, + src.type.valueFormat ? src.type.valueFormat : null, + ); + return src.name + ":dict<" + key + ", " + value + ">"; } - const key = createTypeFormat( - src.type.key, - src.type.keyFormat ? src.type.keyFormat : null, - ); - const value = createTypeFormat( - src.type.value, - src.type.valueFormat ? src.type.valueFormat : null, - ); - return src.name + ":dict<" + key + ", " + value + ">"; } - - throw Error("Unsupported ABI field"); } function createTupleSignature(name: string): { @@ -169,15 +168,14 @@ export function resolveSignatures(ctx: CompilerContext) { return { signature, id, tlb }; } - for (const k in types) { - const t = types[k]; + Object.values(types).forEach((t) => { if (t.kind === "struct") { const r = createTupleSignature(t.name); t.tlb = r.tlb; t.signature = r.signature; t.header = r.id; } - } + }); checkMessageOpcodesUnique(ctx); @@ -283,8 +281,7 @@ function checkMessageOpcodesUniqueInContractOrTrait( function checkMessageOpcodesUnique(ctx: CompilerContext) { const allTypes = getAllTypes(ctx); - for (const aggregateId in allTypes) { - const aggregate = allTypes[aggregateId]; + Object.values(allTypes).forEach((aggregate) => { switch (aggregate.kind) { case "contract": case "trait": @@ -296,5 +293,5 @@ function checkMessageOpcodesUnique(ctx: CompilerContext) { default: break; } - } + }); } diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts index 6db679cdf..61cfcea9a 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -182,352 +182,394 @@ function processStatements( } // Process statement - if (s.kind === "statement_let") { - // Process expression - ctx = resolveExpression(s.expression, sctx, ctx); - - // Check variable name - checkVariableExists(sctx, s.name); - - // Check type - const expressionType = getExpType(ctx, s.expression); - if (s.type !== null) { - const variableType = resolveTypeRef(ctx, s.type); - if (!isAssignable(expressionType, variableType)) { - throwCompilationError( - `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "${printTypeRef(variableType)}"`, - s.loc, - ); + switch (s.kind) { + case "statement_let": + { + // Process expression + ctx = resolveExpression(s.expression, sctx, ctx); + + // Check variable name + checkVariableExists(sctx, s.name); + + // Check type + const expressionType = getExpType(ctx, s.expression); + if (s.type !== null) { + const variableType = resolveTypeRef(ctx, s.type); + if (!isAssignable(expressionType, variableType)) { + throwCompilationError( + `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "${printTypeRef(variableType)}"`, + s.loc, + ); + } + sctx = addVariable(s.name, variableType, sctx); + } else { + if (expressionType.kind === "null") { + throwCompilationError( + `Cannot infer type for ${idTextErr(s.name)}`, + s.loc, + ); + } + if (expressionType.kind === "void") { + throwCompilationError( + `The inferred type of variable ${idTextErr(s.name)} is "void", which is not allowed`, + s.loc, + ); + } + sctx = addVariable(s.name, expressionType, sctx); + } } - sctx = addVariable(s.name, variableType, sctx); - } else { - if (expressionType.kind === "null") { - throwCompilationError( - `Cannot infer type for ${idTextErr(s.name)}`, - s.loc, - ); + break; + case "statement_assign": + { + const tempSctx = { ...sctx, requiredFields: [] }; + // Process lvalue + ctx = resolveExpression(s.path, tempSctx, ctx); + const path = tryExtractPath(s.path); + if (path === null) { + throwCompilationError( + `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + s.path.loc, + ); + } + + // Process expression + ctx = resolveExpression(s.expression, sctx, ctx); + + // Check type + const expressionType = getExpType(ctx, s.expression); + const tailType = getExpType(ctx, s.path); + if (!isAssignable(expressionType, tailType)) { + throwCompilationError( + `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "${printTypeRef(tailType)}"`, + s.loc, + ); + } + + // Mark as assigned + if (path.length === 2 && path[0]!.text === "self") { + const field = path[1]!.text; + if ( + sctx.requiredFields.findIndex((v) => v === field) >= + 0 + ) { + sctx = removeRequiredVariable(field, sctx); + } + } } - if (expressionType.kind === "void") { - throwCompilationError( - `The inferred type of variable ${idTextErr(s.name)} is "void", which is not allowed`, - s.loc, - ); + break; + case "statement_augmentedassign": + { + // Process lvalue + const tempSctx = { ...sctx, requiredFields: [] }; + ctx = resolveExpression(s.path, tempSctx, ctx); + const path = tryExtractPath(s.path); + if (path === null) { + throwCompilationError( + `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + s.path.loc, + ); + } + + // Process expression + ctx = resolveExpression(s.expression, sctx, ctx); + + // Check type + const expressionType = getExpType(ctx, s.expression); + const tailType = getExpType(ctx, s.path); + // Check if types are Int + if ( + expressionType.kind !== "ref" || + expressionType.name !== "Int" || + expressionType.optional || + tailType.kind !== "ref" || + tailType.name !== "Int" || + tailType.optional + ) { + throwCompilationError( + `Type error: Augmented assignment is only allowed for Int type`, + s.loc, + ); + } } - sctx = addVariable(s.name, expressionType, sctx); - } - } else if (s.kind === "statement_assign") { - const tempSctx = { ...sctx, requiredFields: [] }; - // Process lvalue - ctx = resolveExpression(s.path, tempSctx, ctx); - const path = tryExtractPath(s.path); - if (path === null) { - throwCompilationError( - `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, - s.path.loc, - ); - } - - // Process expression - ctx = resolveExpression(s.expression, sctx, ctx); - - // Check type - const expressionType = getExpType(ctx, s.expression); - const tailType = getExpType(ctx, s.path); - if (!isAssignable(expressionType, tailType)) { - throwCompilationError( - `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "${printTypeRef(tailType)}"`, - s.loc, - ); - } - - // Mark as assigned - if (path.length === 2 && path[0].text === "self") { - const field = path[1].text; - if (sctx.requiredFields.findIndex((v) => v === field) >= 0) { - sctx = removeRequiredVariable(field, sctx); + break; + case "statement_expression": + { + // Process expression + ctx = resolveExpression(s.expression, sctx, ctx); + // take `throw` and `throwNative` into account when doing + // return-reachability analysis + if ( + s.expression.kind === "static_call" && + ["throw", "nativeThrow"].includes( + idText(s.expression.function), + ) + ) { + returnAlwaysReachable = true; + } } - } - } else if (s.kind == "statement_augmentedassign") { - // Process lvalue - const tempSctx = { ...sctx, requiredFields: [] }; - ctx = resolveExpression(s.path, tempSctx, ctx); - const path = tryExtractPath(s.path); - if (path === null) { - throwCompilationError( - `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, - s.path.loc, - ); - } - - // Process expression - ctx = resolveExpression(s.expression, sctx, ctx); + break; + case "statement_condition": + { + // Process condition (expression resolved inside) + const r = processCondition(s, sctx, ctx); + ctx = r.ctx; + sctx = r.sctx; + returnAlwaysReachable ||= r.returnAlwaysReachable; + + // Check type + const expressionType = getExpType(ctx, s.condition); + if ( + expressionType.kind !== "ref" || + expressionType.name !== "Bool" || + expressionType.optional + ) { + throwCompilationError( + `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "Bool"`, + s.loc, + ); + } + } + break; + case "statement_return": + { + if (s.expression) { + // Process expression + ctx = resolveExpression(s.expression, sctx, ctx); + + // Check type + const expressionType = getExpType(ctx, s.expression); + + // Actually, we might relax the following restriction in the future + // Because `return foo()` means `foo(); return` for a void-returning function + // And `return foo()` looks nicer when the user needs early exit from a function + // right after executing `foo()` + if (expressionType.kind == "void") { + throwCompilationError( + `'return' statement can only be used with non-void types`, + s.loc, + ); + } + if (!isAssignable(expressionType, sctx.returns)) { + throwCompilationError( + `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "${printTypeRef(sctx.returns)}"`, + s.loc, + ); + } + } else { + if (sctx.returns.kind !== "void") { + throwCompilationError( + `The function fails to return a result of type "${printTypeRef(sctx.returns)}"`, + s.loc, + ); + } + } + + // Check if all required variables are assigned + if (sctx.requiredFields.length > 0) { + if (sctx.requiredFields.length === 1) { + throwCompilationError( + `Field "${sctx.requiredFields[0]}" is not set`, + sctx.root, + ); + } else { + throwCompilationError( + `Fields ${sctx.requiredFields.map((x) => '"' + x + '"').join(", ")} are not set`, + sctx.root, + ); + } + } + + returnAlwaysReachable = true; + } + break; + case "statement_repeat": + { + // Process expression + ctx = resolveExpression(s.iterations, sctx, ctx); + + // Process statements + const r = processStatements(s.statements, sctx, ctx); + ctx = r.ctx; + + // Check type + const expressionType = getExpType(ctx, s.iterations); + if ( + expressionType.kind !== "ref" || + expressionType.name !== "Int" || + expressionType.optional + ) { + throwCompilationError( + `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "Int"`, + s.loc, + ); + } + } + break; + case "statement_until": + { + // Process expression + ctx = resolveExpression(s.condition, sctx, ctx); + + // Process statements + const r = processStatements(s.statements, sctx, ctx); + ctx = r.ctx; + // XXX a do-until loop is a weird place to always return from a function + // so we might want to issue a warning here + returnAlwaysReachable ||= r.returnAlwaysReachable; + + // Check type + const expressionType = getExpType(ctx, s.condition); + if ( + expressionType.kind !== "ref" || + expressionType.name !== "Bool" || + expressionType.optional + ) { + throwCompilationError( + `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "Bool"`, + s.loc, + ); + } + } + break; + case "statement_while": + { + // Process expression + ctx = resolveExpression(s.condition, sctx, ctx); + + // Process statements + const r = processStatements(s.statements, sctx, ctx); + ctx = r.ctx; + // a while loop might be executed zero times, so + // even if its body always returns from a function + // we don't care + + // Check type + const expressionType = getExpType(ctx, s.condition); + if ( + expressionType.kind !== "ref" || + expressionType.name !== "Bool" || + expressionType.optional + ) { + throwCompilationError( + `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "Bool"`, + s.loc, + ); + } + } + break; + case "statement_try": + { + // Process inner statements + const r = processStatements(s.statements, sctx, ctx); + ctx = r.ctx; + sctx = r.sctx; + // try-statement might not return from the current function + // because the control flow can go to the empty catch block + } + break; + case "statement_try_catch": + { + let initialCtx = sctx; + + // Process inner statements + const r = processStatements(s.statements, sctx, ctx); + ctx = r.ctx; + + let catchCtx = sctx; + + // Process catchName variable for exit code + checkVariableExists(initialCtx, s.catchName); + catchCtx = addVariable( + s.catchName, + { kind: "ref", name: "Int", optional: false }, + initialCtx, + ); - // Check type - const expressionType = getExpType(ctx, s.expression); - const tailType = getExpType(ctx, s.path); - // Check if types are Int - if ( - expressionType.kind !== "ref" || - expressionType.name !== "Int" || - expressionType.optional || - tailType.kind !== "ref" || - tailType.name !== "Int" || - tailType.optional - ) { - throwCompilationError( - `Type error: Augmented assignment is only allowed for Int type`, - s.loc, - ); - } - } else if (s.kind === "statement_expression") { - // Process expression - ctx = resolveExpression(s.expression, sctx, ctx); - // take `throw` and `throwNative` into account when doing - // return-reachability analysis - if ( - s.expression.kind === "static_call" && - ["throw", "nativeThrow"].includes(idText(s.expression.function)) - ) { - returnAlwaysReachable = true; - } - } else if (s.kind === "statement_condition") { - // Process condition (expression resolved inside) - const r = processCondition(s, sctx, ctx); - ctx = r.ctx; - sctx = r.sctx; - returnAlwaysReachable ||= r.returnAlwaysReachable; - - // Check type - const expressionType = getExpType(ctx, s.condition); - if ( - expressionType.kind !== "ref" || - expressionType.name !== "Bool" || - expressionType.optional - ) { - throwCompilationError( - `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "Bool"`, - s.loc, - ); - } - } else if (s.kind === "statement_return") { - if (s.expression) { - // Process expression - ctx = resolveExpression(s.expression, sctx, ctx); - - // Check type - const expressionType = getExpType(ctx, s.expression); - - // Actually, we might relax the following restriction in the future - // Because `return foo()` means `foo(); return` for a void-returning function - // And `return foo()` looks nicer when the user needs early exit from a function - // right after executing `foo()` - if (expressionType.kind == "void") { - throwCompilationError( - `'return' statement can only be used with non-void types`, - s.loc, + // Process catch statements + const rCatch = processStatements( + s.catchStatements, + catchCtx, + ctx, ); + ctx = rCatch.ctx; + catchCtx = rCatch.sctx; + // if both catch- and try- blocks always return from the current function + // we mark the whole try-catch statement as always returning + returnAlwaysReachable ||= + r.returnAlwaysReachable && rCatch.returnAlwaysReachable; + + // Merge statement contexts + const removed: string[] = []; + for (const f of initialCtx.requiredFields) { + if (!catchCtx.requiredFields.find((v) => v === f)) { + removed.push(f); + } + } + for (const r of removed) { + initialCtx = removeRequiredVariable(r, initialCtx); + } } - if (!isAssignable(expressionType, sctx.returns)) { + break; + case "statement_foreach": { + let initialCtx = sctx; // Preserve initial context to use later for merging + + // Resolve map expression + ctx = resolveExpression(s.map, sctx, ctx); + const mapPath = tryExtractPath(s.map); + if (mapPath === null) { throwCompilationError( - `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "${printTypeRef(sctx.returns)}"`, - s.loc, + `foreach is only allowed over maps that are path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + s.map.loc, ); } - } else { - if (sctx.returns.kind !== "void") { + + // Check if map is valid + const mapType = getExpType(ctx, s.map); + if (mapType.kind !== "map") { throwCompilationError( - `The function fails to return a result of type "${printTypeRef(sctx.returns)}"`, - s.loc, + `foreach can only be used on maps, but "${mapPath.map((id) => id.text).join(".")}" has type "${printTypeRef(mapType)}"`, + s.map.loc, ); } - } - // Check if all required variables are assigned - if (sctx.requiredFields.length > 0) { - if (sctx.requiredFields.length === 1) { - throwCompilationError( - `Field "${sctx.requiredFields[0]}" is not set`, - sctx.root, + let foreachCtx = sctx; + + // Add key and value to statement context + if (!isWildcard(s.keyName)) { + checkVariableExists(initialCtx, s.keyName); + foreachCtx = addVariable( + s.keyName, + { kind: "ref", name: mapType.key, optional: false }, + initialCtx, ); - } else { - throwCompilationError( - `Fields ${sctx.requiredFields.map((x) => '"' + x + '"').join(", ")} are not set`, - sctx.root, + } + if (!isWildcard(s.valueName)) { + checkVariableExists(foreachCtx, s.valueName); + foreachCtx = addVariable( + s.valueName, + { kind: "ref", name: mapType.value, optional: false }, + foreachCtx, ); } - } - - returnAlwaysReachable = true; - } else if (s.kind === "statement_repeat") { - // Process expression - ctx = resolveExpression(s.iterations, sctx, ctx); - // Process statements - const r = processStatements(s.statements, sctx, ctx); - ctx = r.ctx; - - // Check type - const expressionType = getExpType(ctx, s.iterations); - if ( - expressionType.kind !== "ref" || - expressionType.name !== "Int" || - expressionType.optional - ) { - throwCompilationError( - `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "Int"`, - s.loc, - ); - } - } else if (s.kind === "statement_until") { - // Process expression - ctx = resolveExpression(s.condition, sctx, ctx); - - // Process statements - const r = processStatements(s.statements, sctx, ctx); - ctx = r.ctx; - // XXX a do-until loop is a weird place to always return from a function - // so we might want to issue a warning here - returnAlwaysReachable ||= r.returnAlwaysReachable; - - // Check type - const expressionType = getExpType(ctx, s.condition); - if ( - expressionType.kind !== "ref" || - expressionType.name !== "Bool" || - expressionType.optional - ) { - throwCompilationError( - `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "Bool"`, - s.loc, - ); - } - } else if (s.kind === "statement_while") { - // Process expression - ctx = resolveExpression(s.condition, sctx, ctx); - - // Process statements - const r = processStatements(s.statements, sctx, ctx); - ctx = r.ctx; - // a while loop might be executed zero times, so - // even if its body always returns from a function - // we don't care - - // Check type - const expressionType = getExpType(ctx, s.condition); - if ( - expressionType.kind !== "ref" || - expressionType.name !== "Bool" || - expressionType.optional - ) { - throwCompilationError( - `Type mismatch: "${printTypeRef(expressionType)}" is not assignable to "Bool"`, - s.loc, - ); - } - } else if (s.kind === "statement_try") { - // Process inner statements - const r = processStatements(s.statements, sctx, ctx); - ctx = r.ctx; - sctx = r.sctx; - // try-statement might not return from the current function - // because the control flow can go to the empty catch block - } else if (s.kind === "statement_try_catch") { - let initialCtx = sctx; - - // Process inner statements - const r = processStatements(s.statements, sctx, ctx); - ctx = r.ctx; - - let catchCtx = sctx; - - // Process catchName variable for exit code - checkVariableExists(initialCtx, s.catchName); - catchCtx = addVariable( - s.catchName, - { kind: "ref", name: "Int", optional: false }, - initialCtx, - ); - - // Process catch statements - const rCatch = processStatements(s.catchStatements, catchCtx, ctx); - ctx = rCatch.ctx; - catchCtx = rCatch.sctx; - // if both catch- and try- blocks always return from the current function - // we mark the whole try-catch statement as always returning - returnAlwaysReachable ||= - r.returnAlwaysReachable && rCatch.returnAlwaysReachable; - - // Merge statement contexts - const removed: string[] = []; - for (const f of initialCtx.requiredFields) { - if (!catchCtx.requiredFields.find((v) => v === f)) { - removed.push(f); + // Process inner statements + const r = processStatements(s.statements, foreachCtx, ctx); + ctx = r.ctx; + foreachCtx = r.sctx; + + // Merge statement contexts (similar to catch block merging) + const removed: string[] = []; + for (const f of initialCtx.requiredFields) { + if (!foreachCtx.requiredFields.find((v) => v === f)) { + removed.push(f); + } } - } - for (const r of removed) { - initialCtx = removeRequiredVariable(r, initialCtx); - } - } else if (s.kind === "statement_foreach") { - let initialCtx = sctx; // Preserve initial context to use later for merging - - // Resolve map expression - ctx = resolveExpression(s.map, sctx, ctx); - const mapPath = tryExtractPath(s.map); - if (mapPath === null) { - throwCompilationError( - `foreach is only allowed over maps that are path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, - s.map.loc, - ); - } - - // Check if map is valid - const mapType = getExpType(ctx, s.map); - if (mapType.kind !== "map") { - throwCompilationError( - `foreach can only be used on maps, but "${mapPath.map((id) => id.text).join(".")}" has type "${printTypeRef(mapType)}"`, - s.map.loc, - ); - } - - let foreachCtx = sctx; - - // Add key and value to statement context - if (!isWildcard(s.keyName)) { - checkVariableExists(initialCtx, s.keyName); - foreachCtx = addVariable( - s.keyName, - { kind: "ref", name: mapType.key, optional: false }, - initialCtx, - ); - } - if (!isWildcard(s.valueName)) { - checkVariableExists(foreachCtx, s.valueName); - foreachCtx = addVariable( - s.valueName, - { kind: "ref", name: mapType.value, optional: false }, - foreachCtx, - ); - } - - // Process inner statements - const r = processStatements(s.statements, foreachCtx, ctx); - ctx = r.ctx; - foreachCtx = r.sctx; - - // Merge statement contexts (similar to catch block merging) - const removed: string[] = []; - for (const f of initialCtx.requiredFields) { - if (!foreachCtx.requiredFields.find((v) => v === f)) { - removed.push(f); + for (const r of removed) { + initialCtx = removeRequiredVariable(r, initialCtx); } - } - for (const r of removed) { - initialCtx = removeRequiredVariable(r, initialCtx); - } - sctx = initialCtx; // Re-assign the modified initial context back to sctx after merging - } else { - throw Error("Unknown statement"); + sctx = initialCtx; // Re-assign the modified initial context back to sctx after merging + } } } @@ -625,62 +667,72 @@ export function resolveStatements(ctx: CompilerContext) { { kind: "ref", name: t.name, optional: false }, sctx, ); - if ( - f.selector.kind === "internal-binary" || - f.selector.kind === "external-binary" - ) { - sctx = addVariable( - f.selector.name, - { kind: "ref", name: f.selector.type, optional: false }, - sctx, - ); - } else if ( - f.selector.kind === "internal-empty" || - f.selector.kind === "external-empty" || - f.selector.kind === "external-comment" || - f.selector.kind === "internal-comment" - ) { - // Nothing to add to context - } else if ( - f.selector.kind === "internal-comment-fallback" || - f.selector.kind === "external-comment-fallback" - ) { - sctx = addVariable( - f.selector.name, - { kind: "ref", name: "String", optional: false }, - sctx, - ); - } else if ( - f.selector.kind === "internal-fallback" || - f.selector.kind === "external-fallback" - ) { - sctx = addVariable( - f.selector.name, - { kind: "ref", name: "Slice", optional: false }, - sctx, - ); - } else if (f.selector.kind === "bounce-fallback") { - sctx = addVariable( - f.selector.name, - { kind: "ref", name: "Slice", optional: false }, - sctx, - ); - } else if (f.selector.kind === "bounce-binary") { - sctx = addVariable( - f.selector.name, - f.selector.bounced - ? { kind: "ref_bounced", name: f.selector.type } - : { - kind: "ref", - name: f.selector.type, - optional: false, - }, - sctx, - ); - } else { - throw Error("Unknown selector"); + switch (f.selector.kind) { + case "internal-binary": + case "external-binary": + { + sctx = addVariable( + f.selector.name, + { + kind: "ref", + name: f.selector.type, + optional: false, + }, + sctx, + ); + } + break; + case "internal-empty": + case "external-empty": + case "external-comment": + case "internal-comment": + // Nothing to add to context + break; + case "internal-comment-fallback": + case "external-comment-fallback": + { + sctx = addVariable( + f.selector.name, + { kind: "ref", name: "String", optional: false }, + sctx, + ); + } + break; + case "internal-fallback": + case "external-fallback": + { + sctx = addVariable( + f.selector.name, + { kind: "ref", name: "Slice", optional: false }, + sctx, + ); + } + break; + case "bounce-fallback": + { + sctx = addVariable( + f.selector.name, + { kind: "ref", name: "Slice", optional: false }, + sctx, + ); + } + break; + case "bounce-binary": + { + sctx = addVariable( + f.selector.name, + f.selector.bounced + ? { kind: "ref_bounced", name: f.selector.type } + : { + kind: "ref", + name: f.selector.type, + optional: false, + }, + sctx, + ); + } + break; } - // Process ctx = processFunctionBody(f.ast.statements, sctx, ctx); } @@ -702,10 +754,7 @@ export function resolveStatements(ctx: CompilerContext) { sctx = addVariable(a.name, a.type, sctx); } - // Process - if (f.ast.statements) { - ctx = processFunctionBody(f.ast.statements, sctx, ctx); - } + ctx = processFunctionBody(f.ast.statements, sctx, ctx); } } } diff --git a/src/types/types.ts b/src/types/types.ts index 9d3061d5f..eafd28cd4 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,4 +1,5 @@ import { ABIField, Address, Cell } from "@ton/core"; +import { throwInternalCompilerError } from "../errors"; import { AstConstantDef, AstFunctionDef, @@ -13,7 +14,7 @@ import { AstFunctionDecl, AstConstantDecl, } from "../grammar/ast"; -import { ItemOrigin } from "../grammar/grammar"; +import { dummySrcInfo, ItemOrigin } from "../grammar/grammar"; // import { // Value // } from "../grammar/value"; @@ -80,6 +81,31 @@ export type Value = | CommentValue | StructValue; +export function showValue(val: Value): string { + if (typeof val === "bigint") { + return val.toString(10); + } else if (typeof val === "string") { + return val; + } else if (typeof val === "boolean") { + return val ? "true" : "false"; + } else if (Address.isAddress(val)) { + return val.toRawString(); + } else if (val instanceof Cell) { + return val.toString(); + } else if (val === null) { + return "null"; + } else if (val instanceof CommentValue) { + return val.comment; + } else if (typeof val === "object" && "$tactStruct" in val) { + const assocList = Object.entries(val).map(([key, value]) => { + return `${key}: ${showValue(value)}`; + }); + return `{${assocList.join(",")}}`; + } else { + throwInternalCompilerError("Invalid value", dummySrcInfo); + } +} + export type FieldDescription = { name: string; index: number; @@ -234,18 +260,17 @@ export type InitDescription = { }; export function printTypeRef(src: TypeRef): string { - if (src.kind === "ref") { - return src.name + (src.optional ? "?" : ""); - } else if (src.kind === "map") { - return `map<${src.key + (src.keyAs ? " as " + src.keyAs : "")}, ${src.value + (src.valueAs ? " as " + src.valueAs : "")}>`; - } else if (src.kind === "void") { - return ""; - } else if (src.kind === "null") { - return ""; - } else if (src.kind === "ref_bounced") { - return `bounced<${src.name}>`; - } else { - throw Error("Invalid type ref"); + switch (src.kind) { + case "ref": + return `${src.name}${src.optional ? "?" : ""}`; + case "map": + return `map<${src.key + (src.keyAs ? " as " + src.keyAs : "")}, ${src.value + (src.valueAs ? " as " + src.valueAs : "")}>`; + case "void": + return ""; + case "null": + return ""; + case "ref_bounced": + return `bounced<${src.name}>`; } } diff --git a/src/utils/crc16.ts b/src/utils/crc16.ts index e907f5b73..6c8ee9966 100644 --- a/src/utils/crc16.ts +++ b/src/utils/crc16.ts @@ -37,10 +37,10 @@ export function crc16(data: string | Buffer) { let crc = 0; - for (let index = 0; index < data.length; index++) { - const byte = data[index]; - crc = (TABLE[((crc >> 8) ^ byte) & 0xff] ^ (crc << 8)) & 0xffff; - } + data.forEach( + (byte) => + (crc = (TABLE[((crc >> 8) ^ byte) & 0xff]! ^ (crc << 8)) & 0xffff), + ); return crc; } diff --git a/src/utils/crc32.ts b/src/utils/crc32.ts index 6ef03e942..71560cee2 100644 --- a/src/utils/crc32.ts +++ b/src/utils/crc32.ts @@ -6,8 +6,9 @@ export function crc32(bytes: Uint8Array, crc = 0xffffffff) { if (crc32_table === undefined) { calcTable(); } - for (let i = 0; i < bytes.length; ++i) - crc = crc32_table![(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8); + bytes.forEach( + (byte) => (crc = crc32_table![(crc ^ byte) & 0xff]! ^ (crc >>> 8)), + ); return (crc ^ -1) >>> 0; } diff --git a/src/utils/errorToString.ts b/src/utils/errorToString.ts index ef18511eb..a0477e996 100644 --- a/src/utils/errorToString.ts +++ b/src/utils/errorToString.ts @@ -2,6 +2,7 @@ export function errorToString(src: unknown): string { if (src instanceof Error) { return src.stack || src.message; } else { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions return `${src}`; } } diff --git a/src/utils/text.ts b/src/utils/text.ts index 895ba2be9..116b7666a 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -1,31 +1,26 @@ -export function isBlank(src: string) { +export function isBlank(src: string): boolean { return src.trim().length === 0; } -export function indentWidth(src: string) { - for (let i = 0; i < src.length; i++) { - if (!isBlank(src[i])) { - return i; - } - } - return src.length; +export function indentWidth(src: string): number { + return src.length - src.trimStart().length; } -export function trimIndent(src: string) { - // Prase lines +export function trimIndent(src: string): string { + // Parse lines let lines = src.split("\n"); if (lines.length === 0) { return ""; } if (lines.length === 1) { - return lines[0].trim(); + return lines[0]!.trim(); } // Remove first and last empty line - if (isBlank(lines[0])) { + if (isBlank(lines[0]!)) { lines = lines.slice(1); } - if (isBlank(lines[lines.length - 1])) { + if (isBlank(lines[lines.length - 1]!)) { lines = lines.slice(0, lines.length - 1); } if (lines.length === 0) { diff --git a/src/verify.ts b/src/verify.ts index 6f05c1c15..06784f739 100644 --- a/src/verify.ts +++ b/src/verify.ts @@ -37,6 +37,10 @@ export async function verify(args: { return { ok: false, error: "invalid-package-format" }; } + if (unpacked.sources === undefined) { + return { ok: false, error: "invalid-package-format" }; + } + // Check compiler and version if (unpacked.compiler.name !== "tact") { return { ok: false, error: "invalid-compiler" }; @@ -69,9 +73,9 @@ export async function verify(args: { }; // Build - const files: { [key: string]: string } = {}; - for (const s in unpacked.sources) { - files["contract/" + s] = unpacked.sources[s]; + const files: Record = {}; + for (const [name, source] of Object.entries(unpacked.sources)) { + files["contract/" + name] = source; } const result = await run({ config, files, logger }); diff --git a/tsconfig.json b/tsconfig.json index 757a55252..145828e33 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -36,7 +36,7 @@ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + "noUncheckedIndexedAccess": true /* Include 'undefined' in index signature results */, // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ /* Module Resolution Options */