From 1c890ad57aef22407a427cdaebf5493216a195bb Mon Sep 17 00:00:00 2001 From: jeshecdom Date: Thu, 26 Sep 2024 03:55:23 +0200 Subject: [PATCH 1/2] Added test cases for: - chainings of mutating functions for structs, maps, contracts and integers. - Nested maps --- .../e2e-emulated/contracts/semantics.tact | 787 +++++++++++++++++- src/test/e2e-emulated/semantics.spec.ts | 20 + 2 files changed, 804 insertions(+), 3 deletions(-) diff --git a/src/test/e2e-emulated/contracts/semantics.tact b/src/test/e2e-emulated/contracts/semantics.tact index 32eed5555..7a9a5208c 100644 --- a/src/test/e2e-emulated/contracts/semantics.tact +++ b/src/test/e2e-emulated/contracts/semantics.tact @@ -13,7 +13,8 @@ struct SC { c1: Int; } -// Wrapper struct in order to pass maps to mutating functions. See issue #815 +// Wrapper struct in order to pass maps to mutating functions. See issue #815. +// Also, it is useful for nesting maps inside maps. struct MapWrapper { m: map } @@ -47,6 +48,29 @@ extends mutates fun changeSomeFields(self: SA) { self.a2.b2.c1 += 1; } +extends fun copyStruct(self: SA): SA { + // Since structs are passed by value, "self" will be a copy of the struct given as parameter + return self; +} + +// Identity function, just to test chaining of mutating functions. +extends mutates fun reflectStruct(self: SA): SA { + return self; +} + +extends mutates fun incrementIntFields(self: SA): SA { + self.a1 += 1; + self.a2.b2.c1 += 1; + self.a2.b3 += 1; + return self; +} + +extends mutates fun flipBooleanFields(self: SA): SA { + self.a2.b1 = !self.a2.b1; + return self; +} + + /**** Auxiliary functions for maps **********/ fun getNewMap(m: map): map { @@ -137,6 +161,27 @@ extends mutates fun changeSomeEntries(self: MapWrapper) { self.m.del(3); } +extends fun unwrapAndCopyMap(self: MapWrapper): map { + // Since structs are passed by value, "self" will be a copy of the wrapped map given as parameter + // Hence, the unwrapped map is a copy of the map. + return self.m; +} + +extends mutates fun unwrapMap(self: MapWrapper): map { + // Even though "self" is a copy, the mutates function will assign the copy back into "self" once the function finishes. + // Observe that the function will return a copy of the wrapped map as well. + // This means that there are actually TWO assignments when one does something like this: + // m = wm.unwrapMap(); + // After "unwrapMap" finishes execution, wm will be reassigned with the copy of self (which in this case is identical + // to the input to "unwrapMap"), and m will be assigned with "self.m" (computed using the copy of "self"). + return self.m; +} + +// Identity function, just to test chaining of mutating functions. +extends mutates fun reflectMap(self: MapWrapper): MapWrapper { + return self; +} + /**** Auxiliary functions for contracts **********/ fun copyAndModifyContract(c: SemanticsTester): SemanticsTester { @@ -161,6 +206,45 @@ extends mutates fun changeSomeContractFields(self: SemanticsTester) { self.sA.a2.b2.c1 += 30; } +// Identity function, but as a simple extends function. +extends fun copyContract(self: SemanticsTester): SemanticsTester { + return self; +} + +// Identity function, just to test chaining of mutating functions. +extends mutates fun reflectContract(self: SemanticsTester): SemanticsTester { + return self; +} + +extends mutates fun incrementIntFieldsInUB(self: SemanticsTester): SemanticsTester { + self.uB.b2.c1 += 1; + self.uB.b3 += 1; + return self; +} + +extends mutates fun flipBooleanFieldsInUB(self: SemanticsTester): SemanticsTester { + self.uB.b1 = !self.uB.b1; + return self; +} + + +/**** Auxiliary functions for integers **********/ + +extends mutates fun multiplyBy2(self: Int): Int { + self *= 2; + return self; +} + +extends mutates fun increment(self: Int): Int { + self += 1; + return self; +} + +extends mutates fun doNothing(self: Int): Int { + return self; +} + + contract SemanticsTester { // Currently, declaring fields like this: @@ -189,6 +273,8 @@ contract SemanticsTester { mA: map; mB: map; + mC: map; // Simulate nested maps by wrapping them in a struct, + // "Morally", mC has type map>. // Flag storing the result of calling method mutateContractState. @@ -219,7 +305,7 @@ contract SemanticsTester { // Initialize the mA map self.mA.set(1, self.sA); // The compiler does not complain that self.mA is not initialized, because // map fields in contracts are implicitly initialized as empty. - // Function checkMapInit will later check this + // Function checkMapInit will later check this. // Make a copy of sA to assign a different key value pair in the map let s = self.sA; @@ -235,6 +321,10 @@ contract SemanticsTester { self.mA.set(3, s); + // Initialize the mC map. The nested map will contain a copy of self.sA. + let nestedMap = MapWrapper {m: emptyMap()}; + nestedMap.m.set(10, self.sA); + self.mC.set(100, nestedMap); /****** Contracts *****/ @@ -311,6 +401,20 @@ contract SemanticsTester { result = result && (k == 1 || k == 2 || k == 3); } + // The map self.mC has a single entry 100 -> MapWrapper {m: 10 -> self.sA}. + foreach (k1, v1 in self.mC) { + foreach (k2, v2 in v1.m) { + result = result && + k1 == 100 && + k2 == 10 && + + v2.a1 == 20 && + v2.a2.b1 == true && + v2.a2.b2.c1 == 5 && + v2.a2.b3 == 10; + } + } + return result; } @@ -418,6 +522,249 @@ contract SemanticsTester { s.a2.b3 == 10; } + get fun testReturnedStructs(): Bool { + // The call to the non-mutating extends function "copyStruct" always makes a copy + // of the parameter struct. Therefore, passing the result to the mutating function + // "changeSomeFields" will modify the copy, not the original struct. + + self.sA.copyStruct().changeSomeFields(); + + let result = + self.checkAllContractFieldsAreUnchanged(); + + // For the following test, make a copy of self.sA. + let s = self.sA; + + // What is the effect of executing the following line? + + s.reflectStruct().changeSomeFields(); + + // First, reflectStruct makes a copy of s. When reflectStruct finalizes, it assigns the copy back into + // s. Additionally, reflectStruct returns the copy of s, call it s'. + // s' is then passed to changeSomeFields, which makes again a copy of s', call it s''. + // changeSomeFields changes some fields of s'' and reassigns s' with this changed s''. + // Additionally changeSomeFields returns the changed s''. + // Note that the only place where s is reassigned is after reflectStruct executes. + // This means that the above expression does NOT change s. + + result = result && + self.checkAllContractFieldsAreUnchanged() && + s.a1 == 20 && + s.a2.b1 == true && + s.a2.b2.c1 == 5 && + s.a2.b3 == 10; + + // Therefore, if we actually want to change s using changeSomeFields, we + // should split the chain steps: + + s.reflectStruct(); // Each step changes s + s.changeSomeFields(); + + // Note that doing the trick of assigning back s + // + // s = s.reflectStruct().changeSomeFields(); + // + // is not possible because changeSomeFields has void return type. + + // s is now changed by changeSomeFields, and the rest remains the same + result = result && + self.checkAllContractFieldsAreUnchanged() && + s.a1 == 120 && + s.a2.b1 == true && + s.a2.b2.c1 == 6 && + s.a2.b3 == 10; + + // For further examples of chaining of mutating functions, see the tests mutatesChainStruct + + return result; + } + + /* + The following functions exemplify how to properly chain mutating functions. + Each function in the chain returns a copy to the next function in the chain. + Hence, to change the original variable, we need to reassign the value returned + from the last mutating function. + */ + + get fun mutatesChainStruct1(): Bool { + let s = self.sA; + s.reflectStruct().incrementIntFields().flipBooleanFields(); // s changed only by the first function + + let t = self.sA; + t = t.reflectStruct().incrementIntFields().flipBooleanFields(); // Assign back to t the struct returned by the last function + + // The less confusing solution is to simply break the chain. + // This solution even works when the last function in the chain has void return type. + // Each step changes z + let z = self.sA; + z.reflectStruct(); + z.incrementIntFields(); + z.flipBooleanFields(); + + return self.checkAllContractFieldsAreUnchanged() && + s.a1 == 20 && + s.a2.b1 == true && + s.a2.b2.c1 == 5 && + s.a2.b3 == 10 && + + t.a1 == 21 && + t.a2.b1 == false && + t.a2.b2.c1 == 6 && + t.a2.b3 == 11 && + + z.a1 == 21 && + z.a2.b1 == false && + z.a2.b2.c1 == 6 && + z.a2.b3 == 11; + + } + + get fun mutatesChainStruct2(): Bool { + let s = self.sA; + s.reflectStruct().flipBooleanFields().incrementIntFields(); + + let t = self.sA; + t = t.reflectStruct().flipBooleanFields().incrementIntFields(); + + let z = self.sA; + z.reflectStruct(); + z.flipBooleanFields(); + z.incrementIntFields(); + + return self.checkAllContractFieldsAreUnchanged() && + s.a1 == 20 && + s.a2.b1 == true && + s.a2.b2.c1 == 5 && + s.a2.b3 == 10 && + + t.a1 == 21 && + t.a2.b1 == false && + t.a2.b2.c1 == 6 && + t.a2.b3 == 11 && + + z.a1 == 21 && + z.a2.b1 == false && + z.a2.b2.c1 == 6 && + z.a2.b3 == 11; + } + + get fun mutatesChainStruct3(): Bool { + let s = self.sA; + s.incrementIntFields().reflectStruct().flipBooleanFields(); + + let t = self.sA; + t = t.incrementIntFields().reflectStruct().flipBooleanFields(); + + let z = self.sA; + z.incrementIntFields(); + z.reflectStruct(); + z.flipBooleanFields(); + + return self.checkAllContractFieldsAreUnchanged() && + s.a1 == 21 && + s.a2.b1 == true && + s.a2.b2.c1 == 6 && + s.a2.b3 == 11 && + + t.a1 == 21 && + t.a2.b1 == false && + t.a2.b2.c1 == 6 && + t.a2.b3 == 11 && + + z.a1 == 21 && + z.a2.b1 == false && + z.a2.b2.c1 == 6 && + z.a2.b3 == 11; + } + + get fun mutatesChainStruct4(): Bool { + let s = self.sA; + s.incrementIntFields().flipBooleanFields().reflectStruct(); + + let t = self.sA; + t = t.incrementIntFields().flipBooleanFields().reflectStruct(); + + let z = self.sA; + z.incrementIntFields(); + z.flipBooleanFields(); + z.reflectStruct(); + + return self.checkAllContractFieldsAreUnchanged() && + s.a1 == 21 && + s.a2.b1 == true && + s.a2.b2.c1 == 6 && + s.a2.b3 == 11 && + + t.a1 == 21 && + t.a2.b1 == false && + t.a2.b2.c1 == 6 && + t.a2.b3 == 11 && + + z.a1 == 21 && + z.a2.b1 == false && + z.a2.b2.c1 == 6 && + z.a2.b3 == 11; + } + + get fun mutatesChainStruct5(): Bool { + let s = self.sA; + s.flipBooleanFields().incrementIntFields().reflectStruct(); + + let t = self.sA; + t = t.flipBooleanFields().incrementIntFields().reflectStruct(); + + let z = self.sA; + z.flipBooleanFields(); + z.incrementIntFields(); + z.reflectStruct(); + + return self.checkAllContractFieldsAreUnchanged() && + s.a1 == 20 && + s.a2.b1 == false && + s.a2.b2.c1 == 5 && + s.a2.b3 == 10 && + + t.a1 == 21 && + t.a2.b1 == false && + t.a2.b2.c1 == 6 && + t.a2.b3 == 11 && + + z.a1 == 21 && + z.a2.b1 == false && + z.a2.b2.c1 == 6 && + z.a2.b3 == 11; + } + + get fun mutatesChainStruct6(): Bool { + let s = self.sA; + s.flipBooleanFields().reflectStruct().incrementIntFields(); + + let t = self.sA; + t = t.flipBooleanFields().reflectStruct().incrementIntFields(); + + let z = self.sA; + z.flipBooleanFields(); + z.reflectStruct(); + z.incrementIntFields(); + + return self.checkAllContractFieldsAreUnchanged() && + s.a1 == 20 && + s.a2.b1 == false && + s.a2.b2.c1 == 5 && + s.a2.b3 == 10 && + + t.a1 == 21 && + t.a2.b1 == false && + t.a2.b2.c1 == 6 && + t.a2.b3 == 11 && + + z.a1 == 21 && + z.a2.b1 == false && + z.a2.b2.c1 == 6 && + z.a2.b3 == 11; + } + + /*************** Maps ********************/ // Assigning a map to a variable preserves contents @@ -595,6 +942,262 @@ contract SemanticsTester { return result; } + get fun testReturnedMaps1(): Bool { + // The "get" function for maps always creates a copy of the input map + // because "get" is a non-mutating function. This means that + // the following expression will not change the struct in entry 2 of self.mA, + // even if "changeSomeFields" is a mutating function for structs. + // The mutating function "changeSomeFields" is changing the copy + // returned by "get". + + self.mA.get(2)!!.changeSomeFields(); + + // Everything remains the same + + return self.checkAllContractFieldsAreUnchanged(); + } + + /* + The following test cannot be carried out because FunC reports errors. See issue #866. + But the comments inside the test are good hypotheses of what I expect it will happen + when the issue is resolved, because this is what happens in the case of structs (see + function testReturnedStructs). + + get fun testReturnedMaps2(): Bool { + // The call to the non-mutating extends function "unwrapAndCopyMap" always makes a copy + // of the parameter. Therefore, passing the result to the mutating function + // "del" will modify the copy, not the original. + + let wm = MapWrapper {m: self.mA}; + + wm.unwrapAndCopyMap().del(1); // FunC error: issue #866 + + // The contract fields remain the same and also wm + let result = + self.checkAllContractFieldsAreUnchanged() && + wm.m.get(1)!!.a1 == 20 && + wm.m.get(1)!!.a2.b1 == true && + wm.m.get(1)!!.a2.b2.c1 == 5 && + wm.m.get(1)!!.a2.b3 == 10 && + + wm.m.get(2)!!.a1 == 20 && + wm.m.get(2)!!.a2.b1 == true && + wm.m.get(2)!!.a2.b2.c1 == 100 && + wm.m.get(2)!!.a2.b3 == 0 && + + wm.m.get(3)!!.a1 == 5 && + wm.m.get(3)!!.a2.b1 == false && + wm.m.get(3)!!.a2.b2.c1 == 150 && + wm.m.get(3)!!.a2.b3 == 0; + + foreach (k, _ in wm.m) { + result = result && (k == 1 || k == 2 || k == 3); + } + + // Instead, passing "wm" to the mutating function "unwrapMap", + // will return a copy of "wm.m", which is then given as input to "del". + // This means that the following expression does not delete entry 1 + // from wm.m, but deleted from the copy of wm.m: + + wm.unwrapMap().del(1); // FunC error: issue #866 + + // The contract fields remain the same and also wm + result = result && + self.checkAllContractFieldsAreUnchanged() && + wm.m.get(1)!!.a1 == 20 && + wm.m.get(1)!!.a2.b1 == true && + wm.m.get(1)!!.a2.b2.c1 == 5 && + wm.m.get(1)!!.a2.b3 == 10 && + + wm.m.get(2)!!.a1 == 20 && + wm.m.get(2)!!.a2.b1 == true && + wm.m.get(2)!!.a2.b2.c1 == 100 && + wm.m.get(2)!!.a2.b3 == 0 && + + wm.m.get(3)!!.a1 == 5 && + wm.m.get(3)!!.a2.b1 == false && + wm.m.get(3)!!.a2.b2.c1 == 150 && + wm.m.get(3)!!.a2.b3 == 0; + + foreach (k, _ in wm.m) { + result = result && (k == 1 || k == 2 || k == 3); + } + + // Since unwrapMap always returns a copy of wm.m, it is not possible + // to delete entries indirectly through the use of unwrapMap. + // Therefore, the only way is to directly unwrap the map: + + wm.m.del(1); + + // The contract fields remain the same and wm only lost entry 1 + result = result && + self.checkAllContractFieldsAreUnchanged() && + + wm.m.get(2)!!.a1 == 20 && + wm.m.get(2)!!.a2.b1 == true && + wm.m.get(2)!!.a2.b2.c1 == 100 && + wm.m.get(2)!!.a2.b3 == 0 && + + wm.m.get(3)!!.a1 == 5 && + wm.m.get(3)!!.a2.b1 == false && + wm.m.get(3)!!.a2.b2.c1 == 150 && + wm.m.get(3)!!.a2.b3 == 0; + + foreach (k, _ in wm.m) { + result = result && (k == 2 || k == 3); + } + + // Hence, chaining mutating functions to attempt to delete another entry, + // will not change wm, because those functions always return copies: + + wm.reflectMap().unwrapMap().del(2); // FunC error: issue #866 + + // The contract fields remain the same and wm remains as in the previous step + result = result && + self.checkAllContractFieldsAreUnchanged() && + + wm.m.get(2)!!.a1 == 20 && + wm.m.get(2)!!.a2.b1 == true && + wm.m.get(2)!!.a2.b2.c1 == 100 && + wm.m.get(2)!!.a2.b3 == 0 && + + wm.m.get(3)!!.a1 == 5 && + wm.m.get(3)!!.a2.b1 == false && + wm.m.get(3)!!.a2.b2.c1 == 150 && + wm.m.get(3)!!.a2.b3 == 0; + + foreach (k, _ in wm.m) { + result = result && (k == 2 || k == 3); + } + + return result; + } + + FINISHES Test that cannot be carried out. + */ + + get fun mutateNestedMap1(): Bool { + // Make a local copy of self.mC for the tests. + let m = self.mC; + + // The map m has the single entry 100 -> MapWrap {m: 10 -> copy of self.sA} + + // Modify the internal struct in m + // This should not change self.mC. + + // Note that it is not possible to change the internal struct directly using: + + // m.get(100)!!.m.get(10)!!.a2.b2.c1 = XXX; + + // because the left side of an assignment must be a path expression. + // Hence, we need to assign the struct first into a variable: + + let s = m.get(100)!!.m.get(10)!!; + + // But recall that s is a COPY of the struct inside the nested map (since assignments are always by value). + // Indeed, if we change s, the nested map in m does not change. + + s.a2.b2.c1 = 1000; + + // Check that s does change + + let result = s.a1 == 20 && + s.a2.b1 == true && + s.a2.b2.c1 == 1000 && + s.a2.b3 == 10; + + // But m does not change + foreach (k1, v1 in m) { + foreach (k2, v2 in v1.m) { + result = result && + k1 == 100 && + k2 == 10 && + + v2.a1 == 20 && + v2.a2.b1 == true && + v2.a2.b2.c1 == 5 && + v2.a2.b3 == 10; + } + } + + // Hence, we need to assign s back into m + + // However, the following expression will not work, + // because the "get" function always makes a copy of "m" (see test "testReturnedMaps1" above). + // m.get(100)!!.m.set(10, s); + + // This means we are forced to update the entire entry for key 100. + + // The fact that "get" always returns copies of nested structures has one big consequence: + // it is not possible to update a deeply nested map without rebuilding the map of the root + // key that leads to the updated nested map, i.e., + // if the map is: + // key1 -> [ + // key1.1 -> [key1.1.1 -> val1], + // key1.2 -> [key1.2.1 -> val2] + // ] + // Then, updating the entry key1.1.1 with value val3 would require + // updating key1 with + // [ + // key1.1 -> [key1.1.1 -> val3], + // key1.2 -> [key1.2.1 -> val2] + // ] + + // First build the map containing the updated s and wrap it + let nestedMap1: map = emptyMap(); + nestedMap1.set(10, s); + let wrappedNested1 = MapWrapper {m: nestedMap1}; + + // As a side note: we can also achieve the same result by doing this: + let wrappedNested2 = MapWrapper {m: emptyMap()}; + wrappedNested2.m.set(10, s); + + // Indeed, wrappedNested1 and wrappedNested2 are equal + + foreach (k2, v2 in wrappedNested1.m) { + result = result && + k2 == 10 && + + v2.a1 == 20 && + v2.a2.b1 == true && + v2.a2.b2.c1 == 1000 && + v2.a2.b3 == 10; + } + + foreach (k2, v2 in wrappedNested2.m) { + result = result && + k2 == 10 && + + v2.a1 == 20 && + v2.a2.b1 == true && + v2.a2.b2.c1 == 1000 && + v2.a2.b3 == 10; + } + + // Finally, assign the wrapped map to the key 100 + m.set(100, wrappedNested1); + + // Check that m changed + + foreach (k1, v1 in m) { + foreach (k2, v2 in v1.m) { + result = result && + k1 == 100 && + k2 == 10 && + + v2.a1 == 20 && + v2.a2.b1 == true && + v2.a2.b2.c1 == 1000 && + v2.a2.b3 == 10; + } + } + + // The rest of the fields in the contract did not change (including self.mC). + result = result && self.checkAllContractFieldsAreUnchanged(); + + return result; + } + /*************** Contracts ********************/ get fun contractAssign1(): Bool { @@ -835,6 +1438,20 @@ contract SemanticsTester { result = result && (k == 1 || k == 2 || k == 3); } + // The map self.mC has a single entry 100 -> MapWrapper {m: 10 -> original self.sA}. + foreach (k1, v1 in self.mC) { + foreach (k2, v2 in v1.m) { + result = result && + k1 == 100 && + k2 == 10 && + + v2.a1 == 20 && + v2.a2.b1 == true && + v2.a2.b2.c1 == 5 && + v2.a2.b3 == 10; + } + } + return result; } @@ -864,6 +1481,63 @@ contract SemanticsTester { return result; } + get fun testReturnedContracts(): Bool { + // The call to the non-mutating extends function "copyContract" always makes a copy + // of the parameter contract. Therefore, passing the result to the mutating function + // "changeSomeContractFields" will modify the copy, not the original contract. + + self.copyContract().changeSomeContractFields(); + + let result = + self.checkAllContractFieldsAreUnchanged(); + + // For the following test, make a copy of self. + let c = self; + + // Now consider this expression: + + c.reflectContract().changeSomeContractFields(); + + // Again, passing "c" to the mutating function "reflectContract", + // will reassign back to c a copy of c, but it will also pass the copy of c to + // changeSomeContractFields. + // This means that the above expression will not modify c. + // Note that since the return type of changeSomeContractFields is void, + // we cannot use the trick: + // + // c = c.reflectContract().changeSomeContractFields(); + // + // to actually update c. Hence, the only ways are to directly call + // changeSomeContractFields or divide the expression into steps: + // + // c.changeSomeContractFields(); + // + // or: + // + // c.reflectContract(); + // c.changeSomeContractFields(); + + // Nothing changed + result = result && + self.checkAllContractFieldsAreUnchanged() && + c.checkAllContractFieldsAreUnchanged(); + + // When chaining mutating functions, one needs to reassign c in order to mutate it, + // because the chain passes copies to the next mutating function, + // and the copy returned by the last mutating function will be lost + // if not reassigned back into c: + + c = c.reflectContract().incrementIntFieldsInUB().flipBooleanFieldsInUB(); + + // c is now changed, the rest remains the same. + result = result && + self.checkAllContractFieldsAreUnchanged() && + c.checkFieldsEqualTo(SB {b1: true, b2: SC {c1: 1}, b3: 15}, 5); + + return result; + + } + get fun mutateContractStateFlag(): Bool { return self.mutateContractStateResult; } @@ -871,4 +1545,111 @@ contract SemanticsTester { get fun changesPersisted(): Bool { return self.checkFieldsEqualTo(SB {b1: true, b2: SC {c1: 77}, b3: 88}, 35); } -} \ No newline at end of file + + + /*************** Integers ********************/ + + /* + The following functions exemplify how to properly chain mutating functions. + Each function in the chain returns a copy to the next function in the chain. + Hence, to change the original variable, we need to reassign the value returned + from the last mutating function. + + Another way that is actually less confusing, is to simply break the chain + into single steps. + + */ + get fun mutatesChainInt1(): Bool { + let s = 2; + s.multiplyBy2().increment().doNothing(); // s is changed only by the first mutating function. + + let t = 2; + t = t.multiplyBy2().increment().doNothing(); // t reassigned with the value returned by the last function. + + // The less confusing solution is to simply break the chain. + // This solution even works when the last function in the chain has void return type. + // Each step changes z + + let z = 2; + z.multiplyBy2(); + z.increment(); + z.doNothing(); + + return s == 4 && t == 5 && z == 5; + } + + get fun mutatesChainInt2(): Bool { + let s = 2; + s.multiplyBy2().doNothing().increment(); + + let t = 2; + t = t.multiplyBy2().doNothing().increment(); + + let z = 2; + z.multiplyBy2(); + z.doNothing(); + z.increment(); + + return s == 4 && t == 5 && z == 5; + } + + get fun mutatesChainInt3(): Bool { + let s = 2; + s.doNothing().multiplyBy2().increment(); + + let t = 2; + t = t.doNothing().multiplyBy2().increment(); + + let z = 2; + z.doNothing(); + z.multiplyBy2(); + z.increment(); + + return s == 2 && t == 5 && z == 5; + } + + get fun mutatesChainInt4(): Bool { + let s = 2; + s.doNothing().increment().multiplyBy2(); + + let t = 2; + t = t.doNothing().increment().multiplyBy2(); + + let z = 2; + z.doNothing(); + z.increment(); + z.multiplyBy2(); + + return s == 2 && t == 6 && z == 6; + } + + get fun mutatesChainInt5(): Bool { + let s = 2; + s.increment().doNothing().multiplyBy2(); + + let t = 2; + t = t.increment().doNothing().multiplyBy2(); + + let z = 2; + z.increment(); + z.doNothing(); + z.multiplyBy2(); + + return s == 3 && t == 6 && z == 6; + } + + get fun mutatesChainInt6(): Bool { + let s = 2; + s.increment().multiplyBy2().doNothing(); + + let t = 2; + t = t.increment().multiplyBy2().doNothing(); + + let z = 2; + z.increment(); + z.multiplyBy2(); + z.doNothing(); + + return s == 3 && t == 6 && z == 6; + } +} diff --git a/src/test/e2e-emulated/semantics.spec.ts b/src/test/e2e-emulated/semantics.spec.ts index 15a94ac16..4d7c768ec 100644 --- a/src/test/e2e-emulated/semantics.spec.ts +++ b/src/test/e2e-emulated/semantics.spec.ts @@ -42,6 +42,13 @@ describe("semantics", () => { expect(await contract.getParamStruct2()).toEqual(true); expect(await contract.getMutateParamStruct1()).toEqual(true); expect(await contract.getMutateParamStruct2()).toEqual(true); + expect(await contract.getTestReturnedStructs()).toEqual(true); + expect(await contract.getMutatesChainStruct1()).toEqual(true); + expect(await contract.getMutatesChainStruct2()).toEqual(true); + expect(await contract.getMutatesChainStruct3()).toEqual(true); + expect(await contract.getMutatesChainStruct4()).toEqual(true); + expect(await contract.getMutatesChainStruct5()).toEqual(true); + expect(await contract.getMutatesChainStruct6()).toEqual(true); // Maps @@ -51,6 +58,18 @@ describe("semantics", () => { expect(await contract.getParamMap2()).toEqual(true); expect(await contract.getMutateParamMap1()).toEqual(true); expect(await contract.getMutateParamMap2()).toEqual(true); + expect(await contract.getTestReturnedMaps1()).toEqual(true); + // expect(await contract.getTestReturnedMaps2()).toEqual(true); + expect(await contract.getMutateNestedMap1()).toEqual(true); + + // Integers + + expect(await contract.getMutatesChainInt1()).toEqual(true); + expect(await contract.getMutatesChainInt2()).toEqual(true); + expect(await contract.getMutatesChainInt3()).toEqual(true); + expect(await contract.getMutatesChainInt4()).toEqual(true); + expect(await contract.getMutatesChainInt5()).toEqual(true); + expect(await contract.getMutatesChainInt6()).toEqual(true); // Contracts @@ -58,6 +77,7 @@ describe("semantics", () => { expect(await contract.getContractAssign2()).toEqual(true); expect(await contract.getParamContract()).toEqual(true); expect(await contract.getMutateParamContract()).toEqual(true); + expect(await contract.getTestReturnedContracts()).toEqual(true); // Obtain the address before the contract gets modified const address1 = await contract.getAddress(); From 76bc85d16df025f7d714c8cc22dd0b2f70a705fa Mon Sep 17 00:00:00 2001 From: jeshecdom Date: Thu, 26 Sep 2024 04:07:16 +0200 Subject: [PATCH 2/2] Changed "result = result &&" for "result &&=" in new tests. --- .../e2e-emulated/contracts/semantics.tact | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/test/e2e-emulated/contracts/semantics.tact b/src/test/e2e-emulated/contracts/semantics.tact index fc765dcec..0ba57c58d 100644 --- a/src/test/e2e-emulated/contracts/semantics.tact +++ b/src/test/e2e-emulated/contracts/semantics.tact @@ -404,7 +404,7 @@ contract SemanticsTester { // The map self.mC has a single entry 100 -> MapWrapper {m: 10 -> self.sA}. foreach (k1, v1 in self.mC) { foreach (k2, v2 in v1.m) { - result = result && + result &&= k1 == 100 && k2 == 10 && @@ -547,7 +547,7 @@ contract SemanticsTester { // Note that the only place where s is reassigned is after reflectStruct executes. // This means that the above expression does NOT change s. - result = result && + result &&= self.checkAllContractFieldsAreUnchanged() && s.a1 == 20 && s.a2.b1 == true && @@ -567,7 +567,7 @@ contract SemanticsTester { // is not possible because changeSomeFields has void return type. // s is now changed by changeSomeFields, and the rest remains the same - result = result && + result &&= self.checkAllContractFieldsAreUnchanged() && s.a1 == 120 && s.a2.b1 == true && @@ -991,7 +991,7 @@ contract SemanticsTester { wm.m.get(3)!!.a2.b3 == 0; foreach (k, _ in wm.m) { - result = result && (k == 1 || k == 2 || k == 3); + result &&= (k == 1 || k == 2 || k == 3); } // Instead, passing "wm" to the mutating function "unwrapMap", @@ -1002,7 +1002,7 @@ contract SemanticsTester { wm.unwrapMap().del(1); // FunC error: issue #866 // The contract fields remain the same and also wm - result = result && + result &&= self.checkAllContractFieldsAreUnchanged() && wm.m.get(1)!!.a1 == 20 && wm.m.get(1)!!.a2.b1 == true && @@ -1020,7 +1020,7 @@ contract SemanticsTester { wm.m.get(3)!!.a2.b3 == 0; foreach (k, _ in wm.m) { - result = result && (k == 1 || k == 2 || k == 3); + result &&= (k == 1 || k == 2 || k == 3); } // Since unwrapMap always returns a copy of wm.m, it is not possible @@ -1030,7 +1030,7 @@ contract SemanticsTester { wm.m.del(1); // The contract fields remain the same and wm only lost entry 1 - result = result && + result &&= self.checkAllContractFieldsAreUnchanged() && wm.m.get(2)!!.a1 == 20 && @@ -1044,7 +1044,7 @@ contract SemanticsTester { wm.m.get(3)!!.a2.b3 == 0; foreach (k, _ in wm.m) { - result = result && (k == 2 || k == 3); + result &&= (k == 2 || k == 3); } // Hence, chaining mutating functions to attempt to delete another entry, @@ -1053,7 +1053,7 @@ contract SemanticsTester { wm.reflectMap().unwrapMap().del(2); // FunC error: issue #866 // The contract fields remain the same and wm remains as in the previous step - result = result && + result &&= self.checkAllContractFieldsAreUnchanged() && wm.m.get(2)!!.a1 == 20 && @@ -1067,7 +1067,7 @@ contract SemanticsTester { wm.m.get(3)!!.a2.b3 == 0; foreach (k, _ in wm.m) { - result = result && (k == 2 || k == 3); + result &&= (k == 2 || k == 3); } return result; @@ -1109,7 +1109,7 @@ contract SemanticsTester { // But m does not change foreach (k1, v1 in m) { foreach (k2, v2 in v1.m) { - result = result && + result &&= k1 == 100 && k2 == 10 && @@ -1155,7 +1155,7 @@ contract SemanticsTester { // Indeed, wrappedNested1 and wrappedNested2 are equal foreach (k2, v2 in wrappedNested1.m) { - result = result && + result &&= k2 == 10 && v2.a1 == 20 && @@ -1165,7 +1165,7 @@ contract SemanticsTester { } foreach (k2, v2 in wrappedNested2.m) { - result = result && + result &&= k2 == 10 && v2.a1 == 20 && @@ -1181,7 +1181,7 @@ contract SemanticsTester { foreach (k1, v1 in m) { foreach (k2, v2 in v1.m) { - result = result && + result &&= k1 == 100 && k2 == 10 && @@ -1193,7 +1193,7 @@ contract SemanticsTester { } // The rest of the fields in the contract did not change (including self.mC). - result = result && self.checkAllContractFieldsAreUnchanged(); + result &&= self.checkAllContractFieldsAreUnchanged(); return result; } @@ -1441,7 +1441,7 @@ contract SemanticsTester { // The map self.mC has a single entry 100 -> MapWrapper {m: 10 -> original self.sA}. foreach (k1, v1 in self.mC) { foreach (k2, v2 in v1.m) { - result = result && + result &&= k1 == 100 && k2 == 10 && @@ -1518,7 +1518,7 @@ contract SemanticsTester { // c.changeSomeContractFields(); // Nothing changed - result = result && + result &&= self.checkAllContractFieldsAreUnchanged() && c.checkAllContractFieldsAreUnchanged(); @@ -1530,7 +1530,7 @@ contract SemanticsTester { c = c.reflectContract().incrementIntFieldsInUB().flipBooleanFieldsInUB(); // c is now changed, the rest remains the same. - result = result && + result &&= self.checkAllContractFieldsAreUnchanged() && c.checkFieldsEqualTo(SB {b1: true, b2: SC {c1: 1}, b3: 15}, 5);