< Summary - FSharp.Interop.Dlr

Information
Class: FSharp.Interop.Dlr.TypePairComparer
Assembly: FSharp.Interop.Dlr
File(s): /home/runner/work/FSharp.Interop.Dlr/FSharp.Interop.Dlr/FSharp.Interop.Dlr/Reflection.fs
Line coverage
100%
Covered lines: 3
Uncovered lines: 0
Coverable lines: 3
Total lines: 257
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/FSharp.Interop.Dlr/FSharp.Interop.Dlr/FSharp.Interop.Dlr/Reflection.fs

#LineLine coverage
 1namespace FSharp.Interop.Dlr
 2
 3open System
 4open System.Diagnostics.CodeAnalysis
 5open System.Dynamic
 6open System.Linq.Expressions
 7open System.Reflection
 8open System.Runtime.CompilerServices
 9open Microsoft.CSharp.RuntimeBinder
 10open FSharp.Quotations
 11
 12/// The accessibility rule the C# binder applies from its context type — public; internal from the
 13/// assembly or one it names in [<InternalsVisibleTo>] (F# `private` compiles to internal);
 14/// protected from a derived type, through a receiver of that type; private from inside the
 15/// declaring type — for a member and for its declaring type at every nesting level (a constructed
 16/// generic is as visible as its arguments). Our own reflection lookups apply the same rule.
 17module internal Accessibility =
 18    /// Whether `declaring`'s assembly opens its internals to `context`'s: the same one, or one
 19    /// it names in an [<InternalsVisibleTo>] (the C# binder honours that too). Per pair, cached.
 20    let private opensTo = System.Collections.Concurrent.ConcurrentDictionary<struct (Reflection.Assembly * Reflection.As
 21    let private sameAssembly (context: Type) (declaring: Type) =
 22        let c, d = context.Assembly, declaring.Assembly
 23        c = d
 24        || opensTo.GetOrAdd(struct (d, c), fun _ ->
 25            let name = c.GetName().Name
 26            d.GetCustomAttributes(typeof<System.Runtime.CompilerServices.InternalsVisibleToAttribute>, false)
 27            |> Seq.cast<System.Runtime.CompilerServices.InternalsVisibleToAttribute>
 28            |> Seq.exists (fun a -> let n = a.AssemblyName in (match n.IndexOf ',' with -1 -> n | i -> n.Substring(0, i)
 29    /// A generic type by its definition: a type nested in `Outer<T>` has the open `Outer<T>` as
 30    /// its DeclaringType, which no constructed `Outer<int>` is assignable to.
 31    let private definition (t: Type) = if t.IsGenericType && not t.IsGenericTypeDefinition then t.GetGenericTypeDefiniti
 32    let rec private within (context: Type) (declaring: Type) =
 33        not (isNull context) && (definition context = definition declaring || (context.IsNested && within context.Declar
 34    let rec private derived (context: Type) (declaring: Type) =
 35        let d = definition declaring
 36        let rec bases (t: Type) = not (isNull t) && (definition t = d || bases t.BaseType)
 37        not (isNull context) && (bases context || (context.IsNested && derived context.DeclaringType declaring))
 38
 39    /// C#'s qualifier rule for a protected instance member: the receiver's type is the context
 40    /// (or an enclosing type) or a subclass of it — not the base, nor a sibling derived type.
 41    /// No receiver (a static member, a nested type) has no such rule.
 42    let rec private qualifies (context: Type) (receiver: Type) =
 43        isNull receiver || (not (isNull context) && (context.IsAssignableFrom receiver || (context.IsNested && qualifies
 44
 45    /// The C# rule: public; internal from the assembly; protected from a derived type, through
 46    /// a receiver of that type; protected internal from either; private protected from a
 47    /// derived type in the assembly; private from inside the declaring type (including nested
 48    /// types).
 49    let private accessible (context: Type) (declaring: Type) (receiver: Type) (isPublic, isAssembly, isFamily, isFamilyO
 50        let family () = derived context declaring && qualifies context receiver
 51        isPublic
 52        || (isAssembly && sameAssembly context declaring)
 53        || (isFamily && family ())
 54        || (isFamilyOrAssembly && (sameAssembly context declaring || family ()))
 55        || (isFamilyAndAssembly && sameAssembly context declaring && family ())
 56        || (isPrivate && within context declaring)
 57
 58    /// Whether the type itself can be named from `context`, by the same rule at each nesting
 59    /// level: C# `dynamic` binds against an inaccessible runtime type (an `internal` class of
 60    /// another assembly, an anonymous type) as its nearest accessible base, so a public member
 61    /// of such a type is not found either.
 62    let rec private typeVisible (context: Type) (t: Type) =
 63        if isNull t then true
 64        // A constructed generic is as visible as its arguments (`List<Hidden>` is not).
 65        elif t.IsConstructedGenericType then
 66            typeVisible context (t.GetGenericTypeDefinition()) && (t.GetGenericArguments() |> Array.forall (typeVisible 
 67        elif t.IsNested then
 68            accessible context t.DeclaringType null (t.IsNestedPublic, t.IsNestedAssembly, t.IsNestedFamily, t.IsNestedF
 69            && typeVisible context t.DeclaringType
 70        else t.IsPublic || sameAssembly context t
 71
 72    /// `receiver` is the target's runtime type for an instance member (the qualifier rule).
 73    let method' (context: Type) (receiver: Type) (m: MethodBase) =
 74        typeVisible context m.DeclaringType
 75        && accessible context m.DeclaringType (if m.IsStatic then null else receiver) (m.IsPublic, m.IsAssembly, m.IsFam
 76
 77    let field (context: Type) (receiver: Type) (f: FieldInfo) =
 78        typeVisible context f.DeclaringType
 79        && accessible context f.DeclaringType (if f.IsStatic then null else receiver) (f.IsPublic, f.IsAssembly, f.IsFam
 80
 81    let all = BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Instance
 82
 83/// The implicit conversions C# applies to an argument, so our own rules accept what its binder would.
 84module internal Conversions =
 85    /// C#'s implicit numeric conversions, so an `int` argument fits an `int64` or `float` domain.
 86    let widens (from: Type) (``to``: Type) =
 87        let n = [ typeof<sbyte>, [ typeof<int16>; typeof<int32>; typeof<int64>; typeof<single>; typeof<double>; typeof<d
 88                  typeof<byte>, [ typeof<int16>; typeof<uint16>; typeof<int32>; typeof<uint32>; typeof<int64>; typeof<ui
 89                  typeof<int16>, [ typeof<int32>; typeof<int64>; typeof<single>; typeof<double>; typeof<decimal> ]
 90                  typeof<uint16>, [ typeof<int32>; typeof<uint32>; typeof<int64>; typeof<uint64>; typeof<single>; typeof
 91                  typeof<int32>, [ typeof<int64>; typeof<single>; typeof<double>; typeof<decimal> ]
 92                  typeof<uint32>, [ typeof<int64>; typeof<uint64>; typeof<single>; typeof<double>; typeof<decimal> ]
 93                  typeof<int64>, [ typeof<single>; typeof<double>; typeof<decimal> ]
 94                  typeof<uint64>, [ typeof<single>; typeof<double>; typeof<decimal> ]
 95                  typeof<char>, [ typeof<uint16>; typeof<int32>; typeof<uint32>; typeof<int64>; typeof<uint64>; typeof<s
 96                  typeof<single>, [ typeof<double> ] ]
 97        n |> List.exists (fun (f, ts) -> f = from && List.contains ``to`` ts)
 98
 99    /// Assignable, or a C# implicit numeric widening (`Expression.Convert` does the widening in the rule).
 100    let fits (paramType: Type) (argType: Type) = paramType.IsAssignableFrom argType || widens argType paramType
 101
 102/// Two generic helpers over quotations, each the one place its idiom is spelled out.
 103module internal Quotation =
 104    /// The method the first call in `e` calls: how a MethodInfo is taken from a quotation of a
 105    /// call (`<@ f x @>`; a curried static member quotes as an application of a lambda around it).
 106    let methodOf (e: Expr) : MethodInfo =
 107        let rec find (e: Expr) =
 108            match e with
 109            | Patterns.Call(_, mi, _) -> Some mi
 110            | ExprShape.ShapeVar _ -> None
 111            | ExprShape.ShapeLambda(_, body) -> find body
 112            | ExprShape.ShapeCombination(_, args) -> List.tryPick find args
 113        match find e with
 114        | Some mi -> mi
 115        | None -> invalidArg "e" (sprintf "a quotation of a call, not %A" e)
 116
 117    /// One level of a rewrite: `f` on each child of `e`, the node rebuilt around the results. The
 118    /// fallthrough of every walk that rewrites the nodes it knows and recurses into the rest.
 119    let rebuild (f: Expr -> Expr) (e: Expr) : Expr =
 120        match e with
 121        | ExprShape.ShapeVar _ -> e
 122        | ExprShape.ShapeLambda(v, body) -> Expr.Lambda(v, f body)
 123        | ExprShape.ShapeCombination(shape, args) -> ExprShape.RebuildShapeCombination(shape, List.map f args)
 124
 125/// CLR tuples past seven elements, `Tuple` and `ValueTuple` alike: the first seven, then the rest
 126/// as an eighth, `Rest`, itself a tuple. The one place that nesting is spelled out — for building
 127/// a tuple's type, reading an element, constructing one in a LINQ tree or a quotation, or at run time.
 128module internal Tuples =
 129    let private valueTupleDefinitions =
 130        [| typedefof<ValueTuple<_>>; typedefof<ValueTuple<_, _>>; typedefof<ValueTuple<_, _, _>>; typedefof<ValueTuple<_
 131           typedefof<ValueTuple<_, _, _, _, _>>; typedefof<ValueTuple<_, _, _, _, _, _>>; typedefof<ValueTuple<_, _, _, 
 132           typedefof<ValueTuple<_, _, _, _, _, _, _, _>> |]
 133
 134    /// The `ValueTuple` of these element types (one or more).
 135    let rec valueTupleOf (types: Type list) : Type =
 136        if types.Length <= 7 then valueTupleDefinitions.[types.Length - 1].MakeGenericType(Array.ofList types)
 137        else valueTupleDefinitions.[7].MakeGenericType(Array.ofList (List.take 7 types @ [ valueTupleOf (List.skip 7 typ
 138
 139    /// The member names leading to element `i`: `Item(i+1)`, after a `Rest` per seven before it.
 140    let rec elementPath (i: int) : string list =
 141        if i < 7 then [ sprintf "Item%d" (i + 1) ] else "Rest" :: elementPath (i - 7)
 142
 143    /// Element `i` of a tuple in a LINQ tree (a reference tuple's property, a struct one's field).
 144    let element (tuple: Expression) (i: int) : Expression =
 145        elementPath i |> List.fold (fun e name -> Expression.PropertyOrField(e, name) :> Expression) tuple
 146
 147    /// The fields leading to element `i` of a `ValueTuple` (for a quotation's FieldGet).
 148    let fieldPath (valueTuple: Type) (i: int) : FieldInfo list =
 149        elementPath i |> List.mapFold (fun (t: Type) name -> let f = t.GetField name in f, f.FieldType) valueTuple |> fs
 150
 151    /// The arguments of a tuple constructor: the first seven values and the rest built by `nest`.
 152    let private split (t: Type) (values: 'E list) (nest: Type -> 'E list -> 'E) =
 153        let elements = t.GetGenericArguments()
 154        if elements.Length = 8 then elements, List.take 7 values @ [ nest elements.[7] (List.skip 7 values) ] else eleme
 155
 156    /// A tuple of type `t` (reference or struct) from these values, in a LINQ tree.
 157    let rec newExpression (t: Type) (values: Expression list) : Expression =
 158        let elements, args = split t values newExpression
 159        Expression.New(t.GetConstructor elements, args) :> Expression
 160
 161    /// A tuple of type `t` (reference or struct) from these values, in a quotation.
 162    let rec newQuotation (t: Type) (values: Expr list) : Expr =
 163        let elements, args = split t values newQuotation
 164        Expr.NewObject(t.GetConstructor elements, args)
 165
 166    /// A tuple of type `t` from these values, at run time: only `Binders.ByRefSite`'s body,
 167    /// which never runs (see there), so excluded from coverage with it.
 168    [<ExcludeFromCodeCoverage>]
 169    let rec make (t: Type) (values: obj list) : obj =
 170        let _, args = split t values make
 171        Activator.CreateInstance(t, Array.ofList args)
 172
 173/// Reference-equality comparer for a pair of types: the default struct-tuple comparer boxes and
 174/// costs ~100 ns per lookup, which the per-call conversion caches pay each time.
 175/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 176[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 4177type TypePairComparer() =
 178    static member val Instance = TypePairComparer()
 179    interface System.Collections.Generic.IEqualityComparer<struct (Type * Type)> with
 6134180        member _.Equals(struct (a1, a2), struct (b1, b2)) = obj.ReferenceEquals(a1, b1) && obj.ReferenceEquals(a2, b2)
 3726181        member _.GetHashCode(struct (a, b)) = RuntimeHelpers.GetHashCode a * 31 + RuntimeHelpers.GetHashCode b
 182
 183/// A delegate type's own members. F# compiles a delegate at the *type's* accessibility, so an
 184/// `internal` delegate has a non-public `Invoke` and constructor where C#'s stay public; asking for
 185/// the public one gives null — a null-reference error far from the cause, or a rule that silently
 186/// does not apply and C#'s own "invalid arguments" in its place (#121, #150).
 187module internal DelegateMembers =
 188    let private flags = BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.NonPublic
 189    let invokeOf (delegateType: Type) : MethodInfo = delegateType.GetMethod("Invoke", flags)
 190    /// Whether a delegate of this many parameters has a `Func`/`Action` (17 type parameters, the
 191    /// result among them); past it the delegate type is one emitted at run time.
 192    let funcFits (parameterCount: int) = parameterCount + 1 <= 17
 193    let constructorOf (delegateType: Type) : ConstructorInfo =
 194        delegateType.GetConstructor(flags, null, [| typeof<obj>; typeof<nativeint> |], null)
 195    let onNetFramework = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith ".NET Framewo
 196    /// Whether C#'s binder would crash invoking a value of this type: .NET Framework's
 197    /// `Expression.Invoke` (which C#'s Invoke binder builds) finds `Invoke` by public lookup only,
 198    /// and throws a NullReferenceException for an F# `internal` delegate's (#151).
 199    let csharpCannotInvoke (t: Type) =
 200        onNetFramework && typeof<Delegate>.IsAssignableFrom t && (let i = invokeOf t in not (isNull i) && not i.IsPublic
 201    /// .NET Framework's `Expression.Lambda` finds a delegate type's `Invoke` by public lookup only,
 202    /// so a lambda at an F# `internal` delegate type fails there: the public delegate type of the
 203    /// same signature to compile it at instead (`Func`/`Action`, or one emitted past sixteen
 204    /// parameters), which `DelegateLiteral.From` rebinds (#125). None where no stand-in is needed,
 205    /// or for a byref signature (left as it was).
 206    let standIn (delegateType: Type) : Type option =
 207        let invoke = invokeOf delegateType
 208        let ps = invoke.GetParameters() |> Array.map (fun p -> p.ParameterType)
 209        if onNetFramework && not invoke.IsPublic && not (ps |> Array.exists (fun p -> p.IsByRef)) then
 210            Some(Expression.GetDelegateType(Array.append ps [| invoke.ReturnType |]))
 211        else None
 212
 213/// When an F# function and a delegate can stand for each other — one rule for both directions.
 214/// The side that receives a value must accept it: the same type, or for reference types the
 215/// variance delegates have (a parameter may take a more general type, a result may be a more
 216/// specific one); no boxing, so value types match exactly. A `void` delegate is a `unit` result,
 217/// and so is one returning `Unit` itself (`Func<int, unit>`).
 218module internal Signatures =
 219    let private accepts (receiver: Type) (given: Type) =
 220        receiver = given || (not receiver.IsValueType && not given.IsValueType && receiver.IsAssignableFrom given)
 221
 222    let private results (receiver: Type) (given: Type) (unitResult: bool) (isVoid: bool) =
 223        if isVoid then unitResult else accepts receiver given
 224
 225    /// A function of `domains` → `result` serves as a delegate of `parameters` → `returns`: the
 226    /// delegate's arguments go to the function, its result comes back. A `unit` result serves
 227    /// only a `void` or `Unit`-returning delegate, as in F# (`fun x -> ()` is no `Func<int, obj>`).
 228    let functionServesDelegate (domains: Type list) (result: Type) (parameters: Type list) (returns: Type) =
 229        domains.Length = parameters.Length && List.forall2 accepts domains parameters
 230        && (if result = typeof<unit> then returns = typeof<Void> || returns = typeof<unit>
 231            else results returns result false (returns = typeof<Void>))
 232
 233    /// A delegate of `parameters` → `returns` serves as a function of `domains` → `result`: the
 234    /// function's arguments go to the delegate, its result comes back.
 235    let delegateServesFunction (domains: Type list) (result: Type) (parameters: Type list) (returns: Type) =
 236        domains.Length = parameters.Length && List.forall2 accepts parameters domains
 237        && results result returns (result = typeof<unit>) (returns = typeof<Void>)
 238
 239    /// The same signature, no variance: what ranks a candidate among several a delegate or function
 240    /// argument fits (an exact match beats one through variance, as an exact type does).
 241    let exactly (domains: Type list) (result: Type) (parameters: Type list) (returns: Type) =
 242        domains = parameters && (returns = result || (returns = typeof<Void> && result = typeof<unit>))
 243
 244/// Delegates over IL emitted once — `new Adapter(f)` and a delegate constructor, a rewrap — so a
 245/// per-call conversion costs an allocation, not `ConstructorInfo.Invoke` or
 246/// `Delegate.CreateDelegate`'s validation (~150–300 ns). One policy: `skipVisibility`, which covers
 247/// a non-public member (an F# `internal` delegate's `Invoke` and constructor); and any failure
 248/// falls back, not only an unsupported platform — the fallback is correct by construction, and a
 249/// failure inside a static initializer would otherwise poison that type for the process.
 250module internal Emit =
 251    let factory<'F when 'F :> Delegate> (name: string) (returnType: Type) (parameters: Type[]) (owner: Module)
 252                                       (emit: System.Reflection.Emit.ILGenerator -> unit) (fallback: unit -> 'F) : 'F =
 253        try
 254            let dm = System.Reflection.Emit.DynamicMethod(name, returnType, parameters, owner, true)
 255            emit (dm.GetILGenerator())
 256            dm.CreateDelegate(typeof<'F>) :?> 'F
 257        with _ -> fallback ()