using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Ariadne.Generator { /// /// A call in a passage to a passage that runs with a frame of its own, and what the caller holds while it's paused /// inside it. That is a passage of another arc, or a passage that owns a frame, whose locals can't live in the /// arc's frame. /// public class ForeignSite { public const string ReceiverSuffix = "receiver"; /// /// Holds the passage called, as its own class declares it. /// public readonly IMethodSymbol Passage; /// /// Holds the script class that declares the passage. /// public readonly ScriptClass Owner; /// /// Holds the expression for the arc the passage runs on, which is worked out once when the call begins. It's /// null for a call on the calling arc, which only a passage that owns a frame needs a site for. /// public readonly ExpressionSyntax Receiver; /// /// Holds the type of that expression. /// public readonly ITypeSymbol ReceiverType; /// /// Holds the type arguments the call gives a generic passage, as <int, string>, or "" for any /// other. /// public readonly string TypeArguments; /// /// Holds the name of the table the callee's locals are saved in, among the calling arc's. It ends with a /// slash, which tells it from a local. /// public readonly string ScopeName; /// /// Holds the name of the field in the calling arc's frame that keeps the receiver. /// public readonly string ReceiverField; /// /// Holds the name of the field in the calling arc's frame that keeps the callee's frame. /// public readonly string FrameField; /// /// Holds the type of the callee's frame. For a passage on this arc that owns a frame that is its own frame, /// and for one on another arc it is the frame of that arc, which the owned frame is lent beside. /// public readonly string FrameType; /// /// Holds the name of the field in the calling arc's frame that keeps the owned frame of a passage called on /// another arc, or null for any other call. /// public readonly string OwnedFrameField; /// /// Holds the type of that owned frame, or null. /// public readonly string OwnedFrameType; /// /// Holds the call, which a save library's analyzer reports a problem with the receiver's type on. /// public readonly Location Where; /// /// Holds the open generic frame class a polymorphic call builds its callee's frame from when it loads, as /// Wrap__Frame<,>, or null for any other call. /// public readonly string FrameDefinition; /// /// Holds the call's type arguments as typeof expressions, to save beside the callee's locals. It is /// null for any call that isn't polymorphic. /// public readonly string TypeArgumentTypes; /// /// Whether the call is through base, eg: base.Run. The base class's passage keeps its locals in a frame /// of the base class's type, made for the call, so that they stay apart from the locals of the one that calls /// it. /// public readonly bool IsBaseCall; private ForeignSite(IMethodSymbol passage, ScriptClass owner, ExpressionSyntax receiver, ITypeSymbol receiverType, string typeArguments, string frameType, string ownedFrameType, string scopeName, string fieldStem, Location where, string frameDefinition = null, string typeArgumentTypes = null, bool isBaseCall = false) { IsBaseCall = isBaseCall; FrameDefinition = frameDefinition; TypeArgumentTypes = typeArgumentTypes; Passage = passage; Owner = owner; Receiver = receiver; ReceiverType = receiverType; TypeArguments = typeArguments; ScopeName = scopeName; ReceiverField = fieldStem + "_" + ReceiverSuffix; FrameField = fieldStem + "_frame"; FrameType = frameType; OwnedFrameType = ownedFrameType; if (ownedFrameType != null) { OwnedFrameField = fieldStem + "_ownedFrame"; } Where = where; } /// /// Gets whether the call is to a passage on the calling arc. /// public bool OnThisArc { get { return Receiver == null; } } /// /// Gets whether the call is to a passage that owns a frame on another arc, which runs with that frame beside /// the frame of that arc. /// public bool HasOwnedFrame { get { return OwnedFrameType != null; } } /// /// Gets whether the call is to a passage in the caller's own cycle with a type argument built from the /// caller's type parameters, so each lap's frame is a type no earlier lap made. /// public bool Polymorphic => FrameDefinition != null; /// /// Gets the type the field that keeps the callee's frame is declared as. A polymorphic call uses the base /// frame type, since every lap's frame is a type the last didn't have. /// public string FrameFieldType => Polymorphic ? "global::Ariadne.Frame" : FrameType; /// /// Gets the expression a passage copy reads the callee's frame through, as the callee's frame type. /// public string TypedFrameAccess => Polymorphic ? "((" + FrameType + ")" + FrameAccess + ")" : FrameAccess; /// /// Gets the expression a passage copy reads the receiver through. /// public string ReceiverAccess { get { return PassageWriter.FrameName + "." + ReceiverField; } } /// /// Gets the expression a passage copy reads the callee's frame through. /// public string FrameAccess { get { return PassageWriter.FrameName + "." + FrameField; } } /// /// Gets the expression a passage copy reads the owned frame through. /// public string OwnedFrameAccess { get { return PassageWriter.FrameName + "." + OwnedFrameField; } } /// /// Gets the key the receiver is told apart by in the calling arc's frame, which saves it as /// in the callee's table. /// public string ReceiverKey { get { return ScopeName + ReceiverSuffix; } } /// /// Returns the site of , made in the passage named at the /// pause keyed . /// public static ForeignSite Of(ScriptClass script, string callerName, InvocationExpressionSyntax call, string key, IMethodSymbol passage) { ExpressionSyntax receiver = ((MemberAccessExpressionSyntax)call.Expression).Expression; ITypeSymbol receiverType = script.ModelFor(receiver).GetTypeInfo(receiver).Type; ScriptClass owner = script.OwnerOf(passage); // The callee's frame is typed as the owner is constructed at the call, eg: Plant.__Frame. var target = (IMethodSymbol)script.ModelFor(call).GetSymbolInfo(call).Symbol; string ownerType = target.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); string frameType = ownerType + "." + PassageWriter.FrameTypeName; string typeArguments = ""; string ownedFrameType = null; if (script.OwnsFrame(passage)) { typeArguments = TypeArgumentsOf(target); ownedFrameType = ownerType + "." + PassageWriter.OwnedFrameName(passage) + typeArguments; } return new ForeignSite(passage, owner, receiver, receiverType ?? owner.Type, typeArguments, frameType, ownedFrameType, callerName + "." + key + "/", FieldStemOf(callerName, key), call.GetLocation()); } // A key is a label or a passage name, with #1, #2 after it for a second pause under the same label. The # // can't be in an identifier. private static string FieldStemOf(string callerName, string key) { return callerName + "____call_" + key.Replace("#", "__n"); } private static string TypeArgumentsOf(IMethodSymbol target) { if (target.TypeArguments.Length == 0) { return ""; } return "<" + string.Join(", ", target.TypeArguments.Select(argument => argument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))) + ">"; } /// /// Returns the site of a call to base.Run, made in the passage named at /// the pause keyed . /// public static ForeignSite OfBase(ScriptClass script, string callerName, InvocationExpressionSyntax call, string key, IMethodSymbol passage) { // Both Runs would otherwise save their locals under the same keys, and the arc's frame holds only one set. var target = (IMethodSymbol)script.ModelFor(call).GetSymbolInfo(call).Symbol; string frameType = target.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + "." + PassageWriter.FrameTypeName; return new ForeignSite(passage, script.OwnerOf(passage), null, null, "", frameType, null, callerName + "." + key + "/", FieldStemOf(callerName, key), call.GetLocation(), isBaseCall: true); } /// /// Returns the site of to a passage on this arc that owns a frame, made in the passage /// named at the pause keyed . /// public static ForeignSite OfOwnedFrame(ScriptClass script, string callerName, InvocationExpressionSyntax call, string key, IMethodSymbol passage) { var target = (IMethodSymbol)script.ModelFor(call).GetSymbolInfo(call).Symbol; string typeArguments = TypeArgumentsOf(target); // The frame is a class of the arc that declares the passage, constructed with the call's type arguments. string frameClass = target.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + "." + PassageWriter.OwnedFrameName(passage); string frameDefinition = null; string typeArgumentTypes = null; IMethodSymbol caller = script.Passages.FirstOrDefault(each => each.Name == callerName); if (caller != null && script.InSameCycle(caller, passage) && target.TypeArguments.Any(argument => ScriptChecks.BuiltFrom(argument, caller.TypeParameters))) { frameDefinition = frameClass + "<" + new string(',', target.TypeArguments.Length - 1) + ">"; typeArgumentTypes = string.Join(", ", target.TypeArguments.Select(argument => "typeof(" + argument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + ")")); } return new ForeignSite(passage, script.OwnerOf(passage), null, null, typeArguments, frameClass + typeArguments, null, callerName + "." + key + "/", FieldStemOf(callerName, key), call.GetLocation(), frameDefinition, typeArgumentTypes); } } }