diff --git a/Directory.Build.props b/Directory.Build.props index a16683b..0fd6b4f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ JasperFx.Events, JasperFx.Events.SourceGenerator) set $(JasperFxVersion) so they always release together. Bump this one value to release the whole set. --> - 2.1.2 + 2.1.3 13 1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618 Jeremy D. Miller;Jaedyn Tonee diff --git a/src/JasperFx.SourceGenerator.Tests/InputParserGeneratorTests.cs b/src/JasperFx.SourceGenerator.Tests/InputParserGeneratorTests.cs new file mode 100644 index 0000000..5da0eec --- /dev/null +++ b/src/JasperFx.SourceGenerator.Tests/InputParserGeneratorTests.cs @@ -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 flag (e.g. the inherited +// NetCoreInput.ConfigFlag) to IDictionary, 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 ConfigFlag = new(); + } + + public class SampleCommand : JasperFxCommand + { + public override bool Execute(SampleInput input) => true; + } + """; + + private static (string? parser, IEnumerable diagnostics) Run() + { + var tree = CSharpSyntaxTree.ParseText(Source); + var runtimeDir = Path.GetDirectoryName(typeof(object).Assembly.Location)!; + var references = new List + { + 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(); + } +} diff --git a/src/JasperFx.SourceGenerator/InputParserGenerator.cs b/src/JasperFx.SourceGenerator/InputParserGenerator.cs index f77b403..f7fa43b 100644 --- a/src/JasperFx.SourceGenerator/InputParserGenerator.cs +++ b/src/JasperFx.SourceGenerator/InputParserGenerator.cs @@ -451,7 +451,13 @@ private static void GenerateParserClass(SourceProductionContext context, InputMo { var sb = new StringBuilder(); sb.AppendLine("// "); - 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), which are runtime-identical to but nullably-distinct from members declared as + // Dictionary (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;");