Skip to content

[Repo Assist] Fix: removeAt and updateAt raise ArgumentException for out-of-range index#315

Merged
dsyme merged 2 commits intomainfrom
repo-assist/fix-removeat-updateat-bounds-check-c6a739625f0bc0db
Apr 20, 2026
Merged

[Repo Assist] Fix: removeAt and updateAt raise ArgumentException for out-of-range index#315
dsyme merged 2 commits intomainfrom
repo-assist/fix-removeat-updateat-bounds-check-c6a739625f0bc0db

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

🤖 This PR was created by Repo Assist, an automated AI assistant.

Summary

AsyncSeq.removeAt and AsyncSeq.updateAt silently returned the source sequence unchanged when the given index was ≥ the sequence length. This inconsistency with the rest of the F# standard library could lead to subtle bugs.

Root Cause

The implementations iterated the sequence and simply skipped the operation when the index was never reached, with no post-loop validation:

// before — silently does nothing if index >= length
let removeAt (index : int) (source : AsyncSeq<'T>) : AsyncSeq<'T> = asyncSeq {
    if index < 0 then invalidArg "index" "must be non-negative"
    let mutable i = 0
    for x in source do
      if i <> index then yield x
      i <- i + 1 }   // ← no check after the loop

Fix

After the enumeration loop, check whether i <= index (meaning the element at index was never visited) and raise ArgumentException with the same message already used by AsyncSeq.insertAt:

    if i <= index then
      invalidArg "index" "The index is outside the range of elements in the collection."

Consistency

Function Negative index Out-of-range index
List.removeAt ✅ raises ✅ raises
Array.removeAt ✅ raises ✅ raises
AsyncSeq.insertAt ✅ raises ✅ raises
AsyncSeq.removeAt (before) ✅ raises ❌ silently ignores
AsyncSeq.updateAt (before) ✅ raises ❌ silently ignores
AsyncSeq.removeAt (after) ✅ raises ✅ raises
AsyncSeq.updateAt (after) ✅ raises ✅ raises

Trade-offs

This is technically a breaking change for any code that relied on the (buggy) silent-ignore behaviour. However, that behaviour was clearly unintentional and inconsistent with every comparable function.

Test Status

Build: succeeded (pre-existing warnings only — no new warnings)
Tests: 418/418 passed (7 new tests added: 3 for removeAt out-of-range, 3 for updateAt out-of-range)

Generated by 🌈 Repo Assist, see workflow run. Learn more.

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@11c9a2c442e519ff2b427bf58679f5a525353f76

…ge index

AsyncSeq.removeAt and AsyncSeq.updateAt previously silently returned the
source sequence unchanged when the index was >= the sequence length, which
is inconsistent with List.removeAt, Array.removeAt, and AsyncSeq.insertAt.

After the enumeration loop, check that the index was actually reached; if
not (i.e. i <= index at end), raise ArgumentException matching the message
used by AsyncSeq.insertAt.

Adds 6 new tests covering out-of-range and boundary indices.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dsyme dsyme marked this pull request as ready for review April 20, 2026 02:29
@dsyme dsyme merged commit eed6a31 into main Apr 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant