Skip to content

Commit 5cfdadb

Browse files
authored
[Rust] - Support [<Emit(type)>] on a type (#2904)
* Working * Emit no longer implicitly wraps an Emit type in an Rc
1 parent de89ab1 commit 5cfdadb

File tree

5 files changed

+127
-84
lines changed

5 files changed

+127
-84
lines changed

src/Fable.Transforms/Rust/AST/Rust.AST.Helpers.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,10 @@ module Types =
11001100
mkGenericTypeArgs tys
11011101
|> mkGenericPathTy path
11021102

1103+
let mkExtMacroTy value tys: Ty =
1104+
TyKind.EmitTypeExpression(value, mkVec tys)
1105+
|> mkTy
1106+
11031107
let TODO_TYPE name: Ty =
11041108
mkGenericPathTy ["TODO_TYPE_" + name] None
11051109

src/Fable.Transforms/Rust/AST/Rust.AST.State.fs

Lines changed: 73 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,75 @@ let visibility_qualified(vis: ast.Visibility, s: string): string =
236236
// member self.deref_mut().Target =
237237
// self.s
238238

239+
let print_emit_expr self value (args: Vec<_>, printArgs) =
240+
let args = args.ToArray()
241+
// printer.AddLocation(loc)
242+
243+
let inline replace pattern (f: System.Text.RegularExpressions.Match -> string) input =
244+
System.Text.RegularExpressions.Regex.Replace(input, pattern, f)
245+
246+
let printSegment (printer: Pretty.Printer) (value: string) segmentStart segmentEnd =
247+
let segmentLength = segmentEnd - segmentStart
248+
if segmentLength > 0 then
249+
let segment = value.Substring(segmentStart, segmentLength)
250+
self.s.word(segment)
251+
252+
// Macro transformations
253+
// https://fable.io/docs/communicate/js-from-fable.html#Emit-when-F-is-not-enough
254+
let value =
255+
value
256+
|> replace @"\$(\d+)\.\.\." (fun m ->
257+
let rep = ResizeArray()
258+
let i = int m.Groups.[1].Value
259+
for j = i to args.Length - 1 do
260+
rep.Add("$" + string j)
261+
String.concat ", " rep)
262+
263+
// |> replace @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" (fun m ->
264+
// let i = int m.Groups.[1].Value
265+
// match args.[i] with
266+
// | Literal(BooleanLiteral(value=value)) when value -> m.Groups.[2].Value
267+
// | _ -> m.Groups.[3].Value)
268+
269+
|> replace @"\{\{([^\}]*\$(\d+).*?)\}\}" (fun m ->
270+
let i = int m.Groups.[2].Value
271+
match Array.tryItem i args with
272+
| Some _ -> m.Groups.[1].Value
273+
| None -> "")
274+
275+
// If placeholder is followed by !, emit string literals as JS: "let $0! = $1"
276+
// |> replace @"\$(\d+)!" (fun m ->
277+
// let i = int m.Groups.[1].Value
278+
// match Array.tryItem i args with
279+
// | Some(Literal(Literal.StringLiteral(StringLiteral(value, _)))) -> value
280+
// | _ -> "")
281+
282+
let matches = System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+")
283+
if matches.Count > 0 then
284+
for i = 0 to matches.Count - 1 do
285+
let m = matches.[i]
286+
let isSurroundedWithParens =
287+
m.Index > 0
288+
&& m.Index + m.Length < value.Length
289+
&& value.[m.Index - 1] = '('
290+
&& value.[m.Index + m.Length] = ')'
291+
292+
let segmentStart =
293+
if i > 0 then matches.[i-1].Index + matches.[i-1].Length
294+
else 0
295+
296+
printSegment self.s value segmentStart m.Index
297+
298+
let argIndex = int m.Value.[1..]
299+
match Array.tryItem argIndex args with
300+
| Some e -> printArgs e
301+
| None -> self.s.word("undefined")
302+
303+
let lastMatch = matches.[matches.Count - 1]
304+
printSegment self.s value (lastMatch.Index + lastMatch.Length) value.Length
305+
else
306+
printSegment self.s value 0 value.Length
307+
239308
type PrintState = State
240309
// abstract comments: unit -> Option<Comments>
241310
// abstract print_ident: ident: Ident -> unit
@@ -868,6 +937,9 @@ type State with
868937
self.print_mac(m)
869938
| ast.TyKind.CVarArgs ->
870939
self.s.word("...")
940+
| ast.TyKind.EmitTypeExpression(m, p) ->
941+
print_emit_expr self m (p, self.print_type)
942+
()
871943
self.s.end_()
872944

873945
member self.print_foreign_item(item: ast.ForeignItem) =
@@ -1427,76 +1499,6 @@ type State with
14271499
m.span()
14281500
)
14291501

