| | | 1 | | namespace FSharp.Interop.Dlr |
| | | 2 | | |
| | | 3 | | open System |
| | | 4 | | open System.Collections.Concurrent |
| | | 5 | | open System.Reflection |
| | | 6 | | open FSharp.Quotations |
| | | 7 | | open FSharp.Quotations.Patterns |
| | | 8 | | open 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 on the 10.0 compiler, and the one shape Release leaves to the closure), using the |
| | | 12 | | /// `[<ReflectedDefinition>]` of the enclosing member. |
| | | 13 | | module 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) = |
| | 364236 | 31 | | try Choice1Of2(Expr.TryGetReflectedDefinition m) with e -> Choice2Of2 e |
| | | 32 | | |
| | | 33 | | /// Assemblies whose reflected definitions FSharp.Core cannot read at all, with its error. It |
| | | 34 | | /// reads them together and caches nothing when that fails, so every lookup reads them all again |
| | | 35 | | /// (#284): known, a block fails after one read, not one per member of the assembly. |
| | | 36 | | let private unreadable = ConcurrentDictionary<Reflection.Assembly, exn>() |
| | | 37 | | let private probing = ConcurrentDictionary<Reflection.Assembly, obj>() |
| | | 38 | | |
| | | 39 | | /// Whether the assembly of a block's container cannot be read at all. The container never has a |
| | | 40 | | /// reflected definition, so decoding one of its methods throws only then: FSharp.Core reads an |
| | | 41 | | /// assembly's definitions together, and one it cannot bind (a function a signature hides, |
| | | 42 | | /// erased by Release inlining, #213; a `[<Literal>]` in a reflected module, #284) fails every |
| | | 43 | | /// method of every type. A body it refuses (`typeof<System.Void>`) fails only its own member. |
| | | 44 | | /// Told apart by this, not by FSharp.Core's (localized) message, which for Void names no member. |
| | | 45 | | let private unreadableAssembly (closureType: Type) : exn option = |
| | 13529 | 46 | | let assembly = closureType.Assembly |
| | 13529 | 47 | | match unreadable.TryGetValue assembly with |
| | 12 | 48 | | | true, e -> Some e |
| | | 49 | | | _ -> |
| | | 50 | | // One probe at a time per assembly: blocks first called together would each read a |
| | | 51 | | // failing table again. A readable one FSharp.Core keeps, so its probe costs nothing after |
| | | 52 | | // the first, and is not cached here. |
| | 13541 | 53 | | lock (probing.GetOrAdd(assembly, fun _ -> obj ())) (fun () -> |
| | 13517 | 54 | | match unreadable.TryGetValue assembly with |
| | 0 | 55 | | | true, e -> Some e |
| | 13517 | 56 | | | _ -> |
| | 89567 | 57 | | match closureType.GetMethods all |> Seq.tryPick (fun m -> match tryReflected m with Choice2Of2 e -> |
| | 9 | 58 | | | Some e -> Some(unreadable.GetOrAdd(assembly, e)) |
| | 27025 | 59 | | | None -> None) |
| | | 60 | | |
| | | 61 | | let rec private reflected (t: Type) : (MethodBase * Expr) list = |
| | 80750 | 62 | | reflectedCache.GetOrAdd(t, fun t -> |
| | 51766 | 63 | | let failures = ResizeArray() |
| | 51766 | 64 | | let found = |
| | 405100 | 65 | | [ for m in Seq.append (t.GetMethods all |> Seq.cast<MethodBase>) (t.GetConstructors all |> Seq.cast<Meth |
| | 301404 | 66 | | match tryReflected m with |
| | 10067 | 67 | | | Choice1Of2(Some q) -> yield m, q |
| | 88739 | 68 | | | Choice1Of2 None -> () |
| | 30 | 69 | | | Choice2Of2 e -> failures.Add((m, e)) |
| | 205856 | 70 | | for n in t.GetNestedTypes all do |
| | 102324 | 71 | | yield! reflected n ] |
| | 51790 | 72 | | if failures.Count > 0 then undecodable.[t] <- List.ofSeq failures |
| | 132516 | 73 | | found) |
| | | 74 | | |
| | | 75 | | /// The decode failures recorded for `types` and their nested types, for the not-found message. |
| | | 76 | | let rec private failuresIn (types: seq<Type>) : (MethodBase * exn) list = |
| | 2490495 | 77 | | [ for t in types do |
| | 1245090 | 78 | | match undecodable.TryGetValue t with |
| | 411 | 79 | | | true, fs -> yield! fs |
| | 622386 | 80 | | | _ -> () |
| | 1245195 | 81 | | yield! failuresIn (t.GetNestedTypes all) ] |
| | | 82 | | |
| | | 83 | | /// Every `builder.Run(builder.Delay((fun () -> body), file, line))` in `e` at this file and line. |
| | | 84 | | let rec private runsAt (builderType: Type) (file: string) (line: int) (e: Expr) : Expr list = |
| | 575761718 | 85 | | match e with |
| | | 86 | | | Call(Some receiver, mi, [ Call(_, delay, [ Lambda(_, body); Value(f, _); Value(l, _) ]) ]) |
| | 24398760 | 87 | | when receiver.Type = builderType && mi.Name = "Run" && delay.Name = "Delay" |
| | 8147234 | 88 | | && unbox<int> l = line && unbox<string> f = file -> |
| | | 89 | | // Blocks nested inside this one compile with it (see Translate), so do not list them. |
| | 13778 | 90 | | [ body ] |
| | 141415571 | 91 | | | ShapeVar _ -> [] |
| | 126982713 | 92 | | | ShapeLambda(_, body) -> runsAt builderType file line body |
| | 461128212 | 93 | | | ShapeCombination(_, args) -> args |> List.collect (runsAt builderType file line) |
| | | 94 | | |
| | | 95 | | /// What a block needs from its enclosing member's reflected definition. |
| | | 96 | | type Found = |
| | | 97 | | { /// Type declaring the enclosing member: the binder's accessibility context, as in C#. |
| | | 98 | | Context: Type |
| | | 99 | | /// The whole reflected body of the enclosing member, for resolving let-bound values |
| | | 100 | | /// the optimizer inlined instead of capturing. |
| | | 101 | | MemberBody: Expr |
| | | 102 | | /// The block's body (the Delay lambda's body). |
| | | 103 | | Body: Expr } |
| | | 104 | | |
| | | 105 | | /// For a block inside a generic member, the closure class carries the member's type parameters |
| | | 106 | | /// (and its type's) that it uses, under the same names, instantiated. Rebuild the member with |
| | | 107 | | /// those arguments so its reflected definition comes back with concrete types. A parameter the |
| | | 108 | | /// closure does not carry is unused by the block, so any instantiation will do: `obj`, or, |
| | | 109 | | /// where a constraint rejects `obj` (#222), the parameter itself, left open. |
| | | 110 | | let private instantiate (closureType: Type) (m: MethodBase) : MethodBase = |
| | 53156 | 111 | | if not (m.IsGenericMethodDefinition || m.DeclaringType.IsGenericTypeDefinition) then m |
| | | 112 | | else |
| | | 113 | | let concrete = |
| | 132 | 114 | | if closureType.IsGenericType then |
| | 108 | 115 | | Array.zip (closureType.GetGenericTypeDefinition().GetGenericArguments()) (closureType.GetGenericArgu |
| | 174 | 116 | | |> Array.map (fun (p, a) -> p.Name, a) |
| | 108 | 117 | | |> dict |
| | 24 | 118 | | else dict [] |
| | | 119 | | let build (unused: Type -> Type) = |
| | 456 | 120 | | let resolve (p: Type) = match concrete.TryGetValue p.Name with | true, t -> t | _ -> unused p |
| | | 121 | | let onType = |
| | 180 | 122 | | if m.DeclaringType.IsGenericTypeDefinition then |
| | 192 | 123 | | let constructed = m.DeclaringType.MakeGenericType(m.DeclaringType.GetGenericArguments() |> Array |
| | 72 | 124 | | MethodBase.GetMethodFromHandle(m.MethodHandle, constructed.TypeHandle) |
| | 84 | 125 | | else m |
| | 156 | 126 | | match onType with |
| | 156 | 127 | | | :? MethodInfo as mi when mi.IsGenericMethodDefinition -> |
| | 216 | 128 | | mi.MakeGenericMethod(mi.GetGenericArguments() |> Array.map resolve) :> MethodBase |
| | 48 | 129 | | | other -> other |
| | 312 | 130 | | try build (fun _ -> typeof<obj>) |
| | 144 | 131 | | with :? ArgumentException -> build id |
| | | 132 | | |
| | | 133 | | /// A body with its variables renamed canonically, so two trees compare equal exactly when they |
| | | 134 | | /// are the same up to their variables: each binder by its position, each free variable by its |
| | | 135 | | /// name, type and mutability. `shared` gives both trees the same `Var` for the same name, as |
| | | 136 | | /// `Expr` compares variables by reference. Two free variables of one name in one tree would |
| | | 137 | | /// become one; the reflected definition is the unoptimized source, where a block cannot see |
| | | 138 | | /// two variables of one name (the inner one shadows the outer), so that does not arise. |
| | | 139 | | let private canonical (shared: System.Collections.Generic.Dictionary<string, Var>) (e: Expr) : Expr = |
| | | 140 | | let var (key: string) (t: Type) (isMutable: bool) = |
| | 2154 | 141 | | let key = sprintf "%s:%O:%b" key t isMutable |
| | 2154 | 142 | | match shared.TryGetValue key with |
| | 1143 | 143 | | | true, v -> v |
| | 3033 | 144 | | | _ -> let v = Var(key, t, isMutable) in shared.[key] <- v; v |
| | 672 | 145 | | let mutable binders = 0 |
| | | 146 | | let rec go (bound: Map<Var, Var>) (e: Expr) = |
| | 5610 | 147 | | match e with |
| | | 148 | | | ShapeVar v -> |
| | 2112 | 149 | | match bound.TryFind v with |
| | 210 | 150 | | | Some c -> Expr.Var c |
| | 1902 | 151 | | | None -> Expr.Var(var ("free " + v.Name) v.Type v.IsMutable) |
| | | 152 | | | ShapeLambda(v, body) -> |
| | 252 | 153 | | binders <- binders + 1 |
| | 252 | 154 | | let c = var (sprintf "bound %d" binders) v.Type v.IsMutable |
| | 252 | 155 | | Expr.Lambda(c, go (bound.Add(v, c)) body) |
| | 3246 | 156 | | | _ -> Quotation.rebuild (go bound) e |
| | 672 | 157 | | go Map.empty e |
| | | 158 | | |
| | | 159 | | /// The one block `bodies` are, when they are several copies of it. The quotation holds a match |
| | | 160 | | /// arm whose or-pattern binds a variable once per alternative, each copy over its own `Var`s, |
| | | 161 | | /// so the block is found once per alternative at its line (#263). Copies are taken as one block |
| | | 162 | | /// only when it cannot matter which is used: the same up to their variables, and every variable |
| | | 163 | | /// the block reads either a field of its own container (as the translator sees them), read by |
| | | 164 | | /// name where the translator reads it so (not an alias it substitutes), or one whose recovered definition is the sa |
| | | 165 | | /// reaches only such variables in turn (#271): the translator recovers it from the first copy, |
| | | 166 | | /// and the copy that ran would give the same. A variable whose definitions differ (an inlined |
| | | 167 | | /// pattern variable, `Choice1Of2`'s field in one copy and `Choice2Of2`'s in another; another |
| | | 168 | | /// block's look-alike on the same line) keeps the copies refused. The builder is never |
| | | 169 | | /// captured (the translator drops it by type, as here). `Error` names the variables that kept |
| | | 170 | | /// copies from being taken as one, or is empty when the runs are not copies at all. |
| | | 171 | | let private oneOf (builderType: Type) (closureType: Type) (memberBody: Expr) (bodies: Expr list) : Result<Expr, stri |
| | 354 | 172 | | match bodies with |
| | 84 | 173 | | | [ body ] -> Ok body |
| | | 174 | | | first :: rest -> |
| | 270 | 175 | | let shared = System.Collections.Generic.Dictionary<string, Var>() |
| | 270 | 176 | | let shape = canonical shared first |
| | 864 | 177 | | if not (rest |> List.forall (fun b -> canonical shared b = shape)) then Error [] |
| | | 178 | | else |
| | | 179 | | // The translator's own view of the container's fields: a machine's `Data` and |
| | | 180 | | // `ResumptionPoint` are not captures, though a variable may be named either. |
| | 258 | 181 | | let fields = TranslateBlock.Captures.fields closureType |
| | | 182 | | // Per tree, its free variables by name: None when two share one (not the unoptimized |
| | | 183 | | // source's, where the inner shadows the outer). |
| | | 184 | | let byName (e: Expr) = |
| | 876 | 185 | | let vs = e.GetFreeVars() |> List.ofSeq |
| | 2574 | 186 | | let names = vs |> List.map (fun v -> v.Name) |
| | 5724 | 187 | | if List.length (List.distinct names) = names.Length then Some(dict [ for v in vs -> v.Name, v ]) els |
| | | 188 | | // Per copy, its own free variables, which the translator never takes for aliases. |
| | 1224 | 189 | | let owns = bodies |> List.map (fun b -> b.GetFreeVars() |> Set.ofSeq) |
| | | 190 | | // Whether it cannot matter which copy's variable this is (`vars`, one per copy, of one name). |
| | | 191 | | let rec agree (vars: Var list) = |
| | 831 | 192 | | let v = List.head vars |
| | 831 | 193 | | v.Type = builderType |
| | | 194 | | // Read from its field, as the translator reads it: not an alias it substitutes, |
| | | 195 | | // which a field of its name may sit beside, holding another variable (#272 review). |
| | 2133 | 196 | | || (fields.ContainsKey v.Name && not (List.exists2 (fun own w -> TranslateBlock.Captures.isAlias own |
| | | 197 | | // Bound outside the copies: one variable, one definition. |
| | 189 | 198 | | || vars |> List.forall (fun w -> w = v) |
| | 180 | 199 | | || (let defs = vars |> List.map (fun w -> TranslatePatterns.letDefinition w memberBody |> Option.orE |
| | 60 | 200 | | match List.choose id defs with |
| | 60 | 201 | | | first :: rest when rest.Length = defs.Length - 1 -> |
| | 60 | 202 | | let shared = System.Collections.Generic.Dictionary<string, Var>() |
| | 60 | 203 | | let shape = canonical shared first |
| | 120 | 204 | | rest |> List.forall (fun d -> canonical shared d = shape) |
| | 27 | 205 | | && (match List.choose byName (first :: rest) with |
| | 27 | 206 | | | reached :: others when others.Length = rest.Length -> |
| | 27 | 207 | | reached.Keys |> Seq.forall (fun n -> |
| | 54 | 208 | | others |> List.forall (fun o -> o.ContainsKey n) |
| | 135 | 209 | | && agree (reached.[n] :: [ for o in others -> o.[n] ])) |
| | 0 | 210 | | | _ -> false) |
| | 0 | 211 | | | _ -> false) |
| | 258 | 212 | | match List.choose byName bodies with |
| | 258 | 213 | | | copies when copies.Length = bodies.Length -> |
| | 258 | 214 | | let firstVars = List.head copies |
| | | 215 | | let uncaptured = |
| | 258 | 216 | | firstVars.Keys |
| | 258 | 217 | | |> Seq.filter (fun n -> |
| | 7344 | 218 | | let vars = [ for c in copies -> match c.TryGetValue n with | true, v -> Some v | _ -> None ] |
| | 2556 | 219 | | not (List.forall Option.isSome vars && agree (List.choose id vars))) |
| | 258 | 220 | | |> List.ofSeq |
| | 258 | 221 | | match uncaptured with |
| | 225 | 222 | | | [] -> Ok first |
| | 33 | 223 | | | uncaptured -> Error uncaptured |
| | | 224 | | // Two free variables of one name in a body: not seen in a reflected one (an inner |
| | | 225 | | // binding shadows), kept so that it would be refused, not guessed. |
| | 0 | 226 | | | _ -> Error [ for v in first.GetFreeVars() -> v.Name ] |
| | 0 | 227 | | | [] -> Error [] |
| | | 228 | | |
| | | 229 | | /// The error for a block whose assembly's reflected definitions cannot be read (`unreadableAssembly`). |
| | | 230 | | let private unreadableError (file: string) (line: int) (holder: Type) (closureType: Type) (e: exn) = |
| | 21 | 231 | | DlrTranslationException( |
| | 21 | 232 | | sprintf "dlr { } at %s:%d cannot find its body: FSharp.Core could not decode any of %s's reflected definitio |
| | 21 | 233 | | file line (holder.Assembly.GetName().Name) e.Message closureType.Name holder.FullName) |
| | | 234 | | |
| | | 235 | | /// The block whose Delay closure has type `closureType`, at `file:line`. |
| | | 236 | | let findBody (builderType: Type) (closureType: Type) (file: string) (line: int) : Found = |
| | 26938 | 237 | | let holder = if isNull closureType.DeclaringType then closureType else closureType.DeclaringType |
| | 13469 | 238 | | match unreadableAssembly closureType with |
| | 21 | 239 | | | Some e -> raise (unreadableError file line holder closureType e) |
| | 13448 | 240 | | | None -> () |
| | | 241 | | let search (types: seq<Type>) = |
| | 13532 | 242 | | types |
| | 13532 | 243 | | |> Seq.collect reflected |
| | 10172473 | 244 | | |> Seq.collect (fun (m, q) -> runsAt builderType file line q |> List.map (fun b -> m, q, b)) |
| | 13532 | 245 | | |> List.ofSeq |
| | 13448 | 246 | | let searched = ResizeArray<Type>() |
| | | 247 | | let matches = |
| | 13448 | 248 | | searched.Add holder |
| | 13448 | 249 | | match search [ holder ] with |
| | | 250 | | | [] -> |
| | | 251 | | // A member of a type declared in a namespace gets its closures nested in the file's |
| | | 252 | | // <StartupCode$…> class, not in the type: fall back to the whole assembly. |
| | 724346 | 253 | | let all = closureType.Assembly.GetTypes() |> Seq.filter (fun t -> not t.IsNested) |> List.ofSeq |
| | 84 | 254 | | searched.AddRange all |
| | 84 | 255 | | search all |
| | 13364 | 256 | | | found -> found |
| | | 257 | | // Copies of one block in one member (an or-pattern's arm) are that block, not several. |
| | | 258 | | let matches = |
| | 13448 | 259 | | match matches with |
| | 7335 | 260 | | | (m, memberBody, _) :: rest when not rest.IsEmpty && rest |> List.forall (fun (m', _, _) -> m' = m) -> |
| | 1026 | 261 | | match oneOf builderType closureType memberBody (matches |> List.map (fun (_, _, b) -> b)) with |
| | 201 | 262 | | | Ok body -> [ m, memberBody, body ] |
| | 12 | 263 | | | Error [] -> matches |
| | | 264 | | | Error uncaptured -> |
| | 33 | 265 | | raise (DlrTranslationException( |
| | 33 | 266 | | sprintf "dlr { } at %s:%d is held %d times in its member's reflected definition (an or-patte |
| | 33 | 267 | | file line matches.Length (uncaptured |> List.map (sprintf "'%s'") |> String.concat ", ") |
| | 33 | 268 | | (String.Join(", ", (TranslateBlock.Captures.fields closureType).Keys)))) |
| | 13202 | 269 | | | _ -> matches |
| | 13415 | 270 | | match matches with |
| | | 271 | | | [ m, memberBody, body ] -> |
| | | 272 | | let m, memberBody, body = |
| | 13343 | 273 | | match instantiate closureType m with |
| | 26578 | 274 | | | inst when obj.ReferenceEquals(inst, m) -> m, memberBody, body |
| | | 275 | | | inst -> |
| | 108 | 276 | | match tryReflected inst with |
| | | 277 | | | Choice1Of2(Some q) -> |
| | 108 | 278 | | match oneOf builderType closureType q (runsAt builderType file line q) with |
| | 108 | 279 | | | Ok b -> inst, q, b |
| | 0 | 280 | | | Error _ -> m, memberBody, body |
| | 0 | 281 | | | _ -> m, memberBody, body |
| | 13343 | 282 | | { Context = m.DeclaringType; MemberBody = memberBody; Body = body } |
| | | 283 | | | [] -> |
| | | 284 | | // Checked before the search too; a table may also fail between (another thread's block). |
| | 60 | 285 | | let assemblyWide = unreadableAssembly closureType |
| | | 286 | | // A member with the attribute whose quotation would not decode is the other reason a |
| | | 287 | | // body is not found — when it is the member the closure belongs to: the compiler |
| | | 288 | | // names a block's closure `<member>@<line>`. A name alone also matches a same-named |
| | | 289 | | // member of another type (#248), so this is stated only for a member declared in the |
| | | 290 | | // type holding the closure. A class's closures are nested in its module, which its |
| | | 291 | | // sibling classes share, so a class member is left to the hint below, whose wording |
| | | 292 | | // allows for a wrong guess, like any other match. |
| | 453 | 293 | | let own = failuresIn searched |> List.tryFind (fun (m, _) -> closureType.Name.StartsWith(m.Name + "@") && m. |
| | 60 | 294 | | match assemblyWide, own with |
| | 0 | 295 | | | Some e, _ -> raise (unreadableError file line holder closureType e) |
| | | 296 | | | None, Some(m, e) -> |
| | 15 | 297 | | raise (DlrTranslationException( |
| | 15 | 298 | | sprintf "dlr { } at %s:%d is in %s.%s, whose reflected definition FSharp.Core could not decode ( |
| | 15 | 299 | | file line m.DeclaringType.Name m.Name e.Message closureType.Name holder.FullName)) |
| | | 300 | | | None, None -> |
| | | 301 | | // A block in a local function has a closure named after the local, not the member |
| | | 302 | | // (Debug; Release names the machine after the member), so the member is unknown |
| | | 303 | | // here (#245). Members that failed to decode in the closure's type and the types |
| | | 304 | | // nested in it (a class in the module) are then the likely place. A type declared in |
| | | 305 | | // a namespace has its closures in the file's <StartupCode$…> class, which nothing |
| | | 306 | | // ties to the types the file declares: there, the assembly's. The attribute may still |
| | | 307 | | // be missing, so they are a hint after the missing-attribute message, not a |
| | | 308 | | // replacement for it. |
| | | 309 | | let candidates = |
| | 171 | 310 | | if not (isNull holder.Namespace) && holder.Namespace.StartsWith "<StartupCode$" then failuresIn sear |
| | 57 | 311 | | else failuresIn [ holder ] |
| | 21 | 312 | | // `searched` holds the closure's type twice when the assembly-wide fallback ran, |
| | 21 | 313 | | // and a nested type comes once more through its parent. |
| | 45 | 314 | | |> List.distinctBy fst |
| | | 315 | | let hint = |
| | 45 | 316 | | match candidates with |
| | | 317 | | | [] -> |
| | | 318 | | // A trimmed publish keeps the stored definitions but not what FSharp.Core |
| | | 319 | | // matches them by, so the block is not found although the attribute is |
| | | 320 | | // there: say so where the assembly holds reflected definitions at all. |
| | 45 | 321 | | if holder.Assembly.GetManifestResourceNames() |> Array.exists (fun n -> n.StartsWith "ReflectedD |
| | 18 | 322 | | " If the attribute is there and the app is published trimmed (PublishTrimmed), trimming is t |
| | 0 | 323 | | else "" |
| | | 324 | | | [ m, e ] -> |
| | 0 | 325 | | sprintf " If the block is in %s.%s (in a local function there, say), the attribute is there, but |
| | 0 | 326 | | m.DeclaringType.Name m.Name e.Message |
| | | 327 | | | several -> |
| | | 328 | | // One error per distinct message, with the members it was raised for. |
| | | 329 | | let named = |
| | 27 | 330 | | several |
| | 135 | 331 | | |> List.groupBy (fun (_, e) -> e.Message) |
| | 207 | 332 | | |> List.map (fun (message, ms) -> sprintf "%s (%s)" (ms |> List.map (fun (m, _) -> m.Declari |
| | 27 | 333 | | |> String.concat "; " |
| | 27 | 334 | | sprintf " If the block is in one of %s (in a local function there, say), the attribute is there, |
| | 27 | 335 | | named |
| | 45 | 336 | | raise (DlrTranslationException( |
| | 45 | 337 | | sprintf "dlr { } at %s:%d needs [<ReflectedDefinition>] on the function or member that contains |
| | 45 | 338 | | file line closureType.Name holder.FullName hint)) |
| | | 339 | | | many -> |
| | 12 | 340 | | raise (DlrTranslationException( |
| | 12 | 341 | | sprintf "%d dlr { } blocks share %s:%d; put each dlr { } on its own line." many.Length file line)) |