Skip to content

Commit c197b3c

Browse files
jasonginvmoroz
authored andcommitted
Generate adapter code for arrays and typed arrays (#15)
1 parent 6c476c2 commit c197b3c

13 files changed

+527
-74
lines changed

Generator/AdapterGenerator.cs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ internal class AdapterGenerator : SourceGenerator
2121

2222
private readonly Dictionary<string, ISymbol> _adaptedMembers = new();
2323
private readonly Dictionary<string, ITypeSymbol> _adaptedStructs = new();
24+
private readonly Dictionary<string, ITypeSymbol> _adaptedArrays = new();
2425

2526
internal AdapterGenerator(GeneratorExecutionContext context)
2627
{
@@ -132,6 +133,19 @@ private string GetStructAdapterName(ITypeSymbol structType, bool toJS)
132133
return (getAdapterName, setAdapterName);
133134
}
134135

136+
private string GetArrayAdapterName(ITypeSymbol elementType, bool toJS)
137+
{
138+
string ns = GetNamespace(elementType);
139+
string elementName = elementType.Name;
140+
string prefix = toJS ? AdapterFromPrefix : AdapterToPrefix;
141+
string adapterName = $"{prefix}{ns.Replace('.', '_')}_{elementName}_Array";
142+
if (!_adaptedArrays.ContainsKey(adapterName))
143+
{
144+
_adaptedArrays.Add(adapterName, elementType);
145+
}
146+
return adapterName;
147+
}
148+
135149
internal void GenerateAdapters(SourceBuilder s)
136150
{
137151
foreach (KeyValuePair<string, ISymbol> nameAndSymbol in _adaptedMembers)
@@ -164,6 +178,14 @@ internal void GenerateAdapters(SourceBuilder s)
164178
ITypeSymbol structSymbol = nameAndSymbol.Value;
165179
GenerateStructAdapter(ref s, adapterName, structSymbol);
166180
}
181+
182+
foreach (KeyValuePair<string, ITypeSymbol> nameAndSymbol in _adaptedArrays)
183+
{
184+
s++;
185+
string adapterName = nameAndSymbol.Key;
186+
ITypeSymbol elementSymbol = nameAndSymbol.Value;
187+
GenerateArrayAdapter(ref s, adapterName, elementSymbol);
188+
}
167189
}
168190

169191
private void GenerateConstructorAdapter(
@@ -345,6 +367,59 @@ private void GenerateStructAdapter(
345367
}
346368
}
347369

