From fe9f6db3ee1df5769979e4b0194d35b5d7ab9d91 Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Sun, 16 Aug 2026 10:05:13 +0200 Subject: [PATCH] Emit C# 7.1 default literals where the context supplies the type Shortening default(T) is the same problem as removing the redundant cast around a lambda whose delegate type the context already fixes, so it uses the same mechanism: ConvertTo makes the explicit type implicit when the conversion is an identity conversion and the caller allows an implicit one. The literal keeps the type it was shortened from, so any later conversion to a different type - or any context that requires an explicit type, such as an overload resolution recheck falling back to CastArguments - can spell default(T) out again. That keeps the value intact where the bare literal would change it, e.g. "object o = default(SomeStruct)", which boxes a non-null struct while "default" would be null. Because the shortened literal resolves to DefaultLiteralResolveResult, CallBuilder's existing overload resolution recheck sees a real default literal and rejects ambiguous calls on its own; no separate bookkeeping about which arguments may stay untyped is needed. Only the contexts that supply no target type at all restore the explicit form: an awaited expression, and arguments of operator methods, which later become operator or cast syntax rather than calls. Assisted-by: Claude:claude-opus-5[1m]:Claude Code --- .../PrettyTestRunner.cs | 18 ++ .../Semantics/ConversionTests.cs | 18 +- .../Correctness/OverloadResolution.cs | 74 ++++++ .../ILPretty/Issue2260SwitchString.cs | 4 +- .../SpanConversionOperatorMismatch.cs | 2 +- .../TestCases/Pretty/AsyncAwaitPatterns.cs | 42 +++- .../TestCases/Pretty/AsyncUsing.cs | 4 +- .../Pretty/CompoundAssignmentTest.cs | 56 +++++ .../TestCases/Pretty/DeconstructionTests.cs | 22 +- .../TestCases/Pretty/DefaultLiteral.cs | 139 +++++++++++ .../TestCases/Pretty/DelegateConstruction.cs | 4 + .../TestCases/Pretty/ExpressionTrees.cs | 4 + .../Pretty/FirstClassSpanConversions.cs | 2 +- .../TestCases/Pretty/FirstClassSpanTypes.cs | 2 +- .../TestCases/Pretty/InitializerTests.cs | 12 + .../TestCases/Pretty/InlineArrayTests.cs | 4 +- .../TestCases/Pretty/Issue3571_A.cs | 6 +- .../TestCases/Pretty/Issue3571_B.cs | 6 +- .../TestCases/Pretty/Issue3571_C.cs | 6 +- .../TestCases/Pretty/Issue3584.cs | 4 + .../TestCases/Pretty/Issue3909.cs | 4 +- .../TestCases/Pretty/LocalFunctions.cs | 20 +- .../TestCases/Pretty/Loops.cs | 4 + .../TestCases/Pretty/MultidimensionalArray.cs | 4 + .../TestCases/Pretty/NullPropagation.cs | 8 + .../TestCases/Pretty/NullableRefTypes.cs | 4 +- .../TestCases/Pretty/OutVariables.cs | 2 +- .../TestCases/Pretty/OverloadResolution.cs | 160 ++++++++++++ .../TestCases/Pretty/PointerArithmetic.cs | 8 + .../TestCases/Pretty/QueryExpressions.cs | 16 +- .../TestCases/Pretty/RefFields.cs | 60 ++--- .../TestCases/Pretty/RefStructInterfaces.cs | 8 +- .../Pretty/StaticAbstractInterfaceMembers.cs | 2 +- .../TestCases/Pretty/StringInterpolation.cs | 4 + .../TestCases/Pretty/Structs.cs | 4 + .../TestCases/Pretty/TargetTypedDefault.cs | 227 ++++++++++++++++++ .../TestCases/Pretty/UnsafeCode.cs | 8 + .../Pretty/UserDefinedConversions.cs | 24 ++ .../TestCases/Pretty/Using.cs | 16 ++ .../TestCases/Pretty/UsingVariables.cs | 2 +- .../TestCases/Pretty/ValueTypes.cs | 16 ++ .../TestCases/VBPretty/Async.cs | 4 +- .../TestCases/VBPretty/Issue1906.cs | 2 +- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 7 + .../OutputVisitor/CSharpOutputVisitor.cs | 13 +- .../CSharp/Resolver/CSharpConversions.cs | 3 +- .../CSharp/StatementBuilder.cs | 7 + .../Expressions/DefaultValueExpression.cs | 2 +- .../CSharp/Transforms/DeclareVariables.cs | 6 +- .../CSharp/TranslatedExpression.cs | 40 +++ ICSharpCode.Decompiler/DecompilerSettings.cs | 8 + .../Semantics/Conversion.cs | 12 + .../Semantics/DefaultLiteralResolveResult.cs | 57 +++++ ILSpy/Properties/Resources.Designer.cs | 9 + ILSpy/Properties/Resources.resx | 3 + 55 files changed, 1104 insertions(+), 99 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/OverloadResolution.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/TargetTypedDefault.cs create mode 100644 ICSharpCode.Decompiler/Semantics/DefaultLiteralResolveResult.cs diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index bafa49605..01b6228c9 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -482,6 +482,24 @@ namespace ICSharpCode.Decompiler.Tests 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 ab1bf9af9..c027f6ef8 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs @@ -1638,14 +1638,20 @@ namespace ICSharpCode.Decompiler.Tests.Semantics 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 10a6406e4..c8107d1ec 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs @@ -43,8 +43,82 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness 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 97796482c..30f11b3cc 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue2260SwitchString.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue2260SwitchString.cs @@ -5,8 +5,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty { 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 c2dfcf0d4..30765337e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SpanConversionOperatorMismatch.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SpanConversionOperatorMismatch.cs @@ -6,7 +6,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty { 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/AsyncAwaitPatterns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatterns.cs index 61db4f542..858f8e6f0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatterns.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatterns.cs @@ -35,7 +35,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait { public TaskAwaiter GetAwaiter() { +#if CS71 + return default; +#else return default(TaskAwaiter); +#endif } } } @@ -51,7 +55,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait { public ValueTask DisposeAsync() { - return default(ValueTask); + return default; } } #endif @@ -247,7 +251,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait { public static TaskAwaiter GetAwaiter(this IAwaitableMarker marker) { +#if CS71 + return default; +#else return default(TaskAwaiter); +#endif } public static TaskAwaiter GetAwaiter(this int millisecondsDelay) @@ -257,12 +265,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait public static TaskAwaiter GetAwaiter(this Action action) { +#if CS71 + return default; +#else return default(TaskAwaiter); +#endif } public static TaskAwaiter GetAwaiter(this IEnumerable> tasks) { +#if CS71 + return default; +#else return default(TaskAwaiter); +#endif } #if CS70 && !NET40 @@ -573,7 +589,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait public TaskAwaiter Self() { +#if CS71 + return default; +#else return default(TaskAwaiter); +#endif } } #endif @@ -582,7 +602,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait { public TaskAwaiter GetAwaiter() { +#if CS71 + return default; +#else return default(TaskAwaiter); +#endif } } @@ -609,7 +633,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait { TaskAwaiter IAwaitable.GetAwaiter() { +#if CS71 + return default; +#else return default(TaskAwaiter); +#endif } } @@ -618,7 +646,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait { TaskAwaiter IAwaitable.GetAwaiter() { +#if CS71 + return default; +#else return default(TaskAwaiter); +#endif } } @@ -626,7 +658,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait { public TaskAwaiter GetAwaiter() { +#if CS71 + return default; +#else return default(TaskAwaiter); +#endif } } @@ -664,7 +700,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait public TaskAwaiter GetAwaiter() { Counter++; +#if CS71 + return default; +#else return default(TaskAwaiter); +#endif } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs index a0daf7acb..ce7251b06 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs @@ -41,7 +41,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static async void TestAsyncUsingStruct() { - await using (AsyncDisposableStruct asyncDisposableStruct = default(AsyncDisposableStruct)) + await using (AsyncDisposableStruct asyncDisposableStruct = default) { Use(asyncDisposableStruct); } @@ -49,7 +49,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static async void TestAsyncUsingNullableStruct() { - await using (AsyncDisposableStruct? asyncDisposableStruct = new AsyncDisposableStruct?(default(AsyncDisposableStruct))) + await using (AsyncDisposableStruct? asyncDisposableStruct = new AsyncDisposableStruct?(default)) { Use(asyncDisposableStruct); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs index 33d071564..980b41aa7 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs @@ -5024,7 +5024,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 7cbf45a4f..c4a0133e3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs @@ -79,7 +79,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static implicit operator MyInt(int x) { - return default(MyInt); + return default; } } @@ -89,8 +89,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void Deconstruct(out T a, out T2 b) { - a = default(T); - b = default(T2); + a = default; + b = default; } } @@ -100,9 +100,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void Deconstruct(out T a, out T2 b) { - a = default(T); - b = default(T2); + a = default; + b = default; } } @@ -177,7 +177,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private StructDeconstructionSource GetStructSource() { - return default(StructDeconstructionSource); + return default; } private ref T GetRef() @@ -187,12 +187,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 000000000..515188dd3 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs @@ -0,0 +1,139 @@ +// 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 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 01a0ed57e..d870d8d2a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -261,7 +261,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction { 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 d2253581f..d6a768dce 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs @@ -966,7 +966,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 f1f0d9024..12da109d9 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanConversions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanConversions.cs @@ -34,7 +34,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { 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 5bfdd89ed..800bf93f8 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs @@ -114,7 +114,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 5afcdb5a2..2a9526df6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs @@ -73,7 +73,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests public S this[int index] { get { +#if CS71 + return default; +#else return default(S); +#endif } set { } @@ -81,7 +85,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests public S this[object key] { get { +#if CS71 + return default; +#else return default(S); +#endif } set { } @@ -158,7 +166,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests 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 537db46ad..75638c501 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs @@ -125,12 +125,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 27a69d844..9d3529acc 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_A.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3571_A.cs @@ -8,13 +8,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty [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 a5dd91f7f..c00f9126b 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 8f20467df..f33b25a2d 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 3c583d827..6ec198a4d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3584.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3584.cs @@ -14,7 +14,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 f46d5df67..5dceaa246 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3909.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3909.cs @@ -35,7 +35,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public virtual U? ReturnOnly() { - return default(U); + return default; } public virtual T?[] Nested(T?[] values) @@ -78,7 +78,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 838c529fd..a2278c99e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -38,7 +38,7 @@ namespace LocalFunctions 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 @@ namespace LocalFunctions int NonStaticMethod(int unused) #endif { - t2 = default(T2); + t2 = default; int l = 0; return NonStaticMethod3() + NonStaticMethod3() + z.GetHashCode(); int NonStaticMethod3() @@ -118,7 +118,7 @@ namespace LocalFunctions 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 @@ namespace LocalFunctions 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 @@ namespace LocalFunctions int StaticInvokeAsFunc2(Func func) #endif { - return func(default(T)); + return func(default); } #if CS80 static int StaticMethod() where T3 : struct @@ -209,19 +209,19 @@ namespace LocalFunctions 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 a0038f90f..b22516ba4 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs @@ -515,7 +515,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 e2086b1f2..645b85942 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MultidimensionalArray.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MultidimensionalArray.cs @@ -37,7 +37,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 aa834b0d1..c630c5d4f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs @@ -50,7 +50,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } 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 e5e154c12..fa6d934c5 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs @@ -118,7 +118,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { public static TValue? Default() { - return default(TValue); + return default; } public static void CallDefault() @@ -204,7 +204,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty [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 37a621916..03b424a3c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs @@ -46,7 +46,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 000000000..39df9103e --- /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 563b63f2d..bd3a39bcf 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PointerArithmetic.cs @@ -56,7 +56,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 55cd3c37d..ae87090b0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/QueryExpressions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/QueryExpressions.cs @@ -33,12 +33,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { 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 0c5d0395c..6efae7ffe 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs @@ -66,12 +66,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public Span NoCaptureOut(out int value) { value = 0; - return default(Span); + return default; } public Span Identity(Span span) @@ -137,7 +137,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public int ReassignScopedSpanFromStackAlloc(bool b) { - scoped Span span = default(Span); + scoped Span span = default; if (b) { span = stackalloc int[1]; @@ -186,7 +186,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public int ClassParameterReceiverDoesNotNarrow(SpanProvider provider, bool b) { - Span span = default(Span); + Span span = default; if (b) { span = provider.GetBuffer(); @@ -263,7 +263,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public Holder ReadonlyExtensionReceiverDoesNotRequireScoped(bool b) { - Holder holder = default(Holder); + Holder holder = default; if (b) { Span inner = stackalloc int[4]; @@ -466,7 +466,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { 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 d8560f4ae..5b95c1ed6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefStructInterfaces.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefStructInterfaces.cs @@ -100,7 +100,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static SpanFactory Create(int size) { - return default(SpanFactory); + return default; } } @@ -182,7 +182,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void InvokeDelegate(RefStructAction> action) { - action(default(Span)); + action(default); } public static void LocalFunctionAllows() @@ -217,7 +217,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 dde1d6d76..aeca248a9 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StaticAbstractInterfaceMembers.cs @@ -98,7 +98,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.StaticAbstractInterfaceM } 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 9dac65804..9aac72724 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs @@ -150,7 +150,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 da116b2c0..bbc3d1026 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs @@ -30,7 +30,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #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 000000000..2966c5d6c --- /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 e1f19ac1f..41243cf8f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs @@ -547,7 +547,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 4c8fce1f0..a32f538a4 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UserDefinedConversions.cs @@ -36,7 +36,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static implicit operator C(bool b) { +#if CS71 + return default; +#else return default(C); +#endif } } @@ -77,12 +81,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 5e5673565..8018e1585 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs @@ -50,12 +50,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 84cd63884..85b2d7f94 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UsingVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UsingVariables.cs @@ -113,7 +113,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 40a2d311f..6be8a90d0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs @@ -134,7 +134,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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 4bff91902..506307e17 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs @@ -40,7 +40,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty 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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty 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 ff0a3b34a..558806625 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 60dfe1509..126ac8608 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1075,6 +1075,13 @@ namespace ICSharpCode.Decompiler.CSharp } 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. + arg = arg.RestoreDefaultLiteralType(expressionBuilder); + } if (parameter.ReferenceKind != ReferenceKind.None) { diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 5509a990a..0cd5ac761 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -890,11 +890,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor 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 200ef491e..d06231f01 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -91,7 +91,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver 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 3eeb55e89..92a28762c 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -596,6 +596,13 @@ namespace ICSharpCode.Decompiler.CSharp if (var.LoadCount > 0 || var.AddressCount > 0) { var type = settings.AnonymousTypes && var.Type.ContainsAnonymousType() ? new SimpleType("var") : exprBuilder.ConvertType(var.Type); + if (resource is DefaultValueExpression) + { + // Unlike "using (expr)", the declaration spells out the type, so the + // resource may use the default literal. + resource = new TranslatedExpression(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 c14a5da44..da027f078 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DefaultValueExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DefaultValueExpression.cs @@ -37,6 +37,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax 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 bddb05959..4f17042a6 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -708,7 +708,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms 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 1a9ca1a85..d36ddaf3a 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -168,6 +168,20 @@ namespace ICSharpCode.Decompiler.CSharp 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,37 @@ namespace ICSharpCode.Decompiler.CSharp 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); + } if (NormalizeTypeVisitor.IgnoreNullabilityAndTuples.EquivalentTypes(type, targetType)) { // Make explicit conversion implicit, if possible if (allowImplicitConversion) { + if (Expression is DefaultValueExpression { Type: not null } + && expressionBuilder.settings.DefaultLiterals) + { + // The target type is supplied by the context, so "default(T)" can be + // shortened to the C# 7.1 default literal. + var shortened = new DefaultValueExpression(); + shortened.CopyAnnotationsFrom(Expression); + shortened.RemoveAnnotations(); + return shortened.WithRR(new DefaultLiteralResolveResult(type)) + .WithoutILInstruction(); + } switch (ResolveResult) { case ConversionResolveResult conversion: diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index be0f91eeb..3a5f0537b 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -633,6 +633,14 @@ namespace ICSharpCode.Decompiler [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 f4787b13a..2e77b07b8 100644 --- a/ICSharpCode.Decompiler/Semantics/Conversion.cs +++ b/ICSharpCode.Decompiler/Semantics/Conversion.cs @@ -104,6 +104,11 @@ namespace ICSharpCode.Decompiler.Semantics /// 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 @@ namespace ICSharpCode.Decompiler.Semantics 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 @@ namespace ICSharpCode.Decompiler.Semantics 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 @@ namespace ICSharpCode.Decompiler.Semantics 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 000000000..de7a260c1 --- /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 332d80a90..28a6ade9a 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1640,6 +1640,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// 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 f65ae29f5..73d84bc0d 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