Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not remove cast that would change what runtime type a virtual method dispatches to #76102

Merged
merged 3 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13935,6 +13935,27 @@ public void AppendFormatted<T>(T value)
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75424")]
public async Task KeepNecessaryObjectOverrideCast()
{
await new VerifyCS.Test
{
TestCode = """
using System;

class C
{
public void M()
{
var x = ((byte)3).GetType();
}
}
""",
LanguageVersion = LanguageVersion.CSharp12,
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71695")]
public async Task TestObjectToDynamic1()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,22 +428,27 @@ private static bool IsConversionCastSafeToRemove(

#region allowed cases that allow this cast to be removed.

// In code like `((X)y).Z()` the cast to (X) can be removed if the same 'Z' method would be called.
// The rules here can be subtle. For example, if Z is virtual, and (X) is a cast up the inheritance
// hierarchy then this is *normally* ok. HOwever, the language resolve default parameter values
// from the overridden method. So if they differ, we can't actually remove the cast.
// In code like `((X)y).Z()` the cast to (X) can be removed if the same 'Z' method would be called. The rules
// here can be subtle. For example, if Z is virtual, and (X) is a cast up the inheritance hierarchy then this
// is *normally* ok. However, the language resolve default parameter values from the overridden method. So if
// they differ, we can't actually remove the cast.
//
// Similarly, if (X) is a cast to an interface, and Z is an impl of that interface method, it might
// be possible to remove, but only if y's type is sealed, as otherwise the interface method could be
// reimplemented in a derived type.
// Similarly, if (X) is a cast to an interface, and Z is an impl of that interface method, it might be possible
// to remove, but only if y's type is sealed, as otherwise the interface method could be reimplemented in a
// derived type.
//
// Note: this path is fundamentally different from the other forms of cast removal we perform. The
// casts are removed because statically they make no difference to the meaning of the code. Here,
// the code statically changes meaning. However, we can use our knowledge of how the language/runtime
// works to know at *runtime* that the user will get the exact same behavior.
// Note: this path is fundamentally different from the other forms of cast removal we perform. The casts are
// removed because statically they make no difference to the meaning of the code. Here, the code statically
// changes meaning. However, we can use our knowledge of how the language/runtime works to know at *runtime*
// that the user will get the exact same behavior.
if (castNode.WalkUpParentheses().Parent is MemberAccessExpressionSyntax memberAccessExpression)
{
if (IsComplementaryMemberAccessAfterCastRemoval(
// Note: because this involves virtual calls, it is only safe if the original cast didn't change the runtime
// representation of the value at all. So we only allow this for representation preserving casts. For example,
// `string->object` preserves representation. As does `int -> icomparable`
var isRepresentationPreservingCast = originalConversion.IsIdentityOrImplicitReference() || originalConversion.IsBoxing;
if (isRepresentationPreservingCast &&
IsComplementaryMemberAccessAfterCastRemoval(
memberAccessExpression, rewrittenExpression, originalSemanticModel, rewrittenSemanticModel, cancellationToken))
{
return true;
Expand Down
Loading