-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Cannot rename named tuples #14115
Comments
@jaredpar brought up an interesting one more complexity with rename with tuples: renaming in scenarios with OHI (overriding, hiding, implementing). For instance: virtual void M((int a, int b) c) {...}
override void M((int a, int b) x) {...} Renaming |
Since I noticed too that renaming named tuples is currently not possible, I looked into this issue a bit. It looks like some of the work done on value tuples since this issue was opened might make enabling rename refactoring possible soon. With just some minor changes, I can get the rename refactoring to work in most scenarios, except the one with OHI as mentioned by @jcouv above. A rename can be performed in that scenario, but the signature in the base class is not changed. This results in an error, which is easily recoverable. The scenarios I tested are: private void TupleRename()
{
// amount and text can be renamed, updating declaration as well
var tuple = (amount: 6, text: "Hello");
var amount = tuple.amount;
var text = tuple.text;
}
public void CallMethodWithNamedTuple()
{
TupleRenameParameter((amount: 5, anotherAmount: 10));
}
private void TupleRenameParameter((int amount, int anotherAmount) tuple)
{
// amount and text can be renamed, updating declaration in parameter as well
// and in callers to method
var amount = tuple.amount;
var text = tuple.anotherAmount;
}
private void TupleRenameTernary()
{
// Rename of 'a' renames all declarations of 'a' (i.e. three times)
var ternary = true ? (a: 1, b: 2) : (a: 11, b: 22);
var ternaryResult = ternary.a;
}
private void TupleRenameInvalidTernary()
{
// Rename of 'ternary.a' is not allowed because locations cannot be determined due to warnings on tuple declarations
var invalidTernary = true ? (a: 1, b: 2) : (a: 1, c: 3);
var invalidTernaryResult = invalidTernary.a;
} As you can see, I also tested the examples of #16566 by @kuhlenh. BTW: In the changes I did, #19578 is resolved too. The NullRef is caused by trying to rename a tuple of which the current declaration is invalid, causing the exception when trying to get the locations to rename. Please let me know what you think. I can of course open a pull request with my changes if you'd like, but I wanted to hear your opinion first, especially since I don't know how to resolve the OHI scenario. Thanks in advance! |
We actually have a design proposal that is mostly complete that hasn't been posted here yet. If we can make that public it would certainly allow someone to complete the initial implementation. |
I guess there would be some problems when tuples cross methods' boundaries
with type inference?
would it track type arguments?
multiple affected type members and their usages?
also I believe that renaming tuples in return types should affect return statements in cases like this:
as they are clearly intended to provide hints for the constant expressions used in tuple expressions under returns. Actually mismatching names here would result in compiler warninings (and errors if 'warnings as errors' option is enabled) These are just the simplest examples immediately springing to mind. Basically it should support all kinds of type inference and type substitution provided by the language. |
@TessenR Thanks for the additional examples. I need to write up the proposal and highlight a number of cases that we need to figure out. Then we can talk about which ones need to be handled in the initial work and which ones can wait for later. |
Just for the test suite there are also things like best common type inference, i.e. (same with array creation expressions)
|
To expand the brainstorming for examples here are two more: public (int c, int d) void M(int c) // Rename parameter 'c' to 'a'
{
var t = (c, 2);
Console.WriteLine(t.c);
return (t.c, 3);
} struct V1 { public int x; }
struct V2 { public int y; } // Rename 'y' to 'x'
void M(V1 v1, V2 v2) {
var tuple = (v1.x, v2.y);
Console.WriteLine(tuple.x);
} |
As a general concept, the shapes of tuples contained within the same method is considered when renaming a named tuple element. For example: T M<T>(T t1, T t2)
{
var ab = (a: 1, b: 2); // rename 'a' to 'd'
var ac = (a: 1, c: 2);
var x = M(ab, ac);
Console.WriteLine(x.a);
return default;
} Should produce the following result: T M<T>(T t1, T t2)
{
var ab = (d: 1, b: 2);
var ac = (d: 1, c: 2);
var x = M(ab, ac);
Console.WriteLine(x.d);
return default;
} |
But you can't always rename the source of a tuple component's name...
|
Example 1: Rename explicit named elementPrinciples:
T M<T>(T a, T t2)
{
T t1 = default;
var ab = (a: t1, b: t2); // Rename 'a' to 'd'
var ac =(a, c: t2);
var x = M(ab, ac);
Console.WriteLine(x.a);
return default;
} Produces this: T M<T>(T a, T t2)
{
T t1 = default;
var ab = (d: t1, b: t2);
var ac =(d: a, c: t2);
var x = M(ab, ac);
Console.WriteLine(x.d);
return default;
} Example 2: Rename parameterPrinciples:
T M<T>(T a, T t2) // Rename 'a' to 'd'
{
T t1 = default;
var ab = (a: t1, b: t2);
var ac =(a, c: t2);
var x = M(ab, ac);
Console.WriteLine(x.a);
return default;
} Produces this: T M<T>(T d, T t2)
{
T t1 = default;
var ab = (d: t1, b: t2);
var ac =(d, c: t2);
var x = M(ab, ac);
Console.WriteLine(x.d);
return default;
} Example 3: Rename implicit named elementPrinciples:
T M<T>(T a, T t2)
{
T t1 = default;
var ab = (a: t1, b: t2);
var ac =(a, c: t2); // Rename 'a' to 'd'
var x = M(ab, ac);
Console.WriteLine(x.a);
return default;
} Produces this: T M<T>(T d, T t2)
{
T t1 = default;
var ab = (d: t1, b: t2);
var ac =(d, c: t2);
var x = M(ab, ac);
Console.WriteLine(x.d);
return default;
} Example 4: Rename usage of implicit named elementPrinciples:
T M<T>(T a, T t2)
{
T t1 = default;
var ab = (a: t1, b: t2);
var ac =(a, c: t2);
var x = M(ab, ac);
Console.WriteLine(x.a); // Rename 'a' to 'd'
return default;
} Produces this: T M<T>(T a, T t2)
{
T t1 = default;
var ab = (d: t1, b: t2);
var ac =(d: a, c: t2);
var x = M(ab, ac);
Console.WriteLine(x.d);
return default;
} |
Great brainstorming. Another thing to consider is the following: class Test
{
void A()
{
var tupleA = (a: "", b: "");
}
}
class Test2
{
void B()
{
var tupleB = (a: "", b: "");
}
} Right now, selecting |
What would this produce I wonder?
And just in case it's still supposed to rename the type argument, how about this?
|
📝 It matters where the refactoring is triggered and what the new name is. Note that I updated my previous comment to show all the cases for identifiers |
Proposal for Tuple Renaming🚧 This post is a work in progress. I will add to it as examples are added to reveal specific behaviors. Fundamentals
|
Any updates on this one? |
It makes sense to consider local functions the same as methods in this regard because they have signatures, right? Whereas anonymous methods and lambdas would just be treated the same as any other nested scopes? These rules don't mention what should happen when a tuple element is renamed but it's not within a method. E.g. in an initializer, I'd assume all tuples with the same shape in the same initializer are examined: class Foo
{
// Rename any 'A' in the initializer to 'D'
private readonly (int A, int B) bar = ((A: 1, B: 2).A + (A: 1, B: 2).B);
} class Foo
{
private readonly (int A, int B) bar = ((D: 1, B: 2).D + (D: 1, B: 2).B);
} |
Also, would renames inside |
I don't understand why I would want the behavior in example 2. Is there a real world example that came from? Why would renaming a parameter that happens to have the same name and type as an otherwise unrelated tuple field propagate onto the tuple field? Is that supposed to be a tuple parameter on M()? |
Jetbrains Rider recently shipped with this, and ReSharper as well. Perhaps the implementation could be implemented however JetBrains does it into Roslyn as well. |
(Updated - jcouv: a more detailed spec-in-progress below)
Steps to Reproduce:
greeting
intuple.greeting
greeting
Expected Behavior:
Inline rename offered for
tuple.greeting
Actual Behavior:

The text was updated successfully, but these errors were encountered: