using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Ariadne.Generator { /// /// Finds every passage's saved locals in one script, and which passages need a LocalsToSave method to list them. /// public class ScriptLocals { public readonly Dictionary ByPassage = new Dictionary(SymbolEqualityComparer.Default); /// /// This field holds the passages that save locals or call a passage that does. /// public readonly HashSet Saving = new HashSet(SymbolEqualityComparer.Default); /// /// This field holds Run's locals when Run keeps them in a frame of its own, which the arc's frame holds from /// the start. It's null otherwise. /// public PassageLocals OwnedRun; /// /// Returns every call the arc's own frame keeps, which is every call in the script except those in passages /// that own a frame, whose frames keep their own. /// public List Sites() { var returnMe = new List(); foreach (PassageLocals passage in ByPassage.Values.Where(each => !each.OwnsFrame)) { returnMe.AddRange(passage.Plan.Sites); } return returnMe; } // A passage of a base class that saves locals has its keys collected through the passage that calls it. private static bool CallsInheritedSaver(ScriptClass script, PassageLocals locals) { foreach (Pause pause in locals.Plan.Pauses) { if (pause.Kind != PauseKind.PassageCall || pause.Foreign != null) { continue; } InvocationExpressionSyntax call = pause.Call ?? script.AsPassageCall(pause.Statement).Invocation; IMethodSymbol callee = script.PassageCalled(call); ScriptClass owner = script.OwnerOf(callee); if (owner != script && script.Registry.LocalsOf(owner, new List()).Saving.Contains(callee)) { return true; } } return false; } public static ScriptLocals Find(ScriptClass script, List diagnostics) { var returnMe = new ScriptLocals(); if (script.FromMetadata) { returnMe.Saving.UnionWith(script.Passages.Where(script.WroteLocalsToSave)); return returnMe; } foreach (IMethodSymbol passage in script.Passages) { PassageLocals locals = PassageLocals.Find(script, passage, diagnostics); returnMe.ByPassage.Add(passage, locals); if (locals.OwnsFrame && SymbolEqualityComparer.Default.Equals(passage, script.Run)) { returnMe.OwnedRun = locals; } // An override saves locals the passage it overrides doesn't know, so every one of them lists its own. if (locals.Saved.Count > 0 || script.IsVirtualPassage(passage) || locals.HasSavedTemps || locals.Plan.Sites.Count > 0 || CallsInheritedSaver(script, locals)) { returnMe.Saving.Add(passage); } } bool changed = true; while (changed) { changed = false; foreach (IMethodSymbol passage in script.Passages) { if (!returnMe.Saving.Contains(passage) && script.Callees[passage].Any(returnMe.Saving.Contains)) { returnMe.Saving.Add(passage); changed = true; } } } return returnMe; } } }