Jelajahi Sumber

Merge pull request #244 from nuskey8/improve/property-generation

Add: support for init/set/get only properties for SourceGenerator
Akito Inoue 2 minggu lalu
induk
melakukan
73a09f25c9

+ 85 - 24
src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs

@@ -13,7 +13,8 @@ partial class LuaObjectGenerator
             : "(";
     }
 
-    static bool TryEmit(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, in SourceProductionContext context, Dictionary<INamedTypeSymbol, TypeMetadata> metaDict)
+    static bool TryEmit(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, in SourceProductionContext context, Dictionary<INamedTypeSymbol, TypeMetadata> metaDict,
+        TempCollections tempCollections)
     {
         try
         {
@@ -87,19 +88,19 @@ partial class LuaObjectGenerator
 
             using var _ = builder.BeginBlockScope($"partial {typeDeclarationKeyword} {typeMetadata.TypeName} : global::Lua.ILuaUserData");
 
-            var metamethodSet = new HashSet<LuaObjectMetamethod>();
+            var metamethodSet = tempCollections.Metamethods;
 
             if (!TryEmitMethods(typeMetadata, builder, references, compilation, metamethodSet, context))
             {
                 return false;
             }
 
-            if (!TryEmitIndexMetamethod(typeMetadata, builder, references, compilation, context))
+            if (!TryEmitIndexMetamethod(typeMetadata, builder, references, compilation, context, tempCollections))
             {
                 return false;
             }
 
-            if (!TryEmitNewIndexMetamethod(typeMetadata, builder, references, context))
+            if (!TryEmitNewIndexMetamethod(typeMetadata, builder, references, context, tempCollections))
             {
                 return false;
             }
@@ -269,20 +270,26 @@ partial class LuaObjectGenerator
         return isValid;
     }
 
-    static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, in SourceProductionContext context)
+    static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, in SourceProductionContext context, TempCollections tempCollections)
     {
         builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_index = new global::Lua.LuaFunction(""index"", (context, ct) =>");
 
         using (builder.BeginBlockScope())
         {
             builder.AppendLine($"var userData = context.GetArgument<{typeMetadata.FullTypeName}>(0);");
-            builder.AppendLine($"var key = context.GetArgument<global::System.String>(1);");
+            builder.AppendLine("var key = context.GetArgument<global::System.String>(1);");
             builder.AppendLine("var result = key switch");
-
             using (builder.BeginBlockScope())
             {
                 foreach (var propertyMetadata in typeMetadata.Properties)
                 {
+                    if (propertyMetadata.IsWriteOnly)
+                    {
+                        tempCollections.InvalidMemberNames.Add(propertyMetadata.LuaMemberName);
+                        continue;
+                    }
+
+
                     var conversionPrefix = GetLuaValuePrefix(propertyMetadata.Type, references, compilation);
                     if (propertyMetadata.IsStatic)
                     {
@@ -300,7 +307,35 @@ partial class LuaObjectGenerator
                     builder.AppendLine(@$"""{methodMetadata.LuaMemberName}"" => new global::Lua.LuaValue(__function_{methodMetadata.LuaMemberName}),");
                 }
 
-                builder.AppendLine(@$"_ => global::Lua.LuaValue.Nil,");
+                builder.Append("_ => ");
+                {
+                    if (tempCollections.InvalidMemberNames.Count > 0)
+                    {
+                        builder.Append("(key is ", false);
+                        for (var index = 0; index < tempCollections.InvalidMemberNames.Count; index++)
+                        {
+                            var name = tempCollections.InvalidMemberNames[index];
+                            builder.Append("\"", false);
+                            builder.Append(name, false);
+                            builder.Append("\"", false);
+                            if (index < tempCollections.InvalidMemberNames.Count - 1)
+                            {
+                                builder.Append(" or ", false);
+                            }
+                        }
+
+                        builder.AppendLine(")", false);
+                        using (builder.BeginIndentScope())
+                        {
+                            builder.AppendLine(@"? throw new global::Lua.LuaRuntimeException(context.State, $""'{key}' cannot be read."")");
+                            builder.AppendLine(": global::Lua.LuaValue.Nil,");
+                        }
+
+                        tempCollections.InvalidMemberNames.Clear();
+                    }
+                    else
+                        builder.AppendLine(@"global::Lua.LuaValue.Nil,");
+                }
             }
 
             builder.AppendLine(";");
@@ -313,29 +348,31 @@ partial class LuaObjectGenerator
         return true;
     }
 
-    static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, in SourceProductionContext context)
+    static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, in SourceProductionContext context, TempCollections tempCollections)
     {
         builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_newindex = new global::Lua.LuaFunction(""newindex"", (context, ct) =>");
 
         using (builder.BeginBlockScope())
         {
             builder.AppendLine($"var userData = context.GetArgument<{typeMetadata.FullTypeName}>(0);");
-            builder.AppendLine($"var key = context.GetArgument<global::System.String>(1);");
+            builder.AppendLine("var key = context.GetArgument<global::System.String>(1);");
             builder.AppendLine("switch (key)");
 
             using (builder.BeginBlockScope())
             {
                 foreach (var propertyMetadata in typeMetadata.Properties)
                 {
+                    if (propertyMetadata.IsReadOnly)
+                    {
+                        tempCollections.InvalidMemberNames.Add(propertyMetadata.LuaMemberName);
+                        continue;
+                    }
+
                     builder.AppendLine(@$"case ""{propertyMetadata.LuaMemberName}"":");
 
                     using (builder.BeginIndentScope())
                     {
-                        if (propertyMetadata.IsReadOnly)
-                        {
-                            builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State, $""'{{key}}' cannot overwrite."");");
-                        }
-                        else if (propertyMetadata.IsStatic)
+                        if (propertyMetadata.IsStatic)
                         {
                             if (SymbolEqualityComparer.Default.Equals(propertyMetadata.Type, references.LuaValue))
                             {
@@ -367,19 +404,42 @@ partial class LuaObjectGenerator
                 foreach (var methodMetadata in typeMetadata.Methods
                              .Where(x => x.HasMemberAttribute))
                 {
-                    builder.AppendLine(@$"case ""{methodMetadata.LuaMemberName}"":");
-
-                    using (builder.BeginIndentScope())
-                    {
-                        builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State, $""'{{key}}' cannot overwrite."");");
-                    }
+                    tempCollections.InvalidMemberNames.Add(methodMetadata.LuaMemberName);
                 }
 
-                builder.AppendLine(@$"default:");
+                builder.AppendLine(@"default:");
 
                 using (builder.BeginIndentScope())
                 {
-                    builder.AppendLine(@$"throw new global::Lua.LuaRuntimeException(context.State, $""'{{key}}' not found."");");
+                    if (tempCollections.InvalidMemberNames.Count > 0)
+                    {
+                        builder.AppendLine("throw new global::Lua.LuaRuntimeException(context.State,");
+                        using (builder.BeginIndentScope())
+                        {
+                            builder.Append("(key is ");
+                            for (var index = 0; index < tempCollections.InvalidMemberNames.Count; index++)
+                            {
+                                var name = tempCollections.InvalidMemberNames[index];
+                                builder.Append("\"", false);
+                                builder.Append(name, false);
+                                builder.Append("\"", false);
+                                if (index < tempCollections.InvalidMemberNames.Count - 1)
+                                {
+                                    builder.Append(" or ", false);
+                                }
+                            }
+
+                            builder.AppendLine(")", false);
+                            using (builder.BeginIndentScope())
+                            {
+                                builder.AppendLine(@"? $""'{key}' cannot overwrite.""");
+                                tempCollections.InvalidMemberNames.Clear();
+                                builder.AppendLine(@": $""'{key}' not found."");");
+                            }
+                        }
+                    }
+                    else
+                        builder.AppendLine(@"throw new global::Lua.LuaRuntimeException(context.State, $""'{key}' not found."");");
                 }
             }
 
@@ -471,7 +531,8 @@ partial class LuaObjectGenerator
                     }
                     else
                     {
-                        builder.AppendLine($"var arg{index} = context.HasArgument({index}) ?  context.GetArgument<{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>({index}) : {syntax.Default!.Value.ToFullString()};");
+                        builder.AppendLine(
+                            $"var arg{index} = context.HasArgument({index}) ?  context.GetArgument<{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>({index}) : {syntax.Default!.Value.ToFullString()};");
                     }
                 }
                 else

