Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

chore: Update YamlDotNet version to 16.3.0 #10541

Merged
merged 8 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<PackageVersion Include="System.Composition" Version="9.0.2" />
<PackageVersion Include="System.Formats.Asn1" Version="9.0.2" />
<PackageVersion Include="System.Text.Json" Version="9.0.2" />
<PackageVersion Include="YamlDotNet" Version="15.3.0" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>

<!-- .slnx solution format is supported Microsoft.Build 17.13.9 or later. -->
Expand Down
10 changes: 8 additions & 2 deletions src/Docfx.YamlSerialization/Helpers/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@ internal static class ReflectionExtensions
/// Determines whether the specified type has a default constructor.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="allowPrivateConstructors">Allow private constructor.</param>
/// <returns>
/// <c>true</c> if the type has a default constructor; otherwise, <c>false</c>.
/// </returns>
public static bool HasDefaultConstructor(this Type type)
public static bool HasDefaultConstructor(this Type type, bool allowPrivateConstructors)
{
return type.IsValueType || type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null) != null;
var bindingFlags = BindingFlags.Public | BindingFlags.Instance;
if (allowPrivateConstructors)
{
bindingFlags |= BindingFlags.NonPublic;
}
return type.IsValueType || type.GetConstructor(bindingFlags, null, Type.EmptyTypes, null) != null;
}

public static IEnumerable<PropertyInfo> GetPublicProperties(this Type type)
Expand Down
83 changes: 83 additions & 0 deletions src/Docfx.YamlSerialization/Helpers/TypeConverterCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using YamlDotNet.Serialization;

namespace Docfx.YamlSerialization.Helpers;

