Skip to content

Underlying BaseVec type + refacto multi generic class name #60

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
"rest_service": "Substrate.RestService",
"rest_client": "Substrate.RestClient"
},
"metadata": {
"websocket": "ws://127.0.0.1:9944"
},
"metadata": {
"websocket": "ws://127.0.0.1:9944",
"refineMetadata": true
},
"rest_client_settings": {
"service_assembly": "Substrate.RestService.dll"
}
Expand Down
49 changes: 27 additions & 22 deletions Tools/Substrate.DotNet/Extensions/ReflectedEndpointExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,30 @@ namespace Substrate.DotNet.Extensions
/// </summary>
internal static class ReflectedEndpointExtensions
{
/// <summary>
/// Ensure we are importing all model items.
/// Not actually required since we use fully qualified items but we want to get rid of that later.
/// </summary>
/// <param name="currentNamespace"></param>
/// <param name="defaultReturnType"></param>
public static void ManageNamespace(CodeNamespace currentNamespace, IReflectedEndpointType defaultReturnType)
{
currentNamespace.Imports.Add(new CodeNamespaceImport(defaultReturnType.Type.Namespace));
foreach (Type genericArgument in defaultReturnType.Type.GenericTypeArguments)
{
currentNamespace.Imports.Add(new CodeNamespaceImport(genericArgument.Namespace));

// Also import namespace from generic sub arguments
if (genericArgument.IsGenericType)
{
foreach (Type genericSubArgument in genericArgument.GenericTypeArguments)
{
currentNamespace.Imports.Add(new CodeNamespaceImport(genericSubArgument.Namespace));
}
}
}
}

/// <summary>
/// Converts a reflected endpoint to an interface method code element.
/// The generated method is being implemented by an actual client.
Expand Down Expand Up @@ -73,13 +97,7 @@ internal static CodeTypeMember ToMockupInterfaceMethod(this IReflectedEndpoint e
IReflectedEndpointType defaultReturnType = endpoint.GetResponse().GetSuccessReturnType();
if (defaultReturnType != null)
{
// Ensure we are importing all model items.
// Not actually required since we use fully qualified items but we want to get rid of that later.
currentNamespace.Imports.Add(new CodeNamespaceImport(defaultReturnType.Type.Namespace));
foreach (Type genericArgument in defaultReturnType.Type.GenericTypeArguments)
{
currentNamespace.Imports.Add(new CodeNamespaceImport(genericArgument.Namespace));
}
ManageNamespace(currentNamespace, defaultReturnType);
method.Parameters.Add(new CodeParameterDeclarationExpression(defaultReturnType.Type, "value"));
}

Expand Down Expand Up @@ -229,13 +247,7 @@ internal static CodeTypeMember ToMockupClientMethod(this IReflectedEndpoint endp
IReflectedEndpointType defaultReturnType = endpoint.GetResponse().GetSuccessReturnType();
if (defaultReturnType != null)
{
// Ensure we are importing all model items.
// Not actually required since we use fully qualified items but we want to get rid of that later.
clientNamespace.Imports.Add(new CodeNamespaceImport(defaultReturnType.Type.Namespace));
foreach (Type genericArgument in defaultReturnType.Type.GenericTypeArguments)
{
clientNamespace.Imports.Add(new CodeNamespaceImport(genericArgument.Namespace));
}
ManageNamespace(clientNamespace, defaultReturnType);
method.Parameters.Add(new CodeParameterDeclarationExpression(defaultReturnType.Type, "value"));
}

Expand Down Expand Up @@ -317,14 +329,7 @@ internal static CodeTypeMember ToUnitTestMethod(this IReflectedEndpoint endpoint
IReflectedEndpointType defaultReturnType = endpoint.GetResponse().GetSuccessReturnType();
if (defaultReturnType != null)
{
// Ensure we are importing all model items.
// Not actually required since we use fully qualified items but we want to get rid of that later.
clientNamespace.Imports.Add(new CodeNamespaceImport(defaultReturnType.Type.Namespace));
foreach (Type genericArgument in defaultReturnType.Type.GenericTypeArguments)
{
clientNamespace.Imports.Add(new CodeNamespaceImport(genericArgument.Namespace));
}

ManageNamespace(clientNamespace, defaultReturnType);
GenerateMockupValueStatement(currentMembers, method, defaultReturnType.Type);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,7 @@ internal static CodeTypeReference ToInterfaceMethodReturnType(this IReflectedEnd
return new CodeTypeReference(typeof(void));
}

// Ensure we are importing all model items.
// Not actually required since we use fully qualified items but we want to get rid of that later.
currentNamespace.Imports.Add(new CodeNamespaceImport(defaultReturnType.Type.Namespace));
foreach (Type genericArgument in defaultReturnType.Type.GenericTypeArguments)
{
currentNamespace.Imports.Add(new CodeNamespaceImport(genericArgument.Namespace));
}

ReflectedEndpointExtensions.ManageNamespace(currentNamespace, defaultReturnType);
return new CodeTypeReference(typeof(Task<>).MakeGenericType(new[] { defaultReturnType.Type }));
}

Expand Down
14 changes: 13 additions & 1 deletion Tools/Substrate.DotNet/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Substrate.DotNet.Service.Generators.Base;

namespace Substrate.DotNet
{
Expand Down Expand Up @@ -166,11 +167,22 @@ private static async Task<bool> UpgradeOrUpdateSubstrateEnvironmentAsync(bool fe

Log.Information("Using Runtime {runtime}", configuration.Metadata.Runtime);

// Service
if (configuration.Metadata.IsMetadataRefined)
{
Log.Information("MetaData refined option is activated");
SolutionGeneratorBase.RefinedUnnecessaryWrapper(metadata);
}

// NetApi
Log.Information("Generate {NetApi} classes", configuration.Projects.NetApi);
GenerateNetApiClasses(metadata, configuration);

// Service
Log.Information("Generate {RestService} classes", configuration.Projects.RestService);
GenerateRestServiceClasses(metadata, configuration);

// Client
Log.Information("Generate {RestClient} classes", configuration.Projects.RestClient);
GenerateRestClientClasses(configuration);

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Reflection.Metadata.Ecma335;

namespace Substrate.DotNet.Service.Generators.Base
{
Expand Down Expand Up @@ -145,6 +147,9 @@ private static Dictionary<string, int> GetRuntimeIndex(Dictionary<uint, NodeType

protected static void GetGenericStructs(Dictionary<uint, NodeType> nodeTypes)
{
var metadataNaming = new MetadataNaming(nodeTypes);
List<string> rewritedName = new();

Dictionary<string, int> _countPaths = new();
for (uint id = 0; id < nodeTypes.Keys.Max(); id++)
{
Expand Down Expand Up @@ -196,10 +201,207 @@ protected static void GetGenericStructs(Dictionary<uint, NodeType> nodeTypes)

if (generics.Contains(key))
{
type.Path[^1] = type.Path[^1] + "T" + (_countPaths.ContainsKey(key) ? _countPaths[key] : 1);
string suggestedClassName = metadataNaming.WriteClassName(type);
if(suggestedClassName != type.Path[^1] && !rewritedName.Any(x => x == suggestedClassName))
{
type.Path[^1] = suggestedClassName;
} else
{
type.Path[^1] = type.Path[^1] + "T" + (_countPaths.ContainsKey(key) ? _countPaths[key] : 1);
}
rewritedName.Add(type.Path[^1]);
}
}
}
}

/// <summary>
/// Refine current metadata by removing unecessary classes that encapsulate mostly Rust lists
/// C# is more permissive so we don't need to wrap BaseVec<> into a class
/// </summary>
public static void RefinedUnnecessaryWrapper(MetaData metadata)
{
Dictionary<uint, NodeType> nodeTypes = metadata.NodeMetadata.Types;
var metadataNaming = new MetadataNaming(nodeTypes);

bool hasSomethingChanged = false;
do
{
IDictionary<uint, uint> wrapperNodes = ExtractWrappers(nodeTypes);

// Loop over all node types to switch sourceId to new destinationId
hasSomethingChanged = MapSourceToDestination(nodeTypes, wrapperNodes);

if(hasSomethingChanged)
{
RemoveSourceIds(nodeTypes, metadataNaming, wrapperNodes);
RefineModules(metadata, wrapperNodes);
}
} while (hasSomethingChanged);
}

/// <summary>
/// Remove all unecessary id
/// </summary>
/// <param name="nodeTypes"></param>
/// <param name="metadataNaming"></param>
/// <param name="wrapperNodes"></param>
private static void RemoveSourceIds(Dictionary<uint, NodeType> nodeTypes, MetadataNaming metadataNaming, IDictionary<uint, uint> wrapperNodes)
{
foreach (KeyValuePair<uint, uint> node in wrapperNodes)
{
Log.Verbose("\t Replace {sourceType} (id = {sourceKey}) by {destinationType} (id = {destinationKey})", metadataNaming.WriteType(node.Key), node.Key, metadataNaming.WriteType(node.Value), node.Value);
nodeTypes.Remove(node.Key);
}
}

/// <summary>
/// Loop over all modules and replace old occurences
/// </summary>
/// <param name="metadata"></param>
/// <param name="wrapperNodes"></param>
private static void RefineModules(MetaData metadata, IDictionary<uint, uint> wrapperNodes)
{
Dictionary<uint, PalletModule> modules = metadata.NodeMetadata.Modules;
foreach (KeyValuePair<uint, PalletModule> module in modules)
{
PalletStorage storage = module.Value.Storage;

if (storage == null || storage.Entries == null)
{
continue;
}

foreach (Entry entry in storage.Entries)
{
if (wrapperNodes.ContainsKey(entry.TypeMap.Item1))
{
entry.TypeMap = new(wrapperNodes[entry.TypeMap.Item1], entry.TypeMap.Item2);
}
if (entry.TypeMap.Item2 != null && wrapperNodes.ContainsKey(entry.TypeMap.Item2.Key))
{
entry.TypeMap.Item2.Key = wrapperNodes[entry.TypeMap.Item2.Key];
}

if (entry.TypeMap.Item2 != null && wrapperNodes.ContainsKey(entry.TypeMap.Item2.Value))
{
entry.TypeMap.Item2.Value = wrapperNodes[entry.TypeMap.Item2.Value];
}

}
}
}

/// <summary>
/// Check every TypeDef composite which have only on TypeDef Sequence as property field.
/// Target multi generic references (BoundedVec, WeakBoundedVec etc)
/// Return a dictionnary of sourceId, destinationId
/// </summary>
/// <param name="nodeTypes"></param>
/// <returns></returns>
private static IDictionary<uint, uint> ExtractWrappers(Dictionary<uint, NodeType> nodeTypes)
{
var wrappers =
nodeTypes
.Where(x => x.Value.TypeDef == TypeDefEnum.Composite)
.Select(x => (NodeTypeComposite)x.Value)
.Where(x => x.Path != null)
.GroupBy(x => string.Join('.', x.Path))
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.Where(x => x.TypeFields != null && x.TypeFields.Length == 1)
.Where(x => nodeTypes[x.TypeFields[0].TypeId].TypeDef == TypeDefEnum.Sequence)
.Select(x => new
{
sourceId = x.Id,
sourceName = x.Path != null ? string.Join(".", x.Path) : string.Empty,
destinationId = nodeTypes[x.TypeFields[0].TypeId].Id
});

IDictionary<uint, uint> wrapperNodes = wrappers.ToDictionary(x => x.sourceId, x => x.destinationId);
return wrapperNodes;
}

/// <summary>
/// Change all occurences of old Id to Destination Id
/// </summary>
/// <param name="nodeTypes"></param>
/// <param name="wrapperNodes"></param>
/// <returns>True if a node has been changed</returns>
private static bool MapSourceToDestination(Dictionary<uint, NodeType> nodeTypes, IDictionary<uint, uint> wrapperNodes)
{
bool anyUpdate = false;
foreach (KeyValuePair<uint, NodeType> node in nodeTypes)
{
switch (node.Value)
{
case NodeTypeVariant detailVariant when detailVariant.Variants is not null:
foreach (NodeTypeField nodeTypeField in detailVariant.Variants
.Where(x => x.TypeFields != null)
.SelectMany(x => x.TypeFields))
{
if (wrapperNodes.ContainsKey(nodeTypeField.TypeId))
{
nodeTypeField.TypeId = wrapperNodes[nodeTypeField.TypeId];
anyUpdate = true;
}
}
break;

case NodeTypeCompact detailCompact when wrapperNodes.ContainsKey(detailCompact.TypeId):
detailCompact.TypeId = wrapperNodes[detailCompact.TypeId];
anyUpdate = true;
break;

case NodeTypeComposite detailComposite when detailComposite.TypeFields is not null:
foreach (NodeTypeField typeField in detailComposite.TypeFields)
{
if (wrapperNodes.ContainsKey(typeField.TypeId))
{
typeField.TypeId = wrapperNodes[typeField.TypeId];
anyUpdate = true;
}
}
break;

case NodeTypeSequence detailSequence when wrapperNodes.ContainsKey(detailSequence.TypeId):
detailSequence.TypeId = wrapperNodes[detailSequence.TypeId];
anyUpdate = true;
break;

case NodeTypeTuple detailTuple when detailTuple.TypeIds != null:
for (int i = 0; i < detailTuple.TypeIds.Length; i++)
{
if (wrapperNodes.ContainsKey(detailTuple.TypeIds[i]))
{
detailTuple.TypeIds[i] = wrapperNodes[detailTuple.TypeIds[i]];
anyUpdate = true;
}
}
break;

case NodeTypeArray detailArray when wrapperNodes.ContainsKey(detailArray.TypeId):
detailArray.TypeId = wrapperNodes[detailArray.TypeId];
anyUpdate = true;
break;

case NodeTypeBitSequence detailBitSequence:
if(wrapperNodes.ContainsKey(detailBitSequence.TypeIdStore))
{
detailBitSequence.TypeIdStore = wrapperNodes[detailBitSequence.TypeIdStore];
}

if (wrapperNodes.ContainsKey(detailBitSequence.TypeIdOrder))
{
detailBitSequence.TypeIdOrder = wrapperNodes[detailBitSequence.TypeIdOrder];
}

string x = "1";
break;
}
}

return anyUpdate;
}
}
}
Loading