void CollectObjectSegment(INamedTypeSymbol type, bool fromNullable, bool asKey) { if (type == null) { return; } if (!alreadyCollected.Add(Tuple.Create(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), fromNullable, asKey))) { return; } if (KnownFormatterSpec.IsPrimitive(type)) { return; } if (type.GetAttributes().FindAttributeShortName(UnionAttributeShortName) != null) { return; } if (type.GetAttributes().FindAttributeShortName(DynamicUnionAttributeShortName) != null) { return; } if (type.TypeKind == TypeKind.Enum) { CollectEnum(type, fromNullable, asKey); return; } if (allowCustomTypes.Contains(type.ToDisplayString())) { return; } if (disallowInMetadata && type.Locations[0].IsInMetadata) { return; } if (!IsAllowType(type)) { return; } if (type.IsGenericType) { var genericType = type.ConstructUnboundGenericType(); var genericTypeString = genericType.ToDisplayString(); if (genericTypeString == "T?") { CollectObjectSegment(type.TypeArguments[0] as INamedTypeSymbol, true, asKey); return; } else if (allowCustomTypes.Contains(genericTypeString)) { foreach (var t in type.TypeArguments) { CollectObjectSegment(t as INamedTypeSymbol, fromNullable, asKey); } return; } else if (genericTypeString == "System.Collections.Generic.IList<>" || genericTypeString == "System.Collections.Generic.IDictionary<,>" || genericTypeString == "System.Collections.Generic.Dictionary<,>" || genericTypeString == "ZeroFormatter.ILazyDictionary<,>" || genericTypeString == "System.Collections.Generic.IReadOnlyList<>" || genericTypeString == "System.Collections.Generic.ICollection<>" || genericTypeString == "System.Collections.Generic.IEnumerable<>" || genericTypeString == "System.Collections.Generic.ISet<>" || genericTypeString == "ZeroFormatter.ILazyReadOnlyDictionary<,>" || genericTypeString == "System.Collections.ObjectModel.ReadOnlyCollection<>" || genericTypeString == "System.Linq.ILookup<,>" || genericTypeString == "ZeroFormatter.ILazyLookup<,>" || genericTypeString.StartsWith("System.Collections.Generic.KeyValuePair") || genericTypeString.StartsWith("ZeroFormatter.KeyTuple")) { var elementTypes = string.Join(", ", type.TypeArguments.Select(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); var isDictionaryKey = false; if (genericTypeString == "System.Collections.Generic.IList<>") { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.List, ElementTypes = elementTypes }); } else if (genericTypeString == "System.Collections.Generic.IReadOnlyList<>") { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.ReadOnlyList, ElementTypes = elementTypes }); } else if (genericTypeString == "System.Collections.Generic.IDictionary<,>" || genericTypeString == "System.Collections.Generic.Dictionary<,>") { isDictionaryKey = true; genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.Dictionary, ElementTypes = elementTypes }); } else if (genericTypeString == "ZeroFormatter.ILazyDictionary<,>") { isDictionaryKey = true; genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.LazyDictionary, ElementTypes = elementTypes }); } else if (genericTypeString == "ZeroFormatter.ILazyReadOnlyDictionary<,>") { isDictionaryKey = true; genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.LazyReadOnlyDictionary, ElementTypes = elementTypes }); } else if (genericTypeString == "ZeroFormatter.ILazyLookup<,>") { isDictionaryKey = true; genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.LazyLookup, ElementTypes = elementTypes }); } else if (genericTypeString == "System.Linq.ILookup<,>") { isDictionaryKey = true; genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.Lookup, ElementTypes = elementTypes }); } else if (genericTypeString.StartsWith("ZeroFormatter.KeyTuple")) { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.KeyTuple, ElementTypes = elementTypes }); } else if (genericTypeString.StartsWith("System.Collections.Generic.KeyValuePair")) { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.KeyValuePair, ElementTypes = elementTypes }); } else if (genericTypeString.StartsWith("System.Collections.Generic.ICollection<>")) { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.InterfaceCollection, ElementTypes = elementTypes }); } else if (genericTypeString.StartsWith("System.Collections.Generic.IEnumerable<>")) { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.Enumerable, ElementTypes = elementTypes }); } else if (genericTypeString.StartsWith("System.Collections.ObjectModel.ReadOnlyCollection<>")) { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.ReadOnlyCollection, ElementTypes = elementTypes }); } var argIndex = 0; foreach (var t in type.TypeArguments) { if (isDictionaryKey && argIndex == 0) { CollectObjectSegment(t as INamedTypeSymbol, fromNullable, true); } else { CollectObjectSegment(t as INamedTypeSymbol, fromNullable, asKey); } argIndex++; } return; } else if (type.AllInterfaces.Any(x => (x.IsGenericType ? x.ConstructUnboundGenericType().ToDisplayString() : "") == "System.Collections.Generic.ICollection<>")) { var elementTypes = string.Join(", ", type.TypeArguments.Select(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))) + ", " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.Collection, ElementTypes = elementTypes }); foreach (var t in type.TypeArguments) { CollectObjectSegment(t as INamedTypeSymbol, fromNullable, asKey); } return; } } if (type.GetAttributes().FindAttributeShortName(ZeroFormattableAttributeShortName) == null) { throw new Exception($"Type must be marked with ZeroFormattableAttribute. {type.Name}. Location:{type.Locations[0]}"); } if (!type.IsValueType) { if (!type.Constructors.Any(x => x.Parameters.Length == 0)) { throw new Exception($"Type must needs parameterless constructor. {type.Name}. Location:{type.Locations[0]}"); } } else { var indexes = new List <Tuple <int, IPropertySymbol> >(); foreach (var item in type.GetMembers().OfType <IPropertySymbol>()) { if (item.IsStatic) { continue; } if (item.ExplicitInterfaceImplementations.Length != 0) { continue; } var indexAttr = item.GetAttributes().FindAttributeShortName(IndexAttributeShortName); if (indexAttr != null) { var index = (int)indexAttr.ConstructorArguments[0].Value; indexes.Add(Tuple.Create(index, item)); } } indexes = indexes.OrderBy(x => x.Item1).ToList(); var expected = 0; foreach (var item in indexes) { if (item.Item1 != expected) { throw new Exception($"Struct index must be started with 0 and be sequential. Type: {type.Name}, InvalidIndex: {item.Item1}"); } expected++; } var foundConstructor = false; var ctors = (type as INamedTypeSymbol)?.Constructors; foreach (var ctor in ctors) { var isMatch = indexes.Select(x => x.Item2.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) .SequenceEqual(ctor.Parameters.Select(x => x.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); if (isMatch) { foundConstructor = true; } } if (!foundConstructor && indexes.Count != 0) { throw new Exception($"Struct needs full parameter constructor of index property types. Type: {type.Name}"); } } var list = new List <ObjectSegmentType.PropertyTuple>(); var definedIndexes = new HashSet <int>(); var distinctName = new HashSet <string>(); foreach (var property in type.GetAllMembers()) { if (!distinctName.Add(property.Name)) { continue; } if (property.IsStatic) { continue; } var propSymbol = property as IPropertySymbol; var fieldSymbol = property as IFieldSymbol; if ((propSymbol == null && fieldSymbol == null)) { continue; } if (property.DeclaredAccessibility != Accessibility.Public) { continue; } if (propSymbol != null && propSymbol.ExplicitInterfaceImplementations.Length != 0) { continue; } var attributes = property.GetAttributes(); if (attributes.FindAttributeShortName(IgnoreAttributeShortName) != null) { continue; } if (!type.IsValueType) { if (!property.IsVirtual) { if (property.IsOverride && !property.IsSealed) { // okay, it is override property. } else { throw new Exception($"Public property's accessor must be virtual. {type.Name}.{property.Name}. Location:{type.Locations[0]}"); } } } if (propSymbol != null && propSymbol.FindAttributeIncludeBasePropertyShortName(UnionKeyAttributeShortName) != null) { continue; } var indexAttr = attributes.FindAttributeShortName(IndexAttributeShortName); if (indexAttr == null || indexAttr.ConstructorArguments.Length == 0) { throw new Exception($"Public property must be marked with IndexAttribute or IgnoreFormatAttribute. {type.Name}.{property.Name}. Location:{type.Locations[0]}"); } var index = indexAttr.ConstructorArguments[0]; if (index.IsNull) { continue; // null is normal compiler error. } if (!definedIndexes.Add((int)index.Value)) { throw new Exception($"IndexAttribute is not allow duplicate number. {type.Name}.{property.Name}, Index:{index.Value} Location:{type.Locations[0]}"); } if (!type.IsValueType) { if (propSymbol == null) { throw new Exception($"Class does not allow that field marks IndexAttribute. {type.Name }{property.Name}"); } if (propSymbol.GetMethod == null || propSymbol.SetMethod == null || propSymbol.GetMethod.DeclaredAccessibility == Accessibility.Private || propSymbol.SetMethod.DeclaredAccessibility == Accessibility.Private) { throw new Exception($"Public property must needs both public/protected get and set accessor. {type.Name}.{property.Name}. Location:{type.Locations[0]}"); } } var memberType = (propSymbol != null) ? propSymbol.Type : fieldSymbol.Type; if (memberType.TypeKind == TypeKind.Array) { var array = memberType as IArrayTypeSymbol; while (array != null) { var t = array.ElementType; if (!KnownFormatterSpec.AllowArrayType(t)) { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.Array, ElementTypes = t.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) }); } var namedType = t as INamedTypeSymbol; if (namedType != null && namedType.Kind != SymbolKind.ArrayType) // if <T> is unnamed type, it can't analyze. { // Recursive CollectObjectSegment(namedType, fromNullable, asKey); break; } array = t as IArrayTypeSymbol; } } else { var namedType = memberType as INamedTypeSymbol; if (namedType != null) // if <T> is unnamed type, it can't analyze. { // Recursive CollectObjectSegment(namedType, fromNullable, asKey); } } var length = KnownFormatterSpec.GetLength(memberType); var prop = new ObjectSegmentType.PropertyTuple { Name = property.Name, Type = memberType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), Index = (int)index.Value, IsGetProtected = (propSymbol != null) ? propSymbol?.GetMethod?.DeclaredAccessibility == Accessibility.Protected : false, IsSetProtected = (propSymbol != null) ? propSymbol?.SetMethod?.DeclaredAccessibility == Accessibility.Protected : false, FixedSize = length ?? 0, IsProperty = propSymbol != null, IsCacheSegment = !KnownFormatterSpec.IsLazySegment(memberType), IsFixedSize = (length != null) }; list.Add(prop); } var segment = new ObjectSegmentType { Name = type.ToDisplayString(typeNameFormat).Replace(".", "_"), FullName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), Namespace = type.ContainingNamespace.IsGlobalNamespace ? null : type.ContainingNamespace.ToDisplayString(), LastIndex = list.Select(x => x.Index).DefaultIfEmpty(0).Max(), Properties = list.OrderBy(x => x.Index).ToArray(), }; if (type.IsValueType) { structContainer.Add(segment); } else { objectContainer.Add(segment); } }
void CollectObjectSegment(INamedTypeSymbol type) { if (type == null) { return; } if (!alreadyCollected.Add(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))) { return; } if (KnownFormatterSpec.IsPrimitive(type)) { return; } if (type.TypeKind == TypeKind.Enum) { CollectEnum(type); return; } if (type.IsGenericType) { var genericType = type.ConstructUnboundGenericType(); var genericTypeString = genericType.ToDisplayString(); if (genericTypeString == "T?") { CollectObjectSegment(type.TypeArguments[0] as INamedTypeSymbol); return; } else if (genericTypeString == "System.Collections.Generic.List<>") { throw new Exception($"List does not support in ZeroFormatter because List have to deserialize all objects. You can use IList<T> instead of List. {type.Name}."); } else if (genericTypeString == "System.Collections.Generic.Dictionary<,>") { throw new Exception($"Dictionary does not support in ZeroFormatter because Dictionary have to deserialize all objects. You can use IDictionary<TK, TV> instead of Dictionary. {type.Name}."); } else if (genericTypeString == "System.Collections.Generic.IList<>" || genericTypeString == "System.Collections.Generic.IDictionary<,>" || genericTypeString == "ZeroFormatter.ILazyDictionary<,>" || genericTypeString == "System.Collections.Generic.IReadOnlyList<>" || genericTypeString == "System.Collections.Generic.IReadOnlyDictionary<,>" || genericTypeString == "ZeroFormatter.ILazyReadOnlyDictionary<,>" || genericTypeString == "System.Linq.ILookup<,>" || genericTypeString == "ZeroFormatter.ILazyLookup<,>" || genericTypeString.StartsWith("ZeroFormatter.KeyTuple")) { var elementTypes = string.Join(", ", type.TypeArguments.Select(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); if (genericTypeString == "System.Collections.Generic.IList<>") { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.List, ElementTypes = elementTypes }); } else if (genericTypeString == "System.Collections.Generic.IReadOnlyList<>") { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.ReadOnlyList, ElementTypes = elementTypes }); } else if (genericTypeString == "System.Collections.Generic.IDictionary<,>") { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.Dictionary, ElementTypes = elementTypes }); } else if (genericTypeString == "System.Collections.Generic.IReadOnlyDictionary<,>") { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.ReadOnlyDictionary, ElementTypes = elementTypes }); } else if (genericTypeString == "ZeroFormatter.ILazyDictionary<,>") { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.LazyDictionary, ElementTypes = elementTypes }); } else if (genericTypeString == "ZeroFormatter.ILazyReadOnlyDictionary<,>") { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.LazyReadOnlyDictionary, ElementTypes = elementTypes }); } else if (genericTypeString == "ZeroFormatter.ILazyLookup<,>") { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.LazyLookup, ElementTypes = elementTypes }); } else if (genericTypeString == "System.Linq.ILookup<,>") { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.Lookup, ElementTypes = elementTypes }); } else if (genericTypeString.StartsWith("ZeroFormatter.KeyTuple")) { genericTypeContainer.Add(new GenericType { TypeKind = GenericTypeKind.KeyTuple, ElementTypes = elementTypes }); } foreach (var t in type.TypeArguments) { CollectObjectSegment(t as INamedTypeSymbol); } return; } } if (type.GetAttributes().FindAttributeShortName(ZeroFormattableAttributeShortName) == null) { throw new Exception($"Type must mark ZeroFormattableAttribute. {type.Name}. Location:{type.Locations[0]}"); } if (!type.IsValueType) { if (!type.Constructors.Any(x => x.Parameters.Length == 0)) { throw new Exception($"Type must needs parameterless constructor. {type.Name}. Location:{type.Locations[0]}"); } } else { var indexes = new List <Tuple <int, IPropertySymbol> >(); foreach (var item in type.GetMembers().OfType <IPropertySymbol>()) { var indexAttr = item.GetAttributes().FindAttributeShortName(IndexAttributeShortName); if (indexAttr != null) { var index = (int)indexAttr.ConstructorArguments[0].Value; indexes.Add(Tuple.Create(index, item)); } } indexes = indexes.OrderBy(x => x.Item1).ToList(); var expected = 0; foreach (var item in indexes) { if (item.Item1 != expected) { throw new Exception($"Struct index must be started with 0 and be sequential. Type: {type.Name}, InvalidIndex: {item.Item1}"); } expected++; } var foundConstructor = false; var ctors = (type as INamedTypeSymbol)?.Constructors; foreach (var ctor in ctors) { var isMatch = indexes.Select(x => x.Item2.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) .SequenceEqual(ctor.Parameters.Select(x => x.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); if (isMatch) { foundConstructor = true; } } if (!foundConstructor && indexes.Count != 0) { throw new Exception($"Struct needs full parameter constructor of index property types. Type: {type.Name}"); } } var list = new List <ObjectSegmentType.PropertyTuple>(); var definedIndexes = new HashSet <int>(); foreach (var property in type.GetAllMembers()) { var propSymbol = property as IPropertySymbol; var fieldSymbol = property as IFieldSymbol; if ((propSymbol == null && fieldSymbol == null)) { continue; } if (property.DeclaredAccessibility != Accessibility.Public) { continue; } var attributes = property.GetAttributes(); if (attributes.FindAttributeShortName(IgnoreAttributeShortName) != null) { continue; } if (!type.IsValueType) { if (!property.IsVirtual) { throw new Exception($"Public property's accessor must be virtual. {type.Name}.{property.Name}. Location:{type.Locations[0]}"); } } var indexAttr = attributes.FindAttributeShortName(IndexAttributeShortName); if (indexAttr == null || indexAttr.ConstructorArguments.Length == 0) { throw new Exception($"Public property must mark IndexAttribute or IgnoreFormatAttribute. {type.Name}.{property.Name}. Location:{type.Locations[0]}"); } var index = indexAttr.ConstructorArguments[0]; if (index.IsNull) { continue; // null is normal compiler error. } if (!definedIndexes.Add((int)index.Value)) { throw new Exception($"IndexAttribute can not allow duplicate. {type.Name}.{property.Name}, Index:{index.Value} Location:{type.Locations[0]}"); } if (!type.IsValueType) { if (propSymbol == null) { throw new Exception($"Class does not allow that field marks IndexAttribute. {type.Name }{property.Name}"); } if (propSymbol.GetMethod == null || propSymbol.SetMethod == null || propSymbol.GetMethod.DeclaredAccessibility == Accessibility.Private || propSymbol.SetMethod.DeclaredAccessibility == Accessibility.Private) { throw new Exception($"Public property's accessor must needs both public/protected get and set. {type.Name}.{property.Name}. Location:{type.Locations[0]}"); } } var memberType = (propSymbol != null) ? propSymbol.Type : fieldSymbol.Type; if (memberType.TypeKind == TypeKind.Array) { var array = memberType as IArrayTypeSymbol; var t = array.ElementType; if (t.SpecialType != SpecialType.System_Byte) // allows byte[] { throw new Exception($"Array does not support in ZeroFormatter(except byte[]) because Array have to deserialize all objects. You can use IList<T> instead of T[]. {type.Name}.{property.Name}. Location:{type.Locations[0]}"); } } else { var namedType = memberType as INamedTypeSymbol; if (namedType != null) // if <T> is unnamed type, it can't analyze. { // Recursive CollectObjectSegment(namedType); } } var length = KnownFormatterSpec.GetLength(memberType); var prop = new ObjectSegmentType.PropertyTuple { Name = property.Name, Type = memberType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), Index = (int)index.Value, IsGetProtected = (propSymbol != null) ? propSymbol.GetMethod.DeclaredAccessibility == Accessibility.Protected : false, IsSetProtected = (propSymbol != null) ? propSymbol.SetMethod.DeclaredAccessibility == Accessibility.Protected : false, FixedSize = length ?? 0, IsProperty = propSymbol != null, IsCacheSegment = KnownFormatterSpec.CanAcceptCacheSegment(memberType), IsFixedSize = (length != null) }; list.Add(prop); } var segment = new ObjectSegmentType { Name = type.Name, FullName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), Namespace = type.ContainingNamespace.IsGlobalNamespace ? null : type.ContainingNamespace.ToDisplayString(), LastIndex = list.Select(x => x.Index).DefaultIfEmpty(0).Max(), Properties = list.OrderBy(x => x.Index).ToArray(), }; if (type.IsValueType) { structContainer.Add(segment); } else { objectContainer.Add(segment); } }