Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a refactoring to convert a common for-loop form to a foreach-statement. #25469

Merged
merged 48 commits into from
Mar 23, 2018

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Mar 14, 2018

Tagging @heejaechang .

Todo:

  • VB
  • Tests

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner March 14, 2018 19:40
// x += constant_1

ExpressionSyntax operand;
if (incrementor.Kind() == SyntaxKind.PostIncrementExpression)
Copy link
Contributor

@Neme12 Neme12 Mar 14, 2018

Choose a reason for hiding this comment

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

switch? #Resolved

if (TypeStyleHelper.IsImplicitStylePreferred(
optionSet, isBuiltInTypeContext, isTypeApparentContext: false))
{
typeNode = SyntaxFactory.IdentifierName("var");
Copy link
Contributor

@Neme12 Neme12 Mar 14, 2018

Choose a reason for hiding this comment

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

duplication #Resolved

Copy link
Member Author

@CyrusNajmabadi CyrusNajmabadi Mar 14, 2018

Choose a reason for hiding this comment

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

sorry, don't know what this means. #Resolved

Copy link
Contributor

@Neme12 Neme12 Mar 14, 2018

Choose a reason for hiding this comment

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

could you use ?:? #Resolved

Copy link
Contributor

@Neme12 Neme12 Mar 14, 2018

Choose a reason for hiding this comment

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

typeNode = is duplicated in each branch #Resolved

Copy link
Member Author

@CyrusNajmabadi CyrusNajmabadi Mar 14, 2018

Choose a reason for hiding this comment

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

it feels like it would be very large and unweildy. I found this to read better. #Resolved

Copy link
Member Author

@CyrusNajmabadi CyrusNajmabadi Mar 14, 2018

Choose a reason for hiding this comment

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

we don't have a general rule of avoiding hte pattern of:

SomeVar v;

// initialize through branchign code

// use v

Devs decide on a case by case basis. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

#wontfix

Copy link
Contributor

Choose a reason for hiding this comment

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

this is nice. I should reuse this.


private static bool IsImplicitStylePreferred(TypeStylePreference stylePreferences,
Copy link
Contributor

Choose a reason for hiding this comment

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

i thought we weren't supposed to format this way?


// position has to be inside the 'for' span, or if there is a selection, it must
// match the 'for' span exactly.
if (context.Span.IsEmpty && !token.Span.IntersectsWith(context.Span.Start))
Copy link
Contributor

@Neme12 Neme12 Mar 14, 2018

Choose a reason for hiding this comment

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

i would personally also expect this offered after ')'

}

var forStatement = token.Parent as TForStatementSyntax;
if (forStatement == null)
Copy link
Contributor

Choose a reason for hiding this comment

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

pattern matching?

@CyrusNajmabadi
Copy link
Member Author

@Neme12 This has not been submitted for review. it is a WIP (work in progress). I will let you know when it's ready :)

@CyrusNajmabadi
Copy link
Member Author

CyrusNajmabadi commented Mar 14, 2018

Feature looks like:

image

it also supports figuring out the foreach variable if the user declared it in the for loop:

image

@CyrusNajmabadi
Copy link
Member Author

@heejaechang I've extracted out the helper to generate a unique name token in a specific context. you can port that over if you want.

@CyrusNajmabadi
Copy link
Member Author

VB side:

image

@CyrusNajmabadi CyrusNajmabadi changed the title WIP - Initial work to support converting a for-statement to a foreach. Add a refactoring to convert a common for-loop form to a foreach-statement. Mar 14, 2018
@CyrusNajmabadi
Copy link
Member Author

VB is done. Tests coming now.

@dotnet/roslyn-ide @heejaechang @Neme12 @jinujoe this is ready for review now.

return;
}

var forStatement = token.Parent.AncestorsAndSelf().OfType<TForStatementSyntax>().FirstOrDefault();
Copy link
Contributor

@Neme12 Neme12 Mar 14, 2018

Choose a reason for hiding this comment

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

this is a little weird. isn't there something like FirstAncestorOrSelf<> or GetAncestorOrSelf<> you could use? #Resolved

Copy link
Member Author

@CyrusNajmabadi CyrusNajmabadi Mar 14, 2018

Choose a reason for hiding this comment

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

yup. #resolved #Resolved

@@ -290,5 +293,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic

Return {semanticModel.GetDeclaredSymbol(memberDeclaration, cancellationToken)}
End Function

Private Function ISemanticFactsService_GenerateUniqueName(semanticModel As SemanticModel, location As SyntaxNode, containerOpt As SyntaxNode, baseName As String, cancellationToken As CancellationToken) As SyntaxToken Implements ISemanticFactsService.GenerateUniqueName
Copy link
Contributor

Choose a reason for hiding this comment

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

