Skip to content

Some fixes for patterns in IDE#26474

Closed
jcouv wants to merge 19 commits intodotnet:features/recursive-patternsfrom
jcouv:patterns-ide
Closed

Some fixes for patterns in IDE#26474
jcouv wants to merge 19 commits intodotnet:features/recursive-patternsfrom
jcouv:patterns-ide

Conversation

@jcouv
Copy link
Copy Markdown
Member

@jcouv jcouv commented Apr 28, 2018

Addressed:

  • When typing a property-based pattern, completion should be offered for the name of the property. (this is working for many cases, but not all. A more general solution will use the upcoming GetTypeInfo API from the compiler)
  • switch keyword offered for switch expression.
  • Manually verified that UpgradeProject is properly offered.
  • spacing between type and positional pattern
  • missing spacing before positional pattern
  • typing e is { gets a newline before brace

Not yet addressed:

  • typing e switch { then return produces badly indented closing brace. Formatting the document doesn't fix the issue. So that's likely a formatting problem.
  • completion isn't triggered in some contexts (inside property pattern in switch expression).
        _ = this switch
        {
        }; // typing this introduces extra newline and a bad indent on closing brace

        _ = this switch { { P1: 1 } => 1, _ => 2 }; // missing completion on P1

@jcouv jcouv added this to the 16.0 milestone Apr 28, 2018
@jcouv jcouv self-assigned this Apr 28, 2018
@jcouv jcouv requested review from a team as code owners April 28, 2018 14:26
{
// This occurs in error cases.
Debug.Assert(recursive.HasAnyErrors);
//Debug.Assert(recursive.HasAnyErrors);
Copy link
Copy Markdown
Member Author

@jcouv jcouv Apr 28, 2018

Choose a reason for hiding this comment

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

I won't merge this change. It's to unblock manual validation in IDE.
Filed #26467 #Closed

Copy link
Copy Markdown
Member

@gafter gafter Apr 28, 2018

Choose a reason for hiding this comment

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

Fix for that is in #26481

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.

Should a colon be displayed in the completion? Prop:
Should typing enter or tab automatically enter the colon?

}
}

return false;
Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

can this reuse the same logic that is and as keywords use? those also show up generally after an expression #Closed

Copy link
Copy Markdown
Member Author

@jcouv jcouv Apr 28, 2018

Choose a reason for hiding this comment

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

Excellent idea. Thanks #Closed

}

// `switch` follows expressions in switch expressions
internal static bool IsAfterExpression(CSharpSyntaxContext context)
Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

why internal? #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

consider also verifying "P1" and "P2" is absent here #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

nit: extra comma #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

