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 feature to allow converting from a tuple to a named-struct. #28257

Merged
merged 38 commits into from
Jul 12, 2018

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Jul 3, 2018

Note: this is a WIP. There are several parts to complete. But i wanted to send this out for early review.

This is a sibling PR to Convert Anonymous Type to Class and Convert Anonymous Type to Tuple.

it should probably go in once those go in. This feature is most similar to 'Convert Anonymous Type to Class'. However, it has a lot more complexity in it due to the nature of Tuples versus Anonymous-Types. Specifically, Anonymous Types are simpler to manage because their types cannot 'leak' out of the containing method. Indeed, their types cannot actually be uttered in source. Tuples are far different. Their types can spread all over a codebase.

Before continuing on that issue, first let me show a picture of what this looks like:

image

--

First: note the concept of 'scopes' that the refactoring will apply to. Specifically, when you invoke it, we ask you to let us know how broadly you want us to search for matching types to convert to the new type you're introducing. The scopes are:

  1. Method/Member level. For when you've just used a tuple in a single member (like an anonymous type) and you decide you want a real type before leaking it out elsewhere.
  2. Type-level. For when you've used a tuple inside the impl of a type, and realize you wnt a named type before you expose it.
  3. Project level. When you want to update all usages of that tuple type in the current project you're in.
  4. Dependent projects. And when you want all downstream projects to be updated as well.

This approach gives you control of how wide you want the update to go. This is necessary because it's quite possible to use the same tuple type in several places in a project and want (or not want) to update all those places. For example, one part of a codebase may use it's own (int x, int y) tuple in some code. And that type may show up far elsewhere. It's not necessarily the case that when you are updating the first, that you want the other area to be affected.

--

Second: Note that the struct we generate has far more parts than the standard anonymous-type-to-class conversion. Specifically, on top of all the standard constructor/Equals/GetHashCode that we generate. We also generate conversion to/from tuples, as well as a Deconstruct method. This allows this type to seamlessly be used even when working with external APIs (or other APIs in your own code) that have not been updated. For example, say you have:

void Foo()
{
    var t = (x: 0, y: 1);
    Bar(t);
}

void Bar((int, int) tuple) { ... }

Here we have two distinct tuple types. The one with the names 'x' and 'y' in it, and the anonymous one. If you introduce a type for (x: 0, y: 1) this will produce:

void Foo()
{
    var t = new NewStruct(x: 0, y: 1);
    Bar(t);
}

void Bar((int, int) tuple) { ... }

And this code will continue to work because NewStruct is implicitly convertible to (int, int).

--

Third: When replacing, we only replace tuple types that exactly match the starting type. So, if you rename and reassign tuple members in code, we will consdier that a different type that we will not fixup. This is necessary so that we don't end up deciding to replace all 2-tuples in the world any time you're just trying to fixup your particular 2-tuple with your specific names.

--

Finally, there are some caveats here we can't really do anything about (Esp. as analyzers can't do global analysis). For example, if a tuple is used with a == expression, that has special meaning in the language that only tuples can support. This means that such code will break when converting to tuples.

--

TODO:

  • Actually implement hte Project/Dependent-project scopes. The stubs are there, but i need to fill them in.
  • Update SyntaxTreeIndex to contain information about what tuple shapes are found inside. We can use that to help speed up the global fixup operations.
  • Tests. Lots of tests needed.
  • Properly support cross-language scenarios. Right now i'm doing direct Type-Equality checks. Those won't work cross language. We need to use the IDE equality comparers.

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner July 3, 2018 08:13
@CyrusNajmabadi
Copy link
Member Author

Tagging @jcouv @sharwell @jinujoseph @kuhlenh @dotnet/roslyn-ide

Note: not quite ready for review. I will ping when it is.

{
refactoringSuggestedActions.Add(new CodeRefactoringSuggestedAction(
_owner, workspace, _subjectBuffer, refactoring.Provider, action));
}
Copy link
Member Author

Choose a reason for hiding this comment

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

