Skip to content

Commit

Permalink
prepare source-generator diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
neuecc committed Sep 19, 2024
1 parent 36508ee commit 9cff3eb
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 26 deletions.
4 changes: 1 addition & 3 deletions MemoryPack.sln
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MemoryPack.AspNetCoreMvcFor
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SandboxNet6", "sandbox\SandboxNet6\SandboxNet6.csproj", "{87C0CEAA-E511-46AA-93AB-AF742A1F8EE2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{C56A9A52-EE3A-44A5-A8EA-AE36C79FFB6C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClassLibrary", "sandbox\ClassLibrary\ClassLibrary.csproj", "{0ADCE3AF-C900-4FCB-938B-654211EDD6BE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeAot", "sandbox\NativeAot\NativeAot.csproj", "{6E18AECF-34B2-48F9-9694-54150FB156EB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net6VsNet7", "sandbox\Net6VsNet7\Net6VsNet7.csproj", "{5612B811-586A-4EB3-9AE7-60CAD4969A1B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MemoryPack.UnityShims", "src\MemoryPack.UnityShims\MemoryPack.UnityShims.csproj", "{9339C66C-25E5-4130-A213-9BA1804AD562}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MemoryPack.UnityShims", "src\MemoryPack.UnityShims\MemoryPack.UnityShims.csproj", "{9339C66C-25E5-4130-A213-9BA1804AD562}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { MemoryPackWriter } from "./MemoryPackWriter.js";
import { MemoryPackReader } from "./MemoryPackReader.js";

export class FooBarBazDayonDattayon {
myProperty: number;

constructor() {
this.myProperty = 0;

}

static serialize(value: FooBarBazDayonDattayon | null): Uint8Array {
const writer = MemoryPackWriter.getSharedInstance();
this.serializeCore(writer, value);
return writer.toArray();
}

static serializeCore(writer: MemoryPackWriter, value: FooBarBazDayonDattayon | null): void {
if (value == null) {
writer.writeNullObjectHeader();
return;
}

writer.writeObjectHeader(1);
writer.writeInt32(value.myProperty);

}

static serializeArray(value: (FooBarBazDayonDattayon | null)[] | null): Uint8Array {
const writer = MemoryPackWriter.getSharedInstance();
this.serializeArrayCore(writer, value);
return writer.toArray();
}

static serializeArrayCore(writer: MemoryPackWriter, value: (FooBarBazDayonDattayon | null)[] | null): void {
writer.writeArray(value, (writer, x) => FooBarBazDayonDattayon.serializeCore(writer, x));
}

static deserialize(buffer: ArrayBuffer): FooBarBazDayonDattayon | null {
return this.deserializeCore(new MemoryPackReader(buffer));
}

static deserializeCore(reader: MemoryPackReader): FooBarBazDayonDattayon | null {
const [ok, count] = reader.tryReadObjectHeader();
if (!ok) {
return null;
}

const value = new FooBarBazDayonDattayon();
if (count == 1) {
value.myProperty = reader.readInt32();

}
else if (count > 1) {
throw new Error("Current object's property count is larger than type schema, can't deserialize about versioning.");
}
else {
if (count == 0) return value;
value.myProperty = reader.readInt32(); if (count == 1) return value;

}
return value;
}

static deserializeArray(buffer: ArrayBuffer): (FooBarBazDayonDattayon | null)[] | null {
return this.deserializeArrayCore(new MemoryPackReader(buffer));
}

static deserializeArrayCore(reader: MemoryPackReader): (FooBarBazDayonDattayon | null)[] | null {
return reader.readArray(reader => FooBarBazDayonDattayon.deserializeCore(reader));
}
}
3 changes: 2 additions & 1 deletion src/MemoryPack.Generator/MemoryPack.Generator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
<None Include="../../Icon.png" Pack="true" PackagePath="/" />

<!-- https://learn.microsoft.com/en-us/visualstudio/extensibility/roslyn-version-support?view=vs-2022 -->
<!-- require to support SyntaxValueProvider.ForAttributeWithMetadataName(Roslyn 4.3.1, VS2022 17.3 -->
<!-- require to support SyntaxValueProvider.ForAttributeWithMetadataName(Roslyn 4.3.0, VS2022 17.3) -->
<!-- Unity 2022.3.12f1 or newer supports 4.3.0 -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
<PrivateAssets>all</PrivateAssets>
Expand Down
4 changes: 2 additions & 2 deletions tests/MemoryPack.Tests/GeneratorDiagnosticsTest.TypeScript.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public partial class GeneratorDiagnosticsTest
{
void Compile2(int id, string code, bool allowMultipleError = false)
{
var diagnostics = CSharpGeneratorRunner.RunGenerator(code, options: new TypeScriptOptionProvider());
var (_, diagnostics) = CSharpGeneratorRunner.RunGenerator(code, options: new TypeScriptOptionProvider());
if (!allowMultipleError)
{
diagnostics.Length.Should().Be(1);
Expand All @@ -41,7 +41,7 @@ string CompileAndRead(string code, string fileName, bool enableNullableTypes = t
optionProvider["build_property.MemoryPackGenerator_TypeScriptOutputDirectory"] = outputDir;
optionProvider["build_property.MemoryPackGenerator_TypeScriptEnableNullableTypes"] = enableNullableTypes ? "true" : "false";

CSharpGeneratorRunner.RunGenerator(code,options: optionProvider);
CSharpGeneratorRunner.RunGenerator(code, options: optionProvider);

var outputFilePath = Path.Combine(outputDir, fileName);

Expand Down
6 changes: 3 additions & 3 deletions tests/MemoryPack.Tests/GeneratorDiagnosticsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public partial class GeneratorDiagnosticsTest
{
void Compile(int id, string code, bool allowMultipleError = false)
{
var diagnostics = CSharpGeneratorRunner.RunGenerator(code);
var (_, diagnostics) = CSharpGeneratorRunner.RunGenerator(code);
if (!allowMultipleError)
{
diagnostics.Length.Should().Be(1);
Expand Down Expand Up @@ -528,11 +528,11 @@ public partial struct Hoge
""";

{
var diagnostics = CSharpGeneratorRunner.RunGenerator(code, preprocessorSymbols: new[] { "NET7_0_OR_GREATER" });
var (_, diagnostics) = CSharpGeneratorRunner.RunGenerator(code, preprocessorSymbols: new[] { "NET7_0_OR_GREATER" });
diagnostics.Length.Should().Be(0);
}
{
var diagnostics = CSharpGeneratorRunner.RunGenerator(code, preprocessorSymbols: new string[] { });
var (_, diagnostics) = CSharpGeneratorRunner.RunGenerator(code, preprocessorSymbols: new string[] { });
diagnostics.Length.Should().Be(1);
diagnostics[0].Id.Should().Be("MEMPACK034");
}
Expand Down
160 changes: 143 additions & 17 deletions tests/MemoryPack.Tests/Utils/CSharpGeneratorRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using System.IO;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;

Expand All @@ -15,29 +18,34 @@ public static class CSharpGeneratorRunner
[ModuleInitializer]
public static void InitializeCompilation()
{
// running .NET Core system assemblies dir path
var baseAssemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
var systemAssemblies = Directory.GetFiles(baseAssemblyPath)
.Where(x =>
{
var fileName = Path.GetFileName(x);
if (fileName.EndsWith("Native.dll")) return false;
return fileName.StartsWith("System") || (fileName is "mscorlib.dll" or "netstandard.dll");
});
var globalUsings = """
global using System;
global using System.Linq;
global using System.Collections;
global using System.Collections.Generic;
global using System.Threading;
global using System.Threading.Tasks;
global using System.ComponentModel.DataAnnotations;
global using MemoryPack;
""";

var systemAssemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => !x.IsDynamic && !string.IsNullOrWhiteSpace(x.Location));

var references = systemAssemblies
.Append(typeof(MemoryPackableAttribute).Assembly.Location) // System Assemblies + MemoryPack.Core.dll
.Select(x => MetadataReference.CreateFromFile(x))
.Append(typeof(MemoryPackableAttribute).Assembly) // System Assemblies + MemoryPack.Core.dll
.Select(x => MetadataReference.CreateFromFile(x.Location))
.ToArray();

var compilation = CSharpCompilation.Create("generatortest",
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
syntaxTrees: [CSharpSyntaxTree.ParseText(globalUsings, path: "GlobalUsings.cs")],
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true));

baseCompilation = compilation;
}

public static Diagnostic[] RunGenerator(string source, string[]? preprocessorSymbols = null, AnalyzerConfigOptionsProvider? options = null)
public static (Compilation, ImmutableArray<Diagnostic>) RunGenerator(string source, string[]? preprocessorSymbols = null, AnalyzerConfigOptionsProvider? options = null)
{
if (preprocessorSymbols == null)
{
Expand All @@ -55,8 +63,126 @@ public static Diagnostic[] RunGenerator(string source, string[]? preprocessorSym

driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out var diagnostics);

// combine diagnostics as result.(ignore warning)
var compilationDiagnostics = newCompilation.GetDiagnostics();
return diagnostics.Concat(compilationDiagnostics).Where(x => x.Severity == DiagnosticSeverity.Error).ToArray();
return (newCompilation, diagnostics);
}

public static (string Key, string Reasons)[][] GetIncrementalGeneratorTrackedStepsReasons(string keyPrefixFilter, params string[] sources)
{
var parseOptions = new CSharpParseOptions(LanguageVersion.CSharp11);
var driver = CSharpGeneratorDriver.Create(
[new MemoryPackGenerator().AsSourceGenerator()],
driverOptions: new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: true))
.WithUpdatedParseOptions(parseOptions);

var generatorResults = sources
.Select(source =>
{
var compilation = baseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(source, parseOptions));
driver = driver.RunGenerators(compilation);
return driver.GetRunResult().Results[0];
})
.ToArray();

var reasons = generatorResults
.Select(x => x.TrackedSteps
.Where(x => x.Key.StartsWith(keyPrefixFilter) || x.Key == "SourceOutput")
.Select(x =>
{
if (x.Key == "SourceOutput")
{
var values = x.Value.Where(x => x.Inputs[0].Source.Name?.StartsWith(keyPrefixFilter) ?? false);
return (
x.Key,
Reasons: string.Join(", ", values.SelectMany(x => x.Outputs).Select(x => x.Reason).ToArray())
);
}
else
{
return (
Key: x.Key.Substring(keyPrefixFilter.Length),
Reasons: string.Join(", ", x.Value.SelectMany(x => x.Outputs).Select(x => x.Reason).ToArray())
);
}
})
.OrderBy(x => x.Key)
.ToArray())
.ToArray();

return reasons;
}
}

public class VerifyHelper(ITestOutputHelper output, string idPrefix)
{
// Diagnostics Verify

public void Ok([StringSyntax("C#-test")] string code, [CallerArgumentExpression("code")] string? codeExpr = null)
{
output.WriteLine(codeExpr);

var (compilation, diagnostics) = CSharpGeneratorRunner.RunGenerator(code);
foreach (var item in diagnostics)
{
output.WriteLine(item.ToString());
}
OutputGeneratedCode(compilation);

diagnostics.Length.Should().Be(0);
}

public void Verify(int id, [StringSyntax("C#-test")] string code, string diagnosticsCodeSpan, [CallerArgumentExpression("code")] string? codeExpr = null)
{
output.WriteLine(codeExpr);

var (compilation, diagnostics) = CSharpGeneratorRunner.RunGenerator(code);
foreach (var item in diagnostics)
{
output.WriteLine(item.ToString());
}
OutputGeneratedCode(compilation);

diagnostics.Length.Should().Be(1);
diagnostics[0].Id.Should().Be(idPrefix + id.ToString("000"));

var text = GetLocationText(diagnostics[0], compilation.SyntaxTrees);
text.Should().Be(diagnosticsCodeSpan);
}

public (string, string)[] Verify([StringSyntax("C#-test")] string code, [CallerArgumentExpression("code")] string? codeExpr = null)
{
output.WriteLine(codeExpr);

var (compilation, diagnostics) = CSharpGeneratorRunner.RunGenerator(code);
OutputGeneratedCode(compilation);
return diagnostics.Select(x => (x.Id, GetLocationText(x, compilation.SyntaxTrees))).ToArray();
}

string GetLocationText(Diagnostic diagnostic, IEnumerable<SyntaxTree> syntaxTrees)
{
var location = diagnostic.Location;

var textSpan = location.SourceSpan;
var sourceTree = location.SourceTree;
if (sourceTree == null)
{
var lineSpan = location.GetLineSpan();
if (lineSpan.Path == null) return "";

sourceTree = syntaxTrees.FirstOrDefault(x => x.FilePath == lineSpan.Path);
if (sourceTree == null) return "";
}

var text = sourceTree.GetText().GetSubText(textSpan).ToString();
return text;
}

void OutputGeneratedCode(Compilation compilation)
{
foreach (var syntaxTree in compilation.SyntaxTrees)
{
// only shows ConsoleApp.Run/Builder generated code
if (!syntaxTree.FilePath.Contains("g.cs")) continue;
output.WriteLine(syntaxTree.ToString());
}
}
}

0 comments on commit 9cff3eb

Please sign in to comment.