use quote::ToTokens;
use crate::apply::{Apply, check_expand_limit, err_ty, err_ty_at};
use crate::ast::*;
use crate::parse::parse_primitive;
use proc_macro2::Span;
pub(crate) fn map_range(
start: usize, end: usize, inclusive: bool, span: Span, f: impl Fn(usize) -> Ty,
) -> Ty {
let end_mark = if inclusive { "=" } else { "" };
let ns: Vec<_> =
if inclusive { (start..=end).collect() } else { (start..end).collect() };
if ns.is_empty() {
return err_ty_at(
&format!(
"batch-impl: range `{}..{}{}` is empty (start not below end); no impls will be generated",
start, end, end_mark
),
span,
);
}
if let Some(e) = check_expand_limit(
&format!("range `{}..{}{}`", start, end, end_mark),
ns.len(),
) {
return e;
}
TyArray(ns.into_iter().map(f).collect()).into()
}
fn tuple_pow(mut elems: Vec<Ty>, n: usize) -> Ty {
if let Some(e) = check_expand_limit(&format!("tuple `^{}`", n), n) {
return e;
}
match elems.len() {
0 => pow_empty(n),
1 => pow_single(elems.remove(0), n),
_ => pow_cartesian(elems, n),
}
}
fn pow_empty(n: usize) -> Ty {
if n == 0 {
return TyTuple(vec![]).into();
}
let params = fresh_params(n);
let param_names = params.iter().map(|p| p.to_token_stream()).collect::<Vec<_>>();
let tp: Ty = TyTypeParam {
params: param_names.into_iter().map(|n| (n, None)).collect(),
bindings: vec![],
}
.into();
tp.apply(TyTuple(params).into())
}
fn pow_single(template: Ty, n: usize) -> Ty {
let template_span = template.span;
if let TyKind::TypeParam(tp) = template.kind.clone() {
if tp.params.len() != 1 || tp.params[0].1.is_some() {
return err_ty(
"batch-impl: unexpected bound parameter in (<Trait>)⁁; this is an internal error",
);
}
let params = fresh_params(n);
let param_names =
params.iter().map(|p| p.to_token_stream()).collect::<Vec<_>>();
let bound_tokens = tp.params[0].0.clone().into_iter().collect::<Vec<_>>();
return Ty::new(
template_span,
TyKind::TypeParam(TyTypeParam {
params: param_names
.into_iter()
.map(|n| (n, parse_primitive(&bound_tokens, None).into()))
.collect(),
bindings: vec![],
}),
)
.apply(TyTuple(params).into());
}
TyTuple((0..n).map(|_| Ty::new(template_span, template.kind.clone())).collect())
.into()
}
fn pow_cartesian(elems: Vec<Ty>, n: usize) -> Ty {
let mut combos = vec![vec![]];
for _ in 0..n {
let mut next = vec![];
for existing in &combos {
for elem in &elems {
let mut extended = existing.clone();
extended.push(elem.clone());
next.push(extended);
}
}
if let Some(e) = check_expand_limit("tuple Cartesian product", next.len()) {
return e;
}
combos = next;
}
TyArray(combos.into_iter().map(instantiate_combo).collect()).into()
}
fn instantiate_combo(elems: Vec<Ty>) -> Ty {
let mut tuple_elems = vec![];
let mut param_decls = vec![];
for elem in elems {
let elem_span = elem.span;
match elem.kind {
TyKind::TypeParam(tp) => {
let name = fresh_param();
let params = tp
.params
.iter()
.map(|(_, bound)| (name.clone(), bound.clone()))
.collect();
param_decls.push(TyTypeParam { params, bindings: vec![] });
tuple_elems
.push(Ty::new(elem_span, TyKind::Primitive(TyPrimitive(name))));
}
_ => tuple_elems.push(Ty::new(elem_span, elem.kind)),
}
}
let tuple = TyTuple(tuple_elems).into();
if param_decls.is_empty() {
return tuple;
}
let mut merged = TyTypeParam { params: vec![], bindings: vec![] };
for tp in param_decls {
merged.extend(tp);
}
Ty::new(Span::call_site(), TyKind::TypeParam(merged)).apply(tuple)
}
fn fresh_params(n: usize) -> Vec<Ty> {
(0..n).map(|_| TyPrimitive(fresh_param()).into()).collect()
}
impl Apply for TyTuple {
fn apply_help(mut self, o: Ty, span: Span) -> Ty {
let o_span = o.span;
match o.kind {
TyKind::Num(TyNum(n)) => tuple_pow(self.0, n),
_ => {
self.0.push(Ty::new(o_span, o.kind));
Ty::new(span, TyKind::Tuple(self))
}
}
}
}
impl Apply for TyGroup {
fn apply_help(self, o: Ty, span: Span) -> Ty {
let o_span = o.span;
match o.kind {
TyKind::Num(TyNum(n)) => {
tuple_pow(vec![Ty::new(span, TyKind::Group(self))], n)
}
_ => self.0.apply(Ty::new(o_span, o.kind)),
}
}
}
impl Apply for TyFn {
fn apply_help(self, o: Ty, span: Span) -> Ty {
let o_span = o.span;
match self {
TyFn(None, None, is_unsafe) => match o.kind {
TyKind::Tuple(t) => {
Ty::new(span, TyKind::Fn(TyFn(t.0.into(), None, is_unsafe)))
}
_ => err_ty_at(
"batch-impl: the right side of the `fn` prefix must be a tuple type, e.g. fn^(i32, u32)",
span,
),
},
TyFn(Some(params), None, is_unsafe) => Ty::new(
span,
TyKind::Fn(TyFn(
params.into(),
Ty::new(o_span, o.kind).into(),
is_unsafe,
)),
),
TyFn(Some(_), Some(_), _) => err_ty_at(
"batch-impl: the `fn` type already has a return type; cannot apply again",
span,
),
TyFn(None, Some(_), _) => err_ty_at(
"batch-impl: the `fn` type is missing a parameter list; internal error",
span,
),
}
}
}
impl Apply for TyWithAttr {
fn apply_help(self, o: Ty, span: Span) -> Ty {
Ty::new(span, TyKind::WithAttr(TyWithAttr(self.0, o.into())))
}
}
impl Apply for TyTypeParam {
fn apply_help(self, o: Ty, span: Span) -> Ty {
Ty::new(span, TyKind::WithType(TyWithType(self, o.into())))
}
}
impl Apply for TyNum {
fn apply_help(self, _: Ty, span: Span) -> Ty {
err_ty_at(
&format!(
"batch-impl: number `{}` cannot be a left operand; use it on the right (e.g. T^{})",
self.0, self.0
),
span,
)
}
}
impl Apply for TyRange {
fn apply_help(self, _: Ty, span: Span) -> Ty {
let end_mark = if self.inclusive { "=" } else { "" };
err_ty_at(
&format!(
"batch-impl: range `{}..{}{}` cannot be a left operand; it goes on the right (e.g. T^{}..{}{})",
self.start, self.end, end_mark, self.start, self.end, end_mark
),
span,
)
}
}
impl Apply for TyPrimitiveArray {
fn apply_help(self, o: Ty, span: Span) -> Ty {
match (self.0, self.1) {
(None, None) => Ty::new(
span,
TyKind::PrimitiveArray(TyPrimitiveArray(o.into(), None)),
),
(Some(elem), None) => Ty::new(
span,
TyKind::PrimitiveArray(TyPrimitiveArray(
elem.into(),
o.to_token_stream().into(),
)),
),
_ => err_ty_at(
"batch-impl: fixed-size array `[T; N]` cannot be a left operand",
span,
),
}
}
}
macro_rules! impl_apply_optional_inner {
($ty:ident, $variant:ident) => {
impl Apply for $ty {
fn apply_help(self, o: Ty, span: Span) -> Ty {
let inner = match self.0 {
Some(t) => t.apply(o),
None => o,
};
Ty::new(span, TyKind::$variant($ty(inner.into(), self.1)))
}
}
};
}
macro_rules! impl_apply_inner {
($ty:ident, $variant:ident) => {
impl Apply for $ty {
fn apply_help(self, o: Ty, span: Span) -> Ty {
Ty::new(span, TyKind::$variant($ty(self.0, self.1.apply(o).into())))
}
}
};
}
impl_apply_inner!(TyWithTrait, WithTrait);
impl_apply_inner!(TyWithType, WithType);
impl_apply_optional_inner!(TyWithCode, WithCode);
impl_apply_optional_inner!(TyWithWhere, WithWhere);