Skip to content

Commit d352e86

Browse files
dbrattliclaude
andauthored
[Python/Beam] Add F# quotation support (<@ ... @>) (#4398)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c39aac2 commit d352e86

26 files changed

Lines changed: 1771 additions & 7 deletions

src/Fable.AST/Fable.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,11 +836,13 @@ type Expr =
836836

837837
| Unresolved of expr: UnresolvedExpr * typ: Type * range: SourceLocation option
838838
| Extended of expr: ExtendedSet * range: SourceLocation option
839+
| Quote of quotedExpr: Expr * isTyped: bool * range: SourceLocation option
839840

840841
member this.Type =
841842
match this with
842843
| Unresolved(_, t, _) -> t
843844
| Extended(kind, _) -> kind.Type
845+
| Quote _ -> Any
844846
| Test _ -> Boolean
845847
| Value(kind, _) -> kind.Type
846848
| IdentExpr id -> id.Type
@@ -892,6 +894,7 @@ type Expr =
892894
| Set(_, _, _, _, r)
893895
| ForLoop(_, _, _, _, _, r)
894896
| WhileLoop(_, _, r) -> r
897+
| Quote(_, _, r) -> r
895898

896899
// module PrettyPrint =
897900
// let rec printType (t: Type) = "T" // TODO

src/Fable.Cli/CHANGELOG.md

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

1010
### Added
1111

12+
* [Python/Beam] Add F# quotation support — construction, pattern matching, and evaluation via `LeafExpressionConverter.EvaluateQuotation` (by @dbrattli)
1213
* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` (by @OnurGumus)
1314
* [All] Add missing `Array`, `List`, and `Seq` random choice/shuffle/sample members and tests (by @ncave)
1415
* [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
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

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

src/Fable.Transforms/Beam/Fable2Beam.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,10 @@ let rec transformExpr (com: IBeamCompiler) (ctx: Context) (expr: Expr) : Beam.Er
11751175
]
11761176
)
11771177

1178+
| Quote(body, _isTyped, _r) ->
1179+
let emitted = QuotationEmitter.emitQuotedExpr com body
1180+
transformExpr com ctx emitted
1181+
11781182
| Extended(kind, _range) ->
11791183
match kind with
11801184
| Throw(Some exprArg, _typ) ->

src/Fable.Transforms/Beam/Replacements.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5679,7 +5679,8 @@ let tryCall
56795679
| _ -> emitExpr r t [ c ] "maps:get(name, $0)" |> Some
56805680
| _ -> None
56815681
| "System.Text.StringBuilder" -> bclType com ctx r t info thisArg args
5682-
| _ -> None
5682+
// F# Quotations
5683+
| typeName -> Quotations.tryQuotationCall "quotation" com ctx r t info thisArg args typeName
56835684

56845685
let tryBaseConstructor
56855686
(_com: ICompiler)

src/Fable.Transforms/Dart/Fable2Dart.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,6 +2191,10 @@ module Util =
21912191
],
21922192
None
21932193

2194+
| Fable.Quote _ ->
2195+
addError com [] None "Quotations are not yet supported for Dart target"
2196+
[], None
2197+
21942198
let getLocalFunctionGenericParams
21952199
(_com: IDartCompiler)
21962200
(ctx: Context)

src/Fable.Transforms/FSharp2Fable.fs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,10 +1456,11 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs
14561456
$"Cannot compile ILFieldGet(%A{ownerTyp}, %s{fieldName})"
14571457
|> addErrorAndReturnNull com ctx.InlinePath (makeRangeFrom fsExpr)
14581458

1459-
| FSharpExprPatterns.Quote _ ->
1460-
return
1461-
"Quotes are not currently supported by Fable"
1462-
|> addErrorAndReturnNull com ctx.InlinePath (makeRangeFrom fsExpr)
1459+
| FSharpExprPatterns.Quote quotedExpr ->
1460+
let! body = transformExpr com ctx [] quotedExpr
1461+
let exprType = fsExpr.Type
1462+
let isTyped = exprType.GenericArguments.Count > 0
1463+
return Fable.Quote(body, isTyped, makeRangeFrom fsExpr)
14631464

14641465
| FSharpExprPatterns.AddressOf expr ->
14651466
let r = makeRangeFrom fsExpr
@@ -2475,6 +2476,8 @@ let resolveInlineExpr (com: IFableCompiler) ctx info expr =
24752476
|> makeValue r
24762477
| Fable.TypeInfo(t, d) -> Fable.TypeInfo(resolveInlineType ctx.GenericArgs t, d) |> makeValue r
24772478

2479+
| Fable.Quote(e, isTyped, r) -> Fable.Quote(resolveInlineExpr com ctx info e, isTyped, r)
2480+
24782481
| Fable.Extended(kind, r) as e ->
24792482
match kind with
24802483
| Fable.Curry(e, arity) -> Fable.Extended(Fable.Curry(resolveInlineExpr com ctx info e, arity), r)

src/Fable.Transforms/Fable.Transforms.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<Compile Include="Beam/Replacements.fs" />
2929
<Compile Include="Replacements.fs" />
3030
<Compile Include="Replacements.Api.fs" />
31+
<Compile Include="QuotationEmitter.fs" />
3132
<Compile Include="FSharp2Fable.fsi" />
3233
<Compile Include="FSharp2Fable.fs" />
3334
<Compile Include="FableTransforms.fsi" />

src/Fable.Transforms/Fable2Babel.fs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,7 @@ module Util =
11141114
| Fable.CurriedApply _
11151115
| Fable.Operation _
11161116
| Fable.Get _
1117+
| Fable.Quote _
11171118
| Fable.Test _ -> false
11181119

11191120
| Fable.TypeCast(e, _) -> isJsStatement ctx preferStatement e
@@ -3141,6 +3142,8 @@ but thanks to the optimisation done below we get
31413142
| Fable.Throw _
31423143
| Fable.Debugger -> iife com ctx expr
31433144

3145+
| Fable.Quote _ -> addErrorAndReturnNull com None "Quotations are not yet supported for JS/TS target"
3146+
31443147
let rec transformAsStatements (com: IBabelCompiler) ctx returnStrategy (expr: Fable.Expr) : Statement array =
31453148
match expr with
31463149
| Fable.Unresolved(_, _, r) ->
@@ -3308,6 +3311,10 @@ but thanks to the optimisation done below we get
33083311
)
33093312
|]
33103313

3314+
| Fable.Quote _ ->
3315+
addError com [] None "Quotations are not yet supported for JS/TS target"
3316+
[||]
3317+
33113318
let transformFunction com ctx name (args: Fable.Ident list) (body: Fable.Expr) : Parameter array * BlockStatement =
33123319
let tailcallChance =
33133320
Option.map (fun name -> NamedTailCallOpportunity(com, ctx, name, args) :> ITailCallOpportunity) name

src/Fable.Transforms/FableTransforms.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ let noSideEffectBeforeIdent identName expr =
159159
findIdentOrSideEffect e
160160
| Import _
161161
| Lambda _
162-
| Delegate _ -> false
162+
| Delegate _
163+
| Quote _ -> false
163164
| Extended((Throw _ | Debugger), _) -> true
164165
| Extended(Curry(e, _), _) -> findIdentOrSideEffect e
165166
| CurriedApply(callee, args, _, _) -> callee :: args |> findIdentOrSideEffectInList |> orSideEffect

0 commit comments

Comments
 (0)