diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 51d24fc07..51ed72689 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -89,6 +89,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index 6c66f55c3..e7786ac67 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -425,6 +425,12 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary(cscOptions: cscOptions);
}
+ [Test]
+ public void UserDefinedConversions([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
+ {
+ RunForLibrary(cscOptions: cscOptions);
+ }
+
[Test]
public void Discards([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs
new file mode 100644
index 000000000..a91ca7a82
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs
@@ -0,0 +1,134 @@
+// Copyright (c) 2019 Daniel Grunwald
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
+{
+ internal class T01Issue1574
+ {
+ private struct A
+ {
+ private bool val;
+
+ public static implicit operator bool(A a)
+ {
+ return a.val;
+ }
+ }
+
+ private struct C
+ {
+ private int val;
+
+ public static implicit operator C(bool b)
+ {
+ return default(C);
+ }
+ }
+
+ private C ChainedConversion()
+ {
+ return (bool)default(A);
+ }
+
+ public void Call_Overloaded()
+ {
+ Overloaded((bool)default(A));
+ }
+
+ private void Overloaded(A a)
+ {
+ }
+
+ private void Overloaded(bool a)
+ {
+ }
+ }
+
+ internal class T02BothDirectAndChainedConversionPossible
+ {
+ private struct A
+ {
+ private bool val;
+
+ public static implicit operator bool(A a)
+ {
+ return a.val;
+ }
+ }
+
+ private struct C
+ {
+ private int val;
+
+ public static implicit operator C(bool b)
+ {
+ return default(C);
+ }
+
+ public static implicit operator C(A a)
+ {
+ return default(C);
+ }
+
+ public static bool operator ==(C a, C b)
+ {
+ return true;
+ }
+ public static bool operator !=(C a, C b)
+ {
+ return false;
+ }
+ }
+
+ private C DirectConvert(A a)
+ {
+ return a;
+ }
+
+ private C IndirectConvert(A a)
+ {
+ return (bool)a;
+ }
+
+ private C? LiftedDirectConvert(A? a)
+ {
+ return a;
+ }
+
+ private C? LiftedIndirectConvert(A? a)
+ {
+ return (bool?)a;
+ }
+
+ private bool Compare(A a, C c)
+ {
+ return a == c;
+ }
+
+ private void LiftedCompare(A? a, C? c)
+ {
+ UseBool(a == c);
+ UseBool(a == default(C));
+ UseBool(c == default(A));
+ }
+
+ private void UseBool(bool b)
+ {
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/Annotations.cs b/ICSharpCode.Decompiler/CSharp/Annotations.cs
index 064da15f2..ea813e554 100644
--- a/ICSharpCode.Decompiler/CSharp/Annotations.cs
+++ b/ICSharpCode.Decompiler/CSharp/Annotations.cs
@@ -240,4 +240,18 @@ namespace ICSharpCode.Decompiler.CSharp
this.Leave = leave;
}
}
+
+ ///
+ /// Annotates an expression when an implicit user-defined conversion was omitted.
+ ///
+ public class ImplicitConversionAnnotation
+ {
+ public readonly ConversionResolveResult ConversionResolveResult;
+ public IType TargetType => ConversionResolveResult.Type;
+
+ public ImplicitConversionAnnotation(ConversionResolveResult conversionResolveResult)
+ {
+ this.ConversionResolveResult = conversionResolveResult;
+ }
+ }
}
diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
index e0d3dbae6..e270f5563 100644
--- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
+++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
@@ -191,7 +191,11 @@ namespace ICSharpCode.Decompiler.CSharp
conversion.Input.Type,
type, targetType
)) {
- return this.UnwrapChild(cast.Expression);
+ var result = this.UnwrapChild(cast.Expression);
+ if (conversion.Conversion.IsUserDefined) {
+ result.Expression.AddAnnotation(new ImplicitConversionAnnotation(conversion));
+ }
+ return result;
} else if (Expression is ObjectCreateExpression oce && conversion.Conversion.IsMethodGroupConversion
&& oce.Arguments.Count == 1 && expressionBuilder.settings.UseImplicitMethodGroupConversion) {
return this.UnwrapChild(oce.Arguments.Single());
@@ -211,6 +215,18 @@ namespace ICSharpCode.Decompiler.CSharp
if (targetType.Kind == TypeKind.Unknown || targetType.Kind == TypeKind.Void || targetType.Kind == TypeKind.None) {
return this; // don't attempt to insert cast to '?' or 'void' as these are not valid.
}
+ var convAnnotation = this.Expression.Annotation();
+ if (convAnnotation != null) {
+ // If an implicit user-defined conversion was stripped from this expression;
+ // it needs to be re-introduced before we can apply other casts to this expression.
+ // This happens when the CallBuilder discovers that the conversion is necessary in
+ // order to choose the correct overload.
+ this.Expression.RemoveAnnotations();
+ return new CastExpression(expressionBuilder.ConvertType(convAnnotation.TargetType), Expression)
+ .WithoutILInstruction()
+ .WithRR(convAnnotation.ConversionResolveResult)
+ .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion);
+ }
if (Expression is TupleExpression tupleExpr && targetType is TupleType targetTupleType
&& tupleExpr.Elements.Count == targetTupleType.ElementTypes.Length)
{
@@ -231,8 +247,9 @@ namespace ICSharpCode.Decompiler.CSharp
}
var compilation = expressionBuilder.compilation;
var conversions = Resolver.CSharpConversions.Get(compilation);
- if (ResolveResult is ConversionResolveResult conv && Expression is CastExpression cast2 &&
- CastCanBeMadeImplicit(conversions, conv.Conversion, conv.Input.Type, type, targetType))
+ if (ResolveResult is ConversionResolveResult conv && Expression is CastExpression cast2
+ && !conv.Conversion.IsUserDefined
+ && CastCanBeMadeImplicit(conversions, conv.Conversion, conv.Input.Type, type, targetType))
{
var unwrapped = this.UnwrapChild(cast2.Expression);
if (allowImplicitConversion)