WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 374d1f8

Browse files
committed
feat(ir): Introduce branded types
Closes #191
1 parent 16914d8 commit 374d1f8

File tree

12 files changed

+74
-57
lines changed

12 files changed

+74
-57
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121

2222
### Changed
2323
- Improved and optimized the test suite: PR [#184](https://github.com/nowarp/misti/pull/184)
24+
- Introduced the branded type pattern to improve type safety: Issue [#191](https://github.com/nowarp/misti/issues/191)
2425

2526
## [0.4.2] - 2024-10-12
2627

examples/implicit-init/implicitInit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ASTDetector } from "../../src/detectors/detector";
2-
import { CompilationUnit } from "../../src/internals/ir";
2+
import { CompilationUnit, FunctionName } from "../../src/internals/ir";
33
import { MistiTactWarning, Severity } from "../../src/internals/warnings";
44

55
/**
@@ -32,7 +32,7 @@ export class ImplicitInit extends ASTDetector {
3232

3333
async check(cu: CompilationUnit): Promise<MistiTactWarning[]> {
3434
return Array.from(cu.contracts).reduce((foundErrors, [_, contract]) => {
35-
if (!cu.findMethodCFGByName(contract.name, "init")) {
35+
if (!cu.findMethodCFGByName(contract.name, "init" as FunctionName)) {
3636
const err = this.makeWarning(
3737
`Contract ${contract.name} doesn't define an init function`,
3838
contract.ref,

src/detectors/builtin/argCopyMutation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class ArgCopyMutation extends ASTDetector {
6464
fun.kind === "function_def" &&
6565
this.usedInAllReturns(
6666
argName,
67-
returnStatements.get(idText(fun.name))!,
67+
returnStatements.get(idText(fun.name) as FunctionName)!,
6868
)
6969
) {
7070
return;

src/internals/ir/astStore.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { InternalException } from "../exceptions";
22
import { mergeMaps } from "../util";
3+
import { FunctionName } from "./types";
34
import {
45
AstAsmFunctionDef,
56
AstConstantDef,
@@ -31,8 +32,6 @@ export type AstItemParams = Partial<{
3132
filename?: string;
3233
}>;
3334

34-
type FunctionName = string;
35-
type MethodName = string;
3635
type Filename = string;
3736

3837
/**
@@ -536,12 +535,14 @@ export class TactASTStore {
536535
*/
537536
public getReturnTypes(): Map<FunctionName, AstType | null> {
538537
const result = new Map<FunctionName, AstType | null>();
539-
this.asmFunctions.forEach((f) => result.set(idText(f.name), f.return));
538+
this.asmFunctions.forEach((f) =>
539+
result.set(idText(f.name) as FunctionName, f.return),
540+
);
540541
this.functions.forEach((f) => {
541542
if (!this.isContractItem(f.id))
542543
result.set(
543544
// Other kinds of functions cannot be present on the top-level
544-
idText((f as AstFunctionDef).name),
545+
idText((f as AstFunctionDef).name) as FunctionName,
545546
(f as AstFunctionDef).return,
546547
);
547548
});
@@ -557,8 +558,8 @@ export class TactASTStore {
557558
entryId: AstNode["id"],
558559
withTraits: boolean = true,
559560
visited = new Set<number>(),
560-
): Map<MethodName, AstType | null> {
561-
let result = new Map<MethodName, AstType | null>();
561+
): Map<FunctionName, AstType | null> {
562+
let result = new Map<FunctionName, AstType | null>();
562563

563564
// Avoid recursion if used on AST before typechecking
564565
if (visited.has(entryId)) return result;
@@ -570,7 +571,7 @@ export class TactASTStore {
570571
this.functions.forEach((f) => {
571572
// Don't consider receivers/inits since we cannot call them from the contract
572573
if (f.kind === "function_def" && contractEntries.has(f.id))
573-
result.set(idText(f.name), f.return);
574+
result.set(idText(f.name) as FunctionName, f.return);
574575
});
575576
}
576577

src/internals/ir/builders/ir.ts

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {
2+
ContractIdx,
23
BasicBlock,
34
BasicBlockIdx,
45
BasicBlockKind,
56
CFG,
67
CFGIdx,
78
CompilationUnit,
89
Contract,
9-
ContractIdx,
1010
ContractName,
1111
Edge,
1212
FunctionKind,
@@ -113,14 +113,15 @@ export class TactIRBuilder {
113113
private registerFunctions(): void {
114114
this.functionIndexes = this.ast.functions.reduce((acc, fun) => {
115115
if (fun.kind == "function_def") {
116+
const funName = fun.name.text as FunctionName;
116117
const idx = this.registerCFGIdx(
117-
fun.name.text,
118+
funName,
118119
fun.id,
119120
"function",
120121
fun.loc.origin,
121122
fun.loc,
122123
);
123-
acc.set(fun.name.text, idx);
124+
acc.set(funName, idx);
124125
}
125126
return acc;
126127
}, new Map<FunctionName, CFGIdx>());
@@ -133,12 +134,13 @@ export class TactIRBuilder {
133134
private createFunctions(): Map<CFGIdx, CFG> {
134135
return this.ast.functions.reduce((acc, fun) => {
135136
if (fun.kind == "function_def") {
136-
const idx = this.functionIndexes.get(fun.name.text)!;
137+
const funName = fun.name.text as FunctionName;
138+
const idx = this.functionIndexes.get(funName)!;
137139
acc.set(
138140
idx,
139141
this.createCFGFromStatements(
140142
idx,
141-
fun.name.text,
143+
funName,
142144
fun.id,
143145
"function",
144146
fun.loc.origin,
@@ -157,13 +159,21 @@ export class TactIRBuilder {
157159
private getMethodInfo(
158160
decl: AstContractDeclaration,
159161
contractId: number,
160-
): [string | undefined, FunctionKind | undefined, AstStatement[] | null] {
162+
): [
163+
FunctionName | undefined,
164+
FunctionKind | undefined,
165+
AstStatement[] | null,
166+
] {
161167
return decl.kind === "function_def"
162-
? [decl.name.text, "method", decl.statements]
168+
? [decl.name.text as FunctionName, "method", decl.statements]
163169
: decl.kind === "contract_init"
164-
? [`init_${contractId}`, "method", decl.statements]
170+
? [`init_${contractId}` as FunctionName, "method", decl.statements]
165171
: decl.kind === "receiver"
166-
? [generateReceiveName(decl), "receive", decl.statements]
172+
? [
173+
generateReceiveName(decl) as FunctionName,
174+
"receive",
175+
decl.statements,
176+
]
167177
: [undefined, undefined, null];
168178
}
169179

@@ -173,7 +183,7 @@ export class TactIRBuilder {
173183
private registerContracts(): void {
174184
this.methodIndexes = this.ast.types.reduce((acc, entry) => {
175185
if (entry.kind == "contract") {
176-
const contractName = entry.name.text;
186+
const contractName = entry.name.text as ContractName;
177187
const methodsMap = entry.declarations.reduce((methodAcc, decl) => {
178188
const [name, kind, _] = this.getMethodInfo(decl, entry.id);
179189
// NOTE: We don't create CFG entries for asm functions.
@@ -201,7 +211,7 @@ export class TactIRBuilder {
201211
private createContracts(): Map<ContractIdx, Contract> {
202212
return this.ast.types.reduce((acc, entry) => {
203213
if (entry.kind == "contract") {
204-
const contractName = entry.name.text;
214+
const contractName = entry.name.text as ContractName;
205215
const methodsMap = this.methodIndexes.get(contractName)!;
206216
const methodCFGs = entry.declarations.reduce((methodAcc, decl) => {
207217
const [name, kind, stmts] = this.getMethodInfo(decl, entry.id);
@@ -281,11 +291,15 @@ export class TactIRBuilder {
281291
parentCalls: Set<CFGIdx> = new Set(),
282292
): Set<CFGIdx> {
283293
switch (expr.kind) {
284-
case "method_call": // method
294+
case "method_call":
285295
if (expr.self.kind === "id" && isSelfId(expr.self)) {
286-
const contractMethods = this.methodIndexes.get(expr.self.text);
296+
const contractMethods = this.methodIndexes.get(
297+
expr.self.text as ContractName,
298+
);
287299
if (contractMethods) {
288-
const methodIdx = contractMethods.get(expr.method.text);
300+
const methodIdx = contractMethods.get(
301+
expr.method.text as FunctionName,
302+
);
289303
if (methodIdx !== undefined) {
290304
parentCalls.add(methodIdx);
291305
} else {
@@ -310,8 +324,10 @@ export class TactIRBuilder {
310324
expr.args.forEach((arg) => this.collectFunctionCalls(arg, parentCalls));
311325
this.collectFunctionCalls(expr.self, parentCalls);
312326
break;
313-
case "static_call": // free function
314-
const funcIdx = this.functionIndexes.get(expr.function.text);
327+
case "static_call":
328+
const funcIdx = this.functionIndexes.get(
329+
expr.function.text as FunctionName,
330+
);
315331
if (funcIdx !== undefined) {
316332
parentCalls.add(funcIdx);
317333
}

src/internals/ir/cfg.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
*/
77
import { TactASTStore } from "./astStore";
88
import { IdxGenerator } from "./indices";
9-
import { BasicBlockIdx, CFGIdx, EdgeIdx, FunctionName } from "./types";
9+
import { FunctionName } from "./types";
1010
import { InternalException } from "../exceptions";
1111
import { AstStatement, SrcInfo } from "@tact-lang/compiler/dist/grammar/ast";
1212
import { ItemOrigin } from "@tact-lang/compiler/dist/grammar/grammar";
1313

14+
export type EdgeIdx = number & { readonly __brand: unique symbol };
15+
export type BasicBlockIdx = number & { readonly __brand: unique symbol };
16+
export type CFGIdx = number & { readonly __brand: unique symbol };
17+
1418
/**
1519
* Represents an edge in a Control Flow Graph (CFG), connecting two basic blocks.
1620
* Each edge signifies a potential flow of control from one statement to another.
@@ -24,7 +28,7 @@ export class Edge {
2428
public src: BasicBlockIdx,
2529
public dst: BasicBlockIdx,
2630
) {
27-
this.idx = IdxGenerator.next("cfg_edge");
31+
this.idx = IdxGenerator.next("cfg_edge") as EdgeIdx;
2832
}
2933
}
3034

@@ -61,12 +65,12 @@ export type BasicBlockKind =
6165
export class BasicBlock {
6266
public idx: BasicBlockIdx;
6367
constructor(
64-
public stmtID: number,
68+
public stmtID: AstStatement["id"],
6569
public kind: BasicBlockKind,
6670
public srcEdges: Set<EdgeIdx> = new Set<EdgeIdx>(),
6771
public dstEdges: Set<EdgeIdx> = new Set<EdgeIdx>(),
6872
) {
69-
this.idx = IdxGenerator.next("cfg_bb");
73+
this.idx = IdxGenerator.next("cfg_bb") as BasicBlockIdx;
7074
}
7175

7276
/**
@@ -122,7 +126,7 @@ export class CFG {
122126
public ref: SrcInfo,
123127
idx: CFGIdx | undefined = undefined,
124128
) {
125-
this.idx = idx ? idx : IdxGenerator.next("cfg");
129+
this.idx = idx ? idx : (IdxGenerator.next("cfg") as CFGIdx);
126130
this.bbsMap = new Map();
127131
this.initializeMapping(this.bbsMap, nodes);
128132
this.edgesMap = new Map();
@@ -163,7 +167,7 @@ export class CFG {
163167
isSrc: boolean,
164168
): BasicBlock[] | undefined {
165169
return Array.from(edgeIdxs).reduce(
166-
(acc, srcIdx: BasicBlockIdx) => {
170+
(acc, srcIdx) => {
167171
if (acc === undefined) {
168172
return undefined;
169173
}

src/internals/ir/imports.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { SrcInfo } from "@tact-lang/compiler/dist/grammar/ast";
33
import { ItemOrigin } from "@tact-lang/compiler/dist/grammar/grammar";
44
import path from "path";
55

6-
export type ImportNodeIdx = number;
7-
export type ImportEdgeIdx = number;
6+
export type ImportNodeIdx = number & { readonly __brand: unique symbol };
7+
export type ImportEdgeIdx = number & { readonly __brand: unique symbol };
88
export type ImportLanguage = "tact" | "func";
99
export type ImportDirection = "forward" | "backward";
1010

@@ -27,7 +27,7 @@ export class ImportNode {
2727
public inEdges: Set<ImportEdgeIdx> = new Set(),
2828
public outEdges: Set<ImportEdgeIdx> = new Set(),
2929
) {
30-
this.idx = IdxGenerator.next("import_node");
30+
this.idx = IdxGenerator.next("import_node") as ImportNodeIdx;
3131
}
3232
}
3333

@@ -42,7 +42,7 @@ export class ImportEdge {
4242
/** Source location of the `import` statement. */
4343
public loc: SrcInfo,
4444
) {
45-
this.idx = IdxGenerator.next("import_edge");
45+
this.idx = IdxGenerator.next("import_edge") as ImportEdgeIdx;
4646
}
4747
}
4848

src/internals/ir/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export * from "./cfg";
22
export * from "./astStore";
33
export * from "./imports";
4-
export { CompilationUnit, Contract } from "./ir";
4+
export * from "./ir";
55
export * from "./types";
66
export * from "./builders/";

src/internals/ir/ir.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,15 @@
33
*
44
* @packageDocumentation
55
*/
6+
import { CFGIdx, ContractName, FunctionName, ProjectName } from ".";
67
import { TactASTStore } from "./astStore";
78
import { BasicBlock, CFG } from "./cfg";
89
import { ImportGraph } from "./imports";
910
import { IdxGenerator } from "./indices";
10-
import {
11-
BasicBlockIdx,
12-
CFGIdx,
13-
ContractIdx,
14-
ContractName,
15-
FunctionName,
16-
ProjectName,
17-
} from "./types";
1811
import { AstStatement, SrcInfo } from "@tact-lang/compiler/dist/grammar/ast";
1912

13+
export type ContractIdx = number & { readonly __brand: unique symbol };
14+
2015
/**
2116
* Represents a Compilation Unit, encapsulating the information necessary for
2217
* analyzing a single Tact project.
@@ -42,7 +37,7 @@ export class CompilationUnit {
4237
* Looks for a CFG with a specific index.
4338
* @returns Found CFG or `undefined` if not found.
4439
*/
45-
public findCFGByIdx(idx: BasicBlockIdx): CFG | undefined {
40+
public findCFGByIdx(idx: CFGIdx): CFG | undefined {
4641
const funCfg = this.functions.get(idx);
4742
if (funCfg) return funCfg;
4843
return Array.from(this.contracts.values())
@@ -173,6 +168,6 @@ export class Contract {
173168
public ref: SrcInfo,
174169
idx: ContractIdx | undefined = undefined,
175170
) {
176-
this.idx = idx ? idx : IdxGenerator.next("ir_contract");
171+
this.idx = idx ? idx : (IdxGenerator.next("ir_contract") as ContractIdx);
177172
}
178173
}

src/internals/ir/types.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
11
export type ProjectName = string & { readonly __brand: unique symbol };
2-
export type FunctionName = string;
3-
export type ContractName = string;
4-
5-
export type EdgeIdx = number;
6-
export type BasicBlockIdx = number;
7-
export type CFGIdx = number;
8-
export type ContractIdx = number;
2+
export type FunctionName = string & { readonly __brand: unique symbol };
3+
export type ContractName = string & { readonly __brand: unique symbol };

0 commit comments

Comments
 (0)