Path: blob/main/crates/polars-plan/src/plans/aexpr/schema.rs
6940 views
#[cfg(feature = "dtype-decimal")]1use polars_core::chunked_array::arithmetic::{2_get_decimal_scale_add_sub, _get_decimal_scale_div, _get_decimal_scale_mul,3};4use polars_utils::format_pl_smallstr;5use recursive::recursive;67use super::*;89fn validate_expr(node: Node, arena: &Arena<AExpr>, schema: &Schema) -> PolarsResult<()> {10let mut ctx = ToFieldContext {11schema,12arena,13validate: true,14};15arena.get(node).to_field_impl(&mut ctx).map(|_| ())16}1718struct ToFieldContext<'a> {19schema: &'a Schema,20arena: &'a Arena<AExpr>,21// Traverse all expressions to validate they are in the schema.22validate: bool,23}2425impl AExpr {26pub fn to_dtype(&self, schema: &Schema, arena: &Arena<AExpr>) -> PolarsResult<DataType> {27self.to_field(schema, arena).map(|f| f.dtype)28}2930/// Get Field result of the expression. The schema is the input data. The provided31/// context will be used to coerce the type into a List if needed, also known as auto-implode.32pub fn to_field_with_ctx(33&self,34schema: &Schema,35ctx: Context,36arena: &Arena<AExpr>,37) -> PolarsResult<Field> {38// Indicates whether we should auto-implode the result. This is initialized to true if we are39// in an aggregation context, so functions that return scalars should explicitly set this40// to false in `to_field_impl`.41let agg_list = matches!(ctx, Context::Aggregation);42let mut ctx = ToFieldContext {43schema,44arena,45validate: true,46};47let mut field = self.to_field_impl(&mut ctx)?;4849if agg_list {50if !self.is_scalar(arena) {51field.coerce(field.dtype().clone().implode());52}53}5455Ok(field)56}5758/// Get Field result of the expression. The schema is the input data. The result will59/// not be coerced (also known as auto-implode): this is the responsibility of the caller.60pub fn to_field(&self, schema: &Schema, arena: &Arena<AExpr>) -> PolarsResult<Field> {61let mut ctx = ToFieldContext {62schema,63arena,64validate: true,65};6667let field = self.to_field_impl(&mut ctx)?;6869Ok(field)70}7172/// Get Field result of the expression. The schema is the input data.73///74/// This is taken as `&mut bool` as for some expressions this is determined by the upper node75/// (e.g. `alias`, `cast`).76#[recursive]77pub fn to_field_impl(&self, ctx: &mut ToFieldContext) -> PolarsResult<Field> {78use AExpr::*;79use DataType::*;80match self {81Len => Ok(Field::new(PlSmallStr::from_static(LEN), IDX_DTYPE)),82Window {83function,84options,85partition_by,86order_by,87} => {88if ctx.validate {89for node in partition_by {90validate_expr(*node, ctx.arena, ctx.schema)?;91}92if let Some((node, _)) = order_by {93validate_expr(*node, ctx.arena, ctx.schema)?;94}95}9697let e = ctx.arena.get(*function);98let mut field = e.to_field_impl(ctx)?;99100let mut implicit_implode = false;101102implicit_implode |= matches!(options, WindowType::Over(WindowMapping::Join));103#[cfg(feature = "dynamic_group_by")]104{105implicit_implode |= matches!(options, WindowType::Rolling(_));106}107108if implicit_implode && !is_scalar_ae(*function, ctx.arena) {109field.dtype = field.dtype.implode();110}111112Ok(field)113},114Explode { expr, .. } => {115let field = ctx.arena.get(*expr).to_field_impl(ctx)?;116let field = match field.dtype() {117List(inner) => Field::new(field.name().clone(), *inner.clone()),118#[cfg(feature = "dtype-array")]119Array(inner, ..) => Field::new(field.name().clone(), *inner.clone()),120_ => field,121};122123Ok(field)124},125Column(name) => ctx126.schema127.get_field(name)128.ok_or_else(|| PolarsError::ColumnNotFound(name.to_string().into())),129Literal(sv) => Ok(match sv {130LiteralValue::Series(s) => s.field().into_owned(),131_ => Field::new(sv.output_column_name().clone(), sv.get_datatype()),132}),133BinaryExpr { left, right, op } => {134use DataType::*;135136let field = match op {137Operator::Lt138| Operator::Gt139| Operator::Eq140| Operator::NotEq141| Operator::LogicalAnd142| Operator::LtEq143| Operator::GtEq144| Operator::NotEqValidity145| Operator::EqValidity146| Operator::LogicalOr => {147let out_field;148let out_name = {149out_field = ctx.arena.get(*left).to_field_impl(ctx)?;150out_field.name()151};152Field::new(out_name.clone(), Boolean)153},154Operator::TrueDivide => get_truediv_field(*left, *right, ctx)?,155_ => get_arithmetic_field(*left, *right, *op, ctx)?,156};157158Ok(field)159},160Sort { expr, .. } => ctx.arena.get(*expr).to_field_impl(ctx),161Gather { expr, idx, .. } => {162if ctx.validate {163validate_expr(*idx, ctx.arena, ctx.schema)?164}165ctx.arena.get(*expr).to_field_impl(ctx)166},167SortBy { expr, .. } => ctx.arena.get(*expr).to_field_impl(ctx),168Filter { input, by } => {169if ctx.validate {170validate_expr(*by, ctx.arena, ctx.schema)?171}172ctx.arena.get(*input).to_field_impl(ctx)173},174Agg(agg) => {175use IRAggExpr::*;176match agg {177Max { input: expr, .. }178| Min { input: expr, .. }179| First(expr)180| Last(expr) => ctx.arena.get(*expr).to_field_impl(ctx),181Sum(expr) => {182let mut field = ctx.arena.get(*expr).to_field_impl(ctx)?;183let dt = match field.dtype() {184Boolean => Some(IDX_DTYPE),185UInt8 | Int8 | Int16 | UInt16 => Some(Int64),186_ => None,187};188if let Some(dt) = dt {189field.coerce(dt);190}191Ok(field)192},193Median(expr) => {194let mut field = ctx.arena.get(*expr).to_field_impl(ctx)?;195match field.dtype {196Date => field.coerce(Datetime(TimeUnit::Microseconds, None)),197_ => {198let field = [ctx.arena.get(*expr).to_field_impl(ctx)?];199let mapper = FieldsMapper::new(&field);200return mapper.moment_dtype();201},202}203Ok(field)204},205Mean(expr) => {206let mut field = ctx.arena.get(*expr).to_field_impl(ctx)?;207match field.dtype {208Date => field.coerce(Datetime(TimeUnit::Microseconds, None)),209_ => {210let field = [ctx.arena.get(*expr).to_field_impl(ctx)?];211let mapper = FieldsMapper::new(&field);212return mapper.moment_dtype();213},214}215Ok(field)216},217Implode(expr) => {218let mut field = ctx.arena.get(*expr).to_field_impl(ctx)?;219field.coerce(DataType::List(field.dtype().clone().into()));220Ok(field)221},222Std(expr, _) => {223let field = [ctx.arena.get(*expr).to_field_impl(ctx)?];224let mapper = FieldsMapper::new(&field);225mapper.moment_dtype()226},227Var(expr, _) => {228let field = [ctx.arena.get(*expr).to_field_impl(ctx)?];229let mapper = FieldsMapper::new(&field);230mapper.var_dtype()231},232NUnique(expr) => {233let mut field = ctx.arena.get(*expr).to_field_impl(ctx)?;234field.coerce(IDX_DTYPE);235Ok(field)236},237Count { input, .. } => {238let mut field = ctx.arena.get(*input).to_field_impl(ctx)?;239field.coerce(IDX_DTYPE);240Ok(field)241},242AggGroups(expr) => {243let mut field = ctx.arena.get(*expr).to_field_impl(ctx)?;244field.coerce(IDX_DTYPE.implode());245Ok(field)246},247Quantile { expr, .. } => {248let field = [ctx.arena.get(*expr).to_field_impl(ctx)?];249let mapper = FieldsMapper::new(&field);250mapper.map_numeric_to_float_dtype(true)251},252}253},254Cast { expr, dtype, .. } => {255let field = ctx.arena.get(*expr).to_field_impl(ctx)?;256Ok(Field::new(field.name().clone(), dtype.clone()))257},258Ternary { truthy, falsy, .. } => {259// During aggregation:260// left: col(foo): list<T> nesting: 1261// right; col(foo).first(): T nesting: 0262// col(foo) + col(foo).first() will have nesting 1 as we still maintain the groups list.263let mut truthy = ctx.arena.get(*truthy).to_field_impl(ctx)?;264let falsy = ctx.arena.get(*falsy).to_field_impl(ctx)?;265266let st = if let DataType::Null = *truthy.dtype() {267falsy.dtype().clone()268} else {269try_get_supertype(truthy.dtype(), falsy.dtype())?270};271272truthy.coerce(st);273Ok(truthy)274},275AnonymousFunction {276input,277function,278fmt_str,279..280} => {281let fields = func_args_to_fields(input, ctx)?;282polars_ensure!(!fields.is_empty(), ComputeError: "expression: '{}' didn't get any inputs", fmt_str);283let function = function.clone().materialize()?;284let out = function.get_field(ctx.schema, &fields)?;285Ok(out)286},287Eval {288expr,289evaluation,290variant,291} => {292let field = ctx.arena.get(*expr).to_field_impl(ctx)?;293294let element_dtype = variant.element_dtype(field.dtype())?;295let schema = Schema::from_iter([(PlSmallStr::EMPTY, element_dtype.clone())]);296297let mut ctx = ToFieldContext {298schema: &schema,299arena: ctx.arena,300validate: ctx.validate,301};302let mut output_field = ctx.arena.get(*evaluation).to_field_impl(&mut ctx)?;303output_field.dtype = output_field.dtype.materialize_unknown(false)?;304305output_field.dtype = match variant {306EvalVariant::List => DataType::List(Box::new(output_field.dtype)),307EvalVariant::Cumulative { .. } => output_field.dtype,308};309output_field.name = field.name;310311Ok(output_field)312},313Function {314function,315input,316options: _,317} => {318let fields = func_args_to_fields(input, ctx)?;319polars_ensure!(!fields.is_empty(), ComputeError: "expression: '{}' didn't get any inputs", function);320let out = function.get_field(ctx.schema, &fields)?;321322Ok(out)323},324Slice {325input,326offset,327length,328} => {329if ctx.validate {330validate_expr(*offset, ctx.arena, ctx.schema)?;331validate_expr(*length, ctx.arena, ctx.schema)?;332}333334ctx.arena.get(*input).to_field_impl(ctx)335},336}337}338339pub fn to_name(&self, expr_arena: &Arena<AExpr>) -> PlSmallStr {340use AExpr::*;341use IRAggExpr::*;342match self {343Len => crate::constants::get_len_name(),344Window {345function: expr,346options: _,347partition_by: _,348order_by: _,349}350| BinaryExpr { left: expr, .. }351| Explode { expr, .. }352| Sort { expr, .. }353| Gather { expr, .. }354| SortBy { expr, .. }355| Filter { input: expr, .. }356| Cast { expr, .. }357| Ternary { truthy: expr, .. }358| Eval { expr, .. }359| Slice { input: expr, .. }360| Agg(Max { input: expr, .. })361| Agg(Min { input: expr, .. })362| Agg(First(expr))363| Agg(Last(expr))364| Agg(Sum(expr))365| Agg(Median(expr))366| Agg(Mean(expr))367| Agg(Implode(expr))368| Agg(Std(expr, _))369| Agg(Var(expr, _))370| Agg(NUnique(expr))371| Agg(Count { input: expr, .. })372| Agg(AggGroups(expr))373| Agg(Quantile { expr, .. }) => expr_arena.get(*expr).to_name(expr_arena),374AnonymousFunction { input, fmt_str, .. } => {375if input.is_empty() {376fmt_str.as_ref().clone()377} else {378input[0].output_name().clone()379}380},381Function {382input, function, ..383} => match function.output_name().and_then(|v| v.into_inner()) {384Some(name) => name,385None if input.is_empty() => format_pl_smallstr!("{}", &function),386None => input[0].output_name().clone(),387},388Column(name) => name.clone(),389Literal(lv) => lv.output_column_name().clone(),390}391}392}393394fn func_args_to_fields(input: &[ExprIR], ctx: &mut ToFieldContext) -> PolarsResult<Vec<Field>> {395input396.iter()397.map(|e| {398ctx.arena.get(e.node()).to_field_impl(ctx).map(|mut field| {399field.name = e.output_name().clone();400field401})402})403.collect()404}405406#[allow(clippy::too_many_arguments)]407fn get_arithmetic_field(408left: Node,409right: Node,410op: Operator,411ctx: &mut ToFieldContext,412) -> PolarsResult<Field> {413use DataType::*;414let left_ae = ctx.arena.get(left);415let right_ae = ctx.arena.get(right);416417// don't traverse tree until strictly needed. Can have terrible performance.418// # 3210419420// take the left field as a whole.421// don't take dtype and name separate as that splits the tree every node422// leading to quadratic behavior. # 4736423//424// further right_type is only determined when needed.425let mut left_field = left_ae.to_field_impl(ctx)?;426427let super_type = match op {428Operator::Minus => {429let right_type = right_ae.to_field_impl(ctx)?.dtype;430match (&left_field.dtype, &right_type) {431#[cfg(feature = "dtype-struct")]432(Struct(_), Struct(_)) => {433return Ok(left_field);434},435// This matches the engine output. TODO: revisit pending resolution of GH issue #23797436#[cfg(feature = "dtype-struct")]437(Struct(_), r) if r.is_numeric() => {438return Ok(left_field);439},440(Duration(_), Datetime(_, _))441| (Datetime(_, _), Duration(_))442| (Duration(_), Date)443| (Date, Duration(_))444| (Duration(_), Time)445| (Time, Duration(_)) => try_get_supertype(left_field.dtype(), &right_type)?,446(Datetime(tu, _), Date) | (Date, Datetime(tu, _)) => Duration(*tu),447// T - T != T if T is a datetime / date448(Datetime(tul, _), Datetime(tur, _)) => Duration(get_time_units(tul, tur)),449(_, Datetime(_, _)) | (Datetime(_, _), _) => {450polars_bail!(InvalidOperation: "{} not allowed on {} and {}", op, left_field.dtype, right_type)451},452(Date, Date) => Duration(TimeUnit::Microseconds),453(_, Date) | (Date, _) => {454polars_bail!(InvalidOperation: "{} not allowed on {} and {}", op, left_field.dtype, right_type)455},456(Duration(tul), Duration(tur)) => Duration(get_time_units(tul, tur)),457(_, Duration(_)) | (Duration(_), _) => {458polars_bail!(InvalidOperation: "{} not allowed on {} and {}", op, left_field.dtype, right_type)459},460(Time, Time) => Duration(TimeUnit::Nanoseconds),461(_, Time) | (Time, _) => {462polars_bail!(InvalidOperation: "{} not allowed on {} and {}", op, left_field.dtype, right_type)463},464(l @ List(a), r @ List(b))465if ![a, b]466.into_iter()467.all(|x| x.is_supported_list_arithmetic_input()) =>468{469polars_bail!(470InvalidOperation:471"cannot {} two list columns with non-numeric inner types: (left: {}, right: {})",472"sub", l, r,473)474},475(list_dtype @ List(_), other_dtype) | (other_dtype, list_dtype @ List(_)) => {476// FIXME: This should not use `try_get_supertype()`! It should instead recursively use the enclosing match block.477// Otherwise we will silently permit addition operations between logical types (see above).478// This currently doesn't cause any problems because the list arithmetic implementation checks and raises errors479// if the leaf types aren't numeric, but it means we don't raise an error until execution and the DSL schema480// may be incorrect.481list_dtype.cast_leaf(try_get_supertype(482list_dtype.leaf_dtype(),483other_dtype.leaf_dtype(),484)?)485},486#[cfg(feature = "dtype-array")]487(list_dtype @ Array(..), other_dtype) | (other_dtype, list_dtype @ Array(..)) => {488list_dtype.cast_leaf(try_get_supertype(489list_dtype.leaf_dtype(),490other_dtype.leaf_dtype(),491)?)492},493#[cfg(feature = "dtype-decimal")]494(Decimal(_, Some(scale_left)), Decimal(_, Some(scale_right))) => {495let scale = _get_decimal_scale_add_sub(*scale_left, *scale_right);496Decimal(None, Some(scale))497},498(left, right) => try_get_supertype(left, right)?,499}500},501Operator::Plus => {502let right_type = right_ae.to_field_impl(ctx)?.dtype;503match (&left_field.dtype, &right_type) {504#[cfg(feature = "dtype-struct")]505(Struct(_), Struct(_)) => {506return Ok(left_field);507},508// This matches the engine output. TODO: revisit pending resolution of GH issue #23797509#[cfg(feature = "dtype-struct")]510(Struct(_), r) if r.is_numeric() => {511return Ok(left_field);512},513(Duration(_), Datetime(_, _))514| (Datetime(_, _), Duration(_))515| (Duration(_), Date)516| (Date, Duration(_))517| (Duration(_), Time)518| (Time, Duration(_)) => try_get_supertype(left_field.dtype(), &right_type)?,519(_, Datetime(_, _))520| (Datetime(_, _), _)521| (_, Date)522| (Date, _)523| (Time, _)524| (_, Time) => {525polars_bail!(InvalidOperation: "{} not allowed on {} and {}", op, left_field.dtype, right_type)526},527(Duration(tul), Duration(tur)) => Duration(get_time_units(tul, tur)),528(_, Duration(_)) | (Duration(_), _) => {529polars_bail!(InvalidOperation: "{} not allowed on {} and {}", op, left_field.dtype, right_type)530},531(Boolean, Boolean) => IDX_DTYPE,532(l @ List(a), r @ List(b))533if ![a, b]534.into_iter()535.all(|x| x.is_supported_list_arithmetic_input()) =>536{537polars_bail!(538InvalidOperation:539"cannot {} two list columns with non-numeric inner types: (left: {}, right: {})",540"add", l, r,541)542},543(list_dtype @ List(_), other_dtype) | (other_dtype, list_dtype @ List(_)) => {544list_dtype.cast_leaf(try_get_supertype(545list_dtype.leaf_dtype(),546other_dtype.leaf_dtype(),547)?)548},549#[cfg(feature = "dtype-array")]550(list_dtype @ Array(..), other_dtype) | (other_dtype, list_dtype @ Array(..)) => {551list_dtype.cast_leaf(try_get_supertype(552list_dtype.leaf_dtype(),553other_dtype.leaf_dtype(),554)?)555},556#[cfg(feature = "dtype-decimal")]557(Decimal(_, Some(scale_left)), Decimal(_, Some(scale_right))) => {558let scale = _get_decimal_scale_add_sub(*scale_left, *scale_right);559Decimal(None, Some(scale))560},561(left, right) => try_get_supertype(left, right)?,562}563},564_ => {565let right_type = right_ae.to_field_impl(ctx)?.dtype;566567match (&left_field.dtype, &right_type) {568#[cfg(feature = "dtype-struct")]569(Struct(_), Struct(_)) => {570return Ok(left_field);571},572// This matches the engine output. TODO: revisit pending resolution of GH issue #23797573#[cfg(feature = "dtype-struct")]574(Struct(_), r) if r.is_numeric() => {575return Ok(left_field);576},577(Datetime(_, _), _)578| (_, Datetime(_, _))579| (Time, _)580| (_, Time)581| (Date, _)582| (_, Date) => {583polars_bail!(InvalidOperation: "{} not allowed on {} and {}", op, left_field.dtype, right_type)584},585(Duration(_), Duration(_)) => {586// True divide handled somewhere else587polars_bail!(InvalidOperation: "{} not allowed on {} and {}", op, left_field.dtype, right_type)588},589(l, Duration(_)) if l.is_primitive_numeric() => match op {590Operator::Multiply => {591left_field.coerce(right_type);592return Ok(left_field);593},594_ => {595polars_bail!(InvalidOperation: "{} not allowed on {} and {}", op, left_field.dtype, right_type)596},597},598(Duration(_), r) if r.is_primitive_numeric() => match op {599Operator::Multiply => {600return Ok(left_field);601},602_ => {603polars_bail!(InvalidOperation: "{} not allowed on {} and {}", op, left_field.dtype, right_type)604},605},606#[cfg(feature = "dtype-decimal")]607(Decimal(_, Some(scale_left)), Decimal(_, Some(scale_right))) => {608let scale = match op {609Operator::Multiply => _get_decimal_scale_mul(*scale_left, *scale_right),610Operator::Divide | Operator::TrueDivide => {611_get_decimal_scale_div(*scale_left)612},613_ => {614debug_assert!(false);615*scale_left616},617};618let dtype = Decimal(None, Some(scale));619left_field.coerce(dtype);620return Ok(left_field);621},622623(l @ List(a), r @ List(b))624if ![a, b]625.into_iter()626.all(|x| x.is_supported_list_arithmetic_input()) =>627{628polars_bail!(629InvalidOperation:630"cannot {} two list columns with non-numeric inner types: (left: {}, right: {})",631op, l, r,632)633},634// List<->primitive operations can be done directly after casting the to the primitive635// supertype for the primitive values on both sides.636(list_dtype @ List(_), other_dtype) | (other_dtype, list_dtype @ List(_)) => {637let dtype = list_dtype.cast_leaf(try_get_supertype(638list_dtype.leaf_dtype(),639other_dtype.leaf_dtype(),640)?);641left_field.coerce(dtype);642return Ok(left_field);643},644#[cfg(feature = "dtype-array")]645(list_dtype @ Array(..), other_dtype) | (other_dtype, list_dtype @ Array(..)) => {646let dtype = list_dtype.cast_leaf(try_get_supertype(647list_dtype.leaf_dtype(),648other_dtype.leaf_dtype(),649)?);650left_field.coerce(dtype);651return Ok(left_field);652},653_ => {654// Avoid needlessly type casting numeric columns during arithmetic655// with literals.656if (left_field.dtype.is_integer() && right_type.is_integer())657|| (left_field.dtype.is_float() && right_type.is_float())658{659match (left_ae, right_ae) {660(AExpr::Literal(_), AExpr::Literal(_)) => {},661(AExpr::Literal(_), _) => {662// literal will be coerced to match right type663left_field.coerce(right_type);664return Ok(left_field);665},666(_, AExpr::Literal(_)) => {667// literal will be coerced to match right type668return Ok(left_field);669},670_ => {},671}672}673},674}675676try_get_supertype(&left_field.dtype, &right_type)?677},678};679680left_field.coerce(super_type);681Ok(left_field)682}683684fn get_truediv_field(left: Node, right: Node, ctx: &mut ToFieldContext) -> PolarsResult<Field> {685let mut left_field = ctx.arena.get(left).to_field_impl(ctx)?;686let right_field = ctx.arena.get(right).to_field_impl(ctx)?;687let out_type = get_truediv_dtype(left_field.dtype(), right_field.dtype())?;688left_field.coerce(out_type);689Ok(left_field)690}691692fn get_truediv_dtype(left_dtype: &DataType, right_dtype: &DataType) -> PolarsResult<DataType> {693use DataType::*;694695// TODO: Re-investigate this. A lot of "_" is being used on the RHS match because this code696// originally (mostly) only looked at the LHS dtype.697let out_type = match (left_dtype, right_dtype) {698#[cfg(feature = "dtype-struct")]699(Struct(a), Struct(b)) => {700polars_ensure!(a.len() == b.len() || b.len() == 1,701InvalidOperation: "cannot {} two structs of different length (left: {}, right: {})",702"div", a.len(), b.len()703);704let mut fields = Vec::with_capacity(a.len());705// In case b.len() == 1, we broadcast the first field (b[0]).706// Safety is assured by the constraints above.707let b_iter = (0..a.len()).map(|i| b.get(i.min(b.len() - 1)).unwrap());708for (left, right) in a.iter().zip(b_iter) {709let name = left.name.clone();710let (left, right) = (left.dtype(), right.dtype());711if !(left.is_numeric() && right.is_numeric()) {712polars_bail!(InvalidOperation:713"cannot {} two structs with non-numeric fields: (left: {}, right: {})",714"div", left, right,)715};716let field = Field::new(name, get_truediv_dtype(left, right)?);717fields.push(field);718}719Struct(fields)720},721#[cfg(feature = "dtype-struct")]722(Struct(a), n) if n.is_numeric() => {723let mut fields = Vec::with_capacity(a.len());724for left in a.iter() {725let name = left.name.clone();726let left = left.dtype();727if !(left.is_numeric()) {728polars_bail!(InvalidOperation:729"cannot {} a struct with non-numeric field: (left: {})",730"div", left)731};732let field = Field::new(name, get_truediv_dtype(left, n)?);733fields.push(field);734}735Struct(fields)736},737(l @ List(a), r @ List(b))738if ![a, b]739.into_iter()740.all(|x| x.is_supported_list_arithmetic_input()) =>741{742polars_bail!(743InvalidOperation:744"cannot {} two list columns with non-numeric inner types: (left: {}, right: {})",745"div", l, r,746)747},748(list_dtype @ List(_), other_dtype) | (other_dtype, list_dtype @ List(_)) => {749let dtype = get_truediv_dtype(list_dtype.leaf_dtype(), other_dtype.leaf_dtype())?;750list_dtype.cast_leaf(dtype)751},752#[cfg(feature = "dtype-array")]753(list_dtype @ Array(..), other_dtype) | (other_dtype, list_dtype @ Array(..)) => {754let dtype = get_truediv_dtype(list_dtype.leaf_dtype(), other_dtype.leaf_dtype())?;755list_dtype.cast_leaf(dtype)756},757(Boolean, Float32) => Float32,758(Boolean, b) if b.is_numeric() => Float64,759(Boolean, Boolean) => Float64,760#[cfg(feature = "dtype-u8")]761(Float32, UInt8 | Int8) => Float32,762#[cfg(feature = "dtype-u16")]763(Float32, UInt16 | Int16) => Float32,764(Float32, other) if other.is_integer() => Float64,765(Float32, Float64) => Float64,766(Float32, _) => Float32,767(String, _) | (_, String) => polars_bail!(768InvalidOperation: "division with 'String' datatypes is not allowed"769),770#[cfg(feature = "dtype-decimal")]771(Decimal(_, Some(scale_left)), Decimal(_, _)) => {772let scale = _get_decimal_scale_div(*scale_left);773Decimal(None, Some(scale))774},775#[cfg(feature = "dtype-u8")]776(UInt8 | Int8, Float32) => Float32,777#[cfg(feature = "dtype-u16")]778(UInt16 | Int16, Float32) => Float32,779(dt, _) if dt.is_primitive_numeric() => Float64,780#[cfg(feature = "dtype-duration")]781(Duration(_), Duration(_)) => Float64,782#[cfg(feature = "dtype-duration")]783(Duration(_), dt) if dt.is_primitive_numeric() => left_dtype.clone(),784#[cfg(feature = "dtype-duration")]785(Duration(_), dt) => {786polars_bail!(InvalidOperation: "true division of {} with {} is not allowed", left_dtype, dt)787},788#[cfg(feature = "dtype-datetime")]789(Datetime(_, _), _) => {790polars_bail!(InvalidOperation: "division of 'Datetime' datatype is not allowed")791},792#[cfg(feature = "dtype-time")]793(Time, _) => polars_bail!(InvalidOperation: "division of 'Time' datatype is not allowed"),794#[cfg(feature = "dtype-date")]795(Date, _) => polars_bail!(InvalidOperation: "division of 'Date' datatype is not allowed"),796// we don't know what to do here, best return the dtype797(dt, _) => dt.clone(),798};799Ok(out_type)800}801802803