Skip to content

Commit 3698b04

Browse files
authored
[Rust] Support Rust module imports (fable-compiler#2951)
* [Rust] Don't output empty modules * [Rust] Support direct imports
1 parent c1b2072 commit 3698b04

File tree

10 files changed

+133
-93
lines changed

10 files changed

+133
-93
lines changed

src/Fable.Core/Fable.Core.Rust.fs

+16-5
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,22 @@ type InnerAttrAttribute private (name: string, value: string option, items: stri
2020
new (name: string, value: string) = InnerAttrAttribute(name, Some value, [||])
2121
new (name: string, items: string[]) = InnerAttrAttribute(name, None, items)
2222

23-
24-
//Rc/arc control
25-
type RefType =
23+
//Rc/Arc control
24+
type PointerType =
2625
| Rc = 0
2726
| Arc = 1
27+
2828
// Rust - Defines the pointer type that is to be used to wrap the object (Rc/Arc)
29-
type ReferenceTypeAttribute(pointerType: RefType) =
30-
inherit Attribute()
29+
type ReferenceTypeAttribute(pointerType: PointerType) =
30+
inherit Attribute()
31+
32+
/// Destructure a tuple of arguments and apply them to literal code as with EmitAttribute.
33+
/// E.g. `emitExpr (arg1, arg2) "$0 + $1"` becomes `arg1 + arg2`
34+
let emitExpr<'T> (args: obj) (code: string): 'T = nativeOnly
35+
36+
/// Works like `ImportAttribute` (same semantics as Dart imports).
37+
/// You can use "*" selector.
38+
let import<'T> (selector: string) (path: string): 'T = nativeOnly
39+
40+
/// Imports a whole external module.
41+
let importAll<'T> (path: string): 'T = nativeOnly

src/Fable.Transforms/Rust/Fable2Rust.fs

+87-73
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type Import =
1414
{ Selector: string
1515
LocalIdent: string
1616
ModuleName: string
17-
FullPath: string
17+
ModulePath: string
1818
Path: string }
1919

2020
type ITailCallOpportunity =
@@ -627,12 +627,14 @@ module TypeInfo =
627627
| [:? string as macro] -> Some macro
628628
| _ -> None
629629
else None)
630+
630631
type PointerType =
631632
| Rc
632633
| Arc
634+
633635
let (|HasReferenceTypeAttribute|_|) (ent: Fable.Entity) =
634636
ent.Attributes |> Seq.tryPick (fun att ->
635-
if att.Entity.FullName.StartsWith(Atts.refType) then
637+
if att.Entity.FullName.StartsWith(Atts.referenceType) then
636638
match att.ConstructorArgs with
637639
| [:? int as ptrType] ->
638640
match ptrType with
@@ -2691,15 +2693,18 @@ module Util =
26912693
let relPath = Fable.Path.getRelativeFileOrDirPath false com.CurrentFile false filePath
26922694
com.GetImportName(ctx, "*", relPath, None) |> ignore
26932695
)
2694-
let makeModItems (fullPath, moduleName) =
2695-
let importPath = Fable.Path.getRelativePath com.CurrentFile fullPath
2696-
let attrs = [mkEqAttr "path" importPath]
2696+
let makeModItems (modulePath, moduleName) =
2697+
let relPath = Fable.Path.getRelativePath com.CurrentFile modulePath
2698+
let attrs = [mkEqAttr "path" relPath]
26972699
let modItem = mkUnloadedModItem attrs moduleName
26982700
let useItem = mkGlobUseItem [] [moduleName]
26992701
if isFableLibrary com
27002702
then [modItem; useItem |> mkPublicItem] // export modules at top level
27012703
else [modItem]
2702-
let modItems = com.GetAllModules() |> List.sortBy fst |> List.collect makeModItems
2704+
let modItems =
2705+
com.GetAllModules()
2706+
|> List.sortBy fst
2707+
|> List.collect makeModItems
27032708
modItems
27042709
else []
27052710

@@ -2709,14 +2714,14 @@ module Util =
27092714
| Some path ->
27102715
// add some imports for main
27112716
let asStr = getLibraryImportName com ctx "String" "string"
2712-
let asArr = getLibraryImportName com ctx "Native" "arrayFrom"
2717+
let asArr = getLibraryImportName com ctx "Native" "array"
27132718

27142719
// main entrypoint
27152720
let mainName = String.concat "::" path
27162721
let strBody = [
27172722
$"let args: Vec<String> = std::env::args().collect()"
27182723
$"let args: Vec<Rc<str>> = args[1..].iter().map(|s| {asStr}(s)).collect()"
2719-
$"{mainName}({asArr}(&args))"
2724+
$"{mainName}({asArr}(args))"
27202725
]
27212726
let fnBody = strBody |> Seq.map mkEmitSemiStmt |> mkBlock |> Some
27222727

@@ -3643,14 +3648,18 @@ module Util =
36433648

36443649
match decl with
36453650
| Fable.ModuleDeclaration decl ->
3646-
// TODO: perhaps collect other use decls from usage in body
3647-
let useItem = mkGlobUseItem [] ["super"]
3648-
let useDecls = [useItem]
36493651
let memberDecls = decl.Members |> List.collect (transformDecl com ctx)
3650-
let attrs = []
3651-
let modDecls = useDecls @ memberDecls
3652-
let modItem = modDecls |> mkModItem attrs decl.Name
3653-
[modItem |> mkPublicItem]
3652+
if List.isEmpty memberDecls then
3653+
[] // don't output empty modules
3654+
else
3655+
// TODO: perhaps collect other use decls from usage in body
3656+
let useItem = mkGlobUseItem [] ["super"]
3657+
let useDecls = [useItem]
3658+
let attrs = []
3659+
let modDecls = useDecls @ memberDecls
3660+
let modItem = modDecls |> mkModItem attrs decl.Name
3661+
[modItem |> mkPublicItem]
3662+
36543663

36553664
| Fable.ActionDeclaration decl ->
36563665
// TODO: use ItemKind.Static with IIFE closure?
@@ -3668,34 +3677,50 @@ module Util =
36683677
| Fable.ClassDeclaration decl ->
36693678
transformClassDecl com ctx decl
36703679

3671-
let getImportFullPath (com: IRustCompiler) (path: string) =
3672-
let isAbsolutePath =
3673-
path.StartsWith("/") || path.StartsWith("\\") || path.IndexOf(":") = 1
3674-
let isLibraryPath =
3675-
path.StartsWith(com.LibraryDir)
3676-
if isAbsolutePath || isLibraryPath then
3677-
Fable.Path.normalizePath path
3678-
else
3679-
let currentDir = Fable.Path.GetDirectoryName(com.CurrentFile)
3680-
Fable.Path.Combine(currentDir, path)
3681-
|> Fable.Path.normalizeFullPath
3680+
// F# hash function is unstable and gives different results in different runs
3681+
// Taken from fable-library/Util.ts. Possible variant in https://stackoverflow.com/a/1660613
3682+
let stableStringHash (s: string) =
3683+
let mutable h = 5381
3684+
for i = 0 to s.Length - 1 do
3685+
h <- (h * 33) ^^^ (int s.[i])
3686+
h
36823687

36833688
let isFableLibrary (com: IRustCompiler) =
36843689
List.contains "FABLE_LIBRARY" com.Options.Define //TODO: look in project defines too
36853690

3686-
let isFableLibraryImport (com: IRustCompiler) (path: string) =
3687-
not (isFableLibrary com) && path.StartsWith(com.LibraryDir)
3691+
let isFableLibraryPath (com: IRustCompiler) (path: string) =
3692+
not (isFableLibrary com) && (path.StartsWith(com.LibraryDir) || path = "fable_library_rust")
3693+
3694+
let getImportModulePath (com: IRustCompiler) (path: string) =
3695+
let isAbsolutePath =
3696+
path.StartsWith("/") || path.StartsWith("\\") || path.IndexOf(":") = 1
3697+
let modulePath =
3698+
if isAbsolutePath || (isFableLibraryPath com path) then
3699+
Fable.Path.normalizePath path
3700+
else
3701+
let currentDir = Fable.Path.GetDirectoryName(com.CurrentFile)
3702+
Fable.Path.Combine(currentDir, path)
3703+
|> Fable.Path.normalizeFullPath
3704+
modulePath
3705+
3706+
let getImportModuleName (com: IRustCompiler) (modulePath: string) =
3707+
System.String.Format("module_{0:x}", stableStringHash modulePath)
36883708

36893709
let transformImports (com: IRustCompiler) ctx (imports: Import list): Rust.Item list =
36903710
imports
3691-
|> List.groupBy (fun import -> import.FullPath)
3692-
|> List.collect (fun (_fullPath, moduleImports) ->
3711+
|> List.groupBy (fun import -> import.ModulePath)
3712+
|> List.sortBy (fun (modulePath, _) -> modulePath)
3713+
|> List.collect (fun (_modulePath, moduleImports) ->
36933714
moduleImports
3715+
|> List.sortBy (fun import -> import.Selector)
36943716
|> List.map (fun import ->
36953717
let modPath =
3696-
if isFableLibraryImport com import.Path
3697-
then ["fable_library_rust"]
3698-
else ["crate"; import.ModuleName]
3718+
if import.Path.Length = 0
3719+
then [] // empty path, means direct import of the selector
3720+
else
3721+
if isFableLibraryPath com import.Path
3722+
then ["fable_library_rust"]
3723+
else ["crate"; import.ModuleName]
36993724
match import.Selector with
37003725
| "" | "*" | "default" ->
37013726
mkGlobUseItem [] modPath
@@ -3715,24 +3740,13 @@ module Util =
37153740
| _ -> splitFullName selector |> List.last
37163741
|> getUniqueNameInRootScope ctx
37173742

3718-
// F# hash function is unstable and gives different results in different runs
3719-
// Taken from fable-library/Util.ts. Possible variant in https://stackoverflow.com/a/1660613
3720-
let stableStringHash (s: string) =
3721-
let mutable h = 5381
3722-
for i = 0 to s.Length - 1 do
3723-
h <- (h * 33) ^^^ (int s.[i])
3724-
h
3725-
3726-
let getModuleName fullPath =
3727-
System.String.Format("module_{0:x}", stableStringHash fullPath)
3728-
37293743

37303744
module Compiler =
37313745
open System.Collections.Generic
37323746
open System.Collections.Concurrent
37333747
open Util
37343748

3735-
// global level (across files)
3749+
// global list of import modules (across files)
37363750
let importModules = ConcurrentDictionary<string, string>()
37373751

37383752
// per file
@@ -3745,42 +3759,42 @@ module Compiler =
37453759
if onlyOnceWarnings.Add(msg) then
37463760
addWarning com [] range msg
37473761

3748-
member this.GetImportName(ctx, selector, path, r) =
3762+
member self.GetImportName(ctx, selector, path, r) =
37493763
if selector = Fable.Naming.placeholder then
37503764
"`importMember` must be assigned to a variable"
37513765
|> addError com [] r
37523766
let path = path |> Fable.Naming.replaceSuffix ".fs" ".rs"
3753-
if path.Contains("::") then
3754-
path + "::" + selector // direct Rust import
3755-
else
3756-
let cacheKey = path + "::" + selector
3757-
let import =
3758-
match imports.TryGetValue(cacheKey) with
3759-
| true, import -> import
3760-
| false, _ ->
3761-
let fullPath = getImportFullPath this path
3762-
let localIdent = getIdentForImport ctx path selector
3763-
let moduleName = getModuleName fullPath
3764-
let import = {
3765-
Selector = selector
3766-
LocalIdent = localIdent
3767-
ModuleName = moduleName
3768-
FullPath = fullPath
3769-
Path = path
3770-
}
3771-
if not (isFableLibraryImport this path) then
3772-
importModules.TryAdd(fullPath, moduleName) |> ignore
3773-
imports.Add(cacheKey, import)
3774-
import
3775-
$"{import.LocalIdent}"
3767+
let cacheKey =
3768+
if (isFableLibraryPath self path)
3769+
then "fable_library_rust::" + selector
3770+
elif path.Length = 0 then selector
3771+
else path + "::" + selector
3772+
let import =
3773+
match imports.TryGetValue(cacheKey) with
3774+
| true, import -> import
3775+
| false, _ ->
3776+
let localIdent = getIdentForImport ctx path selector
3777+
let modulePath = getImportModulePath self path
3778+
let moduleName = getImportModuleName self modulePath
3779+
let import = {
3780+
Selector = selector
3781+
LocalIdent = localIdent
3782+
ModuleName = moduleName
3783+
ModulePath = modulePath
3784+
Path = path
3785+
}
3786+
// add import module to a global list (across files)
3787+
if path.Length > 0 && not (isFableLibraryPath self path) then
3788+
importModules.TryAdd(modulePath, moduleName) |> ignore
3789+
3790+
imports.Add(cacheKey, import)
3791+
import
3792+
$"{import.LocalIdent}"
37763793

37773794
member _.GetAllImports() = imports.Values |> Seq.toList
37783795
member _.GetAllModules() = importModules |> Seq.map (fun p -> p.Key, p.Value) |> Seq.toList
37793796

3780-
member this.TransformAsExpr(ctx, e) = transformAsExpr this ctx e
3781-
// member this.TransformAsStatements(ctx, ret, e) = transformAsStatements this ctx ret e
3782-
// member this.TransformFunction(ctx, name, args, body) = transformFunction this ctx name args body
3783-
// member this.TransformImport(ctx, selector, path) = transformImport this ctx None selector path
3797+
member self.TransformAsExpr(ctx, e) = transformAsExpr self ctx e
37843798

37853799
member _.GetEntity(fullName) =
37863800
match com.TryGetEntity(fullName) with

src/Fable.Transforms/Rust/Replacements.fs

+10
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,16 @@ let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp
818818
| _, [RequireStringConst com ctx r selector; RequireStringConst com ctx r path] -> makeImportUserGenerated r t selector path |> Some
819819
| _ -> None
820820
| _ -> None
821+
| "Fable.Core.Rust", _ ->
822+
match i.CompiledName, args with
823+
| "import", [RequireStringConst com ctx r selector; RequireStringConst com ctx r path] ->
824+
makeImportUserGenerated r t selector path |> Some
825+
| "importAll", [RequireStringConst com ctx r path] ->
826+
makeImportUserGenerated r t "*" path |> Some
827+
| "emitExpr", [args; RequireStringConst com ctx r macro] ->
828+
let args = destructureTupleArgs [args]
829+
emitExpr r t args macro |> Some
830+
| _ -> None
821831
| _ -> None
822832

823833
let refCells (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) =

src/Fable.Transforms/Transforms.Util.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module Atts =
2929
let [<Literal>] inject = "Fable.Core.InjectAttribute" // typeof<Fable.Core.InjectAttribute>.FullName
3030
let [<Literal>] paramList = "Fable.Core.ParamListAttribute"// typeof<Fable.Core.ParamListAttribute>.FullName
3131
let [<Literal>] paramObject = "Fable.Core.ParamObjectAttribute"// typeof<Fable.Core.ParamObjectAttribute>.FullName
32-
let [<Literal>] refType = "Fable.Core.Rust.ReferenceTypeAttribute" // typeof<Fable.Core.PointerTypeAttribute>.FullName
32+
let [<Literal>] referenceType = "Fable.Core.Rust.ReferenceTypeAttribute" // typeof<Fable.Core.PointerTypeAttribute>.FullName
3333
let [<Literal>] jsDecorator = "Fable.Core.JS.DecoratorAttribute" // typeof<Fable.Core.JS.DecoratorAttribute>.FullName
3434
let [<Literal>] jsReflectedDecorator = "Fable.Core.JS.ReflectedDecoratorAttribute" // typeof<Fable.Core.JS.ReflectedDecoratorAttribute>.FullName
3535
let [<Literal>] jsxComponent = "Fable.Core.JSX.ComponentAttribute" // typeof<Fable.Core.JSX.ComponentAttribute>.FullName

src/fable-library-rust/src/Native.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,14 @@ pub mod Native_ {
7272
mkRefMut(x)
7373
}
7474

75-
#[inline]
76-
pub fn array<T: Clone>(v: Vec<T>) -> Array<T> {
77-
mkRefMut(v)
78-
}
79-
8075
// -----------------------------------------------------------
8176
// Arrays
8277
// -----------------------------------------------------------
8378

79+
pub fn array<T: Clone>(v: Vec<T>) -> Array<T> {
80+
mkRefMut(v)
81+
}
82+
8483
pub fn arrayEmpty<T: Clone>() -> Array<T> {
8584
array(Vec::new())
8685
}

src/fable-library-rust/src/String.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub mod String_ {
9191
string(s.trim_matches(c))
9292
}
9393

94-
pub fn trimChars(s: string, a: Array<char> ) -> string {
94+
pub fn trimChars(s: string, a: Array<char>) -> string {
9595
string(s.trim_matches(a.as_slice()))
9696
}
9797

@@ -103,7 +103,7 @@ pub mod String_ {
103103
string(s.trim_end_matches(c))
104104
}
105105

106-
pub fn trimEndChars(s: string, a: Array<char> ) -> string {
106+
pub fn trimEndChars(s: string, a: Array<char>) -> string {
107107
string(s.trim_end_matches(a.as_slice()))
108108
}
109109

@@ -115,7 +115,7 @@ pub mod String_ {
115115
string(s.trim_start_matches(c))
116116
}
117117

118-
pub fn trimStartChars(s: string, a: Array<char> ) -> string {
118+
pub fn trimStartChars(s: string, a: Array<char>) -> string {
119119
string(s.trim_start_matches(a.as_slice()))
120120
}
121121

@@ -165,7 +165,6 @@ pub mod String_ {
165165
string(&[left.as_ref(), right.as_ref()].concat())
166166
}
167167

168-
169168
fn toIndex(offset: i32, opt: Option<(&str, &str)>) -> i32 {
170169
match opt {
171170
Some((s, _)) => offset + s.chars().count() as i32,

src/fable-library-rust/src/lib.fs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
module Fable_Library_Rust
22

3-
//this is currently needed because nothing in Async is directly referenced by the library.
4-
let placeholderAsync:unit = Fable.Core.JsInterop.importAll "./Async.rs"
3+
open Fable.Core.Rust
4+
5+
// import helper that imports but doesn't emit anything except ()
6+
let inline private importAll path = emitExpr (importAll path) "()"
7+
8+
// import native modules (not referenced elsewhere)
9+
let imports(): unit =
10+
importAll "./Async.rs"
11+
importAll "./Func.rs"

tests/Rust/tests/src/AsyncTests.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ let shouldConvertTaskToASyncAndEvalCorrectly () =
4949
// let t = Async.StartAsTask comp
5050
// t.Result |> equal 5
5151

52-
[<Fable.Core.Rust.ReferenceType(Fable.Core.Rust.RefType.Arc)>]
52+
[<Fable.Core.Rust.ReferenceType(Fable.Core.Rust.PointerType.Arc)>]
5353
type ArcRecord = {
5454
A: int
5555
}

tests/Rust/tests/src/RecordTests.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ let ``Records with ref-type interior mutability`` () =
146146
x.MutRefValue <- x.MutRefValue + "c"
147147
x.MutRefValue |> equal "abc"
148148

149-
[<Fable.Core.Rust.ReferenceType(Fable.Core.Rust.RefType.Arc)>]
149+
[<Fable.Core.Rust.ReferenceType(Fable.Core.Rust.PointerType.Arc)>]
150150
type ArcRecord = {
151151
a: int
152152
b: string

0 commit comments

Comments
 (0)