Skip to content

Commit 63757ea

Browse files
Fix #3382: Support compiler-generated throw-helper invocations in switch-expression implicit default-case.
Assisted-by: Claude:claude-fable-5:Claude Code
1 parent c6ec2c5 commit 63757ea

6 files changed

Lines changed: 384 additions & 11 deletions

File tree

ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,20 @@ public void AllFilesHaveTests()
141141
CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest,
142142
});
143143

144+
static readonly CompilerOptions[] roslyn3OrNewerWithNet40Roslyn4Options = Tester.SupportedOnCurrentPlatform(new[]
145+
{
146+
CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40,
147+
CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40,
148+
CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40,
149+
CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40,
150+
CompilerOptions.UseRoslyn3_11_0,
151+
CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0,
152+
CompilerOptions.UseRoslyn4_14_0,
153+
CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0,
154+
CompilerOptions.UseRoslynLatest,
155+
CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest,
156+
});
157+
144158
static readonly CompilerOptions[] roslyn3OrNewerOptions = Tester.SupportedOnCurrentPlatform(new[]
145159
{
146160
CompilerOptions.UseRoslyn3_11_0,
@@ -291,8 +305,14 @@ await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
291305
});
292306
}
293307

308+
// Runs on Roslyn 3.x and Roslyn 4.x or newer, with TargetNet40 variants only for
309+
// Roslyn 4.x or newer: targeting net40 makes the compiler emit the
310+
// ThrowInvalidOperationException throw helper (SwitchExpressionException does not
311+
// exist there), but Roslyn 3.x emits a plain inline
312+
// "throw new InvalidOperationException()" instead, which is indistinguishable from
313+
// user code and therefore intentionally not transformed.
294314
[Test]
295-
public async Task SwitchExpressions([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions)
315+
public async Task SwitchExpressions([ValueSource(nameof(roslyn3OrNewerWithNet40Roslyn4Options))] CompilerOptions cscOptions)
296316
{
297317
await RunForLibrary(cscOptions: cscOptions);
298318
}

ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,5 +159,175 @@ public static string Issue2222()
159159
_ => "default",
160160
};
161161
}
162+
#pragma warning disable CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive).
163+
public static int Issue3382(StringComparison c)
164+
{
165+
return c switch {
166+
StringComparison.Ordinal => 0,
167+
StringComparison.OrdinalIgnoreCase => 1,
168+
};
169+
}
170+
171+
public static void Issue3382b(ref StringComparison? c)
172+
{
173+
#if NET40
174+
c = c switch {
175+
null => StringComparison.Ordinal,
176+
StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase,
177+
StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture,
178+
};
179+
#else
180+
StringComparison? stringComparison = c;
181+
c = stringComparison switch {
182+
null => StringComparison.Ordinal,
183+
StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase,
184+
StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture,
185+
};
186+
#endif
187+
}
188+
189+
public static void Issue3382c(StringComparison? c)
190+
{
191+
c = c switch {
192+
null => StringComparison.Ordinal,
193+
StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase,
194+
StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture,
195+
};
196+
}
197+
198+
public static void Issue3382d(ref StringComparison c)
199+
{
200+
#if NET40
201+
c = c switch {
202+
StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase,
203+
StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture,
204+
};
205+
#else
206+
StringComparison stringComparison = c;
207+
c = stringComparison switch {
208+
StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase,
209+
StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture,
210+
};
211+
#endif
212+
}
213+
214+
public static int SwitchOnStringImplicitDefault(string s)
215+
{
216+
return s switch {
217+
"Hello" => 42,
218+
"World" => 4711,
219+
"!" => 7,
220+
"Foo" => 13,
221+
"Bar" => 21,
222+
"Baz" => 84,
223+
"Qux" => 168,
224+
"Quux" => 336,
225+
"Corge" => 672,
226+
"Grault" => 1344,
227+
"Garply" => 2688,
228+
};
229+
}
230+
public static int SwitchOnStringImplicitDefaultFewCases(string s)
231+
{
232+
return s switch {
233+
"red" => 1,
234+
"green" => 2,
235+
"blue" => 3,
236+
};
237+
}
238+
239+
public static int SwitchOnStringImplicitDefaultUniqueLengths(string s)
240+
{
241+
return s switch {
242+
"a" => 1,
243+
"bb" => 2,
244+
"ccc" => 3,
245+
"dddd" => 4,
246+
"eeeee" => 5,
247+
"ffffff" => 6,
248+
"ggggggg" => 7,
249+
"hhhhhhhh" => 8,
250+
"iiiiiiiii" => 9,
251+
};
252+
}
253+
254+
public static int SwitchOnStringImplicitDefaultSameLength(string s)
255+
{
256+
return s switch {
257+
"aabb" => 1,
258+
"abab" => 2,
259+
"abba" => 3,
260+
"baab" => 4,
261+
"baba" => 5,
262+
"bbaa" => 6,
263+
"bbbb" => 7,
264+
"aaab" => 8,
265+
"aaba" => 9,
266+
};
267+
}
268+
269+
public static int SwitchOnStringImplicitDefaultWithNullCase(string s)
270+
{
271+
return s switch {
272+
"a" => 1,
273+
"bb" => 2,
274+
"ccc" => 3,
275+
"dddd" => 4,
276+
"eeeee" => 5,
277+
"ffffff" => 6,
278+
"ggggggg" => 7,
279+
"hhhhhhhh" => 8,
280+
"iiiiiiiii" => 9,
281+
null => -1,
282+
};
283+
}
284+
285+
public static int SwitchOnStringImplicitDefaultTwoCases(string s)
286+
{
287+
return s switch {
288+
"red" => 1,
289+
"green" => 2,
290+
};
291+
}
292+
293+
public static string SwitchOnCharImplicitDefault(char c)
294+
{
295+
return c switch {
296+
'a' => "first",
297+
'b' => "second",
298+
'c' => "third",
299+
};
300+
}
301+
302+
public static string SwitchOnLongImplicitDefault(long l)
303+
{
304+
return l switch {
305+
1L => "one",
306+
100L => "hundred",
307+
10000L => "ten thousand",
308+
long.MaxValue => "max",
309+
};
310+
}
311+
312+
public static void SwitchExpressionAsArgumentImplicitDefault(int i)
313+
{
314+
Console.WriteLine(i switch {
315+
0 => "zero",
316+
5 => "five",
317+
10 => "ten",
318+
});
319+
}
320+
321+
public static int SwitchExpressionNestedImplicitDefault(StringComparison c, int i)
322+
{
323+
return c switch {
324+
StringComparison.Ordinal => i switch {
325+
0 => 1,
326+
1 => 2,
327+
},
328+
StringComparison.OrdinalIgnoreCase => 3,
329+
};
330+
}
331+
#pragma warning restore CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive).
162332
}
163333
}

ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4135,10 +4135,13 @@ protected internal override TranslatedExpression VisitSwitchInstruction(SwitchIn
41354135
switchExpr.SwitchSections.Add(ses);
41364136
}
41374137

4138-
var defaultSES = new SwitchExpressionSection();
4139-
defaultSES.Pattern = new IdentifierExpression("_");
4140-
defaultSES.Body = TranslateSectionBody(defaultSection);
4141-
switchExpr.SwitchSections.Add(defaultSES);
4138+
if (!defaultSection.IsCompilerGeneratedDefaultSection)
4139+
{
4140+
var defaultSES = new SwitchExpressionSection();
4141+
defaultSES.Pattern = new IdentifierExpression("_");
4142+
defaultSES.Body = TranslateSectionBody(defaultSection);
4143+
switchExpr.SwitchSections.Add(defaultSES);
4144+
}
41424145

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

0 commit comments

Comments
 (0)