diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 49a2317ff..506f3278a 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -106,6 +106,7 @@ namespace ICSharpCode.Decompiler.CSharp
// Pretty much all transforms that open up new expression inlining
// opportunities belong in this category.
new ExpressionTransforms(),
+ new NullCoalescingTransform(),
new TransformArrayInitializers(),
new TransformCollectionAndObjectInitializers(),
new ILInlining()
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 779c8a263..3d81ef64c 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -1592,7 +1592,28 @@ namespace ICSharpCode.Decompiler.CSharp
.WithILInstruction(block)
.WithRR(resolver.WithCheckForOverflow(inst.CheckForOverflow).ResolveUnaryOperator(op, target.ResolveResult));
}
-
+
+ protected internal override TranslatedExpression VisitNullCoalescingInstruction(NullCoalescingInstruction inst, TranslationContext context)
+ {
+ var value = Translate(inst.ValueInst);
+ var fallback = Translate(inst.FallbackInst);
+ var rr = resolver.ResolveBinaryOperator(BinaryOperatorType.NullCoalescing, value.ResolveResult, fallback.ResolveResult);
+ if (rr.IsError) {
+ IType targetType;
+ if (!value.Type.Equals(SpecialType.NullType) && !fallback.Type.Equals(SpecialType.NullType) && !value.Type.Equals(fallback.Type)) {
+ targetType = compilation.FindType(inst.ResultType.ToKnownTypeCode());
+ } else {
+ targetType = value.Type.Equals(SpecialType.NullType) ? fallback.Type : value.Type;
+ }
+ value = value.ConvertTo(targetType, this);
+ fallback = fallback.ConvertTo(targetType, this);
+ rr = new ResolveResult(targetType);
+ }
+ return new BinaryOperatorExpression(value, BinaryOperatorType.NullCoalescing, fallback)
+ .WithILInstruction(inst)
+ .WithRR(rr);
+ }
+
protected internal override TranslatedExpression VisitIfInstruction(IfInstruction inst, TranslationContext context)
{
var condition = TranslateCondition(inst.Condition);
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index ada11e794..5d2520d31 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -283,8 +283,10 @@
+
+
diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs
index cb5569716..6a426461b 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions.cs
@@ -59,6 +59,8 @@ namespace ICSharpCode.Decompiler.IL
Leave,
/// If statement / conditional expression. if (condition) trueExpr else falseExpr
IfInstruction,
+ /// Null coalescing operator expression. if.notnull(valueInst, fallbackInst)
+ NullCoalescingInstruction,
/// Switch statement
SwitchInstruction,
/// Switch section within a switch statement
@@ -836,7 +838,6 @@ namespace ICSharpCode.Decompiler.IL
{
}
public override StackType ResultType { get { return StackType.I4; } }
-
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitLogicNot(this);
@@ -1185,6 +1186,94 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
+{
+ /// Null coalescing operator expression. if.notnull(valueInst, fallbackInst)
+ public sealed partial class NullCoalescingInstruction : ILInstruction
+ {
+ public static readonly SlotInfo ValueInstSlot = new SlotInfo("ValueInst");
+ ILInstruction valueInst;
+ public ILInstruction ValueInst {
+ get { return this.valueInst; }
+ set {
+ ValidateChild(value);
+ SetChildInstruction(ref this.valueInst, value, 0);
+ }
+ }
+ public static readonly SlotInfo FallbackInstSlot = new SlotInfo("FallbackInst");
+ ILInstruction fallbackInst;
+ public ILInstruction FallbackInst {
+ get { return this.fallbackInst; }
+ set {
+ ValidateChild(value);
+ SetChildInstruction(ref this.fallbackInst, value, 1);
+ }
+ }
+ protected sealed override int GetChildCount()
+ {
+ return 2;
+ }
+ protected sealed override ILInstruction GetChild(int index)
+ {
+ switch (index) {
+ case 0:
+ return this.valueInst;
+ case 1:
+ return this.fallbackInst;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ protected sealed override void SetChild(int index, ILInstruction value)
+ {
+ switch (index) {
+ case 0:
+ this.ValueInst = value;
+ break;
+ case 1:
+ this.FallbackInst = value;
+ break;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ protected sealed override SlotInfo GetChildSlot(int index)
+ {
+ switch (index) {
+ case 0:
+ return ValueInstSlot;
+ case 1:
+ return FallbackInstSlot;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ public sealed override ILInstruction Clone()
+ {
+ var clone = (NullCoalescingInstruction)ShallowClone();
+ clone.ValueInst = this.valueInst.Clone();
+ clone.FallbackInst = this.fallbackInst.Clone();
+ return clone;
+ }
+ public override void AcceptVisitor(ILVisitor visitor)
+ {
+ visitor.VisitNullCoalescingInstruction(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor)
+ {
+ return visitor.VisitNullCoalescingInstruction(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor, C context)
+ {
+ return visitor.VisitNullCoalescingInstruction(this, context);
+ }
+ protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
+ {
+ var o = other as NullCoalescingInstruction;
+ return o != null && this.valueInst.PerformMatch(o.valueInst, ref match) && this.fallbackInst.PerformMatch(o.fallbackInst, ref match);
+ }
+ }
+}
+namespace ICSharpCode.Decompiler.IL
{
/// Switch statement
public sealed partial class SwitchInstruction : ILInstruction
@@ -4020,6 +4109,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
+ protected internal virtual void VisitNullCoalescingInstruction(NullCoalescingInstruction inst)
+ {
+ Default(inst);
+ }
protected internal virtual void VisitSwitchInstruction(SwitchInstruction inst)
{
Default(inst);
@@ -4290,6 +4383,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
+ protected internal virtual T VisitNullCoalescingInstruction(NullCoalescingInstruction inst)
+ {
+ return Default(inst);
+ }
protected internal virtual T VisitSwitchInstruction(SwitchInstruction inst)
{
return Default(inst);
@@ -4560,6 +4657,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
+ protected internal virtual T VisitNullCoalescingInstruction(NullCoalescingInstruction inst, C context)
+ {
+ return Default(inst, context);
+ }
protected internal virtual T VisitSwitchInstruction(SwitchInstruction inst, C context)
{
return Default(inst, context);
@@ -4780,6 +4881,7 @@ namespace ICSharpCode.Decompiler.IL
"br",
"leave",
"if",
+ "if.notnull",
"switch",
"switch.section",
"try.catch",
diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt
index a735d806e..be5cbf167 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.tt
+++ b/ICSharpCode.Decompiler/IL/Instructions.tt
@@ -87,6 +87,12 @@
new ChildInfo("trueInst"),
new ChildInfo("falseInst"),
}), CustomConstructor, CustomComputeFlags, CustomWriteTo),
+ new OpCode("if.notnull", "Null coalescing operator expression. if.notnull(valueInst, fallbackInst)",
+ CustomClassName("NullCoalescingInstruction"),
+ CustomChildren(new []{
+ new ChildInfo("valueInst"),
+ new ChildInfo("fallbackInst"),
+ }), CustomConstructor, CustomComputeFlags, CustomWriteTo),
new OpCode("switch", "Switch statement",
CustomClassName("SwitchInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo, ResultType("Void"),
MatchCondition("Value.PerformMatch(o.Value, ref match) && DefaultBody.PerformMatch(o.DefaultBody, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match)")),
diff --git a/ICSharpCode.Decompiler/IL/Instructions/NullCoalescingInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/NullCoalescingInstruction.cs
new file mode 100644
index 000000000..415c8c7dd
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Instructions/NullCoalescingInstruction.cs
@@ -0,0 +1,69 @@
+// Copyright (c) 2017 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.Collections.Generic;
+using System.Threading;
+using ICSharpCode.Decompiler.IL.Transforms;
+using Mono.Cecil;
+using ICSharpCode.Decompiler.Disassembler;
+using System.Linq;
+using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.Util;
+using System.Diagnostics;
+
+namespace ICSharpCode.Decompiler.IL
+{
+ /// Null coalescing operator expression. if.notnull(valueInst, fallbackInst)
+ partial class NullCoalescingInstruction
+ {
+ public NullCoalescingInstruction(ILInstruction valueInst, ILInstruction fallbackInst) : base(OpCode.NullCoalescingInstruction)
+ {
+ this.ValueInst = valueInst;
+ this.FallbackInst = fallbackInst;
+ }
+
+ public override StackType ResultType {
+ get {
+ return CommonResultType(valueInst.ResultType, fallbackInst.ResultType);
+ }
+ }
+
+ public override InstructionFlags DirectFlags {
+ get {
+ return InstructionFlags.ControlFlow;
+ }
+ }
+
+ protected override InstructionFlags ComputeFlags()
+ {
+ return InstructionFlags.ControlFlow | SemanticHelper.CombineBranches(valueInst.Flags, fallbackInst.Flags);
+ }
+
+ public override void WriteTo(ITextOutput output)
+ {
+ output.Write(OpCode);
+ output.Write(" (");
+ valueInst.WriteTo(output);
+ output.Write(", ");
+ fallbackInst.WriteTo(output);
+ output.Write(")");
+
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs
new file mode 100644
index 000000000..097945580
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs
@@ -0,0 +1,64 @@
+// Copyright (c) 2017 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.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ICSharpCode.Decompiler.IL.Transforms
+{
+ class NullCoalescingTransform : IBlockTransform
+ {
+ BlockTransformContext context;
+
+ void IBlockTransform.Run(Block block, BlockTransformContext context)
+ {
+ this.context = context;
+ for (int i = block.Instructions.Count - 1; i >= 0; i--) {
+ if (TransformNullCoalescing(block, i)) {
+ block.Instructions.RemoveAt(i);
+ continue;
+ }
+ }
+ }
+
+ ///
+ /// stloc s(valueInst)
+ /// if (comp(ldloc s == ldnull)) {
+ /// stloc s(fallbackInst)
+ /// }
+ /// =>
+ /// stloc s(if.notnull(valueInst, fallbackInst)
+ ///
+ bool TransformNullCoalescing(Block block, int i)
+ {
+ if (i == 0) return false;
+ if (!(block.Instructions[i] is IfInstruction ifInstruction) || !(block.Instructions[i - 1] is StLoc stloc) || stloc.Variable.Kind != VariableKind.StackSlot)
+ return false;
+ if (!ifInstruction.Condition.MatchCompEquals(out var left, out var right) || !left.MatchLdLoc(stloc.Variable) || !right.MatchLdNull())
+ return false;
+ if (!ifInstruction.FalseInst.MatchNop() || !(ifInstruction.TrueInst is Block b) || b.Instructions.Count != 1 || !(b.Instructions[0] is StLoc fallbackStore) || fallbackStore.Variable != stloc.Variable)
+ return false;
+ context.Step("TransformNullCoalescing", stloc);
+ stloc.Value.ReplaceWith(new NullCoalescingInstruction(stloc.Value.Clone(), fallbackStore.Value.Clone()));
+ return true;
+ }
+ }
+}