diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 3a16f4270c..b3013dff4c 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -482,6 +482,24 @@ public async Task OutVariables([ValueSource(nameof(roslyn2OrNewerOptions))] Comp await RunForLibrary(cscOptions: cscOptions); } + [Test] + public async Task DefaultLiteral([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task OverloadResolution([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task TargetTypedDefault([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + [Test] public async Task PatternMatching([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs index ab1bf9af9e..c027f6ef8e 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs @@ -1638,14 +1638,20 @@ public void UserDefinedImplicitConversion_OperatorDeclaredInBaseClassOfSource() Assert.That(c.Method.DeclaringType.Name, Is.EqualTo("OperatorInBaseClass")); } - [Test, Ignore("C# standard 10.2.16 is not implemented: CSharpConversions.ImplicitConversion has a TODO for default literal conversions, and no ResolveResult represents a typeless default literal")] + [Test] public void DefaultLiteralConversions() { - // C# standard 10.2.16: an implicit conversion exists from a default_literal to - // any type, producing the default value of the inferred type. Once the semantic - // model gains a typeless default-literal ResolveResult, this test should assert - // that it converts to int, string, int? and type parameters. - Assert.Fail("Default literal conversions are not implemented."); + // C# standard 10.2.16: an implicit conversion exists from a default_literal to any type + var defaultLiteral = new DefaultLiteralResolveResult(); + // a default_value_expression is a constant expression (C# standard 12.8.21) + Assert.That(defaultLiteral.IsCompileTimeConstant); + Assert.That(conversions.ImplicitConversion(defaultLiteral, compilation.FindType(KnownTypeCode.Int32)), Is.EqualTo(C.DefaultLiteralConversion)); + Assert.That(conversions.ImplicitConversion(defaultLiteral, compilation.FindType(KnownTypeCode.String)), Is.EqualTo(C.DefaultLiteralConversion)); + Assert.That(conversions.ImplicitConversion(defaultLiteral, compilation.FindType(typeof(int?))), Is.EqualTo(C.DefaultLiteralConversion)); + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T"); + Assert.That(conversions.ImplicitConversion(defaultLiteral, t), Is.EqualTo(C.DefaultLiteralConversion)); + // explicit conversions include all implicit conversions, so (T)default is also valid + Assert.That(conversions.ExplicitConversion(defaultLiteral, compilation.FindType(KnownTypeCode.Int32)), Is.EqualTo(C.DefaultLiteralConversion)); } [Test, Ignore("C# standard 10.2.18 is not implemented: no ResolveResult represents a switch expression; the decompiler converts each arm separately in ILAst")] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs index 10a6406e46..c8107d1ec0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs @@ -43,8 +43,82 @@ static void Main() Issue2444.M2(); Issue2741.B.Test(new Issue2741.C()); ExtensionMethodDemo.Issue2165.Test(); +#if CS71 + DefaultLiteralTests(); +#endif + } + +#if CS71 + static void DefaultLiteralTests() + { + // The decompiled output may shorten default(T) to a default literal; + // re-compilation must still pick the same overloads and operators. + DefaultOverload(default(DataStruct)); + DefaultOverload(default(OtherStruct)); + DefaultNullableOverload(default(DataStruct)); + Console.WriteLine(default(DataStruct) == new DataStruct()); + Console.WriteLine(GenericDefault("x", default)); + Console.WriteLine(GenericDefault(42, default)); + } + + struct DataStruct + { + public int Field; + + public static bool operator ==(DataStruct a, DataStruct b) + { + Console.WriteLine("DataStruct operator =="); + return a.Field == b.Field; + } + + public static bool operator !=(DataStruct a, DataStruct b) + { + return a.Field != b.Field; + } + + public override bool Equals(object obj) + { + return obj is DataStruct other && Field == other.Field; + } + + public override int GetHashCode() + { + return Field; + } } + struct OtherStruct + { + public int Field; + } + + static void DefaultOverload(DataStruct data) + { + Console.WriteLine("DefaultOverload(DataStruct)"); + } + + static void DefaultOverload(OtherStruct data) + { + Console.WriteLine("DefaultOverload(OtherStruct)"); + } + + static void DefaultNullableOverload(DataStruct data) + { + Console.WriteLine("DefaultNullableOverload(DataStruct)"); + } + + static void DefaultNullableOverload(DataStruct? data) + { + Console.WriteLine("DefaultNullableOverload(DataStruct?)"); + } + + static T GenericDefault(T a, T b) + { + Console.WriteLine("GenericDefault: " + typeof(T).Name); + return b; + } +#endif + #region ConstructorTest static void ConstructorTest() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue2260SwitchString.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue2260SwitchString.cs index 97796482c7..30f11b3ccd 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue2260SwitchString.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue2260SwitchString.cs @@ -5,8 +5,8 @@ internal class Issue2260 { private void dgvItemList_CellValueChanged(object sender, DataGridViewCellEventArgs e) { - string text = default(string); - string s = default(string); + string text = default; + string s = default; switch (text) { case "rowno": diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SpanConversionOperatorMismatch.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SpanConversionOperatorMismatch.cs index c2dfcf0d44..30765337e8 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SpanConversionOperatorMismatch.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SpanConversionOperatorMismatch.cs @@ -6,7 +6,7 @@ public class SpanConversionOperatorMismatch { public static implicit operator ReadOnlySpan(object o) { - return default(ReadOnlySpan); + return default; } public static ReadOnlySpan ConvertString(string s) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs index a0daf7acba..876ff41735 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs @@ -41,7 +41,7 @@ public static async void TestAsyncUsingClass() public static async void TestAsyncUsingStruct() { - await using (AsyncDisposableStruct asyncDisposableStruct = default(AsyncDisposableStruct)) + await using (AsyncDisposableStruct asyncDisposableStruct = default) { Use(asyncDisposableStruct); } @@ -49,7 +49,7 @@ public static async void TestAsyncUsingStruct() public static async void TestAsyncUsingNullableStruct() { - await using (AsyncDisposableStruct? asyncDisposableStruct = new AsyncDisposableStruct?(default(AsyncDisposableStruct))) + await using (AsyncDisposableStruct? asyncDisposableStruct = default(AsyncDisposableStruct)) { Use(asyncDisposableStruct); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs index 33d071564b..980b41aa7a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs @@ -5024,7 +5024,11 @@ public static void CustomClassPreDecTest(CustomClass p, CustomClass c, CustomStr } public static void CustomStructAddTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif p += default(CustomStruct); num += default(CustomStruct); Use(ref num); @@ -5051,7 +5055,11 @@ public static void CustomStructAddTest(CustomStruct p, CustomClass c, CustomStru public static void CustomStructSubtractTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif p -= default(CustomStruct); num -= default(CustomStruct); Use(ref num); @@ -5078,7 +5086,11 @@ public static void CustomStructSubtractTest(CustomStruct p, CustomClass c, Custo public static void CustomStructMultiplyTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif p *= default(CustomStruct); num *= default(CustomStruct); Use(ref num); @@ -5105,7 +5117,11 @@ public static void CustomStructMultiplyTest(CustomStruct p, CustomClass c, Custo public static void CustomStructDivideTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif p /= default(CustomStruct); num /= default(CustomStruct); Use(ref num); @@ -5132,7 +5148,11 @@ public static void CustomStructDivideTest(CustomStruct p, CustomClass c, CustomS public static void CustomStructModulusTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif p %= default(CustomStruct); num %= default(CustomStruct); Use(ref num); @@ -5159,7 +5179,11 @@ public static void CustomStructModulusTest(CustomStruct p, CustomClass c, Custom public static void CustomStructLeftShiftTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif p <<= 5; num <<= 5; Use(ref num); @@ -5186,7 +5210,11 @@ public static void CustomStructLeftShiftTest(CustomStruct p, CustomClass c, Cust public static void CustomStructRightShiftTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif p >>= 5; num >>= 5; Use(ref num); @@ -5239,7 +5267,11 @@ public static void CustomStructUnsignedRightShiftTest(CustomStruct p, CustomClas public static void CustomStructBitAndTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif p &= default(CustomStruct); num &= default(CustomStruct); Use(ref num); @@ -5266,7 +5298,11 @@ public static void CustomStructBitAndTest(CustomStruct p, CustomClass c, CustomS public static void CustomStructBitOrTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif p |= default(CustomStruct); num |= default(CustomStruct); Use(ref num); @@ -5293,7 +5329,11 @@ public static void CustomStructBitOrTest(CustomStruct p, CustomClass c, CustomSt public static void CustomStructBitXorTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif p ^= default(CustomStruct); num ^= default(CustomStruct); Use(ref num); @@ -5320,7 +5360,11 @@ public static void CustomStructBitXorTest(CustomStruct p, CustomClass c, CustomS public static void CustomStructPostIncTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif X(p++); X(num++); Use(ref num); @@ -5347,7 +5391,11 @@ public static void CustomStructPostIncTest(CustomStruct p, CustomClass c, Custom public static void CustomStructPreIncTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif X(++p); X(++num); Use(ref num); @@ -5373,7 +5421,11 @@ public static void CustomStructPreIncTest(CustomStruct p, CustomClass c, CustomS } public static void CustomStructPostDecTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif X(p--); X(num--); Use(ref num); @@ -5400,7 +5452,11 @@ public static void CustomStructPostDecTest(CustomStruct p, CustomClass c, Custom public static void CustomStructPreDecTest(CustomStruct p, CustomClass c, CustomStruct2 s) { +#if CS71 + CustomStruct num = default; +#else CustomStruct num = default(CustomStruct); +#endif X(--p); X(--num); Use(ref num); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs index 7cbf45a4ff..c4a0133e3f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs @@ -79,7 +79,7 @@ public static implicit operator int(MyInt x) public static implicit operator MyInt(int x) { - return default(MyInt); + return default; } } @@ -89,8 +89,8 @@ private class DeconstructionSource public void Deconstruct(out T a, out T2 b) { - a = default(T); - b = default(T2); + a = default; + b = default; } } @@ -100,9 +100,9 @@ private class DeconstructionSource public void Deconstruct(out T a, out T2 b, out T3 c) { - a = default(T); - b = default(T2); - c = default(T3); + a = default; + b = default; + c = default; } } @@ -112,8 +112,8 @@ public struct StructDeconstructionSource public void Deconstruct(out T a, out T2 b) { - a = default(T); - b = default(T2); + a = default; + b = default; } } @@ -177,7 +177,7 @@ private DeconstructionSource GetSource() private StructDeconstructionSource GetStructSource() { - return default(StructDeconstructionSource); + return default; } private ref T GetRef() @@ -187,12 +187,12 @@ private ref T GetRef() private (T, T2) GetTuple() { - return default((T, T2)); + return default; } private (T, T2, T3) GetTuple() { - return default((T, T2, T3)); + return default; } private List GetList() diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs new file mode 100644 index 0000000000..b8a8e5911b --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs @@ -0,0 +1,145 @@ +// Copyright (c) 2026 Siegfried Pammer +// +// 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.Threading.Tasks; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + internal class DefaultLiteral + { + public struct Data + { + public int Field; + + public static Data operator +(Data a, Data b) + { + return new Data { + Field = a.Field + b.Field + }; + } + } + + public struct OtherData + { + public int Field; + } + + public int this[Data d] => d.Field; + + public int DeclarationWithInitializer() + { + Data data = default; + return data.Field + data.Field; + } + + public int Assignment(Data data) + { + int field = data.Field; + data = default; + return field + data.Field; + } + + public Data Return() + { + return default; + } + + public T ReturnGeneric() + { + return default; + } + + public async Task ReturnAsync() + { + await Task.Yield(); + return default; + } + + public string AmbiguousArgumentStaysTyped() + { + return Overloaded(default(Data)); + } + + public string BetterConversionTargetArgument() + { + return OverloadedNullable(default); + } + + public int UnambiguousArgument() + { + return Single(default); + } + + public string BoxedArgumentStaysTyped() + { + return Boxed(default(Data)); + } + + public int IndexerArgument() + { + return this[default]; + } + + public Data OperatorOperandStaysTyped(Data data) + { + return data + default(Data); + } + + public bool NullableWithValueStaysTyped() + { + Data? data = default(Data); + return data.HasValue; + } + + public string NonIdentityConversionsStayTyped() + { + object obj = default(Data); + return obj.ToString() + obj.ToString(); + } + + private int Single(Data data) + { + return data.Field; + } + + private string Boxed(object o) + { + return o.ToString(); + } + + private string Overloaded(Data x) + { + return "Data"; + } + + private string Overloaded(OtherData x) + { + return "OtherData"; + } + + private string OverloadedNullable(Data x) + { + return "Data"; + } + + private string OverloadedNullable(Data? x) + { + return "Data?"; + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index 01a0ed57e8..d870d8d2a5 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -261,7 +261,11 @@ public class GenericTest { public Func GetFunc(Func f) { +#if CS71 + TCaptured captured = f(default); +#else TCaptured captured = f(default(TNonCaptured)); +#endif return () => { Console.WriteLine(captured.GetType().FullName); return captured; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs index d2253581f0..d6a768dce3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs @@ -966,7 +966,11 @@ public static dynamic ToJson(this object o) public static DateTime ParseDateTime(this object str) { +#if CS71 + return default; +#else return default(DateTime); +#endif } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanConversions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanConversions.cs index f1f0d90243..12da109d9c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanConversions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanConversions.cs @@ -34,7 +34,7 @@ internal class SpanConvertible { public static implicit operator ReadOnlySpan(SpanConvertible c) { - return default(ReadOnlySpan); + return default; } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs index 5bfdd89ed1..800bf93f83 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs @@ -114,7 +114,7 @@ public static void RefSpanOrByValue(ReadOnlySpan s) public static void OutSpanOrByValue(out ReadOnlySpan s) { - s = default(ReadOnlySpan); + s = default; } public static void OutSpanOrByValue(ReadOnlySpan s) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs index 5afcdb5a21..2a9526df63 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs @@ -73,7 +73,11 @@ public class C public S this[int index] { get { +#if CS71 + return default; +#else return default(S); +#endif } set { } @@ -81,7 +85,11 @@ public S this[int index] { public S this[object key] { get { +#if CS71 + return default; +#else return default(S); +#endif } set { } @@ -158,7 +166,11 @@ private struct StructData public StructData(int initialValue) { +#if CS71 + this = default; +#else this = default(StructData); +#endif Field = initialValue; Property = initialValue; } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs index 537db46ad1..75638c5017 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs @@ -125,12 +125,12 @@ public void OverloadResolution() public Byte16 GetByte16() { - return default(Byte16); + return default; } public Generic16 GetGeneric() { - return default(Generic16); + return default; } public int GetIndex() diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_A.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_A.cs index 27a69d8448..9d3529acce 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_A.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_A.cs @@ -8,13 +8,13 @@ internal static class Issue3571_A [StructLayout(LayoutKind.Sequential, Size = 1)] public readonly struct fsResult { - public static fsResult Success => default(fsResult); - public static fsResult Failure => default(fsResult); + public static fsResult Success => default; + public static fsResult Failure => default; public bool Succeeded => true; public bool Failed => false; public static fsResult operator +(fsResult a, fsResult b) { - return default(fsResult); + return default; } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_B.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_B.cs index a5dd91f7f1..c00f9126b3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_B.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_B.cs @@ -6,13 +6,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.Issue3571_B [StructLayout(LayoutKind.Sequential, Size = 1)] public readonly struct fsResult { - public static fsResult Success => default(fsResult); - public static fsResult Failure => default(fsResult); + public static fsResult Success => default; + public static fsResult Failure => default; public bool Succeeded => true; public bool Failed => false; public static fsResult operator +(fsResult a, fsResult b) { - return default(fsResult); + return default; } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_C.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_C.cs index 8f20467dfa..f33b25a2db 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_C.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_C.cs @@ -25,13 +25,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.Issue3571_Helper [StructLayout(LayoutKind.Sequential, Size = 1)] public readonly struct fsResult { - public static fsResult Success => default(fsResult); - public static fsResult Failure => default(fsResult); + public static fsResult Success => default; + public static fsResult Failure => default; public bool Succeeded => true; public bool Failed => false; public static fsResult operator +(fsResult a, fsResult b) { - return default(fsResult); + return default; } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3584.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3584.cs index 3c583d827c..6ec198a4d1 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3584.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3584.cs @@ -14,7 +14,11 @@ public T this[int i] { get { if (i >= Length || i < 0) { +#if CS71 + return default; +#else return default(T); +#endif } if (results[i] != null && results[i].Equals(default(T))) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3909.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3909.cs index f46d5df67e..5dceaa246e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3909.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3909.cs @@ -35,7 +35,7 @@ public abstract class Base public virtual U? ReturnOnly() { - return default(U); + return default; } public virtual T?[] Nested(T?[] values) @@ -78,7 +78,7 @@ public sealed class Derived : Base public override U? ReturnOnly() where U : default { - return default(U); + return default; } public override T?[] Nested(T?[] values) where T : default diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 838c529fdc..a2278c99e5 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -38,7 +38,7 @@ public class Generic where T1 : struct, ICloneable, IConvertible public int MixedLocalFunction() where T2 : ICloneable, IConvertible { #pragma warning disable CS0219 - T2 t2 = default(T2); + T2 t2 = default; object z = this; for (int i = 0; i < 10; i++) { @@ -52,7 +52,7 @@ int NonStaticMethod<[My] T3>([My] int unused) int NonStaticMethod(int unused) #endif { - t2 = default(T2); + t2 = default; int l = 0; return NonStaticMethod3() + NonStaticMethod3() + z.GetHashCode(); int NonStaticMethod3() @@ -118,7 +118,7 @@ int NonStaticMethod4() public int MixedLocalFunction2Delegate() where T2 : ICloneable, IConvertible { - T2 t2 = default(T2); + T2 t2 = default; object z = this; for (int i = 0; i < 10; i++) { @@ -126,7 +126,7 @@ public int MixedLocalFunction2Delegate() where T2 : ICloneable, IConvertible i2 += StaticInvokeAsFunc(NonStaticMethod); int NonStaticMethod() { - t2 = default(T2); + t2 = default; int l = 0; return StaticInvokeAsFunc(NonStaticMethod3) + StaticInvokeAsFunc(NonStaticMethod3) + z.GetHashCode(); int NonStaticMethod3() @@ -155,7 +155,7 @@ static int StaticInvokeAsFunc2(Func func) int StaticInvokeAsFunc2(Func func) #endif { - return func(default(T)); + return func(default); } #if CS80 static int StaticMethod() where T3 : struct @@ -209,19 +209,19 @@ int StaticMethod5(T dd) public static void Test_CaptureT() { #pragma warning disable CS0219 - T2 t2 = default(T2); + T2 t2 = default; Method(); void Method() { - t2 = default(T2); + t2 = default; T2 t2x = t2; - T3 t3 = default(T3); + T3 t3 = default; Method2(); void Method2() { - t2 = default(T2); + t2 = default; t2x = t2; - t3 = default(T3); + t3 = default; } } #pragma warning restore CS0219 diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs index a0038f90f3..b22516ba43 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs @@ -515,7 +515,11 @@ public static void ForeachWithCapturedVariable(List items) public static T LastOrDefault(IEnumerable items) { +#if CS71 + T result = default; +#else T result = default(T); +#endif foreach (T item in items) { result = item; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MultidimensionalArray.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MultidimensionalArray.cs index e2086b1f2e..645b859427 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MultidimensionalArray.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MultidimensionalArray.cs @@ -37,7 +37,11 @@ public class MultidimensionalArray public void TestB(S x, ref S y) { b[5, 3] = new S[10]; +#if CS71 + b[5, 3][0] = default; +#else b[5, 3][0] = default(S); +#endif b[5, 3][1] = x; b[5, 3][2] = y; } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs index aa834b0d1b..c630c5d4f8 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs @@ -50,7 +50,11 @@ private struct MyStruct 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) { @@ -58,7 +62,11 @@ private struct MyStruct } public MyStruct Method2(int arg) { +#if CS71 + return default; +#else return default(MyStruct); +#endif } public void Done() diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs index e5e154c12b..fa6d934c5c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs @@ -118,7 +118,7 @@ public class T05_NullableUnconstrainedGeneric { public static TValue? Default() { - return default(TValue); + return default; } public static void CallDefault() @@ -204,7 +204,7 @@ public static void ThrowIfNull([NotNull] object? o) [return: MaybeNull] public T FirstOrDefault(IEnumerable source) { - return default(T); + return default; } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs index 37a621916e..03b424a3cf 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs @@ -46,7 +46,7 @@ public static Action CapturedOutVarInShortCircuit(Dictionary d) private bool TryGet(out T result) { - result = default(T); + result = default; return true; } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OverloadResolution.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OverloadResolution.cs new file mode 100644 index 0000000000..39df9103ed --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OverloadResolution.cs @@ -0,0 +1,160 @@ +// Copyright (c) 2026 Siegfried Pammer +// +// 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. + +#pragma warning disable 660, 661 + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + internal class OverloadResolution + { + public struct Data + { + public int Field; + + public static bool operator ==(Data a, Data b) + { + return a.Field == b.Field; + } + + public static bool operator !=(Data a, Data b) + { + return a.Field != b.Field; + } + } + + public struct OtherData + { + public int Field; + } + + public void IntegerOverloads() + { + Integer(1); + Integer((short)1); + Integer(1L); + } + + public void ReferenceTypeOverloads() + { + RefType("string"); + RefType((object)"string"); + RefType(null); + RefType((object)null); + } + + public void NullableOverloads() + { + NullableInt(1); + NullableInt((int?)1); + NullableInt(null); + } + + public void ParamsOverloads(int n) + { + Params(1); + Params(1, 2); + Params(default(int), default(int), default(int)); + Params(new int[n]); + } + + public void GenericOverloads() + { + Generic(1); + Generic(1); + } + + public string AmbiguousDefaultArgumentStaysTyped() + { + return Ambiguous(default(Data)); + } + + public string DefaultArgumentWithBetterConversionTarget() + { + return WithNullable(default); + } + + public bool EqualityWithDefaultStaysTyped(Data data) + { + return data == default(Data); + } + + private static void Integer(short s) + { + } + + private static void Integer(int i) + { + } + + private static void Integer(long l) + { + } + + private static void RefType(object o) + { + } + + private static void RefType(string s) + { + } + + private static void NullableInt(int i) + { + } + + private static void NullableInt(int? i) + { + } + + private static void Params(int i) + { + } + + private static void Params(params int[] xs) + { + } + + private static void Generic(int i) + { + } + + private static void Generic(T a) + { + } + + private static string Ambiguous(Data x) + { + return "Data"; + } + + private static string Ambiguous(OtherData x) + { + return "OtherData"; + } + + private static string WithNullable(Data x) + { + return "Data"; + } + + private static string WithNullable(Data? x) + { + return "Data?"; + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs index 563b63f2db..bd3a39bcfd 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs @@ -56,7 +56,11 @@ public unsafe static void AssignmentGuidPointerToDateTimePointer(Guid* ptr) public unsafe static void AssignmentGuidPointerToDateTimePointerDefault(Guid* ptr) { +#if CS71 + ((DateTime*)ptr)[2] = default; +#else ((DateTime*)ptr)[2] = default(DateTime); +#endif } public unsafe static void AssignmentGuidPointerToDateTimePointer_2(Guid* ptr) @@ -66,7 +70,11 @@ public unsafe static void AssignmentGuidPointerToDateTimePointer_2(Guid* ptr) public unsafe static void AssignmentGuidPointerToDateTimePointerDefault_2(Guid* ptr) { +#if CS71 + *(DateTime*)(ptr + 2) = default; +#else *(DateTime*)(ptr + 2) = default(DateTime); +#endif } public unsafe static DateTime AccessGuidPointerToDateTimePointer(Guid* ptr) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/QueryExpressions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/QueryExpressions.cs index 0870735b1d..2c500a0adf 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/QueryExpressions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/QueryExpressions.cs @@ -33,12 +33,20 @@ public static class MaybeExtensions { public static Maybe Select(this Maybe a, Func fn) { +#if CS71 + return default; +#else return default(Maybe); +#endif } public static Maybe Where(this Maybe a, Func predicate) { +#if CS71 + return default; +#else return default(Maybe); +#endif } } @@ -48,13 +56,19 @@ public class MaybeHolder { public Maybe Value; -#if CS60 +#if CS71 + public Maybe this[int index] => default; +#elif CS60 public Maybe this[int index] => default(Maybe); #endif public Func> Factory() { +#if CS71 + return () => default; +#else return () => default(Maybe); +#endif } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs index 0c5d0395c6..6efae7ffee 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs @@ -66,12 +66,12 @@ public Span ScopedRefSpan(scoped ref Span span) public Span ScopedSpan(scoped Span span) { - return default(Span); + return default; } public void OutSpan(out Span span) { - span = default(Span); + span = default; } public void CaptureIntoRef(ref Holder holder, Span inner) @@ -81,13 +81,13 @@ public void CaptureIntoRef(ref Holder holder, Span inner) public void CaptureIntoOut(out Holder holder, Span inner) { - holder = default(Holder); + holder = default; holder.Inner = inner; } public void CaptureRefIntoOut(out Holder holder, ref int value) { - holder = default(Holder); + holder = default; holder.Inner = new Span(ref value); } @@ -105,7 +105,7 @@ public Span CaptureOut([UnscopedRef] out int value) public Span NoCaptureOut(out int value) { value = 0; - return default(Span); + return default; } public Span Identity(Span span) @@ -137,7 +137,7 @@ public int ReassignScopedRefToLocal(bool b, ref int x) public int ReassignScopedSpanFromOut(bool b) { int value = 0; - scoped Span span = default(Span); + scoped Span span = default; if (b) { span = CaptureOut(out value); @@ -147,7 +147,7 @@ public int ReassignScopedSpanFromOut(bool b) public int ImplicitScopedOutDoesNotCapture() { - Span span = default(Span); + Span span = default; span = NoCaptureOut(out var value); Console.WriteLine(span.Length); return span.Length + value; @@ -155,8 +155,8 @@ public int ImplicitScopedOutDoesNotCapture() public int ReassignScopedSpanFromReceiver(bool b) { - UnscopedRefStruct unscopedRefStruct = default(UnscopedRefStruct); - scoped Span span = default(Span); + UnscopedRefStruct unscopedRefStruct = default; + scoped Span span = default; if (b) { span = unscopedRefStruct.AsSpan(); @@ -166,7 +166,7 @@ public int ReassignScopedSpanFromReceiver(bool b) public int ReassignScopedSpanFromRefParameter(bool b, ref int value) { - scoped Span span = default(Span); + scoped Span span = default; if (b) { span = CreateAndCapture(ref value); @@ -176,7 +176,7 @@ public int ReassignScopedSpanFromRefParameter(bool b, ref int value) public int ReassignScopedSpanFromStackAlloc(bool b) { - scoped Span span = default(Span); + scoped Span span = default; if (b) { span = stackalloc int[1]; @@ -186,7 +186,7 @@ public int ReassignScopedSpanFromStackAlloc(bool b) public int ReassignScopedSpanFromScopedValue(bool b, scoped Span value) { - scoped Span span = default(Span); + scoped Span span = default; if (b) { span = value; @@ -197,7 +197,7 @@ public int ReassignScopedSpanFromScopedValue(bool b, scoped Span value) public int ReassignScopedSpanFromNestedCall(bool b) { int value = 0; - scoped Span span = default(Span); + scoped Span span = default; if (b) { span = Identity(CreateAndCapture(ref value)); @@ -208,7 +208,7 @@ public int ReassignScopedSpanFromNestedCall(bool b) public int ReassignScopedSpanFromLocalCopy(bool b) { Span span = stackalloc int[1]; - scoped Span span2 = default(Span); + scoped Span span2 = default; if (b) { span2 = span; @@ -219,7 +219,7 @@ public int ReassignScopedSpanFromLocalCopy(bool b) public int ReassignScopedSpanFromScopedRefValue(bool b) { Span span = stackalloc int[1]; - scoped Span span2 = default(Span); + scoped Span span2 = default; if (b) { span2 = ScopedRefSpan(ref span); @@ -252,7 +252,7 @@ public int NarrowThenWideDoesNotNeedScoped(bool b, ref int value) public int ClassParameterReceiverDoesNotNarrow(SpanProvider provider, bool b) { - Span span = default(Span); + Span span = default; if (b) { span = provider.GetBuffer(); @@ -263,7 +263,7 @@ public int ClassParameterReceiverDoesNotNarrow(SpanProvider provider, bool b) public int ClassLocalReceiverDoesNotNarrow(bool b) { SpanProvider spanProvider = new SpanProvider(); - Span span = default(Span); + Span span = default; if (b) { span = spanProvider.GetBuffer(); @@ -283,7 +283,7 @@ public ref int PlainStructReceiverDoesNotForceScoped(bool b, ref Buffer buffer, public int FieldStoreRequiresScoped() { - scoped Holder holder = default(Holder); + scoped Holder holder = default; Span inner = stackalloc int[4]; holder.Inner = inner; return holder.Inner.Length; @@ -291,7 +291,7 @@ public int FieldStoreRequiresScoped() public int ReceiverCallRequiresScoped() { - scoped Holder holder = default(Holder); + scoped Holder holder = default; Span inner = stackalloc int[4]; holder.Set(inner); return holder.Inner.Length; @@ -299,7 +299,7 @@ public int ReceiverCallRequiresScoped() public int ExtensionReceiverCallRequiresScoped() { - scoped Holder holder = default(Holder); + scoped Holder holder = default; Span inner = stackalloc int[4]; holder.SetExtension(inner); return holder.Inner.Length; @@ -307,7 +307,7 @@ public int ExtensionReceiverCallRequiresScoped() public int RefArgumentCallRequiresScoped() { - scoped Holder holder = default(Holder); + scoped Holder holder = default; Span inner = stackalloc int[4]; CaptureIntoRef(ref holder, inner); return holder.Inner.Length; @@ -315,7 +315,7 @@ public int RefArgumentCallRequiresScoped() public int OutArgumentCallRequiresScoped() { - scoped Holder holder = default(Holder); + scoped Holder holder = default; Span inner = stackalloc int[4]; CaptureIntoOut(out holder, inner); return holder.Inner.Length; @@ -324,28 +324,28 @@ public int OutArgumentCallRequiresScoped() public int OutArgumentCapturesRefRequiresScoped() { int value = 0; - scoped Holder holder = default(Holder); + scoped Holder holder = default; CaptureRefIntoOut(out holder, ref value); return holder.Inner[0]; } public Holder RefArgumentWideValueDoesNotRequireScoped(Span wide) { - Holder holder = default(Holder); + Holder holder = default; CaptureIntoRef(ref holder, wide); return holder; } public Holder OutArgumentWideValueDoesNotRequireScoped(Span wide) { - Holder holder = default(Holder); + Holder holder = default; CaptureIntoOut(out holder, wide); return holder; } public int RefReturnFieldStoreRequiresScoped() { - scoped Holder holder = default(Holder); + scoped Holder holder = default; Span inner = stackalloc int[4]; Identity(ref holder).Inner = inner; return holder.Inner.Length; @@ -353,14 +353,14 @@ public int RefReturnFieldStoreRequiresScoped() public Holder RefReturnFieldStoreWideValueDoesNotRequireScoped(Span wide) { - Holder holder = default(Holder); + Holder holder = default; Identity(ref holder).Inner = wide; return holder; } public Holder ReadonlyReceiverDoesNotRequireScoped(bool b) { - Holder result = default(Holder); + Holder result = default; if (b) { Span inner = stackalloc int[4]; @@ -371,7 +371,7 @@ public Holder ReadonlyReceiverDoesNotRequireScoped(bool b) public Holder ReadonlyExtensionReceiverDoesNotRequireScoped(bool b) { - Holder holder = default(Holder); + Holder holder = default; if (b) { Span inner = stackalloc int[4]; @@ -466,7 +466,7 @@ internal sealed class SpanProvider { public Span GetBuffer() { - return default(Span); + return default; } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefStructInterfaces.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefStructInterfaces.cs index d8560f4aef..5b95c1ed68 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefStructInterfaces.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefStructInterfaces.cs @@ -100,7 +100,7 @@ public void Optional() public static SpanFactory Create(int size) { - return default(SpanFactory); + return default; } } @@ -182,7 +182,7 @@ public static T CreateViaFactory(int size) where T : IStaticFactory, allow public static T CreateDefault() where T : allows ref struct { - return default(T); + return default; } public static void CombinedUnmanaged() where T : unmanaged, allows ref struct @@ -204,7 +204,7 @@ public static void ScopedAndRef(scoped T value, ref T byRef, in T input, out public static void InvokeDelegate(RefStructAction> action) { - action(default(Span)); + action(default); } public static void LocalFunctionAllows() @@ -217,7 +217,7 @@ static void Local() where T : allows ref struct public static void CapturingLocalFunction(int seed) where T : allows ref struct { - Nested(default(T)); + Nested(default); Console.WriteLine(seed); void Nested(scoped T value) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs index dde1d6d76c..aeca248a9b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs @@ -98,7 +98,7 @@ static virtual void M(object x) } static virtual implicit operator T(string s) { - return default(T); + return default; } static virtual explicit operator string(T t) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs index 9dac658041..9aac727249 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs @@ -150,7 +150,11 @@ public string ConcatStringCharCSSC(string s, char c) public static TReturn Get() { +#if CS71 + return default; +#else return default(TReturn); +#endif } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs index da116b2c01..bbc3d10260 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs @@ -30,7 +30,11 @@ public class Structs #if CS100 public StructWithDefaultCtor M() { +#if CS71 + return default; +#else return default(StructWithDefaultCtor); +#endif } public StructWithDefaultCtor M2() diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TargetTypedDefault.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TargetTypedDefault.cs new file mode 100644 index 0000000000..2966c5d6c9 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TargetTypedDefault.cs @@ -0,0 +1,227 @@ +// Copyright (c) 2026 Siegfried Pammer +// +// 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.Linq.Expressions; +using System.Threading; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public class TargetTypedDefault + { + public struct ValueHolder + { + public int Value; + } + + public enum Flavor + { + None, + Sweet + } + +#if !OPT + public Guid guidField = default; +#else + public Guid guidField; +#endif + + public void OptGuid(Guid guid = default(Guid)) + { + } + + public void OptCancellationToken(CancellationToken cancellationToken = default(CancellationToken)) + { + } + + public void OptTimeSpan(TimeSpan timeSpan = default(TimeSpan)) + { + } + + public void OptDateTime(DateTime dateTime = default(DateTime)) + { + } + + public void OptCustomStruct(ValueHolder holder = default(ValueHolder)) + { + } + + public void OptDecimal(decimal d = 0m) + { + } + + public void OptNullable(int? x = null) + { + } + + public void OptEnum(Flavor flavor = Flavor.None) + { + } + + public void OptString(string s = null) + { + } + + public void OptInt(int i = 0) + { + } + + public void OptGeneric(T value = default(T)) + { + } + + public void OptGenericStruct(T value = default(T)) where T : struct + { + } + + public void OptTuple((int, string) pair = default((int, string))) + { + } + + public void CallsWithExplicitDefaults() + { + OptGuid(); + OptCancellationToken(); + OptTimeSpan(); + OptCustomStruct(); + OptNullable(); + OptString(); + OptGeneric(); + OptTuple(); + } + + public void TakeGuid(Guid guid) + { + } + + public void TakeInGuid(in Guid guid) + { + } + + public void Over(int x) + { + } + + public void Over(string s) + { + } + + public void OverStruct(Guid guid) + { + } + + public void OverStruct(TimeSpan timeSpan) + { + } + + public void CallOverloads() + { + Over(0); + Over(null); + OverStruct(default(Guid)); + OverStruct(default(TimeSpan)); + } + + public void ArgumentPositions(bool b, Guid guid) + { + TakeGuid(default); + TakeInGuid(default(Guid)); + TakeGuid(b ? guid : default(Guid)); + } + + public T GenericReturn() + { + return default; + } + + public T GenericClassReturn() where T : class + { + return null; + } + + public T GenericNewReturn() where T : new() + { + return default; + } + + public ValueHolder StructReturn() + { + return default; + } + + public (int, string) TupleReturn() + { + return default; + } + + public int? NullableReturn() + { + return null; + } + + public void OutDefault(out Guid guid) + { + guid = default; + } + + public bool GuidIsDefault(Guid guid) + { + return guid == default(Guid); + } + + public bool TimeSpanIsDefault(TimeSpan timeSpan) + { + return timeSpan == default(TimeSpan); + } + + public bool IntIsDefault(int i) + { + return i == 0; + } + + public bool NullableIsDefault(int? x) + { + return !x.HasValue; + } + + public string CoalesceDefault(string s) + { + return s ?? null; + } + + public T[] DefaultInitializedArray() + { + return new T[3]; + } + + public unsafe int* PointerDefault() + { + return null; + } + + public Expression> GuidExpressionTree() + { + return () => default; + } + + public Expression> ZeroExpressionTree() + { + return () => 0; + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs index e1f19ac1fb..41243cf8f7 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs @@ -547,7 +547,11 @@ public unsafe string StackAllocStruct(int count) private unsafe void Issue990() { +#if CS71 + Data data = default; +#else Data data = default(Data); +#endif Data* ptr = &data; ConvertIntToFloat(ptr->Position.GetHashCode()); } @@ -562,7 +566,11 @@ private unsafe static void Issue1021(ref byte* bytePtr, ref short* shortPtr) private static T Get() { +#if CS71 + return default; +#else return default(T); +#endif } private unsafe static ResultStruct NestedFixedBlocks(byte[] array) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs index 4c8fce1f00..a32f538a4b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs @@ -36,7 +36,11 @@ private struct C public static implicit operator C(bool b) { +#if CS71 + return default; +#else return default(C); +#endif } } @@ -77,12 +81,20 @@ private struct C public static implicit operator C(bool b) { +#if CS71 + return default; +#else return default(C); +#endif } public static implicit operator C(A a) { +#if CS71 + return default; +#else return default(C); +#endif } public static bool operator ==(C a, C b) @@ -141,12 +153,20 @@ private struct T public static implicit operator T(in int val) { +#if CS71 + return default; +#else return default(T); +#endif } public static explicit operator T(in long val) { +#if CS71 + return default; +#else return default(T); +#endif } } @@ -156,7 +176,11 @@ private struct U public static implicit operator T(in U u) { +#if CS71 + return default; +#else return default(T); +#endif } public static explicit operator int(in U u) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs index 5e56735650..8018e15856 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs @@ -50,12 +50,20 @@ public void Dispose() #if !ROSLYN3 public static implicit operator TypeB_Issue3385(TypeA_Issue3385 a) { +#if CS71 + return default; +#else return default(TypeB_Issue3385); +#endif } #else public static implicit operator TypeB_Issue3385(in TypeA_Issue3385 a) { +#if CS71 + return default; +#else return default(TypeB_Issue3385); +#endif } #endif } @@ -191,9 +199,17 @@ public void UsingRefStruct1(UsingRefStruct s) public static void Issue3385() { #if ROSLYN3 +#if CS71 + using (TypeA_Issue3385 a = default) +#else using (TypeA_Issue3385 a = default(TypeA_Issue3385)) +#endif +#else +#if CS71 + using (TypeA_Issue3385 typeA_Issue = default) #else using (TypeA_Issue3385 typeA_Issue = default(TypeA_Issue3385)) +#endif #endif { #if ROSLYN3 diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UsingVariables.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UsingVariables.cs index 84cd63884b..85b2d7f94d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UsingVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UsingVariables.cs @@ -113,7 +113,7 @@ public async Task SimpleUsingVarAsync() public void UsingVarPatternBasedDispose() { Console.WriteLine("before using"); - using RefStructWithDispose refStructWithDispose = default(RefStructWithDispose); + using RefStructWithDispose refStructWithDispose = default; Console.WriteLine("inside using"); refStructWithDispose.Use(); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs index 40a2d311f3..6be8a90d09 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs @@ -134,7 +134,11 @@ public static void CallMethodViaField() #if !(ROSLYN && OPT) || COPY_PROPAGATION_FIXED public static S InitObj1() { +#if CS71 + S result = default; +#else S result = default(S); +#endif MakeArray(); return result; } @@ -142,12 +146,20 @@ public static S InitObj1() public static S InitObj2() { +#if CS71 + return default; +#else return default(S); +#endif } public static void InitObj3(out S p) { +#if CS71 + p = default; +#else p = default(S); +#endif } public static S CallValueTypeCtor() @@ -252,7 +264,11 @@ public static void CompareEqual0IsReallyEqual(IComparable a) public static T Get() { +#if CS71 + return default; +#else return default(T); +#endif } public static void CallOnTemporary() diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs index 4bff919024..506307e175 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs @@ -40,7 +40,7 @@ public async void AwaitYield() public async void AwaitDefaultYieldAwaitable() { #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) - YieldAwaitable yieldAwaitable2 = default(YieldAwaitable); + YieldAwaitable yieldAwaitable2 = default; YieldAwaitable yieldAwaitable = yieldAwaitable2; await yieldAwaitable; #else @@ -51,7 +51,7 @@ public async void AwaitDefaultYieldAwaitable() public async void AwaitDefaultHopToThreadPool() { #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) - HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = default(HopToThreadPoolAwaitable); + HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = default; HopToThreadPoolAwaitable hopToThreadPoolAwaitable = hopToThreadPoolAwaitable2; await hopToThreadPoolAwaitable; #else diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Issue1906.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Issue1906.cs index ff0a3b34af..558806625f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Issue1906.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Issue1906.cs @@ -4,6 +4,6 @@ public class Issue1906 { public void M() { - Console.WriteLine(Math.Min(Math.Max(long.MinValue, default(long)), long.MaxValue)); + Console.WriteLine(Math.Min(Math.Max(long.MinValue, default), long.MaxValue)); } } diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index b56fa2e489..17984f3625 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1075,6 +1075,15 @@ private ArgumentList BuildArgumentList(ExpectedTargetDetails expectedTargetDetai } arg = arg.ConvertTo(parameterType, expressionBuilder, allowImplicitConversion: arg.Type.Kind != TypeKind.Dynamic); + if (method.IsOperator) + { + // Operator calls do not survive as calls: ReplaceMethodCallsWithOperators turns + // them into operator or cast syntax, where the operand determines which operator + // is resolved, so it must keep its explicit type. Unlike the null literal, which + // still narrows the candidate set, the default literal converts to every type: + // C# rejects it as the operand of any binary operator except == and != (CS8310). + arg = arg.RestoreDefaultLiteralType(expressionBuilder); + } if (parameter.ReferenceKind != ReferenceKind.None) { diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index cdbe7920a3..f224c684a4 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4425,6 +4425,8 @@ protected internal override TranslatedExpression VisitAwait(Await inst, Translat if (expectedType != null) { value = value.ConvertTo(expectedType, this, allowImplicitConversion: true); + // The awaited expression is not target-typed: "await default" does not compile. + value = value.RestoreDefaultLiteralType(this); } return new UnaryOperatorExpression(UnaryOperatorType.Await, value.Expression) .WithILInstruction(inst) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index f5d2794565..3ba628a72d 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -886,11 +886,14 @@ public virtual void VisitDefaultValueExpression(DefaultValueExpression defaultVa StartNode(defaultValueExpression); WriteKeyword(DefaultValueExpression.DefaultKeyword); - LPar(); - Space(policy.SpacesWithinTypeOfParentheses); - defaultValueExpression.Type.AcceptVisitor(this); - Space(policy.SpacesWithinTypeOfParentheses); - RPar(); + if (defaultValueExpression.Type is not null) + { + LPar(); + Space(policy.SpacesWithinTypeOfParentheses); + defaultValueExpression.Type.AcceptVisitor(this); + Space(policy.SpacesWithinTypeOfParentheses); + RPar(); + } EndNode(defaultValueExpression); } diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 200ef491ee..d06231f011 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -91,7 +91,8 @@ private Conversion ImplicitConversion(ResolveResult resolveResult, IType toType, if (c != Conversion.None) return c; // C# 9.0 spec: ยง10.2.16 default literal conversions - // TODO + if (resolveResult is DefaultLiteralResolveResult) + return Conversion.DefaultLiteralConversion; if (resolveResult.IsCompileTimeConstant) { c = StandardImplicitConversion(resolveResult.Type, toType, allowTuple); diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index d7cb0c3a54..24110b6306 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -532,11 +532,11 @@ protected internal override TranslatedStatement VisitLockInstruction(LockInstruc protected internal override TranslatedStatement VisitUsingInstruction(UsingInstruction inst) { - var resource = exprBuilder.Translate(inst.ResourceExpression).Expression; + var resource = exprBuilder.Translate(inst.ResourceExpression); var transformed = TransformToForeach(inst, resource); if (transformed != null) return transformed.WithILInstruction(inst); - AstNode usingInit = resource; + AstNode usingInit = resource.Expression; var var = inst.Variable; KnownTypeCode knownTypeCode; IType disposeType; @@ -567,7 +567,7 @@ protected internal override TranslatedStatement VisitUsingInstruction(UsingInstr disposeInvocation = new UnaryOperatorExpression { Expression = disposeInvocation, Operator = UnaryOperatorType.Await }; } return new BlockStatement { - new ExpressionStatement(new AssignmentExpression(exprBuilder.ConvertVariable(var).Expression, resource.Detach())), + new ExpressionStatement(new AssignmentExpression(exprBuilder.ConvertVariable(var).Expression, resource.Expression.Detach())), new TryCatchStatement { TryBlock = ConvertAsBlock(inst.Body), FinallyBlock = new BlockStatement() { @@ -585,6 +585,12 @@ protected internal override TranslatedStatement VisitUsingInstruction(UsingInstr if (var.LoadCount > 0 || var.AddressCount > 0) { var type = settings.AnonymousTypes && var.Type.ContainsAnonymousType() ? new SimpleType("var") : exprBuilder.ConvertType(var.Type); + if (!type.IsVar()) + { + // Unlike "using (expr)", the declaration spells out the type, so the + // resource may leave conversions to it implicit. + resource = resource.ConvertTo(var.Type, exprBuilder, allowImplicitConversion: true); + } var vds = new VariableDeclarationStatement(type, var.Name!, resource); vds.Variables.Single().AddAnnotation(new ILVariableResolveResult(var, var.Type)); usingInit = vds; diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DefaultValueExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DefaultValueExpression.cs index c14a5da44f..da027f0786 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DefaultValueExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DefaultValueExpression.cs @@ -37,6 +37,6 @@ public sealed partial class DefaultValueExpression : Expression public const string DefaultKeyword = "default"; [Slot("Type")] - public partial AstType Type { get; set; } + public partial AstType? Type { get; set; } } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index bddb05959d..4f17042a68 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -708,7 +708,11 @@ void InsertVariableDeclarations(TransformContext context) AstType type = context.TypeSystemAstBuilder.ConvertType(v.Type); if (v.DefaultInitialization == VariableInitKind.NeedsDefaultValue) { - initializer = new DefaultValueExpression(type.Clone()); + // The declaration always spells out the type, so the default literal is + // equivalent to default(T). + initializer = context.Settings.DefaultLiterals + ? new DefaultValueExpression() + : new DefaultValueExpression(type.Clone()); } var vds = new VariableDeclarationStatement(type, v.Name, initializer); if (context.Settings.ScopedRef && v.ILVariable.IsScopedWithoutInitializer) diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 1a9ca1a854..d682da2ebf 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -168,6 +168,20 @@ public TranslatedExpression UnwrapChild(Expression descendant) throw new ArgumentException("descendant must be a descendant of the current node"); } + /// + /// Undoes the shortening to the C# 7.1 default literal that + /// applies, restoring the original "default(T)". Use in contexts that supply no target + /// type for the literal, e.g. an awaited expression or an argument of a call that later + /// becomes a cast or an operator. + /// + public TranslatedExpression RestoreDefaultLiteralType(ExpressionBuilder expressionBuilder) + { + if (ResolveResult is not DefaultLiteralResolveResult literal) + return this; + return expressionBuilder.GetDefaultValueExpression(literal.ShortenedFrom) + .WithILInstruction(this.ILInstructions); + } + /// /// Adds casts (if necessary) to convert this expression to the specified target type. /// @@ -195,11 +209,46 @@ public TranslatedExpression UnwrapChild(Expression descendant) public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expressionBuilder, bool checkForOverflow = false, bool allowImplicitConversion = false) { var type = this.Type; + if (ResolveResult is DefaultLiteralResolveResult literal) + { + if (allowImplicitConversion + && NormalizeTypeVisitor.IgnoreNullabilityAndTuples.EquivalentTypes(literal.ShortenedFrom, targetType)) + { + // The context still supplies the type the literal was shortened from. + return this; + } + // Either an explicit type is required here (e.g. overload resolution needs the + // typed form to stay unambiguous), or the context supplies a different type, in + // which case the literal would produce a different value (e.g. null instead of + // a boxed struct). + return RestoreDefaultLiteralType(expressionBuilder) + .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); + } + // Unwrapping a conversion hands its operand to a different target type. A default + // literal takes its value from that type, so it may have to be spelled out again: + // "T? x = new T?(default)" holds a value, whereas "T? x = default" is null. + TranslatedExpression Unwrapped(TranslatedExpression operand) + { + if (operand.ResolveResult is not DefaultLiteralResolveResult) + return operand; + return operand.ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); + } if (NormalizeTypeVisitor.IgnoreNullabilityAndTuples.EquivalentTypes(type, targetType)) { // Make explicit conversion implicit, if possible if (allowImplicitConversion) { + if (Expression is DefaultValueExpression { Type: not null } defaultValue + && expressionBuilder.settings.DefaultLiterals) + { + // The target type is supplied by the context, so "default(T)" can be + // shortened to the C# 7.1 default literal. + defaultValue.Type = null; + defaultValue.RemoveAnnotations(); + var literalRR = new DefaultLiteralResolveResult(type); + defaultValue.AddAnnotation(literalRR); + return new TranslatedExpression(defaultValue, literalRR); + } switch (ResolveResult) { case ConversionResolveResult conversion: @@ -211,7 +260,7 @@ public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expres type, targetType )) { - var result = this.UnwrapChild(cast.Expression); + var result = Unwrapped(this.UnwrapChild(cast.Expression)); if (conversion.Conversion.IsUserDefined) { result.Expression.AddAnnotation(new ImplicitConversionAnnotation(conversion)); @@ -230,7 +279,7 @@ public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expres if (Expression is ObjectCreateExpression oce && oce.Arguments.Count == 1 && invocation.Type.IsKnownType(KnownTypeCode.NullableOfT)) { - return this.UnwrapChild(oce.Arguments.Single()); + return Unwrapped(this.UnwrapChild(oce.Arguments.Single())); } break; } @@ -302,7 +351,7 @@ public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expres && !conv.Conversion.IsUserDefined && CastCanBeMadeImplicit(conversions, conv.Conversion, conv.Input.Type, type, targetType)) { - var unwrapped = this.UnwrapChild(cast2.Expression); + var unwrapped = Unwrapped(this.UnwrapChild(cast2.Expression)); if (allowImplicitConversion) return unwrapped; return unwrapped.ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index be0f91eebf..3a5f0537b5 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -633,6 +633,14 @@ public bool LifetimeAnnotations { [DecompilerSetting(CSharp.LanguageVersion.CSharp7)] public partial bool ThrowExpressions { get; set; } + /// + /// Gets/Sets whether the C# 7.1 default literal default should be used instead of + /// default(T), wherever the surrounding syntax already supplies the type. + /// + [Description("DecompilerSettings.UseDefaultLiterals")] + [DecompilerSetting(CSharp.LanguageVersion.CSharp7_1)] + public partial bool DefaultLiterals { get; set; } + /// /// Gets/Sets whether implicit conversions between tuples /// should be used in the decompiled output. diff --git a/ICSharpCode.Decompiler/Semantics/Conversion.cs b/ICSharpCode.Decompiler/Semantics/Conversion.cs index f4787b13a4..2e77b07b8b 100644 --- a/ICSharpCode.Decompiler/Semantics/Conversion.cs +++ b/ICSharpCode.Decompiler/Semantics/Conversion.cs @@ -104,6 +104,11 @@ public static Conversion EnumerationConversion(bool isImplicit, bool isLifted) /// public static readonly Conversion ExplicitSpanConversion = new BuiltinConversion(false, 14); + /// + /// C# 7.1 default literal being converted to an arbitrary type. + /// + public static readonly Conversion DefaultLiteralConversion = new BuiltinConversion(true, 15); + public static Conversion UserDefinedConversion(IMethod operatorMethod, bool isImplicit, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted = false, bool isAmbiguous = false) { if (operatorMethod == null) @@ -265,6 +270,7 @@ public override bool IsThrowExpressionConversion { public override bool IsInlineArrayConversion => type == 12; public override bool IsImplicitSpanConversion => type == 13; public override bool IsExplicitSpanConversion => type == 14; + public override bool IsDefaultLiteralConversion => type == 15; public override string ToString() { @@ -306,6 +312,8 @@ public override string ToString() return "implicit span conversion"; case 14: return "explicit span conversion"; + case 15: + return "default-literal conversion"; } return (isImplicit ? "implicit " : "explicit ") + name + " conversion"; } @@ -499,6 +507,10 @@ public virtual bool IsThrowExpressionConversion { get { return false; } } + public virtual bool IsDefaultLiteralConversion { + get { return false; } + } + public virtual bool IsIdentityConversion { get { return false; } } diff --git a/ICSharpCode.Decompiler/Semantics/DefaultLiteralResolveResult.cs b/ICSharpCode.Decompiler/Semantics/DefaultLiteralResolveResult.cs new file mode 100644 index 0000000000..de7a260c1d --- /dev/null +++ b/ICSharpCode.Decompiler/Semantics/DefaultLiteralResolveResult.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2026 Siegfried Pammer +// +// 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 ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.Semantics +{ + /// + /// Represents a typeless default literal `default`, which is implicitly convertible + /// to any type (C# standard 10.2.16 default literal conversions). + /// + class DefaultLiteralResolveResult : ResolveResult + { + /// + /// The type of the "default(T)" expression the literal was shortened from; it is restored + /// wherever the surrounding syntax stops supplying that very type. + /// if the literal does not stand for a shortened + /// expression. + /// + public readonly IType ShortenedFrom; + + public DefaultLiteralResolveResult() : this(SpecialType.UnknownType) + { + } + + public DefaultLiteralResolveResult(IType shortenedFrom) : base(SpecialType.NoType) + { + this.ShortenedFrom = shortenedFrom; + } + + // A default_value_expression is a constant expression (C# standard 12.8.21); + // like the null literal, the typeless default literal is modeled as a constant + // with value null (the target-typed value is only known after conversion). + public override bool IsCompileTimeConstant { + get { return true; } + } + + public override object ConstantValue { + get { return null; } + } + } +} diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 332d80a90a..28a6ade9ae 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1640,6 +1640,15 @@ public static string DecompilerSettings_UnsignedRightShift { } } + /// + /// Looks up a localized string similar to Use "default" literals without an explicit type. + /// + public static string DecompilerSettings_UseDefaultLiterals { + get { + return ResourceManager.GetString("DecompilerSettings.UseDefaultLiterals", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use discards. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index f65ae29f58..73d84bc0df 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -579,6 +579,9 @@ Are you sure you want to continue? Unsigned right shift (>>>) + + Use "default" literals without an explicit type + Use discards