Skip to content

Commit

Permalink
Merge pull request #67146 from CyrusNajmabadi/gotobaseConstructor
Browse files Browse the repository at this point in the history
Support 'go to base' on a constructor symbol
  • Loading branch information
CyrusNajmabadi authored Mar 3, 2023
2 parents eb719b7 + 52620f4 commit bffe0df
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 11 deletions.
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

0 comments on commit bffe0df

Please sign in to comment.