diff --git a/src/Fable.Transforms/Rust/AST/Rust.AST.Helpers.fs b/src/Fable.Transforms/Rust/AST/Rust.AST.Helpers.fs index f657f9c293..3a88eac538 100644 --- a/src/Fable.Transforms/Rust/AST/Rust.AST.Helpers.fs +++ b/src/Fable.Transforms/Rust/AST/Rust.AST.Helpers.fs @@ -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 diff --git a/src/Fable.Transforms/Rust/AST/Rust.AST.State.fs b/src/Fable.Transforms/Rust/AST/Rust.AST.State.fs index 7bd31d99f9..4c280ec6a9 100644 --- a/src/Fable.Transforms/Rust/AST/Rust.AST.State.fs +++ b/src/Fable.Transforms/Rust/AST/Rust.AST.State.fs @@ -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 // abstract print_ident: ident: Ident -> unit @@ -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) = @@ -1427,76 +1499,6 @@ type State with m.span() ) - member self.print_emit_expr(value, args: Vec) = - 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>) = self.s.popen() self.commasep_exprs(pp.Breaks.Inconsistent, args) @@ -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) diff --git a/src/Fable.Transforms/Rust/AST/Rust.AST.Types.fs b/src/Fable.Transforms/Rust/AST/Rust.AST.Types.fs index 357455f72b..7c866bfb4d 100644 --- a/src/Fable.Transforms/Rust/AST/Rust.AST.Types.fs +++ b/src/Fable.Transforms/Rust/AST/Rust.AST.Types.fs @@ -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 /// The explicit `Self` type in a "qualified path". The actual @@ -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 + /// Syntax used to declare a trait object. [] diff --git a/src/Fable.Transforms/Rust/Fable2Rust.fs b/src/Fable.Transforms/Rust/Fable2Rust.fs index 7e623765cc..b2ac709826 100644 --- a/src/Fable.Transforms/Rust/Fable2Rust.fs +++ b/src/Fable.Transforms/Rust/Fable2Rust.fs @@ -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 @@ -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 diff --git a/tests/Rust/tests/src/InteropTests.fs b/tests/Rust/tests/src/InteropTests.fs index 4a6c935cad..c94a02a4d8 100644 --- a/tests/Rust/tests/src/InteropTests.fs +++ b/tests/Rust/tests/src/InteropTests.fs @@ -9,7 +9,7 @@ module Subs = [] let mul a b = jsNative - [] + [] let fixedVec a b = jsNative //doesn't currently work, but would be preferred @@ -19,16 +19,24 @@ module Subs = // member x.Push a = jsNative module Vec = - [] - type VecT = + [>>")>] + type VecT<'a> = [] abstract Push: 'a -> unit - [] - let create (): VecT = jsNative + [] + let create (): VecT<'a> = jsNative [] - let push item (vec: VecT) = jsNative + let push (item: 'a) (vec: VecT<'a>) = jsNative [] - let append item (vec: VecT): VecT = jsNative + let append (item: 'a) (vec: VecT<'a>): VecT<'a> = jsNative + + [] + let len (vec: VecT<'a>): nativeint = jsNative + + module FnExps = + let push42 (v: VecT<_>) = + v.Push 42 + v module Float = [] @@ -82,4 +90,14 @@ let ``vec instance mutable push should work`` () = b.Push 2 a |> equal b +[] +let ``vec instance type emit should work`` () = + let a = Subs.Vec.create() + a.Push 42 + +[] +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 \ No newline at end of file