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

Intellisense not showing methods from the Base class in Signature Help #1030

Merged
merged 17 commits into from
Dec 8, 2017

Conversation

akshita31
Copy link
Contributor

@akshita31 akshita31 commented Nov 23, 2017

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

@@ -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>();
Copy link
Member

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")
Copy link
Member

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

Copy link
Contributor Author

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")
Copy link

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>());
Copy link

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.

Copy link
Contributor Author

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()
Copy link

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()
Copy link

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()
Copy link
Contributor

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:

  1. 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()
        }
    }
  2. 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()
        }
    }
  3. An inherited protected method appears in signature help after base..

    class A
    {
        protected void M1() { }
    }
    
    class B : A
    {
        void M2()
        {
            base.$$ // Only M1()
        }
    }
  4. 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() { }
    }
  5. 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() { }
    }


<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<LangVersion>7.1</LangVersion>
</PropertyGroup>
Copy link
Contributor

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;
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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"

Copy link
Contributor

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)
Copy link
Contributor

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;
Copy link
Contributor

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?

Copy link

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).

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link

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?

Copy link
Contributor

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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)
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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]
Copy link
Contributor

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; }
Copy link

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;
Copy link

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());
Copy link

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.

Copy link
Contributor Author

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 ?

Copy link

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());
Copy link

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.
Copy link
Contributor

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.

@akshita31 akshita31 changed the title Intellisense not showing methods from the Base class while parameters are suggested Intellisense not showing methods from the Base class in Signature Help Nov 30, 2017

namespace OmniSharp.Roslyn.CSharp.Services.Signatures
{
//TO DO: Reomove this class once a public API for Signature Help from Roslyn is available
Copy link

@rchande rchande Dec 7, 2017

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;
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants