use std::collections::HashMap;
use zngur_def::{
Merge, RustPathAndGenerics, RustTrait, RustType, TypeVar, ZngurConstructor, ZngurField,
ZngurMethod, ZngurMethodDetails, ZngurType, ZngurVariant,
};
fn matches_template<'a, 'b>(
ty: &'a RustType,
generic: &'b RustType,
mapping: &mut HashMap<&'b TypeVar, &'a RustType>,
) -> bool {
fn match_lists<'a, 'b>(
v1: &'a [RustType],
v2: &'b [RustType],
mapping: &mut HashMap<&'b TypeVar, &'a RustType>,
) -> bool {
v1.len() == v2.len() && match_iters(v1, v2, mapping)
}
fn match_iters<'a, 'b>(
i1: impl IntoIterator<Item = &'a RustType>,
i2: impl IntoIterator<Item = &'b RustType>,
mapping: &mut HashMap<&'b TypeVar, &'a RustType>,
) -> bool {
i1.into_iter()
.zip(i2)
.all(|(ty1, ty2)| matches_template(ty1, ty2, mapping))
}
fn match_generics<'a, 'b>(
t1: &'a RustPathAndGenerics,
t2: &'b RustPathAndGenerics,
mapping: &mut HashMap<&'b TypeVar, &'a RustType>,
) -> bool {
t1.path == t2.path
&& match_lists(&t1.generics, &t2.generics, mapping)
&& t1.named_generics.len() == t2.named_generics.len()
&& t1
.named_generics
.iter()
.zip(t2.named_generics.iter())
.all(|((n1, t1), (n2, t2))| n1 == n2 && matches_template(t1, t2, mapping))
}
fn match_trait<'a, 'b>(
t1: &'a RustTrait,
t2: &'b RustTrait,
mapping: &mut HashMap<&'b TypeVar, &'a RustType>,
) -> bool {
match (t1, t2) {
(RustTrait::Normal(t1), RustTrait::Normal(t2)) => match_generics(t1, t2, mapping),
(
RustTrait::Fn {
name: n1,
inputs: i1,
output: o1,
},
RustTrait::Fn {
name: n2,
inputs: i2,
output: o2,
},
) => n1 == n2 && match_lists(i1, i2, mapping) && matches_template(o1, o2, mapping),
(_, _) => false,
}
}
match (ty, generic) {
(ty, RustType::TypeVar(v)) => mapping
.insert(v, ty)
.map(|prev_binding| prev_binding == ty)
.unwrap_or(true),
(RustType::Primitive(p1), RustType::Primitive(p2)) => p1 == p2,
(RustType::Ref(m1, t1), RustType::Ref(m2, t2))
| (RustType::Raw(m1, t1), RustType::Raw(m2, t2)) => {
m1 == m2 && matches_template(t1, t2, mapping)
}
(RustType::Boxed(t1), RustType::Boxed(t2)) | (RustType::Slice(t1), RustType::Slice(t2)) => {
matches_template(t1, t2, mapping)
}
(RustType::Dyn(t1, b1), RustType::Dyn(t2, b2))
| (RustType::Impl(t1, b1), RustType::Impl(t2, b2)) => {
(b1 == b2) && match_trait(t1, t2, mapping)
}
(RustType::Tuple(tys1), RustType::Tuple(tys2)) => match_lists(tys1, tys2, mapping),
(RustType::Adt(adt1), RustType::Adt(adt2)) => match_generics(adt1, adt2, mapping),
(_, _) => false,
}
}
#[derive(Debug)]
enum SubstitutionError<'a> {
UnboundVar(&'a TypeVar),
}
fn substitute_vars<'a>(
ty: &'a RustType,
mapping: &HashMap<&TypeVar, &RustType>,
) -> Result<RustType, SubstitutionError<'a>> {
fn substitute_vec<'a>(
vec: &'a Vec<RustType>,
mapping: &HashMap<&TypeVar, &RustType>,
) -> Result<Vec<RustType>, &'a TypeVar> {
vec.iter().map(|ty| substitute_type(ty, mapping)).collect()
}
fn substitute_generics<'a>(
path_and_generics: &'a RustPathAndGenerics,
mapping: &HashMap<&TypeVar, &RustType>,
) -> Result<RustPathAndGenerics, &'a TypeVar> {
let RustPathAndGenerics {
path,
generics,
named_generics,
} = path_and_generics;
let result = RustPathAndGenerics {
path: path.clone(),
generics: substitute_vec(generics, mapping)?,
named_generics: named_generics
.iter()
.map(|(name, ty)| substitute_type(ty, mapping).map(|ty| (name.clone(), ty)))
.collect::<Result<_, _>>()?,
};
Ok(result)
}
fn substitute_trait<'a>(
rust_trait: &'a RustTrait,
mapping: &HashMap<&TypeVar, &RustType>,
) -> Result<RustTrait, &'a TypeVar> {
let result = match rust_trait {
RustTrait::Normal(path_and_generics) => {
RustTrait::Normal(substitute_generics(path_and_generics, mapping)?)
}
RustTrait::Fn {
name,
inputs,
output,
} => RustTrait::Fn {
name: name.clone(),
inputs: substitute_vec(inputs, mapping)?,
output: Box::new(substitute_type(output, mapping)?),
},
};
Ok(result)
}
fn substitute_type<'a>(
ty: &'a RustType,
mapping: &HashMap<&TypeVar, &RustType>,
) -> Result<RustType, &'a TypeVar> {
let ty = match ty {
RustType::TypeVar(v) => match mapping.get(v) {
Some(ty) => (*ty).clone(),
None => return Err(v),
},
p @ RustType::Primitive(_) => p.clone(),
RustType::Ref(m, t) => RustType::Ref(*m, Box::new(substitute_type(t, mapping)?)),
RustType::Raw(m, t) => RustType::Raw(*m, Box::new(substitute_type(t, mapping)?)),
RustType::Boxed(t) => RustType::Boxed(Box::new(substitute_type(t, mapping)?)),
RustType::Slice(t) => RustType::Slice(Box::new(substitute_type(t, mapping)?)),
RustType::Dyn(rust_trait, bounds) => {
RustType::Dyn(substitute_trait(rust_trait, mapping)?, bounds.clone())
}
RustType::Impl(rust_trait, bounds) => {
RustType::Impl(substitute_trait(rust_trait, mapping)?, bounds.clone())
}
RustType::Tuple(tys) => RustType::Tuple(substitute_vec(tys, mapping)?),
RustType::Adt(path_and_generics) => {
RustType::Adt(substitute_generics(path_and_generics, mapping)?)
}
};
Ok(ty)
}
substitute_type(ty, mapping).map_err(SubstitutionError::UnboundVar)
}
fn substitute_method_vars<'a>(
m: &'a ZngurMethodDetails,
mapping: &HashMap<&TypeVar, &RustType>,
) -> Result<ZngurMethodDetails, SubstitutionError<'a>> {
let ZngurMethodDetails {
data:
ZngurMethod {
name,
generics,
receiver,
inputs,
output,
is_safe,
},
use_path,
deref,
cpp_name,
} = m;
Ok(ZngurMethodDetails {
data: ZngurMethod {
name: name.clone(),
generics: generics
.iter()
.map(|ty| substitute_vars(ty, mapping))
.collect::<Result<_, _>>()?,
receiver: *receiver,
inputs: inputs
.iter()
.map(|ty| substitute_vars(ty, mapping))
.collect::<Result<_, _>>()?,
output: substitute_vars(output, mapping)?,
is_safe: *is_safe,
},
use_path: use_path.clone(),
deref: match deref {
Some((ty, mutability)) => Some((substitute_vars(&ty, mapping)?, *mutability)),
None => None,
},
cpp_name: cpp_name.clone(),
})
}
pub fn try_match_template(ty: &RustType, template: &ZngurType) -> Option<TemplateMatch> {
let mut mapping = HashMap::new();
if !matches_template(ty, &template.ty, &mut mapping) {
return None;
}
let ZngurType {
ty: template_ty,
layout,
wellknown_traits,
exhaustive,
methods,
constructor,
variants,
fields,
cpp_ref,
cpp_value,
cpp_stack_owned,
} = template;
debug_assert_eq!(substitute_vars(template_ty, &mapping).unwrap(), *ty);
let new_ty = ZngurType {
ty: ty.clone(),
layout: *layout,
wellknown_traits: wellknown_traits.clone(),
exhaustive: *exhaustive,
methods: methods
.iter()
.map(|method| match substitute_method_vars(method, &mapping) {
Ok(m) => m,
Err(SubstitutionError::UnboundVar(var)) => unreachable!(
"Unbound type variable {} in method {} in template {} for type {}",
var.0, method.data.name, template.ty, ty
),
})
.collect(),
constructor: constructor.as_ref().map(|constructor| {
match constructor
.inputs
.iter()
.map(|(name, ty)| substitute_vars(ty, &mapping).map(|ty| (name.clone(), ty)))
.collect()
{
Ok(inputs) => ZngurConstructor { inputs },
Err(SubstitutionError::UnboundVar(var)) => unreachable!(
"Unbound type variable {} in constructor in template {} for type {}",
var.0, template.ty, ty
),
}
}),
variants: variants.iter().map(|variant| {
let fields = variant.fields.iter().map(|field| match substitute_vars(&field.ty, &mapping) {
Ok(ty) => ZngurField {
name: field.name.clone(),
ty,
offset: field.offset,
},
Err(SubstitutionError::UnboundVar(var)) => unreachable!(
"Unbound type variable {} in field {} of variant {}, in template {} for type {}",
var.0, field.name, variant.name, template.ty, ty
),
}).collect();
ZngurVariant {
name: variant.name.clone(),
exhaustive: variant.exhaustive,
fields,
}
}).collect(),
fields: fields
.iter()
.map(|field| match substitute_vars(&field.ty, &mapping) {
Ok(ty) => ZngurField {
name: field.name.clone(),
ty,
offset: field.offset,
},
Err(SubstitutionError::UnboundVar(var)) => unreachable!(
"Unbound type variable {} in field {} in template {} for type {}",
var.0, field.name, template.ty, ty
),
})
.collect(),
cpp_value: cpp_value.clone(),
cpp_ref: cpp_ref.clone(),
cpp_stack_owned: cpp_stack_owned.clone(),
};
Some(TemplateMatch(new_ty))
}
pub struct TemplateMatch(ZngurType);
impl Merge<ZngurType> for TemplateMatch {
fn merge(self, into: &mut ZngurType) -> zngur_def::MergeResult {
let TemplateMatch(mut ty) = self;
if into.layout.is_some() {
ty.layout = None;
}
ty.merge(into)
}
}