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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
JasperFx.Events, JasperFx.Events.SourceGenerator) set
<Version>$(JasperFxVersion)</Version> so they always release together. Bump this one
value to release the whole set. -->
<JasperFxVersion>2.1.2</JasperFxVersion>
<JasperFxVersion>2.1.3</JasperFxVersion>
<LangVersion>13</LangVersion>
<NoWarn>1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618</NoWarn>
<Authors>Jeremy D. Miller;Jaedyn Tonee</Authors>
Expand Down
71 changes: 71 additions & 0 deletions src/JasperFx.SourceGenerator.Tests/InputParserGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using JasperFx;
using JasperFx.SourceGenerator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Shouldly;

namespace JasperFx.SourceGenerator.Tests;

// GH-378: the generated input parser cast a Dictionary<string, string?> flag (e.g. the inherited
// NetCoreInput.ConfigFlag) to IDictionary<string, string>, which is CS8619 under
// `#nullable enable` + TreatWarningsAsErrors and broke consumer builds (notably net10 — Wolverine).
public class InputParserGeneratorTests
{
private const string Source = """
using System.Collections.Generic;
using JasperFx.CommandLine;

namespace App;

public class SampleInput
{
// Mirrors the inherited NetCoreInput.ConfigFlag that broke the Wolverine build.
public Dictionary<string, string?> ConfigFlag = new();
}

public class SampleCommand : JasperFxCommand<SampleInput>
{
public override bool Execute(SampleInput input) => true;
}
""";

private static (string? parser, IEnumerable<Diagnostic> diagnostics) Run()
{
var tree = CSharpSyntaxTree.ParseText(Source);
var runtimeDir = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(IJasperFxExtension).Assembly.Location),
MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "System.Runtime.dll")),
MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "System.Collections.dll")),
};

// Nullable enabled so the CS8619 nullability mismatch would surface if it regressed.
var compilation = CSharpCompilation.Create("TestAssembly", [tree], references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
nullableContextOptions: NullableContextOptions.Enable));

GeneratorDriver driver = CSharpGeneratorDriver.Create(new InputParserGenerator());
driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var output, out _);

var parser = driver.GetRunResult().GeneratedTrees
.Select(t => t.GetText().ToString())
.FirstOrDefault(t => t.Contains("SampleInputParser"));

return (parser, output.GetDiagnostics());
}

[Fact]
public void dictionary_flag_parser_compiles_clean_under_nullable()
{
var (parser, diagnostics) = Run();

parser.ShouldNotBeNull("The InputParserGenerator should have produced a parser for SampleInput");
parser.ShouldContain("#nullable disable");
diagnostics.Where(d => d.Id == "CS8619").ShouldBeEmpty();
}
}
8 changes: 7 additions & 1 deletion src/JasperFx.SourceGenerator/InputParserGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,13 @@ private static void GenerateParserClass(SourceProductionContext context, InputMo
{
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />");
sb.AppendLine("#nullable enable");
// Disable nullable analysis in the generated parser. It casts input flag members to the
// delegate types that GeneratedDictionaryFlag/GeneratedFlag expect (e.g. IDictionary<string,
// string>), which are runtime-identical to but nullably-distinct from members declared as
// Dictionary<string, string?> (such as the inherited NetCoreInput.ConfigFlag). Under
// `#nullable enable` + TreatWarningsAsErrors that mismatch is CS8619 and breaks the consumer
// build (notably net10). Generated code shouldn't participate in nullable analysis. See GH-378.
sb.AppendLine("#nullable disable");
sb.AppendLine("using System;");
sb.AppendLine("using System.Collections.Generic;");
sb.AppendLine("using JasperFx.CommandLine.Parsing;");
Expand Down
Loading