in sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala [201:277]
def canCast(from: DataType, to: DataType): Boolean = (from, to) match {
case (fromType, toType) if fromType == toType => true
case (NullType, _) => true
case (_, _: StringType) => true
case (_: StringType, BinaryType) => true
case (_: IntegralType, BinaryType) => true
case (_: StringType, BooleanType) => true
case (DateType, BooleanType) => true
case (TimestampType, BooleanType) => true
case (_: NumericType, BooleanType) => true
case (_: StringType, TimestampType) => true
case (BooleanType, TimestampType) => true
case (DateType, TimestampType) => true
case (_: NumericType, TimestampType) => true
case (TimestampNTZType, TimestampType) => true
case (_: StringType, TimestampNTZType) => true
case (DateType, TimestampNTZType) => true
case (TimestampType, TimestampNTZType) => true
case (_: StringType, DateType) => true
case (_: StringType, _: TimeType) => true
case (TimestampType, DateType) => true
case (TimestampNTZType, DateType) => true
case (_: StringType, CalendarIntervalType) => true
case (_: StringType, _: DayTimeIntervalType) => true
case (_: StringType, _: YearMonthIntervalType) => true
case (_: IntegralType, DayTimeIntervalType(s, e)) if s == e => true
case (_: IntegralType, YearMonthIntervalType(s, e)) if s == e => true
case (_: DayTimeIntervalType, _: DayTimeIntervalType) => true
case (_: YearMonthIntervalType, _: YearMonthIntervalType) => true
case (_: AnsiIntervalType, _: IntegralType | _: DecimalType) => true
case (_: IntegralType | _: DecimalType, _: AnsiIntervalType) => true
case (_: StringType, _: NumericType) => true
case (BooleanType, _: NumericType) => true
case (DateType, _: NumericType) => true
case (TimestampType, _: NumericType) => true
case (_: NumericType, _: NumericType) => true
case (VariantType, _) => variant.VariantGet.checkDataType(to)
// Structs and Maps can't be cast to Variants since the Variant spec does not yet contain
// lossless equivalents for these types. The `to_variant_object` expression can be used instead
// to convert data of these types to Variant Objects.
case (_, VariantType) => variant.VariantGet.checkDataType(from, allowStructsAndMaps = false)
case (ArrayType(fromType, fn), ArrayType(toType, tn)) =>
canCast(fromType, toType) &&
resolvableNullability(fn || forceNullable(fromType, toType), tn)
case (MapType(fromKey, fromValue, fn), MapType(toKey, toValue, tn)) =>
canCast(fromKey, toKey) &&
(!forceNullable(fromKey, toKey)) &&
canCast(fromValue, toValue) &&
resolvableNullability(fn || forceNullable(fromValue, toValue), tn)
case (StructType(fromFields), StructType(toFields)) =>
fromFields.length == toFields.length &&
fromFields.zip(toFields).forall {
case (fromField, toField) =>
canCast(fromField.dataType, toField.dataType) &&
resolvableNullability(
fromField.nullable || forceNullable(fromField.dataType, toField.dataType),
toField.nullable)
}
case (udt1: UserDefinedType[_], udt2: UserDefinedType[_]) if udt2.acceptsType(udt1) => true
case _ => false
}