/// <summary>
/// A cache / map for <see cref="IYamlTypeConverter"/> instances.
/// </summary>
/// <remarks>
/// This class is copied from following YamlDotNet implementation.
/// https://github.com/aaubry/YamlDotNet/blob/master/YamlDotNet/Serialization/Utilities/TypeConverterCache.cs
/// </remarks>
internal sealed class TypeConverterCache
{
private readonly IYamlTypeConverter[] typeConverters;
private readonly ConcurrentDictionary<Type, (bool HasMatch, IYamlTypeConverter? TypeConverter)> cache = new();

public TypeConverterCache(IEnumerable<IYamlTypeConverter>? typeConverters)
: this(typeConverters?.ToArray() ?? [])
{
}

public TypeConverterCache(IYamlTypeConverter[] typeConverters)
{
this.typeConverters = typeConverters;
}

/// <summary>
/// Returns the first <see cref="IYamlTypeConverter"/> that accepts the given type.
/// </summary>
/// <param name="type">The <see cref="Type"/> to lookup.</param>
/// <param name="typeConverter">The <see cref="IYamlTypeConverter" /> that accepts this type or <see langword="false" /> if no converter is found.</param>
/// <returns><see langword="true"/> if a type converter was found; <see langword="false"/> otherwise.</returns>
public bool TryGetConverterForType(Type type, [NotNullWhen(true)] out IYamlTypeConverter? typeConverter)
{
var result = cache.GetOrAdd(type, static (t, tc) => LookupTypeConverter(t, tc), typeConverters);

typeConverter = result.TypeConverter;
return result.HasMatch;
}

/// <summary>
/// Returns the <see cref="IYamlTypeConverter"/> of the given type.
/// </summary>
/// <param name="converter">The type of the converter.</param>
/// <returns>The <see cref="IYamlTypeConverter"/> of the given type.</returns>
/// <exception cref="ArgumentException">If no type converter of the given type is found.</exception>
/// <remarks>
/// Note that this method searches on the type of the <see cref="IYamlTypeConverter"/> itself. If you want to find a type converter
/// that accepts a given <see cref="Type"/>, use <see cref="TryGetConverterForType(Type, out IYamlTypeConverter?)"/> instead.
/// </remarks>
public IYamlTypeConverter GetConverterByType(Type converter)
{
// Intentially avoids LINQ as this is on a hot path
foreach (var typeConverter in typeConverters)
{
if (typeConverter.GetType() == converter)
{
return typeConverter;
}
}

throw new ArgumentException($"{nameof(IYamlTypeConverter)} of type {converter.FullName} not found", nameof(converter));
}

private static (bool HasMatch, IYamlTypeConverter? TypeConverter) LookupTypeConverter(Type type, IYamlTypeConverter[] typeConverters)
{
// Intentially avoids LINQ as this is on a hot path
foreach (var typeConverter in typeConverters)
{
if (typeConverter.Accepts(type))
{
return (true, typeConverter);
}
}

return (false, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,22 @@ namespace Docfx.YamlSerialization.NodeDeserializers;

public class EmitArrayNodeDeserializer : INodeDeserializer
{
private readonly INamingConvention _enumNamingConvention;
private readonly ITypeInspector _typeDescriptor;

private static readonly MethodInfo DeserializeHelperMethod =
typeof(EmitArrayNodeDeserializer).GetMethod(nameof(DeserializeHelper))!;
private static readonly ConcurrentDictionary<Type, Func<IParser, Type, Func<IParser, Type, object?>, object?>> _funcCache =

private static readonly ConcurrentDictionary<Type, Func<IParser, Type, Func<IParser, Type, object?>, INamingConvention, ITypeInspector, object?>> _funcCache =
new();

bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
public EmitArrayNodeDeserializer(INamingConvention enumNamingConvention, ITypeInspector typeDescriptor)
{
_enumNamingConvention = enumNamingConvention;
_typeDescriptor = typeDescriptor;
}

bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!expectedType.IsArray)
{
Expand All @@ -26,27 +36,44 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IPars
}

var func = _funcCache.GetOrAdd(expectedType, AddItem);
value = func(reader, expectedType, nestedObjectDeserializer);
value = func(reader, expectedType, nestedObjectDeserializer, _enumNamingConvention, _typeDescriptor);
return true;
}

[EditorBrowsable(EditorBrowsableState.Never)]
public static TItem[] DeserializeHelper<TItem>(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer)
public static TItem[] DeserializeHelper<TItem>(
IParser reader,
Type expectedType,
Func<IParser, Type, object?> nestedObjectDeserializer,
INamingConvention enumNamingConvention,
ITypeInspector typeDescriptor)
{
var items = new List<TItem>();
EmitGenericCollectionNodeDeserializer.DeserializeHelper(reader, expectedType, nestedObjectDeserializer, items);
EmitGenericCollectionNodeDeserializer.DeserializeHelper(reader, expectedType, nestedObjectDeserializer, items, enumNamingConvention, typeDescriptor);
return items.ToArray();
}

private static Func<IParser, Type, Func<IParser, Type, object?>, object?> AddItem(Type expectedType)
private static Func<IParser, Type, Func<IParser, Type, object?>, INamingConvention, ITypeInspector, object?> AddItem(Type expectedType)
{
var dm = new DynamicMethod(string.Empty, typeof(object), [typeof(IParser), typeof(Type), typeof(Func<IParser, Type, object>)]);
var dm = new DynamicMethod(
string.Empty,
returnType: typeof(object),
parameterTypes:
[
typeof(IParser), // reader
typeof(Type), // expectedType
typeof(Func<IParser, Type, object?>), // nestedObjectDeserializer
typeof(INamingConvention), // enumNamingConvention
typeof(ITypeInspector), // typeDescriptor
]);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldarg_3);
il.Emit(OpCodes.Ldarg_S, (byte)4);
il.Emit(OpCodes.Call, DeserializeHelperMethod.MakeGenericMethod(expectedType.GetElementType()!));
il.Emit(OpCodes.Ret);
return (Func<IParser, Type, Func<IParser, Type, object?>, object?>)dm.CreateDelegate(typeof(Func<IParser, Type, Func<IParser, Type, object?>, object?>));
return (Func<IParser, Type, Func<IParser, Type, object?>, INamingConvention, ITypeInspector, object?>)dm.CreateDelegate(typeof(Func<IParser, Type, Func<IParser, Type, object?>, INamingConvention, ITypeInspector, object?>));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.Utilities;
using EditorBrowsable = System.ComponentModel.EditorBrowsableAttribute;
using EditorBrowsableState = System.ComponentModel.EditorBrowsableState;
Expand All @@ -19,15 +18,21 @@ public class EmitGenericCollectionNodeDeserializer : INodeDeserializer
private static readonly MethodInfo DeserializeHelperMethod =
typeof(EmitGenericCollectionNodeDeserializer).GetMethod(nameof(DeserializeHelper))!;
private readonly IObjectFactory _objectFactory;
private readonly Dictionary<Type, Type?> _gpCache = [];
private readonly Dictionary<Type, Action<IParser, Type, Func<IParser, Type, object?>, object?>> _actionCache = [];
private readonly INamingConvention _enumNamingConvention;
private readonly ITypeInspector _typeDescriptor;
private readonly Dictionary<Type, Type?> _gpCache =
new();
private readonly Dictionary<Type, Action<IParser, Type, Func<IParser, Type, object?>, object?, INamingConvention, ITypeInspector>> _actionCache =
new();

public EmitGenericCollectionNodeDeserializer(IObjectFactory objectFactory)
public EmitGenericCollectionNodeDeserializer(IObjectFactory objectFactory, INamingConvention enumNamingConvention, ITypeInspector typeDescriptor)
{
_objectFactory = objectFactory;
_enumNamingConvention = enumNamingConvention;
_typeDescriptor = typeDescriptor;
}

bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!_gpCache.TryGetValue(expectedType, out var gp))
{
Expand Down Expand Up @@ -55,39 +60,58 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IPars
value = _objectFactory.Create(expectedType);
if (!_actionCache.TryGetValue(gp, out var action))
{
var dm = new DynamicMethod(string.Empty, typeof(void), [typeof(IParser), typeof(Type), typeof(Func<IParser, Type, object>), typeof(object)]);
var dm = new DynamicMethod(
string.Empty,
returnType: typeof(void),
[
typeof(IParser),
typeof(Type),
typeof(Func<IParser, Type, object?>),
typeof(object),
typeof(INamingConvention),
typeof(ITypeInspector)
]);

var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldarg_3);
il.Emit(OpCodes.Ldarg_0); // reader
il.Emit(OpCodes.Ldarg_1); // expectedType
il.Emit(OpCodes.Ldarg_2); // nestedObjectDeserializer
il.Emit(OpCodes.Ldarg_3); // result
il.Emit(OpCodes.Castclass, typeof(ICollection<>).MakeGenericType(gp));
il.Emit(OpCodes.Ldarg_S, (byte)4); // enumNamingConvention
il.Emit(OpCodes.Ldarg_S, (byte)5); // typeDescriptor
il.Emit(OpCodes.Call, DeserializeHelperMethod.MakeGenericMethod(gp));
il.Emit(OpCodes.Ret);
action = (Action<IParser, Type, Func<IParser, Type, object?>, object?>)dm.CreateDelegate(typeof(Action<IParser, Type, Func<IParser, Type, object?>, object?>));
action = (Action<IParser, Type, Func<IParser, Type, object?>, object?, INamingConvention, ITypeInspector>)dm.CreateDelegate(typeof(Action<IParser, Type, Func<IParser, Type, object?>, object?, INamingConvention, ITypeInspector>));
_actionCache[gp] = action;
}

action(reader, expectedType, nestedObjectDeserializer, value);
action(reader, expectedType, nestedObjectDeserializer, value, _enumNamingConvention, _typeDescriptor);
return true;
}

