Fix #3702: Don't rewrite GetValueOrDefault() to == true in ?. chains
Turning bool?.GetValueOrDefault() into `== true` ends an enclosing null-conditional chain, so `c?.Flag.GetValueOrDefault()` became `(c?.Flag == true)` of type bool instead of bool?. A following `?? false` then fails to compile (CS0019), and a following `.ToString()` yields "False" instead of null when c is null. Keep the call when its target is part of a ?. chain.
This commit is contained in:
1 parent
8e3e41fedb
commit
ffe7e7b3f1
4 files changed
+37
-2
No files matched your search
@@ -7,6 +7,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
|
|||||||
static void Main()
|
static void Main()
|
||||||
{
|
{
|
||||||
new NullPropagation().TestNotCoalescing();
|
new NullPropagation().TestNotCoalescing();
|
||||||
|
new NullPropagation().TestNullableBoolInChain();
|
||||||
#if CS72
|
#if CS72
|
||||||
new NullPropagation().TestRefStructNotCoalescing();
|
new NullPropagation().TestRefStructNotCoalescing();
|
||||||
#endif
|
#endif
|
||||||
@@ -15,6 +16,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
|
|||||||
class MyClass
|
class MyClass
|
||||||
{
|
{
|
||||||
public string Text;
|
public string Text;
|
||||||
|
public bool? Flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestNotCoalescing()
|
void TestNotCoalescing()
|
||||||
@@ -29,6 +31,26 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
|
|||||||
return c != null ? c.Text : "Hello";
|
return c != null ? c.Text : "Hello";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestNullableBoolInChain()
|
||||||
|
{
|
||||||
|
Console.WriteLine("TestNullableBoolInChain:");
|
||||||
|
foreach (MyClass c in new[] { null, new MyClass(), new MyClass { Flag = false }, new MyClass { Flag = true } })
|
||||||
|
{
|
||||||
|
Console.WriteLine(EqualsTrue(c));
|
||||||
|
Console.WriteLine(GetValueOrDefaultToString(c) ?? "<null>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EqualsTrue(MyClass c)
|
||||||
|
{
|
||||||
|
return c?.Flag == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetValueOrDefaultToString(MyClass c)
|
||||||
|
{
|
||||||
|
return c?.Flag.GetValueOrDefault().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
#if CS72
|
#if CS72
|
||||||
ref struct ByRefLikeStruct
|
ref struct ByRefLikeStruct
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
|||||||
public readonly MyStruct ReadonlyStructField;
|
public readonly MyStruct ReadonlyStructField;
|
||||||
public string Text;
|
public string Text;
|
||||||
public MyClass Field;
|
public MyClass Field;
|
||||||
|
public bool? NullableBool;
|
||||||
public MyClass Property { get; set; }
|
public MyClass Property { get; set; }
|
||||||
public MyClass this[int index] => null;
|
public MyClass this[int index] => null;
|
||||||
public MyClass Method(int arg)
|
public MyClass Method(int arg)
|
||||||
@@ -380,5 +381,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
|||||||
return x?.ToString();
|
return x?.ToString();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool Issue3702(MyClass c)
|
||||||
|
{
|
||||||
|
return c?.NullableBool.GetValueOrDefault() ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Issue3702ToString(MyClass c)
|
||||||
|
{
|
||||||
|
return c?.NullableBool.GetValueOrDefault().ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,7 @@ using System.Text;
|
|||||||
|
|
||||||
using ICSharpCode.Decompiler.CSharp.Resolver;
|
using ICSharpCode.Decompiler.CSharp.Resolver;
|
||||||
using ICSharpCode.Decompiler.CSharp.Syntax;
|
using ICSharpCode.Decompiler.CSharp.Syntax;
|
||||||
|
using ICSharpCode.Decompiler.CSharp.Transforms;
|
||||||
using ICSharpCode.Decompiler.IL;
|
using ICSharpCode.Decompiler.IL;
|
||||||
using ICSharpCode.Decompiler.IL.Transforms;
|
using ICSharpCode.Decompiler.IL.Transforms;
|
||||||
using ICSharpCode.Decompiler.Semantics;
|
using ICSharpCode.Decompiler.Semantics;
|
||||||
@@ -594,7 +595,8 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
if (settings.LiftNullables && method.Name == "GetValueOrDefault"
|
if (settings.LiftNullables && method.Name == "GetValueOrDefault"
|
||||||
&& method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT)
|
&& method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT)
|
||||||
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Boolean)
|
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Boolean)
|
||||||
&& argumentList.Length == 0)
|
&& argumentList.Length == 0
|
||||||
|
&& !IntroduceQueryExpressions.IsNullConditional(target))
|
||||||
{
|
{
|
||||||
argumentList.CheckNoNamedOrOptionalArguments();
|
argumentList.CheckNoNamedOrOptionalArguments();
|
||||||
return new BinaryOperatorExpression(
|
return new BinaryOperatorExpression(
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsNullConditional(Expression target) => target switch {
|
internal static bool IsNullConditional(Expression target) => target switch {
|
||||||
UnaryOperatorExpression { Operator: UnaryOperatorType.NullConditional } => true,
|
UnaryOperatorExpression { Operator: UnaryOperatorType.NullConditional } => true,
|
||||||
MemberReferenceExpression member => IsNullConditional(member.Target),
|
MemberReferenceExpression member => IsNullConditional(member.Target),
|
||||||
InvocationExpression invocation => IsNullConditional(invocation.Target),
|
InvocationExpression invocation => IsNullConditional(invocation.Target),
|
||||||
|
|||||||
Reference in new issue
Block a user