Skip to content

Commit

Permalink
wip: removing reflection from introspective type resolver system
Browse files Browse the repository at this point in the history
  • Loading branch information
definitelyokay committed May 6, 2024
1 parent ef534d6 commit 5a3658c
Show file tree
Hide file tree
Showing 24 changed files with 571 additions and 104 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Chickensoft.Introspection.Generator.Tests.TestCases;

using System.Collections.Generic;
using Chickensoft.Serialization;

[Meta]
public partial class Collections {
[Save("nested_collections")]
public Dictionary<List<string>, List<List<int>>> NestedCollections { get; } = new();
}
25 changes: 18 additions & 7 deletions Chickensoft.Introspection.Generator/src/TypeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Chickensoft.Introspection.Generator;
using System.IO;
using System.Linq;
using System.Threading;
using Chickensoft.Introspection.Generator.Types.Models;
using Chickensoft.Introspection.Generator.Models;
using Chickensoft.Introspection.Generator.Utils;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -418,15 +418,16 @@ IndentedTextWriter code

var setter = property.HasSetter
? $"(object obj, object? value) => (({type.Reference.Name})obj)" +
$".{property.Name} = ({property.Type}){propertyValue}"
$".{property.Name} = ({property.GenericType.ClosedType}){propertyValue}"
: "(object obj, object? _) => { }";

code.WriteLine($"Name: \"{property.Name}\",");
code.WriteLine($"Type: typeof({property.Type}),");
code.WriteLine($"Getter: (object obj) => (({type.Reference.Name})obj).{property.Name},");
code.WriteLine($"Setter: {setter},");
code.WriteLine($"GenericTypeGetter: (ITypeReceiver receiver) => receiver.Receive<{property.Type}>(),");
code.WriteLine("AttributesByType: new System.Collections.Generic.Dictionary<System.Type, System.Attribute[]>() {");
code.Write("GenericType: ");
property.GenericType.Write(code);
code.WriteLine(",");
code.WriteLine("Attributes: new System.Collections.Generic.Dictionary<System.Type, System.Attribute[]>() {");

GenerateAttributeMapping(property.Attributes, code);

Expand Down Expand Up @@ -853,14 +854,24 @@ TypeDeclarationSyntax type

var isNullable =
property.Type is NullableTypeSyntax ||
(property.Type is GenericNameSyntax generic && generic.Identifier.ValueText == "Nullable");
(
property.Type is GenericNameSyntax generic &&
generic.Identifier.ValueText == "Nullable"
);

var genericType = property.Type is GenericNameSyntax genericSyntax
? GenericTypeNode.Create(genericSyntax)
: new GenericTypeNode(
Type: property.Type.NormalizeWhitespace().ToString(),
Children: ImmutableArray<GenericTypeNode>.Empty
);

properties.Add(
new DeclaredProperty(
Name: property.Identifier.ValueText,
Type: property.Type.NormalizeWhitespace().ToString(),
HasSetter: hasSetter,
IsNullable: isNullable,
GenericType: genericType,
Attributes: propertyAttributes
)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

public enum Construction {
StaticClass,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

using System.Collections.Immutable;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

using System.Collections.Immutable;

/// <summary>
/// Represents a property on a metatype. Properties are opt-in and persisted.
/// </summary>
/// <param name="Name">Name of the property.</param>
/// <param name="Type">Type of the property.</param>
/// <param name="HasSetter">True if the property has a setter.</param>
/// <param name="IsNullable">True if the property is nullable.</param>
/// <param name="GenericType">Type of the property.</param>
/// <param name="Attributes">Attributes applied to the property.</param>
public record DeclaredProperty(
string Name,
string Type,
bool HasSetter,
bool IsNullable,
GenericTypeNode GenericType,
ImmutableArray<DeclaredAttribute> Attributes
);
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

using System;
using System.Collections.Generic;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

public enum DeclaredTypeKind {
StaticClass,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

using System.Collections.Immutable;

Expand Down
95 changes: 95 additions & 0 deletions Chickensoft.Introspection.Generator/src/models/GenericTypeNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
namespace Chickensoft.Introspection.Generator.Models;

using System.CodeDom.Compiler;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

public record GenericTypeNode(
string Type,
ImmutableArray<GenericTypeNode> Children
) {
/// <summary>
/// Name of the type, including any open generics portion of the name (if the
/// type is generic) — i.e., the open generic type.
/// </summary>
public string OpenType =>
Type + TypeReference.GetOpenGenerics(Children.Length);

/// <summary>
/// Name of the type, including any generic type arguments — i.e., the closed
/// generic type.
/// </summary>
public string ClosedType => Type + TypeReference.GetGenerics(
Children.Select(child => child.ClosedType).ToImmutableArray()
);

/// <summary>
/// Recursively constructs a generic type node from a generic name syntax.
/// </summary>
/// <param name="genericName">Generic name syntax.</param>
/// <returns>Generic type node tree.</returns>
public static GenericTypeNode Create(GenericNameSyntax genericName) {
var type = genericName.Identifier.NormalizeWhitespace().ToString();

var children = genericName.TypeArgumentList.Arguments
.Select(arg => arg switch {
GenericNameSyntax genericNameSyntax => Create(genericNameSyntax),
_ => new GenericTypeNode(
arg.NormalizeWhitespace().ToString(),
ImmutableArray<GenericTypeNode>.Empty
)
})
.ToImmutableArray();

return new GenericTypeNode(type, children);
}

public void Write(IndentedTextWriter writer) {
var openType = Type + TypeReference.GetOpenGenerics(Children.Length);
var closedType = Type + TypeReference.GetGenerics(
Children.Select(child => child.ClosedType).ToImmutableArray()
);

writer.WriteLine("new GenericType(");
writer.Indent++;
writer.WriteLine($"OpenType: typeof({openType}),");
writer.WriteLine($"ClosedType: typeof({closedType}),");

if (Children.Length > 0) {
writer.WriteLine("Arguments: new GenericType[] {");
writer.Indent++;
foreach (var child in Children) {
var isLast = Children[Children.Length - 1] == child;
child.Write(writer);
if (!isLast) {
writer.WriteLine(",");
}
}
writer.Indent--;
writer.WriteLine("},");
}
else {
writer.WriteLine("Arguments: System.Array.Empty<GenericType>(),");
}

writer.WriteLine(
"GenericTypeGetter: receiver => " +
$"receiver.Receive<{closedType}>(),"
);
// TODO: Pass child types
if (Children.Length >= 2) {
writer.WriteLine(
"GenericTypeGetter2: receiver => " +
$"receiver.Receive<{Children[0].ClosedType}, {Children[1].ClosedType}>()"
);
}
else {
writer.WriteLine("GenericTypeGetter2: default");
}
writer.Indent--;
writer.Write(")");
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

using System.Collections.Immutable;
using System.Linq;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

using System.Collections.Immutable;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

using System.Collections.Generic;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

using System;
using System.Collections.Generic;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Chickensoft.Introspection.Generator.Types.Models;
namespace Chickensoft.Introspection.Generator.Models;

/// <summary>
/// Using directive. In C# 12, a using can be global, static, and an alias all
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Chickensoft.Introspection.Generator.Utils;

using System.Collections.Generic;
using System.Linq;
using Chickensoft.Introspection.Generator.Types.Models;
using Chickensoft.Introspection.Generator.Models;
using Microsoft.CodeAnalysis;

public static class Diagnostics {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ public class PropertyMetadataTest {
public void Initializes() {
var property = new PropertyMetadata(
Name: "Name",
Type: typeof(string),
Getter: _ => "Value",
Setter: (_, _) => { },
GenericTypeGetter: _ => { },
AttributesByType: new Dictionary<Type, Attribute[]>()
GenericType: new GenericType(typeof(string), typeof(string), [], _ => { }, _ => { }),
Attributes: new Dictionary<Type, Attribute[]>()
);

property.ShouldBeOfType<PropertyMetadata>();
Expand Down
20 changes: 10 additions & 10 deletions Chickensoft.Introspection/src/TypeGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ internal class TypeGraph : ITypeGraph {
private readonly ConcurrentDictionary<Type, HashSet<Type>> _typesByAncestor =
new();
private readonly ConcurrentDictionary<Type, IEnumerable<PropertyMetadata>>
_propertiesByType = new();
_properties = new();
private readonly ConcurrentDictionary<Type, IDictionary<Type, Attribute[]>>
_attributesByType = new();
_attributes = new();
private readonly ConcurrentDictionary<string, Type> _introspectiveTypesById =
new();
#endregion Caches
Expand All @@ -40,8 +40,8 @@ internal class TypeGraph : ITypeGraph {
_metatypes.Clear();
_typesByBaseType.Clear();
_typesByAncestor.Clear();
_propertiesByType.Clear();
_attributesByType.Clear();
_properties.Clear();
_attributes.Clear();
_introspectiveTypesById.Clear();
}

Expand Down Expand Up @@ -101,25 +101,25 @@ internal class TypeGraph : ITypeGraph {

public IEnumerable<PropertyMetadata> GetProperties(Type type) {
// Cache the properties for a type once computed.
if (!_propertiesByType.TryGetValue(type, out var properties)) {
_propertiesByType[type] = GetMetatypeAndBaseMetatypes(type)
if (!_properties.TryGetValue(type, out var properties)) {
_properties[type] = GetMetatypeAndBaseMetatypes(type)
.SelectMany((t) => Metatypes[t].Properties)
.Distinct();
}
return _propertiesByType[type];
return _properties[type];
}

public IDictionary<Type, Attribute[]> GetAttributes(Type type) {
// Cache the attributes for a type once computed.
if (!_attributesByType.TryGetValue(type, out var attributes)) {
_attributesByType[type] = GetMetatypeAndBaseMetatypes(type)
if (!_attributes.TryGetValue(type, out var attributes)) {
_attributes[type] = GetMetatypeAndBaseMetatypes(type)
.SelectMany((t) => Metatypes[t].Attributes)
.ToDictionary(
keySelector: (typeToAttr) => typeToAttr.Key,
elementSelector: (typeToAttr) => typeToAttr.Value
);
}
return _attributesByType[type];
return _attributes[type];
}

#endregion ITypeGraph
Expand Down
21 changes: 21 additions & 0 deletions Chickensoft.Introspection/src/models/GenericType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Chickensoft.Introspection;

using System;

/// <summary>
/// Generic type representation.
/// </summary>
/// <param name="OpenType">Open generic type.</param>
/// <param name="ClosedType">Closed generic type.</param>
/// <param name="Arguments">Type arguments.</param>
/// <param name="GenericTypeGetter">Action which invokes the generic type
/// receiver with the generic type.</param>
/// <param name="GenericTypeGetter2">Action which invokes the generic type
/// receiver with its two child generic types.</param>
public record GenericType(
Type OpenType,
Type ClosedType,
GenericType[] Arguments,
Action<ITypeReceiver> GenericTypeGetter,
Action<ITypeReceiver2>? GenericTypeGetter2
);
14 changes: 7 additions & 7 deletions Chickensoft.Introspection/src/models/PropertyMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ namespace Chickensoft.Introspection;
/// Represents property metadata on an introspective type.
/// </summary>
/// <param name="Name">Property name.</param>
/// <param name="Type">Property type.</param>
/// <param name="Getter">Getter function.</param>
/// <param name="Setter">Setter function.</param>
/// <param name="GenericTypeGetter">Function which invokes the provided
/// <see cref="ITypeReceiver" /> with the generic type of the property.</param>
/// <param name="AttributesByType">Map of attribute types to attribute
/// <param name="GenericType">If the property's type is a closed constructed
/// generic type, this will be the root node of a generic node tree that
/// provides access to the individual types comprising the closed constructed
/// type.</param>
/// <param name="Attributes">Map of attribute types to attribute
/// instances.</param>
public sealed record PropertyMetadata(
string Name,
Type Type,
Func<object, object?> Getter,
Action<object, object?>? Setter,
Action<ITypeReceiver> GenericTypeGetter,
Dictionary<Type, Attribute[]> AttributesByType
GenericType GenericType,
Dictionary<Type, Attribute[]> Attributes
);
13 changes: 13 additions & 0 deletions Chickensoft.Introspection/src/types/ITypeReceiver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,16 @@ public interface ITypeReceiver {
/// <typeparam name="T">Generic type.</typeparam>
void Receive<T>();
}

/// <summary>
/// Object containing a single method that can receive generic type arguments.
/// Provided for your convenience since C# does not support generic lambdas.
/// </summary>
public interface ITypeReceiver2 {
/// <summary>
/// Generic method which receives 2 generic type arguments.
/// </summary>
/// <typeparam name="TA">First generic type.</typeparam>
/// <typeparam name="TB">Second generic type.</typeparam>
void Receive<TA, TB>();
}
Loading

0 comments on commit 5a3658c

Please sign in to comment.