提交 81166360 编写于 作者: V VSadov

Added more tests for ?. codegen and fixed some issues that were uncovered.

上级 8f49b0ed
......@@ -6437,7 +6437,7 @@ private BoundExpression GetReceiverForConditionalBinding(ExpressionSyntax bindin
receiverType = receiverType.GetNullableUnderlyingType();
}
receiver = new BoundConditionalReceiver(receiver.Syntax, receiverType) { WasCompilerGenerated = true };
receiver = new BoundConditionalReceiver(receiver.Syntax, 0, receiverType) { WasCompilerGenerated = true };
return receiver;
}
......
......@@ -973,11 +973,22 @@
<Field Name="Receiver" Type="BoundExpression"/>
<Field Name="WhenNotNull" Type="BoundExpression"/>
<Field Name="WhenNullOpt" Type="BoundExpression" Null="allow"/>
<!--
Async rewriter needs to replace receivers with their spilled values
and for that it needs to match receivers and the containing conditional
expressions.
To be able to do that, during lowering, we will assign
BoundLoweredConditionalAccess and corresponding BoundConditionalReceiver
matching ID that are integers unique for the containing method body.
-->
<Field Name="ID" Type="int"/>
</Node>
<!-- represents the receiver of a conditional access expression -->
<Node Name="BoundConditionalReceiver" Base="BoundExpression">
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
<!-- See the comment in BoundLoweredConditionalAccess -->
<Field Name="ID" Type="int"/>
</Node>
<!-- This node represents a complex receiver for a conditional access.
......
......@@ -1232,7 +1232,7 @@ public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalA
EnsureStackState(cookie); // implicit label here
}
return node.Update(receiver, whenNotNull, whenNull, node.Type);
return node.Update(receiver, whenNotNull, whenNull, node.ID, node.Type);
}
public override BoundNode VisitComplexConditionalReceiver(BoundComplexConditionalReceiver node)
......
......@@ -274,9 +274,13 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState,
arguments[i] = F.Convert(manager.System_Object,
new BoundLoweredConditionalAccess(F.Syntax,
F.Field(F.This(), property.BackingField),
F.Call(new BoundConditionalReceiver(F.Syntax, property.BackingField.Type), manager.System_Object__ToString),
F.Call(new BoundConditionalReceiver(
F.Syntax,
iD: i,
type: property.BackingField.Type), manager.System_Object__ToString),
null,
manager.System_String),
iD: i,
type: manager.System_String),
ConversionKind.ImplicitReference);
}
formatString.Builder.Append(" }}");
......
......@@ -897,11 +897,11 @@ public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalA
var whenNotNull = VisitExpression(ref whenNotNullBuilder, node.WhenNotNull);
BoundSpillSequenceBuilder whenNullBuilder = null;
var whenNull = VisitExpression(ref whenNullBuilder, node.WhenNullOpt);
var whenNullOpt = VisitExpression(ref whenNullBuilder, node.WhenNullOpt);
if (whenNotNullBuilder == null && whenNullBuilder == null)
{
return UpdateExpression(receiverBuilder, node.Update(receiver, whenNotNull, whenNull, node.Type));
return UpdateExpression(receiverBuilder, node.Update(receiver, whenNotNull, whenNullOpt, node.ID, node.Type));
}
if (receiverBuilder == null) receiverBuilder = new BoundSpillSequenceBuilder();
......@@ -943,15 +943,18 @@ public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalA
receiver = _F.ComplexConditionalReceiver(receiver, _F.Local(clone));
}
whenNullOpt = whenNullOpt ?? _F.Default(node.Type);
if (node.Type.SpecialType == SpecialType.System_Void)
{
var wnenNotNullStatement = UpdateStatement(whenNotNullBuilder, _F.ExpressionStatement(whenNotNull), substituteTemps: false);
wnenNotNullStatement = ConditionalReceiverReplacer.Replace(wnenNotNullStatement, receiver);
wnenNotNullStatement = ConditionalReceiverReplacer.Replace(wnenNotNullStatement, receiver, node.ID);
receiverBuilder.AddStatement(
_F.If(condition,
wnenNotNullStatement,
UpdateStatement(whenNullBuilder, _F.ExpressionStatement(whenNull), substituteTemps: false)));
UpdateStatement(whenNullBuilder, _F.ExpressionStatement(whenNullOpt), substituteTemps: false)));
return receiverBuilder.Update(_F.Default(node.Type));
}
......@@ -960,13 +963,13 @@ public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalA
Debug.Assert(_F.Syntax.IsKind(SyntaxKind.AwaitExpression));
var tmp = _F.SynthesizedLocal(node.Type, kind: SynthesizedLocalKind.AwaitSpill, syntax: _F.Syntax);
var wnenNotNullStatement = UpdateStatement(whenNotNullBuilder, _F.Assignment(_F.Local(tmp), whenNotNull), substituteTemps: false);
wnenNotNullStatement = ConditionalReceiverReplacer.Replace(wnenNotNullStatement, receiver);
wnenNotNullStatement = ConditionalReceiverReplacer.Replace(wnenNotNullStatement, receiver, node.ID);
receiverBuilder.AddLocal(tmp, _F.Diagnostics);
receiverBuilder.AddStatement(
_F.If(condition,
wnenNotNullStatement,
UpdateStatement(whenNullBuilder, _F.Assignment(_F.Local(tmp), whenNull), substituteTemps: false)));
UpdateStatement(whenNullBuilder, _F.Assignment(_F.Local(tmp), whenNullOpt), substituteTemps: false)));
return receiverBuilder.Update(_F.Local(tmp));
}
......@@ -975,20 +978,22 @@ public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalA
private class ConditionalReceiverReplacer: BoundTreeRewriter
{
private readonly BoundExpression _receiver;
private readonly int _receiverID;
#if DEBUG
// we must replace exatly one node
private int _replaced;
#endif
private ConditionalReceiverReplacer(BoundExpression receiver)
private ConditionalReceiverReplacer(BoundExpression receiver, int receiverID)
{
this._receiver = receiver;
this._receiverID = receiverID;
}
public static BoundStatement Replace(BoundNode node, BoundExpression receiver)
public static BoundStatement Replace(BoundNode node, BoundExpression receiver, int receiverID)
{
var replacer = new ConditionalReceiverReplacer(receiver);
var replacer = new ConditionalReceiverReplacer(receiver, receiverID);
var result = (BoundStatement)replacer.Visit(node);
#if DEBUG
Debug.Assert(replacer._replaced == 1, "should have replaced exactly one node");
......@@ -999,10 +1004,15 @@ public static BoundStatement Replace(BoundNode node, BoundExpression receiver)
public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node)
{
if (node.ID == this._receiverID)
{
#if DEBUG
_replaced++;
_replaced++;
#endif
return _receiver;
return _receiver;
}
return node;
}
}
......
......@@ -510,6 +510,7 @@ private BoundExpression RewriteLiftedBinaryOperator(CSharpSyntaxNode syntax, Bin
conditionalLeft.Receiver,
whenNotNull: result,
whenNullOpt: whenNullOpt,
iD: conditionalLeft.ID,
type: result.Type
);
}
......@@ -1749,7 +1750,7 @@ private MethodSymbol GetNullableMethod(CSharpSyntaxNode syntax, TypeSymbol nulla
var whenNull = kind == BinaryOperatorKind.NullableNullEqual ? MakeBooleanConstant(syntax, true) : null;
return conditionalAccess.Update(conditionalAccess.Receiver, whenNotNull, whenNull, whenNotNull.Type);
return conditionalAccess.Update(conditionalAccess.Receiver, whenNotNull, whenNull, conditionalAccess.ID, whenNotNull.Type);
}
MethodSymbol get_HasValue = GetNullableMethod(syntax, nullable.Type, SpecialMember.System_Nullable_T_get_HasValue);
......
......@@ -20,6 +20,7 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
// null when currently enclosing conditional access node
// is not supposed to be lowered.
private BoundExpression _currentConditionalAccessTarget = null;
private int _currentConditionalAccessID = 0;
private enum ConditionalAccessLoweringKind
{
......@@ -63,13 +64,19 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
var previousConditionalAccesTarget = _currentConditionalAccessTarget;
var currentConditionalAccessID = ++this._currentConditionalAccessID;
LocalSymbol temp = null;
BoundExpression unconditionalAccess = null;
switch (loweringKind)
{
case ConditionalAccessLoweringKind.LoweredConditionalAccess:
_currentConditionalAccessTarget = null;
_currentConditionalAccessTarget = new BoundConditionalReceiver(
loweredReceiver.Syntax,
currentConditionalAccessID,
receiverType);
break;
case ConditionalAccessLoweringKind.Ternary:
......@@ -130,7 +137,9 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
node.Syntax,
loweredReceiver,
loweredAccessExpression,
rewrittenWhenNull, type);
rewrittenWhenNull,
currentConditionalAccessID,
type);
break;
......@@ -176,14 +185,11 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node)
{
if (_currentConditionalAccessTarget == null)
{
return node;
}
var newtarget = _currentConditionalAccessTarget;
if (newtarget.Type.IsNullableType())
{
Debug.Assert(newtarget.Kind != BoundKind.ConditionalReceiver);
newtarget = MakeOptimizedGetValueOrDefault(node.Syntax, newtarget);
}
......
......@@ -130,6 +130,7 @@ private bool TryGetOptimizableNullableConditionalAccess(BoundExpression operand,
conditionalAccess.Receiver,
whenNotNull: notNullAccess,
whenNullOpt: whenNullOpt,
iD: conditionalAccess.ID,
type: rewrittenResultType
);
}
......
......@@ -5786,5 +5786,279 @@ .maxstack 2
IL_000e: ret
}");
}
[Fact]
public void ConditionalBoolExpr02()
{
var source = @"
class C
{
public static void Main()
{
System.Console.Write(HasLength(null, 0));
System.Console.Write(HasLength(null, 3));
System.Console.Write(HasLength(""q"", 2));
}
static bool HasLength(string s, int len)
{
return (s?.Length ?? 2) + 1 == len;
}
}
";
var verifier = CompileAndVerify(source, expectedOutput: @"FalseTrueTrue");
verifier.VerifyIL("C.HasLength", @"
{
// Code size 18 (0x12)
.maxstack 2
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0006
IL_0003: ldc.i4.2
IL_0004: br.s IL_000c
IL_0006: ldarg.0
IL_0007: call ""int string.Length.get""
IL_000c: ldc.i4.1
IL_000d: add
IL_000e: ldarg.1
IL_000f: ceq
IL_0011: ret
}");
}
[Fact]
public void ConditionalBoolExpr03()
{
var source = @"
using System.Threading.Tasks;
static class C
{
public static void Main()
{
System.Console.Write(HasLength(null, 0).Result);
System.Console.Write(HasLength(null, 3).Result);
System.Console.Write(HasLength(""q"", 2).Result);
}
static async Task<bool> HasLength(string s, int len)
{
return (s?.Foo(await Bar()) ?? await Bar() + await Bar()) + 1 == len;
}
static int Foo(this string s, int arg)
{
return s.Length;
}
static async Task<int> Bar()
{
await Task.Yield();
return 1;
}
}
";
var c = CreateCompilationWithMscorlib45(source, new[] { SystemRef_v4_0_30319_17929, SystemCoreRef_v4_0_30319_17929, CSharpRef }, TestOptions.ReleaseExe);
var comp = CompileAndVerify(c, expectedOutput: @"FalseTrueTrue");
}
[Fact]
public void ConditionalBoolExpr04()
{
var source = @"
using System.Threading.Tasks;
static class C
{
public static void Main()
{
System.Console.Write(HasLength((string)null, 0).Result);
System.Console.Write(HasLength((string)null, 3).Result);
System.Console.Write(HasLength(""q"", 2).Result);
}
static async Task<bool> HasLength<T>(T s, int len)
{
return (s?.Foo(await Bar()) ?? 2) + 1 == len;
}
static int Foo<T>(this T s, int arg)
{
return ((string)(object)s).Length;
}
static async Task<int> Bar()
{
await Task.Yield();
return 1;
}
}
";
var c = CreateCompilationWithMscorlib45(source, new[] { SystemRef_v4_0_30319_17929, SystemCoreRef_v4_0_30319_17929, CSharpRef }, TestOptions.ReleaseExe);
var comp = CompileAndVerify(c, expectedOutput: @"FalseTrueTrue");
}
[Fact]
public void ConditionalBoolExpr05()
{
var source = @"
using System.Threading.Tasks;
static class C
{
public static void Main()
{
System.Console.Write(HasLength((string)null, 0).Result);
System.Console.Write(HasLength((string)null, 3).Result);
System.Console.Write(HasLength(""q"", 2).Result);
}
static async Task<bool> HasLength<T>(T s, int len)
{
return (s?.Foo(await Bar(await Bar())) ?? 2) + 1 == len;
}
static int Foo<T>(this T s, int arg)
{
return ((string)(object)s).Length;
}
static async Task<int> Bar()
{
await Task.Yield();
return 1;
}
static async Task<int> Bar(int arg)
{
await Task.Yield();
return arg;
}
}
";
var c = CreateCompilationWithMscorlib45(source, new[] { SystemRef_v4_0_30319_17929, SystemCoreRef_v4_0_30319_17929, CSharpRef }, TestOptions.ReleaseExe);
var comp = CompileAndVerify(c, expectedOutput: @"FalseTrueTrue");
}
[Fact]
public void ConditionalBoolExpr06()
{
var source = @"
using System.Threading.Tasks;
static class C
{
public static void Main()
{
System.Console.Write(HasLength(null, 0).Result);
System.Console.Write(HasLength(null, 7).Result);
System.Console.Write(HasLength(""q"", 7).Result);
}
static async Task<bool> HasLength(string s, int len)
{
System.Console.WriteLine(s?.Foo(await Bar())?.Foo(await Bar()) + ""#"");
return s?.Foo(await Bar())?.Foo(await Bar()).Length == len;
}
static string Foo(this string s, string arg)
{
return s + arg;
}
static async Task<string> Bar()
{
await Task.Yield();
return ""Bar"";
}
}
";
var c = CreateCompilationWithMscorlib45(source, new[] { SystemRef_v4_0_30319_17929, SystemCoreRef_v4_0_30319_17929, CSharpRef }, TestOptions.ReleaseExe);
var comp = CompileAndVerify(c, expectedOutput: @"#
False#
FalseqBarBar#
True");
}
[Fact]
public void ConditionalBoolExpr07()
{
var source = @"
using System.Threading.Tasks;
static class C
{
public static void Main()
{
System.Console.WriteLine(Test(null).Result);
System.Console.WriteLine(Test(""q"").Result);
}
static async Task<bool> Test(string s)
{
return (await Bar(s))?.Foo(await Bar())?.ToString()?.Length > 1;
}
static string Foo(this string s, string arg1)
{
return s + arg1;
}
static async Task<string> Bar()
{
await Task.Yield();
return ""Bar"";
}
static async Task<string> Bar(string arg)
{
await Task.Yield();
return arg;
}
}
";
var c = CreateCompilationWithMscorlib45(source, new[] { SystemRef_v4_0_30319_17929, SystemCoreRef_v4_0_30319_17929, CSharpRef }, TestOptions.ReleaseExe);
var comp = CompileAndVerify(c, expectedOutput: @"False
True");
}
[Fact]
public void ConditionalBoolExpr08()
{
var source = @"
using System.Threading.Tasks;
static class C
{
public static void Main()
{
System.Console.WriteLine(Test(null).Result);
System.Console.WriteLine(Test(""q"").Result);
}
static async Task<bool> Test(string s)
{
return (await Bar(s))?.Insert(0, await Bar())?.ToString()?.Length > 1;
}
static async Task<string> Bar()
{
await Task.Yield();
return ""Bar"";
}
static async Task<dynamic> Bar(string arg)
{
await Task.Yield();
return arg;
}
}";
var c = CreateCompilationWithMscorlib45(source, new[] { SystemRef_v4_0_30319_17929, SystemCoreRef_v4_0_30319_17929, CSharpRef }, TestOptions.ReleaseExe);
var comp = CompileAndVerify(c, expectedOutput: @"False
True");
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册