Skip to content

Use polymorphic shape declarations in the source generator #388

@eiriktsarpalis

Description

@eiriktsarpalis

The source generation strategy used by PolyType today is emphatically monomorphic. In other words, it will use distinct implementations for ITypeShape<List<string>> and ITypeShape<List<object>> even though these are mostly identical, modulo the ITypeShape used for the element type. Here's an example of how ITypShape<List<string>> is generated today:

namespace PolyType.SourceGenerator
{
    internal sealed partial class TypeShapeProvider_PolyType_TestCases
    {
        /// <summary>Gets a generated shape for the specified type.</summary>
        #nullable disable annotations // Use nullable-oblivious property type
        public global::PolyType.ITypeShape<global::System.Collections.Generic.List<string>> List_String => __List_String ?? __Init_Singleton(ref __List_String, __Create_List_String());
        #nullable enable annotations // Use nullable-oblivious property type
        private global::PolyType.ITypeShape<global::System.Collections.Generic.List<string>>? __List_String;

        private global::PolyType.ITypeShape<global::System.Collections.Generic.List<string>> __Create_List_String()
        {
            return new global::PolyType.SourceGenModel.SourceGenEnumerableTypeShape<global::System.Collections.Generic.List<string>, string>
            {
                ElementTypeFactory = () => String,
                ConstructionStrategy = global::PolyType.Abstractions.CollectionConstructionStrategy.Mutable,
                DefaultConstructor = static (in global::PolyType.Abstractions.CollectionConstructionOptions<string> options) => new global::System.Collections.Generic.List<string>(options.Capacity ?? 0),
                SupportedComparer = global::PolyType.Abstractions.CollectionComparerOptions.None,
                GetEnumerable = static obj => obj,
                Appender = static (ref global::System.Collections.Generic.List<string> obj, string value) => { obj.Add(value); return true; },
                Rank = 1,
                Provider = this,
           };
        }
    }
}

We should update the source generator strategy such that this method is defined polymorphically -- in other words, the __Create_List_String() method should be replaced by a generic __Create_List<T>(Func<ITypeShape<T>> elementType) method that is reused across generic instantiations. This functorial approach does introduce a few challenges, namely

  1. It needs to account for generics that nest within the declaring type, e.g.
    record Foo<T>(List<T[]> nestedCollection);
    which should convert to a Create_Foo<T>(Func<ITypeShape<List<T[]>>) factory that gets invoked accordingly.
  2. The current strategy implicitly handles recursive type support by directly referencing the property for the corresponding element type. Care must be taken such that the new strategy account for this in a different way.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions