提交 81b8888d 编写于 作者: V VSadov

Code review feedback. More tests.

上级 0c69ef23
......@@ -332,6 +332,36 @@ public override bool SuppressVirtualCalls
{
get { return this.IsBaseConversion; }
}
/// <summary>
/// Returns true when conversion itself (not the operand) may have sideeffects
/// A typical sideeffect of a conversion is an exception when conversion is unsuccessful.
/// </summary>
/// <returns></returns>
internal bool ConversionHasSideEffects()
{
// only some intrinsic conversions are side effect free
// the only side effect of an intrinsic conversion is a throw when we fail to convert.
// and some intrinsic conversion always succeed
switch (this.ConversionKind)
{
case ConversionKind.Identity:
// NOTE: even explicit float/double identity conversion does not have side
// effects since it does not throw
case ConversionKind.ImplicitNumeric:
case ConversionKind.ImplicitEnumeration:
// implicit ref cast does not throw ...
case ConversionKind.ImplicitReference:
case ConversionKind.Boxing:
return false;
// unchecked numeric conversion does not throw
case ConversionKind.ExplicitNumeric:
return this.Checked;
}
return true;
}
}
internal partial class BoundObjectCreationExpression
......
......@@ -979,16 +979,16 @@
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.
matching Id that are integers unique for the containing method body.
-->
<Field Name="ID" Type="int"/>
<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"/>
<Field Name="Id" Type="int"/>
</Node>
<!-- This node represents a complex receiver for a conditional access.
......
......@@ -49,7 +49,7 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
break;
case BoundKind.ComplexConditionalReceiver:
EmitComplexConditionalReceiver((BoundComplexConditionalReceiver)expression);
EmitComplexConditionalReceiverAddress((BoundComplexConditionalReceiver)expression);
break;
case BoundKind.Parameter:
......@@ -100,7 +100,7 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
return null;
}
private void EmitComplexConditionalReceiver(BoundComplexConditionalReceiver expression)
private void EmitComplexConditionalReceiverAddress(BoundComplexConditionalReceiver expression)
{
Debug.Assert(!expression.Type.IsReferenceType);
Debug.Assert(!expression.Type.IsValueType);
......
......@@ -26,7 +26,7 @@ private void EmitConversionExpression(BoundConversion conversion, bool used)
return;
}
if (!used && !LocalRewriter.ConversionHasSideEffects(conversion))
if (!used && !conversion.ConversionHasSideEffects())
{
EmitExpression(conversion.Operand, false); // just do expr side effects
return;
......
......@@ -1231,8 +1231,12 @@ public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalA
whenNull = (BoundExpression)this.Visit(whenNull);
EnsureStackState(cookie); // implicit label here
}
else
{
_counter += 1;
}
return node.Update(receiver, whenNotNull, whenNull, node.ID, node.Type);
return node.Update(receiver, whenNotNull, whenNull, node.Id, node.Type);
}
public override BoundNode VisitComplexConditionalReceiver(BoundComplexConditionalReceiver node)
......@@ -1251,7 +1255,7 @@ public override BoundNode VisitComplexConditionalReceiver(BoundComplexConditiona
EnsureStackState(cookie); // implicit label here
this._evalStack = origStack; // alternative is evaluated with original stack
var referenceTypeReceiver = (BoundExpression)this.Visit(node.ReferenceTypeReceiver);
var referenceTypeReceiver = (BoundExpression)this.Visit(node.ReferenceTypeReceiver);
EnsureStackState(cookie); // implicit label here
......
......@@ -276,10 +276,10 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState,
F.Field(F.This(), property.BackingField),
F.Call(new BoundConditionalReceiver(
F.Syntax,
iD: i,
id: i,
type: property.BackingField.Type), manager.System_Object__ToString),
null,
iD: i,
id: i,
type: manager.System_String),
ConversionKind.ImplicitReference);
}
......
......@@ -901,7 +901,7 @@ public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalA
if (whenNotNullBuilder == null && whenNullBuilder == null)
{
return UpdateExpression(receiverBuilder, node.Update(receiver, whenNotNull, whenNullOpt, node.ID, node.Type));
return UpdateExpression(receiverBuilder, node.Update(receiver, whenNotNull, whenNullOpt, node.Id, node.Type));
}
if (receiverBuilder == null) receiverBuilder = new BoundSpillSequenceBuilder();
......@@ -942,19 +942,15 @@ 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, node.ID);
wnenNotNullStatement = ConditionalReceiverReplacer.Replace(wnenNotNullStatement, receiver, node.Id);
receiverBuilder.AddStatement(
_F.If(condition,
wnenNotNullStatement,
UpdateStatement(whenNullBuilder, _F.ExpressionStatement(whenNullOpt), substituteTemps: false)));
Debug.Assert(whenNullOpt == null || !LocalRewriter.ReadIsSideeffecting(whenNullOpt));
receiverBuilder.AddStatement(_F.If(condition, wnenNotNullStatement));
return receiverBuilder.Update(_F.Default(node.Type));
}
......@@ -963,7 +959,9 @@ 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, node.ID);
wnenNotNullStatement = ConditionalReceiverReplacer.Replace(wnenNotNullStatement, receiver, node.Id);
whenNullOpt = whenNullOpt ?? _F.Default(node.Type);
receiverBuilder.AddLocal(tmp, _F.Diagnostics);
receiverBuilder.AddStatement(
......@@ -1004,7 +1002,7 @@ public static BoundStatement Replace(BoundNode node, BoundExpression receiver, i
public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node)
{
if (node.ID == this._receiverID)
if (node.Id == this._receiverID)
{
#if DEBUG
_replaced++;
......
......@@ -271,7 +271,7 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO
if (operatorKind.IsLifted())
{
return RewriteLiftedBinaryOperator(syntax, operatorKind, ref loweredLeft, loweredRight, type, method);
return RewriteLiftedBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
}
if (operatorKind.IsUserDefined())
......@@ -475,7 +475,7 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO
new BoundBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, null, null, LookupResultKind.Viable, type);
}
private BoundExpression RewriteLiftedBinaryOperator(CSharpSyntaxNode syntax, BinaryOperatorKind operatorKind, ref BoundExpression loweredLeft, BoundExpression loweredRight, TypeSymbol type, MethodSymbol method)
private BoundExpression RewriteLiftedBinaryOperator(CSharpSyntaxNode syntax, BinaryOperatorKind operatorKind, BoundExpression loweredLeft, BoundExpression loweredRight, TypeSymbol type, MethodSymbol method)
{
var conditionalLeft = loweredLeft as BoundLoweredConditionalAccess;
......@@ -501,16 +501,19 @@ private BoundExpression RewriteLiftedBinaryOperator(CSharpSyntaxNode syntax, Bin
{
BoundExpression whenNullOpt = null;
if (operatorKind.Operator() == BinaryOperatorKind.NotEqual)
// for all operators null-in means null-out
// except for the Equal/NotEqual since null == null ==> true
if (operatorKind.Operator() == BinaryOperatorKind.NotEqual ||
operatorKind.Operator() == BinaryOperatorKind.Equal)
{
whenNullOpt = MakeBooleanConstant(syntax, true);
whenNullOpt = RewriteLiftedBinaryOperator(syntax, operatorKind, _factory.Default(loweredLeft.Type), loweredRight, type, method);
}
result = conditionalLeft.Update(
conditionalLeft.Receiver,
whenNotNull: result,
whenNullOpt: whenNullOpt,
iD: conditionalLeft.ID,
id: conditionalLeft.Id,
type: result.Type
);
}
......@@ -704,7 +707,7 @@ private BoundExpression MakeTruthTestForDynamicLogicalOperator(CSharpSyntaxNode
if (operatorKind.IsLifted())
{
return RewriteLiftedBinaryOperator(syntax, operatorKind, ref loweredLeft, loweredRight, type, method);
return RewriteLiftedBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
}
// Otherwise, nothing special here.
......@@ -1736,7 +1739,7 @@ private MethodSymbol GetNullableMethod(CSharpSyntaxNode syntax, TypeSymbol nulla
type: returnType);
}
// arr?.Leghth == null
// arr?.Length == null
var conditionalAccess = nullable as BoundLoweredConditionalAccess;
if (conditionalAccess != null &&
(conditionalAccess.WhenNullOpt == null || conditionalAccess.WhenNullOpt.IsDefaultValue()))
......@@ -1750,7 +1753,7 @@ private MethodSymbol GetNullableMethod(CSharpSyntaxNode syntax, TypeSymbol nulla
var whenNull = kind == BinaryOperatorKind.NullableNullEqual ? MakeBooleanConstant(syntax, true) : null;
return conditionalAccess.Update(conditionalAccess.Receiver, whenNotNull, whenNull, conditionalAccess.ID, 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);
......
......@@ -620,7 +620,7 @@ private BoundExpression BoxReceiver(BoundExpression rewrittenReceiver, NamedType
}
// a simple check for common nonsideeffecting expressions
private static bool ReadIsSideeffecting(
internal static bool ReadIsSideeffecting(
BoundExpression expression)
{
if (expression.ConstantValue != null)
......@@ -645,7 +645,7 @@ private BoundExpression BoxReceiver(BoundExpression rewrittenReceiver, NamedType
case BoundKind.Conversion:
var conv = (BoundConversion)expression;
return ConversionHasSideEffects(conv) ||
return conv.ConversionHasSideEffects() ||
ReadIsSideeffecting(conv.Operand);
case BoundKind.ObjectCreationExpression:
......@@ -664,30 +664,6 @@ private BoundExpression BoxReceiver(BoundExpression rewrittenReceiver, NamedType
}
}
internal static bool ConversionHasSideEffects(BoundConversion conversion)
{
// only some intrinsic conversions are side effect free the only side effect of an
// intrinsic conversion is a throw when we fail to convert.
switch (conversion.ConversionKind)
{
case ConversionKind.Identity:
// NOTE: even explicit float/double identity conversion does not have side
// effects since it does not throw
case ConversionKind.ImplicitNumeric:
case ConversionKind.ImplicitEnumeration:
// implicit ref cast does not throw ...
case ConversionKind.ImplicitReference:
case ConversionKind.Boxing:
return false;
// unchecked numeric conversion does not throw
case ConversionKind.ExplicitNumeric:
return conversion.Checked;
}
return true;
}
// nontrivial literals do not change between reads
// but may require re-constructing, so it is better
// to treat them as potentially changing.
......
......@@ -39,6 +39,10 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
var receiverType = loweredReceiver.Type;
ConditionalAccessLoweringKind loweringKind;
// nullable and dynamic receivers are not directly supported in codegen and need to be lowered
// in particular nullable receiver implies that the condition of the
// conditional and the access receiver are actually different expressions
// (HasValue and GetValueOrDefault respectively)
var lowerToTernary = receiverType.IsNullableType() || node.AccessExpression.Type.IsDynamic();
if (!lowerToTernary)
......@@ -127,8 +131,6 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
BoundExpression result;
var objectType = _compilation.GetSpecialType(SpecialType.System_Object);
rewrittenWhenNull = rewrittenWhenNull ?? _factory.Default(nodeType);
switch (loweringKind)
{
case ConditionalAccessLoweringKind.LoweredConditionalAccess:
......@@ -165,7 +167,7 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
result = RewriteConditionalOperator(node.Syntax,
condition,
consequence,
rewrittenWhenNull,
rewrittenWhenNull ?? _factory.Default(nodeType),
null,
nodeType);
......
......@@ -130,7 +130,7 @@ private bool TryGetOptimizableNullableConditionalAccess(BoundExpression operand,
conditionalAccess.Receiver,
whenNotNull: notNullAccess,
whenNullOpt: whenNullOpt,
iD: conditionalAccess.ID,
id: conditionalAccess.Id,
type: rewrittenResultType
);
}
......
......@@ -5948,6 +5948,100 @@ .maxstack 2
}");
}
[Fact]
public void ConditionalBoolExpr02a()
{
var source = @"
class C
{
public static void Main()
{
System.Console.Write(NotHasLength(null, 0));
System.Console.Write(NotHasLength(null, 3));
System.Console.Write(NotHasLength(""q"", 2));
}
static bool NotHasLength(string s, int len)
{
return s?.Length + 1 != len;
}
}
";
var verifier = CompileAndVerify(source, expectedOutput: @"TrueTrueFalse");
verifier.VerifyIL("C.NotHasLength", @"
{
// Code size 20 (0x14)
.maxstack 2
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0005
IL_0003: ldc.i4.1
IL_0004: ret
IL_0005: ldarg.0
IL_0006: call ""int string.Length.get""
IL_000b: ldc.i4.1
IL_000c: add
IL_000d: ldarg.1
IL_000e: ceq
IL_0010: ldc.i4.0
IL_0011: ceq
IL_0013: ret
}");
}
[Fact]
public void ConditionalBoolExpr02b()
{
var source = @"
class C
{
public static void Main()
{
System.Console.Write(NotHasLength(null, 0));
System.Console.Write(NotHasLength(null, 3));
System.Console.Write(NotHasLength(""q"", 2));
System.Console.Write(NotHasLength(null, null));
}
static bool NotHasLength(string s, int? len)
{
return s?.Length + 1 != len;
}
}
";
var verifier = CompileAndVerify(source, expectedOutput: @"TrueTrueFalseFalse");
verifier.VerifyIL("C.NotHasLength", @"
{
// Code size 43 (0x2b)
.maxstack 2
.locals init (int? V_0)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_000b
IL_0003: ldarga.s V_1
IL_0005: call ""bool int?.HasValue.get""
IL_000a: ret
IL_000b: ldarg.0
IL_000c: call ""int string.Length.get""
IL_0011: ldc.i4.1
IL_0012: add
IL_0013: ldarg.1
IL_0014: stloc.0
IL_0015: ldloca.s V_0
IL_0017: call ""int int?.GetValueOrDefault()""
IL_001c: beq.s IL_0020
IL_001e: ldc.i4.1
IL_001f: ret
IL_0020: ldloca.s V_0
IL_0022: call ""bool int?.HasValue.get""
IL_0027: ldc.i4.0
IL_0028: ceq
IL_002a: ret
}");
}
[Fact]
public void ConditionalBoolExpr03()
{
......@@ -6181,5 +6275,323 @@ static async Task<dynamic> Bar(string arg)
True");
}
[Fact]
public void ConditionalUserDef01()
{
var source = @"
class C
{
struct S1
{
public static bool operator ==(S1? x, S1?y)
{
System.Console.Write(""=="");
return true;
}
public static bool operator !=(S1? x, S1? y)
{
System.Console.Write(""!="");
return false;
}
}
class C1
{
public S1 Foo()
{
return new S1();
}
}
public static void Main()
{
System.Console.WriteLine(TestEq(null, new S1()));
System.Console.WriteLine(TestEq(new C1(), new S1()));
System.Console.WriteLine(TestNeq(null, new S1()));
System.Console.WriteLine(TestNeq(new C1(), new S1()));
}
static bool TestEq(C1 c, S1 arg)
{
return c?.Foo() == arg;
}
static bool TestNeq(C1 c, S1 arg)
{
return c?.Foo() != arg;
}
}
";
var verifier = CompileAndVerify(source, expectedOutput: @"==True
==True
!=False
!=False");
verifier.VerifyIL("C.TestNeq", @"
{
// Code size 37 (0x25)
.maxstack 2
.locals init (C.S1? V_0)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_000e
IL_0003: ldloca.s V_0
IL_0005: initobj ""C.S1?""
IL_000b: ldloc.0
IL_000c: br.s IL_0019
IL_000e: ldarg.0
IL_000f: call ""C.S1 C.C1.Foo()""
IL_0014: newobj ""C.S1?..ctor(C.S1)""
IL_0019: ldarg.1
IL_001a: newobj ""C.S1?..ctor(C.S1)""
IL_001f: call ""bool C.S1.op_Inequality(C.S1?, C.S1?)""
IL_0024: ret
}");
}
[Fact]
public void ConditionalUserDef01n()
{
var source = @"
class C
{
struct S1
{
public static bool operator ==(S1? x, S1?y)
{
System.Console.Write(""=="");
return true;
}
public static bool operator !=(S1? x, S1? y)
{
System.Console.Write(""!="");
return false;
}
}
class C1
{
public S1 Foo()
{
return new S1();
}
}
public static void Main()
{
System.Console.WriteLine(TestEq(null, new S1()));
System.Console.WriteLine(TestEq(new C1(), new S1()));
System.Console.WriteLine(TestEq(new C1(), null));
System.Console.WriteLine(TestNeq(null, new S1()));
System.Console.WriteLine(TestNeq(new C1(), new S1()));
System.Console.WriteLine(TestNeq(new C1(), null));
}
static bool TestEq(C1 c, S1? arg)
{
return c?.Foo() == arg;
}
static bool TestNeq(C1 c, S1? arg)
{
return c?.Foo() != arg;
}
}
";
var verifier = CompileAndVerify(source, expectedOutput: @"==True
==True
==True
!=False
!=False
!=False");
verifier.VerifyIL("C.TestNeq", @"
{
// Code size 32 (0x20)
.maxstack 2
.locals init (C.S1? V_0)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_000e
IL_0003: ldloca.s V_0
IL_0005: initobj ""C.S1?""
IL_000b: ldloc.0
IL_000c: br.s IL_0019
IL_000e: ldarg.0
IL_000f: call ""C.S1 C.C1.Foo()""
IL_0014: newobj ""C.S1?..ctor(C.S1)""
IL_0019: ldarg.1
IL_001a: call ""bool C.S1.op_Inequality(C.S1?, C.S1?)""
IL_001f: ret
}");
}
[Fact]
public void ConditionalUserDef02()
{
var source = @"
class C
{
struct S1
{
public static bool operator ==(S1 x, S1 y)
{
System.Console.Write(""=="");
return true;
}
public static bool operator !=(S1 x, S1 y)
{
System.Console.Write(""!="");
return false;
}
}
class C1
{
public S1 Foo()
{
return new S1();
}
}
public static void Main()
{
System.Console.WriteLine(TestEq(null, new S1()));
System.Console.WriteLine(TestEq(new C1(), new S1()));
System.Console.WriteLine(TestNeq(null, new S1()));
System.Console.WriteLine(TestNeq(new C1(), new S1()));
}
static bool TestEq(C1 c, S1 arg)
{
return c?.Foo() == arg;
}
static bool TestNeq(C1 c, S1 arg)
{
return c?.Foo() != arg;
}
}
";
var verifier = CompileAndVerify(source, expectedOutput: @"False
==True
True
!=False");
verifier.VerifyIL("C.TestNeq", @"
{
// Code size 18 (0x12)
.maxstack 2
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0005
IL_0003: ldc.i4.1
IL_0004: ret
IL_0005: ldarg.0
IL_0006: call ""C.S1 C.C1.Foo()""
IL_000b: ldarg.1
IL_000c: call ""bool C.S1.op_Inequality(C.S1, C.S1)""
IL_0011: ret
}");
}
[Fact]
public void ConditionalUserDef02n()
{
var source = @"
class C
{
struct S1
{
public static bool operator ==(S1 x, S1 y)
{
System.Console.Write(""=="");
return true;
}
public static bool operator !=(S1 x, S1 y)
{
System.Console.Write(""!="");
return false;
}
}
class C1
{
public S1 Foo()
{
return new S1();
}
}
public static void Main()
{
System.Console.WriteLine(TestEq(null, new S1()));
System.Console.WriteLine(TestEq(new C1(), new S1()));
System.Console.WriteLine(TestEq(new C1(), null));
System.Console.WriteLine(TestNeq(null, new S1()));
System.Console.WriteLine(TestNeq(new C1(), new S1()));
System.Console.WriteLine(TestNeq(new C1(), null));
}
static bool TestEq(C1 c, S1? arg)
{
return c?.Foo() == arg;
}
static bool TestNeq(C1 c, S1? arg)
{
return c?.Foo() != arg;
}
}
";
var verifier = CompileAndVerify(source, expectedOutput: @"False
==True
False
True
!=False
True");
verifier.VerifyIL("C.TestNeq", @"
{
// Code size 45 (0x2d)
.maxstack 2
.locals init (C.S1 V_0,
C.S1? V_1)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_000b
IL_0003: ldarga.s V_1
IL_0005: call ""bool C.S1?.HasValue.get""
IL_000a: ret
IL_000b: ldarg.0
IL_000c: call ""C.S1 C.C1.Foo()""
IL_0011: stloc.0
IL_0012: ldarg.1
IL_0013: stloc.1
IL_0014: ldloca.s V_1
IL_0016: call ""bool C.S1?.HasValue.get""
IL_001b: brtrue.s IL_001f
IL_001d: ldc.i4.1
IL_001e: ret
IL_001f: ldloc.0
IL_0020: ldloca.s V_1
IL_0022: call ""C.S1 C.S1?.GetValueOrDefault()""
IL_0027: call ""bool C.S1.op_Inequality(C.S1, C.S1)""
IL_002c: ret
}");
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册