| | | 1 | | namespace FSharp.Interop.Dlr |
| | | 2 | | |
| | | 3 | | open System |
| | | 4 | | open System.Diagnostics.CodeAnalysis |
| | | 5 | | open System.Dynamic |
| | | 6 | | open System.Linq.Expressions |
| | | 7 | | open System.Reflection |
| | | 8 | | open System.Runtime.CompilerServices |
| | | 9 | | open Microsoft.CSharp.RuntimeBinder |
| | | 10 | | open FSharp.Quotations |
| | | 11 | | |
| | | 12 | | /// Builds Microsoft.CSharp binders and emits the quotation fragment that calls a |
| | | 13 | | /// pre-created CallSite: `Call(FieldGet(Value site, Target), Invoke, site :: args)`. |
| | | 14 | | /// The `Value site` becomes an Expression.Constant, so the site is baked into the |
| | | 15 | | /// compiled delegate. |
| | | 16 | | module internal Binders = |
| | | 17 | | |
| | | 18 | | /// `voidType` makes the F# compiler emit IL the JIT rejects, so get it indirectly. |
| | | 19 | | let private voidType = typeof<Action>.GetMethod("Invoke").ReturnType |
| | | 20 | | |
| | | 21 | | /// One argument as the binder sees it: its expression, the static type used in |
| | | 22 | | /// the site delegate, and the C# argument-info flags. |
| | | 23 | | type Arg = |
| | | 24 | | { Expr: Expr |
| | | 25 | | Type: Type |
| | | 26 | | Flags: CSharpArgumentInfoFlags |
| | | 27 | | Name: string |
| | | 28 | | /// A `Dlr.ref`'s variable: two refs over the same one are one storage at the call, as in C#. |
| | | 29 | | Alias: obj } |
| | | 30 | | |
| | | 31 | | /// A dynamically typed argument: the binder dispatches on its runtime type. |
| | | 32 | | let dynamicArg (e: Expr) = |
| | 11000 | 33 | | { Expr = e; Type = typeof<obj>; Flags = CSharpArgumentInfoFlags.None; Name = null; Alias = null } |
| | | 34 | | |
| | | 35 | | /// A type as the target (`Dlr.Static<T>.Overloads`, `Dlr.new'<T>`): argument 0 of the site is |
| | | 36 | | /// `typeof<T>` flagged as a static type, the C# compiler's shape for `T.Member(…)`. |
| | | 37 | | let staticTarget (t: Type) = |
| | 220 | 38 | | { Expr = Expr.Value(t, typeof<Type>); Type = typeof<Type>; Flags = CSharpArgumentInfoFlags.UseCompileTimeType || |
| | | 39 | | |
| | 6559 | 40 | | let isStatic (a: Arg) = a.Flags.HasFlag CSharpArgumentInfoFlags.IsStaticType |
| | | 41 | | |
| | | 42 | | /// `Dlr.Static<T>.Overloads` is for calls; C#'s binder has no static form of the other operations |
| | | 43 | | /// (GetMember, SetMember, IsEvent, indexers) and plain F# already has them: `T.P`. |
| | | 44 | | let private callsOnly (what: string) (target: Arg) = |
| | 9789 | 45 | | if isStatic target then |
| | 24 | 46 | | raise (DlrTranslationException(sprintf "dlr { } does not support %s on Dlr.Static<T>.Overloads, which is for |
| | | 47 | | |
| | | 48 | | /// A statically typed argument: the binder uses the quotation's type, as C# would. |
| | | 49 | | /// An `obj`-typed expression stays dynamic so F# callers get FSharp.Interop.Dynamic-like |
| | | 50 | | /// overload resolution on boxed values. |
| | | 51 | | let typedArg (e: Expr) = |
| | 7254 | 52 | | if e.Type = typeof<obj> then dynamicArg e |
| | 5802 | 53 | | else { Expr = e; Type = e.Type; Flags = CSharpArgumentInfoFlags.UseCompileTimeType; Name = null; Alias = null } |
| | | 54 | | |
| | | 55 | | let private withFlag (flag: CSharpArgumentInfoFlags) (arg: Arg) = |
| | | 56 | | // Spelled out with int locals: `|||` straight on the enum resolved to the dynamic |
| | | 57 | | // (throwing) FSharp.Core path when compiled against the FSharp.Core floor. |
| | 6057 | 58 | | let current: int = LanguagePrimitives.EnumToValue arg.Flags |
| | 6057 | 59 | | let added: int = LanguagePrimitives.EnumToValue flag |
| | 6057 | 60 | | { arg with Flags = LanguagePrimitives.EnumOfValue(current ||| added) } |
| | | 61 | | |
| | | 62 | | /// A literal argument: the binder applies C#'s constant conversions (an in-range `int` |
| | | 63 | | /// literal to `byte`, `0` to an enum, `null` to any reference type). |
| | 3281 | 64 | | let constant (arg: Arg) = withFlag CSharpArgumentInfoFlags.Constant arg |
| | | 65 | | |
| | | 66 | | /// A `ref` / `out` argument (`Dlr.ref v`, `Dlr.out`): the site's parameter is `t&`, and |
| | | 67 | | /// `initial` is the value passed in (null for an out); `alias` is a ref's variable (null for an out). |
| | | 68 | | let byRefArg (isOut: bool) (t: Type) (initial: Expr) (alias: obj) = |
| | 4386 | 69 | | withFlag (if isOut then CSharpArgumentInfoFlags.IsOut else CSharpArgumentInfoFlags.IsRef) |
| | 1462 | 70 | | { Expr = initial; Type = t.MakeByRefType(); Flags = CSharpArgumentInfoFlags.UseCompileTimeType; Name = null; |
| | | 71 | | |
| | | 72 | | let named (name: string) (arg: Arg) = |
| | 2628 | 73 | | { withFlag CSharpArgumentInfoFlags.NamedArgument arg with Name = name } |
| | | 74 | | |
| | 17399 | 75 | | let private argInfo (a: Arg) = CSharpArgumentInfo.Create(a.Flags, a.Name) |
| | | 76 | | |
| | | 77 | | /// C#'s flags for a call whose result is discarded (a statement) or used. |
| | 5240 | 78 | | let private resultFlags (discard: bool) = if discard then CSharpBinderFlags.ResultDiscarded else CSharpBinderFlags.N |
| | | 79 | | |
| | | 80 | | /// No argument is named: the positional F# rules apply (a named call is C#'s alone). |
| | 11994 | 81 | | let private allPositional (args: Arg list) = args |> List.forall (fun a -> isNull a.Name) |
| | | 82 | | |
| | | 83 | | /// A `CallSite<_>` for `binder` over `args`, as a `Value` node (a constant in the compiled tree). |
| | | 84 | | let private site (binder: CallSiteBinder) (args: Arg list) (resultType: Type) = |
| | 22819 | 85 | | let delegateType = |
| | 99088 | 86 | | Expression.GetDelegateType(Array.ofList (typeof<CallSite> :: [ for a in args -> a.Type ] @ [ resultType ])) |
| | 22819 | 87 | | let siteType = typedefof<CallSite<_>>.MakeGenericType delegateType |
| | 22819 | 88 | | Expr.Value(siteType.GetMethod("Create").Invoke(null, [| box binder |]), siteType) |
| | | 89 | | |
| | | 90 | | /// An in-place operation on a struct copy, then its write-back (`inPlace` in `TranslateBlock.fs`): the |
| | | 91 | | /// write-back must run even when the operation throws after mutating, as plain F# keeps the |
| | | 92 | | /// mutation — a `finally`, which the quotation converter has no form for. This placeholder is |
| | | 93 | | /// what the translator emits; the LINQ `SiteHoister` rewrites it into a TryFinally. The body is |
| | | 94 | | /// the slow path, for a tree the hoister has not seen (the write-back then only on success); |
| | | 95 | | /// the hoister sees every tree, so the body never runs and is excluded from coverage. |
| | | 96 | | type InPlace = |
| | | 97 | | [<ExcludeFromCodeCoverage>] |
| | | 98 | | static member Then<'T>(operation: 'T, writeBack: unit) : 'T = ignore writeBack; operation |
| | | 99 | | /// Past Func's 17 type parameters (15 arguments and up) a site's delegate is a type emitted at run time, |
| | | 100 | | /// which must not be named in a quotation: FSharp.Core's checks ask its assembly |
| | | 101 | | /// `ReflectionOnly`, unimplemented on browser-wasm. Such a call is written as one of these |
| | | 102 | | /// placeholders — every type in it a plain one — and the LINQ `SiteHoister` (`SiteHoister.fs`) |
| | | 103 | | /// rewrites it into the typed `Invoke` after conversion. The bodies are the slow path, for a |
| | | 104 | | /// tree the hoister has not seen; the hoister sees every tree, so they never run and are |
| | | 105 | | /// excluded from coverage. |
| | | 106 | | [<ExcludeFromCodeCoverage>] |
| | | 107 | | let private wideInvoke (site: CallSite) (args: obj[]) = |
| | | 108 | | let target = site.GetType().GetField("Target").GetValue site :?> Delegate |
| | | 109 | | target.DynamicInvoke(Array.append [| box site |] args) |
| | | 110 | | [<ExcludeFromCodeCoverage>] |
| | | 111 | | type WideSite = |
| | | 112 | | static member Invoke(site: CallSite, delegateType: Type, args: obj[]) : obj = ignore delegateType; wideInvoke si |
| | | 113 | | static member InvokeVoid(site: CallSite, delegateType: Type, args: obj[]) : unit = ignore delegateType; wideInvo |
| | 4 | 114 | | let private wideMethod (name: string) = typeof<WideSite>.GetMethod(name, BindingFlags.Static ||| BindingFlags.Public |
| | | 115 | | let private wideInvokeMethod = wideMethod "Invoke" |
| | | 116 | | let private wideInvokeVoidMethod = wideMethod "InvokeVoid" |
| | | 117 | | |
| | | 118 | | /// The holder a byref call returns: the result (`obj`), then each byref argument's value after |
| | | 119 | | /// the call, typed, in argument order — a `ValueTuple`, nested in its `Rest` past seven, so |
| | | 120 | | /// nothing is boxed or allocated on the way out. |
| | 657 | 121 | | let byRefHolderType (types: Type list) : Type = Tuples.valueTupleOf types |
| | | 122 | | /// Element `i` of a holder: `Item(i+1)`, through `Rest` past the seventh. |
| | 1418 | 123 | | let byRefHolderPath (holder: Type) (i: int) : FieldInfo list = Tuples.fieldPath holder i |
| | | 124 | | |
| | | 125 | | /// A site with byref parameters (`Dlr.out`, `Dlr.ref`). No quotation can pass a byref, so the |
| | | 126 | | /// call is this placeholder: the values go in as an `obj[]` (target first) and come back as the |
| | | 127 | | /// holder `'H` (see `byRefHolderType`). `byRefs` are the byref positions in `args`, `outs` those |
| | | 128 | | /// of them that are out (whose value in is not read: it starts as the default), and `sameAs` |
| | | 129 | | /// per position the earlier one over the same variable (-1 if none), which shares its storage. The LINQ |
| | | 130 | | /// `SiteHoister` rewrites it into the typed `Invoke` (no array, no boxing); this body is the |
| | | 131 | | /// slow path, correct on the JIT (`DynamicInvoke` writes byref parameters back into its array) |
| | | 132 | | /// but not on Mono wasm, which is why the rewrite is required there. The hoister sees every |
| | | 133 | | /// tree, so the body never runs and is excluded from coverage. |
| | | 134 | | [<ExcludeFromCodeCoverage>] |
| | | 135 | | type ByRefSite = |
| | | 136 | | static member Invoke<'H>(site: CallSite, delegateType: Type, args: obj[], byRefs: int[], outs: int[], sameAs: in |
| | | 137 | | ignore (delegateType, outs, sameAs) |
| | | 138 | | let target = site.GetType().GetField("Target").GetValue site :?> Delegate |
| | | 139 | | let all = Array.append [| box site |] args |
| | | 140 | | let result = |
| | | 141 | | // A binder's error, or the callee's, arrives as itself, as at any other site. |
| | | 142 | | try target.DynamicInvoke all |
| | | 143 | | with :? TargetInvocationException as e when not (isNull e.InnerException) -> |
| | | 144 | | System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e.InnerException).Throw() |
| | | 145 | | null |
| | | 146 | | let values = result :: [ for i in byRefs -> all.[i + 1] ] |
| | | 147 | | Tuples.make typeof<'H> values :?> 'H |
| | | 148 | | let private byRefInvokeMethod = typeof<ByRefSite>.GetMethod("Invoke", BindingFlags.Static ||| BindingFlags.Public || |
| | | 149 | | |
| | | 150 | | /// The holder type of a byref call over `all` (target first): the result, then each byref's type. |
| | 17724 | 151 | | let byRefHolderOf (all: Arg list) = byRefHolderType (typeof<obj> :: [ for a in all do if a.Type.IsByRef then yield a |
| | | 152 | | |
| | | 153 | | /// A byref site (see `ByRefSite`) for `binder` over `all`, returning `resultType` (`Void` for a |
| | | 154 | | /// discarded result); the expression is the holder of result and byref values. |
| | | 155 | | let private byRefSite (binder: CallSiteBinder) (all: Arg list) (resultType: Type) : Expr = |
| | 7910 | 156 | | let delegateType = Expression.GetDelegateType(Array.ofList (typeof<CallSite> :: [ for a in all -> a.Type ] @ [ r |
| | 1294 | 157 | | let site = typedefof<CallSite<_>>.MakeGenericType(delegateType).GetMethod("Create").Invoke(null, [| box binder | |
| | 17448 | 158 | | let byRefs = [| for i, a in List.indexed all do if a.Type.IsByRef then yield i |] |
| | 17378 | 159 | | let outs = [| for i, a in List.indexed all do if a.Flags.HasFlag CSharpArgumentInfoFlags.IsOut then yield i |] |
| | | 160 | | // Per position: the first earlier position whose ref is over the same variable, else -1. |
| | 1294 | 161 | | let sameAs = |
| | 6616 | 162 | | [| for i, a in List.indexed all -> |
| | 7986 | 163 | | if isNull a.Alias then -1 |
| | 1645 | 164 | | else match all |> List.take i |> List.tryFindIndex (fun b -> obj.ReferenceEquals(b.Alias, a.Alias)) with |
| | 1294 | 165 | | let holder = byRefHolderOf all |
| | 15966 | 166 | | let boxed = [ for a in all -> if a.Expr.Type = typeof<obj> then a.Expr else Expr.Coerce(a.Expr, typeof<obj>) ] |
| | 1294 | 167 | | Expr.Call(byRefInvokeMethod.MakeGenericMethod holder, |
| | 1294 | 168 | | [ Expr.Value(site, typeof<CallSite>); Expr.Value(delegateType, typeof<Type>) |
| | 1294 | 169 | | Expr.NewArray(typeof<obj>, boxed); Expr.Value(byRefs); Expr.Value(outs); Expr.Value(sameAs) ]) |
| | | 170 | | |
| | | 171 | | /// `x?M(…, Dlr.out, Dlr.ref v, …)`: C#'s InvokeMember over byref parameters (target first in `all`). |
| | | 172 | | let invokeMemberByRef (context: Type) (name: string) (typeArgs: Type list) (discard: bool) (all: Arg list) : Expr = |
| | 1218 | 173 | | let flags = resultFlags discard |
| | 2436 | 174 | | let typeArgSeq = match typeArgs with [] -> null | ts -> ts :> seq<Type> |
| | 9874 | 175 | | byRefSite (Binder.InvokeMember(flags, name, typeArgSeq, context, [ for a in all -> argInfo a ])) all (if discard |
| | | 176 | | |
| | | 177 | | /// `Dlr.call f (…, Dlr.out, …)` / `Dlr.apply`: C#'s Invoke of the value itself over byref parameters. |
| | | 178 | | let invokeByRef (context: Type) (discard: bool) (all: Arg list) : Expr = |
| | 68 | 179 | | callsOnly "invoking a value (Dlr.call / Dlr.apply)" (List.head all) |
| | 68 | 180 | | let flags = resultFlags discard |
| | 1000 | 181 | | byRefSite (FSharpByRefInvokeBinder(Binder.Invoke(flags, context, [ for a in all -> argInfo a ]) :?> InvokeBinder |
| | | 182 | | |
| | | 183 | | /// `Dlr.new'<T>(…, Dlr.out, …)`: C#'s InvokeConstructor over byref parameters; the result is typed `t`. |
| | | 184 | | let invokeConstructorByRef (context: Type) (t: Type) (args: Arg list) : Expr = |
| | 8 | 185 | | let all = staticTarget t :: args |
| | 48 | 186 | | byRefSite (Binder.InvokeConstructor(CSharpBinderFlags.None, context, [ for a in all -> argInfo a ])) all t |
| | | 187 | | |
| | | 188 | | /// Emits the call-site invocation for `binder` over `args`, returning `resultType` |
| | | 189 | | /// (`Void` for a discarded result, which yields an Action-shaped site). |
| | | 190 | | let siteCall (binder: CallSiteBinder) (args: Arg list) (resultType: Type) : Expr = |
| | 21931 | 191 | | let siteExpr = site binder args resultType |
| | 43862 | 192 | | let siteValue = match siteExpr with FSharp.Quotations.Patterns.Value(v, _) -> v | _ -> null |
| | 21931 | 193 | | let siteType = siteExpr.Type |
| | 21931 | 194 | | let siteDelegate = siteType.GetGenericArguments().[0] |
| | | 195 | | // `args` holds the target too; the site's delegate takes the CallSite before them. |
| | 21931 | 196 | | if DelegateMembers.funcFits (args.Length + 1) then |
| | 21855 | 197 | | let target = Expr.FieldGet(siteExpr, siteType.GetField("Target")) |
| | 92532 | 198 | | Expr.Call(target, siteDelegate.GetMethod("Invoke"), siteExpr :: [ for a in args -> a.Expr ]) |
| | | 199 | | else |
| | 76 | 200 | | let isVoid = resultType = typeof<Action>.GetMethod("Invoke").ReturnType |
| | 4500 | 201 | | let boxed = [ for a in args -> if a.Expr.Type = typeof<obj> then a.Expr else Expr.Coerce(a.Expr, typeof<obj> |
| | 228 | 202 | | let call = Expr.Call((if isVoid then wideInvokeVoidMethod else wideInvokeMethod), |
| | 76 | 203 | | [ Expr.Value(siteValue, typeof<CallSite>); Expr.Value(siteDelegate, typeof<Type>); Expr |
| | 300 | 204 | | if isVoid || resultType = typeof<obj> then call else Expr.Coerce(call, resultType) |
| | | 205 | | |
| | | 206 | | let getMember (context: Type) (name: string) (target: Arg) = |
| | 8659 | 207 | | callsOnly "reading a member" target |
| | 8655 | 208 | | siteCall (Binder.GetMember(CSharpBinderFlags.None, name, context, [ argInfo target ])) [ target ] typeof<obj> |
| | | 209 | | |
| | | 210 | | let setMember (context: Type) (name: string) (target: Arg) (value: Arg) = |
| | 166 | 211 | | callsOnly "setting a member" target |
| | 162 | 212 | | let csharp = Binder.SetMember(CSharpBinderFlags.None, name, context, [ argInfo target; argInfo value ]) :?> SetM |
| | 162 | 213 | | siteCall (MetaObjectAwareBinder(FSharpSetMemberBinder(context, name, csharp))) [ target; value ] typeof<obj> |
| | | 214 | | |
| | | 215 | | /// C#'s Convert to `resultType`; to a delegate or F# function type through |
| | | 216 | | /// `FSharpConvertBinder`, so a function value becomes a delegate and back (#202). Every |
| | | 217 | | /// other type keeps C#'s binder as it is. |
| | | 218 | | let private convertBinder (context: Type) (flags: CSharpBinderFlags) (resultType: Type) : CallSiteBinder = |
| | 9187 | 219 | | let csharp = Binder.Convert(flags, resultType, context) |
| | 27285 | 220 | | if typeof<Delegate>.IsAssignableFrom resultType || FSharp.Reflection.FSharpType.IsFunction resultType then |
| | 324 | 221 | | FSharpConvertBinder(csharp :?> ConvertBinder) :> CallSiteBinder |
| | 8863 | 222 | | else csharp |
| | | 223 | | |
| | | 224 | | /// C#'s InvokeMember binder wrapped to apply F# function values (see FSharpInvokeMemberBinder) |
| | | 225 | | /// for positional, non-generic calls of any arity; otherwise C#'s binder as is. |
| | | 226 | | let private smartInvokeMember (context: Type) (name: string) (typeArgs: Type list) (discard: bool) (all: Arg list) : |
| | 3327 | 227 | | let args = List.tail all |
| | 3327 | 228 | | let flags = resultFlags discard |
| | 6654 | 229 | | let typeArgSeq = match typeArgs with [] -> null | ts -> ts :> seq<Type> |
| | 19506 | 230 | | let csharp = Binder.InvokeMember(flags, name, typeArgSeq, context, [ for a in all -> argInfo a ]) |
| | 3327 | 231 | | let positional = allPositional args |
| | | 232 | | // A static target has no instance for the function-member rules, but the argument rules |
| | | 233 | | // (optional parameters, function/delegate conversions) apply to its static methods. |
| | 3327 | 234 | | if isStatic (List.head all) then |
| | 332 | 235 | | (if not positional || not typeArgs.IsEmpty then csharp else FSharpStaticInvokeMemberBinder(context, name, cs |
| | | 236 | | else |
| | | 237 | | // Discarded results too: the site is void-returning and the DLR drops the rule's value. |
| | 18954 | 238 | | let csharpInvoke = Binder.Invoke(flags, context, [ for a in all -> argInfo a ]) :?> InvokeBinder |
| | 10117 | 239 | | let inner = if not positional || not typeArgs.IsEmpty then csharp else FSharpInvokeMemberBinder(context, nam |
| | | 240 | | // An instance target may be a meta-object: F# function arguments become delegates for it. |
| | 3231 | 241 | | MetaObjectAwareBinder(inner :?> DynamicMetaObjectBinder) :> CallSiteBinder |
| | | 242 | | |
| | | 243 | | /// `x?Name(args)` whose inferred type is `A -> R`: InvokeMember, applying an F# function value |
| | | 244 | | /// held by the member when C# cannot invoke it. |
| | | 245 | | let invokeMemberOrApply (context: Type) (name: string) (typeArgs: Type list) (discard: bool) (target: Arg) (args: Ar |
| | 3027 | 246 | | let all = target :: args |
| | 9081 | 247 | | siteCall (smartInvokeMember context name typeArgs discard all) all (if discard then voidType else typeof<obj>) |
| | | 248 | | |
| | | 249 | | /// `Dlr.new'<T>(args)`: C#'s `new T(…)` with the constructor chosen by the arguments' |
| | | 250 | | /// runtime types. The type goes in as argument 0 of the site, flagged as a static type, |
| | | 251 | | /// exactly as the C# compiler emits it. |
| | | 252 | | let invokeConstructor (context: Type) (t: Type) (args: Arg list) = |
| | 96 | 253 | | let all = staticTarget t :: args |
| | | 254 | | // Result typed `t`, as the C# compiler's own site for `new T(…)` is: the binder types a |
| | | 255 | | // constructor's result as `T`, and an obj-typed site would reject that for a value type. |
| | 540 | 256 | | let csharp = Binder.InvokeConstructor(CSharpBinderFlags.None, context, [ for a in all -> argInfo a ]) :?> Dynami |
| | 96 | 257 | | let positional = allPositional args |
| | 192 | 258 | | let binder = if positional then FSharpInvokeConstructorBinder(context, t, csharp) :> CallSiteBinder else csharp |
| | 96 | 259 | | siteCall binder all t |
| | | 260 | | |
| | | 261 | | /// `Dlr.call target args` / `Dlr.apply args target`: invoke `target` itself, applying it when |
| | | 262 | | /// it is an F# function. |
| | | 263 | | let invokeOrApply (context: Type) (discard: bool) (target: Arg) (args: Arg list) = |
| | 162 | 264 | | callsOnly "invoking a value (Dlr.call / Dlr.apply)" target |
| | 158 | 265 | | let all = target :: args |
| | 1002 | 266 | | let csharp = Binder.Invoke(resultFlags discard, context, [ for a in all -> argInfo a ]) |
| | 158 | 267 | | let positional = allPositional args |
| | | 268 | | let binder = |
| | 170 | 269 | | if not positional then csharp |
| | 146 | 270 | | else FSharpInvokeBinder(csharp :?> InvokeBinder) :> CallSiteBinder |
| | 474 | 271 | | siteCall (MetaObjectAwareBinder(binder :?> DynamicMetaObjectBinder)) all (if discard then voidType else typeof<o |
| | | 272 | | |
| | | 273 | | /// `asFunction`'s factories past five, per (function type, invoke site type). |
| | | 274 | | let private factories = System.Collections.Concurrent.ConcurrentDictionary<struct (Type * Type), Delegate>(TypePairC |
| | | 275 | | |
| | | 276 | | /// A value read as an F# function type (see FunctionMember): the argument types come from the |
| | | 277 | | /// function type's domains, curried or tupled; `binderFor` gives the site's binder for those |
| | | 278 | | /// typed argument slots (`unit -> R` gets an empty list), and `shortcut` says to return the |
| | | 279 | | /// target itself when it already is a function of that type (a bare value, not a member read). |
| | | 280 | | /// Any arity, curried or tupled: typed helpers up to five, functions built at run time past it. |
| | | 281 | | let private asFunction (context: Type) (functionType: Type) (original: Arg) (shortcut: bool) (binderFor: Arg list -> |
| | 308 | 282 | | let target = original |
| | | 283 | | let rec domains (t: Type) = |
| | 874 | 284 | | if FSharp.Reflection.FSharpType.IsFunction t then |
| | 566 | 285 | | let d, r = FSharp.Reflection.FSharpType.GetFunctionElements t |
| | 566 | 286 | | let ds, result = domains r |
| | 566 | 287 | | d :: ds, result |
| | 308 | 288 | | else [], t |
| | 308 | 289 | | let ds, resultType = domains functionType |
| | | 290 | | let tupled, argTypes = |
| | 308 | 291 | | match ds with |
| | 310 | 292 | | | [ d ] when d = typeof<unit> -> false, [] |
| | 192 | 293 | | | [ d ] when FSharp.Reflection.FSharpType.IsTuple d -> true, List.ofArray (FSharp.Reflection.FSharpType.GetT |
| | 138 | 294 | | | ds -> false, ds |
| | | 295 | | |
| | | 296 | | // With the shortcut the target is evaluated once into a variable that both the type test |
| | | 297 | | // and the invoker read. |
| | 308 | 298 | | let targetVar = Var("target", typeof<obj>) |
| | 616 | 299 | | let target = if shortcut then { target with Expr = Expr.Var targetVar } else target |
| | 2756 | 300 | | let invokeArgs = [ for t in argTypes -> typedArg (Expr.Value(null, t)) ] |
| | 308 | 301 | | let all = target :: invokeArgs |
| | | 302 | | // `… -> unit` invokes with the result discarded (a void site), as a statement call does. |
| | 308 | 303 | | let discard = resultType = typeof<unit> |
| | 924 | 304 | | let invokeSite = site (binderFor all discard) all (if discard then voidType else typeof<obj>) |
| | 924 | 305 | | let shape = (if tupled then "Tupled" else "Curried") + string argTypes.Length |
| | | 306 | | let built = |
| | 308 | 307 | | if argTypes.Length > 5 then |
| | | 308 | | // Beyond the typed helpers, the function as a factory compiled once here (see |
| | | 309 | | // FunctionBuilder): the sites and the target in, the function over them out. The |
| | | 310 | | // sites stay in the quotation, as `CallSite` constants (a per-key site lifts them), |
| | | 311 | | // typed as the base: a wide site's delegate type is emitted, which a quotation |
| | | 312 | | // must not name (see WideSite). |
| | | 313 | | let sites = |
| | 176 | 314 | | [ yield invokeSite |
| | 248 | 315 | | if not discard then yield site (convertBinder context CSharpBinderFlags.None resultType) [ dynamic |
| | 252 | 316 | | |> List.map (function Patterns.Value(v, t) -> v, t | _ -> failwith "unreachable") |
| | 592 | 317 | | let factoryType = Expression.GetFuncType(Array.ofList ([ for _ in sites -> typeof<CallSite> ] @ [ typeof |
| | | 318 | | // The factory depends on the function type and the sites' types, not on the sites: |
| | | 319 | | // compiled once per shape, which a per-key site's template (built again per key, for |
| | | 320 | | // its sites) then finds here rather than compiling and discarding its own. |
| | 88 | 321 | | let factory = |
| | 88 | 322 | | factories.GetOrAdd(struct (functionType, snd sites.Head), fun _ -> |
| | 252 | 323 | | let siteParams = [ for i in 0 .. sites.Length - 1 -> Expression.Parameter(typeof<CallSite>, spri |
| | 36 | 324 | | let targetParam = Expression.Parameter(typeof<obj>, "target") |
| | 88 | 325 | | // Captured: each site as its own type, then the target. |
| | 228 | 326 | | let captured = [ for p, (_, t) in List.zip siteParams sites -> Expression.Convert(p, t) :> Expre |
| | 88 | 327 | | let finish (captured: Expression list) (args: Expression list) = |
| | 88 | 328 | | let invoke (i: int) (args: Expression list) = |
| | 60 | 329 | | Expression.Invoke(Expression.Field(captured.[i], "Target"), captured.[i] :: args) :> Exp |
| | 36 | 330 | | let raw = invoke 0 (List.last captured :: args) |
| | 72 | 331 | | if discard then FunctionBuilder.unitOf raw else invoke 1 [ raw ] |
| | 180 | 332 | | let body = FunctionBuilder.build (if tupled then Some (List.head ds) else None) argTypes (if dis |
| | 124 | 333 | | Expression.Lambda(factoryType, body, "dlr-functionFactory", siteParams @ [ targetParam ]).Compil |
| | 88 | 334 | | Expr.Call(Expr.Value(factory, factoryType), factoryType.GetMethod("Invoke"), |
| | 428 | 335 | | [ for v, _ in sites -> Expr.Value(v, typeof<CallSite>) ] @ [ target.Expr ]) |
| | 220 | 336 | | elif discard then |
| | 20 | 337 | | let helper = typeof<FunctionMember>.GetMethod(shape + "Unit") |
| | 40 | 338 | | let helper = if argTypes.IsEmpty then helper else helper.MakeGenericMethod(Array.ofList argTypes) |
| | 20 | 339 | | Expr.Call(helper, [ invokeSite; target.Expr ]) |
| | | 340 | | else |
| | 200 | 341 | | let convertSite = site (convertBinder context CSharpBinderFlags.None resultType) [ dynamicArg (Expr.Valu |
| | 200 | 342 | | let helper = typeof<FunctionMember>.GetMethod(shape).MakeGenericMethod(Array.ofList (argTypes @ [ result |
| | 200 | 343 | | Expr.Call(helper, [ invokeSite; convertSite; target.Expr ]) |
| | 308 | 344 | | if shortcut then |
| | | 345 | | // A value that already is a function of this type is that function: no invoker. |
| | 48 | 346 | | Expr.Let(targetVar, original.Expr, |
| | 48 | 347 | | Expr.IfThenElse(Expr.TypeTest(Expr.Var targetVar, functionType), Expr.Coerce(Expr.Var targetVar, fu |
| | 260 | 348 | | else built |
| | | 349 | | |
| | | 350 | | /// The binder invoking member `name` with `all` (the target, then the arguments): with no |
| | | 351 | | /// arguments, a property is read (and applied) or a parameterless method invoked. |
| | | 352 | | let private memberInvoker (context: Type) (name: string) (target: Arg) (all: Arg list) (discard: bool) : CallSiteBin |
| | 422 | 353 | | let flags = resultFlags discard |
| | 422 | 354 | | if all.Length = 1 then |
| | 122 | 355 | | let csharp = Binder.InvokeMember(flags, name, null, context, [ argInfo target ]) :?> InvokeMemberBinder |
| | 122 | 356 | | let csharpInvoke = Binder.Invoke(flags, context, [ argInfo target ]) :?> InvokeBinder |
| | 122 | 357 | | FSharpReadOrInvokeBinder(context, name, csharp, csharpInvoke) :> CallSiteBinder |
| | 300 | 358 | | else smartInvokeMember context name [] discard all |
| | | 359 | | |
| | | 360 | | /// `x?Name` read as an F# function type: `unit -> R` reads a property or invokes a |
| | | 361 | | /// parameterless method; otherwise a typed InvokeMember site. |
| | | 362 | | let functionMember (context: Type) (name: string) (functionType: Type) (target: Arg) : Expr = |
| | 264 | 363 | | callsOnly "reading a member as a function" target |
| | 260 | 364 | | asFunction context functionType target false (memberInvoker context name target) |
| | | 365 | | |
| | | 366 | | /// Whether a member read as delegate type `t` is ours (`delegateMember`), and past fourteen |
| | | 367 | | /// parameters the F# function type its invoker takes: tupled, one step over the tuple (up to |
| | | 368 | | /// fourteen, the invoker takes the delegate's own signature). None when `t` is no concrete |
| | | 369 | | /// delegate type, has a byref parameter or result, or past fourteen returns an F# function |
| | | 370 | | /// (which the function type would read as more parameters) or does not convert: such a read |
| | | 371 | | /// stays C#'s. |
| | | 372 | | let delegateRead (t: Type) : Type option option = |
| | 14629 | 373 | | if not (typeof<Delegate>.IsAssignableFrom t) || t = typeof<Delegate> || t = typeof<MulticastDelegate> || t.Conta |
| | 170 | 374 | | let invoke = DelegateMembers.invokeOf t |
| | 1790 | 375 | | let ps = [ for p in invoke.GetParameters() -> p.ParameterType ] |
| | 1232 | 376 | | if invoke.ReturnType.IsByRef || ps |> List.exists (fun p -> p.IsByRef) then None |
| | | 377 | | else |
| | 340 | 378 | | let result = if invoke.ReturnType = voidType then typeof<unit> else invoke.ReturnType |
| | 324 | 379 | | if ps.Length <= 14 then Some None |
| | 24 | 380 | | elif FSharp.Reflection.FSharpType.IsFunction result then None |
| | | 381 | | else |
| | 8 | 382 | | let functionType = FSharp.Reflection.FSharpType.MakeFunctionType(FSharp.Reflection.FSharpType.MakeTupleT |
| | 16 | 383 | | FunctionConversions.tryConversion functionType t |> Option.map (fun _ -> Some functionType) |
| | | 384 | | |
| | | 385 | | /// `x?Name` read as a delegate type (#201): C#'s read of the member, converted, unless the |
| | | 386 | | /// member is a method (`FSharpGetMemberOrMethodBinder` yields `MethodGroup`); then the delegate |
| | | 387 | | /// over a `MemberInvokers` instance holding the sites and the target (`FunctionConversions.over`), |
| | | 388 | | /// or past fourteen parameters `functionMember` at `functionType`, converted. |
| | | 389 | | let delegateMember (context: Type) (name: string) (delegateType: Type) (functionType: Type option) (target: Arg) : E |
| | 170 | 390 | | callsOnly "reading a member" target |
| | 170 | 391 | | let targetVar = Var("target", target.Expr.Type) |
| | 170 | 392 | | let target' = { target with Expr = Expr.Var targetVar } |
| | 170 | 393 | | let valueVar = Var("value", typeof<obj>) |
| | 170 | 394 | | let csharp = Binder.GetMember(CSharpBinderFlags.None, name, context, [ argInfo target ]) :?> GetMemberBinder |
| | 170 | 395 | | let read = siteCall (FSharpGetMemberOrMethodBinder(context, name, csharp)) [ target' ] typeof<obj> |
| | | 396 | | let invoke (factory: Func<obj, Delegate>) (instance: Expr) = |
| | 170 | 397 | | Expr.Coerce(Expr.Call(Expr.Value(factory, typeof<Func<obj, Delegate>>), typeof<Func<obj, Delegate>>.GetMetho |
| | 170 | 398 | | [ Expr.Coerce(instance, typeof<obj>) ]), delegateType) |
| | | 399 | | let invoker = |
| | 170 | 400 | | match functionType with |
| | | 401 | | | None -> |
| | | 402 | | // The delegate straight over a `MemberInvokers` instance holding the sites: one hop |
| | | 403 | | // from the delegate to the site call. |
| | 78 | 404 | | let invokeMethod = DelegateMembers.invokeOf delegateType |
| | 662 | 405 | | let ps = [ for p in invokeMethod.GetParameters() -> p.ParameterType ] |
| | 78 | 406 | | let discard = invokeMethod.ReturnType = voidType |
| | 448 | 407 | | let all = target' :: [ for t in ps -> typedArg (Expr.Value(null, t)) ] |
| | 234 | 408 | | let invokeSite = site (memberInvoker context name target' all discard) all (if discard then voidType els |
| | | 409 | | let sites, typeArgs = |
| | 88 | 410 | | if discard then [ invokeSite ], ps |
| | 68 | 411 | | else [ invokeSite; site (convertBinder context CSharpBinderFlags.None invokeMethod.ReturnType) [ dyn |
| | 390 | 412 | | let def = typeof<MemberInvokers.Action0>.DeclaringType.GetNestedType((if discard then "Action" else "Fun |
| | 156 | 413 | | let invokerType = if typeArgs.IsEmpty then def else def.MakeGenericType(Array.ofList typeArgs) |
| | 78 | 414 | | invoke (FunctionConversions.over invokerType delegateType) (Expr.NewObject(invokerType.GetConstructors() |
| | | 415 | | | Some functionType -> |
| | | 416 | | // Past fourteen the site is wide: the member read as a function, converted |
| | | 417 | | // (`delegateRead` checked that it converts). |
| | 8 | 418 | | invoke (FunctionConversions.tryConversion functionType delegateType).Value (functionMember context name |
| | 170 | 419 | | let converted = siteCall (convertBinder context CSharpBinderFlags.None delegateType) [ dynamicArg (Expr.Var valu |
| | 170 | 420 | | Expr.Let(targetVar, target.Expr, |
| | 170 | 421 | | Expr.Let(valueVar, read, |
| | 170 | 422 | | Expr.IfThenElse(Expr.Call(typeof<MethodGroup>.GetMethod("Is"), [ Expr.Var valueVar ]), invoker, converted))) |
| | | 423 | | |
| | | 424 | | /// `Dlr.call x` read as an F# function type: the target itself as that function — a typed |
| | | 425 | | /// Invoke site (F# function values through FSharpInvokeBinder), or the target as it is when |
| | | 426 | | /// it already is a function of the type. |
| | | 427 | | let functionTarget (context: Type) (functionType: Type) (target: Arg) : Expr = |
| | 48 | 428 | | callsOnly "Dlr.call" target |
| | 48 | 429 | | asFunction context functionType target true (fun all discard -> |
| | 48 | 430 | | let flags = resultFlags discard |
| | 360 | 431 | | FSharpInvokeBinder(Binder.Invoke(flags, context, [ for a in all -> argInfo a ]) :?> InvokeBinder) :> CallSit |
| | | 432 | | |
| | | 433 | | let getIndex (context: Type) (target: Arg) (indexes: Arg list) = |
| | 86 | 434 | | callsOnly "indexing" target |
| | 82 | 435 | | let all = target :: indexes |
| | 450 | 436 | | siteCall (Binder.GetIndex(CSharpBinderFlags.None, context, [ for a in all -> argInfo a ])) all typeof<obj> |
| | | 437 | | |
| | | 438 | | let setIndex (context: Type) (target: Arg) (indexes: Arg list) (value: Arg) = |
| | 64 | 439 | | callsOnly "indexing" target |
| | 64 | 440 | | let all = target :: indexes @ [ value ] |
| | | 441 | | // C#'s SetIndex binder, our value conversion as its error suggestion; a meta-object target |
| | | 442 | | // with an F# function value gets the delegate. |
| | 408 | 443 | | let csharp = Binder.SetIndex(CSharpBinderFlags.None, context, [ for a in all -> argInfo a ]) :?> SetIndexBinder |
| | 64 | 444 | | siteCall (MetaObjectAwareBinder(FSharpSetIndexBinder(context, csharp))) all typeof<obj> |
| | | 445 | | |
| | | 446 | | /// C#'s `d.Name += v` / `-=`: an IsEvent site decides at run time between the event |
| | | 447 | | /// accessor (`add_Name`/`remove_Name`, invoked as a special name) and read-modify-write |
| | | 448 | | /// (GetMember, AddAssign/SubtractAssign, SetMember flagged as a compound assignment). |
| | | 449 | | /// `target` and `value` must be variables, since both branches mention them. An F# function |
| | | 450 | | /// value converts on both: to the event's delegate type at the accessor (the invoke-member |
| | | 451 | | /// seam), and to the delegate of its signature for a meta-object's member (a COM event's |
| | | 452 | | /// bound event takes delegates only), through `MetaObjectAwareBinder`. |
| | | 453 | | let compoundAssign (context: Type) (name: string) (subtract: bool) (target: Arg) (value: Arg) : Expr = |
| | 102 | 454 | | callsOnly "addAssign/subtractAssign" target |
| | 98 | 455 | | let isEvent = |
| | 98 | 456 | | siteCall (Binder.IsEvent(CSharpBinderFlags.None, name, context)) [ target ] typeof<bool> |
| | | 457 | | let accessor = |
| | 294 | 458 | | let accessorName = (if subtract then "remove_" else "add_") + name |
| | 98 | 459 | | let infos = [ argInfo target; argInfo value ] |
| | 98 | 460 | | let csharp = |
| | 98 | 461 | | Binder.InvokeMember(CSharpBinderFlags.InvokeSpecialName ||| CSharpBinderFlags.ResultDiscarded, accessorN |
| | 98 | 462 | | let csharpInvoke = Binder.Invoke(CSharpBinderFlags.ResultDiscarded, context, infos) :?> InvokeBinder |
| | 98 | 463 | | siteCall (FSharpInvokeMemberBinder(context, accessorName, csharp :?> InvokeMemberBinder, csharpInvoke)) [ ta |
| | | 464 | | let readModifyWrite = |
| | 98 | 465 | | let current = siteCall (Binder.GetMember(CSharpBinderFlags.None, name, context, [ argInfo target ])) [ targe |
| | 196 | 466 | | let op = if subtract then ExpressionType.SubtractAssign else ExpressionType.AddAssign |
| | | 467 | | let combined = |
| | 98 | 468 | | let csharp = Binder.BinaryOperation(CSharpBinderFlags.None, op, context, [ argInfo (dynamicArg current); |
| | 98 | 469 | | siteCall (MetaObjectAwareBinder(csharp :?> DynamicMetaObjectBinder)) [ dynamicArg current; value ] typeo |
| | 49 | 470 | | let set = |
| | 49 | 471 | | Binder.SetMember(CSharpBinderFlags.ValueFromCompoundAssignment, name, context, [ argInfo target; argInfo |
| | 98 | 472 | | siteCall set [ target; dynamicArg combined ] typeof<obj> |
| | 98 | 473 | | Expr.IfThenElse(isEvent, Expr.Sequential(accessor, Expr.Value(())), Expr.Sequential(readModifyWrite, Expr.Value( |
| | | 474 | | |
| | | 475 | | let binaryOperation (context: Type) (op: ExpressionType) (left: Arg) (right: Arg) = |
| | 238 | 476 | | let csharp = Binder.BinaryOperation(CSharpBinderFlags.None, op, context, [ argInfo left; argInfo right ]) :?> Bi |
| | 476 | 477 | | let binder = if FSharpBinaryOperationBinder.IsComparison op then FSharpBinaryOperationBinder csharp :> CallSiteB |
| | 238 | 478 | | siteCall binder [ left; right ] typeof<obj> |
| | | 479 | | |
| | | 480 | | let unaryOperation (context: Type) (op: ExpressionType) (operand: Arg) = |
| | 10 | 481 | | let binder = Binder.UnaryOperation(CSharpBinderFlags.None, op, context, [ argInfo operand ]) |
| | 20 | 482 | | siteCall binder [ operand ] typeof<obj> |
| | | 483 | | |
| | | 484 | | /// Implicit conversion of an `obj`-typed expression to `resultType`. `obj` is a no-op and |
| | | 485 | | /// `unit` discards the value. |
| | | 486 | | let convert (context: Type) (resultType: Type) (e: Expr) : Expr = |
| | 9395 | 487 | | if resultType = typeof<obj> then e |
| | 9025 | 488 | | elif resultType = typeof<unit> then Expr.Sequential(e, Expr.Value(())) |
| | 8573 | 489 | | else siteCall (convertBinder context CSharpBinderFlags.None resultType) [ dynamicArg e ] resultType |
| | | 490 | | |
| | | 491 | | /// Explicit conversion (a C# cast) of an `obj`-typed expression to `resultType`. |
| | | 492 | | let convertExplicit (context: Type) (resultType: Type) (e: Expr) : Expr = |
| | 26 | 493 | | siteCall (convertBinder context CSharpBinderFlags.ConvertExplicit resultType) [ dynamicArg e ] resultType |