Skip to content

Commit

Permalink
fix: failure for toString called as a static function instead of a …
Browse files Browse the repository at this point in the history
…method (#745)

This required redesigning the compiler context storage implementation,
because it used `Record` type which has `toString` inherited from
`Object.prototype`.
This fix changes `Record` to `Map` to avoid these issues and convey
the meaning in a clearer way.
  • Loading branch information
anton-trunov authored Aug 26, 2024
1 parent 626953f commit 7055ce3
Show file tree
Hide file tree
Showing 20 changed files with 1,182 additions and 1,117 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Traits can override inherited abstract functions: PR [#724](https://github.com/tact-lang/tact/pull/724)
- Fix code generation bug for maps from unsigned integers to Boolean values: PR [#725](https://github.com/tact-lang/tact/pull/725)
- Compiler failure when `toString` gets called as a static function and not a method: PR [#745](https://github.com/tact-lang/tact/pull/745)

## [1.4.4] - 2024-08-18

Expand Down
3 changes: 1 addition & 2 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"ignoreDependencies": [
"@tact-lang/ton-abi",
"@tact-lang/ton-jest",
"@types/jest",
"dist"
"@types/jest"
]
}
2 changes: 1 addition & 1 deletion src/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function check(args: {
}): CheckResult {
// Create context
const stdlib = createVirtualFileSystem("@stdlib/", files);
let ctx: CompilerContext = new CompilerContext({ shared: {} });
let ctx: CompilerContext = new CompilerContext();
ctx = featureEnable(ctx, "debug"); // Enable debug flag (does not affect type checking in practice)
ctx = featureEnable(ctx, "masterchain"); // Enable masterchain flag to avoid masterchain-specific errors
ctx = featureEnable(ctx, "external"); // Enable external messages flag to avoid external-specific errors
Expand Down
53 changes: 23 additions & 30 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,42 @@
type Key = string | number;
export type Store<T> = Map<Key, T>;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Stores = Map<symbol, Store<any> | undefined>;

export class CompilerContext {
readonly shared: Record<symbol, object | undefined> = {};
readonly stores: Stores = new Map();

constructor(
args: { shared: Record<symbol, object | undefined> } = {
shared: {},
args: { stores: Stores } = {
stores: new Map(),
},
) {
this.shared = args.shared;
Object.freeze(this.shared);
this.stores = args.stores;
Object.freeze(this.stores);
Object.freeze(this);
}

addShared = <T>(store: symbol, key: string | number, value: T) => {
let sh: Record<string, T> = {};
if (this.shared[store]) {
sh = { ...this.shared[store] };
}
sh[key] = value;
return new CompilerContext({ shared: { ...this.shared, [store]: sh } });
updateStore = <T>(storeDispatch: symbol, key: Key, value: T) => {
const store: Store<T> = new Map(this.stores.get(storeDispatch) ?? []);
store.set(key, value);
const updatedStores = new Map(this.stores);
updatedStores.set(storeDispatch, store);
return new CompilerContext({ stores: updatedStores });
};
}

export function createContextStore<T>() {
const symbol = Symbol();
return {
get(ctx: CompilerContext, key: string | number) {
if (!ctx.shared[symbol]) {
return null;
}
const m = ctx.shared[symbol] as Record<string | number, T>;
if (m[key]) {
return m[key];
} else {
return null;
}
get(ctx: CompilerContext, key: Key): T | null {
return ctx.stores.get(symbol)?.get(key) ?? null;
},
all(ctx: CompilerContext): Record<string | number, T> {
if (!ctx.shared[symbol]) {
return {} as Record<string | number, T>;
}
const m = ctx.shared[symbol] as Record<string | number, T>;
return m;
all(ctx: CompilerContext): Store<T> {
return ctx.stores.get(symbol) ?? new Map();
},
set(ctx: CompilerContext, key: string | number, v: T) {
return ctx.addShared(symbol, key, v);
set(ctx: CompilerContext, key: Key, v: T): CompilerContext {
return ctx.updateStore(symbol, key, v);
},
};
}
4 changes: 2 additions & 2 deletions src/generator/createABI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getAllTypes } from "../types/resolveDescriptors";
import { getAllErrors } from "../types/resolveErrors";

export function createABI(ctx: CompilerContext, name: string): ContractABI {
const allTypes = Object.values(getAllTypes(ctx));
const allTypes = getAllTypes(ctx);

// Contract
const contract = allTypes.find((v) => v.name === name);
Expand Down Expand Up @@ -39,7 +39,7 @@ export function createABI(ctx: CompilerContext, name: string): ContractABI {

// // Receivers
const receivers: ABIReceiver[] = [];
for (const r of Object.values(contract.receivers)) {
for (const r of contract.receivers) {
if (r.selector.kind === "internal-binary") {
receivers.push({
receiver: "internal",
Expand Down
5 changes: 2 additions & 3 deletions src/generator/writeProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ function writeAll(
abiLink: string,
) {
// Load all types
const allTypes = Object.values(getAllTypes(ctx));
const allTypes = getAllTypes(ctx);
const contracts = allTypes.filter((v) => v.kind === "contract");
const c = contracts.find((v) => v.name === name);
if (!c) {
Expand Down Expand Up @@ -377,8 +377,7 @@ function writeAll(
}

// Static functions
const sf = getAllStaticFunctions(ctx);
Object.values(sf).forEach((f) => {
getAllStaticFunctions(ctx).forEach((f) => {
writeFunction(f, wCtx);
});

Expand Down
2 changes: 1 addition & 1 deletion src/generator/writers/writeContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export function writeMainContract(
ctx.append(``);

// Write receivers
for (const r of Object.values(type.receivers)) {
for (const r of type.receivers) {
writeReceiver(type, r, ctx);
}

Expand Down
2 changes: 1 addition & 1 deletion src/generator/writers/writeSerialization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe("writeSerialization", () => {
"user",
wCtx,
);
for (const t of Object.values(getAllTypes(ctx))) {
for (const t of getAllTypes(ctx)) {
if (t.kind === "contract" || t.kind === "struct") {
writeAccessors(t, "user", wCtx);
}
Expand Down
2 changes: 1 addition & 1 deletion src/pipeline/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export async function build(args: {
const logger: ILogger = args.logger ?? new Logger();

// Configure context
let ctx: CompilerContext = new CompilerContext({ shared: {} });
let ctx: CompilerContext = new CompilerContext();
const cfg: string = JSON.stringify({
entrypoint: posixNormalize(config.path),
options: config.options ?? {},
Expand Down
18 changes: 12 additions & 6 deletions src/storage/resolveAllocation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CompilerContext, createContextStore } from "../context";
import { getAllTypes, getType, toBounced } from "../types/resolveDescriptors";
import { getType, toBounced, getAllTypes } from "../types/resolveDescriptors";
import { TypeDescription } from "../types/types";
import { topologicalSort } from "../utils/utils";
import { StorageAllocation } from "./StorageAllocation";
Expand All @@ -12,23 +12,29 @@ import { idText } from "../grammar/ast";

const store = createContextStore<StorageAllocation>();

export function getAllocation(ctx: CompilerContext, name: string) {
export function getAllocation(
ctx: CompilerContext,
name: string,
): StorageAllocation {
const t = store.get(ctx, name);
if (!t) {
throwInternalCompilerError(`Allocation for ${name} not found`);
}
return t;
}

export function getAllocations(ctx: CompilerContext) {
export function getAllocations(ctx: CompilerContext): {
allocation: StorageAllocation;
type: TypeDescription;
}[] {
return getSortedTypes(ctx).map((v) => ({
allocation: getAllocation(ctx, v.name),
type: v,
}));
}

export function getSortedTypes(ctx: CompilerContext) {
const types = Object.values(getAllTypes(ctx)).filter(
export function getSortedTypes(ctx: CompilerContext): TypeDescription[] {
const types = getAllTypes(ctx).filter(
(v) => v.kind === "struct" || v.kind === "contract",
);
let structs = types.filter((t) => t.kind === "struct");
Expand All @@ -54,7 +60,7 @@ export function getSortedTypes(ctx: CompilerContext) {
return structs;
}

export function resolveAllocations(ctx: CompilerContext) {
export function resolveAllocations(ctx: CompilerContext): CompilerContext {
// Load topological order of structs and contracts
const types = getSortedTypes(ctx);

Expand Down
Loading

0 comments on commit 7055ce3

Please sign in to comment.