Skip to content

Commit bdbd76a

Browse files
authored
[Python] Fix type annotations for inref, IList, DateKind, and regex collections (#4317)
1 parent 09baffb commit bdbd76a

File tree

11 files changed

+127
-75
lines changed

11 files changed

+127
-75
lines changed

src/Fable.Cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
* [Python] Fix type annotations for inref, IList, DateKind, and regex collections (by @dbrattli)
1213
* [Python] Fix type annotations for protocols, ABCs, Atom, and Set module (by @dbrattli)
1314
* [Python] Fix type annotations for async functions, date operations, and None handling (by @dbrattli)
1415
* [Python] Fix type annotations for tuple indexing, generic defaults, and reflection (by @dbrattli)

src/Fable.Compiler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
* [Python] Fix type annotations for inref, IList, DateKind, and regex collections (by @dbrattli)
1213
* [Python] Fix type annotations for protocols, ABCs, Atom, and Set module (by @dbrattli)
1314
* [Python] Fix type annotations for async functions, date operations, and None handling (by @dbrattli)
1415
* [Python] Fix type annotations for tuple indexing, generic defaults, and reflection (by @dbrattli)

src/Fable.Transforms/Python/Fable2Python.Annotation.fs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ open Fable.Transforms.Python.AST
1212
open Fable.Transforms.Python.Types
1313
open Fable.Transforms.Python.Util
1414

15+
/// Check if type is an inref (in-reference) or Any type.
16+
/// In F#, struct instance method's `this` parameter is represented as inref<StructType>,
17+
/// but in Python the struct is passed directly, not wrapped in FSharpRef.
18+
let isInRefOrAnyType (com: IPythonCompiler) =
19+
function
20+
| Replacements.Util.IsInRefType com _ -> true
21+
| Fable.Any -> true
22+
| _ -> false
23+
1524
let tryPyConstructor (com: IPythonCompiler) ctx ent =
1625
match Py.Replacements.tryConstructor com ent with
1726
| Some e -> com.TransformAsExpr(ctx, e) |> Some
@@ -253,7 +262,7 @@ let typeAnnotation
253262
makeGenericTypeAnnotation com ctx "list" [ genArg ] repeatedGenerics, []
254263
| Fable.Array(genArg, _) -> fableModuleTypeHint com ctx "array_" "Array" [ genArg ] repeatedGenerics
255264
| Fable.List genArg -> fableModuleTypeHint com ctx "list" "FSharpList" [ genArg ] repeatedGenerics
256-
| Replacements.Util.Builtin kind -> makeBuiltinTypeAnnotation com ctx kind repeatedGenerics
265+
| Replacements.Util.Builtin kind as typ -> makeBuiltinTypeAnnotation com ctx typ repeatedGenerics kind
257266
| Fable.AnonymousRecordType(_, _genArgs, _) ->
258267
let value = Expression.name "dict"
259268
let any, stmts = stdlibModuleTypeHint com ctx "typing" "Any" [] repeatedGenerics
@@ -370,8 +379,8 @@ let makeEntityTypeAnnotation com ctx (entRef: Fable.EntityRef) genArgs repeatedG
370379
fableModuleAnnotation com ctx "protocols" "ICollection" resolved, stmts
371380
| Types.ilist, _
372381
| Types.ilistGeneric, _ ->
373-
// Map IList<T> to list[T] in Python since arrays are lists
374-
makeGenericTypeAnnotation com ctx "list" genArgs repeatedGenerics, []
382+
// Map IList<T> to MutableSequence[T] which both list and FSharpArray implement
383+
stdlibModuleTypeHint com ctx "collections.abc" "MutableSequence" genArgs repeatedGenerics
375384
| Types.idisposable, _ -> libValue com ctx "util" "IDisposable", []
376385
| Types.iobserverGeneric, _ ->
377386
let resolved, stmts = resolveGenerics com ctx genArgs repeatedGenerics
@@ -450,12 +459,17 @@ let makeEntityTypeAnnotation com ctx (entRef: Fable.EntityRef) genArgs repeatedG
450459
| _ -> stdlibModuleTypeHint com ctx "typing" "Any" [] repeatedGenerics
451460
| None -> stdlibModuleTypeHint com ctx "typing" "Any" [] repeatedGenerics
452461

453-
let makeBuiltinTypeAnnotation com ctx kind repeatedGenerics =
462+
let makeBuiltinTypeAnnotation com ctx typ repeatedGenerics kind =
454463
match kind with
455464
| Replacements.Util.BclGuid -> stdlibModuleTypeHint com ctx "uuid" "UUID" [] repeatedGenerics
456465
| Replacements.Util.FSharpReference genArg ->
457-
let resolved, stmts = resolveGenerics com ctx [ genArg ] repeatedGenerics
458-
fableModuleAnnotation com ctx "types" "FSharpRef" resolved, stmts
466+
// For inref types (like struct instance member's 'this' parameter),
467+
// use the inner type directly since Python doesn't wrap them in FSharpRef
468+
if isInRefOrAnyType com typ then
469+
typeAnnotation com ctx repeatedGenerics genArg
470+
else
471+
let resolved, stmts = resolveGenerics com ctx [ genArg ] repeatedGenerics
472+
fableModuleAnnotation com ctx "types" "FSharpRef" resolved, stmts
459473
(*
460474
| Replacements.Util.BclTimeSpan -> NumberTypeAnnotation
461475
| Replacements.Util.BclDateTime -> makeSimpleTypeAnnotation com ctx "Date"

src/Fable.Transforms/Python/Replacements.fs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2339,7 +2339,6 @@ let intrinsicFunctions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisAr
23392339
let upper =
23402340
match upper with
23412341
| Value(NewOption(None, _, _), _) -> Helper.GlobalCall("len", t, [ ar ], [ t ], ?loc = r)
2342-
//getExpr None (Int32.Number) ar (makeStrConst "length2")
23432342
| _ -> add upper (makeIntConst 1)
23442343

23452344
Helper.InstanceCall(ar, "slice", t, [ lower; upper ], ?loc = r) |> Some

src/fable-library-py/fable_library/async_builder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,11 @@ def run_later(
199199
action: Callable[[], None],
200200
due_time: float = 0.0,
201201
):
202-
loop = asyncio.get_event_loop()
202+
loop = asyncio.get_running_loop()
203203
loop.call_later(due_time, action)
204204

205205
def run(self, action: Callable[[], None]):
206-
loop = asyncio.get_event_loop()
206+
loop = asyncio.get_running_loop()
207207

208208
if self.increment_and_check():
209209
self.call_count = 0

src/fable-library-py/fable_library/core/array.pyi

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ class FSharpArray[T](MutableSequence[T]):
5151
def __len__(self) -> int: ...
5252
def __setitem__(self, idx: int | slice, value: Any) -> None: ...
5353

54+
# F# interop property
55+
@property
56+
def length(self) -> Int32: ...
57+
5458
# IEnumerable implementation (for .NET compatibility)
5559
def GetEnumerator(self, __unit: None = None) -> IEnumerator[T]: ...
5660

@@ -367,7 +371,7 @@ def pairwise[T](array: FSharpArray[T] | Iterable[T]) -> FSharpArray[tuple[T, T]]
367371
def partition[T](
368372
f: Callable[[T], bool], array: FSharpArray[T] | Iterable[T], cons: FSharpCons[T] | None = None
369373
) -> tuple[FSharpArray[T], FSharpArray[T]]: ...
370-
def permute[T](f: Callable[[SupportsInt], SupportsInt], array: FSharpArray[T] | Iterable[T]) -> FSharpArray[T]: ...
374+
def permute[T](f: Callable[[Int32], SupportsInt], array: FSharpArray[T] | Iterable[T]) -> FSharpArray[T]: ...
371375
def pick[T, U](chooser: Callable[[T], U | None], array: FSharpArray[T] | Iterable[T]) -> U: ...
372376
def reduce[T](reduction: Callable[[T, T], T], array: FSharpArray[T] | Iterable[T]) -> T: ...
373377
def reduce_back[T](reduction: Callable[[T, T], T], array: FSharpArray[T] | Iterable[T]) -> T: ...

src/fable-library-py/fable_library/date.py

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
import re
44
from datetime import UTC, datetime, timedelta
55
from math import fmod
6-
from typing import Any, overload
6+
from typing import Any, SupportsInt, overload
77

88
from .singleton_local_time_zone import local_time_zone
99
from .time_span import TimeSpan, total_microseconds
1010
from .time_span import create as create_time_span
11-
from .types import FSharpRef, float64
11+
from .types import FSharpRef, float64, int32, int64
1212
from .util import DateKind
1313

1414

@@ -86,7 +86,7 @@ def create(
8686
s: int = 0,
8787
ms: int = 0,
8888
mc: int = 0,
89-
kind: DateKind | None = None,
89+
kind: int32 | None = None,
9090
) -> datetime:
9191
if kind == DateKind.UTC:
9292
date = datetime(
@@ -107,36 +107,36 @@ def create(
107107
return date
108108

109109

110-
def year(d: datetime) -> int:
111-
return d.year
110+
def year(d: datetime) -> int32:
111+
return int32(d.year)
112112

113113

114-
def month(d: datetime) -> int:
115-
return d.month
114+
def month(d: datetime) -> int32:
115+
return int32(d.month)
116116

117117

118-
def day(d: datetime) -> int:
119-
return d.day
118+
def day(d: datetime) -> int32:
119+
return int32(d.day)
120120

121121

122-
def hour(d: datetime) -> int:
123-
return d.hour
122+
def hour(d: datetime) -> int32:
123+
return int32(d.hour)
124124

125125

126-
def minute(d: datetime) -> int:
127-
return d.minute
126+
def minute(d: datetime) -> int32:
127+
return int32(d.minute)
128128

129129

130-
def second(d: datetime) -> int:
131-
return d.second
130+
def second(d: datetime) -> int32:
131+
return int32(d.second)
132132

133133

134-
def millisecond(d: datetime) -> int:
135-
return d.microsecond // 1_000
134+
def millisecond(d: datetime) -> int32:
135+
return int32(d.microsecond // 1_000)
136136

137137

138-
def microsecond(d: datetime) -> int:
139-
return d.microsecond
138+
def microsecond(d: datetime) -> int32:
139+
return int32(d.microsecond)
140140

141141

142142
def to_universal_time(d: datetime) -> datetime:
@@ -365,15 +365,15 @@ def date_to_string_with_custom_format(date: datetime, format: str, utc: bool) ->
365365

366366
match kind(date):
367367
case DateKind.UTC:
368-
utc_offet_text = localized_date.strftime("%z")
368+
utc_offset_text = localized_date.strftime("%z")
369369
case DateKind.Local:
370-
utc_offet_text = localized_date.strftime("%z")
371-
case DateKind.Unspecified:
372-
utc_offet_text = to_local_time(date).strftime("%z")
370+
utc_offset_text = localized_date.strftime("%z")
371+
case _: # DateKind.Unspecified
372+
utc_offset_text = to_local_time(date).strftime("%z")
373373

374-
sign = utc_offet_text[:1]
375-
hours = int(utc_offet_text[1:3])
376-
minutes = int(utc_offet_text[3:5])
374+
sign = utc_offset_text[:1]
375+
hours = int(utc_offset_text[1:3])
376+
minutes = int(utc_offset_text[3:5])
377377

378378
match token_length:
379379
case 1:
@@ -600,16 +600,16 @@ def days_in_month(year: int, month: int) -> int:
600600
return 31
601601

602602

603-
def add_years(d: datetime, v: int) -> datetime:
603+
def add_years(d: datetime, v: SupportsInt) -> datetime:
604604
new_month = month(d)
605-
new_year = year(d) + v
605+
new_year = year(d) + int(v)
606606
_days_in_month = days_in_month(new_year, new_month)
607607
new_day = min(_days_in_month, day(d))
608608
return create(new_year, new_month, new_day, hour(d), minute(d), second(d), millisecond(d), microsecond(d), kind(d))
609609

610610

611-
def add_months(d: datetime, v: int) -> datetime:
612-
new_month = month(d) + v
611+
def add_months(d: datetime, v: SupportsInt) -> datetime:
612+
new_month = int(month(d)) + int(v)
613613
new_month_ = 0
614614
year_offset = 0
615615
if new_month > 12:
@@ -620,37 +620,37 @@ def add_months(d: datetime, v: int) -> datetime:
620620
new_month_ = 12 + int(fmod(new_month, 12))
621621
year_offset = new_month // 12 + (-1 if new_month_ == 12 else 0)
622622
new_month = new_month_
623-
new_year = year(d) + year_offset
623+
new_year = int(year(d)) + year_offset
624624
_days_in_month = days_in_month(new_year, new_month)
625-
new_day = min(_days_in_month, day(d))
625+
new_day = min(_days_in_month, int(day(d)))
626626
return create(new_year, new_month, new_day, hour(d), minute(d), second(d), millisecond(d), microsecond(d), kind(d))
627627

628628

629-
def add_days(d: datetime, v: int) -> datetime:
629+
def add_days(d: datetime, v: SupportsInt) -> datetime:
630630
return d + timedelta(days=int(v))
631631

632632

633-
def add_hours(d: datetime, v: int) -> datetime:
633+
def add_hours(d: datetime, v: SupportsInt) -> datetime:
634634
return d + timedelta(hours=int(v))
635635

636636

637-
def add_minutes(d: datetime, v: int) -> datetime:
637+
def add_minutes(d: datetime, v: SupportsInt) -> datetime:
638638
return d + timedelta(minutes=int(v))
639639

640640

641-
def add_seconds(d: datetime, v: int) -> datetime:
641+
def add_seconds(d: datetime, v: SupportsInt) -> datetime:
642642
return d + timedelta(seconds=int(v))
643643

644644

645-
def add_milliseconds(d: datetime, v: int) -> datetime:
645+
def add_milliseconds(d: datetime, v: SupportsInt) -> datetime:
646646
return d + timedelta(milliseconds=int(v))
647647

648648

649-
def add_microseconds(d: datetime, v: int) -> datetime:
649+
def add_microseconds(d: datetime, v: SupportsInt) -> datetime:
650650
return d + timedelta(microseconds=int(v))
651651

652652

653-
def kind(d: datetime) -> DateKind:
653+
def kind(d: datetime) -> int32:
654654
if d.tzinfo == UTC:
655655
return DateKind.UTC
656656

@@ -661,11 +661,11 @@ def kind(d: datetime) -> DateKind:
661661
return DateKind.Local
662662

663663

664-
def specify_kind(d: datetime, kind: DateKind) -> datetime:
664+
def specify_kind(d: datetime, kind: int32) -> datetime:
665665
return create(year(d), month(d), day(d), hour(d), minute(d), second(d), millisecond(d), microsecond(d), kind)
666666

667667

668-
def ticks(d: datetime) -> int:
668+
def ticks(d: datetime) -> int64:
669669
# Note: It can happens that Ticks differs a little bit from the .NET implementation
670670
# because of some rounding/precision issues in Python
671671
# DateTime(1, 1, 1, 0, 0, 0, 0, 99, DateTimeKind.Utc).Ticks should be 990
@@ -674,20 +674,20 @@ def ticks(d: datetime) -> int:
674674
# - returns -62135596799.9999
675675
# - instead of -62135596800000
676676
# compute timestamp in microseconds
677-
return unix_epoch_microseconds_to_ticks(int(d.timestamp() * 1_000_000), date_offset(d) * 1_000)
677+
return unix_epoch_microseconds_to_ticks(int64(d.timestamp() * 1_000_000), date_offset(d) * 1_000)
678678

679679

680-
def unix_epoch_microseconds_to_ticks(us: int, offset: int) -> int:
681-
return int(((us + 62135596800000000) + offset) * 10)
680+
def unix_epoch_microseconds_to_ticks(us: int64, offset: int64) -> int64:
681+
return ((us + 62135596800000000) + offset) * 10
682682

683683

684-
def ticks_to_unix_epoch_microseconds(ticks: int) -> int:
685-
return int((ticks - 621355968000000000) // 10)
684+
def ticks_to_unix_epoch_microseconds(ticks: SupportsInt) -> int:
685+
return (int(ticks) - 621355968000000000) // 10
686686

687687

688-
def date_offset(d: datetime) -> int:
688+
def date_offset(d: datetime) -> int64:
689689
if d.tzinfo == UTC:
690-
return 0
690+
return int64.ZERO
691691
else:
692692
utc_offset = d.utcoffset()
693693

@@ -696,14 +696,14 @@ def date_offset(d: datetime) -> int:
696696
if utc_offset is None:
697697
forced_utc_offset = d.astimezone().utcoffset()
698698
assert forced_utc_offset is not None
699-
return int(forced_utc_offset.total_seconds() * 1_000)
699+
return int64(forced_utc_offset.total_seconds() * 1_000)
700700
else:
701-
return int(utc_offset.total_seconds() * 1_000)
701+
return int64(utc_offset.total_seconds() * 1_000)
702702

703703
# return 0 if d.tzinfo == timezone.utc else
704704

705705

706-
def create_from_epoch_microseconds(us: int, kind: DateKind | None = None) -> datetime:
706+
def create_from_epoch_microseconds(us: int, kind: int32 | None = None) -> datetime:
707707
if kind == DateKind.UTC:
708708
date = datetime.fromtimestamp(us / 1_000_000, UTC)
709709
else:
@@ -714,7 +714,7 @@ def create_from_epoch_microseconds(us: int, kind: DateKind | None = None) -> dat
714714
return date
715715

716716

717-
def from_ticks(ticks: int, kind: DateKind | None = None) -> datetime:
717+
def from_ticks(ticks: SupportsInt, kind: int32 | None = None) -> datetime:
718718
# Better default than Unspecified
719719
kind = kind or DateKind.Local
720720
date = create_from_epoch_microseconds(ticks_to_unix_epoch_microseconds(ticks), kind)

0 commit comments

Comments
 (0)