Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust - Support [<Emit(type)>] on a type to assist with binding to built in types #2904

Merged
merged 2 commits into from
May 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Fable.Transforms/Rust/AST/Rust.AST.Helpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,10 @@ module Types =
mkGenericTypeArgs tys
|> mkGenericPathTy path

let mkExtMacroTy value tys: Ty =
TyKind.EmitTypeExpression(value, mkVec tys)
|> mkTy

let TODO_TYPE name: Ty =
mkGenericPathTy ["TODO_TYPE_" + name] None

Expand Down
144 changes: 73 additions & 71 deletions src/Fable.Transforms/Rust/AST/Rust.AST.State.fs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,75 @@ let visibility_qualified(vis: ast.Visibility, s: string): string =
// member self.deref_mut().Target =
// self.s

let print_emit_expr self value (args: Vec<_>, printArgs) =
let args = args.ToArray()
// printer.AddLocation(loc)

let inline replace pattern (f: System.Text.RegularExpressions.Match -> string) input =
System.Text.RegularExpressions.Regex.Replace(input, pattern, f)

let printSegment (printer: Pretty.Printer) (value: string) segmentStart segmentEnd =
let segmentLength = segmentEnd - segmentStart
if segmentLength > 0 then
let segment = value.Substring(segmentStart, segmentLength)
self.s.word(segment)

// Macro transformations
// https://fable.io/docs/communicate/js-from-fable.html#Emit-when-F-is-not-enough
let value =
value
|> replace @"\$(\d+)\.\.\." (fun m ->
let rep = ResizeArray()
let i = int m.Groups.[1].Value
for j = i to args.Length - 1 do
rep.Add("$" + string j)
String.concat ", " rep)

// |> replace @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" (fun m ->
// let i = int m.Groups.[1].Value
// match args.[i] with
// | Literal(BooleanLiteral(value=value)) when value -> m.Groups.[2].Value
// | _ -> m.Groups.[3].Value)

|> replace @"\{\{([^\}]*\$(\d+).*?)\}\}" (fun m ->
let i = int m.Groups.[2].Value
match Array.tryItem i args with
| Some _ -> m.Groups.[1].Value
| None -> "")

// If placeholder is followed by !, emit string literals as JS: "let $0! = $1"
// |> replace @"\$(\d+)!" (fun m ->
// let i = int m.Groups.[1].Value
// match Array.tryItem i args with
// | Some(Literal(Literal.StringLiteral(StringLiteral(value, _)))) -> value
// | _ -> "")

let matches = System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+")
if matches.Count > 0 then
for i = 0 to matches.Count - 1 do
let m = matches.[i]
let isSurroundedWithParens =
m.Index > 0
&& m.Index + m.Length < value.Length
&& value.[m.Index - 1] = '('
&& value.[m.Index + m.Length] = ')'

let segmentStart =
if i > 0 then matches.[i-1].Index + matches.[i-1].Length
else 0

printSegment self.s value segmentStart m.Index

let argIndex = int m.Value.[1..]
match Array.tryItem argIndex args with
| Some e -> printArgs e
| None -> self.s.word("undefined")

let lastMatch = matches.[matches.Count - 1]
printSegment self.s value (lastMatch.Index + lastMatch.Length) value.Length
else
printSegment self.s value 0 value.Length

type PrintState = State
// abstract comments: unit -> Option<Comments>
// abstract print_ident: ident: Ident -> unit
Expand Down Expand Up @@ -868,6 +937,9 @@ type State with
self.print_mac(m)
| ast.TyKind.CVarArgs ->
self.s.word("...")
| ast.TyKind.EmitTypeExpression(m, p) ->
print_emit_expr self m (p, self.print_type)
()
self.s.end_()

member self.print_foreign_item(item: ast.ForeignItem) =
Expand Down Expand Up @@ -1427,76 +1499,6 @@ type State with
m.span()
)

member self.print_emit_expr(value, args: Vec<Types.Expr>) =
let args = args.ToArray()
// printer.AddLocation(loc)

let inline replace pattern (f: System.Text.RegularExpressions.Match -> string) input =
System.Text.RegularExpressions.Regex.Replace(input, pattern, f)

let printSegment (printer: Pretty.Printer) (value: string) segmentStart segmentEnd =
let segmentLength = segmentEnd - segmentStart
if segmentLength > 0 then
let segment = value.Substring(segmentStart, segmentLength)
self.s.word(segment)

// Macro transformations
// https://fable.io/docs/communicate/js-from-fable.html#Emit-when-F-is-not-enough
let value =
value
|> replace @"\$(\d+)\.\.\." (fun m ->
let rep = ResizeArray()
let i = int m.Groups.[1].Value
for j = i to args.Length - 1 do
rep.Add("$" + string j)
String.concat ", " rep)

// |> replace @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" (fun m ->
// let i = int m.Groups.[1].Value
// match args.[i] with
// | Literal(BooleanLiteral(value=value)) when value -> m.Groups.[2].Value
// | _ -> m.Groups.[3].Value)

