Browse Source

Implement support for C# 11 switch on (ReadOnly)Span<char>.

pull/3058/head
Siegfried Pammer 2 years ago
parent
commit
70616b301c
  1. 5
      ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs
  2. 1
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  3. 49
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs
  4. 2
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  5. 2
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  6. 18
      ICSharpCode.Decompiler/DecompilerSettings.cs
  7. 15
      ICSharpCode.Decompiler/IL/Instructions/StringToInt.cs
  8. 209
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
  9. 9
      ILSpy/Properties/Resources.Designer.cs
  10. 3
      ILSpy/Properties/Resources.resx

5
ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs

@ -60,13 +60,16 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -60,13 +60,16 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
var vbcPath = roslynToolset.GetVBCompiler(roslynVersion);
IEnumerable<string> references;
string libPath;
if ((flags & CompilerOptions.UseRoslynMask) != 0 && (flags & CompilerOptions.TargetNet40) == 0)
{
references = coreDefaultReferences.Select(r => "-r:\"" + r + "\"");
libPath = coreRefAsmPath;
}
else
{
references = defaultReferences.Select(r => "-r:\"" + r + "\"");
libPath = RefAsmPath;
}
if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic))
{
@ -116,7 +119,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -116,7 +119,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
}
var command = Cli.Wrap(vbcPath)
.WithArguments($"{otherOptions}{string.Join(" ", references)} -out:\"{Path.GetFullPath(results.PathToAssembly)}\" {string.Join(" ", sourceFileNames.Select(fn => '"' + Path.GetFullPath(fn) + '"'))}")
.WithArguments($"{otherOptions}-libpath:\"{libPath}\" {string.Join(" ", references)} -out:\"{Path.GetFullPath(results.PathToAssembly)}\" {string.Join(" ", sourceFileNames.Select(fn => '"' + Path.GetFullPath(fn) + '"'))}")
.WithValidation(CommandResultValidation.None);
Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}");

1
ICSharpCode.Decompiler.Tests/Helpers/Tester.cs

@ -295,6 +295,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -295,6 +295,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
"System.Linq.Expressions.dll",
"System.Linq.Queryable.dll",
"System.IO.FileSystem.Watcher.dll",
"System.Memory.dll",
"System.Threading.dll",
"System.Threading.Thread.dll",
"System.Runtime.dll",

49
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs

@ -1509,5 +1509,54 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -1509,5 +1509,54 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
break;
}
}
#if CS110 && NET70
public static string SwitchOverReadOnlySpanChar1(ReadOnlySpan<char> text)
{
Console.WriteLine("SwitchOverReadOnlySpanChar1:");
switch (text)
{
case "First case":
return "Text1";
case "Second case":
case "2nd case":
return "Text2";
case "Third case":
return "Text3";
case "Fourth case":
return "Text4";
case "Fifth case":
return "Text5";
case "Sixth case":
return "Text6";
default:
return "Default";
}
}
public static string SwitchOverSpanChar1(Span<char> text)
{
Console.WriteLine("SwitchOverSpanChar1:");
switch (text)
{
case "First case":
return "Text1";
case "Second case":
case "2nd case":
return "Text2";
case "Third case":
return "Text3";
case "Fourth case":
return "Text4";
case "Fifth case":
return "Text5";
case "Sixth case":
return "Text6";
default:
return "Default";
}
}
#endif
}
}

2
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -3898,7 +3898,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -3898,7 +3898,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
value = Translate(strToInt.Argument)
.ConvertTo(
typeSystem.FindType(KnownTypeCode.String),
strToInt.ExpectedType,
this,
allowImplicitConversion: false // switch-expression does not support implicit conversions
);

2
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -214,7 +214,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -214,7 +214,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
value = exprBuilder.Translate(strToInt.Argument)
.ConvertTo(
typeSystem.FindType(KnownTypeCode.String),
strToInt.ExpectedType,
exprBuilder,
// switch statement does support implicit conversions in general, however, the rules are
// not very intuitive and in order to prevent bugs, we emit an explicit cast.

18
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -1206,6 +1206,24 @@ namespace ICSharpCode.Decompiler @@ -1206,6 +1206,24 @@ namespace ICSharpCode.Decompiler
}
}
bool switchOnReadOnlySpanChar = true;
/// <summary>
/// Gets/Sets whether to use C# 11.0 switch on (ReadOnly)Span&lt;char&gt;
/// </summary>
[Category("C# 11.0 / VS 2022.4")]
[Description("DecompilerSettings.SwitchOnReadOnlySpanChar")]
public bool SwitchOnReadOnlySpanChar {
get { return switchOnReadOnlySpanChar; }
set {
if (switchOnReadOnlySpanChar != value)
{
switchOnReadOnlySpanChar = value;
OnPropertyChanged();
}
}
}
bool unsignedRightShift = true;
/// <summary>

