提交 4c84e35e 编写于 作者: A Alireza Habibi 提交者: Julien Couvreur

Optimize ternary deconstruction (#29645)

Merged on behalf of @alrz
上级 4ddd01ac
......@@ -34,14 +34,46 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct
/// </summary>
private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed)
{
var lhsTemps = ArrayBuilder<LocalSymbol>.GetInstance();
var lhsEffects = ArrayBuilder<BoundExpression>.GetInstance();
ArrayBuilder<Binder.DeconstructionVariable> lhsTargets = GetAssignmentTargetsAndSideEffects(left, lhsTemps, lhsEffects);
BoundExpression result = RewriteDeconstruction(lhsTargets, conversion, left.Type, right, isUsed);
Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets);
if (result is null)
{
lhsTemps.Free();
lhsEffects.Free();
return null;
}
return _factory.Sequence(lhsTemps.ToImmutableAndFree(), lhsEffects.ToImmutableAndFree(), result);
}
private BoundExpression RewriteDeconstruction(
ArrayBuilder<Binder.DeconstructionVariable> lhsTargets,
Conversion conversion,
TypeSymbol leftType,
BoundExpression right,
bool isUsed)
{
if (right.Kind == BoundKind.ConditionalOperator)
{
var conditional = (BoundConditionalOperator)right;
Debug.Assert(!conditional.IsRef);
return conditional.Update(
conditional.IsRef,
VisitExpression(conditional.Condition),
RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Consequence, isUsed: true),
RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Alternative, isUsed: true),
conditional.ConstantValue,
leftType);
}
var temps = ArrayBuilder<LocalSymbol>.GetInstance();
var effects = DeconstructionSideEffects.GetInstance();
ArrayBuilder<Binder.DeconstructionVariable> lhsTargets = GetAssignmentTargetsAndSideEffects(left, temps, effects.init);
BoundExpression returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, isUsed, inInit: true);
effects.Consolidate();
BoundExpression result;
if (!isUsed)
{
// When a deconstruction is not used, the last effect is used as return value
......@@ -49,15 +81,13 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers
var last = effects.PopLast();
if (last is null)
{
// Deconstructions with no effects lower to nothing. For example, `(_, _) = (1, 2);`
result = null;
temps.Free();
_ = effects.ToImmutableAndFree();
}
else
{
result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), last);
effects.Free();
// Deconstructions with no effects lower to nothing. For example, `(_, _) = (1, 2);`
return null;
}
return _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), last);
}
else
{
......@@ -65,11 +95,9 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers
{
returnValue = VisitExpression(returnValue);
}
result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnValue);
}
Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets);
return result;
return _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnValue);
}
}
/// <summary>
......@@ -85,7 +113,7 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers
bool isUsed, bool inInit)
{
Debug.Assert(conversion.Kind == ConversionKind.Deconstruction);
ImmutableArray<BoundExpression> rightParts = GetRightParts(right, conversion, ref temps, effects, ref inInit);
ImmutableArray<BoundExpression> rightParts = GetRightParts(right, conversion, temps, effects, ref inInit);
ImmutableArray<Conversion> underlyingConversions = conversion.UnderlyingConversions;
Debug.Assert(!underlyingConversions.IsDefault);
......@@ -105,7 +133,7 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers
var rightPart = rightParts[i];
if (inInit)
{
rightPart = EvaluateSideEffectingArgumentToTemp(VisitExpression(rightPart), inInit ? effects.init : effects.deconstructions, ref temps);
rightPart = EvaluateSideEffectingArgumentToTemp(VisitExpression(rightPart), effects.init, temps);
}
BoundExpression leftTarget = leftTargets[i].Single;
......@@ -137,7 +165,7 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers
}
private ImmutableArray<BoundExpression> GetRightParts(BoundExpression right, Conversion conversion,
ref ArrayBuilder<LocalSymbol> temps, DeconstructionSideEffects effects, ref bool inInit)
ArrayBuilder<LocalSymbol> temps, DeconstructionSideEffects effects, ref bool inInit)
{
// Example:
// var (x, y) = new Point(1, 2);
......@@ -147,10 +175,10 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers
Debug.Assert(!IsTupleExpression(right.Kind));
BoundExpression evaluationResult = EvaluateSideEffectingArgumentToTemp(VisitExpression(right),
inInit ? effects.init : effects.deconstructions, ref temps);
inInit ? effects.init : effects.deconstructions, temps);
inInit = false;
return InvokeDeconstructMethod(deconstructionInfo, evaluationResult, effects.deconstructions, ref temps);
return InvokeDeconstructMethod(deconstructionInfo, evaluationResult, effects.deconstructions, temps);
}
// Example:
......@@ -235,11 +263,11 @@ private static bool IsTupleExpression(BoundKind kind)
return expression;
}
var evalConversion = MakeConversionNode(expression.Syntax, expression, conversion, destinationType, @checked: false);
return EvaluateSideEffectingArgumentToTemp(evalConversion, effects, ref temps);
return EvaluateSideEffectingArgumentToTemp(evalConversion, effects, temps);
}
private ImmutableArray<BoundExpression> InvokeDeconstructMethod(DeconstructMethodInfo deconstruction, BoundExpression target,
ArrayBuilder<BoundExpression> effects, ref ArrayBuilder<LocalSymbol> temps)
ArrayBuilder<BoundExpression> effects, ArrayBuilder<LocalSymbol> temps)
{
AddPlaceholderReplacement(deconstruction.InputPlaceholder, target);
......@@ -269,18 +297,12 @@ private static bool IsTupleExpression(BoundKind kind)
}
private BoundExpression EvaluateSideEffectingArgumentToTemp(BoundExpression arg, ArrayBuilder<BoundExpression> effects,
ref ArrayBuilder<LocalSymbol> temps)
ArrayBuilder<LocalSymbol> temps)
{
if (CanChangeValueBetweenReads(arg, localsMayBeAssignedOrCaptured: true, structThisCanChangeValueBetweenReads: true))
{
BoundAssignmentOperator store;
var temp = _factory.StoreToTemp(arg, out store);
if (temps == null)
{
temps = ArrayBuilder<LocalSymbol>.GetInstance();
}
temps.Add(temp.LocalSymbol);
effects.Add(store);
return temp;
......@@ -372,6 +394,11 @@ internal ImmutableArray<BoundExpression> ToImmutableAndFree()
{
return init.ToImmutableAndFree();
}
internal void Free()
{
init.Free();
}
}
}
}
......@@ -82,14 +82,14 @@ private BoundExpression ReplaceTerminalElementsWithTemps(BoundExpression expr, T
{
// Optimization: if the nullable expression always has a value, we'll replace that value
// with a temp saving that value
BoundExpression savedValue = EvaluateSideEffectingArgumentToTemp(value, initEffects, ref temps);
BoundExpression savedValue = EvaluateSideEffectingArgumentToTemp(value, initEffects, temps);
var objectCreation = (BoundObjectCreationExpression)loweredExpr;
return objectCreation.UpdateArgumentsAndInitializer(ImmutableArray.Create(savedValue), objectCreation.ArgumentRefKindsOpt, objectCreation.InitializerExpressionOpt);
}
}
// Note: lowered nullable expressions that never have a value also don't have side-effects
return EvaluateSideEffectingArgumentToTemp(loweredExpr, initEffects, ref temps);
return EvaluateSideEffectingArgumentToTemp(loweredExpr, initEffects, temps);
}
private BoundExpression RewriteTupleOperator(TupleBinaryOperatorInfo @operator,
......
......@@ -381,6 +381,52 @@ class D2
comp.VerifyDiagnostics();
}
[Fact]
public void VerifyExecutionOrder_Deconstruct_Conditional()
{
string source = @"
using System;
class C
{
int x { set { Console.WriteLine($""setX""); } }
int y { set { Console.WriteLine($""setY""); } }
C getHolderForX() { Console.WriteLine(""getHolderforX""); return this; }
C getHolderForY() { Console.WriteLine(""getHolderforY""); return this; }
C getDeconstructReceiver() { Console.WriteLine(""getDeconstructReceiver""); return this; }
static void Main()
{
C c = new C();
bool b = true;
(c.getHolderForX().x, c.getHolderForY().y) = b ? c.getDeconstructReceiver() : default;
}
public void Deconstruct(out D1 x, out D2 y) { x = new D1(); y = new D2(); Console.WriteLine(""Deconstruct""); }
}
class D1
{
public static implicit operator int(D1 d) { Console.WriteLine(""Conversion1""); return 1; }
}
class D2
{
public static implicit operator int(D2 d) { Console.WriteLine(""Conversion2""); return 2; }
}
";
string expected =
@"getHolderforX
getHolderforY
getDeconstructReceiver
Deconstruct
Conversion1
Conversion2
setX
setY
";
var comp = CompileAndVerify(source, expectedOutput: expected);
comp.VerifyDiagnostics();
}
[Fact]
public void VerifyExecutionOrder_TupleLiteral()
{
......@@ -426,6 +472,52 @@ class D2
comp.VerifyDiagnostics();
}
[Fact]
public void VerifyExecutionOrder_TupleLiteral_Conditional()
{
string source = @"
using System;
class C
{
int x { set { Console.WriteLine($""setX""); } }
int y { set { Console.WriteLine($""setY""); } }
C getHolderForX() { Console.WriteLine(""getHolderforX""); return this; }
C getHolderForY() { Console.WriteLine(""getHolderforY""); return this; }
static void Main()
{
C c = new C();
bool b = true;
(c.getHolderForX().x, c.getHolderForY().y) = b ? (new D1(), new D2()) : default;
}
}
class D1
{
public D1() { Console.WriteLine(""Constructor1""); }
public static implicit operator int(D1 d) { Console.WriteLine(""Conversion1""); return 1; }
}
class D2
{
public D2() { Console.WriteLine(""Constructor2""); }
public static implicit operator int(D2 d) { Console.WriteLine(""Conversion2""); return 2; }
}
";
string expected =
@"getHolderforX
getHolderforY
Constructor1
Constructor2
Conversion1
Conversion2
setX
setY
";
var comp = CompileAndVerify(source, expectedOutput: expected);
comp.VerifyDiagnostics();
}
[Fact]
public void VerifyExecutionOrder_TupleLiteralAndDeconstruction()
{
......@@ -446,7 +538,7 @@ class C
static void Main()
{
C c = new C();
(c.getHolderForW().x, (c.getHolderForY().y, c.getHolderForZ().z), c.getHolderForX().x) = (new D1(), new D2(), new D3());
(c.getHolderForW().w, (c.getHolderForY().y, c.getHolderForZ().z), c.getHolderForX().x) = (new D1(), new D2(), new D3());
}
}
class D1
......@@ -477,7 +569,68 @@ class D3
Constructor3
Conversion3
deconstruct
setW
setY
setZ
setX
";
var comp = CompileAndVerify(source, expectedOutput: expected);
comp.VerifyDiagnostics();
}
[Fact]
public void VerifyExecutionOrder_TupleLiteralAndDeconstruction_Conditional()
{
string source = @"
using System;
class C
{
int w { set { Console.WriteLine($""setW""); } }
int x { set { Console.WriteLine($""setX""); } }
int y { set { Console.WriteLine($""setY""); } }
int z { set { Console.WriteLine($""setZ""); } }
C getHolderForW() { Console.WriteLine(""getHolderforW""); return this; }
C getHolderForX() { Console.WriteLine(""getHolderforX""); return this; }
C getHolderForY() { Console.WriteLine(""getHolderforY""); return this; }
C getHolderForZ() { Console.WriteLine(""getHolderforZ""); return this; }
static void Main()
{
C c = new C();
bool b = false;
(c.getHolderForW().w, (c.getHolderForY().y, c.getHolderForZ().z), c.getHolderForX().x) = b ? default : (new D1(), new D2(), new D3());
}
}
class D1
{
public D1() { Console.WriteLine(""Constructor1""); }
public static implicit operator int(D1 d) { Console.WriteLine(""Conversion1""); return 1; }
}
class D2
{
public D2() { Console.WriteLine(""Constructor2""); }
public void Deconstruct(out int x, out int y) { x = 2; y = 3; Console.WriteLine(""deconstruct""); }
}
class D3
{
public D3() { Console.WriteLine(""Constructor3""); }
public static implicit operator int(D3 d) { Console.WriteLine(""Conversion3""); return 3; }
}
";
string expected =
@"getHolderforW
getHolderforY
getHolderforZ
getHolderforX
Constructor1
Constructor2
Constructor3
deconstruct
Conversion1
Conversion3
setW
setY
setZ
setX
......@@ -2674,6 +2827,66 @@ class D3
comp.VerifyDiagnostics();
}
[Fact]
public void VerifyNestedExecutionOrder_Conditional()
{
string source = @"
using System;
class C
{
int x { set { Console.WriteLine($""setX""); } }
int y { set { Console.WriteLine($""setY""); } }
int z { set { Console.WriteLine($""setZ""); } }
C getHolderForX() { Console.WriteLine(""getHolderforX""); return this; }
C getHolderForY() { Console.WriteLine(""getHolderforY""); return this; }
C getHolderForZ() { Console.WriteLine(""getHolderforZ""); return this; }
C getDeconstructReceiver() { Console.WriteLine(""getDeconstructReceiver""); return this; }
static void Main()
{
C c = new C();
bool b = false;
(c.getHolderForX().x, (c.getHolderForY().y, c.getHolderForZ().z)) = b ? default : c.getDeconstructReceiver();
}
public void Deconstruct(out D1 x, out C1 t) { x = new D1(); t = new C1(); Console.WriteLine(""Deconstruct1""); }
}
class C1
{
public void Deconstruct(out D2 y, out D3 z) { y = new D2(); z = new D3(); Console.WriteLine(""Deconstruct2""); }
}
class D1
{
public static implicit operator int(D1 d) { Console.WriteLine(""Conversion1""); return 1; }
}
class D2
{
public static implicit operator int(D2 d) { Console.WriteLine(""Conversion2""); return 2; }
}
class D3
{
public static implicit operator int(D3 d) { Console.WriteLine(""Conversion3""); return 3; }
}
";
string expected =
@"getHolderforX
getHolderforY
getHolderforZ
getDeconstructReceiver
Deconstruct1
Deconstruct2
Conversion1
Conversion2
Conversion3
setX
setY
setZ
";
var comp = CompileAndVerify(source, expectedOutput: expected);
comp.VerifyDiagnostics();
}
[Fact]
public void VerifyNestedExecutionOrder2()
{
......@@ -8430,19 +8643,13 @@ public ValueTuple(T1 item1, T2 item2)
source,
assemblyName: "39f5d0e8-2935-4207-a74d-517a8e55af08",
parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7));
comp.VerifyEmitDiagnostics(
// (5,22): error CS8128: Member 'Item2' was not found on type 'ValueTuple<T1, T2>' from assembly '39f5d0e8-2935-4207-a74d-517a8e55af08, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
// var (x, y) = c ? ((object)1, a) : (b, 2);
Diagnostic(ErrorCode.ERR_PredefinedTypeMemberNotFoundInAssembly, "c ? ((object)1, a) : (b, 2)").WithArguments("Item2", "System.ValueTuple<T1, T2>", "39f5d0e8-2935-4207-a74d-517a8e55af08, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(5, 22));
comp.VerifyEmitDiagnostics();
// C# 7.1
comp = CreateCompilation(
source,
assemblyName: "39f5d0e8-2935-4207-a74d-517a8e55af08",
parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1));
comp.VerifyEmitDiagnostics(
// (5,22): error CS8128: Member 'Item2' was not found on type 'ValueTuple<T1, T2>' from assembly '39f5d0e8-2935-4207-a74d-517a8e55af08, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
// var (x, y) = c ? ((object)1, a) : (b, 2);
Diagnostic(ErrorCode.ERR_PredefinedTypeMemberNotFoundInAssembly, "c ? ((object)1, a) : (b, 2)").WithArguments("Item2", "System.ValueTuple<T1, T2>", "39f5d0e8-2935-4207-a74d-517a8e55af08, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(5, 22));
comp.VerifyEmitDiagnostics();
}
[Fact]
......@@ -8514,5 +8721,358 @@ public void M()
var comp = CreateCompilation(source, options: TestOptions.DebugExe);
CompileAndVerify(comp, expectedOutput: "44 42 43");
}
[Fact]
public void AssigningConditional_OutParams()
{
string source = @"
using System;
class C
{
static void Main()
{
Test(true, false);
Test(false, true);
Test(false, false);
}
static void Test(bool b1, bool b2)
{
M(out int x, out int y, b1, b2);
Console.Write(x);
Console.Write(y);
}
static void M(out int x, out int y, bool b1, bool b2)
{
(x, y) = b1 ? (10, 20) : b2 ? (30, 40) : (50, 60);
}
}
";
var comp = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: "102030405060");
comp.VerifyDiagnostics();
comp.VerifyIL("C.M", @"
{
// Code size 33 (0x21)
.maxstack 2
IL_0000: ldarg.2
IL_0001: brtrue.s IL_0018
IL_0003: ldarg.3
IL_0004: brtrue.s IL_000f
IL_0006: ldarg.0
IL_0007: ldc.i4.s 50
IL_0009: stind.i4
IL_000a: ldarg.1
IL_000b: ldc.i4.s 60
IL_000d: stind.i4
IL_000e: ret
IL_000f: ldarg.0
IL_0010: ldc.i4.s 30
IL_0012: stind.i4
IL_0013: ldarg.1
IL_0014: ldc.i4.s 40
IL_0016: stind.i4
IL_0017: ret
IL_0018: ldarg.0
IL_0019: ldc.i4.s 10
IL_001b: stind.i4
IL_001c: ldarg.1
IL_001d: ldc.i4.s 20
IL_001f: stind.i4
IL_0020: ret
}");
}
[Fact]
public void AssigningConditional_VarDeconstruction()
{
string source = @"
using System;
class C
{
static void Main()
{
M(true, false);
M(false, true);
M(false, false);
}
static void M(bool b1, bool b2)
{
var (x, y) = b1 ? (10, 20) : b2 ? (30, 40) : (50, 60);
Console.Write(x);
Console.Write(y);
}
}
";
var comp = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: "102030405060");
comp.VerifyDiagnostics();
comp.VerifyIL("C.M", @"
{
// Code size 41 (0x29)
.maxstack 1
.locals init (int V_0, //x
int V_1) //y
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0016
IL_0003: ldarg.1
IL_0004: brtrue.s IL_000e
IL_0006: ldc.i4.s 50
IL_0008: stloc.0
IL_0009: ldc.i4.s 60
IL_000b: stloc.1
IL_000c: br.s IL_001c
IL_000e: ldc.i4.s 30
IL_0010: stloc.0
IL_0011: ldc.i4.s 40
IL_0013: stloc.1
IL_0014: br.s IL_001c
IL_0016: ldc.i4.s 10
IL_0018: stloc.0
IL_0019: ldc.i4.s 20
IL_001b: stloc.1
IL_001c: ldloc.0
IL_001d: call ""void System.Console.Write(int)""
IL_0022: ldloc.1
IL_0023: call ""void System.Console.Write(int)""
IL_0028: ret
}");
}
[Fact]
public void AssigningConditional_MixedDeconstruction()
{
string source = @"
using System;
class C
{
static void Main()
{
M(true, false);
M(false, true);
M(false, false);
}
static void M(bool b1, bool b2)
{
(var x, long y) = b1 ? (10, 20) : b2 ? (30, 40) : (50, 60);
Console.Write(x);
Console.Write(y);
}
}
";
var comp = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: "102030405060");
comp.VerifyDiagnostics();
comp.VerifyIL("C.M", @"
{
// Code size 50 (0x32)
.maxstack 1
.locals init (int V_0, //x
long V_1, //y
long V_2)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_001c
IL_0003: ldarg.1
IL_0004: brtrue.s IL_0011
IL_0006: ldc.i4.s 60
IL_0008: conv.i8
IL_0009: stloc.2
IL_000a: ldc.i4.s 50
IL_000c: stloc.0
IL_000d: ldloc.2
IL_000e: stloc.1
IL_000f: br.s IL_0025
IL_0011: ldc.i4.s 40
IL_0013: conv.i8
IL_0014: stloc.2
IL_0015: ldc.i4.s 30
IL_0017: stloc.0
IL_0018: ldloc.2
IL_0019: stloc.1
IL_001a: br.s IL_0025
IL_001c: ldc.i4.s 20
IL_001e: conv.i8
IL_001f: stloc.2
IL_0020: ldc.i4.s 10
IL_0022: stloc.0
IL_0023: ldloc.2
IL_0024: stloc.1
IL_0025: ldloc.0
IL_0026: call ""void System.Console.Write(int)""
IL_002b: ldloc.1
IL_002c: call ""void System.Console.Write(long)""
IL_0031: ret
}");
}
[Fact]
public void AssigningConditional_SideEffects()
{
string source = @"
using System;
class C
{
static void Main()
{
M(true, false);
M(false, true);
M(false, false);
SideEffect(true);
SideEffect(false);
}
static int left;
static int right;
static ref int SideEffect(bool isLeft)
{
Console.WriteLine($""{(isLeft ? ""left"" : ""right"")}: {(isLeft ? left : right)}"");
return ref isLeft ? ref left : ref right;
}
static void M(bool b1, bool b2)
{
(SideEffect(isLeft: true), SideEffect(isLeft: false)) = b1 ? (10, 20) : b2 ? (30, 40) : (50, 60);
}
}
";
var expected =
@"left: 0
right: 0
left: 10
right: 20
left: 30
right: 40
left: 50
right: 60";
var comp = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: expected);
comp.VerifyDiagnostics();
comp.VerifyIL("C.M", @"
{
// Code size 47 (0x2f)
.maxstack 2
.locals init (int& V_0,
int& V_1)
IL_0000: ldc.i4.1
IL_0001: call ""ref int C.SideEffect(bool)""
IL_0006: stloc.0
IL_0007: ldc.i4.0
IL_0008: call ""ref int C.SideEffect(bool)""
IL_000d: stloc.1
IL_000e: ldarg.0
IL_000f: brtrue.s IL_0026
IL_0011: ldarg.1
IL_0012: brtrue.s IL_001d
IL_0014: ldloc.0
IL_0015: ldc.i4.s 50
IL_0017: stind.i4
IL_0018: ldloc.1
IL_0019: ldc.i4.s 60
IL_001b: stind.i4
IL_001c: ret
IL_001d: ldloc.0
IL_001e: ldc.i4.s 30
IL_0020: stind.i4
IL_0021: ldloc.1
IL_0022: ldc.i4.s 40
IL_0024: stind.i4
IL_0025: ret
IL_0026: ldloc.0
IL_0027: ldc.i4.s 10
IL_0029: stind.i4
IL_002a: ldloc.1
IL_002b: ldc.i4.s 20
IL_002d: stind.i4
IL_002e: ret
}");
}
[Fact]
public void AssigningConditional_SideEffects_RHS()
{
string source = @"
using System;
class C
{
static void Main()
{
M(true, false);
M(false, true);
M(false, false);
}
static T Echo<T>(T v, int i)
{
Console.WriteLine(i + "": "" + v);
return v;
}
static void M(bool b1, bool b2)
{
var (x, y) = Echo(b1, 1) ? Echo((10, 20), 2) : Echo(b2, 3) ? Echo((30, 40), 4) : Echo((50, 60), 5);
Console.WriteLine(""x: "" + x);
Console.WriteLine(""y: "" + y);
Console.WriteLine();
}
}
";
var expectedOutput =
@"1: True
2: (10, 20)
x: 10
y: 20
1: False
3: True
4: (30, 40)
x: 30
y: 40
1: False
3: False
5: (50, 60)
x: 50
y: 60
";
var comp = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: expectedOutput);
comp.VerifyDiagnostics();
}
[Fact]
public void AssigningConditional_UnusedDeconstruction()
{
string source = @"
class C
{
static void M(bool b1, bool b2)
{
(_, _) = b1 ? (10, 20) : b2 ? (30, 40) : (50, 60);
}
}
";
var comp = CompileAndVerify(source);
comp.VerifyDiagnostics();
comp.VerifyIL("C.M", @"
{
// Code size 6 (0x6)
.maxstack 1
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0005
IL_0003: ldarg.1
IL_0004: pop
IL_0005: ret
}");
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册