|> replace @"\{\{([^\}]*\$(\d+).*?)\}\}" (fun m ->
let i = int m.Groups.[2].Value
match Array.tryItem i args with
| Some _ -> m.Groups.[1].Value
| None -> "")

// If placeholder is followed by !, emit string literals as JS: "let $0! = $1"
// |> replace @"\$(\d+)!" (fun m ->
// let i = int m.Groups.[1].Value
// match Array.tryItem i args with
// | Some(Literal(Literal.StringLiteral(StringLiteral(value, _)))) -> value
// | _ -> "")

let matches = System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+")
if matches.Count > 0 then
for i = 0 to matches.Count - 1 do
let m = matches.[i]
let isSurroundedWithParens =
m.Index > 0
&& m.Index + m.Length < value.Length
&& value.[m.Index - 1] = '('
&& value.[m.Index + m.Length] = ')'

let segmentStart =
if i > 0 then matches.[i-1].Index + matches.[i-1].Length
else 0

printSegment self.s value segmentStart m.Index

let argIndex = int m.Value.[1..]
match Array.tryItem argIndex args with
| Some e when isSurroundedWithParens -> self.print_expr(e)
| Some e -> self.print_expr(e)
| None -> self.s.word("undefined")

let lastMatch = matches.[matches.Count - 1]
printSegment self.s value (lastMatch.Index + lastMatch.Length) value.Length
else
printSegment self.s value 0 value.Length

member self.print_call_post(args: Vec<P<ast.Expr>>) =
self.s.popen()
self.commasep_exprs(pp.Breaks.Inconsistent, args)
Expand Down Expand Up @@ -2018,7 +2020,7 @@ type State with
self.s.pclose()
| ast.ExprKind.MacCall(m) -> self.print_mac(m)
| ast.ExprKind.EmitExpression(e, args) ->
self.print_emit_expr(e, args)
print_emit_expr self e (args, self.print_expr)
| ast.ExprKind.Paren(e) ->
self.s.popen()
self.print_inner_attributes_inline(attrs)
Expand Down
5 changes: 4 additions & 1 deletion src/Fable.Transforms/Rust/AST/Rust.AST.Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,7 @@ type ExprKind =
/// Placeholder for an expression that wasn't syntactically well formed in some way.
| Err

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

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


/// Syntax used to declare a trait object.
[<RequireQualifiedAccess>]
Expand Down
26 changes: 21 additions & 5 deletions src/Fable.Transforms/Rust/Fable2Rust.fs
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,13 @@ module TypeInfo =
-> true

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

let isCloneableType (com: IRustCompiler) t =
match t with
Expand Down Expand Up @@ -588,11 +592,23 @@ module TypeInfo =
let bounds = [mkTypeTraitGenericBound pathNames genArgs]
mkDynTraitTy bounds

let (|HasEmitAttribute|_|) (ent: Fable.Entity) =
ent.Attributes |> Seq.tryPick (fun att ->
if att.Entity.FullName.StartsWith(Atts.emit) then
match att.ConstructorArgs with
| [:? string as macro] -> Some macro
| _ -> None
else None)

let transformDeclaredType (com: IRustCompiler) ctx entRef genArgs: Rust.Ty =
let ent = com.GetEntity(entRef)
if ent.IsInterface then
match com.GetEntity(entRef) with
| HasEmitAttribute tmpl ->
let genArgs = genArgs |> List.map (transformType com ctx)
mkExtMacroTy tmpl genArgs
| ent when ent.IsInterface = true ->
transformInterfaceType com ctx entRef genArgs
else

| ent ->
let genArgs = transformGenArgs com ctx genArgs
makeFullNamePathTy ent.FullName genArgs

Expand Down
32 changes: 25 additions & 7 deletions tests/Rust/tests/src/InteropTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module Subs =
[<Emit("$0 * $1")>]
let mul a b = jsNative

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

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

module Vec =
[<Erase>]
type VecT =
[<Emit("Rc<MutCell<Vec<$0>>>")>]
type VecT<'a> =
[<Emit("$0.get_mut().push($1)")>]
abstract Push: 'a -> unit
[<Emit("MutCell::from(std::vec::Vec::new())")>]
let create (): VecT = jsNative
[<Emit("Rc::from(MutCell::from(std::vec::Vec::new()))")>]
let create (): VecT<'a> = jsNative
[<Emit("$1.get_mut().push($0)")>]
let push item (vec: VecT) = jsNative
let push (item: 'a) (vec: VecT<'a>) = jsNative
[<Emit("{ $1.get_mut().append(&mut vec![$0]); $1 }")>]
let append item (vec: VecT): VecT = jsNative
let append (item: 'a) (vec: VecT<'a>): VecT<'a> = jsNative

[<Emit("$0.len()")>]
let len (vec: VecT<'a>): nativeint = jsNative

module FnExps =
let push42 (v: VecT<_>) =
v.Push 42
v

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

[<Fact>]
let ``vec instance type emit should work`` () =
let a = Subs.Vec.create()
a.Push 42

[<Fact>]
let ``vec instance pass over boundary should work`` () =
let a = Subs.Vec.create()
let res = Subs.Vec.FnExps.push42 a
res |> Subs.Vec.len |> int |> equal 1
#endif