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;
}
}
}