@@ -10,14 +10,20 @@ open Replacements.Util
1010/// to construct a quotation AST. The input is the Fable.Expr captured
1111/// inside a Quote node; the output is a Fable.Expr that calls the
1212/// quotation runtime library to build the AST at runtime.
13- let rec emitQuotedExpr ( com : Compiler ) ( expr : Expr ) : Expr =
13+ let rec private emitQuotedExprIn ( com : Compiler ) ( bound : Set < string > ) ( expr : Expr ) : Expr =
1414 match expr with
15- | Value( kind, r) -> emitQuotedValue com kind r
15+ | Value( kind, r) -> emitQuotedValue com bound kind r
16+
17+ | IdentExpr ident when not ( Set.contains ident.Name bound) ->
18+ // Free in the quotation, so it is a local captured from the enclosing
19+ // scope rather than a quotation variable. .NET splices the captured
20+ // *value* in as a Value node; emitting a Var here instead left consumers
21+ // with a name and no value to bind -- which is precisely what a query
22+ // translator needs in order to turn a captured local into a parameter.
23+ Helper.LibCall( com, " quotation" , " mkValue" , Any, [ IdentExpr ident; makeStrConst ( typeToString ident.Type) ])
1624
1725 | IdentExpr ident ->
18- // Reference to a variable already introduced by a lambda/let in the quotation.
19- // Emit: quotation.mkVar(Var)
20- // We need a var reference. Create a var and then wrap it.
26+ // Bound by a lambda or let inside the quotation: a genuine Var.
2127 let varExpr =
2228 Helper.LibCall(
2329 com,
@@ -47,14 +53,14 @@ let rec emitQuotedExpr (com: Compiler) (expr: Expr) : Expr =
4753 ]
4854 )
4955
50- let bodyExpr = emitQuotedExpr com body
56+ let bodyExpr = emitQuotedExprIn com ( Set.add arg.Name bound ) body
5157 Helper.LibCall( com, " quotation" , " mkLambda" , Any, [ varExpr; bodyExpr ])
5258
5359 | Delegate( args, body, _ name, _ tags) ->
5460 // Multi-arg delegate: nest as curried lambdas
55- let rec nestLambdas args body =
61+ let rec nestLambdas bound args body =
5662 match args with
57- | [] -> emitQuotedExpr com body
63+ | [] -> emitQuotedExprIn com bound body
5864 | ( arg: Ident) :: rest ->
5965 let varExpr =
6066 Helper.LibCall(
@@ -69,10 +75,10 @@ let rec emitQuotedExpr (com: Compiler) (expr: Expr) : Expr =
6975 ]
7076 )
7177
72- let innerBody = nestLambdas rest body
78+ let innerBody = nestLambdas ( Set.add arg.Name bound ) rest body
7379 Helper.LibCall( com, " quotation" , " mkLambda" , Any, [ varExpr; innerBody ])
7480
75- nestLambdas args body
81+ nestLambdas bound args body
7682
7783 | Let( ident, value, body) ->
7884 let varExpr =
@@ -88,33 +94,33 @@ let rec emitQuotedExpr (com: Compiler) (expr: Expr) : Expr =
8894 ]
8995 )
9096
91- let valueExpr = emitQuotedExpr com value
92- let bodyExpr = emitQuotedExpr com body
97+ let valueExpr = emitQuotedExprIn com bound value
98+ let bodyExpr = emitQuotedExprIn com ( Set.add ident.Name bound ) body
9399
94100 Helper.LibCall( com, " quotation" , " mkLet" , Any, [ varExpr; valueExpr; bodyExpr ])
95101
96102 | IfThenElse( guardExpr, thenExpr, elseExpr, _ r) ->
97- let guard = emitQuotedExpr com guardExpr
98- let thenE = emitQuotedExpr com thenExpr
99- let elseE = emitQuotedExpr com elseExpr
103+ let guard = emitQuotedExprIn com bound guardExpr
104+ let thenE = emitQuotedExprIn com bound thenExpr
105+ let elseE = emitQuotedExprIn com bound elseExpr
100106 Helper.LibCall( com, " quotation" , " mkIfThenElse" , Any, [ guard; thenE; elseE ])
101107
102108 | CurriedApply( applied, args, _ typ, _ r) ->
103109 // Emit nested applications: Application(Application(f, a1), a2)
104- let appliedExpr = emitQuotedExpr com applied
110+ let appliedExpr = emitQuotedExprIn com bound applied
105111
106112 args
107113 |> List.fold
108114 ( fun acc arg ->
109- let argExpr = emitQuotedExpr com arg
115+ let argExpr = emitQuotedExprIn com bound arg
110116 Helper.LibCall( com, " quotation" , " mkApplication" , Any, [ acc; argExpr ])
111117 )
112118 appliedExpr
113119
114120 | Call( callee, info, _ typ, _ r) ->
115121 let instanceExpr =
116122 match info.ThisArg with
117- | Some thisArg -> emitQuotedExpr com thisArg
123+ | Some thisArg -> emitQuotedExprIn com bound thisArg
118124 // Static/operator call: no instance.
119125 | None -> mkNoInstanceExpr com
120126
@@ -135,16 +141,16 @@ let rec emitQuotedExpr (com: Compiler) (expr: Expr) : Expr =
135141 else
136142 let methodExpr = makeStrConst methodName
137143 let declTypeExpr = makeStrConst declaringType
138- let argExprs = mkExprArray com ( info.Args |> List.map ( emitQuotedExpr com))
144+ let argExprs = mkExprArray com ( info.Args |> List.map ( emitQuotedExprIn com bound ))
139145 Helper.LibCall( com, " quotation" , " mkCall" , Any, [ instanceExpr; methodExpr; argExprs; declTypeExpr ])
140146
141147 | Sequential exprs ->
142148 match exprs with
143- | [] -> emitQuotedExpr com ( Value( UnitConstant, None))
144- | [ single ] -> emitQuotedExpr com single
149+ | [] -> emitQuotedExprIn com bound ( Value( UnitConstant, None))
150+ | [ single ] -> emitQuotedExprIn com bound single
145151 | first :: rest ->
146- let restExpr = emitQuotedExpr com ( Sequential rest)
147- let firstExpr = emitQuotedExpr com first
152+ let restExpr = emitQuotedExprIn com bound ( Sequential rest)
153+ let firstExpr = emitQuotedExprIn com bound first
148154 Helper.LibCall( com, " quotation" , " mkSequential" , Any, [ firstExpr; restExpr ])
149155
150156 | Operation( kind, _ tags, _ typ, _ r) ->
@@ -195,13 +201,13 @@ let rec emitQuotedExpr (com: Compiler) (expr: Expr) : Expr =
195201 let methodExpr = makeStrConst opName
196202 let instanceExpr = mkNoInstanceExpr com
197203
198- let argExprs = mkExprArray com ( args |> List.map ( emitQuotedExpr com))
204+ let argExprs = mkExprArray com ( args |> List.map ( emitQuotedExprIn com bound ))
199205
200206 // Operators have no declaring type; pass an empty string.
201207 Helper.LibCall( com, " quotation" , " mkCall" , Any, [ instanceExpr; methodExpr; argExprs; makeStrConst " " ])
202208
203209 | Get( expr, kind, _ typ, _ r) ->
204- let target = emitQuotedExpr com expr
210+ let target = emitQuotedExprIn com bound expr
205211
206212 match kind with
207213 | TupleIndex index -> Helper.LibCall( com, " quotation" , " mkTupleGet" , Any, [ target; makeIntConst index ])
@@ -227,8 +233,8 @@ let rec emitQuotedExpr (com: Compiler) (expr: Expr) : Expr =
227233 Helper.LibCall( com, " quotation" , " mkValue" , Any, [ makeStrConst msg; makeStrConst " string" ])
228234
229235 | Set( expr, kind, _ typ, value, _ r) ->
230- let target = emitQuotedExpr com expr
231- let valueExpr = emitQuotedExpr com value
236+ let target = emitQuotedExprIn com bound expr
237+ let valueExpr = emitQuotedExprIn com bound value
232238
233239 match kind with
234240 | ValueSet ->
@@ -242,7 +248,7 @@ let rec emitQuotedExpr (com: Compiler) (expr: Expr) : Expr =
242248
243249 | TypeCast( innerExpr, _ typ) ->
244250 // Coerce/cast: just emit the inner expression for now
245- emitQuotedExpr com innerExpr
251+ emitQuotedExprIn com bound innerExpr
246252
247253 | DecisionTree( decisionExpr, targets) ->
248254 // Inline each DecisionTreeSuccess leaf into its target body via nested Lets, turning
@@ -261,18 +267,18 @@ let rec emitQuotedExpr (com: Compiler) (expr: Expr) : Expr =
261267 | e -> e
262268 )
263269
264- emitQuotedExpr com inlined
270+ emitQuotedExprIn com bound inlined
265271
266272 | DecisionTreeSuccess( idx, boundValues, _ typ) ->
267273 match boundValues with
268- | [] -> emitQuotedExpr com ( Value( UnitConstant, None))
269- | [ single ] -> emitQuotedExpr com single
274+ | [] -> emitQuotedExprIn com bound ( Value( UnitConstant, None))
275+ | [ single ] -> emitQuotedExprIn com bound single
270276 | _ ->
271277 let msg = " Unsupported quotation node: DecisionTreeSuccess"
272278 Helper.LibCall( com, " quotation" , " mkValue" , Any, [ makeStrConst msg; makeStrConst " string" ])
273279
274280 | Test( testExpr, kind, _ r) ->
275- let target = emitQuotedExpr com testExpr
281+ let target = emitQuotedExprIn com bound testExpr
276282
277283 // Note: real .NET quotations use dedicated UnionCaseTest/TypeTest node kinds here, not
278284 // Call — this emitter renders all of these as synthetic Calls instead (e.g. "get_IsCons",
@@ -282,7 +288,7 @@ let rec emitQuotedExpr (com: Compiler) (expr: Expr) : Expr =
282288 | UnionCaseTest tag ->
283289 // Represent as: (unionTag target) = tag
284290 let tagExpr = Helper.LibCall( com, " quotation" , " mkUnionTag" , Any, [ target ])
285- let tagConst = emitQuotedExpr com ( makeIntConst tag)
291+ let tagConst = emitQuotedExprIn com bound ( makeIntConst tag)
286292
287293 Helper.LibCall(
288294 com,
@@ -364,7 +370,7 @@ and private mkNoInstanceExpr (com: Compiler) : Expr =
364370 // so isCall/isFieldGet don't conflate the two.
365371 mkNullExpr com " novalue"
366372
367- and private emitQuotedValue ( com : Compiler ) ( kind : ValueKind ) ( _r : SourceLocation option ) : Expr =
373+ and private emitQuotedValue ( com : Compiler ) ( bound : Set < string >) ( kind : ValueKind ) ( _r : SourceLocation option ) : Expr =
368374 match kind with
369375 | BoolConstant b -> Helper.LibCall( com, " quotation" , " mkValue" , Any, [ makeBoolConst b; makeStrConst " bool" ])
370376
@@ -388,7 +394,8 @@ and private emitQuotedValue (com: Compiler) (kind: ValueKind) (_r: SourceLocatio
388394 Helper.LibCall( com, " quotation" , " mkValue" , Any, [ Value( CharConstant c, None); makeStrConst " char" ])
389395
390396 | NewTuple( values, _ isStruct) ->
391- let emittedValues = mkExprArray com ( values |> List.map ( emitQuotedExpr com))
397+ let emittedValues =
398+ mkExprArray com ( values |> List.map ( emitQuotedExprIn com bound))
392399
393400 Helper.LibCall( com, " quotation" , " mkNewTuple" , Any, [ emittedValues ])
394401
@@ -398,7 +405,8 @@ and private emitQuotedValue (com: Compiler) (kind: ValueKind) (_r: SourceLocatio
398405 | Some ent -> ent.FullName
399406 | None -> entRef.FullName
400407
401- let emittedValues = mkExprArray com ( values |> List.map ( emitQuotedExpr com))
408+ let emittedValues =
409+ mkExprArray com ( values |> List.map ( emitQuotedExprIn com bound))
402410
403411 if com.Options.Language = Rust then
404412 // Rust needs the case name to build a real UnionCaseInfo; other targets keep (name, tag, fields).
@@ -429,7 +437,8 @@ and private emitQuotedValue (com: Compiler) (kind: ValueKind) (_r: SourceLocatio
429437 | Some ent -> ent.FSharpFields |> List.map ( fun f -> makeStrConst f.Name) |> makeArray Any
430438 | None -> makeArray Any []
431439
432- let emittedValues = mkExprArray com ( values |> List.map ( emitQuotedExpr com))
440+ let emittedValues =
441+ mkExprArray com ( values |> List.map ( emitQuotedExprIn com bound))
433442
434443 if com.Options.Language = Rust then
435444 // Rust needs the record type name; other targets keep (fieldNames, values).
@@ -449,7 +458,7 @@ and private emitQuotedValue (com: Compiler) (kind: ValueKind) (_r: SourceLocatio
449458
450459 match value with
451460 | Some v ->
452- let emitted = mkExprArray com [ emitQuotedExpr com v ]
461+ let emitted = mkExprArray com [ emitQuotedExprIn com bound v ]
453462
454463 Helper.LibCall(
455464 com,
@@ -472,15 +481,15 @@ and private emitQuotedValue (com: Compiler) (kind: ValueKind) (_r: SourceLocatio
472481 | NewOption( value, _ typ, _ isStruct) ->
473482 match value with
474483 | Some v ->
475- let emitted = emitQuotedExpr com v
484+ let emitted = emitQuotedExprIn com bound v
476485 Helper.LibCall( com, " quotation" , " mkValue" , Any, [ emitted; makeStrConst " option" ])
477486 | None -> mkNullExpr com " option"
478487
479488 | NewList( headAndTail, _ typ) ->
480489 match headAndTail with
481490 | Some( head, tail) ->
482- let headExpr = emitQuotedExpr com head
483- let tailExpr = emitQuotedExpr com tail
491+ let headExpr = emitQuotedExprIn com bound head
492+ let tailExpr = emitQuotedExprIn com bound tail
484493 Helper.LibCall( com, " quotation" , " mkNewList" , Any, [ headExpr; tailExpr ])
485494 | None -> mkNullExpr com " list"
486495
@@ -549,3 +558,7 @@ and private mkExprArray (com: Compiler) (elements: Expr list) : Expr =
549558 match elements with
550559 | [] when com.Options.Language = Rust -> Helper.LibCall( com, " quotation" , " emptyExprArray" , Any, [])
551560 | _ -> makeArray Any elements
561+
562+ /// Entry point. Nothing is bound at the top of a quotation, so any identifier
563+ /// still free by the time it is reached is a captured local.
564+ let emitQuotedExpr ( com : Compiler ) ( expr : Expr ) : Expr = emitQuotedExprIn com Set.empty expr
0 commit comments