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

[Compiler] fix object creation #1184

Merged
merged 11 commits into from
Oct 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
using Neo.VM;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Neo.Compiler;
Expand Down Expand Up @@ -48,6 +50,76 @@ private void ConvertObjectCreationExpression(SemanticModel model, BaseObjectCrea

private void ConvertObjectCreationExpressionInitializer(SemanticModel model, InitializerExpressionSyntax initializer)
{
// Handle different types of initializer expressions:
//
// ObjectInitializerExpression:
// Example: new Person { Name = "John", Age = 30 }
// Used for initializing properties of an object.
//
// CollectionInitializerExpression:
// Example: new List<int> { 1, 2, 3 }
// Used for initializing collections like lists or sets.
//
// ArrayInitializerExpression:
// Example: new int[] { 1, 2, 3 }
// Used for initializing arrays.
//
// ComplexElementInitializerExpression:
// Example: new Dictionary<string, int> { { "one", 1 }, { "two", 2 } }
// Used for initializing complex elements like dictionary entries.
//
// NullLiteralExpression:
// Example: new Person { Name = null }
// Used when explicitly setting a property to null in an initializer.

if (initializer.IsKind(SyntaxKind.CollectionInitializerExpression))
{
ITypeSymbol type;
if (initializer.Expressions.Count > 0)
{
var firstExpression = initializer.Expressions[0];
var typeInfo = model.GetTypeInfo(firstExpression);
type = typeInfo.Type!;
}
else
{
// Handle empty collection case if necessary
throw new CompilationException(initializer, DiagnosticId.SyntaxNotSupported, "Cannot determine item type from an empty collection initializer.");
}

AddInstruction(OpCode.DROP);
if (type.SpecialType == SpecialType.System_Byte)
{
var values = initializer.Expressions.Select(p => model.GetConstantValue(p)).ToArray();
if (values.Any(p => !p.HasValue))
{
Push(values.Length);
AddInstruction(OpCode.NEWBUFFER);
for (var i = 0; i < initializer.Expressions.Count; i++)
{
AddInstruction(OpCode.DUP);
Push(i);
ConvertExpression(model, initializer.Expressions[i]);
AddInstruction(OpCode.SETITEM);
}
}
else
{
var data = values.Select(p => (byte)System.Convert.ChangeType(p.Value, typeof(byte))!).ToArray();
Push(data);
ChangeType(VM.Types.StackItemType.Buffer);
}
}
else
{
for (var i = initializer.Expressions.Count - 1; i >= 0; i--)
ConvertExpression(model, initializer.Expressions[i]);
Push(initializer.Expressions.Count);
AddInstruction(OpCode.PACK);
}
return;
}

foreach (ExpressionSyntax e in initializer.Expressions)
{
if (e is not AssignmentExpressionSyntax ae)
Expand All @@ -63,9 +135,38 @@ private void ConvertObjectCreationExpressionInitializer(SemanticModel model, Ini
AddInstruction(OpCode.SETITEM);
break;
case IPropertySymbol property:
ConvertExpression(model, ae.Right);
AddInstruction(OpCode.OVER);
CallMethodWithConvention(model, property.SetMethod!, CallingConvention.Cdecl);
// Special handling for Map and List initialization is required due to their unique initialization syntax and behavior.
// Map and List properties are not defined explicitly like regular types

// Examples:
// Map: new Map<string, int> { ["key"] = 42 };
// This is equivalent to: map["key"] = 42;
// Regular: new MyClass { Property = value };
// This uses the standard property setter.
if (property.ContainingType.Name is "Map")
{
// Duplicate the object reference for Map and List
AddInstruction(OpCode.DUP);

if (ae.Left is ImplicitElementAccessSyntax elementAccess)
{
ConvertExpression(model, elementAccess.ArgumentList.Arguments[0].Expression);
}
else
{
ConvertExpression(model, ae.Left);
}
// Convert the value to be assigned (for both Map and List)
ConvertExpression(model, ae.Right);
AddInstruction(OpCode.SETITEM);
}
else
{
// For regular properties:
ConvertExpression(model, ae.Right);
AddInstruction(OpCode.OVER);
CallMethodWithConvention(model, property.SetMethod!, CallingConvention.Cdecl);
}
break;
default:
throw new CompilationException(ae.Left, DiagnosticId.SyntaxNotSupported, $"Unsupported symbol: {symbol}");
Expand Down
30 changes: 30 additions & 0 deletions tests/Neo.Compiler.CSharp.TestContracts/Contract_PropertyMethod.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Neo.SmartContract.Framework;

namespace Neo.Compiler.CSharp.TestContracts;

public class Contract_PropertyMethod : SmartContract.Framework.SmartContract
Expand All @@ -13,6 +15,30 @@ public static void testProperty2()
var p = new Person("NEO3", 10);
}

public static Person testProperty3()
{
return new Person()
{
Name = "NEO3",
};
}

public static Map<string, string> testProperty4()
{
return new Map<string, string>()
{
["Name"] = "NEO3",
};
}

public static List<int> testProperty5()
{
return new List<int>()
{
1, 2, 3, 4, 5
};
}

public static (string, int, string) testPropertyInit()
{
var p = new Person("NEO3", 10) { Address = "123 Blockchain St" };
Expand All @@ -30,5 +56,9 @@ public Person(string name, int age)
Name = name;
Age = age;
}

public Person()
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ public abstract class Contract_NEP11(Neo.SmartContract.Testing.SmartContractInit
{
#region Compiled data

public static Neo.SmartContract.Manifest.ContractManifest Manifest => Neo.SmartContract.Manifest.ContractManifest.Parse(@"{""name"":""Contract_NEP11"",""groups"":[],""features"":{},""supportedstandards"":[""NEP-11""],""abi"":{""methods"":[{""name"":""symbol"",""parameters"":[],""returntype"":""String"",""offset"":0,""safe"":true},{""name"":""decimals"",""parameters"":[],""returntype"":""Integer"",""offset"":7,""safe"":true},{""name"":""totalSupply"",""parameters"":[],""returntype"":""Integer"",""offset"":9,""safe"":true},{""name"":""balanceOf"",""parameters"":[{""name"":""owner"",""type"":""Hash160""}],""returntype"":""Integer"",""offset"":35,""safe"":true},{""name"":""ownerOf"",""parameters"":[{""name"":""tokenId"",""type"":""ByteArray""}],""returntype"":""Hash160"",""offset"":218,""safe"":true},{""name"":""properties"",""parameters"":[{""name"":""tokenId"",""type"":""ByteArray""}],""returntype"":""Map"",""offset"":377,""safe"":true},{""name"":""tokens"",""parameters"":[],""returntype"":""InteropInterface"",""offset"":419,""safe"":true},{""name"":""tokensOf"",""parameters"":[{""name"":""owner"",""type"":""Hash160""}],""returntype"":""InteropInterface"",""offset"":447,""safe"":true},{""name"":""transfer"",""parameters"":[{""name"":""to"",""type"":""Hash160""},{""name"":""tokenId"",""type"":""ByteArray""},{""name"":""data"",""type"":""Any""}],""returntype"":""Boolean"",""offset"":536,""safe"":false},{""name"":""_initialize"",""parameters"":[],""returntype"":""Void"",""offset"":837,""safe"":false}],""events"":[{""name"":""Transfer"",""parameters"":[{""name"":""from"",""type"":""Hash160""},{""name"":""to"",""type"":""Hash160""},{""name"":""amount"",""type"":""Integer""},{""name"":""tokenId"",""type"":""ByteArray""}]}]},""permissions"":[{""contract"":""0x726cb6e0cd8628a1350a611384688911ab75f51b"",""methods"":[""sha256""]},{""contract"":""0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0"",""methods"":[""deserialize"",""serialize""]},{""contract"":""0xfffdc93764dbaddd97c48f252a53ea4643faa3fd"",""methods"":[""getContract""]},{""contract"":""*"",""methods"":[""onNEP11Payment""]}],""trusts"":[],""extra"":{""nef"":{""optimization"":""All""}}}");
public static Neo.SmartContract.Manifest.ContractManifest Manifest => Neo.SmartContract.Manifest.ContractManifest.Parse(@"{""name"":""Contract_NEP11"",""groups"":[],""features"":{},""supportedstandards"":[""NEP-11""],""abi"":{""methods"":[{""name"":""symbol"",""parameters"":[],""returntype"":""String"",""offset"":0,""safe"":true},{""name"":""decimals"",""parameters"":[],""returntype"":""Integer"",""offset"":7,""safe"":true},{""name"":""totalSupply"",""parameters"":[],""returntype"":""Integer"",""offset"":9,""safe"":true},{""name"":""balanceOf"",""parameters"":[{""name"":""owner"",""type"":""Hash160""}],""returntype"":""Integer"",""offset"":35,""safe"":true},{""name"":""ownerOf"",""parameters"":[{""name"":""tokenId"",""type"":""ByteArray""}],""returntype"":""Hash160"",""offset"":218,""safe"":true},{""name"":""properties"",""parameters"":[{""name"":""tokenId"",""type"":""ByteArray""}],""returntype"":""Map"",""offset"":377,""safe"":true},{""name"":""tokens"",""parameters"":[],""returntype"":""InteropInterface"",""offset"":424,""safe"":true},{""name"":""tokensOf"",""parameters"":[{""name"":""owner"",""type"":""Hash160""}],""returntype"":""InteropInterface"",""offset"":452,""safe"":true},{""name"":""transfer"",""parameters"":[{""name"":""to"",""type"":""Hash160""},{""name"":""tokenId"",""type"":""ByteArray""},{""name"":""data"",""type"":""Any""}],""returntype"":""Boolean"",""offset"":541,""safe"":false},{""name"":""_initialize"",""parameters"":[],""returntype"":""Void"",""offset"":842,""safe"":false}],""events"":[{""name"":""Transfer"",""parameters"":[{""name"":""from"",""type"":""Hash160""},{""name"":""to"",""type"":""Hash160""},{""name"":""amount"",""type"":""Integer""},{""name"":""tokenId"",""type"":""ByteArray""}]}]},""permissions"":[{""contract"":""0x726cb6e0cd8628a1350a611384688911ab75f51b"",""methods"":[""sha256""]},{""contract"":""0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0"",""methods"":[""deserialize"",""serialize""]},{""contract"":""0xfffdc93764dbaddd97c48f252a53ea4643faa3fd"",""methods"":[""getContract""]},{""contract"":""*"",""methods"":[""onNEP11Payment""]}],""trusts"":[],""extra"":{""nef"":{""optimization"":""All""}}}");

/// <summary>
/// Optimization: "All"
/// </summary>
public static Neo.SmartContract.NefFile Nef => Neo.IO.Helper.AsSerializable<Neo.SmartContract.NefFile>(Convert.FromBase64String(@"TkVGM1Rlc3RpbmdFbmdpbmUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATA7znO4OTpJcbCoGp54UQN2G/OrAtkZXNlcmlhbGl6ZQEAAQ/A7znO4OTpJcbCoGp54UQN2G/OrAlzZXJpYWxpemUBAAEP/aP6Q0bqUyolj8SX3a3bZDfJ/f8LZ2V0Q29udHJhY3QBAAEPG/V1qxGJaIQTYQo1oSiGzeC2bHIGc2hhMjU2AQABDwAA/UgDDARURVNUQBBAWNgmFwwBAEH2tGviQZJd6DFK2CYERRBKYEBXAQF4cGgLlyYFCCINeErZKFDKABSzq6omJQwgVGhlIGFyZ3VtZW50ICJvd25lciIgaXMgaW52YWxpZC46QZv2Z84REYhOEFHQUBLAcHhowUVTi1BBkl3oMUrYJgRFENshQFcCAkGb9mfOERGIThBR0FASwHB4aMFFU4tQQZJd6DFK2CYERRDbIXFpeZ5xaRC1JgQJQGkQsyYQeGjBRVOLUEEvWMXtIg9peGjBRVOLUEHmPxiECEBXAwF4ygBAtyY8DDdUaGUgYXJndW1lbnQgInRva2VuSWQiIHNob3VsZCBiZSA2NCBvciBsZXNzIGJ5dGVzIGxvbmcuOhMRiE4QUdBBm/ZnzhLAcHhowUVTi1BBkl3oMUrYJjRFDC5UaGUgdG9rZW4gd2l0aCBnaXZlbiAidG9rZW5JZCIgZG9lcyBub3QgZXhpc3QuOnFpNwAAcmoQzkBXAgETEYhOEFHQQZv2Z84SwHB4aMFFU4tQQZJd6DE3AABxyGkRzktT0EBXAQATEYhOEFHQQZv2Z84SwHATaMFFQd8wuJpAVwEBeHBoC5cmBQgiDXhK2ShQygAUs6uqJiQMH1RoZSBhcmd1bWVudCAib3duZXIiIGlzIGludmFsaWQ6FBGIThBR0EGb9mfOEsBwE3howUVTi1BB3zC4mkBXAwN4cGgLlyYFCCINeErZKFDKABSzq6omIgwdVGhlIGFyZ3VtZW50ICJ0byIgaXMgaW52YWxpZC46ExGIThBR0EGb9mfOEsBweWjBRVOLUEGSXegxNwAAcWkQznJqQfgn7IyqJgQJQGp4mCYleEppEFHQRWk3AQBKeWjBRVOLUEHmPxiERQ95ajQPEXl4NAp6eXhqNEUIQFcCA3p4NdD9//9FQZv2Z84UEYhOEFHQUBLAcHh5i9socXoQtyYQEGlowUVTi1BB5j8YhEBpaMFFU4tQQS9Yxe1AVwEEwkp4z0p5z0oRz0p6zwwIVHJhbnNmZXJBlQFvYXlwaAuXqiQFCSILeTcCAHBoC5eqJiB7ehF4FMAfDA5vbk5FUDExUGF5bWVudHlBYn1bUkVAVgFAfGetTA=="));
public static Neo.SmartContract.NefFile Nef => Neo.IO.Helper.AsSerializable<Neo.SmartContract.NefFile>(Convert.FromBase64String(@"TkVGM1Rlc3RpbmdFbmdpbmUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATA7znO4OTpJcbCoGp54UQN2G/OrAtkZXNlcmlhbGl6ZQEAAQ/A7znO4OTpJcbCoGp54UQN2G/OrAlzZXJpYWxpemUBAAEP/aP6Q0bqUyolj8SX3a3bZDfJ/f8LZ2V0Q29udHJhY3QBAAEPG/V1qxGJaIQTYQo1oSiGzeC2bHIGc2hhMjU2AQABDwAA/U0DDARURVNUQBBAWNgmFwwBAEH2tGviQZJd6DFK2CYERRBKYEBXAQF4cGgLlyYFCCINeErZKFDKABSzq6omJQwgVGhlIGFyZ3VtZW50ICJvd25lciIgaXMgaW52YWxpZC46QZv2Z84REYhOEFHQUBLAcHhowUVTi1BBkl3oMUrYJgRFENshQFcCAkGb9mfOERGIThBR0FASwHB4aMFFU4tQQZJd6DFK2CYERRDbIXFpeZ5xaRC1JgQJQGkQsyYQeGjBRVOLUEEvWMXtIg9peGjBRVOLUEHmPxiECEBXAwF4ygBAtyY8DDdUaGUgYXJndW1lbnQgInRva2VuSWQiIHNob3VsZCBiZSA2NCBvciBsZXNzIGJ5dGVzIGxvbmcuOhMRiE4QUdBBm/ZnzhLAcHhowUVTi1BBkl3oMUrYJjRFDC5UaGUgdG9rZW4gd2l0aCBnaXZlbiAidG9rZW5JZCIgZG9lcyBub3QgZXhpc3QuOnFpNwAAcmoQzkBXAgETEYhOEFHQQZv2Z84SwHB4aMFFU4tQQZJd6DE3AABxyEoMBG5hbWVpEc7QQFcBABMRiE4QUdBBm/ZnzhLAcBNowUVB3zC4mkBXAQF4cGgLlyYFCCINeErZKFDKABSzq6omJAwfVGhlIGFyZ3VtZW50ICJvd25lciIgaXMgaW52YWxpZDoUEYhOEFHQQZv2Z84SwHATeGjBRVOLUEHfMLiaQFcDA3hwaAuXJgUIIg14StkoUMoAFLOrqiYiDB1UaGUgYXJndW1lbnQgInRvIiBpcyBpbnZhbGlkLjoTEYhOEFHQQZv2Z84SwHB5aMFFU4tQQZJd6DE3AABxaRDOcmpB+CfsjKomBAlAaniYJiV4SmkQUdBFaTcBAEp5aMFFU4tQQeY/GIRFD3lqNA8ReXg0Cnp5eGo0RQhAVwIDeng1y/3//0VBm/ZnzhQRiE4QUdBQEsBweHmL2yhxehC3JhAQaWjBRVOLUEHmPxiEQGlowUVTi1BBL1jF7UBXAQTCSnjPSnnPShHPSnrPDAhUcmFuc2ZlckGVAW9heXBoC5eqJAUJIgt5NwIAcGgLl6omIHt6EXgUwB8MDm9uTkVQMTFQYXltZW50eUFifVtSRUBWAUB17R06"));

#endregion

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ public abstract class Contract_PropertyMethod(Neo.SmartContract.Testing.SmartCon
{
#region Compiled data

public static Neo.SmartContract.Manifest.ContractManifest Manifest => Neo.SmartContract.Manifest.ContractManifest.Parse(@"{""name"":""Contract_PropertyMethod"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""testProperty"",""parameters"":[],""returntype"":""Array"",""offset"":0,""safe"":false},{""name"":""testProperty2"",""parameters"":[],""returntype"":""Void"",""offset"":50,""safe"":false},{""name"":""testPropertyInit"",""parameters"":[],""returntype"":""Array"",""offset"":71,""safe"":false}],""events"":[]},""permissions"":[],""trusts"":[],""extra"":{""nef"":{""optimization"":""All""}}}");
public static Neo.SmartContract.Manifest.ContractManifest Manifest => Neo.SmartContract.Manifest.ContractManifest.Parse(@"{""name"":""Contract_PropertyMethod"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""testProperty"",""parameters"":[],""returntype"":""Array"",""offset"":0,""safe"":false},{""name"":""testProperty2"",""parameters"":[],""returntype"":""Void"",""offset"":50,""safe"":false},{""name"":""testProperty3"",""parameters"":[],""returntype"":""Any"",""offset"":71,""safe"":false},{""name"":""testProperty4"",""parameters"":[],""returntype"":""Map"",""offset"":94,""safe"":false},{""name"":""testProperty5"",""parameters"":[],""returntype"":""Array"",""offset"":110,""safe"":false},{""name"":""testPropertyInit"",""parameters"":[],""returntype"":""Array"",""offset"":120,""safe"":false}],""events"":[]},""permissions"":[],""trusts"":[],""extra"":{""nef"":{""optimization"":""All""}}}");

/// <summary>
/// Optimization: "All"
/// </summary>
public static Neo.SmartContract.NefFile Nef => Neo.IO.Helper.AsSerializable<Neo.SmartContract.NefFile>(Convert.FromBase64String(@"TkVGM1Rlc3RpbmdFbmdpbmUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAINXAQALEAsTwBoMBE5FTzMSTTQPcMVKaBDOz0poEc7PQFcAA3lKeBBR0EV6SngRUdBFQFcBAAsQCxPAGgwETkVPMxJNNN1wQFcBAAsQCxPAGgwETkVPMxJNNMgMETEyMyBCbG9ja2NoYWluIFN0SxJR0HDFSmgQzs9KaBHOz0poEs7PQGh8TO4="));
public static Neo.SmartContract.NefFile Nef => Neo.IO.Helper.AsSerializable<Neo.SmartContract.NefFile>(Convert.FromBase64String(@"TkVGM1Rlc3RpbmdFbmdpbmUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALRXAQALEAsTwBoMBE5FTzMSTTQPcMVKaBDOz0poEc7PQFcAA3lKeBBR0EV6SngRUdBFQFcBAAsQCxPAGgwETkVPMxJNNN1wQAsQCxPASjQNDARORU8zSxBR0EBXAAFAyEoMBE5hbWUMBE5FTzPQQMJFFRQTEhEVwEBXAQALEAsTwBoMBE5FTzMSTTSXDBExMjMgQmxvY2tjaGFpbiBTdEsSUdBwxUpoEM7PSmgRzs9KaBLOz0Dp1QFJ"));

#endregion

Expand All @@ -33,6 +33,24 @@ public abstract class Contract_PropertyMethod(Neo.SmartContract.Testing.SmartCon
[DisplayName("testProperty2")]
public abstract void TestProperty2();

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testProperty3")]
public abstract object? TestProperty3();

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testProperty4")]
public abstract IDictionary<object, object>? TestProperty4();

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testProperty5")]
public abstract IList<object>? TestProperty5();

/// <summary>
/// Unsafe method
/// </summary>
Expand Down
42 changes: 42 additions & 0 deletions tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Property_Method.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.SmartContract.Testing;
using Neo.VM.Types;
using System.Numerics;
using Array = Neo.VM.Types.Array;

namespace Neo.Compiler.CSharp.UnitTests
{
Expand All @@ -27,6 +29,46 @@ public void TestPropertyMethod2()
// No errors
}

[TestMethod]
public void TestPropertyMethod3()
{
var person = Contract.TestProperty3()! as Array;
AssertGasConsumed(1309080); // Adjust this value based on actual gas consumption

Assert.IsNotNull(person);
Assert.AreEqual(3, person.Count);
Assert.AreEqual((person[0] as StackItem)!.GetString(), "NEO3");
Assert.AreEqual(person[1], new BigInteger(0));
}

[TestMethod]
public void TestPropertyMethod4()
{
var map = Contract.TestProperty4()!;
AssertGasConsumed(1230570); // Adjust this value based on actual gas consumption

Assert.IsNotNull(map);
Assert.AreEqual(1, map.Count);

var key = (ByteString)"Name";
Assert.IsTrue(map.ContainsKey(key));
Assert.AreEqual((map[key] as StackItem)!.GetString(), "NEO3");
}

[TestMethod]
public void TestPropertyMethod5()
{
var list = Contract.TestProperty5()!;
AssertGasConsumed(1046190); // Adjust this value based on actual gas consumption

Assert.IsNotNull(list);
Assert.AreEqual(5, list.Count);
for (var i = 0; i < 5; i++)
{
Assert.AreEqual(i + 1, (int)(BigInteger)list[i]);
}
}

[TestMethod]
public void TestPropertyInit()
{
Expand Down
Loading
Loading