Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate adapter code for arrays and typed arrays #15

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions Generator/AdapterGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal class AdapterGenerator : SourceGenerator

private readonly Dictionary<string, ISymbol> _adaptedMembers = new();
private readonly Dictionary<string, ITypeSymbol> _adaptedStructs = new();
private readonly Dictionary<string, ITypeSymbol> _adaptedArrays = new();

internal AdapterGenerator(GeneratorExecutionContext context)
{
Expand Down Expand Up @@ -132,6 +133,19 @@ private string GetStructAdapterName(ITypeSymbol structType, bool toJS)
return (getAdapterName, setAdapterName);
}

private string GetArrayAdapterName(ITypeSymbol elementType, bool toJS)
{
string ns = GetNamespace(elementType);
string elementName = elementType.Name;
string prefix = toJS ? AdapterFromPrefix : AdapterToPrefix;
string adapterName = $"{prefix}{ns.Replace('.', '_')}_{elementName}_Array";
if (!_adaptedArrays.ContainsKey(adapterName))
{
_adaptedArrays.Add(adapterName, elementType);
}
return adapterName;
}

internal void GenerateAdapters(SourceBuilder s)
{
foreach (KeyValuePair<string, ISymbol> nameAndSymbol in _adaptedMembers)
Expand Down Expand Up @@ -164,6 +178,14 @@ internal void GenerateAdapters(SourceBuilder s)
ITypeSymbol structSymbol = nameAndSymbol.Value;
GenerateStructAdapter(ref s, adapterName, structSymbol);
}

foreach (KeyValuePair<string, ITypeSymbol> nameAndSymbol in _adaptedArrays)
{
s++;
string adapterName = nameAndSymbol.Key;
ITypeSymbol elementSymbol = nameAndSymbol.Value;
GenerateArrayAdapter(ref s, adapterName, elementSymbol);
}
}

private void GenerateConstructorAdapter(
Expand Down Expand Up @@ -345,6 +367,59 @@ private void GenerateStructAdapter(
}
}

private void GenerateArrayAdapter(
ref SourceBuilder s,
string adapterName,
ITypeSymbol elementType)
{
string ns = GetNamespace(elementType);
string elementName = elementType.Name;

if (adapterName.StartsWith(AdapterFromPrefix))
{
s += $"private static JSValue {adapterName}({ns}.{elementName}[] array)";
s += "{";
s += "JSArray jsArray = new JSArray(array.Length);";
s += "for (int i = 0; i < array.Length; i++)";
s += "{";
s += $"jsArray[i] = {Convert("array[i]", elementType, null)};";
s += "}";
s += "return jsArray;";
s += "}";
}
else
{
s += $"private static {ns}.{elementName}[] {adapterName}(JSValue value)";
s += "{";
s += "JSArray jsArray = (JSArray)value;";
s += $"{ns}.{elementName}[] array = new {ns}.{elementName}[jsArray.Length];";
s += "for (int i = 0; i < array.Length; i++)";
s += "{";
s += $"array[i] = {Convert("jsArray[i]", null, elementType)};";
s += "}";
s += "return array;";
s += "}";
}
}

private bool IsTypedArrayType(ITypeSymbol elementType)
{
return elementType.SpecialType switch
{
SpecialType.System_SByte => true,
SpecialType.System_Byte => true,
SpecialType.System_Int16 => true,
SpecialType.System_UInt16 => true,
SpecialType.System_Int32 => true,
SpecialType.System_UInt32 => true,
SpecialType.System_Int64 => true,
SpecialType.System_UInt64 => true,
SpecialType.System_Single => true,
SpecialType.System_Double => true,
_ => false,
};
}

