From c1f013f3bba294b3186fecf6cb8700abb7dfe663 Mon Sep 17 00:00:00 2001 From: Daniil Sedov <42098239+Gusarich@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:07:05 +0300 Subject: [PATCH] fix: use maps instead of objects for tables of globals (#208) to avoid issues with `toString`, `valueOf`, etc. that exist for JS objects and clash with Tact's builtins when querying e.g. the global functions map --- CHANGELOG.md | 1 + src/abi/global.ts | 36 ++--- src/abi/map.ts | 16 +- src/abi/struct.ts | 8 +- src/generator/writers/writeExpression.ts | 19 ++- .../resolveStatements.spec.ts.snap | 77 +++++++++ src/types/resolveDescriptors.ts | 151 +++++++++--------- src/types/resolveExpression.ts | 16 +- src/types/resolveStatements.ts | 15 +- src/types/stmts-failed/case-30.tact | 9 ++ src/types/stmts/case-10.tact | 14 ++ src/types/stmts/case-11.tact | 6 + src/types/stmts/case-12.tact | 17 ++ 13 files changed, 253 insertions(+), 132 deletions(-) create mode 100644 src/types/stmts-failed/case-30.tact create mode 100644 src/types/stmts/case-10.tact create mode 100644 src/types/stmts/case-11.tact create mode 100644 src/types/stmts/case-12.tact diff --git a/CHANGELOG.md b/CHANGELOG.md index b3d154549..4b27b351b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `@stdlib/stoppable` now imports `@stdlib/ownable` so the programmer does not have to do it separately: PR [#193](https://github.com/tact-lang/tact/pull/193) ### Fixed +- Incorrect "already exists" errors when using names such as `toString` or `valueOf`: PR [#208](https://github.com/tact-lang/tact/pull/208) ## [1.2.0] - 2024-02-29 diff --git a/src/abi/global.ts b/src/abi/global.ts index 7265dc589..6f756f8cb 100644 --- a/src/abi/global.ts +++ b/src/abi/global.ts @@ -8,8 +8,8 @@ import { getErrorId } from "../types/resolveErrors"; import { AbiFunction } from "./AbiFunction"; import { sha256_sync } from "@ton/crypto"; -export const GlobalFunctions: { [key: string]: AbiFunction } = { - ton: { +export const GlobalFunctions: Map = new Map([ + ['ton', { name: 'ton', resolve: (ctx, args, ref) => { if (args.length !== 1) { @@ -30,8 +30,8 @@ export const GlobalFunctions: { [key: string]: AbiFunction } = { const str = resolveConstantValue({ kind: 'ref', name: 'String', optional: false }, resolved[0], ctx.ctx) as string; return toNano(str).toString(10); } - }, - pow: { + }], + ['pow', { name: 'pow', resolve: (ctx, args, ref) => { if (args.length !== 2) { @@ -59,8 +59,8 @@ export const GlobalFunctions: { [key: string]: AbiFunction } = { const b = resolveConstantValue({ kind: 'ref', name: 'Int', optional: false }, resolved[1], ctx.ctx) as bigint; return (a ** b).toString(10); } - }, - require: { + }], + ['require', { name: 'require', resolve: (ctx, args, ref) => { if (args.length !== 2) { @@ -87,8 +87,8 @@ export const GlobalFunctions: { [key: string]: AbiFunction } = { const str = resolveConstantValue({ kind: 'ref', name: 'String', optional: false }, resolved[1], ctx.ctx) as string; return `throw_unless(${getErrorId(str, ctx.ctx)}, ${writeExpression(resolved[0], ctx)})`; } - }, - address: { + }], + ['address', { name: 'address', resolve: (ctx, args, ref) => { if (args.length !== 1) { @@ -122,8 +122,8 @@ export const GlobalFunctions: { [key: string]: AbiFunction } = { ctx.used(res); return res + '()'; } - }, - cell: { + }], + ['cell', { name: 'cell', resolve: (ctx, args, ref) => { if (args.length !== 1) { @@ -156,8 +156,8 @@ export const GlobalFunctions: { [key: string]: AbiFunction } = { ctx.used(res); return `${res}()`; } - }, - dump: { + }], + ['dump', { name: 'dump', resolve: (ctx, args, ref) => { if (args.length !== 1) { @@ -196,8 +196,8 @@ export const GlobalFunctions: { [key: string]: AbiFunction } = { throwError('dump() not supported for argument', ref); } } - }, - emptyMap: { + }], + ['emptyMap', { name: 'emptyMap', resolve: (ctx, args, ref) => { if (args.length !== 0) { @@ -208,8 +208,8 @@ export const GlobalFunctions: { [key: string]: AbiFunction } = { generate: (_ctx, _args, _resolved, _ref) => { return 'null()'; } - }, - sha256: { + }], + ['sha256', { name: 'sha256', resolve: (ctx, args, ref) => { if (args.length !== 1) { @@ -254,5 +254,5 @@ export const GlobalFunctions: { [key: string]: AbiFunction } = { throwError('sha256 expects string or slice argument', ref); } - } -} + }] +]) diff --git a/src/abi/map.ts b/src/abi/map.ts index 7e72d1516..881e7f36f 100644 --- a/src/abi/map.ts +++ b/src/abi/map.ts @@ -4,8 +4,8 @@ import { throwError } from "../grammar/ast"; import { getType } from "../types/resolveDescriptors"; import { AbiFunction } from "./AbiFunction"; -export const MapFunctions: { [key: string]: AbiFunction } = { - set: { +export const MapFunctions: Map = new Map([ + ['set', { name: 'set', resolve(ctx, args, ref) { @@ -152,8 +152,8 @@ export const MapFunctions: { [key: string]: AbiFunction } = { throwError(`set expects a map with Int keys`, ref); } - }, - get: { + }], + ['get', { name: 'get', resolve(ctx, args, ref) { @@ -277,8 +277,8 @@ export const MapFunctions: { [key: string]: AbiFunction } = { throwError(`set expects a map with Int keys`, ref); } - }, - asCell: { + }], + ['asCell', { name: 'asCell', resolve(ctx, args, ref) { @@ -304,5 +304,5 @@ export const MapFunctions: { [key: string]: AbiFunction } = { return writeExpression(exprs[0], ctx); } - } -} \ No newline at end of file + }] +]); diff --git a/src/abi/struct.ts b/src/abi/struct.ts index 5d2fc7a90..e8656b35d 100644 --- a/src/abi/struct.ts +++ b/src/abi/struct.ts @@ -4,8 +4,8 @@ import { throwError } from "../grammar/ast"; import { getType } from "../types/resolveDescriptors"; import { AbiFunction } from "./AbiFunction"; -export const StructFunctions: { [key: string]: AbiFunction } = { - toCell: { +export const StructFunctions: Map = new Map([ + ['toCell', { name: 'toCell', resolve: (ctx, args, ref) => { if (args.length !== 1) { @@ -29,5 +29,5 @@ export const StructFunctions: { [key: string]: AbiFunction } = { } return `${ops.writerCell(args[0].name, ctx)}(${resolved.map((v) => writeExpression(v, ctx)).join(', ')})`; } - } -} \ No newline at end of file + }] +]); diff --git a/src/generator/writers/writeExpression.ts b/src/generator/writers/writeExpression.ts index e0eff15b5..0dc1deb42 100644 --- a/src/generator/writers/writeExpression.ts +++ b/src/generator/writers/writeExpression.ts @@ -471,8 +471,8 @@ export function writeExpression(f: ASTExpression, ctx: WriterContext): string { if (f.kind === 'op_static_call') { // Check global functions - if (GlobalFunctions[f.name]) { - return GlobalFunctions[f.name].generate(ctx, + if (GlobalFunctions.has(f.name)) { + return GlobalFunctions.get(f.name)!.generate(ctx, f.args.map((v) => getExpType(ctx.ctx, v)), f.args, f.ref); @@ -531,9 +531,14 @@ export function writeExpression(f: ASTExpression, ctx: WriterContext): string { // Check struct ABI if (t.kind === 'struct') { - const abi = StructFunctions[f.name]; - if (abi) { - return abi.generate(ctx, [src, ...f.args.map((v) => getExpType(ctx.ctx, v))], [f.src, ...f.args], f.ref); + if (StructFunctions.has(f.name)) { + const abi = StructFunctions.get(f.name)!; + return abi.generate( + ctx, + [src, ...f.args.map((v) => getExpType(ctx.ctx, v))], + [f.src, ...f.args], + f.ref + ); } } @@ -577,10 +582,10 @@ export function writeExpression(f: ASTExpression, ctx: WriterContext): string { // Map types if (src.kind === 'map') { - const abf = MapFunctions[f.name]; - if (!abf) { + if (!MapFunctions.has(f.name)) { throwError(`Map function "${f.name}" not found`, f.ref); } + const abf = MapFunctions.get(f.name)!; return abf.generate(ctx, [src, ...f.args.map((v) => getExpType(ctx.ctx, v))], [f.src, ...f.args], f.ref); } diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index c09d918ed..a873302e2 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -300,6 +300,16 @@ Line 5, col 39: " `; +exports[`resolveStatements should fail statements for case-30 1`] = ` +":7:9: Static function "toString" does not exist +Line 7, col 9: + 6 | init() { +> 7 | toString(); // non-existent function + ^~~~~~~~~~ + 8 | } +" +`; + exports[`resolveStatements should resolve statements for case-0 1`] = ` [ [ @@ -812,3 +822,70 @@ exports[`resolveStatements should resolve statements for case-9 1`] = ` ], ] `; + +exports[`resolveStatements should resolve statements for case-10 1`] = ` +[ + [ + "123", + "Int", + ], + [ + "toString()", + "Int", + ], +] +`; + +exports[`resolveStatements should resolve statements for case-11 1`] = ` +[ + [ + "1", + "Int", + ], + [ + "toString", + "Int", + ], + [ + "dump(toString)", + "", + ], +] +`; + +exports[`resolveStatements should resolve statements for case-12 1`] = ` +[ + [ + "a", + "Int", + ], + [ + "b", + "Int", + ], + [ + "a + b", + "Int", + ], + [ + "2", + "Int", + ], + [ + "(a + b) / 2", + "Int", + ], + [ + "1", + "Int", + ], + [ + "10", + "Int", + ], + [ + "valueOf(1, 10)", + "Int", + ], +] +`; diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts index bb025f670..1c7eb53c8 100644 --- a/src/types/resolveDescriptors.ts +++ b/src/types/resolveDescriptors.ts @@ -104,9 +104,9 @@ export function resolveTypeRef(ctx: CompilerContext, src: ASTTypeRef): TypeRef { throw Error('Invalid type ref'); } -function buildTypeRef(src: ASTTypeRef, types: { [key: string]: TypeDescription }): TypeRef { +function buildTypeRef(src: ASTTypeRef, types: Map): TypeRef { if (src.kind === 'type_ref_simple') { - if (!types[src.name]) { + if (!types.has(src.name)) { throwError('Type ' + src.name + ' not found', src.ref); } return { @@ -116,10 +116,10 @@ function buildTypeRef(src: ASTTypeRef, types: { [key: string]: TypeDescription } }; } if (src.kind === 'type_ref_map') { - if (!types[src.key]) { + if (!types.has(src.key)) { throwError('Type ' + src.key + ' not found', src.ref); } - if (!types[src.value]) { + if (!types.has(src.value)) { throwError('Type ' + src.value + ' not found', src.ref); } return { @@ -140,19 +140,19 @@ function buildTypeRef(src: ASTTypeRef, types: { [key: string]: TypeDescription } throw Error('Unknown type ref'); } -function uidForName(name: string, types: { [key: string]: TypeDescription }) { +function uidForName(name: string, types: Map) { // Resolve unique typeid from crc16 let uid = crc16(name); - while (Object.values(types).find((v) => v.uid === uid)) { + while (Array.from(types.values()).find((v) => v.uid === uid)) { uid = (uid + 1) % 65536; } return uid; } export function resolveDescriptors(ctx: CompilerContext) { - const types: { [key: string]: TypeDescription } = {}; - const staticFunctions: { [key: string]: FunctionDescription } = {}; - const staticConstants: { [key: string]: ConstantDescription } = {}; + const types: Map = new Map(); + const staticFunctions: Map = new Map(); + const staticConstants: Map = new Map(); const ast = getRawAST(ctx); // @@ -160,14 +160,14 @@ export function resolveDescriptors(ctx: CompilerContext) { // for (const a of ast.types) { - if (types[a.name]) { + if (types.has(a.name)) { throwError(`Type ${a.name} already exists`, a.ref); } const uid = uidForName(a.name, types); if (a.kind === 'primitive') { - types[a.name] = { + types.set(a.name, { kind: 'primitive', origin: a.origin, name: a.name, @@ -185,9 +185,9 @@ export function resolveDescriptors(ctx: CompilerContext) { interfaces: [], constants: [], partialFieldCount: 0 - }; + }); } else if (a.kind === 'def_contract') { - types[a.name] = { + types.set(a.name, { kind: 'contract', origin: a.origin, name: a.name, @@ -205,9 +205,9 @@ export function resolveDescriptors(ctx: CompilerContext) { interfaces: a.attributes.filter((v) => v.type === 'interface').map((v) => v.name.value), constants: [], partialFieldCount: 0 - }; + }); } else if (a.kind === 'def_struct') { - types[a.name] = { + types.set(a.name, { kind: 'struct', origin: a.origin, name: a.name, @@ -225,9 +225,9 @@ export function resolveDescriptors(ctx: CompilerContext) { interfaces: [], constants: [], partialFieldCount: 0 - }; + }); } else if (a.kind === 'def_trait') { - types[a.name] = { + types.set(a.name, { kind: 'trait', origin: a.origin, name: a.name, @@ -245,7 +245,7 @@ export function resolveDescriptors(ctx: CompilerContext) { interfaces: a.attributes.filter((v) => v.type === 'interface').map((v) => v.name.value), constants: [], partialFieldCount: 0 - }; + }); } } @@ -285,24 +285,24 @@ export function resolveDescriptors(ctx: CompilerContext) { if (a.kind === 'def_contract') { for (const f of a.declarations) { if (f.kind === 'def_field') { - if (types[a.name].fields.find((v) => v.name === f.name)) { + if (types.get(a.name)!.fields.find((v) => v.name === f.name)) { throwError(`Field ${f.name} already exists`, f.ref); } - if (types[a.name].constants.find((v) => v.name === f.name)) { + if (types.get(a.name)!.constants.find((v) => v.name === f.name)) { throwError(`Constant ${f.name} already exists`, f.ref); } - types[a.name].fields.push(buildFieldDescription(f, types[a.name].fields.length)); + types.get(a.name)!.fields.push(buildFieldDescription(f, types.get(a.name)!.fields.length)); } else if (f.kind === 'def_constant') { - if (types[a.name].fields.find((v) => v.name === f.name)) { + if (types.get(a.name)!.fields.find((v) => v.name === f.name)) { throwError(`Field ${f.name} already exists`, f.ref); } - if (types[a.name].constants.find((v) => v.name === f.name)) { + if (types.get(a.name)!.constants.find((v) => v.name === f.name)) { throwError(`Constant ${f.name} already exists`, f.ref); } if (f.attributes.find((v) => v.type !== 'overrides')) { throwError(`Constant can be only overridden`, f.ref); } - types[a.name].constants.push(buildConstantDescription(f)); + types.get(a.name)!.constants.push(buildConstantDescription(f)); } } } @@ -311,10 +311,10 @@ export function resolveDescriptors(ctx: CompilerContext) { if (a.kind === 'def_struct') { for (const f of a.fields) { - if (types[a.name].fields.find((v) => v.name === f.name)) { + if (types.get(a.name)!.fields.find((v) => v.name === f.name)) { throwError(`Field ${f.name} already exists`, f.ref); } - types[a.name].fields.push(buildFieldDescription(f, types[a.name].fields.length)); + types.get(a.name)!.fields.push(buildFieldDescription(f, types.get(a.name)!.fields.length)); } if (a.fields.length === 0 && !a.message) { throwError(`Struct ${a.name} must have at least one field`, a.ref); @@ -325,18 +325,18 @@ export function resolveDescriptors(ctx: CompilerContext) { if (a.kind === 'def_trait') { for (const f of a.declarations) { if (f.kind === 'def_field') { - if (types[a.name].fields.find((v) => v.name === f.name)) { + if (types.get(a.name)!.fields.find((v) => v.name === f.name)) { throwError(`Field ${f.name} already exists`, f.ref); } if (f.as) { throwError(`Trait field cannot have serialization specifier`, f.ref); } - types[a.name].fields.push(buildFieldDescription(f, types[a.name].fields.length)); + types.get(a.name)!.fields.push(buildFieldDescription(f, types.get(a.name)!.fields.length)); } else if (f.kind === 'def_constant') { - if (types[a.name].fields.find((v) => v.name === f.name)) { + if (types.get(a.name)!.fields.find((v) => v.name === f.name)) { throwError(`Field ${f.name} already exists`, f.ref); } - if (types[a.name].constants.find((v) => v.name === f.name)) { + if (types.get(a.name)!.constants.find((v) => v.name === f.name)) { throwError(`Constant ${f.name} already exists`, f.ref); } if (f.attributes.find((v) => v.type === 'overrides')) { @@ -345,7 +345,7 @@ export function resolveDescriptors(ctx: CompilerContext) { // if (f.attributes.find((v) => v.type === 'abstract')) { // continue; // Do not materialize abstract constants // } - types[a.name].constants.push(buildConstantDescription(f)); + types.get(a.name)!.constants.push(buildConstantDescription(f)); } } } @@ -355,8 +355,8 @@ export function resolveDescriptors(ctx: CompilerContext) { // Populate partial serialization info // - for (const t in types) { - types[t].partialFieldCount = resolvePartialFields(ctx, types[t]) + for (const t of types.values()) { + t.partialFieldCount = resolvePartialFields(ctx, t); } // @@ -439,7 +439,7 @@ export function resolveDescriptors(ctx: CompilerContext) { // Check virtual if (isVirtual) { - const t = types[self!]!; + const t = types.get(self!)!; if (t.kind !== 'trait') { throwError('Virtual functions must be defined within a trait', isVirtual.ref); } @@ -447,7 +447,7 @@ export function resolveDescriptors(ctx: CompilerContext) { // Check abstract if (isAbstract) { - const t = types[self!]!; + const t = types.get(self!)!; if (t.kind !== 'trait') { throwError('Abstract functions must be defined within a trait', isAbstract.ref); } @@ -455,7 +455,7 @@ export function resolveDescriptors(ctx: CompilerContext) { // Check overrides if (isOverrides) { - const t = types[self!]!; + const t = types.get(self!)!; if (t.kind !== 'contract') { throwError('Overrides functions must be defined within a contract', isOverrides.ref); } @@ -492,7 +492,7 @@ export function resolveDescriptors(ctx: CompilerContext) { if (args[0].type.optional) { throwError('Extend functions must have a non-optional type as the first argument', args[0].ref); } - if (!types[args[0].type.name]) { + if (!types.has(args[0].type.name)) { throwError('Type ' + args[0].type.name + ' not found', args[0].ref); } @@ -573,7 +573,7 @@ export function resolveDescriptors(ctx: CompilerContext) { for (const a of ast.types) { if (a.kind === 'def_contract' || a.kind === 'def_trait') { - const s = types[a.name]; + const s = types.get(a.name)!; for (const d of a.declarations) { if (d.kind === 'def_function') { const f = resolveFunctionDescriptor(s.name, d, s.origin); @@ -611,7 +611,7 @@ export function resolveDescriptors(ctx: CompilerContext) { } // Check resolved argument type - const t = types[arg.type.name]; + const t = types.get(arg.type.name); if (!t) { throwError('Type ' + arg.type.name + ' not found', d.ref); } @@ -731,7 +731,7 @@ export function resolveDescriptors(ctx: CompilerContext) { ast: d }); } else { - const type = types[arg.type.name]; + const type = types.get(arg.type.name)!; if (type.ast.kind !== 'def_struct' || !type.ast.message) { throwError('Bounce receive function can only accept bounced message, message or Slice', d.ref); } @@ -753,7 +753,7 @@ export function resolveDescriptors(ctx: CompilerContext) { } } else if (arg.type.kind === "type_ref_bounced") { - const t = types[arg.type.name]; + const t = types.get(arg.type.name)!; if (t.kind !== 'struct') { throwError('Bounce receive function can only accept bounced struct types', d.ref); } @@ -793,8 +793,7 @@ export function resolveDescriptors(ctx: CompilerContext) { // Check for missing init methods // - for (const k in types) { - const t = types[k]; + for (const t of types.values()) { if (t.kind === 'contract') { if (!t.init) { t.init = { @@ -814,8 +813,7 @@ export function resolveDescriptors(ctx: CompilerContext) { // Flatten and resolve traits // - for (const k in types) { - const t = types[k]; + for (const t of types.values()) { if (t.ast.kind === 'def_trait' || t.ast.kind === 'def_contract') { // Flatten traits @@ -827,7 +825,7 @@ export function resolveDescriptors(ctx: CompilerContext) { if (visited.has(name)) { return; } - const tt = types[name]; + const tt = types.get(name); if (!tt) { throwError('Trait ' + name + ' not found', t.ast.ref) } @@ -858,21 +856,19 @@ export function resolveDescriptors(ctx: CompilerContext) { // Verify trait fields // - for (const k in types) { - const t = types[k]; - + for (const t of types.values()) { for (const tr of t.traits) { // Check that trait is valid - if (!types[tr.name]) { + if (!types.has(tr.name)) { throwError('Trait ' + tr.name + ' not found', t.ast.ref); } - if (types[tr.name].kind !== 'trait') { + if (types.get(tr.name)!.kind !== 'trait') { throwError('Type ' + tr.name + ' is not a trait', t.ast.ref); } // Check that trait has all required fields - const ttr = types[tr.name]; + const ttr = types.get(tr.name)!; for (const f of ttr.fields) { // Check if field exists @@ -1024,24 +1020,24 @@ export function resolveDescriptors(ctx: CompilerContext) { return; } if (processing.has(name)) { - throwError(`Circular dependency detected for type ${name}`, types[name].ast.ref); + throwError(`Circular dependency detected for type ${name}`, types.get(name)!.ast.ref); } processing.has(name); // Process dependencies first - const dependencies = Object.values(types).filter((v) => v.traits.find((v2) => v2.name === name)); + const dependencies = Array.from(types.values()).filter((v) => v.traits.find((v2) => v2.name === name)); for (const d of dependencies) { processType(d.name); } // Copy traits - copyTraits(types[name]); + copyTraits(types.get(name)!); // Mark as processed processed.add(name); processing.delete(name); } - for (const k in types) { + for (const k of types.keys()) { processType(k); } @@ -1049,12 +1045,11 @@ export function resolveDescriptors(ctx: CompilerContext) { // Register dependencies // - for (const k in types) { - const t = types[k]; + for (const [k, t] of types) { const dependsOn = new Set(); const handler = (src: ASTNode) => { if (src.kind === 'init_of') { - if (!types[src.name]) { + if (!types.has(src.name)) { throwError(`Type ${src.name} not found`, src.ref); } dependsOn.add(src.name); @@ -1072,7 +1067,7 @@ export function resolveDescriptors(ctx: CompilerContext) { // Add dependencies for (const s of dependsOn) { if (s !== k) { - t.dependsOn.push(types[s]!); + t.dependsOn.push(types.get(s)!); } } } @@ -1082,7 +1077,7 @@ export function resolveDescriptors(ctx: CompilerContext) { // function collectTransient(name: string, to: Set) { - const t = types[name]; + const t = types.get(name)!; for (const d of t.dependsOn) { if (to.has(d.name)) { continue; @@ -1091,13 +1086,13 @@ export function resolveDescriptors(ctx: CompilerContext) { collectTransient(d.name, to); } } - for (const k in types) { + for (const k of types.keys()) { const dependsOn = new Set(); dependsOn.add(k); collectTransient(k, dependsOn); for (const s of dependsOn) { - if (s !== k && !types[k].dependsOn.find((v) => v.name === s)) { - types[k].dependsOn.push(types[s]!); + if (s !== k && !types.get(k)!.dependsOn.find((v) => v.name === s)) { + types.get(k)!.dependsOn.push(types.get(s)!); } } } @@ -1109,18 +1104,18 @@ export function resolveDescriptors(ctx: CompilerContext) { for (const a of ast.functions) { const r = resolveFunctionDescriptor(null, a, a.origin); if (r.self) { - if (types[r.self].functions.has(r.name)) { + if (types.get(r.self)!.functions.has(r.name)) { throwError(`Function ${r.name} already exists in type ${r.self}`, r.ast.ref); } - types[r.self].functions.set(r.name, r); + types.get(r.self)!.functions.set(r.name, r); } else { - if (staticFunctions[r.name]) { + if (staticFunctions.has(r.name)) { throwError(`Static function ${r.name} already exists`, r.ast.ref); } - if (staticConstants[r.name]) { + if (staticConstants.has(r.name)) { throwError(`Static constant ${r.name} already exists`, a.ref); } - staticFunctions[r.name] = r; + staticFunctions.set(r.name, r); } } @@ -1129,27 +1124,27 @@ export function resolveDescriptors(ctx: CompilerContext) { // for (const a of ast.constants) { - if (staticConstants[a.name]) { + if (staticConstants.has(a.name)) { throwError(`Static constant ${a.name} already exists`, a.ref); } - if (staticFunctions[a.name]) { + if (staticFunctions.has(a.name)) { throwError(`Static function ${a.name} already exists`, a.ref); } - staticConstants[a.name] = buildConstantDescription(a); + staticConstants.set(a.name, buildConstantDescription(a)); } // // Register types and functions in context // - for (const t in types) { - ctx = store.set(ctx, t, types[t]); + for (const [k, t] of types) { + ctx = store.set(ctx, k, t); } - for (const t in staticFunctions) { - ctx = staticFunctionsStore.set(ctx, t, staticFunctions[t]); + for (const [k, t] of staticFunctions) { + ctx = staticFunctionsStore.set(ctx, k, t); } - for (const t in staticConstants) { - ctx = staticConstantsStore.set(ctx, t, staticConstants[t]); + for (const [k, t] of staticConstants) { + ctx = staticConstantsStore.set(ctx, k, t); } return ctx; diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index 360fd8de1..871f97082 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -239,8 +239,8 @@ function resolveField(exp: ASTOpField, sctx: StatementContext, ctx: CompilerCont function resolveStaticCall(exp: ASTOpCallStatic, sctx: StatementContext, ctx: CompilerContext): CompilerContext { // Check if abi global function - if (GlobalFunctions[exp.name]) { - const f = GlobalFunctions[exp.name]; + if (GlobalFunctions.has(exp.name)) { + const f = GlobalFunctions.get(exp.name)!; // Resolve arguments for (const e of exp.args) { @@ -317,8 +317,8 @@ function resolveCall(exp: ASTOpCall, sctx: StatementContext, ctx: CompilerContex // Check struct ABI if (srcT.kind === 'struct') { - const abi = StructFunctions[exp.name]; - if (abi) { + if (StructFunctions.has(exp.name)) { + const abi = StructFunctions.get(exp.name)!; const resolved = abi.resolve(ctx, [src, ...exp.args.map((v) => getExpType(ctx, v))], exp.ref); return registerExpType(ctx, exp, resolved); } @@ -347,10 +347,10 @@ function resolveCall(exp: ASTOpCall, sctx: StatementContext, ctx: CompilerContex // Handle map if (src.kind === 'map') { - const abf = MapFunctions[exp.name]; - if (!abf) { + if (!MapFunctions.has(exp.name)) { throwError(`Map function "${exp.name}" not found`, exp.ref); } + const abf = MapFunctions.get(exp.name)!; const resolved = abf.resolve(ctx, [src, ...exp.args.map((v) => getExpType(ctx, v))], exp.ref); return registerExpType(ctx, exp, resolved); } @@ -418,7 +418,7 @@ export function resolveConditional(ast: ASTConditional, sctx: StatementContext, export function resolveLValueRef(path: ASTLvalueRef[], sctx: StatementContext, ctx: CompilerContext): CompilerContext { const paths: ASTLvalueRef[] = path; - let t = sctx.vars[paths[0].name]; + let t = sctx.vars.get(paths[0].name); if (!t) { throwError(`Variable "${paths[0].name}" not found`, paths[0].ref); } @@ -487,7 +487,7 @@ export function resolveExpression(exp: ASTExpression, sctx: StatementContext, ct if (exp.kind === 'id') { // Find variable - const v = sctx.vars[exp.value]; + const v = sctx.vars.get(exp.value); if (!v) { if (!hasStaticConstant(ctx, exp.value)) { throwError('Unable to resolve id ' + exp.value, exp.ref); diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts index cfbe48df6..0f80759ca 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -8,7 +8,7 @@ import { printTypeRef, TypeRef } from "./types"; export type StatementContext = { root: ASTRef, returns: TypeRef, - vars: { [name: string]: TypeRef }; + vars: Map; requiredFields: string[]; }; @@ -16,7 +16,7 @@ function emptyContext(root: ASTRef, returns: TypeRef): StatementContext { return { root, returns, - vars: {}, + vars: new Map(), requiredFields: [] }; } @@ -46,16 +46,13 @@ function removeRequiredVariable(name: string, src: StatementContext): StatementC } function addVariable(name: string, ref: TypeRef, src: StatementContext): StatementContext { - if (src.vars[name]) { + if (src.vars.has(name)) { throw Error('Variable already exists: ' + name); // Should happen earlier } return { ...src, - vars: { - ...src.vars, - [name]: ref - } - }; + vars: new Map(src.vars).set(name, ref) + } } function processCondition(condition: ASTCondition, sctx: StatementContext, ctx: CompilerContext): { ctx: CompilerContext, sctx: StatementContext } { @@ -141,7 +138,7 @@ function processStatements(statements: ASTStatement[], sctx: StatementContext, c } // Add variable to statement context - if (sctx.vars[s.name]) { + if (sctx.vars.has(s.name)) { throwError(`Variable already exists: ${s.name}`, s.ref); } sctx = addVariable(s.name, variableType, sctx); diff --git a/src/types/stmts-failed/case-30.tact b/src/types/stmts-failed/case-30.tact new file mode 100644 index 000000000..70c1d724c --- /dev/null +++ b/src/types/stmts-failed/case-30.tact @@ -0,0 +1,9 @@ +import "@stdlib/deploy"; + +trait BaseTrait { } + +contract SampleTactContract { + init() { + toString(); // non-existent function + } +} \ No newline at end of file diff --git a/src/types/stmts/case-10.tact b/src/types/stmts/case-10.tact new file mode 100644 index 000000000..ef35a06f1 --- /dev/null +++ b/src/types/stmts/case-10.tact @@ -0,0 +1,14 @@ +import "@stdlib/deploy"; +primitive Int; + +trait BaseTrait { } + +fun toString(): Int { + return 123; +} + +contract SampleTactContract { + init() { + toString(); + } +} \ No newline at end of file diff --git a/src/types/stmts/case-11.tact b/src/types/stmts/case-11.tact new file mode 100644 index 000000000..571d7e457 --- /dev/null +++ b/src/types/stmts/case-11.tact @@ -0,0 +1,6 @@ +primitive Int; + +fun testFunction() { + let toString: Int = 1; + dump(toString); +} \ No newline at end of file diff --git a/src/types/stmts/case-12.tact b/src/types/stmts/case-12.tact new file mode 100644 index 000000000..a6d36ab35 --- /dev/null +++ b/src/types/stmts/case-12.tact @@ -0,0 +1,17 @@ +import "@stdlib/deploy"; + +primitive Int; + +trait BaseTrait { } + +fun valueOf(a: Int, b: Int): Int { + return (a + b) / 2; +} + +contract SampleTactContract { + init() { + } + get fun result(): Int { + return valueOf(1, 10); + } +} \ No newline at end of file