line way too long

Copy link
Member Author

Choose a reason for hiding this comment

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

in this type, considering so many methods are simply to just represent the interface signature, we don't wrap and just forward things along. I agree it's not consistent, but it helps keep this type from literally becoming 5x as long and having the signatures mask what's actually relevant.

{
protected abstract ISyntaxFactsService SyntaxFactsService { get; }

public SyntaxToken GenerateUniqueName(
Copy link
Contributor

Choose a reason for hiding this comment

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

would a better place for this be inside NameGenerator?

Copy link
Member Author

Choose a reason for hiding this comment

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

NameGenerator is designed to be Roslyn agnostic. i.e. it's literally jsut string manipulation. we've deliberately kept it that way so that partners can use it/copy it trivially.

protected override bool TryGetForStatementComponents(
ForStatementSyntax forStatement,
out SyntaxToken iterationVariable, out ExpressionSyntax initializer,
out MemberAccessExpressionSyntax memberAccess, out ExpressionSyntax stepValue,
Copy link
Contributor

@Neme12 Neme12 Mar 14, 2018

Choose a reason for hiding this comment

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

it's a little confusing to use 'stepValue' for both the value and the expression it's calculated from, consider adding 'expression' to the name or differentiating it in some other way #Resolved

Copy link
Member Author

@CyrusNajmabadi CyrusNajmabadi Mar 14, 2018

Choose a reason for hiding this comment

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

Sure. #Resolved

Copy link
Member Author

@CyrusNajmabadi CyrusNajmabadi Mar 14, 2018

Choose a reason for hiding this comment

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

#resolved #Resolved

identifierName.Identifier.ValueText == iterationVariable.ValueText &&
binaryExpression.Right is MemberAccessExpressionSyntax)
{
memberAccess = (MemberAccessExpressionSyntax)binaryExpression.Right;
Copy link
Contributor

@Neme12 Neme12 Mar 14, 2018

Choose a reason for hiding this comment

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

i see waht's going on here and it's not very nice, but i can't think of any way how to improve this #WontFix

Copy link
Member Author

@CyrusNajmabadi CyrusNajmabadi Mar 14, 2018

Choose a reason for hiding this comment

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

#won'tfix #Resolved

Copy link
Contributor

Choose a reason for hiding this comment

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

can these move to abstract one and use IOperation rather than doing it on language specific way?

Copy link
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 think so. Vb and C# literally uses different types here (IForToLoop vs IForLoop). So you'd have to write custom code anyways. Given that, i don't mind explicitly writing this at the language level since each language is different.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also, i tried out hte IOperation approach and i had to jump through lots of hoops due to conversion operations in the code. It was actually more annoying.

memberAccess = (MemberAccessExpressionSyntax)binaryExpression.Right;

var incrementor = forStatement.Incrementors[0];
if (TryGetStepValue(iterationVariable, incrementor, out stepValue, cancellationToken))
Copy link
Contributor

Choose a reason for hiding this comment

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

can you do return TryStepGetValue?

Copy link
Member Author

Choose a reason for hiding this comment

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

sure.

Copy link
Member Author

Choose a reason for hiding this comment

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

#resolved

ExpressionSyntax operand;
switch (incrementor.Kind())
{
case SyntaxKind.PostIncrementExpression:
Copy link
Contributor

@Neme12 Neme12 Mar 14, 2018

Choose a reason for hiding this comment

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

it makes me sad that there's no UnaryExpressionSyntax that would let us merge these cases #WontFix

Copy link
Member Author

@CyrusNajmabadi CyrusNajmabadi Mar 14, 2018

Choose a reason for hiding this comment

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

#won'tfix #Resolved


return operand is IdentifierNameSyntax identifierName &&
identifierName.Identifier.ValueText == iterationVariable.ValueText;
}
Copy link
Contributor

@Neme12 Neme12 Mar 14, 2018

Choose a reason for hiding this comment

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

few ideas:

  1. could you use pattern matching instead of the casts? for assignment you'd have to use when .IsKind(), do you think that to be an improvement or not?
  2. i don't like this imperative style with variables outside assigned from each case with a break everywhere, it seems like this would deserve maybe a local function or a method that could have a single return from each case with a tuple of (operand, stepValue)... in default it would return a null operand and the operand is IdentifierNameSyntax check would still work as expected... but this is all up to you, I'm just throwing some suggestions/ideas

Copy link
Member Author

Choose a reason for hiding this comment

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

i'm ok with it as it is. If we had a match expression, i might consider using that. But i'm ok with things as they are here.

@CyrusNajmabadi
Copy link
Member Author

tests added. @dotnet/roslyn-ide please review at your earliest convenience.

@CyrusNajmabadi
Copy link
Member Author

Note: one of the best things i could get from reviewers are potentially interesting cases i missed in both languages. VB can be especially surprising with all the forms it supports :)

@CyrusNajmabadi
Copy link
Member Author

@heejaechang Can you review? (in exchange for the reviews i did :))

@CyrusNajmabadi
Copy link
Member Author

@jcouv Woudl appreciate your eyes here.

@CyrusNajmabadi
Copy link
Member Author

@Pilchie This refactoring seems very safe for 15.7. Woudl you want it there, or would 15.8 make sense?

@Pilchie
Copy link
Member

Pilchie commented Mar 15, 2018

I believe we want it in 15.7 as long as it's code reviewed and buddy tested in within the next week. @jinujoseph / @kuhlenh

@CyrusNajmabadi
Copy link
Member Author

That would be great! The ball's in your court :)

@jinujoseph jinujoseph added this to the 15.7 milestone Mar 15, 2018
{
[||]for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]);
Copy link
Member

