public static void Make(ISymbol member, ITypeSymbol type, CodeWriter code, string name, Location location, ScopeTracker scope = null) { var disposable = scope = scope == null?code.Encapsulate() : scope.Reference(); using (disposable) { var nullable = type.NullableAnnotation == NullableAnnotation.Annotated; var hasUnderlying = false; if (nullable) { var underlying = GenerationEngine.GetNamedTypeSymbol(type).TypeArguments.FirstOrDefault(); hasUnderlying = underlying != null; type = underlying ?? type.WithNullableAnnotation(NullableAnnotation.None); var check = hasUnderlying ? ".HasValue" : " is not null"; code.AppendLine($"writer.Write({name}{check});"); code.AppendLine($"if ({name}{check})"); code.Open(); } name = nullable && hasUnderlying ? $"{name}.Value" : name; if (GenerationEngine.DefaultSerialization.TryGetValue(GenerationEngine.GetQualifiedName(type), out var serialization)) { serialization.Serialize(member, type, code, name, GenerationEngine.GetIdentifierWithArguments(type), location); return; } if (GenerationEngine.IsPrimitive(type)) { if (!type.IsValueType) { using (code.BeginScope($"if ({name} is default({GenerationEngine.GetIdentifierWithArguments(type)}))")) { code.AppendLine( $"throw new Exception(\"Member '{name}' is a primitive and has no value (null). If this is not an issue, please declare it as nullable.\");"); } } code.AppendLine($"writer.Write({name});"); } else { if (type.TypeKind != TypeKind.Struct && type.TypeKind != TypeKind.Enum && !nullable) { code.AppendLine($"writer.Write({name} is not null);"); code.AppendLine($"if ({name} is not null)"); code.Open(); } switch (type.TypeKind) { case TypeKind.Enum: code.AppendLine($"writer.Write((int) {name});"); break; case TypeKind.Interface: case TypeKind.Struct: case TypeKind.Class: var enumerable = GenerationEngine.GetQualifiedName(type) == GenerationEngine.EnumerableQualifiedName ? GenerationEngine.GetNamedTypeSymbol(type) : type.AllInterfaces.FirstOrDefault(self => GenerationEngine.GetQualifiedName(self) == GenerationEngine.EnumerableQualifiedName); if (enumerable != null) { var elementType = enumerable.TypeArguments.First(); using (code.BeginScope()) { var countTechnique = GenerationEngine.GetAllMembers(type) .Where(self => self is IPropertySymbol) .Aggregate("Count()", (current, symbol) => symbol.Name switch { "Count" => "Count", "Length" => "Length", _ => current }); var prefix = GenerationEngine.GetVariableName(name); code.AppendLine($"var {prefix}Count = {name}.{countTechnique};"); code.AppendLine($"writer.Write({prefix}Count);"); using (code.BeginScope($"foreach (var {prefix}Entry in {name})")) { Make(member, elementType, code, $"{prefix}Entry", location, scope); } } }
public static void Make(ISymbol member, ITypeSymbol type, CodeWriter code, string name, Location location, ScopeTracker scope = null) { var disposable = scope = scope == null?code.Encapsulate() : scope.Reference(); using (disposable) { var nullable = type.NullableAnnotation == NullableAnnotation.Annotated; { if (nullable) { var underlying = GenerationEngine.GetNamedTypeSymbol(type).TypeArguments.FirstOrDefault(); type = underlying ?? type.WithNullableAnnotation(NullableAnnotation.None); code.AppendLine("if (reader.ReadBoolean())"); code.Open(); } } if (GenerationEngine.DefaultSerialization.TryGetValue(GenerationEngine.GetQualifiedName(type), out var serialization)) { serialization.Deserialize(member, type, code, name, GenerationEngine.GetIdentifierWithArguments(type), location); return; } if (GenerationEngine.IsPrimitive(type)) { code.AppendLine( $"{name} = reader.Read{(GenerationEngine.PredefinedTypes.TryGetValue(type.Name, out var result) ? result : type.Name)}();"); } else { if (type.TypeKind != TypeKind.Struct && type.TypeKind != TypeKind.Enum && !nullable) { code.AppendLine("if (reader.ReadBoolean())"); code.Open(); } switch (type.TypeKind) { case TypeKind.Enum: code.AppendLine( $"{name} = ({GenerationEngine.GetIdentifierWithArguments(type)}) reader.ReadInt32();"); break; case TypeKind.Interface: case TypeKind.Struct: case TypeKind.Class: var enumerable = GenerationEngine.GetQualifiedName(type) == GenerationEngine.EnumerableQualifiedName ? GenerationEngine.GetNamedTypeSymbol(type) : type.AllInterfaces.FirstOrDefault(self => GenerationEngine.GetQualifiedName(self) == GenerationEngine.EnumerableQualifiedName); if (enumerable != null) { var elementType = enumerable.TypeArguments.First(); if (type.TypeKind == TypeKind.Interface && GenerationEngine.GetQualifiedName(type) != GenerationEngine.EnumerableQualifiedName) { var problem = new SerializationProblem { Descriptor = new DiagnosticDescriptor(ProblemId.InterfaceProperties, "Interface Properties", "Could not deserialize property '{0}' of type {1} because Interface types are not supported", "serialization", DiagnosticSeverity.Error, true), Locations = new[] { member.Locations.FirstOrDefault(), location }, Format = new object[] { member.Name, type.Name } }; GenerationEngine.Instance.Problems.Add(problem); code.AppendLine( $"throw new Exception(\"{string.Format(problem.Descriptor.MessageFormat.ToString(), problem.Format)}\");"); return; } using (code.BeginScope()) { var prefix = GenerationEngine.GetVariableName(name); code.AppendLine($"var {prefix}Count = reader.ReadInt32();"); var constructor = GenerationEngine.GetNamedTypeSymbol(type).Constructors.FirstOrDefault( self => GenerationEngine.GetQualifiedName(self.Parameters.FirstOrDefault() ?.Type) == GenerationEngine.EnumerableQualifiedName); var method = GenerationEngine.HasImplementation(type, "Add", GenerationEngine.GetQualifiedName(elementType)); var deconstructed = false; if (GenerationEngine.DeconstructionTypes.ContainsKey( GenerationEngine.GetQualifiedName(elementType)) && elementType is INamedTypeSymbol named) { deconstructed = GenerationEngine.HasImplementation(type, "Add", named.TypeArguments .Select(GenerationEngine.GetNamedTypeSymbol) .Select(GenerationEngine.GetQualifiedName) .ToArray()); } if (method || deconstructed) { code.AppendLine( $"{name} = new {GenerationEngine.GetIdentifierWithArguments(type)}();"); } else { code.AppendLine( $"var {prefix}Temp = new {GenerationEngine.GetIdentifierWithArguments(elementType)}[{prefix}Count];"); } var indexName = $"{prefix}Idx"; using (code.BeginScope( $"for (var {indexName} = 0; {indexName} < {prefix}Count; {indexName}++)")) { var pointer = !method && !deconstructed; var variable = pointer ? $"{prefix}TempEntry" : $"{prefix}Transient"; code.AppendLine( $"{GenerationEngine.GetIdentifierWithArguments(elementType)} {variable};"); Make(member, elementType, code, variable, location, scope); if (pointer) { code.AppendLine($"{prefix}Temp[{indexName}] = {variable};"); } else if (method) { code.AppendLine($"{name}.Add({prefix}Transient);"); } else { var arguments = GenerationEngine .DeconstructionTypes[GenerationEngine.GetQualifiedName(elementType)] .Select(self => $"{prefix}Transient.{self}"); code.AppendLine($"{name}.Add({string.Join(",", arguments)});"); } } if (method || deconstructed) { return; } if (GenerationEngine.GetQualifiedName(type) == GenerationEngine.EnumerableQualifiedName) { code.AppendLine($"{name} = {prefix}Temp;"); return; } if (constructor != null) { code.AppendLine( $"{name} = new {GenerationEngine.GetIdentifierWithArguments(type)}({prefix}Temp);"); return; } var problem = new SerializationProblem { Descriptor = new DiagnosticDescriptor(ProblemId.EnumerableProperties, "Enumerable Properties", "Could not deserialize property '{0}' because enumerable type {1} did not contain a suitable way of adding items", "serialization", DiagnosticSeverity.Error, true), Locations = new[] { member.Locations.FirstOrDefault(), location }, Format = new object[] { member.Name, type.Name, elementType.Name } }; GenerationEngine.Instance.Problems.Add(problem); code.AppendLine( $"throw new Exception(\"{string.Format(problem.Descriptor.MessageFormat.ToString(), problem.Format)}\");"); } } else { if (type.TypeKind == TypeKind.Interface) { var problem = new SerializationProblem { Descriptor = new DiagnosticDescriptor(ProblemId.InterfaceProperties, "Interface Properties", "Could not deserialize property '{0}' of type {1} because Interface types are not supported", "serialization", DiagnosticSeverity.Error, true), Locations = new[] { member.Locations.FirstOrDefault(), location }, Format = new object[] { member.Name, type.Name } }; GenerationEngine.Instance.Problems.Add(problem); code.AppendLine( $"throw new Exception(\"{string.Format(problem.Descriptor.MessageFormat.ToString(), problem.Format)}\");"); return; } if (GenerationEngine.GetNamedTypeSymbol(type).Constructors.Any(self => self.Parameters.Length == 1 && self.Parameters.Single().Type.MetadataName == "BinaryReader") || GenerationEngine.HasMarkedAsSerializable(type)) { code.AppendLine( $"{name} = new {GenerationEngine.GetIdentifierWithArguments(type)}(reader);"); } else { var hasConstructor = false; foreach (var constructor in GenerationEngine.GetNamedTypeSymbol(type).Constructors) { hasConstructor = true; var parameters = constructor.Parameters; var members = GenerationEngine.GetMembers(type, GenerationType.Read); var index = 0; foreach (var(_, valueType) in members) { if (parameters.Length <= index) { hasConstructor = false; continue; } var parameter = parameters[index]; if (parameter.Type.MetadataName != valueType.MetadataName) { hasConstructor = false; } index++; } if (hasConstructor) { break; } } if (hasConstructor) { var members = GenerationEngine.GetMembers(type, GenerationType.Read); foreach (var(deep, valueType) in GenerationEngine.GetMembers(type, GenerationType.Read)) { code.AppendLine( $"{GenerationEngine.GetIdentifierWithArguments(valueType)} {GenerationEngine.GetVariableName(name + deep.Name)} = default;"); } GenerationEngine.Generate($"{GenerationEngine.GetVariableName(name)}", type, code, GenerationType.Read); code.AppendLine( $"{name} = new {GenerationEngine.GetIdentifierWithArguments(type)}({string.Join(", ", members.Select(self => GenerationEngine.GetVariableName(name + self.Item1.Name)))});"); } else { code.AppendLine( $"{name} = new {GenerationEngine.GetIdentifierWithArguments(type)}();"); using (code.BeginScope()) { GenerationEngine.Generate($"{name}.", type, code, GenerationType.Read); } } } } break; case TypeKind.Array: var array = (IArrayTypeSymbol)type; using (code.BeginScope()) { var prefix = GenerationEngine.GetVariableName(name); code.AppendLine($"var {prefix}Length = reader.ReadInt32();"); if (GenerationEngine.GetQualifiedName(array.ElementType) == "System.Byte") { code.AppendLine($"{name} = reader.ReadBytes({prefix}Length);"); } else { var indexName = $"{prefix}Idx"; code.AppendLine( $"{name} = new {GenerationEngine.GetIdentifierWithArguments(array.ElementType)}[{prefix}Length];"); using (code.BeginScope( $"for (var {indexName} = 0; {indexName} < {prefix}Length; {indexName}++)")) { Make(member, array.ElementType, code, $"{name}[{indexName}]", location, scope); } } } break; } } } }