Skip to content

Commit c8b218e

Browse files
committed
fix: Handle multiline definitions
1 parent 4ce2ba3 commit c8b218e

7 files changed

Lines changed: 75 additions & 20 deletions

File tree

chapters/compatibility.fs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ from .NET when targeting Python with Fable.
88
99
## Common Types and Objects
1010
11-
Some F#/.NET types have counterparts in Python. Fable takes advantage of this
12-
to compile to native types that are more performant and reduce code size.
11+
Some F#/.NET types have counterparts in Python. Fable takes advantage of
12+
this to compile to native types that are more performant and reduce code
13+
size. Native types also simplify interop with Python code and libraries.
1314
The most important common types are:
1415
1516
| F#/.NET Type | Python Type | Notes |
@@ -20,7 +21,7 @@ The most important common types are:
2021
| `Tuple` | `tuple` | Native Python tuple |
2122
| `ResizeArray<T>` | `list` | Native Python list |
2223
| `Dictionary<K,V>` | `dict` | Native Python dict |
23-
| `seq<T>` / `IEnumerable` | iterator | Uses `__iter__` protocol |
24+
| `seq<T>` / `IEnumerable` | `Iterable` | Uses `__iter__` protocol |
2425
| `Array` | `FSharpArray` | Custom wrapper for F# semantics |
2526
2627
## .NET Base Class Library
@@ -46,7 +47,7 @@ Most FSharp.Core operators are supported, including formatting with `sprintf`,
4647
| F# Type | Python |
4748
| ----------------- | -------------------------- |
4849
| `Tuple` | `tuple` |
49-
| `Option<T>` | erased (see caveats) |
50+
| `Option<T>` | erased to `T \| None` |
5051
| `string` | `str` |
5152
| `List<T>` | `List.fs` (immutable list) |
5253
| `Map<K,V>` | `Map.fs` (immutable map) |
@@ -235,6 +236,11 @@ let processed =
235236
squared)
236237

237238
// Becomes a separate function in Python
239+
(**
240+
We can see that the mapping becomes a separate function in the generated Python code.
241+
*)
242+
243+
(*** include-python: mapping, processed ***)
238244

