Skip to content

Commit

Permalink
[Rust] Support namespaces across multiple files (#3581)
Browse files Browse the repository at this point in the history
  • Loading branch information
ncave authored Nov 5, 2023
1 parent 9f309c7 commit f606dc0
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 287 deletions.
13 changes: 7 additions & 6 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Changed

#### Python

* Use `Any` type for all non-repeated generic arguments (by @dbrattli)
* Don't generate unnecessary type type-vars if generic type is replaced by `Any` (by @dbrattli)
* Generate new style `_T | None` instead of `Optional[_T]` (by @dbrattli)

#### Rust

* Support multiple namespaces sharing a prefix in the same file (by @ncave)
* Support imports with the same namespace across multiple files (by @ncave)

### Fixed

#### JavaScript

* Fix #3571: `[<AttachMembers>]` not compatible with f# member `this.Item` (by @ncave)

### Changed

#### Rust

* Support multiple namespaces sharing a prefix in the same file (by @ncave)

## 4.4.1 - 2023-10-25

### Changed
Expand Down
8 changes: 8 additions & 0 deletions src/Fable.Transforms/Rust/AST/Rust.AST.Helpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,14 @@ module Attrs =
let kind = mkAttrKind name args
mkAttribute kind AttrStyle.Outer

let mkLineCommentAttr (comment: Symbol): Attribute =
let kind = AttrKind.DocComment(token.CommentKind.Line, comment)
mkAttribute kind AttrStyle.Outer

let mkBlockCommentAttr (comment: Symbol): Attribute =
let kind = AttrKind.DocComment(token.CommentKind.Block, comment)
mkAttribute kind AttrStyle.Outer

let mkEqAttr (name: Symbol) (value: Symbol): Attribute =
let args = MacArgs.Eq(DUMMY_SP, mkStrToken value)
let kind = mkAttrKind name args
Expand Down
147 changes: 119 additions & 28 deletions src/Fable.Transforms/Rust/Fable2Rust.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ type HashSet<'T> = System.Collections.Generic.HashSet<'T>
type Import = {
Selector: string
LocalIdent: string
ModuleName: string
ModulePath: string
Path: string
mutable Depths: int list
Expand Down Expand Up @@ -62,7 +61,9 @@ type IRustCompiler =
abstract WarnOnlyOnce: string * ?range: SourceLocation -> unit
abstract GetAllImports: Context -> Import list
abstract ClearAllImports: Context -> unit
abstract GetAllModules: unit -> (string * string) list
abstract GetAllModules: unit -> string list
abstract GetAllNamespaces: unit -> (string * string) list
abstract AddNamespace: string * string -> unit
abstract GetImportName: Context * selector: string * path: string * SourceLocation option -> string
abstract TransformExpr: Context * Fable.Expr -> Rust.Expr
abstract GetEntity: entRef: Fable.EntityRef -> Fable.Entity
Expand All @@ -80,6 +81,48 @@ module Helpers =
| Some old -> acc |> Map.add key (aggregateFn old value)
| None -> acc |> Map.add key value)


module Namespace =

type Trie<'K, 'V when 'K: comparison and 'V: comparison> = {
Values: Set<'V>
Children: Map<'K, Trie<'K, 'V>>
}

module Trie =
let empty = {
Values = Set.empty
Children = Map.empty
}

let isLeaf trie =
not (Set.isEmpty trie.Values)

let isEmpty trie =
Map.isEmpty trie.Children && not (isLeaf trie)

let rec add path value trie =
match path with
| [] ->
{ trie with Values = Set.add value trie.Values }
| x::xs ->
let child =
trie.Children
|> Map.tryFind x
|> Option.defaultValue empty
|> add xs value
let children =
trie.Children
|> Map.add x child
{ trie with Children = children }

let ofSeq (xs: (string * string) seq) =
(empty, xs)
||> Seq.fold (fun st (m, n) ->
let path = n.Split('.') |> List.ofArray
add path m st
)

module UsageTracking =

// type ConsumptionType =
Expand Down Expand Up @@ -3025,19 +3068,44 @@ module Util =
let relPath = Path.getRelativePath com.CurrentFile filePath
com.GetImportName(ctx, "*", relPath, None) |> ignore
)
let makeModItems (modulePath, moduleName) =
// re-export modules at crate level
let makeModItems modulePath =
let relPath = Path.getRelativePath com.CurrentFile modulePath
let modName = getImportModuleName com modulePath
let attrs = [mkEqAttr "path" relPath]
let modItem = mkUnloadedModItem attrs moduleName
let useItem = mkGlobUseItem [] [moduleName]
[modItem; useItem |> mkPublicItem] // re-export modules at top level
let modItem = mkUnloadedModItem attrs modName
let useItem = mkGlobUseItem [] [modName]
[modItem; useItem |> mkPublicItem]
let modItems =
com.GetAllModules()
|> List.sortBy fst
|> List.sort
|> List.collect makeModItems
modItems
else []

