1use std::fmt::Display;
2use std::hash::{DefaultHasher, Hash, Hasher};
3use std::sync::Arc;
4
5use arrow_schema::{DataType, Field, IntervalUnit, TimeUnit};
6use pyo3::exceptions::{PyTypeError, PyValueError};
7use pyo3::intern;
8use pyo3::prelude::*;
9use pyo3::types::{PyCapsule, PyTuple, PyType};
10
11use crate::error::PyArrowResult;
12use crate::export::{Arro3DataType, Arro3Field};
13use crate::ffi::from_python::utils::import_schema_pycapsule;
14use crate::ffi::to_python::nanoarrow::to_nanoarrow_schema;
15use crate::ffi::to_schema_pycapsule;
16use crate::PyField;
17
18struct PyTimeUnit(arrow_schema::TimeUnit);
19
20impl<'a> FromPyObject<'_, 'a> for PyTimeUnit {
21 type Error = PyErr;
22
23 fn extract(obj: Borrowed<'_, 'a, PyAny>) -> Result<Self, Self::Error> {
24 let s: String = obj.extract()?;
25 match s.to_lowercase().as_str() {
26 "s" => Ok(Self(TimeUnit::Second)),
27 "ms" => Ok(Self(TimeUnit::Millisecond)),
28 "us" => Ok(Self(TimeUnit::Microsecond)),
29 "ns" => Ok(Self(TimeUnit::Nanosecond)),
30 _ => Err(PyValueError::new_err("Unexpected time unit")),
31 }
32 }
33}
34
35#[derive(PartialEq, Eq, Debug)]
37#[pyclass(module = "arro3.core._core", name = "DataType", subclass, frozen)]
38pub struct PyDataType(DataType);
39
40impl PyDataType {
41 pub fn new(data_type: DataType) -> Self {
43 Self(data_type)
44 }
45
46 pub fn from_arrow_pycapsule(capsule: &Bound<PyCapsule>) -> PyResult<Self> {
48 let schema_ptr = import_schema_pycapsule(capsule)?;
49 let data_type =
50 DataType::try_from(schema_ptr).map_err(|err| PyTypeError::new_err(err.to_string()))?;
51 Ok(Self::new(data_type))
52 }
53
54 pub fn into_inner(self) -> DataType {
56 self.0
57 }
58
59 pub fn to_arro3<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
61 let arro3_mod = py.import(intern!(py, "arro3.core"))?;
62 arro3_mod.getattr(intern!(py, "DataType"))?.call_method1(
63 intern!(py, "from_arrow_pycapsule"),
64 PyTuple::new(py, vec![self.__arrow_c_schema__(py)?])?,
65 )
66 }
67
68 pub fn into_arro3(self, py: Python) -> PyResult<Bound<PyAny>> {
70 let arro3_mod = py.import(intern!(py, "arro3.core"))?;
71 let capsule = to_schema_pycapsule(py, &self.0)?;
72 arro3_mod.getattr(intern!(py, "DataType"))?.call_method1(
73 intern!(py, "from_arrow_pycapsule"),
74 PyTuple::new(py, vec![capsule])?,
75 )
76 }
77
78 pub fn to_nanoarrow<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
80 to_nanoarrow_schema(py, &self.__arrow_c_schema__(py)?)
81 }
82
83 pub fn into_pyarrow(self, py: Python) -> PyResult<Bound<PyAny>> {
87 let pyarrow_mod = py.import(intern!(py, "pyarrow"))?;
88 let pyarrow_field = pyarrow_mod
89 .getattr(intern!(py, "field"))?
90 .call1(PyTuple::new(py, vec![self.into_pyobject(py)?])?)?;
91 pyarrow_field.getattr(intern!(py, "type"))
92 }
93}
94
95impl From<PyDataType> for DataType {
96 fn from(value: PyDataType) -> Self {
97 value.0
98 }
99}
100
101impl From<DataType> for PyDataType {
102 fn from(value: DataType) -> Self {
103 Self(value)
104 }
105}
106
107impl From<&DataType> for PyDataType {
108 fn from(value: &DataType) -> Self {
109 Self(value.clone())
110 }
111}
112
113impl AsRef<DataType> for PyDataType {
114 fn as_ref(&self) -> &DataType {
115 &self.0
116 }
117}
118
119impl Display for PyDataType {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 write!(f, "arro3.core.DataType<")?;
122 self.0.fmt(f)?;
123 writeln!(f, ">")?;
124 Ok(())
125 }
126}
127
128#[allow(non_snake_case)]
129#[pymethods]
130impl PyDataType {
131 pub(crate) fn __arrow_c_schema__<'py>(
132 &'py self,
133 py: Python<'py>,
134 ) -> PyArrowResult<Bound<'py, PyCapsule>> {
135 let field = Field::new("", self.0.clone(), true);
138 to_schema_pycapsule(py, &field)
139 }
140
141 fn __eq__(&self, other: PyDataType) -> bool {
142 self.equals(other, false)
143 }
144
145 fn __hash__(&self) -> u64 {
146 let mut hasher = DefaultHasher::new();
147 self.0.hash(&mut hasher);
148 hasher.finish()
149 }
150
151 fn __repr__(&self) -> String {
152 self.to_string()
153 }
154
155 #[classmethod]
156 fn from_arrow(_cls: &Bound<PyType>, input: Self) -> Self {
157 input
158 }
159
160 #[classmethod]
161 #[pyo3(name = "from_arrow_pycapsule")]
162 fn from_arrow_pycapsule_py(_cls: &Bound<PyType>, capsule: &Bound<PyCapsule>) -> PyResult<Self> {
163 Self::from_arrow_pycapsule(capsule)
164 }
165
166 #[getter]
167 fn bit_width(&self) -> Option<usize> {
168 self.0.primitive_width().map(|width| width * 8)
169 }
170
171 #[pyo3(signature=(other, *, check_metadata=false))]
172 fn equals(&self, other: PyDataType, check_metadata: bool) -> bool {
173 let other = other.into_inner();
174 if check_metadata {
175 self.0 == other
176 } else {
177 self.0.equals_datatype(&other)
178 }
179 }
180
181 #[getter]
182 fn list_size(&self) -> Option<i32> {
183 match &self.0 {
184 DataType::FixedSizeList(_, list_size) => Some(*list_size),
185 _ => None,
186 }
187 }
188
189 #[getter]
190 fn num_fields(&self) -> usize {
191 match &self.0 {
192 DataType::Null
193 | DataType::Boolean
194 | DataType::Int8
195 | DataType::Int16
196 | DataType::Int32
197 | DataType::Int64
198 | DataType::UInt8
199 | DataType::UInt16
200 | DataType::UInt32
201 | DataType::UInt64
202 | DataType::Float16
203 | DataType::Float32
204 | DataType::Float64
205 | DataType::Timestamp(_, _)
206 | DataType::Date32
207 | DataType::Date64
208 | DataType::Time32(_)
209 | DataType::Time64(_)
210 | DataType::Duration(_)
211 | DataType::Interval(_)
212 | DataType::Binary
213 | DataType::FixedSizeBinary(_)
214 | DataType::LargeBinary
215 | DataType::BinaryView
216 | DataType::Utf8
217 | DataType::LargeUtf8
218 | DataType::Utf8View
219 | DataType::Decimal32(_, _)
220 | DataType::Decimal64(_, _)
221 | DataType::Decimal128(_, _)
222 | DataType::Decimal256(_, _) => 0,
223 DataType::List(_)
224 | DataType::ListView(_)
225 | DataType::FixedSizeList(_, _)
226 | DataType::LargeList(_)
227 | DataType::LargeListView(_) => 1,
228 DataType::Struct(fields) => fields.len(),
229 DataType::Union(fields, _) => fields.len(),
230 DataType::Dictionary(_, _) | DataType::Map(_, _) | DataType::RunEndEncoded(_, _) => 2,
232 }
233 }
234
235 #[getter]
236 fn time_unit(&self) -> Option<&str> {
237 match &self.0 {
238 DataType::Time32(unit)
239 | DataType::Time64(unit)
240 | DataType::Timestamp(unit, _)
241 | DataType::Duration(unit) => match unit {
242 TimeUnit::Second => Some("s"),
243 TimeUnit::Millisecond => Some("ms"),
244 TimeUnit::Microsecond => Some("us"),
245 TimeUnit::Nanosecond => Some("ns"),
246 },
247 _ => None,
248 }
249 }
250
251 #[getter]
252 fn tz(&self) -> Option<&str> {
253 match &self.0 {
254 DataType::Timestamp(_, tz) => tz.as_deref(),
255 _ => None,
256 }
257 }
258
259 #[getter]
260 fn value_type(&self) -> Option<Arro3DataType> {
261 match &self.0 {
262 DataType::FixedSizeList(value_field, _)
263 | DataType::List(value_field)
264 | DataType::LargeList(value_field)
265 | DataType::ListView(value_field)
266 | DataType::LargeListView(value_field)
267 | DataType::RunEndEncoded(_, value_field) => {
268 Some(PyDataType::new(value_field.data_type().clone()).into())
269 }
270 DataType::Dictionary(_key_type, value_type) => {
271 Some(PyDataType::new(*value_type.clone()).into())
272 }
273 _ => None,
274 }
275 }
276
277 #[getter]
278 fn value_field(&self) -> Option<Arro3Field> {
279 match &self.0 {
280 DataType::FixedSizeList(value_field, _)
281 | DataType::List(value_field)
282 | DataType::LargeList(value_field)
283 | DataType::ListView(value_field)
284 | DataType::LargeListView(value_field) => {
285 Some(PyField::new(value_field.clone()).into())
286 }
287 _ => None,
288 }
289 }
290
291 #[getter]
292 fn fields(&self) -> Option<Vec<Arro3Field>> {
293 match &self.0 {
294 DataType::Struct(fields) => Some(
295 fields
296 .into_iter()
297 .map(|f| PyField::new(f.clone()).into())
298 .collect::<Vec<_>>(),
299 ),
300 _ => None,
301 }
302 }
303
304 #[classmethod]
307 fn null(_: &Bound<PyType>) -> Self {
308 Self(DataType::Null)
309 }
310
311 #[classmethod]
312 fn bool(_: &Bound<PyType>) -> Self {
313 Self(DataType::Boolean)
314 }
315
316 #[classmethod]
317 fn int8(_: &Bound<PyType>) -> Self {
318 Self(DataType::Int8)
319 }
320
321 #[classmethod]
322 fn int16(_: &Bound<PyType>) -> Self {
323 Self(DataType::Int16)
324 }
325
326 #[classmethod]
327 fn int32(_: &Bound<PyType>) -> Self {
328 Self(DataType::Int32)
329 }
330
331 #[classmethod]
332 fn int64(_: &Bound<PyType>) -> Self {
333 Self(DataType::Int64)
334 }
335
336 #[classmethod]
337 fn uint8(_: &Bound<PyType>) -> Self {
338 Self(DataType::UInt8)
339 }
340
341 #[classmethod]
342 fn uint16(_: &Bound<PyType>) -> Self {
343 Self(DataType::UInt16)
344 }
345
346 #[classmethod]
347 fn uint32(_: &Bound<PyType>) -> Self {
348 Self(DataType::UInt32)
349 }
350
351 #[classmethod]
352 fn uint64(_: &Bound<PyType>) -> Self {
353 Self(DataType::UInt64)
354 }
355
356 #[classmethod]
357 fn float16(_: &Bound<PyType>) -> Self {
358 Self(DataType::Float16)
359 }
360
361 #[classmethod]
362 fn float32(_: &Bound<PyType>) -> Self {
363 Self(DataType::Float32)
364 }
365
366 #[classmethod]
367 fn float64(_: &Bound<PyType>) -> Self {
368 Self(DataType::Float64)
369 }
370
371 #[classmethod]
372 fn time32(_: &Bound<PyType>, unit: PyTimeUnit) -> PyArrowResult<Self> {
373 if unit.0 == TimeUnit::Microsecond || unit.0 == TimeUnit::Nanosecond {
374 return Err(PyValueError::new_err("Unexpected timeunit for time32").into());
375 }
376
377 Ok(Self(DataType::Time32(unit.0)))
378 }
379
380 #[classmethod]
381 fn time64(_: &Bound<PyType>, unit: PyTimeUnit) -> PyArrowResult<Self> {
382 if unit.0 == TimeUnit::Second || unit.0 == TimeUnit::Millisecond {
383 return Err(PyValueError::new_err("Unexpected timeunit for time64").into());
384 }
385
386 Ok(Self(DataType::Time64(unit.0)))
387 }
388
389 #[classmethod]
390 #[pyo3(signature = (unit, *, tz=None))]
391 fn timestamp(_: &Bound<PyType>, unit: PyTimeUnit, tz: Option<String>) -> Self {
392 Self(DataType::Timestamp(unit.0, tz.map(|s| s.into())))
393 }
394
395 #[classmethod]
396 fn date32(_: &Bound<PyType>) -> Self {
397 Self(DataType::Date32)
398 }
399
400 #[classmethod]
401 fn date64(_: &Bound<PyType>) -> Self {
402 Self(DataType::Date64)
403 }
404
405 #[classmethod]
406 fn duration(_: &Bound<PyType>, unit: PyTimeUnit) -> Self {
407 Self(DataType::Duration(unit.0))
408 }
409
410 #[classmethod]
411 fn month_day_nano_interval(_: &Bound<PyType>) -> Self {
412 Self(DataType::Interval(IntervalUnit::MonthDayNano))
413 }
414
415 #[classmethod]
416 #[pyo3(signature = (length=None))]
417 fn binary(_: &Bound<PyType>, length: Option<i32>) -> Self {
418 if let Some(length) = length {
419 Self(DataType::FixedSizeBinary(length))
420 } else {
421 Self(DataType::Binary)
422 }
423 }
424
425 #[classmethod]
426 fn string(_: &Bound<PyType>) -> Self {
427 Self(DataType::Utf8)
428 }
429
430 #[classmethod]
431 fn utf8(_: &Bound<PyType>) -> Self {
432 Self(DataType::Utf8)
433 }
434
435 #[classmethod]
436 fn large_binary(_: &Bound<PyType>) -> Self {
437 Self(DataType::LargeBinary)
438 }
439
440 #[classmethod]
441 fn large_string(_: &Bound<PyType>) -> Self {
442 Self(DataType::LargeUtf8)
443 }
444
445 #[classmethod]
446 fn large_utf8(_: &Bound<PyType>) -> Self {
447 Self(DataType::LargeUtf8)
448 }
449
450 #[classmethod]
451 fn binary_view(_: &Bound<PyType>) -> Self {
452 Self(DataType::BinaryView)
453 }
454
455 #[classmethod]
456 fn string_view(_: &Bound<PyType>) -> Self {
457 Self(DataType::Utf8View)
458 }
459
460 #[classmethod]
461 fn decimal128(_: &Bound<PyType>, precision: u8, scale: i8) -> Self {
462 Self(DataType::Decimal128(precision, scale))
463 }
464
465 #[classmethod]
466 fn decimal256(_: &Bound<PyType>, precision: u8, scale: i8) -> Self {
467 Self(DataType::Decimal256(precision, scale))
468 }
469
470 #[classmethod]
471 #[pyo3(signature = (value_type, list_size=None))]
472 fn list(_: &Bound<PyType>, value_type: PyField, list_size: Option<i32>) -> Self {
473 if let Some(list_size) = list_size {
474 Self(DataType::FixedSizeList(value_type.into(), list_size))
475 } else {
476 Self(DataType::List(value_type.into()))
477 }
478 }
479
480 #[classmethod]
481 fn large_list(_: &Bound<PyType>, value_type: PyField) -> Self {
482 Self(DataType::LargeList(value_type.into()))
483 }
484
485 #[classmethod]
486 fn list_view(_: &Bound<PyType>, value_type: PyField) -> Self {
487 Self(DataType::ListView(value_type.into()))
488 }
489
490 #[classmethod]
491 fn large_list_view(_: &Bound<PyType>, value_type: PyField) -> Self {
492 Self(DataType::LargeListView(value_type.into()))
493 }
494
495 #[classmethod]
496 fn map(_: &Bound<PyType>, key_type: PyField, item_type: PyField, keys_sorted: bool) -> Self {
497 let data_type = DataType::Map(
500 Arc::new(Field::new(
501 "entries",
502 DataType::Struct(vec![key_type.into_inner(), item_type.into_inner()].into()),
503 false, )),
505 keys_sorted,
506 );
507 Self(data_type)
508 }
509
510 #[classmethod]
511 fn r#struct(_: &Bound<PyType>, fields: Vec<PyField>) -> Self {
512 Self(DataType::Struct(
513 fields.into_iter().map(|field| field.into_inner()).collect(),
514 ))
515 }
516
517 #[classmethod]
518 fn dictionary(_: &Bound<PyType>, index_type: PyDataType, value_type: PyDataType) -> Self {
519 Self(DataType::Dictionary(
520 Box::new(index_type.into_inner()),
521 Box::new(value_type.into_inner()),
522 ))
523 }
524
525 #[classmethod]
526 fn run_end_encoded(_: &Bound<PyType>, run_end_type: PyField, value_type: PyField) -> Self {
527 Self(DataType::RunEndEncoded(
528 run_end_type.into_inner(),
529 value_type.into_inner(),
530 ))
531 }
532
533 #[staticmethod]
536 fn is_boolean(t: PyDataType) -> bool {
537 t.0 == DataType::Boolean
538 }
539
540 #[staticmethod]
541 fn is_integer(t: PyDataType) -> bool {
542 t.0.is_integer()
543 }
544
545 #[staticmethod]
546 fn is_signed_integer(t: PyDataType) -> bool {
547 t.0.is_signed_integer()
548 }
549
550 #[staticmethod]
551 fn is_unsigned_integer(t: PyDataType) -> bool {
552 t.0.is_unsigned_integer()
553 }
554
555 #[staticmethod]
556 fn is_int8(t: PyDataType) -> bool {
557 t.0 == DataType::Int8
558 }
559 #[staticmethod]
560 fn is_int16(t: PyDataType) -> bool {
561 t.0 == DataType::Int16
562 }
563 #[staticmethod]
564 fn is_int32(t: PyDataType) -> bool {
565 t.0 == DataType::Int32
566 }
567 #[staticmethod]
568 fn is_int64(t: PyDataType) -> bool {
569 t.0 == DataType::Int64
570 }
571 #[staticmethod]
572 fn is_uint8(t: PyDataType) -> bool {
573 t.0 == DataType::UInt8
574 }
575 #[staticmethod]
576 fn is_uint16(t: PyDataType) -> bool {
577 t.0 == DataType::UInt16
578 }
579 #[staticmethod]
580 fn is_uint32(t: PyDataType) -> bool {
581 t.0 == DataType::UInt32
582 }
583 #[staticmethod]
584 fn is_uint64(t: PyDataType) -> bool {
585 t.0 == DataType::UInt64
586 }
587 #[staticmethod]
588 fn is_floating(t: PyDataType) -> bool {
589 t.0.is_floating()
590 }
591 #[staticmethod]
592 fn is_float16(t: PyDataType) -> bool {
593 t.0 == DataType::Float16
594 }
595 #[staticmethod]
596 fn is_float32(t: PyDataType) -> bool {
597 t.0 == DataType::Float32
598 }
599 #[staticmethod]
600 fn is_float64(t: PyDataType) -> bool {
601 t.0 == DataType::Float64
602 }
603 #[staticmethod]
604 fn is_decimal(t: PyDataType) -> bool {
605 matches!(t.0, DataType::Decimal128(_, _) | DataType::Decimal256(_, _))
606 }
607 #[staticmethod]
608 fn is_decimal128(t: PyDataType) -> bool {
609 matches!(t.0, DataType::Decimal128(_, _))
610 }
611 #[staticmethod]
612 fn is_decimal256(t: PyDataType) -> bool {
613 matches!(t.0, DataType::Decimal256(_, _))
614 }
615
616 #[staticmethod]
617 fn is_list(t: PyDataType) -> bool {
618 matches!(t.0, DataType::List(_))
619 }
620 #[staticmethod]
621 fn is_large_list(t: PyDataType) -> bool {
622 matches!(t.0, DataType::LargeList(_))
623 }
624 #[staticmethod]
625 fn is_fixed_size_list(t: PyDataType) -> bool {
626 matches!(t.0, DataType::FixedSizeList(_, _))
627 }
628 #[staticmethod]
629 fn is_list_view(t: PyDataType) -> bool {
630 matches!(t.0, DataType::ListView(_))
631 }
632 #[staticmethod]
633 fn is_large_list_view(t: PyDataType) -> bool {
634 matches!(t.0, DataType::LargeListView(_))
635 }
636 #[staticmethod]
637 fn is_struct(t: PyDataType) -> bool {
638 matches!(t.0, DataType::Struct(_))
639 }
640 #[staticmethod]
641 fn is_union(t: PyDataType) -> bool {
642 matches!(t.0, DataType::Union(_, _))
643 }
644 #[staticmethod]
645 fn is_nested(t: PyDataType) -> bool {
646 t.0.is_nested()
647 }
648 #[staticmethod]
649 fn is_run_end_encoded(t: PyDataType) -> bool {
650 t.0.is_run_ends_type()
651 }
652 #[staticmethod]
653 fn is_temporal(t: PyDataType) -> bool {
654 t.0.is_temporal()
655 }
656 #[staticmethod]
657 fn is_timestamp(t: PyDataType) -> bool {
658 matches!(t.0, DataType::Timestamp(_, _))
659 }
660 #[staticmethod]
661 fn is_date(t: PyDataType) -> bool {
662 matches!(t.0, DataType::Date32 | DataType::Date64)
663 }
664 #[staticmethod]
665 fn is_date32(t: PyDataType) -> bool {
666 t.0 == DataType::Date32
667 }
668 #[staticmethod]
669 fn is_date64(t: PyDataType) -> bool {
670 t.0 == DataType::Date64
671 }
672 #[staticmethod]
673 fn is_time(t: PyDataType) -> bool {
674 matches!(t.0, DataType::Time32(_) | DataType::Time64(_))
675 }
676 #[staticmethod]
677 fn is_time32(t: PyDataType) -> bool {
678 matches!(t.0, DataType::Time32(_))
679 }
680 #[staticmethod]
681 fn is_time64(t: PyDataType) -> bool {
682 matches!(t.0, DataType::Time64(_))
683 }
684 #[staticmethod]
685 fn is_duration(t: PyDataType) -> bool {
686 matches!(t.0, DataType::Duration(_))
687 }
688 #[staticmethod]
689 fn is_interval(t: PyDataType) -> bool {
690 matches!(t.0, DataType::Interval(_))
691 }
692 #[staticmethod]
693 fn is_null(t: PyDataType) -> bool {
694 t.0 == DataType::Null
695 }
696 #[staticmethod]
697 fn is_binary(t: PyDataType) -> bool {
698 t.0 == DataType::Binary
699 }
700 #[staticmethod]
701 fn is_unicode(t: PyDataType) -> bool {
702 t.0 == DataType::Utf8
703 }
704 #[staticmethod]
705 fn is_string(t: PyDataType) -> bool {
706 t.0 == DataType::Utf8
707 }
708 #[staticmethod]
709 fn is_large_binary(t: PyDataType) -> bool {
710 t.0 == DataType::LargeBinary
711 }
712 #[staticmethod]
713 fn is_large_unicode(t: PyDataType) -> bool {
714 t.0 == DataType::LargeUtf8
715 }
716 #[staticmethod]
717 fn is_large_string(t: PyDataType) -> bool {
718 t.0 == DataType::LargeUtf8
719 }
720 #[staticmethod]
721 fn is_binary_view(t: PyDataType) -> bool {
722 t.0 == DataType::BinaryView
723 }
724 #[staticmethod]
725 fn is_string_view(t: PyDataType) -> bool {
726 t.0 == DataType::Utf8View
727 }
728 #[staticmethod]
729 fn is_fixed_size_binary(t: PyDataType) -> bool {
730 matches!(t.0, DataType::FixedSizeBinary(_))
731 }
732 #[staticmethod]
733 fn is_map(t: PyDataType) -> bool {
734 matches!(t.0, DataType::Map(_, _))
735 }
736 #[staticmethod]
737 fn is_dictionary(t: PyDataType) -> bool {
738 matches!(t.0, DataType::Dictionary(_, _))
739 }
740 #[staticmethod]
741 fn is_primitive(t: PyDataType) -> bool {
742 t.0.is_primitive()
743 }
744 #[staticmethod]
745 fn is_numeric(t: PyDataType) -> bool {
746 t.0.is_numeric()
747 }
748 #[staticmethod]
749 fn is_dictionary_key_type(t: PyDataType) -> bool {
750 t.0.is_dictionary_key_type()
751 }
752}