using UnitTestSharp; namespace Ariadne.Generator.UnitTests { public class PassageLocalsTests : TestFixture { public class FindTests : TestFixture { public void LocalOfAnyType_IsSavedWithoutComplaint() { GeneratorRun run = GeneratorRun.Of(@" public partial class Listed : Arc { protected override void Run(World world) { var gates = new List(); wait: while (gates.Count == 0) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains( "public global::System.Collections.Generic.List Run__gates;")); } public void AnonymousTypedLocalAcrossAPause_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Anonymous : Arc { protected override void Run(World world) { var pair = new { Name = ""x"" }; wait: while (world.Open.Count == 0) { Yield(); } world.Open.Add(pair.Name); } }"); CheckEqual(new[] { "ARI0027:pair" }, run.Reported); CheckTrue(run.Messages.Contains("ARI0027:'pair' is kept in a field across a pause, but an " + "anonymous type has no name to declare the field with. Write a class or struct for it, or use " + "a tuple.")); } public void AnonymousTypeInsideAnArrayOrGeneric_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public partial class Anonymous : Arc { protected override void Run(World world) { var array = new[] { new { Name = ""x"" } }; var list = array.ToList(); wait: while (world.Open.Count == 0) { Yield(); } world.Open.Add(array[0].Name + list[0].Name); } }"); CheckEqual(new[] { "ARI0027:array", "ARI0027:list" }, run.Reported); } public void RefStructLocalAcrossAPause_IsReported() { GeneratorRun run = GeneratorRun.Of(@" public ref struct Window { public int Size; } public partial class Windowed : Arc { protected override void Run(World world) { Window window = new Window(); wait: while (world.Open.Count == 0) { Yield(); } world.Open.Add(window.Size.ToString()); } }"); CheckEqual(new[] { "ARI0027:window" }, run.Reported); CheckTrue(run.Messages.Contains("ARI0027:'window' is kept in a field across a pause, but a ref " + "struct can't be the type of a field in a class.")); } public void AnonymousTypedLocalNotUsedAfterThePause_IsAllowed() { GeneratorRun run = GeneratorRun.Of(@" public partial class Anonymous : Arc { protected override void Run(World world) { var pair = new { Name = ""x"" }; world.Open.Add(pair.Name); wait: while (world.Open.Count == 1) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); } public void LocalNeededAtTwoPauses_GetsOneField() { GeneratorRun run = GeneratorRun.Of(@" public partial class Listed : Arc { protected override void Run(World world) { var gates = new List(); first: while (gates.Count == 0) { Yield(); } second: while (gates.Count == 1) { Yield(); } } }"); CheckEqual(0, run.Compiled.Count); CheckEqual(1, run.CodeBehind.Split(new[] { " Run__gates;" }, System.StringSplitOptions.None).Length - 1); } public void OutVariable_IsKeptInAFieldAndAssignedThroughItsAlias() { GeneratorRun run = GeneratorRun.Of(@" public partial class Parsed : Arc { protected override void Run(World world) { if (!int.TryParse(""3"", out int count)) { return; } wait: while (count > 0) { count--; Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Run__count;")); CheckTrue(run.CodeBehind.Contains("int.TryParse(\"3\", out count)")); } public void LabeledDeclaration_CanBeSaved() { GeneratorRun run = GeneratorRun.Of(@" public partial class Labeled : Arc { protected override void Run(World world) { first: int count = world.Open.Count; wait: 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 RefParameter_IsNotSaved() { GeneratorRun run = GeneratorRun.Of(@" public partial class ByRef : Arc { private void Act(ref int count) { wait: while (count > 0) { count--; Yield(); } } protected override void Run(World world) { int count = 3; Act(ref count); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("Act__count")); } public void InParameterUsedAfterAPause_IsSavedLikeAByValueParameter() { GeneratorRun run = GeneratorRun.Of(@" public partial class ByRef : Arc { private void Act(in int count) { wait: Yield(); Run2(count); } private void Run2(int value) { } protected override void Run(World world) { int count = 3; Act(in count); } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckTrue(run.CodeBehind.Contains("public int Act__count;")); CheckTrue(run.CodeBehind.Contains("in int count__argument")); } public void ContextParameterAndConstant_AreNotSaved() { GeneratorRun run = GeneratorRun.Of(@" public partial class Unsaved : Arc { protected override void Run(World world) { const string Gate = ""wait""; wait: while (!world.Open.Contains(Gate)) { Yield(); } } }"); CheckEqual(0, run.Reported.Count); CheckEqual(0, run.Compiled.Count); CheckFalse(run.CodeBehind.Contains("SaveLocal")); } } } }