Browse Source

Fix #1574: When re-introducing an explicit cast for an implicit user-defined conversion; ensure we use a direct cast and don't go through the code path for builtin conversions.

pull/1596/head
Daniel Grunwald 6 years ago
parent
commit
e6489d543e
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 134
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs
  4. 14
      ICSharpCode.Decompiler/CSharp/Annotations.cs
  5. 23
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -89,6 +89,7 @@ @@ -89,6 +89,7 @@
<Compile Include="TestCases\ILPretty\Issue1323.cs" />
<Compile Include="TestCases\Pretty\CustomAttributes2.cs" />
<Compile Include="TestCases\Pretty\EnumTests.cs" />
<Compile Include="TestCases\Pretty\UserDefinedConversions.cs" />
<None Include="TestCases\ILPretty\Unsafe.il" />
<None Include="TestCases\Pretty\NullableRefTypes.cs" />
<Compile Include="TestCases\Pretty\TypeMemberTests.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -425,6 +425,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -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)
{

134
ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs

@ -0,0 +1,134 @@ @@ -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)
{
}
}
}

14
ICSharpCode.Decompiler/CSharp/Annotations.cs

@ -240,4 +240,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -240,4 +240,18 @@ namespace ICSharpCode.Decompiler.CSharp
this.Leave = leave;
}
}
/// <summary>
/// Annotates an expression when an implicit user-defined conversion was omitted.
/// </summary>
public class ImplicitConversionAnnotation
{
public readonly ConversionResolveResult ConversionResolveResult;
public IType TargetType => ConversionResolveResult.Type;
public ImplicitConversionAnnotation(ConversionResolveResult conversionResolveResult)
{
this.ConversionResolveResult = conversionResolveResult;
}
}
}

23
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -191,7 +191,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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 @@ -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<ImplicitConversionAnnotation>();
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<ImplicitConversionAnnotation>();
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 @@ -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)

Loading…
Cancel
Save