Skip to content

Commit

Permalink
Add support for Unbound Generic Types (#158)
Browse files Browse the repository at this point in the history
* Use Avalonia 0.9.*

* Use Avalonia 0.9.* as inline data

* Update package identity tests

* Remove extra file

* Don't add global:: if the type is unbound

* Support generic type parameters in Rx*Events classes

* Support inheritance from generic base types

* Approve the new generated API

* Formatting fixes

* Further formatting fixes

* Use the GenerateName local function
  • Loading branch information
worldbeater authored Aug 19, 2020
1 parent 1571291 commit 3e64ef6
Show file tree
Hide file tree
Showing 20 changed files with 1,201 additions and 27,961 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,40 @@ private static ClassDeclarationSyntax GenerateStaticClass(string namespaceName,
.WithLeadingTrivia(XmlSyntaxFactory.GenerateSummarySeeAlsoComment("A class that contains extension methods to wrap events for classes contained within the {0} namespace.", namespaceName))
.WithMembers(List<MemberDeclarationSyntax>(declarations.Select(declaration =>
{
var eventsClassName = IdentifierName("Rx" + declaration.Name + "Events");
return MethodDeclaration(eventsClassName, Identifier("Events"))
return BuildMethodDeclaration(declaration)
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
.WithParameterList(ParameterList(SingletonSeparatedList(
Parameter(Identifier("item"))
.WithModifiers(TokenList(Token(SyntaxKind.ThisKeyword)))
.WithType(IdentifierName(declaration.GenerateFullGenericName())))))
.WithExpressionBody(ArrowExpressionClause(
ObjectCreationExpression(eventsClassName)
.WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName("item")))))))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
.WithObsoleteAttribute(declaration)
.WithLeadingTrivia(XmlSyntaxFactory.GenerateSummarySeeAlsoComment("A wrapper class which wraps all the events contained within the {0} class.", declaration.ConvertToDocument()));

static MethodDeclarationSyntax BuildMethodDeclaration(ITypeDefinition declaration)
{
if (declaration.IsUnboundGenericTypeDefinition())
{
var args = string.Join(", ", declaration.TypeArguments.Select(param => param.FullName));
var genericEventsClassName = IdentifierName("Rx" + declaration.Name + "Events<" + args + ">");
return MethodDeclaration(genericEventsClassName, Identifier("Events"))
.WithTypeParameterList(TypeParameterList(
Token(SyntaxKind.LessThanToken),
SeparatedList(declaration.TypeArguments.Select(arg => TypeParameter(arg.FullName))),
Token(SyntaxKind.GreaterThanToken)))
.WithExpressionBody(ArrowExpressionClause(
ObjectCreationExpression(genericEventsClassName)
.WithArgumentList(ArgumentList(SingletonSeparatedList(
Argument(IdentifierName("item")))))));
}

var eventsClassName = IdentifierName("Rx" + declaration.Name + "Events");
return MethodDeclaration(eventsClassName, Identifier("Events"))
.WithExpressionBody(ArrowExpressionClause(
ObjectCreationExpression(eventsClassName)
.WithArgumentList(ArgumentList(SingletonSeparatedList(
Argument(IdentifierName("item")))))));
}
})));
}

Expand Down Expand Up @@ -112,9 +133,29 @@ private static ClassDeclarationSyntax GenerateEventWrapperClass(ITypeDefinition
.WithObsoleteAttribute(typeDefinition)
.WithLeadingTrivia(XmlSyntaxFactory.GenerateSummarySeeAlsoComment("A class which wraps the events contained within the {0} class as observables.", typeDefinition.ConvertToDocument()));

if (typeDefinition.IsUnboundGenericTypeDefinition())
{
classDeclaration = classDeclaration.WithTypeParameterList(TypeParameterList(
Token(SyntaxKind.LessThanToken),
SeparatedList(typeDefinition.TypeArguments.Select(arg => TypeParameter(arg.FullName))),
Token(SyntaxKind.GreaterThanToken)));
}

if (baseTypeDefinition != null)
{
classDeclaration = classDeclaration.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(IdentifierName($"global::{baseTypeDefinition.Namespace}.Rx{baseTypeDefinition.Name}Events")))));
var baseTypeName = $"global::{baseTypeDefinition.Namespace}.Rx{baseTypeDefinition.Name}Events";
if (baseTypeDefinition.IsUnboundGenericTypeDefinition())
{
var directBaseType = typeDefinition.DirectBaseTypes
.First(directBase => directBase.FullName == baseTypeDefinition.FullName);
var argumentList = directBaseType.TypeArguments.Select(arg => arg.GenerateFullGenericName());
var argumentString = "<" + string.Join(", ", argumentList) + ">";
baseTypeName += argumentString;
}

classDeclaration = classDeclaration.WithBaseList(BaseList(
SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(
IdentifierName(baseTypeName)))));
}

return classDeclaration;
Expand Down
18 changes: 15 additions & 3 deletions src/Pharmacist.Core/Generation/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,26 @@ public static string GenerateFullGenericName(this IType currentType)

if (currentType.TypeParameterCount > 0)
{
sb.Append('<')
.Append(string.Join(", ", currentType.TypeArguments.Select(GenerateFullGenericName)))
.Append('>');
var isUnbound = currentType.IsUnbound();
var arguments = string.Join(", ", currentType.TypeArguments.Select(GenerateName));
sb.Append('<').Append(arguments).Append('>');

string GenerateName(IType type) => isUnbound ? type.FullName : GenerateFullGenericName(type);
}

return sb.ToString();
}

/// <summary>
/// Checks if the specified generic type definition is unbound.
/// </summary>
/// <param name="definition">The type to check properties for.</param>
/// <returns>Returns true if type is an unbound generic type, otherwise false.</returns>
public static bool IsUnboundGenericTypeDefinition(this ITypeDefinition definition)
{
return definition.TypeParameterCount > 0 && definition.IsUnbound();
}

private static IEnumerable<ITypeDefinition> GetPublicTypeDefinitionsWithEvents(ICompilation compilation)
{
return _publicEventsTypeMapping.GetOrAdd(
Expand Down
Loading

0 comments on commit 3e64ef6

Please sign in to comment.