using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; namespace Vellum.Generator { /// /// Describes a class or struct that implements interfaces, as plain data, so it can be checked for an interface /// family that saves it without ambiguity. /// public sealed class FamilyMember : IEquatable { public readonly string Name; /// /// Holds the name of the type and of each class it derives from, which a class family would save it by. /// public readonly EquatableArray ClassNames; /// /// Holds the name of each interface it implements, the ones that extend others first. /// public readonly EquatableArray InterfaceNames; /// /// Holds the name of each generic interface it implements with more than one set of type arguments. /// public readonly EquatableArray RepeatedInterfaces; public readonly SourcePlace Place; public FamilyMember(string name, EquatableArray classNames, EquatableArray interfaceNames, EquatableArray repeatedInterfaces, SourcePlace place) { Name = name; ClassNames = classNames; InterfaceNames = interfaceNames; RepeatedInterfaces = repeatedInterfaces; Place = place; } /// /// Returns as plain data, or null if it can never be a value that's saved or /// implements no interface. /// public static FamilyMember Of(INamedTypeSymbol type) { bool isClassOrStruct = type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct; if (type.IsAbstract || type.IsStatic || !isClassOrStruct) { return null; } EquatableArray interfaces = SaveableTypes.InterfaceNamesOf(type); if (interfaces.Items.Length == 0) { return null; } return new FamilyMember(type.Name, SaveableTypes.ClassNamesOf(type), interfaces, SaveableTypes.RepeatedInterfacesOf(type), SourcePlace.Of(type.Locations.FirstOrDefault())); } // A type can only implement an interface if its assembly defines it or references the one that does. That // keeps the scan off the BCL and off every other assembly with no part in a family. private static bool CouldImplement(IAssemblySymbol assembly, HashSet interfaceAssemblies) { if (interfaceAssemblies.Contains(assembly.Name)) { return true; } foreach (IModuleSymbol module in assembly.Modules) { foreach (IAssemblySymbol referenced in module.ReferencedAssemblySymbols) { if (interfaceAssemblies.Contains(referenced.Name)) { return true; } } } return false; } /// /// Returns each type in the referenced assemblies that implements the interface of an interface family in /// , which is all that can find fault with. /// public static EquatableArray Referenced(Compilation compilation, ConverterList converters) { ImmutableArray.Builder returnMe = ImmutableArray.CreateBuilder(); Dictionary> interfaceFamilies = converters.InterfaceFamilies(); if (interfaceFamilies.Count > 0) { HashSet interfaceAssemblies = converters.InterfaceFamilyAssemblies(); foreach (IAssemblySymbol assembly in compilation.SourceModule.ReferencedAssemblySymbols) { if (!CouldImplement(assembly, interfaceAssemblies)) { continue; } var pending = new Stack(); var types = new Stack(); pending.Push(assembly.GlobalNamespace); while (pending.Count > 0) { INamespaceSymbol current = pending.Pop(); foreach (INamespaceSymbol child in current.GetNamespaceMembers()) { pending.Push(child); } foreach (INamedTypeSymbol type in current.GetTypeMembers()) { types.Push(type); } } while (types.Count > 0) { INamedTypeSymbol type = types.Pop(); foreach (INamedTypeSymbol nested in type.GetTypeMembers()) { types.Push(nested); } FamilyMember member = Of(type); if (member != null && member.InterfaceNames.Items.Any(interfaceFamilies.ContainsKey)) { returnMe.Add(member); } } } } return new EquatableArray(returnMe.ToImmutable()); } /// /// Returns what is wrong with the type in , given the base class or interface /// of each family and, for the interface ones, the interfaces /// each extends. /// public List Check(Compilation compilation, Dictionary> families, Dictionary> interfaceFamilies) { var returnMe = new List(); // A class family saves the type before any interface family is asked. if (ClassNames.Items.Any(families.ContainsKey)) { return returnMe; } List matched = InterfaceNames.Items.Where(interfaceFamilies.ContainsKey).ToList(); List mostDerived = FamilyChoice.MostDerived(matched, (derived, baseName) => interfaceFamilies[derived].Items.Contains(baseName)); if (mostDerived.Count > 1) { returnMe.Add(Diagnostic.Create(VellumDiagnostics.AmbiguousInterfaceFamilies, Place.LocationIn(compilation), Name, string.Join(" and ", mostDerived))); } foreach (string name in mostDerived.Where(derived => RepeatedInterfaces.Items.Contains(derived))) { returnMe.Add(Diagnostic.Create(VellumDiagnostics.RepeatedFamilyInterface, Place.LocationIn(compilation), Name, name)); } return returnMe; } public bool Equals(FamilyMember other) { return other != null && Name == other.Name && ClassNames.Equals(other.ClassNames) && InterfaceNames.Equals(other.InterfaceNames) && RepeatedInterfaces.Equals(other.RepeatedInterfaces) && Place.Equals(other.Place); } public override bool Equals(object obj) { return Equals(obj as FamilyMember); } public override int GetHashCode() { return Name.GetHashCode() * 31 + InterfaceNames.GetHashCode(); } } }