Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,59 @@ public static string Issue2222()
_ => "default",
};
}
#pragma warning disable CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive).
public static int Issue3382(StringComparison c)
{
return c switch {
StringComparison.Ordinal => 0,
StringComparison.OrdinalIgnoreCase => 1,
};
}

public static void Issue3382b(ref StringComparison? c)
{
StringComparison? stringComparison = c;
c = stringComparison switch {
null => StringComparison.Ordinal,
StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase,
StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture,
};
}

public static void Issue3382c(StringComparison? c)
{
c = c switch {
null => StringComparison.Ordinal,
StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase,
StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture,
};
}

public static void Issue3382d(ref StringComparison c)
{
StringComparison stringComparison = c;
c = stringComparison switch {
StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase,
StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture,
};
}

public static int SwitchOnStringImplicitDefault(string s)
{
return s switch {
"Hello" => 42,
"World" => 4711,
"!" => 7,
"Foo" => 13,
"Bar" => 21,
"Baz" => 84,
"Qux" => 168,
"Quux" => 336,
"Corge" => 672,
"Grault" => 1344,
"Garply" => 2688,
};
}
#pragma warning restore CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive).
}
}
11 changes: 7 additions & 4 deletions ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4135,10 +4135,13 @@ protected internal override TranslatedExpression VisitSwitchInstruction(SwitchIn
switchExpr.SwitchSections.Add(ses);
}

var defaultSES = new SwitchExpressionSection();
defaultSES.Pattern = new IdentifierExpression("_");
defaultSES.Body = TranslateSectionBody(defaultSection);
switchExpr.SwitchSections.Add(defaultSES);
if (!defaultSection.IsCompilerGeneratedDefaultSection)
{
var defaultSES = new SwitchExpressionSection();
defaultSES.Pattern = new IdentifierExpression("_");
defaultSES.Body = TranslateSectionBody(defaultSection);
switchExpr.SwitchSections.Add(defaultSES);
}

return switchExpr.WithILInstruction(inst).WithRR(new ResolveResult(resultType));

Expand Down
112 changes: 111 additions & 1 deletion ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

using ICSharpCode.Decompiler.FlowAnalysis;
Expand Down Expand Up @@ -216,12 +218,118 @@ void ProcessBlock(Block block, ref bool blockContainerNeedsCleanup)
// (1st pass was in ControlFlowSimplification)
SimplifySwitchInstruction(block, context);
}

InlineSwitchExpressionDefaultCaseThrowHelper(block, context);
}

// call ThrowInvalidOperationException(...)
// leave IL_0000 (ldloc temp)
//
// -or-
//
// call ThrowSwitchExpressionException(...)
// leave IL_0000 (ldloc temp)
//
// -to-
//
// throw(newobj SwitchExpressionException(...))
internal static void InlineSwitchExpressionDefaultCaseThrowHelper(Block block, ILTransformContext context)
{
#nullable enable
// due to our of of basic blocks at this point,
// switch instructions can only appear as last instruction
var sw = block.Instructions.LastOrDefault() as SwitchInstruction;
if (sw == null)
return;

IMethod[] exceptionCtorTable = new IMethod[2];

exceptionCtorTable[0] = FindConstructor("System.InvalidOperationException")!;
exceptionCtorTable[1] = FindConstructor("System.Runtime.CompilerServices.SwitchExpressionException", typeof(object))!;

if (exceptionCtorTable[0] == null && exceptionCtorTable[1] == null)
return;

if (sw.GetDefaultSection() is not { Body: Branch { TargetBlock: Block defaultBlock } } defaultSection)
return;
if (defaultBlock is { Instructions: [var call, Branch or Leave] })
{
if (!MatchThrowHelperCall(call, out IMethod? exceptionCtor, out ILInstruction? value))
return;
context.Step("SwitchExpressionDefaultCaseTransform", block.Instructions[0]);
var newObj = new NewObj(exceptionCtor);
if (value != null)
newObj.Arguments.Add(value);
defaultBlock.Instructions[0] = new Throw(newObj).WithILRange(defaultBlock.Instructions[0]).WithILRange(defaultBlock.Instructions[1]);
defaultBlock.Instructions.RemoveAt(1);
defaultSection.IsCompilerGeneratedDefaultSection = true;
}
else if (defaultBlock is { Instructions: [Throw { Argument: NewObj { Method: var ctor, Arguments: [var arg] } newObj }] }
&& ctor.Equals(exceptionCtorTable[1]))
{
defaultSection.IsCompilerGeneratedDefaultSection = true;
}
else
{
return;
}

bool MatchThrowHelperCall(ILInstruction inst, [NotNullWhen(true)] out IMethod? exceptionCtor, out ILInstruction? value)
{
exceptionCtor = null;
value = null;
if (inst is not Call call)
return false;
if (call.Method.DeclaringType.FullName != "<PrivateImplementationDetails>")
return false;
switch (call.Arguments.Count)
{
case 0:
if (call.Method.Name != "ThrowInvalidOperationException")
return false;
exceptionCtor = exceptionCtorTable[0];
break;
case 1:
if (call.Method.Name != "ThrowSwitchExpressionException")
return false;
exceptionCtor = exceptionCtorTable[1];
value = call.Arguments[0];
break;
default:
return false;
}
return exceptionCtor != null;
}

IMethod? FindConstructor(string fullTypeName, params Type[] argumentTypes)
{
IType exceptionType = context.TypeSystem.FindType(new FullTypeName(fullTypeName));
var types = argumentTypes.SelectArray(context.TypeSystem.FindType);

foreach (var ctor in exceptionType.GetConstructors(m => !m.IsStatic && m.Parameters.Count == argumentTypes.Length))
{
bool found = true;
foreach (var pair in ctor.Parameters.Select(p => p.Type).Zip(types))
{
if (!NormalizeTypeVisitor.IgnoreNullability.EquivalentTypes(pair.Item1, pair.Item2))
{
found = false;
break;
}
}
if (found)
return ctor;
}

return null;
}
#nullable restore
}