needed to support refactorings with nested options. Up till now we've never had any of those.

@CyrusNajmabadi
Copy link
Member Author

I would not review this until #28066 goes in. This PR depends on a lot of the changes in that PR. So there would be a lot of duplication currently.

@jcouv jcouv added Area-IDE Community The pull request was submitted by a contributor who is not a Microsoft employee. labels Jul 3, 2018
@jinujoseph jinujoseph added this to the 16.0 milestone Jul 5, 2018
@CyrusNajmabadi
Copy link
Member Author

Tagging @jcouv @sharwell @jinujoseph @kuhlenh @dotnet/roslyn-ide

This is ready for review.

}
}

private bool InfoProbablyContainsTupleFieldNames(SyntaxTreeIndex info, ImmutableArray<string> tupleFieldNames)
Copy link
Member

Choose a reason for hiding this comment

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

InfoProbablyContainsTupleFieldNames [](start = 21, length = 35)

static?
Then maybe callers might be made static too?

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.

});
}

private async Task<Solution> ApplyChangesAsync(
Copy link
Member

@jcouv jcouv Jul 11, 2018

Choose a reason for hiding this comment

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

static here as well? #Closed

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.

});
}

private SeparatedSyntaxList<TArgumentSyntax> ConvertArguments(ISyntaxFactsService syntaxFacts, SyntaxGenerator generator, SeparatedSyntaxList<TArgumentSyntax> arguments)
Copy link
Member

@jcouv jcouv Jul 11, 2018

Choose a reason for hiding this comment

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

ConvertArguments [](start = 53, length = 16)

One of the two ConvertArguments overloads may be unused. Hum, it's good to double-check, but I think they are both used after all. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

both are used. :)

Copy link
Member Author

Choose a reason for hiding this comment

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

the second is called by the first.

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 (iteration 34).

@CyrusNajmabadi
Copy link
Member Author

@jcouv Thanks. I will ping when ready to go in!

@CyrusNajmabadi
Copy link
Member Author

test windows_debug_vs-integration_prtest

@CyrusNajmabadi
Copy link
Member Author

@jcouv @ivanbasov this can be merged in now. Thanks!

@jcouv
Copy link
Member

jcouv commented Jul 12, 2018

<data name="Convert_to_tuple" xml:space="preserve">

This shouldn't be here


Refers to: src/Features/Core/Portable/FeaturesResources.resx:1379 in e00a3fe. [](commit_id = e00a3fe, deletion_comment = False)

@jcouv
Copy link
Member

jcouv commented Jul 12, 2018

        public const string CodeActionsConvertAnonymousTypeToTuple = "CodeActions.ConvertAnonymousTypeToTuple";

This shouldn't be here #Closed


Refers to: src/Test/Utilities/Portable/Traits/Traits.cs:52 in e00a3fe. [](commit_id = e00a3fe, deletion_comment = False)

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.

Done with review pass (iteration 38)

@CyrusNajmabadi
Copy link
Member Author

@jcouv I don't know what your Traits.cs comment refers to...

@CyrusNajmabadi
Copy link
Member Author

@jcouv i also do not understand the Featuresresources.resx comment...

@jcouv
Copy link
Member

jcouv commented Jul 12, 2018

I did a diff in CodeFlow between iterations 34 and 38, and saw an addition of:
public const string CodeActionsConvertAnonymousTypeToTuple = "CodeActions.ConvertAnonymousTypeToTuple";

And in the resx, it shows an addition of <data name="Convert_to_tuple" xml:space="preserve">.

Not sure what's going on.

@jcouv
Copy link
Member

jcouv commented Jul 12, 2018

Hum, if I just look at iteration 38, then those additions do not appear. CodeFlow must be confused by the merge from master...
Sorry about that.

LGTM (iteration 38)

@jcouv jcouv merged commit 569124e into dotnet:master Jul 12, 2018
@CyrusNajmabadi
Copy link
Member Author

Thanks!

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. Feature Request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants