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