Skip to content

Commit 60b0d86

Browse files
nino v2.0.9
Nino.Serialization v2.0.9 - [Feature] Allow serialize/deserialize interfaces (subtypes) and records (closes #134) - [Fix] Generated deserialization code should not instantiate an IDicitonary - [Fix] Generated deserialization code for any IDictionary implementations should use `dict[key] = val` to set the value, rather than naively call `Add`, so it can support more implementations, i.e. `ConcurrentDictionary`
1 parent fe5b0af commit 60b0d86

File tree

12 files changed

+207
-164
lines changed

12 files changed

+207
-164
lines changed

Nino.unitypackage

422 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
512 Bytes
Binary file not shown.

src/Nino.Core/Nino.Core.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@
77
<LangVersion>9</LangVersion>
88
<PackageId>Nino.Serialization</PackageId>
99
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
10-
<Version>2.0.8</Version>
10+
<Version>2.0.9</Version>
1111
<Title>Nino.Serialization</Title>
1212
<Authors>JasonXuDeveloper</Authors>
1313
<Description>High performance and low size binary serialization solution, especially for Unity.</Description>
1414
<Copyright>JasonXuDeveloper</Copyright>
1515
<RepositoryUrl>https://github.com/JasonXuDeveloper/Nino</RepositoryUrl>
1616
<RepositoryType>git</RepositoryType>
1717
<PackageTags>Nino;Serialization;Binary</PackageTags>
18-
<PackageReleaseNotes>Nino.Serialization v2.0.8
19-
- [Fix] Fix compilation error for deserializing members with abstract types</PackageReleaseNotes>
18+
<PackageReleaseNotes>Nino.Serialization v2.0.9
19+
- [Feature] Allow serialize/deserialize interfaces (subtypes) and records
20+
- [Fix] Generated deserialization code should not instantiate an IDicitonary
21+
- [Fix] Generated deserialization code for any IDictionary implementations should use `dict[key] = val` to set the value, rather than naively call `Add`, so it can support more implementations, i.e. `ConcurrentDictionary`</PackageReleaseNotes>
2022
<PackageLicenseExpression>MIT</PackageLicenseExpression>
2123
<PackageProjectUrl>https://nino.xgamedev.net/</PackageProjectUrl>
2224
<PackageReadmeFile>README.md</PackageReadmeFile>

src/Nino.Core/NinoTypeAttribute.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace Nino.Core
44
{
5-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
5+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct |
6+
AttributeTargets.Interface
7+
, Inherited = false)]
68
public class NinoTypeAttribute : Attribute
79
{
810
public bool AutoCollect;

src/Nino.Generator/DeserializerGenerator.cs

Lines changed: 10 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -23,74 +23,20 @@ private static void Execute(Compilation compilation, ImmutableArray<TypeDeclarat
2323
SourceProductionContext spc)
2424
{
2525
// get type full names from models (namespaces + type names)
26-
var typeFullNames = models.Where(m => m is ClassDeclarationSyntax)
26+
var typeFullNames = models.Where(m => m.IsReferenceType())
2727
.Select(m => m.GetTypeFullName()).ToList();
2828
//sort by typename
2929
typeFullNames.Sort();
3030

31-
int GetId(string typeFullName)
32-
{
33-
int index = typeFullNames.IndexOf(typeFullName);
34-
return index + 4;
35-
}
36-
3731
var types = new StringBuilder();
3832
foreach (var typeFullName in typeFullNames)
3933
{
40-
types.AppendLine($" * {GetId(typeFullName)} - {typeFullName}");
34+
types.AppendLine($" * {typeFullNames.GetId(typeFullName)} - {typeFullName}");
4135
}
4236

43-
var ninoTypeModels = models.Select(m => m.GetTypeFullName()).ToImmutableArray();
44-
Dictionary<string, List<string>> inheritanceMap = new(); // type -> all base types
45-
Dictionary<string, List<string>> subTypeMap = new(); //top type -> all subtypes
46-
//get top nino types (i.e. types that are not inherited by other nino types)
47-
var topNinoTypes = ninoTypeModels.Where(ninoTypeFullName =>
48-
{
49-
List<string> inheritedTypes = new();
50-
inheritanceMap.Add(ninoTypeFullName, inheritedTypes);
51-
INamedTypeSymbol? subTypeSymbol = compilation.GetTypeByMetadataName(ninoTypeFullName);
52-
if (subTypeSymbol == null)
53-
{
54-
//check if is a nested type
55-
TypeDeclarationSyntax? typeDeclarationSyntax = models.FirstOrDefault(m =>
56-
string.Equals(m.GetTypeFullName(), ninoTypeFullName, StringComparison.Ordinal));
57-
58-
if (typeDeclarationSyntax == null)
59-
return false;
60-
61-
var ninoTypeFullName2 = typeDeclarationSyntax.GetTypeFullName("+");
62-
subTypeSymbol = compilation.GetTypeByMetadataName(ninoTypeFullName2);
63-
if (subTypeSymbol == null)
64-
return false;
65-
}
66-
67-
//get toppest ninotype base type
68-
INamedTypeSymbol? baseType = subTypeSymbol;
69-
while (baseType.BaseType != null)
70-
{
71-
baseType = baseType.BaseType;
72-
string baseTypeFullName = baseType.ToString();
73-
if (ninoTypeModels.Contains(baseTypeFullName))
74-
{
75-
if (subTypeMap.ContainsKey(baseTypeFullName))
76-
{
77-
subTypeMap[baseTypeFullName].Add(ninoTypeFullName);
78-
}
79-
else
80-
{
81-
subTypeMap.Add(baseTypeFullName, [ninoTypeFullName]);
82-
}
83-
84-
inheritedTypes.Add(baseTypeFullName);
85-
}
86-
else
87-
{
88-
break;
89-
}
90-
}
91-
92-
return inheritedTypes.Count == 0;
93-
}).ToImmutableArray();
37+
var (inheritanceMap,
38+
subTypeMap,
39+
topNinoTypes) = compilation.GetInheritanceMap(models);
9440

9541
var sb = new StringBuilder();
9642
var subTypes = new StringBuilder();
@@ -183,7 +129,7 @@ void WriteMembers(List<MemberDeclarationSyntax> members, string valName)
183129
});
184130

