From 71d0a9edc7476d992f1b8e6d9c21f31d7fcc5a6b Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Sun, 16 Aug 2026 10:05:13 +0200 Subject: [PATCH 1/3] 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/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 + .../CSharp/ExpressionBuilder.cs | 2 + .../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, 1065 insertions(+), 98 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 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..ce7251b060 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 = new AsyncDisposableStruct?(default)) { 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..515188dd3a --- /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 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..209cb0fc16 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1075,6 +1075,13 @@ 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. + 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..492eeca184 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -585,6 +585,13 @@ 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 (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 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..d36ddaf3a6 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,37 @@ 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); + } 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 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 From 97ca8a33ae1f6b1f0c79df4093946483acba2916 Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Sun, 16 Aug 2026 10:48:28 +0200 Subject: [PATCH 2/3] Shorten default(T) in place, and say why operators keep the type Mutating the DefaultValueExpression is enough here; ConvertTo already hands out mutated input nodes elsewhere (UnwrapChild), so building a replacement node and copying the annotations over bought nothing. The operator special case is easy to mistake for a cosmetic preference, because the null literal is accepted in the same position: it converts only to reference and nullable types, so it still narrows operator overload resolution, whereas the default literal converts to everything and C# rejects it outright for every binary operator except == and !=. Assisted-by: Claude:claude-opus-5[1m]:Claude Code --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 4 +++- .../CSharp/TranslatedExpression.cs | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 209cb0fc16..17984f3625 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1079,7 +1079,9 @@ private ArgumentList BuildArgumentList(ExpectedTargetDetails expectedTargetDetai { // 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. + // 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); } diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index d36ddaf3a6..4d40d998f8 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -229,16 +229,16 @@ public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expres // Make explicit conversion implicit, if possible if (allowImplicitConversion) { - if (Expression is DefaultValueExpression { Type: not null } + 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. - var shortened = new DefaultValueExpression(); - shortened.CopyAnnotationsFrom(Expression); - shortened.RemoveAnnotations(); - return shortened.WithRR(new DefaultLiteralResolveResult(type)) - .WithoutILInstruction(); + defaultValue.Type = null; + defaultValue.RemoveAnnotations(); + var literalRR = new DefaultLiteralResolveResult(type); + defaultValue.AddAnnotation(literalRR); + return new TranslatedExpression(defaultValue, literalRR); } switch (ResolveResult) { From 4a9b3843f295db23ed3fd907a78829a51226c181 Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Sun, 16 Aug 2026 11:16:12 +0200 Subject: [PATCH 3/3] Keep the default literal's type when a conversion is unwrapped Making a conversion implicit by unwrapping it hands the operand to a different target type, and a default literal takes its value from that type: "S? x = new S?(default)" holds a value, while "S? x = default" is null. Unwrapping the nullable constructor around a shortened literal therefore turned "S? x = default(S)" into a null nullable. The literal is spelled out again whenever unwrapping moves it to a type other than the one it was shortened from. Converting a using resource to the declared variable type is unconditional now (except when the declaration says "var", which supplies no type): the declaration always spells the type out, so any conversion to it may stay implicit, which is also what shortens default(T) there. Assisted-by: Claude:claude-opus-5[1m]:Claude Code --- .../TestCases/Pretty/AsyncUsing.cs | 2 +- .../TestCases/Pretty/DefaultLiteral.cs | 6 ++++++ ICSharpCode.Decompiler/CSharp/StatementBuilder.cs | 13 ++++++------- .../CSharp/TranslatedExpression.cs | 15 ++++++++++++--- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs index ce7251b060..876ff41735 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs @@ -49,7 +49,7 @@ public static async void TestAsyncUsingStruct() public static async void TestAsyncUsingNullableStruct() { - await using (AsyncDisposableStruct? asyncDisposableStruct = new AsyncDisposableStruct?(default)) + await using (AsyncDisposableStruct? asyncDisposableStruct = default(AsyncDisposableStruct)) { Use(asyncDisposableStruct); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs index 515188dd3a..b8a8e5911b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs @@ -100,6 +100,12 @@ 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); diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 492eeca184..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,12 +585,11 @@ 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 (resource is DefaultValueExpression) + if (!type.IsVar()) { // 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); + // 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)); diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 4d40d998f8..d682da2ebf 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -224,6 +224,15 @@ public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expres 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 @@ -251,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)); @@ -270,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; } @@ -342,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);