+ 3 - 1
src/Lua.SourceGenerator/LuaObjectGenerator.cs

@@ -39,10 +39,11 @@ public partial class LuaObjectGenerator : IIncrementalGenerator
                     metaDict.Add(symbol, typeMeta);
                 }
 
+                var tempCollections = new TempCollections();
                 foreach (var pair in metaDict)
                 {
                     var typeMeta = pair.Value;
-                    if (TryEmit(typeMeta, builder, references, compilation, in sourceProductionContext, metaDict))
+                    if (TryEmit(typeMeta, builder, references, compilation, in sourceProductionContext, metaDict, tempCollections))
                     {
                         var fullType = typeMeta.FullTypeName
                             .Replace("global::", "")
@@ -52,6 +53,7 @@ public partial class LuaObjectGenerator : IIncrementalGenerator
                         sourceProductionContext.AddSource($"{fullType}.LuaObject.g.cs", builder.ToString());
                     }
 
+                    tempCollections.Clear();
                     builder.Clear();
                 }
             });

+ 3 - 1
src/Lua.SourceGenerator/PropertyMetadata.cs

@@ -9,6 +9,7 @@ public class PropertyMetadata
     public string TypeFullName { get; }
     public bool IsStatic { get; }
     public bool IsReadOnly { get; }
