From 44b8280cba826d8acad38bfeaab948786533dc20 Mon Sep 17 00:00:00 2001 From: akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Sat, 10 Oct 2026 21:02:18 +0900 Subject: [PATCH] Prevent unsupported return types from aborting LuaObject generation --- .../LuaObjectGenerator.Emit.cs | 8 +- src/Lua.SourceGenerator/MethodMetadata.cs | 9 +- .../LuaObjectGeneratorTests.cs | 157 +++++++++++++++++- 3 files changed, 162 insertions(+), 12 deletions(-) diff --git a/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs b/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs index c703825a..ed649329 100644 --- a/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs +++ b/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs @@ -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) ) ); @@ -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) ) ); diff --git a/src/Lua.SourceGenerator/MethodMetadata.cs b/src/Lua.SourceGenerator/MethodMetadata.cs index 0f3396c9..528c8d41 100644 --- a/src/Lua.SourceGenerator/MethodMetadata.cs +++ b/src/Lua.SourceGenerator/MethodMetadata.cs @@ -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" diff --git a/tests/Lua.SourceGenerator.Tests/LuaObjectGeneratorTests.cs b/tests/Lua.SourceGenerator.Tests/LuaObjectGeneratorTests.cs index a69a4725..9369b793 100644 --- a/tests/Lua.SourceGenerator.Tests/LuaObjectGeneratorTests.cs +++ b/tests/Lua.SourceGenerator.Tests/LuaObjectGeneratorTests.cs @@ -7,6 +7,150 @@ namespace Lua.SourceGenerator.Tests; public class LuaObjectGeneratorTests { + [TestCase("LuaValue[]", "System.Array.Empty()", "Lua.LuaValue[]")] + [TestCase("LuaValue[,]", "new LuaValue[0, 0]", "Lua.LuaValue[*,*]")] + [TestCase("int[]", "System.Array.Empty()", "int[]")] + [TestCase("string[]?", "null", "string[]?")] + [TestCase( + "Task", + "Task.FromResult(System.Array.Empty())", + "Lua.LuaValue[]" + )] + [TestCase( + "ValueTask", + "new ValueTask(System.Array.Empty())", + "Lua.LuaValue[]" + )] + [TestCase("int*", "null", "int*")] + [TestCase("delegate*", "null", "delegate*")] + 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 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 Read() => Task.FromResult(1);")] + [TestCase("public ValueTask Read() => new ValueTask(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();" @@ -252,7 +396,8 @@ public void Method() { } static ( bool Success, ImmutableArray Diagnostics, - SyntaxTree UserSource + SyntaxTree UserSource, + ImmutableArray GeneratedTrees ) CompileGeneratedSource(string source, bool warningsAsErrors = true) { var parseOptions = CSharpParseOptions @@ -269,7 +414,8 @@ SyntaxTree UserSource [syntaxTree], references, new CSharpCompilationOptions( - OutputKind.DynamicallyLinkedLibrary + OutputKind.DynamicallyLinkedLibrary, + allowUnsafe: true ).WithGeneralDiagnosticOption( warningsAsErrors ? ReportDiagnostic.Error : ReportDiagnostic.Default ) @@ -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]