Choose a reason for hiding this comment

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

array[i] [](start = 30, length = 8)

Consider adding a test where array[i] appears more than once in the foreach block.

Copy link
Member Author

Choose a reason for hiding this comment

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

done.

Copy link
Member Author

Choose a reason for hiding this comment

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

#resolved

@CyrusNajmabadi
Copy link
Member Author

CyrusNajmabadi commented Mar 22, 2018

Overall LGTM. I have some comments about handling certain additional (and uncommon) bail out cases. Also had some comments about use of IOperation when analyzing semantics to simplify code, but existing code is also fine. Great work @CyrusNajmabadi!

Thanks! I'm going to add some more checks and warnings for potential mutations to the collection. I think that's very reasonable!

@CyrusNajmabadi
Copy link
Member Author

@jinujoseph PR feedback addressed. this can go in once green.

@CyrusNajmabadi
Copy link
Member Author

@dotnet/roslyn-infrastructure I'm getting a failure on the new spanish unit test build:

error MSB6002: The command-line for the "CreatePkgDef" task is too long. Command-lines longer than 32000 characters are likely to fail. Try reducing the length of the command-line by breaking down the call to "CreatePkgDef" into multiple calls with fewer parameters per call. [D:\j\workspace\windows_debug---51589b87\src\VisualStudio\IntegrationTest\TestSetup\VisualStudioIntegrationTestSetup.csproj]

Does anyone know what this is about?

@CyrusNajmabadi
Copy link
Member Author

@tmeschter @jasonmalinowski do you know anything about that msbuild error?

@jasonmalinowski
Copy link
Member

@CyrusNajmabadi That queue is still experimental, safe to ignore.

@CyrusNajmabadi
Copy link
Member Author

oh thanks. i missed that.

@CyrusNajmabadi
Copy link
Member Author

retest windows_debug_unit32_prtest please

@jinujoseph
Copy link
Contributor

@Pilchie for ask mode approval

@jinujoseph jinujoseph closed this Mar 23, 2018
@jinujoseph jinujoseph reopened this Mar 23, 2018
@CyrusNajmabadi
Copy link
Member Author

@jinujoseph This has passed and can be merged in. All the failures are the current issue with "error MSB6002: The command-line for the "CreatePkgDef" task is too long."

@jinujoseph
Copy link
Contributor

thanks @CyrusNajmabadi , this is pending approval from @Pilchie

@Pilchie
Copy link
Member

Pilchie commented Mar 23, 2018

Approved - thanks @CyrusNajmabadi

@jinujoseph
Copy link
Contributor

@333fred test failures expected ?

@jinujoseph
Copy link
Contributor

test windows_release_vs-integration_prtest

@CyrusNajmabadi
Copy link
Member Author

@jinujoseph It's not a test failure. The error with integration tests (as i mentioned above) is:

error MSB6002: The command-line for the "CreatePkgDef" task is too long.
Command-lines longer than 32000 characters are likely to fail. 

I don't know what can be done about this. Thoughts @333fred @jasonmalinowski

--

Jason, this is not the spanish queue. This is hte integration test queue. I'm not sure i feel comfortable having my stuff not go through integration testing, even if htat queue isn't required.

@jasonmalinowski
Copy link
Member

The integration test queues are in need of the same fix as the Spanish queue. You can disregard those failures.

@CyrusNajmabadi
Copy link
Member Author

Thanks @jasonmalinowski . @jinujoseph This can be merged in when convenient for you.

@jinujoseph jinujoseph merged commit 0b35308 into dotnet:dev15.7.x Mar 23, 2018
@CyrusNajmabadi
Copy link
Member Author

Thanks all. This is going to be a great release!

@Pilchie
Copy link
Member

Pilchie commented Mar 23, 2018

Agreed - so excited.

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.

10 participants