Skip to content

Commit

Permalink
fix(codegen): wrap more non-Lvalues into _not_mut functions (#683)
Browse files Browse the repository at this point in the history
Also, some renames: src -> selfTyRef, t -> selfTy, ff -> methodDescr
  • Loading branch information
anton-trunov authored Aug 13, 2024
1 parent 5d470b3 commit 1e616db
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Getters now return flattened types for structs as before: PR [#679](https://github.com/tact-lang/tact/pull/679)
- New bindings cannot shadow global constants: PR [#680](https://github.com/tact-lang/tact/pull/680)
- Disallow using assignment operators on constants: PR [#682](https://github.com/tact-lang/tact/pull/682)
- Fix code generation for some non-Lvalues that weren't turned into Lvalues by wrapping them in a function call: PR [#683](https://github.com/tact-lang/tact/pull/683)

## [1.4.1] - 2024-07-26

Expand Down
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"langle",
"lparen",
"lvalue",
"lvalues",
"masterchain",
"maxint",
"minmax",
Expand Down
50 changes: 28 additions & 22 deletions src/generator/writers/writeExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
import { ops } from "./ops";
import { writeCastedExpression } from "./writeFunction";
import { evalConstantExpression } from "../../constEval";
import { isLvalue } from "../../types/resolveStatements";

function isNull(f: AstExpression): boolean {
return f.kind === "null";
Expand Down Expand Up @@ -547,64 +548,67 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string {

if (f.kind === "method_call") {
// Resolve source type
const src = getExpType(wCtx.ctx, f.self);
const selfTyRef = getExpType(wCtx.ctx, f.self);

// Reference type
if (src.kind === "ref") {
if (src.optional) {
if (selfTyRef.kind === "ref") {
if (selfTyRef.optional) {
throwCompilationError(
`Cannot call function of non - direct type: "${printTypeRef(src)}"`,
`Cannot call function of non - direct type: "${printTypeRef(selfTyRef)}"`,
f.loc,
);
}

// Render function call
const t = getType(wCtx.ctx, src.name);
const selfTy = getType(wCtx.ctx, selfTyRef.name);

// Check struct ABI
if (t.kind === "struct") {
if (selfTy.kind === "struct") {
if (StructFunctions.has(idText(f.method))) {
const abi = StructFunctions.get(idText(f.method))!;
return abi.generate(
wCtx,
[src, ...f.args.map((v) => getExpType(wCtx.ctx, v))],
[
selfTyRef,
...f.args.map((v) => getExpType(wCtx.ctx, v)),
],
[f.self, ...f.args],
f.loc,
);
}
}

// Resolve function
const ff = t.functions.get(idText(f.method))!;
let name = ops.extension(src.name, idText(f.method));
const methodDescr = selfTy.functions.get(idText(f.method))!;
let name = ops.extension(selfTyRef.name, idText(f.method));
if (
ff.ast.kind === "function_def" ||
ff.ast.kind === "function_decl"
methodDescr.ast.kind === "function_def" ||
methodDescr.ast.kind === "function_decl"
) {
wCtx.used(name);
} else {
name = idText(ff.ast.nativeName);
name = idText(methodDescr.ast.nativeName);
if (name.startsWith("__tact")) {
wCtx.used(name);
}
}

// Render arguments
let renderedArguments = f.args.map((a, i) =>
writeCastedExpression(a, ff.params[i]!.type, wCtx),
writeCastedExpression(a, methodDescr.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 (methodDescr.isMutating) {
if (f.args.length === 1) {
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
methodDescr.params[0]!.type.kind === "ref" &&
!methodDescr.params[0]!.type.optional
) {
renderedArguments = [
`${ops.typeTensorCast(tt.name, wCtx)}(${renderedArguments[0]})`,
Expand All @@ -616,8 +620,10 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string {

// Render
const s = writeExpression(f.self, wCtx);
if (ff.isMutating) {
if (f.self.kind === "id" || f.self.kind === "field_access") {
if (methodDescr.isMutating) {
// check if it's an l-value
const path = tryExtractPath(f.self);
if (path !== null && isLvalue(path, wCtx.ctx)) {
return `${s}~${name}(${renderedArguments.join(", ")})`;
} else {
return `${wCtx.used(ops.nonModifying(name))}(${[s, ...renderedArguments].join(", ")})`;
Expand All @@ -628,7 +634,7 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string {
}

// Map types
if (src.kind === "map") {
if (selfTyRef.kind === "map") {
if (!MapFunctions.has(idText(f.method))) {
throwCompilationError(
`Map function "${idText(f.method)}" not found`,
Expand All @@ -638,18 +644,18 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string {
const abf = MapFunctions.get(idText(f.method))!;
return abf.generate(
wCtx,
[src, ...f.args.map((v) => getExpType(wCtx.ctx, v))],
[selfTyRef, ...f.args.map((v) => getExpType(wCtx.ctx, v))],
[f.self, ...f.args],
f.loc,
);
}

if (src.kind === "ref_bounced") {
if (selfTyRef.kind === "ref_bounced") {
throw Error("Unimplemented");
}

throwCompilationError(
`Cannot call function of non - direct type: "${printTypeRef(src)}"`,
`Cannot call function of non - direct type: "${printTypeRef(selfTyRef)}"`,
f.loc,
);
}
Expand Down
1 change: 1 addition & 0 deletions src/test/codegen/all-contracts.tact
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ import "./struct-field-storage-annotation.tact";
import "./struct-field-func-keywords-name-clash";
import "./message-opcode-parsing.tact";
import "./struct-with-default-and-optional-fields";
import "./mutating-method-on-non-lvalues";
17 changes: 17 additions & 0 deletions src/test/codegen/mutating-method-on-non-lvalues.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
struct Foo { x : Int }

extends mutates fun inc(self: Int) { self += 1 }

const GlobalConst: Int = 42;

contract MutatingMethodOnNonLvalues {
const ContractConst: Int = 43;

receive("foo") {
41.inc();
GlobalConst.inc();
self.ContractConst.inc();
Foo {x: 44}.x.inc();
}
}

2 changes: 1 addition & 1 deletion src/types/resolveStatements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ function processCondition(

// Precondition: `self` here means a contract or a trait,
// and not a `self` parameter of a mutating method
function isLvalue(path: AstId[], ctx: CompilerContext): boolean {
export function isLvalue(path: AstId[], ctx: CompilerContext): boolean {
const headId = path[0]!;
if (isSelfId(headId) && path.length > 1) {
// we can be dealing with a contract/trait constant `self.constFoo`
Expand Down

0 comments on commit 1e616db

Please sign in to comment.