239245
(**
240246
### Numeric Types
@@ -271,6 +277,12 @@ let wrapped: int = maxInt + 1 // Wraps around like .NET
271277
// bigint uses Python's native arbitrary-precision int
272278
let huge: bigint = 999999999999999999999999999999I
273279

280+
(**
281+
This generates:
282+
*)
283+
284+
(*** include-python: small, big, wrapped, huge ***)
285+
274286
(**
275287
### Computation Expressions
276288
@@ -289,7 +301,8 @@ If your project has `[<EntryPoint>]`, you need:
289301
</PropertyGroup>
290302
```
291303
292-
This ensures absolute imports in generated Python.
304+
This ensures the use of absolute imports in generated Python. Applications
305+
in Python must use absolute imports to run correctly.
293306
294307
### Libraries
295308
@@ -307,7 +320,7 @@ Libraries use relative imports by default, which is correct for packages.
307320
Fable.Python provides excellent F# support. The main things to watch for are:
308321
309322
- Option erasure in edge cases
310-
- Multi-line lambda lifting
323+
- Multi-line lambda lifting, will not be anonymous
311324
- Some .NET APIs may be missing
312325
313326
For most F# code, you can write idiomatic functional code and it will

chapters/getting-started.fs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,24 @@ dotnet new console -lang F#
2626
2727
# Set up local tools and install Fable 5 (alpha)
2828
dotnet new tool-manifest
29-
dotnet tool install fable --version 5.0.0-alpha.17
29+
dotnet tool install fable --version 5.0.0-alpha.20
3030
3131
# Add Fable.Core package
32-
dotnet add package Fable.Core --version 5.0.0-beta.2
32+
dotnet add package Fable.Core --version 5.0.0-beta.4
3333
```
3434
3535
## Install Python Dependencies
3636
3737
Fable-generated Python code requires the `fable-library` runtime:
3838
3939
```bash
40-
pip install "fable-library==5.0.0a17"
40+
pip install "fable-library==5.0.0a20"
4141
```
4242
4343
---
4444
4545
**Note:** Version pinning matters. The fable-library version must match
46-
your Fable compiler version. PyPI uses `5.0.0a17` format instead of `5.0.0-alpha.17`.
46+
your Fable compiler version. PyPI uses `5.0.0a20` format instead of `5.0.0-alpha.20`.
4747
4848
---
4949

chapters/python.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ unwrap the option first.
205205
| Dictionary | `{"a": 1}` | `Map.ofList [("a", 1)]` |
206206
| None check | `if x is None:` | `match x with None ->` |
207207
| String format | `f"Hello {name}"` | `$"Hello {name}"` |
208-
| Type annotation | `x: int` | `x: int` (same!) |
208+
| Type annotation | `x: int` | `x: int32`. |
209209
| Comments | `# comment` | `// comment` |
210210
| Multiline string | `"""text"""` | `"""text"""` (same!) |
211211

docs/compatibility.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ from .NET when targeting Python with Fable.
55

66
## Common Types and Objects
77

8-
Some F#/.NET types have counterparts in Python. Fable takes advantage of this
9-
to compile to native types that are more performant and reduce code size.
8+
Some F#/.NET types have counterparts in Python. Fable takes advantage of
9+
this to compile to native types that are more performant and reduce code
10+
size. Native types also simplify interop with Python code and libraries.
1011
The most important common types are:
1112

1213
| F#/.NET Type | Python Type | Notes |
@@ -17,7 +18,7 @@ The most important common types are:
1718
| `Tuple` | `tuple` | Native Python tuple |
1819
| `ResizeArray<T>` | `list` | Native Python list |
1920
| `Dictionary<K,V>` | `dict` | Native Python dict |
20-
| `seq<T>` / `IEnumerable` | iterator | Uses `__iter__` protocol |
21+
| `seq<T>` / `IEnumerable` | `Iterable` | Uses `__iter__` protocol |
2122
| `Array` | `FSharpArray` | Custom wrapper for F# semantics |
2223

2324
## .NET Base Class Library
@@ -43,7 +44,7 @@ Most FSharp.Core operators are supported, including formatting with `sprintf`,
4344
| F# Type | Python |
4445
| ----------------- | -------------------------- |
4546
| `Tuple` | `tuple` |
46-
| `Option<T>` | erased (see caveats) |
47+
| `Option<T>` | erased to `T \| None` |
4748
| `string` | `str` |
4849
| `List<T>` | `List.fs` (immutable list) |
4950
| `Map<K,V>` | `Map.fs` (immutable map) |
@@ -96,6 +97,8 @@ is_enabled: bool = True
9697
coordinates: tuple[float64, float64] = (float64(10.5), float64(20.3))
9798

9899
numbers: FSharpList[int32] = of_array(
100+
Array[int32]([int32.ONE, int32.TWO, int32.THREE, int32.FOUR, int32.FIVE])
101+
)
99102

100103
mutable_list: list[int32] = []
101104
```
@@ -250,6 +253,15 @@ let processed =
250253
// Becomes a separate function in Python
251254
```
252255

256+
```python
257+
def mapping(x_1: int32) -> int32:
258+
return x_1 * x_1
259+
260+
processed: FSharpList[int32] = map(
261+
mapping, of_array(Array[int32]([int32.ONE, int32.TWO, int32.THREE]))
262+
)
263+
```
264+
253265
### Numeric Types
254266

255267
Numeric types in Fable.Python are implemented using custom PyO3 wrapper types

docs/fabletext.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,14 @@ let flushCodeBuffer (ctx: ParseContext) : ParseContext =
167167
let mutable pythonFileContent: string option = None
168168
169169
/// Checks if a line starts a new top-level definition (not indented).
170+
/// Excludes closing brackets which are continuation of previous definitions.
170171
let isTopLevelDefinition (line: string) : bool =
172+
let trimmed = line.Trim()
171173
not (String.IsNullOrWhiteSpace line)
172174
&& not (line.StartsWith " ")
173175
&& not (line.StartsWith "\t")
174176
&& not (line.StartsWith "#")
177+
&& not (trimmed = ")" || trimmed = "]" || trimmed = "}")
175178
176179
/// Checks if a line is a decorator.
177180
let isDecorator (line: string) : bool =
@@ -221,7 +224,13 @@ let extractSymbol (symbol: string) (lines: string array) : string option =
221224
|> Option.defaultValue 0
222225
223226
let defLine = lines[defIndex].TrimStart()
224-
let isMultiline = defLine.StartsWith "class " || defLine.StartsWith "def "
227+
// Multi-line if: class/def, or assignment ending with open paren/bracket
228+
let isMultiline =
229+
defLine.StartsWith "class "
230+
|| defLine.StartsWith "def "
231+
|| defLine.EndsWith "("
232+
|| defLine.EndsWith "["
233+
|| defLine.EndsWith "{"
225234
226235
if not isMultiline then
227236
lines[defIndex]
@@ -373,8 +382,20 @@ def extract_symbol(symbol: str, lines: Array[str]) -> str | None:
373382
def_line: str = lines[def_index].lstrip()
374383
if not (
375384
True
376-
if starts_with_exact(def_line, "class ")
377-
else starts_with_exact(def_line, "def ")
385+
if (
386+
True
387+
if (
388+
True
389+
if (
390+
True
391+
if starts_with_exact(def_line, "class ")
392+
else starts_with_exact(def_line, "def ")
393+
)
394+
else ends_with_exact(def_line, "(")
395+
)
396+
else ends_with_exact(def_line, "[")
397+
)
398+
else ends_with_exact(def_line, "{")
378399
):
379400
return lines[def_index]
380401

docs/python.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ unwrap the option first.
196196
| Dictionary | `{"a": 1}` | `Map.ofList [("a", 1)]` |
197197
| None check | `if x is None:` | `match x with None ->` |
198198
| String format | `f"Hello {name}"` | `$"Hello {name}"` |
199-
| Type annotation | `x: int` | `x: int` (same!) |
199+
| Type annotation | `x: int` | `x: int32`. |
200200
| Comments | `# comment` | `// comment` |
201201
| Multiline string | `"""text"""` | `"""text"""` (same!) |
202202

tools/fabletext.fs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,14 @@ let flushCodeBuffer (ctx: ParseContext) : ParseContext =
175175
let mutable pythonFileContent: string option = None
176176

177177
/// Checks if a line starts a new top-level definition (not indented).
178+
/// Excludes closing brackets which are continuation of previous definitions.
178179
let isTopLevelDefinition (line: string) : bool =
180+
let trimmed = line.Trim()
179181
not (String.IsNullOrWhiteSpace line)
180182
&& not (line.StartsWith " ")
181183
&& not (line.StartsWith "\t")
182184
&& not (line.StartsWith "#")
185+
&& not (trimmed = ")" || trimmed = "]" || trimmed = "}")
183186

184187
/// Checks if a line is a decorator.
185188
let isDecorator (line: string) : bool =
@@ -229,7 +232,13 @@ let extractSymbol (symbol: string) (lines: string array) : string option =
229232
|> Option.defaultValue 0
230233

231234
let defLine = lines[defIndex].TrimStart()
232-
let isMultiline = defLine.StartsWith "class " || defLine.StartsWith "def "
235+
// Multi-line if: class/def, or assignment ending with open paren/bracket
236+
let isMultiline =
237+
defLine.StartsWith "class "
238+
|| defLine.StartsWith "def "
239+
|| defLine.EndsWith "("
240+
|| defLine.EndsWith "["
241+
|| defLine.EndsWith "{"
233242

234243
if not isMultiline then
235244
lines[defIndex]

0 commit comments

Comments
 (0)