Skip to content

Commit 9918458

Browse files
authored
[JS/TS] Add F# quotation support (#4474)
1 parent d160469 commit 9918458

12 files changed

Lines changed: 852 additions & 5 deletions

File tree

src/Fable.Cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
* [Python/Beam] Add F# quotation support — construction, pattern matching, and evaluation via `LeafExpressionConverter.EvaluateQuotation` (by @dbrattli)
13+
* [JS/TS] Add F# quotation support — construction, pattern matching, and evaluation (by @OnurGumus)
1314
* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` (by @OnurGumus)
1415
* [All] Add missing `Array`, `List`, and `Seq` random choice/shuffle/sample members and tests (by @ncave)
1516
* [Dart/Rust] Add missing `System.Random` implementations and tests (by @ncave)

src/Fable.Compiler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
* [Python/Beam] Add F# quotation support — construction, pattern matching, and evaluation via `LeafExpressionConverter.EvaluateQuotation` (by @dbrattli)
13+
* [JS/TS] Add F# quotation support — construction, pattern matching, and evaluation (by @OnurGumus)
1314
* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` (by @OnurGumus)
1415
* [All] Add missing `Array`, `List`, and `Seq` random choice/shuffle/sample members and tests (by @ncave)
1516
* [Dart/Rust] Add missing `System.Random` implementations and tests (by @ncave)

src/Fable.Core/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
12+
* Add `Fable.Core.Quotations` namespace with `QuotExpr` and `QuotVar` types for .NET-side deserialization of Fable quotation ASTs
13+
1014
## 5.0.0-rc.1 - 2026-02-26
1115

1216
### Added
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
namespace Fable.Core.Quotations
2+
3+
/// Serializable variable in a quotation expression.
4+
/// Matches the JSON shape produced by Fable's quotation runtime.
5+
type QuotVar =
6+
{
7+
Name: string
8+
Type: string
9+
IsMutable: bool
10+
}
11+
12+
/// Serializable quotation expression tree.
13+
/// Designed for cross-platform JSON deserialization (e.g. via Thoth.Json)
14+
/// of quotations constructed on Fable JS/TS clients.
15+
///
16+
/// JSON format: ["Tag", ...fields] arrays, e.g. ["Value", 42, "int32"]
17+
[<RequireQualifiedAccess>]
18+
type QuotExpr =
19+
| Value of value: obj * typeName: string
20+
| Var of var: QuotVar
21+
| Lambda of var: QuotVar * body: QuotExpr
22+
| Application of func: QuotExpr * arg: QuotExpr
23+
| Let of var: QuotVar * value: QuotExpr * body: QuotExpr
24+
| IfThenElse of guard: QuotExpr * thenExpr: QuotExpr * elseExpr: QuotExpr
25+
| Call of instance: QuotExpr option * methodName: string * args: QuotExpr array
26+
| Sequential of first: QuotExpr * second: QuotExpr
27+
| NewTuple of elements: QuotExpr array
28+
| TupleGet of tuple: QuotExpr * index: int
29+
| NewUnion of typeName: string * tag: int * fields: QuotExpr array
30+
| UnionTag of expr: QuotExpr
31+
| UnionField of expr: QuotExpr * index: int
32+
| NewRecord of fieldNames: string array * values: QuotExpr array
33+
| FieldGet of expr: QuotExpr * fieldName: string
34+
| FieldSet of expr: QuotExpr * fieldName: string * value: QuotExpr
35+
| VarSet of target: QuotExpr * value: QuotExpr
36+
| NewList of head: QuotExpr * tail: QuotExpr

src/Fable.Core/Fable.Core.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<Compile Include="Fable.Core.RustInterop.fs" />
2222
<Compile Include="Fable.Core.BeamInterop.fs" />
2323
<Compile Include="Fable.Core.Extensions.fs" />
24+
<Compile Include="Fable.Core.Quotations.fs" />
2425
<Content Include="CHANGELOG.md" />
2526
</ItemGroup>
2627
<ItemGroup>

src/Fable.Transforms/Fable2Babel.fs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3142,7 +3142,9 @@ but thanks to the optimisation done below we get
31423142
| Fable.Throw _
31433143
| Fable.Debugger -> iife com ctx expr
31443144

3145-
| Fable.Quote _ -> addErrorAndReturnNull com None "Quotations are not yet supported for JS/TS target"
3145+
| Fable.Quote(quotedExpr, _isTyped, _r) ->
3146+
let emitted = QuotationEmitter.emitQuotedExpr com quotedExpr
3147+
transformAsExpr com ctx emitted
31463148

31473149
let rec transformAsStatements (com: IBabelCompiler) ctx returnStrategy (expr: Fable.Expr) : Statement array =
31483150
match expr with
@@ -3311,9 +3313,9 @@ but thanks to the optimisation done below we get
33113313
)
33123314
|]
33133315

3314-
| Fable.Quote _ ->
3315-
addError com [] None "Quotations are not yet supported for JS/TS target"
3316-
[||]
3316+
| Fable.Quote(quotedExpr, _isTyped, _r) ->
3317+
let emitted = QuotationEmitter.emitQuotedExpr com quotedExpr
3318+
[| transformAsExpr com ctx emitted |> resolveExpr Fable.Any returnStrategy |]
33173319

33183320
let transformFunction com ctx name (args: Fable.Ident list) (body: Fable.Expr) : Parameter array * BlockStatement =
33193321
let tailcallChance =

src/Fable.Transforms/Replacements.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,8 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o
13091309
// Erased operators.
13101310
// KeyValuePair is already compiled as a tuple
13111311
| ("KeyValuePattern" | "Identity" | "Box" | "Unbox" | "ToEnum"), [ arg ] -> TypeCast(arg, t) |> Some
1312+
// Quotation splice: %expr inside a quotation. The arg is already a QuotExpr at runtime.
1313+
| ("SpliceExpression" | "SpliceUntypedExpression"), [ arg ] -> Some arg
13121314
// Cast to unit to make sure nothing is returned when wrapped in a lambda, see #1360
13131315
| "Ignore", _ -> TypeCast(args.Head, Unit) |> Some
13141316
// Number and String conversions
@@ -4344,7 +4346,8 @@ let tryCall (com: ICompiler) (ctx: Context) r t (info: CallInfo) (thisArg: Expr
43444346
getTypeName com ctx loc exprType |> StringConstant |> makeValue r |> Some
43454347
| c -> Helper.LibCall(com, "Reflection", "name", t, [ c ], ?loc = r) |> Some
43464348
| _ -> None
4347-
| _ -> None
4349+
// F# Quotations
4350+
| typeName -> Quotations.tryQuotationCall "quotation" com ctx r t info thisArg args typeName
43484351

43494352
let tryBaseConstructor com ctx (ent: EntityRef) (argTypes: Lazy<Type list>) genArgs args =
43504353
match ent.FullName with

src/fable-library-ts/Fable.Library.TypeScript.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
<TypeScriptCompile Include="Timer.ts" />
6666
<TypeScriptCompile Include="TimeSpan.ts" />
6767
<Content Include="tsconfig.json" />
68+
<TypeScriptCompile Include="quotation.ts" />
6869
<TypeScriptCompile Include="Types.ts" />
6970
<TypeScriptCompile Include="Unicode.13.0.0.ts" />
7071
<TypeScriptCompile Include="Uri.ts" />

0 commit comments

Comments
 (0)