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

Support 'go to base' on a constructor symbol #67146

Merged
merged 3 commits into from
Mar 3, 2023
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
25 changes: 23 additions & 2 deletions src/EditorFeatures/CSharp/GoToBase/CSharpGoToBaseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

using System;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ExternalAccess.Pythia.Api;
using Microsoft.CodeAnalysis.GoToBase;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;

namespace Microsoft.CodeAnalysis.CSharp.GoToBase
{
Expand All @@ -16,8 +20,25 @@ internal sealed class CSharpGoToBaseService : AbstractGoToBaseService
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CSharpGoToBaseService()
: base()
{
}

protected override async Task<IMethodSymbol?> FindNextConstructorInChainAsync(
Solution solution, IMethodSymbol constructor, CancellationToken cancellationToken)
{
if (constructor.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken) is not ConstructorDeclarationSyntax constructorDeclaration)
return null;

var document = solution.GetDocument(constructorDeclaration.SyntaxTree);
if (document is null)
return null;

// this constructor must be calling an accessible no-arg constructor in the base type.
if (constructorDeclaration.Initializer is null)
return FindBaseNoArgConstructor(constructor);

var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
return semanticModel.GetSymbolInfo(constructorDeclaration.Initializer, cancellationToken).GetAnySymbol() as IMethodSymbol;
}
}
}
112 changes: 112 additions & 0 deletions src/EditorFeatures/Test2/GoToBase/CSharpGoToBaseTests.vb
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,118 @@ struct S : I { int I.$$P { get; set; } }
interface I { int [|P|] { get; set; } }")
End Function

#End Region

#Region "Constructors"

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain1() As Task
Await TestAsync("
class C
{
public $$C(int i) : this(i.ToString())
{
}

public [|C|](string s)
{
}
}
")
End Function

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain2() As Task
Await TestAsync("
class Base
{
public [|Base|](string s)
{
}
}

class C : Base
{
public $$C(int i) : base(i.ToString())
{
}
}
")
End Function

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain3() As Task
Await TestAsync("
class [|Base|]
{
}

class C : Base
{
public $$C(int i)
{
}
}
")
End Function

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain4() As Task
Await TestAsync("
class Base
{
public [|Base|](int i)
{
}
}

class C : Base
{
public $$C(int i) : base(i)
{
}
}
")
End Function

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain5() As Task
Await TestAsync("
class Base
{
public [|Base|](int i = 0)
{
}
}

class C : Base
{
public $$C(int i)
{
}
}
")
End Function

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain6() As Task
Await TestAsync("
class Base
{
public [|Base|](params int[] i)
{
}
}

class C : Base
{
public $$C(int i)
{
}
}
")
End Function

#End Region

End Class
Expand Down
103 changes: 103 additions & 0 deletions src/EditorFeatures/Test2/GoToBase/VisuaBasicGoToBaseTests.vb
Original file line number Diff line number Diff line change
Expand Up @@ -667,5 +667,108 @@ End Interface")
End Function
#End Region

#Region "Constructors"

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain1() As Task
Await TestAsync("
class C
public sub $$new(i as integer)
me.new(i.ToString())
end sub

public sub [|new|](s as string)
end sub
end class
")
End Function

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain2() As Task
Await TestAsync("
class Base
public sub [|new|](s as string)
end sub
end class

class C
inherits Base

public sub $$new(i as integer)
mybase.new(i.ToString())
end sub
end sub
")
End Function

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain3() As Task
Await TestAsync("
class [|Base|]
end class

class C
inherits Base

public sub $$new(i as integer)
end sub
end class
")
End Function

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain4() As Task
Await TestAsync("
class Base
public sub [|new|](i as integer)
end sub
end class

class C
inherits Base

public sub $$new(i as integer)
mybase.new(i)
end sub
end class
")
End Function

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain5() As Task
Await TestAsync("
class Base
public sub [|new|](optional i as integer = 0)
end sub
end class

class C
inherits Base

public sub $$new(i as integer)
end sub
end class
")
End Function

<Fact, WorkItem(44944, "https://github.com/dotnet/roslyn/issues/44944")>
Public Async Function TestNextConstructorInChain6() As Task
Await TestAsync("
class Base
public sub [|new|](paramarray i as integer())
end sub
}

class C
inherits Base

public sub $$new(i as integer)
end sub
end class
")
End Function

#End Region

End Class
End Namespace
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
' See the LICENSE file in the project root for more information.

Imports System.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis.GoToBase
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Namespace Microsoft.CodeAnalysis.VisualBasic.GoToBase
<ExportLanguageService(GetType(IGoToBaseService), LanguageNames.VisualBasic), [Shared]>
Expand All @@ -15,7 +16,32 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GoToBase
<ImportingConstructor>
<Obsolete(MefConstruction.ImportingConstructorMessage, True)>
Public Sub New()
MyBase.New()
End Sub

Protected Overrides Async Function FindNextConstructorInChainAsync(solution As Solution, constructor As IMethodSymbol, cancellationToken As CancellationToken) As Task(Of IMethodSymbol)
Dim subNew = TryCast(constructor.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken), SubNewStatementSyntax)
If subNew Is Nothing Then
Return Nothing
End If

Dim constructorBlock = TryCast(subNew.Parent, ConstructorBlockSyntax)
If constructorBlock Is Nothing Then
Return Nothing
End If

Dim initializer As MemberAccessExpressionSyntax = Nothing
If constructorBlock.Statements.Count = 0 OrElse
Not constructorBlock.Statements(0).IsConstructorInitializer(initializer) Then
Return FindBaseNoArgConstructor(constructor)
End If

Dim document = solution.GetDocument(constructorBlock.SyntaxTree)
If document Is Nothing Then
Return Nothing
End If

Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
Return TryCast(semanticModel.GetSymbolInfo(initializer, cancellationToken).GetAnySymbol(), IMethodSymbol)
End Function
End Class
End Namespace
21 changes: 19 additions & 2 deletions src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,30 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.GoToBase
{
internal abstract class AbstractGoToBaseService : IGoToBaseService
{
protected AbstractGoToBaseService()
protected abstract Task<IMethodSymbol?> FindNextConstructorInChainAsync(
Solution solution, IMethodSymbol constructor, CancellationToken cancellationToken);

protected static IMethodSymbol? FindBaseNoArgConstructor(IMethodSymbol constructor)
{
var baseType = constructor.ContainingType.BaseType;
if (baseType is null)
return null;

return baseType.InstanceConstructors.FirstOrDefault(
baseConstructor => baseConstructor.IsAccessibleWithin(constructor.ContainingType) &&
baseConstructor.Parameters.All(p => p.IsOptional || p.IsParams));
}

public async Task FindBasesAsync(IFindUsagesContext context, Document document, int position, CancellationToken cancellationToken)
Expand All @@ -33,6 +44,12 @@ await context.ReportMessageAsync(

var solution = project.Solution;
var bases = await FindBaseHelpers.FindBasesAsync(symbol, solution, cancellationToken).ConfigureAwait(false);
if (bases.Length == 0 && symbol is IMethodSymbol { MethodKind: MethodKind.Constructor } constructor)
{
var nextConstructor = await FindNextConstructorInChainAsync(solution, constructor, cancellationToken).ConfigureAwait(false);
if (nextConstructor != null)
bases = ImmutableArray.Create<ISymbol>(nextConstructor);
}

await context.SetSearchTitleAsync(
string.Format(FeaturesResources._0_bases,
Expand Down
Loading