From 6932586827a4a2dcaf8ed760138f5897685f5764 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 24 Jul 2026 19:15:02 +0200 Subject: [PATCH] Document two CSharpConversions bugs found by differential fuzzing csc Dumping the implicit/explicit classification of all pairs from a 120-type universe and diffing it against what csc actually compiles surfaced two divergences (16 affected pairs), both proven by a compiling-and-running snippet and captured here as ignored known-bug tests: 1. Nullable conversions derived from tuple conversions are missing: csc accepts "(long, object)? a = t;" for t of type (int, string) as well as the lifted and explicit forms, CSharpConversions returns None. The ECMA spec's 10.6.1 does not list tuple conversions as liftable, so this is a case of Roslyn exceeding the spec. 2. An explicit user-defined conversion to a nullable target is rejected when the operator result additionally needs an explicit numeric conversion: csc accepts "(int?)new ImplicitToLong()" (operator to long, then explicit long -> int?), CSharpConversions returns None. The same sweep confirmed the SByte..Decimal TypeCode range in ImplicitEnumerationConversion is correct: csc accepts zero constants of any numeric type (0.0, 0f, 0m) for enum conversion and rejects '\0', matching the implementation exactly. Assisted-by: Claude:claude-fable-5:Claude Code --- .../Semantics/ConversionTests.cs | 12 ++++++++++ .../Semantics/ExplicitConversionTest.cs | 23 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs index e4c315c45..7a9b9f51f 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs @@ -1592,6 +1592,18 @@ namespace ICSharpCode.Decompiler.Tests.Semantics Is.EqualTo(C.None)); } + [Test, Ignore("Known bug: CSharpConversions does not support nullable conversions derived from tuple conversions, but csc accepts them")] + public void LiftedTupleConversions() + { + // csc accepts the nullable conversions derived from tuple conversions: + // (int, string) t = (1, "one"); + // (long, object)? a = t; + // (int, string)? tn = t; + // (long, object)? b = tn; + Assert.That(ImplicitConversion(typeof((int, string)), typeof((long, object)?)).IsValid, "(int, string) -> (long, object)?"); + Assert.That(ImplicitConversion(typeof((int, string)?), typeof((long, object)?)).IsValid, "(int, string)? -> (long, object)?"); + } + [Test] public void UserDefinedImplicitConversion_OperatorDeclaredInBaseClassOfSource() { diff --git a/ICSharpCode.Decompiler.Tests/Semantics/ExplicitConversionTest.cs b/ICSharpCode.Decompiler.Tests/Semantics/ExplicitConversionTest.cs index ba096e0ff..32808dc46 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/ExplicitConversionTest.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/ExplicitConversionTest.cs @@ -731,6 +731,29 @@ namespace ICSharpCode.Decompiler.Tests.Semantics Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(StructImplementingIEnumerableOfString)), Is.EqualTo(C.None)); } + [Test, Ignore("Known bug: CSharpConversions does not support nullable conversions derived from tuple conversions, but csc accepts them")] + public void ExplicitLiftedTupleConversions() + { + // csc accepts the explicit nullable conversions derived from tuple conversions: + // (int, string)? tn = ...; + // (long, object) c = ((long, object))tn; + // (int, string)? d = ((int, string)?)lo; with lo of type (long, object)? + Assert.That(ExplicitConversion(typeof((int, string)?), typeof((long, object))).IsValid, "(int, string)? -> (long, object)"); + Assert.That(ExplicitConversion(typeof((long, object)?), typeof((int, string)?)).IsValid, "(long, object)? -> (int, string)?"); + } + + [Test, Ignore("Known bug: CSharpConversions rejects explicit user-defined conversions to a nullable target when the operator result needs an explicit numeric conversion, but csc accepts them")] + public void UserDefinedExplicitConversion_ExplicitNumericConversionToNullableTarget() + { + // csc accepts: + // int? d = (int?)new ImplicitToLong(); -- UD op to long, then explicit long -> int? + // int? e = (int?)new ExplicitToLongOrUInt(); -- UD ops to long/uint, then explicit -> int? + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ImplicitToLong), typeof(int?)); + Assert.That(c.IsValid, "(int?)ImplicitToLong"); + var c2 = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ExplicitToLongOrUInt), typeof(int?)); + Assert.That(c2.IsValid, "(int?)ExplicitToLongOrUInt"); + } + [Test] public void ExplicitTypeParameterConversionFromEffectiveBaseClass() {