Skip to content

Commit

Permalink
generate main trait impl from main trait
Browse files Browse the repository at this point in the history
  • Loading branch information
mversic committed Oct 8, 2023
1 parent 1175dda commit e26bf26
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 64 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "disjoint_impls"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/mversic/disjoint_impls"
Expand Down
159 changes: 115 additions & 44 deletions src/main_trait.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Contains logic related to generating impl of the main trait

use rustc_hash::FxHashSet;
use syn::visit_mut::VisitMut;
use syn::{parse_quote, visit_mut::VisitMut};

use super::*;

Expand All @@ -16,67 +16,138 @@ struct ImplItemResolver {

/// Generate main trait impl
pub fn gen(main_trait: Option<&ItemTrait>, impls: &[ItemImpl]) -> Option<ItemImpl> {
let Some(example_impl) = impls.get(0).cloned() else {
return None;
};
let example_impl = impls.get(0)?;
let self_ty = &example_impl.self_ty;

let helper_trait_ident = main_trait.as_ref().map_or_else(
let mut main_item_impl = main_trait
.map(|main_trait| gen_dummy_impl_from_trait_definition(main_trait, self_ty))
.unwrap_or_else(|| gen_dummy_impl_from_inherent_impl(example_impl, self_ty));

let helper_trait_ident = main_item_impl.trait_.as_ref().map_or_else(
|| {
if let syn::Type::Path(type_path) = &*example_impl.self_ty {
if let Some(last_seg) = type_path.path.segments.last() {
return Some(helper_trait::gen_ident(&last_seg.ident));
}
return Some(helper_trait::gen_ident(
&type_path.path.segments.last()?.ident,
));
}

None
},
|main_trait| Some(helper_trait::gen_ident(&main_trait.ident)),
|main_trait| {
Some(helper_trait::gen_ident(
&main_trait.1.segments.last()?.ident,
))
},
)?;

let ItemImpl {
defaultness,
unsafety,
trait_,
self_ty,
..
} = example_impl;

let AssocBounds {
type_param_idents, ..
} = AssocBounds::find(impls);

let mut generics_resolver =
GenericsResolver::new(main_trait, &helper_trait_ident, &type_param_idents);

let attrs = main_trait
.map(|trait_| &trait_.attrs)
.unwrap_or(&example_impl.attrs);
let mut generics = example_impl.generics;
let mut items = example_impl.items;

generics_resolver.visit_generics_mut(&mut generics);
let trait_ = trait_.as_ref().map(|trait_| &trait_.1);
main_item_impl.generics = example_impl.generics.clone();
generics_resolver.visit_generics_mut(&mut main_item_impl.generics);

let mut impl_item_resolver =
ImplItemResolver::new(main_trait, &helper_trait_ident, &type_param_idents);

items
main_item_impl
.items
.iter_mut()
.for_each(|item| impl_item_resolver.visit_impl_item_mut(item));

let (impl_generics, _, where_clause) = generics.split_for_impl();
let for_ = if main_trait.is_some() {
quote!(for)
} else {
quote!()
};
Some(main_item_impl)
}

/// Generates main trait implementation with item values set to dummy values
fn gen_dummy_impl_from_inherent_impl(
inherent_impl: &ItemImpl,
self_ty: &SelfType,
) -> syn::ItemImpl {
let mut main_item_impl = inherent_impl.clone();

main_item_impl.attrs = Vec::new();
main_item_impl.self_ty = Box::new(self_ty.clone());
main_item_impl.items.iter_mut().for_each(|item| match item {
syn::ImplItem::Const(item) => {
item.expr = parse_quote! { DUMMY };
}
syn::ImplItem::Type(item) => {
item.ty = parse_quote! { DUMMY };
}
syn::ImplItem::Fn(item) => {
item.block = parse_quote! { DUMMY };
}
syn::ImplItem::Macro(_) => unimplemented!("Macro expansion not supported yet"),
syn::ImplItem::Verbatim(_) => unimplemented!("Verbatim not supported yet"),
_ => unimplemented!("Unknown item"),
});

main_item_impl
}

Some(syn::parse_quote! {
#(#attrs)*
#defaultness #unsafety impl #impl_generics #trait_ #for_ #self_ty #where_clause {
/// Generates main trait implementation with item values set to dummy values
fn gen_dummy_impl_from_trait_definition(
main_trait: &ItemTrait,
self_ty: &SelfType,
) -> syn::ItemImpl {
let ItemTrait {
unsafety,
generics,
ident,
items,
..
} = main_trait;

let items = items.iter().map(|item| {
let item: syn::ImplItem = match item {
syn::TraitItem::Const(item) => {
let syn::TraitItemConst {
generics,
ident,
ty,
..
} = item;

let (_, ty_generics, where_clause) = generics.split_for_impl();

parse_quote! {
const #ident #ty_generics: #ty = DUMMY #where_clause;
}
}
syn::TraitItem::Type(item) => {
let syn::TraitItemType {
generics, ident, ..
} = item;

let (_, ty_generics, where_clause) = generics.split_for_impl();

parse_quote! {
type #ident #ty_generics = DUMMY #where_clause;
}
}
syn::TraitItem::Fn(item) => {
let sig = &item.sig;

parse_quote! {
#sig { /* DUMMY */ }
}
}
syn::TraitItem::Macro(_) => unimplemented!("Macro expansion not supported yet"),
syn::TraitItem::Verbatim(_) => unimplemented!("Verbatim not supported yet"),
_ => unimplemented!("Unknown item"),
};

item
});

let (_, ty_generics, where_clause) = generics.split_for_impl();

parse_quote! {
#unsafety impl #ident #ty_generics for #self_ty #where_clause {
#(#items)*
}
})
}
}

fn gen_assoc_bounds<'a>(
Expand Down Expand Up @@ -162,7 +233,7 @@ impl VisitMut for GenericsResolver {
.type_params()
.map(|type_param| {
let type_param = &type_param.ident;
syn::parse_quote!(#type_param)
parse_quote!(#type_param)
})
.collect();

Expand All @@ -184,7 +255,7 @@ impl VisitMut for GenericsResolver {

fn visit_where_clause_mut(&mut self, node: &mut syn::WhereClause) {
let predicates = &self.where_clause_predicates;
node.predicates = syn::parse_quote! {#(#predicates),*};
node.predicates = parse_quote! {#(#predicates),*};
}
}

Expand All @@ -193,7 +264,7 @@ impl VisitMut for ImplItemResolver {
let self_as_helper_trait = &self.self_as_helper_trait;

let ident = &node.ident;
node.expr = syn::parse_quote!(
node.expr = parse_quote!(
#self_as_helper_trait::#ident
);
}
Expand All @@ -209,10 +280,10 @@ impl VisitMut for ImplItemResolver {
} = &node.sig;

let inputs = inputs.iter().map(|input| match input {
syn::FnArg::Receiver(_) => syn::parse_quote!(self),
syn::FnArg::Receiver(_) => parse_quote!(self),
syn::FnArg::Typed(arg) => arg.pat.clone(),
});
node.block = syn::parse_quote!({
node.block = parse_quote!({
#self_as_helper_trait::#ident(#(#inputs,)* #variadic)
});
}
Expand All @@ -221,7 +292,7 @@ impl VisitMut for ImplItemResolver {
let self_as_helper_trait = &self.self_as_helper_trait;

let ident = &node.ident;
node.ty = syn::parse_quote!(
node.ty = parse_quote!(
#self_as_helper_trait::#ident
);
}
Expand Down
38 changes: 19 additions & 19 deletions src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ fn compare_trait_items(trait_items: &[syn::TraitItem], second: &[syn::ImplItem])
syn::ImplItem::Const(item) => {
second_consts.insert(&item.ident, item);
}
syn::ImplItem::Fn(item) => {
second_fns.insert(&item.sig.ident, item);
}
syn::ImplItem::Type(item) => {
second_types.insert(&item.ident, item);
}
syn::ImplItem::Fn(item) => {
second_fns.insert(&item.sig.ident, item);
}
syn::ImplItem::Macro(_) => unimplemented!("Macro expansion not supported yet"),
syn::ImplItem::Verbatim(_) => unimplemented!("Verbatim not supported yet"),
_ => unimplemented!("Unknown item"),
Expand All @@ -75,15 +75,15 @@ fn compare_trait_items(trait_items: &[syn::TraitItem], second: &[syn::ImplItem])
abort!(trait_item, "Missing in one of the impls");
}
}
syn::TraitItem::Fn(trait_item) => {
if second_fns.remove(&trait_item.sig.ident).is_none()
&& trait_item.default.is_none()
syn::TraitItem::Type(trait_item) => {
if second_types.remove(&trait_item.ident).is_none() && trait_item.default.is_none()
{
abort!(trait_item, "Missing in one of the impls");
}
}
syn::TraitItem::Type(trait_item) => {
if second_types.remove(&trait_item.ident).is_none() && trait_item.default.is_none()
syn::TraitItem::Fn(trait_item) => {
if second_fns.remove(&trait_item.sig.ident).is_none()
&& trait_item.default.is_none()
{
abort!(trait_item, "Missing in one of the impls");
}
Expand All @@ -97,10 +97,10 @@ fn compare_trait_items(trait_items: &[syn::TraitItem], second: &[syn::ImplItem])
for (second_item, _) in second_consts {
abort!(second_item, "Not found in trait definition");
}
for (second_item, _) in second_fns {
for (second_item, _) in second_types {
abort!(second_item, "Not found in trait definition");
}
for (second_item, _) in second_types {
for (second_item, _) in second_fns {
abort!(second_item, "Not found in trait definition");
}
}
Expand All @@ -114,12 +114,12 @@ fn compare_inherent_items(first: &[syn::ImplItem], second: &[syn::ImplItem]) {
syn::ImplItem::Const(item) => {
second_consts.insert(&item.ident, item);
}
syn::ImplItem::Fn(item) => {
second_fns.insert(&item.sig.ident, item);
}
syn::ImplItem::Type(item) => {
second_types.insert(&item.ident, item);
}
syn::ImplItem::Fn(item) => {
second_fns.insert(&item.sig.ident, item);
}
syn::ImplItem::Macro(_) => unimplemented!("Macro expansion not supported yet"),
syn::ImplItem::Verbatim(_) => unimplemented!("Verbatim not supported yet"),
_ => unimplemented!("Unknown item"),
Expand All @@ -136,13 +136,13 @@ fn compare_inherent_items(first: &[syn::ImplItem], second: &[syn::ImplItem]) {
abort!(first_item, "Not found in one of the impls");
}
}
syn::ImplItem::Fn(first_item) => {
if second_fns.remove(&first_item.sig.ident).is_none() {
syn::ImplItem::Type(first_item) => {
if second_types.remove(&first_item.ident).is_none() {
abort!(first_item, "Not found in one of the impls");
}
}
syn::ImplItem::Type(first_item) => {
if second_types.remove(&first_item.ident).is_none() {
syn::ImplItem::Fn(first_item) => {
if second_fns.remove(&first_item.sig.ident).is_none() {
abort!(first_item, "Not found in one of the impls");
}
}
Expand All @@ -155,10 +155,10 @@ fn compare_inherent_items(first: &[syn::ImplItem], second: &[syn::ImplItem]) {
for (second_item, _) in second_consts {
abort!(second_item, "Not found in one of the impls");
}
for (second_item, _) in second_fns {
for (second_item, _) in second_types {
abort!(second_item, "Not found in one of the impls");
}
for (second_item, _) in second_types {
for (second_item, _) in second_fns {
abort!(second_item, "Not found in one of the impls");
}
}
52 changes: 52 additions & 0 deletions tests/concrete_assoc_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use disjoint_impls::disjoint_impls;

pub trait Dispatch {
type Group;
}

pub enum GroupA {}
impl Dispatch for String {
type Group = GroupA;
}
impl<T> Dispatch for Vec<T> {
type Group = GroupA;
}

pub enum GroupB {}
impl Dispatch for i32 {
type Group = GroupB;
}
impl Dispatch for u32 {
type Group = GroupB;
}

disjoint_impls! {
pub trait Kita {
type Item;

fn kita() -> Self::Item;
}

impl<T: Dispatch<Group = GroupA>> Kita for T {
type Item = u32;

fn kita() -> u32 {
0
}
}
impl<U: Dispatch<Group = GroupB>> Kita for U {
type Item = u16;

fn kita() -> u16 {
1
}
}
}

#[test]
fn main() {
assert_eq!(0, String::kita());
assert_eq!(0, Vec::<u32>::kita());
assert_eq!(1, u32::kita());
assert_eq!(1, i32::kita());
}

0 comments on commit e26bf26

Please sign in to comment.