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 namespaces across multiple files #3581

Merged
merged 1 commit into from
Nov 5, 2023
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
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
Loading