let getNamespaceItems (com: IRustCompiler) ctx =
if isLastFileInProject com then
// convert namespace trie to modules and glob use items
let rec toItems mods trie: Rust.Item list = [
if Namespace.Trie.isLeaf trie then
let modNames = List.rev mods
for filePath in trie.Values do
let modName = getImportModuleName com filePath
let useItem = mkGlobUseItem [] ("crate"::modName::modNames)
yield useItem |> mkPublicItem
for KeyValue(key, trie) in trie.Children do
let items = toItems (key::mods) trie
let modItem = mkModItem [] key items
yield modItem |> mkPublicItem
]
// re-export globally merged namespaces at crate level
let nsItems =
com.GetAllNamespaces()
|> Namespace.Trie.ofSeq
|> toItems []
nsItems
else []

let getEntryPointItems (com: IRustCompiler) ctx decls =
let entryPoint = decls |> List.tryPick (tryFindEntryPoint com)
match entryPoint with
Expand Down Expand Up @@ -4139,7 +4207,9 @@ module Util =
[] // don't output empty modules
else
let ent = com.GetEntity(decl.Entity)
// if ent.IsNamespace then // maybe do something different
if ent.IsNamespace then
// add the namespace to a global list to be re-exported
com.AddNamespace(com.CurrentFile, ent.FullName)
let useDecls =
let useItem = mkGlobUseItem [] ["super"]
let importItems = com.GetAllImports(ctx) |> transformImports com ctx
Expand Down Expand Up @@ -4177,9 +4247,18 @@ module Util =
transformClassDecl com ctx decl

let transformDeclarations (com: IRustCompiler) ctx decls =
decls
|> mergeNamespaceDecls com ctx
|> List.collect (transformDecl com ctx)
let items =
decls
|> mergeNamespaceDecls com ctx
|> List.collect (transformDecl com ctx)
// wrap the last file in a module to consistently handle namespaces
if isLastFileInProject com then
let modPath = fixFileExtension com com.CurrentFile
let modName = getImportModuleName com modPath
let modItem = mkModItem [] modName items
let useItem = mkGlobUseItem [] [modName]
[modItem; useItem |> mkPublicItem]
else items

// F# hash function is unstable and gives different results in different runs
// Taken from fable-library/Util.ts. Possible variant in https://stackoverflow.com/a/1660613
Expand Down Expand Up @@ -4208,7 +4287,7 @@ module Util =
modulePath

let getImportModuleName (com: IRustCompiler) (modulePath: string) =
let relPath = Path.getRelativePath com.CurrentFile modulePath
let relPath = Path.getRelativePath com.ProjectFile modulePath
System.String.Format("module_{0:x}", stableStringHash relPath)

let transformImports (com: IRustCompiler) ctx (imports: Import list): Rust.Item list =
Expand All @@ -4225,18 +4304,22 @@ module Util =
else
if isFableLibraryPath com import.Path
then ["fable_library_rust"]
else ["crate"; import.ModuleName]
else ["crate"]
match import.Selector with
| "" | "*" | "default" ->
mkGlobUseItem [] modPath
// let useItem = mkGlobUseItem [] modPath
// [useItem]
[]
| _ ->
let parts = splitNameParts import.Selector
let alias =
if List.last parts <> import.LocalIdent
then Some(import.LocalIdent)
else None
mkSimpleUseItem [] (modPath @ parts) alias
let useItem = mkSimpleUseItem [] (modPath @ parts) alias
[useItem]
)
|> List.concat
)

