From 03e3a17f1b05b65410464a5586be4445b2dca172 Mon Sep 17 00:00:00 2001
From: Christoph Wille <christoph.wille@gmail.com>
Date: Sun, 17 Mar 2024 12:22:11 +0100
Subject: [PATCH 1/3] Update to Roslyn 4.9.2

---
 Directory.Packages.props | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Directory.Packages.props b/Directory.Packages.props
index 2492143b7c..dc161bfe5a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -15,8 +15,8 @@
     <PackageVersion Include="JunitXml.TestLogger" Version="3.1.12" />
     <PackageVersion Include="K4os.Compression.LZ4" Version="1.3.6" />
     <PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.0" />
-    <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
-    <PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.8.0" />
+    <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
+    <PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.9.2" />
     <PackageVersion Include="Microsoft.DiaSymReader.Converter.Xml" Version="1.1.0-beta2-22171-02" />
     <PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" />
     <PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" />

From 9ba47db69b00bbb4e7d101ad8d65be8bb7143e04 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 29 Mar 2024 21:14:18 +0100
Subject: [PATCH 2/3] Add new a.GetValueOrDefault(b) -> a ?? b transform for
 side-effect-free default values.

---
 .../IL/Transforms/ExpressionTransforms.cs     | 12 ++++++++++++
 .../IL/Transforms/NullableLiftingTransform.cs | 19 ++++++++++++++++++-
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
index 7f494cad6b..5385bbaae6 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
@@ -274,6 +274,18 @@ void VisitLogicNot(Comp inst, ILInstruction arg)
 
 		protected internal override void VisitCall(Call inst)
 		{
+			if (NullableLiftingTransform.MatchGetValueOrDefault(inst, out var nullableValue, out var fallback)
+				&& SemanticHelper.IsPure(fallback.Flags))
+			{
+				context.Step("call Nullable{T}.GetValueOrDefault(a, b) -> a ?? b", inst);
+				var ldObj = new LdObj(nullableValue, inst.Method.DeclaringType);
+				var replacement = new NullCoalescingInstruction(NullCoalescingKind.NullableWithValueFallback, ldObj, fallback) {
+					UnderlyingResultType = fallback.ResultType
+				};
+				inst.ReplaceWith(replacement.WithILRange(inst));
+				replacement.AcceptVisitor(this);
+				return;
+			}
 			base.VisitCall(inst);
 			TransformAssignment.HandleCompoundAssign(inst, context);
 		}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
index 44f73e3e47..4e297b00d9 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
@@ -16,7 +16,6 @@
 // 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.Linq;
@@ -1047,6 +1046,24 @@ internal static bool MatchGetValueOrDefault(ILInstruction inst, out ILInstructio
 			return true;
 		}
 
+		/// <summary>
+		/// Matches 'call nullableValue.GetValueOrDefault(fallback)'
+		/// </summary>
+		internal static bool MatchGetValueOrDefault(ILInstruction inst, out ILInstruction nullableValue, out ILInstruction fallback)
+		{
+			nullableValue = null;
+			fallback = null;
+			if (!(inst is Call call))
+				return false;
+			if (call.Method.Name != "GetValueOrDefault" || call.Arguments.Count != 2)
+				return false;
+			if (call.Method.DeclaringTypeDefinition?.KnownTypeCode != KnownTypeCode.NullableOfT)
+				return false;
+			nullableValue = call.Arguments[0];
+			fallback = call.Arguments[1];
+			return true;
+		}
+
 		/// <summary>
 		/// Matches 'call Nullable{T}.GetValueOrDefault(ldloca v)'
 		/// </summary>

From 969e3e546aa603e80c0e8162c0d8a44c1c2f0eb5 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 29 Mar 2024 21:17:51 +0100
Subject: [PATCH 3/3] Add support for switch on (ReadOnly)Span<char> using a
 compiler-generated hash function.

---
 .../IL/ControlFlow/SwitchDetection.cs         |  4 +-
 .../IL/Transforms/SwitchOnStringTransform.cs  | 39 ++++++++++++++-----
 2 files changed, 30 insertions(+), 13 deletions(-)

diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
index 3fb1734daa..dc6bd309c9 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
@@ -16,12 +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.Linq;
 
-using ICSharpCode.Decompiler.CSharp.Transforms;
 using ICSharpCode.Decompiler.FlowAnalysis;
 using ICSharpCode.Decompiler.IL.Transforms;
 using ICSharpCode.Decompiler.TypeSystem;
