diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 6f3f1cbac..2ca023b03 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -265,6 +265,25 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitIsInst(IsInst inst, TranslationContext context) { var arg = Translate(inst.Argument); + if (inst.Type.IsReferenceType == false) { + // isinst with a value type results in an expression of "boxed value type", + // which is not supported in C#. + // Note that several other instructions special-case isinst arguments: + // unbox.any T(isinst T(expr)) ==> "expr as T" for nullable value types + // comp(isinst T(expr) != null) ==> "expr is T" + // on block level (StatementBuilder.VisitIsInst) => "expr is T" + if (SemanticHelper.IsPure(inst.Argument.Flags)) { + // We can emulate isinst using + // expr is T ? expr : null + return new ConditionalExpression( + new IsExpression(arg, ConvertType(inst.Type)).WithILInstruction(inst), + arg.Expression.Clone(), + new NullReferenceExpression() + ).WithoutILInstruction().WithRR(new ResolveResult(arg.Type)); + } else { + return ErrorExpression("isinst with value type is only supported in some contexts"); + } + } arg = UnwrapBoxingConversion(arg); return new AsExpression(arg.Expression, ConvertType(inst.Type)) .WithILInstruction(inst) @@ -1948,12 +1967,17 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitUnboxAny(UnboxAny inst, TranslationContext context) { - var arg = Translate(inst.Argument); - if (arg.Type.Equals(inst.Type) && inst.Argument.OpCode == OpCode.IsInst) { - // isinst followed by unbox.any of the same type is used for as-casts to generic types - return arg.WithILInstruction(inst); + TranslatedExpression arg; + if (inst.Argument is IsInst isInst && inst.Type.Equals(isInst.Type)) { + // unbox.any T(isinst T(expr)) ==> expr as T + // This is used for generic types and nullable value types + arg = UnwrapBoxingConversion(Translate(isInst.Argument)); + return new AsExpression(arg, ConvertType(inst.Type)) + .WithILInstruction(inst) + .WithRR(new ConversionResolveResult(inst.Type, arg.ResolveResult, Conversion.TryCast)); } + arg = Translate(inst.Argument); IType targetType = inst.Type; if (targetType.Kind == TypeKind.TypeParameter) { var rr = resolver.ResolveCast(targetType, arg.ResolveResult); diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 2a8b12a24..64282f5a2 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -66,6 +66,22 @@ namespace ICSharpCode.Decompiler.CSharp return new ExpressionStatement(exprBuilder.Translate(inst)); } + protected internal override Statement VisitIsInst(IsInst inst) + { + // isinst on top-level (unused result) can be translated in general + // (even for value types) by using "is" instead of "as" + // This can happen when the result of "expr is T" is unused + // and the C# compiler optimizes away the null check portion of the "is" operator. + return new ExpressionStatement( + new IsExpression( + exprBuilder.Translate(inst.Argument), + exprBuilder.ConvertType(inst.Type) + ) + .WithRR(new ResolveResult(exprBuilder.compilation.FindType(KnownTypeCode.Boolean))) + .WithILInstruction(inst) + ); + } + protected internal override Statement VisitStLoc(StLoc inst) { var expr = exprBuilder.Translate(inst);