diff --git a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs index 0c7ae7293a84d..40d5f3ef5901b 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnnecessaryCast; CSharpRemoveUnnecessaryCastCodeFixProvider>; [Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryCast)] -public class RemoveUnnecessaryCastTests +public sealed class RemoveUnnecessaryCastTests { [Theory, CombinatorialData] public void TestStandardProperty(AnalyzerProperty property) @@ -13934,4 +13934,145 @@ public void AppendFormatted(T value) ReferenceAssemblies = ReferenceAssemblies.Net.Net80, }.RunAsync(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71695")] + public async Task TestObjectToDynamic1() + { + await new VerifyCS.Test + { + TestCode = """ + #nullable enable + + class C + { + public T? M(object? o) + { + return (dynamic?)o; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71695")] + public async Task TestObjectToDynamic2() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public T M(object o) + { + return (dynamic)o; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71695")] + public async Task TestNumericThroughDynamic() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public int M(long o) + { + return (dynamic)o; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71695")] + public async Task TestNullableThroughDynamic() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public int M(int? o) + { + return (dynamic)o; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71695")] + public async Task TestEnumThroughDynamic1() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + public int M(ConsoleColor o) + { + return (dynamic)o; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71695")] + public async Task TestEnumThroughDynamic2() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + public ConsoleColor M(int o) + { + return (dynamic)o; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71695")] + public async Task TestConstantThroughDynamic() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + public int M() + { + return (dynamic)0L; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }.RunAsync(); + } } diff --git a/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeFixVerifier`2.cs b/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeFixVerifier`2.cs index d534c9087c602..e27df549efa78 100644 --- a/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeFixVerifier`2.cs +++ b/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeFixVerifier`2.cs @@ -56,11 +56,17 @@ public static async Task VerifyCodeFixAsync( => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); /// - public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + public static async Task VerifyCodeFixAsync( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string source, + DiagnosticResult expected, + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string fixedSource) => await VerifyCodeFixAsync(source, [expected], fixedSource); /// - public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + public static async Task VerifyCodeFixAsync( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string source, + DiagnosticResult[] expected, + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string fixedSource) { var test = new Test { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs index b8933465c52de..6abad5dc7bbcc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs @@ -349,6 +349,14 @@ private static bool IsConversionCastSafeToRemove( if (rewrittenConvertedType is null || rewrittenConvertedType.TypeKind == TypeKind.Error || !rewrittenConversion.Exists) return false; + // If removing the conversion caused us to now become an explicit conversion (a conversion that can cause + // lossyness), then we must block as that's disallowed by the language. + // + // Note: compiler API is slightly odd here as they return such an 'IsExplicit+Exists' conversion when casting + // the expression inside a string interpolation. So we ignore that case here + if (rewrittenConversion.IsExplicit && castNode.WalkUpParentheses().Parent is not InterpolationSyntax) + return false; + if (CastRemovalWouldCauseUnintendedReferenceComparisonWarning(rewrittenExpression, rewrittenSemanticModel, cancellationToken)) return false;