Completion for property-based pattern and 'switch' keyword in switch expression#26553
Conversation
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Yes, I followed object initializers very closely. I copied the abstract object initializer code to start with ;-)
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
nit: consider renameing "IsAfterCompletelyWrittenExpression". but i don't care :)
| namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders | ||
| { | ||
| [Trait(Traits.Feature, Traits.Features.Completion)] | ||
| public class PropertySubPatternCompletionProviderTests : AbstractCSharpCompletionProviderTests |
There was a problem hiding this comment.
nit: Pattern should probably be lowercase to match SyntaxKind.PropertySubpattern
| _ = this is ( , { $$ }) | ||
| } | ||
|
|
||
| public void Deconstruct(out Program x, out Program y) => throw null; |
There was a problem hiding this comment.
consider changing this test or adding a new one where these two types are different
| } | ||
|
|
||
| [Fact] | ||
| public async Task PropertiesInRecursivePattern_WithPositionalPattern() |
There was a problem hiding this comment.
is this test different from PropertiesInRecursivePattern_WithPositional?
|
|
||
| void M() | ||
| { | ||
| _ = this is Program (1, 2) { $$ } |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
It looks like TryGetOpenBraceOrCommaInPropertyPattern only uses the semanticModel to get the syntax tree. I would prefer if we could:
- pass in the syntax tree directly
- 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) |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
IsKind returns bool so == true is not needed
There was a problem hiding this comment.
if you remove that, please remove the parens as well
| } | ||
|
|
||
| var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken); | ||
| token = token.GetPreviousTokenIfTouchingWord(position); |
There was a problem hiding this comment.
nit: consider merging this with the previous statement: .FindTokenOnLeftOfPosition(...).GetPreviousTokenIfTouchingWord
There was a problem hiding this comment.
Left as-is. I find it better for debugging.
| } | ||
|
|
||
| var pattern = (PatternSyntax)token.Parent.Parent; | ||
| var type = semanticModel.GetTypeInfo(pattern).ConvertedType; |
| return true; | ||
| } | ||
|
|
||
| if (symbol.IsKind(SymbolKind.Property) && !((IPropertySymbol)symbol).IsWriteOnly) |
There was a problem hiding this comment.
nit: please use pattern matching
There was a problem hiding this comment.
what about indexers? are we sure they're not offered?
There was a problem hiding this comment.
Correct, no indexers in current recursive patterns. See grammar.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
hm, it looks like the compiler itself is inconsistent about the casing of SubPattern
There was a problem hiding this comment.
also, the name of this property is a little confusing... it doesn't return subpatterns... maybe it should just be Elements?
There was a problem hiding this comment.
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).
|
@DustinCampbell @dotnet/roslyn-ide for review. Thanks This completion provider is much like the one for object creation. |
| { | ||
| [Trait(Traits.Feature, Traits.Features.Completion)] | ||
| public class PropertySubPatternCompletionProviderTests : AbstractCSharpCompletionProviderTests | ||
| public class PropertySubpatternCompletionProviderTests : AbstractCSharpCompletionProviderTests |
There was a problem hiding this comment.
tiny nit: the file name doesn't match anymore
|
@dotnet-bot retest this please. |
|
Note: there should also be a completion provider for |
|
@jasonmalinowski I still only see two CI legs |
|
@jasonmalinowski Never mind. Closing and re-opening fixed the issue. Thanks |
|
@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. |
|
@dotnet/roslyn-ide Please review. |
|
@dotnet/roslyn-ide Please review. This PR didn't receive feedback for a week. Thanks |
|
@jcouv i can look later tonight if htat helps! |
| "; | ||
| // VerifyItemExistsAsync also tests with the item typed. | ||
| await VerifyItemExistsAsync(markup, "P1"); | ||
| await VerifyItemExistsAsync(markup, "P2"); |
There was a problem hiding this comment.
do these test when nothing after the $$ is in the file? Or do we need specific tests for that?
There was a problem hiding this comment.
@CyrusNajmabadi they do not (I was curious about making them do that once)
| { | ||
| void M() | ||
| { | ||
| _ = this is ({ $$ }) // no deconstruction into 1 element |
There was a problem hiding this comment.
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 ```({ $$``?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
can implicitly declared members ever be referenced by name? (i'm guessing this is copied from elsewhere... if so, fine to leave as is).
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
i feel like this could be inlined. up to you.
|
@jinujoseph Good to merge? |
|
Thanks all! |
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
GetTypeInfoAPI.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
GetTypeInfoAPI 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.