Skip to content
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
18 changes: 17 additions & 1 deletion src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2676,10 +2676,26 @@ let intrinsicFunctions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisAr
| "GetArray", _, [ ar; idx ] -> getExpr r t ar idx |> Some
| "SetArray", _, [ ar; idx; value ] -> setExpr r ar idx value |> Some
| ("GetArraySlice" | "GetStringSlice"), None, [ ar; lower; upper ] ->
// Python slicing counts a negative index from the end, .NET clamps it to 0
let clampToZero (index: Expr) =
match index with
| Value(NumberConstant(NumberValue.Int32 n, _), _) when n >= 0 -> index
| _ -> Helper.LibCall(com, "util", "slice_index", Int32.Number, [ index ], ?loc = r)

let lower =
match lower with
| Value(NewOption(None, _, _), _) -> makeIntConst 0
| Value(NewOption(Some lower, _, _), _)
| lower -> clampToZero lower

let upper =
match upper with
| Value(NewOption(None, _, _), _) -> Helper.GlobalCall("len", t, [ ar ], [ t ], ?loc = r)
| _ -> add upper (makeIntConst 1)
| Value(NewOption(Some upper, _, _), _)
| upper ->
match upper with
| Value(NumberConstant(NumberValue.Int32 n, _), _) when n >= 0 -> makeIntConst (n + 1)
| upper -> add upper (makeIntConst 1) |> clampToZero

Helper.InstanceCall(ar, "slice", t, [ lower; upper ], ?loc = r) |> Some
| "SetArraySlice", None, args ->
Expand Down
18 changes: 17 additions & 1 deletion src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3096,10 +3096,26 @@ let intrinsicFunctions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisAr
| "GetArray", _, [ ar; idx ] -> arrayGetItem com r t [ idx; ar ] (i.SignatureArgTypes |> List.rev)
| "SetArray", _, [ ar; idx; value ] -> setExpr r ar idx value |> Some
| ("GetArraySlice" | "GetStringSlice"), None, [ ar; lower; upper ] ->
// JS slice counts a negative index from the end, .NET clamps it to 0
let clampToZero (index: Expr) =
match index with
| Value(NumberConstant(NumberValue.Int32 n, _), _) when n >= 0 -> index
| _ -> Helper.GlobalCall("Math", Int32.Number, [ index; makeIntConst 0 ], memb = "max")

let lower =
match lower with
| Value(NewOption(None, _, _), _) -> makeIntConst 0
| Value(NewOption(Some lower, _, _), _)
| lower -> clampToZero lower

let upper =
match upper with
| Value(NewOption(None, _, _), _) -> getExpr None (Int32.Number) ar (makeStrConst "length")
| _ -> add upper (makeIntConst 1)
| Value(NewOption(Some upper, _, _), _)
| upper ->
match upper with
| Value(NumberConstant(NumberValue.Int32 n, _), _) when n >= 0 -> makeIntConst (n + 1)
| upper -> add upper (makeIntConst 1) |> clampToZero

Helper.InstanceCall(ar, "slice", t, [ lower; upper ], ?loc = r) |> Some
| "SetArraySlice", None, args ->
Expand Down
6 changes: 6 additions & 0 deletions src/fable-library-py/fable_library/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,11 @@ def random_shared() -> random.Random:
return _shared_random


def slice_index(index: int) -> int:
"""Clamp a slice bound to 0, as .NET does where Python would count from the end."""
return index if index > 0 else 0


def random_int(rand: random.Random, a: int, b: int) -> int:
if a == b:
return int32(a)
Expand Down Expand Up @@ -990,6 +995,7 @@ def range(start: int, stop: int, step: int = 1) -> Iterable[int]:
"random_shared",
"range",
"round",
"slice_index",
"to_iterable",
"unescape_data_string",
]
15 changes: 15 additions & 0 deletions tests/Js/Main/ArrayTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,21 @@ let tests =
a.[3..3] <- [|999|]
a |> equal [|999; 2; 3; 999; 5; 6|]

testCase "Array slices clamp the bounds like .NET" <| fun () -> // See #5164
let xs = [| 1; 2; 3 |]
xs.[-1..1] |> equal [| 1; 2 |]
xs.[-5..] |> equal [| 1; 2; 3 |]
xs.[1..5] |> equal [| 2; 3 |]
xs.[..5] |> equal [| 1; 2; 3 |]
xs.[.. -2] |> equal [||]
xs.[-5.. -1] |> equal [||]
xs.[5..6] |> equal [||]
xs.[2..1] |> equal [||]
let lower, upper = -1, 1
xs.[lower..upper] |> equal [| 1; 2 |]
let strings = [| "a"; "b"; "c" |]
strings.[-1..1] |> equal [| "a"; "b" |]

testCase "Array literals work" <| fun () ->
let x = [| 1; 2; 3; 4; 5 |]
equal 5 x.Length
Expand Down
11 changes: 11 additions & 0 deletions tests/Js/Main/StringTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,17 @@ let tests = testList "Strings" [
sprintf "%s" s.[2..] |> equal "t and dog"
sprintf "%s" s.[..8] |> equal "cat and d"

testCase "String slices clamp the bounds like .NET" <| fun () -> // See #5164
let s = "abc"
s.[-1..1] |> equal "ab"
s.[-5..] |> equal "abc"
s.[1..5] |> equal "bc"
s.[.. -2] |> equal ""
s.[-3.. -2] |> equal ""
s.[2..1] |> equal ""
let lower, upper = -1, 1
s.[lower..upper] |> equal "ab"

testCase "String.Format works" <| fun () ->
let arg1, arg2, arg3 = "F#", "Fable", "Babel"
String.Format(CultureInfo.InvariantCulture, "{2} is to {1} what {1} is to {0}", arg1, arg2, arg3)
Expand Down
16 changes: 16 additions & 0 deletions tests/Python/TestArray.fs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,22 @@ let ``test Array slice with non-numeric arrays work`` () =
equal xs.[2] "X"
equal xs.[3] "D"

[<Fact>]
let ``test Array slices clamp the bounds like .NET`` () =
let xs = [| 1; 2; 3 |]
xs.[-1..1] |> equal [| 1; 2 |]
xs.[-5..] |> equal [| 1; 2; 3 |]
xs.[1..5] |> equal [| 2; 3 |]
xs.[..5] |> equal [| 1; 2; 3 |]
xs.[.. -2] |> equal [||]
xs.[-5.. -1] |> equal [||]
xs.[5..6] |> equal [||]
xs.[2..1] |> equal [||]
let lower, upper = -1, 1
xs.[lower..upper] |> equal [| 1; 2 |]
let strings = [| "a"; "b"; "c" |]
strings.[-1..1] |> equal [| "a"; "b" |]

[<Fact>]
let ``test Array literals work`` () =
let x = [| 1; 2; 3; 4; 5 |]
Expand Down
12 changes: 12 additions & 0 deletions tests/Python/TestString.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1215,3 +1215,15 @@ let ``test unicode categories`` () =
Char.IsLetterOrDigit c |> ignore
)
#endif

[<Fact>]
let ``test String slices clamp the bounds like .NET`` () =
let s = "abc"
s.[-1..1] |> equal "ab"
s.[-5..] |> equal "abc"
s.[1..5] |> equal "bc"
s.[.. -2] |> equal ""
s.[-3.. -2] |> equal ""
s.[2..1] |> equal ""
let lower, upper = -1, 1
s.[lower..upper] |> equal "ab"
Loading