1430-
member self.print_emit_expr(value, args: Vec<Types.Expr>) =
1431-
let args = args.ToArray()
1432-
// printer.AddLocation(loc)
1433-
1434-
let inline replace pattern (f: System.Text.RegularExpressions.Match -> string) input =
1435-
System.Text.RegularExpressions.Regex.Replace(input, pattern, f)
1436-
1437-
let printSegment (printer: Pretty.Printer) (value: string) segmentStart segmentEnd =
1438-
let segmentLength = segmentEnd - segmentStart
1439-
if segmentLength > 0 then
1440-
let segment = value.Substring(segmentStart, segmentLength)
1441-
self.s.word(segment)
1442-
1443-
// Macro transformations
1444-
// https://fable.io/docs/communicate/js-from-fable.html#Emit-when-F-is-not-enough
1445-
let value =
1446-
value
1447-
|> replace @"\$(\d+)\.\.\." (fun m ->
1448-
let rep = ResizeArray()
1449-
let i = int m.Groups.[1].Value
1450-
for j = i to args.Length - 1 do
1451-
rep.Add("$" + string j)
1452-
String.concat ", " rep)
1453-
1454-
// |> replace @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" (fun m ->
1455-
// let i = int m.Groups.[1].Value
1456-
// match args.[i] with
1457-
// | Literal(BooleanLiteral(value=value)) when value -> m.Groups.[2].Value
1458-
// | _ -> m.Groups.[3].Value)
1459-
1460-
|> replace @"\{\{([^\}]*\$(\d+).*?)\}\}" (fun m ->
1461-
let i = int m.Groups.[2].Value
1462-
match Array.tryItem i args with
1463-
| Some _ -> m.Groups.[1].Value
1464-
| None -> "")
1465-
1466-
// If placeholder is followed by !, emit string literals as JS: "let $0! = $1"
1467-
// |> replace @"\$(\d+)!" (fun m ->
1468-
// let i = int m.Groups.[1].Value
1469-
// match Array.tryItem i args with
1470-
// | Some(Literal(Literal.StringLiteral(StringLiteral(value, _)))) -> value
1471-
// | _ -> "")
1472-
1473-
let matches = System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+")
1474-
if matches.Count > 0 then
1475-
for i = 0 to matches.Count - 1 do
1476-
let m = matches.[i]
1477-
let isSurroundedWithParens =
1478-
m.Index > 0
1479-
&& m.Index + m.Length < value.Length
1480-
&& value.[m.Index - 1] = '('
1481-
&& value.[m.Index + m.Length] = ')'
1482-
1483-
let segmentStart =
1484-
if i > 0 then matches.[i-1].Index + matches.[i-1].Length
1485-
else 0
1486-
1487-
printSegment self.s value segmentStart m.Index
1488-
1489-
let argIndex = int m.Value.[1..]
1490-
match Array.tryItem argIndex args with
1491-
| Some e when isSurroundedWithParens -> self.print_expr(e)
1492-
| Some e -> self.print_expr(e)
1493-
| None -> self.s.word("undefined")
1494-
1495-
let lastMatch = matches.[matches.Count - 1]
1496-
printSegment self.s value (lastMatch.Index + lastMatch.Length) value.Length
1497-
else
1498-
printSegment self.s value 0 value.Length
1499-
15001502
member self.print_call_post(args: Vec<P<ast.Expr>>) =
15011503
self.s.popen()
15021504
self.commasep_exprs(pp.Breaks.Inconsistent, args)
@@ -2018,7 +2020,7 @@ type State with
20182020
self.s.pclose()
20192021
| ast.ExprKind.MacCall(m) -> self.print_mac(m)
20202022
| ast.ExprKind.EmitExpression(e, args) ->
2021-
self.print_emit_expr(e, args)
2023+
print_emit_expr self e (args, self.print_expr)
20222024
| ast.ExprKind.Paren(e) ->
20232025
self.s.popen()
20242026
self.print_inner_attributes_inline(attrs)

src/Fable.Transforms/Rust/AST/Rust.AST.Types.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ type ExprKind =
964964
/// Placeholder for an expression that wasn't syntactically well formed in some way.
965965
| Err
966966

967-
/// Escape hatch to allow adding custom macros - This is not in the core rust AST - Use with caution!!!
967+
/// Escape hatch to allow adding custom macros - This is |not in the core rust AST - Use with caution!!!
968968
| EmitExpression of value: string * args: Vec<Expr>
969969

970970
/// The explicit `Self` type in a "qualified path". The actual
@@ -1232,6 +1232,9 @@ type TyKind =
12321232
| Err
12331233
/// Placeholder for a `va_list`.
12341234
| CVarArgs
1235+
/// Escape hatch to allow adding custom macros - This is not in the core rust AST - Use with caution!!!
1236+
| EmitTypeExpression of value: string * args: Vec<Ty>
1237+
12351238

