using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; namespace Vellum.Generator { /// /// Describes a class that derives from others, as plain data, so it can be checked for a converter of its own. /// public sealed class UnsavedSubtype : IEquatable { public readonly string Name; /// /// Holds the class's name as matched against the types something has a converter for. /// public readonly string ConvertedName; /// /// Holds the name of each class it derives from, nearest first, as matched against the same. /// public readonly EquatableArray BaseNames; public readonly SourcePlace Place; /// /// Holds the name of the class and of each class it derives from, as matched against the base classes of /// the family converters. /// public readonly EquatableArray FamilyNames; public UnsavedSubtype(string name, string convertedName, EquatableArray baseNames, SourcePlace place, EquatableArray familyNames = default) { FamilyNames = familyNames; Name = name; ConvertedName = convertedName; BaseNames = baseNames; Place = place; } /// /// Returns as plain data, or null if it can never be a value that's saved: it is /// abstract or static, or derives from nothing but object. /// public static UnsavedSubtype Of(INamedTypeSymbol type) { if (type.IsAbstract || type.IsStatic || type.TypeKind != TypeKind.Class) { return null; } ImmutableArray.Builder baseNames = ImmutableArray.CreateBuilder(); for (INamedTypeSymbol current = type.BaseType; current != null && current.SpecialType != SpecialType.System_Object; current = current.BaseType) { baseNames.Add(SaveableTypes.NameOf(current)); // A generic converter is matched by the generic type it converts, not by each type made from it. if (current.IsGenericType) { baseNames.Add(SaveableTypes.NameOf(current.OriginalDefinition)); } } if (baseNames.Count == 0) { return null; } return new UnsavedSubtype(type.Name, SaveableTypes.NameOf(type), new EquatableArray(baseNames.ToImmutable()), SourcePlace.Of(type.Locations.FirstOrDefault()), SaveableTypes.FamilyNamesOf(type)); } /// /// Returns what is wrong with the class in , given the names /// of what converters convert and the base classes /// of the family converters, which save every class derived from them. /// public List Check(Compilation compilation, HashSet converted, Dictionary> families) { var returnMe = new List(); if (converted.Contains(ConvertedName) || FamilyNames.Items.Any(families.ContainsKey)) { return returnMe; } string saved = BaseNames.Items.FirstOrDefault(converted.Contains); if (saved != null) { returnMe.Add(Diagnostic.Create(VellumDiagnostics.UnsavedSubtype, Place.LocationIn(compilation), Name, saved)); } return returnMe; } public bool Equals(UnsavedSubtype other) { return other != null && Name == other.Name && ConvertedName == other.ConvertedName && BaseNames.Equals(other.BaseNames) && FamilyNames.Equals(other.FamilyNames) && Place.Equals(other.Place); } public override bool Equals(object obj) { return Equals(obj as UnsavedSubtype); } public override int GetHashCode() { return ConvertedName.GetHashCode() * 31 + BaseNames.GetHashCode(); } } }