< Summary - FSharp.Interop.Dlr

Information
Class: FSharp.Interop.Dlr.FSharpReadOrInvokeBinder
Assembly: FSharp.Interop.Dlr
File(s): /home/runner/work/FSharp.Interop.Dlr/FSharp.Interop.Dlr/FSharp.Interop.Dlr/Seam.fs
Line coverage
87%
Covered lines: 14
Uncovered lines: 2
Coverable lines: 16
Total lines: 702
Line coverage: 87.5%
Branch coverage
83%
Covered branches: 10
Total branches: 12
Branch coverage: 83.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
FallbackInvokeMember(...)83.33%131284.61%
FallbackInvoke(...)100%11100%

File(s)

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

#LineLine coverage
 1namespace FSharp.Interop.Dlr
 2
 3open System
 4open System.Dynamic
 5open System.Linq.Expressions
 6open System.Reflection
 7open System.Runtime.CompilerServices
 8open Microsoft.CSharp.RuntimeBinder
 9open FSharp.Quotations
 10
 11/// The reflection fallback: the rules the binders offer C# as its error suggestion (or, where C#
 12/// would bind wrongly or crash, ahead of it — `Seam`). Calls (`tryCall`, `tryStaticCall`),
 13/// constructors (`tryConstruct`), a delegate target's `Invoke` (`tryInvokeDelegate`) and
 14/// assignments (`trySet`), each choosing among candidates by one rule (`tryInvoke`); every argument
 15/// meets its slot through `convertValue`. F# optional parameters (`?arg`) compile to
 16/// `FSharpOption<'T>` parameters carrying `[<OptionalArgument>]`, which C#'s binder neither omits
 17/// nor fills: here omitted ones are `None` and a bare value is wrapped in `Some`.
 18module internal Fallback =
 19    let private isOptional (p: ParameterInfo) =
 20        p.GetCustomAttributes(typeof<OptionalArgumentAttribute>, false).Length > 0
 21        && p.ParameterType.IsGenericType
 22        && p.ParameterType.GetGenericTypeDefinition() = typedefof<option<_>>
 23
 24    let private isNullValue = FunctionShapes.isNullValue
 25
 26    /// A concrete delegate type: `Delegate` and `MulticastDelegate` themselves have no `Invoke`.
 27    let private isDelegate (t: Type) = typeof<Delegate>.IsAssignableFrom t && not (isNull (DelegateMembers.invokeOf t))
 28    let private isAbstractDelegate (t: Type) = t = typeof<Delegate> || t = typeof<MulticastDelegate>
 29
 30    /// An F# function value for a delegate-typed parameter: a delegate over the function
 31    /// (`FunctionAdapters`), as F# itself converts a lambda argument to a `Func`/`Action`
 32    /// parameter at a static call.
 33    let private functionToDelegate (delegateType: Type) (a: DynamicMetaObject) : Expression option =
 34        match FunctionConversions.tryConversion a.LimitType delegateType with
 35        | None -> None
 36        | Some _ ->
 37            let make = FunctionConversions.makeMethod
 38            Some(Expression.Convert(Expression.Call(make, Expression.Constant delegateType, Expression.Convert(a.Express
 39
 40    /// A delegate for an F# function-typed parameter: `DelegateFunction`'s typed wrapper, offered
 41    /// exactly when `DelegateConversions.tryTyped` has one for this pair (`Signatures`).
 42    let private delegateToFunction (funcType: Type) (a: DynamicMetaObject) : Expression option =
 43        match DelegateConversions.tryTyped funcType a.LimitType with
 44        | Some _ ->
 45            let make = DelegateFunction.makeMethod
 46            Some(Expression.Convert(Expression.Call(make, Expression.Constant funcType, Expression.Convert(a.Expression,
 47        | None -> None
 48
 49    /// An F# function for a slot typed `Delegate` itself (Control.Invoke): the Func/Action F#
 50    /// would build for the function. Left to C#, FSharpFunc's own op_Implicit makes a
 51    /// Converter<Unit, R> of a `unit -> R` — a one-parameter delegate, wrong for a
 52    /// `DynamicInvoke()` — so this goes first wherever such a slot is the target.
 53    let private toAbstractDelegate (a: DynamicMetaObject) : Expression option =
 54        match FunctionShapes.parameters a.LimitType with
 55        | Some(ds, _, _) when not (DelegateMembers.funcFits ds.Length) -> None     // no Func/Action of that many parame
 56        | Some(ds, _, result) ->
 57            let delegateType = if result = typeof<unit> then Expression.GetActionType(Array.ofList ds) else Expression.G
 58            functionToDelegate delegateType a
 59        | None -> None
 60
 61    /// The conversions an argument can need for a typed slot that neither assignment nor widening
 62    /// gives: an F# function for a delegate slot, a delegate for a function slot (both by the one
 63    /// rule, `Signatures`), an F# function for a slot typed `Delegate` itself.
 64    let conversion (slot: Type) (a: DynamicMetaObject) : Expression option =
 65        let at = a.LimitType
 66        if isDelegate slot && (FunctionShapes.domains at).IsSome then functionToDelegate slot a
 67        elif isDelegate at && (FunctionShapes.domains slot).IsSome then delegateToFunction slot a
 68        elif isAbstractDelegate slot && (FunctionShapes.domains at).IsSome then toAbstractDelegate a
 69        else None
 70
 71    /// The one place a dynamic argument meets a typed slot: the argument as a `slot`, or None if it
 72    /// does not fit — assignable or C#-widened (unboxed at its runtime type first: converting `obj`
 73    /// straight to `int64` would unbox a boxed `int` as `int64` and throw), a null for a reference
 74    /// or nullable slot, or one of the `conversion`s.
 75    let convertValue (slot: Type) (a: DynamicMetaObject) : Expression option =
 76        if isNullValue a then
 77            if slot.IsValueType && isNull (Nullable.GetUnderlyingType slot) then None
 78            else Some(Expression.Constant(null, slot) :> Expression)
 79        elif Conversions.fits slot a.LimitType then Some(Expression.Convert(Expression.Convert(a.Expression, a.LimitType
 80        else conversion slot a
 81
 82    /// An argument for a method parameter: `convertValue`, or a bare value for an F# optional
 83    /// parameter (`?x`, an `FSharpOption`) as `Some`.
 84    let private fit (p: ParameterInfo) (a: DynamicMetaObject) : Expression option =
 85        let pt = p.ParameterType
 86        match convertValue pt a with
 87        | Some e -> Some e
 88        | None when isOptional p && Conversions.fits (pt.GetGenericArguments().[0]) a.LimitType ->
 89            let inner = pt.GetGenericArguments().[0]
 90            Some(Expression.Call(pt.GetMethod("Some"), Expression.Convert(Expression.Convert(a.Expression, a.LimitType),
 91        | None -> None
 92
 93    /// A call has an argument that is an F# function in a slot typed `Delegate` in some candidate
 94    /// method: C# would bind it through op_Implicit to a `Converter`, wrongly, so our rule goes first.
 95    let hasAbstractDelegateSlot (context: Type) (t: Type) (name: string) (args: DynamicMetaObject[]) =
 96        t.GetMethods(Accessibility.all)
 97        |> Array.exists (fun m ->
 98            m.Name = name && Accessibility.method' context t m
 99            && (let ps = m.GetParameters()
 100                ps.Length >= args.Length
 101                && Array.exists2 (fun (p: ParameterInfo) (a: DynamicMetaObject) -> isAbstractDelegate p.ParameterType &&
 102
 103    // `FunctionShapes.applyCall` (defined before the conversions, which use it for the largest
 104    // delegates) takes them through this hook for an F# function's domains.
 105    do FunctionShapes.convertArgument <- conversion
 106
 107    /// Not C#'s overload resolution, but deterministic: among the candidates the arguments fit,
 108    /// the one with the most exactly-typed argument slots wins, then the one with the fewest
 109    /// omitted parameters; a tie is ambiguous and left to C#'s error. `instance` is the receiver
 110    /// for instance methods, None for static methods and constructors; `call` builds the
 111    /// invocation of the chosen candidate.
 112    /// A delegate argument for a function slot, or a function for a delegate slot, of exactly the
 113    /// slot's signature (no variance): counted as an exact match when ranking candidates.
 114    let private exactConversion (slot: Type) (argType: Type) =
 115        let signature (funcType: Type) (delegateType: Type) =
 116            match FunctionShapes.parameters funcType, DelegateMembers.invokeOf delegateType with
 117            | Some(ds, _, result), invoke when not (isNull invoke) ->
 118                Signatures.exactly ds result [ for p in invoke.GetParameters() -> p.ParameterType ] invoke.ReturnType
 119            | _ -> false
 120        if isDelegate argType && (FunctionShapes.domains slot).IsSome then signature slot argType
 121        elif isDelegate slot && (FunctionShapes.domains argType).IsSome then signature argType slot
 122        else false
 123
 124    let private tryInvoke (candidates: MethodBase[]) (call: MethodBase -> Expression list -> Expression) (targetRestrict
 125        let fitting =
 126            candidates
 127            |> Array.choose (fun m ->
 128                let ps = m.GetParameters()
 129                let required = ps |> Array.filter (fun p -> not (isOptional p)) |> Array.length
 130                if args.Length < required || args.Length > ps.Length then None
 131                else
 132                    let supplied = [ for i in 0 .. args.Length - 1 -> fit ps.[i] args.[i] ]
 133                    if supplied |> List.exists Option.isNone then None
 134                    else
 135                        let exact = Seq.zip ps args |> Seq.filter (fun (p, a) -> p.ParameterType = a.LimitType || exactC
 136                        Some(m, ps, List.choose id supplied, exact))
 137            |> Array.sortByDescending (fun (_, ps, _, exact) -> exact, -ps.Length)
 138        match List.ofArray fitting with
 139        | (_, ps1, _, e1) :: (_, ps2, _, e2) :: _ when e1 = e2 && ps1.Length = ps2.Length -> None   // ambiguous
 140        | (m, ps, supplied, _) :: _ ->
 141            let omitted = [ for i in args.Length .. ps.Length - 1 -> Expression.Constant(null, ps.[i].ParameterType) :> 
 142            let invocation = call m (supplied @ omitted)
 143            let value =
 144                if invocation.Type = typeof<Void> then Expression.Block(invocation, Expression.Constant(null, typeof<obj
 145                else Expression.Convert(invocation, typeof<obj>) :> Expression
 146            let restrictions = Array.fold (fun (r: BindingRestrictions) a -> r.Merge(FunctionShapes.restrictArg a)) targ
 147            Some(DynamicMetaObject(value, restrictions))
 148        | [] -> None
 149
 150    /// The target as a `t` to call or assign through: a struct in its box (`Unbox`), as C# does, so
 151    /// a mutating method or an assignment changes the boxed value — `Convert` would unbox a copy.
 152    let private receiver (t: Type) (target: DynamicMetaObject) : Expression =
 153        if t.IsValueType then Expression.Unbox(target.Expression, t) :> Expression else Expression.Convert(target.Expres
 154
 155    /// An instance method of `t` named `name` the arguments fit.
 156    let tryCall (context: Type) (t: Type) (name: string) (target: DynamicMetaObject) (args: DynamicMetaObject[]) : Dynam
 157        let candidates =
 158            t.GetMethods(Accessibility.all)
 159            |> Array.filter (fun m -> m.Name = name && not m.IsGenericMethodDefinition && Accessibility.method' context 
 160            |> Array.map (fun m -> m :> MethodBase)
 161        let self = receiver t target
 162        tryInvoke candidates (fun m ps -> Expression.Call(self, m :?> MethodInfo, ps) :> Expression) (BindingRestriction
 163
 164    /// The index types C# takes for an array: int, uint, long, ulong, and those that widen to them.
 165    let private arrayIndexTypes =
 166        [| typeof<int>; typeof<uint32>; typeof<int64>; typeof<uint64>; typeof<sbyte>; typeof<byte>; typeof<int16>; typeo
 167
 168    /// The settable slots of `t` an assignment names: the property or field `name`, or with
 169    /// `indexes` the indexer (the `DefaultMember`) of that many indexes, or an array's element.
 170    let private slotTypes (t: Type) (name: string option) (indexCount: int) : Type list =
 171        let indexerName = match t.GetCustomAttributes(typeof<DefaultMemberAttribute>, true) with [| :? DefaultMemberAttr
 172        [ for p in t.GetProperties(Accessibility.all) do
 173            if p.Name = defaultArg name indexerName && p.GetIndexParameters().Length = indexCount && p.CanWrite then yie
 174          match name with
 175          | Some n when indexCount = 0 ->
 176              for f in t.GetFields(Accessibility.all) do if f.Name = n && not f.IsInitOnly && not f.IsLiteral then yield
 177          | None when t.IsArray && t.GetArrayRank() = indexCount -> yield t.GetElementType()
 178          | _ -> () ]
 179
 180    /// An F# function assigned to a slot typed `Delegate` itself: C# would bind it, wrongly, through
 181    /// FSharpFunc's op_Implicit (see `toAbstractDelegate`), so `trySet` goes first there.
 182    let assignsAbstractDelegate (t: Type) (name: string option) (indexCount: int) (value: DynamicMetaObject) =
 183        (FunctionShapes.domains value.LimitType).IsSome && slotTypes t name indexCount |> List.exists isAbstractDelegate
 184
 185    /// An assignment C# could not bind because the value needs one of the conversions above (an F#
 186    /// function for a delegate-typed slot, a delegate for a function-typed one; #153): a property's
 187    /// setter, or with `indexes` an indexer's, called with the indexes then the value through
 188    /// `tryInvoke`; a field or an array element assigned the converted value. The result is the
 189    /// value as assigned, boxed, as C#'s assignment's is. None when nothing of that name fits.
 190    let trySet (context: Type) (t: Type) (name: string option) (target: DynamicMetaObject) (indexes: DynamicMetaObject[]
 191        let self = receiver t target
 192        let targetRestriction = BindingRestrictions.GetTypeRestriction(target.Expression, t)
 193        let convertSlot (slotType: Type) = conversion slotType value
 194        let restrictions () = Array.fold (fun (r: BindingRestrictions) a -> r.Merge(FunctionShapes.restrictArg a)) targe
 195        let assigned (slot: Expression) (converted: Expression) =
 196            let v = Expression.Variable(slot.Type, "value")
 197            Expression.Block([ v ], Expression.Assign(v, converted), Expression.Assign(slot, v), Expression.Convert(v, t
 198        let indexerName = match t.GetCustomAttributes(typeof<DefaultMemberAttribute>, true) with [| :? DefaultMemberAttr
 199        let setters =
 200            t.GetProperties(Accessibility.all)
 201            |> Array.filter (fun p ->
 202                p.Name = defaultArg name indexerName && p.GetIndexParameters().Length = indexes.Length
 203                && (let m = p.GetSetMethod(true) in not (isNull m) && Accessibility.method' context t m))
 204            |> Array.map (fun p -> p.GetSetMethod(true) :> MethodBase)
 205        if setters.Length > 0 then
 206            let call (m: MethodBase) (ps: Expression list) =
 207                let v = Expression.Variable((List.last ps).Type, "value")
 208                Expression.Block([ v ], Expression.Assign(v, List.last ps),
 209                                 Expression.Call(self, m :?> MethodInfo, List.take (ps.Length - 1) ps @ [ v :> Expressio
 210                                 v) :> Expression
 211            tryInvoke setters call targetRestriction (Array.append indexes [| value |])
 212        else
 213            match name with
 214            | Some name when indexes.Length = 0 ->
 215                t.GetFields(Accessibility.all)
 216                |> Array.tryFind (fun f -> f.Name = name && not f.IsInitOnly && not f.IsLiteral && Accessibility.field c
 217                |> Option.bind (fun f ->
 218                    convertSlot f.FieldType
 219                    |> Option.map (fun converted -> DynamicMetaObject(assigned (Expression.Field(self, f)) converted, re
 220            | None when t.IsArray && t.GetArrayRank() = indexes.Length && indexes |> Array.forall (fun i -> arrayIndexTy
 221                convertSlot (t.GetElementType())
 222                |> Option.map (fun converted ->
 223                    // Unboxed at its runtime type, then checked to int, as C#'s index would overflow.
 224                    let element = Expression.ArrayAccess(self, [ for i in indexes -> Expression.ConvertChecked(Expressio
 225                    DynamicMetaObject(assigned element converted, restrictions ()))
 226            | _ -> None
 227
 228    /// A static method of `t` named `name` the arguments fit (`Dlr.Static<T>.Overloads`).
 229    let tryStaticCall (context: Type) (t: Type) (name: string) (target: DynamicMetaObject) (args: DynamicMetaObject[]) :
 230        let candidates =
 231            t.GetMethods(BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Static ||| BindingFlags.Flatten
 232            |> Array.filter (fun m -> m.Name = name && not m.IsGenericMethodDefinition && Accessibility.method' context 
 233            |> Array.map (fun m -> m :> MethodBase)
 234        tryInvoke candidates (fun m ps -> Expression.Call(m :?> MethodInfo, ps) :> Expression) (BindingRestrictions.GetI
 235
 236    /// A constructor of `t` the arguments fit (`Dlr.new'<T>`).
 237    let tryConstruct (context: Type) (t: Type) (target: DynamicMetaObject) (args: DynamicMetaObject[]) : DynamicMetaObje
 238        let candidates =
 239            t.GetConstructors(BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Instance)
 240            |> Array.filter (fun c -> Accessibility.method' context null c)
 241            |> Array.map (fun c -> c :> MethodBase)
 242        tryInvoke candidates (fun c ps -> Expression.New(c :?> ConstructorInfo, ps) :> Expression) (BindingRestrictions.
 243
 244    /// A delegate target's `Invoke` the arguments fit (`Dlr.call` on a delegate, a delegate-typed
 245    /// member, an Expando's delegate member): the conversions above apply to its parameters.
 246    let tryInvokeDelegate (target: DynamicMetaObject) (args: DynamicMetaObject[]) : DynamicMetaObject option =
 247        let dt = target.LimitType
 248        if not (typeof<Delegate>.IsAssignableFrom dt) || isNull (DelegateMembers.invokeOf dt) then None
 249        else
 250            let invoke = DelegateMembers.invokeOf dt
 251            let self = Expression.Convert(target.Expression, dt)
 252            tryInvoke [| invoke |] (fun m ps -> Expression.Call(self, m :?> MethodInfo, ps) :> Expression) (BindingRestr
 253
 254/// Where our rule stands relative to C#'s (the seam, `docs/binders.md`): C# binds first and ours
 255/// is its error suggestion, used only where C# fails — except where C# would bind *wrongly* or
 256/// crash, where ours goes first. Each binder states which case it is in through this one call.
 257module internal Seam =
 258    /// Our rule first when `csharpWouldBeWrong` and there is one; else C#'s binding (`csharp`, given
 259    /// the error suggestion), with ours, if any, as that suggestion.
 260    let oursFirstWhen (csharpWouldBeWrong: bool) (ours: DynamicMetaObject option) (errorSuggestion: DynamicMetaObject)
 261                      (csharp: DynamicMetaObject -> DynamicMetaObject) : DynamicMetaObject =
 262        match ours with
 263        | Some rule when csharpWouldBeWrong -> rule
 264        | _ -> csharp (defaultArg ours errorSuggestion)
 265
 266/// Equality and ordering with F# semantics where C# has none: records, unions, tuples, lists,
 267/// options, sets and any other type without the CLR operator get `=`/`compare` (structural,
 268/// through `LanguagePrimitives`) instead of C#'s reference equality or "operator cannot be
 269/// applied". Types C# handles itself — primitives, enums, strings, delegates, and any type that
 270/// declares the operator — keep C#'s binding, as do all non-comparison operators.
 271/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 272[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 273type FSharpBinaryOperationBinder(csharp: BinaryOperationBinder) =
 274    inherit BinaryOperationBinder(csharp.Operation)
 275
 276    static let operatorName =
 277        dict [ ExpressionType.Equal, "op_Equality"; ExpressionType.NotEqual, "op_Inequality"
 278               ExpressionType.LessThan, "op_LessThan"; ExpressionType.LessThanOrEqual, "op_LessThanOrEqual"
 279               ExpressionType.GreaterThan, "op_GreaterThan"; ExpressionType.GreaterThanOrEqual, "op_GreaterThanOrEqual" 
 280
 281    static let equality = Quotation.methodOf <@ LanguagePrimitives.GenericEquality (box 1) (box 2) @>
 282
 283    static let comparison =
 284        (Quotation.methodOf <@ LanguagePrimitives.GenericComparison (box 1 :?> IComparable) (box 2 :?> IComparable) @>)
 285            .GetGenericMethodDefinition().MakeGenericMethod typeof<obj>
 286
 287    /// A type C#'s own operators cover, or that binds for itself (a dynamic object, whose own
 288    /// rule reaches us as the error suggestion through C#). Strings and bools have C# equality
 289    /// but no C# ordering, so `<` on them is F#'s (ordinal; `false < true`).
 290    static let native (op: ExpressionType) (t: Type) =
 291        let t = match Nullable.GetUnderlyingType t with null -> t | u -> u
 292        let equality = op = ExpressionType.Equal || op = ExpressionType.NotEqual
 293        (t.IsPrimitive && (equality || t <> typeof<bool>)) || t.IsEnum || t = typeof<decimal>
 294        || typeof<Delegate>.IsAssignableFrom t || typeof<IDynamicMetaObjectProvider>.IsAssignableFrom t
 295        || (t = typeof<string> && equality)
 296
 297    static let declares (name: string) (t: Type) =
 298        t.GetMethods(BindingFlags.Public ||| BindingFlags.Static ||| BindingFlags.FlattenHierarchy)
 299        |> Array.exists (fun m -> m.Name = name && m.GetParameters().Length = 2)
 300
 301    /// Whether the operation is one this binder may bind structurally.
 302    static member IsComparison(op: ExpressionType) = operatorName.ContainsKey op
 303
 304    override this.FallbackBinaryOperation(target, arg, errorSuggestion) =
 305        let structural =
 306            match operatorName.TryGetValue this.Operation with
 307            | true, name ->
 308                let operands = [ target; arg ] |> List.filter (fun a -> not (FunctionShapes.isNullValue a)) |> List.map 
 309                not operands.IsEmpty && operands |> List.forall (fun t -> not (native this.Operation t) && not (declares
 310            | _ -> false
 311        if not structural then csharp.FallbackBinaryOperation(target, arg, errorSuggestion)
 312        else
 313            let boxed (a: DynamicMetaObject) = Expression.Convert(a.Expression, typeof<obj>) :> Expression
 314            let l, r = boxed target, boxed arg
 315            let value : Expression =
 316                match this.Operation with
 317                | ExpressionType.Equal -> Expression.Call(equality, l, r)
 318                | ExpressionType.NotEqual -> Expression.Not(Expression.Call(equality, l, r))
 319                | op ->
 320                    let c = Expression.Call(comparison, l, r)
 321                    let zero = Expression.Constant 0
 322                    match op with
 323                    | ExpressionType.LessThan -> Expression.LessThan(c, zero)
 324                    | ExpressionType.LessThanOrEqual -> Expression.LessThanOrEqual(c, zero)
 325                    | ExpressionType.GreaterThan -> Expression.GreaterThan(c, zero)
 326                    | _ -> Expression.GreaterThanOrEqual(c, zero)
 327            DynamicMetaObject(Expression.Convert(value, typeof<obj>), (FunctionShapes.restrictArg target).Merge(Function
 328
 329/// A seam rule for meta-object targets: an F# function argument (or value) handed to an
 330/// `IDynamicMetaObjectProvider` — a script object, a `DynamicObject` someone wrote — becomes the
 331/// delegate of its own signature before the meta-object sees it. Every meta-object understands
 332/// delegates and none an `FSharpFunc`; for a CLR target the parameter type drives the conversion
 333/// instead, so nothing C# binds changes. Restricted on the function type, so one rule serves every
 334/// lambda of that signature.
 335module internal MetaObjectArguments =
 336    let private isMetaObject (target: DynamicMetaObject) =
 337        target.HasValue && (target.Value :? IDynamicMetaObjectProvider)
 338
 339    /// The delegate type an F# function of `funcType` is naturally: `Func<…>`, `Action<…>` for a
 340    /// unit result; a tupled function by its elements.
 341    let private delegateTypeOf (funcType: Type) =
 342        match FunctionShapes.parameters funcType with
 343        | Some(ds, _, result) ->
 344            let result = if result = typeof<unit> then typeof<Void> else result
 345            Some(Expression.GetDelegateType(Array.ofList (ds @ [ result ])))
 346        | None -> None
 347
 348    /// `a` as the delegate of its signature, if it is an F# function value; else `a` restricted on
 349    /// its type (or on null), so the rule is per argument-type combination and a later function in
 350    /// this slot binds anew rather than reaching the meta-object raw.
 351    let private asDelegate (a: DynamicMetaObject) =
 352        let asIs () = DynamicMetaObject(a.Expression, FunctionShapes.restrictArg a, (if a.HasValue then a.Value else nul
 353        if not a.HasValue || isNull a.Value then asIs ()
 354        else
 355        match FunctionShapes.funcBase a.LimitType with
 356        | None -> asIs ()
 357        | Some funcType ->
 358            match delegateTypeOf funcType |> Option.bind (fun dt -> FunctionConversions.tryConversion funcType dt |> Opt
 359            | None -> asIs ()
 360            | Some(delegateType, factory) ->
 361                let expr = Expression.Convert(Expression.Invoke(Expression.Constant factory, Expression.Convert(a.Expres
 362                let restriction = BindingRestrictions.GetExpressionRestriction(Expression.TypeIs(a.Expression, funcType)
 363                DynamicMetaObject(expr, restriction, factory.Invoke a.Value)
 364
 365    /// Whether the rule applies: a meta-object target with an F# function among the arguments.
 366    let applies (target: DynamicMetaObject) (args: DynamicMetaObject[]) =
 367        isMetaObject target && args |> Array.exists (fun a -> a.HasValue && not (isNull a.Value) && (FunctionShapes.func
 368
 369    /// The rule: a nested site on `inner` whose arguments are the delegates — a meta-object (a
 370    /// `DynamicObject` above all) wants each argument to be the site's own parameter, which the
 371    /// nested site's are — restricted on the target's type and each function's type.
 372    let rule (inner: DynamicMetaObjectBinder) (target: DynamicMetaObject) (args: DynamicMetaObject[]) =
 373        let converted = Array.map asDelegate args
 374        let restrictions =
 375            converted
 376            |> Array.fold (fun (r: BindingRestrictions) a -> r.Merge a.Restrictions)
 377                (BindingRestrictions.GetTypeRestriction(target.Expression, target.LimitType))
 378        let call = Expression.Dynamic(inner, typeof<obj>, Array.append [| target.Expression |] [| for a in converted -> 
 379        DynamicMetaObject(call, restrictions)
 380
 381/// The standard binders seal `Bind`, so the rule sits one level out: this binder answers a
 382/// meta-object target with an F# function argument by the nested-site rule, and hands every
 383/// other bind to the real binder, which the meta-object then sees as usual.
 384/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 385[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 386type MetaObjectAwareBinder(inner: DynamicMetaObjectBinder) =
 387    inherit DynamicMetaObjectBinder()
 388    member _.Inner = inner
 389    override _.Bind(target: DynamicMetaObject, args: DynamicMetaObject[]) =
 390        if MetaObjectArguments.applies target args then MetaObjectArguments.rule inner target args
 391        else inner.Bind(target, args)
 392
 393/// C#'s Invoke binder, aware of F# function targets (`Dlr.call` on a function value, and the
 394/// value step of a member invocation).
 395/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 396[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 397type FSharpInvokeBinder(csharp: InvokeBinder) =
 398    inherit InvokeBinder(csharp.CallInfo)
 399
 400    override this.FallbackInvoke(target, args, errorSuggestion) =
 401        // A dynamic object's member value arrives without a value at bind time: defer, so the
 402        // nested site binds through this binder once the value is known.
 403        if not target.HasValue || args |> Array.exists (fun a -> not a.HasValue) then this.Defer(target, args)
 404        else
 405            match FunctionShapes.tryApply target args with
 406            | Some rule -> rule
 407            | None ->
 408                // A delegate target: C# invokes it, and our rule for F# function / delegate /
 409                // optional-parameter arguments is its error suggestion — or goes first where C#
 410                // would crash rather than bind (an internal delegate on .NET Framework).
 411                Seam.oursFirstWhen (DelegateMembers.csharpCannotInvoke target.LimitType) (Fallback.tryInvokeDelegate tar
 412                    (fun suggestion -> csharp.FallbackInvoke(target, args, suggestion))
 413
 414/// C#'s Invoke over byref parameters (`Dlr.call f (…, Dlr.out)`, `Dlr.apply`), with our rule — the
 415/// delegate's own `Invoke` called directly, the byref arguments passed as the site's own byref
 416/// parameters so LINQ writes them back, the others fitted by `convertValue` (so an F# function
 417/// for a delegate parameter converts, as in a call without byrefs) — as C#'s error suggestion.
 418/// It goes first only where C# would crash: on .NET Framework, invoking an F# `internal` delegate
 419/// (its `Expression.Invoke` looks `Invoke` up public-only; `DelegateMembers.csharpCannotInvoke`).
 420/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 421[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 422type FSharpByRefInvokeBinder(csharp: InvokeBinder, discard: bool, flags: CSharpArgumentInfoFlags[]) =
 423    inherit InvokeBinder(csharp.CallInfo)
 424
 425    override _.FallbackInvoke(target, args, errorSuggestion) =
 426        let direct =
 427            let dt = target.LimitType
 428            let invoke = if target.HasValue && not (isNull target.Value) && typeof<Delegate>.IsAssignableFrom dt then De
 429            if isNull invoke then None
 430            else
 431                let ps = invoke.GetParameters()
 432                // Which parameter each argument is, as C# matches them: the positional ones first,
 433                // then each named one by its name (`Dlr.named`). None for an unknown or repeated
 434                // name, or a parameter left without an argument.
 435                let names = List.ofSeq csharp.CallInfo.ArgumentNames
 436                let positional = args.Length - names.Length
 437                let slots =
 438                    [ for i in 0 .. positional - 1 -> Some i ] @ [ for n in names -> ps |> Array.tryFindIndex (fun p -> 
 439                let order = if slots |> List.exists Option.isNone then None else Some(List.map Option.get slots)
 440                match order with
 441                | Some order when ps.Length = args.Length && List.length (List.distinct order) = order.Length
 442                                  // A void delegate's result is C#'s error unless the site discards it.
 443                                  && (discard || invoke.ReturnType <> typeof<Void>) ->
 444                    let passed =
 445                        List.zip order (List.ofArray args)
 446                        |> List.mapi (fun i (slot, a) ->
 447                            let p = ps.[slot]
 448                            let pt = p.ParameterType
 449                            let isOut = flags.[i].HasFlag CSharpArgumentInfoFlags.IsOut
 450                            let isRef = flags.[i].HasFlag CSharpArgumentInfoFlags.IsRef
 451                            let expr =
 452                                if pt.IsByRef then
 453                                    // The site's byref parameter itself, `out` exactly for an out
 454                                    // parameter as C# requires: anything else is C#'s to refuse.
 455                                    match a.Expression with
 456                                    | :? ParameterExpression as v when v.IsByRef && v.Type = pt.GetElementType() && (isO
 457                                    | _ -> None
 458                                elif isOut || isRef then None                  // a ref or out for a plain parameter
 459                                else Fallback.convertValue pt a
 460                            slot, expr)
 461                    if passed |> List.exists (snd >> Option.isNone) then None
 462                    else
 463                        let byPosition = passed |> List.sortBy fst |> List.map (snd >> Option.get)
 464                        let call = Expression.Call(Expression.Convert(target.Expression, dt), invoke, byPosition)
 465                        let value =
 466                            if invoke.ReturnType = typeof<Void> then Expression.Block(call, Expression.Constant(null, ty
 467                            else Expression.Convert(call, typeof<obj>) :> Expression
 468                        let restrictions =
 469                            List.zip order (List.ofArray args)
 470                            |> List.fold (fun (r: BindingRestrictions) (slot, a) -> if ps.[slot].ParameterType.IsByRef t
 471                                (BindingRestrictions.GetTypeRestriction(target.Expression, dt))
 472                        Some(DynamicMetaObject(value, restrictions))
 473                | _ -> None
 474        Seam.oursFirstWhen (DelegateMembers.csharpCannotInvoke target.LimitType) direct errorSuggestion
 475            (fun suggestion -> csharp.FallbackInvoke(target, args, suggestion))
 476
 477/// C#'s InvokeMember binder, aware of F# function values: when C# cannot invoke a member because
 478/// it holds an `FSharpFunc` rather than a delegate, the rule applies the function instead. The
 479/// decision is a binding rule restricted to the runtime type, so a site that sees several kinds of
 480/// target keeps one cached rule per kind, as the DLR intends: no exceptions, no per-site state.
 481/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 482[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 483type FSharpInvokeMemberBinder(context: Type, name: string, csharp: InvokeMemberBinder, csharpInvoke: InvokeBinder) =
 484    inherit InvokeMemberBinder(name, false, csharp.CallInfo)
 485    let invoke = FSharpInvokeBinder(csharpInvoke)
 486
 487    /// A CLR target: an accessible property or field of that name whose declared type is a fitting
 488    /// FSharpFunc is applied directly; one whose declared type says nothing (`obj`, an interface)
 489    /// is read and handed to a nested Invoke site that decides by the value's runtime type;
 490    /// otherwise C#'s own binding, with a rule for F# optional parameters as its error suggestion.
 491    override _.FallbackInvokeMember(target, args, errorSuggestion) =
 492        let t = target.LimitType
 493        let restrictions = FunctionShapes.restrictions target t args
 494        let direct =
 495            FunctionShapes.clrMember context t name target
 496            |> Option.bind (fun (mt, read) ->
 497                match FunctionShapes.applyCall mt read args with
 498                | Some call -> Some(DynamicMetaObject(call, restrictions))
 499                | None when FunctionShapes.opaque mt || typeof<Delegate>.IsAssignableFrom mt ->
 500                    // Opaque (`obj`, an interface): decide by the value's runtime type. A delegate
 501                    // member: invoke it through a site whose binder converts F# function arguments.
 502                    let nested = Expression.Dynamic(invoke, typeof<obj>, (read :: [ for a in args -> a.Expression ]))
 503                    Some(DynamicMetaObject(nested, restrictions))
 504                | None -> None)
 505        let hasMethod =
 506            t.GetMethods(Accessibility.all) |> Array.exists (fun m -> m.Name = name && Accessibility.method' context t m
 507        let allValues = target.HasValue && (args |> Array.forall (fun a -> a.HasValue))
 508        // A function argument for a `Delegate`-typed parameter: C# would bind FSharpFunc's
 509        // op_Implicit Converter, wrongly, so our call goes first there.
 510        let abstractDelegateCall =
 511            if allValues && Fallback.hasAbstractDelegateSlot context t name args then Fallback.tryCall context t name ta
 512            else None
 513        match direct, abstractDelegateCall with
 514        | Some rule, _ when not hasMethod -> rule
 515        | _, Some rule -> rule
 516        | _ ->
 517            // A method of that name exists: C# binds it; our rules (a function-valued member of
 518            // the same name, or F# optional parameters) are only its error suggestion.
 519            let suggestion =
 520                match direct with
 521                | Some rule -> rule
 522                | None ->
 523                    if allValues then
 524                        match Fallback.tryCall context t name target args with
 525                        | Some rule -> rule
 526                        | None -> errorSuggestion
 527                    else errorSuggestion
 528            csharp.FallbackInvokeMember(target, args, suggestion)
 529
 530    /// A dynamic target (Expando, DynamicObject) produced the member's value and asks for it to be
 531    /// invoked: an F# function is applied, anything else is C#'s Invoke (see FSharpInvokeBinder).
 532    override _.FallbackInvoke(target, args, errorSuggestion) = invoke.FallbackInvoke(target, args, errorSuggestion)
 533
 534/// C#'s InvokeMember binder for a static target (`Dlr.Static<T>.Overloads?M(…)`): C# binds, and
 535/// our rule for F# optional parameters and function/delegate arguments on the static methods of
 536/// `T` is its error suggestion — the same rules an instance call gets.
 537/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 538[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 539type FSharpStaticInvokeMemberBinder(context: Type, name: string, csharp: InvokeMemberBinder) =
 540    inherit InvokeMemberBinder(name, false, csharp.CallInfo)
 541
 542    override _.FallbackInvokeMember(target, args, errorSuggestion) =
 543        let suggestion =
 544            if target.HasValue && (args |> Array.forall (fun a -> a.HasValue)) then
 545                match Fallback.tryStaticCall context (target.Value :?> Type) name target args with
 546                | Some rule -> rule
 547                | None -> errorSuggestion
 548            else errorSuggestion
 549        csharp.FallbackInvokeMember(target, args, suggestion)
 550
 551    override _.FallbackInvoke(target, args, errorSuggestion) = csharp.FallbackInvoke(target, args, errorSuggestion)
 552
 553/// C#'s InvokeConstructor binder (`Dlr.new'<T>`) with our rule for F# optional parameters and
 554/// function/delegate arguments. C#'s constructor binder takes no error suggestion — its failure
 555/// is a rule that throws — so ours applies when C#'s bind is that throw, and only then.
 556/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 557[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 558type FSharpInvokeConstructorBinder(context: Type, t: Type, csharp: DynamicMetaObjectBinder) =
 559    inherit DynamicMetaObjectBinder()
 560
 561    static let rec isThrow (e: Expression) =
 562        match e.NodeType with
 563        | ExpressionType.Throw -> true
 564        | ExpressionType.Convert | ExpressionType.ConvertChecked -> isThrow (e :?> UnaryExpression).Operand
 565        | ExpressionType.Block -> isThrow (e :?> BlockExpression).Result
 566        | _ -> false
 567
 568    override _.ReturnType = t
 569
 570    override _.Bind(target, args) =
 571        let csharpRule = csharp.Bind(target, args)
 572        if isThrow csharpRule.Expression && target.HasValue && (args |> Array.forall (fun a -> a.HasValue)) then
 573            match Fallback.tryConstruct context t target args with
 574            | Some rule -> DynamicMetaObject(Expression.Convert(rule.Expression, t), rule.Restrictions)
 575            | None -> csharpRule
 576        else csharpRule
 577
 578/// C#'s SetMember, with an F# function value handed to a meta-object as the delegate of its
 579/// signature (`w?onClick <- fun () -> …` on a script object, through MetaObjectAwareBinder); on a
 580/// CLR target, our conversion of the value to the slot's type (an F# function to a delegate-typed
 581/// property or field, a delegate to a function-typed one) as C#'s error suggestion (#153).
 582/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 583[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 584type FSharpSetMemberBinder(context: Type, name: string, csharp: SetMemberBinder) =
 585    inherit SetMemberBinder(name, false)
 586    override _.FallbackSetMember(target, value, errorSuggestion) =
 587        let ours =
 588            if target.HasValue && value.HasValue && not (isNull target.Value) then
 589                Fallback.trySet context target.LimitType (Some name) target [||] value
 590            else None
 591        // An F# function into a `Delegate`-typed slot: C# would bind FSharpFunc's op_Implicit Converter.
 592        Seam.oursFirstWhen (Fallback.assignsAbstractDelegate target.LimitType (Some name) 0 value) ours errorSuggestion
 593            (fun suggestion -> csharp.FallbackSetMember(target, value, suggestion))
 594
 595/// C#'s SetIndex, with the same conversion of the value as C#'s error suggestion: an F# function
 596/// into a delegate-typed indexer slot or array element (a `Dictionary<string, Func<…>>`), a
 597/// delegate into a function-typed one (#153).
 598/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 599[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 600type FSharpSetIndexBinder(context: Type, csharp: SetIndexBinder) =
 601    inherit SetIndexBinder(csharp.CallInfo)
 602    override this.FallbackSetIndex(target, indexes, value, errorSuggestion) =
 603        if not target.HasValue || not value.HasValue || indexes |> Array.exists (fun i -> not i.HasValue) then
 604            this.Defer(Array.concat [ [| target |]; indexes; [| value |] ])
 605        else
 606            let ours = if isNull target.Value then None else Fallback.trySet context target.LimitType None target indexe
 607            Seam.oursFirstWhen (Fallback.assignsAbstractDelegate target.LimitType None indexes.Length value) ours errorS
 608                (fun suggestion -> csharp.FallbackSetIndex(target, indexes, value, suggestion))
 609
 610/// The value of a member read as `unit -> R`: an F# function is applied, a delegate invoked, any
 611/// other value is the result itself.
 612/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 613[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 614type FSharpReadOrInvokeValueBinder(csharpInvoke: InvokeBinder) =
 615    inherit InvokeBinder(csharpInvoke.CallInfo)
 616
 617    override this.FallbackInvoke(target, args, errorSuggestion) =
 618        if not target.HasValue then this.Defer(target, args)
 619        else
 620            match FunctionShapes.tryApply target args with
 621            | Some rule -> rule
 622            | None ->
 623                match target.RuntimeType with
 624                | rt when not (isNull rt) && typeof<Delegate>.IsAssignableFrom rt -> csharpInvoke.FallbackInvoke(target,
 625                | rt when not (isNull rt) -> DynamicMetaObject(Expression.Convert(target.Expression, typeof<obj>), Bindi
 626                | _ -> DynamicMetaObject(Expression.Convert(target.Expression, typeof<obj>), BindingRestrictions.GetInst
 627
 628/// A member read as `unit -> R`: a parameterless method is invoked; a property or field is read,
 629/// then applied or invoked if it holds a function or delegate (decided by declared type when it
 630/// says so, by runtime type through a nested site otherwise), else its value is the result.
 631/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 632[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 633type FSharpReadOrInvokeBinder(context: Type, name: string, csharp: InvokeMemberBinder, csharpInvoke: InvokeBinder) =
 122634    inherit InvokeMemberBinder(name, false, csharp.CallInfo)
 122635    let value = FSharpReadOrInvokeValueBinder(csharpInvoke)
 636
 637    override _.FallbackInvokeMember(target, args, errorSuggestion) =
 120638        let t = target.LimitType
 120639        match FunctionShapes.clrMember context t name target with
 640        | None ->
 641            // A method of optional parameters only, which C# cannot call with none, as error suggestion.
 642            let suggestion =
 72643                if target.HasValue then
 72644                    match Fallback.tryCall context t name target args with
 36645                    | Some rule -> rule
 36646                    | None -> errorSuggestion
 0647                else errorSuggestion
 72648            csharp.FallbackInvokeMember(target, args, suggestion)
 649        | Some(mt, read) ->
 48650            let restriction = BindingRestrictions.GetTypeRestriction(target.Expression, t)
 48651            match FunctionShapes.applyCall mt read args with
 0652            | Some call -> DynamicMetaObject(call, restriction)
 100653            | None when mt.IsValueType || mt = typeof<string> -> DynamicMetaObject(Expression.Convert(read, typeof<obj>)
 4654            | None -> DynamicMetaObject(Expression.Dynamic(value, typeof<obj>, read), restriction)
 655
 12656    override _.FallbackInvoke(target, args, errorSuggestion) = value.FallbackInvoke(target, args, errorSuggestion)
 657
 658/// A value converted to a delegate or F# function type (a block's result, `Dlr.implicit`,
 659/// `Dlr.cast`; #202): C#'s Convert, with the argument conversions (`Fallback.conversion`: an F#
 660/// function to a delegate, a delegate to a function) as its error suggestion, where C# sees an
 661/// `FSharpFunc` and a `Func` as unrelated. To `Delegate` itself ours goes first: C# would bind
 662/// FSharpFunc's op_Implicit to a `Converter`, wrong for a `unit -> R` (`DynamicInvoke()` fails).
 663/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 664[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 665type FSharpConvertBinder(csharp: ConvertBinder) =
 666    inherit ConvertBinder(csharp.Type, csharp.Explicit)
 667    override this.FallbackConvert(target, errorSuggestion) =
 668        let ours =
 669            if target.HasValue && not (FunctionShapes.isNullValue target) then
 670                Fallback.conversion this.Type target
 671                |> Option.map (fun e -> DynamicMetaObject(Expression.Convert(e, this.Type), FunctionShapes.restrictArg t
 672            else None
 673        let toAbstractDelegate = (this.Type = typeof<Delegate> || this.Type = typeof<MulticastDelegate>) && target.HasVa
 674        Seam.oursFirstWhen toAbstractDelegate ours errorSuggestion (fun suggestion -> csharp.FallbackConvert(target, sug
 675
 676/// A member read as a delegate type (`Api.Fold(dlr { return x?Add })`, #201): C#'s GetMember, with
 677/// our rule as its error suggestion where C# fails because the name is a method, or the target is
 678/// a dynamic object with nothing to read: the `MethodGroup` marker, which the block answers with
 679/// an invoker of the member as the delegate (a dynamic object is asked at the call).
 680/// A property, field or dynamic object's member keeps its own rule (a meta-object's, then C#'s),
 681/// so that read costs what it did.
 682/// Not part of the supported API: public only because compiled blocks call it, and it may change in any release.
 683[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
 684type FSharpGetMemberOrMethodBinder(context: Type, name: string, csharp: GetMemberBinder) =
 685    inherit GetMemberBinder(name, false)
 686    override _.FallbackGetMember(target, errorSuggestion) =
 687        let ours =
 688            if target.HasValue && not (isNull target.Value) then
 689                let t = target.LimitType
 690                let isMethod () =
 691                    t.GetMethods(Accessibility.all)
 692                    |> Array.exists (fun m -> m.Name = name && not m.IsStatic && not m.IsSpecialName && Accessibility.me
 693                // A dynamic object with nothing to read may still answer a call (`TryInvokeMember`,
 694                // a proxy): the invoker asks it at the call, as a member read as a function does.
 695                if target.Value :? IDynamicMetaObjectProvider || isMethod () then
 696                    Some(DynamicMetaObject(Expression.Constant(MethodGroup.Instance, typeof<obj>), BindingRestrictions.G
 697                else None
 698            else None
 699        // Ours only where nothing else is suggested: a meta-object's own rule (a DynamicObject's
 700        // `TryGetMember ? value : fallback`) arrives as the suggestion and comes first, as in C#;
 701        // its first probe, with none, gets ours as the inner fallback.
 702        csharp.FallbackGetMember(target, (if isNull errorSuggestion then defaultArg ours null else errorSuggestion))