#[cfg(test)]
use proptest_derive::Arbitrary;
use std::num::NonZeroU64;
use thiserror::Error;
use super::row_var::MaybeRV;
use super::{check_typevar_decl, RowVariable, Substitution, Type, TypeBase, TypeBound};
use crate::extension::ExtensionRegistry;
use crate::extension::ExtensionSet;
use crate::extension::SignatureError;
#[derive(
Clone, Debug, PartialEq, Eq, Hash, derive_more::Display, serde::Deserialize, serde::Serialize,
)]
#[display("{}", "_0.map(|i|i.to_string()).unwrap_or(\"-\".to_string())")]
#[cfg_attr(test, derive(Arbitrary))]
pub struct UpperBound(Option<NonZeroU64>);
impl UpperBound {
fn valid_value(&self, val: u64) -> bool {
match (val, self.0) {
(0, _) | (_, None) => true,
(val, Some(inner)) if NonZeroU64::new(val).unwrap() < inner => true,
_ => false,
}
}
fn contains(&self, other: &UpperBound) -> bool {
match (self.0, other.0) {
(None, _) => true,
(Some(b1), Some(b2)) if b1 >= b2 => true,
_ => false,
}
}
pub fn value(&self) -> &Option<NonZeroU64> {
&self.0
}
}
#[derive(
Clone, Debug, PartialEq, Eq, Hash, derive_more::Display, serde::Deserialize, serde::Serialize,
)]
#[non_exhaustive]
#[serde(tag = "tp")]
pub enum TypeParam {
Type {
b: TypeBound,
},
BoundedNat {
bound: UpperBound,
},
String,
List {
param: Box<TypeParam>,
},
#[display("Tuple({})", "params.iter().map(|t|t.to_string()).join(\", \")")]
Tuple {
params: Vec<TypeParam>,
},
Extensions,
}
impl TypeParam {
pub const fn max_nat() -> Self {
Self::BoundedNat {
bound: UpperBound(None),
}
}
pub const fn bounded_nat(upper_bound: NonZeroU64) -> Self {
Self::BoundedNat {
bound: UpperBound(Some(upper_bound)),
}
}
pub fn new_list(elem: impl Into<TypeParam>) -> Self {
Self::List {
param: Box::new(elem.into()),
}
}
fn contains(&self, other: &TypeParam) -> bool {
match (self, other) {
(TypeParam::Type { b: b1 }, TypeParam::Type { b: b2 }) => b1.contains(*b2),
(TypeParam::BoundedNat { bound: b1 }, TypeParam::BoundedNat { bound: b2 }) => {
b1.contains(b2)
}
(TypeParam::String, TypeParam::String) => true,
(TypeParam::List { param: e1 }, TypeParam::List { param: e2 }) => e1.contains(e2),
(TypeParam::Tuple { params: es1 }, TypeParam::Tuple { params: es2 }) => {
es1.len() == es2.len() && es1.iter().zip(es2).all(|(e1, e2)| e1.contains(e2))
}
(TypeParam::Extensions, TypeParam::Extensions) => true,
_ => false,
}
}
}
impl From<TypeBound> for TypeParam {
fn from(bound: TypeBound) -> Self {
Self::Type { b: bound }
}
}
impl From<UpperBound> for TypeParam {
fn from(bound: UpperBound) -> Self {
Self::BoundedNat { bound }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
#[non_exhaustive]
#[serde(tag = "tya")]
pub enum TypeArg {
Type {
#[allow(missing_docs)]
ty: Type,
},
BoundedNat {
#[allow(missing_docs)]
n: u64,
},
String {
#[allow(missing_docs)]
arg: String,
},
Sequence {
#[allow(missing_docs)]
elems: Vec<TypeArg>,
},
Extensions {
#[allow(missing_docs)]
es: ExtensionSet,
},
Variable {
#[allow(missing_docs)]
#[serde(flatten)]
v: TypeArgVariable,
},
}
impl<RV: MaybeRV> From<TypeBase<RV>> for TypeArg {
fn from(value: TypeBase<RV>) -> Self {
match value.try_into_type() {
Ok(ty) => TypeArg::Type { ty },
Err(RowVariable(idx, bound)) => TypeArg::new_var_use(idx, TypeParam::new_list(bound)),
}
}
}
impl From<u64> for TypeArg {
fn from(n: u64) -> Self {
Self::BoundedNat { n }
}
}
impl From<String> for TypeArg {
fn from(arg: String) -> Self {
TypeArg::String { arg }
}
}
impl From<Vec<TypeArg>> for TypeArg {
fn from(elems: Vec<TypeArg>) -> Self {
Self::Sequence { elems }
}
}
impl From<ExtensionSet> for TypeArg {
fn from(es: ExtensionSet) -> Self {
Self::Extensions { es }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
pub struct TypeArgVariable {
idx: usize,
cached_decl: TypeParam,
}
impl TypeArg {
pub const UNIT: Self = Self::Type { ty: Type::UNIT };
pub fn new_var_use(idx: usize, decl: TypeParam) -> Self {
match decl {
TypeParam::Type { b } => Type::new_var_use(idx, b).into(),
TypeParam::Extensions => TypeArg::Extensions {
es: ExtensionSet::type_var(idx),
},
_ => TypeArg::Variable {
v: TypeArgVariable {
idx,
cached_decl: decl,
},
},
}
}
pub(crate) fn validate(
&self,
extension_registry: &ExtensionRegistry,
var_decls: &[TypeParam],
) -> Result<(), SignatureError> {
match self {
TypeArg::Type { ty } => ty.validate(extension_registry, var_decls),
TypeArg::BoundedNat { .. } | TypeArg::String { .. } => Ok(()),
TypeArg::Sequence { elems } => elems
.iter()
.try_for_each(|a| a.validate(extension_registry, var_decls)),
TypeArg::Extensions { es: _ } => Ok(()),
TypeArg::Variable {
v: TypeArgVariable { idx, cached_decl },
} => {
assert!(
!matches!(cached_decl, TypeParam::Type { .. }),
"Malformed TypeArg::Variable {} - should be inconstructible",
cached_decl
);
check_typevar_decl(var_decls, *idx, cached_decl)
}
}
}
pub(crate) fn substitute(&self, t: &Substitution) -> Self {
match self {
TypeArg::Type { ty } => {
ty.substitute1(t).into()
}
TypeArg::BoundedNat { .. } | TypeArg::String { .. } => self.clone(), TypeArg::Sequence { elems } => {
let mut are_types = elems.iter().map(|ta| match ta {
TypeArg::Type { .. } => true,
TypeArg::Variable { v } => v.bound_if_row_var().is_some(),
_ => false,
});
let elems = match are_types.next() {
Some(true) => {
assert!(are_types.all(|b| b)); elems
.iter()
.flat_map(|ta| match ta.substitute(t) {
ty @ TypeArg::Type { .. } => vec![ty],
TypeArg::Sequence { elems } => elems,
_ => panic!("Expected Type or row of Types"),
})
.collect()
}
_ => {
elems.iter().map(|ta| ta.substitute(t)).collect()
}
};
TypeArg::Sequence { elems }
}
TypeArg::Extensions { es } => TypeArg::Extensions {
es: es.substitute(t),
},
TypeArg::Variable {
v: TypeArgVariable { idx, cached_decl },
} => t.apply_var(*idx, cached_decl),
}
}
}
impl TypeArgVariable {
pub fn index(&self) -> usize {
self.idx
}
pub fn bound_if_row_var(&self) -> Option<TypeBound> {
if let TypeParam::List { param } = &self.cached_decl {
if let TypeParam::Type { b } = **param {
return Some(b);
}
}
None
}
}
pub fn check_type_arg(arg: &TypeArg, param: &TypeParam) -> Result<(), TypeArgError> {
match (arg, param) {
(
TypeArg::Variable {
v: TypeArgVariable { cached_decl, .. },
},
_,
) if param.contains(cached_decl) => Ok(()),
(TypeArg::Type { ty }, TypeParam::Type { b: bound })
if bound.contains(ty.least_upper_bound()) =>
{
Ok(())
}
(TypeArg::Sequence { elems }, TypeParam::List { param }) => {
elems.iter().try_for_each(|arg| {
if let (TypeArg::Variable { v }, TypeParam::Type { b: param_bound }) =
(arg, &**param)
{
if v.bound_if_row_var()
.is_some_and(|arg_bound| param_bound.contains(arg_bound))
{
return Ok(());
}
}
check_type_arg(arg, param)
})
}
(TypeArg::Sequence { elems: items }, TypeParam::Tuple { params: types }) => {
if items.len() != types.len() {
Err(TypeArgError::WrongNumberTuple(items.len(), types.len()))
} else {
items
.iter()
.zip(types.iter())
.try_for_each(|(arg, param)| check_type_arg(arg, param))
}
}
(TypeArg::BoundedNat { n: val }, TypeParam::BoundedNat { bound })
if bound.valid_value(*val) =>
{
Ok(())
}
(TypeArg::String { .. }, TypeParam::String) => Ok(()),
(TypeArg::Extensions { .. }, TypeParam::Extensions) => Ok(()),
_ => Err(TypeArgError::TypeMismatch {
arg: arg.clone(),
param: param.clone(),
}),
}
}
pub fn check_type_args(args: &[TypeArg], params: &[TypeParam]) -> Result<(), TypeArgError> {
if args.len() != params.len() {
return Err(TypeArgError::WrongNumberArgs(args.len(), params.len()));
}
for (a, p) in args.iter().zip(params.iter()) {
check_type_arg(a, p)?;
}
Ok(())
}
#[derive(Clone, Debug, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum TypeArgError {
#[allow(missing_docs)]
#[error("Type argument {arg:?} does not fit declared parameter {param:?}")]
TypeMismatch { param: TypeParam, arg: TypeArg },
#[error("Wrong number of type arguments: {0} vs expected {1} declared type parameters")]
WrongNumberArgs(usize, usize),
#[error("Wrong number of type arguments to tuple parameter: {0} vs expected {1} declared type parameters")]
WrongNumberTuple(usize, usize),
#[error("Opaque type argument does not fit declared parameter type: {0:?}")]
OpaqueTypeMismatch(#[from] crate::types::CustomCheckFailure),
#[error("Invalid value of type argument")]
InvalidValue(TypeArg),
}
#[cfg(test)]
mod test {
use itertools::Itertools;
use super::{check_type_arg, Substitution, TypeArg, TypeParam};
use crate::extension::prelude::{BOOL_T, PRELUDE_REGISTRY, USIZE_T};
use crate::types::{type_param::TypeArgError, TypeBound, TypeRV};
#[test]
fn type_arg_fits_param() {
let rowvar = TypeRV::new_row_var_use;
fn check(arg: impl Into<TypeArg>, param: &TypeParam) -> Result<(), TypeArgError> {
check_type_arg(&arg.into(), param)
}
fn check_seq<T: Clone + Into<TypeArg>>(
args: &[T],
param: &TypeParam,
) -> Result<(), TypeArgError> {
let arg = args.iter().cloned().map_into().collect_vec().into();
check_type_arg(&arg, param)
}
check(USIZE_T, &TypeBound::Copyable.into()).unwrap();
let seq_param = TypeParam::new_list(TypeBound::Copyable);
check(USIZE_T, &seq_param).unwrap_err();
check_seq(&[USIZE_T], &TypeBound::Any.into()).unwrap_err();
check(rowvar(0, TypeBound::Copyable), &seq_param).unwrap();
check(vec![], &seq_param).unwrap();
check_seq(&[rowvar(0, TypeBound::Copyable)], &seq_param).unwrap();
check_seq(
&[
rowvar(1, TypeBound::Any),
USIZE_T.into(),
rowvar(0, TypeBound::Copyable),
],
&TypeParam::new_list(TypeBound::Any),
)
.unwrap();
check_seq(
&[
rowvar(1, TypeBound::Any),
USIZE_T.into(),
rowvar(0, TypeBound::Copyable),
],
&seq_param,
)
.unwrap_err();
check(
vec![USIZE_T.into(), vec![USIZE_T.into()].into()],
&seq_param,
)
.unwrap_err();
check(5, &TypeParam::max_nat()).unwrap();
check_seq(&[5], &TypeParam::max_nat()).unwrap_err();
let list_of_nat = TypeParam::new_list(TypeParam::max_nat());
check(5, &list_of_nat).unwrap_err();
check_seq(&[5], &list_of_nat).unwrap();
check(TypeArg::new_var_use(0, list_of_nat.clone()), &list_of_nat).unwrap();
check(
vec![5.into(), TypeArg::new_var_use(0, list_of_nat.clone())],
&list_of_nat,
)
.unwrap_err();
let usize_and_ty = TypeParam::Tuple {
params: vec![TypeParam::max_nat(), TypeBound::Copyable.into()],
};
check(vec![5.into(), USIZE_T.into()], &usize_and_ty).unwrap();
check(vec![USIZE_T.into(), 5.into()], &usize_and_ty).unwrap_err(); let two_types = TypeParam::Tuple {
params: vec![TypeBound::Any.into(), TypeBound::Any.into()],
};
check(TypeArg::new_var_use(0, two_types.clone()), &two_types).unwrap();
check(TypeArg::new_var_use(0, seq_param), &two_types).unwrap_err();
}
#[test]
fn type_arg_subst_row() {
let row_param = TypeParam::new_list(TypeBound::Copyable);
let row_arg: TypeArg = vec![BOOL_T.into(), TypeArg::UNIT].into();
check_type_arg(&row_arg, &row_param).unwrap();
let outer_param = TypeParam::new_list(TypeBound::Any);
let outer_arg = TypeArg::Sequence {
elems: vec![
TypeRV::new_row_var_use(0, TypeBound::Copyable).into(),
USIZE_T.into(),
],
};
check_type_arg(&outer_arg, &outer_param).unwrap();
let outer_arg2 = outer_arg.substitute(&Substitution(&[row_arg], &PRELUDE_REGISTRY));
assert_eq!(
outer_arg2,
vec![BOOL_T.into(), TypeArg::UNIT, USIZE_T.into()].into()
);
check_type_arg(&outer_arg2, &outer_param).unwrap();
}
#[test]
fn subst_list_list() {
let outer_param = TypeParam::new_list(TypeParam::new_list(TypeBound::Any));
let row_var_decl = TypeParam::new_list(TypeBound::Copyable);
let row_var_use = TypeArg::new_var_use(0, row_var_decl.clone());
let good_arg = TypeArg::Sequence {
elems: vec![
vec![USIZE_T.into()].into(),
row_var_use.clone(),
vec![row_var_use, USIZE_T.into()].into(),
],
};
check_type_arg(&good_arg, &outer_param).unwrap();
let TypeArg::Sequence { mut elems } = good_arg.clone() else {
panic!()
};
elems.push(USIZE_T.into());
assert_eq!(
check_type_arg(&TypeArg::Sequence { elems }, &outer_param),
Err(TypeArgError::TypeMismatch {
arg: USIZE_T.into(),
param: TypeParam::new_list(TypeBound::Any)
})
);
let row_var_arg = vec![USIZE_T.into(), BOOL_T.into()].into();
check_type_arg(&row_var_arg, &row_var_decl).unwrap();
let subst_arg =
good_arg.substitute(&Substitution(&[row_var_arg.clone()], &PRELUDE_REGISTRY));
check_type_arg(&subst_arg, &outer_param).unwrap(); assert_eq!(
subst_arg,
TypeArg::Sequence {
elems: vec![
vec![USIZE_T.into()].into(),
row_var_arg,
vec![USIZE_T.into(), BOOL_T.into(), USIZE_T.into()].into()
]
}
);
}
mod proptest {
use proptest::prelude::*;
use super::super::{TypeArg, TypeArgVariable, TypeParam, UpperBound};
use crate::extension::ExtensionSet;
use crate::proptest::RecursionDepth;
use crate::types::{Type, TypeBound};
impl Arbitrary for TypeArgVariable {
type Parameters = RecursionDepth;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy {
(any::<usize>(), any_with::<TypeParam>(depth))
.prop_map(|(idx, cached_decl)| Self { idx, cached_decl })
.boxed()
}
}
impl Arbitrary for TypeParam {
type Parameters = RecursionDepth;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy {
use prop::collection::vec;
use prop::strategy::Union;
let mut strat = Union::new([
Just(Self::Extensions).boxed(),
Just(Self::String).boxed(),
any::<TypeBound>().prop_map(|b| Self::Type { b }).boxed(),
any::<UpperBound>()
.prop_map(|bound| Self::BoundedNat { bound })
.boxed(),
]);
if !depth.leaf() {
strat = strat
.or(any_with::<Self>(depth.descend())
.prop_map(|x| Self::List { param: Box::new(x) })
.boxed())
.or(vec(any_with::<Self>(depth.descend()), 0..3)
.prop_map(|params| Self::Tuple { params })
.boxed())
}
strat.boxed()
}
}
impl Arbitrary for TypeArg {
type Parameters = RecursionDepth;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy {
use prop::collection::vec;
use prop::strategy::Union;
let mut strat = Union::new([
any::<u64>().prop_map(|n| Self::BoundedNat { n }).boxed(),
any::<String>().prop_map(|arg| Self::String { arg }).boxed(),
any::<ExtensionSet>()
.prop_map(|es| Self::Extensions { es })
.boxed(),
any_with::<Type>(depth)
.prop_map(|ty| Self::Type { ty })
.boxed(),
any_with::<TypeArgVariable>(depth)
.prop_map(|v| Self::Variable { v })
.boxed(),
]);
if !depth.leaf() {
strat = strat.or(vec(any_with::<Self>(depth.descend()), 0..3)
.prop_map(|elems| Self::Sequence { elems })
.boxed());
}
strat.boxed()
}
}
}
}