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