internal static void SimplifySwitchInstruction(Block block, ILTransformContext context)
{
// due to our of of basic blocks at this point,
// switch instructions can only appear as last insturction
// switch instructions can only appear as last instruction
var sw = block.Instructions.LastOrDefault() as SwitchInstruction;
if (sw == null)
return;
Expand Down Expand Up @@ -445,7 +553,9 @@ private void AddNullCase(List<ControlFlowNode> flowNodes, List<ControlFlowNode>
!nullableBlock.Instructions.SecondToLastOrDefault().MatchIfInstruction(out var cond, out var trueInst) ||
!cond.MatchLogicNot(out var getHasValue) ||
!NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILInstruction nullableInst))
{
return;
}

// could check that nullableInst is ldloc or ldloca and that the switch variable matches a GetValueOrDefault
// but the effect of adding an incorrect block to the flowBlock list would only be disasterous if it branched directly
Expand Down
8 changes: 8 additions & 0 deletions ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ public SwitchSection()
/// </summary>
public bool HasNullLabel { get; set; }

/// <summary>
/// If true, this section only contains a compiler-generated throw helper
/// used in a switch expression and will not be visible in the decompiled source code.
/// </summary>
public bool IsCompilerGeneratedDefaultSection { get; set; }

/// <summary>
/// The set of labels that cause execution to jump to this switch section.
/// </summary>
Expand All @@ -221,6 +227,8 @@ public override InstructionFlags DirectFlags {
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
WriteILRange(output, options);
if (IsCompilerGeneratedDefaultSection)
output.Write("generated.");
output.WriteLocalReference("case", this, isDefinition: true);
output.Write(' ');
if (HasNullLabel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1169,7 +1169,11 @@ SwitchInstruction ReplaceWithSwitchInstruction(int offset)
}
var newSwitch = new SwitchInstruction(new StringToInt(switchValueInst, values, switchValueLoad.Variable.Type));
newSwitch.Sections.AddRange(sections);
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultSection.Body });
newSwitch.Sections.Add(new SwitchSection {
Labels = defaultLabel,
Body = defaultSection.Body,
IsCompilerGeneratedDefaultSection = defaultSection.IsCompilerGeneratedDefaultSection
});
instructions[offset].ReplaceWith(newSwitch);
return newSwitch;
}
Expand Down Expand Up @@ -1310,6 +1314,7 @@ private bool MatchRoslynSwitchOnStringUsingLengthAndChar(Block block, int i)
}
instructions[i] = newSwitch;
instructions.RemoveRange(i + 1, instructions.Count - (i + 1));
SwitchDetection.InlineSwitchExpressionDefaultCaseThrowHelper(block, context);
return true;

bool MatchGetChars(ILInstruction instruction, ILVariable switchValueVar, out int index)
Expand Down
Loading