Repository: pandas-dev/pandas (python)

Issue:
BUG: dot on Arrow Series produces a Numpy object result
### Pandas version checks

- [x] I have checked that this issue has not already been reported.

- [x] I have confirmed this bug exists on the [latest version](https://pandas.pydata.org/docs/whatsnew/index.html) of pandas.

- [x] I have confirmed this bug exists on the [main branch](https://pandas.pydata.org/docs/dev/getting_started/install.html#installing-the-development-version-of-pandas) of pandas.


### Reproducible Example

```python
import pandas as pd

series_result = pd.Series({"a": 1.0}, dtype="Float64").dot(
    pd.DataFrame({"col1": {"a": 2.0}, "col2": {"a": 3.0}}, dtype="Float64")
)
series_result.dtype  # is dtype('O')

series_result_2 = pd.Series({"a": 1.0}, dtype="float[pyarrow]").dot(
    pd.DataFrame({"col1": {"a": 2.0}, "col2": {"a": 3.0}}, dtype="float[pyarrow]")
)
series_result_2.dtype  # same, is dtype('O')

# `DataFrame.dot` was already fixed
df_result = pd.DataFrame({"col1": {"a": 2.0}, "col2": {"a": 3.0}}, dtype="Float64").T.dot(
    pd.Series({"a": 1.0}, dtype="Float64")
)
df_result.dtype  # is Float64Dtype()
```

### Issue Description

`Series.dot` with Arrow or nullable dtypes returns series result with numpy object dtype. This was reported in #53979 and fixed for DataFrames in #54025.

Possibly side notes: I believe the "real" issue here is that the implementation uses `.values` which returns a `dtype=object` array for the DataFrame. This seems directly related to #60038 and at least somewhat related to #60301 (which is also referenced in a comment on the former).

### Expected Behavior

I would expect `Series.dot` to return the "best" common datatype for the input datatypes (in the examples, would expect the appropriate float dtype)

```python
import pandas as pd

series_result = pd.Series({"a": 1.0}, dtype="Float64").dot(
    pd.DataFrame({"col1": {"a": 2.0}, "col2": {"a": 3.0}}, dtype="Float64")
)
series_result.dtype  # would expect Float64Dtype()

series_result_2 = pd.Series({"a": 1.0}, dtype="float[pyarrow]").dot(
    pd.DataFrame({"col1": {"a": 2.0}, "col2": {"a": 3.0}}, dtype="float[pyarrow]")
)
series_result_2.dtype  # would expect float[pyarrow]
```

### Installed Versions

<details>

INSTALLED VERSIONS
------------------
commit                : 0691c5cf90477d3503834d983f69350f250a6ff7
python                : 3.11.12
python-bits           : 64
OS                    : Windows
OS-release            : 10
Version               : 10.0.19045
machine               : AMD64
processor             : Intel64 Family 6 Model 85 Stepping 7, GenuineIntel
byteorder             : little
LC_ALL                : None
LANG                  : None
LOCALE                : English_United States.1252

pandas                : 2.2.3
numpy                 : 2.2.5
pytz                  : 2025.2
dateutil              : 2.9.0.post0
pip                   : 25.1
Cython                : None
sphinx                : 8.2.3
IPython               : 9.2.0
adbc-driver-postgresql: None
adbc-driver-sqlite    : None
bs4                   : 4.13.4
blosc                 : None
bottleneck            : None
dataframe-api-compat  : None
fastparquet           : None
fsspec                : 2025.3.2
html5lib              : None
hypothesis            : None
gcsfs                 : None
jinja2                : 3.1.6
lxml.etree            : None
matplotlib            : 3.10.1
numba                 : 0.61.2
numexpr               : 2.10.2
odfpy                 : None
openpyxl              : 3.1.5
pandas_gbq            : None
psycopg2              : 2.9.9
pymysql               : None
pyarrow               : 19.0.1
pyreadstat            : None
pytest                : 8.3.5
python-calamine       : None
pyxlsb                : None
s3fs                  : None
scipy                 : 1.15.2
sqlalchemy            : 2.0.40
tables                : 3.10.2
tabulate              : None
xarray                : None
xlrd                  : 2.0.1
xlsxwriter            : None
zstandard             : 0.23.0
tzdata                : 2025.2
qtpy                  : None
pyqt5                 : None

</details>


Pull request:
BUG: Series.dot for arrow and nullable dtypes returns object-dtyped series
fixes #61375 by porting DataFrame fix (from #54025 as reported in #53979)

- [x] closes #61375 
- [x] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [x] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.


Patch:
diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst
index ab3316e7fc..0e779ff470 100644
--- a/doc/source/whatsnew/v3.0.0.rst
+++ b/doc/source/whatsnew/v3.0.0.rst
@@ -710,6 +710,7 @@ Numeric
 ^^^^^^^
 - Bug in :meth:`DataFrame.corr` where numerical precision errors resulted in correlations above ``1.0`` (:issue:`61120`)
 - Bug in :meth:`DataFrame.quantile` where the column type was not preserved when ``numeric_only=True`` with a list-like ``q`` produced an empty result (:issue:`59035`)
+- Bug in :meth:`Series.dot` returning ``object`` dtype for :class:`ArrowDtype` and nullable-dtype data (:issue:`61375`)
 - Bug in ``np.matmul`` with :class:`Index` inputs raising a ``TypeError`` (:issue:`57079`)
 
 Conversion
diff --git a/pandas/core/series.py b/pandas/core/series.py
index d6a982c65e..5ed094349c 100644
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -2951,8 +2951,9 @@ class Series(base.IndexOpsMixin, NDFrame):  # type: ignore[misc]
                 )
 
         if isinstance(other, ABCDataFrame):
+            common_type = find_common_type([self.dtypes] + list(other.dtypes))
             return self._constructor(
-                np.dot(lvals, rvals), index=other.columns, copy=False
+                np.dot(lvals, rvals), index=other.columns, copy=False, dtype=common_type
             ).__finalize__(self, method="dot")
         elif isinstance(other, Series):
             return np.dot(lvals, rvals)


Test patch:
diff --git a/pandas/tests/frame/methods/test_dot.py b/pandas/tests/frame/methods/test_dot.py
index 3e01f67c87..b365ceb2ab 100644
--- a/pandas/tests/frame/methods/test_dot.py
+++ b/pandas/tests/frame/methods/test_dot.py
@@ -153,3 +153,19 @@ def test_arrow_dtype(dtype, exp_dtype):
     expected = DataFrame([[1, 2], [3, 4], [5, 6]], dtype=exp_dtype)
 
     tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+    "dtype,exp_dtype",
+    [("Float32", "Float64"), ("Int16", "Int32"), ("float[pyarrow]", "double[pyarrow]")],
+)
+def test_arrow_dtype_series(dtype, exp_dtype):
+    pytest.importorskip("pyarrow")
+
+    cols = ["a", "b"]
+    series_a = Series([1, 2], index=cols, dtype="int32")
+    df_b = DataFrame([[1, 0], [0, 1]], index=cols, dtype=dtype)
+    result = series_a.dot(df_b)
+    expected = Series([1, 2], dtype=exp_dtype)
+
+    tm.assert_series_equal(result, expected)