Do not store temp of ref conditional operator by value
Fixes #74115
It looks like there are no tests.
Done with review pass (commit 2)
@AlekseyTs do you have an idea for a test?
The scenarios in the issue being fixed should be included as tests, for starters. Additional tests depend on the nature of the product change, but it looks like that's not yet finalized, so it's too early to tell.
Done with review pass (commit 5)
@AlekseyTs can you write a test or help me to do so for the linked issue.
I explain what I would want to test:
compile and execute the following code:
string str = "a2";
int x = 1;
int y = 2;
ref int r =
ref str is "whatever" ? ref x
: ref str is { Length: >= 2 and <= 10 or 22 } ? ref y
: ref System.Runtime.CompilerServices.Unsafe.NullRef<int>();
r++;
r++;
r++;
int xxx = r;
System.Console.WriteLine(xxx); //5
System.Console.WriteLine(x); //1
System.Console.WriteLine(y); //expected 5 - but we get 2
It should print (because r should be a reference to the local y):
5
1
5
currently it prints:
5
1
2
And it already works if no "Or Pattern" is used (without or 22)
The reason is because the alternative
ref str is { Length: >= 2 and <= 10 or 22 } ? ref y
: ref System.Runtime.CompilerServices.Unsafe.NullRef<int>()
needs spilling and stores its result in a temporary by value.
For writing a test, take a look at TestRefConditionalDifferentTypes3.
The broad strokes are CreateCompilation, CompileAndVerify (verifies execution among other things), VerifyDiagnostics and VerifyIL.
I added the following test method:
[Fact]
public void TestRefConditionalWithSpilling()
{
var source = """
class C
{
public static void Main()
{
string str = "a2";
int x = 1;
int y = 2;
int z = 777;
ref int r =
ref str is "whatever" ? ref x
: ref str is { Length: >= 2 and <= 10 or 22 } ? ref y
: ref z;
r++;
r++;
r++;
int xxx = r;
System.Console.WriteLine(xxx); //5
System.Console.WriteLine(x); //1
System.Console.WriteLine(y); //expected 5 - but we get 2
}
}
""";
var comp = CompileAndVerify(source,
expectedOutput: """
5
1
5
""");
comp.VerifyDiagnostics();
}
But if I try to execute it, I get:
System.NullReferenceException : Object reference not set to an instance of an object.
C.Main() in :line 10
RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
Do you know why?
I got it: it must be a ref-assignment...
@AlekseyTs can you review again?
Done with review pass (commit 11)