consider also verifying "P2" is absent here #Resolved

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 adding a test that has a comma inside a nested pattern, such as:
this is Program { P2: (1, $$ or this is Program { P2: { something: 1, $$ we don't want P1 here, but we do want after the closing ) or } and a comma

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

consider adding a test where the properties are private but inside Program and therefore accessible #Closed

Copy link
Copy Markdown
Member Author

@jcouv jcouv Apr 28, 2018

Choose a reason for hiding this comment

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

Good idea. Note that is allowed. #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

what about setter only properties? should they be offered or not? #Closed

Copy link
Copy Markdown
Member Author

@jcouv jcouv Apr 28, 2018

Choose a reason for hiding this comment

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

The shouldn't be offered (just like we don't offer read-only properties in object initializers). Fixed.
Thanks! #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

is there a test where this type (Program) is explicitly different from the switch expression type? #Closed

Copy link
Copy Markdown
Member Author

@jcouv jcouv Apr 28, 2018

Choose a reason for hiding this comment

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

Add PropertiesInRecursivePattern_WithOtherType #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

consider adding a nested test where P2 itself is a field #Closed

Copy link
Copy Markdown
Member Author

@jcouv jcouv Apr 28, 2018

Choose a reason for hiding this comment

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

Added PropertiesInRecursivePattern_NestedInField #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

you're not actually testing that P1 is absent, which seems like it was the purpose of this test #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

is there an IsKind overload that takes two arguments? so you could get rid of the || #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

nit: I would probably consider making these separate variables instead of reassigning, but up to you #ByDesign

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

or just inlining this directly: var members = semanticModel.LookupSymbols(position, type).Where #Resolved

Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi Apr 30, 2018

Choose a reason for hiding this comment

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

nit: Please use 'var'. #ByDesign

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 get an error in assignment below if I use var here. Leaving as-is.


In reply to: 185140445 [](ancestors = 185140445)

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

why is this separate? #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

nit: should be untestedMember #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

nit: => #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

consider naming TryGetPatternType #Closed

Copy link
Copy Markdown
Contributor

@Neme12 Neme12 Apr 28, 2018

Choose a reason for hiding this comment

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

can this be static? #Closed

case IPropertySymbol propertySymbol:
return (propertySymbol.Type, token.GetLocation());
case IFieldSymbol fieldSymbol:
return (fieldSymbol.Type, token.GetLocation());
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.

is returning this location really needed here? is it not suffiient to just find the token in the calling method and just use that locatoin? isn't the location just used for things like simplification?

context.IsStatementContext ||
context.IsGlobalStatementContext;
context.IsGlobalStatementContext ||
context.IsIsOrAsContext; // ie. following an expression
Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi Apr 30, 2018

Choose a reason for hiding this comment

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

this seems super bizarre. If you really want to reuse this property, then call it IsIsOrAsOrSwitchContext #Closed

return false;
}

// PROTOTYPE(recursive-patterns): Rename this method
Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi Apr 30, 2018

Choose a reason for hiding this comment

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

yes plz. #Closed

Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi Apr 30, 2018

Choose a reason for hiding this comment

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

possibly: FollowsCompleteExpression #Closed

public static bool IsColonInCasePatternSwitchLabel(this SyntaxToken token)
{
return token.Kind() == SyntaxKind.ColonToken && token.Parent is CasePatternSwitchLabelSyntax;
}
Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi Apr 30, 2018

Choose a reason for hiding this comment

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

nit: => #Closed

{
AddSuppressWrappingIfOnSingleLineOperation(list, switchExpressionArm.GetFirstToken(), switchExpressionArm.GetLastToken());
return;
}
Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi Apr 30, 2018

Choose a reason for hiding this comment

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

do you need a similar suppressoin for the entire switch expression? #Closed

Copy link
Copy Markdown
Member Author

@jcouv jcouv May 1, 2018

Choose a reason for hiding this comment

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

Done. Thanks #Closed


if (currentToken.IsPattern())
{
return null;
Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi Apr 30, 2018

Choose a reason for hiding this comment

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

why? #Closed

Copy link
Copy Markdown
Member Author

@jcouv jcouv May 1, 2018

Choose a reason for hiding this comment

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

Thanks. I've added a suppression on switch expression and pattern case. Then I removed this special case (on similar one below, at line 49). #Closed

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.

Hum, after I removed this, I get a newline before the brace when typing _ = e is {.
It's not showing up in tests for some reason, only in IDE at the moment.

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.

@CyrusNajmabadi
Would you know a good way to test the formatting-as-you-type experience? That will help me capture and investigate behaviors.
In particular, I need to find out what generates a newline when typing x is {. Then I can see what prevents the same from happening when typing if (true) { }, and do the same.


if (currentToken.IsPattern())
{
return 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.

why? this doesn't seem right to me. We would want to adjust newlines. We'd just all want that be overridden by a supression if it was on a single line to start with. But if this is spread over multiple lines, we would want this fixed up, right?

{
_ = this is {
P1: 1,
P2: 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.

indentation seems wrong here.

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've tried various things in IndentBlockFormattingRules, but none seems to have any effect.

@CyrusNajmabadi @heejaechang Any tips on this?

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.

let me see.

Copy link
Copy Markdown
Contributor

@heejaechang heejaechang May 1, 2018

Choose a reason for hiding this comment

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

this kind of thing (expression level), we do not force indentation. looks like it is doing right thing. it is maintaining relative indentation from "_"

if test was written

_ = this is {
    P1 : 1 ...

it will maintain relative indentation between _ and P1.

same way something like below is formatted.

method(1,
2, 
     4);

and format, you will see relative indentation of 2 and 4 are maintained aginst method

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 latest commit I pushed finally has some effect. But it's super weird. Only the first arm of the switch expression got indented... I'll continue poking around.

var endToken = arms.Last().GetLastToken(includeZeroWidth: true);
var span = CommonFormattingHelpers.GetSpanIncludingTrailingAndLeadingTriviaOfAdjacentTokens(startToken, endToken);

AddIndentBlockOperation(list, switchExpression.OpenBraceToken, startToken, endToken, IndentBlockOption.RelativeToFirstTokenOnBaseTokenLine);
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 you want to really force this? that means users can't put it anywhere else?

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 think so. The switch expression should be formatted strictly, like a switch statement (except a bit simpler).

// }
// ```
// Each sub-pattern gets a new line
return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.ForceLines);
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.

we usually never use force line in general formatting rule. that means, user can never put lines between propertysubpatternSyntax.

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.

is this syntax type only exist in your feature branch?

PropertySubpatternSyntax

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 syntax is in features/recursive-patterns (and also in features/build-demo).

Copy link
Copy Markdown
Member Author

@jcouv jcouv May 1, 2018

Choose a reason for hiding this comment

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

This is analogous to the colon in switch labels (see below, line 124).
I'm mistaken. The handling of switch labels below is doing PreserveLines. I'll fix.

{
_ = this switch
{
{ P1: 1 } => true,
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 you dont have new line rule for ","

}

if (token.Parent.IsKind(SyntaxKind.PropertySubpattern) == false ||
token.Parent.Parent.IsKind(SyntaxKind.PropertyPattern) == false)
Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi May 1, 2018

Choose a reason for hiding this comment

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

== false and || feels weird.... why not just if token.parent.iskind() && token.parent.parent.iskind() ? token : default? #Closed

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.

the parent of a PropertySubpattern can also be a DeconstructionPattern:

(1, 2) { P1: 1, P2: 2 }

// PROTOTYPE(NullableReferenceTypes):
// All the cases above should be replaced by using the semantic model's GetTypeInfo (ConvertedType) once that is integrated

return 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.

i feel like there's a bunch of a logic shared here and between the work i'm doing in my PR where i update the TypeInferenceService... i don't have a great suggestion on how to unify things though :-/

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 logic for TryGetPatternType can be removed once Neal's PR for GetTypeInfo gets merged.

There will still be some holes. For instance, when typing a property pattern inside a positional pattern and the positional pattern isn't completely typed yet, so the Deconstruct method may not be fully resolved yet. But I think we'll fix that from compiler side (best overload candidate should be returned in that case, for IDE to use (just as in SignatureHelp).

Beyond that, I don't expect much need for inference from IDE side in patterns.

if (token.Parent is PropertySubpatternSyntax subpattern)
{
return new HashSet<string>(subpattern.SubPatterns.Select(
p => p.NameColon?.Name?.Identifier.ValueText).Where(s => !string.IsNullOrEmpty(s)));
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 have subpatterns, can you not have a NameColon or Name? Are those really optional? if so... i'm curious why....

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.

See error syntax, which shows no NameColon.
I don't know about no Name, but I preferred to be defensive.

{
// is Goo { $$
// is Goo { P1: 0, $$
var propertyPattern = (PropertyPatternSyntax)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.

This can also be a DeconstructionPatternSyntax

}

[Fact(Skip = "PROTOTYPE(patterns2)")]
public async Task PropertiesInRecursivePattern_WithPositionalPattern()
Copy link
Copy Markdown
Contributor

@Neme12 Neme12 May 1, 2018

Choose a reason for hiding this comment

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

Please fix this. You need to take into account that the parent of a PropertySubpattern can also be a DeconstructionPattern, not just PropertyPattern, everywhere you expect that to be the case.

See https://sharplab.io/#v2:EYLgZgpghgLgrgJwgZwLRIMaOQSwG4SoAOsMECAdsgDQwhTIzUAmIA1AD4ACADAARcAjAG4AsACguAZgEAmPgGE+Abwl91AmVwAsfALIAKAJRqNq8Rst8A+nwC8fGAAscyPq74HB1PrKMq+AAVBED5vINlQ+QBfMQsNaNN1JPcKGCDBAIBzCBhhPkT49Rw0iOzc/MLLFJ0+ABEIDAB7KhgEOAwYAya4dJL0qB8evtLgEyKVFMsoez5gWZ44y0LooA===

then make sure it works both with and without a type just like with a PropertyPattern:

this is C (1, 2) { P1: 1, P2: 2 };
this is (1, 2) { P1: 1, P2: 2 };

Because this is a different SyntaxNode and you need to have separate cases to get the type in each one, I would expect additional tests such as
PropertiesInRecursivePattern_UseStaticTypeFromIs_DeconstructionPattern
PropertiesInRecursivePattern_UseStaticTypeFromSwitchStatement_DeconstructionPattern and
PropertiesInRecursivePattern_WithOtherType_DeconstructionPattern

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 don't plan to expand the cases handled by the completion provider in this PR. As noted, we have a design to supercede this approach, using GetTypeInfo. I'm waiting for that to be merged.

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.

Note: even when you do that, you will still have to update the code to get the DeconstructionPattern in order to use GetTypeInfo on it. Right now I found 2 places in the code that assume the parent can only be a PropertyPattern and bail out if it's not.

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 2, 2018

I'm going to close this PR in favor of:

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.

5 participants