private void AdaptThisArg(ref SourceBuilder s, ISymbol symbol)
{

Expand Down Expand Up @@ -425,6 +500,14 @@ private string Convert(string fromExpression, ITypeSymbol? fromType, ITypeSymbol
}
else if (toType.TypeKind == TypeKind.Struct)
{
if (toType is INamedTypeSymbol namedType &&
namedType.TypeParameters.Length == 1 &&
namedType.OriginalDefinition.Name == "Memory" &&
IsTypedArrayType(namedType.TypeArguments[0]))
{
return $"((JSTypedArray<{namedType.TypeArguments[0]}>){fromExpression}).AsMemory()";
}

VerifyReferencedTypeIsExported(toType);

string adapterName = GetStructAdapterName(toType, toJS: false);
Expand All @@ -438,6 +521,26 @@ private string Convert(string fromExpression, ITypeSymbol? fromType, ITypeSymbol
return $"{adapterName}({fromExpression})";
}
}
else if (toType.TypeKind == TypeKind.Array)
{
ITypeSymbol elementType = ((IArrayTypeSymbol)toType).ElementType;
VerifyReferencedTypeIsExported(elementType);

string adapterName = GetArrayAdapterName(elementType, toJS: false);
if (isNullable)
{
return $"({fromExpression}).IsNullOrUndefined() ? ({elementType}[]?)null : " +
$"{adapterName}({fromExpression})";
}
else
{
return $"{adapterName}({fromExpression})";
}
}
else if (toType is INamedTypeSymbol namedType && namedType.TypeParameters.Length > 0)
{
// TODO: Handle generic collections.
}

// TODO: Handle other kinds of conversions from JSValue.
// TODO: Handle unwrapping external values.
Expand Down Expand Up @@ -483,6 +586,14 @@ private string Convert(string fromExpression, ITypeSymbol? fromType, ITypeSymbol
}
else if (fromType.TypeKind == TypeKind.Struct)
{
if (fromType is INamedTypeSymbol namedType &&
namedType.TypeParameters.Length == 1 &&
namedType.OriginalDefinition.Name == "Memory" &&
IsTypedArrayType(namedType.TypeArguments[0]))
{
return $"new JSTypedArray<{namedType.TypeArguments[0]}>({fromExpression})";
}

VerifyReferencedTypeIsExported(fromType);

string adapterName = GetStructAdapterName(fromType, toJS: true);
Expand All @@ -496,6 +607,26 @@ private string Convert(string fromExpression, ITypeSymbol? fromType, ITypeSymbol
return $"{adapterName}({fromExpression})";
}
}
else if (fromType.TypeKind == TypeKind.Array)
{
ITypeSymbol elementType = ((IArrayTypeSymbol)fromType).ElementType;
VerifyReferencedTypeIsExported(elementType);

string adapterName = GetArrayAdapterName(elementType, toJS: true);
if (isNullable)
{
return $"{fromExpression} == null ? JSValue.Null : " +
$"{adapterName}({fromExpression})";
}
else
{
return $"{adapterName}({fromExpression})";
}
}
else if (fromType is INamedTypeSymbol namedType && namedType.TypeParameters.Length > 0)
{
// TODO: Handle generic collections.
}

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

private void VerifyReferencedTypeIsExported(ITypeSymbol type)
{
switch (type.SpecialType)
{
case SpecialType.System_Object:
case SpecialType.System_String: return;
default: break;
}

if (ModuleGenerator.GetJSExportAttribute(type) == null)
{
// TODO: Consider an option to automatically export referenced classes?
Expand Down
4 changes: 2 additions & 2 deletions Generator/ModuleGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ public void Execute(GeneratorExecutionContext context)
// No type definitions are generated when using a custom init function.
if (moduleInitializer is not IMethodSymbol)
{
SourceText typeDefinitions = TypeDefinitionsGenerator.GenerateTypeDefinitions(
exportItems);
TypeDefinitionsGenerator tsGenerator = new(exportItems);
SourceText typeDefinitions = tsGenerator.GenerateTypeDefinitions();
if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(
"build_property.TargetPath", out string? targetPath))
{
Expand Down
106 changes: 93 additions & 13 deletions Generator/TypeDefinitionsGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,29 @@

namespace NodeApi.Generator;

// An analyzer bug results in incorrect reports of CA1822 against methods in this class.
#pragma warning disable CA1822 // Mark members as static

internal class TypeDefinitionsGenerator : SourceGenerator
{
private static readonly Regex s_newlineRegex = new("\n *");
private static readonly Regex s_summaryRegex = new("<summary>(.*)</summary>");
private static readonly Regex s_remarksRegex = new("<remarks>(.*)</remarks>");

internal static SourceText GenerateTypeDefinitions(IEnumerable<ISymbol> exportItems)
private readonly IEnumerable<ISymbol> _exportItems;

public TypeDefinitionsGenerator(IEnumerable<ISymbol> exportItems)
{
_exportItems = exportItems;
}

internal SourceText GenerateTypeDefinitions()
{
var s = new SourceBuilder();

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

foreach (ISymbol exportItem in exportItems)
foreach (ISymbol exportItem in _exportItems)
{
if (exportItem is ITypeSymbol exportType &&
(exportType.TypeKind == TypeKind.Class || exportType.TypeKind == TypeKind.Struct))
Expand Down Expand Up @@ -50,7 +60,7 @@ internal static SourceText GenerateTypeDefinitions(IEnumerable<ISymbol> exportIt
return s;
}

private static void GenerateClassTypeDefinitions(ref SourceBuilder s, ITypeSymbol exportClass)
private void GenerateClassTypeDefinitions(ref SourceBuilder s, ITypeSymbol exportClass)
{
s++;
GenerateDocComments(ref s, exportClass);
Expand Down Expand Up @@ -117,8 +127,10 @@ member is IMethodSymbol exportConstructor &&
s += "}";
}

private static string GetTSType(ITypeSymbol type)
private string GetTSType(ITypeSymbol type)
{
string tsType = "unknown";

string? specialType = type.SpecialType switch
{
SpecialType.System_Void => "void",
Expand All @@ -137,25 +149,93 @@ private static string GetTSType(ITypeSymbol type)
////SpecialType.System_DateTime => "Date",
_ => null,
};

if (specialType != null)
{
return specialType;
tsType = specialType;
}

if (type.TypeKind == TypeKind.Class)
else if (type.TypeKind == TypeKind.Array)
{
// TODO: Check if class is exported.
ITypeSymbol elementType = ((IArrayTypeSymbol)type).ElementType;
tsType = GetTSType(elementType) + "[]";
}
else if (type.TypeKind == TypeKind.Array)
else if (type is INamedTypeSymbol namedType && namedType.TypeParameters.Length > 0)
{
if (namedType.OriginalDefinition.Name == "Nullable")
{
tsType = GetTSType(namedType.TypeArguments[0]) + " | null";
}
else if (namedType.OriginalDefinition.Name == "Memory")
{
ITypeSymbol elementType = namedType.TypeArguments[0];
tsType = elementType.SpecialType switch
{
SpecialType.System_SByte => "Int8Array",
SpecialType.System_Int16 => "Int16Array",
SpecialType.System_Int32 => "Int32Array",
SpecialType.System_Int64 => "BigInt64Array",
SpecialType.System_Byte => "Uint8Array",
SpecialType.System_UInt16 => "Uint16Array",
SpecialType.System_UInt32 => "Uint32Array",
SpecialType.System_UInt64 => "BigUint64Array",
SpecialType.System_Single => "Float32Array",
SpecialType.System_Double => "Float64Array",
_ => "unknown",
};
}
else if (namedType.OriginalDefinition.Name == "IList")
{
tsType = GetTSType(namedType.TypeArguments[0]) + "[]";
}
else if (namedType.OriginalDefinition.Name == "IReadOnlyList")
{
tsType = "readonly " + GetTSType(namedType.TypeArguments[0]) + "[]";
}
else if (namedType.OriginalDefinition.Name == "ICollection" ||
namedType.OriginalDefinition.Name == "ISet")
{
string elementTsType = GetTSType(namedType.TypeArguments[0]);
return $"Set<{elementTsType}>";
}
else if (namedType.OriginalDefinition.Name == "IReadOnlyCollection" ||
namedType.OriginalDefinition.Name == "IReadOnlySet")
{
string elementTsType = GetTSType(namedType.TypeArguments[0]);
return $"ReadonlySet<{elementTsType}>";
}
else if (namedType.OriginalDefinition.Name == "IEnumerable")
{
string elementTsType = GetTSType(namedType.TypeArguments[0]);
return $"Iterable<{elementTsType}>";
}
else if (namedType.OriginalDefinition.Name == "IDictionary")
{
string keyTSType = GetTSType(namedType.TypeArguments[0]);
string valueTSType = GetTSType(namedType.TypeArguments[1]);
tsType = $"Map<{keyTSType}, {valueTSType}>";
}
else if (namedType.OriginalDefinition.Name == "IReadOnlyDictionary")
{
string keyTSType = GetTSType(namedType.TypeArguments[0]);
string valueTSType = GetTSType(namedType.TypeArguments[1]);
tsType = $"ReadonlyMap<{keyTSType}, {valueTSType}>";
}
}
else if (_exportItems.Contains(type, SymbolEqualityComparer.Default))
{
tsType = type.Name;
}

if (type.NullableAnnotation == NullableAnnotation.Annotated &&
tsType != "any" && !tsType.EndsWith(" | null"))
{
// TODO: Get element type.
return "any[]";
tsType += " | null";
}

return "any";
return tsType;
}

private static string GetTSParameters(IMethodSymbol method, string indent)
private string GetTSParameters(IMethodSymbol method, string indent)
{
if (method.Parameters.Length == 0)
{
Expand Down
Loading