185131
// only applicable for reference types
186-
bool isReferenceType = typeSymbol.IsReferenceType;
132+
bool isReferenceType = model.IsReferenceType();
187133
if (isReferenceType)
188134
{
189135
sb.AppendLine(" switch (typeId)");
@@ -200,10 +146,10 @@ void WriteMembers(List<MemberDeclarationSyntax> members, string valName)
200146
var subTypeSymbol = compilation.GetTypeSymbol(subType, models);
201147
subTypes.AppendLine(
202148
subType.GeneratePublicDeserializeMethodBodyForSubType(typeFullName, " "));
203-
if (!subTypeSymbol.IsAbstract)
149+
if (subTypeSymbol.IsInstanceType())
204150
{
205151
string valName = subType.Replace(".", "_").ToLower();
206-
int id = GetId(subType);
152+
int id = typeFullNames.GetId(subType);
207153
sb.AppendLine($" case {id}:");
208154
sb.AppendLine(" {");
209155
sb.AppendLine($" {subType} {valName} = new {subType}();");
@@ -224,11 +170,11 @@ void WriteMembers(List<MemberDeclarationSyntax> members, string valName)
224170
}
225171
}
226172

227-
if (!typeSymbol.IsAbstract)
173+
if (typeSymbol.IsInstanceType())
228174
{
229175
if (isReferenceType)
230176
{
231-
sb.AppendLine($" case {GetId(typeFullName)}:");
177+
sb.AppendLine($" case {typeFullNames.GetId(typeFullName)}:");
232178
sb.AppendLine(" {");
233179
}
234180

src/Nino.Generator/EmbedTypeDeserializerGenerator.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,8 @@ bool IsTypeParameter(ITypeSymbol ts)
229229
}
230230
}
231231

