Skip to content

Commit aff9649

Browse files
authored
Parenthesize interpolations containing global:: (#3463)
* Parenthesize interpolations containing global:: * Improvements: * Cleaner output * More unit testing * More efficient tree search * Implement revisions * Update Lambda1 to be invariant * Visit descendents before deciding whether or not to parenthesize an interpolation expression * Rename local function * Remove branch for conditional expressions * Handle Lambda expressions without a block body * Check for parenthesized expressions * `NET60` instead of `!NET40`
1 parent e7a6e27 commit aff9649

File tree

4 files changed

+71
-0
lines changed

4 files changed

+71
-0
lines changed

ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
<Compile Include="TestCases\ILPretty\Issue3442.cs" />
138138
<Compile Include="TestCases\ILPretty\MonoFixed.cs" />
139139
<Compile Include="TestCases\Pretty\Comparisons.cs" />
140+
<Compile Include="TestCases\Pretty\GloballyQualifiedTypeInStringInterpolation.cs" />
140141
<Compile Include="TestCases\Pretty\Issue3406.cs" />
141142
<Compile Include="TestCases\Pretty\PointerArithmetic.cs" />
142143
<Compile Include="TestCases\Pretty\Issue3439.cs" />

ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,19 @@ public async Task UsingVariables([ValueSource(nameof(roslyn3OrNewerWithNet40Opti
308308
await RunForLibrary(cscOptions: cscOptions);
309309
}
310310

311+
[Test]
312+
public async Task GloballyQualifiedTypeInStringInterpolation([ValueSource(nameof(roslynOnlyWithNet40Options))] CompilerOptions cscOptions)
313+
{
314+
// https://github.com/icsharpcode/ILSpy/issues/3447
315+
await RunForLibrary(
316+
cscOptions: cscOptions,
317+
configureDecompiler: settings => {
318+
settings.UsingDeclarations = false;
319+
settings.AlwaysUseGlobal = true;
320+
}
321+
);
322+
}
323+
311324
[Test]
312325
public async Task LiftedOperators([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
313326
{
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
2+
{
3+
public static class GloballyQualifiedTypeInStringInterpolation
4+
{
5+
public static string Root => $"Prefix {(global::System.DateTime.Now)} suffix";
6+
public static string Cast => $"Prefix {((int)global::System.DateTime.Now.Ticks)} suffix";
7+
#if CS100 && NET60
8+
public static string Lambda1 => $"Prefix {(() => global::System.DateTime.Now)} suffix";
9+
#else
10+
public static string Lambda1 => $"Prefix {(global::System.Func<global::System.DateTime>)(() => global::System.DateTime.Now)} suffix";
11+
#endif
12+
public static string Lambda2 => $"Prefix {((global::System.Func<global::System.DateTime>)(() => global::System.DateTime.Now))()} suffix";
13+
public static string Method1 => $"Prefix {M(global::System.DateTime.Now)} suffix";
14+
public static string Method2 => $"Prefix {(global::System.DateTime.Now.Ticks)} suffix";
15+
public static string Method3 => $"Prefix {(global::System.DateTime.Equals(global::System.DateTime.Now, global::System.DateTime.Now))} suffix";
16+
public static string ConditionalExpression1 => $"Prefix {(Boolean ? global::System.DateTime.Now : global::System.DateTime.UtcNow)} suffix";
17+
public static string ConditionalExpression2 => $"Prefix {(Boolean ? global::System.DateTime.Now : global::System.DateTime.UtcNow).Ticks} suffix";
18+
19+
private static bool Boolean => false;
20+
private static long M(global::System.DateTime time)
21+
{
22+
return time.Ticks;
23+
}
24+
}
25+
}

ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,38 @@ public override void VisitAsExpression(AsExpression asExpression)
389389
base.VisitAsExpression(asExpression);
390390
}
391391

392+
public override void VisitInterpolation(Interpolation interpolation)
393+
{
394+
// Need to do this first, in case the descendents parenthesize themselves.
395+
base.VisitInterpolation(interpolation);
396+
397+
// If an interpolation contains global::, we need to parenthesize the expression.
398+
if (InterpolationNeedsParenthesis(interpolation))
399+
Parenthesize(interpolation.Expression);
400+
401+
static bool InterpolationNeedsParenthesis(AstNode node)
402+
{
403+
if (node is MemberType { IsDoubleColon: true })
404+
return true;
405+
406+
if (node is ParenthesizedExpression)
407+
return false;
408+
if (node is AnonymousMethodExpression or LambdaExpression { Body: BlockStatement })
409+
return false;
410+
if (node is InvocationExpression invocation)
411+
return InterpolationNeedsParenthesis(invocation.Target);
412+
if (node is CastExpression cast)
413+
return InterpolationNeedsParenthesis(cast.Expression);
414+
415+
foreach (var child in node.Children)
416+
{
417+
if (InterpolationNeedsParenthesis(child))
418+
return true;
419+
}
420+
return false;
421+
}
422+
}
423+
392424
// Conditional operator
393425
public override void VisitConditionalExpression(ConditionalExpression conditionalExpression)
394426
{

0 commit comments

Comments
 (0)