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(codegen): wrap more non-Lvalues into _not_mut functions #683

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
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
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
Loading