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
8 changes: 4 additions & 4 deletions src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,8 @@ typeSymbol is not INamedTypeSymbol namedTypeSymbol
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.InvalidReturnType,
typeSymbol.Locations.FirstOrDefault(),
typeSymbol.Name
method.Symbol.Locations.FirstOrDefault(),
typeSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
)
);

Expand Down Expand Up @@ -493,8 +493,8 @@ typeSymbol is not INamedTypeSymbol namedTypeSymbol
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.InvalidParameterType,
typeSymbol.Locations.FirstOrDefault(),
typeSymbol.Name
parameterSymbol.Locations.FirstOrDefault(),
typeSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)
)
);

Expand Down
9 changes: 4 additions & 5 deletions src/Lua.SourceGenerator/MethodMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ public MethodMetadata(IMethodSymbol symbol, SymbolReferences references)
IsStatic = symbol.IsStatic;

var returnType = symbol.ReturnType;
// Arrays/pointers have no containing namespace; type parameters are not awaitable types.
var fullName =
(
returnType.ContainingNamespace.IsGlobalNamespace
? ""
: returnType.ContainingNamespace + "."
) + returnType.Name;
returnType is INamedTypeSymbol namedType && namedType.ContainingNamespace is { } ns
? (ns.IsGlobalNamespace ? "" : ns + ".") + namedType.Name
: null;
IsAsync =
fullName
is "System.Threading.Tasks.Task"
Expand Down
157 changes: 154 additions & 3 deletions tests/Lua.SourceGenerator.Tests/LuaObjectGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,150 @@ namespace Lua.SourceGenerator.Tests;

public class LuaObjectGeneratorTests
{
[TestCase("LuaValue[]", "System.Array.Empty<LuaValue>()", "Lua.LuaValue[]")]
[TestCase("LuaValue[,]", "new LuaValue[0, 0]", "Lua.LuaValue[*,*]")]
[TestCase("int[]", "System.Array.Empty<int>()", "int[]")]
[TestCase("string[]?", "null", "string[]?")]
[TestCase(
"Task<LuaValue[]>",
"Task.FromResult(System.Array.Empty<LuaValue>())",
"Lua.LuaValue[]"
)]
[TestCase(
"ValueTask<LuaValue[]>",
"new ValueTask<LuaValue[]>(System.Array.Empty<LuaValue>())",
"Lua.LuaValue[]"
)]
[TestCase("int*", "null", "int*")]
[TestCase("delegate*<int>", "null", "delegate*<int>")]
public void UnsupportedReturnType_DoesNotStopOtherLuaObjects(
string returnType,
string returnExpression,
string diagnosticType
)
{
var usingTasks = returnType.Contains("Task") ? "using System.Threading.Tasks;" : "";
var result = CompileGeneratedSource(
$$"""
#nullable enable
using Lua;
{{usingTasks}}
[LuaObject]
internal unsafe partial class UnsupportedObject
{
[LuaMember] public {{returnType}} Read() => {{returnExpression}};
}
[LuaObject]
internal partial class HealthyObject
{
[LuaMember] public int Value => 1;
}
internal static class Consumer
{
public static LuaValue Convert() => new HealthyObject();
}
"""
);

AssertUnsupportedMemberDiagnostic(
result.Diagnostics,
result.UserSource,
"LUACS005",
"Read",
diagnosticType
);
Assert.That(
result.GeneratedTrees.Select(x => Path.GetFileName(x.FilePath)),
Is.EquivalentTo(new[] { "HealthyObject.LuaObject.g.cs" })
);
}

[TestCase("LuaValue[] values", "Lua.LuaValue[]")]
[TestCase("ref LuaValue[] values", "Lua.LuaValue[]")]
[TestCase("out LuaValue[] values", "Lua.LuaValue[]")]
public void UnsupportedParameterType_HasALocatedDiagnostic(
string parameter,
string diagnosticType
)
{
var result = CompileGeneratedSource(
$$"""
using Lua;
[LuaObject]
internal partial class UnsupportedObject
{
[LuaMember] public void Read({{parameter}}) => throw new System.NotSupportedException();
}
[LuaObject]
internal partial class HealthyObject
{
[LuaMember] public int Value => 1;
}
internal static class Consumer
{
public static LuaValue Convert() => new HealthyObject();
}
"""
);

AssertUnsupportedMemberDiagnostic(
result.Diagnostics,
result.UserSource,
"LUACS006",
"values",
diagnosticType
);
}

static void AssertUnsupportedMemberDiagnostic(
ImmutableArray<Diagnostic> diagnostics,
SyntaxTree userSource,
string diagnosticId,
string memberName,
string typeName
)
{
Assert.That(diagnostics, Has.Length.EqualTo(1), string.Join("\n", diagnostics));
var diagnostic = diagnostics[0];
Assert.That(diagnostic.Id, Is.EqualTo(diagnosticId));
Assert.That(diagnostic.Severity, Is.EqualTo(DiagnosticSeverity.Error));
Assert.That(diagnostic.GetMessage(), Does.Contain($"'{typeName}'"));
Assert.That(diagnostic.Location.SourceTree, Is.SameAs(userSource));
Assert.That(
userSource.GetText().ToString(diagnostic.Location.SourceSpan),
Is.EqualTo(memberName)
);
}

[TestCase("public int Read(ref int value, out int extra) { extra = ++value; return value; }")]
[TestCase("public static void Read(out int extra, ref int value) { extra = ++value; }")]
[TestCase("public Task<int> Read() => Task.FromResult(1);")]
[TestCase("public ValueTask<int> Read() => new ValueTask<int>(1);")]
[TestCase("public Task Read() => Task.CompletedTask;")]
[TestCase("public ValueTask Read() => default;")]
public void SupportedMethods_StillGenerateWithoutDiagnostics(string method)
{
var usingTasks = method.Contains("Task") ? "using System.Threading.Tasks;" : "";
var result = CompileGeneratedSource(
$$"""
using Lua;
{{usingTasks}}
[LuaObject]
internal partial class HealthyObject
{
[LuaMember] {{method}}
}
internal static class Consumer
{
public static LuaValue Convert() => new HealthyObject();
}
"""
);

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

[TestCase(
"[LuaMetamethod(LuaObjectMetamethod.Call)] public static BaseObject New() => new();",
"[LuaMetamethod(LuaObjectMetamethod.Call)] public new static DerivedObject New() => new();"
Expand Down Expand Up @@ -252,7 +396,8 @@ public void Method() { }
static (
bool Success,
ImmutableArray<Diagnostic> Diagnostics,
SyntaxTree UserSource
SyntaxTree UserSource,
ImmutableArray<SyntaxTree> GeneratedTrees
) CompileGeneratedSource(string source, bool warningsAsErrors = true)
{
var parseOptions = CSharpParseOptions
Expand All @@ -269,7 +414,8 @@ SyntaxTree UserSource
[syntaxTree],
references,
new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary
OutputKind.DynamicallyLinkedLibrary,
allowUnsafe: true
).WithGeneralDiagnosticOption(
warningsAsErrors ? ReportDiagnostic.Error : ReportDiagnostic.Default
)
Expand All @@ -287,7 +433,12 @@ out var diagnostics
using var assembly = new MemoryStream();
using var documentation = new MemoryStream();
var result = output.Emit(assembly, xmlDocumentationStream: documentation);
return (result.Success, diagnostics.AddRange(result.Diagnostics), syntaxTree);
return (
result.Success,
diagnostics.AddRange(result.Diagnostics),
syntaxTree,
driver.GetRunResult().GeneratedTrees
);
}

[Test]
Expand Down
Loading