-
Notifications
You must be signed in to change notification settings - Fork 419
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
Intellisense not showing methods from the Base class in Signature Help #1030
Conversation
@@ -133,7 +133,15 @@ private IEnumerable<IMethodSymbol> GetMethodOverloads(SemanticModel semanticMode | |||
return new IMethodSymbol[] { }; | |||
} | |||
|
|||
return symbol.ContainingType.GetMembers(symbol.Name).OfType<IMethodSymbol>(); | |||
var MethodOverloads = symbol.ContainingType.GetMembers(symbol.Name).OfType<IMethodSymbol>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
local variables should start with lowercase
return symbol.ContainingType.GetMembers(symbol.Name).OfType<IMethodSymbol>(); | ||
var MethodOverloads = symbol.ContainingType.GetMembers(symbol.Name).OfType<IMethodSymbol>(); | ||
var BaseType = symbol.ContainingType.BaseType; | ||
while(BaseType!=null && BaseType.ContainingNamespace.Name!= "System") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spacing is off here.
Also, perhaps it would be better to check for baseTypeSymbol.SpecialType != SpecialType.System_Object
and terminate there instead rather than on "System" namespace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above mentioned change is failing for the case of attribute constructor as the System.Attribute class doesn't have the SpecialType as System_Object, hence for that case we will also get an Attribute.Attribute method which is not required.This is the reason the System Namespace has been used.
return symbol.ContainingType.GetMembers(symbol.Name).OfType<IMethodSymbol>(); | ||
var methodOverloads = symbol.ContainingType.GetMembers(symbol.Name).OfType<IMethodSymbol>(); | ||
var baseTypeSymbol = symbol.ContainingType.BaseType; | ||
while(baseTypeSymbol != null && baseTypeSymbol.ContainingNamespace.Name != "System") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like Roslyn has a public "GetMemberGroup" extension method you can call that might do all of this for you. Should we just call that? (http://source.roslyn.io/#Microsoft.CodeAnalysis.CSharp.Features/SignatureHelp/InvocationExpressionSignatureHelpProvider.cs,71)
var baseTypeSymbol = symbol.ContainingType.BaseType; | ||
while(baseTypeSymbol != null && baseTypeSymbol.ContainingNamespace.Name != "System") | ||
{ | ||
methodOverloads = methodOverloads.Concat(baseTypeSymbol.GetMembers(symbol.Name).OfType<IMethodSymbol>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will add inaccessible members with the same name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it is returning all the methods regardless of their accessibility.
var actual = await GetSignatureHelp(source); | ||
Assert.Equal(3, actual.Signatures.Count()); | ||
Assert.Equal(1, actual.ActiveParameter); | ||
Assert.Contains("ctor2", actual.Signatures.ElementAt(actual.ActiveSignature).Documentation); | ||
} | ||
|
||
[Fact] | ||
public async Task SignatureHelpForOverloadedMethodsInheritance() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a case for methods with the same name that shouldn't be accessible? Eg:
class Program
{
static void Main(string[] args)
{
}
private void Foo() { }
}
class G : Program
{
protected void Foo() { }
}
} | ||
|
||
[Fact] | ||
public async Task SignatureHelpForOverloadedInaccesibleMethods() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also add a test to verify behavior if there's an extension method with the same name?
} | ||
|
||
[Fact] | ||
public async Task SignatureHelpForInheritedInaccesibleMethods() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see a few more tests for variants of this scenario:
-
An inherited protected method appears in signature help within the a sub type.
class A { protected void M1() { } } class B : A { void M2() { $$ // Both M1() and M2() } }
-
An inherited protected method appears in signature help after
this.
.class A { protected void M1() { } } class B : A { void M2() { this.$$ // Both M1() and M2() } }
-
An inherited protected method appears in signature help after
base.
.class A { protected void M1() { } } class B : A { void M2() { base.$$ // Only M1() } }
-
Only static methods appear in a static context.
class A { protected static void M1() { } public void M2() { } } class B : A { static void M3() { A.$$ // Only M1() } public void M4() { } }
class A { protected static void M1() { } public void M2() { } } class B : A { static void M3() { B.$$ // Both M1() and M3() } public void M4() { } }
-
Static and instance methods appear in an instance context.
class A { protected static void M1() { } public void M2() { } } class B : A { static void M3() { $$ // M1(), M2(), M3() and M4() appear } public void M4() { } }
cbfe81a
to
132b636
Compare
132b636
to
9cb58b4
Compare
|
||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> | ||
<LangVersion>7.1</LangVersion> | ||
</PropertyGroup> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding this to a particular project, could you remove this properties here and just add <LangVersion>7.1</LangVersion>
to https://github.com/OmniSharp/omnisharp-roslyn/blob/master/build/Settings.props so that all projects are using C# 7.1?
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sort usings. System
namespaces go first. (Note that this is a setting in Visual Studio)
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
namespace OmniSharp.Roslyn.CSharp.Services.Signatures |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a line between the using directives and the namespace.
using System.Linq; | ||
namespace OmniSharp.Roslyn.CSharp.Services.Signatures | ||
{ | ||
static internal class CheckForStatic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sort modifiers. "internal" before "static"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By convention, classes containing extension methods are typically named with an Extensions
suffix?
// any other location is considered static | ||
return true; | ||
} | ||
public static SyntaxTokenList GetModifiers(SyntaxNode member) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a blank line between methods.
{ | ||
var throughExpression = ((MemberAccessExpressionSyntax)invocation.Receiver).Expression; | ||
throughSymbol = invocation.SemanticModel.GetSpeculativeSymbolInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsExpression).Symbol; | ||
throughType = invocation.SemanticModel.GetSpeculativeTypeInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to retrieve speculative symbol and type info here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our heuristic is to show instance members if the thing left of the dot can bind as a non-type and to show static members if the thing left of the dot can bind as a type (and both, in the case of Color Color).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just copied and pasted from Roslyn?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Roslyn uses a different API for doing the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Roslyn does awkward type lookup to achieve the same effect: http://source.roslyn.io/#Microsoft.CodeAnalysis.CSharp.Features/SignatureHelp/InvocationExpressionSignatureHelpProvider_MethodGroup.cs,44. @DustinCampbell does what they are doing seem materially different to you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup. It's similar. This looks fine.
throughSymbol = invocation.SemanticModel.GetSpeculativeSymbolInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsExpression).Symbol; | ||
throughType = invocation.SemanticModel.GetSpeculativeTypeInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; | ||
var includeInstance = throughSymbol != null && !(throughSymbol is ITypeSymbol); | ||
var includeStatic = (throughSymbol is INamedTypeSymbol) || throughType != null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are the two clauses in the statements above reversed? Was that intentional? Why are we checking for null? Doesn't the is
check already do that for you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the first case if we remove the null check,then if throughSymbol is null,the second statement will evaluate to true (and we want it to be false on a null).
In the second case one statement is checking throughSymbol and the other throughType, so we need to have both of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see it now -- thanks!
methodGroup = methodGroup.Where(m => (m.IsStatic && includeStatic) || (!m.IsStatic && includeInstance)); | ||
} | ||
|
||
else if (invocation.Receiver is SimpleNameSyntax && invocation.IsStatic) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary blank line above this.
|
||
}"; | ||
var actual = await GetSignatureHelp(source); | ||
Assert.Single(actual.Signatures); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we verify which signature we got?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we should.Added that.
var actual = await GetSignatureHelp(source); | ||
Assert.Equal(4, actual.Signatures.Count()); | ||
} | ||
[Fact] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a blank line before this method
@@ -13,23 +13,26 @@ internal class InvocationContext | |||
public SyntaxNode Receiver { get; } | |||
public IEnumerable<TypeInfo> ArgumentTypes { get; } | |||
public IEnumerable<SyntaxToken> Separators { get; } | |||
public bool IsStatic { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsInStaticContext
{ | ||
var throughExpression = ((MemberAccessExpressionSyntax)invocation.Receiver).Expression; | ||
throughSymbol = invocation.SemanticModel.GetSpeculativeSymbolInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsExpression).Symbol; | ||
throughType = invocation.SemanticModel.GetSpeculativeTypeInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our heuristic is to show instance members if the thing left of the dot can bind as a non-type and to show static members if the thing left of the dot can bind as a type (and both, in the case of Color Color).
@@ -94,19 +113,19 @@ public SignatureHelpService(OmniSharpWorkspace workspace) | |||
if (node is InvocationExpressionSyntax invocation && invocation.ArgumentList.Span.Contains(position)) | |||
{ | |||
var semanticModel = await document.GetSemanticModelAsync(); | |||
return new InvocationContext(semanticModel, position, invocation.Expression, invocation.ArgumentList); | |||
return new InvocationContext(semanticModel, position, invocation.Expression, invocation.ArgumentList, invocation.IsInStaticContext()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can InvocationContext take care of calling IsInStaticContext()? You've making the same call in all the initializations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case we will have to pass the invocation object to InvocationContext and since there are three different types of such objects we will have to add another constructor in InvocationContext. Which is better of the two ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll leave it up to you.
} | ||
|
||
if (node is ObjectCreationExpressionSyntax objectCreation && objectCreation.ArgumentList.Span.Contains(position)) | ||
{ | ||
var semanticModel = await document.GetSemanticModelAsync(); | ||
return new InvocationContext(semanticModel, position, objectCreation, objectCreation.ArgumentList); | ||
return new InvocationContext(semanticModel, position, objectCreation, objectCreation.ArgumentList,objectCreation.IsInStaticContext()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing comma between objectCreation.ArgumentList and objectCreation
@@ -1,4 +1,6 @@ | |||
using System; | |||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might add a comment for where this was copied from.
|
||
namespace OmniSharp.Roslyn.CSharp.Services.Signatures | ||
{ | ||
//TO DO: Reomove this class once a public API for Signature Help from Roslyn is available |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reomove -> Remove
{ | ||
var throughExpression = ((MemberAccessExpressionSyntax)invocation.Receiver).Expression; | ||
throughSymbol = invocation.SemanticModel.GetSpeculativeSymbolInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsExpression).Symbol; | ||
throughType = invocation.SemanticModel.GetSpeculativeTypeInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup. It's similar. This looks fine.
Issue : dotnet/vscode-csharp#1440
During the search for the signature help, only the immediate Type that contains the given method was being searched for possible method overloads hence the overloads present in the Base Classes were not appearing. Added the code to search for members using the GetMemberGroup. Added a test for the same.
Please review @DustinCampbell @TheRealPiotrP @rchande