///|
/// Logical data type for a column.
///
/// `Null` is the dtype of a *missing value*, not of a column: it is what
/// `Scalar::Null.dtype()` reports, and so what a `TypeMismatch`'s "expected T,
/// got Null" names when a null cell reaches a typed read. A hand-built `Schema`
/// may also carry it, but nothing materialises a column on it —
/// `DataFrame::empty` raises `Unsupported` for such a field, `cast` refuses it
/// as a target, and **no reader infers it**: an all-null probe window falls back
/// to `String`.
/// Concrete columns always carry one of the other variants.
///
/// A logical dtype is more than its variant: its display name, the physical
/// buffer its columns ride, and its numericity are registered in
/// the dtype registry, whose per-dtype rows are the compile-forced
/// registration point for a dtype added here.
pub(all) enum DataType {
Int
Float
Bool
String
/// A calendar date, stored as whole days since the Unix epoch
/// (`1970-01-01` = day 0, negative for earlier dates) — an `I64` physical
/// buffer with date semantics. Not numeric: arithmetic on days is a
/// `TypeMismatch`, comparisons / ordering / grouping are date-wise. Readers
/// never infer it (an ISO-shaped CSV column arrives as `String`).
Date
Null
} derive(Eq, Debug)
///|
pub extend DataType with Eq::{equal, not_equal}
///|
pub extend DataType with Show::{to_string, output}
///|
pub extend DataType with Debug::{to_repr}
///|
/// `true` for `Int` and `Float` — both are usable in numeric reductions.
/// Reads the registry's `numeric` flag, so an `I64`-riding dtype like `Date`
/// is correctly *not* numeric.
pub fn DataType::is_numeric(self : DataType) -> Bool {
self.dtype_profile().numeric
}
///|
/// Whether a pair of dtypes reaches a comparison kernel's value arms: the same
/// dtype, or two numeric dtypes (`Int` vs `Float` compares exactly). This is
/// the column-level compare gate — it keeps an `I64`-riding dtype like `Date`
/// from comparing against a plain `Int`, whose cells are indistinguishable at
/// the physical level but unrelated logically. A derived rule, not a
/// registered one: same dtype or two `is_numeric` dtypes, with no per-dtype
/// arm of its own.
#doc(hidden)
#internal(engine, "MoonFrame execution engine API")
pub fn DataType::comparable_with(self : DataType, other : DataType) -> Bool {
self == other || (self.is_numeric() && other.is_numeric())
}
///|
/// The `Show` spelling renders the registry's display name — the name is
/// registered per dtype, not matched here.
pub impl Show for DataType with fn output(self, logger) {
logger.write_string(self.dtype_profile().display)
}