370+
private void GenerateArrayAdapter(
371+
ref SourceBuilder s,
372+
string adapterName,
373+
ITypeSymbol elementType)
374+
{
375+
string ns = GetNamespace(elementType);
376+
string elementName = elementType.Name;
377+
378+
if (adapterName.StartsWith(AdapterFromPrefix))
379+
{
380+
s += $"private static JSValue {adapterName}({ns}.{elementName}[] array)";
381+
s += "{";
382+
s += "JSArray jsArray = new JSArray(array.Length);";
383+
s += "for (int i = 0; i < array.Length; i++)";
384+
s += "{";
385+
s += $"jsArray[i] = {Convert("array[i]", elementType, null)};";
386+
s += "}";
387+
s += "return jsArray;";
388+
s += "}";
389+
}
390+
else
391+
{
392+
s += $"private static {ns}.{elementName}[] {adapterName}(JSValue value)";
393+
s += "{";
394+
s += "JSArray jsArray = (JSArray)value;";
395+
s += $"{ns}.{elementName}[] array = new {ns}.{elementName}[jsArray.Length];";
396+
s += "for (int i = 0; i < array.Length; i++)";
397+
s += "{";
398+
s += $"array[i] = {Convert("jsArray[i]", null, elementType)};";
399+
s += "}";
400+
s += "return array;";
401+
s += "}";
402+
}
403+
}
404+
405+
private bool IsTypedArrayType(ITypeSymbol elementType)
406+
{
407+
return elementType.SpecialType switch
408+
{
409+
SpecialType.System_SByte => true,
410+
SpecialType.System_Byte => true,
411+
SpecialType.System_Int16 => true,
412+
SpecialType.System_UInt16 => true,
413+
SpecialType.System_Int32 => true,
414+
SpecialType.System_UInt32 => true,
415+
SpecialType.System_Int64 => true,
416+
SpecialType.System_UInt64 => true,
417+
SpecialType.System_Single => true,
418+
SpecialType.System_Double => true,
419+
_ => false,
420+
};
421+
}
422+
348423
private void AdaptThisArg(ref SourceBuilder s, ISymbol symbol)
349424
{
350425

@@ -425,6 +500,14 @@ private string Convert(string fromExpression, ITypeSymbol? fromType, ITypeSymbol
425500
}
426501
else if (toType.TypeKind == TypeKind.Struct)
427502
{
503+
if (toType is INamedTypeSymbol namedType &&
504+
namedType.TypeParameters.Length == 1 &&
505+
namedType.OriginalDefinition.Name == "Memory" &&
506+
IsTypedArrayType(namedType.TypeArguments[0]))
507+
{
508+
return $"((JSTypedArray<{namedType.TypeArguments[0]}>){fromExpression}).AsMemory()";
509+
}
510+
428511
VerifyReferencedTypeIsExported(toType);
429512

430513
string adapterName = GetStructAdapterName(toType, toJS: false);
@@ -438,6 +521,26 @@ private string Convert(string fromExpression, ITypeSymbol? fromType, ITypeSymbol
438521
return $"{adapterName}({fromExpression})";
439522
}
440523
}
524+
else if (toType.TypeKind == TypeKind.Array)
525+
{
526+
ITypeSymbol elementType = ((IArrayTypeSymbol)toType).ElementType;
527+
VerifyReferencedTypeIsExported(elementType);
528+
529+
string adapterName = GetArrayAdapterName(elementType, toJS: false);
530+
if (isNullable)
531+
{
532+
return $"({fromExpression}).IsNullOrUndefined() ? ({elementType}[]?)null : " +
533+
$"{adapterName}({fromExpression})";
534+
}
535+
else
536+
{
537+
return $"{adapterName}({fromExpression})";
538+
}
539+
}
540+
else if (toType is INamedTypeSymbol namedType && namedType.TypeParameters.Length > 0)
541+
{
542+
// TODO: Handle generic collections.
543+
}
441544

442545
// TODO: Handle other kinds of conversions from JSValue.
443546
// TODO: Handle unwrapping external values.
@@ -483,6 +586,14 @@ private string Convert(string fromExpression, ITypeSymbol? fromType, ITypeSymbol
483586
}
484587
else if (fromType.TypeKind == TypeKind.Struct)
485588
{
589+
if (fromType is INamedTypeSymbol namedType &&
590+
namedType.TypeParameters.Length == 1 &&
591+
namedType.OriginalDefinition.Name == "Memory" &&
592+
IsTypedArrayType(namedType.TypeArguments[0]))
593+
{
594+
return $"new JSTypedArray<{namedType.TypeArguments[0]}>({fromExpression})";
595+
}
596+
486597
VerifyReferencedTypeIsExported(fromType);
487598

488599
string adapterName = GetStructAdapterName(fromType, toJS: true);
@@ -496,6 +607,26 @@ private string Convert(string fromExpression, ITypeSymbol? fromType, ITypeSymbol
496607
return $"{adapterName}({fromExpression})";
497608
}
498609
}
610+
else if (fromType.TypeKind == TypeKind.Array)
611+
{
612+
ITypeSymbol elementType = ((IArrayTypeSymbol)fromType).ElementType;
613+
VerifyReferencedTypeIsExported(elementType);
614+
615+
string adapterName = GetArrayAdapterName(elementType, toJS: true);
616+
if (isNullable)
617+
{
618+
return $"{fromExpression} == null ? JSValue.Null : " +
619+
$"{adapterName}({fromExpression})";
620+
}
621+
else
622+
{
623+
return $"{adapterName}({fromExpression})";
624+
}
625+
}
626+
else if (fromType is INamedTypeSymbol namedType && namedType.TypeParameters.Length > 0)
627+
{
628+
// TODO: Handle generic collections.
629+
}
499630

500631
// TODO: Handle other kinds of conversions to JSValue.
501632
// TODO: Consider wrapping unsupported types in a value of type "external".
@@ -512,6 +643,13 @@ private string Convert(string fromExpression, ITypeSymbol? fromType, ITypeSymbol
512643

513644
private void VerifyReferencedTypeIsExported(ITypeSymbol type)
514645
{
646+
switch (type.SpecialType)
647+
{
648+
case SpecialType.System_Object:
649+
case SpecialType.System_String: return;
650+
default: break;
651+
}
652+
515653
if (ModuleGenerator.GetJSExportAttribute(type) == null)
516654
{
517655
// TODO: Consider an option to automatically export referenced classes?

Generator/ModuleGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ public void Execute(GeneratorExecutionContext context)
5757
// No type definitions are generated when using a custom init function.
5858
if (moduleInitializer is not IMethodSymbol)
5959
{
60-
SourceText typeDefinitions = TypeDefinitionsGenerator.GenerateTypeDefinitions(
61-
exportItems);
60+
TypeDefinitionsGenerator tsGenerator = new(exportItems);
61+
SourceText typeDefinitions = tsGenerator.GenerateTypeDefinitions();
6262
if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(
6363
"build_property.TargetPath", out string? targetPath))
6464
{

Generator/TypeDefinitionsGenerator.cs

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,29 @@
88

99
namespace NodeApi.Generator;
1010

11+
// An analyzer bug results in incorrect reports of CA1822 against methods in this class.
12+
#pragma warning disable CA1822 // Mark members as static
13+
1114
internal class TypeDefinitionsGenerator : SourceGenerator
1215
{
1316
private static readonly Regex s_newlineRegex = new("\n *");
1417
private static readonly Regex s_summaryRegex = new("<summary>(.*)</summary>");
1518
private static readonly Regex s_remarksRegex = new("<remarks>(.*)</remarks>");
1619

17-
internal static SourceText GenerateTypeDefinitions(IEnumerable<ISymbol> exportItems)
20+
private readonly IEnumerable<ISymbol> _exportItems;
21+
22+
public TypeDefinitionsGenerator(IEnumerable<ISymbol> exportItems)
23+
{
24+
_exportItems = exportItems;
25+
}
26+
27+
internal SourceText GenerateTypeDefinitions()
1828
{
1929
var s = new SourceBuilder();
2030

2131
s += "// Generated type definitions for .NET module";
2232

23-
foreach (ISymbol exportItem in exportItems)
33+
foreach (ISymbol exportItem in _exportItems)
2434
{
2535
if (exportItem is ITypeSymbol exportType &&
2636
(exportType.TypeKind == TypeKind.Class || exportType.TypeKind == TypeKind.Struct))
@@ -50,7 +60,7 @@ internal static SourceText GenerateTypeDefinitions(IEnumerable<ISymbol> exportIt
5060
return s;
5161
}
5262

53-
private static void GenerateClassTypeDefinitions(ref SourceBuilder s, ITypeSymbol exportClass)
63+
private void GenerateClassTypeDefinitions(ref SourceBuilder s, ITypeSymbol exportClass)
5464
{
5565
s++;
5666
GenerateDocComments(ref s, exportClass);
@@ -117,8 +127,10 @@ member is IMethodSymbol exportConstructor &&
117127
s += "}";
118128
}
119129

120-
private static string GetTSType(ITypeSymbol type)
130+
private string GetTSType(ITypeSymbol type)
121131
{
132+
string tsType = "unknown";
133+
122134
string? specialType = type.SpecialType switch
123135
{
124136
SpecialType.System_Void => "void",
@@ -137,25 +149,93 @@ private static string GetTSType(ITypeSymbol type)
137149
////SpecialType.System_DateTime => "Date",
138150
_ => null,
139151
};
152+
140153
if (specialType != null)
141154
{
142-
return specialType;
155+
tsType = specialType;
143156
}
144-
145-
if (type.TypeKind == TypeKind.Class)
157+
else if (type.TypeKind == TypeKind.Array)
146158
{
147-
// TODO: Check if class is exported.
159+
ITypeSymbol elementType = ((IArrayTypeSymbol)type).ElementType;
160+
tsType = GetTSType(elementType) + "[]";
148161
}
149-
else if (type.TypeKind == TypeKind.Array)
162+
else if (type is INamedTypeSymbol namedType && namedType.TypeParameters.Length > 0)
163+
{
164+
if (namedType.OriginalDefinition.Name == "Nullable")
165+
{
166+
tsType = GetTSType(namedType.TypeArguments[0]) + " | null";
167+
}
168+
else if (namedType.OriginalDefinition.Name == "Memory")
169+
{
170+
ITypeSymbol elementType = namedType.TypeArguments[0];
171+
tsType = elementType.SpecialType switch
172+
{
173+
SpecialType.System_SByte => "Int8Array",
174+
SpecialType.System_Int16 => "Int16Array",
175+
SpecialType.System_Int32 => "Int32Array",
176+
SpecialType.System_Int64 => "BigInt64Array",
177+
SpecialType.System_Byte => "Uint8Array",
178+
SpecialType.System_UInt16 => "Uint16Array",
179+
SpecialType.System_UInt32 => "Uint32Array",
180+
SpecialType.System_UInt64 => "BigUint64Array",
181+
SpecialType.System_Single => "Float32Array",
182+
SpecialType.System_Double => "Float64Array",
183+
_ => "unknown",
184+
};
185+
}
186+
else if (namedType.OriginalDefinition.Name == "IList")
187+
{
188+
tsType = GetTSType(namedType.TypeArguments[0]) + "[]";
189+
}
190+
else if (namedType.OriginalDefinition.Name == "IReadOnlyList")
191+
{
192+
tsType = "readonly " + GetTSType(namedType.TypeArguments[0]) + "[]";
193+
}
194+
else if (namedType.OriginalDefinition.Name == "ICollection" ||
195+
namedType.OriginalDefinition.Name == "ISet")
196+
{
197+
string elementTsType = GetTSType(namedType.TypeArguments[0]);
198+
return $"Set<{elementTsType}>";
199+
}
200+
else if (namedType.OriginalDefinition.Name == "IReadOnlyCollection" ||
201+
namedType.OriginalDefinition.Name == "IReadOnlySet")
202+
{
203+
string elementTsType = GetTSType(namedType.TypeArguments[0]);
204+
return $"ReadonlySet<{elementTsType}>";
205+
}
206+
else if (namedType.OriginalDefinition.Name == "IEnumerable")
207+
{
208+
string elementTsType = GetTSType(namedType.TypeArguments[0]);
209+
return $"Iterable<{elementTsType}>";
210+
}
211+
else if (namedType.OriginalDefinition.Name == "IDictionary")
212+
{
213+
string keyTSType = GetTSType(namedType.TypeArguments[0]);
214+
string valueTSType = GetTSType(namedType.TypeArguments[1]);
215+
tsType = $"Map<{keyTSType}, {valueTSType}>";
216+
}
217+
else if (namedType.OriginalDefinition.Name == "IReadOnlyDictionary")
218+
{
219+
string keyTSType = GetTSType(namedType.TypeArguments[0]);
220+
string valueTSType = GetTSType(namedType.TypeArguments[1]);
221+
tsType = $"ReadonlyMap<{keyTSType}, {valueTSType}>";
222+
}
223+
}
224+
else if (_exportItems.Contains(type, SymbolEqualityComparer.Default))
225+
{
226+
tsType = type.Name;
227+
}
228+
229+
if (type.NullableAnnotation == NullableAnnotation.Annotated &&
230+
tsType != "any" && !tsType.EndsWith(" | null"))
150231
{
151-
// TODO: Get element type.
152-
return "any[]";
232+
tsType += " | null";
153233
}
154234

155-
return "any";
235+
return tsType;
156236
}
157237

158-
private static string GetTSParameters(IMethodSymbol method, string indent)
238+
private string GetTSParameters(IMethodSymbol method, string indent)
159239
{
160240
if (method.Parameters.Length == 0)
161241
{

0 commit comments

Comments
 (0)