< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 182
Coverable lines: 182
Total lines: 401
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 56
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/FSharpCoreReflectionProxy.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Reflection;
 8using System.Text.Json.Serialization;
 9
 10namespace System.Text.Json.Serialization.Metadata
 11{
 12    // Recognizing types emitted by the F# compiler requires consuming APIs from the FSharp.Core runtime library.
 13    // Every F# application ships with a copy of FSharp.Core, however it is not available statically to System.Text.Json
 14    // The following class uses reflection to access the relevant APIs required to detect the various F# types we are lo
 15
 16    /// <summary>
 17    /// Proxy class used to access FSharp.Core metadata and reflection APIs that are not statically available to System.
 18    /// </summary>
 19    internal sealed class FSharpCoreReflectionProxy
 20    {
 21        /// <summary>
 22        /// The various categories of F# types that System.Text.Json supports.
 23        /// </summary>
 24        public enum FSharpKind
 25        {
 26            Unrecognized,
 27            Option,
 28            ValueOption,
 29            List,
 30            Set,
 31            Map,
 32            Record,
 33            Union
 34        }
 35
 36        // Binding a struct getter method to a delegate requires that the struct parameter is passed byref.
 37        public delegate TResult StructGetter<TStruct, TResult>(ref TStruct @this) where TStruct : struct;
 38
 39        public const string FSharpCoreUnreferencedCodeMessage = "Uses Reflection to access FSharp.Core components at run
 40
 41        private static FSharpCoreReflectionProxy? s_singletonInstance;
 42
 43        // Every type generated by the F# compiler is annotated with the CompilationMappingAttribute
 44        // containing all relevant metadata required to determine its kind:
 45        // https://fsharp.github.io/fsharp-core-docs/reference/fsharp-core-compilationmappingattribute.html#SourceConstr
 46        private const string CompilationMappingAttributeTypeName = "Microsoft.FSharp.Core.CompilationMappingAttribute";
 47        private readonly Type _compilationMappingAttributeType;
 48        private readonly MethodInfo? _sourceConstructFlagsGetter;
 49
 50        private readonly Type? _fsharpOptionType;
 51        private readonly Type? _fsharpValueOptionType;
 52        private readonly Type? _fsharpListType;
 53        private readonly Type? _fsharpSetType;
 54        private readonly Type? _fsharpMapType;
 55
 56        private readonly MethodInfo? _fsharpListCtor;
 57        private readonly MethodInfo? _fsharpSetCtor;
 58        private readonly MethodInfo? _fsharpMapCtor;
 59
 60        // Union-related reflection members
 61        private readonly MethodInfo? _getUnionCases;
 62        private readonly MethodInfo? _preComputeUnionTagReader;
 63        private readonly MethodInfo? _preComputeUnionReader;
 64        private readonly MethodInfo? _preComputeUnionConstructor;
 65        private readonly MethodInfo? _unionCaseInfoNameGetter;
 66        private readonly MethodInfo? _unionCaseInfoTagGetter;
 67        private readonly MethodInfo? _unionCaseInfoGetFields;
 68        private readonly MethodInfo? _unionCaseInfoGetCustomAttributes;
 69
 70        /// <summary>
 71        /// Checks if the provided System.Type instance is emitted by the F# compiler.
 72        /// If true, also initializes the proxy singleton for future by other F# types.
 73        /// </summary>
 74        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 75        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 76        public static bool IsFSharpType(Type type)
 077        {
 078            if (s_singletonInstance is null)
 079            {
 080                if (GetFSharpCoreAssembly(type) is Assembly fsharpCoreAssembly)
 081                {
 82                    // Type is F# type, initialize the singleton instance.
 083                    s_singletonInstance ??= new FSharpCoreReflectionProxy(fsharpCoreAssembly);
 84
 085                    return true;
 86                }
 87
 088                return false;
 89            }
 90
 091            return s_singletonInstance.GetFSharpCompilationMappingAttribute(type) is not null;
 092        }
 93
 94        /// <summary>
 95        /// Gets the singleton proxy instance; prerequires a successful IsFSharpType call for proxy initialization.
 96        /// </summary>
 97        public static FSharpCoreReflectionProxy Instance
 98        {
 99            get
 0100            {
 0101                Debug.Assert(s_singletonInstance is not null, "should be initialized via a successful IsFSharpType call.
 0102                return s_singletonInstance;
 0103            }
 104        }
 105
 106        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 107        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 0108        private FSharpCoreReflectionProxy(Assembly fsharpCoreAssembly)
 0109        {
 0110            Debug.Assert(fsharpCoreAssembly.GetName().Name == "FSharp.Core");
 111
 0112            Type compilationMappingAttributeType = fsharpCoreAssembly.GetType(CompilationMappingAttributeTypeName)!;
 0113            _sourceConstructFlagsGetter = compilationMappingAttributeType.GetMethod("get_SourceConstructFlags", BindingF
 0114            _compilationMappingAttributeType = compilationMappingAttributeType;
 115
 0116            _fsharpOptionType = fsharpCoreAssembly.GetType("Microsoft.FSharp.Core.FSharpOption`1");
 0117            _fsharpValueOptionType = fsharpCoreAssembly.GetType("Microsoft.FSharp.Core.FSharpValueOption`1");
 0118            _fsharpListType = fsharpCoreAssembly.GetType("Microsoft.FSharp.Collections.FSharpList`1");
 0119            _fsharpSetType = fsharpCoreAssembly.GetType("Microsoft.FSharp.Collections.FSharpSet`1");
 0120            _fsharpMapType = fsharpCoreAssembly.GetType("Microsoft.FSharp.Collections.FSharpMap`2");
 121
 0122            _fsharpListCtor = fsharpCoreAssembly.GetType("Microsoft.FSharp.Collections.ListModule")?.GetMethod("OfSeq", 
 0123            _fsharpSetCtor = fsharpCoreAssembly.GetType("Microsoft.FSharp.Collections.SetModule")?.GetMethod("OfSeq", Bi
 0124            _fsharpMapCtor = fsharpCoreAssembly.GetType("Microsoft.FSharp.Collections.MapModule")?.GetMethod("OfSeq", Bi
 125
 126            // Union reflection APIs from Microsoft.FSharp.Reflection namespace
 0127            Type? fsharpType = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.FSharpType");
 0128            Type? fsharpValue = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.FSharpValue");
 0129            Type? unionCaseInfoType = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.UnionCaseInfo");
 130
 0131            _getUnionCases = fsharpType?.GetMethod("GetUnionCases", BindingFlags.Public | BindingFlags.Static);
 0132            _preComputeUnionTagReader = fsharpValue?.GetMethod("PreComputeUnionTagReader", BindingFlags.Public | Binding
 133
 0134            if (unionCaseInfoType is not null)
 0135            {
 0136                _preComputeUnionReader = fsharpValue?.GetMethod("PreComputeUnionReader", BindingFlags.Public | BindingFl
 0137                _preComputeUnionConstructor = fsharpValue?.GetMethod("PreComputeUnionConstructor", BindingFlags.Public |
 0138                _unionCaseInfoNameGetter = unionCaseInfoType.GetMethod("get_Name", BindingFlags.Public | BindingFlags.In
 0139                _unionCaseInfoTagGetter = unionCaseInfoType.GetMethod("get_Tag", BindingFlags.Public | BindingFlags.Inst
 0140                _unionCaseInfoGetFields = unionCaseInfoType.GetMethod("GetFields", BindingFlags.Public | BindingFlags.In
 0141                _unionCaseInfoGetCustomAttributes = unionCaseInfoType.GetMethod("GetCustomAttributes", BindingFlags.Publ
 0142            }
 0143        }
 144
 145        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 146        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 147        public FSharpKind DetectFSharpKind(Type type)
 0148        {
 0149            Attribute? compilationMappingAttribute = GetFSharpCompilationMappingAttribute(type);
 150
 0151            if (compilationMappingAttribute is null)
 0152            {
 0153                return FSharpKind.Unrecognized;
 154            }
 155
 0156            if (type.IsGenericType)
 0157            {
 0158                Type genericType = type.GetGenericTypeDefinition();
 0159                if (genericType == _fsharpOptionType) return FSharpKind.Option;
 0160                if (genericType == _fsharpValueOptionType) return FSharpKind.ValueOption;
 0161                if (genericType == _fsharpListType) return FSharpKind.List;
 0162                if (genericType == _fsharpSetType) return FSharpKind.Set;
 0163                if (genericType == _fsharpMapType) return FSharpKind.Map;
 0164            }
 165
 0166            return (GetSourceConstructFlags(compilationMappingAttribute) & SourceConstructFlags.KindMask) switch
 0167            {
 0168                SourceConstructFlags.RecordType => FSharpKind.Record,
 0169                SourceConstructFlags.SumType => FSharpKind.Union,
 0170                _ => FSharpKind.Unrecognized
 0171            };
 0172        }
 173
 174        /// <summary>
 175        /// Gets the union case metadata for the specified F# discriminated union type.
 176        /// Returns an array of case descriptors with pre-computed delegates for tag reading,
 177        /// field reading, and case construction.
 178        /// </summary>
 179        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 180        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 181        public FSharpUnionCaseInfo[] GetUnionCaseInfos(Type unionType)
 0182        {
 0183            MethodInfo getUnionCases = EnsureMemberExists(_getUnionCases, "Microsoft.FSharp.Reflection.FSharpType.GetUni
 0184            MethodInfo preComputeUnionReader = EnsureMemberExists(_preComputeUnionReader, "Microsoft.FSharp.Reflection.F
 0185            MethodInfo preComputeUnionConstructor = EnsureMemberExists(_preComputeUnionConstructor, "Microsoft.FSharp.Re
 0186            MethodInfo nameGetter = EnsureMemberExists(_unionCaseInfoNameGetter, "Microsoft.FSharp.Reflection.UnionCaseI
 0187            MethodInfo tagGetter = EnsureMemberExists(_unionCaseInfoTagGetter, "Microsoft.FSharp.Reflection.UnionCaseInf
 0188            MethodInfo getFields = EnsureMemberExists(_unionCaseInfoGetFields, "Microsoft.FSharp.Reflection.UnionCaseInf
 0189            MethodInfo getCustomAttributes = EnsureMemberExists(_unionCaseInfoGetCustomAttributes, "Microsoft.FSharp.Ref
 190
 191            // FSharpType.GetUnionCases(type, bindingFlags: null) returns UnionCaseInfo[]
 0192            object[] cases = (object[])getUnionCases.Invoke(null, new object?[] { unionType, null })!;
 0193            var result = new FSharpUnionCaseInfo[cases.Length];
 194
 0195            for (int i = 0; i < cases.Length; i++)
 0196            {
 0197                object caseInfo = cases[i];
 0198                string caseName = (string)nameGetter.Invoke(caseInfo, null)!;
 0199                int caseTag = (int)tagGetter.Invoke(caseInfo, null)!;
 0200                PropertyInfo[] fields = (PropertyInfo[])getFields.Invoke(caseInfo, null)!;
 201
 202                // Read custom attributes to check for JsonPropertyNameAttribute
 0203                object[] customAttributes = (object[])getCustomAttributes.Invoke(caseInfo, null)!;
 0204                string? jsonPropertyName = null;
 0205                foreach (object attr in customAttributes)
 0206                {
 0207                    if (attr is JsonPropertyNameAttribute jpn)
 0208                    {
 0209                        jsonPropertyName = jpn.Name;
 0210                        break;
 211                    }
 0212                }
 213
 214                // PreComputeUnionReader returns FSharpFunc<obj, obj[]>
 0215                Func<object, object[]> fieldReader = ConvertFSharpFunc<object, object[]>(
 0216                    preComputeUnionReader.Invoke(null, new object?[] { caseInfo, null })!);
 217
 218                // PreComputeUnionConstructor returns FSharpFunc<obj[], obj>
 0219                Func<object[], object> constructor = ConvertFSharpFunc<object[], object>(
 0220                    preComputeUnionConstructor.Invoke(null, new object?[] { caseInfo, null })!);
 221
 0222                result[i] = new FSharpUnionCaseInfo(caseName, caseTag, fields, jsonPropertyName, fieldReader, constructo
 0223            }
 224
 0225            return result;
 0226        }
 227
 228        /// <summary>
 229        /// Creates a tag reader delegate for the specified F# union type.
 230        /// The delegate takes a union value (boxed) and returns the integer tag.
 231        /// </summary>
 232        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 233        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 234        public Func<object, int> CreateUnionTagReader(Type unionType)
 0235        {
 0236            MethodInfo preComputeUnionTagReader = EnsureMemberExists(_preComputeUnionTagReader, "Microsoft.FSharp.Reflec
 237
 238            // PreComputeUnionTagReader returns FSharpFunc<obj, int>
 0239            return ConvertFSharpFunc<object, int>(
 0240                preComputeUnionTagReader.Invoke(null, new object?[] { unionType, null })!);
 0241        }
 242
 243        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 244        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 245        public Func<TFSharpOption, T> CreateFSharpOptionValueGetter<[DynamicallyAccessedMembers(DynamicallyAccessedMembe
 0246        {
 0247            Debug.Assert(typeof(TFSharpOption).GetGenericTypeDefinition() == _fsharpOptionType);
 0248            MethodInfo valueGetter = EnsureMemberExists(typeof(TFSharpOption).GetMethod("get_Value", BindingFlags.Public
 0249            return CreateDelegate<Func<TFSharpOption, T>>(valueGetter);
 0250        }
 251
 252        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 253        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 254        public Func<TElement?, TFSharpOption> CreateFSharpOptionSomeConstructor<[DynamicallyAccessedMembers(DynamicallyA
 0255        {
 0256            Debug.Assert(typeof(TFSharpOption).GetGenericTypeDefinition() == _fsharpOptionType);
 0257            MethodInfo methodInfo = EnsureMemberExists(typeof(TFSharpOption).GetMethod("Some", BindingFlags.Public | Bin
 0258            return CreateDelegate<Func<TElement?, TFSharpOption>>(methodInfo);
 0259        }
 260
 261        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 262        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 263        public StructGetter<TFSharpValueOption, TElement> CreateFSharpValueOptionValueGetter<[DynamicallyAccessedMembers
 264            where TFSharpValueOption : struct
 0265        {
 0266            Debug.Assert(typeof(TFSharpValueOption).GetGenericTypeDefinition() == _fsharpValueOptionType);
 0267            MethodInfo valueGetter = EnsureMemberExists(typeof(TFSharpValueOption).GetMethod("get_Value", BindingFlags.P
 0268            return CreateDelegate<StructGetter<TFSharpValueOption, TElement>>(valueGetter);
 0269        }
 270
 271        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 272        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 273        public Func<TElement?, TFSharpOption> CreateFSharpValueOptionSomeConstructor<[DynamicallyAccessedMembers(Dynamic
 0274        {
 0275            Debug.Assert(typeof(TFSharpOption).GetGenericTypeDefinition() == _fsharpValueOptionType);
 0276            MethodInfo methodInfo = EnsureMemberExists(typeof(TFSharpOption).GetMethod("Some", BindingFlags.Public | Bin
 0277            return CreateDelegate<Func<TElement?, TFSharpOption>>(methodInfo);
 0278        }
 279
 280        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 281        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 282        public Func<IEnumerable<TElement>, TFSharpList> CreateFSharpListConstructor<TFSharpList, TElement>()
 0283        {
 0284            Debug.Assert(typeof(TFSharpList).GetGenericTypeDefinition() == _fsharpListType);
 0285            return CreateDelegate<Func<IEnumerable<TElement>, TFSharpList>>(EnsureMemberExists(_fsharpListCtor, "Microso
 0286        }
 287
 288        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 289        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 290        public Func<IEnumerable<TElement>, TFSharpSet> CreateFSharpSetConstructor<TFSharpSet, TElement>()
 0291        {
 0292            Debug.Assert(typeof(TFSharpSet).GetGenericTypeDefinition() == _fsharpSetType);
 0293            return CreateDelegate<Func<IEnumerable<TElement>, TFSharpSet>>(EnsureMemberExists(_fsharpSetCtor, "Microsoft
 0294        }
 295
 296        [RequiresUnreferencedCode(FSharpCoreUnreferencedCodeMessage)]
 297        [RequiresDynamicCode(FSharpCoreUnreferencedCodeMessage)]
 298        public Func<IEnumerable<Tuple<TKey, TValue>>, TFSharpMap> CreateFSharpMapConstructor<TFSharpMap, TKey, TValue>()
 0299        {
 0300            Debug.Assert(typeof(TFSharpMap).GetGenericTypeDefinition() == _fsharpMapType);
 0301            return CreateDelegate<Func<IEnumerable<Tuple<TKey, TValue>>, TFSharpMap>>(EnsureMemberExists(_fsharpMapCtor,
 0302        }
 303
 304        private Attribute? GetFSharpCompilationMappingAttribute(Type type)
 0305        {
 0306            object[] attributes = type.GetCustomAttributes(_compilationMappingAttributeType, inherit: true);
 0307            return attributes.Length == 0 ? null : (Attribute)attributes[0];
 0308        }
 309
 310        private SourceConstructFlags GetSourceConstructFlags(Attribute compilationMappingAttribute)
 0311            => _sourceConstructFlagsGetter is null ? SourceConstructFlags.None : (SourceConstructFlags)_sourceConstructF
 312
 313        // If the provided type is generated by the F# compiler, returns the runtime FSharp.Core assembly.
 314        private static Assembly? GetFSharpCoreAssembly(Type type)
 0315        {
 0316            foreach (Attribute attr in type.GetCustomAttributes(inherit: true))
 0317            {
 0318                Type attributeType = attr.GetType();
 0319                if (attributeType.FullName == CompilationMappingAttributeTypeName)
 0320                {
 0321                    return attributeType.Assembly;
 322                }
 0323            }
 324
 0325            return null;
 0326        }
 327
 328        private static TDelegate CreateDelegate<TDelegate>(MethodInfo methodInfo) where TDelegate : Delegate
 0329            => (TDelegate)Delegate.CreateDelegate(typeof(TDelegate), methodInfo, throwOnBindFailure: true)!;
 330
 331        // Converts an FSharpFunc<TArg, TResult> (which is not statically known) into a Func<TArg, TResult>.
 332        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:GetMethod",
 333            Justification = "FSharpFunc<TArg, TResult>.Invoke is always available. Callers are marked RequiresUnreferenc
 334        private static Func<TArg, TResult> ConvertFSharpFunc<TArg, TResult>(object fsharpFunc)
 0335        {
 336            // FSharpFunc<TArg, TResult> has an Invoke(TArg) method.
 337            // Create a closed delegate to avoid MethodInfo.Invoke overhead and object[] allocation per call.
 0338            MethodInfo invokeMethod = fsharpFunc.GetType().GetMethod("Invoke", BindingFlags.Public | BindingFlags.Instan
 0339            return (Func<TArg, TResult>)Delegate.CreateDelegate(typeof(Func<TArg, TResult>), fsharpFunc, invokeMethod);
 0340        }
 341
 342        private static TMemberInfo EnsureMemberExists<TMemberInfo>(TMemberInfo? memberInfo, string memberName) where TMe
 0343        {
 0344            if (memberInfo is null)
 0345            {
 0346                ThrowHelper.ThrowMissingMemberException_MissingFSharpCoreMember(memberName);
 347            }
 348
 0349            return memberInfo;
 0350        }
 351
 352        // Replicates the F# source construct flags enum
 353        // https://fsharp.github.io/fsharp-core-docs/reference/fsharp-core-sourceconstructflags.html
 354        private enum SourceConstructFlags
 355        {
 356            None = 0,
 357            SumType = 1,
 358            RecordType = 2,
 359            ObjectType = 3,
 360            Field = 4,
 361            Exception = 5,
 362            Closure = 6,
 363            Module = 7,
 364            UnionCase = 8,
 365            Value = 9,
 366            KindMask = 31,
 367            NonPublicRepresentation = 32
 368        }
 369
 370        /// <summary>
 371        /// Represents metadata for a single F# discriminated union case.
 372        /// </summary>
 373        internal sealed class FSharpUnionCaseInfo
 374        {
 0375            public FSharpUnionCaseInfo(
 0376                string name,
 0377                int tag,
 0378                PropertyInfo[] fields,
 0379                string? jsonPropertyName,
 0380                Func<object, object[]> fieldReader,
 0381                Func<object[], object> constructor)
 0382            {
 0383                Name = name;
 0384                Tag = tag;
 0385                Fields = fields;
 0386                JsonPropertyName = jsonPropertyName;
 0387                FieldReader = fieldReader;
 0388                Constructor = constructor;
 0389            }
 390
 0391            public string Name { get; }
 0392            public int Tag { get; }
 0393            public PropertyInfo[] Fields { get; }
 0394            public string? JsonPropertyName { get; }
 0395            public Func<object, object[]> FieldReader { get; }
 0396            public Func<object[], object> Constructor { get; }
 0397            public bool IsFieldless => Fields.Length == 0;
 398        }
 399    }
 400}
 401