15
ICSharpCode.Decompiler/IL/Instructions/StringToInt.cs

@ -20,21 +20,26 @@ @@ -20,21 +20,26 @@
using System.Collections.Generic;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL
{
partial class StringToInt
{
public List<(string? Key, int Value)> Map { get; }
public StringToInt(ILInstruction argument, List<(string? Key, int Value)> map)
public IType ExpectedType { get; }
public StringToInt(ILInstruction argument, List<(string? Key, int Value)> map, IType expectedType)
: base(OpCode.StringToInt)
{
this.Argument = argument;
this.Map = map;
this.ExpectedType = expectedType;
}
public StringToInt(ILInstruction argument, string?[] map)
: this(argument, ArrayToDictionary(map))
public StringToInt(ILInstruction argument, string?[] map, IType expectedType)
: this(argument, ArrayToDictionary(map), expectedType)
{
}
@ -51,7 +56,9 @@ namespace ICSharpCode.Decompiler.IL @@ -51,7 +56,9 @@ namespace ICSharpCode.Decompiler.IL
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
WriteILRange(output, options);
output.Write("string.to.int (");
output.Write("string.to.int ");
ExpectedType.WriteTo(output);
output.Write('(');
Argument.WriteTo(output, options);
output.Write(", { ");
int i = 0;

209
ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

@ -78,7 +78,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -78,7 +78,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
changed = true;
continue;
}
if (MatchRoslynSwitchOnStringUsingLengthAndChar(block.Instructions, ref i))
if (MatchRoslynSwitchOnStringUsingLengthAndChar(block, i))
{
changed = true;
continue;
@ -334,7 +334,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -334,7 +334,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
int offset = firstBlock == null ? 1 : 0;
var sections = new List<SwitchSection>(values.Skip(offset).SelectWithIndex((index, s) => new SwitchSection { Labels = new LongSet(index), Body = s.Item2 is Block b ? new Branch(b) : s.Item2.Clone() }));
sections.Add(new SwitchSection { Labels = new LongSet(new LongInterval(0, sections.Count)).Invert(), Body = currentCaseBlock != null ? (ILInstruction)new Branch(currentCaseBlock) : new Leave((BlockContainer)nextCaseBlock) });
var stringToInt = new StringToInt(switchValue, values.Skip(offset).Select(item => item.Item1).ToArray());
var stringToInt = new StringToInt(switchValue, values.Skip(offset).Select(item => item.Item1).ToArray(), context.TypeSystem.FindType(KnownTypeCode.String));
var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections);
if (removeExtraLoad)
@ -450,7 +450,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -450,7 +450,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var sections = new List<SwitchSection>(values.SelectWithIndex((index, b) => new SwitchSection { Labels = new LongSet(index), Body = b.Item2 }));
sections.Add(new SwitchSection { Labels = new LongSet(new LongInterval(0, sections.Count)).Invert(), Body = new Branch(currentCaseBlock) });
var stringToInt = new StringToInt(switchValue, values.SelectArray(item => item.Item1));
var stringToInt = new StringToInt(switchValue, values.SelectArray(item => item.Item1), context.TypeSystem.FindType(KnownTypeCode.String));
var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections);
@ -671,7 +671,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -671,7 +671,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
switchValue = new LdLoc(switchValueVar);
keepAssignmentBefore = true;
}
var stringToInt = new StringToInt(switchValue, stringValues);
var stringToInt = new StringToInt(switchValue, stringValues, switchValueVar.Type);
var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections);
instructions[i + 1].ReplaceWith(inst);
@ -860,7 +860,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -860,7 +860,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// stloc tmp(ldloc switch-value)
// stloc switchVariable(ldloc tmp)
// if (comp(ldloc tmp == ldnull)) br nullCaseBlock
// br getItemBloc
// br getItemBlock
if (block.Instructions.Count != i + 4)
return false;
if (!block.Instructions[i].MatchStLoc(out var tmp, out var switchValue))
@ -919,7 +919,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -919,7 +919,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
context.Step(nameof(MatchLegacySwitchOnStringWithHashtable), block.Instructions[i]);
var stringToInt = new StringToInt(switchValue, stringValues);
var stringToInt = new StringToInt(switchValue, stringValues, context.TypeSystem.FindType(KnownTypeCode.String));
var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections);
inst.AddILRange(block.Instructions[i]);
@ -998,15 +998,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -998,15 +998,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
switchBlockInstructions = nextBlockJump.TargetBlock.Instructions;
switchBlockInstructionsOffset = 0;
}
// stloc switchValueVar(call ComputeStringHash(switchValue))
// stloc switchValueVar(call ComputeStringHash(switchValueLoad))
// switch (ldloc switchValueVar) {
// case [211455823..211455824): br caseBlock1
// ... more cases ...
// case [long.MinValue..-365098645),...,[1697255802..long.MaxValue]: br defaultBlock
// }
if (!(switchBlockInstructionsOffset + 1 < switchBlockInstructions.Count && switchBlockInstructions[switchBlockInstructionsOffset + 1] is SwitchInstruction switchInst && switchInst.Value.MatchLdLoc(out var switchValueVar) &&
MatchComputeStringHashCall(switchBlockInstructions[switchBlockInstructionsOffset], switchValueVar, out LdLoc switchValueLoad)))
if (!(switchBlockInstructionsOffset + 1 < switchBlockInstructions.Count
&& switchBlockInstructions[switchBlockInstructionsOffset + 1] is SwitchInstruction switchInst
&& switchInst.Value.MatchLdLoc(out var switchValueVar)
&& MatchComputeStringHashCall(switchBlockInstructions[switchBlockInstructionsOffset],
switchValueVar, out LdLoc switchValueLoad)))
{
return false;
}
if (instForNullCheck != null && !instForNullCheck.MatchLdLoc(switchValueLoad.Variable))
{
@ -1137,7 +1142,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1137,7 +1142,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var body = bodyInstruction is Block b ? new Branch(b) : bodyInstruction;
sections[idx] = new SwitchSection { Labels = new LongSet(idx), Body = body };
}
var newSwitch = new SwitchInstruction(new StringToInt(switchValueInst, values));
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 });
instructions[offset].ReplaceWith(newSwitch);
@ -1145,54 +1150,63 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1145,54 +1150,63 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
private bool MatchRoslynSwitchOnStringUsingLengthAndChar(InstructionCollection<ILInstruction> instructions, ref int i)
private bool MatchRoslynSwitchOnStringUsingLengthAndChar(Block block, int i)
{
var instructions = block.Instructions;
// implements https://github.com/dotnet/roslyn/pull/66081
if (i >= instructions.Count - 1)
return false;
// if (comp(ldloc switchValueVar == ldnull)) br nullCase
// br nextBlock
if (!instructions[i].MatchIfInstruction(out var condition, out var exitBlockJump)
|| !condition.MatchCompEqualsNull(out var ldloc)
|| ldloc is not LdLoc { Variable: var switchValueVar })
return false;
if (!instructions[i + 1].MatchBranch(out var defaultCase))
return false;
if (!exitBlockJump.MatchBranch(out var nullCase))
return false;
// if (comp(ldloc switchValueVar == ldnull)) br defaultCase
// br switchOnLengthBlock
Block switchOnLengthBlock;
if (defaultCase.IncomingEdgeCount == 1
&& defaultCase.Instructions[0].MatchIfInstruction(out condition, out exitBlockJump)
&& condition.MatchCompEqualsNull(out ldloc)
&& ldloc.MatchLdLoc(switchValueVar))
int switchOnLengthBlockStartOffset;
Block nullCase = null;
if (instructions[i].MatchIfInstruction(out var condition, out var exitBlockJump)
&& condition.MatchCompEqualsNull(out var ldloc)
&& ldloc is LdLoc { Variable: var switchValueVar })
{
if (!defaultCase.Instructions[1].MatchBranch(out switchOnLengthBlock))
if (!instructions[i + 1].MatchBranch(out var nextBlock))
return false;
if (!exitBlockJump.MatchBranch(out nullCase))
return false;
if (!exitBlockJump.MatchBranch(out defaultCase))
// if (comp(ldloc switchValueVar == ldnull)) br ...
// br switchOnLengthBlock
if (nextBlock.IncomingEdgeCount == 1
&& nextBlock.Instructions[0].MatchIfInstruction(out condition, out _)
&& condition.MatchCompEqualsNull(out ldloc)
&& ldloc.MatchLdLoc(switchValueVar))
{
if (!nextBlock.Instructions[1].MatchBranch(out switchOnLengthBlock))
return false;
}
else
{
switchOnLengthBlock = nextBlock;
}
if (switchOnLengthBlock.IncomingEdgeCount != 1)
return false;
switchOnLengthBlockStartOffset = 0;
}
else
{
switchOnLengthBlock = defaultCase;
defaultCase = nullCase;
switchOnLengthBlock = block;
switchValueVar = null; // will be extracted in MatchSwitchOnLengthBlock
switchOnLengthBlockStartOffset = i;
}
if (!MatchSwitchOnLengthBlock(switchValueVar, switchOnLengthBlock, out var blocksByLength))
Block defaultCase = null;
if (!MatchSwitchOnLengthBlock(ref switchValueVar, switchOnLengthBlock, switchOnLengthBlockStartOffset, out var blocksByLength))
return false;
List<(string, ILInstruction)> stringValues = new();
foreach (var block in blocksByLength)
foreach (var b in blocksByLength)
{
if (block.Length.Count() != 1)
if (b.Length.Count() != 1)
{
if (block.TargetBlock != nullCase)
if (b.TargetBlock != nullCase)
return false;
}
else
{
int length = (int)block.Length.Intervals[0].Start;
if (MatchSwitchOnCharBlock(block.TargetBlock, length, switchValueVar, out var mapping)
|| MatchIfElseOnCharBlock(block.TargetBlock, length, switchValueVar, out mapping))
int length = (int)b.Length.Intervals[0].Start;
if (MatchSwitchOnCharBlock(b.TargetBlock, length, switchValueVar, out var mapping)
|| MatchIfElseOnCharBlock(b.TargetBlock, length, switchValueVar, out mapping))
{
foreach (var item in mapping)
{
@ -1206,9 +1220,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1206,9 +1220,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
else if (MatchRoslynCaseBlockHead(block.TargetBlock, switchValueVar, out var bodyOrLeave, out var exit, out string stringValue, out _))
else if (MatchRoslynCaseBlockHead(b.TargetBlock, switchValueVar, out var bodyOrLeave, out var exit, out string stringValue, out _))
{
if (exit != nullCase)
if (exit != defaultCase)
return false;
if (!stringValues.Any(x => x.Item1 == stringValue))
{
@ -1221,7 +1235,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1221,7 +1235,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
else if (length == 0)
{
stringValues.Add(("", block.TargetBlock));
stringValues.Add(("", b.TargetBlock));
}
else
{
@ -1236,7 +1250,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1236,7 +1250,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
stringValues.Add((null, nullBlock));
}
else if (nullCase != defaultCase)
else if (nullCase != null && nullCase != defaultCase)
{
stringValues.Add((null, nullCase));
}
@ -1252,23 +1266,38 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1252,23 +1266,38 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var body = bodyInstruction is Block b ? new Branch(b) : bodyInstruction;
sections[idx] = new SwitchSection { Labels = new LongSet(idx), Body = body };
}
var newSwitch = new SwitchInstruction(new StringToInt(new LdLoc(switchValueVar), values));
var newSwitch = new SwitchInstruction(new StringToInt(new LdLoc(switchValueVar), values, switchValueVar.Type));
newSwitch.Sections.AddRange(sections);
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = new Branch(defaultCase) });
newSwitch.AddILRange(instructions[i]);
newSwitch.AddILRange(instructions[i + 1]);
instructions[i].ReplaceWith(newSwitch);
instructions.RemoveAt(i + 1);
if (nullCase != null)
{
newSwitch.AddILRange(instructions[i + 1]);
}
instructions[i] = newSwitch;
instructions.RemoveRange(i + 1, instructions.Count - (i + 1));
return true;
bool MatchGetChars(ILInstruction instruction, ILVariable switchValueVar, out int index)
{
index = -1;
return instruction is Call call
&& call.Method.FullNameIs("System.String", "get_Chars")
&& call.Arguments.Count == 2
&& call.Arguments[0].MatchLdLoc(switchValueVar)
&& call.Arguments[1].MatchLdcI4(out index);
if (context.Settings.SwitchOnReadOnlySpanChar && instruction.MatchLdObj(out var target, out var type) && type.IsKnownType(KnownTypeCode.UInt16))
{
return target is Call call
&& (call.Method.FullNameIs("System.ReadOnlySpan", "get_Item")
|| call.Method.FullNameIs("System.Span", "get_Item"))
&& call.Arguments.Count == 2
&& call.Arguments[0].MatchLdLoca(switchValueVar)
&& call.Arguments[1].MatchLdcI4(out index);
}
else
{
return instruction is Call call
&& call.Method.FullNameIs("System.String", "get_Chars")
&& call.Arguments.Count == 2
&& call.Arguments[0].MatchLdLoc(switchValueVar)
&& call.Arguments[1].MatchLdcI4(out index);
}
}
bool MatchSwitchOnCharBlock(Block block, int length, ILVariable switchValueVar, out List<(string StringValue, ILInstruction BodyOrLeave)> results)
@ -1375,7 +1404,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1375,7 +1404,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
break;
if (!MatchRoslynCaseBlockHead(headBlock, switchValueVar, out var bodyOrLeave, out var exit, out var stringValue, out _))
break;
if (exit != nullCase)
if (exit != defaultCase)
return false;
results ??= new();
results.Add((stringValue, bodyOrLeave));
@ -1385,28 +1414,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1385,28 +1414,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
bool MatchSwitchOnLengthBlock(ILVariable switchValueVar, Block switchOnLengthBlock, out List<(LongSet Length, Block TargetBlock)> blocks)
bool MatchSwitchOnLengthBlock(ref ILVariable switchValueVar, Block switchOnLengthBlock, int startOffset, out List<(LongSet Length, Block TargetBlock)> blocks)
{
blocks = null;
if (switchOnLengthBlock.IncomingEdgeCount != 1)
return false;
SwitchInstruction @switch;
ILInstruction getLengthCall;
ILVariable lengthVar;
if (switchOnLengthBlock.FinalInstruction is not Nop)
return false;
switch (switchOnLengthBlock.Instructions.Count)
switch (switchOnLengthBlock.Instructions.Count - startOffset)
{
case 1:
@switch = switchOnLengthBlock.Instructions[0] as SwitchInstruction;
@switch = switchOnLengthBlock.Instructions[startOffset] as SwitchInstruction;
if (@switch == null)
return false;
getLengthCall = @switch.Value;
break;
case 2:
if (!switchOnLengthBlock.Instructions[0].MatchStLoc(out lengthVar, out getLengthCall))
if (!switchOnLengthBlock.Instructions[startOffset].MatchStLoc(out lengthVar, out getLengthCall))
return false;
@switch = switchOnLengthBlock.Instructions[1] as SwitchInstruction;
@switch = switchOnLengthBlock.Instructions[startOffset + 1] as SwitchInstruction;
if (@switch == null)
return false;
if (!@switch.Value.MatchLdLoc(lengthVar))
@ -1414,13 +1439,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1414,13 +1439,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
break;
case 3:
@switch = null;
if (!switchOnLengthBlock.Instructions[0].MatchStLoc(out lengthVar, out getLengthCall))
if (!switchOnLengthBlock.Instructions[startOffset].MatchStLoc(out lengthVar, out getLengthCall))
return false;
if (!switchOnLengthBlock.Instructions[1].MatchIfInstruction(out var cond, out var gotoLength))
if (!switchOnLengthBlock.Instructions[startOffset + 1].MatchIfInstruction(out var cond, out var gotoLength))
return false;
if (!gotoLength.MatchBranch(out var target))
return false;
if (!switchOnLengthBlock.Instructions[2].MatchBranch(out var gotoElse))
if (!switchOnLengthBlock.Instructions[startOffset + 2].MatchBranch(out var gotoElse))
return false;
if (!cond.MatchCompEquals(out var lhs, out var rhs))
{
@ -1430,8 +1455,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1430,8 +1455,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
target = gotoElse;
gotoElse = t;
}
if (gotoElse != defaultCase)
return false;
defaultCase = gotoElse;
if (!lhs.MatchLdLoc(lengthVar) || !rhs.MatchLdcI4(out int length))
return false;
@ -1445,12 +1469,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1445,12 +1469,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
if (getLengthCall is not Call call
|| call.Arguments.Count != 1
|| !call.Method.FullNameIs("System.String", "get_Length"))
|| call.Method.Name != "get_Length")
{
return false;
}
if (!call.Arguments[0].MatchLdLoc(switchValueVar))
return false;
var declaringTypeCode = call.Method.DeclaringTypeDefinition?.KnownTypeCode;
switch (declaringTypeCode)
{
case KnownTypeCode.String:
if (!call.Arguments[0].MatchLdLoc(switchValueVar))
return false;
break;
case KnownTypeCode.ReadOnlySpanOfT:
case KnownTypeCode.SpanOfT:
if (!context.Settings.SwitchOnReadOnlySpanChar)
return false;
if (!call.Arguments[0].MatchLdLoca(out switchValueVar))
return false;
break;
default:
return false;
}
if (@switch == null)
return true;
blocks = new(@switch.Sections.Count);
@ -1462,7 +1501,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1462,7 +1501,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (section.Labels.Count() != 1)
{
if (!section.Body.MatchBranch(defaultCase))
defaultCase ??= target;
if (defaultCase != target)
return false;
}
else
@ -1644,8 +1684,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1644,8 +1684,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
/// <summary>
/// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))'
/// Matches 'call string.op_Equality(ldloc(variable), ldstr stringValue)'
/// or 'comp(ldloc(variable) == ldnull)'
/// or 'call SequenceEqual(ldloc variable, call AsSpan(ldstr stringValue))'
/// </summary>
bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue, out bool isVBCompareString)
{
@ -1653,8 +1694,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1653,8 +1694,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
/// <summary>
/// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))'
/// Matches 'call string.op_Equality(ldloc(variable), ldstr stringValue)'
/// or 'comp(ldloc(variable) == ldnull)'
/// or 'call SequenceEqual(ldloc variable, call AsSpan(ldstr stringValue))'
/// </summary>
bool MatchStringEqualityComparison(ILInstruction condition, out ILVariable variable, out string stringValue, out bool isVBCompareString)
{
@ -1676,8 +1718,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1676,8 +1718,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
right = c.Arguments[1];
}
else if (c.Method.IsStatic && c.Method.Name == "CompareString"
&& c.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators"
&& c.Arguments.Count == 3)
&& c.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators"
&& c.Arguments.Count == 3)
{
left = c.Arguments[0];
right = c.Arguments[1];
@ -1690,6 +1732,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1690,6 +1732,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
}
else if (c.Method.IsStatic && c.Method.Name == "SequenceEqual"
&& c.Method.DeclaringType.FullName == "System.MemoryExtensions"
&& c.Arguments.Count == 2)
{
left = c.Arguments[0];
if (c.Arguments[1] is Call {
Method.IsStatic: true,
Method.Name: "AsSpan",
Method.DeclaringType.FullName: "System.MemoryExtensions",
Arguments: [var ldStr]
} asSpanCall)
{
right = ldStr;
}
else
{
return false;
}
}
else
{
return false;

9
ILSpy/Properties/Resources.Designer.cs generated

@ -1316,6 +1316,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1316,6 +1316,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Use switch on (ReadOnly)Span&lt;char&gt;.
/// </summary>
public static string DecompilerSettings_SwitchOnReadOnlySpanChar {
get {
return ResourceManager.GetString("DecompilerSettings.SwitchOnReadOnlySpanChar", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unsigned right shift (&gt;&gt;&gt;).
/// </summary>

3
ILSpy/Properties/Resources.resx

@ -462,6 +462,9 @@ Are you sure you want to continue?</value> @@ -462,6 +462,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.SwitchExpressions" xml:space="preserve">
<value>Switch expressions</value>
</data>
<data name="DecompilerSettings.SwitchOnReadOnlySpanChar" xml:space="preserve">
<value>Use switch on (ReadOnly)Span&lt;char&gt;</value>
</data>
<data name="DecompilerSettings.UnsignedRightShift" xml:space="preserve">
<value>Unsigned right shift (&gt;&gt;&gt;)</value>
</data>

Loading…
Cancel
Save