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
4 changes: 2 additions & 2 deletions src/Lua.SourceGenerator/MethodMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class MethodMetadata
public bool IsStatic { get; }
public bool IsAsync { get; }
public bool HasReturnValue { get; }
public bool HasMemberAttribute { get; }
public bool HasMetamethodAttribute { get; }
public bool HasMemberAttribute { get; set; }
public bool HasMetamethodAttribute { get; set; }
public string LuaMemberName { get; }
public LuaObjectMetamethod Metamethod { get; }

Expand Down
106 changes: 74 additions & 32 deletions src/Lua.SourceGenerator/TypeMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,50 +25,92 @@ SymbolReferences references
TypeName = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
FullTypeName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

Properties = Symbol
.GetAllMembers(false)
.Where(x => x is (IFieldSymbol or IPropertySymbol) and { IsImplicitlyDeclared: false })
.Where(x =>
var properties = new List<PropertyMetadata>();
var methods = new List<MethodMetadata>();
var memberOwners = new Dictionary<string, INamedTypeSymbol>();
var metamethodOwners = new Dictionary<LuaObjectMetamethod, INamedTypeSymbol>();

foreach (var member in Symbol.GetAllMembers())
{
if (
member.IsImplicitlyDeclared
|| member.ContainsAttribute(references.LuaIgnoreMemberAttribute)
)
{
continue;
}

if (member is IFieldSymbol or IPropertySymbol)
{
if (!x.ContainsAttribute(references.LuaMemberAttribute))
if (
!member.ContainsAttribute(references.LuaMemberAttribute)
|| member is IPropertySymbol { IsIndexer: true }
)
{
return false;
continue;
}

if (x.ContainsAttribute(references.LuaIgnoreMemberAttribute))
var property = new PropertyMetadata(member, references);
if (SelectBinding(memberOwners, property.LuaMemberName, member))
{
return false;
properties.Add(property);
}

if (x is IPropertySymbol p)
}
else if (member is IMethodSymbol methodSymbol)
{
if (
!member.ContainsAttribute(references.LuaMemberAttribute)
&& !member.ContainsAttribute(references.LuaMetamethodAttribute)
)
{
if (p.IsIndexer)
{
return false;
}
continue;
}

return true;
})
.Select(x => new PropertyMetadata(x, references))
.ToArray();

Methods = Symbol
.GetAllMembers(false)
.Where(x => x is IMethodSymbol and { IsImplicitlyDeclared: false })
.Select(x => (IMethodSymbol)x)
.Where(x =>
{
if (x.ContainsAttribute(references.LuaIgnoreMemberAttribute))
var method = new MethodMetadata(methodSymbol, references);
// A method's member and metamethod bindings can be replaced independently.
if (method.HasMemberAttribute)
{
method.HasMemberAttribute = SelectBinding(
memberOwners,
method.LuaMemberName,
member
);
}
if (method.HasMetamethodAttribute)
{
method.HasMetamethodAttribute = SelectBinding(
metamethodOwners,
method.Metamethod,
member
);
}
if (method.HasMemberAttribute || method.HasMetamethodAttribute)
{
return false;
methods.Add(method);
}
}
}

Properties = properties.ToArray();
Methods = methods.ToArray();
}

static bool SelectBinding<TKey>(
Dictionary<TKey, INamedTypeSymbol> owners,
TKey key,
ISymbol member
)
where TKey : notnull
{
if (!owners.TryGetValue(key, out var owner))
{
owners.Add(key, member.ContainingType);
return true;
}

return x.ContainsAttribute(references.LuaMemberAttribute)
|| x.ContainsAttribute(references.LuaMetamethodAttribute);
})
.Select(x => new MethodMetadata(x, references))
.ToArray();
// Derived bindings replace inherited ones. Keep duplicates within one type
// so that invalid declarations still produce diagnostics.
return SymbolEqualityComparer.Default.Equals(owner, member.ContainingType);
}

public bool IsPartial()
Expand Down
25 changes: 5 additions & 20 deletions src/Lua.SourceGenerator/Utilities/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,14 @@ public static bool ContainsAttribute(this ISymbol symbol, INamedTypeSymbol attri
);
}