[EditorBrowsable(EditorBrowsableState.Never)]
public static void DeserializeHelper<TItem>(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, ICollection<TItem> result)
public static void DeserializeHelper<TItem>(
IParser reader,
Type expectedType,
Func<IParser, Type, object?> nestedObjectDeserializer,
ICollection<TItem> result,
INamingConvention enumNamingConvention,
ITypeInspector typeDescriptor)
{
reader.Consume<SequenceStart>();
while (!reader.Accept<SequenceEnd>(out _))
{
var value = nestedObjectDeserializer(reader, typeof(TItem));
if (value is not IValuePromise promise)
{
result.Add(TypeConverter.ChangeType<TItem>(value, NullNamingConvention.Instance));
result.Add(TypeConverter.ChangeType<TItem>(value, enumNamingConvention, typeDescriptor));
}
else if (result is IList<TItem> list)
{
var index = list.Count;
result.Add(default!);
promise.ValueAvailable += v => list[index] = TypeConverter.ChangeType<TItem>(v, NullNamingConvention.Instance);
promise.ValueAvailable += v => list[index] = TypeConverter.ChangeType<TItem>(v, enumNamingConvention, typeDescriptor);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public EmitGenericDictionaryNodeDeserializer(IObjectFactory objectFactory)
_objectFactory = objectFactory;
}

bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!_gpCache.TryGetValue(expectedType, out var gp))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.Utilities;

namespace Docfx.YamlSerialization.NodeDeserializers;
Expand All @@ -14,15 +13,19 @@ public sealed class ExtensibleObjectNodeDeserializer : INodeDeserializer
private readonly IObjectFactory _objectFactory;
private readonly ITypeInspector _typeDescriptor;
private readonly bool _ignoreUnmatched;
private readonly bool _caseInsensitivePropertyMatching;
private readonly INamingConvention _enumNamingConvention;

public ExtensibleObjectNodeDeserializer(IObjectFactory objectFactory, ITypeInspector typeDescriptor, bool ignoreUnmatched)
public ExtensibleObjectNodeDeserializer(IObjectFactory objectFactory, ITypeInspector typeDescriptor, INamingConvention enumNamingConvention, bool ignoreUnmatched, bool caseInsensitivePropertyMatching)
{
_objectFactory = objectFactory;
_typeDescriptor = typeDescriptor;
_enumNamingConvention = enumNamingConvention;
_ignoreUnmatched = ignoreUnmatched;
_caseInsensitivePropertyMatching = caseInsensitivePropertyMatching;
}

bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!reader.TryConsume<MappingStart>(out _))
{
Expand All @@ -34,7 +37,7 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IPars
while (!reader.Accept<MappingEnd>(out _))
{
var propertyName = reader.Consume<Scalar>();
var property = _typeDescriptor.GetProperty(expectedType, value, propertyName.Value, _ignoreUnmatched);
var property = _typeDescriptor.GetProperty(expectedType, value, propertyName.Value, _ignoreUnmatched, _caseInsensitivePropertyMatching);
if (property == null)
{
reader.SkipThisAndNestedEvents();
Expand All @@ -44,15 +47,15 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func<IPars
var propertyValue = nestedObjectDeserializer(reader, property.Type);
if (propertyValue is not IValuePromise propertyValuePromise)
{
var convertedValue = TypeConverter.ChangeType(propertyValue, property.Type, NullNamingConvention.Instance);
var convertedValue = TypeConverter.ChangeType(propertyValue, property.Type, _enumNamingConvention, _typeDescriptor);
property.Write(value, convertedValue);
}
else
{
var valueRef = value;
propertyValuePromise.ValueAvailable += v =>
{
var convertedValue = TypeConverter.ChangeType(v, property.Type, NullNamingConvention.Instance);
var convertedValue = TypeConverter.ChangeType(v, property.Type, _enumNamingConvention, _typeDescriptor);
property.Write(valueRef, convertedValue);
};
}
Expand Down
Loading