From b403b7bb3de02bfcf37c6269b66bdd62f9f0f58e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 9 Oct 2025 08:24:49 +0200 Subject: [PATCH] Fix #3542: Invalid explicit cast for implicit conversion to generic struct with interface type constraint --- .../Semantics/ConversionTests.cs | 11 +++++++++++ .../TypeSystem/TypeSystemTestCase.cs | 13 +++++++++++++ .../CSharp/Resolver/CSharpConversions.cs | 19 ++++++++++++++++--- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs index a017f02b7..80ca2165b 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs @@ -529,6 +529,17 @@ namespace ICSharpCode.Decompiler.Tests.Semantics Assert.That(c.Method.FullName, Is.EqualTo("System.DateTimeOffset.op_Implicit")); Assert.That(ImplicitConversion(typeof(DateTimeOffset), typeof(DateTime)), Is.EqualTo(C.None)); + + ITypeDefinition classImplementingIDisposable = compilation.FindType(typeof(ClassImplementingIDisposable)).GetDefinition(); + ITypeDefinition genericStructWithIDisposableConstraintAndImplicitConversion = compilation.FindType(typeof(GenericStructWithIDisposableConstraintAndImplicitConversion<>)).GetDefinition(); + IType genericStructIDisposableInstance = new ParameterizedType(genericStructWithIDisposableConstraintAndImplicitConversion, ImmutableArray.Create(compilation.FindType(typeof(IDisposable)))); + + // C => S + Conversion c2 = conversions.ImplicitConversion(classImplementingIDisposable, genericStructIDisposableInstance); + Assert.That(c2.IsImplicit && c2.IsUserDefined); + Assert.That(c2.Method.FullName, Is.EqualTo("ICSharpCode.Decompiler.Tests.TypeSystem.GenericStructWithIDisposableConstraintAndImplicitConversion.op_Implicit")); + + Assert.That(conversions.ImplicitConversion(genericStructIDisposableInstance, classImplementingIDisposable), Is.EqualTo(C.None)); } [Test] diff --git a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs index 1e273e21e..3c32788a9 100644 --- a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs +++ b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs @@ -559,6 +559,19 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem } } + public struct GenericStructWithIDisposableConstraintAndImplicitConversion where T : IDisposable + { + public static implicit operator GenericStructWithIDisposableConstraintAndImplicitConversion(T s) + { + return default(GenericStructWithIDisposableConstraintAndImplicitConversion); + } + } + + public class ClassImplementingIDisposable : IDisposable + { + public void Dispose() { } + } + public class ClassWithAttributeOnTypeParameter<[Double(2)] T> { } [Guid("790C6E0B-9194-4cc9-9426-A48A63185696"), InterfaceType(ComInterfaceType.InterfaceIsDual)] diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 31890f398..056ef2d54 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -955,13 +955,12 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /// bool IsEncompassedBy(IType a, IType b) { - return a.Kind != TypeKind.Interface && b.Kind != TypeKind.Interface && StandardImplicitConversion(a, b).IsValid; + return StandardImplicitConversion(a, b).IsValid; } bool IsEncompassingOrEncompassedBy(IType a, IType b) { - return a.Kind != TypeKind.Interface && b.Kind != TypeKind.Interface - && (StandardImplicitConversion(a, b).IsValid || StandardImplicitConversion(b, a).IsValid); + return (StandardImplicitConversion(a, b).IsValid || StandardImplicitConversion(b, a).IsValid); } IType FindMostEncompassedType(IEnumerable candidates) @@ -1027,6 +1026,13 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver Conversion UserDefinedImplicitConversion(ResolveResult fromResult, IType fromType, IType toType) { // C# 4.0 spec §6.4.4 User-defined implicit conversions + + // user-defined conversions are not supported with interfaces + if (fromType.Kind == TypeKind.Interface || toType.Kind == TypeKind.Interface) + { + return Conversion.None; + } + var operators = GetApplicableConversionOperators(fromResult, fromType, toType, false); if (operators.Count > 0) @@ -1069,6 +1075,13 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver Conversion UserDefinedExplicitConversion(ResolveResult fromResult, IType fromType, IType toType) { // C# 4.0 spec §6.4.5 User-defined explicit conversions + + // user-defined conversions are not supported with interfaces + if (fromType.Kind == TypeKind.Interface || toType.Kind == TypeKind.Interface) + { + return Conversion.None; + } + var operators = GetApplicableConversionOperators(fromResult, fromType, toType, true); if (operators.Count > 0) {