Skip to content

Commit

Permalink
improve goto-def on an invalid override (#75901)
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored Nov 14, 2024
2 parents e75664f + 23edf8d commit 5c91b9d
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,95 @@ class C
Await TestAsync(workspace)
End Function

<WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/57110")>
Public Async Function TestCSharpGoToOverriddenDefinition_FromOverride_LooseMatch() As Task
Dim workspace =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
class C
{
public virtual void [|F|](bool x)
{
}
}

class D : C
{
public $$override void F(int x)
{
}
}
</Document>
</Project>
</Workspace>

Await TestAsync(workspace)
End Function

<WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/57110")>
Public Async Function TestCSharpGoToOverriddenDefinition_FromOverride_LooseMatch2() As Task
Dim workspace =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
class C
{
public virtual void F()
{
}

public virtual void [|F|](bool x)
{
}
}

class D : C
{
public $$override void F(int x)
{
}
}
</Document>
</Project>
</Workspace>

Await TestAsync(workspace)
End Function

<WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/57110")>
Public Async Function TestCSharpGoToOverriddenDefinition_FromOverride_LooseMatch3() As Task
Dim workspace =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
class B
{
public virtual void F(bool x)
{
}
}

class C
{
public virtual void [|F|]()
{
}
}

class D : C
{
public $$override void F(int x)
{
}
}
</Document>
</Project>
</Workspace>

Await TestAsync(workspace)
End Function

<WpfFact>
Public Async Function TestCSharpGoToUnmanaged_Keyword() As Task
Dim workspace =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public static TokenSemanticInfo GetSemanticInfo(
{
// on an "override" token, we'll find the overridden symbol
var overriddingSymbol = semanticFacts.GetDeclaredSymbol(semanticModel, overriddingIdentifier.Value, cancellationToken);
var overriddenSymbol = overriddingSymbol.GetOverriddenMember();
var overriddenSymbol = overriddingSymbol.GetOverriddenMember(allowLooseMatch: true);

allSymbols = overriddenSymbol is null ? [] : [overriddenSymbol];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,69 @@ public static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
return visibility;
}

public static ISymbol? GetOverriddenMember(this ISymbol? symbol)
=> symbol switch
public static ISymbol? GetOverriddenMember(this ISymbol? symbol, bool allowLooseMatch = false)
{
if (symbol is null)
return null;

ISymbol? exactMatch = symbol switch
{
IMethodSymbol method => method.OverriddenMethod,
IPropertySymbol property => property.OverriddenProperty,
IEventSymbol @event => @event.OverriddenEvent,
_ => null,
};

if (exactMatch != null)
return exactMatch;

if (allowLooseMatch)
{
foreach (var baseType in symbol.ContainingType.GetBaseTypes())
{
if (TryFindLooseMatch(symbol, baseType, out var looseMatch))
return looseMatch;
}
}

return null;

bool TryFindLooseMatch(ISymbol symbol, INamedTypeSymbol baseType, [NotNullWhen(true)] out ISymbol? looseMatch)
{
IMethodSymbol? bestMethod = null;
var parameterCount = symbol.GetParameters().Length;

foreach (var member in baseType.GetMembers(symbol.Name))
{
if (member.Kind != symbol.Kind)
continue;

if (member.IsSealed)
continue;

if (!member.IsVirtual && !member.IsOverride && !member.IsAbstract)
continue;

if (symbol.Kind is SymbolKind.Event or SymbolKind.Property)
{
// We've found a matching event/property in the base type (perhaps differing by return type). This
// is a good enough match to return as a loose match for the starting symbol.
looseMatch = member;
return true;
}
else if (member is IMethodSymbol method)
{
// Prefer methods that are closed in parameter count to the original method we started with.
if (bestMethod is null || Math.Abs(method.Parameters.Length - parameterCount) < Math.Abs(bestMethod.Parameters.Length - parameterCount))
bestMethod = method;
}
}

looseMatch = bestMethod;
return looseMatch != null;
}
}

public static ImmutableArray<ISymbol> ExplicitInterfaceImplementations(this ISymbol symbol)
=> symbol switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ public static IEnumerable<ITypeSymbol> GetBaseTypesAndThis(this ITypeSymbol? typ
}
}

public static IEnumerable<INamedTypeSymbol> GetBaseTypes(this ITypeSymbol type)
public static IEnumerable<INamedTypeSymbol> GetBaseTypes(this ITypeSymbol? type)
{
var current = type.BaseType;
var current = type?.BaseType;
while (current != null)
{
yield return current;
Expand Down

0 comments on commit 5c91b9d

Please sign in to comment.