232-
//if type implements IDictionary or is IDictionary
233-
if (typeFullName.StartsWith("System.Collections.Generic.IDictionary")
234-
|| type.AllInterfaces.Any(namedTypeSymbol =>
232+
//if type implements IDictionary only
233+
if (type.AllInterfaces.Any(namedTypeSymbol =>
235234
namedTypeSymbol.Name == "IDictionary" && namedTypeSymbol.TypeArguments.Length == 2))
236235
{
237236
if (type is INamedTypeSymbol { TypeArguments.Length: 2 } namedTypeSymbol)
@@ -365,11 +364,11 @@ public static void Deserialize(out {{typeFullName}} value, ref Reader reader)
365364
return;
366365
case TypeCollector.CollectionTypeId:
367366
reader.Read(out int length);
368-
value = new Dictionary<{{type1}}, {{type2}}>(length);
367+
value = new {{typeFullName}}();
369368
for (int i = 0; i < length; i++)
370369
{
371370
Deserialize(out KeyValuePair<{{type1}}, {{type2}}> kvp, ref reader);
372-
value.Add(kvp.Key, kvp.Value);
371+
value[kvp.Key] = kvp.Value;
373372
}
374373
return;
375374
default:

src/Nino.Generator/Nino.Generator.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@
1010
<RootNamespace>Nino.Generator</RootNamespace>
1111
<PackageId>Nino.Generator</PackageId>
1212
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
13-
<Version>2.0.8</Version>
13+
<Version>2.0.9</Version>
1414
<Title>Nino.Generator</Title>
1515
<Authors>JasonXuDeveloper</Authors>
1616
<Description>Source Generator for the high performance and low size binary serialization solution, especially for Unity.</Description>
1717
<Copyright>JasonXuDeveloper</Copyright>
1818
<RepositoryUrl>https://github.com/JasonXuDeveloper/Nino</RepositoryUrl>
1919
<RepositoryType>git</RepositoryType>
2020
<PackageTags>Nino;Serialization;Binary;Generator</PackageTags>
21-
<PackageReleaseNotes>Nino.Serialization v2.0.8
22-
- [Fix] Fix compilation error for deserializing members with abstract types</PackageReleaseNotes>
21+
<PackageReleaseNotes>Nino.Serialization v2.0.9
22+
- [Feature] Allow serialize/deserialize interfaces (subtypes) and records
23+
- [Fix] Generated deserialization code should not instantiate an IDicitonary
24+
- [Fix] Generated deserialization code for any IDictionary implementations should use `dict[key] = val` to set the value, rather than naively call `Add`, so it can support more implementations, i.e. `ConcurrentDictionary`</PackageReleaseNotes>
2325
<PackageLicenseExpression>MIT</PackageLicenseExpression>
2426
<AnalyzerLanguage>cs</AnalyzerLanguage>
2527
<IncludeBuildOutput>false</IncludeBuildOutput>

src/Nino.Generator/NinoTypeHelper.cs

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,116 @@ public static bool IsNinoType(this ITypeSymbol typeSymbol)
3030
return typeSymbol.GetAttributes().Any(static a => a.AttributeClass?.Name == "NinoTypeAttribute");
3131
}
3232

33+
public static bool IsReferenceType(this TypeDeclarationSyntax typeDecl)
34+
{
35+
return typeDecl is ClassDeclarationSyntax or RecordDeclarationSyntax or InterfaceDeclarationSyntax;
36+
}
37+
38+
public static bool IsInstanceType(this ITypeSymbol typeSymbol)
39+
{
40+
//can't be interface or abstract class
41+
return typeSymbol.TypeKind switch
42+
{
43+
TypeKind.Interface => false,
44+
TypeKind.Class => !typeSymbol.IsAbstract,
45+
TypeKind.Struct => true,
46+
TypeKind.Array => true,
47+
_ => false
48+
};
49+
}
50+
51+
public static int GetId(this List<string> typeFullNames,string typeFullName)
52+
{
53+
int index = typeFullNames.IndexOf(typeFullName);
54+
return index + 4;
55+
}
56+
57+
public static (Dictionary<string, List<string>> inheritanceMap,
58+
Dictionary<string, List<string>> subTypeMap,
59+
ImmutableArray<string> topNinoTypes) GetInheritanceMap(this Compilation compilation, ImmutableArray<TypeDeclarationSyntax> models)
60+
{
61+
var ninoTypeModels = models.Select(m => m.GetTypeFullName()).ToImmutableArray();
62+
Dictionary<string, List<string>> inheritanceMap = new(); // type -> all base types
63+
Dictionary<string, List<string>> subTypeMap = new(); //top type -> all subtypes
64+
//get top nino types (i.e. types that are not inherited by other nino types)
65+
var topNinoTypes = ninoTypeModels.Where(ninoTypeFullName =>
66+
{
67+
List<string> inheritedTypes = new();
68+
inheritanceMap.Add(ninoTypeFullName, inheritedTypes);
69+
INamedTypeSymbol? subTypeSymbol = compilation.GetTypeByMetadataName(ninoTypeFullName);
70+
if (subTypeSymbol == null)
71+
{
72+
//check if is a nested type
73+
TypeDeclarationSyntax? typeDeclarationSyntax = models.FirstOrDefault(m =>
74+
string.Equals(m.GetTypeFullName(), ninoTypeFullName, StringComparison.Ordinal));
75+
76+
if (typeDeclarationSyntax == null)
77+
return false;
78+
79+
var ninoTypeFullName2 = typeDeclarationSyntax.GetTypeFullName("+");
80+
subTypeSymbol = compilation.GetTypeByMetadataName(ninoTypeFullName2);
81+
if (subTypeSymbol == null)
82+
return false;
83+
}
84+
85+
//get toppest ninotype base type
86+
INamedTypeSymbol? baseType = subTypeSymbol;
87+
List<string> interfaces = new();
88+
interfaces.AddRange(baseType.Interfaces.Select(i => i.ToString()));
89+
while (baseType.BaseType != null)
90+
{
91+
baseType = baseType.BaseType;
92+
string baseTypeFullName = baseType.ToString();
93+
if (ninoTypeModels.Contains(baseTypeFullName))
94+
{
95+
interfaces.AddRange(baseType.Interfaces.Select(i => i.ToString()));
96+
if (subTypeMap.ContainsKey(baseTypeFullName))
97+
{
98+
subTypeMap[baseTypeFullName].Add(ninoTypeFullName);
99+
}
100+
else
101+
{
102+
subTypeMap.Add(baseTypeFullName, [ninoTypeFullName]);
103+
}
104+
105+
inheritedTypes.Add(baseTypeFullName);
106+
}
107+
else
108+
{
109+
break;
110+
}
111+
}
112+
//it may implement interfaces that are nino types
113+
foreach (var @interface in interfaces)
114+
{
115+
if (ninoTypeModels.Contains(@interface))
116+
{
117+
if (subTypeMap.ContainsKey(@interface))
118+
{
119+
subTypeMap[@interface].Add(ninoTypeFullName);
120+
}
121+
else
122+
{
123+
subTypeMap.Add(@interface, [ninoTypeFullName]);
124+
}
125+
126+
inheritedTypes.Add(@interface);
127+
}
128+
}
129+
130+
return inheritedTypes.Count == 0;
131+
}).ToImmutableArray();
132+
133+
return (inheritanceMap, subTypeMap, topNinoTypes);
134+
}
135+
33136
public static string GetDeserializePrefix(this ITypeSymbol ts)
34137
{
35138
if (!ts.IsNinoType())
36139
{
37140
return "Deserialize";
38141
}
142+
39143
var assName = ts.ContainingAssembly.Name;
40144
var curNamespace = $"{assName!}";
41145
if (!string.IsNullOrEmpty(curNamespace))
@@ -46,7 +150,7 @@ public static string GetDeserializePrefix(this ITypeSymbol ts)
46150
curNamespace =
47151
new string(curNamespace.Select(c => char.IsLetterOrDigit(c) ? c : '_').ToArray());
48152
curNamespace += "Nino";
49-
153+
50154
return $"{curNamespace}.Deserializer.Deserialize";
51155
}
52156

@@ -56,6 +160,7 @@ public static string GetSerializePrefix(this ITypeSymbol ts)
56160
{
57161
return "Serialize";
58162
}
163+
59164
var assName = ts.ContainingAssembly.Name;
60165
var curNamespace = $"{assName!}";
61166
if (!string.IsNullOrEmpty(curNamespace))
@@ -66,7 +171,7 @@ public static string GetSerializePrefix(this ITypeSymbol ts)
66171
curNamespace =
67172
new string(curNamespace.Select(c => char.IsLetterOrDigit(c) ? c : '_').ToArray());
68173
curNamespace += "Nino";
69-
174+
70175
return $"{curNamespace}.Serializer.Serialize";
71176
}
72177

0 commit comments

Comments
 (0)