diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index 65a3c7311e..77bed3bf65 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -1874,14 +1874,24 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex let lists (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg, args with - // Use methods for Head and Tail (instead of Get(ListHead) for example) to check for empty lists - | ReplaceName [ "get_Head", "head" - "get_Tail", "tail" - "get_Item", "item" - "get_Length", "length" - "GetSlice", "getSlice" ] methName, - Some x, - _ -> + // list.Head and list.Tail raise InvalidOperationException, List.head and List.tail ArgumentException + | ("get_Head" | "get_Tail"), Some x, _ -> + let mangledName = + Naming.buildNameWithoutSanitationFrom "FSharpList" false i.CompiledName "" + + Helper.LibCall( + com, + "List", + mangledName, + t, + [], + i.SignatureArgTypes, + genArgs = i.GenericArgs, + thisArg = x, + ?loc = r + ) + |> Some + | ReplaceName [ "get_Item", "item"; "get_Length", "length"; "GetSlice", "getSlice" ] methName, Some x, _ -> let args = match args with | [ ExprType Unit ] -> [ x ] diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 248e3266ef..559fe0176a 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -2107,14 +2107,13 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex let lists (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg, args with - // Use methods for Head and Tail (instead of Get(ListHead) for example) to check for empty lists - | ReplaceName [ "get_Head", "head" - "get_Tail", "tail" - "get_Item", "item" - "get_Length", "length" - "GetSlice", "getSlice" ] methName, - Some x, - _ -> + // list.Head and list.Tail raise InvalidOperationException, List.head and List.tail ArgumentException + | ("get_Head" | "get_Tail"), Some x, _ -> + let mangledName = + Naming.buildNameWithoutSanitationFrom "FSharpList" false i.CompiledName "" + + Helper.LibCall(com, "list", mangledName, t, [], thisArg = x, ?loc = r) |> Some + | ReplaceName [ "get_Item", "item"; "get_Length", "length"; "GetSlice", "getSlice" ] methName, Some x, _ -> let args = match args with | [ ExprType Unit ] -> [ x ] diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 8109a1ae2a..b96b3ef2f1 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -2283,8 +2283,6 @@ let nativeArrayFunctions = "Filter", "filter" "ForAll", "every" "Iterate", "forEach" - "Reduce", "reduce" - "ReduceBack", "reduceRight" "SortInPlaceWith", "sort" |] @@ -2403,7 +2401,13 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex let lists (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg, args with - // Use methods for Head and Tail (instead of Get(ListHead) for example) to check for empty lists + // list.Head and list.Tail raise InvalidOperationException, List.head and List.tail ArgumentException + // PHP's FSharpList has no such members, so PHP keeps the head/tail functions below. + | ("get_Head" | "get_Tail"), Some x, _ when com.Options.Language <> Php -> + let mangledName = + Naming.buildNameWithoutSanitationFrom "FSharpList" false i.CompiledName "" + + Helper.LibCall(com, "List", mangledName, t, [], thisArg = x, ?loc = r) |> Some | ReplaceName [ "get_Head", "head" "get_Tail", "tail" "get_Item", "item" diff --git a/src/fable-library-beam/Range.fs b/src/fable-library-beam/Range.fs index e97846a5af..6d729e8cf7 100644 --- a/src/fable-library-beam/Range.fs +++ b/src/fable-library-beam/Range.fs @@ -4,7 +4,7 @@ let makeRangeStepFunction<'T when 'T: comparison> (step: 'T) (stop: 'T) (zero: ' let stepComparedWithZero = compare step zero if stepComparedWithZero = 0 then - failwith "The step of a range cannot be zero" + invalidArg "step" "The step of a range cannot be zero." let stepGreaterThanZero = stepComparedWithZero > 0 diff --git a/src/fable-library-beam/Seq.fs b/src/fable-library-beam/Seq.fs index bdb4b24110..71671d5394 100644 --- a/src/fable-library-beam/Seq.fs +++ b/src/fable-library-beam/Seq.fs @@ -992,7 +992,7 @@ let reduce folder (xs: seq<'T>) = if e.MoveNext() then loop e.Current else - invalidOp SR.inputSequenceEmpty + invalidArg "source" SR.inputSequenceEmpty let reduceBack folder (xs: seq<'T>) = let arr = toArray xs @@ -1000,7 +1000,7 @@ let reduceBack folder (xs: seq<'T>) = if arr.Length > 0 then Array.reduceBack folder arr else - invalidOp SR.inputSequenceEmpty + invalidArg "source" SR.inputSequenceEmpty let replicate n x = initialize n (fun _ -> x) @@ -1035,7 +1035,7 @@ let skip count (source: seq<'T>) = try for _ = 1 to count do if not (e.MoveNext()) then - invalidArg "source" SR.notEnoughElements + invalidOp SR.notEnoughElements let compensation () = () Enumerator.enumerateThenFinally compensation e @@ -1057,7 +1057,21 @@ let skipWhile predicate (xs: seq<'T>) = ) ) -let tail (xs: seq<'T>) = skip 1 xs +// FSharp.Core's Seq.tail raises ArgumentException on an empty input, Seq.skip InvalidOperationException +let tail (xs: seq<'T>) = + mkSeq (fun () -> + let e = ofSeq xs + + try + if not (e.MoveNext()) then + invalidArg "source" SR.notEnoughElements + + let compensation () = () + Enumerator.enumerateThenFinally compensation e + with _ -> + e.Dispose() + reraise () + ) let take count (xs: seq<'T>) = generateIndexed @@ -1067,7 +1081,7 @@ let take count (xs: seq<'T>) = if e.MoveNext() then Some(e.Current) else - invalidArg "source" SR.notEnoughElements + invalidOp SR.notEnoughElements else None ) diff --git a/src/fable-library-beam/fable_list.erl b/src/fable-library-beam/fable_list.erl index 15c526e5f8..580eb7c9e5 100644 --- a/src/fable-library-beam/fable_list.erl +++ b/src/fable-library-beam/fable_list.erl @@ -225,10 +225,16 @@ sort_by_descending(Fn, Comparer, List) -> sort_with(Fn, List) -> lists:sort(fun(A, B) -> (Fn(A))(B) =< 0 end, List). +index_not_found() -> + erlang:error(#{ + exn_type => key_not_found_exception, + message => <<"An index satisfying the predicate was not found in the collection.">> + }). + find(Fn, List) -> case lists:dropwhile(fun(E) -> not Fn(E) end, List) of [H | _] -> H; - [] -> erlang:error(<<"key_not_found">>) + [] -> index_not_found() end. try_find(Fn, List) -> @@ -245,7 +251,7 @@ find_index(Fn, [H | T], I) -> false -> find_index(Fn, T, I + 1) end; find_index(_Fn, [], _I) -> - erlang:error(<<"key_not_found">>). + index_not_found(). try_find_index(Fn, List) -> try_find_index(Fn, List, 0). @@ -387,7 +393,8 @@ try_item(_, _) -> undefined. exactly_one([X]) -> X; -exactly_one(_) -> erlang:error(<<"Sequence contains more than one element">>). +exactly_one([]) -> invalid_arg(<<"source">>, <<"The input sequence was empty.">>); +exactly_one(_) -> invalid_arg(<<"source">>, <<"The input sequence contains more than one element.">>). try_exactly_one([X]) -> fable_option:some(X); try_exactly_one(_) -> undefined. @@ -580,7 +587,7 @@ map_fold_back(Fn, List, State) -> pick(Fn, List) -> case try_pick(Fn, List) of - undefined -> erlang:error(<<"key_not_found">>); + undefined -> index_not_found(); V -> V end. @@ -594,7 +601,7 @@ try_pick(Fn, [H | T]) -> find_index_back(Fn, List) -> case try_find_index_back(Fn, List) of - undefined -> erlang:error(<<"key_not_found">>); + undefined -> index_not_found(); Idx -> Idx end. @@ -673,7 +680,11 @@ get_slice(Lower, Upper, List) -> take(Count, List) -> case erlang:length(List) >= Count of true -> lists:sublist(List, Count); - false -> erlang:error(<<"The input sequence has an insufficient number of elements.">>) + false -> + erlang:error(#{ + exn_type => invalid_operation_exception, + message => <<"The input sequence has an insufficient number of elements.">> + }) end. %% ---- Random functions -------------------------------------------------------- @@ -697,13 +708,13 @@ random_shuffle(Xs) -> -spec random_choice_by(fun((ok) -> float()), list()) -> term(). random_choice_by(_Randomizer, []) -> - erlang:error(<<"The input sequence was empty.">>); + invalid_arg(<<"source">>, <<"The input sequence was empty.">>); random_choice_by(Randomizer, Xs) -> Len = erlang:length(Xs), R = Randomizer(ok), if R < 0.0; R >= 1.0 -> - erlang:error(iolist_to_binary(io_lib:format("The index is outside the legal range.~nrandomizer returned ~w, should be in range [0.0, 1.0).", [R]))); + randomizer_out_of_range(R); true -> lists:nth(erlang:trunc(R * Len) + 1, Xs) end. @@ -720,9 +731,9 @@ random_choice(Xs) -> random_choices_by(Randomizer, Count, Xs) -> if Count < 0 -> - erlang:error(<<"The input must be non-negative.">>); + invalid_arg(<<"count">>, <<"The input must be non-negative.">>); Count > 0 andalso Xs =:= [] -> - erlang:error(<<"The input sequence was empty.">>); + invalid_arg(<<"source">>, <<"The input sequence was empty.">>); true -> Arr = erlang:list_to_tuple(Xs), Len = erlang:tuple_size(Arr), @@ -736,7 +747,7 @@ choices_loop(Randomizer, Arr, Len, N, Acc) -> R = Randomizer(ok), if R < 0.0; R >= 1.0 -> - erlang:error(iolist_to_binary(io_lib:format("The index is outside the legal range.~nrandomizer returned ~w, should be in range [0.0, 1.0).", [R]))); + randomizer_out_of_range(R); true -> E = erlang:element(erlang:trunc(R * Len) + 1, Arr), choices_loop(Randomizer, Arr, Len, N - 1, [E | Acc]) @@ -754,14 +765,14 @@ random_choices(Count, Xs) -> random_sample_by(Randomizer, Count, Xs) -> if Count < 0 -> - erlang:error(<<"The input must be non-negative.">>); + invalid_arg(<<"count">>, <<"The input must be non-negative.">>); true -> Len = erlang:length(Xs), if Len =:= 0 andalso Count > 0 -> - erlang:error(<<"The input sequence was empty.">>); + invalid_arg(<<"source">>, <<"The input sequence was empty.">>); Count > Len -> - erlang:error(<<"The input sequence has an insufficient number of elements.">>); + invalid_arg(<<"count">>, <<"The input sequence has an insufficient number of elements.">>); true -> sample_loop(Randomizer, erlang:list_to_tuple(Xs), Len, Count, 0) end @@ -782,7 +793,7 @@ sample_loop(Randomizer, Arr, Len, Count, I) -> R = Randomizer(ok), if R < 0.0; R >= 1.0 -> - erlang:error(iolist_to_binary(io_lib:format("The index is outside the legal range.~nrandomizer returned ~w, should be in range [0.0, 1.0).", [R]))); + randomizer_out_of_range(R); true -> J = I + erlang:trunc(R * (Len - I)), EI = erlang:element(I + 1, Arr), @@ -798,7 +809,7 @@ shuffle_loop(Randomizer, Arr, I) -> R = Randomizer(ok), if R < 0.0; R >= 1.0 -> - erlang:error(iolist_to_binary(io_lib:format("The index is outside the legal range.~nrandomizer returned ~w, should be in range [0.0, 1.0).", [R]))); + randomizer_out_of_range(R); true -> J = erlang:trunc(R * (I + 1)), EI = erlang:element(I + 1, Arr), @@ -806,3 +817,20 @@ shuffle_loop(Randomizer, Arr, I) -> Arr2 = erlang:setelement(I + 1, erlang:setelement(J + 1, Arr, EI), EJ), shuffle_loop(Randomizer, Arr2, I - 1) end. + +randomizer_out_of_range(R) -> + erlang:error(#{ + exn_type => argument_out_of_range_exception, + message => iolist_to_binary( + io_lib:format( + "The index is outside the legal range.~nrandomizer returned ~w, should be in range [0.0, 1.0). (Parameter 'randomizer')", + [R] + ) + ) + }). + +invalid_arg(ParamName, Message) -> + erlang:error(#{ + exn_type => argument_exception, + message => fable_utils:argument_exception_message(Message, ParamName) + }). diff --git a/src/fable-library-beam/fable_map.erl b/src/fable-library-beam/fable_map.erl index d5f257ea4c..7ab10c6338 100644 --- a/src/fable-library-beam/fable_map.erl +++ b/src/fable-library-beam/fable_map.erl @@ -74,7 +74,7 @@ iterate(Fn, Map) -> find_key(Fn, Map) -> case lists:dropwhile(fun({K, V}) -> not (Fn(K))(V) end, maps:to_list(Map)) of [{K, _} | _] -> K; - [] -> erlang:error(<<"key_not_found">>) + [] -> key_not_found(<<"An index satisfying the predicate was not found in the collection.">>) end. try_find_key(Fn, Map) -> @@ -109,7 +109,7 @@ try_get_value(Key, Map, OutRef) -> pick(Fn, Map) -> case try_pick(Fn, Map) of - undefined -> erlang:error(<<"key_not_found">>); + undefined -> key_not_found(<<"The given key was not present in the dictionary.">>); V -> V end. @@ -142,3 +142,9 @@ change(Key, Fn, Map) -> NewV -> maps:put(Key, NewV, Map) end end. + +key_not_found(Message) -> + erlang:error(#{ + exn_type => key_not_found_exception, + message => Message + }). diff --git a/src/fable-library-dart/Array.fs b/src/fable-library-dart/Array.fs index 447fd3cdb6..c9fdd1480f 100644 --- a/src/fable-library-dart/Array.fs +++ b/src/fable-library-dart/Array.fs @@ -101,9 +101,10 @@ type Native = static member firstWhere(xs: 'T[], predicate: 'T -> bool) : 'T = jsNative let private indexNotFound () = - failwith "An index satisfying the predicate was not found in the collection." + raise (KeyNotFoundException(SR.keyNotFoundAlt)) -let private differentLengths () = failwith "Arrays had different lengths" +let private differentLengths (paramName: string) = + invalidArg paramName "The arrays have different lengths." // https://stackoverflow.com/a/9113136 let reverseInPlace (xs: 'T[]) : unit = @@ -168,25 +169,25 @@ let map (f: 'T -> 'U) (source: 'T[]) : 'U[] = let mapIndexed2 (f: int -> 'T1 -> 'T2 -> 'U) (source1: 'T1[]) (source2: 'T2[]) : 'U[] = if source1.Length <> source2.Length then - failwith "Arrays had different lengths" + differentLengths "array1" Native.generate source1.Length (fun i -> f i source1[i] source2[i]) let map2 (f: 'T1 -> 'T2 -> 'U) (source1: 'T1[]) (source2: 'T2[]) : 'U[] = if source1.Length <> source2.Length then - failwith "Arrays had different lengths" + differentLengths "array1" Native.generate source1.Length (fun i -> f source1[i] source2[i]) let mapIndexed3 (f: int -> 'T1 -> 'T2 -> 'T3 -> 'U) (source1: 'T1[]) (source2: 'T2[]) (source3: 'T3[]) : 'U[] = if source1.Length <> source2.Length || source2.Length <> source3.Length then - failwith "Arrays had different lengths" + differentLengths "array1, array2, array3" Native.generate source1.Length (fun i -> f i source1[i] source2[i] source3[i]) let map3 (f: 'T1 -> 'T2 -> 'T3 -> 'U) (source1: 'T1[]) (source2: 'T2[]) (source3: 'T3[]) : 'U[] = if source1.Length <> source2.Length || source2.Length <> source3.Length then - failwith "Arrays had different lengths" + differentLengths "array1, array2, array3" Native.generate source1.Length (fun i -> f source1[i] source2[i] source3[i]) @@ -344,7 +345,7 @@ let scanBack<'T, 'State> (folder: 'T -> 'State -> 'State) (array: 'T[]) (state: let skip count (array: 'T[]) = if count > array.Length then - invalidArg "count" "count is greater than array length" + raise (System.ArgumentOutOfRangeException("count", "The index is outside the legal range.")) if count = array.Length then Array.empty @@ -367,7 +368,7 @@ let take count (array: 'T[]) = invalidArg "count" LanguagePrimitives.ErrorStrings.InputMustBeNonNegativeString if count > array.Length then - invalidArg "count" "count is greater than array length" + invalidOp SR.notEnoughElements if count = 0 then Array.empty @@ -423,7 +424,10 @@ let partition (f: 'T -> bool) (source: 'T[]) = Native.asFixed res1, Native.asFixed res2 -let find (predicate: 'T -> bool) (array: 'T[]) : 'T = Native.firstWhere (array, predicate) +let find (predicate: 'T -> bool) (array: 'T[]) : 'T = + match Native.indexWhere (array, predicate) with + | -1 -> indexNotFound () + | index -> array[index] let tryFind (predicate: 'T -> bool) (array: 'T[]) : 'T option = try @@ -546,14 +550,14 @@ let iterateIndexed (action: int -> 'T -> unit) (array: 'T[]) : unit = let iterate2 (action: 'T1 -> 'T2 -> unit) (array1: 'T1[]) (array2: 'T2[]) : unit = if array1.Length <> array2.Length then - differentLengths () + differentLengths "array1" for i = 0 to array1.Length - 1 do action array1[i] array2[i] let iterateIndexed2 (action: int -> 'T1 -> 'T2 -> unit) (array1: 'T1[]) (array2: 'T2[]) : unit = if array1.Length <> array2.Length then - differentLengths () + differentLengths "array1" for i = 0 to array1.Length - 1 do action i array1[i] array2[i] @@ -570,7 +574,7 @@ let permute (f: int -> int) (array: 'T[]) : 'T[] = let j = f i if j < 0 || j >= size then - invalidOp "Not a valid permutation" + invalidArg "indexMap" "The function did not compute a permutation." res[j] <- x checkFlags[j] <- 1 @@ -580,7 +584,7 @@ let permute (f: int -> int) (array: 'T[]) : 'T[] = let isValid = checkFlags |> forAll ((=) 1) if not isValid then - invalidOp "Not a valid permutation" + invalidArg "indexMap" "The function did not compute a permutation." res @@ -678,14 +682,14 @@ let unzip3 (array: ('a * 'b * 'c)[]) : 'a[] * 'b[] * 'c[] = let zip (array1: 'T[]) (array2: 'U[]) : ('T * 'U)[] = // Shorthand version: map2 (fun x y -> x, y) array1 array2 if array1.Length <> array2.Length then - differentLengths () + differentLengths "array1" Native.generate array1.Length (fun i -> array1[i], array2[i]) let zip3 (array1: 'T1[]) (array2: 'T2[]) (array3: 'T3[]) : ('T1 * 'T2 * 'T3)[] = // Shorthand version: map3 (fun x y z -> x, y, z) array1 array2 array3 if array1.Length <> array2.Length || array2.Length <> array3.Length then - differentLengths () + differentLengths "array1, array2, array3" Native.generate array1.Length (fun i -> array1[i], array2[i], array3[i]) @@ -708,9 +712,12 @@ let chunkBySize (chunkSize: int) (array: 'T[]) : 'T[][] = Native.asFixed result let splitAt (index: int) (array: 'T[]) : 'T[] * 'T[] = - if index < 0 || index > array.Length then + if index < 0 then invalidArg "index" SR.indexOutOfBounds + if index > array.Length then + invalidOp SR.notEnoughElements + Native.sublist (array, 0, index), Native.sublist (array, index) // Note that, unlike the `compare` operator, Array.compareWith compares elements first, @@ -822,7 +829,7 @@ let fold2<'T1, 'T2, 'State> let mutable acc = state if array1.Length <> array2.Length then - failwith "Arrays have different lengths" + differentLengths "array1" for i = 0 to array1.Length - 1 do acc <- folder acc array1[i] array2[i] @@ -839,7 +846,7 @@ let foldBack2<'T1, 'T2, 'State> let mutable acc = state if array1.Length <> array2.Length then - differentLengths () + differentLengths "array1" for i = array1.Length - 1 downto 0 do acc <- folder array1[i] array2[i] acc @@ -848,12 +855,14 @@ let foldBack2<'T1, 'T2, 'State> let reduce (reduction: 'T -> 'T -> 'T) (array: 'T[]) : 'T = // Dart's native reduce will fail if collection is empty - // if Array.isEmpty array then invalidOp LanguagePrimitives.ErrorStrings.InputArrayEmptyString + if Array.isEmpty array then + invalidArg "array" LanguagePrimitives.ErrorStrings.InputArrayEmptyString + Native.reduce reduction array let reduceBack (reduction: 'T -> 'T -> 'T) (array: 'T[]) : 'T = if Array.isEmpty array then - invalidOp LanguagePrimitives.ErrorStrings.InputArrayEmptyString + invalidArg "array" LanguagePrimitives.ErrorStrings.InputArrayEmptyString let mutable i = array.Length - 1 let mutable state = array[i] @@ -884,7 +893,7 @@ let rec existsOffset2 predicate (array1: _[]) (array2: _[]) index = let rec exists2 predicate (array1: _[]) (array2: _[]) = if array1.Length <> array2.Length then - differentLengths () + differentLengths "array1" existsOffset2 predicate array1 array2 0 @@ -970,7 +979,7 @@ let averageBy (projection: 'T -> 'T2) (array: 'T[]) ([] averager: IGener let windowed (windowSize: int) (source: 'T[]) : 'T[][] = if windowSize <= 0 then - failwith "windowSize must be positive" + invalidArg "windowSize" "The input must be positive." let len = Operators.max 0 (source.Length - windowSize + 1) Native.generate len (fun i -> source[i .. i + windowSize - 1]) @@ -1017,7 +1026,7 @@ let transpose (arrays: 'T[] seq) : 'T[][] = let lenInner = firstArray.Length if arrays |> forAll (fun a -> a.Length = lenInner) |> not then - differentLengths () + differentLengths "array.[0]" Native.generate lenInner (fun i -> Native.generate len (fun j -> arrays[j][i])) @@ -1138,7 +1147,7 @@ let randomShuffleInPlaceBy (randomizer: unit -> float) (xs: 'T[]) : unit = let r = randomizer () if r < 0.0 || r >= 1.0 then - invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException + raise (System.ArgumentOutOfRangeException("randomizer", "The index is outside the legal range.")) let j = int (r * float (i + 1)) let tmp = xs[i] @@ -1169,7 +1178,7 @@ let randomChoiceBy (randomizer: unit -> float) (xs: 'T[]) : 'T = let r = randomizer () if r < 0.0 || r >= 1.0 then - invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException + raise (System.ArgumentOutOfRangeException("randomizer", "The index is outside the legal range.")) xs[int (r * float len)] @@ -1193,7 +1202,7 @@ let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T[]) : 'T[] = let r = randomizer () if r < 0.0 || r >= 1.0 then - invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException + raise (System.ArgumentOutOfRangeException("randomizer", "The index is outside the legal range.")) xs[int (r * float len)] ) @@ -1221,7 +1230,7 @@ let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T[]) : 'T[] = let r = randomizer () if r < 0.0 || r >= 1.0 then - invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException + raise (System.ArgumentOutOfRangeException("randomizer", "The index is outside the legal range.")) let j = i + int (r * float (len - i)) let tmp = arr[i] diff --git a/src/fable-library-dart/Global.fs b/src/fable-library-dart/Global.fs index 0fcd96d1d0..5da4b222b5 100644 --- a/src/fable-library-dart/Global.fs +++ b/src/fable-library-dart/Global.fs @@ -16,7 +16,7 @@ module SR = let indexOutOfBounds = "The index was outside the range of elements in the collection." - let inputWasEmpty = "Collection was empty." + let inputWasEmpty = "The input list was empty." let inputMustBeNonNegative = "The input must be non-negative." let inputSequenceEmpty = "The input sequence was empty." diff --git a/src/fable-library-dart/List.fs b/src/fable-library-dart/List.fs index d0cd36bb3b..50c0948e56 100644 --- a/src/fable-library-dart/List.fs +++ b/src/fable-library-dart/List.fs @@ -27,14 +27,17 @@ and [] LinkedList<'T>(tail_: 'T list member this.Head: 'T = match this with | :? ConsList<'T> as this -> this.GetHead() - | _ -> failwith "Empty list" + | _ -> invalidOp SR.inputWasEmpty member this.TryHead: 'T option = match this with | :? ConsList<'T> as this -> this.GetHead() |> Some | _ -> None - member _.Tail: LinkedList<'T> = Option.get tail + member _.Tail: LinkedList<'T> = + match tail with + | Some t -> t + | None -> invalidOp SR.inputWasEmpty member _.TryTail: LinkedList<'T> option = tail @@ -159,7 +162,10 @@ let isEmpty (xs: 'T list) = xs.IsEmpty let length (xs: 'T list) = xs.Length -let head (xs: 'T list) = xs.Head +let head (xs: 'T list) = + match xs with + | :? ConsList<'T> as xs -> xs.GetHead() + | _ -> invalidArg "list" SR.inputWasEmpty let tryHead (xs: 'T list) = if xs.IsEmpty then @@ -167,7 +173,10 @@ let tryHead (xs: 'T list) = else Some xs.Head -let tail (xs: 'T list) = xs.Tail +let tail (xs: 'T list) = + match xs.TryTail with + | Some t -> t + | None -> invalidArg "list" SR.inputWasEmpty let rec tryLast (xs: 'T list) = if xs.IsEmpty then @@ -183,7 +192,7 @@ let rec tryLast (xs: 'T list) = let last (xs: 'T list) = match tryLast xs with | Some x -> x - | None -> failwith SR.inputWasEmpty + | None -> invalidArg "list" SR.inputWasEmpty let compareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list) : int = let rec loop (xs: 'T list) (ys: 'T list) = @@ -572,13 +581,13 @@ let replicate n x = initialize n (fun _ -> x) let reduce f (xs: 'T list) = if xs.IsEmpty then - invalidOp SR.inputWasEmpty + invalidArg "list" SR.inputWasEmpty else fold f (head xs) (tail xs) let reduceBack f (xs: 'T list) = if xs.IsEmpty then - invalidOp SR.inputWasEmpty + invalidArg "list" SR.inputWasEmpty else foldBack f (tail xs) (head xs) @@ -710,7 +719,7 @@ let rec skip count (xs: 'T list) = if count <= 0 then xs elif xs.IsEmpty then - invalidArg "list" SR.notEnoughElements + raise (System.ArgumentOutOfRangeException("count", "The index is outside the legal range.")) else skip (count - 1) xs.Tail @@ -730,7 +739,7 @@ let take count (xs: 'T list) = if i <= 0 then acc elif xs.IsEmpty then - invalidArg "list" SR.notEnoughElements + invalidOp SR.notEnoughElements else loop (i - 1) (acc.AppendConsNoTail xs.Head) xs.Tail @@ -796,7 +805,7 @@ let splitAt index (xs: 'T list) = invalidArg "index" SR.inputMustBeNonNegative if index > xs.Length then - invalidArg "index" SR.notEnoughElements + invalidOp SR.notEnoughElements take index xs, skip index xs diff --git a/src/fable-library-dart/Map.fs b/src/fable-library-dart/Map.fs index 050ec50915..dc03ad26b2 100644 --- a/src/fable-library-dart/Map.fs +++ b/src/fable-library-dart/Map.fs @@ -607,9 +607,9 @@ module MapTree = started = false } - let notStarted () = failwith "enumeration not started" + let notStarted () = invalidOp SR.enumerationNotStarted - let alreadyFinished () = failwith "enumeration already finished" + let alreadyFinished () = invalidOp SR.enumerationAlreadyFinished let current i = if i.started then diff --git a/src/fable-library-dart/Range.fs b/src/fable-library-dart/Range.fs index 2e921701bd..75477dbe4e 100644 --- a/src/fable-library-dart/Range.fs +++ b/src/fable-library-dart/Range.fs @@ -4,7 +4,7 @@ let makeRangeStepFunction<'T> (step: 'T) (stop: 'T) (zero: 'T) (add: 'T -> 'T -> let stepComparedWithZero = compare step zero if stepComparedWithZero = 0 then - failwith "The step of a range cannot be zero" + invalidArg "step" "The step of a range cannot be zero." let stepGreaterThanZero = stepComparedWithZero > 0 diff --git a/src/fable-library-dart/Seq.fs b/src/fable-library-dart/Seq.fs index 36d5fdfb2f..dc22feb87f 100644 --- a/src/fable-library-dart/Seq.fs +++ b/src/fable-library-dart/Seq.fs @@ -944,13 +944,13 @@ let reduce (folder: 'T -> 'T -> 'T) (xs: seq<'T>) : 'T = if e.MoveNext() then loop e.Current else - invalidOp SR.inputSequenceEmpty + invalidArg "source" SR.inputSequenceEmpty let reduceBack (folder: 'T -> 'T -> 'T) (xs: seq<'T>) : 'T = let arr = toArray xs if Array.isEmpty arr then - invalidOp SR.inputSequenceEmpty + invalidArg "source" SR.inputSequenceEmpty Array.reduceBack folder arr @@ -987,7 +987,7 @@ let skip (count: int) (source: seq<'T>) : seq<'T> = try for _ = 1 to count do if not (e.MoveNext()) then - invalidArg "source" SR.notEnoughElements + invalidOp SR.notEnoughElements let compensation () = () Enumerator.enumerateThenFinally compensation e @@ -1009,7 +1009,21 @@ let skipWhile (predicate: 'T -> bool) (xs: seq<'T>) : seq<'T> = ) ) -let tail (xs: seq<'T>) : seq<'T> = skip 1 xs +// FSharp.Core's Seq.tail raises ArgumentException on an empty input, Seq.skip InvalidOperationException +let tail (xs: seq<'T>) : seq<'T> = + mkSeq (fun () -> + let e = ofSeq xs + + try + if not (e.MoveNext()) then + invalidArg "source" SR.notEnoughElements + + let compensation () = () + Enumerator.enumerateThenFinally compensation e + with _ -> + e.Dispose() + reraise () + ) let take (count: int) (xs: seq<'T>) : seq<'T> = Enumerator.generateIndexed @@ -1019,7 +1033,7 @@ let take (count: int) (xs: seq<'T>) : seq<'T> = if e.MoveNext() then Some(e.Current) else - invalidArg "source" SR.notEnoughElements + invalidOp SR.notEnoughElements else None ) diff --git a/src/fable-library-dart/Set.fs b/src/fable-library-dart/Set.fs index 6d24568e3b..0381ed456f 100644 --- a/src/fable-library-dart/Set.fs +++ b/src/fable-library-dart/Set.fs @@ -480,12 +480,12 @@ module SetTree = let minimumElement s = match minimumElementOpt s with | Some k -> k - | None -> failwith "Set contains no elements" + | None -> invalidArg "s" "Set contains no elements." let maximumElement s = match maximumElementOpt s with | Some k -> k - | None -> failwith "Set contains no elements" + | None -> invalidArg "s" "Set contains no elements." // Imperative left-to-right iterators. [] @@ -515,9 +515,9 @@ module SetTree = started = false } - let notStarted () = failwith "Enumeration not started" + let notStarted () = invalidOp SR.enumerationNotStarted - let alreadyFinished () = failwith "Enumeration already started" + let alreadyFinished () = invalidOp SR.enumerationAlreadyFinished let current i = if i.started then diff --git a/src/fable-library-py/fable_library/List.fs b/src/fable-library-py/fable_library/List.fs index 509cf2d126..9859354847 100644 --- a/src/fable-library-py/fable_library/List.fs +++ b/src/fable-library-py/fable_library/List.fs @@ -48,12 +48,12 @@ type LinkedList<'T> = member xs.Head = match xs.tail with - | None -> invalidArg "list" SR.inputWasEmpty + | None -> invalidOp SR.inputWasEmpty | Some _ -> xs.head member xs.Tail = match xs.tail with - | None -> invalidArg "list" SR.inputWasEmpty + | None -> invalidOp SR.inputWasEmpty | Some t -> t member xs.Item @@ -177,7 +177,10 @@ let isEmpty (xs: 'T list) = xs.IsEmpty let length (xs: 'T list) = xs.Length -let head (xs: 'T list) = xs.Head +let head (xs: 'T list) = + match xs.tail with + | None -> invalidArg "list" SR.inputWasEmpty + | Some _ -> xs.head let tryHead (xs: 'T list) = if xs.IsEmpty then @@ -185,7 +188,10 @@ let tryHead (xs: 'T list) = else Some xs.Head -let tail (xs: 'T list) = xs.Tail +let tail (xs: 'T list) = + match xs.tail with + | None -> invalidArg "list" SR.inputWasEmpty + | Some t -> t let rec tryLast (xs: 'T list) = if xs.IsEmpty then @@ -201,7 +207,7 @@ let rec tryLast (xs: 'T list) = let last (xs: 'T list) = match tryLast xs with | Some x -> x - | None -> failwith SR.inputWasEmpty + | None -> invalidArg "list" SR.inputWasEmpty let compareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list) : int = let rec loop (xs: 'T list) (ys: 'T list) = @@ -596,13 +602,13 @@ let replicate n x = initialize n (fun _ -> x) let reduce f (xs: 'T list) = if xs.IsEmpty then - invalidOp SR.inputWasEmpty + invalidArg "list" SR.inputWasEmpty else fold f (head xs) (tail xs) let reduceBack f (xs: 'T list) = if xs.IsEmpty then - invalidOp SR.inputWasEmpty + invalidArg "list" SR.inputWasEmpty else foldBack f (tail xs) (head xs) @@ -738,7 +744,7 @@ let rec skip count (xs: 'T list) = if count <= 0 then xs elif xs.IsEmpty then - invalidArg "list" SR.notEnoughElements + raise (System.ArgumentOutOfRangeException("count", "The index is outside the legal range.")) else skip (count - 1) xs.Tail @@ -758,7 +764,7 @@ let take count (xs: 'T list) = if i <= 0 then acc elif xs.IsEmpty then - invalidArg "list" SR.notEnoughElements + invalidOp SR.notEnoughElements else loop (i - 1) (acc.AppendConsNoTail xs.Head) xs.Tail @@ -824,7 +830,7 @@ let splitAt index (xs: 'T list) = invalidArg "index" SR.inputMustBeNonNegative if index > xs.Length then - invalidArg "index" SR.notEnoughElements + invalidOp SR.notEnoughElements take index xs, skip index xs diff --git a/src/fable-library-py/fable_library/Map.fs b/src/fable-library-py/fable_library/Map.fs index 6416366633..c3368deb0a 100644 --- a/src/fable-library-py/fable_library/Map.fs +++ b/src/fable-library-py/fable_library/Map.fs @@ -593,9 +593,11 @@ module MapTree = started = false } - let notStarted () = failwith "enumeration not started" + let notStarted () = + invalidOp "Enumeration has not started. Call MoveNext." - let alreadyFinished () = failwith "enumeration already finished" + let alreadyFinished () = + invalidOp "Enumeration already finished." let current i = if i.started then @@ -900,14 +902,14 @@ type Map<[] 'Key, [> with member m.Add x = ignore x - raise (System.NotSupportedException("Map cannot be mutated")) + raise (System.NotSupportedException("Map values cannot be mutated.")) member m.Clear() = - raise (System.NotSupportedException("Map cannot be mutated")) + raise (System.NotSupportedException("Map values cannot be mutated.")) member m.Remove x = ignore x - raise (System.NotSupportedException("Map cannot be mutated")) + raise (System.NotSupportedException("Map values cannot be mutated.")) member m.Contains x = m.ContainsKey x.Key && Unchecked.equals m.[x.Key] x.Value diff --git a/src/fable-library-py/fable_library/Range.fs b/src/fable-library-py/fable_library/Range.fs index db1175c507..617f2472a8 100644 --- a/src/fable-library-py/fable_library/Range.fs +++ b/src/fable-library-py/fable_library/Range.fs @@ -4,7 +4,7 @@ let makeRangeStepFunction<'T when 'T: comparison> (step: 'T) (stop: 'T) (zero: ' let stepComparedWithZero = compare step zero if stepComparedWithZero = 0 then - failwith "The step of a range cannot be zero" + invalidArg "step" "The step of a range cannot be zero." let stepGreaterThanZero = stepComparedWithZero > 0 diff --git a/src/fable-library-py/fable_library/Seq.fs b/src/fable-library-py/fable_library/Seq.fs index 7991b5b6ad..e15b92cd11 100644 --- a/src/fable-library-py/fable_library/Seq.fs +++ b/src/fable-library-py/fable_library/Seq.fs @@ -880,7 +880,7 @@ let reduce folder (xs: seq<'T>) = if e.MoveNext() then loop e.Current else - invalidOp SR.inputSequenceEmpty + invalidArg "source" SR.inputSequenceEmpty let reduceBack folder (xs: seq<'T>) = let arr = toArray xs @@ -888,7 +888,7 @@ let reduceBack folder (xs: seq<'T>) = if arr.Length > 0 then Array.reduceBack folder arr else - invalidOp SR.inputSequenceEmpty + invalidArg "source" SR.inputSequenceEmpty let replicate n x = initialize n (fun _ -> x) @@ -923,7 +923,7 @@ let skip count (source: seq<'T>) = try for _ = 1 to count do if not (e.MoveNext()) then - invalidArg "source" SR.notEnoughElements + invalidOp SR.notEnoughElements let compensation () = () Enumerator.enumerateThenFinally compensation e @@ -945,7 +945,21 @@ let skipWhile predicate (xs: seq<'T>) = ) ) -let tail (xs: seq<'T>) = skip 1 xs +// FSharp.Core's Seq.tail raises ArgumentException on an empty input, Seq.skip InvalidOperationException +let tail (xs: seq<'T>) = + mkSeq (fun () -> + let e = ofSeq xs + + try + if not (e.MoveNext()) then + invalidArg "source" SR.notEnoughElements + + let compensation () = () + Enumerator.enumerateThenFinally compensation e + with _ -> + e.Dispose() + reraise () + ) // `take`/`takeWhile`: no F# impl - Replacements.fs redirects both straight to `seq_native`. diff --git a/src/fable-library-py/fable_library/Set.fs b/src/fable-library-py/fable_library/Set.fs index 7398b1d7de..0e326e35b1 100644 --- a/src/fable-library-py/fable_library/Set.fs +++ b/src/fable-library-py/fable_library/Set.fs @@ -482,12 +482,12 @@ module SetTree = let minimumElement s = match minimumElementOpt s with | Some k -> k - | None -> failwith "Set contains no elements" + | None -> invalidArg "s" "Set contains no elements." let maximumElement s = match maximumElementOpt s with | Some k -> k - | None -> failwith "Set contains no elements" + | None -> invalidArg "s" "Set contains no elements." // Imperative left-to-right iterators. [] @@ -517,9 +517,11 @@ module SetTree = started = false } - let notStarted () = failwith "Enumeration not started" + let notStarted () = + invalidOp "Enumeration has not started. Call MoveNext." - let alreadyFinished () = failwith "Enumeration already started" + let alreadyFinished () = + invalidOp "Enumeration already finished." let current i = if i.started then diff --git a/src/fable-library-py/fable_library/seq_native.py b/src/fable-library-py/fable_library/seq_native.py index 335ba69ebe..f9a9750ce4 100644 --- a/src/fable-library-py/fable_library/seq_native.py +++ b/src/fable-library-py/fable_library/seq_native.py @@ -26,6 +26,7 @@ from .list import try_item as list_try_item from .option import Option, some, value from .protocols import IEnumerable_1, IEnumerator +from .system import ArgumentException__ctor_Z384F8060, InvalidOperationException__ctor_Z721C83C5 from .types import UNIT, Unit from .util import Enumerator, get_enumerator, to_iterator @@ -165,7 +166,9 @@ def gen() -> Iterator[T]: for _ in range(count): v = next(it, _SENTINEL) if v is _SENTINEL: - raise Exception("The input sequence has an insufficient number of elements. (Parameter 'source')") + raise InvalidOperationException__ctor_Z721C83C5( + "The input sequence has an insufficient number of elements." + ) yield v return NativeSeq(gen) @@ -187,7 +190,7 @@ def try_head[T](xs: IEnumerable_1[T]) -> Option[T]: def head[T](xs: IEnumerable_1[T]) -> T: result = try_head(xs) if result is None: - raise Exception("The input sequence was empty. (Parameter 'source')") + raise ArgumentException__ctor_Z384F8060("The input sequence was empty.", "source") return value(result) @@ -213,7 +216,7 @@ def try_item[T](index: int, xs: IEnumerable_1[T]) -> Option[T]: def item[T](index: int, xs: IEnumerable_1[T]) -> T: result = try_item(index, xs) if result is None: - raise Exception("The input sequence has an insufficient number of elements. (Parameter 'index')") + raise ArgumentException__ctor_Z384F8060("The input sequence has an insufficient number of elements.", "index") return value(result) diff --git a/src/fable-library-ts/Array.fs b/src/fable-library-ts/Array.fs index 9632b11662..066ecf9e29 100644 --- a/src/fable-library-ts/Array.fs +++ b/src/fable-library-ts/Array.fs @@ -11,9 +11,10 @@ open Native open Native.Helpers let private indexNotFound () = - failwith "An index satisfying the predicate was not found in the collection." + raise (KeyNotFoundException(SR.keyNotFoundAlt)) -let private differentLengths () = failwith "Arrays had different lengths" +let private differentLengths (paramName: string) = + invalidArg paramName "The arrays have different lengths." // Pay attention when benchmarking to append and filter functions below // if implementing via native JS array .concat() and .filter() do not fall behind due to js-native transitions. @@ -77,7 +78,7 @@ let mapIndexed2 : 'U[] = if source1.Length <> source2.Length then - failwith "Arrays had different lengths" + differentLengths "array1" let result = allocateArrayFromCons cons source1.Length @@ -88,7 +89,7 @@ let mapIndexed2 let map2 (f: 'T1 -> 'T2 -> 'U) (source1: 'T1[]) (source2: 'T2[]) ([] cons: Cons<'U>) : 'U[] = if source1.Length <> source2.Length then - failwith "Arrays had different lengths" + differentLengths "array1" let result = allocateArrayFromCons cons source1.Length @@ -106,7 +107,7 @@ let mapIndexed3 : 'U[] = if source1.Length <> source2.Length || source2.Length <> source3.Length then - failwith "Arrays had different lengths" + differentLengths "array1, array2, array3" let result = allocateArrayFromCons cons source1.Length @@ -124,7 +125,7 @@ let map3 : 'U[] = if source1.Length <> source2.Length || source2.Length <> source3.Length then - failwith "Arrays had different lengths" + differentLengths "array1, array2, array3" let result = allocateArrayFromCons cons source1.Length @@ -328,7 +329,7 @@ let scanBack<'T, 'State> folder (array: 'T[]) (state: 'State) ([] cons: Cons<'T>) = if count > array.Length then - invalidArg "count" "count is greater than array length" + raise (System.ArgumentOutOfRangeException("count", "The index is outside the legal range.")) if count = array.Length then allocateArrayFromCons cons 0 @@ -357,7 +358,7 @@ let take count (array: 'T[]) ([] cons: Cons<'T>) = invalidArg "count" LanguagePrimitives.ErrorStrings.InputMustBeNonNegativeString if count > array.Length then - invalidArg "count" "count is greater than array length" + invalidOp SR.notEnoughElements if count = 0 then allocateArrayFromCons cons 0 @@ -571,14 +572,14 @@ let iterateIndexed action (array: 'T[]) = let iterate2 action (array1: 'T1[]) (array2: 'T2[]) = if array1.Length <> array2.Length then - differentLengths () + differentLengths "array1" for i = 0 to array1.Length - 1 do action array1.[i] array2.[i] let iterateIndexed2 action (array1: 'T1[]) (array2: 'T2[]) = if array1.Length <> array2.Length then - differentLengths () + differentLengths "array1" for i = 0 to array1.Length - 1 do action i array1.[i] array2.[i] @@ -599,14 +600,15 @@ let forAll predicate (array: 'T[]) = let permute f (array: 'T[]) = let size = array.Length let res = copyImpl array - let checkFlags = allocateArray size + // Native `every` skips the empty slots of `new Array(size)` + let checkFlags = fillImpl (allocateArray size) 0 0 size iterateIndexed (fun i x -> let j = f i if j < 0 || j >= size then - invalidOp "Not a valid permutation" + invalidArg "indexMap" "The function did not compute a permutation." res.[j] <- x checkFlags.[j] <- 1 @@ -616,7 +618,7 @@ let permute f (array: 'T[]) = let isValid = checkFlags |> forAllImpl ((=) 1) if not isValid then - invalidOp "Not a valid permutation" + invalidArg "indexMap" "The function did not compute a permutation." res @@ -719,7 +721,7 @@ let unzip3 (array: _[]) = let zip (array1: 'T[]) (array2: 'U[]) = // Shorthand version: map2 (fun x y -> x, y) array1 array2 if array1.Length <> array2.Length then - differentLengths () + differentLengths "array1" let result = allocateArray array1.Length @@ -731,7 +733,7 @@ let zip (array1: 'T[]) (array2: 'U[]) = let zip3 (array1: 'T[]) (array2: 'U[]) (array3: 'V[]) = // Shorthand version: map3 (fun x y z -> x, y, z) array1 array2 array3 if array1.Length <> array2.Length || array2.Length <> array3.Length then - differentLengths () + differentLengths "array1, array2, array3" let result = allocateArray array1.Length @@ -758,9 +760,12 @@ let chunkBySize (chunkSize: int) (array: 'T[]) : 'T[][] = result |> asArray let splitAt (index: int) (array: 'T[]) : 'T[] * 'T[] = - if index < 0 || index > array.Length then + if index < 0 then invalidArg "index" SR.indexOutOfBounds + if index > array.Length then + invalidOp SR.notEnoughElements + subArrayImpl array 0 index, skipImpl array index // Note that, though it's not consistent with `compare` operator, @@ -921,7 +926,7 @@ let foldIndexed2 folder state (array1: _[]) (array2: _[]) = let mutable acc = state if array1.Length <> array2.Length then - failwith "Arrays have different lengths" + differentLengths "array1" for i = 0 to array1.Length - 1 do acc <- folder i acc array1.[i] array2.[i] @@ -935,7 +940,7 @@ let foldBackIndexed2<'T1, 'T2, 'State> folder (array1: 'T1[]) (array2: 'T2[]) (s let mutable acc = state if array1.Length <> array2.Length then - differentLengths () + differentLengths "array1" let size = array1.Length @@ -949,7 +954,7 @@ let foldBack2<'T1, 'T2, 'State> f (array1: 'T1[]) (array2: 'T2[]) (state: 'State let reduce reduction (array: 'T[]) = if array.Length = 0 then - invalidOp LanguagePrimitives.ErrorStrings.InputArrayEmptyString + invalidArg "array" LanguagePrimitives.ErrorStrings.InputArrayEmptyString // if isTypedArrayImpl array then // foldIndexed (fun i acc x -> if i = 0 then x else reduction acc x) Unchecked.defaultof<_> array // else @@ -957,7 +962,7 @@ let reduce reduction (array: 'T[]) = let reduceBack reduction (array: 'T[]) = if array.Length = 0 then - invalidOp LanguagePrimitives.ErrorStrings.InputArrayEmptyString + invalidArg "array" LanguagePrimitives.ErrorStrings.InputArrayEmptyString // if isTypedArrayImpl array then // foldBackIndexed (fun i x acc -> if i = 0 then x else reduction acc x) array Unchecked.defaultof<_> // else @@ -983,7 +988,7 @@ let rec existsOffset2 predicate (array1: _[]) (array2: _[]) index = let rec exists2 predicate (array1: _[]) (array2: _[]) = if array1.Length <> array2.Length then - differentLengths () + differentLengths "array1" existsOffset2 predicate array1 array2 0 @@ -1069,7 +1074,7 @@ let averageBy (projection: 'T -> 'T2) (array: 'T[]) ([] averager: IGener let windowed (windowSize: int) (source: 'T[]) : 'T[][] = if windowSize <= 0 then - failwith "windowSize must be positive" + invalidArg "windowSize" "The input must be positive." let res = FSharp.Core.Operators.max 0 (source.Length - windowSize + 1) |> allocateArray @@ -1120,7 +1125,7 @@ let transpose (arrays: 'T[] seq) ([] cons: Cons<'T>) : let lenInner = firstArray.Length if arrays |> forAll (fun a -> a.Length = lenInner) |> not then - differentLengths () + differentLengths "array.[0]" let result: 'T[][] = allocateArray lenInner @@ -1178,7 +1183,7 @@ let randomShuffleInPlaceBy (randomizer: unit -> float) (xs: 'T[]) : unit = let r = randomizer () if r < 0.0 || r >= 1.0 then - invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException + raise (System.ArgumentOutOfRangeException("randomizer", "The index is outside the legal range.")) let j = int (r * float (i + 1)) let tmp = xs[i] @@ -1209,7 +1214,7 @@ let randomChoiceBy (randomizer: unit -> float) (xs: 'T[]) : 'T = let r = randomizer () if r < 0.0 || r >= 1.0 then - invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException + raise (System.ArgumentOutOfRangeException("randomizer", "The index is outside the legal range.")) xs[int (r * float len)] @@ -1239,7 +1244,7 @@ let randomChoicesBy let r = randomizer () if r < 0.0 || r >= 1.0 then - invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException + raise (System.ArgumentOutOfRangeException("randomizer", "The index is outside the legal range.")) xs[int (r * float len)] ) @@ -1274,7 +1279,7 @@ let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T[]) : 'T[] = let r = randomizer () if r < 0.0 || r >= 1.0 then - invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException + raise (System.ArgumentOutOfRangeException("randomizer", "The index is outside the legal range.")) let j = i + int (r * float (len - i)) let tmp = arr[i] diff --git a/src/fable-library-ts/Global.fs b/src/fable-library-ts/Global.fs index 8b132814d3..416e179656 100644 --- a/src/fable-library-ts/Global.fs +++ b/src/fable-library-ts/Global.fs @@ -22,7 +22,7 @@ module SR = let indexOutOfBounds = "The index was outside the range of elements in the collection." - let inputWasEmpty = "Collection was empty." + let inputWasEmpty = "The input list was empty." let inputMustBeNonNegative = "The input must be non-negative." let inputSequenceEmpty = "The input sequence was empty." diff --git a/src/fable-library-ts/List.fs b/src/fable-library-ts/List.fs index e41e9724c7..bf29b18fa2 100644 --- a/src/fable-library-ts/List.fs +++ b/src/fable-library-ts/List.fs @@ -48,12 +48,12 @@ type LinkedList<'T> = member xs.Head = match xs.tail with - | None -> invalidArg "list" SR.inputWasEmpty + | None -> invalidOp SR.inputWasEmpty | Some _ -> xs.head member xs.Tail = match xs.tail with - | None -> invalidArg "list" SR.inputWasEmpty + | None -> invalidOp SR.inputWasEmpty | Some t -> t member xs.Item @@ -186,7 +186,10 @@ let isEmpty (xs: 'T list) = xs.IsEmpty let length (xs: 'T list) = xs.Length -let head (xs: 'T list) = xs.Head +let head (xs: 'T list) = + match xs.tail with + | None -> invalidArg "list" SR.inputWasEmpty + | Some _ -> xs.head let tryHead (xs: 'T list) = if xs.IsEmpty then @@ -194,7 +197,10 @@ let tryHead (xs: 'T list) = else Some xs.Head -let tail (xs: 'T list) = xs.Tail +let tail (xs: 'T list) = + match xs.tail with + | None -> invalidArg "list" SR.inputWasEmpty + | Some t -> t let rec tryLast (xs: 'T list) = if xs.IsEmpty then @@ -210,7 +216,7 @@ let rec tryLast (xs: 'T list) = let last (xs: 'T list) = match tryLast xs with | Some x -> x - | None -> failwith SR.inputWasEmpty + | None -> invalidArg "list" SR.inputWasEmpty let compareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list) : int = let rec loop (xs: 'T list) (ys: 'T list) = @@ -605,13 +611,13 @@ let replicate n x = initialize n (fun _ -> x) let reduce f (xs: 'T list) = if xs.IsEmpty then - invalidOp SR.inputWasEmpty + invalidArg "list" SR.inputWasEmpty else fold f (head xs) (tail xs) let reduceBack f (xs: 'T list) = if xs.IsEmpty then - invalidOp SR.inputWasEmpty + invalidArg "list" SR.inputWasEmpty else foldBack f (tail xs) (head xs) @@ -747,7 +753,7 @@ let rec skip count (xs: 'T list) = if count <= 0 then xs elif xs.IsEmpty then - invalidArg "list" SR.notEnoughElements + raise (System.ArgumentOutOfRangeException("count", "The index is outside the legal range.")) else skip (count - 1) xs.Tail @@ -767,7 +773,7 @@ let take count (xs: 'T list) = if i <= 0 then acc elif xs.IsEmpty then - invalidArg "list" SR.notEnoughElements + invalidOp SR.notEnoughElements else loop (i - 1) (acc.AppendConsNoTail xs.Head) xs.Tail @@ -833,7 +839,7 @@ let splitAt index (xs: 'T list) = invalidArg "index" SR.inputMustBeNonNegative if index > xs.Length then - invalidArg "index" SR.notEnoughElements + invalidOp SR.notEnoughElements take index xs, skip index xs diff --git a/src/fable-library-ts/Map.fs b/src/fable-library-ts/Map.fs index ee52a22643..97d8f177fc 100644 --- a/src/fable-library-ts/Map.fs +++ b/src/fable-library-ts/Map.fs @@ -592,9 +592,11 @@ module MapTree = started = false } - let notStarted () = failwith "enumeration not started" + let notStarted () = + invalidOp "Enumeration has not started. Call MoveNext." - let alreadyFinished () = failwith "enumeration already finished" + let alreadyFinished () = + invalidOp "Enumeration already finished." let current i = if i.started then @@ -905,14 +907,14 @@ type Map<[] 'Key, [> with member m.Add x = ignore x - raise (System.NotSupportedException("Map cannot be mutated")) + raise (System.NotSupportedException("Map values cannot be mutated.")) member m.Clear() = - raise (System.NotSupportedException("Map cannot be mutated")) + raise (System.NotSupportedException("Map values cannot be mutated.")) member m.Remove x = ignore x - raise (System.NotSupportedException("Map cannot be mutated")) + raise (System.NotSupportedException("Map values cannot be mutated.")) member m.Contains x = m.ContainsKey x.Key && Unchecked.equals m.[x.Key] x.Value @@ -935,11 +937,11 @@ type Map<[] 'Key, [ Seq.map (fun p -> p.Key, p.Value) @@ -948,7 +950,7 @@ type Map<[] 'Key, [ Seq.map (fun p -> p.Key) member m.set(k, v) = - failwith "Map cannot be mutated" + raise (System.NotSupportedException("Map values cannot be mutated.")) m :> JS.Map<'Key, 'Value> member m.values() = m |> Seq.map (fun p -> p.Value) diff --git a/src/fable-library-ts/Range.fs b/src/fable-library-ts/Range.fs index d6ce3d8933..eeaa35e429 100644 --- a/src/fable-library-ts/Range.fs +++ b/src/fable-library-ts/Range.fs @@ -4,7 +4,7 @@ let makeRangeStepFunction<'T when 'T: comparison> (step: 'T) (stop: 'T) (zero: ' let stepComparedWithZero = compare step zero if stepComparedWithZero = 0 then - failwith "The step of a range cannot be zero" + invalidArg "step" "The step of a range cannot be zero." let stepGreaterThanZero = stepComparedWithZero > 0 diff --git a/src/fable-library-ts/Seq.fs b/src/fable-library-ts/Seq.fs index 20631e4a89..fe33447e9a 100644 --- a/src/fable-library-ts/Seq.fs +++ b/src/fable-library-ts/Seq.fs @@ -991,7 +991,7 @@ let reduce folder (xs: seq<'T>) = if e.MoveNext() then loop e.Current else - invalidOp SR.inputSequenceEmpty + invalidArg "source" SR.inputSequenceEmpty let reduceBack folder (xs: seq<'T>) = let arr = toArray xs @@ -999,7 +999,7 @@ let reduceBack folder (xs: seq<'T>) = if arr.Length > 0 then Array.reduceBack folder arr else - invalidOp SR.inputSequenceEmpty + invalidArg "source" SR.inputSequenceEmpty let replicate n x = initialize n (fun _ -> x) @@ -1034,7 +1034,7 @@ let skip count (source: seq<'T>) = try for _ = 1 to count do if not (e.MoveNext()) then - invalidArg "source" SR.notEnoughElements + invalidOp SR.notEnoughElements let compensation () = () Enumerator.enumerateThenFinally compensation e @@ -1056,7 +1056,21 @@ let skipWhile predicate (xs: seq<'T>) = ) ) -let tail (xs: seq<'T>) = skip 1 xs +// FSharp.Core's Seq.tail raises ArgumentException on an empty input, Seq.skip InvalidOperationException +let tail (xs: seq<'T>) = + mkSeq (fun () -> + let e = ofSeq xs + + try + if not (e.MoveNext()) then + invalidArg "source" SR.notEnoughElements + + let compensation () = () + Enumerator.enumerateThenFinally compensation e + with _ -> + e.Dispose() + reraise () + ) let take count (xs: seq<'T>) = generateIndexed @@ -1066,7 +1080,7 @@ let take count (xs: seq<'T>) = if e.MoveNext() then Some(e.Current) else - invalidArg "source" SR.notEnoughElements + invalidOp SR.notEnoughElements else None ) diff --git a/src/fable-library-ts/Set.fs b/src/fable-library-ts/Set.fs index 4a39504015..ac222fc327 100644 --- a/src/fable-library-ts/Set.fs +++ b/src/fable-library-ts/Set.fs @@ -482,12 +482,12 @@ module SetTree = let minimumElement s = match minimumElementOpt s with | Some k -> k - | None -> failwith "Set contains no elements" + | None -> invalidArg "s" "Set contains no elements." let maximumElement s = match maximumElementOpt s with | Some k -> k - | None -> failwith "Set contains no elements" + | None -> invalidArg "s" "Set contains no elements." // Imperative left-to-right iterators. [] @@ -517,9 +517,11 @@ module SetTree = started = false } - let notStarted () = failwith "Enumeration not started" + let notStarted () = + invalidOp "Enumeration has not started. Call MoveNext." - let alreadyFinished () = failwith "Enumeration already started" + let alreadyFinished () = + invalidOp "Enumeration already finished." let current i = if i.started then diff --git a/tests/Beam/ArrayTests.fs b/tests/Beam/ArrayTests.fs index 9b4544b7d5..8fbd898d6d 100644 --- a/tests/Beam/ArrayTests.fs +++ b/tests/Beam/ArrayTests.fs @@ -1343,3 +1343,32 @@ let ``test byte array from literal works`` () = data.[1] |> equal 2uy data.[2] |> equal 3uy data.Length |> equal 3 + +[] +let ``test Array.take raises InvalidOperationException when the array is too short`` () = + raisedExceptionType (fun () -> Array.take 5 [|1|]) |> equal "InvalidOperationException" + +[] +let ``test Array.find and friends raise KeyNotFoundException when nothing matches`` () = + raisedExceptionType (fun () -> Array.find (fun x -> x > 5) [|1|]) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.findBack (fun x -> x > 5) [|1|]) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.findIndex (fun x -> x > 5) [|1|]) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.findIndexBack (fun x -> x > 5) [|1|]) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.pick (fun _ -> None: int option) [|1|]) |> equal "KeyNotFoundException" + +[] +let ``test Array random functions raise ArgumentOutOfRangeException when the randomizer is out of range`` () = + raisedExceptionType (fun () -> Array.randomShuffleBy (fun () -> 1.0) [|1; 2|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomShuffleInPlaceBy (fun () -> -0.1) [|1; 2|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomChoiceBy (fun () -> 1.0) [|1; 2|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomChoicesBy (fun () -> 1.0) 1 [|1; 2|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomSampleBy (fun () -> 1.0) 1 [|1; 2|]) |> equal "ArgumentOutOfRangeException" + +[] +let ``test Array random functions raise ArgumentException for invalid inputs`` () = + raisedExceptionType (fun () -> Array.randomChoice [||]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.randomChoices -1 [|1|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.randomChoices 1 [||]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.randomSample -1 [|1|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.randomSample 3 [|1; 2|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.randomSample 1 [||]) |> equal "ArgumentException" diff --git a/tests/Beam/ListTests.fs b/tests/Beam/ListTests.fs index 2733453992..498beca6e1 100644 --- a/tests/Beam/ListTests.fs +++ b/tests/Beam/ListTests.fs @@ -906,3 +906,37 @@ let ``test List.range works`` () = [0..2..9] |> List.reduce (+) |> equal 20 + +[] +let ``test List.take raises InvalidOperationException when the list is too short`` () = + raisedExceptionType (fun () -> List.take 5 [1]) |> equal "InvalidOperationException" + +[] +let ``test List.exactlyOne raises ArgumentException unless the list has one element`` () = + raisedExceptionType (fun () -> List.exactlyOne []) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.exactlyOne [1; 2]) |> equal "ArgumentException" + List.exactlyOne [1] |> equal 1 + +[] +let ``test List.find and friends raise KeyNotFoundException when nothing matches`` () = + raisedExceptionType (fun () -> List.find (fun x -> x > 5) [1]) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> List.findBack (fun x -> x > 5) [1]) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> List.findIndex (fun x -> x > 5) [1]) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> List.findIndexBack (fun x -> x > 5) [1]) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> List.pick (fun _ -> None: int option) [1]) |> equal "KeyNotFoundException" + +[] +let ``test List random functions raise ArgumentOutOfRangeException when the randomizer is out of range`` () = + raisedExceptionType (fun () -> List.randomShuffleBy (fun () -> 1.0) [1; 2]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> List.randomChoiceBy (fun () -> 1.0) [1; 2]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> List.randomChoicesBy (fun () -> -0.1) 1 [1; 2]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> List.randomSampleBy (fun () -> 1.0) 1 [1; 2]) |> equal "ArgumentOutOfRangeException" + +[] +let ``test List random functions raise ArgumentException for invalid inputs`` () = + raisedExceptionType (fun () -> List.randomChoice []) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.randomChoices -1 [1]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.randomChoices 1 []) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.randomSample -1 [1]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.randomSample 3 [1; 2]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.randomSample 1 []) |> equal "ArgumentException" diff --git a/tests/Beam/MapTests.fs b/tests/Beam/MapTests.fs index 68c4fcf07a..a99b869105 100644 --- a/tests/Beam/MapTests.fs +++ b/tests/Beam/MapTests.fs @@ -409,3 +409,9 @@ let ``test Map.toSeq works`` () = let zs = Map.toSeq ys (Seq.item 2 xs |> snd) = (Seq.item 2 zs |> snd) |> equal true + +[] +let ``test Map.findKey and Map.pick raise KeyNotFoundException when nothing matches`` () = + let m = Map.ofList [ (1, "a"); (2, "b") ] + raisedExceptionType (fun () -> Map.findKey (fun k _ -> k > 5) m) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Map.pick (fun _ _ -> None: int option) m) |> equal "KeyNotFoundException" diff --git a/tests/Beam/SeqTests.fs b/tests/Beam/SeqTests.fs index 1e43a350b3..ea637949d3 100644 --- a/tests/Beam/SeqTests.fs +++ b/tests/Beam/SeqTests.fs @@ -1161,3 +1161,37 @@ let ``test Seq.toList on string works`` () = let text = "ABC" let chars = text |> Seq.toList chars |> List.length |> equal 3 + +[] +let ``test Seq.reduce and Seq.reduceBack raise ArgumentException on an empty sequence`` () = + raisedExceptionType (fun () -> Seq.reduce (+) Seq.empty) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.reduceBack (+) Seq.empty) |> equal "ArgumentException" + +[] +let ``test Seq.min and friends raise ArgumentException on an empty sequence`` () = + raisedExceptionType (fun () -> Seq.min Seq.empty) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.max Seq.empty) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.minBy id Seq.empty) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.maxBy id Seq.empty) |> equal "ArgumentException" + +[] +let ``test Seq.take and Seq.skip raise InvalidOperationException past the end`` () = + raisedExceptionType (fun () -> Seq.take 5 [1] |> Seq.toList) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> Seq.skip 5 [1] |> Seq.toList) |> equal "InvalidOperationException" + +[] +let ``test Seq.tail raises ArgumentException on an empty sequence`` () = + raisedExceptionType (fun () -> Seq.tail Seq.empty |> Seq.toList) |> equal "ArgumentException" + +[] +let ``test Seq random functions raise ArgumentOutOfRangeException when the randomizer is out of range`` () = + raisedExceptionType (fun () -> Seq.randomShuffleBy (fun () -> 1.0) [1; 2] |> Seq.toList) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Seq.randomChoiceBy (fun () -> 1.0) [1; 2]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Seq.randomChoicesBy (fun () -> 1.0) 1 [1; 2] |> Seq.toList) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Seq.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> Seq.toList) |> equal "ArgumentOutOfRangeException" + +[] +let ``test Range expressions raise ArgumentException for a zero step`` () = + raisedExceptionType (fun () -> [1 .. 0 .. 5]) |> equal "ArgumentException" + raisedExceptionType (fun () -> [1L .. 0L .. 5L]) |> equal "ArgumentException" + raisedExceptionType (fun () -> [1.0 .. 0.0 .. 5.0]) |> equal "ArgumentException" diff --git a/tests/Beam/Util.fs b/tests/Beam/Util.fs index 279a109b8c..28fd76e22c 100644 --- a/tests/Beam/Util.fs +++ b/tests/Beam/Util.fs @@ -69,3 +69,15 @@ let throwsAnyError (f: unit -> 'a): unit = let doesntThrow (f: unit -> 'a): unit = run f |> RunResult.wasSuccess + +/// Matches the most specific type first, so an ArgumentOutOfRangeException is not reported as ArgumentException. +let raisedExceptionType (f: unit -> 'a): string = + try + f () |> ignore + "no exception" + with + | :? ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? ArgumentException -> "ArgumentException" + | :? InvalidOperationException -> "InvalidOperationException" + | :? Collections.Generic.KeyNotFoundException -> "KeyNotFoundException" + | e -> "Unexpected exception: " + e.Message diff --git a/tests/Dart/src/ArrayTests.fs b/tests/Dart/src/ArrayTests.fs index 54aaa76232..a2a02efb42 100644 --- a/tests/Dart/src/ArrayTests.fs +++ b/tests/Dart/src/ArrayTests.fs @@ -1328,6 +1328,61 @@ let tests () = equal c2 -1 equal c3 1 + testCase "Array.reduce and Array.min on an empty array raise ArgumentException" <| fun () -> + let xs: int[] = [||] + raisedExceptionType (fun () -> Array.reduce (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.reduceBack (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.Parallel.reduce (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.min xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.max xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.minBy id xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.maxBy id xs) |> equal "ArgumentException" + + testCase "Array.take and Array.splitAt past the end raise InvalidOperationException" <| fun () -> + raisedExceptionType (fun () -> Array.take 5 [|1|]) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> Array.splitAt 5 [|1|]) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> Array.take -1 [|1|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.splitAt -1 [|1|]) |> equal "ArgumentException" + + testCase "Array.skip past the end raises ArgumentOutOfRangeException" <| fun () -> + raisedExceptionType (fun () -> Array.skip 5 [|1|]) |> equal "ArgumentOutOfRangeException" + + testCase "Array functions on arrays of different lengths raise ArgumentException" <| fun () -> + let xs = [|1|] + let ys = [|1; 2|] + raisedExceptionType (fun () -> Array.zip xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.zip3 xs ys xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.map2 (+) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.mapi2 (fun i x y -> i + x + y) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.map3 (fun x y z -> x + y + z) xs ys xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.iter2 (fun _ _ -> ()) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.iteri2 (fun _ _ _ -> ()) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.fold2 (fun acc x y -> acc + x + y) 0 xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.foldBack2 (fun x y acc -> x + y + acc) xs ys 0) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.forall2 (=) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.exists2 (=) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.transpose [| xs; ys |]) |> equal "ArgumentException" + + testCase "Array.find and Array.pick raise KeyNotFoundException when nothing matches" <| fun () -> + let xs = [|1; 2|] + raisedExceptionType (fun () -> Array.find (fun x -> x > 5) xs) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.findBack (fun x -> x > 5) xs) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.findIndex (fun x -> x > 5) xs) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.findIndexBack (fun x -> x > 5) xs) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.pick (fun x -> if x > 5 then Some x else None) xs) |> equal "KeyNotFoundException" + + testCase "Array.windowed and Array.permute with invalid arguments raise ArgumentException" <| fun () -> + raisedExceptionType (fun () -> Array.windowed 0 [|1|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.permute (fun _ -> 5) [|1; 2|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.permute (fun _ -> 0) [|1; 2|]) |> equal "ArgumentException" + + testCase "Array random functions raise ArgumentOutOfRangeException when the randomizer is out of range" <| fun () -> + raisedExceptionType (fun () -> Array.randomShuffleInPlaceBy (fun () -> 1.0) [|1; 2|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomShuffleBy (fun () -> 1.0) [|1; 2|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomChoiceBy (fun () -> 1.0) [|1|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomChoicesBy (fun () -> 1.0) 1 [|1|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomSampleBy (fun () -> 1.0) 1 [|1; 2|]) |> equal "ArgumentOutOfRangeException" + // testCase "System.Array.Resize works" <| fun () -> // let mutable xs = [|1; 2; 3; 4; 5|] // System.Array.Resize(&xs, 3) diff --git a/tests/Dart/src/ListTests.fs b/tests/Dart/src/ListTests.fs index 58cd87c5df..acdde1fa7d 100644 --- a/tests/Dart/src/ListTests.fs +++ b/tests/Dart/src/ListTests.fs @@ -1063,3 +1063,46 @@ let tests() = List.distinct sample |> List.length |> equal 3 throwsAnyError (fun () -> List.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> ignore) throwsAnyError (fun () -> List.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> ignore) + + testCase "List.reduce and List.min on an empty list raise ArgumentException" <| fun () -> + let xs: int list = [] + raisedExceptionType (fun () -> List.reduce (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.reduceBack (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.min xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.max xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.minBy id xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.maxBy id xs) |> equal "ArgumentException" + + testCase "List.head, List.tail and List.last on an empty list raise ArgumentException" <| fun () -> + let xs: int list = [] + raisedExceptionType (fun () -> List.head xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.tail xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.last xs) |> equal "ArgumentException" + + testCase "list.Head and list.Tail on an empty list raise InvalidOperationException" <| fun () -> + let xs: int list = [] + raisedExceptionType (fun () -> xs.Head) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> xs.Tail) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> [1].Tail.Tail) |> equal "InvalidOperationException" + + testCase "List.take and List.splitAt past the end raise InvalidOperationException" <| fun () -> + raisedExceptionType (fun () -> List.take 5 [1]) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> List.splitAt 5 [1]) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> List.take -1 [1]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.splitAt -1 [1]) |> equal "ArgumentException" + + testCase "List.skip past the end raises ArgumentOutOfRangeException" <| fun () -> + raisedExceptionType (fun () -> List.skip 5 [1]) |> equal "ArgumentOutOfRangeException" + + testCase "List functions with invalid arguments raise ArgumentException" <| fun () -> + raisedExceptionType (fun () -> List.windowed 0 [1]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.permute (fun _ -> 5) [1; 2]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.permute (fun _ -> 0) [1; 2]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.foldBack2 (fun x y acc -> x + y + acc) [1] [1; 2] 0) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.transpose [[1]; [2; 3]]) |> equal "ArgumentException" + + testCase "List random functions raise ArgumentOutOfRangeException when the randomizer is out of range" <| fun () -> + raisedExceptionType (fun () -> List.randomShuffleBy (fun () -> 1.0) [1; 2]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> List.randomChoiceBy (fun () -> 1.0) [1]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> List.randomChoicesBy (fun () -> 1.0) 1 [1]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> List.randomSampleBy (fun () -> 1.0) 1 [1; 2]) |> equal "ArgumentOutOfRangeException" diff --git a/tests/Dart/src/MapTests.fs b/tests/Dart/src/MapTests.fs index 68161e9c95..366f6b9dab 100644 --- a/tests/Dart/src/MapTests.fs +++ b/tests/Dart/src/MapTests.fs @@ -295,3 +295,11 @@ let tests() = |> Map.add { Bar = "a"; Foo = 10 } 2 |> Map.count |> equal 1 + + testCase "Map enumerator Current outside the enumeration raises InvalidOperationException" <| fun () -> + let e = (Map.ofList [(1, "a")] :> seq>).GetEnumerator() + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" + e.MoveNext() |> equal true + e.Current.Key |> equal 1 + e.MoveNext() |> equal false + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" diff --git a/tests/Dart/src/SeqTests.fs b/tests/Dart/src/SeqTests.fs index ce53aaf7c8..795c9e297e 100644 --- a/tests/Dart/src/SeqTests.fs +++ b/tests/Dart/src/SeqTests.fs @@ -1163,3 +1163,36 @@ let tests() = Array.distinct sample |> Array.length |> equal 3 throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> Seq.toArray |> ignore) throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> Seq.toArray |> ignore) + + testCase "Seq.reduce and Seq.min on an empty sequence raise ArgumentException" <| fun () -> + let xs: int seq = Seq.empty + raisedExceptionType (fun () -> Seq.reduce (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.reduceBack (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.min xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.max xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.minBy id xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.maxBy id xs) |> equal "ArgumentException" + + testCase "Seq.take and Seq.skip past the end raise InvalidOperationException" <| fun () -> + raisedExceptionType (fun () -> Seq.take 5 [1] |> Seq.toList) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> Seq.skip 5 [1] |> Seq.toList) |> equal "InvalidOperationException" + + testCase "Seq.tail on an empty sequence raises ArgumentException" <| fun () -> + raisedExceptionType (fun () -> Seq.tail Seq.empty |> Seq.toList) |> equal "ArgumentException" + Seq.tail [1; 2; 3] |> Seq.toList |> equal [2; 3] + + testCase "Seq functions with invalid arguments raise ArgumentException" <| fun () -> + raisedExceptionType (fun () -> Seq.windowed 0 [1] |> Seq.toList) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.permute (fun _ -> 5) [1; 2] |> Seq.toList) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.permute (fun _ -> 0) [1; 2] |> Seq.toList) |> equal "ArgumentException" + + testCase "Seq random functions raise ArgumentOutOfRangeException when the randomizer is out of range" <| fun () -> + raisedExceptionType (fun () -> Seq.randomShuffleBy (fun () -> 1.0) [1; 2] |> Seq.toList) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Seq.randomChoiceBy (fun () -> 1.0) [1]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Seq.randomChoicesBy (fun () -> 1.0) 1 [1] |> Seq.toList) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Seq.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> Seq.toList) |> equal "ArgumentOutOfRangeException" + + testCase "Ranges with a zero step raise ArgumentException" <| fun () -> + raisedExceptionType (fun () -> [1 .. 0 .. 5]) |> equal "ArgumentException" + raisedExceptionType (fun () -> [|1L .. 0L .. 5L|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> [1. .. 0. .. 5.]) |> equal "ArgumentException" diff --git a/tests/Dart/src/SetTests.fs b/tests/Dart/src/SetTests.fs index 21d9ea6fb0..bd1b5c7241 100644 --- a/tests/Dart/src/SetTests.fs +++ b/tests/Dart/src/SetTests.fs @@ -289,3 +289,21 @@ let tests() = largeSetB.IsSupersetOf(largeSetA) |> equal true largeSetA.IsProperSubsetOf(largeSetB) |> equal false largeSetA.IsSubsetOf(largeSetB) |> equal true + + testCase "Set.minElement and Set.maxElement on an empty set raise ArgumentException" <| fun () -> + let xs: Set = Set.empty + raisedExceptionType (fun () -> Set.minElement xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Set.maxElement xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> xs.MinimumElement) |> equal "ArgumentException" + raisedExceptionType (fun () -> xs.MaximumElement) |> equal "ArgumentException" + + testCase "Set.intersectMany with no sets raises ArgumentException" <| fun () -> + raisedExceptionType (fun () -> Set.intersectMany ([]: Set list)) |> equal "ArgumentException" + + testCase "Set enumerator Current outside the enumeration raises InvalidOperationException" <| fun () -> + let e = (Set.ofList [1] :> seq).GetEnumerator() + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" + e.MoveNext() |> equal true + e.Current |> equal 1 + e.MoveNext() |> equal false + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" diff --git a/tests/Dart/src/Tests.Util.fs b/tests/Dart/src/Tests.Util.fs index 4e0754c735..d821dfb7b0 100644 --- a/tests/Dart/src/Tests.Util.fs +++ b/tests/Dart/src/Tests.Util.fs @@ -22,6 +22,18 @@ let equal (expected: 'T) (actual: 'T) = Test.expect(actual, Test.equals(expected let notEqual (expected: 'T) (actual: 'T) = Test.expect(actual, Test.isNot(Test.equals(expected))) let throwsAnyError (f: unit -> 'a): unit = Test.expect(f, Test.throwsException) +/// Matches the most specific type first, so an ArgumentOutOfRangeException is not reported as ArgumentException. +let raisedExceptionType (f: unit -> 'a): string = + try + f () |> ignore + "no exception" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | :? System.InvalidOperationException -> "InvalidOperationException" + | :? System.Collections.Generic.KeyNotFoundException -> "KeyNotFoundException" + | e -> "Unexpected exception: " + e.Message + let private run (f: unit -> 'a) = try f() diff --git a/tests/Js/Main/ArrayTests.fs b/tests/Js/Main/ArrayTests.fs index b0b90a14a6..0e97aad4f6 100644 --- a/tests/Js/Main/ArrayTests.fs +++ b/tests/Js/Main/ArrayTests.fs @@ -1526,4 +1526,58 @@ let tests = coll.CopyTo(ys, 0) ys = xs |> equal true + testCase "Array functions raise ArgumentException on empty arrays" <| fun () -> + let xs: int[] = [||] + let ys: string[] = [||] + raisedExceptionType (fun () -> Array.reduce (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.reduce (+) ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.reduceBack (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.reduceBack (+) ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.Parallel.reduce (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.min xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.max ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.minBy id xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.maxBy id ys) |> equal "ArgumentException" + + testCase "Array functions raise FSharp.Core's exception types for out of range counts" <| fun () -> + raisedExceptionType (fun () -> Array.take 5 [|1|]) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> Array.take -1 [|1|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.skip 5 [|1|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.splitAt 5 [|1|]) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> Array.splitAt -1 [|1|]) |> equal "ArgumentException" + + testCase "Array functions raise ArgumentException for arrays of different lengths" <| fun () -> + let xs = [|1|] + let ys = [|1; 2|] + raisedExceptionType (fun () -> Array.zip xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.zip3 xs ys xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.map2 (+) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.mapi2 (fun i x y -> i + x + y) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.map3 (fun x y z -> x + y + z) xs ys xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.iter2 (fun _ _ -> ()) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.iteri2 (fun _ _ _ -> ()) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.fold2 (fun acc x y -> acc + x + y) 0 xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.foldBack2 (fun x y acc -> x + y + acc) xs ys 0) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.forall2 (fun x y -> x = y) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.exists2 (fun x y -> x = y) xs ys) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.transpose [| xs; ys |]) |> equal "ArgumentException" + + testCase "Array functions raise KeyNotFoundException when nothing satisfies the predicate" <| fun () -> + let xs = [|1; 2|] + raisedExceptionType (fun () -> Array.find (fun x -> x > 2) xs) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.findBack (fun x -> x > 2) xs) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.findIndex (fun x -> x > 2) xs) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.findIndexBack (fun x -> x > 2) xs) |> equal "KeyNotFoundException" + raisedExceptionType (fun () -> Array.pick (fun x -> if x > 2 then Some x else None) xs) |> equal "KeyNotFoundException" + + testCase "Array functions raise FSharp.Core's exception types for invalid arguments" <| fun () -> + raisedExceptionType (fun () -> Array.windowed 0 [|1|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.permute (fun i -> i + 1) [|1; 2|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.permute (fun _ -> 0) [|1; 2|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Array.randomShuffleInPlaceBy (fun () -> 1.0) [|1; 2|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomShuffleBy (fun () -> 1.0) [|1; 2|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomChoiceBy (fun () -> 1.0) [|1; 2|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomChoicesBy (fun () -> 1.0) 2 [|1; 2|]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Array.randomSampleBy (fun () -> -0.1) 1 [|1; 2|]) |> equal "ArgumentOutOfRangeException" + ] diff --git a/tests/Js/Main/ListTests.fs b/tests/Js/Main/ListTests.fs index d5f9a68272..4df3113faf 100644 --- a/tests/Js/Main/ListTests.fs +++ b/tests/Js/Main/ListTests.fs @@ -1075,4 +1075,41 @@ let tests = List.distinct sample |> List.length |> equal 3 throwsAnyError (fun () -> List.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> ignore) throwsAnyError (fun () -> List.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> ignore) + + testCase "List functions raise ArgumentException on empty lists" <| fun () -> + let xs: int list = [] + raisedExceptionType (fun () -> List.head xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.tail xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.last xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.reduce (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.reduceBack (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.min xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.max xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.minBy id xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.maxBy id xs) |> equal "ArgumentException" + + testCase "List Head and Tail properties raise InvalidOperationException on empty lists" <| fun () -> + let xs: int list = [] + raisedExceptionType (fun () -> xs.Head) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> xs.Tail) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> [1].Tail.Head) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> [1].Tail.Tail) |> equal "InvalidOperationException" + + testCase "List functions raise FSharp.Core's exception types for out of range counts" <| fun () -> + raisedExceptionType (fun () -> List.take 5 [1]) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> List.take -1 [1]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.skip 5 [1]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> List.splitAt 5 [1]) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> List.splitAt -1 [1]) |> equal "ArgumentException" + + testCase "List functions raise FSharp.Core's exception types for invalid arguments" <| fun () -> + raisedExceptionType (fun () -> List.windowed 0 [1]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.foldBack2 (fun x y acc -> x + y + acc) [1] [1; 2] 0) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.transpose [[1]; [1; 2]]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.permute (fun i -> i + 1) [1; 2]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.permute (fun _ -> 0) [1; 2]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.randomShuffleBy (fun () -> 1.0) [1; 2]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> List.randomChoiceBy (fun () -> 1.0) [1; 2]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> List.randomChoicesBy (fun () -> 1.0) 2 [1; 2]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> List.randomSampleBy (fun () -> -0.1) 1 [1; 2]) |> equal "ArgumentOutOfRangeException" ] diff --git a/tests/Js/Main/MapTests.fs b/tests/Js/Main/MapTests.fs index eee708d352..caa3d043e1 100644 --- a/tests/Js/Main/MapTests.fs +++ b/tests/Js/Main/MapTests.fs @@ -2,6 +2,7 @@ module Fable.Tests.Maps open System.Collections.Generic open Util.Testing +open Fable.Tests.Util type R = { i: int; s: string } type R2 = { kv: KeyValuePair } @@ -320,4 +321,19 @@ let tests = let ys = [| ("D", 4); ("E", 5); ("F", 6) |] |> Array.map KeyValuePair coll.CopyTo(ys, 0) ys = (xs |> Array.map KeyValuePair) |> equal true + + testCase "Map enumerator Current raises InvalidOperationException outside the enumeration" <| fun () -> + let m = Map [1, "a"; 2, "b"] + let e = (m :> seq>).GetEnumerator() + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" + while e.MoveNext() do () + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" + + testCase "Map raises NotSupportedException when mutated through IDictionary or ICollection" <| fun () -> + let dic = Map [1, "a"] :> IDictionary + let coll = Map [1, "a"] :> ICollection> + raisedExceptionType (fun () -> dic.Add(2, "b")) |> equal "NotSupportedException" + raisedExceptionType (fun () -> dic.Remove(1)) |> equal "NotSupportedException" + raisedExceptionType (fun () -> dic.[2] <- "b") |> equal "NotSupportedException" + raisedExceptionType (fun () -> coll.Clear()) |> equal "NotSupportedException" ] diff --git a/tests/Js/Main/SeqTests.fs b/tests/Js/Main/SeqTests.fs index 620324d219..fc19f494ba 100644 --- a/tests/Js/Main/SeqTests.fs +++ b/tests/Js/Main/SeqTests.fs @@ -568,6 +568,13 @@ let tests = |> Seq.reduce (+) |> equal 19990000000000002000I + testCase "Ranges with a zero step raise ArgumentException" <| fun () -> + let step = 0 + raisedExceptionType (fun () -> [0 .. step .. 5]) |> equal "ArgumentException" + raisedExceptionType (fun () -> [|0L .. 0L .. 5L|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> [0. .. 0. .. 1.]) |> equal "ArgumentException" + raisedExceptionType (fun () -> seq {0M .. 0M .. 1M} |> Seq.toList) |> equal "ArgumentException" + testCase "Seq.reduce works" <| fun () -> let xs = [1.; 2.] xs |> Seq.reduce (+) @@ -1162,4 +1169,25 @@ let tests = throwsAnyError (fun () -> Seq.removeManyAt 0 2 [] |> Seq.toList |> ignore) throwsAnyError (fun () -> Seq.removeManyAt -1 2 [1] |> Seq.toList |> ignore) throwsAnyError (fun () -> Seq.removeManyAt 2 2 [1] |> Seq.toList |> ignore) + + testCase "Seq functions raise ArgumentException on empty sequences" <| fun () -> + let xs = seq { for x in [1; 2] do if x > 2 then yield x } + raisedExceptionType (fun () -> Seq.reduce (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.reduceBack (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.min xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.max xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.minBy id xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.maxBy id xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.tail xs |> Seq.toList) |> equal "ArgumentException" + + testCase "Seq functions raise InvalidOperationException when there are not enough elements" <| fun () -> + raisedExceptionType (fun () -> Seq.take 5 [1] |> Seq.toList) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> Seq.skip 5 [1] |> Seq.toList) |> equal "InvalidOperationException" + + testCase "Seq functions raise FSharp.Core's exception types for invalid arguments" <| fun () -> + raisedExceptionType (fun () -> Seq.windowed 0 [1] |> Seq.toList) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.permute (fun i -> i + 1) [1; 2] |> Seq.toList) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.permute (fun _ -> 0) [1; 2] |> Seq.toList) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.randomChoiceBy (fun () -> 1.0) [1; 2]) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Seq.randomShuffleBy (fun () -> 1.0) [1; 2] |> Seq.toList) |> equal "ArgumentOutOfRangeException" ] diff --git a/tests/Js/Main/SetTests.fs b/tests/Js/Main/SetTests.fs index 2f752a51e5..88e36cc8d1 100644 --- a/tests/Js/Main/SetTests.fs +++ b/tests/Js/Main/SetTests.fs @@ -2,6 +2,7 @@ module Fable.Tests.Sets open System.Collections.Generic open Util.Testing +open Fable.Tests.Util let tests = testList "Sets" [ @@ -320,4 +321,19 @@ let tests = let ys = [| ("D", 4); ("E", 5); ("F", 6) |] coll.CopyTo(ys, 0) ys = xs |> equal true + + testCase "Set functions raise ArgumentException on empty sets" <| fun () -> + let s: Set = Set.empty + raisedExceptionType (fun () -> Set.minElement s) |> equal "ArgumentException" + raisedExceptionType (fun () -> Set.maxElement s) |> equal "ArgumentException" + raisedExceptionType (fun () -> s.MinimumElement) |> equal "ArgumentException" + raisedExceptionType (fun () -> s.MaximumElement) |> equal "ArgumentException" + raisedExceptionType (fun () -> Set.intersectMany ([]: Set list)) |> equal "ArgumentException" + + testCase "Set enumerator Current raises InvalidOperationException outside the enumeration" <| fun () -> + let s = set [1; 2] + let e = (s :> seq).GetEnumerator() + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" + while e.MoveNext() do () + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" ] diff --git a/tests/Js/Main/Util/Util.fs b/tests/Js/Main/Util/Util.fs index da64962240..e0c24a8220 100644 --- a/tests/Js/Main/Util/Util.fs +++ b/tests/Js/Main/Util/Util.fs @@ -108,6 +108,19 @@ let doesntThrow (f: unit -> 'a): unit = run f |> RunResult.wasSuccess +/// Matches the most specific type first, so an ArgumentOutOfRangeException is not reported as ArgumentException. +let raisedExceptionType (f: unit -> 'a): string = + try + f () |> ignore + "no exception" + with + | :? ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? ArgumentException -> "ArgumentException" + | :? InvalidOperationException -> "InvalidOperationException" + | :? Collections.Generic.KeyNotFoundException -> "KeyNotFoundException" + | :? NotSupportedException -> "NotSupportedException" + | e -> "Unexpected exception: " + e.Message + let rec sumFirstSeq (zs: seq) (n: int): float = match n with | 0 -> 0. diff --git a/tests/Python/TestList.fs b/tests/Python/TestList.fs index 05badb8fa9..c7db8d1859 100644 --- a/tests/Python/TestList.fs +++ b/tests/Python/TestList.fs @@ -1246,3 +1246,43 @@ let ``test List.collect works II`` () = xs |> List.collect (fun a -> [a.Length]) |> equal [1; 5; 3] + +[] +let ``test List.reduce and List.reduceBack raise ArgumentException on empty input`` () = + let xs: int list = [] + raisedExceptionType (fun () -> List.reduce (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.reduceBack (+) xs) |> equal "ArgumentException" + +[] +let ``test List.min, List.max, List.minBy and List.maxBy raise ArgumentException on empty input`` () = + let xs: int list = [] + raisedExceptionType (fun () -> List.min xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.max xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.minBy id xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.maxBy id xs) |> equal "ArgumentException" + +[] +let ``test List.last, List.head and List.tail raise ArgumentException on empty input`` () = + let xs: int list = [] + raisedExceptionType (fun () -> List.last xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.head xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.tail xs) |> equal "ArgumentException" + +[] +let ``test list.Head and list.Tail raise InvalidOperationException on empty input`` () = + let xs: int list = [] + raisedExceptionType (fun () -> xs.Head) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> xs.Tail) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> [1].Tail.Head) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> [1].Tail.Tail) |> equal "InvalidOperationException" + +[] +let ``test List.take and List.splitAt raise InvalidOperationException when the list is too short`` () = + raisedExceptionType (fun () -> List.take 5 [1]) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> List.splitAt 5 [1]) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> List.take -1 [1]) |> equal "ArgumentException" + raisedExceptionType (fun () -> List.splitAt -1 [1]) |> equal "ArgumentException" + +[] +let ``test List.skip raises ArgumentOutOfRangeException when the list is too short`` () = + raisedExceptionType (fun () -> List.skip 5 [1]) |> equal "ArgumentOutOfRangeException" diff --git a/tests/Python/TestMap.fs b/tests/Python/TestMap.fs index 4c33e1d710..bee7396796 100644 --- a/tests/Python/TestMap.fs +++ b/tests/Python/TestMap.fs @@ -1,6 +1,7 @@ module Fable.Tests.Maps open System.Collections.Generic +open Fable.Tests.Util open Util.Testing type R = { i: int; s: string } @@ -351,3 +352,10 @@ let ``test Map keys and values work correctly`` () = let myMap = Map [ "a", 10; "b", 20 ] :> IDictionary<_,_> myMap.Keys |> Seq.toList |> equal ["a"; "b"] myMap.Values |> Seq.toList |> equal [10; 20] + +[] +let ``test Map enumerator Current raises InvalidOperationException outside the enumeration`` () = + let e = (Map [1, "a"] :> seq<_>).GetEnumerator() + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" + while e.MoveNext() do () + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" diff --git a/tests/Python/TestSeq.fs b/tests/Python/TestSeq.fs index 35a4ff6f8f..78314876de 100644 --- a/tests/Python/TestSeq.fs +++ b/tests/Python/TestSeq.fs @@ -1319,3 +1319,42 @@ let ``test Seq.randomSampleBy works`` () = Array.distinct sample |> Array.length |> equal 3 throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> Seq.toArray |> ignore) throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> Seq.toArray |> ignore) + +[] +let ``test Seq.reduce and Seq.reduceBack raise ArgumentException on empty input`` () = + let xs = Seq.empty + raisedExceptionType (fun () -> Seq.reduce (+) xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.reduceBack (+) xs) |> equal "ArgumentException" + +[] +let ``test Seq.min, Seq.max, Seq.minBy and Seq.maxBy raise ArgumentException on empty input`` () = + let xs = Seq.empty + raisedExceptionType (fun () -> Seq.min xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.max xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.minBy id xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.maxBy id xs) |> equal "ArgumentException" + +[] +let ``test Seq.take and Seq.skip raise InvalidOperationException when the sequence is too short`` () = + raisedExceptionType (fun () -> Seq.take 5 (seq { 1 }) |> Seq.toList) |> equal "InvalidOperationException" + raisedExceptionType (fun () -> Seq.skip 5 (seq { 1 }) |> Seq.toList) |> equal "InvalidOperationException" + +[] +let ``test Seq.tail raises ArgumentException on empty input`` () = + raisedExceptionType (fun () -> Seq.tail Seq.empty |> Seq.toList) |> equal "ArgumentException" + +[] +let ``test range with a zero step raises ArgumentException`` () = + raisedExceptionType (fun () -> [0..0..5]) |> equal "ArgumentException" + raisedExceptionType (fun () -> [0L..0L..5L]) |> equal "ArgumentException" + raisedExceptionType (fun () -> [0.0..0.0..5.0]) |> equal "ArgumentException" + +[] +let ``test Seq.head and Seq.item raise ArgumentException when the sequence is too short`` () = + raisedExceptionType (fun () -> Seq.head Seq.empty) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.head ([]: int list)) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.head ([||]: int[])) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.item 5 (seq { 1 })) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.item 5 [1]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.item 5 [|1|]) |> equal "ArgumentException" + raisedExceptionType (fun () -> Seq.item -1 (seq { 1 })) |> equal "ArgumentException" diff --git a/tests/Python/TestSet.fs b/tests/Python/TestSet.fs index df67710256..fe5e9956d1 100644 --- a/tests/Python/TestSet.fs +++ b/tests/Python/TestSet.fs @@ -1,5 +1,6 @@ module Fable.Tests.Set +open Fable.Tests.Util open Util.Testing [] @@ -333,3 +334,22 @@ let ``test Comparing large sets works`` () = largeSetB.IsSupersetOf(largeSetA) |> equal true largeSetA.IsProperSubsetOf(largeSetB) |> equal false largeSetA.IsSubsetOf(largeSetB) |> equal true + +[] +let ``test Set.minElement and Set.maxElement raise ArgumentException on empty input`` () = + let xs = Set.empty + raisedExceptionType (fun () -> Set.minElement xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> Set.maxElement xs) |> equal "ArgumentException" + raisedExceptionType (fun () -> xs.MinimumElement) |> equal "ArgumentException" + raisedExceptionType (fun () -> xs.MaximumElement) |> equal "ArgumentException" + +[] +let ``test Set.intersectMany raises ArgumentException on empty input`` () = + raisedExceptionType (fun () -> Set.intersectMany ([]: Set list)) |> equal "ArgumentException" + +[] +let ``test Set enumerator Current raises InvalidOperationException outside the enumeration`` () = + let e = (set [1] :> seq<_>).GetEnumerator() + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" + while e.MoveNext() do () + raisedExceptionType (fun () -> e.Current) |> equal "InvalidOperationException" diff --git a/tests/Python/Util.fs b/tests/Python/Util.fs index e80a7e9561..72637b7aa4 100644 --- a/tests/Python/Util.fs +++ b/tests/Python/Util.fs @@ -106,6 +106,17 @@ let doesntThrow (f: unit -> 'a): unit = run f |> RunResult.wasSuccess +/// Matches the most specific type first, so an ArgumentOutOfRangeException is not reported as ArgumentException. +let raisedExceptionType (f: unit -> 'a): string = + try + f () |> ignore + "no exception" + with + | :? ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? ArgumentException -> "ArgumentException" + | :? InvalidOperationException -> "InvalidOperationException" + | e -> "Unexpected exception: " + e.Message + let Int32Array = [|1;2|] module Float64Array =