@@ -361,7 +359,7 @@ private bool UseCSharpSwitch(out KeyValuePair<LongSet, ILInstruction> defaultSec
 		private bool MatchRoslynSwitchOnString()
 		{
 			var insns = analysis.RootBlock.Instructions;
-			return insns.Count >= 3 && SwitchOnStringTransform.MatchComputeStringHashCall(insns[insns.Count - 3], analysis.SwitchVariable, out var switchLdLoc);
+			return insns.Count >= 3 && SwitchOnStringTransform.MatchComputeStringOrReadOnlySpanHashCall(insns[insns.Count - 3], analysis.SwitchVariable, out _);
 		}
 
 		/// <summary>
diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
index d59041381a..e254d243b6 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
@@ -294,6 +294,16 @@ bool AddSwitchSection(string value, ILInstruction inst)
 				keepAssignmentBefore = true;
 				switchValue = new LdLoc(switchValueVar);
 			}
+			if (!switchValueVar.Type.IsKnownType(KnownTypeCode.String))
+			{
+				if (!context.Settings.SwitchOnReadOnlySpanChar)
+					return false;
+				if (!switchValueVar.Type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)
+					&& !switchValueVar.Type.IsKnownType(KnownTypeCode.SpanOfT))
+				{
+					return false;
+				}
+			}
 			// if instruction must be followed by a branch to the next case
 			if (!nextCaseJump.MatchBranch(out Block currentCaseBlock))
 				return false;
@@ -335,7 +345,7 @@ bool AddSwitchSection(string value, ILInstruction inst)
 			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(), context.TypeSystem.FindType(KnownTypeCode.String));
+			var stringToInt = new StringToInt(switchValue, values.Skip(offset).Select(item => item.Item1).ToArray(), switchValueVar.Type);
 			var inst = new SwitchInstruction(stringToInt);
 			inst.Sections.AddRange(sections);
 			if (removeExtraLoad)
@@ -1008,7 +1018,7 @@ bool MatchRoslynSwitchOnString(InstructionCollection<ILInstruction> instructions
 			if (!(switchBlockInstructionsOffset + 1 < switchBlockInstructions.Count
 				&& switchBlockInstructions[switchBlockInstructionsOffset + 1] is SwitchInstruction switchInst
 				&& switchInst.Value.MatchLdLoc(out var switchValueVar)
-				&& MatchComputeStringHashCall(switchBlockInstructions[switchBlockInstructionsOffset],
+				&& MatchComputeStringOrReadOnlySpanHashCall(switchBlockInstructions[switchBlockInstructionsOffset],
 											  switchValueVar, out LdLoc switchValueLoad)))
 			{
 				return false;
@@ -1643,19 +1653,28 @@ bool MatchStringLengthCall(ILInstruction inst, ILVariable switchValueVar)
 		}
 
 		/// <summary>
-		/// Matches 'stloc(targetVar, call ComputeStringHash(ldloc switchValue))'
+		/// Matches
+		/// 'stloc(targetVar, call ComputeStringHash(ldloc switchValue))'
+		/// - or -
+		/// 'stloc(targetVar, call ComputeSpanHash(ldloc switchValue))'
+		/// - or -
+		/// 'stloc(targetVar, call ComputeReadOnlySpanHash(ldloc switchValue))'
 		/// </summary>
-		internal static bool MatchComputeStringHashCall(ILInstruction inst, ILVariable targetVar, out LdLoc switchValue)
+		internal static bool MatchComputeStringOrReadOnlySpanHashCall(ILInstruction inst, ILVariable targetVar, out LdLoc switchValue)
 		{
 			switchValue = null;
 			if (!inst.MatchStLoc(targetVar, out var value))
 				return false;
-			if (!(value is Call c && c.Arguments.Count == 1 && c.Method.Name == "ComputeStringHash" && c.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()))
-				return false;
-			if (!(c.Arguments[0] is LdLoc))
-				return false;
-			switchValue = (LdLoc)c.Arguments[0];
-			return true;
+			if (value is Call c && c.Arguments.Count == 1
+				&& c.Method.Name is "ComputeStringHash" or "ComputeSpanHash" or "ComputeReadOnlySpanHash"
+				&& c.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
+			{
+				if (c.Arguments[0] is not LdLoc ldloc)
+					return false;
+				switchValue = ldloc;
+				return true;
+			}
+			return false;
 		}
 
 		/// <summary>