using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
namespace Ariadne.Generator
{
///
/// An arc class with passages, and which of its methods they are: Run if it overrides it, plus every method that
/// Yields or calls a passage. A class that only holds passages for the scripts that derive from it has no Run.
///
public class ScriptClass
{
public const string ArcMetadataName = "Ariadne.Arc`1";
public const string ReachesRunByNameMetadataName = "Ariadne.ReachesRunByNameAttribute";
///
/// The tool name in the [GeneratedCode] attribute on every __Resume this generator writes.
///
public const string GeneratorName = "Ariadne.Generator";
public const string DeltaTimeName = "DeltaTime";
public const string GameTimeName = "GameTime";
// The name of the parameter on Arc's waits on triggers that takes how long to wait.
public const string TimeoutParameterName = "timeout";
public readonly INamedTypeSymbol Type;
///
/// The constructed base class, which carries the context type.
///
public readonly INamedTypeSymbol ArcBase;
///
/// Holds the Run the class overrides, or null if it only holds passages for scripts that derive from it.
///
public readonly IMethodSymbol Run;
///
/// Every method declared in the class with a block body, which are all the methods that can be passages, and
/// every abstract one.
///
public readonly Dictionary Methods =
new Dictionary(SymbolEqualityComparer.Default);
public readonly HashSet Passages = new HashSet(SymbolEqualityComparer.Default);
///
/// This field holds, for each method in Methods, the methods in Methods it calls.
///
public readonly Dictionary> Callees =
new Dictionary>(SymbolEqualityComparer.Default);
// Whether each passage can reach itself, asked again by every pass over the script.
private readonly Dictionary cycles =
new Dictionary(SymbolEqualityComparer.Default);
private readonly Compilation compilation;
private readonly IMethodSymbol yieldDefinition;
private readonly IMethodSymbol spanWaitDefinition;
private readonly IReadOnlyList triggerWaitDefinitions;
// A new semantic model starts with nothing bound, so asking the compilation for one per question would bind the
// same method body again for each.
private readonly Dictionary models = new Dictionary();
///
/// Holds the other script classes this one's passages call into, which are analyzed on demand.
///
public readonly ScriptRegistry Registry;
private readonly Dictionary targets =
new Dictionary();
private ScriptClass(INamedTypeSymbol type, INamedTypeSymbol arcBase, IMethodSymbol run,
Compilation compilation, IMethodSymbol yieldDefinition, IMethodSymbol spanWaitDefinition,
IReadOnlyList triggerWaitDefinitions, ScriptRegistry registry)
{
Type = type;
ArcBase = arcBase;
Run = run;
this.compilation = compilation;
this.yieldDefinition = yieldDefinition;
this.spanWaitDefinition = spanWaitDefinition;
this.triggerWaitDefinitions = triggerWaitDefinitions;
Registry = registry;
}
public SemanticModel ModelFor(SyntaxNode node)
{
SemanticModel model;
if (!models.TryGetValue(node.SyntaxTree, out model))
{
model = compilation.GetSemanticModel(node.SyntaxTree);
models.Add(node.SyntaxTree, model);
}
return model;
}
// The method an invocation calls, which every pass over a script asks again for the same invocations.
private IMethodSymbol TargetOf(InvocationExpressionSyntax invocation)
{
IMethodSymbol target;
if (!targets.TryGetValue(invocation, out target))
{
target = ModelFor(invocation).GetSymbolInfo(invocation).Symbol as IMethodSymbol;
targets.Add(invocation, target);
}
return target;
}
///
/// Gets whether the class was built into another assembly, so it has passages but no source.
///
public bool FromMetadata
{
get
{
return Type.DeclaringSyntaxReferences.Length == 0;
}
}
///
/// Gets the declaration of Run, which the class must have.
///
public MethodDeclarationSyntax RunSyntax
{
get
{
return Methods[Run];
}
}
///
/// Gets the type every tick hands the script, which is never saved since each tick supplies it fresh.
///
public ITypeSymbol ContextType
{
get
{
return ArcBase.TypeArguments[0];
}
}
///
/// Returns the name of the time property of Arc that is, or null if it is anything
/// else.
///
public string TimeMemberOf(ISymbol symbol)
{
var property = symbol as IPropertySymbol;
if (property != null
&& (property.Name == DeltaTimeName || property.Name == GameTimeName)
&& SymbolEqualityComparer.Default.Equals(property.ContainingType.OriginalDefinition,
ArcBase.OriginalDefinition))
{
return property.Name;
}
return null;
}
public bool IsYield(InvocationExpressionSyntax invocation)
{
IMethodSymbol target = TargetOf(invocation);
return target != null && SymbolEqualityComparer.Default.Equals(target.OriginalDefinition, yieldDefinition);
}
///
/// Returns true if calls a method on this arc itself, rather than on some other
/// object.
///
public static bool IsOnThisArc(InvocationExpressionSyntax invocation)
{
var member = invocation.Expression as MemberAccessExpressionSyntax;
if (member != null)
{
return member.Expression is ThisExpressionSyntax || member.Expression is BaseExpressionSyntax;
}
return !(invocation.Expression is MemberBindingExpressionSyntax);
}
///
/// The passage that calls on this arc, one of this class's or one it inherits,
/// or null if it calls anything else.
///
public IMethodSymbol PassageCalled(InvocationExpressionSyntax invocation)
{
IMethodSymbol target = TargetOf(invocation);
if (target == null || !IsOnThisArc(invocation))
{
return null;
}
if (Passages.Contains(target.OriginalDefinition))
{
return target.OriginalDefinition;
}
// A base class's passage runs on this arc's frame, which holds the base's locals too.
if (!SymbolEqualityComparer.Default.Equals(target.ContainingType, Type))
{
return PassageOfArcClass(target);
}
return null;
}
private bool IsInheritedPassageCall(InvocationExpressionSyntax invocation)
{
IMethodSymbol target = TargetOf(invocation);
return target != null
&& IsOnThisArc(invocation)
&& !SymbolEqualityComparer.Default.Equals(target.ContainingType, Type)
&& PassageOfArcClass(target) != null;
}
// The passage of an arc class that the call's target is, whichever class that is.
private IMethodSymbol PassageOfArcClass(IMethodSymbol target)
{
INamedTypeSymbol owner = target.ContainingType;
if (FindArcBase(owner, ArcBase.OriginalDefinition) == null)
{
return null;
}
ScriptClass ownerScript = Registry.ScriptFor(owner, compilation);
if (ownerScript != null && ownerScript.Passages.Contains(target.OriginalDefinition))
{
return target.OriginalDefinition;
}
return null;
}
///
/// Returns whether is a passage of this class or of any other script class.
///
public bool IsPassage(IMethodSymbol method)
{
return PassageOfArcClass(method) != null;
}
///
/// Returns whether is dispatched on the arc's own type: it is declared virtual or
/// abstract, or overrides a passage that is. Run is not, since only the arc's resume reaches it.
///
public bool IsVirtualPassage(IMethodSymbol passage)
{
return (passage.IsVirtual || passage.IsAbstract || passage.IsOverride)
&& !OverridesArcRun(passage, ArcBase.OriginalDefinition);
}
///
/// Returns the method that first declared , which is the method itself unless it
/// overrides one.
///
public static IMethodSymbol DeclaringMethod(IMethodSymbol method)
{
while (method.IsOverride && method.OverriddenMethod != null)
{
method = method.OverriddenMethod;
}
return method;
}
///
/// Returns the passage of a script class that calls on some other arc, or null if
/// it calls anything else.
///
public IMethodSymbol ForeignPassageCalled(InvocationExpressionSyntax invocation)
{
IMethodSymbol target = TargetOf(invocation);
if (target == null || IsOnThisArc(invocation))
{
return null;
}
return PassageOfArcClass(target);
}
///
/// Returns whether is base.Run or a call through base to another passage a
/// derived class can override, which would otherwise find the override that is running.
///
public bool IsBaseCall(InvocationExpressionSyntax invocation)
{
var member = invocation.Expression as MemberAccessExpressionSyntax;
if (member == null || !(member.Expression is BaseExpressionSyntax))
{
return false;
}
IMethodSymbol passage = PassageCalled(invocation);
return passage != null
&& (SymbolEqualityComparer.Default.Equals(passage, OwnerOf(passage).Run) || IsVirtualPassage(passage));
}
///
/// Returns whether calls Run by its name, which a passage copy resolves in the
/// class that makes the call, never in a class that overrides it.
///
public bool CallsRunByName(InvocationExpressionSyntax invocation)
{
var member = invocation.Expression as MemberAccessExpressionSyntax;
if (member != null && member.Expression is BaseExpressionSyntax)
{
return false;
}
IMethodSymbol passage = PassageCalled(invocation);
return passage != null && SymbolEqualityComparer.Default.Equals(passage, OwnerOf(passage).Run);
}
///
/// Returns the passage calls, on this arc or another, or null if it calls
/// anything else.
///
public IMethodSymbol AnyPassageCalled(InvocationExpressionSyntax invocation)
{
return PassageCalled(invocation) ?? ForeignPassageCalled(invocation);
}
///
/// Returns the script class of each base class that has passages, nearest first.
///
public List InheritedScripts()
{
var returnMe = new List();
for (INamedTypeSymbol baseType = Type.BaseType; baseType != null; baseType = baseType.BaseType)
{
ScriptClass baseScript = Registry.ScriptFor(baseType, compilation);
if (baseScript != null)
{
returnMe.Add(baseScript);
}
}
return returnMe;
}
///
/// Returns the script class that declares , which is this one for its own passages.
///
public ScriptClass OwnerOf(IMethodSymbol passage)
{
return Registry.ScriptFor(passage.ContainingType, compilation);
}
public bool IsYieldStatement(StatementSyntax statement)
{
var expression = statement as ExpressionStatementSyntax;
var invocation = expression?.Expression as InvocationExpressionSyntax;
return invocation != null && IsYield(invocation);
}
private bool Calls(InvocationExpressionSyntax invocation, IMethodSymbol definition)
{
IMethodSymbol target = TargetOf(invocation);
return target != null && SymbolEqualityComparer.Default.Equals(target.OriginalDefinition, definition);
}
public bool IsSpanWait(InvocationExpressionSyntax invocation)
{
return Calls(invocation, spanWaitDefinition);
}
///
/// Returns whether calls Wait with a trigger, or WaitAny or WaitAll, with a
/// timeout or without.
///
public bool IsTriggerWait(InvocationExpressionSyntax invocation)
{
IMethodSymbol target = TargetOf(invocation);
return target != null && triggerWaitDefinitions.Contains(target.OriginalDefinition,
SymbolEqualityComparer.Default);
}
///
/// Returns whether is a call to WaitAll, whose wait ends when every trigger has
/// been raised.
///
public bool WaitsForAll(InvocationExpressionSyntax invocation)
{
return IsTriggerWait(invocation) && TargetOf(invocation).Name == "WaitAll";
}
///
/// Returns whether is a wait for conditions to be set, and not for triggers to be
/// raised.
///
public bool WaitsOnConditions(InvocationExpressionSyntax invocation)
{
return IsTriggerWait(invocation) && WaitedTypeName(invocation) == "Condition";
}
///
/// Returns whether is a wait on a list that can hold triggers and conditions
/// together, which no script can write.
///
public bool WaitsOnMixedList(InvocationExpressionSyntax invocation)
{
return IsTriggerWait(invocation) && WaitedTypeName(invocation) == "Waitable";
}
// The type the wait takes, which for WaitAny and WaitAll is what its params array holds.
private string WaitedTypeName(InvocationExpressionSyntax invocation)
{
ITypeSymbol type = TargetOf(invocation).Parameters
.First(parameter => parameter.Name != TimeoutParameterName).Type;
if (type is IArrayTypeSymbol array)
{
type = array.ElementType;
}
return type.Name;
}
///
/// Returns the argument of a wait on triggers that says how long to wait, or null if the call has none.
///
public ArgumentSyntax TimeoutArgument(InvocationExpressionSyntax invocation)
{
var operation = (IInvocationOperation)ModelFor(invocation).GetOperation(invocation);
foreach (IArgumentOperation argument in operation.Arguments)
{
if (argument.Parameter != null && argument.Parameter.Name == TimeoutParameterName)
{
return argument.Syntax as ArgumentSyntax;
}
}
return null;
}
///
/// Returns the arguments of a wait on triggers that name the triggers to wait for, in the order written.
///
public List TriggerArguments(InvocationExpressionSyntax invocation)
{
ArgumentSyntax timeout = TimeoutArgument(invocation);
var returnMe = new List();
foreach (ArgumentSyntax argument in invocation.ArgumentList.Arguments)
{
if (argument != timeout)
{
returnMe.Add(argument);
}
}
return returnMe;
}
///
/// Returns whether is any call that pauses to wait, for a time or for triggers.
///
public bool IsWait(InvocationExpressionSyntax invocation)
{
return IsSpanWait(invocation) || IsTriggerWait(invocation);
}
///
/// Returns the call if is a call to Wait with a time span on its own, or null.
///
public InvocationExpressionSyntax AsSpanWait(StatementSyntax statement)
{
var expression = statement as ExpressionStatementSyntax;
var invocation = expression?.Expression as InvocationExpressionSyntax;
return invocation != null && IsSpanWait(invocation) ? invocation : null;
}
///
/// Returns as a pause loop, while (…) { …; Yield(); }, or null if it
/// isn't one.
///
public WhileStatementSyntax AsYieldLoop(StatementSyntax statement)
{
var loop = statement as WhileStatementSyntax;
var body = loop?.Statement as BlockSyntax;
if (body == null || body.Statements.Count == 0 || !IsYieldStatement(body.Statements.Last()))
{
return null;
}
return loop;
}
private InvocationExpressionSyntax PassageInvocation(ExpressionSyntax expression)
{
var invocation = expression as InvocationExpressionSyntax;
if (invocation == null || AnyPassageCalled(invocation) == null)
{
return null;
}
return invocation;
}
// A name, or names joined by dots, optionally starting from this. Assigning to one reads nothing the call
// could change, so the assignment can wait until the call has finished.
public static bool IsPlainTarget(ExpressionSyntax target)
{
while (true)
{
if (target is IdentifierNameSyntax || target is ThisExpressionSyntax)
{
return true;
}
var member = target as MemberAccessExpressionSyntax;
if (member == null
|| !member.IsKind(SyntaxKind.SimpleMemberAccessExpression)
|| !(member.Name is IdentifierNameSyntax))
{
return false;
}
target = member.Expression;
}
}
private InvocationExpressionSyntax TriggerWaitInvocation(ExpressionSyntax expression)
{
var invocation = expression as InvocationExpressionSyntax;
if (invocation == null || !IsTriggerWait(invocation))
{
return null;
}
return invocation;
}
// The forms a call can take its value in are the same for a passage and for a wait on triggers.
private PassageCall AsValueCall(StatementSyntax statement,
Func callIn)
{
var expression = statement as ExpressionStatementSyntax;
if (expression != null)
{
InvocationExpressionSyntax call = callIn(expression.Expression);
if (call != null)
{
return new PassageCall(PassageCallKind.Discard, call, null, null);
}
var assignment = expression.Expression as AssignmentExpressionSyntax;
if (assignment != null && assignment.IsKind(SyntaxKind.SimpleAssignmentExpression)
&& IsPlainTarget(assignment.Left))
{
call = callIn(assignment.Right);
if (call != null)
{
return new PassageCall(PassageCallKind.Assign, call, assignment.Left, null);
}
}
return null;
}
var declaration = statement as LocalDeclarationStatementSyntax;
if (declaration != null)
{
if (declaration.Declaration.Variables.Count != 1
|| declaration.IsConst
|| !declaration.UsingKeyword.IsKind(SyntaxKind.None)
|| declaration.Declaration.Type is RefTypeSyntax)
{
return null;
}
VariableDeclaratorSyntax declarator = declaration.Declaration.Variables[0];
InvocationExpressionSyntax call = callIn(declarator.Initializer?.Value);
if (call == null)
{
return null;
}
return new PassageCall(PassageCallKind.Declare, call, null, declarator);
}
var returning = statement as ReturnStatementSyntax;
if (returning != null)
{
InvocationExpressionSyntax call = callIn(returning.Expression);
if (call != null)
{
return new PassageCall(PassageCallKind.Return, call, null, null);
}
}
return null;
}
///
/// Returns as a call of a passage in one of the forms that can take its value, or
/// null if it isn't one.
///
public PassageCall AsPassageCall(StatementSyntax statement)
{
return AsValueCall(statement, PassageInvocation);
}
///
/// Returns as a wait on triggers, in one of the forms that can take the trigger it
/// ended on, or null if it isn't one.
///
public PassageCall AsTriggerWait(StatementSyntax statement)
{
return AsValueCall(statement, TriggerWaitInvocation);
}
///
/// Returns true if keeps its locals in a frame class of its own, instead of in the
/// arc's frame.
///
///
/// That is a generic passage, or one in a cycle of calls, which needs a frame for each lap. Run does too when
/// it is in a cycle, and the arc's own frame holds that frame.
///
public bool OwnsFrame(IMethodSymbol passage)
{
if (passage.IsGenericMethod)
{
return true;
}
ScriptClass owner = OwnerOf(passage);
if (owner != this)
{
return owner.OwnsFrame(passage);
}
if (FromMetadata)
{
return Type.GetTypeMembers(PassageWriter.OwnedFrameName(passage)).Length > 0;
}
return CallsItself(passage);
}
private bool Reaches(IMethodSymbol from, IMethodSymbol to)
{
var seen = new HashSet(SymbolEqualityComparer.Default);
var pending = new Stack(Callees[from]);
while (pending.Count > 0)
{
IMethodSymbol callee = pending.Pop();
if (SymbolEqualityComparer.Default.Equals(callee, to))
{
return true;
}
if (Passages.Contains(callee) && seen.Add(callee))
{
foreach (IMethodSymbol next in Callees[callee])
{
pending.Push(next);
}
}
}
return false;
}
///
/// Returns true if can reach itself through the passages it calls.
///
public bool CallsItself(IMethodSymbol passage)
{
bool returnMe;
if (!cycles.TryGetValue(passage, out returnMe))
{
returnMe = Reaches(passage, passage);
cycles.Add(passage, returnMe);
}
return returnMe;
}
///
/// Returns true if and are passages of this class that each
/// reach the other, so a call from one to the other goes round a cycle.
///
public bool InSameCycle(IMethodSymbol first, IMethodSymbol second)
{
return Passages.Contains(first)
&& Passages.Contains(second)
&& Reaches(first, second)
&& Reaches(second, first);
}
// The overload of a wait placeholder on Arc by the name of its one parameter's type, since Wait is overloaded.
private static IMethodSymbol WaitTaking(INamedTypeSymbol arcDefinition, string name, string parameterType)
{
return arcDefinition.GetMembers(name).OfType().FirstOrDefault(method =>
method.Parameters.Length == 1
&& method.Parameters[0].Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat) ==
parameterType);
}
// Every placeholder that waits on triggers, with a timeout or without.
private static List TriggerWaitsOf(INamedTypeSymbol arcDefinition)
{
var returnMe = new List();
foreach (string name in new[] { "Wait", "WaitAny", "WaitAll" })
{
foreach (IMethodSymbol method in arcDefinition.GetMembers(name).OfType())
{
// Wait is overloaded, and the one with a duration alone waits for time, not triggers.
if (method.Parameters[0].Type.Name != "Duration" || method.Parameters.Length > 1)
{
returnMe.Add(method);
}
}
}
return returnMe;
}
public static bool OverridesArcRun(IMethodSymbol method, INamedTypeSymbol arcDefinition)
{
if (method.Name != "Run" || !method.IsOverride)
{
return false;
}
for (IMethodSymbol overridden = method; overridden != null; overridden = overridden.OverriddenMethod)
{
if (SymbolEqualityComparer.Default.Equals(overridden.ContainingType.OriginalDefinition, arcDefinition))
{
return true;
}
}
return false;
}
private static bool IsOurGeneratedCode(AttributeData attribute)
{
return attribute.AttributeClass?.ToDisplayString() == "System.CodeDom.Compiler.GeneratedCodeAttribute"
&& attribute.ConstructorArguments.Length > 0
&& (attribute.ConstructorArguments[0].Value as string) == GeneratorName;
}
// A class that writes its own __Resume is hand-built in the form the generator emits, eg: to test the runtime
// without it. The generated one doesn't count, since the suppressor sees the compilation that includes it.
public static bool WritesOwnResume(INamedTypeSymbol type)
{
return type.GetMembers("__Resume").OfType()
.Any(method => !method.GetAttributes().Any(IsOurGeneratedCode));
}
public static INamedTypeSymbol FindArcBase(INamedTypeSymbol type, INamedTypeSymbol arcDefinition)
{
for (INamedTypeSymbol baseType = type.BaseType; baseType != null; baseType = baseType.BaseType)
{
if (SymbolEqualityComparer.Default.Equals(baseType.OriginalDefinition, arcDefinition))
{
return baseType;
}
}
return null;
}
// A class built into another assembly has no bodies to read. The generator wrote a copy of each of its
// passages there, which is how they are told from its other methods.
private void FindGeneratedPassages()
{
foreach (IMethodSymbol method in Type.GetMembers().OfType())
{
if (method.MethodKind == MethodKind.Ordinary
&& Type.GetMembers(method.Name + PassageWriter.PassageSuffix).Length > 0)
{
Passages.Add(method);
}
}
}
///
/// Returns the class name the generator marked the copy of with, as the class whose
/// Run a call by name in it stays on, or null if it wasn't marked. Only a class built into another assembly
/// can be asked.
///
public string RunByNameMark(IMethodSymbol passage)
{
foreach (IMethodSymbol copy in Type.GetMembers(passage.Name + PassageWriter.PassageSuffix)
.OfType())
{
foreach (AttributeData attribute in copy.GetAttributes())
{
if (attribute.AttributeClass?.ToDisplayString() == ReachesRunByNameMetadataName
&& attribute.ConstructorArguments.Length > 0)
{
return attribute.ConstructorArguments[0].Value as string;
}
}
}
return null;
}
///
/// Returns whether the copy of a passage takes one of its parameters under another name, which it does for a
/// parameter it saves. Only a class built into another assembly can be asked.
///
public bool RenamesParameter(IMethodSymbol passage, IParameterSymbol parameter)
{
return Type.GetMembers(passage.Name + PassageWriter.PassageSuffix).OfType().Any(copy =>
copy.Parameters.Any(each => each.Name == parameter.Name + PassageWriter.ArgumentSuffix));
}
///
/// Returns whether the generator wrote a method to list the locals a passage saves. Only a class built into
/// another assembly can be asked.
///
public bool WroteLocalsToSave(IMethodSymbol passage)
{
return Type.GetMembers(passage.Name + PassageWriter.LocalsToSaveSuffix).Length > 0;
}
private void FindMethods()
{
foreach (SyntaxReference reference in Type.DeclaringSyntaxReferences)
{
var declaration = reference.GetSyntax() as ClassDeclarationSyntax;
if (declaration == null)
{
continue;
}
SemanticModel model = ModelFor(declaration);
foreach (MethodDeclarationSyntax method in declaration.Members.OfType())
{
var symbol = model.GetDeclaredSymbol(method) as IMethodSymbol;
// An abstract method has no body, but is a passage all the same, for its overrides to follow.
if (symbol != null && (method.Body != null || symbol.IsAbstract))
{
Methods[symbol] = method;
}
}
}
}
// An override might pause even where the method it overrides doesn't, so a virtual method is a passage whether
// or not its own body pauses. So is an override of one.
private bool IsVirtualDeclaration(IMethodSymbol method)
{
if (method.MethodKind != MethodKind.Ordinary)
{
return false;
}
if (method.IsOverride)
{
return method.OverriddenMethod != null && PassageOfArcClass(method.OverriddenMethod) != null;
}
return method.IsVirtual || method.IsAbstract;
}
// A fixed point over the call graph: a method that calls a passage is itself a passage, however indirectly.
private void FindPassages()
{
if (Run != null)
{
Passages.Add(Run);
}
foreach (KeyValuePair method in Methods)
{
if (IsVirtualDeclaration(method.Key))
{
Passages.Add(method.Key);
}
var called = new List();
if (method.Value.Body == null)
{
Callees[method.Key] = called;
continue;
}
foreach (InvocationExpressionSyntax invocation in
method.Value.Body.DescendantNodes().OfType())
{
if (IsYield(invocation)
|| IsWait(invocation)
|| ForeignPassageCalled(invocation) != null
|| IsInheritedPassageCall(invocation))
{
Passages.Add(method.Key);
continue;
}
IMethodSymbol target = TargetOf(invocation);
if (target != null && Methods.ContainsKey(target.OriginalDefinition))
{
called.Add(target.OriginalDefinition);
}
}
Callees[method.Key] = called;
}
bool changed = true;
while (changed)
{
changed = false;
foreach (KeyValuePair> method in Callees)
{
if (!Passages.Contains(method.Key) && method.Value.Any(Passages.Contains))
{
Passages.Add(method.Key);
changed = true;
}
}
}
}
///
/// Analyzes , or returns null if it isn't a script: it writes its own __Resume, or
/// overrides Arc.Run without a block body, or has no Run and no passages.
///
public static ScriptClass Create(INamedTypeSymbol type, Compilation compilation)
{
return Create(type, compilation, new ScriptRegistry());
}
///
/// Analyzes as the other overload does, finding the classes its passages call into
/// through .
///
public static ScriptClass Create(INamedTypeSymbol type, Compilation compilation, ScriptRegistry registry)
{
INamedTypeSymbol arcDefinition = compilation.GetTypeByMetadataName(ArcMetadataName);
if (arcDefinition == null || WritesOwnResume(type))
{
return null;
}
INamedTypeSymbol arcBase = FindArcBase(type, arcDefinition);
IMethodSymbol run = type.GetMembers("Run").OfType()
.FirstOrDefault(method => OverridesArcRun(method, arcDefinition));
IMethodSymbol yieldDefinition = arcDefinition.GetMembers("Yield").OfType().FirstOrDefault();
IMethodSymbol spanWaitDefinition = WaitTaking(arcDefinition, "Wait", "Duration");
List triggerWaitDefinitions = TriggerWaitsOf(arcDefinition);
if (arcBase == null
|| yieldDefinition == null
|| spanWaitDefinition == null
|| triggerWaitDefinitions.Count == 0)
{
return null;
}
var returnMe = new ScriptClass(type, arcBase, run, compilation, yieldDefinition, spanWaitDefinition,
triggerWaitDefinitions, registry);
if (returnMe.FromMetadata)
{
returnMe.FindGeneratedPassages();
return returnMe.Passages.Count == 0 ? null : returnMe;
}
returnMe.FindMethods();
if (run != null && !returnMe.Methods.ContainsKey(run))
{
return null;
}
// Before its passages are found, so a class they call into that calls back finds this one.
registry.Register(returnMe);
returnMe.FindPassages();
if (run == null && returnMe.Passages.Count == 0)
{
return null;
}
return returnMe;
}
}
}