Repository: pandas-dev/pandas

Issue:
API: Index.insert too much dtype inference
```python
idx = pd.Index([1], dtype=object)

idx.insert(0, 1).dtype
```

ATM Index.insert uses `Index._with_infer` to construct a result, which will cast the case here to int64 instead of retaining object.  I think in this case it would be more reasonable to keep object dtype, which we'd get by just calling `Index.__new__` instead of _with_infer.

We have no tests that depend on this ATM. 



Discussion:

Patch:
diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst
index 919ac8b03f936..c2032b0d34536 100644
--- a/doc/source/whatsnew/v2.2.0.rst
+++ b/doc/source/whatsnew/v2.2.0.rst
@@ -454,6 +454,7 @@ Other Deprecations
 - Deprecated allowing non-keyword arguments in :meth:`DataFrame.to_string` except ``buf``. (:issue:`54229`)
 - Deprecated allowing non-keyword arguments in :meth:`DataFrame.to_xml` except ``path_or_buffer``. (:issue:`54229`)
 - Deprecated allowing passing :class:`BlockManager` objects to :class:`DataFrame` or :class:`SingleBlockManager` objects to :class:`Series` (:issue:`52419`)
+- Deprecated behavior of :meth:`Index.insert` with an object-dtype index silently performing type inference on the result, explicitly call ``result.infer_objects(copy=False)`` for the old behavior instead (:issue:`51363`)
 - Deprecated downcasting behavior in :meth:`Series.where`, :meth:`DataFrame.where`, :meth:`Series.mask`, :meth:`DataFrame.mask`, :meth:`Series.clip`, :meth:`DataFrame.clip`; in a future version these will not infer object-dtype columns to non-object dtype, or all-round floats to integer dtype. Call ``result.infer_objects(copy=False)`` on the result for object inference, or explicitly cast floats to ints. To opt in to the future version, use ``pd.set_option("future.no_silent_downcasting", True)`` (:issue:`53656`)
 - Deprecated including the groups in computations when using :meth:`.DataFrameGroupBy.apply` and :meth:`.DataFrameGroupBy.resample`; pass ``include_groups=False`` to exclude the groups (:issue:`7155`)
 - Deprecated indexing an :class:`Index`  with a boolean indexer of length zero (:issue:`55820`)
diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py
index 6c9f93d3482a7..3abe77b97fe58 100644
--- a/pandas/core/indexes/base.py
+++ b/pandas/core/indexes/base.py
@@ -6939,14 +6939,24 @@ def insert(self, loc: int, item) -> Index:
             loc = loc if loc >= 0 else loc - 1
             new_values[loc] = item
 
-        idx = Index._with_infer(new_values, name=self.name)
+        out = Index._with_infer(new_values, name=self.name)
         if (
             using_pyarrow_string_dtype()
-            and is_string_dtype(idx.dtype)
+            and is_string_dtype(out.dtype)
             and new_values.dtype == object
         ):
-            idx = idx.astype(new_values.dtype)
-        return idx
+            out = out.astype(new_values.dtype)
+        if self.dtype == object and out.dtype != object:
+            # GH#51363
+            warnings.warn(
+                "The behavior of Index.insert with object-dtype is deprecated, "
+                "in a future version this will return an object-dtype Index "
+                "instead of inferring a non-object dtype. To retain the old "
+                "behavior, do `idx.insert(loc, item).infer_objects(copy=False)`",
+                FutureWarning,
+                stacklevel=find_stack_level(),
+            )
+        return out
 
     def drop(
         self,
diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py
index e3928621a4e48..c233295b25700 100644
--- a/pandas/core/indexing.py
+++ b/pandas/core/indexing.py
@@ -1893,7 +1893,15 @@ def _setitem_with_indexer(self, indexer, value, name: str = "iloc"):
                     # just replacing the block manager here
                     # so the object is the same
                     index = self.obj._get_axis(i)
-                    labels = index.insert(len(index), key)
+                    with warnings.catch_warnings():
+                        # TODO: re-issue this with setitem-specific message?
+                        warnings.filterwarnings(
+                            "ignore",
+                            "The behavior of Index.insert with object-dtype "
+                            "is deprecated",
+                            category=FutureWarning,
+                        )
+                        labels = index.insert(len(index), key)
 
                     # We are expanding the Series/DataFrame values to match
                     #  the length of thenew index `labels`.  GH#40096 ensure
@@ -2186,7 +2194,14 @@ def _setitem_with_indexer_missing(self, indexer, value):
         # and set inplace
         if self.ndim == 1:
             index = self.obj.index
-            new_index = index.insert(len(index), indexer)
+            with warnings.catch_warnings():
+                # TODO: re-issue this with setitem-specific message?
+                warnings.filterwarnings(
+                    "ignore",
+                    "The behavior of Index.insert with object-dtype is deprecated",
+                    category=FutureWarning,
+                )
+                new_index = index.insert(len(index), indexer)
 
             # we have a coerced indexer, e.g. a float
             # that matches in an int64 Index, so
diff --git a/pandas/core/internals/managers.py b/pandas/core/internals/managers.py
index cc88312d5b58f..6eb4099b4d830 100644
--- a/pandas/core/internals/managers.py
+++ b/pandas/core/internals/managers.py
@@ -1376,8 +1376,14 @@ def insert(self, loc: int, item: Hashable, value: ArrayLike, refs=None) -> None:
         value : np.ndarray or ExtensionArray
         refs : The reference tracking object of the value to set.
         """
-        # insert to the axis; this could possibly raise a TypeError
-        new_axis = self.items.insert(loc, item)
+        with warnings.catch_warnings():
+            # TODO: re-issue this with setitem-specific message?
+            warnings.filterwarnings(
+                "ignore",
+                "The behavior of Index.insert with object-dtype is deprecated",
+                category=FutureWarning,
+            )
+            new_axis = self.items.insert(loc, item)
 
         if value.ndim == 2:
             value = value.T


Test patch:
diff --git a/pandas/tests/indexes/test_old_base.py b/pandas/tests/indexes/test_old_base.py
index f08de8e65451c..0fff6abcfc6a5 100644
--- a/pandas/tests/indexes/test_old_base.py
+++ b/pandas/tests/indexes/test_old_base.py
@@ -407,13 +407,20 @@ def test_where(self, listlike_box, simple_index):
         tm.assert_index_equal(result, expected)
 
     def test_insert_base(self, index):
-        result = index[1:4]
+        trimmed = index[1:4]
 
         if not len(index):
             pytest.skip("Not applicable for empty index")
 
         # test 0th element
-        assert index[0:4].equals(result.insert(0, index[0]))
+        warn = None
+        if index.dtype == object and index.inferred_type == "boolean":
+            # GH#51363
+            warn = FutureWarning
+        msg = "The behavior of Index.insert with object-dtype is deprecated"
+        with tm.assert_produces_warning(warn, match=msg):
+            result = trimmed.insert(0, index[0])
+        assert index[0:4].equals(result)
 
     def test_insert_out_of_bounds(self, index):
         # TypeError/IndexError matches what np.insert raises in these cases