Skip to content

Completion for property-based pattern and 'switch' keyword in switch expression#26553

Merged
jcouv merged 5 commits intodotnet:features/recursive-patternsfrom
jcouv:patterns-completion
May 10, 2018
Merged

Completion for property-based pattern and 'switch' keyword in switch expression#26553
jcouv merged 5 commits intodotnet:features/recursive-patternsfrom
jcouv:patterns-completion

Conversation

@jcouv
Copy link
Copy Markdown
Member

@jcouv jcouv commented May 2, 2018

The first commit in this PR is the subset of changes from last PR (#26474) which relate to completion. The second commit adds some tests and adopts the GetTypeInfo API.
I'll start a separate PR for the subset of changes related to formatting.

@Neme12 @CyrusNajmabadi Sorry for the PR churn. I believe this will be easier to review this way. Also, this allowed me to rebase to use new GetTypeInfo API which addressed many of the comments.

Ask Mode template not completed

Customer scenario

What does the customer do to get into this situation, and why do we think this
is common enough to address for this release. (Granted, sometimes this will be
obvious "Open project, VS crashes" but in general, I need to understand how
common a scenario is)

Bugs this fixes

(either VSO or GitHub links)

Workarounds, if any

Also, why we think they are insufficient for RC vs. RC2, RC3, or RTW

Risk

This is generally a measure our how central the affected code is to adjacent
scenarios and thus how likely your fix is to destabilize a broader area of code

Performance impact

(with a brief justification for that assessment (e.g. "Low perf impact because no extra allocations/no complexity changes" vs. "Low")

Is this a regression from a previous update?

Root cause analysis

How did we miss it? What tests are we adding to guard against it in the future?

How was the bug found?

(E.g. customer reported it vs. ad hoc testing)

Test documentation updated?

If this is a new non-compiler feature or a significant improvement to an existing feature, update https://github.com/dotnet/roslyn/wiki/Manual-Testing once you know which release it is targeting.

@jcouv jcouv added this to the 16.0 milestone May 2, 2018
@jcouv jcouv self-assigned this May 2, 2018
@jcouv jcouv requested a review from a team as a code owner May 2, 2018 02:52
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.

my hope is that this code is very similar to similar code we have for object initializers. Is that the case? If you did have to deviate can you give the broad strokes of why/how?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, I followed object initializers very closely. I copied the abstract object initializer code to start with ;-)

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 method looks pretty busted in many ways. For example:

 if (token.GetAncestor<BlockSyntax>() == null)

That seems like it means that we won't get 'switch' completion in an arrow method. Can you add test for this and fix up this method accordingly.

Note: afaict this method could be so much simpler. It's really just "are we after a complete 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.

nit: consider renameing "IsAfterCompletelyWrittenExpression". but i don't care :)

@jcouv jcouv force-pushed the patterns-completion branch from dffe09b to 1c6184b Compare May 2, 2018 03:54
@jcouv jcouv force-pushed the patterns-completion branch from 2e6e0c4 to 37b6206 Compare May 2, 2018 04:02
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders
{
[Trait(Traits.Feature, Traits.Features.Completion)]
public class PropertySubPatternCompletionProviderTests : AbstractCSharpCompletionProviderTests
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.

nit: Pattern should probably be lowercase to match SyntaxKind.PropertySubpattern

_ = this is ( , { $$ })
}

public void Deconstruct(out Program x, out Program y) => throw null;
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.

consider changing this test or adding a new one where these two types are different

}

[Fact]
public async Task PropertiesInRecursivePattern_WithPositionalPattern()
Copy link
Copy Markdown
Contributor

@Neme12 Neme12 May 2, 2018

Choose a reason for hiding this comment

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

is this test different from PropertiesInRecursivePattern_WithPositional?


