< Summary - FSharp.Interop.Dlr

Information
Class: FSharp.Interop.Dlr.Discover
Assembly: FSharp.Interop.Dlr
File(s): /home/runner/work/FSharp.Interop.Dlr/FSharp.Interop.Dlr/FSharp.Interop.Dlr/Discover.fs
Line coverage
94%
Covered lines: 68
Uncovered lines: 4
Coverable lines: 72
Total lines: 153
Line coverage: 94.4%
Branch coverage
87%
Covered branches: 75
Total branches: 86
Branch coverage: 87.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
tryReflected(...)100%11100%
reflected(...)100%11100%
failuresIn(...)100%44100%
runsAt(...)100%3838100%
instantiate(...)100%44100%
resolve@90(...)50%22100%
Invoke(...)50%22100%
findBody(...)73.33%252489.28%
search@104(...)100%11100%
Invoke(...)100%11100%

File(s)

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

#LineLine coverage
 1namespace FSharp.Interop.Dlr
 2
 3open System
 4open System.Collections.Concurrent
 5open System.Reflection
 6open FSharp.Quotations
 7open FSharp.Quotations.Patterns
 8open FSharp.Quotations.ExprShape
 9
 10/// Finds the quoted body of a `dlr { }` block from the type of its state machine (Release) or its
 11/// Delay closure (Debug, and the one shape Release leaves to the closure), using the
 12/// `[<ReflectedDefinition>]` of the enclosing member.
 13module internal Discover =
 14
 15    let private all =
 16        BindingFlags.Static ||| BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlag
 17
 18    /// Members with a reflected definition on `t` and every type nested in it (closures of a
 19    /// class member are nested in the enclosing module, not the class). Decoding a reflected
 20    /// definition costs microseconds and a module has one per member, so the result is kept
 21    /// per type: later blocks in the same type find their body without decoding again.
 22    let private reflectedCache = ConcurrentDictionary<Type, (MethodBase * Expr) list>()
 23
 24    /// Members whose stored quotation FSharp.Core could not decode, per type: some
 25    /// compiler-generated members have one (a [<CLIEvent>] accessor), and so can a member of the
 26    /// user's whose body FSharp.Core refuses (`typeof<System.Void>` in it). Neither is fatal here
 27    /// — the block may be elsewhere — but when no body is found, the failure is the likely reason.
 28    let private undecodable = ConcurrentDictionary<Type, (MethodBase * exn) list>()
 29
 30    let private tryReflected (m: MethodBase) =
 19762031        try Choice1Of2(Expr.TryGetReflectedDefinition m) with e -> Choice2Of2 e
 32
 33    let rec private reflected (t: Type) : (MethodBase * Expr) list =
 4422734        reflectedCache.GetOrAdd(t, fun t ->
 3200535            let failures = ResizeArray()
 3200536            let found =
 25695337                [ for m in Seq.append (t.GetMethods all |> Seq.cast<MethodBase>) (t.GetConstructors all |> Seq.cast<Meth
 19753238                      match tryReflected m with
 633239                      | Choice1Of2(Some q) -> yield m, q
 5508040                      | Choice1Of2 None -> ()
 441                      | Choice2Of2 e -> failures.Add((m, e))
 12726842                  for n in t.GetNestedTypes all do
 6325843                      yield! reflected n ]
 3200944            if failures.Count > 0 then undecodable.[t] <- List.ofSeq failures
 7623245            found)
 46
 47    /// The decode failures recorded for `types` and their nested types, for the not-found message.
 48    let rec private failuresIn (types: seq<Type>) : (MethodBase * exn) list =
 25617649        [ for t in types do
 12807650              match undecodable.TryGetValue t with
 1251              | true, fs -> yield! fs
 6403652              | _ -> ()
 12808453              yield! failuresIn (t.GetNestedTypes all) ]
 54
 55    /// Every `builder.Run(builder.Delay(fun () -> body), file, line)` in `e` at this file and line.
 56    let rec private runsAt (builderType: Type) (file: string) (line: int) (e: Expr) : Expr list =
 37185895657        match e with
 58        | Call(Some receiver, mi, [ Call(_, delay, [ Lambda(_, body) ]); Value(f, _); Value(l, _) ])
 1582156859            when receiver.Type = builderType && mi.Name = "Run" && delay.Name = "Delay"
 528246260                 && unbox<int> l = line && unbox<string> f = file ->
 61            // Blocks nested inside this one compile with it (see Translate), so do not list them.
 852262            [ body ]
 9200962863        | ShapeVar _ -> []
 8271803864        | ShapeLambda(_, body) -> runsAt builderType file line body
 29567423465        | ShapeCombination(_, args) -> args |> List.collect (runsAt builderType file line)
 66
 67    /// What a block needs from its enclosing member's reflected definition.
 68    type Found =
 69        { /// Type declaring the enclosing member: the binder's accessibility context, as in C#.
 70          Context: Type
 71          /// The whole reflected body of the enclosing member, for resolving let-bound values
 72          /// the optimizer inlined instead of capturing.
 73          MemberBody: Expr
 74          /// The block's body (the Delay lambda's body).
 75          Body: Expr }
 76
 77    /// For a block inside a generic member, the closure class carries the member's type parameters
 78    /// under the same names, instantiated. Rebuild the member with those arguments so its reflected
 79    /// definition comes back with concrete types. A parameter the closure does not carry is unused
 80    /// by the block, so any instantiation will do.
 81    let private instantiate (closureType: Type) (m: MethodBase) : MethodBase =
 3383682        if not (m.IsGenericMethodDefinition || m.DeclaringType.IsGenericTypeDefinition) then m
 83        else
 84            let concrete =
 2085                if closureType.IsGenericType then
 2086                    Array.zip (closureType.GetGenericTypeDefinition().GetGenericArguments()) (closureType.GetGenericArgu
 4487                    |> Array.map (fun (p, a) -> p.Name, a)
 2088                    |> dict
 089                else dict []
 9690            let resolve (p: Type) = match concrete.TryGetValue p.Name with | true, t -> t | _ -> typeof<obj>
 91            let onType =
 2092                if m.DeclaringType.IsGenericTypeDefinition then
 4293                    let constructed = m.DeclaringType.MakeGenericType(m.DeclaringType.GetGenericArguments() |> Array.map
 1494                    MethodBase.GetMethodFromHandle(m.MethodHandle, constructed.TypeHandle)
 695                else m
 2096            match onType with
 2097            | :? MethodInfo as mi when mi.IsGenericMethodDefinition ->
 3098                mi.MakeGenericMethod(mi.GetGenericArguments() |> Array.map resolve) :> MethodBase
 1099            | other -> other
 100
 101    /// The block whose Delay closure has type `closureType`, at `file:line`.
 102    let findBody (builderType: Type) (closureType: Type) (file: string) (line: int) : Found =
 16972103        let holder = if isNull closureType.DeclaringType then closureType else closureType.DeclaringType
 104        let search (types: seq<Type>) =
 8510105            types
 8510106            |> Seq.collect reflected
 6615845107            |> Seq.collect (fun (m, q) -> runsAt builderType file line q |> List.map (fun b -> m, q, b))
 8510108            |> List.ofSeq
 8486109        let searched = ResizeArray<Type>()
 110        let matches =
 8486111            searched.Add holder
 8486112            match search [ holder ] with
 113            | [] ->
 114                // A member of a type declared in a namespace gets its closures nested in the file's
 115                // <StartupCode$…> class, not in the type: fall back to the whole assembly.
 192030116                let all = closureType.Assembly.GetTypes() |> Seq.filter (fun t -> not t.IsNested) |> List.ofSeq
 24117                searched.AddRange all
 24118                search all
 8462119            | found -> found
 8486120        match matches with
 121        | [ m, memberBody, body ] ->
 122            let m, memberBody, body =
 8474123                match instantiate closureType m with
 16908124                | inst when obj.ReferenceEquals(inst, m) -> m, memberBody, body
 125                | inst ->
 40126                    match tryReflected inst with
 127                    | Choice1Of2(Some q) ->
 40128                        match runsAt builderType file line q with
 40129                        | [ b ] -> inst, q, b
 0130                        | _ -> m, memberBody, body
 0131                    | _ -> m, memberBody, body
 8474132            { Context = m.DeclaringType; MemberBody = memberBody; Body = body }
 133        | [] ->
 134            // A member with the attribute whose quotation would not decode is the other reason a
 135            // body is not found — when it is the member the closure belongs to: the compiler
 136            // names a block's closure `<member>@<line>`.
 137            let undecoded =
 24138                match failuresIn searched |> List.tryFind (fun (m, _) -> closureType.Name.StartsWith(m.Name + "@")) with
 4139                | Some(m, e) -> sprintf " The block is in %s.%s, whose reflected definition could not be decoded (%s): i
 4140                | None -> ""
 141            // A trimmed publish keeps the stored definitions but not what FSharp.Core matches them
 142            // by, so the block is not found although the attribute is there: say so where the
 143            // assembly holds reflected definitions at all.
 144            let trimmed =
 20145                if holder.Assembly.GetManifestResourceNames() |> Array.exists (fun n -> n.StartsWith "ReflectedDefinitio
 8146                    " If the attribute is there and the app is published trimmed (PublishTrimmed), trimming is the cause
 0147                else ""
 8148            raise (DlrTranslationException(
 8149                    sprintf "dlr { } at %s:%d needs [<ReflectedDefinition>] on the function or member that contains it, 
 8150                        file line closureType.Name holder.FullName undecoded trimmed))
 151        | many ->
 4152            raise (DlrTranslationException(
 4153                    sprintf "%d dlr { } blocks share %s:%d; put each dlr { } on its own line." many.Length file line))