Repository navigation
fix(js/ts/python/dart/beam): align collection exception types with FSharp.Core - #5153
Merged
MangelMaxime merged 6 commits intoOct 26, 2026
Conversation
Now that invalidArg and invalidOp raise typed exceptions, List, Array, Seq, Map, Set and range functions raise the exception types FSharp.Core raises: - reduce/reduceBack (and min/max/minBy/maxBy, Set.intersectMany) and List.last on empty input raise ArgumentException. Array.reduce and Array.reduceBack no longer map to the native JS reduce, which threw a TypeError. - list.Head and list.Tail raise InvalidOperationException, while List.head and List.tail keep raising ArgumentException. PHP, which shares these replacements, keeps calling List.head and List.tail. - take/splitAt past the end and Seq.skip raise InvalidOperationException; List.skip and Array.skip raise ArgumentOutOfRangeException. Seq.tail keeps raising ArgumentException. - Array length mismatches, windowed, permute and zero range steps raise ArgumentException, and find/pick misses raise KeyNotFoundException. Array.permute now also rejects an index map that is not a bijection: its flags array had empty slots, which `every` skips. - random*By with a randomizer outside [0, 1) raises ArgumentOutOfRangeException. - Map and Set enumerators raise InvalidOperationException, and mutating a Map through IDictionary or ICollection raises NotSupportedException with FSharp.Core's message. SR.inputWasEmpty now reads "The input list was empty." like FSharp.Core.
List, Seq, Set, Map and range functions raised a plain Exception or a different System exception than FSharp.Core. They now match .NET: reduce, min, max and last on empty input raise ArgumentException, Seq.head on empty input and Seq.item past the end raise ArgumentException, take, splitAt and Seq.skip past the end raise InvalidOperationException, List.skip past the end raises ArgumentOutOfRangeException, a zero range step raises ArgumentException, and the Map and Set enumerators raise InvalidOperationException outside the enumeration. Map's ICollection mutators raise NotSupportedException with FSharp.Core's message. list.Head and list.Tail raise InvalidOperationException while List.head and List.tail keep raising ArgumentException, so Replacements now sends the properties to the FSharpList members. Seq.tail gets its own implementation so it keeps raising ArgumentException. The List messages pick up FSharp.Core's "The input list was empty." from the Global.fs shared with fable-library-ts.
List, Array, Seq, Set, Map and range functions raised untyped errors (failwith, or Dart's own StateError for Array.reduce and Array.find) or a different typed exception than FSharp.Core does on .NET. They now raise the same exception types: - reduce/min/max on empty inputs, List.last, List.head/tail and Set.minElement/maxElement: ArgumentException - list.Head/list.Tail on an empty list: InvalidOperationException - take/splitAt past the end and Seq.skip: InvalidOperationException (Seq.tail keeps ArgumentException) - List.skip/Array.skip past the end and an out-of-range randomizer: ArgumentOutOfRangeException - Array length mismatches, windowed, permute and a zero range step: ArgumentException - Array.find/findBack/findIndex/findIndexBack/pick: KeyNotFoundException - Map/Set enumerator Current outside the enumeration: InvalidOperationException SR.inputWasEmpty now reads "The input list was empty." like FSharp.Core.
Collection functions raised untyped errors, or the wrong exception type, where FSharp.Core raises a typed one, so handlers like `:? InvalidOperationException` never caught them: - Seq.reduce, Seq.reduceBack (and Seq.min/max/minBy/maxBy) on an empty sequence raise ArgumentException instead of InvalidOperationException. - Seq.take and Seq.skip past the end raise InvalidOperationException instead of ArgumentException. Seq.tail gets its own implementation so it keeps raising ArgumentException on an empty sequence. - List.take and Array.take past the end raise InvalidOperationException. - List/Array find, findBack, findIndex, findIndexBack and pick raise KeyNotFoundException. - Map.findKey and Map.pick raise KeyNotFoundException when nothing matches. - The random*By functions raise ArgumentOutOfRangeException for a randomizer outside [0, 1), and ArgumentException for an empty source, a negative count or a sample larger than the source. - A range with a zero step raises ArgumentException.
MangelMaxime
force-pushed
the
fix-collection-exception-types
branch
from
October 26, 2026 19:44
a4dfc6e to
d6ac902
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #5152, and stacked on it: this branch includes its commit until it merges.
With
invalidArgandinvalidOpraising typed exceptions, many collection functions turn out to raise a different type than FSharp.Core. I probed the error paths of List, Array, Seq, Map, Set and ranges on .NET and on the JS output, and this fixes the cases where the library already raises, but the wrong type or a plainException:ArgumentException:reduce/reduceBack(andmin/max/minBy/maxBythrough them),List.last,Set.minElement/maxElement, length mismatches (zip,map2,fold2,transpose, ...),windowed,permute, a zero range step.InvalidOperationException:list.Head/list.Tail(List.head/List.tailkeepArgumentException),takeandsplitAtpast the end,Seq.skippast the end, Map and Set enumerators'Currentoutside the enumeration.ArgumentOutOfRangeException:List.skip/Array.skippast the end,random*Bywith a randomizer outside [0, 1).KeyNotFoundException:Array.find/findIndex/pickand theirBackvariants.NotSupportedException: mutating a Map throughIDictionary/ICollection.Notes:
Array.reduce/reduceBackno longer map to nativereduce/reduceRight, which threw aTypeErroron empty arrays.permute's bijection check works again (it skipped the holes ofnew Array(n)). PHP falls back to the JS replacements, so it keeps the oldHead/Tailmapping.seq_native.py. The Rust-backed Array module is unchanged.Seq.fs,Range.fsand the explicit raises infable_list.erl/fable_map.erl. Functions that fail with a native Erlang error have no raise to retype, so they are unchanged.Types and parameter names match .NET; messages follow FSharp.Core where it was cheap. The new tests assert types only.
Not covered: cases where .NET raises and Fable raises nothing (e.g.
List.zipwith different lengths,List.init -1), null checks, and array indexing (arr.[i]should raiseIndexOutOfRangeException, but shares code withResizeArray).The full CI passes on my fork: https://github.com/leonitousconforti/Fable/actions/runs/40171511126. The JS suite also runs under JavaScriptCore with no regressions against #5152.