Skip to content
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
109 changes: 85 additions & 24 deletions src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ static string GetLuaValuePrefix(ITypeSymbol typeSymbol, SymbolReferences referen
: "(";
}

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
{
Expand Down Expand Up @@ -87,19 +88,19 @@ static bool TryEmit(TypeMetadata typeMetadata, CodeBuilder builder, SymbolRefere

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;
}
Expand Down Expand Up @@ -269,20 +270,26 @@ static bool ValidateMembers(TypeMetadata typeMetadata, Compilation compilation,
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)
{
Expand All @@ -300,7 +307,35 @@ static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builde
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(";");
Expand All @@ -313,29 +348,31 @@ static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builde
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))
{
Expand Down Expand Up @@ -367,19 +404,42 @@ static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder bui
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."");");
}
}

Expand Down Expand Up @@ -471,7 +531,8 @@ static void EmitMethodFunction(string functionName, string chunkName, TypeMetada
}
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
Expand Down
4 changes: 3 additions & 1 deletion src/Lua.SourceGenerator/LuaObjectGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
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::", "")
Expand All @@ -52,6 +53,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
sourceProductionContext.AddSource($"{fullType}.LuaObject.g.cs", builder.ToString());
}

tempCollections.Clear();
builder.Clear();
}
});
Expand Down
4 changes: 3 additions & 1 deletion src/Lua.SourceGenerator/PropertyMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -27,7 +28,8 @@ public PropertyMetadata(ISymbol symbol, SymbolReferences references)
{
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
{
Expand Down
12 changes: 12 additions & 0 deletions src/Lua.SourceGenerator/TempCollections.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
5 changes: 0 additions & 5 deletions src/Lua.SourceGenerator/TypeMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ public TypeMetadata(TypeDeclarationSyntax syntax, INamedTypeSymbol symbol, Symbo

if (x is IPropertySymbol p)
{
if (p.GetMethod == null || p.SetMethod == null)
{
return false;
}

if (p.IsIndexer)
{
return false;
Expand Down
54 changes: 27 additions & 27 deletions tests/Lua.Tests/LuaObjectTests.cs
Original file line number Diff line number Diff line change
@@ -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()
Expand Down Expand Up @@ -201,15 +201,15 @@ public async Task Test_CallMetamethod()
Assert.That(results, Has.Length.EqualTo(1));
Assert.That(results[0], Is.EqualTo(new LuaValue("Called!")));
}

[Test]
public async Task Test_ArithMetamethod()
{
var userData = new LuaTestObj();

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)
Expand Down