Skip to content

Introduce a DiscardSyntaxClassifier - #40396

Merged
JoeRobich merged 25 commits into
dotnet:masterfrom
lameox:highlight-out-_
Jan 27, 2020
Merged

Introduce a DiscardSyntaxClassifier#40396
JoeRobich merged 25 commits into
dotnet:masterfrom
lameox:highlight-out-_

Conversation

@lameox

@lameox lameox commented Dec 14, 2019

Copy link
Copy Markdown
Contributor

fixes #39768 .

This is my first contribution so please let me know if there is anything else needed apart from the exported highlighter or if there is a better place in the codebase to introduce the changes.

@lameox
lameox requested a review from a team as a code owner December 14, 2019 19:56
@dnfclas

dnfclas commented Dec 14, 2019

Copy link
Copy Markdown

CLA assistant check
All CLA requirements met.

@jinujoseph jinujoseph added Area-IDE Community The pull request was submitted by a contributor who is not a Microsoft employee. labels Dec 15, 2019
@CyrusNajmabadi

Copy link
Copy Markdown
Contributor

hi @lameox thanks for the contribution!

}


return SpecializedCollections.EmptyEnumerable<TextSpan>();

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.

should be impossible to hit this. we should throw here.

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 use a switch expression here as well.

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.

Since this is argument syntax (as opposed to parameter syntax), wouldn't this be hit for most arguments?

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.

Yeah i think so. This is why I originally used AbstractKeywordHighlighter instead of AbstractKeywordHighlighter<ArgumentSyntax> and overwrote IsHighlightableNode so GetHighlightsForNode only got called for the required types of nodes. However it was suggested I should use AbstractKeywordHighlighter<ArgumentSyntax> which unfortunately seals IsHighlightableNode. Therefore this code always executes and I need to return the empty enumerable.

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'm not seeing how this woudl be subtantively different. Both cases need to check all nodes to see if they should run against them.