void M()
{
_ = this is Program (1, 2) { $$ }
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.

consider making this type different so that this test expects a different behavior from the one below

var document = context.Document;
var position = context.Position;
var cancellationToken = context.CancellationToken;
var workspace = document.Project.Solution.Workspace;
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.

it looks like workspace is unused

var workspace = document.Project.Solution.Workspace;
var semanticModel = await document.GetSemanticModelForSpanAsync(new TextSpan(position, length: 0), cancellationToken).ConfigureAwait(false);

var token = TryGetOpenBraceOrCommaInPropertyPattern(document, semanticModel, position, cancellationToken);
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.

It looks like TryGetOpenBraceOrCommaInPropertyPattern only uses the semanticModel to get the syntax tree. I would prefer if we could:

  1. pass in the syntax tree directly
  2. retrieve the syntax tree through other means than the semantic model and only get the semantic model after the early return in if (token == default) below

=> CompletionUtilities.IsTriggerCharacter(text, characterPosition, options) || text[characterPosition] == ' ';

private static SyntaxToken TryGetOpenBraceOrCommaInPropertyPattern(
Document document, SemanticModel semanticModel, int position, CancellationToken cancellationToken)
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.

apart from my comment about the semanticModel, it looks like document is completely unused

internal override bool IsInsertionTrigger(SourceText text, int characterPosition, OptionSet options)
=> CompletionUtilities.IsTriggerCharacter(text, characterPosition, options) || text[characterPosition] == ' ';

private static SyntaxToken TryGetOpenBraceOrCommaInPropertyPattern(
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.

nit: should be named TryGetOpenBraceOrCommaInPropertySubpattern because it doesn't actually have to be inside a PropertyPattern

return default;
}

return (token.Parent.IsKind(SyntaxKind.PropertySubpattern) == true) ? token : default;
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.

IsKind returns bool so == true is not needed

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.

if you remove that, please remove the parens as well

}

var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken);
token = token.GetPreviousTokenIfTouchingWord(position);
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.

nit: consider merging this with the previous statement: .FindTokenOnLeftOfPosition(...).GetPreviousTokenIfTouchingWord

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Left as-is. I find it better for debugging.

}

var pattern = (PatternSyntax)token.Parent.Parent;
var type = semanticModel.GetTypeInfo(pattern).ConvertedType;
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.

pass in cancellationToken

return true;
}

if (symbol.IsKind(SymbolKind.Property) && !((IPropertySymbol)symbol).IsWriteOnly)
Copy link
Copy Markdown
Contributor

@Neme12 Neme12 May 2, 2018

Choose a reason for hiding this comment

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

nit: please use pattern matching

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 May 2, 2018

Choose a reason for hiding this comment

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

what about indexers? are we sure they're not offered?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correct, no indexers in current recursive patterns. See grammar.

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 May 3, 2018

Choose a reason for hiding this comment

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

Yes, I just wanted to make sure we correctly don't offer indexers as opposed to showing something like this[] in the completion list, because they're a property symbol after all. Maybe they're filtered out somewhere further down in the completion logic so you don't have to worry about that. But if not, there would need to be an additional check here: !property.IsIndexer && !property.IsWriteOnly But I don't know if that's needed, that's why I was asking.

!m.IsImplicitlyDeclared);

// Filter out those members that have already been typed
var alreadyTestedMembers = GetTestedMembers(semanticModel.SyntaxTree, position, cancellationToken);
Copy link
Copy Markdown
Contributor

@Neme12 Neme12 May 2, 2018

Choose a reason for hiding this comment

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

you don't need to pass in the syntax tree or the position. it is only used inside to find the target token which is already found at this point, so you can pass in token directly

}

// List the members that are already tested in this property sub-pattern
private static HashSet<string> GetTestedMembers(SyntaxTree tree, int position, CancellationToken cancellationToken)
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.

if you pass token directly, you can remove all these 3 parameters

.GetPreviousTokenIfTouchingWord(position);

// We should have gotten back a { or ,
if (token.IsKind(SyntaxKind.CommaToken, SyntaxKind.OpenBraceToken))
Copy link
Copy Markdown
Contributor

@Neme12 Neme12 May 2, 2018

Choose a reason for hiding this comment

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

also you can get rid of this check, because this was already done inside TryGetOpenBraceOrCommaInPropertyPattern. the token you get from that will always satisfy this

