{"record":{"id":"286c64e147034baa","repo":"slint-ui/slint","slug":"row-index-out-of-range","errorCode":null,"errorMessage":"row index out of range","messagePattern":"row index out of range","errorType":"exception","errorClass":"IndexError","httpStatus":null,"severity":"error","filePath":"api/python/slint/slint/models.py","lineNumber":131,"sourceCode":"        self.list: list[T]\n        if iterable is not None:\n            self.list = list(iterable)\n        else:\n            self.list = []\n\n    def row_count(self) -> int:\n        return len(self.list)\n\n    def row_data(self, row: int) -> T | None:\n        return self.list[row]\n\n    def set_row_data(self, row: int, value: T) -> None:\n        self.list[row] = value\n        super().notify_row_changed(row if row >= 0 else row + len(self.list))\n\n    def remove_row(self, row: int) -> None:\n        if row < 0 or row >= len(self.list):\n            raise IndexError(\"row index out of range\")\n        del self.list[row]\n        super().notify_row_removed(row, 1)\n\n    def insert_row(self, row: int, value: T) -> None:\n        if row < 0 or row > len(self.list):\n            raise IndexError(\"row index out of range\")\n        self.insert(row, value)\n\n    def __delitem__(self, key: int | slice) -> None:\n        if isinstance(key, slice):\n            rows = range(*key.indices(len(self.list)))\n            if not rows:\n                return\n            if abs(rows.step) == 1:\n                first = rows.start if rows.step > 0 else rows[-1]\n                del self.list[key]\n                super().notify_row_removed(first, len(rows))\n            else:","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/slint-ui/slint/blob/3a7e700487dc74d404cad0726378053ab763d4e0/api/python/slint/slint/models.py#L113-L149","documentation":"SlintPyListModel.remove_row performs explicit bounds validation against the Python list backing the model and raises IndexError when row is negative or >= len(self.list). It means the caller asked to delete a row that does not exist — commonly a stale index after a previous removal, or code that used the row count instead of count-1. The list is left unchanged when this fires.","triggerScenarios":"Calling list_model.remove_row(negative) or remove_row(index >= row_count); removing from a stale index after the model shrank; concurrent modifications invalidating a cached index.","commonSituations":"UI code caching row indices while other callbacks delete rows; off-by-one loops (`for i in range(len+1)`); deleting the same row twice.","solutions":["Check 0 <= row < model.row_count() before calling remove_row.","Recompute indices after each deletion instead of reusing stale positions.","Wrap the call in try/except IndexError to tolerate already-removed rows."],"exampleFix":"# before\nmodel.remove_row(idx)  # IndexError if idx stale\n\n# after\nif 0 <= idx < model.row_count():\n    model.remove_row(idx)","handlingStrategy":"validation","validationCode":"def safe_remove(model, row):\n    if not 0 <= row < model.row_count():\n        return False\n    model.remove_row(row)\n    return True","typeGuard":null,"tryCatchPattern":"try:\n    model.remove_row(idx)\nexcept IndexError:\n    pass  # already removed","preventionTips":["Always check row_count() before deleting by index.","Recompute indices after each mutation; never reuse cached positions across callbacks."],"tags":["python","model","index-out-of-range"],"backgroundTag":"index-out-of-range","analyzedSha":"3a7e700487dc74d404cad0726378053ab763d4e0","analyzedAt":"2026-09-16T01:37:20.251Z","contentChangedAt":"2026-09-16T01:37:20.251Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}