let getIdentForImport (ctx: Context) (path: string) (selector: string) =
Expand All @@ -4245,14 +4328,20 @@ module Util =
| _ -> splitNameParts selector |> List.last
|> getUniqueNameInRootScope ctx

let fixFileExtension (com: IRustCompiler) (path: string) =
if path.EndsWith(".fs") then
let fileExt = com.Options.FileExtension
Path.ChangeExtension(path, fileExt)
else path

module Compiler =
open System.Collections.Generic
open System.Collections.Concurrent
open Util

// global list of import modules (across files)
let importModules = ConcurrentDictionary<string, string>()
// global list of import modules and namespaces (across files)
let importModules = ConcurrentDictionary<string, bool>()
let importNamespaces = ConcurrentDictionary<string * string, bool>()

// per file
type RustCompiler (com: Fable.Compiler) =
Expand All @@ -4270,11 +4359,7 @@ module Compiler =
|> addError com [] r
let isMacro = selector.EndsWith("!")
let selector = selector |> Fable.Naming.replaceSuffix "!" ""
let path =
if path.EndsWith(".fs") then
let fileExt = (self :> Compiler).Options.FileExtension
Path.ChangeExtension(path, fileExt)
else path
let path = fixFileExtension self path
let cacheKey =
let selector = selector.Replace(".", "::").Replace("`", "_")
if (isFableLibraryPath self path)
Expand All @@ -4290,18 +4375,16 @@ module Compiler =
| false, _ ->
let localIdent = getIdentForImport ctx path selector
let modulePath = getImportModulePath self path
let moduleName = getImportModuleName self modulePath
let import = {
Selector = selector
LocalIdent = localIdent
ModuleName = moduleName
ModulePath = modulePath
Path = path
Depths = [ctx.ModuleDepth]
}
// add import module to a global list (across files)
if path.Length > 0 && not (isFableLibraryPath self path) then
importModules.TryAdd(modulePath, moduleName) |> ignore
importModules.TryAdd(modulePath, true) |> ignore

imports.Add(cacheKey, import)
import
Expand All @@ -4326,7 +4409,14 @@ module Compiler =
ctx.UsedNames.RootScope.Remove(import.Value.LocalIdent) |> ignore

member _.GetAllModules() =
importModules |> Seq.map (fun p -> p.Key, p.Value) |> Seq.toList
importModules.Keys |> Seq.toList

member _.GetAllNamespaces() =
importNamespaces.Keys |> Seq.toList

member self.AddNamespace(path, entFullName) =
let path = fixFileExtension self path
importNamespaces.TryAdd((path, entFullName), true) |> ignore

member com.TransformExpr(ctx, e) = transformExpr com ctx e

Expand Down Expand Up @@ -4413,8 +4503,9 @@ module Compiler =
let entryPointItems = file.Declarations |> getEntryPointItems com ctx
let importItems = com.GetAllImports(ctx) |> transformImports com ctx
let declItems = file.Declarations |> transformDeclarations com ctx
let moduleItems = getModuleItems com ctx // global module imports
let crateItems = importItems @ declItems @ moduleItems @ entryPointItems
let modItems = getModuleItems com ctx // global module imports
let nsItems = getNamespaceItems com ctx // global namespace imports
let crateItems = importItems @ declItems @ modItems @ nsItems @ entryPointItems
let innerAttrs = file.Declarations |> getInnerAttributes com ctx
let crateAttrs = topAttrs @ innerAttrs
let crate = mkCrate crateAttrs crateItems
Expand Down
2 changes: 2 additions & 0 deletions src/fable-library-rust/src/Fable.Library.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<ItemGroup>
<Compile Include="Global.fs" />
<Compile Include="System.fs" />
<Compile Include="System.Collections.Generic.fs" />
<Compile Include="System.Text.fs" />
<Compile Include="Interfaces.fs" />
<Compile Include="Range.fs" />
<Compile Include="Set.fs" />
Expand Down
Loading

0 comments on commit f606dc0

Please sign in to comment.