using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; namespace Vellum.Generator { /// /// Finds each generic method, and each member of a generic type, that saves a type built from its own type /// parameters, by following the uses that hand them on to a registered member, and writes the /// [RegisterSaveMethod] attributes that let uses of it be checked. /// public static class SaveMethods { public const string FileName = "Vellum.RegisterSaveMethod.g.cs"; public const string ParameterName = "SaveMethodParameter"; public const string ClassParameterName = "SaveClassParameter"; public const string InheritedReason = "uses a type parameter that the interface it implements, or the base class it overrides a member of, " + "doesn't take as a type argument of its own"; public const string UnspellableReason = "uses a type that can't be spelled in an attribute, such as a private type or a pointer"; // An assembly attribute can only name a type every part of the assembly can see. private static bool CanName(INamedTypeSymbol type) { for (INamedTypeSymbol current = type; current != null; current = current.ContainingType) { switch (current.DeclaredAccessibility) { case Accessibility.Private: case Accessibility.Protected: case Accessibility.ProtectedAndInternal: return false; } } return true; } // An explicit interface implementation has no ID of its own, only its interface's. An accessor of one still // has the kind of an accessor. private static bool HasOwnId(IMethodSymbol method) { if (method.ExplicitInterfaceImplementations.Length > 0) { return false; } switch (method.MethodKind) { case MethodKind.Ordinary: case MethodKind.Constructor: case MethodKind.UserDefinedOperator: case MethodKind.Conversion: case MethodKind.PropertyGet: case MethodKind.PropertySet: case MethodKind.EventAdd: case MethodKind.EventRemove: return true; default: return false; } } private static bool AddId(List ids, IMethodSymbol method) { string id = method.OriginalDefinition.GetDocumentationCommentId(); if (id != null && !ids.Contains(id)) { ids.Add(id); return true; } return false; } // Says which number each type parameter of the type has among the interface's type arguments. A type parameter // that the interface doesn't take as an argument by itself, such as one inside a List, has none. private static EquatableArray ClassMapOf(INamedTypeSymbol type, INamedTypeSymbol implemented) { var map = new int[SavedTypeAnalyzer.ClassArgumentsOf(type).Length]; for (int i = 0; i < map.Length; ++i) { map[i] = -1; } ImmutableArray interfaceArguments = SavedTypeAnalyzer.ClassArgumentsOf(implemented); for (int number = 0; number < interfaceArguments.Length; ++number) { var parameter = interfaceArguments[number] as ITypeParameterSymbol; if (parameter != null && parameter.TypeParameterKind == TypeParameterKind.Type) { int ordinal = SaveTemplate.ClassOrdinalOf(parameter); if (ordinal < map.Length && map[ordinal] < 0) { map[ordinal] = number; } } } return new EquatableArray(map.ToImmutableArray()); } // A converter is only called through ToNode(object) and FromNode(object), so what it overrides has no call to // check. Its base class's parameter is also often a type built from its own, which can't be numbered. private static bool IsConverter(INamedTypeSymbol type) { for (INamedTypeSymbol baseType = type.BaseType; baseType != null; baseType = baseType.BaseType) { if (baseType.MetadataName == "SaveConverter`1" && baseType.ContainingNamespace.ToDisplayString() == "Vellum") { return true; } } return false; } /// /// Returns the IDs is registered under: its own, one for each interface method it /// implements, and one for each method it overrides. /// /// Gets how many of the leading IDs are the method's own, which is zero or one. /// /// Gets, for each ID, how the type parameters of the method's type are numbered by the interface or base class /// that ID is from. The method's own has none. /// public static List RegistrationIds(IMethodSymbol method, out int ownCount, out List> classMaps) { var returnMe = new List(); classMaps = new List>(); INamedTypeSymbol type = method.ContainingType; if (HasOwnId(method) && CanName(type) && AddId(returnMe, method)) { classMaps.Add(default); } ownCount = returnMe.Count; // Saving code usually calls through an interface, eg: generated code that only knows Ariadne's IArcWriter. foreach (INamedTypeSymbol implemented in type.AllInterfaces) { foreach (IMethodSymbol member in implemented.GetMembers().OfType()) { if (SymbolEqualityComparer.Default.Equals( type.FindImplementationForInterfaceMember(member), method) && CanName(implemented) && AddId(returnMe, member)) { classMaps.Add(ClassMapOf(type, implemented)); } } } // A call through the base class runs the override, so what the override saves is saved by that call too. for (IMethodSymbol overridden = IsConverter(type) ? null : method.OverriddenMethod; overridden != null; overridden = overridden.OverriddenMethod) { if (CanName(overridden.ContainingType) && AddId(returnMe, overridden)) { classMaps.Add(ClassMapOf(type, overridden.ContainingType)); } } return returnMe; } /// /// Returns the IDs is registered under: its own, one for each interface method it /// implements, and one for each method it overrides. /// public static List RegistrationIds(IMethodSymbol method) { int ownCount; List> classMaps; return RegistrationIds(method, out ownCount, out classMaps); } /// /// Returns the methods a call to can be registered under: the method, the methods it /// overrides, and the interface methods implements with it. /// /// /// A call through a class reaches a registration made on its interface, or on a base class's method. The /// receiver is the type the call was made on, which can implement an interface that the class declaring the /// method doesn't. Each result is made from the type arguments it is seen with, so its containing type says /// what the type parameters of that type stand for here. /// public static List Candidates(IMethodSymbol method, ITypeSymbol receiver) { var returnMe = new List(); var chain = new List(); // A method constructed from type arguments has no overridden method of its own. Its definition over the // same containing type does. for (IMethodSymbol current = method; current != null; current = current.ConstructedFrom.OverriddenMethod) { returnMe.Add(current); chain.Add(current.OriginalDefinition); } ITypeSymbol type = receiver ?? method.ContainingType; if (type == null || type.TypeKind == TypeKind.Interface) { return returnMe; } foreach (INamedTypeSymbol implemented in type.AllInterfaces) { foreach (ISymbol member in implemented.GetMembers(method.Name)) { var interfaceMethod = member as IMethodSymbol; if (interfaceMethod == null) { continue; } var implementation = type.FindImplementationForInterfaceMember(interfaceMethod) as IMethodSymbol; if (implementation != null && chain.Contains(implementation.OriginalDefinition, SymbolEqualityComparer.Default)) { returnMe.Add(interfaceMethod); } } } return returnMe; } /// /// Returns the IDs a call to can be registered under, which are those of its /// . /// public static List CandidateIds(IMethodSymbol method, ITypeSymbol receiver) { var returnMe = new List(); foreach (IMethodSymbol candidate in Candidates(method, receiver)) { AddId(returnMe, candidate); } return returnMe; } // The stand-in types are what an attribute can spell where a method's own type parameters would go. private static string Attribute(string id, SaveTemplate template) { return "[assembly: global::Vellum.RegisterSaveMethod(\"" + id + "\", typeof(" + template.ToSource() + "))]"; } /// /// Returns a registration for each template that an assembly's [RegisterSaveMethod] attributes, or the /// assemblies it references, name. /// public static EquatableArray SeedsOf(Compilation compilation) { var returnMe = new List(); foreach (KeyValuePair> registered in SavedTypeAnalyzer.Registered(compilation)) { foreach (ITypeSymbol saved in registered.Value) { IMethodSymbol owner = null; SaveTemplate template = SaveTemplate.Of(saved, ref owner); if (template != null) { returnMe.Add(new SaveRegistration(registered.Key, template)); } } } // A dictionary's order isn't promised, and an equal result has to look equal to the pipeline. returnMe.Sort((left, right) => { int byId = string.CompareOrdinal(left.Id, right.Id); return byId != 0 ? byId : string.CompareOrdinal(left.Template.ToSource(), right.Template.ToSource()); }); return new EquatableArray(returnMe.ToImmutableArray()); } private static bool Add(Dictionary> known, string id, SaveTemplate template) { List templates; if (!known.TryGetValue(id, out templates)) { templates = new List(); known.Add(id, templates); } if (templates.Contains(template)) { return false; } templates.Add(template); return true; } /// /// Returns the registrations the calls in add to , by /// handing registered methods' type parameters on. /// /// /// Gets each method that couldn't be registered, and why, since what it saves can't be spelled in an attribute. /// public static List Solve(IEnumerable seeds, IEnumerable edges, List> dropped) { var known = new Dictionary>(); foreach (SaveRegistration seed in seeds) { Add(known, seed.Id, seed.Template); } var returnMe = new List(); List edgeList = edges.ToList(); // A registration can make a caller registered, and so make its own callers registered. This runs until // a pass adds nothing. A template that nests too deep is dropped, which is what ends a method that keeps // passing a bigger type to itself. bool changed = true; while (changed) { changed = false; foreach (SaveEdge edge in edgeList) { for (int callee = 0; callee < edge.CalleeIds.Items.Length; ++callee) { List saved; if (!known.TryGetValue(edge.CalleeIds.Items[callee], out saved)) { continue; } ImmutableArray classArguments = callee < edge.CalleeClassArguments.Items.Length ? edge.CalleeClassArguments.Items[callee].Items : ImmutableArray.Empty; // The list grows when a method hands its own parameters to itself. for (int i = 0; i < saved.Count; ++i) { SaveTemplate composed = saved[i].Fill(edge.Arguments.Items, classArguments); if (composed == null || !(composed.HasParameter || composed.HasUnspellable)) { continue; } string reason = null; if (composed.HasUnspellable) { reason = UnspellableReason; } else if (composed.Depth > SaveTemplate.MaxDepth) { reason = "nests more than " + SaveTemplate.MaxDepth + " types deep"; } for (int caller = 0; caller < edge.CallerIds.Items.Length; ++caller) { string callerId = edge.CallerIds.Items[caller]; SaveTemplate registered = composed; // An interface or base class numbers its own type parameters, so a template that uses // the holding type's parameters is renumbered to be registered under one. if (reason == null && composed.HasClassParameter && caller >= edge.OwnCallerCount) { ImmutableArray map = caller < edge.CallerClassMaps.Items.Length ? edge.CallerClassMaps.Items[caller].Items : ImmutableArray.Empty; registered = composed.RemapClassParameters(map); if (registered == null) { reason = InheritedReason; } } if (reason != null) { var problem = new KeyValuePair(callerId, reason); if (!dropped.Contains(problem)) { dropped.Add(problem); } // The reason belongs to this caller ID alone when it's the interface's or base's. if (reason == InheritedReason) { reason = null; } } else if (Add(known, callerId, registered)) { returnMe.Add(new SaveRegistration(callerId, registered)); changed = true; } } } } } } return returnMe; } /// /// Returns the attribute for each of . /// public static List Attributes(IEnumerable registrations) { return registrations.Select(registration => Attribute(registration.Id, registration.Template)).ToList(); } // A callee with no type parameters is handed the types its Type arguments name. Only a use of one method can // hand on its type arguments, since a use of several has no one list to number them by. private static List HandedTypes(IOperation use, IReadOnlyList targets) { var returnMe = new List(); var invocation = use as IInvocationOperation; if (targets.Count == 1 && targets[0].IsGenericMethod) { returnMe.AddRange(targets[0].TypeArguments); return returnMe; } if (invocation == null) { return returnMe; } foreach (IArgumentOperation argument in invocation.Arguments) { if (argument.Parameter?.Type.ToDisplayString() == "System.Type") { returnMe.Add((argument.Value as ITypeOfOperation)?.TypeOperand); } } return returnMe; } // Returns whether the type uses a type parameter of the member, or of the type around it. A type nests as deep // as the source does, so this keeps its own stack and gives up at a size no real type reaches. private static bool UsesParameterOf(ITypeSymbol type, IMethodSymbol member) { var pending = new Stack(); pending.Push(type); for (int visited = 0; pending.Count > 0 && visited < 4096; ++visited) { ITypeSymbol current = pending.Pop(); var parameter = current as ITypeParameterSymbol; if (parameter != null) { if (parameter.TypeParameterKind == TypeParameterKind.Type || SymbolEqualityComparer.Default.Equals(parameter.DeclaringMethod, member)) { return true; } continue; } var array = current as IArrayTypeSymbol; if (array != null) { pending.Push(array.ElementType); continue; } for (var named = current as INamedTypeSymbol; named != null; named = named.ContainingType) { foreach (ITypeSymbol argument in named.TypeArguments) { pending.Push(argument); } } } return false; } private static bool NamesIn(SyntaxNode code) { if (code == null) { return false; } foreach (SyntaxNode node in code.DescendantNodesAndSelf()) { if (node is IdentifierNameSyntax || node is InvocationExpressionSyntax || node is ElementAccessExpressionSyntax || node is ElementBindingExpressionSyntax) { return true; } } return false; } // Binding is what costs, and any name in a body could be a property. The signature is skipped, since its // types are names too. A primary constructor's code is the arguments it hands its base class. private static bool NamesAnything(SyntaxNode member) { var method = member as BaseMethodDeclarationSyntax; var constructor = member as ConstructorDeclarationSyntax; var accessor = member as AccessorDeclarationSyntax; var type = member as TypeDeclarationSyntax; if (method != null) { return NamesIn(method.Body) || NamesIn(method.ExpressionBody) || (constructor != null && NamesIn(constructor.Initializer)); } if (accessor != null) { return NamesIn(accessor.Body) || NamesIn(accessor.ExpressionBody); } if (type != null) { return type.BaseList != null && type.BaseList.Types.OfType() .Any(baseType => NamesIn(baseType.ArgumentList)); } return NamesIn(member); } // Static members and constants have no instance to be called on, so nothing hands them anything. private static bool IsInstance(SyntaxTokenList modifiers) { return !modifiers.Any(SyntaxKind.StaticKeyword) && !modifiers.Any(SyntaxKind.ConstKeyword); } // An initializer of an instance field or property, which every instance constructor runs. private static bool IsInstanceInitializer(EqualsValueClauseSyntax clause) { var property = clause.Parent as PropertyDeclarationSyntax; if (property != null) { return IsInstance(property.Modifiers); } var field = clause.Parent?.Parent?.Parent as FieldDeclarationSyntax; return field != null && IsInstance(field.Modifiers); } // The code that runs when a member is used: a method, operator, constructor, accessor, expression-bodied // property, or initializer of an instance field or property. A type with a primary constructor is its // constructor. private static bool IsMemberBody(SyntaxNode node) { if (node is MethodDeclarationSyntax || node is AccessorDeclarationSyntax || node is OperatorDeclarationSyntax || node is ConversionOperatorDeclarationSyntax) { return true; } var constructor = node as ConstructorDeclarationSyntax; var clause = node as EqualsValueClauseSyntax; var type = node as TypeDeclarationSyntax; if (constructor != null) { return IsInstance(constructor.Modifiers); } if (clause != null) { return IsInstanceInitializer(clause); } if (type != null) { return type.ParameterList != null; } return node is ArrowExpressionClauseSyntax && (node.Parent is PropertyDeclarationSyntax || node.Parent is IndexerDeclarationSyntax); } /// /// Returns whether is a method, constructor, accessor, initializer or property body /// that names something, and has type parameters of its own or sits inside a type that has them, so what it /// saves could be one of them. /// public static bool MightHoldEdges(SyntaxNode node, CancellationToken cancellation) { if (!IsMemberBody(node)) { return false; } var method = node as MethodDeclarationSyntax; bool hasTypeParameters = method != null && method.TypeParameterList != null; for (SyntaxNode ancestor = node; !hasTypeParameters && ancestor != null; ancestor = ancestor.Parent) { var type = ancestor as TypeDeclarationSyntax; hasTypeParameters = type != null && type.TypeParameterList != null; } return hasTypeParameters && NamesAnything(node); } // What a type the member hands on is in terms of the member's own type parameters. A closed type that can't be // spelled hands nothing on, which is null. One that uses a parameter is the callee's to warn about, if it // saves that argument. private static SaveTemplate HandedTemplate(ITypeSymbol handed, IMethodSymbol member, ref IMethodSymbol owner) { if (handed == null) { return null; } SaveTemplate template = SaveTemplate.Of(handed, ref owner); if (template == null && UsesParameterOf(handed, member)) { template = SaveTemplate.Unspellable; } return template; } private static bool HandsOn(SaveTemplate template) { return template != null && (template.HasParameter || template.HasUnspellable); } /// /// Holds the kinds of operation that run a method, so a use of any of them can be a call to a registered one. /// public static readonly ImmutableArray UseKinds = ImmutableArray.Create( OperationKind.Invocation, OperationKind.ObjectCreation, OperationKind.PropertyReference, OperationKind.EventAssignment, OperationKind.MethodReference, OperationKind.Binary, OperationKind.Unary, OperationKind.Conversion, OperationKind.CompoundAssignment, OperationKind.Increment, OperationKind.Decrement, OperationKind.Loop, OperationKind.DeconstructionAssignment); // A property that returns by reference is assigned through its getter. Which accessors run otherwise depends // on whether the reference is read, written, or both. private static List AccessorsUsed(IPropertyReferenceOperation reference) { IPropertySymbol property = reference.Property; // A tuple on the left of a deconstruction assigns each of its elements. IOperation target = reference; while (target.Parent is ITupleOperation) { target = target.Parent; } bool reads = true; bool writes = false; var assignment = target.Parent as IAssignmentOperation; if (assignment != null && assignment.Target == target) { writes = true; reads = !(assignment is ISimpleAssignmentOperation || assignment is IDeconstructionAssignmentOperation); } else if (reference.Parent is IIncrementOrDecrementOperation) { writes = true; } if (property.ReturnsByRef || property.ReturnsByRefReadonly) { reads = true; writes = false; } var returnMe = new List(); if (reads && property.GetMethod != null) { returnMe.Add(property.GetMethod); } if (writes && property.SetMethod != null) { returnMe.Add(property.SetMethod); } return returnMe; } private static readonly IMethodSymbol[] NoMethods = new IMethodSymbol[0]; private static IReadOnlyList Only(IMethodSymbol method) { return method == null ? NoMethods : new[] { method }; } // The methods a foreach calls, which the compiler finds by pattern. private static IReadOnlyList IteratorMethods(IForEachLoopOperation loop) { SemanticModel model = loop.SemanticModel; var syntax = loop.Syntax as ForEachStatementSyntax; var variableSyntax = loop.Syntax as ForEachVariableStatementSyntax; if (model == null || (syntax == null && variableSyntax == null)) { return NoMethods; } // An array is walked by index, though the model still names the IEnumerable methods for it. ExpressionSyntax collection = syntax != null ? syntax.Expression : variableSyntax.Expression; if (model.GetTypeInfo(collection).Type is IArrayTypeSymbol) { return NoMethods; } ForEachStatementInfo info = syntax != null ? model.GetForEachStatementInfo(syntax) : model.GetForEachStatementInfo(variableSyntax); var returnMe = new List(); foreach (IMethodSymbol method in new[] { info.GetEnumeratorMethod, info.MoveNextMethod, info.CurrentProperty?.GetMethod, info.DisposeMethod }) { if (method != null) { returnMe.Add(method); } } return returnMe; } // The Deconstruct methods a deconstruction calls, one for each level of nesting that has one. private static IReadOnlyList DeconstructMethods(IDeconstructionAssignmentOperation assignment) { SemanticModel model = assignment.SemanticModel; var syntax = assignment.Syntax as AssignmentExpressionSyntax; if (model == null || syntax == null) { return NoMethods; } var returnMe = new List(); var pending = new Stack(); pending.Push(model.GetDeconstructionInfo(syntax)); while (pending.Count > 0) { DeconstructionInfo info = pending.Pop(); if (info.Method != null) { returnMe.Add(info.Method); } if (!info.Nested.IsDefaultOrEmpty) { foreach (DeconstructionInfo nested in info.Nested) { pending.Push(nested); } } } return returnMe; } /// /// Returns the methods running calls. That is the method of a call, a method group or a /// user-defined operator, the constructor of an object creation, the accessors of a property reference or an /// event subscription, and the methods of a foreach or a deconstruction. Anything else calls none. /// public static IReadOnlyList Called(IOperation use) { switch (use.Kind) { case OperationKind.Invocation: return Only(((IInvocationOperation)use).TargetMethod); case OperationKind.ObjectCreation: // A creation of an anonymous type or a struct's default value runs no constructor. return Only(((IObjectCreationOperation)use).Constructor); case OperationKind.PropertyReference: return AccessorsUsed((IPropertyReferenceOperation)use); case OperationKind.EventAssignment: var subscription = (IEventAssignmentOperation)use; IEventSymbol subscribed = (subscription.EventReference as IEventReferenceOperation)?.Event; return Only(subscribed == null ? null : subscription.Adds ? subscribed.AddMethod : subscribed.RemoveMethod); case OperationKind.MethodReference: return Only(((IMethodReferenceOperation)use).Method); case OperationKind.Binary: return Only(((IBinaryOperation)use).OperatorMethod); case OperationKind.Unary: return Only(((IUnaryOperation)use).OperatorMethod); case OperationKind.Conversion: return Only(((IConversionOperation)use).OperatorMethod); case OperationKind.CompoundAssignment: return Only(((ICompoundAssignmentOperation)use).OperatorMethod); case OperationKind.Increment: case OperationKind.Decrement: return Only(((IIncrementOrDecrementOperation)use).OperatorMethod); case OperationKind.Loop: var loop = use as IForEachLoopOperation; return loop == null ? NoMethods : IteratorMethods(loop); case OperationKind.DeconstructionAssignment: return DeconstructMethods((IDeconstructionAssignmentOperation)use); default: return NoMethods; } } /// /// Returns the type reaches its member through, or null if it has no instance. /// public static ITypeSymbol ReceiverOf(IOperation use) { var invocation = use as IInvocationOperation; var property = use as IPropertyReferenceOperation; var subscription = use as IEventAssignmentOperation; var reference = use as IMethodReferenceOperation; if (invocation != null) { return invocation.Instance?.Type; } if (property != null) { return property.Instance?.Type; } if (reference != null) { return reference.Instance?.Type; } return (subscription?.EventReference as IEventReferenceOperation)?.Instance?.Type; } // The edge for a use inside the member, if it hands one of the member's type parameters, or one of the type // around it, on to a callee. private static SaveEdge EdgeOf(IOperation use, IMethodSymbol member) { // Most operations in a body call nothing. if (!UseKinds.Contains(use.Kind)) { return null; } IReadOnlyList targets = Called(use); if (targets.Count == 0) { return null; } // A local function or lambda inside the member has type parameters of its own, which the member's callers // can't be asked to supply. IMethodSymbol owner = member; bool handsOnAParameter = false; var arguments = ImmutableArray.CreateBuilder(); foreach (ITypeSymbol handed in HandedTypes(use, targets)) { SaveTemplate template = HandedTemplate(handed, member, ref owner); handsOnAParameter |= HandsOn(template); arguments.Add(template); } var calleeIds = new List(); var calleeClassArguments = new List>(); ITypeSymbol receiver = ReceiverOf(use); foreach (IMethodSymbol target in targets) { foreach (IMethodSymbol candidate in Candidates(target, receiver)) { string id = candidate.OriginalDefinition.GetDocumentationCommentId(); if (id == null || calleeIds.Contains(id)) { continue; } var classArguments = ImmutableArray.CreateBuilder(); foreach (ITypeSymbol argument in SavedTypeAnalyzer.ClassArgumentsOf(candidate.ContainingType)) { SaveTemplate template = HandedTemplate(argument, member, ref owner); handsOnAParameter |= HandsOn(template); classArguments.Add(template); } calleeIds.Add(id); calleeClassArguments.Add(new EquatableArray(classArguments.ToImmutable())); } } if (!handsOnAParameter) { return null; } int ownCount; List> classMaps; List callerIds = RegistrationIds(member, out ownCount, out classMaps); if (callerIds.Count == 0) { return null; } return new SaveEdge(new EquatableArray(callerIds.ToImmutableArray()), ownCount, new EquatableArray>(classMaps.ToImmutableArray()), new EquatableArray(calleeIds.ToImmutableArray()), new EquatableArray>(calleeClassArguments.ToImmutableArray()), new EquatableArray(arguments.ToImmutable())); } // Every instance constructor runs the initializers, except a record's copy constructor. A type that declares // none has an implicit one that does. private static List InitializedBy(INamedTypeSymbol type) { var returnMe = new List(); foreach (IMethodSymbol constructor in type.InstanceConstructors) { bool copies = type.IsRecord && constructor.Parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(constructor.Parameters[0].Type, type); if (!copies) { returnMe.Add(constructor); } } return returnMe; } // The methods a body belongs to. An expression-bodied property is its getter, and the type of a primary // constructor is that constructor. An initializer belongs to each constructor that runs it. private static List MembersOf(SemanticModel model, SyntaxNode node, CancellationToken cancellation) { var returnMe = new List(); var type = node as TypeDeclarationSyntax; IMethodSymbol member = null; if (node is ArrowExpressionClauseSyntax) { member = (model.GetDeclaredSymbol(node.Parent, cancellation) as IPropertySymbol)?.GetMethod; } else if (node is EqualsValueClauseSyntax) { var container = node.Ancestors().OfType().FirstOrDefault(); var containerType = container == null ? null : model.GetDeclaredSymbol(container, cancellation); if (containerType != null && IsInstanceInitializer((EqualsValueClauseSyntax)node)) { returnMe.AddRange(InitializedBy(containerType)); } } else if (type != null) { INamedTypeSymbol declared = model.GetDeclaredSymbol(type, cancellation); member = declared?.InstanceConstructors.FirstOrDefault( constructor => constructor.DeclaringSyntaxReferences .Any(reference => reference.GetSyntax(cancellation) == type)); } else { member = model.GetDeclaredSymbol(node, cancellation) as IMethodSymbol; } if (member != null) { returnMe.Add(member); } return returnMe; } /// /// Returns the edges for the calls and property uses in the body of the member declared at /// , each once. /// public static ImmutableArray EdgesOfMember(SemanticModel model, SyntaxNode node, CancellationToken cancellation) { List members = MembersOf(model, node, cancellation); IOperation body = members.Count == 0 ? null : model.GetOperation(node, cancellation); if (body == null) { return ImmutableArray.Empty; } ImmutableArray.Builder returnMe = ImmutableArray.CreateBuilder(); foreach (IOperation operation in body.DescendantsAndSelf()) { foreach (IMethodSymbol member in members) { SaveEdge edge = EdgeOf(operation, member); if (edge != null && !returnMe.Contains(edge)) { returnMe.Add(edge); } } } return returnMe.ToImmutable(); } /// /// Returns the edges for the method declared around . /// public static ImmutableArray FindMemberEdges(GeneratorSyntaxContext context, CancellationToken cancellation) { return EdgesOfMember(context.SemanticModel, context.Node, cancellation); } // A call inside a lambda or local function belongs to the method around it. private static IMethodSymbol EnclosingMember(SemanticModel model, SyntaxNode node, CancellationToken cancellation) { ISymbol symbol = model.GetEnclosingSymbol(node.SpanStart, cancellation); var method = symbol as IMethodSymbol; while (method != null && (method.MethodKind == MethodKind.LocalFunction || method.MethodKind == MethodKind.AnonymousFunction)) { symbol = method.ContainingSymbol; method = symbol as IMethodSymbol; } return method; } /// /// Returns the edge for the call or property use at , if it hands one of its member's /// type parameters on. /// public static ImmutableArray EdgesOf(SemanticModel model, SyntaxNode node, CancellationToken cancellation) { IOperation use = model.GetOperation(node, cancellation); IMethodSymbol member = use == null ? null : EnclosingMember(model, node, cancellation); SaveEdge edge = member == null ? null : EdgeOf(use, member); return edge == null ? ImmutableArray.Empty : ImmutableArray.Create(edge); } /// /// Returns a file of , or null if there are none. /// public static string File(IEnumerable attributes) { List sorted = attributes.Distinct().OrderBy(line => line, System.StringComparer.Ordinal).ToList(); if (sorted.Count == 0) { return null; } var code = new CodeWriter(); code.AddLine("// "); code.AddLine("// The generic methods Vellum.Generator found saving their type parameters, so their " + "callers are checked."); foreach (string line in sorted) { code.AddLine(line); } return code.ToString(); } } }