diff --git a/src/EditorFeatures/CSharp/AutomaticCompletion/Sessions/LessAndGreaterThanCompletionSession.cs b/src/EditorFeatures/CSharp/AutomaticCompletion/Sessions/LessAndGreaterThanCompletionSession.cs index 88c700b4f169ee760f736bc7822c531a3074038e..bce9c139c04f6afcdcf51786800be098f586b0e8 100644 --- a/src/EditorFeatures/CSharp/AutomaticCompletion/Sessions/LessAndGreaterThanCompletionSession.cs +++ b/src/EditorFeatures/CSharp/AutomaticCompletion/Sessions/LessAndGreaterThanCompletionSession.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editor.Implementation.AutomaticCompletion; using Microsoft.CodeAnalysis.Editor.Implementation.AutomaticCompletion.Sessions; @@ -58,8 +59,21 @@ private bool PossibleTypeArgument(ITextSnapshot snapshot, SyntaxToken token, Can } var model = document.GetSemanticModelAsync(cancellationToken).WaitAndGetResult(cancellationToken); - var info = model.GetSymbolInfo(node.Left, cancellationToken); + // Analyze node on the left of < operator to verify if it is a generic type or method. + var leftNode = node.Left; + if (leftNode is ConditionalAccessExpressionSyntax) + { + // If node on the left is a conditional access expression, get the member binding expression + // from the innermost conditional acccess expression, which is the left of < operator. + // e.g: Case a?.b?.c< : we need to get the conditional access expression .b?.c and analyze its + // member binding expression (the .c) to see if it is a generic type/method. + // Case a?.b?.c.d< : we need to analyze .c.d + // Case a?.M(x => x?.P)?.M2< : We need to analyze .M2 + leftNode = leftNode.GetInnerMostConditionalAccessExpression().WhenNotNull; + } + + var info = model.GetSymbolInfo(leftNode, cancellationToken); return info.CandidateSymbols.Any(IsGenericTypeOrMethod); } diff --git a/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLessAndGreaterThanCompletionTests.cs b/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLessAndGreaterThanCompletionTests.cs index d69a9ccbbb791faa512a0dbeb9ab8d0a164af0ee..092325b0f0fbbc667c40c3ab1011f5e6757a9dfc 100644 --- a/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLessAndGreaterThanCompletionTests.cs +++ b/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLessAndGreaterThanCompletionTests.cs @@ -138,23 +138,6 @@ public void Multiple_Nested() public void TypeArgument_Invalid() { var code = @"class C -{ - void Method() - { - List$$ - } -}"; - using (var session = CreateSession(code)) - { - Assert.NotNull(session); - CheckStart(session.Session, expectValidSession: false); - } - } - - [Fact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] - public void TypeArgument_Invalid2() - { - var code = @"class C { void Method() { @@ -170,7 +153,7 @@ void Method() } [Fact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] - public void TypeArgument2() + public void TypeArgument1() { var code = @"class C { @@ -187,7 +170,7 @@ void Method() } [Fact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] - public void TypeArgument3() + public void TypeArgument2() { var code = @"class C { @@ -294,6 +277,169 @@ void Test() } } + [WorkItem(1628, "https://github.com/dotnet/roslyn/issues/1628")] + [Fact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] + public void NotInLessThanComparisonOperation() + { + var code = @"using System.Linq; +class C +{ + void Test(int[] args) + { + var a = args[0]$$ + } +}"; + using (var session = CreateSession(code)) + { + Assert.NotNull(session); + CheckStart(session.Session, expectValidSession: false); + } + } + + [WorkItem(1628, "https://github.com/dotnet/roslyn/issues/1628")] + [Fact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] + public void NotInLessThanComparisonOperationAfterConditionalAccessExpression() + { + var code = @"using System.Linq; +class C +{ + void Test(object[] args, object[] other) + { + var a = args?.First()$$ + } +}"; + using (var session = CreateSession(code)) + { + Assert.NotNull(session); + CheckStart(session.Session, expectValidSession: false); + } + } + + [WorkItem(1628, "https://github.com/dotnet/roslyn/issues/1628")] + [Fact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] + public void TypeArgumentInConditionalAccessExpressionSimple() + { + var code = @"using System.Linq; +class C +{ + void Test(object[] args) + { + args?.OfType$$ + } +}"; + using (var session = CreateSession(code)) + { + Assert.NotNull(session); + CheckStart(session.Session); + } + } + + [WorkItem(1628, "https://github.com/dotnet/roslyn/issues/1628")] + [Fact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] + public void TypeArgumentInConditionalAccessExpressionNested() + { + var code = @"class C +{ + void Test() + { + Outer t = new Outer(); + t?.GetInner()?.Method$$ + } +} +class Outer +{ + public Inner GetInner() + { + return new Inner(); + } +} +class Inner +{ + public void Method() { } +}"; + using (var session = CreateSession(code)) + { + Assert.NotNull(session); + CheckStart(session.Session); + Type(session.Session, "int"); + CheckOverType(session.Session); + } + } + + [WorkItem(1628, "https://github.com/dotnet/roslyn/issues/1628")] + [Fact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] + public void TypeArgumentInConditionalAccessExpressionDeeplyNested() + { + var code = @"class C +{ + void Test() + { + new Outer1()?.GetInner()?.GetInner().DoSomething$$ + } +} +internal class Outer1 +{ + public Outer2 GetInner() + { + return new Outer2(); + } +} +internal class Outer2 +{ + public Outer2() { } + public Inner GetInner() + { + return new Inner(); + } +} +internal class Inner +{ + public Inner() { } + public void DoSomething() { } +}"; + using (var session = CreateSession(code)) + { + Assert.NotNull(session); + CheckStart(session.Session); + } + } + + [WorkItem(1628, "https://github.com/dotnet/roslyn/issues/1628")] + [Fact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] + public void TypeArgumentInConditionalAccessExpressionWithLambdas() + { + var code = @"using System; +using System.Collections.Generic; +using System.Linq; + +class Program +{ + void Foo(object[] args) + { + var a = new Outer(); + a?.M(x => x?.ToString())?.Method$$ + } +} + +public class Outer +{ + internal Inner M(Func p) + { + throw new NotImplementedException(); + } +} + +public class Inner +{ + public void Method() { } +}"; + using (var session = CreateSession(code)) + { + Assert.NotNull(session); + CheckStart(session.Session); + } + } + internal Holder CreateSession(string code) { return CreateSession( diff --git a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs index 621ec6b8446daebbfafce7e16783b52fba92957b..47efbdcc0f7fe61f97617187382697aed1954541 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs @@ -1201,5 +1201,21 @@ public static ConditionalAccessExpressionSyntax GetParentConditionalAccessExpres return null; } + + public static ConditionalAccessExpressionSyntax GetInnerMostConditionalAccessExpression(this SyntaxNode node) + { + if (!(node is ConditionalAccessExpressionSyntax)) + { + return null; + } + + var result = (ConditionalAccessExpressionSyntax)node; + while (result.WhenNotNull is ConditionalAccessExpressionSyntax) + { + result = (ConditionalAccessExpressionSyntax)result.WhenNotNull; + } + + return result; + } } }