Browse Source

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
pull/4014/head
Christoph Wille 1 month ago committed by Siegfried Pammer
parent
commit
7bf102f3b4
  1. 2
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs
  2. 6
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs
  3. 13
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  4. 15
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

2
ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncUsing.cs

@ -49,7 +49,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -49,7 +49,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public static async void TestAsyncUsingNullableStruct()
{
await using (AsyncDisposableStruct? asyncDisposableStruct = new AsyncDisposableStruct?(default))
await using (AsyncDisposableStruct? asyncDisposableStruct = default(AsyncDisposableStruct))
{
Use(asyncDisposableStruct);
}

6
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DefaultLiteral.cs

@ -100,6 +100,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -100,6 +100,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return data + default(Data);
}
public bool NullableWithValueStaysTyped()
{
Data? data = default(Data);
return data.HasValue;
}
public string NonIdentityConversionsStayTyped()
{
object obj = default(Data);

13
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -543,11 +543,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -543,11 +543,11 @@ namespace ICSharpCode.Decompiler.CSharp
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;
@ -578,7 +578,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -578,7 +578,7 @@ namespace ICSharpCode.Decompiler.CSharp
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() {
@ -596,12 +596,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -596,12 +596,11 @@ 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)
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));

15
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -224,6 +224,15 @@ namespace ICSharpCode.Decompiler.CSharp @@ -224,6 +224,15 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -251,7 +260,7 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -270,7 +279,7 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -342,7 +351,7 @@ namespace ICSharpCode.Decompiler.CSharp
&& !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);

Loading…
Cancel
Save