Skip to content

Commit 151db12

Browse files
[Python] Import fixes (#4035)
* [Python] Fix #3965: importSideEffects shouldn't generate identifier * [Python] Fix #3481: Resolve also paths for non-qualified imports * [Python] Ignore backwards paths in imports & tests * Update changelog --------- Co-authored-by: Maxime Mangel <[email protected]>
1 parent 1e91221 commit 151db12

File tree

8 files changed

+119
-58
lines changed

8 files changed

+119
-58
lines changed

src/Fable.Cli/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
* [Python] - Print root module and module function comments (by @alfonsogarciacaro)
12+
* [Python] Print root module and module function comments (by @alfonsogarciacaro)
1313
* [Rust] Add support for module comments (by @ncave)
1414
* [Rust] Add support for null strings (by @ncave)
1515

1616
### Fixed
1717

1818
* [JS/TS] - Fix anonymous record printing (#4029) (by @alfonsogarciacaro)
1919
* [Python] - Fix #3998: PhysicalEquality (by @alfonsogarciacaro)
20+
* [Python] Resolve relative paths for non-qualified imports (#3481) (by @alfonsogarciacaro)
21+
* [Python] `importSideEffects` shouldn't generate identifier (#3965) (by @alfonsogarciacaro)
2022
* [JS/TS] Fix #4031: Hoist vars locally in for and while loops (@alfonsogarciacaro)
2123

2224
## 5.0.0-alpha.9 - 2025-01-28

src/Fable.Cli/Pipeline.fs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,8 @@ module Python =
252252
|> Array.choose (fun part ->
253253
i <- i + 1
254254

255-
if part = "." then
255+
if part = "." || part = ".." then
256256
None
257-
elif part = ".." then
258-
Some ""
259257
elif i = parts.Length - 1 then
260258
Some(normalizeFileName part)
261259
else

src/Fable.Compiler/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717

1818
* [JS/TS] - Fix anonymous record printing (#4029) (by @alfonsogarciacaro)
1919
* [Python] - Fix #3998: PhysicalEquality (by @alfonsogarciacaro)
20+
* [Python] Resolve relative paths for non-qualified imports (#3481) (by @alfonsogarciacaro)
21+
* [Python] `importSideEffects` shouldn't generate identifier (#3965) (by @alfonsogarciacaro)
2022
* [JS/TS] Fix #4031: Hoist vars locally in for and while loops (@alfonsogarciacaro)
2123

2224
## 5.0.0-alpha.9 - 2025-01-28

src/Fable.Transforms/Python/Fable2Python.fs

Lines changed: 59 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type ReturnStrategy =
2121
type Import =
2222
{
2323
Module: string
24-
LocalIdent: Identifier option
24+
LocalIdent: Identifier
2525
Name: string option
2626
}
2727

@@ -4249,17 +4249,17 @@ module Util =
42494249
match im.Name with
42504250
| Some "*"
42514251
| Some "default" ->
4252-
let (Identifier local) = im.LocalIdent.Value
4252+
let (Identifier local) = im.LocalIdent
42534253

42544254
if moduleName <> local then
4255-
Some moduleName, Alias.alias im.LocalIdent.Value
4255+
Some moduleName, Alias.alias im.LocalIdent
42564256
else
4257-
None, Alias.alias im.LocalIdent.Value
4257+
None, Alias.alias im.LocalIdent
42584258
| Some name ->
42594259
let name = Naming.toSnakeCase name
42604260

4261-
Some moduleName, Alias.alias (Identifier(Helpers.clean name), ?asname = im.LocalIdent)
4262-
| None -> None, Alias.alias (Identifier(moduleName), ?asname = im.LocalIdent)
4261+
Some moduleName, Alias.alias (Identifier(Helpers.clean name), asname = im.LocalIdent)
4262+
| None -> None, Alias.alias (Identifier(moduleName), asname = im.LocalIdent)
42634263
)
42644264
|> List.groupBy fst
42654265
|> List.map (fun (a, b) -> a, List.map snd b)
@@ -4290,16 +4290,9 @@ module Util =
42904290
let getIdentForImport (ctx: Context) (moduleName: string) (name: string option) =
42914291
// printfn "getIdentForImport: %A" (moduleName, name)
42924292
match name with
4293-
| None -> Path.GetFileNameWithoutExtension(moduleName) |> Identifier |> Some
4294-
| Some name ->
4295-
match name with
4296-
| "default"
4297-
| "*" -> Path.GetFileNameWithoutExtension(moduleName)
4298-
| _ -> name
4299-
|> Naming.toSnakeCase
4300-
|> getUniqueNameInRootScope ctx
4301-
|> Identifier
4302-
|> Some
4293+
| None -> Path.GetFileNameWithoutExtension(moduleName)
4294+
| Some name -> name |> Naming.toSnakeCase |> getUniqueNameInRootScope ctx
4295+
|> Identifier
43034296

43044297
module Compiler =
43054298
open Util
@@ -4317,47 +4310,59 @@ module Compiler =
43174310

43184311
member _.GetImportExpr(ctx, moduleName, ?name, ?r) =
43194312
// printfn "GetImportExpr: %A" (moduleName, name)
4320-
let cachedName = moduleName + "::" + defaultArg name "module"
4313+
let name =
4314+
match name with
4315+
| None
4316+
| Some null -> ""
4317+
| Some name -> name.Trim()
4318+
4319+
let isQualifiedPythonImport =
4320+
match name with
4321+
| ""
4322+
| "default"
4323+
| "*" -> false
4324+
| _ -> true
4325+
4326+
let cachedName =
4327+
moduleName
4328+
+ "::"
4329+
+ if isQualifiedPythonImport then
4330+
name
4331+
else
4332+
""
43214333

43224334
match imports.TryGetValue(cachedName) with
4323-
| true, i ->
4324-
match i.LocalIdent with
4325-
| Some localIdent -> Expression.identifier localIdent
4326-
| None -> Expression.none
4335+
| true, i -> i.LocalIdent |> Expression.identifier
43274336
| false, _ ->
4328-
let local_id = getIdentForImport ctx moduleName name
4329-
4330-
match name with
4331-
| Some "*"
4332-
| None ->
4333-
let i =
4334-
{
4335-
Name = None
4336-
Module = moduleName
4337-
LocalIdent = local_id
4338-
}
4339-
4340-
imports.Add(cachedName, i)
4341-
| Some name ->
4342-
let i =
4343-
{
4344-
Name =
4345-
if name = Naming.placeholder then
4346-
"`importMember` must be assigned to a variable" |> addError com [] r
4347-
4348-
name
4349-
else
4350-
name
4351-
|> Some
4352-
Module = moduleName
4353-
LocalIdent = local_id
4354-
}
4355-
4356-
imports.Add(cachedName, i)
4357-
4358-
match local_id with
4359-
| Some localId -> Expression.identifier localId
4360-
| None -> Expression.none
4337+
let local_id =
4338+
if isQualifiedPythonImport then
4339+
Some name
4340+
else
4341+
None
4342+
|> getIdentForImport ctx moduleName
4343+
4344+
let importName =
4345+
match name with
4346+
| _ when not isQualifiedPythonImport -> None
4347+
| Naming.placeholder ->
4348+
"`importMember` must be assigned to a variable" |> addError com [] r
4349+
Some name
4350+
| _ -> Some name
4351+
4352+
let i =
4353+
{
4354+
Name = importName
4355+
Module = moduleName
4356+
LocalIdent = local_id
4357+
}
4358+
4359+
imports.Add(cachedName, i)
4360+
4361+
// If the import member is empty we understand this is done for side-effects only
4362+
if name = "" then
4363+
Expression.none
4364+
else
4365+
Expression.identifier local_id
43614366

43624367
member _.GetAllImports() =
43634368
imports.Values :> Import seq |> List.ofSeq

src/Fable.Transforms/Python/PythonPrinter.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,12 @@ let run writer (program: Module) : Async<unit> =
873873
| ImportFrom({ Module = Some(Identifier path) } as info) ->
874874
let path = printer.MakeImportPath(path)
875875
ImportFrom { info with Module = Some(Identifier path) }
876+
| Import({ Names = names }) ->
877+
let names =
878+
names
879+
|> List.map (fun n -> { n with Name = printer.MakeImportPath(n.Name.Name) |> Identifier })
880+
881+
Import { Names = names }
876882
| decl -> decl
877883
|> printDeclWithExtraLine false printer
878884

tests/Python/TestPyInterop.fs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,44 @@ let factorial (count : int) : int =
164164
let ``test emitPyStatement works with parameters`` () =
165165
factorial 5 |> equal 120
166166

167+
[<Fact>]
168+
let ``test importSideEffects`` () = // See #3965
169+
importSideEffects "./native_code.py"
170+
let mutable x = 3
171+
Py.python $"""
172+
{x} = native_code.add5({x} + 2)
173+
"""
174+
x |> equal 10
175+
176+
type NativeCode =
177+
abstract add5: int -> int
178+
179+
[<Fact>]
180+
let ``test importAll`` () =
181+
let nativeCode: NativeCode = importAll "./native_code.py"
182+
3 |> nativeCode.add5 |> equal 8
183+
184+
let add5 (x: int): int = importMember "./native_code.py"
185+
186+
[<Fact>]
187+
let ``test importMember`` () =
188+
add5 -1 |> equal 4
189+
190+
// Cannot use the same name as Fable will mangle the identifier
191+
let add7: int -> int = importMember "./native_code.py"
192+
add7 12 |> equal 19
193+
194+
let add5': int -> int = import "add5" "./native_code.py"
195+
add5' 12 |> equal 17
196+
197+
let multiply3 (x: int): int = importMember "./more_native_code.py"
198+
multiply3 9 |> equal 27
199+
200+
[<ImportAll("./native_code.py")>]
201+
let nativeCode: NativeCode = nativeOnly
202+
203+
[<Fact>]
204+
let ``test ImportAll works with relative paths`` () = // See #3481
205+
3 |> nativeCode.add5 |> equal 8
206+
167207
#endif

tests/Python/more_native_code.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def multiply3(x: int) -> int:
2+
return x * 3

tests/Python/native_code.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def add5(x: int) -> int:
2+
return x + 5
3+
4+
5+
def add7(x: int) -> int:
6+
return x + 7

0 commit comments

Comments
 (0)