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