From bf4bb1287e189e024d70a118dd63c4543360ed0d Mon Sep 17 00:00:00 2001 From: Sebastien Lebreton Date: Fri, 26 Jun 2026 09:27:43 +0200 Subject: [PATCH] Fix #3821: keep ref-struct conditional as if/return, not ?. / ?? (CS8978) NullPropagationTransform rewrote `c != null ? c.AccessChain : default` to `c?.AccessChain ?? default` whenever the access-chain result was a non-nullable value type. For a by-ref-like type (a ref struct such as Span) that form does not compile: a ref struct cannot be wrapped in Nullable (CS8978). Exclude by-ref-like return types from the null-coalescing rewrite. Assisted-by: Copilot:claude-opus-4.8:GitHub Copilot CLI --- .../TestCases/Correctness/NullPropagation.cs | 34 +++++++++++++++++++ .../IL/Transforms/NullPropagationTransform.cs | 6 ++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullPropagation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullPropagation.cs index 900e4de0b..90a83bd06 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullPropagation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullPropagation.cs @@ -7,6 +7,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness static void Main() { new NullPropagation().TestNotCoalescing(); +#if CS72 + new NullPropagation().TestRefStructNotCoalescing(); +#endif } class MyClass @@ -25,5 +28,36 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness { return c != null ? c.Text : "Hello"; } + +#if CS72 + ref struct ByRefLikeStruct + { + public int Value; + } + + class RefStructHolder + { + public ByRefLikeStruct Get() + { + ByRefLikeStruct result = default(ByRefLikeStruct); + result.Value = 42; + return result; + } + } + + void TestRefStructNotCoalescing() + { + Console.WriteLine("TestRefStructNotCoalescing:"); + Console.WriteLine(RefStructNotCoalescing(null).Value); + Console.WriteLine(RefStructNotCoalescing(new RefStructHolder()).Value); + } + + // A by-ref-like return type cannot be turned into a ?. / ?? null-propagation: a ref struct + // cannot be wrapped in Nullable, so that form does not compile. + ByRefLikeStruct RefStructNotCoalescing(RefStructHolder holder) + { + return holder != null ? holder.Get() : default(ByRefLikeStruct); + } +#endif } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 90b73174d..7ca131fe3 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs @@ -140,12 +140,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms IntroduceUnwrap(testedVar, varLoad, mode); return new NullableRewrap(nonNullInst); } - else if (!removedRewrapOrNullableCtor && NullableType.IsNonNullableValueType(returnType)) + else if (!removedRewrapOrNullableCtor && NullableType.IsNonNullableValueType(returnType) + && !returnType.IsByRefLike) { context.Step($"Null propagation (mode={mode}, output=null coalescing)", nonNullInst); // testedVar != null ? testedVar.AccessChain : nullInst // => testedVar?.AccessChain ?? nullInst - // (only valid if AccessChain returns a non-nullable value) + // (only valid if AccessChain returns a non-nullable value; a by-ref-like type such as + // Span is excluded because it cannot be wrapped in Nullable for the ?. / ?? form) IntroduceUnwrap(testedVar, varLoad, mode); return new NullCoalescingInstruction( NullCoalescingKind.NullableWithValueFallback,