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) {