use std::collections::HashSet;
use std::fmt;
use std::sync::Arc;
use indexmap::IndexMap;
use wdl_ast::Diagnostic;
use wdl_ast::Span;
use crate::diagnostics::enum_variant_does_not_coerce_to_type;
use crate::diagnostics::no_common_inferred_type_for_enum;
use crate::document::Input;
use crate::document::Output;
pub mod v1;
pub fn display_types(slice: &[Type]) -> impl fmt::Display + use<'_> {
struct Display<'a>(&'a [Type]);
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, ty) in self.0.iter().enumerate() {
if i > 0 {
if self.0.len() == 2 {
write!(f, " ")?;
} else {
write!(f, ", ")?;
}
if i == self.0.len() - 1 {
write!(f, "or ")?;
}
}
write!(f, "{ty:#}")?;
}
Ok(())
}
}
Display(slice)
}
pub trait TypeNameResolver {
fn resolve(&mut self, name: &str, span: Span) -> Result<Type, Diagnostic>;
}
pub trait Optional {
fn is_optional(&self) -> bool;
fn optional(&self) -> Self;
fn require(&self) -> Self;
}
pub trait Coercible {
fn is_coercible_to(&self, target: &Self) -> bool;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PrimitiveType {
Boolean,
Integer,
Float,
String,
File,
Directory,
}
impl Coercible for PrimitiveType {
fn is_coercible_to(&self, target: &Self) -> bool {
if self == target {
return true;
}
match (self, target) {
(Self::String, Self::File) |
(Self::String, Self::Directory) |
(Self::Integer, Self::Float) |
(Self::File, Self::String) |
(Self::Directory, Self::String)
=> true,
_ => false
}
}
}
impl fmt::Display for PrimitiveType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Boolean => write!(f, "Boolean")?,
Self::Integer => write!(f, "Int")?,
Self::Float => write!(f, "Float")?,
Self::String => write!(f, "String")?,
Self::File => write!(f, "File")?,
Self::Directory => write!(f, "Directory")?,
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HiddenType {
Hints,
Input,
Output,
TaskPreEvaluation,
TaskPostEvaluation,
PreviousTaskData,
}
impl fmt::Display for HiddenType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Hints => write!(f, "hints"),
Self::Input => write!(f, "input"),
Self::Output => write!(f, "output"),
Self::TaskPreEvaluation | Self::TaskPostEvaluation => write!(f, "task"),
Self::PreviousTaskData => write!(f, "task.previous"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
Primitive(PrimitiveType, bool),
Compound(CompoundType, bool),
Object,
OptionalObject,
Union,
None,
Hidden(HiddenType),
Call(CallType),
TypeNameRef(CustomType),
}
const _: () = {
assert!(std::mem::size_of::<Type>() <= 24);
};
impl Type {
pub fn as_primitive(&self) -> Option<PrimitiveType> {
match self {
Self::Primitive(ty, _) => Some(*ty),
_ => None,
}
}
pub fn as_compound(&self) -> Option<&CompoundType> {
match self {
Self::Compound(ty, _) => Some(ty),
_ => None,
}
}
pub fn as_array(&self) -> Option<&ArrayType> {
match self {
Self::Compound(ty, _) => ty.as_array(),
_ => None,
}
}
pub fn as_pair(&self) -> Option<&PairType> {
match self {
Self::Compound(ty, _) => ty.as_pair(),
_ => None,
}
}
pub fn as_map(&self) -> Option<&MapType> {
match self {
Self::Compound(ty, _) => ty.as_map(),
_ => None,
}
}
pub fn as_struct(&self) -> Option<&StructType> {
match self {
Self::Compound(ty, _) => ty.as_struct(),
_ => None,
}
}
pub fn as_enum(&self) -> Option<&EnumType> {
match self {
Self::Compound(ty, _) => ty.as_enum(),
_ => None,
}
}
pub fn as_custom(&self) -> Option<&CustomType> {
match self {
Self::Compound(ty, _) => ty.as_custom(),
_ => None,
}
}
pub fn as_type_name_ref(&self) -> Option<&CustomType> {
match self {
Self::TypeNameRef(custom_ty) => Some(custom_ty),
_ => None,
}
}
pub fn as_call(&self) -> Option<&CallType> {
match self {
Self::Call(ty) => Some(ty),
_ => None,
}
}
pub fn is_union(&self) -> bool {
matches!(self, Type::Union)
}
pub fn is_none(&self) -> bool {
matches!(self, Type::None)
}
pub fn promote_scatter(&self) -> Self {
if let Self::Call(ty) = self {
return Self::Call(ty.promote_scatter());
}
Type::Compound(ArrayType::new(self.clone()).into(), false)
}
pub fn common_type(&self, other: &Type) -> Option<Type> {
if other.is_union() {
return Some(self.clone());
}
if self.is_union() {
return Some(other.clone());
}
if other.is_none() {
return Some(self.optional());
}
if self.is_none() {
return Some(other.optional());
}
if other.is_coercible_to(self) {
return Some(self.clone());
}
if self.is_coercible_to(other) {
return Some(other.clone());
}
if let (Some(this), Some(other)) = (self.as_compound(), other.as_compound())
&& let Some(ty) = this.common_type(other)
{
return Some(Self::Compound(ty, self.is_optional()));
}
if let (Some(this), Some(other)) = (self.as_call(), self.as_call())
&& this == other
{
return Some(Self::Call(this.clone()));
}
None
}
pub fn type_name_ref(&self) -> Option<Type> {
match self {
Type::Compound(CompoundType::Custom(ty), _) => Some(Type::TypeNameRef(ty.clone())),
_ => None,
}
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Primitive(ty, optional) => {
write!(
f,
"{prefix}{ty}{opt}{suffix}",
prefix = if f.alternate() { "type `" } else { "" },
opt = if *optional { "?" } else { "" },
suffix = if f.alternate() { "`" } else { "" },
)
}
Self::Compound(CompoundType::Custom(CustomType::Struct(ty)), optional) => {
write!(
f,
"{prefix}{ty}{opt}{suffix}",
prefix = if f.alternate() {
"an instance of struct `"
} else {
""
},
opt = if *optional { "?" } else { "" },
suffix = if f.alternate() { "`" } else { "" },
)
}
Self::Compound(CompoundType::Custom(CustomType::Enum(ty)), optional) => {
write!(
f,
"{prefix}{ty}{opt}{suffix}",
prefix = if f.alternate() {
"an instance of enum `"
} else {
""
},
opt = if *optional { "?" } else { "" },
suffix = if f.alternate() { "`" } else { "" },
)
}
Self::Compound(ty, optional) => {
write!(
f,
"{prefix}{ty}{opt}{suffix}",
prefix = if f.alternate() { "type `" } else { "" },
opt = if *optional { "?" } else { "" },
suffix = if f.alternate() { "`" } else { "" },
)
}
Self::Object => {
write!(
f,
"{prefix}Object{suffix}",
prefix = if f.alternate() { "type `" } else { "" },
suffix = if f.alternate() { "`" } else { "" },
)
}
Self::OptionalObject => {
write!(
f,
"{prefix}Object?{suffix}",
prefix = if f.alternate() { "type `" } else { "" },
suffix = if f.alternate() { "`" } else { "" },
)
}
Self::Union => {
write!(
f,
"{prefix}Union{suffix}",
prefix = if f.alternate() { "built-in type `" } else { "" },
suffix = if f.alternate() { "`" } else { "" },
)
}
Self::None => {
write!(
f,
"{prefix}None{suffix}",
prefix = if f.alternate() { "built-in type `" } else { "" },
suffix = if f.alternate() { "`" } else { "" },
)
}
Self::Hidden(ty) => {
write!(
f,
"{prefix}{ty}{suffix}",
prefix = if f.alternate() { "built-in type `" } else { "" },
suffix = if f.alternate() { "`" } else { "" },
)
}
Self::Call(ty) => ty.fmt(f),
Self::TypeNameRef(ty) => {
write!(
f,
"{prefix}{ty}{suffix}",
prefix = if f.alternate() { "type name `" } else { "" },
suffix = if f.alternate() { "`" } else { "" },
)
}
}
}
}
impl Optional for Type {
fn is_optional(&self) -> bool {
match self {
Self::Primitive(_, optional) => *optional,
Self::Compound(_, optional) => *optional,
Self::OptionalObject | Self::None => true,
Self::Object | Self::Union | Self::Hidden(_) | Self::Call(_) | Self::TypeNameRef(_) => {
false
}
}
}
fn optional(&self) -> Self {
match self {
Self::Primitive(ty, _) => Self::Primitive(*ty, true),
Self::Compound(ty, _) => Self::Compound(ty.clone(), true),
Self::Object => Self::OptionalObject,
Self::Union => Self::None,
Self::Call(ty) => Self::Call(ty.optional()),
ty => ty.clone(),
}
}
fn require(&self) -> Self {
match self {
Self::Primitive(ty, _) => Self::Primitive(*ty, false),
Self::Compound(ty, _) => Self::Compound(ty.clone(), false),
Self::OptionalObject => Self::Object,
Self::None => Self::Union,
ty => ty.clone(),
}
}
}
impl Coercible for Type {
fn is_coercible_to(&self, target: &Self) -> bool {
if self.eq(target) {
return true;
}
match (self, target) {
(Self::Primitive(src, src_opt), Self::Primitive(target, target_opt)) => {
if *src_opt && !*target_opt {
return false;
}
src.is_coercible_to(target)
}
(Self::Compound(src, src_opt), Self::Compound(target, target_opt)) => {
if *src_opt && !*target_opt {
return false;
}
src.is_coercible_to(target)
}
(Self::Object, Self::Object)
| (Self::Object, Self::OptionalObject)
| (Self::OptionalObject, Self::OptionalObject) => true,
(Self::Compound(src, false), Self::Object)
| (Self::Compound(src, false), Self::OptionalObject)
| (Self::Compound(src, _), Self::OptionalObject) => match src {
CompoundType::Map(src) => src
.key_type()
.is_coercible_to(&PrimitiveType::String.into()),
CompoundType::Custom(CustomType::Struct(_)) => true,
_ => false,
},
(Self::Object, Self::Compound(target, _))
| (Self::OptionalObject, Self::Compound(target, true)) => {
match target {
CompoundType::Map(target) => {
Type::from(PrimitiveType::String).is_coercible_to(target.key_type())
}
CompoundType::Custom(CustomType::Struct(_)) => {
true
}
_ => false,
}
}
(Self::Union, _) | (_, Self::Union) => true,
(Self::None, ty) if ty.is_optional() => true,
(
Self::Primitive(PrimitiveType::String, _),
Self::Compound(CompoundType::Custom(CustomType::Enum(_)), _),
)
| (
Self::Compound(CompoundType::Custom(CustomType::Enum(_)), _),
Self::Primitive(PrimitiveType::String, _),
) => true,
_ => false,
}
}
}
impl From<PrimitiveType> for Type {
fn from(value: PrimitiveType) -> Self {
Self::Primitive(value, false)
}
}
impl From<CompoundType> for Type {
fn from(value: CompoundType) -> Self {
Self::Compound(value, false)
}
}
impl From<ArrayType> for Type {
fn from(value: ArrayType) -> Self {
Self::Compound(value.into(), false)
}
}
impl From<PairType> for Type {
fn from(value: PairType) -> Self {
Self::Compound(value.into(), false)
}
}
impl From<MapType> for Type {
fn from(value: MapType) -> Self {
Self::Compound(value.into(), false)
}
}
impl From<StructType> for Type {
fn from(value: StructType) -> Self {
Self::Compound(value.into(), false)
}
}
impl From<EnumType> for Type {
fn from(value: EnumType) -> Self {
Self::Compound(value.into(), false)
}
}
impl From<CallType> for Type {
fn from(value: CallType) -> Self {
Self::Call(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CustomType {
Struct(StructType),
Enum(EnumType),
}
impl CustomType {
pub fn name(&self) -> &str {
match self {
Self::Struct(ty) => ty.name(),
Self::Enum(ty) => ty.name(),
}
}
pub fn as_struct(&self) -> Option<&StructType> {
match self {
Self::Struct(ty) => Some(ty),
_ => None,
}
}
pub fn as_enum(&self) -> Option<&EnumType> {
match self {
Self::Enum(ty) => Some(ty),
_ => None,
}
}
}
impl std::fmt::Display for CustomType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CustomType::Struct(ty) => ty.fmt(f),
CustomType::Enum(ty) => ty.fmt(f),
}
}
}
impl From<StructType> for CustomType {
fn from(value: StructType) -> Self {
Self::Struct(value)
}
}
impl From<EnumType> for CustomType {
fn from(value: EnumType) -> Self {
Self::Enum(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompoundType {
Array(ArrayType),
Pair(PairType),
Map(MapType),
Custom(CustomType),
}
impl CompoundType {
pub fn as_array(&self) -> Option<&ArrayType> {
match self {
Self::Array(ty) => Some(ty),
_ => None,
}
}
pub fn as_pair(&self) -> Option<&PairType> {
match self {
Self::Pair(ty) => Some(ty),
_ => None,
}
}
pub fn as_map(&self) -> Option<&MapType> {
match self {
Self::Map(ty) => Some(ty),
_ => None,
}
}
pub fn as_struct(&self) -> Option<&StructType> {
match self {
Self::Custom(ty) => ty.as_struct(),
_ => None,
}
}
pub fn as_enum(&self) -> Option<&EnumType> {
match self {
Self::Custom(ty) => ty.as_enum(),
_ => None,
}
}
pub fn as_custom(&self) -> Option<&CustomType> {
match self {
Self::Custom(ty) => Some(ty),
_ => None,
}
}
fn common_type(&self, other: &Self) -> Option<CompoundType> {
match (self, other) {
(Self::Array(this), Self::Array(other)) => {
let element_type = this.element_type().common_type(other.element_type())?;
Some(ArrayType::new(element_type).into())
}
(Self::Pair(this), Self::Pair(other)) => {
let left_type = this.left_type().common_type(other.left_type())?;
let right_type = this.right_type().common_type(other.right_type())?;
Some(PairType::new(left_type, right_type).into())
}
(Self::Map(this), Self::Map(other)) => {
let key_type = this.key_type().common_type(other.key_type())?;
let value_type = this.value_type().common_type(other.value_type())?;
Some(MapType::new(key_type, value_type).into())
}
_ => None,
}
}
}
impl fmt::Display for CompoundType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Array(ty) => ty.fmt(f),
Self::Pair(ty) => ty.fmt(f),
Self::Map(ty) => ty.fmt(f),
Self::Custom(CustomType::Struct(ty)) => ty.fmt(f),
Self::Custom(CustomType::Enum(ty)) => ty.fmt(f),
}
}
}
impl Coercible for CompoundType {
fn is_coercible_to(&self, target: &Self) -> bool {
match (self, target) {
(Self::Array(src), Self::Array(target)) => src.is_coercible_to(target),
(Self::Pair(src), Self::Pair(target)) => src.is_coercible_to(target),
(Self::Map(src), Self::Map(target)) => src.is_coercible_to(target),
(Self::Custom(CustomType::Struct(src)), Self::Custom(CustomType::Struct(target))) => {
src.is_coercible_to(target)
}
(Self::Custom(CustomType::Enum(src)), Self::Custom(CustomType::Enum(target))) => {
src.is_coercible_to(target)
}
(Self::Map(src), Self::Custom(CustomType::Struct(target))) => {
if !src
.key_type()
.is_coercible_to(&PrimitiveType::String.into())
{
return false;
}
if !target
.members()
.values()
.all(|ty| src.value_type().is_coercible_to(ty))
{
return false;
}
true
}
(Self::Custom(CustomType::Struct(src)), Self::Map(target)) => {
if !Type::from(PrimitiveType::String).is_coercible_to(target.key_type()) {
return false;
}
if !src
.members()
.values()
.all(|ty| ty.is_coercible_to(target.value_type()))
{
return false;
}
true
}
_ => false,
}
}
}
impl From<ArrayType> for CompoundType {
fn from(value: ArrayType) -> Self {
Self::Array(value)
}
}
impl From<PairType> for CompoundType {
fn from(value: PairType) -> Self {
Self::Pair(value)
}
}
impl From<MapType> for CompoundType {
fn from(value: MapType) -> Self {
Self::Map(value)
}
}
impl From<StructType> for CompoundType {
fn from(value: StructType) -> Self {
Self::Custom(CustomType::Struct(value))
}
}
impl From<EnumType> for CompoundType {
fn from(value: EnumType) -> Self {
Self::Custom(CustomType::Enum(value))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ArrayTypeInner {
element_type: Type,
non_empty: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ArrayType(Arc<ArrayTypeInner>);
impl ArrayType {
pub fn new(element_type: impl Into<Type>) -> Self {
Self(Arc::new(ArrayTypeInner {
element_type: element_type.into(),
non_empty: false,
}))
}
pub fn non_empty(element_type: impl Into<Type>) -> Self {
Self(Arc::new(ArrayTypeInner {
element_type: element_type.into(),
non_empty: true,
}))
}
pub fn element_type(&self) -> &Type {
&self.0.element_type
}
pub fn is_non_empty(&self) -> bool {
self.0.non_empty
}
pub fn unqualified(&self) -> ArrayType {
if self.0.non_empty {
Self(Arc::new(ArrayTypeInner {
element_type: self.0.element_type.clone(),
non_empty: false,
}))
} else {
self.clone()
}
}
}
impl fmt::Display for ArrayType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Array[{ty}]", ty = self.0.element_type)?;
if self.0.non_empty {
write!(f, "+")?;
}
Ok(())
}
}
impl Coercible for ArrayType {
fn is_coercible_to(&self, target: &Self) -> bool {
self.0.element_type.is_coercible_to(&target.0.element_type)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PairType(Arc<(Type, Type)>);
impl PairType {
pub fn new(left_type: impl Into<Type>, right_type: impl Into<Type>) -> Self {
Self(Arc::new((left_type.into(), right_type.into())))
}
pub fn left_type(&self) -> &Type {
&self.0.0
}
pub fn right_type(&self) -> &Type {
&self.0.1
}
}
impl fmt::Display for PairType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Pair[{left}, {right}]",
left = self.left_type(),
right = self.right_type()
)?;
Ok(())
}
}
impl Coercible for PairType {
fn is_coercible_to(&self, target: &Self) -> bool {
self.left_type().is_coercible_to(target.left_type())
&& self.right_type().is_coercible_to(target.right_type())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MapType(Arc<(Type, Type)>);
impl MapType {
pub fn new(key_type: impl Into<Type>, value_type: impl Into<Type>) -> Self {
let key_type = key_type.into();
assert!(
key_type.is_union() || matches!(key_type, Type::Primitive(_, false)),
"map key {key_type:#} is not a non-optional primitive"
);
Self(Arc::new((key_type, value_type.into())))
}
pub fn key_type(&self) -> &Type {
&self.0.0
}
pub fn value_type(&self) -> &Type {
&self.0.1
}
}
impl fmt::Display for MapType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Map[{key}, {value}]",
key = self.key_type(),
value = self.value_type()
)?;
Ok(())
}
}
impl Coercible for MapType {
fn is_coercible_to(&self, target: &Self) -> bool {
self.key_type().is_coercible_to(target.key_type())
&& self.value_type().is_coercible_to(target.value_type())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct StructTypeInner {
name: Arc<String>,
members: IndexMap<String, Type>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StructType(Arc<StructTypeInner>);
impl StructType {
pub fn new<N, T>(name: impl Into<String>, members: impl IntoIterator<Item = (N, T)>) -> Self
where
N: Into<String>,
T: Into<Type>,
{
Self(Arc::new(StructTypeInner {
name: Arc::new(name.into()),
members: members
.into_iter()
.map(|(n, ty)| (n.into(), ty.into()))
.collect(),
}))
}
pub fn name(&self) -> &Arc<String> {
&self.0.name
}
pub fn members(&self) -> &IndexMap<String, Type> {
&self.0.members
}
}
impl fmt::Display for StructType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{name}", name = self.0.name)
}
}
impl Coercible for StructType {
fn is_coercible_to(&self, target: &Self) -> bool {
if self.0.members.len() != target.0.members.len() {
return false;
}
self.0.members.iter().all(|(k, v)| {
target
.0
.members
.get(k)
.map(|target| v.is_coercible_to(target))
.unwrap_or(false)
})
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub struct EnumVariantCacheKey {
enum_index: usize,
variant_index: usize,
}
impl EnumVariantCacheKey {
pub(crate) fn new(enum_index: usize, variant_index: usize) -> Self {
Self {
enum_index,
variant_index,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct EnumTypeInner {
name: String,
inner_value_type: Type,
variants: Arc<[String]>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumType(Arc<EnumTypeInner>);
impl EnumType {
pub fn new(
enum_name: impl Into<String>,
enum_span: Span,
explicit_inner_type: Type,
variants: Vec<(String, Type)>,
variant_spans: &[Span],
) -> Result<Self, Diagnostic> {
assert_eq!(variants.len(), variant_spans.len());
let enum_name = enum_name.into();
let mut results = Vec::with_capacity(variants.len());
for (variant_idx, (variant_name, variant_type)) in variants.iter().enumerate() {
if !variant_type.is_coercible_to(&explicit_inner_type) {
return Err(enum_variant_does_not_coerce_to_type(
&enum_name,
enum_span,
variant_name,
variant_spans[variant_idx],
&explicit_inner_type,
variant_type,
));
}
results.push(variant_name.to_owned());
}
Ok(Self(Arc::new(EnumTypeInner {
name: enum_name,
inner_value_type: explicit_inner_type,
variants: results.into(),
})))
}
pub fn infer(
enum_name: impl Into<String>,
variants: Vec<(String, Type)>,
variant_spans: &[Span],
) -> Result<Self, Diagnostic> {
assert_eq!(variants.len(), variant_spans.len());
let enum_name = enum_name.into();
let mut common_ty: Option<Type> = None;
let mut names = Vec::with_capacity(variants.len());
for (i, (name, variant_ty)) in variants.into_iter().enumerate() {
match common_ty {
Some(current_common_ty) => match current_common_ty.common_type(&variant_ty) {
Some(new_common_ty) => {
common_ty = Some(new_common_ty);
}
None => {
return Err(no_common_inferred_type_for_enum(
&enum_name,
¤t_common_ty,
variant_spans[i - 1],
&variant_ty,
variant_spans[i],
));
}
},
None => common_ty = Some(variant_ty),
}
names.push(name);
}
Ok(Self(Arc::new(EnumTypeInner {
name: enum_name,
inner_value_type: common_ty.unwrap_or(Type::Union),
variants: names.into(),
})))
}
pub fn name(&self) -> &str {
&self.0.name
}
pub fn inner_value_type(&self) -> &Type {
&self.0.inner_value_type
}
pub fn variants(&self) -> &[String] {
&self.0.variants
}
}
impl fmt::Display for EnumType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{name}", name = self.0.name)
}
}
impl Coercible for EnumType {
fn is_coercible_to(&self, target: &Self) -> bool {
self == target
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CallKind {
Task,
Workflow,
}
impl fmt::Display for CallKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Task => write!(f, "task"),
Self::Workflow => write!(f, "workflow"),
}
}
}
#[derive(Debug, Clone, Eq)]
struct CallTypeInner {
kind: CallKind,
namespace: Option<String>,
name: String,
specified: Arc<HashSet<String>>,
inputs: Arc<IndexMap<String, Input>>,
outputs: Arc<IndexMap<String, Output>>,
}
impl PartialEq for CallTypeInner {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self, other)
}
}
#[derive(Debug, Clone, Eq)]
pub struct CallType(Arc<CallTypeInner>);
impl CallType {
pub(crate) fn new(
kind: CallKind,
name: impl Into<String>,
specified: Arc<HashSet<String>>,
inputs: Arc<IndexMap<String, Input>>,
outputs: Arc<IndexMap<String, Output>>,
) -> Self {
Self(Arc::new(CallTypeInner {
kind,
namespace: None,
name: name.into(),
specified,
inputs,
outputs,
}))
}
pub(crate) fn namespaced(
kind: CallKind,
namespace: impl Into<String>,
name: impl Into<String>,
specified: Arc<HashSet<String>>,
inputs: Arc<IndexMap<String, Input>>,
outputs: Arc<IndexMap<String, Output>>,
) -> Self {
Self(Arc::new(CallTypeInner {
kind,
namespace: Some(namespace.into()),
name: name.into(),
specified,
inputs,
outputs,
}))
}
pub fn kind(&self) -> CallKind {
self.0.kind
}
pub fn namespace(&self) -> Option<&str> {
self.0.namespace.as_deref()
}
pub fn name(&self) -> &str {
&self.0.name
}
pub fn specified(&self) -> &HashSet<String> {
&self.0.specified
}
pub fn inputs(&self) -> &IndexMap<String, Input> {
&self.0.inputs
}
pub fn outputs(&self) -> &IndexMap<String, Output> {
&self.0.outputs
}
pub fn optional(&self) -> Self {
let mut inner = self.0.as_ref().clone();
for output in Arc::make_mut(&mut inner.outputs).values_mut() {
*output = Output::new(output.ty().optional(), output.name_span());
}
Self(Arc::new(inner))
}
pub fn promote_scatter(&self) -> Self {
let mut inner = self.0.as_ref().clone();
for output in Arc::make_mut(&mut inner.outputs).values_mut() {
*output = Output::new(output.ty().promote_scatter(), output.name_span());
}
Self(Arc::new(inner))
}
}
impl Coercible for CallType {
fn is_coercible_to(&self, _: &Self) -> bool {
false
}
}
impl fmt::Display for CallType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ns) = &self.0.namespace {
write!(
f,
"call to {kind} `{ns}.{name}`",
kind = self.0.kind,
name = self.0.name,
)
} else {
write!(
f,
"call to {kind} `{name}`",
kind = self.0.kind,
name = self.0.name,
)
}
}
}
impl PartialEq for CallType {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn primitive_type_display() {
assert_eq!(PrimitiveType::Boolean.to_string(), "Boolean");
assert_eq!(PrimitiveType::Integer.to_string(), "Int");
assert_eq!(PrimitiveType::Float.to_string(), "Float");
assert_eq!(PrimitiveType::String.to_string(), "String");
assert_eq!(PrimitiveType::File.to_string(), "File");
assert_eq!(PrimitiveType::Directory.to_string(), "Directory");
assert_eq!(
Type::from(PrimitiveType::Boolean).optional().to_string(),
"Boolean?"
);
assert_eq!(
Type::from(PrimitiveType::Integer).optional().to_string(),
"Int?"
);
assert_eq!(
Type::from(PrimitiveType::Float).optional().to_string(),
"Float?"
);
assert_eq!(
Type::from(PrimitiveType::String).optional().to_string(),
"String?"
);
assert_eq!(
Type::from(PrimitiveType::File).optional().to_string(),
"File?"
);
assert_eq!(
Type::from(PrimitiveType::Directory).optional().to_string(),
"Directory?"
);
}
#[test]
fn array_type_display() {
assert_eq!(
ArrayType::new(PrimitiveType::String).to_string(),
"Array[String]"
);
assert_eq!(
ArrayType::non_empty(PrimitiveType::String).to_string(),
"Array[String]+"
);
let ty: Type = ArrayType::new(ArrayType::new(PrimitiveType::String)).into();
assert_eq!(ty.to_string(), "Array[Array[String]]");
let ty = Type::from(ArrayType::non_empty(
Type::from(ArrayType::non_empty(
Type::from(PrimitiveType::String).optional(),
))
.optional(),
))
.optional();
assert_eq!(ty.to_string(), "Array[Array[String?]+?]+?");
}
#[test]
fn pair_type_display() {
assert_eq!(
PairType::new(PrimitiveType::String, PrimitiveType::Boolean).to_string(),
"Pair[String, Boolean]"
);
let ty: Type = PairType::new(
ArrayType::new(PrimitiveType::String),
ArrayType::new(PrimitiveType::String),
)
.into();
assert_eq!(ty.to_string(), "Pair[Array[String], Array[String]]");
let ty = Type::from(PairType::new(
Type::from(ArrayType::non_empty(
Type::from(PrimitiveType::File).optional(),
))
.optional(),
Type::from(ArrayType::non_empty(
Type::from(PrimitiveType::File).optional(),
))
.optional(),
))
.optional();
assert_eq!(ty.to_string(), "Pair[Array[File?]+?, Array[File?]+?]?");
}
#[test]
fn map_type_display() {
assert_eq!(
MapType::new(PrimitiveType::String, PrimitiveType::Boolean).to_string(),
"Map[String, Boolean]"
);
let ty: Type = MapType::new(
PrimitiveType::Boolean,
ArrayType::new(PrimitiveType::String),
)
.into();
assert_eq!(ty.to_string(), "Map[Boolean, Array[String]]");
let ty: Type = Type::from(MapType::new(
PrimitiveType::String,
Type::from(ArrayType::non_empty(
Type::from(PrimitiveType::File).optional(),
))
.optional(),
))
.optional();
assert_eq!(ty.to_string(), "Map[String, Array[File?]+?]?");
}
#[test]
fn struct_type_display() {
assert_eq!(
StructType::new("Foobar", std::iter::empty::<(String, Type)>()).to_string(),
"Foobar"
);
}
#[test]
fn object_type_display() {
assert_eq!(Type::Object.to_string(), "Object");
assert_eq!(Type::OptionalObject.to_string(), "Object?");
}
#[test]
fn union_type_display() {
assert_eq!(Type::Union.to_string(), "Union");
}
#[test]
fn none_type_display() {
assert_eq!(Type::None.to_string(), "None");
}
#[test]
fn primitive_type_coercion() {
for ty in [
Type::from(PrimitiveType::Boolean),
PrimitiveType::Directory.into(),
PrimitiveType::File.into(),
PrimitiveType::Float.into(),
PrimitiveType::Integer.into(),
PrimitiveType::String.into(),
] {
assert!(ty.is_coercible_to(&ty));
assert!(ty.optional().is_coercible_to(&ty.optional()));
assert!(ty.is_coercible_to(&ty.optional()));
assert!(!ty.optional().is_coercible_to(&ty));
}
assert!(PrimitiveType::String.is_coercible_to(&PrimitiveType::File));
assert!(PrimitiveType::String.is_coercible_to(&PrimitiveType::Directory));
assert!(PrimitiveType::Integer.is_coercible_to(&PrimitiveType::Float));
assert!(PrimitiveType::File.is_coercible_to(&PrimitiveType::String));
assert!(PrimitiveType::Directory.is_coercible_to(&PrimitiveType::String));
assert!(!PrimitiveType::Float.is_coercible_to(&PrimitiveType::Integer));
}
#[test]
fn object_type_coercion() {
assert!(Type::Object.is_coercible_to(&Type::Object));
assert!(Type::Object.is_coercible_to(&Type::OptionalObject));
assert!(Type::OptionalObject.is_coercible_to(&Type::OptionalObject));
assert!(!Type::OptionalObject.is_coercible_to(&Type::Object));
let ty = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
assert!(!Type::OptionalObject.is_coercible_to(&ty));
let ty = MapType::new(PrimitiveType::File, PrimitiveType::String).into();
assert!(!Type::OptionalObject.is_coercible_to(&ty));
let ty = MapType::new(PrimitiveType::Integer, PrimitiveType::String).into();
assert!(!Type::Object.is_coercible_to(&ty));
let ty = Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
assert!(Type::Object.is_coercible_to(&ty));
let ty = Type::from(MapType::new(PrimitiveType::File, PrimitiveType::String)).optional();
assert!(Type::Object.is_coercible_to(&ty));
let ty = Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
assert!(Type::OptionalObject.is_coercible_to(&ty));
let ty = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
assert!(!Type::OptionalObject.is_coercible_to(&ty));
let ty = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
assert!(Type::Object.is_coercible_to(&ty));
let ty = Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional();
assert!(Type::Object.is_coercible_to(&ty));
let ty = Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional();
assert!(Type::OptionalObject.is_coercible_to(&ty));
let ty = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
assert!(!Type::OptionalObject.is_coercible_to(&ty));
}
#[test]
fn array_type_coercion() {
assert!(
ArrayType::new(PrimitiveType::String)
.is_coercible_to(&ArrayType::new(PrimitiveType::String))
);
assert!(
ArrayType::new(PrimitiveType::File)
.is_coercible_to(&ArrayType::new(PrimitiveType::String))
);
assert!(
ArrayType::new(PrimitiveType::String)
.is_coercible_to(&ArrayType::new(PrimitiveType::File))
);
let type1: Type = ArrayType::new(PrimitiveType::String).into();
let type2 = ArrayType::new(Type::from(PrimitiveType::File).optional()).into();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = ArrayType::new(type1).into();
let type2 = ArrayType::new(type2).into();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = ArrayType::non_empty(PrimitiveType::String).into();
let type2 = ArrayType::new(Type::from(PrimitiveType::File).optional()).into();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = ArrayType::non_empty(PrimitiveType::String).into();
let type2 = ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = ArrayType::new(PrimitiveType::String).into();
let type2 = ArrayType::new(PrimitiveType::String).into();
assert!(type1.is_coercible_to(&type2));
assert!(type2.is_coercible_to(&type1));
let type1 = Type::from(ArrayType::new(PrimitiveType::String)).optional();
let type2 = Type::from(ArrayType::new(PrimitiveType::String)).optional();
assert!(type1.is_coercible_to(&type2));
assert!(type2.is_coercible_to(&type1));
let type1: Type = ArrayType::new(PrimitiveType::String).into();
let type2 = Type::from(ArrayType::new(PrimitiveType::String)).optional();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
}
#[test]
fn pair_type_coercion() {
assert!(
PairType::new(PrimitiveType::String, PrimitiveType::String)
.is_coercible_to(&PairType::new(PrimitiveType::String, PrimitiveType::String))
);
assert!(
PairType::new(PrimitiveType::String, PrimitiveType::String).is_coercible_to(
&PairType::new(PrimitiveType::File, PrimitiveType::Directory)
)
);
assert!(
PairType::new(PrimitiveType::File, PrimitiveType::Directory)
.is_coercible_to(&PairType::new(PrimitiveType::String, PrimitiveType::String))
);
let type1: Type = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
let type2 = PairType::new(
Type::from(PrimitiveType::File).optional(),
Type::from(PrimitiveType::Directory).optional(),
)
.into();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = PairType::new(type1.clone(), type1).into();
let type2 = PairType::new(type2.clone(), type2).into();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
let type2 = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
assert!(type1.is_coercible_to(&type2));
assert!(type2.is_coercible_to(&type1));
let type1 =
Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional();
let type2 =
Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional();
assert!(type1.is_coercible_to(&type2));
assert!(type2.is_coercible_to(&type1));
let type1: Type = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
let type2 =
Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
}
#[test]
fn map_type_coercion() {
assert!(
MapType::new(PrimitiveType::String, PrimitiveType::String)
.is_coercible_to(&MapType::new(PrimitiveType::String, PrimitiveType::String))
);
assert!(
MapType::new(PrimitiveType::String, PrimitiveType::String)
.is_coercible_to(&MapType::new(PrimitiveType::File, PrimitiveType::Directory))
);
assert!(
MapType::new(PrimitiveType::File, PrimitiveType::Directory)
.is_coercible_to(&MapType::new(PrimitiveType::String, PrimitiveType::String))
);
let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
let type2 = MapType::new(
PrimitiveType::File,
Type::from(PrimitiveType::Directory).optional(),
)
.into();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = MapType::new(PrimitiveType::String, type1).into();
let type2 = MapType::new(PrimitiveType::Directory, type2).into();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
let type2 = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
assert!(type1.is_coercible_to(&type2));
assert!(type2.is_coercible_to(&type1));
let type1: Type =
Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
let type2: Type =
Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
assert!(type1.is_coercible_to(&type2));
assert!(type2.is_coercible_to(&type1));
let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
let type2 =
Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
let type2 = StructType::new(
"Foo",
[
("foo", PrimitiveType::Integer),
("bar", PrimitiveType::Integer),
("baz", PrimitiveType::Integer),
],
)
.into();
assert!(type1.is_coercible_to(&type2));
let type1: Type = MapType::new(PrimitiveType::File, PrimitiveType::Integer).into();
let type2 = StructType::new(
"Foo",
[
("foo", PrimitiveType::Integer),
("bar", PrimitiveType::Integer),
("baz", PrimitiveType::Integer),
],
)
.into();
assert!(type1.is_coercible_to(&type2));
let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
let type2 = StructType::new(
"Foo",
[
("foo", PrimitiveType::Integer),
("bar", PrimitiveType::String),
("baz", PrimitiveType::Integer),
],
)
.into();
assert!(!type1.is_coercible_to(&type2));
let type1: Type = MapType::new(PrimitiveType::Integer, PrimitiveType::Integer).into();
let type2 = StructType::new(
"Foo",
[
("foo", PrimitiveType::Integer),
("bar", PrimitiveType::Integer),
("baz", PrimitiveType::Integer),
],
)
.into();
assert!(!type1.is_coercible_to(&type2));
let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
assert!(type1.is_coercible_to(&Type::Object));
let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
assert!(type1.is_coercible_to(&Type::OptionalObject));
let type1: Type =
Type::from(MapType::new(PrimitiveType::String, PrimitiveType::Integer)).optional();
assert!(type1.is_coercible_to(&Type::OptionalObject));
let type1: Type = MapType::new(PrimitiveType::File, PrimitiveType::Integer).into();
assert!(type1.is_coercible_to(&Type::Object));
let type1: Type = MapType::new(PrimitiveType::File, PrimitiveType::Integer).into();
assert!(type1.is_coercible_to(&Type::OptionalObject));
let type1: Type =
Type::from(MapType::new(PrimitiveType::File, PrimitiveType::Integer)).optional();
assert!(type1.is_coercible_to(&Type::OptionalObject));
let type1: Type =
Type::from(MapType::new(PrimitiveType::String, PrimitiveType::Integer)).optional();
assert!(!type1.is_coercible_to(&Type::Object));
let type1: Type =
Type::from(MapType::new(PrimitiveType::File, PrimitiveType::Integer)).optional();
assert!(!type1.is_coercible_to(&Type::Object));
let type1: Type = MapType::new(PrimitiveType::Integer, PrimitiveType::Integer).into();
assert!(!type1.is_coercible_to(&Type::Object));
}
#[test]
fn struct_type_coercion() {
let type1: Type = StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::Integer),
],
)
.into();
let type2 = StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::Integer),
],
)
.into();
assert!(type1.is_coercible_to(&type2));
assert!(type2.is_coercible_to(&type1));
let type1: Type = StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::Integer),
],
)
.into();
let type2 = Type::from(StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::Integer),
],
))
.optional();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = Type::from(StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::Integer),
],
))
.optional();
let type2 = Type::from(StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::Integer),
],
))
.optional();
assert!(type1.is_coercible_to(&type2));
assert!(type2.is_coercible_to(&type1));
let type1: Type = StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::Integer),
],
)
.into();
let type2 = StructType::new(
"Bar",
[
("foo", PrimitiveType::File),
("bar", PrimitiveType::Directory),
("baz", PrimitiveType::Float),
],
)
.into();
assert!(type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::Integer),
],
)
.into();
let type2 = StructType::new("Bar", [("baz", PrimitiveType::Float)]).into();
assert!(!type1.is_coercible_to(&type2));
assert!(!type2.is_coercible_to(&type1));
let type1: Type = StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::String),
],
)
.into();
let type2 = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
assert!(type1.is_coercible_to(&type2));
let type1: Type = StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::String),
],
)
.into();
let type2 = MapType::new(PrimitiveType::File, PrimitiveType::String).into();
assert!(type1.is_coercible_to(&type2));
let type1: Type = StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::Integer),
("baz", PrimitiveType::String),
],
)
.into();
let type2 = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
assert!(!type1.is_coercible_to(&type2));
let type1: Type = StructType::new(
"Foo",
[
("foo", PrimitiveType::String),
("bar", PrimitiveType::String),
("baz", PrimitiveType::String),
],
)
.into();
let type2 = MapType::new(PrimitiveType::Integer, PrimitiveType::String).into();
assert!(!type1.is_coercible_to(&type2));
assert!(type1.is_coercible_to(&Type::Object));
assert!(type1.is_coercible_to(&Type::OptionalObject));
let type1: Type =
Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional();
assert!(type1.is_coercible_to(&Type::OptionalObject));
assert!(!type1.is_coercible_to(&Type::Object));
}
#[test]
fn union_type_coercion() {
for ty in [
Type::from(PrimitiveType::Boolean),
PrimitiveType::Directory.into(),
PrimitiveType::File.into(),
PrimitiveType::Float.into(),
PrimitiveType::Integer.into(),
PrimitiveType::String.into(),
] {
assert!(Type::Union.is_coercible_to(&ty));
assert!(Type::Union.is_coercible_to(&ty.optional()));
assert!(ty.is_coercible_to(&Type::Union));
}
for optional in [true, false] {
let ty: Type = ArrayType::new(PrimitiveType::String).into();
let ty = if optional { ty.optional() } else { ty };
let coercible = Type::Union.is_coercible_to(&ty);
assert!(coercible);
let ty: Type = PairType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
let ty = if optional { ty.optional() } else { ty };
let coercible = Type::Union.is_coercible_to(&ty);
assert!(coercible);
let ty: Type = MapType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
let ty = if optional { ty.optional() } else { ty };
let coercible = Type::Union.is_coercible_to(&ty);
assert!(coercible);
let ty: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
let ty = if optional { ty.optional() } else { ty };
let coercible = Type::Union.is_coercible_to(&ty);
assert!(coercible);
}
}
#[test]
fn none_type_coercion() {
for ty in [
Type::from(PrimitiveType::Boolean),
PrimitiveType::Directory.into(),
PrimitiveType::File.into(),
PrimitiveType::Float.into(),
PrimitiveType::Integer.into(),
PrimitiveType::String.into(),
] {
assert!(!Type::None.is_coercible_to(&ty));
assert!(Type::None.is_coercible_to(&ty.optional()));
assert!(!ty.is_coercible_to(&Type::None));
}
for optional in [true, false] {
let ty: Type = ArrayType::new(PrimitiveType::String).into();
let ty = if optional { ty.optional() } else { ty };
let coercible = Type::None.is_coercible_to(&ty);
if optional {
assert!(coercible);
} else {
assert!(!coercible);
}
let ty: Type = PairType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
let ty = if optional { ty.optional() } else { ty };
let coercible = Type::None.is_coercible_to(&ty);
if optional {
assert!(coercible);
} else {
assert!(!coercible);
}
let ty: Type = MapType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
let ty = if optional { ty.optional() } else { ty };
let coercible = Type::None.is_coercible_to(&ty);
if optional {
assert!(coercible);
} else {
assert!(!coercible);
}
let ty: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
let ty = if optional { ty.optional() } else { ty };
let coercible = Type::None.is_coercible_to(&ty);
if optional {
assert!(coercible);
} else {
assert!(!coercible);
}
}
}
#[test]
fn primitive_equality() {
for ty in [
Type::from(PrimitiveType::Boolean),
PrimitiveType::Directory.into(),
PrimitiveType::File.into(),
PrimitiveType::Float.into(),
PrimitiveType::Integer.into(),
PrimitiveType::String.into(),
] {
assert!(ty.eq(&ty));
assert!(!ty.optional().eq(&ty));
assert!(!ty.eq(&ty.optional()));
assert!(ty.optional().eq(&ty.optional()));
assert!(!ty.eq(&Type::Object));
assert!(!ty.eq(&Type::OptionalObject));
assert!(!ty.eq(&Type::Union));
assert!(!ty.eq(&Type::None));
}
}
#[test]
fn array_equality() {
let a: Type = ArrayType::new(PrimitiveType::String).into();
let b: Type = ArrayType::new(PrimitiveType::String).into();
assert!(a.eq(&b));
assert!(!a.optional().eq(&b));
assert!(!a.eq(&b.optional()));
assert!(a.optional().eq(&b.optional()));
let a: Type = ArrayType::new(a).into();
let b: Type = ArrayType::new(b).into();
assert!(a.eq(&b));
let a: Type = ArrayType::non_empty(a).into();
let b: Type = ArrayType::non_empty(b).into();
assert!(a.eq(&b));
let a: Type = ArrayType::new(PrimitiveType::String).into();
let b: Type = ArrayType::non_empty(PrimitiveType::String).into();
assert!(!a.eq(&b));
let a: Type = ArrayType::new(PrimitiveType::String).into();
let b: Type = ArrayType::new(PrimitiveType::Integer).into();
assert!(!a.eq(&b));
assert!(!a.eq(&Type::Object));
assert!(!a.eq(&Type::OptionalObject));
assert!(!a.eq(&Type::Union));
assert!(!a.eq(&Type::None));
}
#[test]
fn pair_equality() {
let a: Type = PairType::new(PrimitiveType::String, PrimitiveType::Integer).into();
let b: Type = PairType::new(PrimitiveType::String, PrimitiveType::Integer).into();
assert!(a.eq(&b));
assert!(!a.optional().eq(&b));
assert!(!a.eq(&b.optional()));
assert!(a.optional().eq(&b.optional()));
let a: Type = PairType::new(a.clone(), a).into();
let b: Type = PairType::new(b.clone(), b).into();
assert!(a.eq(&b));
let a: Type = PairType::new(PrimitiveType::String, PrimitiveType::Integer).into();
let b: Type =
Type::from(PairType::new(PrimitiveType::String, PrimitiveType::Integer)).optional();
assert!(!a.eq(&b));
assert!(!a.eq(&Type::Object));
assert!(!a.eq(&Type::OptionalObject));
assert!(!a.eq(&Type::Union));
assert!(!a.eq(&Type::None));
}
#[test]
fn map_equality() {
let a: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
let b = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
assert!(a.eq(&b));
assert!(!a.optional().eq(&b));
assert!(!a.eq(&b.optional()));
assert!(a.optional().eq(&b.optional()));
let a: Type = MapType::new(PrimitiveType::File, a).into();
let b = MapType::new(PrimitiveType::File, b).into();
assert!(a.eq(&b));
let a: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
let b = MapType::new(PrimitiveType::Integer, PrimitiveType::String).into();
assert!(!a.eq(&b));
assert!(!a.eq(&Type::Object));
assert!(!a.eq(&Type::OptionalObject));
assert!(!a.eq(&Type::Union));
assert!(!a.eq(&Type::None));
}
#[test]
fn struct_equality() {
let a: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
assert!(a.eq(&a));
assert!(!a.optional().eq(&a));
assert!(!a.eq(&a.optional()));
assert!(a.optional().eq(&a.optional()));
let b: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
assert!(a.eq(&b));
let b: Type = StructType::new("Bar", [("foo", PrimitiveType::String)]).into();
assert!(!a.eq(&b));
}
#[test]
fn object_equality() {
assert!(Type::Object.eq(&Type::Object));
assert!(!Type::OptionalObject.eq(&Type::Object));
assert!(!Type::Object.eq(&Type::OptionalObject));
assert!(Type::OptionalObject.eq(&Type::OptionalObject));
}
#[test]
fn union_equality() {
assert!(Type::Union.eq(&Type::Union));
assert!(!Type::None.eq(&Type::Union));
assert!(!Type::Union.eq(&Type::None));
assert!(Type::None.eq(&Type::None));
}
#[test]
fn enum_type_new_with_explicit_type() {
let status = EnumType::new(
"Status",
Span::new(0, 0),
PrimitiveType::String.into(),
vec![
("Pending".into(), PrimitiveType::String.into()),
("Running".into(), PrimitiveType::String.into()),
("Complete".into(), PrimitiveType::String.into()),
],
&[Span::new(0, 0), Span::new(0, 0), Span::new(0, 0)][..],
)
.unwrap();
assert_eq!(status.name(), "Status");
assert_eq!(
status.inner_value_type(),
&Type::from(PrimitiveType::String)
);
assert_eq!(status.variants().len(), 3);
}
#[test]
fn enum_type_new_fails_when_not_coercible() {
let result = EnumType::new(
"Bad",
Span::new(0, 0),
PrimitiveType::Integer.into(),
vec![
("First".into(), PrimitiveType::String.into()),
("Second".into(), PrimitiveType::Integer.into()),
],
&[Span::new(0, 0), Span::new(0, 0)][..],
);
assert!(
matches!(result, Err(diagnostic) if diagnostic.message() == "cannot coerce variant `First` in enum `Bad` from type `String` to type `Int`")
);
}
#[test]
fn enum_type_infer_finds_common_type() {
let priority = EnumType::infer(
"Priority",
vec![
("Low".into(), PrimitiveType::Integer.into()),
("Medium".into(), PrimitiveType::Integer.into()),
("High".into(), PrimitiveType::Integer.into()),
],
&[Span::new(0, 0), Span::new(0, 0), Span::new(0, 0)],
)
.unwrap();
assert_eq!(priority.name(), "Priority");
assert_eq!(
priority.inner_value_type(),
&Type::from(PrimitiveType::Integer)
);
assert_eq!(priority.variants().len(), 3);
}
#[test]
fn enum_type_infer_coerces_int_to_float() {
let mixed = EnumType::infer(
"Mixed",
vec![
("IntValue".into(), PrimitiveType::Integer.into()),
("FloatValue".into(), PrimitiveType::Float.into()),
],
&[Span::new(0, 0), Span::new(0, 0)],
)
.unwrap();
assert_eq!(mixed.name(), "Mixed");
assert_eq!(mixed.inner_value_type(), &Type::from(PrimitiveType::Float));
assert_eq!(mixed.variants().len(), 2);
}
#[test]
fn enum_type_infer_fails_without_common_type() {
let result = EnumType::infer(
"Bad",
vec![
("StringVal".into(), PrimitiveType::String.into()),
("IntVal".into(), PrimitiveType::Integer.into()),
],
&[Span::new(0, 0), Span::new(0, 0)][..],
);
assert!(
matches!(result, Err(diagnostic) if diagnostic.message() == "cannot infer a common type for enum `Bad`")
);
}
#[test]
fn enum_type_empty_has_union_type() {
let result = EnumType::infer("Empty", Vec::<(String, Type)>::new(), &[]);
let empty = result.unwrap();
assert_eq!(empty.name(), "Empty");
assert_eq!(empty.inner_value_type(), &Type::Union);
assert_eq!(empty.variants().len(), 0);
}
#[test]
fn enum_type_display() {
let enum_type = EnumType::new(
"Color",
Span::new(0, 0),
PrimitiveType::String.into(),
vec![("Red".into(), PrimitiveType::String.into())],
&[Span::new(0, 0)][..],
)
.unwrap();
assert_eq!(enum_type.to_string(), "Color");
}
#[test]
fn enum_type_not_coercible_to_other_enums() {
let color = EnumType::new(
"Color",
Span::new(0, 0),
PrimitiveType::String.into(),
vec![("Red".into(), PrimitiveType::String.into())],
&[Span::new(0, 0)][..],
)
.unwrap();
let status = EnumType::new(
"Status",
Span::new(0, 0),
PrimitiveType::String.into(),
vec![("Active".into(), PrimitiveType::String.into())],
&[Span::new(0, 0)][..],
)
.unwrap();
assert!(!color.is_coercible_to(&status));
}
}