diff --git a/src/Neo.Compiler.CSharp/MethodConvert/Expression/AssignmentExpression.SimpleAssignment.cs b/src/Neo.Compiler.CSharp/MethodConvert/Expression/AssignmentExpression.SimpleAssignment.cs index 1ae9f98dd..a28505e94 100644 --- a/src/Neo.Compiler.CSharp/MethodConvert/Expression/AssignmentExpression.SimpleAssignment.cs +++ b/src/Neo.Compiler.CSharp/MethodConvert/Expression/AssignmentExpression.SimpleAssignment.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Neo.VM; using System; +using System.Linq; using System.Runtime.InteropServices; namespace Neo.Compiler; @@ -139,8 +140,34 @@ private void ConvertIdentifierNameAssignment(SemanticModel model, IdentifierName AccessSlot(OpCode.STARG, _parameters[parameter]); break; case IPropertySymbol property: - if (!property.IsStatic) AddInstruction(OpCode.LDARG0); - Call(model, property.SetMethod!, CallingConvention.Cdecl); + // Check if the property is within a constructor and is readonly + // C# document here https://learn.microsoft.com/en-us/dotnet/csharp/properties + // example of this syntax: + // public class Person + // { + // public Person(string firstName) => FirstName = firstName; + // // Readonly property + // public string FirstName { get; } + // } + if (property.SetMethod == null) + { + IFieldSymbol[] fields = property.ContainingType.GetAllMembers().OfType().ToArray(); + fields = fields.Where(p => !p.IsStatic).ToArray(); + int backingFieldIndex = Array.FindIndex(fields, p => SymbolEqualityComparer.Default.Equals(p.AssociatedSymbol, property)); + AccessSlot(OpCode.LDARG, 0); + Push(backingFieldIndex); + AddInstruction(OpCode.ROT); + AddInstruction(OpCode.SETITEM); + } + else if (property.SetMethod != null) + { + if (!property.IsStatic) AddInstruction(OpCode.LDARG0); + Call(model, property.SetMethod, CallingConvention.Cdecl); + } + else + { + throw new CompilationException(left, DiagnosticId.SyntaxNotSupported, $"Property is readonly and not within a constructor: {property.Name}"); + } break; default: throw new CompilationException(left, DiagnosticId.SyntaxNotSupported, $"Unsupported symbol: {symbol}"); diff --git a/tests/Neo.Compiler.CSharp.TestContracts/Contract_PropertyMethod.cs b/tests/Neo.Compiler.CSharp.TestContracts/Contract_PropertyMethod.cs new file mode 100644 index 000000000..b072213f6 --- /dev/null +++ b/tests/Neo.Compiler.CSharp.TestContracts/Contract_PropertyMethod.cs @@ -0,0 +1,27 @@ +namespace Neo.Compiler.CSharp.UnitTests.TestClasses; + +public class Contract_PropertyMethod : SmartContract.Framework.SmartContract +{ + public static (string, int) testProperty() + { + var p = new Person("NEO3", 10); + return (p.Name, p.Age); + } + + public static void testProperty2() + { + var p = new Person("NEO3", 10); + } + + public class Person + { + public string Name { get; set; } + public int Age { get; } + + public Person(string name, int age) + { + Name = name; + Age = age; + } + } +} diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Property_Method.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Property_Method.cs new file mode 100644 index 000000000..45d6ea0b6 --- /dev/null +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Property_Method.cs @@ -0,0 +1,39 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.TestEngine; +using Neo.VM; +using Neo.VM.Types; + +namespace Neo.Compiler.CSharp.UnitTests +{ + [TestClass] + public class UnitTest_Property_Method + { + private TestEngine testEngine; + + [TestInitialize] + public void Init() + { + testEngine = new TestEngine(); + testEngine.AddNoOptimizeEntryScript(Utils.Extensions.TestContractRoot + "Contract_PropertyMethod.cs"); + } + + [TestMethod] + public void TestPropertyMethod() + { + testEngine.Reset(); + var res = testEngine.ExecuteTestCaseStandard("testProperty"); + Assert.AreEqual(testEngine.State, VMState.HALT); + var arr = (Array)res.Pop(); + Assert.AreEqual(arr[0].GetString(), "NEO3"); + Assert.AreEqual(arr[1].GetInteger(), 10); + } + + [TestMethod] + public void TestPropertyMethod2() + { + testEngine.Reset(); + var res = testEngine.ExecuteTestCaseStandard("testProperty2"); + Assert.AreEqual(testEngine.State, VMState.HALT); + } + } +}