using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using UnitTestSharp; namespace Vellum.Generator.UnitTests { public class TypeShapeTests : TestFixture { /// /// Holds generic [Saveable] types: one with a [NoSave] argument, one that holds its argument in a collection, /// one that holds itself, and one that holds a larger version of itself in two ways. /// public const string GenericSaveables = "[Saveable] public partial struct Tagged " + "{ public ValueType Value; [NoSave] public TagType Tag; }\n" + "[Saveable] public partial struct Bag { public List Items; }\n" + "[Saveable] public sealed partial class Link " + "{ public Link Next; public ItemType Item; }\n" + "[Saveable] public sealed partial class Swell " + "{ public Swell> Left; public Swell> Right; }\n"; public class CtorTests : TestFixture { public void KeepsTheDisplayAndTheParts() { var part = new TypePart("Foo", null, PartKind.Plain, 1, 0); var shape = new TypeShape("Foo?", new EquatableArray( System.Collections.Immutable.ImmutableArray.Create(part))); CheckEqual("Foo?", shape.Display); CheckEqual(part, shape.Parts.Items.Single()); } } public class OfTests : TestFixture { public static readonly CSharpCompilation Types = VellumRun.Compile(("Types.cs", ConverterScanTests.Box + ConverterScanTests.Point + "public class Foo { }\npublic class Kid : Foo { }\n" + "[Saveable] public partial struct Marked { }\n" + "[Saveable] public partial class Base { }\npublic partial class Derived : Base { }\n" + "public class Holder { public Box Lost; public ItemType Item; }\n" + "public class Outer { public class Inner { } }\n" + "public class Crate : Box { }\n" + GenericSaveables)); public static INamedTypeSymbol Named(string metadataName) { return Types.GetTypeByMetadataName(metadataName); } public static ITypeSymbol Special(SpecialType type) { return Types.GetSpecialType(type); } public static ITypeSymbol BoxOf(ITypeSymbol argument) { return Named("Box`1").Construct(argument); } public static List Described(TypeShape shape) { return shape.Parts.Items.Select(part => part.Name + ":" + part.Kind + ":" + part.FirstChild + "+" + part.ChildCount).ToList(); } public void Display_IsTheTypeAsWritten() { INamedTypeSymbol nullable = Types.GetSpecialType(SpecialType.System_Nullable_T); CheckEqual("Point?", TypeShape.Of(nullable.Construct(Named("Point"))).Display); } public void NullableType_IsLookedThroughInItsParts() { INamedTypeSymbol nullable = Types.GetSpecialType(SpecialType.System_Nullable_T); CheckEqual(new[] { "Point:Plain:1+0" }, Described(TypeShape.Of(nullable.Construct(Named("Point"))))); } public void BuiltInAndMarkedTypes_AreWhole() { CheckEqual(new[] { "int:Whole:1+0" }, Described(TypeShape.Of(Special(SpecialType.System_Int32)))); CheckEqual(new[] { "Marked:Whole:1+0" }, Described(TypeShape.Of(Named("Marked")))); } public void ClassBelowAMarkedOne_IsWhole() { CheckEqual(new[] { "Derived:Whole:1+0" }, Described(TypeShape.Of(Named("Derived")))); } public void TypeParameterAndErrorType_AreIgnored() { ITypeSymbol lost = ((IFieldSymbol)Named("Holder`1").GetMembers("Lost").Single()).Type; ITypeSymbol item = ((IFieldSymbol)Named("Holder`1").GetMembers("Item").Single()).Type; CheckEqual(new[] { "ItemType:Ignored:1+0" }, Described(TypeShape.Of(item))); CheckEqual(new[] { "Box:Generic:1+1", "Missing:Ignored:2+0" }, Described(TypeShape.Of(lost))); } public void TypeNothingKnowsAbout_IsPlain() { CheckEqual(new[] { "Foo:Plain:1+0" }, Described(TypeShape.Of(Named("Foo")))); } public void GenericType_HasItsArgumentsAsParts() { CheckEqual(new[] { "Box:Generic:1+1", "Foo:Plain:2+0" }, Described(TypeShape.Of(BoxOf(Named("Foo"))))); CheckEqual("Box", TypeShape.Of(BoxOf(Named("Foo"))).Parts.Items[0].OpenName); } public void PlainAndGenericClasses_ListTheirNameAndTheirBaseClassesByGenericType() { TypeShape shape = TypeShape.Of(BoxOf(Named("Kid"))); CheckEqual(new[] { "Box" }, shape.Parts.Items[0].FamilyNames.Items.ToArray()); CheckEqual(new[] { "Kid", "Foo" }, shape.Parts.Items[1].FamilyNames.Items.ToArray()); } public void GenericClassAndItsGenericBases_ListWhereTheirArgumentsAre() { TypeShape shape = TypeShape.Of(Named("Crate`1").Construct(Named("Foo"))); TypePart crate = shape.Parts.Items[0]; CheckEqual(new[] { "Box", "Crate" }.OrderBy(name => name).ToArray(), crate.BaseArguments.Items.Select(arguments => arguments.Name).OrderBy(name => name).ToArray()); BaseArguments box = crate.BaseArguments.Items.Single(arguments => arguments.Name == "Box"); CheckEqual("Foo", shape.Parts.Items[box.FirstChild].Name); CheckEqual(1, box.ChildCount); } public void ClassWithNoGenericBase_ListsNoBaseArguments() { CheckEqual(0, TypeShape.Of(Named("Kid")).Parts.Items[0].BaseArguments.Items.Length); } public void StructAndBuiltInParts_HaveNoFamilyNames() { CheckEqual(0, TypeShape.Of(Named("Point")).Parts.Items[0].FamilyNames.Items.Length); CheckEqual(0, TypeShape.Of(Special(SpecialType.System_Int32)).Parts.Items[0].FamilyNames.Items.Length); } public void NonGenericType_HasNoOpenName() { CheckNull(TypeShape.Of(Named("Foo")).Parts.Items[0].OpenName); } public void OneDimensionalArrayAndBuiltInCollections_AreMadeOfTheirParts() { ITypeSymbol list = Types.GetTypeByMetadataName("System.Collections.Generic.List`1") .Construct(Named("Foo")); ITypeSymbol dictionary = Types.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2") .Construct(Special(SpecialType.System_String), Named("Foo")); CheckEqual(new[] { "Foo[]:Collection:1+1", "Foo:Plain:2+0" }, Described(TypeShape.Of(Types.CreateArrayTypeSymbol(Named("Foo"))))); CheckEqual(new[] { "System.Collections.Generic.List:Collection:1+1", "Foo:Plain:2+0" }, Described(TypeShape.Of(list))); CheckEqual(new[] { "System.Collections.Generic.Dictionary:Collection:1+2", "string:Whole:3+0", "Foo:Plain:3+0", }, Described(TypeShape.Of(dictionary))); } public void ArrayOfTwoOrThreeDimensions_IsACollection() { ITypeSymbol flat = Types.CreateArrayTypeSymbol(Named("Foo"), 2); ITypeSymbol cube = Types.CreateArrayTypeSymbol(Named("Foo"), 3); CheckEqual(new[] { "Foo[*,*]:Collection:1+1", "Foo:Plain:2+0" }, Described(TypeShape.Of(flat))); CheckEqual(new[] { "Foo[*,*,*]:Collection:1+1", "Foo:Plain:2+0" }, Described(TypeShape.Of(cube))); } public void PartsOfOneType_SitTogetherAfterThePartsBeforeIt() { ITypeSymbol nested = BoxOf(BoxOf(Named("Foo"))); CheckEqual(new[] { "Box>:Generic:1+1", "Box:Generic:2+1", "Foo:Plain:3+0" }, Described(TypeShape.Of(nested))); } public void TypeNestedInAGenericType_HasTheArgumentsOfBothAsParts() { ITypeSymbol inner = Named("Outer`1").Construct(Named("Foo")).GetTypeMembers("Inner", 1).Single() .Construct(Named("Point")); CheckEqual(new[] { "Outer.Inner:Generic:1+2", "Foo:Plain:3+0", "Point:Plain:3+0" }, Described(TypeShape.Of(inner))); } public void GenericSaveable_HasTheTypesOfItsSavedMembersAsParts() { ITypeSymbol tagged = Named("Tagged`2").Construct(Named("Foo"), Named("Point")); ITypeSymbol bag = Named("Bag`1").Construct(Named("Foo")); CheckEqual(new[] { "Tagged:SavedMembers:1+1", "Foo:Plain:2+0" }, Described(TypeShape.Of(tagged))); CheckEqual(new[] { "Bag:SavedMembers:1+1", "System.Collections.Generic.List:Collection:2+1", "Foo:Plain:3+0", }, Described(TypeShape.Of(bag))); } public void NonGenericSaveable_IsWholeWithNoParts() { CheckEqual(new[] { "Marked:Whole:1+0" }, Described(TypeShape.Of(Named("Marked")))); } public void GenericSaveableHoldingItself_IsLookedIntoOnceAndTheRepeatIsWhole() { ITypeSymbol link = Named("Link`1").Construct(Named("Foo")); CheckEqual(new[] { "Link:SavedMembers:1+2", "Link:Whole:3+0", "Foo:Plain:3+0" }, Described(TypeShape.Of(link))); } public void GenericSaveableThatKeepsGrowing_StopsBeingLookedIntoAtThePartLimit() { TypeShape shape = TypeShape.Of(Named("Swell`1").Construct(Named("Foo"))); CheckTrue(shape.Parts.Items.Length <= TypeShape.MaxParts + 2); CheckTrue(shape.Parts.Items.Any(part => part.Kind == PartKind.Plain && part.Name.StartsWith("Swell<"))); } public void TypePastTheDepthLimit_HasNoPartsBeyondIt() { ITypeSymbol deepest = Special(SpecialType.System_Int32); for (int i = 0; i < TypeShape.MaxDepth + 5; ++i) { deepest = BoxOf(deepest); } TypeShape shape = TypeShape.Of(deepest); CheckEqual(TypeShape.MaxDepth + 1, shape.Parts.Items.Length); CheckEqual(0, shape.Parts.Items[TypeShape.MaxDepth].ChildCount); CheckEqual(1, shape.Parts.Items[TypeShape.MaxDepth - 1].ChildCount); } } public class FindUnsaveableTests : TestFixture { public const string Sources = ConverterScanTests.Point + ConverterScanTests.Box + "public class Foo { }\npublic struct Bar { }\n" + "public class Holder { public Box Lost; }\n" + "[Saveable] public partial struct Marked { }\n"; public static readonly CSharpCompilation Types = VellumRun.Compile(("Types.cs", Sources + ConverterScanTests.Converter("PointConverter", "Point") + ConverterScanTests.Converter("BoxConverter", "Box") + "public class Pair { }\n" + ConverterScanTests.Converter("PairConverter", "Pair") + GenericSaveables)); public static readonly ConverterList Saveable = ConverterScanTests.Saved(Types); public static INamedTypeSymbol Named(string metadataName) { return Types.GetTypeByMetadataName(metadataName); } public static ITypeSymbol BoxOf(ITypeSymbol argument) { return Named("Box`1").Construct(argument); } public static ITypeSymbol Special(SpecialType type) { return Types.GetSpecialType(type); } public static ITypeSymbol FieldType(string holder, string field) { return ((IFieldSymbol)Named(holder).GetMembers(field).Single()).Type; } /// /// Returns how C# spells the part of that can't be saved, or null if all of it can. /// public static string Unsaveable(ConverterList converters, ITypeSymbol type) { TypeShape shape = TypeShape.Of(type); int found = shape.FindUnsaveable(converters.ConvertedNames(), converters.FamilyBaseNames(), converters.SaveableArguments()); if (found < 0) { return null; } return shape.Parts.Items[found].Name; } public static string Unsaveable(ITypeSymbol type) { return Unsaveable(Saveable, type); } public static readonly CSharpCompilation FamilyTypes = VellumRun.Compile(("Families.cs", "public class Creature { }\npublic class Wolf : Creature { }\npublic class Fish { }\n" + "public class Herd { }\npublic class NameHerd : Herd { }\n" + "public class Crate { }\npublic struct Egg { }\n" + ConverterScanTests.Family("CreatureConverter", "CreatureType", "Creature") + ConverterScanTests.Family("HerdConverter", "HerdType", "Herd"))); public static readonly ConverterList Families = ConverterScanTests.Saved(FamilyTypes); public static INamedTypeSymbol FamilyType(string metadataName) { return FamilyTypes.GetTypeByMetadataName(metadataName); } public static readonly CSharpCompilation MarkedTypes = VellumRun.Compile(("Marked.cs", ConverterScanTests.Point + ConverterScanTests.Box + "public class Foo { }\n" + "public class Pair { }\npublic class Open { }\n" + ConverterScanTests.Converter("PointConverter", "Point") + ConverterScanTests.Converter("BoxConverter", "Box") + ConverterScanTests.Converter("PairConverter", "Pair", marked: false).Replace(" :", " :") + ConverterScanTests.Converter("OpenConverter", "Open", marked: false))); public static readonly ConverterList MarkedSaveable = ConverterScanTests.Saved(MarkedTypes); public static INamedTypeSymbol MarkedType(string metadataName) { return MarkedTypes.GetTypeByMetadataName(metadataName); } public void MarkedArgument_MustBeSaveable() { CheckNull(Unsaveable(MarkedSaveable, MarkedType("Box`1").Construct(MarkedType("Point")))); CheckEqual("Foo", Unsaveable(MarkedSaveable, MarkedType("Box`1").Construct(MarkedType("Foo")))); } public void UnmarkedArgument_CanBeAnything() { CheckNull(Unsaveable(MarkedSaveable, MarkedType("Open`1").Construct(MarkedType("Foo")))); } public void OnlyTheMarkedArguments_AreChecked() { INamedTypeSymbol pair = MarkedType("Pair`2"); CheckNull(Unsaveable(MarkedSaveable, pair.Construct(MarkedType("Foo"), MarkedType("Point")))); CheckEqual("Foo", Unsaveable(MarkedSaveable, pair.Construct(MarkedType("Point"), MarkedType("Foo")))); } public void MarkedArgumentMadeOfAnUnmarkedGenericType_IsSaveable() { INamedTypeSymbol box = MarkedType("Box`1"); CheckNull(Unsaveable(MarkedSaveable, box.Construct(MarkedType("Open`1").Construct(MarkedType("Foo"))))); } public static readonly CSharpCompilation MarkedFamilyTypes = VellumRun.Compile(("MarkedFamilies.cs", "public class Plankton { }\npublic class Herd { }\n" + "public class IntHerd : Herd { }\npublic class PlanktonHerd : Herd { }\n" + "public class Flock : Herd { }\npublic class Mid : Herd { }\n" + "public class DeepHerd : Mid { }\n" + "public class Duo { }\npublic class FirstDuo : Duo { }\n" + "public class SecondDuo : Duo { }\n" + ConverterScanTests.Family("HerdConverter", "HerdType", "Herd") + ConverterScanTests.Family("DuoConverter", "DuoType", "Duo"))); public static readonly ConverterList MarkedFamilies = ConverterScanTests.Saved(MarkedFamilyTypes); public static INamedTypeSymbol MarkedFamilyType(string metadataName) { return MarkedFamilyTypes.GetTypeByMetadataName(metadataName); } public static readonly CSharpCompilation InterfaceFamilyTypes = VellumRun.Compile(("InterfaceFamilies.cs", "public class Plankton { }\npublic class Plain { }\n" + ConverterScanTests.DescribeTests.Pets + "public class Hound : IDog { }\npublic struct Pup : IPet { }\n" + "public class Fishbowl : IBowl { }\npublic class IntBowl : IBowl { }\n" + "public class Wolf : Plain, IBowl { }\n" + ConverterScanTests.Family("PetConverter", "PetType", "IPet") + ConverterScanTests.Family("BowlConverter", "BowlType", "IBowl"))); public static readonly ConverterList InterfaceFamilies = ConverterScanTests.Saved(InterfaceFamilyTypes); public static INamedTypeSymbol InterfaceFamilyType(string metadataName) { return InterfaceFamilyTypes.GetTypeByMetadataName(metadataName); } public void TypeImplementingAnInterfaceAFamilySaves_IsNull() { CheckNull(Unsaveable(InterfaceFamilies, InterfaceFamilyType("Hound"))); CheckNull(Unsaveable(InterfaceFamilies, InterfaceFamilyType("Pup"))); } public void InterfaceTypedMemberAFamilySaves_IsNull() { CheckNull(Unsaveable(InterfaceFamilies, InterfaceFamilyType("IPet"))); CheckNull(Unsaveable(InterfaceFamilies, InterfaceFamilyType("IDog"))); } public void TypeImplementingNoFamilysInterface_IsItself() { CheckEqual("Plain", Unsaveable(InterfaceFamilies, InterfaceFamilyType("Plain"))); } public void InterfaceFamilysMarkedArgument_MustBeSaveable() { CheckEqual("Plankton", Unsaveable(InterfaceFamilies, InterfaceFamilyType("Fishbowl"))); CheckEqual("Plankton", Unsaveable(InterfaceFamilies, InterfaceFamilyType("Wolf"))); CheckNull(Unsaveable(InterfaceFamilies, InterfaceFamilyType("IntBowl"))); } public void InterfaceTypedMemberOfAMarkedGenericInterface_ChecksItsArgument() { INamedTypeSymbol bowl = InterfaceFamilyType("IBowl`1"); CheckEqual("Plankton", Unsaveable(InterfaceFamilies, bowl.Construct(InterfaceFamilyType("Plankton")))); CheckNull(Unsaveable(InterfaceFamilies, bowl.Construct(InterfaceFamilyTypes.GetSpecialType(SpecialType.System_Int32)))); } public void FamilyBaseArgumentTheFamilyMarksAndNothingSaves_IsThatArgument() { CheckEqual("Plankton", Unsaveable(MarkedFamilies, MarkedFamilyType("PlanktonHerd"))); CheckEqual("Plankton", Unsaveable(MarkedFamilies, MarkedFamilyType("Herd`1") .Construct(MarkedFamilyType("Plankton")))); } public void FamilyBaseArgumentTheFamilyMarksAndSomethingSaves_IsNull() { CheckNull(Unsaveable(MarkedFamilies, MarkedFamilyType("IntHerd"))); CheckNull(Unsaveable(MarkedFamilies, MarkedFamilyType("Herd`1").Construct( MarkedFamilyTypes.GetSpecialType(SpecialType.System_Int32)))); } public void OnlyTheBaseArgumentsTheFamilyMarks_AreChecked() { CheckNull(Unsaveable(MarkedFamilies, MarkedFamilyType("FirstDuo"))); CheckEqual("Plankton", Unsaveable(MarkedFamilies, MarkedFamilyType("SecondDuo"))); } public void BaseClassBetweenTheTypeAndTheFamilysBase_IsLookedThrough() { CheckEqual("Plankton", Unsaveable(MarkedFamilies, MarkedFamilyType("DeepHerd"))); } public void TypeHoldingItselfThroughTheFamilysBase_IsSaveable() { CheckNull(Unsaveable(MarkedFamilies, MarkedFamilyType("Flock"))); } public void TypeDerivedFromAFamilysBase_IsNull() { CheckNull(Unsaveable(Families, FamilyType("Creature"))); CheckNull(Unsaveable(Families, FamilyType("Wolf"))); CheckNull(Unsaveable(Families, FamilyType("NameHerd"))); } public void GenericBaseOfAFamily_IsNullWhateverItIsMadeOf() { CheckNull(Unsaveable(Families, FamilyType("Herd`1").Construct(FamilyType("Fish")))); } public void TypeDerivedFromAFamilysBase_IsNullInsideACollectionOrGenericType() { INamedTypeSymbol list = FamilyTypes.GetTypeByMetadataName("System.Collections.Generic.List`1"); CheckNull(Unsaveable(Families, list.Construct(FamilyType("Wolf")))); } public void TypeNoFamilyOrConverterSaves_IsItselfWithFamiliesAround() { CheckEqual("Fish", Unsaveable(Families, FamilyType("Fish"))); CheckEqual("Egg", Unsaveable(Families, FamilyType("Egg"))); CheckEqual("Crate", Unsaveable(Families, FamilyType("Crate`1").Construct(FamilyType("Wolf")))); } public void SaveableType_IsNull() { CheckNull(Unsaveable(Named("Point"))); CheckNull(Unsaveable(Named("Marked"))); CheckNull(Unsaveable(Special(SpecialType.System_Int32))); } public void TypeNothingSaves_IsItself() { CheckEqual("Foo", Unsaveable(Named("Foo"))); } public void GenericTypeOfSaveableArguments_IsNull() { CheckNull(Unsaveable(BoxOf(Special(SpecialType.System_Int32)))); CheckNull(Unsaveable(BoxOf(Named("Point")))); CheckNull(Unsaveable(BoxOf(BoxOf(Named("Marked"))))); } public void GenericTypeWhoseArgumentCantBeSaved_IsTheArgument() { CheckEqual("Foo", Unsaveable(BoxOf(Named("Foo")))); } public void GenericTypeNestedDeep_IsTheInnermostArgumentThatCantBeSaved() { CheckEqual("Foo", Unsaveable(BoxOf(BoxOf(BoxOf(Named("Foo")))))); } public void GenericTypeOfSeveralArguments_IsTheFirstThatCantBeSaved() { ITypeSymbol pair = Named("Pair`2").Construct(Named("Foo"), Special(SpecialType.System_Object)); CheckEqual("Foo", Unsaveable(pair)); CheckNull(Unsaveable(Named("Pair`2").Construct(Named("Point"), Named("Marked")))); } public void GenericTypeWithNoConverter_IsItself() { ITypeSymbol linked = Types.GetTypeByMetadataName("System.Collections.ObjectModel.Collection`1") .Construct(Special(SpecialType.System_Int32)); CheckEqual(linked.ToDisplayString(), Unsaveable(linked)); } public static ITypeSymbol ListOf(ITypeSymbol item) { return Types.GetTypeByMetadataName("System.Collections.Generic.List`1").Construct(item); } public static ITypeSymbol SetOf(ITypeSymbol item) { return Types.GetTypeByMetadataName("System.Collections.Generic.HashSet`1").Construct(item); } public static ITypeSymbol DictionaryOf(ITypeSymbol key, ITypeSymbol value) { return Types.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2").Construct(key, value); } public void CollectionsOfSaveableTypes_AreNull() { ITypeSymbol text = Special(SpecialType.System_String); CheckNull(Unsaveable(ListOf(Named("Point")))); CheckNull(Unsaveable(SetOf(text))); CheckNull(Unsaveable(DictionaryOf(text, BoxOf(Named("Marked"))))); CheckNull(Unsaveable(DictionaryOf(Named("Point"), ListOf(SetOf(text))))); } public void ListOrSetOfTypeThatCantBeSaved_IsTheElement() { CheckEqual("Foo", Unsaveable(ListOf(Named("Foo")))); CheckEqual("Foo", Unsaveable(SetOf(Named("Foo")))); } public void DictionaryWhoseKeyOrValueCantBeSaved_IsTheFirstThatCant() { ITypeSymbol foo = Named("Foo"); ITypeSymbol bar = Named("Bar"); ITypeSymbol text = Special(SpecialType.System_String); CheckEqual("Foo", Unsaveable(DictionaryOf(foo, text))); CheckEqual("Bar", Unsaveable(DictionaryOf(text, bar))); CheckEqual("Foo", Unsaveable(DictionaryOf(foo, bar))); } public void CollectionNestedInCollections_IsTheInnermostElementThatCantBeSaved() { ITypeSymbol nested = DictionaryOf(Special(SpecialType.System_String), ListOf(BoxOf(Named("Foo")))); CheckEqual("Foo", Unsaveable(nested)); } public void NullableOfASaveableType_IsLookedThrough() { INamedTypeSymbol nullable = Types.GetSpecialType(SpecialType.System_Nullable_T); CheckNull(Unsaveable(nullable.Construct(Named("Point")))); } public void NullableOfATypeThatCantBeSaved_IsTheType() { INamedTypeSymbol nullable = Types.GetSpecialType(SpecialType.System_Nullable_T); CheckEqual("Bar", Unsaveable(nullable.Construct(Named("Bar")))); } public void ArrayOfSaveableType_IsNull() { CheckNull(Unsaveable(Types.CreateArrayTypeSymbol(Named("Point")))); CheckNull(Unsaveable( Types.CreateArrayTypeSymbol(Types.CreateArrayTypeSymbol(Special(SpecialType.System_Int32))))); } public void ArrayOfTypeThatCantBeSaved_IsTheElement() { CheckEqual("Foo", Unsaveable(Types.CreateArrayTypeSymbol(Named("Foo")))); } public void ArrayOfAnyNumberOfDimensions_IsLookedInto() { CheckNull(Unsaveable(Types.CreateArrayTypeSymbol(Named("Point"), 2))); CheckNull(Unsaveable(Types.CreateArrayTypeSymbol(Named("Point"), 5))); CheckEqual("Foo", Unsaveable(Types.CreateArrayTypeSymbol(Named("Foo"), 2))); } public void LinkedListAndSortedCollectionsOfTypeThatCantBeSaved_AreTheElement() { ITypeSymbol foo = Named("Foo"); ITypeSymbol text = Special(SpecialType.System_String); CheckEqual("Foo", Unsaveable(Types.GetTypeByMetadataName("System.Collections.Generic.LinkedList`1") .Construct(foo))); CheckEqual("Foo", Unsaveable(Types.GetTypeByMetadataName("System.Collections.Generic.SortedSet`1") .Construct(foo))); CheckEqual("Foo", Unsaveable(Types.GetTypeByMetadataName("System.Collections.Generic.SortedDictionary`2") .Construct(text, foo))); CheckNull(Unsaveable(Types.GetTypeByMetadataName("System.Collections.Generic.SortedList`2") .Construct(text, Named("Point")))); } public void QueueAndStackOfSaveableTypes_AreNull() { ITypeSymbol point = Named("Point"); CheckNull(Unsaveable(Types.GetTypeByMetadataName("System.Collections.Generic.Queue`1") .Construct(point))); CheckNull(Unsaveable(Types.GetTypeByMetadataName("System.Collections.Generic.Stack`1") .Construct(point))); } public void QueueOrStackOfTypeThatCantBeSaved_IsTheElement() { ITypeSymbol foo = Named("Foo"); CheckEqual("Foo", Unsaveable(Types.GetTypeByMetadataName("System.Collections.Generic.Queue`1") .Construct(foo))); CheckEqual("Foo", Unsaveable(Types.GetTypeByMetadataName("System.Collections.Generic.Stack`1") .Construct(foo))); } public void ArrayOfArraysOfListsOfSaveableTypes_IsNull() { ITypeSymbol lists = Types.CreateArrayTypeSymbol(Types.CreateArrayTypeSymbol(ListOf(Named("Point")))); CheckNull(Unsaveable(lists)); } public void GenericTypeNestedAsDeepAsRuntimeAllows_IsNull() { ITypeSymbol deepest = Special(SpecialType.System_Int32); for (int i = 0; i < TypeShape.MaxDepth; ++i) { deepest = BoxOf(deepest); } CheckNull(Unsaveable(deepest)); } public void GenericTypeNestedPastTheLimit_IsTheInnermostGenericType() { ITypeSymbol innermost = BoxOf(Special(SpecialType.System_Int32)); ITypeSymbol deepest = innermost; for (int i = 1; i < TypeShape.MaxDepth + 1; ++i) { deepest = BoxOf(deepest); } CheckEqual(innermost.ToDisplayString(), Unsaveable(deepest)); } public void GenericSaveableOfSaveableArguments_IsNull() { CheckNull(Unsaveable(Named("Tagged`2").Construct(Named("Point"), Named("Marked")))); CheckNull(Unsaveable(Named("Bag`1").Construct(Named("Point")))); } public void GenericSaveableWhoseUsedArgumentCantBeSaved_IsTheArgument() { CheckEqual("Foo", Unsaveable(Named("Tagged`2").Construct(Named("Foo"), Named("Point")))); CheckEqual("Foo", Unsaveable(Named("Bag`1").Construct(Named("Foo")))); CheckEqual("Foo", Unsaveable(BoxOf(Named("Bag`1").Construct(Named("Foo"))))); } public void GenericSaveableWhoseOnlyUserOfAnArgumentIsNoSave_NeedsNoSaveableArgument() { CheckNull(Unsaveable(Named("Tagged`2").Construct(Named("Point"), Named("Foo")))); CheckNull(Unsaveable(Named("Tagged`2").Construct(Named("Point"), Special(SpecialType.System_Object)))); } public void GenericSaveableHoldingItself_IsJudgedByItsArgument() { CheckNull(Unsaveable(Named("Link`1").Construct(Named("Point")))); CheckEqual("Foo", Unsaveable(Named("Link`1").Construct(Named("Foo")))); } public void GenericSaveableThatKeepsGrowing_IsItselfOrAPartOfIt() { CheckNotNull(Unsaveable(Named("Swell`1").Construct(Named("Point")))); } public void NullableOfAGenericSaveable_IsLookedThrough() { INamedTypeSymbol nullable = Types.GetSpecialType(SpecialType.System_Nullable_T); CheckEqual("Foo", Unsaveable(nullable.Construct(Named("Bag`1").Construct(Named("Foo"))))); } public void TypeParameterArgument_IsLeftAlone() { CheckNull(Unsaveable(Named("Box`1"))); } public void ErrorTypeArgument_IsLeftToItsOwnError() { CheckNull(Unsaveable(FieldType("Holder", "Lost"))); } public void ExactConverterOfAClosedGenericType_NeedsNoSaveableArguments() { CSharpCompilation types = VellumRun.Compile(("Types.cs", ConverterScanTests.Box + "public class Foo { }\n" + ConverterScanTests.Converter("BoxConverter", "Box") + ConverterScanTests.Converter("FooBoxConverter", "Box"))); ConverterList converters = ConverterScanTests.Saved(types); ITypeSymbol fooBox = types.GetTypeByMetadataName("Box`1").Construct(types.GetTypeByMetadataName("Foo")); CheckNull(Unsaveable(converters, fooBox)); } public void NullableReferenceTypeOfASaveableType_IsLookedThrough() { CSharpCompilation types = VellumRun.Compile(("Types.cs", "#nullable enable\n" + "public class Foo { }\n" + ConverterScanTests.Converter("FooConverter", "Foo") + "public class Holder { public Foo? Item; public List Items = new List(); }\n")); ConverterList converters = ConverterScanTests.Saved(types); INamedTypeSymbol holder = types.GetTypeByMetadataName("Holder"); CheckNull(Unsaveable(converters, ((IFieldSymbol)holder.GetMembers("Item").Single()).Type)); CheckNull(Unsaveable(converters, ((IFieldSymbol)holder.GetMembers("Items").Single()).Type)); } } public class UnsaveableDiagnosticTests : TestFixture { public static string MessageFor(ITypeSymbol saved, ITypeSymbol unsaveable) { TypeShape shape = TypeShape.Of(saved); int found = shape.Parts.Items.Select(part => part.Name).ToList() .IndexOf(SaveableTypes.WithoutNullable(unsaveable).ToDisplayString()); return shape.UnsaveableDiagnostic(Location.None, "stops", found).GetMessage(); } public void WholeType_NamesNoContext() { ITypeSymbol foo = FindUnsaveableTests.Named("Foo"); CheckEqual("'stops' is saved, but a Foo can't be saved. Mark it [Saveable], or write a " + "SaveConverter for it.", MessageFor(foo, foo)); } public void NullableOfTheWholeType_NamesNoContext() { INamedTypeSymbol nullable = FindUnsaveableTests.Types.GetSpecialType(SpecialType.System_Nullable_T); ITypeSymbol bar = FindUnsaveableTests.Named("Bar"); CheckEqual("'stops' is saved, but a Bar can't be saved. Mark it [Saveable], or write a " + "SaveConverter for it.", MessageFor(nullable.Construct(bar), bar)); } public void PartOfAType_NamesTheTypeItIsInside() { ITypeSymbol foo = FindUnsaveableTests.Named("Foo"); CheckEqual("'stops' is saved, but a Foo (inside Box) can't be saved. Mark it [Saveable], or " + "write a SaveConverter for it.", MessageFor(FindUnsaveableTests.BoxOf(foo), foo)); } } public class EqualsTests : TestFixture { public static TypeShape Of(string display, params TypePart[] parts) { return new TypeShape(display, new EquatableArray( System.Collections.Immutable.ImmutableArray.Create(parts))); } public static TypePart Part(string name = "Foo", string openName = null, PartKind kind = PartKind.Plain, int firstChild = 1, int childCount = 0) { return new TypePart(name, openName, kind, firstChild, childCount); } public void SameValues_AreEqual() { CheckTrue(Of("Foo", Part()).Equals(Of("Foo", Part()))); CheckTrue(Of("Foo", Part()).Equals((object)Of("Foo", Part()))); } public void Null_IsNotEqual() { CheckFalse(Of("Foo", Part()).Equals((TypeShape)null)); CheckFalse(Of("Foo", Part()).Equals((object)null)); } public void OtherType_IsNotEqual() { CheckFalse(Of("Foo", Part()).Equals("Foo")); } public void DifferentDisplay_IsNotEqual() { CheckFalse(Of("Foo", Part()).Equals(Of("Foo?", Part()))); } public void DifferentParts_AreNotEqual() { CheckFalse(Of("Foo", Part()).Equals(Of("Foo", Part(name: "Bar")))); } } public class GetHashCodeTests : TestFixture { public void EqualShapes_HaveEqualHashCodes() { CheckEqual(EqualsTests.Of("Foo", EqualsTests.Part()).GetHashCode(), EqualsTests.Of("Foo", EqualsTests.Part()).GetHashCode()); } } } }