using UnitTestSharp; namespace Ariadne.Generator.UnitTests { public class PassageWriterTests : TestFixture { public class WriteTests : TestFixture { public void YieldLoopWithoutALabel_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Unlabeled : Arc { protected override void Run(World world) { while (!world.Open.Contains(""wait"")) { Yield(); } } }"); CheckEqual(new[] { "ARI0002:while" }, run.Reported); } public void YieldWithoutALabel_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Stray : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { Yield(); } } }"); CheckEqual(new[] { "ARI0002:Yield()" }, run.Reported); } public void YieldInAForeach_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Looping : Arc { protected override void Run(World world) { wait: foreach (string gate in world.Open) { Yield(); } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); } public void YieldInASwitchSection_IsFlattenedIntoADispatcher() { GeneratorRun run = GeneratorRun.Of(@" public partial class Switching : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 0: Yield(); break; default: world.Open.Add(""other""); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("switch (world.Open.Count)")); CheckTrue(run.CodeBehind.Contains("case 0: goto __switch1_0;")); CheckTrue(run.CodeBehind.Contains("default: goto __switch1_1;")); CheckTrue(run.CodeBehind.Contains("__switchEnd1:")); CheckFalse(run.CodeBehind.Contains("goto __switchEnd1;\r\n#line hidden\r\n __switch1_0:")); } public void YieldInAnIfInsideALoop_IsFlattenedToo() { GeneratorRun run = GeneratorRun.Of(@" public partial class Looping : Arc { protected override void Run(World world) { wait: while (!world.Open.Contains(""wait"")) { if (world.Open.Count == 0) { Yield(); } Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"wait#1\": goto wait__1__resume;")); } public void YieldNotLastInItsLoop_IsFlattenedIntoJumps() { GeneratorRun run = GeneratorRun.Of(@" public partial class Early : Arc { protected override void Run(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); world.Open.Add(""waited""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__loopStart1:")); CheckTrue(run.CodeBehind.Contains("if (!(!world.Open.Contains(\"wait\"))) goto __loopEnd1;")); CheckTrue(run.CodeBehind.Contains("goto __loopStart1;")); CheckTrue(run.CodeBehind.Contains("__loopEnd1:")); } public void YieldLoopInsideAnotherLoop_KeepsBothPauses() { GeneratorRun run = GeneratorRun.Of(@" public partial class Nested : Arc { protected override void Run(World world) { wait: while (!world.Open.Contains(""outer"")) { while (!world.Open.Contains(""inner"")) { Yield(); } Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"wait\": goto wait;")); CheckTrue(run.CodeBehind.Contains("case \"wait#1\": goto wait__1__resume;")); } public void PassageCallInAForLoop_IsFlattenedAndKeyedByItsName() { GeneratorRun run = GeneratorRun.Of(@" public partial class Buried : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { for (int i = 0; i < 2; ++i) { Act(world); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"Act\": goto Act;")); } public void PassageCallInAForeach_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Buried : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { foreach (string gate in world.Open) { Act(world); } } }"); CheckEqual(new[] { "ARI0004:Act(world)" }, run.Reported); } public void PassageCalledTwiceWithoutLabels_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Twice : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { Act(world); Act(world); } }"); CheckEqual(new[] { "ARI0005:Act(world)" }, run.Reported); } public void LoopsNotEndingInYield_AreCopiedAsIs() { GeneratorRun run = GeneratorRun.Of(@" public partial class Counting : Arc { protected override void Run(World world) { int count = 0; while (count < 3) { count++; } while (world.Open.Count < count) { world.Open.Add(count.ToString()); } wait: while (!world.Open.Contains(""wait"")) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains( "while (count < 3)\r\n {\r\n count++;\r\n }")); } public void SecondLoopInAStep_IsKeyedWithItsCount() { GeneratorRun run = GeneratorRun.Of(@" public partial class TwoLoops : Arc { protected override void Run(World world) { connect: while (!world.Open.Contains(""breaker"")) { Yield(); } while (!world.Open.Contains(""load"")) { Yield(); } } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"connect#1\": goto connect__1;")); } public void LambdaReadingASavedLocal_ReadsItsField() { GeneratorRun run = GeneratorRun.Of(@" public partial class Captured : Arc { protected override void Run(World world) { string wanted = ""go""; wait: while (!world.Open.Any(gate => gate == wanted)) { Yield(); } } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("world.Open.Any(gate => gate == __frame.Run__wanted)")); } public void NamedArgumentForASavedParameter_NamesTheRenamedParameter() { GeneratorRun run = GeneratorRun.Of(@" public partial class Named : Arc { private void Count(World world, int count) { wait: while (count > 0) { count--; Yield(); } } protected override void Run(World world) { Count(world, count: 3); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("count__argument: __frame.ResumeKey == null ? (3) : default(int)")); } public void BareReturnInsideAFunction_StaysBare() { GeneratorRun run = GeneratorRun.Of(@" public partial class Functions : Arc { protected override void Run(World world) { System.Action lambda = () => { return; }; System.Action simpleLambda = count => { return; }; System.Action anonymous = delegate { return; }; void Local() { return; } lambda(); simpleLambda(1); anonymous(); Local(); wait: while (!world.Open.Contains(""wait"")) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void LoopKeyedLikeAPassageCall_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Clash : Arc { private void Act(World world) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } protected override void Run(World world) { Act(world); Act: while (!world.Open.Contains(""again"")) { Yield(); } } }"); CheckEqual(new[] { "ARI0005:while" }, run.Reported); } public void YieldStatement_MarksItsKeyAndResumesAfterIt() { GeneratorRun run = GeneratorRun.Of(@" public partial class Pausing : Arc { protected override void Run(World world) { world.Open.Add(""before""); wait: Yield(); world.Open.Add(""after""); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"wait\": goto wait__resume;")); CheckTrue(run.CodeBehind.Contains("__frame.Mark(\"wait\"); return false;")); CheckTrue(run.CodeBehind.Contains("wait__resume:")); } public void YieldStatement_CanBeTheTargetOfAGoto() { GeneratorRun run = GeneratorRun.Of(@" public partial class Pausing : Arc { protected override void Run(World world) { wait: Yield(); if (world.Open.Count == 0) { goto wait; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("wait:\r\n")); CheckTrue(run.CodeBehind.IndexOf("wait:\r\n") < run.CodeBehind.IndexOf("wait__resume:\r\n")); } public void SecondYieldInAStep_IsKeyedWithItsCount() { GeneratorRun run = GeneratorRun.Of(@" public partial class Pausing : Arc { protected override void Run(World world) { connect: Yield(); Yield(); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"connect#1\": goto connect__1__resume;")); } public void IfHoldingAPause_BecomesJumpsWithNoResumeTest() { GeneratorRun run = GeneratorRun.Of(@" public partial class Branching : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { wait: Yield(); world.Open.Add(""after""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("if (!(world.Open.Count == 0)) goto __end1;")); CheckTrue(run.CodeBehind.Contains("__end1:")); CheckFalse(run.CodeBehind.Contains("__frame.ResumeKey ==")); } public void IfWithAPauseOnlyInItsElse_JumpsOverTheThenBranch() { GeneratorRun run = GeneratorRun.Of(@" public partial class Branching : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { world.Open.Add(""then""); } else { wait: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("if (!(world.Open.Count == 0)) goto __else1;")); CheckTrue(run.CodeBehind.Contains("goto __end1;")); CheckTrue(run.CodeBehind.Contains("__else1:")); } public void ElseIfChain_GetsATestAndLabelsPerCondition() { GeneratorRun run = GeneratorRun.Of(@" public partial class Branching : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { first: Yield(); } else if (world.Open.Count == 1) { second: Yield(); } else { third: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("goto __else1;")); CheckTrue(run.CodeBehind.Contains("goto __else2;")); CheckTrue(run.CodeBehind.Contains("__end2:")); } public void IfWithoutAPause_IsCopiedAsIs() { GeneratorRun run = GeneratorRun.Of(@" public partial class Branching : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { world.Open.Add(""then""); } wait: Yield(); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("if (world.Open.Count == 0)")); CheckFalse(run.CodeBehind.Contains("__end")); } public void PlainBlockHoldingAPause_IsInlined() { GeneratorRun run = GeneratorRun.Of(@" public partial class Blocked : Arc { protected override void Run(World world) { { wait: Yield(); world.Open.Add(""after""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("wait__resume:")); } public void SiblingBranchesDeclaringTheSameName_CompileInTheirOwnBraces() { GeneratorRun run = GeneratorRun.Of(@" public partial class Branching : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { int count = world.Open.Count; world.Open.Add(count.ToString()); left: Yield(); } else { int count = world.Open.Count + 1; world.Open.Add(count.ToString()); right: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void LocalUsedOnBothSidesOfAPause_IsKeptInAFieldButNotSaved() { GeneratorRun run = GeneratorRun.Of(@" public partial class Across : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { int doubled; wait: Yield(); doubled = world.Open.Count * 2; world.Open.Add(doubled.ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__doubled;")); CheckFalse(run.CodeBehind.Contains("SaveLocal")); CheckTrue(run.CodeBehind.Contains("ref int doubled = ref __frame.Run__doubled;")); } public void ConstantUsedOnBothSidesOfAPause_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Across : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { const int Limit = 3; world.Open.Add(Limit.ToString()); wait: Yield(); world.Open.Add(Limit.ToString()); } } }"); CheckEqual(new[] { "ARI0009:Limit" }, run.Reported); } public void SavedLocalsWithTheSameName_AreNumberedInSourceOrder() { GeneratorRun run = GeneratorRun.Of(@" public partial class Branching : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { int total = 1; left: Yield(); world.Open.Add(total.ToString()); } else { int total = 2; right: Yield(); world.Open.Add(total.ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__total;")); CheckTrue(run.CodeBehind.Contains("public int Run__total__1;")); CheckTrue(run.CodeBehind.Contains("case \"Run.total#1\":")); } public void PatternInAnIfHoldingAPause_IsAssignedThroughATemporary() { GeneratorRun run = GeneratorRun.Of(@" public partial class Parsed : Arc { protected override void Run(World world) { object thing = world; if (thing is World found) { wait: Yield(); found.Open.Add(""seen""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public global::World Run__found;")); CheckTrue(run.CodeBehind.Contains("(thing is World found__pattern && ((found = found__pattern) is var _))")); } public void PassageCallInAnIf_IsKeyedByItsName() { GeneratorRun run = GeneratorRun.Of(@" public partial class Calling : Arc { private void Act(World world) { wait: Yield(); } protected override void Run(World world) { if (world.Open.Count == 0) { Act(world); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"Act\": goto Act;")); } public void YieldLoopInAnIf_IsPausedAtLikeATopLevelOne() { GeneratorRun run = GeneratorRun.Of(@" public partial class Looping : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { wait: while (!world.Open.Contains(""wait"")) { Yield(); } } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"wait\": goto wait;")); } public void EmbeddedYieldInAnIf_IsPausedAtAndKeepsItsLocals() { GeneratorRun run = GeneratorRun.Of(@" public partial class Embedded : Arc { protected override void Run(World world) { int count = 3; wait: if (world.Open.Count == 0) Yield(); world.Open.Add(count.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__count;")); CheckTrue(run.CodeBehind.Contains("case \"wait\": goto wait__resume;")); } public void EmbeddedYieldInAnElse_IsPausedAtAndKeepsItsLocals() { GeneratorRun run = GeneratorRun.Of(@" public partial class Embedded : Arc { protected override void Run(World world) { int count = 3; wait: if (world.Open.Count == 0) world.Open.Add(""then""); else Yield(); world.Open.Add(count.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__count;")); } public void LabeledIf_NamesTheStepOfThePausesInside() { GeneratorRun run = GeneratorRun.Of(@" public partial class Labeled : Arc { protected override void Run(World world) { gate: if (world.Open.Count == 0) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"gate\": goto gate__resume;")); } public void ReturnInsideAFlattenedBranch_EndsThePassage() { GeneratorRun run = GeneratorRun.Of(@" public partial class Returning : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { wait: Yield(); return; } world.Open.Add(""after""); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("{ __frame.Exit(); return true; }\r\n#line hidden\r\n }")); } public void LambdaReadingAHoistedLocal_ReadsItsField() { GeneratorRun run = GeneratorRun.Of(@" public partial class Captured : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { int doubled; wait: Yield(); doubled = world.Open.Count * 2; System.Func read = () => doubled; world.Open.Add(read().ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("() => __frame.Run__doubled")); } public void PassageParameterAndPauseInAnIf_CompileTogether() { GeneratorRun run = GeneratorRun.Of(@" public partial class Parameterized : Arc { private void Count(World world, int count) { if (count > 0) { wait: Yield(); world.Open.Add(count.ToString()); } } protected override void Run(World world) { Count(world, count: 3); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("count__argument: __frame.ResumeKey == null ? (3) : default(int)")); } public void YieldKeyedLikeAPassageCall_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Clash : Arc { private void Act(World world) { wait: Yield(); } protected override void Run(World world) { Act(world); Act: Yield(); } }"); CheckEqual(new[] { "ARI0005:Yield()" }, run.Reported); } public void ForLoop_RunsItsInitializerOnceAndItsStepBeforeEachTest() { GeneratorRun run = GeneratorRun.Of(@" public partial class Counting : Arc { protected override void Run(World world) { wait: for (int i = 0; i < 3; i++) { Yield(); world.Open.Add(i.ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("i = 0;")); CheckTrue(run.CodeBehind.Contains("if (!(i < 3)) goto __loopEnd1;")); CheckTrue(run.CodeBehind.Contains("__loopCont1:")); CheckTrue(run.CodeBehind.Contains("i++;")); CheckTrue(run.CodeBehind.Contains("public int Run__i;")); } public void ForLoopWithoutAConditionOrStep_StillFlattens() { GeneratorRun run = GeneratorRun.Of(@" public partial class Endless : Arc { protected override void Run(World world) { wait: for (;;) { Yield(); if (world.Open.Contains(""stop"")) { break; } } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("goto __loopEnd1;")); } public void DoLoop_TestsItsConditionAfterTheBody() { GeneratorRun run = GeneratorRun.Of(@" public partial class Doing : Arc { protected override void Run(World world) { wait: do { Yield(); world.Open.Add(""again""); } while (world.Open.Count < 3); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("if (world.Open.Count < 3) goto __loopStart1;")); CheckTrue(run.CodeBehind.Contains("__loopCont1:")); } public void BreakAndContinue_BecomeJumpsOutOfAndBackToTheLoop() { GeneratorRun run = GeneratorRun.Of(@" public partial class Jumping : Arc { protected override void Run(World world) { wait: while (true) { if (world.Open.Contains(""skip"")) { continue; } if (world.Open.Contains(""stop"")) { break; } Yield(); world.Open.Add(""again""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("goto __loopStart1;\r\n")); CheckTrue(run.CodeBehind.Contains("goto __loopEnd1;\r\n")); CheckFalse(run.CodeBehind.Contains("continue;")); CheckFalse(run.CodeBehind.Contains("break;")); } public void ContinueInsideASwitch_JumpsButItsBreakStays() { GeneratorRun run = GeneratorRun.Of(@" public partial class Switching : Arc { protected override void Run(World world) { wait: while (!world.Open.Contains(""done"")) { switch (world.Open.Count) { case 0: world.Open.Add(""zero""); break; case 1: continue; } Yield(); world.Open.Add(""again""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("break;")); CheckTrue(run.CodeBehind.Contains("goto __loopStart1;")); CheckFalse(run.CodeBehind.Contains("continue;")); } public void BreakAndContinueInInnerLoops_BelongToThoseLoops() { GeneratorRun run = GeneratorRun.Of(@" public partial class Inner : Arc { protected override void Run(World world) { wait: while (world.Open.Count < 9) { while (true) { break; } do { continue; } while (world.Open.Count > 100); for (;;) { break; } foreach (string gate in world.Open) { continue; } Yield(); world.Open.Add(""again""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckEqual(2, run.CodeBehind.Split(new[] { "break;" }, System.StringSplitOptions.None).Length - 1); CheckEqual(2, run.CodeBehind.Split(new[] { "continue;" }, System.StringSplitOptions.None).Length - 1); } public void ForLoopWithAnExpressionInitializer_RunsItBeforeTheLoop() { GeneratorRun run = GeneratorRun.Of(@" public partial class Counting : Arc { protected override void Run(World world) { int i; wait: for (i = 0; i < 3; i++) { Yield(); world.Open.Add(i.ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("i = 0;")); CheckTrue(run.CodeBehind.Contains("public int Run__i;")); } public void ForLoopWhoseVariableOnlyAppearsInItsInitializer_KeepsItsDeclaration() { GeneratorRun run = GeneratorRun.Of(@" public partial class Counting : Arc { protected override void Run(World world) { wait: for (int unused = world.Open.Count; world.Open.Count < 3;) { Yield(); world.Open.Add(""again""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("int unused = world.Open.Count;")); CheckFalse(run.CodeBehind.Contains("Run__unused")); } public void LoopBodyThatIsOneStatement_PausesAndKeepsItsLocals() { GeneratorRun run = GeneratorRun.Of(@" public partial class Embedded : Arc { protected override void Run(World world) { int count = 3; wait: for (int i = 0; i < 2; i++) Yield(); world.Open.Add(count.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__count;")); CheckTrue(run.CodeBehind.Contains("public int Run__i;")); } public void PauseInAnInnerLoop_FlattensBothLoops() { GeneratorRun run = GeneratorRun.Of(@" public partial class Nesting : Arc { protected override void Run(World world) { wait: while (world.Open.Count < 5) { while (world.Open.Count < 3) { Yield(); world.Open.Add(""inner""); } world.Open.Add(""outer""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__loopStart1:")); CheckTrue(run.CodeBehind.Contains("__loopStart2:")); } public void LoopWithoutAPause_IsCopiedAsIs() { GeneratorRun run = GeneratorRun.Of(@" public partial class Counting : Arc { protected override void Run(World world) { for (int i = 0; i < 3; ++i) { world.Open.Add(i.ToString()); } wait: Yield(); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("for (int i = 0; i < 3; ++i)")); CheckFalse(run.CodeBehind.Contains("__loopStart")); } public void LocalDeclaredInALoopBody_IsKeptInAFieldWhenUsedAfterThePause() { GeneratorRun run = GeneratorRun.Of(@" public partial class Counting : Arc { protected override void Run(World world) { wait: while (world.Open.Count < 3) { int seen = world.Open.Count; Yield(); world.Open.Add(seen.ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__seen;")); } public void OutVariableInALoopCondition_IsKeptForTheBody() { GeneratorRun run = GeneratorRun.Of(@" public partial class Parsed : Arc { protected override void Run(World world) { wait: while (int.TryParse(""3"", out int count)) { Yield(); world.Open.Add(count.ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__count;")); CheckTrue(run.CodeBehind.Contains("out count")); CheckFalse(run.CodeBehind.Contains("out int count")); } public void YieldInALambda_NamesTheLambda() { GeneratorRun run = GeneratorRun.Of(@" public partial class Lambdas : Arc { protected override void Run(World world) { System.Action later = () => { Yield(); }; later(); } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("inside a lambda or local function. It runs as a separate method")); } public void YieldInALocalFunction_NamesTheLocalFunction() { GeneratorRun run = GeneratorRun.Of(@" public partial class Locals : Arc { protected override void Run(World world) { void Later() { Yield(); } Later(); } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("a lambda or local function")); } public void YieldInAUsingStatement_SaysTheResourceWouldBeDisposed() { GeneratorRun run = GeneratorRun.Of(@" public partial class Disposing : Arc { protected override void Run(World world) { using (var stream = new System.IO.MemoryStream()) { Yield(); } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("inside a using statement. A pause would dispose the resource")); } public void YieldInALock_SaysTheLockWouldBeReleased() { GeneratorRun run = GeneratorRun.Of(@" public partial class Locking : Arc { protected override void Run(World world) { lock (world) { Yield(); } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("inside a lock statement. A pause would release the lock")); } public void YieldInATry_SaysItWouldRunTheFinally() { GeneratorRun run = GeneratorRun.Of(@" public partial class Trying : Arc { protected override void Run(World world) { try { Yield(); } finally { world.Open.Add(""done""); } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("inside a try statement. A pause would run its finally block")); } public void YieldInACatch_IsStillInsideTheTry() { GeneratorRun run = GeneratorRun.Of(@" public partial class Catching : Arc { protected override void Run(World world) { try { world.Open.Add(""work""); } catch (System.Exception) { Yield(); } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("a try statement")); } public void YieldInAFixedInsideAnUnsafeBlock_NamesTheFixed() { GeneratorRun run = GeneratorRun.Of(@" public partial class Pinning : Arc { protected override void Run(World world) { unsafe { int value = 0; fixed (int* pointer = &value) { Yield(); } } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("inside a fixed statement. The pin would end")); } public void YieldInCheckedUncheckedAndUnsafeBlocks_NamesThemAll() { GeneratorRun run = GeneratorRun.Of(@" public partial class Contexts : Arc { protected override void Run(World world) { checked { Yield(); } unchecked { Yield(); } unsafe { Yield(); } } }"); CheckEqual(3, run.Reported.Count); foreach (string message in run.Messages) { CheckTrue(message.Contains("an unsafe, checked or unchecked block")); } } public void YieldInAForeach_SuggestsAnIndexLoop() { GeneratorRun run = GeneratorRun.Of(@" public partial class Looping : Arc { protected override void Run(World world) { foreach (string gate in world.Open) { Yield(); } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("inside a foreach loop.")); CheckTrue(run.Messages[0].Contains("Write an index for loop instead.")); } public void YieldInAnIfInAForeach_NamesTheForeach() { GeneratorRun run = GeneratorRun.Of(@" public partial class Looping : Arc { protected override void Run(World world) { foreach (string gate in world.Open) { if (gate.Length > 0) { while (world.Open.Count > 5) { Yield(); } } } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("a foreach loop")); } public void InnermostConstructIsNamed_WhenSeveralSurroundThePause() { GeneratorRun run = GeneratorRun.Of(@" public partial class Nested : Arc { protected override void Run(World world) { foreach (string gate in world.Open) { lock (world) { Yield(); } } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("a lock statement")); } public void PassageCallInATry_NamesThePassageAndTheTry() { GeneratorRun run = GeneratorRun.Of(@" public partial class Calling : Arc { private void Act(World world) { wait: Yield(); } protected override void Run(World world) { try { Act(world); } catch (System.Exception) { } } }"); CheckEqual(new[] { "ARI0004:Act(world)" }, run.Reported); CheckTrue(run.Messages[0].Contains("'Act' pauses, so it can't be called inside a try statement.")); } public void IfCondition_PausesThenTestsTheSpilledValue() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { if (Ask(world) > 2) { again: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"using (__frame.Enter(""Ask""))")); CheckTrue(run.CodeBehind.Contains(@"if (!(__frame.Run____spill1 > 2)) goto __end1;")); } public void WhileCondition_PausesEveryTrip() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { while (Ask(world) > 2) { again: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__loopStart1:")); CheckTrue(run.CodeBehind.Contains(@"if (!(__frame.Run____spill1 > 2)) goto __loopEnd1;")); } public void DoCondition_PausesAfterTheBody() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { do { again: Yield(); } while (Ask(world) > 2); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__loopCont1:")); CheckTrue(run.CodeBehind.Contains(@"if (__frame.Run____spill1 > 2) goto __loopStart1;")); } public void ForInitializerDeclaration_PausesBeforeTheLoop() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { for (int i = Ask(world); i < 5; i++) { again: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__loopStart1:")); } public void ForCondition_PausesEveryTrip() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { for (int i = 0; i < Ask(world); i++) { again: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__loopStart1:")); } public void ForStep_PausesBeforeTheNextTrip() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { for (int i = 0; i < 5; System.Math.Max(Ask(world), 0)) { again: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__loopCont1:")); } public void CompoundAssignmentInAForStep_AsksAfterEachTrip() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { for (int i = 0; i < 5; i += Ask(world)) { again: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void ForStepThatIsOnlyACall_RunsItOnce() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { for (int i = 0; i < 5; Ask(world)) { again: Yield(); ++i; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__loopCont1:")); } public void SwitchGoverningExpression_PausesBeforeTheDispatch() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { switch (Ask(world)) { case 1: first: Yield(); break; default: break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"switch")); } public void LabeledWhileWithConditionalCalls_KeysEachCallUnderTheLabel() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { again: while (world.Open.Contains(""flag"") && Ask(world) > 2 && Ask(world) < 9) { world.Open.Add(""trip""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"case ""again"": goto again;")); CheckTrue(run.CodeBehind.Contains(@"case ""again#1"": goto again__1;")); } public void IfWithAnElseAndACondition_SavesWhatOnlyTheElseReads() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int before = 5; if (Ask(world) > 2) { world.Open.Add(""big""); } else { world.Open.Add(""small"" + before); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"""Run.before""")); } public void SwitchGuardsAndACall_SaveWhatOnlyAGuardReads() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int limit = 3; switch (Ask(world)) { case int count when count > limit: world.Open.Add(""many""); break; default: break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"""Run.limit""")); } public void PassageCallInSeveralForInitializers_NamesTheInitializer() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int a; int b; for (a = Ask(world), b = 1; a < b; a++) { world.Open.Add(""x""); } } }"); CheckEqual(new[] { "ARI0004:Ask(world)" }, run.Reported); CheckTrue(run.Messages[0].Contains(@"inside a for loop's initializer.")); } public void PassageCallInSeveralForDeclarators_NamesTheInitializer() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { for (int a = Ask(world), b = 1; a < b; a++) { world.Open.Add(""x""); } } }"); CheckEqual(new[] { "ARI0004:Ask(world)" }, run.Reported); CheckTrue(run.Messages[0].Contains(@"inside a for loop's initializer.")); } public void SeveralDeclarators_SpillEachInitializerInOrder() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Count; private int Next() { return ++Count; } private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { both: int first = Next(), second = Ask(world), third = Ask(world) + first; world.Open.Add(first + "" "" + second + "" "" + third); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1 = Next();")); CheckTrue(run.CodeBehind.Contains(@"int first = __frame.Run____spill1; second = __frame.Run____spill2;")); } public void SeveralDeclaratorsWithoutInitializers_StillSpillTheOnesThatHaveThem() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int unset, answer = Ask(world); unset = answer; world.Open.Add(unset.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void WhileWhoseOnlyPauseIsItsConditionAndAYield_FlattensTheLoop() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { again: while (Ask(world) > 2) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__loopStart1:")); } public void ConditionalOperatorInAnIfCondition_JumpsAroundTheCall() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { if ((world.Open.Contains(""x"") ? Ask(world) : 0) > 1) { world.Open.Add(""big""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__cond1End:")); } public void UsingDeclarationWithSeveralDeclarators_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { using var first = new System.IO.StringReader(""""), second = new System.IO.StringReader(Ask(world).ToString()); } }"); CheckTrue(run.Reported.Count > 0); } public void PatternDeclaringAVariable_ReadsTheCallFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private object Pick(World world) { pick: Yield(); return null; } protected override void Run(World world) { if (Ask(world) is int n) { show: Yield(); world.Open.Add(n.ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1 is int n__pattern")); } public void IsTypeAndAsOnACall_ReadTheCallFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private object Pick(World world) { pick: Yield(); return null; } protected override void Run(World world) { first: bool text = Pick(world) is string; second: string again = Pick(world) as string; world.Open.Add(text + again); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void RelationalPatternOnACall_IsTakenApart() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private object Pick(World world) { pick: Yield(); return null; } protected override void Run(World world) { bool big = Ask(world) is > 2 and < 9; world.Open.Add(big.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1 is > 2 and < 9")); } public void SwitchExpressionOnACall_ReadsTheCallFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private object Pick(World world) { pick: Yield(); return null; } protected override void Run(World world) { string size = Ask(world) switch { > 2 => ""big"", _ => ""small"" }; world.Open.Add(size); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1 switch")); } public void CallInASwitchExpressionArm_JumpsPastTheArmsNotTaken() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Scale(World world, int factor) { scale: Yield(); return factor; } private int Foo() { return 1; } protected override void Run(World world) { int count = world.Open.Count; int value = count switch { 1 => Ask(world), 2 => 5, _ => Foo() }; world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"if (!((count) is 1)) goto __cond1Arm0;")); CheckTrue(run.CodeBehind.Contains(@"if (!((count) is 2)) goto __cond1Arm1;")); CheckTrue(run.CodeBehind.Contains(@"__cond1End:")); } public void PatternVariableInAnArmWithACall_IsKeptAcrossThePause() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Scale(World world, int factor) { scale: Yield(); return factor; } private int Foo() { return 1; } protected override void Run(World world) { object thing = world.Open.Count; int value = thing switch { int n when n > 0 => Scale(world, n), _ => 0 }; world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"ref int n = ref __frame.Run__n;")); } public void CallInAnArmOfAnIfCondition_JumpsPastTheArmsNotTaken() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Scale(World world, int factor) { scale: Yield(); return factor; } private int Foo() { return 1; } protected override void Run(World world) { if ((world.Open.Count switch { 1 => Ask(world), _ => 0 }) > 1) { world.Open.Add(""big""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"goto __cond1Arm0;")); } public void DiscardArm_NeedsNoTest() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Scale(World world, int factor) { scale: Yield(); return factor; } private int Foo() { return 1; } protected override void Run(World world) { int value = world.Open.Count switch { 1 => 3, _ => Ask(world) }; world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(!run.CodeBehind.Contains(@"is _")); } public void CallInASwitchExpressionGuard_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Scale(World world, int factor) { scale: Yield(); return factor; } private int Foo() { return 1; } protected override void Run(World world) { int value = world.Open.Count switch { int n when Ask(world) > n => 1, _ => 0 }; world.Open.Add(value.ToString()); } }"); CheckTrue(run.Reported.Contains("ARI0004:Ask(world)")); } public void OperandOnAPathNotTaken_IsNotRunBeforeTheCall() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Scale(World world, int factor) { scale: Yield(); return factor; } private int Foo() { return 1; } protected override void Run(World world) { int value = world.Open.Contains(""flag"") ? Foo() + world.Open.Count : Ask(world); world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(!run.CodeBehind.Contains(@"= Foo()")); } public void CompoundAssignmentOnAField_KeepsTheOldValueAndWritesItInFull() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } protected override void Run(World world) { total += Ask(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1 = total;")); CheckTrue(run.CodeBehind.Contains(@"total = (int)(__frame.Run____spill1 + (__frame.Run____spill2));")); } public void CompoundAssignmentOnAByte_CastsTheResultBack() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } protected override void Run(World world) { small += (byte)Ask(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"small = (byte)(")); } public void CompoundAssignmentOnALocal_ReadsTheOldValueFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } protected override void Run(World world) { int sum = 3; sum *= Ask(world) + 1; world.Open.Add(sum.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"sum = (int)(__frame.Run____spill1 * (__frame.Run____spill2 + 1));")); } public void CompoundAssignmentOnAProperty_ReadsTheGetterBeforeThePause() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } protected override void Run(World world) { Count += Ask(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1 = Count;")); } public void AssignmentToAnArraySlot_PicksTheSlotBeforeThePause() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } protected override void Run(World world) { numbers[world.Open.Count] = Ask(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"numbers[__frame.Run____spill1] = __frame.Run____spill2;")); } public void CompoundAssignmentOnAnArraySlot_KeepsTheOldElement() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } protected override void Run(World world) { numbers[world.Open.Count] += Ask(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"numbers[__frame.Run____spill1] = (int)(__frame.Run____spill2 + (__frame.Run____spill3));")); } public void CompoundAssignmentInAForStep_IsWrittenAsAStatement() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } protected override void Run(World world) { for (int i = 0; i < 3; total += Ask(world)) { i++; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"total = (int)(")); } public void CompoundAssignmentInsideAnExpression_IsParenthesized() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } protected override void Run(World world) { world.Open.Add((total += Ask(world)).ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"(total = (int)(")); } public void CoalesceAssignmentOnAName_SkipsTheCallWhenThereIsAValue() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } protected override void Run(World world) { name ??= Describe(world); world.Open.Add(name); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"!= null) goto __cond1End;")); CheckTrue(run.CodeBehind.Contains(@"name ??= __frame.Run____spill1;")); } public void CompoundAssignmentOnAnEvent_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } public event System.Action changed; protected override void Run(World world) { changed += Make(world); } }"); CheckTrue(run.Reported.Contains("ARI0004:Make(world)")); } public void CoalesceAssignmentOnASlot_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private string Describe(World world) { describe: Yield(); return ""x""; } private int total; private byte small; private string name; private int[] numbers = new int[4]; private string[] names = new string[2]; private System.Action Make(World world) { make: Yield(); return null; } public int Count { get; set; } protected override void Run(World world) { names[0] ??= Describe(world); } }"); CheckTrue(run.Reported.Contains("ARI0004:Describe(world)")); } public void AssignmentToAMemberOfACallResult_KeepsTheObjectFromBeforeThePause() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { public class Box { public int Value; } private Box GetBox() { return new Box(); } private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { GetBox().Value = Ask(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1 = GetBox();")); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1.Value = __frame.Run____spill2;")); } public void CompoundAssignmentToAMemberOfACallResult_KeepsTheObjectAndTheOldValue() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { public class Box { public int Value; } private Box GetBox() { return new Box(); } private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { GetBox().Value += Ask(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1 = GetBox();")); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill2 = __frame.Run____spill1.Value;")); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1.Value = (int)(__frame.Run____spill2 + (__frame.Run____spill3));")); } public void VarDeconstructionOfACall_ReadsThePairFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private (int, int) Pair(World world) { pair: Yield(); return (1, 2); } private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { var (a, b) = Pair(world); world.Open.Add(a + "" "" + b); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"= __frame.Run____spill1;")); } public void DeconstructionIntoExistingNames_ReadsThePairFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private (int, int) Pair(World world) { pair: Yield(); return (1, 2); } private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int x; int y; (x, y) = Pair(world); world.Open.Add(x + "" "" + y); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"(x, y) = __frame.Run____spill1;")); } public void TypedDeconstruction_ReadsThePairFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private (int, int) Pair(World world) { pair: Yield(); return (1, 2); } private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { (int x, int y) = Pair(world); world.Open.Add(x + "" "" + y); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void DeconstructionWithADiscard_ReadsThePairFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private (int, int) Pair(World world) { pair: Yield(); return (1, 2); } private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { (_, int y) = Pair(world); world.Open.Add(y.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void NestedDeconstruction_ReadsThePairFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private (int, int) Pair(World world) { pair: Yield(); return (1, 2); } private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int a; int b; (a, (b, _)) = (Ask(world), Pair(world)); world.Open.Add(a + "" "" + b); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void DeconstructionOfATupleWithCalls_SpillsEachElementInOrder() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private (int, int) Pair(World world) { pair: Yield(); return (1, 2); } private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { both: var (a, b) = (Ask(world), Ask(world)); world.Open.Add(a + "" "" + b); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"__frame.Run____spill1 = __value;")); } public void DeconstructionIntoASlot_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private (int, int) Pair(World world) { pair: Yield(); return (1, 2); } private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int[] numbers = new int[2]; (numbers[0], total) = Pair(world); } }"); CheckTrue(run.Reported.Contains("ARI0004:Pair(world)")); } public void OutParameter_IsRenamedAndHandedBackBeforeEachReturnThatEndsThePassage() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private bool TryPick(World world, out int choice) { choice = 1; pick: Yield(); return true; } private void Fill(World world, out int number, out string text) { number = 5; text = ""x""; fill: Yield(); } protected override void Run(World world) { TryPick(world, out total); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"choice__argument = default(")); CheckTrue(run.CodeBehind.Contains(@"choice__argument = __frame.TryPick__choice; __frame.Exit(); return true;")); } public void VoidPassageWithOutParameters_HandsBackEachOne() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private bool TryPick(World world, out int choice) { choice = 1; pick: Yield(); return true; } private void Fill(World world, out int number, out string text) { number = 5; text = ""x""; fill: Yield(); } protected override void Run(World world) { Fill(world, out int number, out string text); world.Open.Add(number + text); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"number__argument = __frame.Fill__number; text__argument = __frame.Fill__text; __frame.Exit(); return true;")); } public void OutVarArgument_BecomesAHoistedVariableTheStatementCanReadAfterward() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private bool TryPick(World world, out int choice) { choice = 1; pick: Yield(); return true; } private void Fill(World world, out int number, out string text) { number = 5; text = ""x""; fill: Yield(); } protected override void Run(World world) { if (TryPick(world, out var picked)) { world.Open.Add(picked.ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"out picked, __result: out __value")); } public void OutArgumentInsideAnExpression_IsTakenByThePausingCall() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private bool TryPick(World world, out int choice) { choice = 1; pick: Yield(); return true; } private void Fill(World world, out int number, out string text) { number = 5; text = ""x""; fill: Yield(); } protected override void Run(World world) { world.Open.Add((TryPick(world, out int picked) ? picked : 0).ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"out picked, __result: out __value")); } public void BareReturnInAVoidPassageWithAnOutParameter_HandsItBackFirst() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private void Fill(World world, out int number) { number = 5; if (world.Open.Contains(""skip"")) { return; } fill: Yield(); number++; } protected override void Run(World world) { Fill(world, out int number); world.Open.Add(number.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"{ number__argument = __frame.Fill__number; __frame.Exit(); return true; }")); } public void RefParameter_IsPassedOnAsIsAndNeverSaved() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private int[] numbers = new int[2]; private void Bump(World world, ref int counter) { counter += 1; bump: Yield(); counter *= 10; } private void Fill(World world, out int number) { number = 1; fill: Yield(); } protected override void Run(World world) { int local = 3; Bump(world, ref local); world.Open.Add(local.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"private bool Bump__Passage(__Frame __frame, global::Ariadne.TickTime __time, World world, ref int counter)")); CheckTrue(run.CodeBehind.Contains(@"Bump__Passage(__frame, __time, world, ref local)")); } public void RefToAField_IsAllowed() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private int[] numbers = new int[2]; private void Bump(World world, ref int counter) { counter += 1; bump: Yield(); counter *= 10; } private void Fill(World world, out int number) { number = 1; fill: Yield(); } protected override void Run(World world) { Bump(world, ref total); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"Bump__Passage(__frame, __time, world, ref total)")); } public void RefToAnArraySlot_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private int[] numbers = new int[2]; private void Bump(World world, ref int counter) { counter += 1; bump: Yield(); counter *= 10; } private void Fill(World world, out int number) { number = 1; fill: Yield(); } protected override void Run(World world) { Bump(world, ref numbers[0]); } }"); CheckTrue(run.Reported.Contains("ARI0023:ref numbers[0]")); } public void RefReadonlyParameter_IsPassedOnAsWrittenAndNeverSaved() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private void Show(World world, ref readonly int amount) { show: Yield(); world.Open.Add(amount.ToString()); } protected override void Run(World world) { int local = 3; Show(world, ref local); again: Show(world, in local); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"Show__Passage(__Frame __frame, global::Ariadne.TickTime __time, World world, ref readonly int amount)")); CheckTrue(run.CodeBehind.Contains(@"Show__Passage(__frame, __time, world, ref local)")); CheckTrue(run.CodeBehind.Contains(@"Show__Passage(__frame, __time, world, in local)")); CheckFalse(run.CodeBehind.Contains(@"amount__argument")); } public void RefReadonlyArgumentThatIsNotAVariable_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Next() { return 1; } private void Show(World world, ref readonly int amount) { show: Yield(); world.Open.Add(amount.ToString()); } protected override void Run(World world) { Show(world, Next()); } }"); CheckTrue(run.Reported.Contains("ARI0023:Next()")); } public void OutToAnArraySlot_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private int[] numbers = new int[2]; private void Bump(World world, ref int counter) { counter += 1; bump: Yield(); counter *= 10; } private void Fill(World world, out int number) { number = 1; fill: Yield(); } protected override void Run(World world) { Fill(world, out numbers[1]); } }"); CheckTrue(run.Reported.Contains("ARI0023:out numbers[1]")); } public void OutDiscard_IsAllowed() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private int[] numbers = new int[2]; private void Bump(World world, ref int counter) { counter += 1; bump: Yield(); counter *= 10; } private void Fill(World world, out int number) { number = 1; fill: Yield(); } protected override void Run(World world) { Fill(world, out _); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void ArgumentToAParameterNotUsedAfterAPause_IsOnlyWorkedOutOnTheWayIn() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Next() { return 1; } private void Hold(World world, int value, params int[] rest) { hold: Yield(); } private void Show(World world, in int amount, string label = ""x"") { show: Yield(); world.Open.Add(label + amount); } protected override void Run(World world) { Hold(world, Next()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"Hold__Passage(__frame, __time, world, __frame.ResumeKey == null ? (Next()) : default(int))")); } public void ExpandedParamsArguments_MakeOneArrayOnTheWayInAndNoneOnResuming() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Next() { return 1; } private void Hold(World world, int value, params int[] rest) { hold: Yield(); } private void Show(World world, in int amount, string label = ""x"") { show: Yield(); world.Open.Add(label + amount); } protected override void Run(World world) { Hold(world, Next(), 1, 2, 3); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"Hold__Passage(__frame, __time, world, __frame.ResumeKey == null ? (Next()) : default(int), __frame.ResumeKey == null ? new int[] { 1, 2, 3 } : null)")); } public void ParamsArgumentGivenAsAnArray_IsAnOrdinaryByValueArgument() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private void Hold(World world, params int[] rest) { hold: Yield(); } protected override void Run(World world) { Hold(world, new[] { 1, 2 }); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains( @"Hold__Passage(__frame, __time, world, __frame.ResumeKey == null ? (new[] { 1, 2 }) : default(int[]))")); } public void ParamsArgumentGivenByName_UsesTheRenamedParameter() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private void Hold(World world, int value, params int[] rest) { hold: Yield(); world.Open.Add(value + "":"" + rest.Length); } protected override void Run(World world) { int[] numbers = new[] { 1, 2 }; Hold(world, rest: numbers, value: 3); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains( @"rest__argument: __frame.ResumeKey == null ? (numbers) : default(int[])")); CheckTrue(run.CodeBehind.Contains( @"value__argument: __frame.ResumeKey == null ? (3) : default(int)")); } public void ParamsCallWithNoExtraArguments_LeavesTheArrayToTheCompiler() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private void Hold(World world, params int[] rest) { hold: Yield(); } protected override void Run(World world) { Hold(world); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("Hold__Passage(__frame, __time, world)")); CheckFalse(run.CodeBehind.Contains("new int[]")); } public void ParamsArgumentsOfAnotherArcsPassage_MakeOneArrayToo() { GeneratorRun run = GeneratorRun.Of(@" public partial class Speaker : Arc { public void Say(World world, params string[] lines) { say: Yield(); } protected override void Run(World world) { } } public partial class Listener : Arc { public Speaker Other = new Speaker(); protected override void Run(World world) { talk: Other.Say(world, ""a"", ""b""); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains( @"__frame.ResumeKey == null ? new string[] { ""a"", ""b"" } : null)")); } public void InArgument_IsCopiedOnTheWayInWithoutItsKeyword() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Next() { return 1; } private void Hold(World world, int value, params int[] rest) { hold: Yield(); } private void Show(World world, in int amount, string label = ""x"") { show: Yield(); world.Open.Add(label + amount); } protected override void Run(World world) { int local = 5; Show(world, in local); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"Show__Passage(__frame, __time, world, __frame.ResumeKey == null ? (local) : default(int))")); } public void NamedArgumentToASavedParameter_UsesTheRenamedParameter() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Next() { return 1; } private void Hold(World world, int value, params int[] rest) { hold: Yield(); } private void Show(World world, in int amount, string label = ""x"") { show: Yield(); world.Open.Add(label + amount); } protected override void Run(World world) { Show(world, label: ""named"", amount: Next()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"label__argument: __frame.ResumeKey == null ? (""named"") : default(string)")); CheckTrue(run.CodeBehind.Contains(@"amount__argument: __frame.ResumeKey == null ? (Next()) : default(int)")); } public void ContextArgument_IsPassedAsWritten() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Next() { return 1; } private void Hold(World world, int value, params int[] rest) { hold: Yield(); } private void Show(World world, in int amount, string label = ""x"") { show: Yield(); world.Open.Add(label + amount); } protected override void Run(World world) { Hold(world, 1); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains(@"Hold__Passage(__frame, __time, world, __frame.ResumeKey")); CheckFalse(run.CodeBehind.Contains(@"default(global::World)")); } public void PassageCallInAForeachCollection_NamesTheForeach() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private System.Collections.Generic.List Pick(World world) { wait: Yield(); return null; } protected override void Run(World world) { foreach (int item in Pick(world)) { again: Yield(); } } }"); CheckEqual(new[] { "ARI0004:Pick(world)", "ARI0003:Yield()" }, run.Reported); } public void PassageCallInsideAnotherExpression_SaysAPauseIsAStatement() { GeneratorRun run = GeneratorRun.Of(@" public partial class Calling : Arc { private void Act(World world) { wait: Yield(); } private void Log(object value) { } protected override void Run(World world) { Log(Act(world)); } }"); CheckEqual(new[] { "ARI0004:Act(world)" }, run.Reported); CheckTrue(run.Messages[0].Contains("inside an expression. A pause must be a statement of its own.")); } public void UsingDeclarationBeforeAPause_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Disposing : Arc { protected override void Run(World world) { using var stream = new System.IO.MemoryStream(); wait: Yield(); } }"); CheckEqual(new[] { "ARI0021:using var stream = new System.IO.MemoryStream();" }, run.Reported); CheckTrue(run.Messages[0].Contains("disposes this using declaration's resource")); } public void UsingDeclarationAfterTheLastPause_IsFine() { GeneratorRun run = GeneratorRun.Of(@" public partial class Disposing : Arc { protected override void Run(World world) { wait: Yield(); using var stream = new System.IO.MemoryStream(); stream.Flush(); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void SwitchWithoutADefault_JumpsPastItsSections() { GeneratorRun run = GeneratorRun.Of(@" public partial class Switching : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 0: Yield(); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case 0: goto __switch1_0;")); CheckTrue(run.CodeBehind.Contains("goto __switchEnd1;")); } public void SeveralLabelsOnOneSection_EachKeepTheirOwnLine() { GeneratorRun run = GeneratorRun.Of(@" public partial class Switching : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 1: case 2: case 3 when world.Open.Contains(""x""): Yield(); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case 1:\r\n")); CheckTrue(run.CodeBehind.Contains("case 2:\r\n")); CheckTrue(run.CodeBehind.Contains("case 3 when world.Open.Contains(\"x\"): goto __switch1_0;")); } public void BreakInASwitchSection_JumpsToTheEndOfTheSwitch() { GeneratorRun run = GeneratorRun.Of(@" public partial class Switching : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 0: Yield(); break; default: world.Open.Add(""other""); break; } world.Open.Add(""after""); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("break;")); CheckEqual(2, run.CodeBehind.Split(new[] { "goto __switchEnd1;" }, System.StringSplitOptions.None).Length - 1); } public void BreakInANestedSwitch_StaysWithThatSwitch() { GeneratorRun run = GeneratorRun.Of(@" public partial class Switching : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 0: switch (world.Open.Count) { case 0: world.Open.Add(""inner""); break; } Yield(); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckEqual(1, run.CodeBehind.Split(new[] { "break;" }, System.StringSplitOptions.None).Length - 1); } public void ContinueInASwitchInALoop_JumpsToTheLoop() { GeneratorRun run = GeneratorRun.Of(@" public partial class Looping : Arc { protected override void Run(World world) { wait: while (world.Open.Count < 5) { switch (world.Open.Count) { case 1: continue; default: Yield(); break; } world.Open.Add(""again""); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("continue;")); CheckTrue(run.CodeBehind.Contains("goto __loopStart1;")); } public void GotoCaseAndGotoDefault_JumpToTheMatchingSections() { GeneratorRun run = GeneratorRun.Of(@" public partial class Jumping : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 0: world.Open.Add(""zero""); goto case 2; case 1: goto default; case 2: Yield(); break; default: world.Open.Add(""other""); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("goto case")); CheckFalse(run.CodeBehind.Contains("goto default")); CheckTrue(run.CodeBehind.Contains("goto __switch1_2;")); CheckTrue(run.CodeBehind.Contains("goto __switch1_3;")); } public void GotoCaseInALoopInASection_StillFindsItsSwitch() { GeneratorRun run = GeneratorRun.Of(@" public partial class Jumping : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 0: while (world.Open.Count < 3) { goto case 1; } break; case 1: Yield(); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("goto case")); } public void GotoCaseInANestedSwitch_BelongsToThatSwitch() { GeneratorRun run = GeneratorRun.Of(@" public partial class Jumping : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 0: switch (world.Open.Count) { case 0: goto case 1; case 1: world.Open.Add(""inner""); break; } Yield(); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("goto case 1;")); } public void LocalSharedBetweenSwitchSections_IsKeptInAField() { GeneratorRun run = GeneratorRun.Of(@" public partial class Sharing : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 0: int total = 3; Yield(); world.Open.Add(total.ToString()); break; case 1: total = 4; world.Open.Add(total.ToString()); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__total;")); } public void PatternVariableInACaseLabel_IsAssignedInItsWhenClause() { GeneratorRun run = GeneratorRun.Of(@" public partial class Patterns : Arc { protected override void Run(World world) { object thing = world; switch (thing) { case World found: wait: Yield(); found.Open.Add(""seen""); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case World found__pattern when ((found = found__pattern) is var _):")); } public void ConstantPatternWithAGuard_NeedsNoVariable() { GeneratorRun run = GeneratorRun.Of(@" public partial class Patterns : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 1 when world.Open.Contains(""x""): Yield(); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void SwitchWithoutAPause_IsCopiedAsIs() { GeneratorRun run = GeneratorRun.Of(@" public partial class Switching : Arc { protected override void Run(World world) { switch (world.Open.Count) { case 0: world.Open.Add(""zero""); break; } wait: Yield(); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case 0:\r\n")); CheckFalse(run.CodeBehind.Contains("__switch")); } public void YieldInASwitchInATry_BlamesTheTry() { GeneratorRun run = GeneratorRun.Of(@" public partial class Trying : Arc { protected override void Run(World world) { try { switch (world.Open.Count) { case 0: Yield(); break; } } finally { } } }"); CheckEqual(new[] { "ARI0003:Yield()" }, run.Reported); CheckTrue(run.Messages[0].Contains("a try statement")); } public void GotoCaseWithNoMatchingSection_IsLeftForTheCompiler() { GeneratorRun run = GeneratorRun.Of(@" public partial class Jumping : Arc { protected override void Run(World world) { wait: switch (world.Open.Count) { case 0: goto case 99; case 1: Yield(); break; } } }"); CheckEqual(0, run.Reported.Count); CheckTrue(run.CodeBehind.Contains("goto case 99;")); } public void NullCase_CanBeJumpedTo() { GeneratorRun run = GeneratorRun.Of(@" public partial class Jumping : Arc { protected override void Run(World world) { string name = world.Open.Count > 0 ? ""x"" : null; wait: switch (name) { case null: Yield(); goto case ""x""; case ""x"": world.Open.Add(""x""); break; case ""y"": goto case null; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("goto case")); } public void DoAndForLoopsInsideAFlattenedLoop_AreFlattenedToo() { GeneratorRun run = GeneratorRun.Of(@" public partial class Nesting : Arc { protected override void Run(World world) { wait: while (world.Open.Count < 9) { do { Yield(); world.Open.Add(""d""); } while (world.Open.Count < 3); for (int i = 0; i < 2; i++) { Yield(); world.Open.Add(""f""); } } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__loopStart3:")); } public void PauseInAnIfInASwitchSection_KeepsTheLocalsTheRestOfTheSectionUses() { GeneratorRun run = GeneratorRun.Of(@" public partial class Sectioned : Arc { protected override void Run(World world) { int count = 3; wait: switch (world.Open.Count) { case 0: if (world.Open.Count == 0) { Yield(); } world.Open.Add(count.ToString()); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__count;")); } public void CaseLabelWithAWhenClause_KeepsItsGuardAfterTheAssignment() { GeneratorRun run = GeneratorRun.Of(@" public partial class Patterns : Arc { protected override void Run(World world) { object thing = ""hello""; switch (thing) { case string text when text.Length > 2: wait: Yield(); world.Open.Add(text); break; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains( "case string text__pattern when ((text = text__pattern) is var _) && (text.Length > 2):")); } public void SwitchArmWithoutAWhenClause_GetsOneThatAssignsTheVariable() { GeneratorRun run = GeneratorRun.Of(@" public partial class Arms : Arc { private int Scale(World world, int factor) { wait: Yield(); return factor * 100; } protected override void Run(World world) { object thing = ""hello""; int value = thing switch { string text => Scale(world, text.Length), _ => -1, }; world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("string text__pattern when ((text = text__pattern) is var _) =>")); } public void SwitchArmWithAWhenClause_KeepsItsGuardAfterTheAssignment() { GeneratorRun run = GeneratorRun.Of(@" public partial class Arms : Arc { private int Scale(World world, int factor) { wait: Yield(); return factor * 100; } protected override void Run(World world) { object thing = ""hello""; int value = thing switch { string text when text.Length > 3 => Scale(world, text.Length), _ => -1, }; world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains( "string text__pattern when ((text = text__pattern) is var _) && (text.Length > 3) =>")); } public void PatternInAnIfWithoutAPause_UsedAfterALaterPause_IsKept() { GeneratorRun run = GeneratorRun.Of(@" public partial class Guarded : Arc { protected override void Run(World world) { object thing = ""hello""; if (!(thing is string text)) { return; } wait: Yield(); world.Open.Add(text); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public string Run__text;")); CheckTrue(run.CodeBehind.Contains( "!((thing is string text__pattern && ((text = text__pattern) is var _)))")); } public void PatternUsedOnlyInsideItsOwnIf_IsLeftAlone() { GeneratorRun run = GeneratorRun.Of(@" public partial class Local : Arc { protected override void Run(World world) { object thing = ""hello""; if (thing is string text) { world.Open.Add(text); } wait: Yield(); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("__pattern")); CheckFalse(run.CodeBehind.Contains("Run__text")); } public void VarPatternWithADeconstruction_AssignsEachVariable() { GeneratorRun run = GeneratorRun.Of(@" public partial class Pairs : Arc { protected override void Run(World world) { (int, int) pair = (1, 2); if (pair is var (low, high)) { wait: Yield(); world.Open.Add((low + high).ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__low;")); CheckTrue(run.CodeBehind.Contains("public int Run__high;")); CheckTrue(run.CodeBehind.Contains("((low = low__pattern) is var _) && ((high = high__pattern) is var _)")); } public void PropertyPatternWithADesignation_IsHoisted() { GeneratorRun run = GeneratorRun.Of(@" public partial class Properties : Arc { protected override void Run(World world) { object thing = ""hello""; if (thing is string { Length: var length } text) { wait: Yield(); world.Open.Add(text + length); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public string Run__text;")); CheckTrue(run.CodeBehind.Contains("public int Run__length;")); } public void TupleDeconstructionLiveAcrossAPause_AssignsIntoTheHoistedLocals() { GeneratorRun run = GeneratorRun.Of(@" public partial class Deconstructing : Arc { protected override void Run(World world) { var (low, high) = (1, 9); wait: Yield(); world.Open.Add(low + ""-"" + high); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("(low, high) = (1, 9);")); } public void TupleDeconstructionWithOneVariableHoisted_KeepsTheOtherDeclared() { GeneratorRun run = GeneratorRun.Of(@" public partial class Deconstructing : Arc { protected override void Run(World world) { var (used, unused) = (world.Open.Count, world.Open.Count + 1); world.Open.Add(unused.ToString()); wait: Yield(); world.Open.Add(used.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("(used, var unused) = ")); CheckFalse(run.CodeBehind.Contains("Run__unused")); } public void NestedAndMixedDeconstructions_AreRewrittenToo() { GeneratorRun run = GeneratorRun.Of(@" public partial class Deconstructing : Arc { protected override void Run(World world) { var (a, (b, _)) = (1, (2, 3)); (int c, long d) = (4, 5L); wait: Yield(); world.Open.Add(a + "","" + b + "","" + c + "","" + d); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("(a, (b, _)) = (1, (2, 3));")); CheckTrue(run.CodeBehind.Contains("(c, d) = (4, 5L);")); } public void VariablesInALambda_AreNotHoisted() { GeneratorRun run = GeneratorRun.Of(@" public partial class Lambdas : Arc { protected override void Run(World world) { object thing = ""hello""; bool match = world.Open.Any(gate => gate is string text && text.Length > 0); wait: Yield(); world.Open.Add(match.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("__pattern")); } public void SiblingBlocksDeclaringTheSameOutVariable_GetTheirOwnFields() { GeneratorRun run = GeneratorRun.Of(@" public partial class Siblings : Arc { protected override void Run(World world) { if (world.Open.Count == 0) { int.TryParse(""1"", out int number); first: Yield(); world.Open.Add(number.ToString()); } else { int.TryParse(""2"", out int number); second: Yield(); world.Open.Add(number.ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__number;")); CheckTrue(run.CodeBehind.Contains("public int Run__number__1;")); } public void OutVariableNobodyKeeps_StaysADeclaration() { GeneratorRun run = GeneratorRun.Of(@" public partial class Scratch : Arc { protected override void Run(World world) { int.TryParse(""1"", out int scratch); world.Open.Add(scratch.ToString()); wait: Yield(); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("out int scratch")); CheckFalse(run.CodeBehind.Contains("Run__scratch")); } public void TupleDeconstructionNobodyKeeps_StaysADeclaration() { GeneratorRun run = GeneratorRun.Of(@" public partial class Scratch : Arc { protected override void Run(World world) { var (low, high) = (1, 2); world.Open.Add((low + high).ToString()); wait: Yield(); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("var (low, high) = (1, 2);")); } public void ValuePassage_TakesAnOutParameterAndHandsItsResultBack() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int choice = Ask(world); world.Open.Add(choice.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("private bool Ask__Passage(__Frame __frame, global::Ariadne.TickTime __time, World world, out int __result)")); CheckTrue(run.CodeBehind.Contains("__result = default(int);")); CheckTrue(run.CodeBehind.Contains("{ __result = world.Open.Count; __frame.Exit(); return true; }")); } public void DeclarationFromAValuePassage_DeclaresTheLocalThenAssignsIt() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int choice = Ask(world); world.Open.Add(choice.ToString()); } }"); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains( "int choice; { int __value; using (__frame.Enter(\"Ask\")) { if (!Ask__Passage(__frame, __time, world, " + "__result: out __value)) { return false; } } choice = __value; }")); } public void VarDeclarationFromAValuePassage_TakesTheReturnType() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { var choice = Ask(world); world.Open.Add(choice.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("int choice; {")); } public void LabelsOnValueCalls_KeyEachOne() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { first: int a = Ask(world); second: int b = Ask(world); world.Open.Add((a + b).ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"first\": goto first;")); CheckTrue(run.CodeBehind.Contains("case \"second\": goto second;")); } public void SameValuePassageTwiceWithoutLabels_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int a = Ask(world); int b = Ask(world); } }"); CheckEqual(new[] { "ARI0005:Ask(world)" }, run.Reported); } public void AssignmentOfAValuePassage_GoesToAPlainTarget() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { first: total = Ask(world); second: this.total = Ask(world); third: _ = Ask(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("total = __value; }")); CheckTrue(run.CodeBehind.Contains("this.total = __value; }")); CheckTrue(run.CodeBehind.Contains("_ = __value; }")); } public void DiscardedValuePassage_ReceivesItsResultInADiscard() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { Ask(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__result: out _")); } public void ReturnOfAValuePassage_HandsTheValueOn() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Outer(World world) { return Ask(world); } protected override void Run(World world) { int value = Outer(world); world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__result = __value; __frame.Exit(); return true; }")); } public void ReturnsInsideBranchesOfAValuePassage_AllHandTheirValueBack() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private string Pick(World world) { wait: while (!world.Open.Contains(""go"")) { Yield(); } if (world.Open.Count > 3) { return ""many""; } else { for (int i = 0; i < 2; i++) { if (i == 1) { return ""one""; } } } return ""few""; } protected override void Run(World world) { string picked = Pick(world); world.Open.Add(picked); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("return \"many\"")); CheckEqual(3, run.CodeBehind.Split(new[] { "__result = \"" }, System.StringSplitOptions.None).Length - 1); } public void HoistedResultLocal_IsNotDeclaredAgain() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int first = Ask(world); wait: Yield(); world.Open.Add(first.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__first;")); CheckFalse(run.CodeBehind.Contains("int first;")); CheckTrue(run.CodeBehind.Contains("first = __value; }")); } public void ValuePassageWithASavedParameter_KeepsItsArgumentLogic() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Countdown(World world, int count) { wait: while (count > 0) { count--; Yield(); } return count; } protected override void Run(World world) { int left = Countdown(world, count: 3); world.Open.Add(left.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("private bool Countdown__Passage(__Frame __frame, global::Ariadne.TickTime __time, World world, int count__argument, " + "out int __result)")); CheckTrue(run.CodeBehind.Contains( "count__argument: __frame.ResumeKey == null ? (3) : default(int), __result: out __value")); } public void ValuePassageWithoutParameters_TakesOnlyTheResult() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Seed() { wait: Yield(); return 4; } protected override void Run(World world) { int seed = Seed(); world.Open.Add(seed.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("private bool Seed__Passage(__Frame __frame, global::Ariadne.TickTime __time, out int __result)")); CheckTrue(run.CodeBehind.Contains("Seed__Passage(__frame, __time, __result: out __value)")); } public void ValuePassageReturningATuple_Compiles() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private (int, string) Pair(World world) { wait: Yield(); return (world.Open.Count, ""x""); } protected override void Run(World world) { (int, string) got = Pair(world); world.Open.Add(got.Item2); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("out (int, string) __result")); } public void ValueCallInsideAnExpression_IsLiftedOutIntoATemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int bigger = Ask(world) + 1; world.Open.Add(bigger.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run____spill1;")); CheckTrue(run.CodeBehind.Contains("__frame.Run____spill1 = __value; }")); CheckTrue(run.CodeBehind.Contains("int bigger = __frame.Run____spill1 + 1;")); } public void ValueCallInADeconstructingAssignment_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private int[] numbers = new int[1]; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { (total, numbers[0]) = (Ask(world), 1); } }"); CheckTrue(run.Reported.Contains("ARI0004:Ask(world)")); } public void VoidPassageCallStillReadsTheSame() { GeneratorRun run = GeneratorRun.Of(@" public partial class Calling : Arc { private void Act(World world) { wait: Yield(); } protected override void Run(World world) { Act(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("using (__frame.Enter(\"Act\")) { if (!Act__Passage(__frame, __time, world)) { return false; } }")); CheckFalse(run.CodeBehind.Contains("__result")); } public void ValueCallAsAReceiverAndAnArgument_IsLiftedOut() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { world.Open.Add(Ask(world).ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("world.Open.Add(__frame.Run____spill1.ToString());")); } public void LabeledValueCallsInOneStatement_AreKeyedWithACount() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { both: int sum = Ask(world) + Ask(world); world.Open.Add(sum.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"both\": goto both;")); CheckTrue(run.CodeBehind.Contains("case \"both#1\": goto both__1;")); CheckTrue(run.CodeBehind.Contains("__frame.SaveLocal(\"Run.__spill1\", writer);")); } public void SiblingBlocksSharingALabel_PointEachGotoAtItsOwnLabel() { GeneratorRun run = GeneratorRun.Of(@" public partial class Siblings : Arc { protected override void Run(World world) { if (world.Open.Count > 0) { step: Yield(); if (world.Open.Count > 5) { goto step; } } else { step: Yield(); if (world.Open.Count > 7) { goto step; } } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("goto step;")); CheckTrue(run.CodeBehind.Contains("goto step__1;")); } public void GotoToALabelOnAnOrdinaryStatement_JumpsToALabelPlacedThere() { GeneratorRun run = GeneratorRun.Of(@" public partial class Plain : Arc { protected override void Run(World world) { again: world.Open.Add(""a""); wait: Yield(); if (world.Open.Count < 3) { goto again; } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("again__target:")); CheckTrue(run.CodeBehind.Contains("goto again__target;")); } public void LabelInACopiedBlock_SharingANameWithAStep_IsRenamedWithItsGoto() { GeneratorRun run = GeneratorRun.Of(@" public partial class Collide : Arc { protected override void Run(World world) { if (world.Open.Count > 0) { again: world.Open.Add(""a""); if (world.Open.Count < 3) { goto again; } } else { again: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("again__inner:")); CheckTrue(run.CodeBehind.Contains("goto again__inner;")); } public void LabelAndGotoInSeparateCopiedStatements_AreRenamedTogether() { GeneratorRun run = GeneratorRun.Of(@" public partial class Collide : Arc { protected override void Run(World world) { if (world.Open.Count > 0) { loop: while (!world.Open.Contains(""go"")) { again: world.Open.Add(""a""); if (world.Open.Count < 3) { goto again; } Yield(); } } else { again: Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("again__inner:")); CheckTrue(run.CodeBehind.Contains("goto again__inner;")); } public void OrdinaryLabelNoGotoReaches_PlacesNothing() { GeneratorRun run = GeneratorRun.Of(@" public partial class Unjumped : Arc { protected override void Run(World world) { note: world.Open.Add(""a""); wait: Yield(); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("note__target")); } public void PauseAfterALabeledCallInTheSameStep_KeepsCounting() { GeneratorRun run = GeneratorRun.Of(@" public partial class Waiting : Arc { private void Wait(World world) { inner: while (!world.Open.Contains(""answer"")) { Yield(); } } protected override void Run(World world) { first: Wait(world); Yield(); world.Open.Add(""done""); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"first\": goto first;")); CheckTrue(run.CodeBehind.Contains("case \"first#1\": goto first__1__resume;")); } public void PauseAfterLabeledValueCallsInTheSameStep_KeepsCounting() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { both: int sum = Ask(world) + Ask(world); Yield(); world.Open.Add(sum.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"both#2\": goto both__2__resume;")); } public void EarlierOperandThatCouldChange_IsStoredBeforeThePause() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total = 5; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Log(int before, int after) { } protected override void Run(World world) { Log(total, Ask(world)); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); int store = run.CodeBehind.IndexOf("__frame.Run____spill1 = total;"); int pause = run.CodeBehind.IndexOf("using (__frame.Enter(\"Ask\"))"); CheckTrue(store >= 0 && store < pause); CheckTrue(run.CodeBehind.Contains("Log(__frame.Run____spill1, __frame.Run____spill2);")); } public void OperandThatCannotChange_StaysWhereItIs() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Log(int first, int second, int third) { } protected override void Run(World world) { int local = 3; Log(local, 7, Ask(world)); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("Run____spill2")); CheckTrue(run.CodeBehind.Contains("Log(local, 7, __frame.Run____spill1);")); } public void LocalAssignedInTheSameStatement_IsStoredFirst() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Log(int first, int second) { } protected override void Run(World world) { int local = 3; Log(local, Ask(world) + (local = 4)); } }"); CheckEqual(0, run.Reported.Count); CheckTrue(run.CodeBehind.Contains("__frame.Run____spill1 = local;")); } public void ResultFedIntoAnotherPassage_IsReadFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Pick(World world, int start) { wait: while (!world.Open.Contains(""pick"")) { Yield(); } return start + 1; } protected override void Run(World world) { int picked = Pick(world, Ask(world)); world.Open.Add(picked.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__frame.Run____spill1")); CheckTrue(run.CodeBehind.Contains("__frame.SaveLocal(\"Run.__spill1\", writer);")); } public void ReturnWithMoreThanTheCall_IsLiftedOut() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Bigger(World world) { return Ask(world) + 1; } protected override void Run(World world) { int value = Bigger(world); world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("{ __result = __frame.Bigger____spill1 + 1; __frame.Exit(); return true; }")); } public void AssignmentThatReadsItsTargetFirst_StoresTheOldValue() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { total = total + Ask(world); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__frame.Run____spill1 = total;")); CheckTrue(run.CodeBehind.Contains("total = __frame.Run____spill1 + __frame.Run____spill2;")); } public void IndexerObjectAndStringParts_AreLiftedOutToo() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int[] numbers = new int[20]; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { one: int value = numbers[Ask(world)]; two: string text = $""you chose {Ask(world)}""; three: var list = new System.Collections.Generic.List(Ask(world)); world.Open.Add(value + text + list.Count); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("numbers[__frame.Run____spill1]")); } public void RefArgumentToAPlainVariable_IsPassedAfterThePause() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int slot; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Put(ref int target, int value) { target = value; } protected override void Run(World world) { Put(ref slot, Ask(world)); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("Put(ref slot, __frame.Run____spill1);")); } public void RefArgumentToAnArrayElement_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int[] numbers = new int[1]; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Put(ref int target, int value) { target = value; } protected override void Run(World world) { Put(ref numbers[0], Ask(world)); } }"); CheckEqual(new[] { "ARI0004:Ask(world)" }, run.Reported); } public void ValueTypeReceiverThatCouldChange_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public struct Tally { public int Sum; public void Add(int amount) { Sum += amount; } } public partial class Asking : Arc { private Tally tally; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { tally.Add(Ask(world)); } }"); CheckEqual(new[] { "ARI0004:Ask(world)" }, run.Reported); } public void ValueCallInALambdaInTheSameStatement_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int total = Ask(world) + world.Open.Count(gate => Ask(world) > 0); } }"); CheckTrue(run.Reported.Count > 0); } public void CastUnaryParenthesesAndTuples_AreTakenApart() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { one: int a = -(Ask(world)); two: long b = (long)Ask(world); three: var pair = (Ask(world), 1); world.Open.Add(a + "","" + b + "","" + pair.Item1); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("a = -(__frame.Run____spill1);")); CheckTrue(run.CodeBehind.Contains("b = (long)__frame.Run____spill2;")); } public void MemberOfAValueCall_IsTakenFromItsTemp() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private string Name(World world) { wait: Yield(); return ""name""; } protected override void Run(World world) { int length = Name(world).Length; world.Open.Add(length.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("int length = __frame.Run____spill1.Length;")); } public void StaticReceiversAndDelegates_NeedNoStoring() { GeneratorRun run = GeneratorRun.Of(@" public static class Registry { public static System.Collections.Generic.List Items = new System.Collections.Generic.List(); } public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Use(System.Collections.Generic.List items, int value) { } protected override void Run(World world) { System.Func callback = value => value + 1; one: int a = System.Math.Max(1, Ask(world)); two: int b = callback(Ask(world)); three: Use(Registry.Items, Ask(world)); world.Open.Add(a + "","" + b); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("System.Math.Max(1, __frame.Run____spill1)")); CheckTrue(run.CodeBehind.Contains("callback(__frame.Run____spill2)")); CheckTrue(run.CodeBehind.Contains("Use(Registry.Items, __frame.Run____spill3);")); } public void IncrementedOperandBeforeACall_IsStoredWithItsSideEffect() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int count; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Log(int first, int second, int third) { } protected override void Run(World world) { Log(count++, ++count, Ask(world)); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__frame.Run____spill1 = count++;")); CheckTrue(run.CodeBehind.Contains("__frame.Run____spill2 = ++count;")); } public void ObjectAssignedInTheStatement_IsStoredEvenThoughItIsAnObject() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Use(object first, int second) { } protected override void Run(World world) { object thing = new object(); Use(thing, Ask(world) + (((thing = null) == null) ? 1 : 2)); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__frame.Run____spill1 = thing;")); } public void ObjectInitializerAroundACall_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public class Box { public int Value; } public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { var box = new Box { Value = Ask(world) }; } }"); CheckEqual(new[] { "ARI0004:Ask(world)" }, run.Reported); } public void OperandWithNoNameableType_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Use(object first, int second) { } protected override void Run(World world) { Use(new { Id = world.Open.Count }, Ask(world)); } }"); CheckEqual(new[] { "ARI0004:Ask(world)" }, run.Reported); } public void LocalOnlyUsedInAStoredOperand_IsNotKeptInAField() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Use(int first, int second) { } protected override void Run(World world) { if (world.Open.Count == 0) { int local = world.Open.Count; Use(local + 1, Ask(world)); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("Run__local")); CheckTrue(run.CodeBehind.Contains("__frame.Run____spill1 = local + 1;")); } public void ValueCallArgumentOfAVoidPassage_IsLiftedOut() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Remember(World world, int value) { wait: Yield(); world.Open.Add(value.ToString()); } protected override void Run(World world) { Remember(world, Ask(world)); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("case \"Remember\": goto Remember;")); CheckTrue(run.CodeBehind.Contains("Remember__Passage(__frame, __time, world, __frame.ResumeKey == null ? (__frame.Run____spill1)")); } public void ValueCallArgumentOfAValuePassageStatement_IsLiftedOut() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Pick(World world, int start) { wait: Yield(); return start; } protected override void Run(World world) { one: Pick(world, Ask(world)); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void ParenthesesThisAndTypeofOperands_NeedNoStoring() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private System.Collections.Generic.List items = new System.Collections.Generic.List(); private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Use(object first, object second, object third, object fourth, int fifth) { } protected override void Run(World world) { int local = 3; Use((local), (this.items), this, typeof(int), Ask(world)); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("Use((local), (this.items), this, typeof(int), __frame.Run____spill1);")); } public void GenericHelperAroundACall_IsLiftedOut() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Remember(ItemType item) { } protected override void Run(World world) { Remember(Ask(world)); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("Remember(__frame.Run____spill1);")); } public void CallingTheResultOfACall_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private System.Action Handler() { return value => { }; } protected override void Run(World world) { Handler()(Ask(world)); } }"); CheckEqual(new[] { "ARI0004:Ask(world)" }, run.Reported); } public void AndWithACallOnTheRight_OnlyCallsWhenTheLeftIsTrue() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { bool ready = world.Open.Count > 0 && Ask(world) > 1; world.Open.Add(ready.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__frame.Run____spill1 = world.Open.Count > 0;")); CheckTrue(run.CodeBehind.Contains("if (!(__frame.Run____spill1)) goto __cond1End;")); CheckTrue(run.CodeBehind.Contains("__cond1End:")); CheckTrue(run.CodeBehind.Contains("bool ready = __frame.Run____spill1 && __frame.Run____spill2 > 1;")); } public void OrWithACallOnTheRight_SkipsItWhenTheLeftIsTrue() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { bool ready = world.Open.Count > 0 || Ask(world) > 1; world.Open.Add(ready.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("if (__frame.Run____spill1) goto __cond1End;")); } public void ConditionalWithACallInEachBranch_JumpsOverTheOtherOne() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Other(World world) { again: while (!world.Open.Contains(""other"")) { Yield(); } return 2; } protected override void Run(World world) { bool flag = world.Open.Count > 0; int value = flag ? Ask(world) : Other(world); world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("if (!(flag)) goto __cond1Else;")); CheckTrue(run.CodeBehind.Contains("goto __cond1End;")); CheckTrue(run.CodeBehind.Contains("__cond1Else:")); CheckTrue(run.CodeBehind.Contains("int value = flag ? __frame.Run____spill1 : __frame.Run____spill2;")); } public void ConditionalWithACallOnlyInTheFalseBranch_HasNothingToRunWhenTrue() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { bool flag = world.Open.Count > 0; int value = flag ? 1 : Ask(world); world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("int value = flag ? 1 : __frame.Run____spill1;")); } public void CoalesceWithACallOnTheRight_SkipsItWhenTheLeftHasAValue() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int? cached; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { int value = cached ?? Ask(world); world.Open.Add(value.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__frame.Run____spill1 = cached;")); CheckTrue(run.CodeBehind.Contains("if ((__frame.Run____spill1) != null) goto __cond1End;")); CheckTrue(run.CodeBehind.Contains("int value = __frame.Run____spill1 ?? __frame.Run____spill2;")); } public void ConditionalAccessWithACallInItsArguments_ChecksTheObjectFirst() { GeneratorRun run = GeneratorRun.Of(@" public class Recorder { public void Record(int value) { } } public partial class Asking : Arc { private Recorder recorder; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { recorder?.Record(Ask(world)); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("if ((recorder) == null) goto __cond1End;")); CheckTrue(run.CodeBehind.Contains("recorder?.Record(__frame.Run____spill1);")); } public void ConditionalAccessWithTheCallBeforeIt_NeedsNoJump() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private string Name(World world) { wait: Yield(); return ""name""; } protected override void Run(World world) { string shout = Name(world)?.ToUpper(); world.Open.Add(shout); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("string shout = __frame.Run____spill1?.ToUpper();")); CheckFalse(run.CodeBehind.Contains("__cond")); } public void ConditionalAccessInsideAChain_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public class Recorder { public Recorder Child; public void Record(int value) { } } public partial class Asking : Arc { private Recorder recorder; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { recorder?.Child?.Record(Ask(world)); } }"); CheckEqual(new[] { "ARI0004:Ask(world)" }, run.Reported); } public void EarlierOperandOfAnExpressionWithAConditional_IsStoredBeforeTheBranch() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int total = 5; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private void Use(int first, bool second) { } protected override void Run(World world) { bool flag = world.Open.Count > 0; Use(total, flag && Ask(world) > 1); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); int store = run.CodeBehind.IndexOf("__frame.Run____spill1 = total;"); int branch = run.CodeBehind.IndexOf("goto __cond1End;"); CheckTrue(store >= 0 && store < branch); } public void ConditionalsInsideEachOther_GetTheirOwnLabels() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Other(World world) { again: while (!world.Open.Contains(""other"")) { Yield(); } return 2; } protected override void Run(World world) { bool a = world.Open.Count > 0; bool b = world.Open.Count > 1; bool both = a && (b || Ask(world) > Other(world)); world.Open.Add(both.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__cond1End:")); CheckTrue(run.CodeBehind.Contains("__cond2End:")); } public void TwoConditionalsInOneStatement_GetNumberedLabels() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } private int Other(World world) { again: while (!world.Open.Contains(""other"")) { Yield(); } return 2; } private void Use(bool first, bool second) { } protected override void Run(World world) { bool a = world.Open.Count > 0; Use(a && Ask(world) > 0, a || Other(world) > 0); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__cond1End:")); CheckTrue(run.CodeBehind.Contains("__cond2End:")); } public void ConditionalOperandsWithNoCallInThem_StayInTheExpression() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { bool flag = world.Open.Count > 0; bool both = Ask(world) > 0 && flag; world.Open.Add(both.ToString()); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("bool both = __frame.Run____spill1 > 0 && flag;")); CheckFalse(run.CodeBehind.Contains("__cond")); } public void ConditionalExpressionWithAnUnsupportedOperand_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public class Box { public int Value; } public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { bool flag = world.Open.Count > 0; int value = flag ? new Box { Value = Ask(world) }.Value : 0; } }"); CheckEqual(new[] { "ARI0004:Ask(world)" }, run.Reported); } public void ConditionalCallInsideAnIfBranch_StillFlattens() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { if (world.Open.Count == 0) { bool ready = world.Open.Count > 1 && Ask(world) > 1; world.Open.Add(ready.ToString()); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("__cond1End:")); } public void ConditionalAccessFollowedByMoreMembers_StaysAnError() { GeneratorRun run = GeneratorRun.Of(@" public class Recorder { public Recorder Child; public void Record(int value) { } } public partial class Asking : Arc { private Recorder recorder; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { recorder?.Child.Record(Ask(world)); } }"); CheckEqual(new[] { "ARI0004:Ask(world)" }, run.Reported); } public void ConditionalIndexerWithACallInIt_ChecksTheObjectFirst() { GeneratorRun run = GeneratorRun.Of(@" public partial class Asking : Arc { private System.Collections.Generic.List names; private int Ask(World world) { wait: while (!world.Open.Contains(""answer"")) { Yield(); } return world.Open.Count; } protected override void Run(World world) { string name = names?[Ask(world)]; world.Open.Add(name); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("if ((names) == null) goto __cond1End;")); CheckTrue(run.CodeBehind.Contains("string name = names?[__frame.Run____spill1];")); } public void CodeAfterAPauseThatDoesNotParse_DoesNotThrow() { GeneratorRun run = GeneratorRun.Of(@" public partial class Broken : Arc { protected override void Run(World world) { int count = 3; wait: Yield(); world.Open.Add(count.ToString(; } }"); CheckEqual(0, run.Reported.Count); CheckTrue(run.Compiled.Count > 0); } } } }