Skip to content
New issue

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

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

Already on GitHub? # to your account

Add support for Unbound Generic Types #158

Merged
merged 11 commits into from
Aug 19, 2020
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