12361239
/// Syntax used to declare a trait object.
12371240
[<RequireQualifiedAccess>]

src/Fable.Transforms/Rust/Fable2Rust.fs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,13 @@ module TypeInfo =
378378
-> true
379379

380380
// conditionally Rc-wrapped
381-
| Replacements.Util.Builtin (Replacements.Util.FSharpChoice _)
382-
| Fable.DeclaredType _ ->
381+
| Replacements.Util.Builtin (Replacements.Util.FSharpChoice _) ->
383382
not (isCopyableType com Set.empty t)
383+
| Fable.DeclaredType (entRef, _) ->
384+
match com.GetEntity(entRef) with
385+
| HasEmitAttribute _ -> false // do not make custom types Rc-wrapped by default. This prevents inconsistency between type and implementation emit
386+
| _ ->
387+
not (isCopyableType com Set.empty t)
384388

385389
let isCloneableType (com: IRustCompiler) t =
386390
match t with
@@ -588,11 +592,23 @@ module TypeInfo =
588592
let bounds = [mkTypeTraitGenericBound pathNames genArgs]
589593
mkDynTraitTy bounds
590594

595+
let (|HasEmitAttribute|_|) (ent: Fable.Entity) =
596+
ent.Attributes |> Seq.tryPick (fun att ->
597+
if att.Entity.FullName.StartsWith(Atts.emit) then
598+
match att.ConstructorArgs with
599+
| [:? string as macro] -> Some macro
600+
| _ -> None
601+
else None)
602+
591603
let transformDeclaredType (com: IRustCompiler) ctx entRef genArgs: Rust.Ty =
592-
let ent = com.GetEntity(entRef)
593-
if ent.IsInterface then
604+
match com.GetEntity(entRef) with
605+
| HasEmitAttribute tmpl ->
606+
let genArgs = genArgs |> List.map (transformType com ctx)
607+
mkExtMacroTy tmpl genArgs
608+
| ent when ent.IsInterface = true ->
594609
transformInterfaceType com ctx entRef genArgs
595-
else
610+
611+
| ent ->
596612
let genArgs = transformGenArgs com ctx genArgs
597613
makeFullNamePathTy ent.FullName genArgs
598614

tests/Rust/tests/src/InteropTests.fs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module Subs =
99
[<Emit("$0 * $1")>]
1010
let mul a b = jsNative
1111

12-
[<Emit("{ let mut v = std::vec::Vec::new(); v.append(&mut vec![$0,$1]); MutCell::from(v) }")>]
12+
[<Emit("{ let mut v = std::vec::Vec::new(); v.append(&mut vec![$0,$1]); Rc::from(MutCell::from(v)) }")>]
1313
let fixedVec a b = jsNative
1414

1515
//doesn't currently work, but would be preferred
@@ -19,16 +19,24 @@ module Subs =
1919
// member x.Push a = jsNative
2020

2121
module Vec =
22-
[<Erase>]
23-
type VecT =
22+
[<Emit("Rc<MutCell<Vec<$0>>>")>]
23+
type VecT<'a> =
2424
[<Emit("$0.get_mut().push($1)")>]
2525
abstract Push: 'a -> unit
26-
[<Emit("MutCell::from(std::vec::Vec::new())")>]
27-
let create (): VecT = jsNative
26+
[<Emit("Rc::from(MutCell::from(std::vec::Vec::new()))")>]
27+
let create (): VecT<'a> = jsNative
2828
[<Emit("$1.get_mut().push($0)")>]
29-
let push item (vec: VecT) = jsNative
29+
let push (item: 'a) (vec: VecT<'a>) = jsNative
3030
[<Emit("{ $1.get_mut().append(&mut vec![$0]); $1 }")>]
31-
let append item (vec: VecT): VecT = jsNative
31+
let append (item: 'a) (vec: VecT<'a>): VecT<'a> = jsNative
32+
33+
[<Emit("$0.len()")>]
34+
let len (vec: VecT<'a>): nativeint = jsNative
35+
36+
module FnExps =
37+
let push42 (v: VecT<_>) =
38+
v.Push 42
39+
v
3240

3341
module Float =
3442
[<Emit("$0.sin()")>]
@@ -82,4 +90,14 @@ let ``vec instance mutable push should work`` () =
8290
b.Push 2
8391
a |> equal b
8492

93+
[<Fact>]
94+
let ``vec instance type emit should work`` () =
95+
let a = Subs.Vec.create()
96+
a.Push 42
97+
98+
[<Fact>]
99+
let ``vec instance pass over boundary should work`` () =
100+
let a = Subs.Vec.create()
101+
let res = Subs.Vec.FnExps.push42 a
102+
res |> Subs.Vec.len |> int |> equal 1
85103
#endif

0 commit comments

Comments
 (0)