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