public static IEnumerable<ISymbol> GetAllMembers(
this INamedTypeSymbol symbol,
bool withoutOverride = true
)
public static IEnumerable<ISymbol> GetAllMembers(this INamedTypeSymbol symbol)
{
// Iterate Parent -> Derived
if (symbol.BaseType != null)
// Include overrides at every level, with the most derived declarations first.
for (var current = symbol; current != null; current = current.BaseType)
{
foreach (var item in GetAllMembers(symbol.BaseType))
foreach (var member in current.GetMembers())
{
// override item already iterated in parent type
if (!withoutOverride || !item.IsOverride)
{
yield return item;
}
}
}

foreach (var item in symbol.GetMembers())
{
if (!withoutOverride || !item.IsOverride)
{
yield return item;
yield return member;
}
}
}
Expand Down
97 changes: 97 additions & 0 deletions tests/Lua.SourceGenerator.Tests/LuaObjectGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,103 @@ namespace Lua.SourceGenerator.Tests;

public class LuaObjectGeneratorTests
{
[TestCase(
"[LuaMetamethod(LuaObjectMetamethod.Call)] public static BaseObject New() => new();",
"[LuaMetamethod(LuaObjectMetamethod.Call)] public new static DerivedObject New() => new();"
)]
[TestCase(
"[LuaMember] public virtual int Value { get; set; }",
"[LuaMember] public override int Value { get; set; }"
)]
[TestCase("[LuaMember] public int Value;", "[LuaMember] public new int Value;")]
[TestCase(
"[LuaMember] public virtual int Read() => 1;",
"[LuaMember] public override int Read() => 2;"
)]
[TestCase(
"[LuaMember(\"read\")] public int BaseRead() => 1;",
"[LuaMember(\"read\")] public int DerivedRead() => 2;"
)]
[TestCase(
"[LuaMember(\"value\")] public int BaseValue { get; set; }",
"[LuaMember(\"value\")] public int DerivedValue;"
)]
[TestCase(
"[LuaMember(\"value\")] public int BaseValue { get; set; }",
"[LuaMember(\"value\")] public int DerivedRead() => 2;"
)]
[TestCase(
"[LuaMember(\"value\")] public int BaseRead() => 1;",
"[LuaMember(\"value\")] public int DerivedValue { get; set; }"
)]
[TestCase(
"[LuaMetamethod(LuaObjectMetamethod.Len)] public int BaseLength() => 1;",
"[LuaMetamethod(LuaObjectMetamethod.Len)] public int DerivedLength() => 2;"
)]
[TestCase("[LuaMember] public virtual int Read() => 1;", "public override int Read() => 2;")]
public void DerivedBindings_ReplaceInheritedBindings(string baseMember, string derivedMember)
{
var result = CompileGeneratedSource(
$$"""
using Lua;
[LuaObject]
internal partial class BaseObject { {{baseMember}} }
[LuaObject]
internal partial class DerivedObject : BaseObject { {{derivedMember}} }
[LuaObject]
internal partial class LeafObject : DerivedObject { }
"""
);

Assert.That(result.Success, Is.True, string.Join("\n", result.Diagnostics));
Assert.That(result.Diagnostics, Is.Empty);
}

[Test]
public void DuplicateMetamethods_InTheSameTypeRemainErrors()
{
var result = CompileGeneratedSource(
"""
using Lua;
[LuaObject]
internal partial class BaseObject
{
[LuaMetamethod(LuaObjectMetamethod.Len)] public int Length() => 1;
}
[LuaObject]
internal partial class DerivedObject : BaseObject
{
[LuaMetamethod(LuaObjectMetamethod.Len)] public int FirstLength() => 2;
[LuaMetamethod(LuaObjectMetamethod.Len)] public int SecondLength() => 3;
}
"""
);

Assert.That(
result.Diagnostics,
Has.Exactly(1).Matches<Diagnostic>(x => x.Id == "LUACS007")
);
}

[Test]
public void DuplicateLuaNames_InTheSameTypeRemainErrors()
{
var result = CompileGeneratedSource(
"""
using Lua;
[LuaObject]
internal partial class DuplicateObject
{
[LuaMember("read")] public int FirstRead() => 1;
[LuaMember("read")] public int SecondRead() => 2;
}
"""
);

Assert.That(result.Success, Is.False);
Assert.That(result.Diagnostics, Has.Some.Matches<Diagnostic>(x => x.Id == "CS0102"));
}

[TestCase("class DocumentedObject", false)]
[TestCase("class DocumentedObject", true)]
[TestCase("class DocumentedObject<T>", false)]
Expand Down
Loading
Loading