Skip to content

Better handle surrounding directives when inlining a local variable.#21472

Merged
sharwell merged 7 commits intodotnet:dev15.7.xfrom
CyrusNajmabadi:inlineTempDirectives
Jan 31, 2018
Merged

Better handle surrounding directives when inlining a local variable.#21472
sharwell merged 7 commits intodotnet:dev15.7.xfrom
CyrusNajmabadi:inlineTempDirectives

Conversation

@CyrusNajmabadi
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi commented Aug 12, 2017

Customer scenario

The Inline Temporary Variable code fix is applied to code containing preprocessor directives. The operation fails to consider the syntax requirements of the preprocessor directives, and the resulting file is no longer valid code.

Bugs this fixes

Fixes #21455

Workarounds, if any

None.

Risk

Low. Existing test behavior was not altered except where the previous code was obviously incorrect, and new tests were implemented to cover the new behavior.

Performance impact

Low. There are no key algorithm changes and the approach is consistent with other cases needing similar patterns.

Is this a regression from a previous update?

No.

Root cause analysis

Tests were configured to ignore trivia by default (including preprocessor directives), so the validation described in previous unit tests did not match the actual validation occurring. The test suite was corrected in #21439, leaving this bug as a follow-up work item.

How was the bug found?

Unit tests.

Test documentation updated?

No.

@CyrusNajmabadi CyrusNajmabadi added the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Aug 12, 2017
@CyrusNajmabadi CyrusNajmabadi force-pushed the inlineTempDirectives branch 2 times, most recently from 431fa4c to 52c908f Compare August 12, 2017 20:39
@CyrusNajmabadi
Copy link
Contributor Author

Tagging @dotnet/roslyn-ide @dotnet/roslyn-compiler @mattwar

As part of this change, i've tweaked out SyntaxNode.RemoveNode works when removing a node from a separated list. Specifically, i've tweaked the heuristic that is used to decide which comma to remove from teh seprated list when removing the node. Previously, we would always remove the preceding comma if there was one. If there wasn't one, we'd remove hte following comma. This was suboptimal for some coding patterns. For example, if the user has:

SomeMethod(a, // comment about a
           b, // some comment about b
           c // come comment about c
);

Then previously removing 'b' would leave you with:

SomeMethod(a, // some comment about b
           c // come comment about c
);

Now it gives you the expected

SomeMethod(a, // comment about a
           c // come comment about c
);

@CyrusNajmabadi CyrusNajmabadi removed the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Aug 12, 2017
@sharwell
Copy link
Contributor

@CyrusNajmabadi I don't see any difference between your before/after examples in the previous comment.

{
int y,
#if true

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 This blank line should be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can live with this behavior. Critical part is not leaving dangling preprocessor directives.

@CyrusNajmabadi
Copy link
Contributor Author

@sharwell

I don't see any difference between your before/after examples in the previous comment.

I've highlighted the relevant part with asterisk here:

SomeMethod(a, // comment about **b**
           c // come comment about c
);

Now it gives you the expected

SomeMethod(a, // comment about **a**
           c // come comment about c
);

@sharwell
Copy link
Contributor

sharwell commented Aug 12, 2017

@CyrusNajmabadi Can you verify that the comment preservation works with leading comments too:

SomeMethod(/*arg1:*/ a,
    /*arg2:*/ b,
    /*arg3:*/ c);

I'd rather leave the extraneous comment on a blank line than have cases where a comment is incorrectly removed. In other words, the following outcome in your original example would be either acceptable or preferred:

SomeMethod(a, // comment about a
           // some comment about b
           c // come comment about c
);

@sharwell
Copy link
Contributor

sharwell commented Aug 12, 2017

Also test the following:

SomeMethod(// comment about a
           a,
           // some comment about b
           b,
           // some comment about c
           c);

return list;
}

private bool ContainsEndOfLine(SyntaxTriviaList triviaList)
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Several other kinds of trivia have a newline built-in. StyleCop Analyzers uses a HasBuiltinEndLine helper method for this function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I considered the cases here, but felt they were not suitably interesting or important enough to handle right now.

@CyrusNajmabadi
Copy link
Contributor Author

Sure, will add additional tests.

@CyrusNajmabadi
Copy link
Contributor Author

Added tests.

@CyrusNajmabadi
Copy link
Contributor Author

@dotnet/roslyn-compiler @mattwar Please review the changes (and tests) to 'RemoveNode'. Thanks!

return list;
}

private bool ContainsEndOfLine(SyntaxTriviaList triviaList)
Copy link
Contributor

Choose a reason for hiding this comment

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

