Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: union with parsedModules #2311

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -56,6 +56,7 @@
"all": "yarn clean && yarn gen && yarn build && yarn coverage && yarn lint:all",
"next-version": "ts-node version.build.ts",
"random-ast": "ts-node ./src/ast/random-ast.ts",
"gen:make-funs": "ts-node ./src/ast/generated/gen-make-functions-script.ts && yarn prettier -w ./src/ast/generated/make-factory.ts",
"top10": "find . -type f -exec du -h {} + | sort -rh | head -n 10"
},
"files": [
@@ -106,6 +107,9 @@
"@types/node": "^22.5.0",
"@typescript-eslint/eslint-plugin": "^8.21.0",
"@typescript-eslint/parser": "^8.21.0",
"@babel/generator": "^7.26.5",
"@babel/parser": "^7.26.5",
"@babel/types": "^7.26.5",
"chalk": "4.1.2",
"cli-table3": "^0.6.5",
"cross-env": "^7.0.3",
308 changes: 308 additions & 0 deletions src/ast/generated/gen-make-functions-script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
import generate from "@babel/generator";
import type { ParserOptions } from "@babel/parser";
import { parse } from "@babel/parser";
import * as t from "@babel/types";
import * as fs from "fs";
import * as path from "path";

function main() {
const options: ParserOptions = {
sourceType: "module",
plugins: ["typescript"],
};

const astModule = fs
.readFileSync(path.join(__dirname, "..", "ast.ts"))
.toString();

const astTypeDecls = parse(astModule, options);
const decls = astTypeDecls.program.body
.filter((stmt) => t.isExportNamedDeclaration(stmt))
.map((stmt) => stmt.declaration)
.filter((stmt) => t.isTSTypeAliasDeclaration(stmt));

const declNames = new Set(decls.map((decl) => decl.id.name));

const finalFunctions: { name: string; code: string }[] = [];

// Extract from the declarations all the union types
const unionTypes = decls
.map((decl) => decl.typeAnnotation)
.filter((decl) => t.isTSUnionType(decl));

for (const unionType of unionTypes) {
const subtypes = unionType.types
.filter((typeDecl) => t.isTSTypeReference(typeDecl))
.map((typeDecl) => typeDecl.typeName)
.filter((typeDecl) => t.isIdentifier(typeDecl));

for (const subtype of subtypes) {
const subtypeDecl = decls.find(
(decl) => decl.id.name === subtype.name,
);
if (typeof subtypeDecl === "undefined") {
throw new Error(`${subtype.name} is not declared.`);
}

const subtypeDeclAnnotation = subtypeDecl.typeAnnotation;
if (t.isTSTypeLiteral(subtypeDeclAnnotation)) {
const genFunctions = createMakeAndDummyFunctions(
subtypeDeclAnnotation,
subtypeDecl.id,
declNames,
);
const genFunctionsFiltered = genFunctions.filter((genF) =>
finalFunctions.every((f) => f.name !== genF.name),
);

if (genFunctionsFiltered.length > 0) {
console.log(
`Generated [${genFunctionsFiltered.map((entry) => entry.name).join(", ")}] for ${subtype.name}`,
);
}

finalFunctions.push(...genFunctionsFiltered);
} else if (t.isTSUnionType(subtypeDeclAnnotation)) {
// Do nothing, since it will be processed later.
} else {
// Unexpected type
throw new Error(
`${subtype.name} is not a reference to a type literal or a union type.`,
);
}
}
}

// Create the make factory file
const makeFactoryTemplate = fs
.readFileSync(path.join(__dirname, "make-factory.template"))
.toString();

const functionCodes = finalFunctions
.map((genFun) => genFun.code)
.join("\n\n");
const functionNames = finalFunctions
.map((genFun) => genFun.name)
.join(",\n");

const makeFactoryCode = makeFactoryTemplate
.replace("<FUNCTIONS>", functionCodes)
.replace("<FUNCTION_NAMES>", functionNames);

fs.writeFileSync(path.join(__dirname, "make-factory.ts"), makeFactoryCode);

console.log("Finished.");
}

function createMakeAndDummyFunctions(
decl: t.TSTypeLiteral,
id: t.Identifier,
decls: Set<string>,
): { name: string; code: string }[] {
const astNamespace = "Ast";
const astFactoryObject = "astF";
const createNodeFunName = "createNode";
const emptySrcInfo = "emptySrcInfo";

const rawFieldsArray = decl.members.filter((decl) =>
t.isTSPropertySignature(decl),
);
const generalParams: { id: t.Identifier; type: t.TSType }[] = [];
const paramsWithLiteralTypes: {
id: t.Identifier;
type: t.TSLiteralType;
}[] = [];

// If there is no loc field,
// the makeDummy function cannot be created
const makeDummy = rawFieldsArray.some(
(f) => t.isIdentifier(f.key) && f.key.name === "loc",
);

for (const field of rawFieldsArray) {
if (!t.isIdentifier(field.key)) {
throw new Error(
`Expected identifier in fields, but found ${field.key.type}`,
);
}
const fieldName = field.key.name;
if (fieldName === "id") {
// The id field should not occur as an argument to the function,
// nor as a parameter to createNode
continue;
}
if (field.typeAnnotation) {
const typeAnnotation = field.typeAnnotation.typeAnnotation;
if (t.isTSLiteralType(typeAnnotation)) {
paramsWithLiteralTypes.push({
id: field.key,
type: typeAnnotation,
});
} else {
generalParams.push({ id: field.key, type: typeAnnotation });
}
} else {
throw new Error(
`Expected field ${fieldName} to have a type annotation`,
);
}
}

const makeFunName = `make${id.name}`;
const makeDummyFunName = `makeDummy${id.name}`;
// The params to the make functions do not have fields with literal types
// Also, the dummy function needs to filter the loc parameter
const createParam = (entry: { id: t.Identifier; type: t.TSType }) => {
const newId = t.identifier(`p_${entry.id.name}`);
newId.typeAnnotation = t.tsTypeAnnotation(
qualifyType(astNamespace, entry.type, decls),
);
return newId;
};
const makeFunParamsArray = generalParams.map((entry) => createParam(entry));
const makeDummyFunParamsArray = generalParams
.filter(({ id, type: _ }) => id.name !== "loc")
.map((entry) => createParam(entry));

// The arguments with literal values to the createNode call inside the make functions body
const createNodeLiteralArgs = paramsWithLiteralTypes.map(({ id, type }) =>
t.objectProperty(id, type.literal),
);
// The non-literal arguments to the createNode call inside the make functions body
const createNodeArgsForMake = generalParams.map(({ id, type: _ }) =>
t.objectProperty(id, t.identifier(`p_${id.name}`)),
);
const createNodeArgsForMakeDummy = generalParams.map(({ id, type: _ }) =>
id.name === "loc"
? t.objectProperty(id, t.identifier(emptySrcInfo))
: t.objectProperty(id, t.identifier(`p_${id.name}`)),
);
const funReturnType = t.tsTypeReference(
t.tsQualifiedName(t.identifier(astNamespace), id),
);
// Function to create the function codes
const createFun = (
name: string,
params: t.Identifier[],
createNodeArgs: t.ObjectProperty[],
) => {
const body = t.returnStatement(
t.tsAsExpression(
t.callExpression(
t.memberExpression(
t.identifier(astFactoryObject),
t.identifier(createNodeFunName),
),
[t.objectExpression(createNodeArgs)],
),
funReturnType,
),
);
const funDecl = t.functionDeclaration(
t.identifier(name),
params,
t.blockStatement([body]),
);
funDecl.returnType = t.tsTypeAnnotation(funReturnType);
return funDecl;
};

const makeFun = createFun(makeFunName, makeFunParamsArray, [
...createNodeLiteralArgs,
...createNodeArgsForMake,
]);
const makeDummyFun = createFun(makeDummyFunName, makeDummyFunParamsArray, [
...createNodeLiteralArgs,
...createNodeArgsForMakeDummy,
]);

if (makeDummy) {
return [
{ name: makeFunName, code: generate(makeFun).code },
{ name: makeDummyFunName, code: generate(makeDummyFun).code },
];
} else {
console.log(
`[WARNING] Skipped makeDummy for ${id.name}, because there is no loc field in ${id.name}.`,
);
return [{ name: makeFunName, code: generate(makeFun).code }];
}
}

function qualifyType(
namespace: string,
typ: t.TSType,
decls: Set<string>,
): t.TSType {
switch (typ.type) {
case "TSTypeReference": {
if (t.isIdentifier(typ.typeName)) {
if (decls.has(typ.typeName.name)) {
return t.tsTypeReference(
t.tsQualifiedName(
t.identifier(namespace),
typ.typeName,
),
);
} else {
// Leave the identifier unchanged, but check if it has type parameters
const typRef = t.tsTypeReference(typ.typeName);
if (typ.typeParameters) {
typRef.typeParameters = t.tsTypeParameterInstantiation(
typ.typeParameters.params.map((t) =>
qualifyType(namespace, t, decls),
),
);
}
return typRef;
}
}
// Leave the type as is
return typ;
}
case "TSUnionType": {
return t.tsUnionType(
typ.types.map((t) => qualifyType(namespace, t, decls)),
);
}
case "TSArrayType": {
return t.tsArrayType(
qualifyType(namespace, typ.elementType, decls),
);
}
case "TSTypeOperator": {
const op = t.tsTypeOperator(
qualifyType(namespace, typ.typeAnnotation, decls),
);
op.operator = typ.operator;
return op;
}
case "TSTupleType": {
if (
// Cannot use guard function isTSNamedTupleMember, because compiler cannot deduce the type inside
// the condition if the guard function is used.
typ.elementTypes.every((ty) => ty.type !== "TSNamedTupleMember")
) {
return t.tsTupleType(
typ.elementTypes.map((t) =>
qualifyType(namespace, t, decls),
),
);
} else {
// Currently unsupported
throw new Error(
"TSNamedTupleMember is currently not supported in TSTupleType",
);
}
}
case "TSUndefinedKeyword":
case "TSStringKeyword":
case "TSBooleanKeyword":
case "TSBigIntKeyword":
return typ;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather throw an exception here, because there might be (or will be) types with identifiers inside that are not (will not be) handled.

Throwing an exception here would be slightly safer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

default:
throw new Error(`${typ.type} is not supported`);
}
}

main();
20 changes: 20 additions & 0 deletions src/ast/generated/make-factory.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// THIS IS AN AUTOGENERATED FILE. TO GENERATE IT AGAIN, EXECUTE yarn gen:make-funs

import type { FactoryAst } from "../ast-helpers";
import type * as Ast from "../ast";
import type { SrcInfo } from "../../grammar";
import type * as TonCore from "@ton/core";
import { getSrcInfo } from "../../grammar/src-info";

export const getMakeAst = (astF: FactoryAst) => {

const emptySrcInfo = getSrcInfo(" ", 0, 0, null, "user");

<FUNCTIONS>

return {
<FUNCTION_NAMES>
};
};

export type MakeAstFactory = ReturnType<typeof getMakeAst>;
1,716 changes: 1,716 additions & 0 deletions src/ast/generated/make-factory.ts

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions src/context/store.ts
Original file line number Diff line number Diff line change
@@ -53,17 +53,15 @@ export function parseModules(sources: Source[], parser: Parser): Ast.Module[] {
* Extends the compiler context by adding AST entries and source information from
* given sources and parsed programs.
* @public
* @param parsedModules An optional array of previously parsed programs. If not defined, they will be parsed from `sources`.
* @param modules Previously parsed sources.
* @returns The updated compiler context.
*/
export function openContext(
ctx: CompilerContext,
sources: Source[],
funcSources: { code: string; path: string }[],
parser: Parser,
parsedModules?: Ast.Module[],
modules: Ast.Module[],
): CompilerContext {
const modules = parsedModules ?? parseModules(sources, parser);
const types: Ast.TypeDecl[] = [];
const functions: (
| Ast.NativeFunctionDecl
17 changes: 12 additions & 5 deletions src/generator/writers/resolveFuncType.spec.ts
Original file line number Diff line number Diff line change
@@ -2,9 +2,10 @@ import { getAstFactory } from "../../ast/ast-helpers";
import { resolveDescriptors } from "../../types/resolveDescriptors";
import { WriterContext } from "../Writer";
import { resolveFuncType } from "./resolveFuncType";
import { openContext } from "../../context/store";
import { openContext, parseModules } from "../../context/store";
import { CompilerContext } from "../../context/context";
import { getParser } from "../../grammar";
import type { Source } from "../../imports/source";

const primitiveCode = `
primitive Int;
@@ -48,11 +49,14 @@ contract Contract2 {
describe("resolveFuncType", () => {
it("should process primitive types", () => {
const ast = getAstFactory();
const sources: Source[] = [
{ code: primitiveCode, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[{ code: primitiveCode, path: "<unknown>", origin: "user" }],
sources,
[],
getParser(ast),
parseModules(sources, getParser(ast)),
);
ctx = resolveDescriptors(ctx, ast);
const wCtx = new WriterContext(ctx, "Contract1");
@@ -117,11 +121,14 @@ describe("resolveFuncType", () => {

it("should process contract and struct types", () => {
const ast = getAstFactory();
const sources: Source[] = [
{ code: primitiveCode, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[{ code: primitiveCode, path: "<unknown>", origin: "user" }],
sources,
[],
getParser(ast),
parseModules(sources, getParser(ast)),
);
ctx = resolveDescriptors(ctx, ast);
const wCtx = new WriterContext(ctx, "Contract1");
10 changes: 7 additions & 3 deletions src/generator/writers/writeExpression.spec.ts
Original file line number Diff line number Diff line change
@@ -4,11 +4,12 @@ import {
} from "../../types/resolveDescriptors";
import { WriterContext } from "../Writer";
import { writeExpression } from "./writeExpression";
import { openContext } from "../../context/store";
import { openContext, parseModules } from "../../context/store";
import { resolveStatements } from "../../types/resolveStatements";
import { CompilerContext } from "../../context/context";
import { getParser } from "../../grammar";
import { getAstFactory } from "../../ast/ast-helpers";
import type { Source } from "../../imports/source";

const code = `
@@ -71,11 +72,14 @@ const golden: string[] = [
describe("writeExpression", () => {
it("should write expression", () => {
const ast = getAstFactory();
const sources: Source[] = [
{ code: code, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[{ code: code, path: "<unknown>", origin: "user" }],
sources,
[],
getParser(ast),
parseModules(sources, getParser(ast)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pleasure to my eyes ❤️

);
ctx = resolveDescriptors(ctx, ast);
ctx = resolveStatements(ctx);
10 changes: 7 additions & 3 deletions src/generator/writers/writeSerialization.spec.ts
Original file line number Diff line number Diff line change
@@ -11,10 +11,11 @@ import {
import { WriterContext } from "../Writer";
import { writeParser, writeSerializer } from "./writeSerialization";
import { writeStdlib } from "./writeStdlib";
import { openContext } from "../../context/store";
import { openContext, parseModules } from "../../context/store";
import { writeAccessors } from "./writeAccessors";
import { getParser } from "../../grammar";
import { getAstFactory } from "../../ast/ast-helpers";
import type { Source } from "../../imports/source";

const code = `
primitive Int;
@@ -60,11 +61,14 @@ describe("writeSerialization", () => {
for (const s of ["A", "B", "C"]) {
it("should write serializer for " + s, () => {
const ast = getAstFactory();
const sources: Source[] = [
{ code, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[{ code, path: "<unknown>", origin: "user" }],
sources,
[],
getParser(ast),
parseModules(sources, getParser(ast)),
);
ctx = resolveDescriptors(ctx, ast);
ctx = resolveAllocations(ctx);
10 changes: 7 additions & 3 deletions src/optimizer/test/interpreter-eval-failed.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { getAstFactory } from "../../ast/ast-helpers";
import { CompilerContext } from "../../context/context";
import { openContext } from "../../context/store";
import { openContext, parseModules } from "../../context/store";
import { getParser } from "../../grammar";
import type { Source } from "../../imports/source";
import { evalComptimeExpressions } from "../../types/evalComptimeExpressions";
import { resolveDescriptors } from "../../types/resolveDescriptors";
import { resolveSignatures } from "../../types/resolveSignatures";
@@ -12,11 +13,14 @@ describe("interpreter-evaluation", () => {
for (const r of loadCases(__dirname + "/failed/")) {
it(`${r.name} should fail compilation`, () => {
const Ast = getAstFactory();
const sources: Source[] = [
{ code: r.code, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[{ code: r.code, path: "<unknown>", origin: "user" }],
sources,
[],
getParser(Ast),
parseModules(sources, getParser(Ast)),
);
expect(() => {
ctx = resolveDescriptors(ctx, Ast);
10 changes: 7 additions & 3 deletions src/optimizer/test/interpreter-eval-success.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { getAstFactory } from "../../ast/ast-helpers";
import { CompilerContext } from "../../context/context";
import { openContext } from "../../context/store";
import { openContext, parseModules } from "../../context/store";
import { getParser } from "../../grammar";
import type { Source } from "../../imports/source";
import { evalComptimeExpressions } from "../../types/evalComptimeExpressions";
import { resolveDescriptors } from "../../types/resolveDescriptors";
import { getAllExpressionTypes } from "../../types/resolveExpression";
@@ -13,11 +14,14 @@ describe("interpreter-evaluation", () => {
for (const r of loadCases(__dirname + "/success/")) {
it(`${r.name} should pass compilation`, () => {
const Ast = getAstFactory();
const sources: Source[] = [
{ code: r.code, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[{ code: r.code, path: "<unknown>", origin: "user" }],
sources,
[],
getParser(Ast),
parseModules(sources, getParser(Ast)),
);
ctx = resolveDescriptors(ctx, Ast);
ctx = resolveStatements(ctx);
10 changes: 8 additions & 2 deletions src/pipeline/precompile.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { CompilerContext } from "../context/context";
import { resolveDescriptors } from "../types/resolveDescriptors";
import { resolveAllocations } from "../storage/resolveAllocation";
import { openContext } from "../context/store";
import { openContext, parseModules } from "../context/store";
import { resolveStatements } from "../types/resolveStatements";
import { resolveErrors } from "../types/resolveErrors";
import { resolveSignatures } from "../types/resolveSignatures";
@@ -25,8 +25,14 @@ export function precompile(
// Load all sources
const imported = resolveImports({ entrypoint, project, stdlib, parser });

// Parse the sources and attach the given parsed modules
const finalModules = [
...parseModules(imported.tact, parser),
...(parsedModules ?? []),
];

// Add information about all the source code entries to the context
ctx = openContext(ctx, imported.tact, imported.func, parser, parsedModules);
ctx = openContext(ctx, imported.tact, imported.func, finalModules);

// First load type descriptors and check that
// they all have valid signatures
14 changes: 8 additions & 6 deletions src/storage/resolveAllocation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import fs from "fs";
import { resolveDescriptors } from "../types/resolveDescriptors";
import { getAllocations, resolveAllocations } from "./resolveAllocation";
import { openContext } from "../context/store";
import { openContext, parseModules } from "../context/store";
import { resolveStatements } from "../types/resolveStatements";
import { CompilerContext } from "../context/context";
import { resolveSignatures } from "../types/resolveSignatures";
import path from "path";
import { getParser } from "../grammar";
import { getAstFactory } from "../ast/ast-helpers";
import { stdlibPath } from "../stdlib/path";
import type { Source } from "../imports/source";

const primitivesPath = path.join(stdlibPath, "/std/internal/primitives.tact");
const stdlib = fs.readFileSync(primitivesPath, "utf-8");
@@ -65,14 +66,15 @@ contract Sample {
describe("resolveAllocation", () => {
it("should write program", () => {
const ast = getAstFactory();
const sources: Source[] = [
{ code: stdlib, path: primitivesPath, origin: "stdlib" },
{ code: src, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[
{ code: stdlib, path: primitivesPath, origin: "stdlib" },
{ code: src, path: "<unknown>", origin: "user" },
],
sources,
[],
getParser(ast),
parseModules(sources, getParser(ast)),
);
ctx = resolveDescriptors(ctx, ast);
ctx = resolveSignatures(ctx, ast);
8 changes: 8 additions & 0 deletions src/test/pipeline/__snapshots__/precompile-ast.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`pre-compilation of ASTs should fail pre-compilation when source files and a manual AST have declaration clashes 1`] = `
"Type "Test" already exists
> 1 |
^
"
`;
7 changes: 7 additions & 0 deletions src/test/pipeline/dummy.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
contract Test {
f: StateInit;

init() {
self.f = initOf Test();
}
}
115 changes: 115 additions & 0 deletions src/test/pipeline/precompile-ast.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import path from "path";
import type * as A from "../../ast/ast";
import { getAstFactory } from "../../ast/ast-helpers";
import { CompilerContext } from "../../context/context";
import { precompile } from "../../pipeline/precompile";
import files from "../../stdlib/stdlib";
import { createVirtualFileSystem } from "../../vfs/createVirtualFileSystem";
import fs from "fs";
import { getParser } from "../../grammar";
import { getMakeAst } from "../../ast/generated/make-factory";
import type { MakeAstFactory } from "../../ast/generated/make-factory";

// This function manually creates a module containing a contract with a reference to StateInit in its single field.
// StateInit is declared in stdlib.
//
// contract Test {
// f: StateInit;
//
// init() {
// self.f = initOf Test();
// }
// }

function makeModule(mF: MakeAstFactory): A.Module {
// The contract field
const field = mF.makeDummyFieldDecl(
mF.makeDummyId("f"),
mF.makeDummyTypeId("StateInit"),
undefined,
undefined,
);
// The contract init function
const initOfExpr = mF.makeDummyInitOf(mF.makeDummyId("Test"), []);
const path = mF.makeDummyFieldAccess(
mF.makeDummyId("self"),
mF.makeDummyId("f"),
);
const assignStmt = mF.makeDummyStatementAssign(path, initOfExpr);
const init = mF.makeDummyContractInit([], [assignStmt]);

// The contract
const contract = mF.makeDummyContract(
mF.makeDummyId("Test"),
[],
[],
undefined,
[field, init],
);

return mF.makeModule([], [contract]);
}

describe("pre-compilation of ASTs", () => {
const astF = getAstFactory();
const mF = getMakeAst(astF);

it("should pass pre-compilation of AST with references to stdlib", () => {
const ctx = new CompilerContext();

// An empty tact file is required so that pre-compile does not complain about
// non-existence of an entry point.
const fileSystem = {
["empty.tact"]: "",
};

const project = createVirtualFileSystem("/", fileSystem, false);
const stdlib = createVirtualFileSystem("@stdlib", files);
const parser = getParser(astF);

precompile(ctx, project, stdlib, "empty.tact", parser, astF, [
makeModule(mF),
]);
});

it("should pass pre-compilation with no manual AST", () => {
const ctx = new CompilerContext();

// The dummy.tact file contains exactly the same declarations
// carried out by the function makeModule()
const fileSystem = {
["dummy.tact"]: fs
.readFileSync(path.join(__dirname, "dummy.tact"))
.toString("base64"),
};

const project = createVirtualFileSystem("/", fileSystem, false);
const stdlib = createVirtualFileSystem("@stdlib", files);
const parser = getParser(astF);

precompile(ctx, project, stdlib, "dummy.tact", parser, astF, []);
});

it("should fail pre-compilation when source files and a manual AST have declaration clashes", () => {
const ctx = new CompilerContext();

// The dummy.tact file contains exactly the same declarations
// carried out by the function makeModule()
const fileSystem = {
["dummy.tact"]: fs
.readFileSync(path.join(__dirname, "dummy.tact"))
.toString("base64"),
};

const project = createVirtualFileSystem("/", fileSystem, false);
const stdlib = createVirtualFileSystem("@stdlib", files);
const parser = getParser(astF);

// So, a clash should occur here, since dummy.tact and makeModule() both declare the contract Test.
expect(() =>
precompile(ctx, project, stdlib, "dummy.tact", parser, astF, [
makeModule(mF),
]),
).toThrowErrorMatchingSnapshot();
});
});
20 changes: 11 additions & 9 deletions src/types/effects.spec.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import { getAllTypes, resolveDescriptors } from "./resolveDescriptors";
import { loadCases } from "../utils/loadCases";
import { openContext } from "../context/store";
import { openContext, parseModules } from "../context/store";
import { resolveStatements } from "./resolveStatements";
import { CompilerContext } from "../context/context";
import { featureEnable } from "../config/features";
import { getParser } from "../grammar";
import { getAstFactory } from "../ast/ast-helpers";
import { computeReceiversEffects } from "./effects";
import type { Source } from "../imports/source";

describe("effects", () => {
for (const testContract of loadCases(__dirname + "/effects/")) {
it(`should correctly compute effects: ${testContract.name}`, () => {
const Ast = getAstFactory();
const sources: Source[] = [
{
code: testContract.code,
path: "<unknown>",
origin: "user",
},
];
let ctx = openContext(
new CompilerContext(),
[
{
code: testContract.code,
path: "<unknown>",
origin: "user",
},
],
sources,
[],
getParser(Ast),
parseModules(sources, getParser(Ast)),
);
ctx = featureEnable(ctx, "external");
ctx = resolveDescriptors(ctx, Ast);
17 changes: 12 additions & 5 deletions src/types/resolveDescriptors.spec.ts
Original file line number Diff line number Diff line change
@@ -6,14 +6,15 @@ import {
} from "./resolveDescriptors";
import { resolveSignatures } from "./resolveSignatures";
import { loadCases } from "../utils/loadCases";
import { openContext } from "../context/store";
import { openContext, parseModules } from "../context/store";
import { featureEnable } from "../config/features";
import type { SrcInfo } from "../grammar";
import { getParser } from "../grammar";
import { getAstFactory } from "../ast/ast-helpers";
import { isSrcInfo } from "../grammar/src-info";
import { resolveStatements } from "./resolveStatements";
import { evalComptimeExpressions } from "./evalComptimeExpressions";
import type { Source } from "../imports/source";

expect.addSnapshotSerializer({
test: (src) => isSrcInfo(src),
@@ -24,11 +25,14 @@ describe("resolveDescriptors", () => {
for (const r of loadCases(__dirname + "/test/")) {
it("should resolve descriptors for " + r.name, () => {
const Ast = getAstFactory();
const sources: Source[] = [
{ code: r.code, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[{ code: r.code, path: "<unknown>", origin: "user" }],
sources,
[],
getParser(Ast),
parseModules(sources, getParser(Ast)),
);
ctx = featureEnable(ctx, "external");
ctx = resolveDescriptors(ctx, Ast);
@@ -40,11 +44,14 @@ describe("resolveDescriptors", () => {
for (const r of loadCases(__dirname + "/test-failed/")) {
it("should fail descriptors for " + r.name, () => {
const Ast = getAstFactory();
const sources: Source[] = [
{ code: r.code, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[{ code: r.code, path: "<unknown>", origin: "user" }],
sources,
[],
getParser(Ast),
parseModules(sources, getParser(Ast)),
);
ctx = featureEnable(ctx, "external");
expect(() => {
17 changes: 12 additions & 5 deletions src/types/resolveStatements.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { getAllExpressionTypes } from "./resolveExpression";
import { resolveDescriptors } from "./resolveDescriptors";
import { loadCases } from "../utils/loadCases";
import { openContext } from "../context/store";
import { openContext, parseModules } from "../context/store";
import { resolveStatements } from "./resolveStatements";
import { CompilerContext } from "../context/context";
import { featureEnable } from "../config/features";
import { getParser } from "../grammar";
import { getAstFactory } from "../ast/ast-helpers";
import { evalComptimeExpressions } from "./evalComptimeExpressions";
import type { Source } from "../imports/source";

describe("resolveStatements", () => {
for (const r of loadCases(__dirname + "/stmts/")) {
it("should resolve statements for " + r.name, () => {
const Ast = getAstFactory();
const sources: Source[] = [
{ code: r.code, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[{ code: r.code, path: "<unknown>", origin: "user" }],
sources,
[],
getParser(Ast),
parseModules(sources, getParser(Ast)),
);
ctx = featureEnable(ctx, "external");
ctx = resolveDescriptors(ctx, Ast);
@@ -29,11 +33,14 @@ describe("resolveStatements", () => {
for (const r of loadCases(__dirname + "/stmts-failed/")) {
it("should fail statements for " + r.name, () => {
const Ast = getAstFactory();
const sources: Source[] = [
{ code: r.code, path: "<unknown>", origin: "user" },
];
let ctx = openContext(
new CompilerContext(),
[{ code: r.code, path: "<unknown>", origin: "user" }],
sources,
[],
getParser(Ast),
parseModules(sources, getParser(Ast)),
);
ctx = featureEnable(ctx, "external");
ctx = resolveDescriptors(ctx, Ast);