+    public bool IsWriteOnly { get; }
     public string LuaMemberName { get; }
 
     public PropertyMetadata(ISymbol symbol, SymbolReferences references)
@@ -27,7 +28,8 @@ public class PropertyMetadata
         {
             Type = property.Type;
             TypeFullName = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
-            IsReadOnly = property.SetMethod == null;
+            IsReadOnly = property.IsReadOnly || property.SetMethod == null || property.SetMethod.IsInitOnly;
+            IsWriteOnly = property.IsWriteOnly;
         }
         else
         {

+ 12 - 0
src/Lua.SourceGenerator/TempCollections.cs

@@ -0,0 +1,12 @@
+namespace Lua.SourceGenerator;
+
+class TempCollections
+{
+    public readonly HashSet<LuaObjectMetamethod> Metamethods = new();
+    public readonly List<string> InvalidMemberNames = new();
+    public void Clear()
+    {
+        Metamethods.Clear();
+        InvalidMemberNames.Clear();
+    }
+}

+ 0 - 5
src/Lua.SourceGenerator/TypeMetadata.cs

@@ -37,11 +37,6 @@ class TypeMetadata
 
                 if (x is IPropertySymbol p)
                 {
-                    if (p.GetMethod == null || p.SetMethod == null)
-                    {
-                        return false;
-                    }
-
                     if (p.IsIndexer)
                     {
                         return false;

+ 27 - 27
tests/Lua.Tests/LuaObjectTests.cs

@@ -1,59 +1,59 @@
 using Lua.Standard;
 
 namespace Lua.Tests;
+
 [LuaObject]
-public partial class LuaTestObj {
+public partial class LuaTestObj
+{
     int x;
     int y;
 
     [LuaMember("x")]
-    public int X {
+    public int X
+    {
         get => x;
         set => x = value;
     }
 
     [LuaMember("y")]
-    public int Y {
+    public int Y
+    {
         get => y;
         set => y = value;
     }
 
     [LuaMember("create")]
-    public static LuaTestObj Create(int x, int y) {
-        return new LuaTestObj() {
-            x = x,
-            y = y
-        };
+    public static LuaTestObj Create(int x, int y)
+    {
+        return new LuaTestObj() { x = x, y = y };
     }
 
     [LuaMetamethod(LuaObjectMetamethod.Add)]
-    public static LuaTestObj Add(LuaTestObj a, LuaTestObj b) {
-        return new LuaTestObj() {
-            x = a.x + b.x,
-            y = a.y + b.y
-        };
+    public static LuaTestObj Add(LuaTestObj a, LuaTestObj b)
+    {
+        return new LuaTestObj() { x = a.x + b.x, y = a.y + b.y };
     }
-    
+
     [LuaMetamethod(LuaObjectMetamethod.Sub)]
-    public static async Task<LuaTestObj> Sub(LuaTestObj a, LuaTestObj b) {
+    public static async Task<LuaTestObj> Sub(LuaTestObj a, LuaTestObj b)
+    {
         await Task.Delay(1);
-        return new LuaTestObj() {
-            x = a.x - b.x,
-            y = a.y - b.y
-        };
+        return new LuaTestObj() { x = a.x - b.x, y = a.y - b.y };
     }
 }
+
 [LuaObject]
 public partial class TestUserData
 {
-    [LuaMember]
-    public int Property { get; set; }
+    [LuaMember] public int Property { get; init; }
 
-    [LuaMember]
-    public LuaValue LuaValueProperty { get; set; }
+    [LuaMember] public int ReadOnlyProperty { get; }
 
-    [LuaMember("p2")]
-    public string PropertyWithName { get; set; } = "";
+    [LuaMember] public int SetOnlyProperty { set { } }
+
+    [LuaMember] public LuaValue LuaValueProperty { get; set; }
+
+    [LuaMember("p2")] public string PropertyWithName { get; set; } = "";
 
     [LuaMember]
     public static void MethodVoid()
@@ -201,7 +201,7 @@ public class LuaObjectTests
         Assert.That(results, Has.Length.EqualTo(1));
         Assert.That(results[0], Is.EqualTo(new LuaValue("Called!")));
     }
-    
+
     [Test]
     public async Task Test_ArithMetamethod()
     {
@@ -209,7 +209,7 @@ public class LuaObjectTests
 
         var state = LuaState.Create();
         state.OpenBasicLibrary();
-        state.Environment["TestObj"]=userData;
+        state.Environment["TestObj"] = userData;
         var results = await state.DoStringAsync("""
                                                 local a = TestObj.create(1, 2)
                                                 local b = TestObj.create(3, 4)