// We should have gotten back a { or ,
if (token.IsKind(SyntaxKind.CommaToken, SyntaxKind.OpenBraceToken))
{
if (token.Parent is PropertySubpatternSyntax subpattern)
Copy link
Copy Markdown
Contributor

@Neme12 Neme12 May 2, 2018

Choose a reason for hiding this comment

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

actually, maybe this method should only accept PropertySubpatternSyntax as a parameter, not the token itself

{
if (token.Parent is PropertySubpatternSyntax subpattern)
{
return new HashSet<string>(subpattern.SubPatterns.Select(
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.

hm, it looks like the compiler itself is inconsistent about the casing of SubPattern

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.

also, the name of this property is a little confusing... it doesn't return subpatterns... maybe it should just be Elements?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The syntax tree is likely to be revised (#26540). I took a note there to review casing. For naming, feel free to chime in there or in the PR (once available).

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 3, 2018

@DustinCampbell @dotnet/roslyn-ide for review. Thanks

This completion provider is much like the one for object creation.
When you type a property pattern { Property1: pattern1, Property2, pattern2, ...} we'll offer completion on "Property1" and "Property2".

{
[Trait(Traits.Feature, Traits.Features.Completion)]
public class PropertySubPatternCompletionProviderTests : AbstractCSharpCompletionProviderTests
public class PropertySubpatternCompletionProviderTests : AbstractCSharpCompletionProviderTests
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.

tiny nit: the file name doesn't match anymore

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 3, 2018

@jaredpar @gafter @dotnet/roslyn-infrastructure For some reason, it looks like the CI legs are no longer getting triggered on this branch (features/recursive-patterns). They were working fine earlier during this PR.

@jasonmalinowski
Copy link
Copy Markdown
Member

@dotnet-bot retest this please.

@Neme12
Copy link
Copy Markdown
Contributor

Neme12 commented May 3, 2018

Note: there should also be a completion provider for name: inside a deconstruction pattern, similar to the one for named arguments. Please file an issue for that

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 4, 2018

@jasonmalinowski I still only see two CI legs

@jcouv jcouv closed this May 4, 2018
@jcouv jcouv reopened this May 4, 2018
@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 4, 2018

@jasonmalinowski Never mind. Closing and re-opening fixed the issue. Thanks

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 4, 2018

@Neme12 Filed #26619 (offer completion in deconstruction pattern)

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 7, 2018

@DustinCampbell @dotnet/roslyn-ide All the feedback so has been addressed (with the exception of a file rename, which I'll do as last thing). Please review.
This PR offers completion in property-based patterns (similar to completion in object creation) and recommends switch keyword for switch expressions.

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 8, 2018

@dotnet/roslyn-ide Please review.

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 9, 2018

@dotnet/roslyn-ide Please review. This PR didn't receive feedback for a week. Thanks

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

@jcouv i can look later tonight if htat helps!

";
// VerifyItemExistsAsync also tests with the item typed.
await VerifyItemExistsAsync(markup, "P1");
await VerifyItemExistsAsync(markup, "P2");
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.

do these test when nothing after the $$ is in the file? Or do we need specific tests for that?

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.

@CyrusNajmabadi they do not (I was curious about making them do that once)

{
void M()
{
_ = this is ({ $$ }) // no deconstruction into 1 element
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 is a shame because it dpeends on the rest of the contents being there after the {}. What happen if all you've typed is ```({ $$``?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we should be able to make deconstruction works like method invocations, so that we use our best guess/overload while you're typing. Tracked by #26470

I'll add the test you suggested

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.

I think the question is: will this work if you only type ({ $$ without the closing brace and paren? is that even parsed as a pattern?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is parsed as a pattern (I suspect because what can follow an is is fairly restricted, so that guides the parsing).
But the semantic information is no better than when the closing brace and paren are present.

}

var semanticModel = await document.GetSemanticModelForSpanAsync(new TextSpan(position, length: 0), cancellationToken).ConfigureAwait(false);
var pattern = (PatternSyntax)token.Parent.Parent;
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.

so PropertySubpatterns must be parented by patterns? Any concern aobut that ever changing? If so, consider an "is" check.

IEnumerable<ISymbol> members = semanticModel.LookupSymbols(position, type);
members = members.Where(m => m.CanBeReferencedByName &&
IsFieldOrReadableProperty(m) &&
!m.IsImplicitlyDeclared);
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.

can implicitly declared members ever be referenced by name? (i'm guessing this is copied from elsewhere... if so, fine to leave as is).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, this is copied from completion logic for object initializers ;-)

}

// List the members that are already tested in this property sub-pattern
private static HashSet<string> GetTestedMembers(PropertySubpatternSyntax propertySubpattern)
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.

i feel like this could be inlined. up to you.

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 10, 2018

@jinujoseph Good to merge?

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 10, 2018

Thanks all!
I'll go ahead and merge.

@jinujoseph
Copy link
Copy Markdown
Contributor

@jcouv thanks, good to merge.
Thanks @dpoeschl for reviewing

@jcouv jcouv merged commit b7b14b6 into dotnet:features/recursive-patterns May 10, 2018
@jcouv jcouv deleted the patterns-completion branch May 10, 2018 22:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants