Skip to content

Tuple support for DeclarationNameCompletionProvider (name suggestion)#22673

Merged
sharwell merged 11 commits intodotnet:masterfrom
MaStr11:DeclarationNameCompletionProvider_TupleSupport
May 25, 2018
Merged

Tuple support for DeclarationNameCompletionProvider (name suggestion)#22673
sharwell merged 11 commits intodotnet:masterfrom
MaStr11:DeclarationNameCompletionProvider_TupleSupport

Conversation

@MaStr11
Copy link
Copy Markdown
Contributor

@MaStr11 MaStr11 commented Oct 12, 2017

(note from jcouv: Merge, do not squash this PR, as it includes merge commits from master)

Customer scenario

Suggested names support for tuples.

Bugs this fixes:

#22342 (Kind of)

Workarounds, if any

No. Feature improvement.

Risk

Name suggestion might be triggered in inappropriate circumstances.

Performance impact

Editor performance and memory critical.

Is this a regression from a previous update?

No. Feature improvement.

Root cause analysis:

Tuple support was missing.

How was the bug found?

Found by investigating #22342. #22342 reports a (very) special case but more general cases were missing too.

Test documentation updated?

No

Remarks

It seems DeclarationNameCompletionProvider does not handle tuples at all. In the following example none of the occurrence of name, action and accessViolationException is suggested):

static void Main(string[] args)
{
    (Array array, Action action) Test((Array array, AccessViolationException accessViolationException) tuple) => default;
    (var array, var action) = Test((null, null));
}

#22342 suggested that there is support for tuples but there isn't (Just typing (Array suggest array because it triggers IsPossibleVariableOrLocalMethodDeclaration.).

This PR adds support for the cases above (except (var array, var action)).

TupleElementDefinition1 to TupleElementDefinition7 test the new tuple support. The remaining two tests fail because

  1. TupleElementTypeInference I couldn't get the typeInference to work and
  2. TupleElementInGenericTypeArgument The expression tree generated by the code of Suggested names not provided for tuple elements in generic type arguments #22342 System.Func<(System.Action $$ has nothing to do with tuples at all (combination of LessThan and Parenthesized expression). It's questionable if this really should be supported.

I hardly understand what my call to IsLastTokenOfType does. It gives the desired result but reviewers should take a close look whether that call is appropriate.

@MaStr11 MaStr11 changed the title Initial support for tuples. WIP Tuple support for DeclarationNameCompletionProvider (name suggestion) Oct 12, 2017
@jcouv jcouv added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label Oct 24, 2017
@MaStr11 MaStr11 requested a review from a team as a code owner December 12, 2017 13:42
@MaStr11 MaStr11 changed the title WIP Tuple support for DeclarationNameCompletionProvider (name suggestion) Tuple support for DeclarationNameCompletionProvider (name suggestion) Dec 12, 2017
@MaStr11
Copy link
Copy Markdown
Contributor Author

MaStr11 commented Jan 17, 2018

Could I get some feedback please?

var typeInferenceService = document.GetLanguageService<ITypeInferenceService>();

if (IsParameterDeclaration(token, semanticModel, position, cancellationToken, out var result)
if (IsTupleElementDeclaration(token, semanticModel, position, cancellationToken, out var result)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Why is IsTupleElementDeclaration up here, but IsTupleExpressionDeclaration called much lower in the list?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsTupleElementDeclaration needs to come before IsParameterDeclaration. Otherwise constructs as
void M((System.Array $$ or (System.Array $$ are treated by IsParameterDeclaration and the completion list is empty. This is covered by TupleElementDefinition6 and TupleElementDefinition7.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise constructs as void M((System.Array $$ [is] treated by IsParameterDeclaration

That itself seems like a bug. IsParameterDeclaration doesn't seem like it should be true here...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsParameterDeclaration doesn't seem like it should be true here

This is the syntax tree of void M((System.Array $$))

2018-03-26 10_41_30-roslyn

So the token is part of a TupleType that itself is a parameter . So @CyrusNajmabadi is right as this is a type declaration taking place in a parameter. This can be solved the way I did (check for tuple declarations before parameter declarations) or by special casing TupleType in IsParameterDeclaration. I'm fine with both ways.


private static bool IsTupleExpressionDeclaration(SyntaxToken token, SemanticModel semanticModel, int position, CancellationToken cancellationToken, out NameDeclarationInfo result)
{
if (token.GetAncestor<TupleExpressionSyntax>() != null)
Copy link
Copy Markdown
Contributor

@sharwell sharwell Mar 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why is this GetAncestor? I'm concerned that in a nested expression scenario this could provide incorrect results. Can you describe the scenarios that IsTupleExpressionDeclaration applies is meant to handle?

Copy link
Copy Markdown
Contributor Author

@MaStr11 MaStr11 Mar 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TupleExpressionSyntax is a few steps above the token:

(System.Array array, System.Action $$

IdentifierNameSyntax IdentifierName Action
↳ MemberAccessExpressionSyntax SimpleMemberAccessExpression System.Action
   ↳ ArgumentSyntax Argument System.Action
     ↳ TupleExpressionSyntax TupleExpression (System.Array array, System.Action

The call IsFollowingTypeOrComma does some further checks of the relationship between the token and the node so nesting shouldn't be a problem.

I also added some tests in b0a7390 and 07f6e63.

@MaStr11
Copy link
Copy Markdown
Contributor Author

MaStr11 commented Mar 25, 2018

@sharwell Is the IDE-team interested in this or should I close it?

switch (argumentSyntax.Expression?.Kind())
{
case SyntaxKind.DeclarationExpression:
return (argumentSyntax.Expression as DeclarationExpressionSyntax).Type;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just do a normal cast, you did the 'case' check above.

case SyntaxKind.DeclarationExpression:
return (argumentSyntax.Expression as DeclarationExpressionSyntax).Type;
default:
return argumentSyntax.Expression;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems odd... you asked for the type syntax, but you're returning just hte expression? I can envision why you want this. But this line def needs a comment explaining why this is ok.

{
switch (argumentSyntax.Expression?.Kind())
{
case SyntaxKind.DeclarationExpression:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pattern matching?

@jaredpar
Copy link
Copy Markdown
Member

We apologize, but we are closing this PR due to code drift. We regret letting this PR go so long without attention, but at this point the code base has changed too much for us to revisit older PRs. Going forward, we will manage PRs better under an SLA currently under draft in issue #26266 – we encourage you to add comments to that draft as you see fit. Although that SLA is still in draft form, we nevertheless want to move forward with the identification of older PRs to give contributors as much early notice as possible to review and update them.

If you are interested in pursuing this PR, please reset it against the head of master and we will reopen it. Thank you!

@jaredpar jaredpar closed this Apr 19, 2018
@jaredpar jaredpar added the Resolution-Expired The request is closed due to inactivity under our contribution review policy. label Apr 19, 2018
@MaStr11
Copy link
Copy Markdown
Contributor Author

MaStr11 commented Apr 27, 2018

@jaredpar Please reopen this PR and assign a "buddy". It was me waiting for feedback not the other way around.

@sharwell sharwell reopened this Apr 27, 2018
@sharwell sharwell self-assigned this Apr 27, 2018
@sharwell sharwell removed the Resolution-Expired The request is closed due to inactivity under our contribution review policy. label Apr 27, 2018
@jcouv jcouv added the Area-IDE label May 3, 2018
}

[WorkItem(22342, "https://github.com/dotnet/roslyn/issues/22342")]
[Fact(Skip = "Not yet supported"), Trait(Traits.Feature, Traits.Features.Completion)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please unskip the test, capture the current behavior and add a link to an issue for tracking the remaining gap.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SyntaxToken token, SemanticModel semanticModel, int position,
CancellationToken cancellationToken, out NameDeclarationInfo result)
{
if (token.GetAncestor(node => node.IsKind(SyntaxKind.TupleExpression)) != null)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ancestor check feels too loose. Imagine an invocation inside a tuple expression, and the caret is after the comma in the argument list of the invocation.
Can we just check the parent or grandparent instead?

Copy link
Copy Markdown
Contributor Author

@MaStr11 MaStr11 May 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to come up with a test to make this fail but I couldn't. Can you have a look in commit 3ebfe28? We somehow need to get a type declaration inside that call to make the completion fail. The method examines code like the following and I don't see how there can be a method invocation:

class Test
{
    void Do()
    {
        (System.Array array, System.Action $$ 
    }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See also my previous discussion with @sharwell #22673 (comment)

}

private static bool IsTupleExpressionDeclaration(
SyntaxToken token, SemanticModel semanticModel, int position,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The position argument seems unused.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a bunch of methods with about the same signature:

                if (IsTupleElementDeclaration(token, semanticModel, position, cancellationToken, out var result)
                    || IsParameterDeclaration(token, semanticModel, position, cancellationToken, out result)
                    || IsTypeParameterDeclaration(token, semanticModel, position, cancellationToken, out result)
                    || IsVariableDeclaration(token, semanticModel, position, cancellationToken, out result)
                    || IsIncompleteMemberDeclaration(token, semanticModel, position, cancellationToken, out result)
                    || IsFieldDeclaration(token, semanticModel, position, cancellationToken, out result)
                    || IsMethodDeclaration(token, semanticModel, position, cancellationToken, out result)
                    || IsPropertyDeclaration(token, semanticModel, position, cancellationToken, out result)
                    || IsPossibleOutVariableDeclaration(token, semanticModel, position, typeInferenceService, cancellationToken, out result)
                    || IsTupleExpressionDeclaration(token, semanticModel, position, cancellationToken, out result)
                    || IsPossibleVariableOrLocalMethodDeclaration(token, semanticModel, position, cancellationToken, out result))

I would like to keep the signature as is just for symmetry.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saw that. Fine to leave as-is :-)

// We also assume that this name could be resolved to a type.
return argumentSyntax.Expression;
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related to this PR: FYI I added a note to the test plan for recursive patterns to test declaration name completion. For instance: e is (System.Array $$.
#25935

return default;
}

private static bool IsTupleElementDeclaration(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Consider renaming this method to IsTupleTypeElement and the following one IsTupleLiteralElement.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done cf1a973.


[WorkItem(22342, "https://github.com/dotnet/roslyn/issues/22342")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async void TupleExpressionDeclaration1()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test name is confusing. This is a tuple type, not a tuple expression.
I think the same applies for some other tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use async Task for all tests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, the existing tests are already fixed if you merge latest master. Please remove async void from your tests too.

Copy link
Copy Markdown
Contributor Author

@MaStr11 MaStr11 May 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jcouv
You are right. The reason for this is that the code under test is parsed as a tuple expression.
2018-05-25 11_55_57-roslyn
I haven't figured out how to find better names for such the tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Neme12 Done (e752f3c). Thanks for also pointing out the already fixed tests in master.

return result.Type != null;
}

private static bool IsTupleExpressionDeclaration(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to add a comment to clarify that this is for handling incomplete code that parsed like a tuple literal.
But the purpose is not to help typing names in legit tuple literals (that's not possible since they don't have any TypeSyntax).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done cf1a973.

Copy link
Copy Markdown
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with review pass (iteration 7).

}

[WorkItem(22342, "https://github.com/dotnet/roslyn/issues/22342")]
[Fact(Skip = "Not yet supported"), Trait(Traits.Feature, Traits.Features.Completion)]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we unskip the previous test, should we unskip this one too?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Muscle memory is strong, my German friend ;-)
"ist"

Copy link
Copy Markdown
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks

@sharwell
Copy link
Copy Markdown
Contributor

@jinujoseph for approval to merge

@jinujoseph
Copy link
Copy Markdown
Contributor

Approved to merge for 15.8.Preview3

@sharwell sharwell merged commit 016111c into dotnet:master May 25, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Approved to merge Area-IDE cla-already-signed Community The pull request was submitted by a contributor who is not a Microsoft employee.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants