GithubHelp home page GithubHelp logo

Always-on AOT about messagepack-csharp HOT 7 CLOSED

AArnott avatar AArnott commented on June 4, 2024
Always-on AOT

from messagepack-csharp.

Comments (7)

pCYSl5EDgo avatar pCYSl5EDgo commented on June 4, 2024 1

Here is my code for handling nested types in source generator.
Generating partial class is much easier than generating partial method because generic partial method's type parameter with type constraints requires its type constraints again.

ContainingTypeInfo.cs
using Microsoft.CodeAnalysis;
using System.Text;

namespace BlazorApp1.SourceGenerator;

public readonly struct ContainingTypeInfo : IEquatable<ContainingTypeInfo>
{
  public readonly string? ContainingNamespaceDisplayName;
  
  /// <summary>
  /// 0: Most inner type
  /// ^1: Most outer type
  /// </summary>
  public readonly ContainingType[] Types;

  private static int CountDepth(INamedTypeSymbol symbol, int depth, CancellationToken cancellationToken)
  {
    cancellationToken.ThrowIfCancellationRequested();
    if (symbol.ContainingType is null)
    {
      return depth;
    }

    return CountDepth(symbol.ContainingType, depth + 1, cancellationToken);
  }

  /// <summary>
  /// Collect minimum information for generating partial class.
  /// </summary>
  /// <param name="symbol">Must be inner type.</param>
  public ContainingTypeInfo(INamedTypeSymbol symbol, CancellationToken cancellationToken)
  {
    cancellationToken.ThrowIfCancellationRequested();
    ContainingNamespaceDisplayName = symbol.ContainingNamespace?.ToDisplayString();
    Types = new ContainingType[CountDepth(symbol, 1, cancellationToken)];
    Types[0] = new(symbol, cancellationToken);
    for (int index = 1; symbol.ContainingType is { } containingTypeSymbol; index++)
    {
      cancellationToken.ThrowIfCancellationRequested();
      Types[index] = new(containingTypeSymbol, cancellationToken);
      symbol = containingTypeSymbol;
    }
  }

  public ContainingTypeInfo()
  {
    ContainingNamespaceDisplayName = null;
    Types = [];
  }

  public StringBuilder AppendFullName(StringBuilder? builder = default)
  {
    builder ??= new StringBuilder(64);
    if (ContainingNamespaceDisplayName is not null)
    {
      builder.Append(ContainingNamespaceDisplayName);
      builder.Append('.');
    }

    for (int i = 0; i < Types.Length;)
    {
      builder.Append(Types[i].Name);
      if (++i < Types.Length)
      {
        builder.Append('.');
      }
    }

    return builder;
  }

  public override bool Equals(object? other)
  {
    if (other is not ContainingTypeInfo value)
    {
      return false;
    }

    return Equals(in value);
  }

  public bool Equals(ContainingTypeInfo other)
  {
    return Equals(in other);
  }

  public bool Equals(in ContainingTypeInfo other)
  {
    if (ContainingNamespaceDisplayName != other.ContainingNamespaceDisplayName)
    {
      return false;
    }

    if (Types.Length != other.Types.Length)
    {
      return false;
    }

    for (int i = 0; i < Types.Length; i++)
    {
      if (!Types[i].Equals(in other.Types[i]))
      {
        return false;
      }
    }

    return true;
  }

  public override int GetHashCode()
  {
    return ((ContainingNamespaceDisplayName?.Length ?? 0) << 16) | (Types.Length);
  }

  public readonly struct ContainingType : IEquatable<ContainingType>
  {
    public readonly string Name;
    public readonly bool IsStatic;
    public readonly bool IsValueType;
    public readonly bool IsRecord;
    public readonly string[] TypeParameters;

    public ContainingType(INamedTypeSymbol symbol, CancellationToken cancellationToken)
    {
      Name = symbol.Name;
      IsStatic = symbol.IsStatic;
      IsValueType = symbol.IsValueType;
      IsRecord = symbol.IsRecord;
      if (symbol.TypeParameters.Length == 0)
      {
        TypeParameters = [];
        return;
      }

      TypeParameters = new string[symbol.TypeParameters.Length];
      for (int i = 0; i < TypeParameters.Length; i++)
      {
        cancellationToken.ThrowIfCancellationRequested();
        TypeParameters[i] = symbol.TypeParameters[i].Name;
      }
    }

    public override bool Equals(object? other)
    {
      if (other is not ContainingType value)
      {
        return false;
      }

      return Equals(in value);
    }

    public bool Equals(ContainingType other)
    {
      return Equals(in other);
    }

    public bool Equals(in ContainingType other)
    {
      if (Name != other.Name)
      {
        return false;
      }
      if (IsStatic != other.IsStatic)
      {
        return false;
      }
      if (IsValueType != other.IsValueType)
      {
        return false;
      }
      if (IsRecord != other.IsRecord)
      {
        return false;
      }

      if (!ReferenceEquals(TypeParameters, other.TypeParameters))
      {
        if (TypeParameters.Length != other.TypeParameters.Length)
        {
          return false;
        }

        for (int i = 0; i < TypeParameters.Length; i++)
        {
          if (TypeParameters[i] != other.TypeParameters[i])
          {
            return false;
          }
        }
      }

      return true;
    }

    public override int GetHashCode()
    {
      return (TypeParameters.Length << 16) | (Name.Length << 3) | ((IsStatic ? 1 : 0) << 2) | ((IsValueType ? 1 : 0) << 1) | (IsRecord ? 1 : 0);
    }

    public static bool operator ==(in ContainingType left, in ContainingType right)
    {
      return left.Equals(in right);
    }

    public static bool operator !=(in ContainingType left, in ContainingType right)
    {
      return !left.Equals(in right);
    }
  }

  public static bool operator ==(in ContainingTypeInfo left, in ContainingTypeInfo right)
  {
    return left.Equals(in right);
  }

  public static bool operator !=(in ContainingTypeInfo left, in ContainingTypeInfo right)
  {
    return !left.Equals(in right);
  }
}

from messagepack-csharp.

AArnott avatar AArnott commented on June 4, 2024

MessagePack attributes that the analyzer responds to only requires a reference to the MessagePack.Annotations package. But it takes a reference to the MessagePack package to actually build a formatter. So the source generator can only do its work if the MessagePack assembly is also referenced. Assemblies that only reference the annotations will be limited to dynamically generated formatters at runtime.

from messagepack-csharp.

AArnott avatar AArnott commented on June 4, 2024

I have the changes ready for this. I'll wait to send the pull request until after #1736 has merged, since the changes are based on that.

from messagepack-csharp.

AArnott avatar AArnott commented on June 4, 2024

What about private member access? Should formatters be generated as private classes of the formatted type to gain access?

We should add an override switch to MessagePackSerializerOptions to force use of dynamic formatters.

from messagepack-csharp.

AArnott avatar AArnott commented on June 4, 2024

We should add an override switch to MessagePackSerializerOptions to force use of dynamic formatters.

Actually that doesn't work, because DynamicObjectResolver, which would need to read the option, isn't given a chance to read the option per the API. And it's incompatible with the FormatterCache<T> which takes no parameters except the type parameter.
But maybe that's a good thing anyway, because it should really be the producer of the type (or formatter) to decide which formatter to use -- not the consumer. If I ship a library, it's my responsibility to ensure that any formatters it ships with (whether manually written or source generated) actually work, and then my consumer can use it.

from messagepack-csharp.

AArnott avatar AArnott commented on June 4, 2024

Maybe the better fix for this is to not overload DynamicObjectResolver to support pre-compiled formatters, but rather to insert a new resolver in the standard resolver chain (in front of the DynamicObjectResolver). It still won't allow for options-based selection of formatters, but at least it will be something that can be included or omitted from the resolver chain on an as-needed basis.

from messagepack-csharp.

AArnott avatar AArnott commented on June 4, 2024

On the subject of AOT formatters that can access private members via being a nested class, I think that'll work. But we'll have to take care for the generated code to handle multiple levels of nesting. e.g. the type that needs a formatter may itself be a nested type.

from messagepack-csharp.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.