@@ -63,7 +63,9 @@ def maybe_promote(dtype: np.dtype) -> tuple[np.dtype, Any]:
63
63
# N.B. these casting rules should match pandas
64
64
dtype_ : np .typing .DTypeLike
65
65
fill_value : Any
66
- if HAS_STRING_DTYPE and np .issubdtype (dtype , np .dtypes .StringDType ()):
66
+ if is_extension_array_dtype (dtype ):
67
+ return dtype , dtype .na_value
68
+ elif HAS_STRING_DTYPE and np .issubdtype (dtype , np .dtypes .StringDType ()):
67
69
# for now, we always promote string dtypes to object for consistency with existing behavior
68
70
# TODO: refactor this once we have a better way to handle numpy vlen-string dtypes
69
71
dtype_ = object
@@ -222,19 +224,51 @@ def isdtype(dtype, kind: str | tuple[str, ...], xp=None) -> bool:
222
224
return xp .isdtype (dtype , kind )
223
225
224
226
225
- def preprocess_types (t ):
226
- if isinstance (t , str | bytes ):
227
- return type (t )
228
- elif isinstance (dtype := getattr (t , "dtype" , t ), np .dtype ) and (
229
- np .issubdtype (dtype , np .str_ ) or np .issubdtype (dtype , np .bytes_ )
230
- ):
227
+ def maybe_promote_to_variable_width (
228
+ array_or_dtype : np .typing .ArrayLike | np .typing .DTypeLike ,
229
+ ) -> np .typing .ArrayLike | np .typing .DTypeLike :
230
+ if isinstance (array_or_dtype , str | bytes ):
231
+ return type (array_or_dtype )
232
+ elif isinstance (
233
+ dtype := getattr (array_or_dtype , "dtype" , array_or_dtype ), np .dtype
234
+ ) and (np .issubdtype (dtype , np .str_ ) or np .issubdtype (dtype , np .bytes_ )):
231
235
# drop the length from numpy's fixed-width string dtypes, it is better to
232
236
# recalculate
233
237
# TODO(keewis): remove once the minimum version of `numpy.result_type` does this
234
238
# for us
235
239
return dtype .type
236
240
else :
237
- return t
241
+ return array_or_dtype
242
+
243
+
244
+ def should_promote_to_object (
245
+ arrays_and_dtypes : np .typing .ArrayLike | np .typing .DTypeLike , xp
246
+ ) -> bool :
247
+ """
248
+ Test whether the given arrays_and_dtypes, when evaluated individually, match the
249
+ type promotion rules found in PROMOTE_TO_OBJECT.
250
+ """
251
+ np_result_types = set ()
252
+ for arr_or_dtype in arrays_and_dtypes :
253
+ try :
254
+ result_type = array_api_compat .result_type (
255
+ maybe_promote_to_variable_width (arr_or_dtype ), xp = xp
256
+ )
257
+ if isinstance (result_type , np .dtype ):
258
+ np_result_types .add (result_type )
259
+ except TypeError :
260
+ # passing individual objects to xp.result_type means NEP-18 implementations won't have
261
+ # a chance to intercept special values (such as NA) that numpy core cannot handle
262
+ pass
263
+
264
+ if np_result_types :
265
+ for left , right in PROMOTE_TO_OBJECT :
266
+ if any (np .issubdtype (t , left ) for t in np_result_types ) and any (
267
+ np .issubdtype (t , right ) for t in np_result_types
268
+ ):
269
+ return True
270
+
271
+ return False
238
272
239
273
240
274
def result_type (
@@ -263,19 +297,9 @@ def result_type(
263
297
if xp is None :
264
298
xp = get_array_namespace (arrays_and_dtypes )
265
299
266
- types = {
267
- array_api_compat .result_type (preprocess_types (t ), xp = xp )
268
- for t in arrays_and_dtypes
269
- }
270
- if any (isinstance (t , np .dtype ) for t in types ):
271
- # only check if there's numpy dtypes – the array API does not
272
- # define the types we're checking for
273
- for left , right in PROMOTE_TO_OBJECT :
274
- if any (np .issubdtype (t , left ) for t in types ) and any (
275
- np .issubdtype (t , right ) for t in types
276
- ):
277
- return np .dtype (object )
300
+ if should_promote_to_object (arrays_and_dtypes , xp ):
301
+ return np .dtype (object )
278
302
279
303
return array_api_compat .result_type (
280
- * map (preprocess_types , arrays_and_dtypes ), xp = xp
304
+ * map (maybe_promote_to_variable_width , arrays_and_dtypes ), xp = xp
281
305
)
0 commit comments