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

Add generic type transpilation #164

Merged
merged 5 commits into from
Feb 13, 2024
Merged
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
9 changes: 8 additions & 1 deletion src/Tapper/DefaultTypeMapperProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ public class DefaultTypeMapperProvider : ITypeMapperProvider
{
private readonly ArrayTypeMapper _arrayTypeMapper;
private readonly TupleTypeMapper _tupleTypeMapper;
private readonly GenericTypeParameterMapper _genericTypeParameterMapper;

private readonly IDictionary<ITypeSymbol, ITypeMapper> _mappers;

public DefaultTypeMapperProvider(Compilation compilation, bool includeReferencedAssemblies)
{
_arrayTypeMapper = new ArrayTypeMapper(compilation);
_tupleTypeMapper = new TupleTypeMapper();
_genericTypeParameterMapper = new GenericTypeParameterMapper();

var dateTimeTypeMapper = new DateTimeTypeMapper(compilation);
var dateTimeOffsetTypeMapper = new DateTimeOffsetTypeMapper(compilation);
Expand Down Expand Up @@ -49,7 +51,12 @@ public ITypeMapper GetTypeMapper(ITypeSymbol type)
return _arrayTypeMapper;
}

var sourceType = type is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType
if (type is ITypeParameterSymbol)
{
return _genericTypeParameterMapper;
}

var sourceType = type is INamedTypeSymbol namedTypeSymbol
? namedTypeSymbol.ConstructedFrom
: type;

Expand Down
17 changes: 17 additions & 0 deletions src/Tapper/GenericTypeParameterMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using Microsoft.CodeAnalysis;

namespace Tapper;

internal class GenericTypeParameterMapper : ITypeMapper
{
public ITypeSymbol Assign { get; } = default!;

public string MapTo(ITypeSymbol typeSymbol, ITranspilationOptions options)
{
if (typeSymbol is not ITypeParameterSymbol typeParameterSymbol)
throw new InvalidOperationException($"{nameof(GenericTypeParameterMapper)} does not support {typeSymbol.ToDisplayString()}.");

return typeParameterSymbol.Name;
}
}
15 changes: 14 additions & 1 deletion src/Tapper/TypeMappers/SourceTypeMapper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis;

namespace Tapper.TypeMappers;
Expand All @@ -14,8 +15,20 @@ public SourceTypeMapper(INamedTypeSymbol sourceTypes)

public string MapTo(ITypeSymbol typeSymbol, ITranspilationOptions options)
{
if (SymbolEqualityComparer.Default.Equals(typeSymbol, Assign))
var symbol = (typeSymbol as INamedTypeSymbol)?.ConstructedFrom ?? typeSymbol;

if (SymbolEqualityComparer.Default.Equals(symbol, Assign))
{
if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType)
{
var mappedTypeParameters = namedTypeSymbol.TypeArguments.Select(param =>
{
var mapper = options.TypeMapperProvider.GetTypeMapper(param);
return mapper.MapTo(param, options);
});
return $"{typeSymbol.Name}<{string.Join(", ", mappedTypeParameters)}>";
}

return typeSymbol.Name;
}

Expand Down
46 changes: 32 additions & 14 deletions src/Tapper/TypeTranslators/DefaultMessageTypeTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ public void Translate(ref CodeWriter codeWriter, INamedTypeSymbol typeSymbol, IT
.IgnoreStatic()
.ToArray();

codeWriter.Append($"/** Transpiled from {typeSymbol.ToDisplayString()} */{newLineString}");
codeWriter.Append($"export type {typeSymbol.Name} = {{{newLineString}");

codeWriter.Append($"/** Transpiled from {typeSymbol.OriginalDefinition.ToDisplayString()} */{newLineString}");
codeWriter.Append($"export type {MessageTypeTranslatorHelper.GetGenericTypeName(typeSymbol)} = {{{newLineString}");

foreach (var member in members)
{
Expand All @@ -43,7 +44,7 @@ public void Translate(ref CodeWriter codeWriter, INamedTypeSymbol typeSymbol, IT

if (MessageTypeTranslatorHelper.IsSourceType(typeSymbol.BaseType, options))
{
codeWriter.Append($" & {typeSymbol.BaseType.Name};");
codeWriter.Append($" & {MessageTypeTranslatorHelper.GetConcreteTypeName(typeSymbol.BaseType, options)};");
}

codeWriter.Append(newLineString);
Expand All @@ -52,6 +53,33 @@ public void Translate(ref CodeWriter codeWriter, INamedTypeSymbol typeSymbol, IT

file static class MessageTypeTranslatorHelper
{
public static string GetConcreteTypeName(INamedTypeSymbol typeSymbol, ITranspilationOptions options)
{
var genericTypeArguments = "";
if (typeSymbol.IsGenericType)
{
var mappedGenericTypeArguments = typeSymbol.TypeArguments.Select(typeArg =>
{
var mapper = options.TypeMapperProvider.GetTypeMapper(typeArg);
return mapper.MapTo(typeArg, options);
});
genericTypeArguments = $"<{string.Join(", ", mappedGenericTypeArguments)}>";
}

return $"{typeSymbol.Name}{genericTypeArguments}";
}

public static string GetGenericTypeName(INamedTypeSymbol typeSymbol)
{
var genericTypeParameters = "";
if (typeSymbol.IsGenericType)
{
genericTypeParameters = $"<{string.Join(", ", typeSymbol.TypeParameters.Select(param => param.Name))}>";
}

return $"{typeSymbol.Name}{genericTypeParameters}";
}

public static (ITypeSymbol TypeSymbol, bool IsNullable) GetMemberTypeSymbol(ISymbol symbol, ITranspilationOptions options)
{
if (symbol is IPropertySymbol propertySymbol)
Expand All @@ -62,11 +90,6 @@ public static (ITypeSymbol TypeSymbol, bool IsNullable) GetMemberTypeSymbol(ISym
{
if (typeSymbol is INamedTypeSymbol namedTypeSymbol)
{
if (!namedTypeSymbol.IsGenericType)
{
return (typeSymbol, false);
}

if (namedTypeSymbol.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T)
{
return (namedTypeSymbol.TypeArguments[0], true);
Expand All @@ -87,11 +110,6 @@ public static (ITypeSymbol TypeSymbol, bool IsNullable) GetMemberTypeSymbol(ISym
{
if (typeSymbol is INamedTypeSymbol namedTypeSymbol)
{
if (!namedTypeSymbol.IsGenericType)
{
return (typeSymbol, false);
}

if (namedTypeSymbol.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T)
{
return (namedTypeSymbol.TypeArguments[0], true);
Expand All @@ -112,7 +130,7 @@ public static bool IsSourceType([NotNullWhen(true)] INamedTypeSymbol? typeSymbol
{
if (typeSymbol is not null && typeSymbol.SpecialType != SpecialType.System_Object)
{
if (options.SourceTypes.Contains(typeSymbol, SymbolEqualityComparer.Default))
if (options.SourceTypes.Contains(typeSymbol.ConstructedFrom, SymbolEqualityComparer.Default))
{
return true;
}
Expand Down
66 changes: 66 additions & 0 deletions tests/Tapper.Test.SourceTypes/GenericClasses.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Space1;
using Tapper;

namespace Tapper.Test.SourceTypes
{

[TranspilationSource]
public class GenericClass1<T>
{
public required string StringProperty { get; set; }
public required T GenericProperty { get; set; }
}

[TranspilationSource]
public class NestedGenericClass<T1, T2>
{
public required string StringProperty { get; set; }
public required T1 GenericProperty { get; set; }
public required GenericClass1<T1> GenericClass1Property { get; set; }
public required GenericClass2<T1, T2> GenericClass2Property { get; set; }
}

[TranspilationSource]
public class DeeplyNestedGenericClass<A, B, C>
{
public required string StringProperty { get; set; }
public required A GenericPropertyA { get; set; }
public required B GenericPropertyB { get; set; }
public required GenericClass1<A> GenericClass1Property { get; set; }
public required GenericClass2<B, C> GenericClass2Property { get; set; }
public required NestedGenericClass<string, B> NestedGenericClassProperty { get; set; }
}

[TranspilationSource]
public class InheritedGenericClass2<T1, T2> : GenericClass1<T1>
{
public required T2 GenericPropertyT2 { get; set; }
}


[TranspilationSource]
public class InheritedConcreteGenericClass : GenericClass2<bool, int>
{
}

[TranspilationSource]
public class InheritedGenericClassWithTheSameName<T>
{
public required T GenericProperty { get; set; }
}
[TranspilationSource]
public class InheritedGenericClassWithTheSameName : InheritedGenericClassWithTheSameName<string>
{
}
}
namespace Space1
{

[TranspilationSource]
public class GenericClass2<T1, T2>
{
public required string StringProperty { get; set; }
public required T1 GenericProperty1 { get; set; }
public required T2 GenericProperty2 { get; set; }
}
}
7 changes: 6 additions & 1 deletion tests/Tapper.Tests/CompilationSingleton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ static CompilationSingleton()
File.ReadAllText("../../../../Tapper.Test.SourceTypes/NestedType.cs"),
options);

var genericClassSyntax = CSharpSyntaxTree.ParseText(
File.ReadAllText("../../../../Tapper.Test.SourceTypes/GenericClasses.cs"),
options);

var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithNullableContextOptions(NullableContextOptions.Enable);

Expand Down Expand Up @@ -91,7 +95,8 @@ static CompilationSingleton()
attributeAnnotatedSyntax,
messagePackAttributesSyntax,
partialClassSyntax,
nestedTypeSyntax
nestedTypeSyntax,
genericClassSyntax,
},
references: references,
options: compilationOptions);
Expand Down
Loading
Loading