use crate::{
scope::{LocalParamID, Scope, ScopePointer, type_parameter::TypeParameter},
r#type::Type,
type_expr::{ScopePortal, ScopedTypeExpr, TypeExpr, TypeExprScope},
};
use std::{borrow::Cow, collections::HashSet};
#[cfg(feature = "json-schema")]
use schemars::JsonSchema;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "tsify")]
use tsify::Tsify;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(
serialize = "T: Serialize, T::Operator: Serialize, S: Serialize",
deserialize = "T: Deserialize<'de>, T::Operator: Deserialize<'de>, S: Deserialize<'de>"
))
)]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
#[cfg_attr(feature = "json-schema", schemars(bound = "T: JsonSchema, T::Operator: JsonSchema, S: JsonSchema"))]
#[cfg_attr(feature = "tsify", derive(Tsify))]
pub struct Conditional<T: Type, S: TypeExprScope> {
pub t_test: TypeExpr<T, S>,
pub t_test_bound: TypeExpr<T, S>,
pub t_then: TypeExpr<T, S>,
pub t_else: TypeExpr<T, S>,
pub infer: HashSet<LocalParamID>,
}
pub struct ConditionalDistribution<'a, T: Type> {
pub new_t_test: Cow<'a, ScopedTypeExpr<T>>,
pub new_t_test_scope: ScopePointer<T>,
pub new_then_else_scope: ScopePointer<T>,
}
impl<'a, T: Type> ConditionalDistribution<'a, T> {
pub fn into_conditional(self, conditional: &Conditional<T, ScopePortal<T>>) -> Conditional<T, ScopePortal<T>> {
Conditional {
t_test: self.new_t_test.into_owned(),
t_test_bound: conditional.t_test_bound.clone(),
t_then: TypeExpr::ScopePortal {
expr: Box::new(conditional.t_then.clone()),
scope: ScopePortal { portal: ScopePointer::clone(&self.new_then_else_scope) },
},
t_else: TypeExpr::ScopePortal {
expr: Box::new(conditional.t_else.clone()),
scope: ScopePortal { portal: self.new_then_else_scope },
},
infer: HashSet::new(),
}
}
}
impl<T: Type> Conditional<T, ScopePortal<T>> {
pub fn distribute(&self, scope: &ScopePointer<T>) -> Option<ScopedTypeExpr<T>> {
use super::SupertypeResult::*;
let (first_dist, remaining_dist) = self.build_conditional_distributions(scope);
if remaining_dist.is_empty() {
return match self.t_test_bound.supertype_of(&self.t_test, scope, scope) {
Supertype => Some(self.t_then.clone()),
Unrelated(_) => Some(self.t_else.clone()),
Unknown => return None,
};
}
let mut current = TypeExpr::Conditional(Box::new(first_dist.into_conditional(self)));
for distribution in remaining_dist {
current = TypeExpr::Union(
Box::new(current),
Box::new(TypeExpr::Conditional(Box::new(distribution.into_conditional(self)))),
);
}
Some(current)
}
pub fn build_conditional_distributions<'a>(
&self,
scope: &ScopePointer<T>,
) -> (ConditionalDistribution<'a, T>, Vec<ConditionalDistribution<'a, T>>) {
let mut distributions = vec![];
self.t_test.traverse_union(scope, &mut |union_expr, union_expr_scope| {
let mut inferred_scope = Scope::new_child(scope);
if let TypeExpr::TypeParameter(param, _infer) = self.t_test {
inferred_scope.define(param, TypeParameter::default());
let _ = inferred_scope.infer(¶m, union_expr.clone(), ScopePointer::clone(union_expr_scope));
distributions.push(ConditionalDistribution {
new_t_test: Cow::Owned(union_expr.clone()),
new_t_test_scope: ScopePointer::clone(union_expr_scope),
new_then_else_scope: ScopePointer::new(inferred_scope),
});
} else {
distributions.push(ConditionalDistribution {
new_t_test: Cow::Owned(union_expr.clone()),
new_t_test_scope: ScopePointer::clone(scope),
new_then_else_scope: ScopePointer::clone(union_expr_scope),
});
};
});
(distributions.pop().expect("Unions was expected to be at least one long"), distributions)
}
}