use serde::{Deserialize, Serialize};
use std::fmt;
use crate::error::AdapterError;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ParametricType {
pub constructor: String,
pub parameters: Vec<TypeParameter>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TypeParameter {
Concrete(String),
Parametric(Box<ParametricType>),
}
impl ParametricType {
pub fn new(constructor: impl Into<String>, parameters: Vec<TypeParameter>) -> Self {
ParametricType {
constructor: constructor.into(),
parameters,
}
}
pub fn list(inner: TypeParameter) -> Self {
ParametricType::new("List", vec![inner])
}
pub fn option(inner: TypeParameter) -> Self {
ParametricType::new("Option", vec![inner])
}
pub fn pair(first: TypeParameter, second: TypeParameter) -> Self {
ParametricType::new("Pair", vec![first, second])
}
pub fn map(key: TypeParameter, value: TypeParameter) -> Self {
ParametricType::new("Map", vec![key, value])
}
pub fn validate(&self) -> Result<(), AdapterError> {
if self.constructor.is_empty() {
return Err(AdapterError::InvalidParametricType(
"Constructor name cannot be empty".to_string(),
));
}
let expected_arity = match self.constructor.as_str() {
"List" | "Option" | "Set" => 1,
"Pair" | "Map" | "Either" => 2,
_ => return Ok(()), };
if self.parameters.len() != expected_arity {
return Err(AdapterError::InvalidParametricType(format!(
"Constructor '{}' expects {} parameters, found {}",
self.constructor,
expected_arity,
self.parameters.len()
)));
}
for param in &self.parameters {
if let TypeParameter::Parametric(nested) = param {
nested.validate()?;
}
}
Ok(())
}
pub fn arity(&self) -> usize {
self.parameters.len()
}
pub fn is_monomorphic(&self) -> bool {
self.parameters.is_empty()
}
pub fn contains_parameter(&self, name: &str) -> bool {
self.parameters.iter().any(|param| match param {
TypeParameter::Concrete(n) => n == name,
TypeParameter::Parametric(nested) => nested.contains_parameter(name),
})
}
pub fn substitute(&self, from: &str, to: &TypeParameter) -> ParametricType {
let new_params = self
.parameters
.iter()
.map(|param| match param {
TypeParameter::Concrete(name) if name == from => to.clone(),
TypeParameter::Concrete(_) => param.clone(),
TypeParameter::Parametric(nested) => {
TypeParameter::Parametric(Box::new(nested.substitute(from, to)))
}
})
.collect();
ParametricType {
constructor: self.constructor.clone(),
parameters: new_params,
}
}
}
impl TypeParameter {
pub fn concrete(name: impl Into<String>) -> Self {
TypeParameter::Concrete(name.into())
}
pub fn parametric(ptype: ParametricType) -> Self {
TypeParameter::Parametric(Box::new(ptype))
}
pub fn as_concrete(&self) -> Option<&str> {
match self {
TypeParameter::Concrete(name) => Some(name),
TypeParameter::Parametric(_) => None,
}
}
pub fn as_parametric(&self) -> Option<&ParametricType> {
match self {
TypeParameter::Concrete(_) => None,
TypeParameter::Parametric(ptype) => Some(ptype),
}
}
}
impl fmt::Display for ParametricType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.constructor)?;
if !self.parameters.is_empty() {
write!(f, "<")?;
for (i, param) in self.parameters.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", param)?;
}
write!(f, ">")?;
}
Ok(())
}
}
impl fmt::Display for TypeParameter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypeParameter::Concrete(name) => write!(f, "{}", name),
TypeParameter::Parametric(ptype) => write!(f, "{}", ptype),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeBound {
pub param_name: String,
pub constraint: BoundConstraint,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum BoundConstraint {
Subtype(String),
Trait(String),
Comparable,
Numeric,
}
impl TypeBound {
pub fn new(param_name: impl Into<String>, constraint: BoundConstraint) -> Self {
TypeBound {
param_name: param_name.into(),
constraint,
}
}
pub fn subtype(param_name: impl Into<String>, supertype: impl Into<String>) -> Self {
TypeBound::new(param_name, BoundConstraint::Subtype(supertype.into()))
}
pub fn trait_bound(param_name: impl Into<String>, trait_name: impl Into<String>) -> Self {
TypeBound::new(param_name, BoundConstraint::Trait(trait_name.into()))
}
pub fn comparable(param_name: impl Into<String>) -> Self {
TypeBound::new(param_name, BoundConstraint::Comparable)
}
pub fn numeric(param_name: impl Into<String>) -> Self {
TypeBound::new(param_name, BoundConstraint::Numeric)
}
}
impl fmt::Display for TypeBound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: ", self.param_name)?;
match &self.constraint {
BoundConstraint::Subtype(s) => write!(f, "Subtype({})", s),
BoundConstraint::Trait(t) => write!(f, "{}", t),
BoundConstraint::Comparable => write!(f, "Comparable"),
BoundConstraint::Numeric => write!(f, "Numeric"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parametric_type_list() {
let list_person = ParametricType::list(TypeParameter::concrete("Person"));
assert_eq!(list_person.constructor, "List");
assert_eq!(list_person.arity(), 1);
assert_eq!(list_person.to_string(), "List<Person>");
}
#[test]
fn test_parametric_type_option() {
let opt_city = ParametricType::option(TypeParameter::concrete("City"));
assert_eq!(opt_city.constructor, "Option");
assert_eq!(opt_city.arity(), 1);
assert_eq!(opt_city.to_string(), "Option<City>");
}
#[test]
fn test_parametric_type_pair() {
let pair = ParametricType::pair(
TypeParameter::concrete("Person"),
TypeParameter::concrete("City"),
);
assert_eq!(pair.constructor, "Pair");
assert_eq!(pair.arity(), 2);
assert_eq!(pair.to_string(), "Pair<Person, City>");
}
#[test]
fn test_parametric_type_map() {
let map = ParametricType::map(
TypeParameter::concrete("String"),
TypeParameter::concrete("Int"),
);
assert_eq!(map.constructor, "Map");
assert_eq!(map.arity(), 2);
assert_eq!(map.to_string(), "Map<String, Int>");
}
#[test]
fn test_nested_parametric_type() {
let list_option_person = ParametricType::list(TypeParameter::parametric(
ParametricType::option(TypeParameter::concrete("Person")),
));
assert_eq!(list_option_person.to_string(), "List<Option<Person>>");
assert!(list_option_person.contains_parameter("Person"));
}
#[test]
fn test_parametric_type_validation() {
let valid = ParametricType::list(TypeParameter::concrete("Person"));
assert!(valid.validate().is_ok());
let invalid = ParametricType::new(
"List",
vec![TypeParameter::concrete("A"), TypeParameter::concrete("B")],
);
assert!(invalid.validate().is_err());
}
#[test]
fn test_parametric_type_substitution() {
let list_t = ParametricType::list(TypeParameter::concrete("T"));
let list_person = list_t.substitute("T", &TypeParameter::concrete("Person"));
assert_eq!(list_person.to_string(), "List<Person>");
}
#[test]
fn test_type_bounds() {
let bound = TypeBound::subtype("T", "Person");
assert_eq!(bound.param_name, "T");
assert_eq!(bound.to_string(), "T: Subtype(Person)");
let comparable = TypeBound::comparable("T");
assert_eq!(comparable.to_string(), "T: Comparable");
let numeric = TypeBound::numeric("N");
assert_eq!(numeric.to_string(), "N: Numeric");
}
}