Open vsfeedback opened 4 years ago
I see this is tagged as "Feature Request" but I see it as a defect: this "refactoring" will change the behavior of the code, which refactorings must not do.
which refactorings must not do.
This is not true. If refactorings were not allowed to change any code behavior then we could never offer them. Instead refactorings are allowed to change the semantics of the thing you asked us to do.
Extracting a method may change the semantics of calling that method. It should not change the observability of private members or anything else unrelated to what the refactoring is trying to do.
Refactorings are a means to get things done faster than manual code editing but only offering them in cases where we've run a 3SAT solver and proven that no change in behavior is ever observable is a non-goal. Instead refactorings should only change the behavior of the thing that you asked us to do.
After some discussion, I'm moving this to a bug and marking as help wanted. Here's some similar examples with behavior we expect to be classified as fixed.
In short, if a lifted variable is modified in the same scope as the extraction point, extract method should use the in
keyword to make sure that the value is at least passed by read only reference.
If the value is also modified within the extracted method, it should use ref
.
private static void M()
{
var x = 0;
Action a = () => {
x++;
};
// Extract Method start
a();
if (1 == x)
{
Console.WriteLine(x);
}
// Extract Method end
}
private static void M()
{
var x = 0;
Action a = () => {
x++;
};
// Extract Method start
NewMethod(a, x);
// Extract Method end
}
private static void NewMethod(Action a, in int x)
{
a();
if (1 == x)
{
Console.WriteLine(x);
}
}
private static void M()
{
var x = new object();
var y = x;
Action a = () =>
{
x = new object();
};
// Extract Method start
a();
if (y != x)
{
Console.WriteLine("Test");
}
// Extract Method end
}
private static void M()
{
var x = new object();
var y = x;
Action a = () =>
{
x = new object();
};
// Extract Method start
NewMethod(x, y, a);
// Extract Method end
}
private static void NewMethod(in object x, object y, Action a)
{
a();
if (y != x)
{
Console.WriteLine("Test");
}
}
@JayBazuzi hello old friend!
Right now this is currently in the 'by design' bucket in-as-far as the 'correct' behavior for extract method was deemed too unpleasant for the common case. i.e. to be 'safe/correct', it would have to use 'ref' in far too many areas where people would not want it.
In short, if a lifted variable is modified in the same scope as the extraction point, extract method should use the in keyword to make sure that the value is at least passed by read only reference.
I'm very wary of this. The threading case is an extreme corner case. Whereas extracting a value that is written elsewhere in a function would be very common.
--
Note: we have a flag to control this somewhat with:
If we were to take the above described fix, i would want it gated behind a flag as well to not impact the common case where people would expect that passing a primitive should not need to happen by-ref
If we were to take the above described fix, i would want it gated behind a flag as well to not impact the common case where people would expect that passing a primitive should not need to happen by-ref
Potentially a flag would be good. Keep in mind that this proposal is only applicable for cases where local variables are lifted and modified. Variables written elsewhere in a function is not the same as lifting a variable to be modified.
Here's a good test case that shouldn't change with the proposed fix https://github.com/dotnet/roslyn/blob/4d19a77445b392f53d4af55ddaf48208b5205df4/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.cs#L903
var code = @"using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
void Test()
{
int i;
[|int b = 10;
if (b < 10)
{
i = 5;
}|]
i = 6;
Console.WriteLine(i);
}
}";
Variables written elsewhere in a function is not the same as lifting a variable to be modified.
Understood. But 'lifitng' (i assume you mean 'captured') is not really sufficient for me. In most cases, it's not an issue. The real strange case here is the specific use of threading constructs. Those constructs break the normal expectation of locals in a method, namely that only your thread could ever modify them. Threading breaks that (primarily because the locals become fields and the fields are touched by different threads). This is def a corner of a corner case.
For example, i would def not want to suddenly pass-by-ref just because i had a lambda/query that happened to use the local as well.
@CyrusNajmabadi capturing the local is not a sufficient condition. It needs to be both captured and have a value written to it in a lambda or local function. Further restrictions may be possible, e.g. special handling for cases where local functions are only called directly and not converted to delegates.
an extreme corner case
Perhaps. But keep in mind that this issue was discovered in the real world, not academically. A developer extracted a method, checked it in, and deployed to production. Later they got a bug report and traced it back to this refactoring. It's real, and it's a subtle product bug that was created by a refactoring tool.
We count on automated refactorings to catch the things that humans can't catch.
Note:
That said, in this case, I think it might be ok to limit a fix to just checking for mutations to the variable in another closure and potentially passing by ref then.
This issue has been moved from a ticket on Developer Community.
Extract a method as indicated in the following code:
As it is, the test passes. If you extract the indicated block, then the test fails. Extract Method should add a
ref
to the parameter on the new method.Original Comments
Visual Studio Feedback System on 9/17/2019, 04:06 AM:
We have directed your feedback to the appropriate engineering team for further evaluation. The team will review the feedback and notify you about the next steps.
Visual Studio Feedback System on 9/19/2019, 03:14 PM:
This issue is currently being investigated. Our team will get back to you if either more information is needed, a workaround is available, or the issue is resolved.
Original Solutions
(no solutions)