Skip to content

Commit

Permalink
Feature generic classes (#143)
Browse files Browse the repository at this point in the history
* generate safe filenames

* add extensions to get generic type parameters and syntax

* add generic type parameters to class declaration

* add additional method declaration

* add generic types if needed

* add generic test class

* adding Parameter Helper Method

* removing processed items + using rootContainingSymbols

* using originalDefinition of generic types

* type format without generics + extra generic type format

* extra extension methods

* using new generic type format for GenerateFullGenericName

* use GetGenericTypeSyntax instead of GenerateFullGenericName

* better type parameters and generic names

* adding more test classes

* fix usage of attributes input

* adding classdeclaration with typeParameterConstraintClauses

* adding extra MethodDeclaration helper

* adding support for type parameter constraints

* clean up
  • Loading branch information
nilsauf authored Sep 26, 2022
1 parent 8bda347 commit 71eb227
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 24 deletions.
47 changes: 47 additions & 0 deletions src/ReactiveMarbles.ObservableEvents.Example/MyForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// See the LICENSE file in the project root for full license information.

using System;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Threading.Tasks;

using ReactiveMarbles.ObservableEvents;
Expand Down Expand Up @@ -54,4 +56,49 @@ public static class StaticTest
{
public static event EventHandler? TestChanged;
}

public class GenericTestClass<TGenericType, TGen2>
{
public event EventHandler<TGenericType> TestChanged;

public GenericTestClass()
{
var events = this.Events().TestChanged;
}
}

public class SecondGenericTextClass<TGen3> : GenericTestClass<AsyncEventsClass, TGen3>
{
public event EventHandler TestChanged2;

public SecondGenericTextClass()
{
var events = this.Events().TestChanged;
}
}

public class GenericClassesTestClass
{
public GenericClassesTestClass()
{
var colInt = new ObservableCollection<int>();
colInt.Events();

var col = new ObservableCollection<AsyncEventsClass>();
col.Events();
}
}

public class GenericConstraintsTestClass<TGen, TGen2, TGen3>
where TGen : AsyncEventsClass
where TGen2 : SecondGenericTextClass<TGen>
where TGen3 : class, new()
{
public event EventHandler<TGen2> TestChanged;

public GenericConstraintsTestClass()
{
var events = this.Events().TestChanged;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ private static void GenerateEventExtensionMethods(GeneratorExecutionContext cont
return;
}

context.AddSource("TestExtensions.FoundEvents.SourceGenerated.cs", SourceText.From(compilationUnit.ToFullString(), Encoding.UTF8));
string sourceText = compilationUnit.ToFullString();
context.AddSource("TestExtensions.FoundEvents.SourceGenerated.cs", SourceText.From(sourceText, Encoding.UTF8));
}

private static void GetAvailableTypes(
Expand Down Expand Up @@ -162,6 +163,11 @@ private static void GetAvailableTypes(
continue;
}

if (callingSymbol.IsGenericType)
{
callingSymbol = callingSymbol.OriginalDefinition;
}

var location = Location.Create(invocation.SyntaxTree, invocation.Span);

instanceNamespaceList.Add((location, callingSymbol));
Expand Down Expand Up @@ -202,23 +208,14 @@ private static bool GenerateEvents(
return true;
}

var processedItems = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);

var fileType = isStatic ? "Static" : "Instance";

var rootContainingSymbols = symbols.Select(x => x.NamedType).ToImmutableSortedSet(TypeDefinitionNameComparer.Default);

bool hasEvents = false;

foreach (var (location, item) in symbols)
{
if (processedItems.Contains(item))
foreach (var item in rootContainingSymbols)
{
continue;
}

processedItems.Add(item);

var namespaceItem = symbolGenerator.Generate(item, context.Compilation.GetTypeByMetadataName);

if (namespaceItem == null)
Expand All @@ -237,7 +234,9 @@ private static bool GenerateEvents(

var sourceText = compilationUnit.ToFullString();

var name = $"SourceClass{item.ToDisplayString(RoslynHelpers.SymbolDisplayFormat)}-{fileType}Events.SourceGenerated.cs";
SymbolDisplayFormat fileNameFormat = RoslynHelpers.SymbolDisplayFormat.WithGenericsOptions(SymbolDisplayGenericsOptions.None);
string genericHint = item.IsGenericType ? $"-{string.Join("-", item.TypeParameters.Select(type => type.Name))}" : string.Empty;
var name = $"SourceClass{item.ToDisplayString(fileNameFormat)}{genericHint}-{fileType}Events.SourceGenerated.cs";

context.AddSource(
name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ private static ConstructorDeclarationSyntax GenerateEventWrapperClassConstructor
},
2);

return ConstructorDeclaration(default, new[] { SyntaxKind.PublicKeyword }, new[] { Parameter(typeDefinition.GenerateFullGenericName(), dataParameterName) }, className, constructorBlock, 1)
return ConstructorDeclaration(default, new[] { SyntaxKind.PublicKeyword }, new[] { Parameter(typeDefinition.GetTypeSyntax(), dataParameterName) }, className, constructorBlock, 1)
.WithLeadingTrivia(
XmlSyntaxFactory.GenerateSummarySeeAlsoComment("Initializes a new instance of the {0} class.", className, (dataParameterName, "The class that is being wrapped.")));
}

private static FieldDeclarationSyntax GenerateEventWrapperField(INamedTypeSymbol typeDefinition)
{
return FieldDeclaration(
typeDefinition.GenerateFullGenericName(),
typeDefinition.GetTypeSyntax(),
DataFieldName,
new[] { SyntaxKind.PrivateKeyword, SyntaxKind.ReadOnlyKeyword },
1);
Expand Down Expand Up @@ -93,6 +93,8 @@ private static IEnumerable<ClassDeclarationSyntax> GenerateEventWrapperClasses(I
obsoleteList,
new[] { SyntaxKind.InternalKeyword },
members.Concat(properties).ToList(),
typeDefinition.GetTypeParameterConstraints(),
typeDefinition.GetTypeParametersAsTypeParameterSyntax(),
1);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -37,11 +33,14 @@ private static MethodDeclarationSyntax GenerateInstanceMethod(INamedTypeSymbol d
{
var eventsClassName = "global::" + declarationType.ContainingNamespace.ToDisplayString(RoslynHelpers.SymbolDisplayFormat) + ".Rx" + declarationType.Name + "Events";
var modifiers = new[] { SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword };
var parameters = new[] { Parameter(declarationType.GenerateFullGenericName(), "item", new[] { SyntaxKind.ThisKeyword }) };
var body = ArrowExpressionClause(ObjectCreationExpression(eventsClassName, new[] { Argument("item") }));
var parameters = new[] { Parameter(declarationType.GetTypeSyntax(), "item", new[] { SyntaxKind.ThisKeyword }) };
var typeParameterConstraints = declarationType.GetTypeParameterConstraints();
var typeParameters = declarationType.GetTypeParametersAsTypeParameterSyntax();
var returnTypeSyntax = declarationType.GetTypeSyntax(eventsClassName);
var body = ArrowExpressionClause(ObjectCreationExpression(returnTypeSyntax, new[] { Argument("item") }));
var attributes = RoslynHelpers.GenerateObsoleteAttributeList(declarationType);

return MethodDeclaration(attributes, modifiers, eventsClassName, "Events", parameters, 0, body)
return MethodDeclaration(attributes, modifiers, returnTypeSyntax, "Events", parameters, typeParameterConstraints, typeParameters, 0, body)
.WithLeadingTrivia(XmlSyntaxFactory.GenerateSummarySeeAlsoComment("A wrapper class which wraps all the events contained within the {0} class.", declarationType.GetArityDisplayName()));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ public static IReadOnlyList<TypeParameterSyntax> ToTypeParameters(this IEnumerab
/// <returns>A type descriptor including the generic arguments.</returns>
public static string GenerateFullGenericName(this ITypeSymbol currentType)
{
return currentType.ToDisplayString(RoslynHelpers.TypeFormat);
return currentType.ToDisplayString(RoslynHelpers.GenericTypeFormat);
}

public static IEnumerable<INamedTypeSymbol> GetBasesWithCondition(this INamedTypeSymbol symbol, Func<INamedTypeSymbol, bool> condition)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.CodeAnalysis;
Expand All @@ -23,7 +24,9 @@ internal static class RoslynHelpers

public static SymbolDisplayFormat SymbolDisplayFormat { get; } = new SymbolDisplayFormat(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeTypeConstraints | SymbolDisplayGenericsOptions.IncludeVariance);

public static SymbolDisplayFormat TypeFormat { get; } = new SymbolDisplayFormat(globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
public static SymbolDisplayFormat GenericTypeFormat { get; } = new SymbolDisplayFormat(globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

public static SymbolDisplayFormat TypeFormat { get; } = new SymbolDisplayFormat(globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.None, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

/// <summary>
/// Gets an argument which access System.Reactive.Unit.Default member.
Expand Down Expand Up @@ -60,5 +63,90 @@ public static AttributeListSyntax[] GenerateObsoleteAttributeList(ISymbol eventD

return Array.Empty<AttributeListSyntax>();
}

public static IReadOnlyCollection<TypeParameterSyntax> GetTypeParametersAsTypeParameterSyntax(
this INamedTypeSymbol typeSymbol)
{
return typeSymbol.GetTypeParametersAs(TypeParameter);
}

public static IReadOnlyCollection<TypeSyntax> GetTypeParametersAsTypeSyntax(
this INamedTypeSymbol typeSymbol)
{
return typeSymbol.GetTypeParametersAs(name => SyntaxFactory.ParseTypeName(name));
}

public static TypeSyntax GetTypeSyntax(
this ITypeSymbol typeSymbol,
string? newName = null,
IReadOnlyCollection<TypeSyntax>? genericTypeParameters = null)
{
string typeName = newName ?? typeSymbol.ToDisplayString(TypeFormat);

if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.TypeParameters.Any())
{
return GenericName(typeName, genericTypeParameters ?? namedTypeSymbol.GetTypeParametersAsTypeSyntax());
}

return IdentifierName(typeName);
}

public static IReadOnlyCollection<TypeParameterConstraintClauseSyntax> GetTypeParameterConstraints(
this INamedTypeSymbol typeSymbol)
{
return typeSymbol.TypeParameters
.Select(tp => (
tp.Name,
Constraints: tp.GetOtherConstrainst()
.Concat(tp.ConstraintTypes.Select(GetAsTypeConstraint))
.Select(tpc => tpc.WithTrailingTrivia(SyntaxFactory.Space).WithoutLeadingTrivia())
.ToArray()))
.Where(tp => tp.Constraints.Length > 0)
.Select(tp => TypeParameterConstraintClause(tp.Name, tp.Constraints))
.ToArray();
}

private static IEnumerable<TypeParameterConstraintSyntax> GetOtherConstrainst(
this ITypeParameterSymbol typeSymbol)
{
if (typeSymbol.HasReferenceTypeConstraint ||
typeSymbol.HasValueTypeConstraint)
{
yield return SyntaxFactory.ClassOrStructConstraint(
typeSymbol.HasReferenceTypeConstraint ? SyntaxKind.ClassConstraint :
typeSymbol.HasValueTypeConstraint ? SyntaxKind.StructConstraint :
throw new InvalidOperationException());
}

if (typeSymbol.HasConstructorConstraint)
{
yield return SyntaxFactory.ConstructorConstraint();
}
}

private static TypeParameterConstraintSyntax GetAsTypeConstraint(
this ITypeSymbol typeSymbol)
{
if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.TypeArguments.Any())
{
return TypeConstraint(namedTypeSymbol.GetTypeSyntax(
genericTypeParameters: namedTypeSymbol.TypeArguments
.Select(tp => tp.GenerateFullGenericName())
.Select(IdentifierName)
.ToArray()));
}

return TypeConstraint(typeSymbol.GetTypeSyntax());
}

private static IReadOnlyCollection<T> GetTypeParametersAs<T>(
this INamedTypeSymbol typeSymbol,
Func<string, T> parseName)
{
return typeSymbol.TypeParameters
.Select(tp => tp.Name)
.Select(parseName)
.ToArray();
}
}
}
Loading

0 comments on commit 71eb227

Please sign in to comment.