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.
396 lines
8.6 KiB
C#
396 lines
8.6 KiB
C#
// Copyright (c) 2018 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.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
|
|
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
|
{
|
|
internal class NullPropagation
|
|
{
|
|
private class MyClass
|
|
{
|
|
public int IntVal;
|
|
public readonly int ReadonlyIntVal;
|
|
public MyStruct StructField;
|
|
public readonly MyStruct ReadonlyStructField;
|
|
public string Text;
|
|
public MyClass Field;
|
|
public bool? NullableBool;
|
|
public MyClass Property { get; set; }
|
|
public MyClass this[int index] => null;
|
|
public MyClass Method(int arg)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public bool GetBool()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public void Done()
|
|
{
|
|
}
|
|
}
|
|
|
|
private struct MyStruct
|
|
{
|
|
public int IntVal;
|
|
public readonly int ReadonlyIntVal;
|
|
public MyClass Field;
|
|
public MyStruct? Property1 => null;
|
|
#if CS71
|
|
public MyStruct Property2 => default;
|
|
#else
|
|
public MyStruct Property2 => default(MyStruct);
|
|
#endif
|
|
public MyStruct? this[int index] => null;
|
|
public MyStruct? Method1(int arg)
|
|
{
|
|
return null;
|
|
}
|
|
public MyStruct Method2(int arg)
|
|
{
|
|
#if CS71
|
|
return default;
|
|
#else
|
|
return default(MyStruct);
|
|
#endif
|
|
}
|
|
|
|
public bool GetBool()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public void Done()
|
|
{
|
|
}
|
|
}
|
|
|
|
private class Container<T1, T2>
|
|
{
|
|
public GenericStruct<T1, T2> Other;
|
|
}
|
|
|
|
private struct GenericStruct<T1, T2>
|
|
{
|
|
public T1 Field1;
|
|
public T2 Field2;
|
|
public Container<T1, T2> Other;
|
|
|
|
public override string ToString()
|
|
{
|
|
return "(" + Field1?.ToString() + ", " + Field2?.ToString() + ")";
|
|
}
|
|
|
|
public int? GetTextLength()
|
|
{
|
|
return Field1?.ToString().Length + Field2?.ToString().Length + 4;
|
|
}
|
|
|
|
public string Chain1()
|
|
{
|
|
return Other?.Other.Other?.Other.Field1?.ToString();
|
|
}
|
|
|
|
public string Chain2()
|
|
{
|
|
return Other?.Other.Other?.Other.Field1?.ToString()?.GetType().Name;
|
|
}
|
|
|
|
public int? Test2()
|
|
{
|
|
return Field1?.ToString().Length ?? 42;
|
|
}
|
|
|
|
public int? GetTextLengthNRE()
|
|
{
|
|
return (Field1?.ToString()).Length;
|
|
}
|
|
}
|
|
|
|
public interface ITest
|
|
{
|
|
int Int();
|
|
ITest Next();
|
|
}
|
|
|
|
private int GetInt()
|
|
{
|
|
return 9;
|
|
}
|
|
|
|
private string GetString()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private MyClass GetMyClass()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private MyStruct? GetMyStruct()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public string Substring()
|
|
{
|
|
return GetString()?.Substring(GetInt());
|
|
}
|
|
|
|
public void CallSubstringAndIgnoreResult()
|
|
{
|
|
GetString()?.Substring(GetInt());
|
|
}
|
|
|
|
private void Use<T>(T t)
|
|
{
|
|
}
|
|
|
|
public void CallDone()
|
|
{
|
|
GetMyClass()?.Done();
|
|
GetMyClass()?.Field?.Done();
|
|
GetMyClass()?.Field.Done();
|
|
GetMyClass()?.Property?.Done();
|
|
GetMyClass()?.Property.Done();
|
|
GetMyClass()?.Method(GetInt())?.Done();
|
|
GetMyClass()?.Method(GetInt()).Done();
|
|
GetMyClass()?[GetInt()]?.Done();
|
|
GetMyClass()?[GetInt()].Done();
|
|
}
|
|
|
|
public void CallGetBool()
|
|
{
|
|
GetMyClass()?.GetBool();
|
|
GetMyClass()?.Field?.GetBool();
|
|
GetMyClass()?.Field.GetBool();
|
|
GetMyClass()?.Property?.GetBool();
|
|
GetMyClass()?.Property.GetBool();
|
|
GetMyClass()?.Method(GetInt())?.GetBool();
|
|
GetMyClass()?.Method(GetInt()).GetBool();
|
|
GetMyClass()?[GetInt()]?.GetBool();
|
|
GetMyClass()?[GetInt()].GetBool();
|
|
}
|
|
|
|
public void CallDoneStruct()
|
|
{
|
|
GetMyStruct()?.Done();
|
|
GetMyStruct()?.Field?.Done();
|
|
GetMyStruct()?.Field.Done();
|
|
GetMyStruct()?.Property1?.Done();
|
|
GetMyStruct()?.Property2.Done();
|
|
GetMyStruct()?.Method1(GetInt())?.Done();
|
|
GetMyStruct()?.Method2(GetInt()).Done();
|
|
GetMyStruct()?[GetInt()]?.Done();
|
|
}
|
|
|
|
public void CallGetBoolStruct()
|
|
{
|
|
GetMyStruct()?.GetBool();
|
|
GetMyStruct()?.Field?.GetBool();
|
|
GetMyStruct()?.Field.GetBool();
|
|
GetMyStruct()?.Property1?.GetBool();
|
|
GetMyStruct()?.Property2.GetBool();
|
|
GetMyStruct()?.Method1(GetInt())?.GetBool();
|
|
GetMyStruct()?.Method2(GetInt()).GetBool();
|
|
GetMyStruct()?[GetInt()]?.GetBool();
|
|
}
|
|
|
|
public void RequiredParentheses()
|
|
{
|
|
(GetMyClass()?.Field).Done();
|
|
(GetMyClass()?.Method(GetInt())).Done();
|
|
(GetMyStruct()?.Property2)?.Done();
|
|
}
|
|
|
|
public int?[] ChainsOnClass()
|
|
{
|
|
return new int?[9] {
|
|
GetMyClass()?.IntVal,
|
|
GetMyClass()?.Field.IntVal,
|
|
GetMyClass()?.Field?.IntVal,
|
|
GetMyClass()?.Property.IntVal,
|
|
GetMyClass()?.Property?.IntVal,
|
|
GetMyClass()?.Method(GetInt()).IntVal,
|
|
GetMyClass()?.Method(GetInt())?.IntVal,
|
|
GetMyClass()?[GetInt()].IntVal,
|
|
GetMyClass()?[GetInt()]?.IntVal
|
|
};
|
|
}
|
|
|
|
public int?[] ChainsStruct()
|
|
{
|
|
return new int?[8] {
|
|
GetMyStruct()?.IntVal,
|
|
GetMyStruct()?.Field.IntVal,
|
|
GetMyStruct()?.Field?.IntVal,
|
|
GetMyStruct()?.Property2.IntVal,
|
|
GetMyStruct()?.Property1?.IntVal,
|
|
GetMyStruct()?.Method2(GetInt()).IntVal,
|
|
GetMyStruct()?.Method1(GetInt())?.IntVal,
|
|
GetMyStruct()?[GetInt()]?.IntVal
|
|
};
|
|
}
|
|
|
|
public int CoalescingReturn()
|
|
{
|
|
return GetMyClass()?.IntVal ?? 1;
|
|
}
|
|
|
|
public void Coalescing()
|
|
{
|
|
Use(GetMyClass()?.IntVal ?? 1);
|
|
}
|
|
|
|
public void CoalescingString()
|
|
{
|
|
Use(GetMyClass()?.Text ?? "Hello");
|
|
}
|
|
|
|
public void CallOnValueTypeField()
|
|
{
|
|
Use(GetMyClass()?.IntVal.ToString());
|
|
Use(GetMyStruct()?.IntVal.ToString());
|
|
Use(GetMyClass()?.ReadonlyIntVal.ToString());
|
|
Use(GetMyStruct()?.ReadonlyIntVal.ToString());
|
|
GetMyClass()?.StructField.Done();
|
|
GetMyClass()?.ReadonlyStructField.Done();
|
|
}
|
|
|
|
public void InvokeDelegate(EventHandler eh)
|
|
{
|
|
eh?.Invoke(null, EventArgs.Empty);
|
|
}
|
|
|
|
public int? InvokeDelegate(Func<int> f)
|
|
{
|
|
return f?.Invoke();
|
|
}
|
|
|
|
private void NotNullPropagation(MyClass c)
|
|
{
|
|
// don't decompile this to "(c?.IntVal ?? 0) != 0"
|
|
if (c != null && c.IntVal != 0)
|
|
{
|
|
Console.WriteLine("non-zero");
|
|
}
|
|
if (c == null || c.IntVal == 0)
|
|
{
|
|
Console.WriteLine("null or zero");
|
|
}
|
|
Console.WriteLine("end of method");
|
|
}
|
|
|
|
private void Setter(MyClass c)
|
|
{
|
|
if (c != null)
|
|
{
|
|
c.IntVal = 1;
|
|
}
|
|
Console.WriteLine();
|
|
if (c != null)
|
|
{
|
|
c.Property = null;
|
|
}
|
|
}
|
|
|
|
private static int? GenericUnconstrainedInt<T>(T t) where T : ITest
|
|
{
|
|
return t?.Int();
|
|
}
|
|
|
|
private static int? GenericClassConstraintInt<T>(T t) where T : class, ITest
|
|
{
|
|
return t?.Int();
|
|
}
|
|
|
|
private static int? GenericStructConstraintInt<T>(T? t) where T : struct, ITest
|
|
{
|
|
return t?.Int();
|
|
}
|
|
|
|
private static int? GenericRefUnconstrainedInt<T>(ref T t) where T : ITest
|
|
{
|
|
return t?.Int();
|
|
}
|
|
|
|
private static int? GenericRefClassConstraintInt<T>(ref T t) where T : class, ITest
|
|
{
|
|
return t?.Int();
|
|
}
|
|
|
|
private static int? GenericRefStructConstraintInt<T>(ref T? t) where T : struct, ITest
|
|
{
|
|
return t?.Int();
|
|
}
|
|
|
|
public int? Issue1709(object obj)
|
|
{
|
|
return (obj as ICollection)?.Count + (obj as ICollection<int>)?.Count;
|
|
}
|
|
|
|
private static void Issue1689(List<byte[]> setsOfNumbers)
|
|
{
|
|
Console.WriteLine(setsOfNumbers?[0]?[1].ToString() == "2");
|
|
Console.WriteLine(setsOfNumbers?[1]?[1].ToString() == null);
|
|
}
|
|
|
|
private static byte[] GetBytes()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private static void ArrayLengthWithFallback()
|
|
{
|
|
Console.WriteLine(GetBytes()?.Length ?? 0);
|
|
Console.WriteLine(GetBytes()?.LongLength ?? 0);
|
|
}
|
|
|
|
private static dynamic DynamicNullProp(dynamic a)
|
|
{
|
|
return a?.b.c(1)?.d[10];
|
|
}
|
|
|
|
private static string Issue3181()
|
|
{
|
|
#if EXPECTED_OUTPUT
|
|
return ((int?)null)?.ToString();
|
|
#else
|
|
int? x = null;
|
|
return x?.ToString();
|
|
#endif
|
|
}
|
|
|
|
private static bool Issue3702(MyClass c)
|
|
{
|
|
return c?.NullableBool.GetValueOrDefault() ?? false;
|
|
}
|
|
|
|
private static string Issue3702ToString(MyClass c)
|
|
{
|
|
return c?.NullableBool.GetValueOrDefault().ToString();
|
|
}
|
|
}
|
|
}
|