#![forbid(unsafe_code)]
#![deny(missing_docs)]
extern crate proc_macro;
mod parse;
mod strata;
use proc_macro::TokenStream;
use proc_macro_error::{abort, emit_error, proc_macro_error};
use quote::{format_ident, quote, quote_spanned};
use std::collections::{HashMap, HashSet};
use std::fmt::{self, Display, Formatter};
use syn::{parse_macro_input, spanned::Spanned, Expr, Ident, Lifetime, Pat, Type};
use parse::{Clause, Fact, FactField, For, Program, Relation, RelationType, Rule};
use strata::Strata;
#[proc_macro]
#[proc_macro_error]
pub fn crepe(input: TokenStream) -> TokenStream {
let program = parse_macro_input!(input as Program);
let context = Context::new(program);
let struct_decls = make_struct_decls(&context);
let runtime_decl = make_runtime_decl(&context);
let runtime_impl = make_runtime_impl(&context);
let expanded = quote! {
#struct_decls
#runtime_decl
#runtime_impl
};
expanded.into()
}
type QuoteWrapper = dyn Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;
struct Context {
has_input_lifetime: bool,
rels_input: HashMap<String, Relation>,
rels_output: HashMap<String, Relation>,
output_order: Vec<Ident>,
rels_intermediate: HashMap<String, Relation>,
rules: Vec<Rule>,
strata: Strata,
}
impl Context {
pub fn new(program: Program) -> Self {
let mut rels_input = HashMap::new();
let mut rels_output = HashMap::new();
let mut rels_intermediate = HashMap::new();
let mut rel_names = HashSet::new();
let mut output_order = Vec::new();
let mut has_input_lifetime = false;
let mut has_output_lifetime = false;
program.relations.into_iter().for_each(|relation| {
let name = relation.name.to_string();
if !rel_names.insert(relation.name.clone()) {
abort!(relation.name.span(), "Duplicate relation name: {}", name);
}
validate_generic_params(&relation);
let num_lifetimes = relation.generics.lifetimes().count();
match relation.relation_type() {
Ok(RelationType::Input) => {
rels_input.insert(name, relation);
if num_lifetimes > 0 {
has_input_lifetime = true;
}
}
Ok(RelationType::Output) => {
output_order.push(relation.name.clone());
rels_output.insert(name, relation);
if num_lifetimes > 0 {
has_output_lifetime = true;
}
}
Ok(RelationType::Intermediate) => {
rels_intermediate.insert(name, relation);
}
Err(attr) => {
abort!(
attr.span(),
"Invalid attribute @{}, expected '@input' or '@output'",
attr
)
}
}
});
if has_output_lifetime && !has_input_lifetime {
for r in rels_output.values() {
if r.generics.lifetimes().next().is_some() {
emit_error!(
r.generics,
"Lifetime on output relation without any input relations having a lifetime"
);
}
}
}
let mut dependencies = HashSet::new();
let check = |fact: &Fact| {
let name = fact.relation.to_string();
if !rel_names.contains(&fact.relation) {
abort!(
fact.relation.span(),
"Relation name '{}' was not found. Did you misspell it?",
name
);
}
let rel = rels_input
.get(&name)
.or_else(|| rels_output.get(&name))
.or_else(|| rels_intermediate.get(&name))
.expect("relation should exist");
if rel.fields.len() != fact.fields.len() {
abort!(
fact.relation.span(),
"Relation '{}' was declared with arity {}, but constructed with arity {} here.",
name,
rel.fields.len(),
fact.fields.len(),
);
}
};
program.rules.iter().for_each(|rule| {
check(&rule.goal);
if rels_input.contains_key(&rule.goal.relation.to_string()) {
abort!(
rule.goal.relation.span(),
"Relations marked as @input cannot be derived from a rule."
)
}
for f in &rule.goal.fields {
match f {
FactField::Ignored(token) => {
abort!(token.span(), "Cannot have _ in goal atom of rule.")
}
FactField::Ref(token, _) => {
abort!(token.span(), "Cannot have `ref` in goal atom of rule.")
}
FactField::Expr(_) => (),
}
}
rule.clauses.iter().for_each(|clause| {
if let Clause::Fact(fact) = clause {
check(fact);
dependencies.insert((&rule.goal.relation, &fact.relation));
}
});
});
let strata = Strata::new(rel_names, dependencies);
for rule in &program.rules {
let goal_stratum = strata.find_relation(&rule.goal.relation);
for clause in &rule.clauses {
if let Clause::Fact(fact) = clause {
if fact.negate.is_some() {
let fact_stratum = strata.find_relation(&fact.relation);
if goal_stratum == fact_stratum {
abort!(
fact.relation.span(),
"Negation of relation '{}' creates a dependency cycle \
and cannot be stratified.",
fact.relation
);
}
assert!(goal_stratum > fact_stratum);
}
}
}
}
let rules = program.rules;
Self {
has_input_lifetime,
rels_input,
rels_output,
output_order,
rels_intermediate,
rules,
strata,
}
}
fn get_relation(&self, name: &str) -> Option<&Relation> {
self.rels_input
.get(name)
.or_else(|| self.rels_intermediate.get(name))
.or_else(|| self.rels_output.get(name))
}
fn all_relations(&self) -> impl Iterator<Item = &Relation> {
self.rels_input
.values()
.chain(self.rels_intermediate.values())
.chain(self.rels_output.values())
}
}
#[derive(Eq, PartialEq, Hash, Copy, Clone)]
enum IndexMode {
Bound,
Free,
}
#[derive(Eq, PartialEq, Hash, Clone)]
struct Index {
name: Ident,
mode: Vec<IndexMode>,
}
impl Index {
fn to_ident(&self) -> Ident {
Ident::new(&self.to_string(), self.name.span())
}
fn key_type<'a>(&self, context: &'a Context) -> Vec<&'a Type> {
let rel = context
.get_relation(&self.name.to_string())
.expect("could not find relation of index name");
self.mode
.iter()
.zip(rel.fields.iter())
.filter_map(|(mode, field)| match mode {
IndexMode::Bound => Some(&field.ty),
IndexMode::Free => None,
})
.collect()
}
fn bound_pos(&self) -> Vec<syn::Index> {
self.mode
.iter()
.enumerate()
.filter_map(|(i, mode)| match mode {
IndexMode::Bound => Some(syn::Index::from(i)),
IndexMode::Free => None,
})
.collect()
}
}
impl Display for Index {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mode: String = self
.mode
.iter()
.map(|mode| match mode {
IndexMode::Bound => 'b',
IndexMode::Free => 'f',
})
.collect();
write!(f, "__{}_index{}", to_lowercase(&self.name), mode)
}
}
fn make_struct_decls(context: &Context) -> proc_macro2::TokenStream {
context
.all_relations()
.map(|relation| {
let attrs = &relation.attrs;
let struct_token = &relation.struct_token;
let vis = &relation.visibility;
let name = &relation.name;
let generics = &relation.generics;
let semi_token = &relation.semi_token;
let fields = &relation.fields;
quote_spanned! {name.span()=>
#[derive(
::core::marker::Copy,
::core::clone::Clone,
::core::cmp::Eq,
::core::cmp::PartialEq,
::core::hash::Hash,
)]
#(#attrs)*
#vis #struct_token #name #generics (#fields)#semi_token
}
})
.collect()
}
fn make_runtime_decl(context: &Context) -> proc_macro2::TokenStream {
let fields: proc_macro2::TokenStream = context
.rels_input
.values()
.map(|relation| {
let rel_ty = relation_type(relation, LifetimeUsage::Item);
let lowercase_name = to_lowercase(&relation.name);
quote! {
#lowercase_name: ::std::vec::Vec<#rel_ty>,
}
})
.collect();
let generics_decl = generic_params_decl(context);
quote! {
#[derive(::core::default::Default)]
pub struct Crepe #generics_decl {
#fields
}
}
}
fn make_runtime_impl(context: &Context) -> proc_macro2::TokenStream {
let builders = make_extend(context);
let run = make_run(context);
let generics_decl = generic_params_decl(context);
let generics_args = generic_params_args(context);
quote! {
impl #generics_decl Crepe #generics_args {
pub fn new() -> Self {
::core::default::Default::default()
}
#run
}
#builders
}
}
fn make_extend(context: &Context) -> proc_macro2::TokenStream {
context
.rels_input
.values()
.map(|relation| {
let rel_ty = relation_type(relation, LifetimeUsage::Item);
let generics_decl = generic_params_decl(context);
let generics_args = generic_params_args(context);
let lower = to_lowercase(&relation.name);
let ref_impl_generics = {
let mut items = vec![quote! { 'a }];
for tp in collect_generic_params(context) {
items.push(merge_bounds_with_required(tp));
}
format_generics(items)
};
quote! {
impl #generics_decl ::core::iter::Extend<#rel_ty> for Crepe #generics_args {
fn extend<__I>(&mut self, iter: __I)
where
__I: ::core::iter::IntoIterator<Item = #rel_ty>,
{
self.#lower.extend(iter);
}
}
impl #ref_impl_generics ::core::iter::Extend<&'a #rel_ty> for Crepe #generics_args {
fn extend<__I>(&mut self, iter: __I)
where
__I: ::core::iter::IntoIterator<Item = &'a #rel_ty>,
{
self.extend(iter.into_iter().copied());
}
}
}
})
.collect()
}
fn make_run(context: &Context) -> proc_macro2::TokenStream {
let mut indices: HashSet<Index> = HashSet::new();
let main_loops = {
let mut loop_wrappers = Vec::new();
for stratum in context.strata.iter() {
loop_wrappers.push(make_stratum(context, stratum, &mut indices));
}
loop_wrappers
.iter()
.zip(context.strata.iter())
.map(|(f, stratum)| f(make_updates(context, stratum, &indices)))
.collect::<proc_macro2::TokenStream>()
};
let initialize = {
let init_rels = context.all_relations().map(|rel| {
let rel_ty = relation_type(rel, LifetimeUsage::Local);
let lower = to_lowercase(&rel.name);
let var = format_ident!("__{}", lower);
let var_update = format_ident!("__{}_update", lower);
quote! {
let mut #var: ::std::collections::HashSet<#rel_ty, CrepeHasher> =
::std::collections::HashSet::default();
let mut #var_update: ::std::collections::HashSet<#rel_ty, CrepeHasher> =
::std::collections::HashSet::default();
}
});
let init_indices = indices.iter().map(|index| {
let rel = context
.get_relation(&index.name.to_string())
.expect("index relation should be found in context");
let rel_ty = relation_type(rel, LifetimeUsage::Local);
let index_name = index.to_ident();
let key_type = index.key_type(context);
quote! {
let mut #index_name:
::std::collections::HashMap<(#(#key_type,)*), ::std::vec::Vec<#rel_ty>, CrepeHasher> =
::std::collections::HashMap::default();
}
});
let load_inputs = context.rels_input.values().map(|rel| {
let lower = to_lowercase(&rel.name);
let var_update = format_ident!("__{}_update", lower);
quote! {
#var_update.extend(self.#lower);
}
});
init_rels
.chain(init_indices)
.chain(load_inputs)
.collect::<proc_macro2::TokenStream>()
};
let output = {
let output_fields = context.output_order.iter().map(|name| {
let lower = to_lowercase(name);
format_ident!("__{}", lower)
});
quote! {
(#(#output_fields,)*)
}
};
let output_ty_hasher = make_output_ty(context, quote! { CrepeHasher });
let output_ty_default = make_output_ty(context, quote! {});
quote! {
#[allow(clippy::collapsible_if)]
pub fn run_with_hasher<CrepeHasher: ::std::hash::BuildHasher + ::core::default::Default>(
self
) -> #output_ty_hasher {
#initialize
#main_loops
#output
}
pub fn run(self) -> #output_ty_default {
self.run_with_hasher::<::std::collections::hash_map::RandomState>()
}
}
}
fn make_stratum(
context: &Context,
stratum: &[Ident],
indices: &mut HashSet<Index>,
) -> Box<QuoteWrapper> {
let stratum: HashSet<_> = stratum.iter().collect();
let current_rels: Vec<_> = stratum
.iter()
.map(|name| {
context
.get_relation(&name.to_string())
.expect("cannot find relation from stratum")
})
.collect();
let empty_cond: proc_macro2::TokenStream = current_rels
.iter()
.map(|rel| {
let lower = to_lowercase(&rel.name);
let rel_update = format_ident!("__{}_update", lower);
quote! {
#rel_update.is_empty() &&
}
})
.chain(std::iter::once(quote! {true}))
.collect();
let new_decls: proc_macro2::TokenStream = current_rels
.iter()
.map(|rel| {
let rel_ty = relation_type(rel, LifetimeUsage::Local);
let lower = to_lowercase(&rel.name);
let rel_new = format_ident!("__{}_new", lower);
quote! {
let mut #rel_new: ::std::collections::HashSet<#rel_ty, CrepeHasher> =
::std::collections::HashSet::default();
}
})
.collect();
let rules: proc_macro2::TokenStream = context
.rules
.iter()
.filter(|rule| stratum.contains(&rule.goal.relation))
.map(|rule| make_rule(rule, &stratum, indices))
.collect();
let set_update_to_new: proc_macro2::TokenStream = current_rels
.iter()
.map(|rel| {
let lower = to_lowercase(&rel.name);
let rel_update = format_ident!("__{}_update", lower);
let rel_new = format_ident!("__{}_new", lower);
quote! {
#rel_update = #rel_new;
}
})
.collect();
Box::new(move |updates| {
quote! {
let mut __crepe_first_iteration = true;
while __crepe_first_iteration || !(#empty_cond) {
#updates
#new_decls
#rules
#set_update_to_new
__crepe_first_iteration = false;
}
}
})
}
fn make_updates(
context: &Context,
stratum: &[Ident],
indices: &HashSet<Index>,
) -> proc_macro2::TokenStream {
let rel_updates = stratum.iter().map(|name| {
let lower = to_lowercase(name);
let rel = format_ident!("__{}", lower);
let rel_update = format_ident!("__{}_update", lower);
quote! {
#rel.extend(&#rel_update);
}
});
let index_updates = indices.iter().filter_map(|index| {
if !stratum.contains(&index.name) {
return None;
}
let rel = context
.get_relation(&index.name.to_string())
.expect("index relation should be found in context");
let rel_ty = relation_type(rel, LifetimeUsage::Local);
let rel_update = format_ident!("__{}_update", to_lowercase(&rel.name));
let index_name = index.to_ident();
let index_name_update = format_ident!("{}_update", index_name);
let key_type = index.key_type(context);
let bound_pos = index.bound_pos();
Some(quote! {
let mut #index_name_update: ::std::collections::HashMap<
(#(#key_type,)*),
::std::vec::Vec<#rel_ty>,
CrepeHasher
> = ::std::collections::HashMap::default();
for __crepe_var in &#rel_update {
#index_name
.entry((#(__crepe_var.#bound_pos,)*))
.or_default()
.push(*__crepe_var);
#index_name_update
.entry((#(__crepe_var.#bound_pos,)*))
.or_default()
.push(*__crepe_var);
}
})
});
rel_updates.chain(index_updates).collect()
}
fn make_rule(
rule: &Rule,
stratum: &HashSet<&Ident>,
indices: &mut HashSet<Index>,
) -> proc_macro2::TokenStream {
let goal = {
let relation = &rule.goal.relation;
let fields = &rule.goal.fields;
let name = format_ident!("__{}", to_lowercase(relation));
let name_new = format_ident!("__{}_new", to_lowercase(relation));
quote! {
let __crepe_goal = #relation(#fields);
if !#name.contains(&__crepe_goal) {
#name_new.insert(__crepe_goal);
}
}
};
let fact_positions: Vec<_> = rule
.clauses
.iter()
.enumerate()
.filter_map(|(i, clause)| match clause {
Clause::Fact(fact) => {
if stratum.contains(&fact.relation) {
Some(i)
} else {
None
}
}
_ => None,
})
.collect();
if fact_positions.is_empty() {
let mut datalog_vars: HashSet<String> = HashSet::new();
#[allow(clippy::needless_collect)]
let fragments: Vec<_> = rule
.clauses
.iter()
.cloned()
.map(|clause| make_clause(clause, false, &mut datalog_vars, indices))
.collect();
let eval_loop = fragments.into_iter().rev().fold(goal, |x, f| f(x));
quote! {
if __crepe_first_iteration {
#eval_loop
}
}
} else {
let mut variants = Vec::new();
for update_position in fact_positions {
let mut datalog_vars: HashSet<String> = HashSet::new();
#[allow(clippy::needless_collect)]
let fragments: Vec<_> = rule
.clauses
.iter()
.cloned()
.enumerate()
.map(|(i, clause)| {
make_clause(clause, update_position == i, &mut datalog_vars, indices)
})
.collect();
let eval_loop = fragments.into_iter().rev().fold(goal.clone(), |x, f| f(x));
variants.push(eval_loop);
}
variants.into_iter().collect()
}
}
fn make_clause(
clause: Clause,
only_update: bool,
datalog_vars: &mut HashSet<String>,
indices: &mut HashSet<Index>,
) -> Box<QuoteWrapper> {
match clause {
Clause::Fact(fact) => {
let name = &fact.relation;
if fact.negate.is_some() {
assert!(!only_update);
let to_mode = |f: &FactField| match f {
FactField::Ignored(_) => IndexMode::Free,
FactField::Ref(_, ident) => {
abort!(ident, "Unable to bind values in negated clause")
}
FactField::Expr(_) => IndexMode::Bound,
};
let index = Index {
name: name.clone(),
mode: fact.fields.iter().map(to_mode).collect(),
};
let index_name = index.to_ident();
indices.insert(index);
let bound_fields: Vec<_> = fact
.fields
.iter()
.filter(|t| matches!(t, FactField::Expr(_)))
.cloned()
.collect();
return Box::new(move |body| {
quote_spanned! {fact.relation.span()=>
if !#index_name.contains_key(&(#(#bound_fields,)*)) {
#body
}
}
});
}
let mut setters = Vec::new();
let mut index_mode = Vec::new();
for (i, field) in fact.fields.iter().enumerate() {
let idx = syn::Index::from(i);
match field {
FactField::Ignored(_) => index_mode.push(IndexMode::Free),
FactField::Ref(_, ident) => {
index_mode.push(IndexMode::Free);
datalog_vars.insert(ident.to_string());
setters.push(quote! {
let #ident = &__crepe_var.#idx;
});
}
FactField::Expr(expr) => {
if let Some(var) = is_datalog_var(expr) {
let var_name = var.to_string();
if datalog_vars.contains(&var_name) {
index_mode.push(IndexMode::Bound);
} else {
index_mode.push(IndexMode::Free);
datalog_vars.insert(var_name);
setters.push(quote! {
let #field = __crepe_var.#idx;
});
}
} else {
index_mode.push(IndexMode::Bound);
}
}
}
}
let setters: proc_macro2::TokenStream = setters.into_iter().collect();
if !index_mode.contains(&IndexMode::Bound) {
let mut rel = format_ident!("__{}", &to_lowercase(name));
if only_update {
rel = format_ident!("{}_update", rel);
}
Box::new(move |body| {
quote_spanned! {fact.relation.span()=>
for __crepe_var in &#rel {
#setters
#body
}
}
})
} else {
let bound_fields: Vec<_> = index_mode
.iter()
.zip(fact.fields.iter())
.filter_map(|(mode, field)| match mode {
IndexMode::Bound => Some(field.clone()),
IndexMode::Free => None,
})
.collect();
let index = Index {
name: name.clone(),
mode: index_mode,
};
let mut index_name = Ident::new(&index.to_string(), name.span());
if only_update {
index_name = format_ident!("{}_update", index_name);
}
indices.insert(index);
Box::new(move |body| {
quote_spanned! {fact.relation.span()=>
if let Some(__crepe_iter) = #index_name.get(&(#(#bound_fields,)*)) {
for __crepe_var in __crepe_iter {
#setters
#body
}
}
}
})
}
}
Clause::Expr(expr) => {
assert!(!only_update);
Box::new(move |body| {
quote! {
#[allow(unused_parens)]
if #expr { #body }
}
})
}
Clause::Let(guard) => {
assert!(!only_update);
pat_datalog_vars(&guard.pat, datalog_vars);
Box::new(move |body| {
quote! {
#[allow(irrefutable_let_patterns)]
if #guard { #body }
}
})
}
Clause::For(For { pat, expr, .. }) => {
assert!(!only_update);
Box::new(move |body| {
quote! {
for #pat in #expr { #body }
}
})
}
}
}
fn make_output_ty(context: &Context, hasher: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let fields = context.output_order.iter().map(|name| {
let rel = context.rels_output.get(&name.to_string()).unwrap();
relation_type(rel, LifetimeUsage::Item)
});
quote! {
(#(::std::collections::HashSet<#fields, #hasher>,)*)
}
}
fn is_datalog_var(expr: &Expr) -> Option<Ident> {
use syn::{ExprPath, Path, PathArguments};
match expr {
Expr::Path(ExprPath {
attrs,
qself: None,
path:
Path {
leading_colon: None,
segments,
},
}) => {
if attrs.is_empty() && segments.len() == 1 {
let segment = segments.iter().next()?;
if let PathArguments::None = segment.arguments {
let ident = segment.ident.clone();
match ident.to_string().chars().next() {
Some('a'..='z') | Some('_') => return Some(ident),
_ => (),
}
}
}
None
}
_ => None,
}
}
fn pat_datalog_vars(pat: &Pat, datalog_vars: &mut HashSet<String>) {
match pat {
Pat::Const(_) => (),
Pat::Ident(pi) => {
datalog_vars.insert(pi.ident.to_string());
if let Some((_, ref p)) = pi.subpat {
pat_datalog_vars(p, datalog_vars);
}
}
Pat::Lit(_) => (),
Pat::Macro(pm) => abort!(pm.span(), "Macros not allowed in let bindings."),
Pat::Or(_) => (),
Pat::Paren(pp) => pat_datalog_vars(&pp.pat, datalog_vars),
Pat::Path(_) => (),
Pat::Range(_) => (),
Pat::Reference(pr) => pat_datalog_vars(&pr.pat, datalog_vars),
Pat::Rest(_) => (),
Pat::Slice(ps) => {
for e in &ps.elems {
pat_datalog_vars(e, datalog_vars);
}
}
Pat::Struct(ps) => {
for field_pat in &ps.fields {
pat_datalog_vars(&field_pat.pat, datalog_vars);
}
}
Pat::Tuple(pt) => {
for e in &pt.elems {
pat_datalog_vars(e, datalog_vars);
}
}
Pat::TupleStruct(pts) => {
for e in &pts.elems {
pat_datalog_vars(e, datalog_vars);
}
}
Pat::Type(pt) => pat_datalog_vars(&pt.pat, datalog_vars),
Pat::Verbatim(pv) => abort!(pv.span(), "Cannot parse verbatim pattern."),
Pat::Wild(_) => (),
_ => abort!(pat.span(), "Unsupported pattern type."),
}
}
fn to_lowercase(name: &Ident) -> Ident {
let s = name.to_string().to_lowercase();
Ident::new(&s, name.span())
}
fn validate_generic_params(relation: &Relation) {
if let Some(c) = relation.generics.const_params().next() {
abort!(
c.span(),
"Const parameters are not yet supported in relations"
);
}
if let Some(where_clause) = &relation.generics.where_clause {
abort!(
where_clause.where_token.span(),
"Where clauses are not yet supported in relations. \
Please specify trait bounds directly on the type parameter instead, e.g., `T: Trait`"
);
}
for type_param in relation.generics.type_params() {
if type_param.default.is_some() {
abort!(
type_param.ident.span(),
"Default type parameters are not supported in relations. \
Please remove the default value from type parameter `{}`",
type_param.ident
);
}
}
for lifetime_param in relation.generics.lifetimes() {
if !lifetime_param.bounds.is_empty() {
abort!(
lifetime_param.lifetime.span(),
"Lifetime bounds are not supported in relations. \
Please remove bounds from lifetime parameter `{}`",
lifetime_param.lifetime
);
}
}
}
fn collect_generic_params(context: &Context) -> Vec<&syn::TypeParam> {
let mut seen = HashSet::new();
let mut params = Vec::new();
for relation in context.rels_input.values() {
for param in relation.generics.type_params() {
if seen.insert(param.ident.to_string()) {
params.push(param);
}
}
}
params
}
fn has_bound(tp: &syn::TypeParam, bound_name: &str) -> bool {
tp.bounds.iter().any(|b| match b {
syn::TypeParamBound::Trait(trait_bound) => trait_bound
.path
.segments
.last()
.is_some_and(|seg| seg.ident == bound_name),
_ => false,
})
}
const REQUIRED_BOUNDS: &[&str] = &["Hash", "Eq", "Clone", "Copy", "Default"];
fn required_bound_token(name: &str) -> proc_macro2::TokenStream {
match name {
"Hash" => quote! { ::core::hash::Hash },
"Eq" => quote! { ::std::cmp::Eq },
"Clone" => quote! { ::std::clone::Clone },
"Copy" => quote! { ::std::marker::Copy },
"Default" => quote! { ::std::default::Default },
_ => panic!("Unknown required bound: {}", name),
}
}
fn merge_bounds_with_required(tp: &syn::TypeParam) -> proc_macro2::TokenStream {
let ident = &tp.ident;
let user_bounds = &tp.bounds;
let missing_bounds: Vec<_> = REQUIRED_BOUNDS
.iter()
.filter(|&req| !has_bound(tp, req))
.map(|req| required_bound_token(req))
.collect();
match (user_bounds.is_empty(), missing_bounds.is_empty()) {
(true, true) => quote! { #ident }, (true, false) => quote! { #ident: #(#missing_bounds)+* },
(false, true) => quote! { #ident: #user_bounds },
(false, false) => quote! { #ident: #user_bounds + #(#missing_bounds)+* },
}
}
fn format_generics(items: Vec<proc_macro2::TokenStream>) -> proc_macro2::TokenStream {
if items.is_empty() {
quote! {}
} else {
quote! { <#(#items),*> }
}
}
fn generic_params_decl(context: &Context) -> proc_macro2::TokenStream {
let mut items = Vec::new();
if context.has_input_lifetime {
items.push(quote! { 'a });
}
items.extend(
collect_generic_params(context)
.into_iter()
.map(merge_bounds_with_required),
);
format_generics(items)
}
fn generic_params_args(context: &Context) -> proc_macro2::TokenStream {
let mut items = Vec::new();
if context.has_input_lifetime {
items.push(quote! { 'a });
}
items.extend(collect_generic_params(context).into_iter().map(|tp| {
let ident = &tp.ident;
quote! { #ident }
}));
format_generics(items)
}
enum LifetimeUsage {
Item,
Local,
}
fn relation_type(rel: &Relation, usage: LifetimeUsage) -> proc_macro2::TokenStream {
let symbol = match rel.relation_type().unwrap() {
RelationType::Input | RelationType::Output => "'a",
RelationType::Intermediate => match usage {
LifetimeUsage::Item => "'a",
LifetimeUsage::Local => "'_",
},
};
let name = &rel.name;
let mut items = Vec::new();
items.extend(rel.generics.lifetimes().map(|l| {
let lifetime = Lifetime::new(symbol, l.span());
quote! { #lifetime }
}));
items.extend(rel.generics.type_params().map(|tp| {
let ident = &tp.ident;
quote! { #ident }
}));
if items.is_empty() {
quote! { #name }
} else {
quote! { #name<#(#items),*> }
}
}