using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace Vellum.Generator
{
///
/// Names the types a compilation can save, and works out what kind of type each is: one Vellum's runtime saves
/// itself, or one with a converter or [Saveable] mark of its own.
///
public static class SaveableTypes
{
///
/// Holds the types the runtime saves without a converter, other than enums, by the full metadata name its own
/// types have.
///
public static readonly HashSet BuiltInNames = new HashSet(
SavedValue.BuiltInTypes.Select(type => type.FullName));
///
/// Holds the generic collections the runtime saves itself, by the full metadata name its own types have.
///
public static readonly HashSet BuiltInCollectionNames = new HashSet(
SaveConverters.BuiltInCollections.Select(definition => definition.FullName));
public const string SavedWithAttributeName = "Vellum.SavedWithAttribute";
///
/// Returns the name of and of each class it derives from, nearest first and leaving
/// out object, as matched against the base classes of the family converters. A generic class is named by its
/// generic type, since a family saves every type made from it.
///
public static EquatableArray ClassNamesOf(ITypeSymbol type)
{
ImmutableArray.Builder returnMe = ImmutableArray.CreateBuilder();
if (type.TypeKind == TypeKind.Class)
{
for (ITypeSymbol current = type; current != null && current.SpecialType != SpecialType.System_Object;
current = current.BaseType)
{
returnMe.Add(NameOf(current.OriginalDefinition));
}
}
return new EquatableArray(returnMe.ToImmutable());
}
///
/// Returns the name of each interface implements, the ones that extend others first,
/// as matched against the interfaces of the family converters. A generic interface is named by its generic
/// type, once however many ways the type implements it. An interface is named too, since a field declared as
/// it holds any type a family of it or of an interface it extends saves.
///
public static EquatableArray InterfaceNamesOf(ITypeSymbol type)
{
if (type.TypeKind != TypeKind.Class && type.TypeKind != TypeKind.Struct
&& type.TypeKind != TypeKind.Interface)
{
return default;
}
IEnumerable implemented = type.AllInterfaces;
if (type.TypeKind == TypeKind.Interface)
{
implemented = implemented.Prepend((INamedTypeSymbol)type);
}
return new EquatableArray(implemented
.OrderByDescending(candidate => candidate.AllInterfaces.Length)
.Select(candidate => NameOf(candidate.OriginalDefinition))
.Distinct()
.ToImmutableArray());
}
///
/// Returns the name of each generic interface implements with more than one set of
/// type arguments.
///
public static EquatableArray RepeatedInterfacesOf(ITypeSymbol type)
{
if (type.TypeKind != TypeKind.Class && type.TypeKind != TypeKind.Struct)
{
return default;
}
return new EquatableArray(type.AllInterfaces
.GroupBy(implemented => NameOf(implemented.OriginalDefinition))
.Where(group => group.Count() > 1)
.Select(group => group.Key)
.ToImmutableArray());
}
///
/// Returns the names of 's classes, then its interfaces, as matched against the family
/// converters. A class family beats an interface family, so the first name a family has is the one that
/// saves the type.
///
public static EquatableArray FamilyNamesOf(ITypeSymbol type)
{
return new EquatableArray(ClassNamesOf(type).Items.AddRange(InterfaceNamesOf(type).Items));
}
///
/// Holds how a type is spelled when matching it to its converter, which leaves out the question mark of a
/// nullable reference type so a Foo? and a Foo are the same type.
///
public static readonly SymbolDisplayFormat NameFormat = SymbolDisplayFormat.CSharpErrorMessageFormat
.RemoveMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
///
/// Returns 's name as matched against the types something has a converter for.
///
public static string NameOf(ITypeSymbol type)
{
return type.ToDisplayString(NameFormat);
}
public static ITypeSymbol WithoutNullable(ITypeSymbol type)
{
var named = type as INamedTypeSymbol;
if (named != null && named.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
{
return named.TypeArguments[0];
}
return type;
}
///
/// Returns the name 's definition has as a runtime Type's FullName, or null for a type
/// that name can't match, such as one nested in another.
///
public static string FullMetadataNameOf(ITypeSymbol type)
{
var definition = type.OriginalDefinition as INamedTypeSymbol;
if (definition == null || definition.ContainingType != null || definition.ContainingNamespace == null)
{
return null;
}
if (definition.ContainingNamespace.IsGlobalNamespace)
{
return definition.MetadataName;
}
return definition.ContainingNamespace.ToDisplayString() + "." + definition.MetadataName;
}
///
/// Returns true if Vellum's SavedValue saves itself, or a Nullable of one.
///
public static bool IsBuiltIn(ITypeSymbol type)
{
type = WithoutNullable(type);
string name = FullMetadataNameOf(type);
return type.TypeKind == TypeKind.Enum || (name != null && BuiltInNames.Contains(name));
}
///
/// Returns true for an array, and for the generic collections SaveConverters saves itself whatever they
/// hold.
///
public static bool IsBuiltInCollection(ITypeSymbol type)
{
if (type is IArrayTypeSymbol)
{
return true;
}
string name = FullMetadataNameOf(type);
return name != null && BuiltInCollectionNames.Contains(name);
}
///
/// Returns true if , what a generic converter converts, is a generic type Vellum
/// already saves itself.
///
public static bool IsBuiltInGeneric(ITypeSymbol converted)
{
return converted.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T
|| IsBuiltInCollection(converted);
}
///
/// Returns the type arguments of , those of the types it's nested in first.
///
public static List ArgumentsOf(INamedTypeSymbol type)
{
var returnMe = new List();
for (INamedTypeSymbol current = type; current != null; current = current.ContainingType)
{
returnMe.InsertRange(0, current.TypeArguments);
}
return returnMe;
}
}
}