| | | 1 | | namespace FSharp.Interop.Dlr |
| | | 2 | | |
| | | 3 | | open System |
| | | 4 | | open System.Dynamic |
| | | 5 | | open System.Linq.Expressions |
| | | 6 | | open System.Reflection |
| | | 7 | | open System.Runtime.CompilerServices |
| | | 8 | | open Microsoft.CSharp.RuntimeBinder |
| | | 9 | | open 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`. |
| | | 18 | | module 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. |
| | | 257 | | module 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)>] |
| | | 273 | | type 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. |
| | | 335 | | module 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)>] |
| | | 386 | | type 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)>] |
| | | 397 | | type 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)>] |
| | | 422 | | type 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)>] |
| | | 483 | | type 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)>] |
| | | 539 | | type 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)>] |
| | | 558 | | type 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)>] |
| | | 584 | | type FSharpSetMemberBinder(context: Type, name: string, csharp: SetMemberBinder) = |
| | 162 | 585 | | inherit SetMemberBinder(name, false) |
| | | 586 | | override _.FallbackSetMember(target, value, errorSuggestion) = |
| | | 587 | | let ours = |
| | 608 | 588 | | if target.HasValue && value.HasValue && not (isNull target.Value) then |
| | 152 | 589 | | Fallback.trySet context target.LimitType (Some name) target [||] value |
| | 0 | 590 | | else None |
| | | 591 | | // An F# function into a `Delegate`-typed slot: C# would bind FSharpFunc's op_Implicit Converter. |
| | 152 | 592 | | Seam.oursFirstWhen (Fallback.assignsAbstractDelegate target.LimitType (Some name) 0 value) ours errorSuggestion |
| | 296 | 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)>] |
| | | 600 | | type 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)>] |
| | | 614 | | type 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)>] |
| | | 633 | | type FSharpReadOrInvokeBinder(context: Type, name: string, csharp: InvokeMemberBinder, csharpInvoke: InvokeBinder) = |
| | | 634 | | inherit InvokeMemberBinder(name, false, csharp.CallInfo) |
| | | 635 | | let value = FSharpReadOrInvokeValueBinder(csharpInvoke) |
| | | 636 | | |
| | | 637 | | override _.FallbackInvokeMember(target, args, errorSuggestion) = |
| | | 638 | | let t = target.LimitType |
| | | 639 | | 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 = |
| | | 643 | | if target.HasValue then |
| | | 644 | | match Fallback.tryCall context t name target args with |
| | | 645 | | | Some rule -> rule |
| | | 646 | | | None -> errorSuggestion |
| | | 647 | | else errorSuggestion |
| | | 648 | | csharp.FallbackInvokeMember(target, args, suggestion) |
| | | 649 | | | Some(mt, read) -> |
| | | 650 | | let restriction = BindingRestrictions.GetTypeRestriction(target.Expression, t) |
| | | 651 | | match FunctionShapes.applyCall mt read args with |
| | | 652 | | | Some call -> DynamicMetaObject(call, restriction) |
| | | 653 | | | None when mt.IsValueType || mt = typeof<string> -> DynamicMetaObject(Expression.Convert(read, typeof<obj>) |
| | | 654 | | | None -> DynamicMetaObject(Expression.Dynamic(value, typeof<obj>, read), restriction) |
| | | 655 | | |
| | | 656 | | 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)>] |
| | | 665 | | type 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)>] |
| | | 684 | | type 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)) |