namespace Microsoft.CodeAnalysis.Editor.CSharp.Highlighting.KeywordHighlighters
{
[ExportHighlighter(LanguageNames.CSharp)]
internal class DiscardParameterHighlighter : AbstractKeywordHighlighter

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.

ther's a different baseclass you can subclass right? where you can specify through a type argument that you are constrained to ArgumentSyntax right?

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.

yes, it's AbstractKeywordHighlighter<ArgumentSyntax>.

@CyrusNajmabadi CyrusNajmabadi left a comment

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 be simplified

{
var syntax = (IdentifierNameSyntax)node.Expression;

if (syntax.Identifier.Text == "_")

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.

Switch to typechecks

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

@CyrusNajmabadi CyrusNajmabadi left a comment

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.

Lgtm

@CyrusNajmabadi

Copy link
Copy Markdown
Contributor

@jinujoseph can we get a buddy for this community pr? Thanks!

Comment on lines +25 to +26
if (node.Expression is IdentifierNameSyntax nameSyntax
&& nameSyntax.Identifier.Text == "_")

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 use pattern matching here:

if (node.Expression is IdentifierNameSyntax { Identifier: { Text: "_" } } nameSyntax)

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.

I still need to get used to pattern matching tbh. Should all new code aim to make use of pattern matching or is it up to preference?

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's up to preference. in some places it works nicely (like here), in others, it can feel more forced/unpleasant.

}


return SpecializedCollections.EmptyEnumerable<TextSpan>();

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.

Since this is argument syntax (as opposed to parameter syntax), wouldn't this be hit for most arguments?

{
void Method()
{
int i = int.TryParse("""", out var {|Cursor:[|_|]|});

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.

💡 Should add a test showing that this does not highlight the argument if you write it as @_. This token has the same ValueText as _, but different Text, and in this case the difference is meaningful.

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.

Added tests covering that case.

@sharwell sharwell changed the title Introduce a DiscardParameterHighlighter which fixes #39768 Introduce a DiscardParameterHighlighter Dec 16, 2019
Added a test to ensure @_ is not highlighted incorrectly.
@lameox lameox changed the title Introduce a DiscardParameterHighlighter Introduce a DiscardArgumentHighlighter Dec 16, 2019
@JoeRobich

JoeRobich commented Dec 16, 2019

Copy link
Copy Markdown
Member

Hi @lameox,

Thanks for taking time to work on this.

I am not sure that a highlighter is the proper way to go about this. The Keyword highlighters are intended for providing extra information about Keywords under the cursor. For instance when the cursor is on an If Keyword the Else and Else If keywords will be highlighted to provided extra context about the If keyword.

The original issue was with the classification of the discard keyword in code like int.TryParse("1", out _). These cases should be identifiable though during a syntax classification pass, since as you've worked out, it is identifiable as an out argument syntax whose expression is an identifier named '_'.
Our syntactic classification occurs in src\Workspaces\CSharp\Portable\Classification\ClassificationHelpers.cs. Since this is an identifier token being classified, I think a new case should be added to the GetClassificationForIdentifier method to handle this pattern. I think tests for this change should be added in SyntacticClassifierTests.cs and also ensure that a test in TotalClassifierTests.cs handles this pattern.

-Joey

@lameox

lameox commented Dec 16, 2019

Copy link
Copy Markdown
Contributor Author

Hey there,

thank you for your feedback. I am really new to the codebase and therefore not yet familiar with where to do certain changes. Since it is 1 am where i live i will try to look into moving this fix to the correct place tomorrow and update this PR accordingly. I am sorry if i caused some wasted time to the people helping me so far. Are there any resources I can use to locate where in the codebase to do changes? I searched through the existing unit tests for this but seem to have arrived at the wrong place :(

Thank's for your time
-Jan

@CyrusNajmabadi CyrusNajmabadi left a comment

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.

c

@JoeRobich JoeRobich left a comment

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.

LGTM

Can you add a test for _ = int.Parse("");?

@lameox

lameox commented Jan 15, 2020

Copy link
Copy Markdown
Contributor Author

@JoeRobich Sure thing. This uncovered the missing case for VariableDeclaratorSyntax. From what i can tell i don't actually need symbols for those and can just check the Identifier for _.

Another thing i was kind of suprised about was the test for

 _ = int.Parse("");

In this case we get the following symbol classifications:

Keyword "_"
Static "Parse"
Method "Parse"

This is in contrast to all other tests where the order of the Static "Parse" and Method "Parse" is reversed. For now i just reversed the expected order in the test but this seems kind of fishy to me. Should i keep it like that or investigate further?

@JoeRobich

JoeRobich commented Jan 15, 2020

Copy link
Copy Markdown
Member

Should i keep it like that or investigate further?

@lameox Leave it as is. We are likely to make some changes with regards to the Static classification in 16.6.

…tion/DiscardSyntaxClassifier.cs

Co-Authored-By: Joey Robichaud <joseph.robichaud@microsoft.com>
…n't discards from the compilers view. Kept the tests around and tweaked them so they now check that nothing is classified in these cases.
@lameox

lameox commented Jan 16, 2020

Copy link
Copy Markdown
Contributor Author

Close and reopen to trigger retest

@lameox lameox closed this Jan 16, 2020
@lameox lameox reopened this Jan 16, 2020

@JoeRobich JoeRobich left a comment

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.

Thanks @lameox!

…rTests.cs

Co-Authored-By: Joey Robichaud <joseph.robichaud@microsoft.com>
Comment on lines +3808 to +3840
[Fact, Trait(Traits.Feature, Traits.Features.Classification)]
public async Task DiscardsInLambda()
{
await TestAsync(@"
class X
{
void N()
{
System.Func<int, int, int> a = (int _, int _) => 0;
}
}",
Namespace("System"),
Delegate("Func"),
Keyword("_"),
Keyword("_"));
}

[Fact, Trait(Traits.Feature, Traits.Features.Classification)]
public async Task DiscardsInLambdaWithInferredType()
{
await TestAsync(@"
class X
{
void N()
{
System.Func<int, int, int> a = (_, _) => 0;
}
}",
Namespace("System"),
Delegate("Func"),
Keyword("_"),
Keyword("_"));
}

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.

These tests did not actually show success. The syntactic pass applied ParameterName in addition to the semantic pass adding Keyword, and the merge step prioritizes ParameterName over Keyword. The tests needed to be written past the merge step.

Fixed in #85225

akhera99 pushed a commit that referenced this pull request Sep 9, 2026
…cally, resolving classification conflict (#85225)

Fixes #51553.

Roslyn classifies discards as keywords for syntax highlighting which
shows whether or not `_` is referring to a symbol.

<img width="329" height="230" alt="image"
src="https://github.com/user-attachments/assets/3364de33-039c-451a-94e5-25cc2f091731"
/>

However, it's not working for lambda parameter discards:
<img width="394" height="49" alt="image"
src="https://github.com/user-attachments/assets/60c81709-2e7d-45ce-9821-f3830cea834a"
/>

There was an attempt to implement it in
#40396 for lambda parameter
discards:

https://github.com/dotnet/roslyn/blob/6de0973c513f7d3940cc0b52ff7cd7d55b869982/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/DiscardSyntaxClassifier.cs#L36-L44

But this was insufficient because it was implemented in the semantic
classifier. The syntactic classifier already classified it as
ParameterName, and the two are merged with ordering rules which cause
ParameterName to win over Keyword.

https://github.com/dotnet/roslyn/blob/6de0973c513f7d3940cc0b52ff7cd7d55b869982/src/EditorFeatures/Core/Classification/ClassificationTypeFormatDefinitions.cs#L520-L523

This PR fixes the issue by implementing the proper classification in the
syntactic classifier instead of the semantic classifier. This is
preferable because the semantic classifier may run after more of a
delay, so this is less visual flicker in the IDE.
###### Microsoft Reviewers: [Open in
CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/dotnet/roslyn/pull/85225)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-IDE 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.

Discard in 'out _' is not classified as a keyword

9 participants