1use std::borrow::Cow;
4
5use polars_compute::cast::CastOptionsImpl;
6#[cfg(feature = "serde-lazy")]
7use serde::{Deserialize, Serialize};
8
9use super::flags::StatisticsFlags;
10#[cfg(feature = "dtype-datetime")]
11use crate::prelude::DataType::Datetime;
12use crate::prelude::*;
13use crate::utils::{handle_array_casting_failures, handle_casting_failures};
14
15#[derive(Copy, Clone, Debug, Default, PartialEq, Hash, Eq)]
16#[cfg_attr(feature = "serde-lazy", derive(Serialize, Deserialize))]
17#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
18#[repr(u8)]
19pub enum CastOptions {
20 #[default]
22 Strict,
23 NonStrict,
25 Overflowing,
27}
28
29impl CastOptions {
30 pub fn is_strict(&self) -> bool {
31 matches!(self, CastOptions::Strict)
32 }
33}
34
35impl From<CastOptions> for CastOptionsImpl {
36 fn from(value: CastOptions) -> Self {
37 let wrapped = match value {
38 CastOptions::Strict | CastOptions::NonStrict => false,
39 CastOptions::Overflowing => true,
40 };
41 CastOptionsImpl {
42 wrapped,
43 partial: false,
44 }
45 }
46}
47
48pub(crate) fn cast_chunks(
49 chunks: &[ArrayRef],
50 dtype: &DataType,
51 options: CastOptions,
52) -> PolarsResult<Vec<ArrayRef>> {
53 let check_nulls = matches!(options, CastOptions::Strict);
54 let options = options.into();
55
56 let arrow_dtype = dtype.try_to_arrow(CompatLevel::newest())?;
57 chunks
58 .iter()
59 .map(|arr| {
60 let out = polars_compute::cast::cast(arr.as_ref(), &arrow_dtype, options);
61 if check_nulls {
62 out.and_then(|new| {
63 if arr.null_count() != new.null_count() {
64 handle_array_casting_failures(&**arr, &*new)?;
65 }
66 Ok(new)
67 })
68 } else {
69 out
70 }
71 })
72 .collect::<PolarsResult<Vec<_>>>()
73}
74
75fn cast_impl_inner(
76 name: PlSmallStr,
77 chunks: &[ArrayRef],
78 dtype: &DataType,
79 options: CastOptions,
80) -> PolarsResult<Series> {
81 let chunks = match dtype {
82 #[cfg(feature = "dtype-decimal")]
83 DataType::Decimal(_, _) => {
84 let mut chunks = cast_chunks(chunks, dtype, options)?;
85 for chunk in chunks.iter_mut() {
87 *chunk = std::mem::take(
88 chunk
89 .as_any_mut()
90 .downcast_mut::<PrimitiveArray<i128>>()
91 .unwrap(),
92 )
93 .to(ArrowDataType::Int128)
94 .to_boxed();
95 }
96 chunks
97 },
98 _ => cast_chunks(chunks, &dtype.to_physical(), options)?,
99 };
100
101 let out = Series::try_from((name, chunks))?;
102 use DataType::*;
103 let out = match dtype {
104 Date => out.into_date(),
105 Datetime(tu, tz) => match tz {
106 #[cfg(feature = "timezones")]
107 Some(tz) => {
108 TimeZone::validate_time_zone(tz)?;
109 out.into_datetime(*tu, Some(tz.clone()))
110 },
111 _ => out.into_datetime(*tu, None),
112 },
113 Duration(tu) => out.into_duration(*tu),
114 #[cfg(feature = "dtype-time")]
115 Time => out.into_time(),
116 #[cfg(feature = "dtype-decimal")]
117 Decimal(precision, scale) => out.into_decimal(*precision, *scale)?,
118 _ => out,
119 };
120
121 Ok(out)
122}
123
124fn cast_impl(
125 name: PlSmallStr,
126 chunks: &[ArrayRef],
127 dtype: &DataType,
128 options: CastOptions,
129) -> PolarsResult<Series> {
130 cast_impl_inner(name, chunks, dtype, options)
131}
132
133#[cfg(feature = "dtype-struct")]
134fn cast_single_to_struct(
135 name: PlSmallStr,
136 chunks: &[ArrayRef],
137 fields: &[Field],
138 options: CastOptions,
139) -> PolarsResult<Series> {
140 polars_ensure!(fields.len() == 1, InvalidOperation: "must specify one field in the struct");
141 let mut new_fields = Vec::with_capacity(fields.len());
142 let mut fields = fields.iter();
144 let fld = fields.next().unwrap();
145 let s = cast_impl_inner(fld.name.clone(), chunks, &fld.dtype, options)?;
146 let length = s.len();
147 new_fields.push(s);
148
149 for fld in fields {
150 new_fields.push(Series::full_null(fld.name.clone(), length, &fld.dtype));
151 }
152
153 StructChunked::from_series(name, length, new_fields.iter()).map(|ca| ca.into_series())
154}
155
156impl<T> ChunkedArray<T>
157where
158 T: PolarsNumericType,
159{
160 fn cast_impl(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
161 if self.dtype() == dtype {
162 let mut out = unsafe {
164 Series::from_chunks_and_dtype_unchecked(
165 self.name().clone(),
166 self.chunks.clone(),
167 dtype,
168 )
169 };
170 out.set_sorted_flag(self.is_sorted_flag());
171 return Ok(out);
172 }
173 match dtype {
174 #[cfg(feature = "dtype-categorical")]
177 DataType::Categorical(cats, _mapping) => {
178 polars_warn!(
179 Deprecation,
180 "casting from {:?} to {dtype:?} is deprecated.\n\
181 Instead of `.cast({dtype:?}`, use `.cat.to({dtype:?})`.",
182 T::get_static_dtype()
183 );
184 let s = self.cast_with_options(&cats.physical().dtype(), options)?;
185 with_match_categorical_physical_type!(cats.physical(), |$C| {
186 type PhysCa = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
188 let ca: &PhysCa = s.as_ref().as_ref();
189 Ok(CategoricalChunked::<$C>::from_cats_and_dtype(ca.clone(), dtype.clone())
190 .into_series())
191 })
192 },
193
194 #[cfg(feature = "dtype-categorical")]
197 DataType::Enum(fcats, _mapping) => {
198 polars_warn!(
199 Deprecation,
200 "casting from {:?} to {dtype:?} is deprecated.\n\
201 Instead of `.cast({dtype:?}`, use `.cat.to({dtype:?})`.",
202 T::get_static_dtype()
203 );
204 let s = self.cast_with_options(&fcats.physical().dtype(), options)?;
205 with_match_categorical_physical_type!(fcats.physical(), |$C| {
206 type PhysCa = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
208 let ca: &PhysCa = s.as_ref().as_ref();
209 Ok(CategoricalChunked::<$C>::from_cats_and_dtype(ca.clone(), dtype.clone()).into_series())
210 })
211 },
212
213 #[cfg(feature = "dtype-struct")]
214 DataType::Struct(fields) => {
215 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
216 },
217 _ => cast_impl_inner(self.name().clone(), &self.chunks, dtype, options).map(|mut s| {
218 let to_signed = dtype.is_signed_integer();
223 let unsigned2unsigned =
224 self.dtype().is_unsigned_integer() && dtype.is_unsigned_integer();
225 let allowed = to_signed || unsigned2unsigned;
226
227 if (allowed)
228 && (s.null_count() == self.null_count())
229 || (self.dtype().to_physical() == dtype.to_physical())
231 {
232 let is_sorted = self.is_sorted_flag();
233 s.set_sorted_flag(is_sorted)
234 }
235 s
236 }),
237 }
238 }
239}
240
241impl<T> ChunkCast for ChunkedArray<T>
242where
243 T: PolarsNumericType,
244{
245 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
246 self.cast_impl(dtype, options)
247 }
248
249 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
250 match dtype {
251 #[cfg(feature = "dtype-categorical")]
254 DataType::Categorical(cats, _mapping) => {
255 polars_ensure!(self.dtype() == &cats.physical().dtype(), ComputeError: "cannot cast numeric types to 'Categorical'");
256 with_match_categorical_physical_type!(cats.physical(), |$C| {
257 type PhysCa = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
259 let ca = unsafe { &*(self as *const ChunkedArray<T> as *const PhysCa) };
260 Ok(CategoricalChunked::<$C>::from_cats_and_dtype_unchecked(ca.clone(), dtype.clone())
261 .into_series())
262 })
263 },
264
265 #[cfg(feature = "dtype-categorical")]
268 DataType::Enum(fcats, _mapping) => {
269 polars_ensure!(self.dtype() == &fcats.physical().dtype(), ComputeError: "cannot cast numeric types to 'Enum'");
270 with_match_categorical_physical_type!(fcats.physical(), |$C| {
271 type PhysCa = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
273 let ca = unsafe { &*(self as *const ChunkedArray<T> as *const PhysCa) };
274 Ok(CategoricalChunked::<$C>::from_cats_and_dtype_unchecked(ca.clone(), dtype.clone()).into_series())
275 })
276 },
277
278 _ => self.cast_impl(dtype, CastOptions::Overflowing),
279 }
280 }
281}
282
283impl ChunkCast for StringChunked {
284 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
285 match dtype {
286 #[cfg(feature = "dtype-categorical")]
287 DataType::Categorical(cats, _mapping) => {
288 with_match_categorical_physical_type!(cats.physical(), |$C| {
289 Ok(CategoricalChunked::<$C>::from_str_iter(self.name().clone(), dtype.clone(), self.iter())?
290 .into_series())
291 })
292 },
293 #[cfg(feature = "dtype-categorical")]
294 DataType::Enum(fcats, _mapping) => {
295 let ret = with_match_categorical_physical_type!(fcats.physical(), |$C| {
296 CategoricalChunked::<$C>::from_str_iter(self.name().clone(), dtype.clone(), self.iter())?
297 .into_series()
298 });
299
300 if options.is_strict() && self.null_count() != ret.null_count() {
301 handle_casting_failures(&self.clone().into_series(), &ret)?;
302 }
303
304 Ok(ret)
305 },
306 #[cfg(feature = "dtype-struct")]
307 DataType::Struct(fields) => {
308 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
309 },
310 #[cfg(feature = "dtype-decimal")]
311 DataType::Decimal(precision, scale) => {
312 let chunks = self.downcast_iter().map(|arr| {
313 polars_compute::cast::binview_to_decimal(&arr.to_binview(), *precision, *scale)
314 .to(ArrowDataType::Int128)
315 });
316 let ca = Int128Chunked::from_chunk_iter(self.name().clone(), chunks);
317 Ok(ca.into_decimal_unchecked(*precision, *scale).into_series())
318 },
319 #[cfg(feature = "dtype-date")]
320 DataType::Date => {
321 let result = cast_chunks(&self.chunks, dtype, options)?;
322 let out = Series::try_from((self.name().clone(), result))?;
323 Ok(out)
324 },
325 #[cfg(feature = "dtype-datetime")]
326 DataType::Datetime(time_unit, time_zone) => match time_zone {
327 #[cfg(feature = "timezones")]
328 Some(time_zone) => {
329 TimeZone::validate_time_zone(time_zone)?;
330 let result = cast_chunks(
331 &self.chunks,
332 &Datetime(time_unit.to_owned(), Some(time_zone.clone())),
333 options,
334 )?;
335 Series::try_from((self.name().clone(), result))
336 },
337 _ => {
338 let result =
339 cast_chunks(&self.chunks, &Datetime(time_unit.to_owned(), None), options)?;
340 Series::try_from((self.name().clone(), result))
341 },
342 },
343 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
344 }
345 }
346
347 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
348 self.cast_with_options(dtype, CastOptions::Overflowing)
349 }
350}
351
352impl BinaryChunked {
353 pub unsafe fn to_string_unchecked(&self) -> StringChunked {
356 let chunks = self
357 .downcast_iter()
358 .map(|arr| unsafe { arr.to_utf8view_unchecked() }.boxed())
359 .collect();
360 let field = Arc::new(Field::new(self.name().clone(), DataType::String));
361
362 let mut ca = StringChunked::new_with_compute_len(field, chunks);
363
364 use StatisticsFlags as F;
365 ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
366 ca
367 }
368}
369
370impl StringChunked {
371 pub fn as_binary(&self) -> BinaryChunked {
372 let chunks = self
373 .downcast_iter()
374 .map(|arr| arr.to_binview().boxed())
375 .collect();
376 let field = Arc::new(Field::new(self.name().clone(), DataType::Binary));
377
378 let mut ca = BinaryChunked::new_with_compute_len(field, chunks);
379
380 use StatisticsFlags as F;
381 ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
382 ca
383 }
384}
385
386impl ChunkCast for BinaryChunked {
387 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
388 match dtype {
389 #[cfg(feature = "dtype-struct")]
390 DataType::Struct(fields) => {
391 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
392 },
393 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
394 }
395 }
396
397 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
398 match dtype {
399 DataType::String => unsafe { Ok(self.to_string_unchecked().into_series()) },
400 _ => self.cast_with_options(dtype, CastOptions::Overflowing),
401 }
402 }
403}
404
405impl ChunkCast for BinaryOffsetChunked {
406 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
407 match dtype {
408 #[cfg(feature = "dtype-struct")]
409 DataType::Struct(fields) => {
410 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
411 },
412 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
413 }
414 }
415
416 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
417 self.cast_with_options(dtype, CastOptions::Overflowing)
418 }
419}
420
421impl ChunkCast for BooleanChunked {
422 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
423 match dtype {
424 #[cfg(feature = "dtype-struct")]
425 DataType::Struct(fields) => {
426 cast_single_to_struct(self.name().clone(), &self.chunks, fields, options)
427 },
428 #[cfg(feature = "dtype-categorical")]
429 DataType::Categorical(_, _) | DataType::Enum(_, _) => {
430 polars_bail!(InvalidOperation: "cannot cast Boolean to Categorical");
431 },
432 _ => cast_impl(self.name().clone(), &self.chunks, dtype, options),
433 }
434 }
435
436 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
437 self.cast_with_options(dtype, CastOptions::Overflowing)
438 }
439}
440
441impl ChunkCast for ListChunked {
444 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
445 let ca = self
446 .trim_lists_to_normalized_offsets()
447 .map_or(Cow::Borrowed(self), Cow::Owned);
448 let ca = ca.propagate_nulls().map_or(ca, Cow::Owned);
449
450 use DataType::*;
451 match dtype {
452 List(child_type) => {
453 match (ca.inner_dtype(), &**child_type) {
454 (old, new) if old == new => Ok(ca.into_owned().into_series()),
455 #[cfg(feature = "dtype-categorical")]
457 (dt, Categorical(_, _) | Enum(_, _))
458 if !matches!(dt, Categorical(_, _) | Enum(_, _) | String | Null) =>
459 {
460 polars_bail!(InvalidOperation: "cannot cast List inner type: '{:?}' to Categorical", dt)
461 },
462 _ => {
463 let (arr, child_type) = cast_list(ca.as_ref(), child_type, options)?;
465 unsafe {
468 Ok(Series::from_chunks_and_dtype_unchecked(
469 ca.name().clone(),
470 vec![arr],
471 &List(Box::new(child_type)),
472 ))
473 }
474 },
475 }
476 },
477 #[cfg(feature = "dtype-array")]
478 Array(child_type, width) => {
479 let physical_type = dtype.to_physical();
480
481 let chunks = cast_chunks(ca.chunks(), &physical_type, options)?;
483 unsafe {
486 Ok(Series::from_chunks_and_dtype_unchecked(
487 ca.name().clone(),
488 chunks,
489 &Array(child_type.clone(), *width),
490 ))
491 }
492 },
493 #[cfg(feature = "dtype-u8")]
494 Binary => {
495 polars_ensure!(
496 matches!(self.inner_dtype(), UInt8),
497 InvalidOperation: "cannot cast List type (inner: '{:?}', to: '{:?}')",
498 self.inner_dtype(),
499 dtype,
500 );
501 let chunks = cast_chunks(self.chunks(), &DataType::Binary, options)?;
502
503 unsafe {
505 Ok(Series::from_chunks_and_dtype_unchecked(
506 self.name().clone(),
507 chunks,
508 &DataType::Binary,
509 ))
510 }
511 },
512 _ => {
513 polars_bail!(
514 InvalidOperation: "cannot cast List type (inner: '{:?}', to: '{:?}')",
515 ca.inner_dtype(),
516 dtype,
517 )
518 },
519 }
520 }
521
522 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
523 use DataType::*;
524 match dtype {
525 List(child_type) => cast_list_unchecked(self, child_type),
526 _ => self.cast_with_options(dtype, CastOptions::Overflowing),
527 }
528 }
529}
530
531#[cfg(feature = "dtype-array")]
534impl ChunkCast for ArrayChunked {
535 fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Series> {
536 let ca = self
537 .trim_lists_to_normalized_offsets()
538 .map_or(Cow::Borrowed(self), Cow::Owned);
539 let ca = ca.propagate_nulls().map_or(ca, Cow::Owned);
540
541 use DataType::*;
542 match dtype {
543 Array(child_type, width) => {
544 polars_ensure!(
545 *width == ca.width(),
546 InvalidOperation: "cannot cast Array to a different width"
547 );
548
549 match (ca.inner_dtype(), &**child_type) {
550 (old, new) if old == new => Ok(ca.into_owned().into_series()),
551 #[cfg(feature = "dtype-categorical")]
553 (dt, Categorical(_, _) | Enum(_, _)) if !matches!(dt, String) => {
554 polars_bail!(InvalidOperation: "cannot cast Array inner type: '{:?}' to dtype: {:?}", dt, child_type)
555 },
556 _ => {
557 let (arr, child_type) =
559 cast_fixed_size_list(ca.as_ref(), child_type, options)?;
560 unsafe {
563 Ok(Series::from_chunks_and_dtype_unchecked(
564 ca.name().clone(),
565 vec![arr],
566 &Array(Box::new(child_type), *width),
567 ))
568 }
569 },
570 }
571 },
572 List(child_type) => {
573 let physical_type = dtype.to_physical();
574 let chunks = cast_chunks(ca.chunks(), &physical_type, options)?;
576 unsafe {
579 Ok(Series::from_chunks_and_dtype_unchecked(
580 ca.name().clone(),
581 chunks,
582 &List(child_type.clone()),
583 ))
584 }
585 },
586 _ => {
587 polars_bail!(
588 InvalidOperation: "cannot cast Array type (inner: '{:?}', to: '{:?}')",
589 ca.inner_dtype(),
590 dtype,
591 )
592 },
593 }
594 }
595
596 unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Series> {
597 self.cast_with_options(dtype, CastOptions::Overflowing)
598 }
599}
600
601fn cast_list(
604 ca: &ListChunked,
605 child_type: &DataType,
606 options: CastOptions,
607) -> PolarsResult<(ArrayRef, DataType)> {
608 let ca = ca.rechunk();
611 let arr = ca.downcast_as_array();
612 let s = unsafe {
614 Series::from_chunks_and_dtype_unchecked(
615 PlSmallStr::EMPTY,
616 vec![arr.values().clone()],
617 ca.inner_dtype(),
618 )
619 };
620 let new_inner = s.cast_with_options(child_type, options)?;
621
622 let inner_dtype = new_inner.dtype().clone();
623 debug_assert_eq!(&inner_dtype, child_type);
624
625 let new_values = new_inner.array_ref(0).clone();
626
627 let dtype = ListArray::<i64>::default_datatype(new_values.dtype().clone());
628 let new_arr = ListArray::<i64>::new(
629 dtype,
630 arr.offsets().clone(),
631 new_values,
632 arr.validity().cloned(),
633 );
634 Ok((new_arr.boxed(), inner_dtype))
635}
636
637unsafe fn cast_list_unchecked(ca: &ListChunked, child_type: &DataType) -> PolarsResult<Series> {
638 let ca = ca.rechunk();
640 let arr = ca.downcast_as_array();
641 let s = unsafe {
643 Series::from_chunks_and_dtype_unchecked(
644 PlSmallStr::EMPTY,
645 vec![arr.values().clone()],
646 ca.inner_dtype(),
647 )
648 };
649 let new_inner = s.cast_unchecked(child_type)?;
650 let new_values = new_inner.array_ref(0).clone();
651
652 let dtype = ListArray::<i64>::default_datatype(new_values.dtype().clone());
653 let new_arr = ListArray::<i64>::new(
654 dtype,
655 arr.offsets().clone(),
656 new_values,
657 arr.validity().cloned(),
658 );
659 Ok(ListChunked::from_chunks_and_dtype_unchecked(
660 ca.name().clone(),
661 vec![Box::new(new_arr)],
662 DataType::List(Box::new(child_type.clone())),
663 )
664 .into_series())
665}
666
667#[cfg(feature = "dtype-array")]
670fn cast_fixed_size_list(
671 ca: &ArrayChunked,
672 child_type: &DataType,
673 options: CastOptions,
674) -> PolarsResult<(ArrayRef, DataType)> {
675 let ca = ca.rechunk();
676 let arr = ca.downcast_as_array();
677 let s = unsafe {
679 Series::from_chunks_and_dtype_unchecked(
680 PlSmallStr::EMPTY,
681 vec![arr.values().clone()],
682 ca.inner_dtype(),
683 )
684 };
685 let new_inner = s.cast_with_options(child_type, options)?;
686
687 let inner_dtype = new_inner.dtype().clone();
688 debug_assert_eq!(&inner_dtype, child_type);
689
690 let new_values = new_inner.array_ref(0).clone();
691
692 let dtype = FixedSizeListArray::default_datatype(new_values.dtype().clone(), ca.width());
693 let new_arr = FixedSizeListArray::new(dtype, ca.len(), new_values, arr.validity().cloned());
694 Ok((Box::new(new_arr), inner_dtype))
695}
696
697#[cfg(test)]
698mod test {
699 use crate::chunked_array::cast::CastOptions;
700 use crate::prelude::*;
701
702 #[test]
703 fn test_cast_list() -> PolarsResult<()> {
704 let mut builder = ListPrimitiveChunkedBuilder::<Int32Type>::new(
705 PlSmallStr::from_static("a"),
706 10,
707 10,
708 DataType::Int32,
709 );
710 builder.append_opt_slice(Some(&[1i32, 2, 3]));
711 builder.append_opt_slice(Some(&[1i32, 2, 3]));
712 let ca = builder.finish();
713
714 let new = ca.cast_with_options(
715 &DataType::List(DataType::Float64.into()),
716 CastOptions::Strict,
717 )?;
718
719 assert_eq!(new.dtype(), &DataType::List(DataType::Float64.into()));
720 Ok(())
721 }
722
723 #[test]
724 #[cfg(feature = "dtype-categorical")]
725 fn test_cast_noop() {
726 let ca = StringChunked::new(PlSmallStr::from_static("foo"), &["bar", "ham"]);
728 let cats = Categories::global();
729 let out = ca
730 .cast_with_options(
731 &DataType::from_categories(cats.clone()),
732 CastOptions::Strict,
733 )
734 .unwrap();
735 let out = out.cast(&DataType::from_categories(cats)).unwrap();
736 assert!(matches!(out.dtype(), &DataType::Categorical(_, _)))
737 }
738}