diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index e6053e5587..5a89e7178f 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -137,6 +137,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index 9b4184aa72..b528cd65ba 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -308,6 +308,19 @@ public async Task UsingVariables([ValueSource(nameof(roslyn3OrNewerWithNet40Opti
await RunForLibrary(cscOptions: cscOptions);
}
+ [Test]
+ public async Task GloballyQualifiedTypeInStringInterpolation([ValueSource(nameof(roslynOnlyWithNet40Options))] CompilerOptions cscOptions)
+ {
+ // https://github.com/icsharpcode/ILSpy/issues/3447
+ await RunForLibrary(
+ cscOptions: cscOptions,
+ configureDecompiler: settings => {
+ settings.UsingDeclarations = false;
+ settings.AlwaysUseGlobal = true;
+ }
+ );
+ }
+
[Test]
public async Task LiftedOperators([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/GloballyQualifiedTypeInStringInterpolation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/GloballyQualifiedTypeInStringInterpolation.cs
new file mode 100644
index 0000000000..78af64e56b
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/GloballyQualifiedTypeInStringInterpolation.cs
@@ -0,0 +1,25 @@
+namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
+{
+ public static class GloballyQualifiedTypeInStringInterpolation
+ {
+ public static string Root => $"Prefix {(global::System.DateTime.Now)} suffix";
+ public static string Cast => $"Prefix {((int)global::System.DateTime.Now.Ticks)} suffix";
+#if CS100 && NET60
+ public static string Lambda1 => $"Prefix {(() => global::System.DateTime.Now)} suffix";
+#else
+ public static string Lambda1 => $"Prefix {(global::System.Func)(() => global::System.DateTime.Now)} suffix";
+#endif
+ public static string Lambda2 => $"Prefix {((global::System.Func)(() => global::System.DateTime.Now))()} suffix";
+ public static string Method1 => $"Prefix {M(global::System.DateTime.Now)} suffix";
+ public static string Method2 => $"Prefix {(global::System.DateTime.Now.Ticks)} suffix";
+ public static string Method3 => $"Prefix {(global::System.DateTime.Equals(global::System.DateTime.Now, global::System.DateTime.Now))} suffix";
+ public static string ConditionalExpression1 => $"Prefix {(Boolean ? global::System.DateTime.Now : global::System.DateTime.UtcNow)} suffix";
+ public static string ConditionalExpression2 => $"Prefix {(Boolean ? global::System.DateTime.Now : global::System.DateTime.UtcNow).Ticks} suffix";
+
+ private static bool Boolean => false;
+ private static long M(global::System.DateTime time)
+ {
+ return time.Ticks;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
index 2fb3a45460..744ad90824 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
@@ -389,6 +389,38 @@ public override void VisitAsExpression(AsExpression asExpression)
base.VisitAsExpression(asExpression);
}
+ public override void VisitInterpolation(Interpolation interpolation)
+ {
+ // Need to do this first, in case the descendents parenthesize themselves.
+ base.VisitInterpolation(interpolation);
+
+ // If an interpolation contains global::, we need to parenthesize the expression.
+ if (InterpolationNeedsParenthesis(interpolation))
+ Parenthesize(interpolation.Expression);
+
+ static bool InterpolationNeedsParenthesis(AstNode node)
+ {
+ if (node is MemberType { IsDoubleColon: true })
+ return true;
+
+ if (node is ParenthesizedExpression)
+ return false;
+ if (node is AnonymousMethodExpression or LambdaExpression { Body: BlockStatement })
+ return false;
+ if (node is InvocationExpression invocation)
+ return InterpolationNeedsParenthesis(invocation.Target);
+ if (node is CastExpression cast)
+ return InterpolationNeedsParenthesis(cast.Expression);
+
+ foreach (var child in node.Children)
+ {
+ if (InterpolationNeedsParenthesis(child))
+ return true;
+ }
+ return false;
+ }
+ }
+
// Conditional operator
public override void VisitConditionalExpression(ConditionalExpression conditionalExpression)
{