Method can be static or perhaps an instance method on SyntaxTriviaList. Or alternatively, replace with triviaList.IndexOf(SyntaxKind.EndOfLineTrivia) >= 0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -199,6 +227,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax
Return list
End Function
Copy link
Contributor

Choose a reason for hiding this comment

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

Can VisitList be shared between C# and VB?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup. I think i can move to a shared helper.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. I looked into this, and we definitely can't share the entire method. First, it accesses a lot of instance state. And we can't make the REmover classes shared either (because htey need to respectively subclass either CSharpSyntaxRewriter and VisualBasicSyntaxRewriter).

However, i can try to share some smaller pieces of logic.

@CyrusNajmabadi
Copy link
Contributor Author

@cston Another look plz?

// If there is no next comma, or the next comma is not on the
// same line, then just remove the preceding comma if there is
// one. If there is no next or previous comma there's nothing
// in the list that needs to be fixed up.
Copy link
Contributor

Choose a reason for hiding this comment

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

The comment and the name of this class are a little confusing because the methods are only classifying separators not removing any nodes.

@CyrusNajmabadi
Copy link
Contributor Author

@dotnet/roslyn-compiler @mattwar i need a second pair of eyes when you get a chance. thanks!

@CyrusNajmabadi
Copy link
Contributor Author

Need another @dotnet/roslyn-compiler review. Thanks!

@CyrusNajmabadi
Copy link
Contributor Author

@dotnet/roslyn-compiler @mattwar i need a second pair of eyes when you get a chance. thanks!

@CyrusNajmabadi
Copy link
Contributor Author

@dotnet/roslyn-compiler @mattwar i need a second pair of eyes when you get a chance. thanks!

@mattwar
Copy link
Contributor

mattwar commented Oct 4, 2017

What about:

SomeMethod( /* A */ a, /* B */ b, /* C */ c);

Removing b and trailing comma gives me:

SomeMethod( /* A */ a, /* B */ c);

Because the comments are trailing the tokens.

Though this pattern is probably less likely to be used than the end-of-line comments.

@CyrusNajmabadi
Copy link
Contributor Author

@mattwar That case works properly (and has tests). Specifically, in your example of SomeMethod( /* A */ a, /* B */ b, /* C */ c); the comma following 'b' is not considered to the be the one "belonging" to 'b'. We only consider the comma to belong to it if it is followed by a newline.

So, in this case, we'll do the right thing and will make SomeMethod( /* A */ a, /* C */ c);

@CyrusNajmabadi
Copy link
Contributor Author

@dotnet/roslyn-compiler Are you ok with just Chuck and Matt's reviews?

@CyrusNajmabadi
Copy link
Contributor Author

@agocke Can you take a look? Thanks!

@CyrusNajmabadi
Copy link
Contributor Author

@sharwell can you, or someone on @dotnet/roslyn-ide help get this in? Thanks!

@jinujoseph
Copy link
Contributor

@jcouv can you get a second review for this from compiler side

if (alternate.Count > 0 && alternate[alternate.Count - 1].IsToken)
CommonSyntaxNodeRemover.GetSeparatorInfo(
withSeps, i, (int)SyntaxKind.EndOfLineTrivia,
out var nextTokenIsSeparator, out var nextSeparatorBelongsToNode);
Copy link
Member

Choose a reason for hiding this comment

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

Please spell out bool types

Copy link
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!


using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Syntax;
Copy link
Member

Choose a reason for hiding this comment

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

Can be removed?

@sharwell sharwell changed the base branch from master to dev15.6.x January 23, 2018 19:37
@CyrusNajmabadi CyrusNajmabadi requested review from a team as code owners January 23, 2018 19:44
@sharwell
Copy link
Contributor

@jcouv Updated for your two changes

@jcouv
Copy link
Member

jcouv commented Jan 23, 2018

@sharwell FYI The PR is all green, if you're ready for ask-mode approval (probably needs template).

@sharwell
Copy link
Contributor

@Pilchie for ask mode

@sharwell sharwell added this to the 15.6 milestone Jan 23, 2018
@sharwell sharwell changed the base branch from dev15.6.x to dev15.7.x January 23, 2018 21:19
@sharwell sharwell modified the milestones: 15.6, 15.7 Jan 23, 2018
@Pilchie
Copy link
Member

Pilchie commented Jan 23, 2018

To comment - this change missed the cutoff for 15.6 Escrow. Given the narrowness of the scenario, we're going to just take it for 15.7 instead.

@Pilchie
Copy link
Member

Pilchie commented Jan 31, 2018

Approved - sorry for the delay

@sharwell sharwell merged commit 63eb3cb into dotnet:dev15.7.x Jan 31, 2018
@CyrusNajmabadi CyrusNajmabadi deleted the inlineTempDirectives branch January 25, 2020 00:45
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