diff --git a/build/Settings.props b/build/Settings.props index 225b86c07d..be3acd24d9 100644 --- a/build/Settings.props +++ b/build/Settings.props @@ -2,6 +2,7 @@ + 7.1 true Debug false diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Signatures/CheckStaticInvocation.cs b/src/OmniSharp.Roslyn.CSharp/Services/Signatures/CheckStaticInvocation.cs new file mode 100644 index 0000000000..d96a26daf6 --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Services/Signatures/CheckStaticInvocation.cs @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Adapted from https://github.com/dotnet/roslyn/blob/master/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace OmniSharp.Roslyn.CSharp.Services.Signatures +{ + //TO DO: Remove this class once a public API for Signature Help from Roslyn is available + internal static class CheckForStaticExtension + { + public static bool IsInStaticContext(this SyntaxNode node) + { + // this/base calls are always static. + if (node.FirstAncestorOrSelf() != null) + { + return true; + } + + var memberDeclaration = node.FirstAncestorOrSelf(); + if (memberDeclaration == null) + { + return false; + } + + switch (memberDeclaration.Kind()) + { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.ConstructorDeclaration: + case SyntaxKind.EventDeclaration: + case SyntaxKind.IndexerDeclaration: + return GetModifiers(memberDeclaration).Any(SyntaxKind.StaticKeyword); + + case SyntaxKind.PropertyDeclaration: + return GetModifiers(memberDeclaration).Any(SyntaxKind.StaticKeyword) || + node.IsFoundUnder((PropertyDeclarationSyntax p) => p.Initializer); + + case SyntaxKind.FieldDeclaration: + case SyntaxKind.EventFieldDeclaration: + // Inside a field one can only access static members of a type (unless it's top-level). + return !memberDeclaration.Parent.IsKind(SyntaxKind.CompilationUnit); + + case SyntaxKind.DestructorDeclaration: + return false; + } + + // Global statements are not a static context. + if (node.FirstAncestorOrSelf() != null) + { + return false; + } + + // any other location is considered static + return true; + } + + public static SyntaxTokenList GetModifiers(SyntaxNode member) + { + if (member != null) + { + switch (member.Kind()) + { + case SyntaxKind.EnumDeclaration: + return ((EnumDeclarationSyntax)member).Modifiers; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.StructDeclaration: + return ((TypeDeclarationSyntax)member).Modifiers; + case SyntaxKind.DelegateDeclaration: + return ((DelegateDeclarationSyntax)member).Modifiers; + case SyntaxKind.FieldDeclaration: + return ((FieldDeclarationSyntax)member).Modifiers; + case SyntaxKind.EventFieldDeclaration: + return ((EventFieldDeclarationSyntax)member).Modifiers; + case SyntaxKind.ConstructorDeclaration: + return ((ConstructorDeclarationSyntax)member).Modifiers; + case SyntaxKind.DestructorDeclaration: + return ((DestructorDeclarationSyntax)member).Modifiers; + case SyntaxKind.PropertyDeclaration: + return ((PropertyDeclarationSyntax)member).Modifiers; + case SyntaxKind.EventDeclaration: + return ((EventDeclarationSyntax)member).Modifiers; + case SyntaxKind.IndexerDeclaration: + return ((IndexerDeclarationSyntax)member).Modifiers; + case SyntaxKind.OperatorDeclaration: + return ((OperatorDeclarationSyntax)member).Modifiers; + case SyntaxKind.ConversionOperatorDeclaration: + return ((ConversionOperatorDeclarationSyntax)member).Modifiers; + case SyntaxKind.MethodDeclaration: + return ((MethodDeclarationSyntax)member).Modifiers; + case SyntaxKind.GetAccessorDeclaration: + case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.AddAccessorDeclaration: + case SyntaxKind.RemoveAccessorDeclaration: + return ((AccessorDeclarationSyntax)member).Modifiers; + } + } + + return default; + } + + public static bool IsFoundUnder(this SyntaxNode node, Func childGetter) + where TParent : SyntaxNode + { + var ancestor = node.GetAncestor(); + if (ancestor == null) + { + return false; + } + + var child = childGetter(ancestor); + + // See if node passes through child on the way up to ancestor. + return node.GetAncestorsOrThis().Contains(child); + } + + public static TNode GetAncestor(this SyntaxNode node) + where TNode : SyntaxNode + { + var current = node.Parent; + while (current != null) + { + if (current is TNode tNode) + { + return tNode; + } + + current = current.GetParent(); + } + + return null; + } + + private static SyntaxNode GetParent(this SyntaxNode node) + { + return node is IStructuredTriviaSyntax trivia ? trivia.ParentTrivia.Token.Parent : node.Parent; + } + + public static TNode FirstAncestorOrSelfUntil(this SyntaxNode node, Func predicate) + where TNode : SyntaxNode + { + for (var current = node; current != null; current = current.GetParent()) + { + if (current is TNode tnode) + { + return tnode; + } + + if (predicate(current)) + { + break; + } + } + + return default; + } + + public static TNode GetAncestorOrThis(this SyntaxNode node) + where TNode : SyntaxNode + { + return node?.GetAncestorsOrThis().FirstOrDefault(); + } + + public static IEnumerable GetAncestorsOrThis(this SyntaxNode node) + where TNode : SyntaxNode + { + var current = node; + while (current != null) + { + if (current is TNode tNode) + { + yield return tNode; + } + + current = current.GetParent(); + } + } + } +} + diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Signatures/InvocationContext.cs b/src/OmniSharp.Roslyn.CSharp/Services/Signatures/InvocationContext.cs index e06abc6bb5..fd2d46e63e 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Signatures/InvocationContext.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Signatures/InvocationContext.cs @@ -13,23 +13,26 @@ internal class InvocationContext public SyntaxNode Receiver { get; } public IEnumerable ArgumentTypes { get; } public IEnumerable Separators { get; } + public bool IsInStaticContext { get; } - public InvocationContext(SemanticModel semModel, int position, SyntaxNode receiver, ArgumentListSyntax argList) + public InvocationContext(SemanticModel semModel, int position, SyntaxNode receiver, ArgumentListSyntax argList, bool isStatic) { SemanticModel = semModel; Position = position; Receiver = receiver; ArgumentTypes = argList.Arguments.Select(argument => semModel.GetTypeInfo(argument.Expression)); Separators = argList.Arguments.GetSeparators(); + IsInStaticContext = isStatic; } - public InvocationContext(SemanticModel semModel, int position, SyntaxNode receiver, AttributeArgumentListSyntax argList) + public InvocationContext(SemanticModel semModel, int position, SyntaxNode receiver, AttributeArgumentListSyntax argList, bool isStatic) { SemanticModel = semModel; Position = position; Receiver = receiver; ArgumentTypes = argList.Arguments.Select(argument => semModel.GetTypeInfo(argument.Expression)); Separators = argList.Arguments.GetSeparators(); + IsInStaticContext = isStatic; } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs index 8dc8a593e2..8687fd1fb7 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using OmniSharp.Mef; @@ -59,7 +60,24 @@ public async Task Handle(SignatureHelpRequest request) foreach (var invocation in invocations) { var types = invocation.ArgumentTypes; - foreach (var methodOverload in GetMethodOverloads(invocation.SemanticModel, invocation.Receiver)) + ISymbol throughSymbol = null; + ISymbol throughType = null; + var methodGroup = invocation.SemanticModel.GetMemberGroup(invocation.Receiver).OfType(); + if (invocation.Receiver is MemberAccessExpressionSyntax) + { + 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; + var includeInstance = throughSymbol != null && !(throughSymbol is ITypeSymbol); + var includeStatic = (throughSymbol is INamedTypeSymbol) || throughType != null; + methodGroup = methodGroup.Where(m => (m.IsStatic && includeStatic) || (!m.IsStatic && includeInstance)); + } + else if (invocation.Receiver is SimpleNameSyntax && invocation.IsInStaticContext) + { + methodGroup = methodGroup.Where(m => m.IsStatic); + } + + foreach (var methodOverload in methodGroup) { var signature = BuildSignature(methodOverload); signaturesSet.Add(signature); @@ -94,19 +112,19 @@ private async Task GetInvocation(Document document, Request r 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()); } 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()); } if (node is AttributeSyntax attributeSyntax && attributeSyntax.ArgumentList.Span.Contains(position)) { var semanticModel = await document.GetSemanticModelAsync(); - return new InvocationContext(semanticModel, position, attributeSyntax, attributeSyntax.ArgumentList); + return new InvocationContext(semanticModel, position, attributeSyntax, attributeSyntax.ArgumentList, attributeSyntax.IsInStaticContext()); } node = node.Parent; @@ -115,30 +133,9 @@ private async Task GetInvocation(Document document, Request r return null; } - private IEnumerable GetMethodOverloads(SemanticModel semanticModel, SyntaxNode node) - { - ISymbol symbol = null; - var symbolInfo = semanticModel.GetSymbolInfo(node); - if (symbolInfo.Symbol != null) - { - symbol = symbolInfo.Symbol; - } - else if (!symbolInfo.CandidateSymbols.IsEmpty) - { - symbol = symbolInfo.CandidateSymbols.First(); - } - - if (symbol == null || symbol.ContainingType == null) - { - return new IMethodSymbol[] { }; - } - - return symbol.ContainingType.GetMembers(symbol.Name).OfType(); - } - private int InvocationScore(IMethodSymbol symbol, IEnumerable types) { - var parameters = GetParameters(symbol); + var parameters = symbol.Parameters; if (parameters.Count() < types.Count()) { return int.MinValue; @@ -172,7 +169,7 @@ private static SignatureHelpItem BuildSignature(IMethodSymbol symbol) signature.Name = symbol.MethodKind == MethodKind.Constructor ? symbol.ContainingType.Name : symbol.Name; signature.Label = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); - signature.Parameters = GetParameters(symbol).Select(parameter => + signature.Parameters = symbol.Parameters.Select(parameter => { return new SignatureHelpParameter() { @@ -185,16 +182,5 @@ private static SignatureHelpItem BuildSignature(IMethodSymbol symbol) return signature; } - private static IEnumerable GetParameters(IMethodSymbol methodSymbol) - { - if (!methodSymbol.IsExtensionMethod) - { - return methodSymbol.Parameters; - } - else - { - return methodSymbol.Parameters.RemoveAt(0); - } - } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs index 031b25d44c..b670e41dc4 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs @@ -153,7 +153,7 @@ private int Foo(int one, int two, int three) } [Fact] - public async Task SignatureHelpforAttributeCtorSingleParam() + public async Task AttributeCtorSingleParam() { const string source = @"using System; @@ -180,7 +180,7 @@ public MyTestAttribute(int value) } [Fact] - public async Task SignatureHelpforAttributeCtorTestParameterLabels() + public async Task AttributeCtorTestParameterLabels() { const string source = @"using System; @@ -210,7 +210,7 @@ public MyTestAttribute(int value1,double value2) } [Fact] - public async Task SignatureHelpforAttributeCtorActiveParamBasedOnComma() + public async Task AttributeCtorActiveParamBasedOnComma() { const string source = @"using System; @@ -233,7 +233,7 @@ public MyTestAttribute(int value1,double value2) } [Fact] - public async Task SignatureHelpforAttributeCtorNoParam() + public async Task AttributeCtorNoParam() { const string source = @"using System; @@ -429,7 +429,7 @@ private int Foo(string m, int n) } [Fact] - public async Task SignatureHelpForCtor() + public async Task TestForConstructorHelp() { const string source = @"class Program @@ -454,7 +454,7 @@ public Program(Program p) } [Fact] - public async Task SignatureHelpForCtorWithOverloads() + public async Task TestForCtorWithOverloads() { const string source = @"class Program @@ -474,7 +474,6 @@ public Program(Program p, int n) { } }"; - var actual = await GetSignatureHelp(source); Assert.Equal(3, actual.Signatures.Count()); Assert.Equal(1, actual.ActiveParameter); @@ -482,30 +481,291 @@ public Program(Program p, int n) } [Fact] - public async Task SkipReceiverOfExtensionMethods() + public async Task TestForInheritedMethods() { const string source = -@"class Program +@"public class MyBase { - public static void Main() + public void MyMethod(int a) { } + public void MyMethod(int a, int b) { } +} + +public class Class1 : MyBase +{ + public void MyMethod(int a, int b, int c) { } + public void MyMethod(int a, int b, int c, int d) { } +} + +public class Class2 +{ + public void foo() { - new Program().B($$); + Class1 c1 = new Class1(); + c1.MyMethod($$); } - public Program() + + }"; + var actual = await GetSignatureHelp(source); + Assert.Equal(4, actual.Signatures.Count()); + } + + [Fact] + public async Task InheritedInaccesibleMethods() + { + const string source = +@"public class MyBase +{ + private void MyMethod(int a) { } +} + +public class Class1 : MyBase +{ + public void MyMethod(int a, int b, int c) { } + protected void MyMethod(int a, int b, int c, int d) { } +} + +public class Class2 +{ + public void foo() { + Class1 c1 = new Class1(); + c1.MyMethod($$); } - public bool B(this Program p, int n) + + }"; + var actual = await GetSignatureHelp(source); + Assert.Single(actual.Signatures); + + var signature = actual.Signatures.ElementAt(0); + Assert.Equal(3, signature.Parameters.Count()); + } + + [Fact] + public async Task InheritedProtectedMethod() + { + const string source = +@"class A +{ + protected void M1() { } +} + +class B : A +{ + void M1(int a) { - return p.Foo() > n; + M1($$) + } +}"; + var actual = await GetSignatureHelp(source); + Assert.Equal(2,actual.Signatures.Count()); + } + + [Fact] + public async Task InheritedProtectedMethodWithThis() + { + const string source = +@"class A +{ + protected void M1() { } +} + +class B : A +{ + void M1(int a) + { + this.M1($$) + } +}"; + var actual = await GetSignatureHelp(source); + Assert.Equal(2, actual.Signatures.Count()); + } + + [Fact] + public async Task InheritedProtectedMethodWithBase() + { + const string source = +@"class A +{ + protected void M1() { } +} + +class B : A +{ + void M1(int a) + { + base.M1($$); } }"; + var actual = await GetSignatureHelp(source); + Assert.Single(actual.Signatures); + Assert.Empty(actual.Signatures.ElementAt(0).Parameters); + } + [Fact] + public async Task StaticContextMethod1() + { + const string source = +@"class A +{ + protected static void M1(int a) { } + public void M1(double b) { } +} + +class B : A +{ + static void M1() + { + A.M1($$); + } + public void M1(string c) { } +}"; var actual = await GetSignatureHelp(source); Assert.Single(actual.Signatures); - Assert.Single(actual.Signatures.ElementAt(actual.ActiveSignature).Parameters); - Assert.Equal("n", actual.Signatures.ElementAt(actual.ActiveSignature).Parameters.ElementAt(0).Name); + + var signature = actual.Signatures.ElementAt(0); + Assert.Single(signature.Parameters); + Assert.Equal("int a", signature.Parameters.ElementAt(0).Label); } + [Fact] + public async Task StaticContextMethod2() + { + const string source = +@"class A +{ + protected static void M1(int a) { } + public void M1(int a,int b) { } +} + +class B : A +{ + static void M1(int a,int b,int c) + { + B.M1($$) + } + public void M1(int a,int b,int c,int d) { } +}"; + var actual = await GetSignatureHelp(source); + Assert.Equal(2, actual.Signatures.Count()); + var signatures = actual.Signatures.OrderBy(sig => sig.Parameters.Count()); + Assert.Single(signatures.ElementAt(0).Parameters); + Assert.Equal(3,signatures.ElementAt(1).Parameters.Count()); + } + + [Fact] + public async Task InstanceContextMethod() + { + const string source = +@"class A +{ + protected static void M1(int a) { } + public void M1(int a, int b) { } +} + +class B : A +{ + void M1(int a,int b,int c) + { + M1($$) + } + static void M1(int a,int b,int c,int d) { } +}"; + var actual = await GetSignatureHelp(source); + Assert.Equal(4, actual.Signatures.Count()); + } + + [Fact] + public async Task OverloadedExtensionMethods1() + { + const string source = +@"public static class ExtensionMethods +{ + public static void MyMethod(this string value, int number) + { + } +} + +class Program +{ + public static void MyMethod(string a, int b) + { + } + public static void Main() + { + string value = ""Hello""; + value.MyMethod($$); + } +}"; + var actual = await GetSignatureHelp(source); + Assert.Single(actual.Signatures); + + var signature = actual.Signatures.ElementAt(0); + Assert.Equal("void string.MyMethod(int number)",signature.Label); + Assert.Single(signature.Parameters); + Assert.Equal("number", signature.Parameters.ElementAt(0).Name); + } + + [Fact] + public async Task OverloadedExtensionMethods2() + { + const string source = +@"public static class ExtensionMethods +{ + public static void MyMethod(this string value, int number) + { + } +} + +class Program +{ + public static void MyMethod(string a, int b) + { + } + public static void Main() + { + string value = ""Hello""; + MyMethod($$); + } +}"; + var actual = await GetSignatureHelp(source); + Assert.Single(actual.Signatures); + + var signature = actual.Signatures.ElementAt(0); + Assert.Equal("void Program.MyMethod(string a, int b)", signature.Label); + } + + [Fact] + public async Task SkipReceiverOfExtensionMethods() + { + const string source = +@"public class Program1 +{ + public Program1() { } +} + +public static class ExtensionClass{ + public static bool B(this Program1 p, int n) + { + return p.Foo() > n; + } +} + +public class ProgramClass +{ + public static void Main() + { + new Program1().B($$); + } +}"; + var actual = await GetSignatureHelp(source); + Assert.Single(actual.Signatures); + + var signature = actual.Signatures.ElementAt(0); + Assert.Single(signature.Parameters); + Assert.Equal("n", signature.Parameters.ElementAt(0).Name); + Assert.Equal("int n", signature.Parameters.ElementAt(0).Label); + } + private async Task GetSignatureHelp(string source) { var testFile = new TestFile("dummy.cs", source);