synbounds

Crates.iosynbounds
lib.rssynbounds
version0.1.3
created_at2025-11-01 15:45:12.959061+00
updated_at2025-11-02 22:06:20.540899+00
descriptionHelper methods and macros for custom derives
homepage
repositoryhttps://github.com/HellButcher/synbounds.git
max_upload_size
id1912104
size78,994
Christoph H (HellButcher)

documentation

README

synbounds

Latest Version Documentation Build Status

synbounds is a Rust crate that provides utilities for writing proc-macro crates that need to manipulate or analyze Rust type bounds.

It leverages the syn crate to parse and work with Rust syntax trees, making it easier to handle complex type bounds in procedural macros.

Features

  • Track Generic Usage: Automatically determine which generic parameters (types, lifetimes, consts) are actually used in your code
  • Extract Minimal Bounds: Generate minimal where clauses containing only the predicates for used generics
  • Lifetime Substitution: Utilities for replacing lifetimes in syntax trees
  • Self Type Substitution: Replace Self with concrete types in method signatures and return types

Usage

Add this to your Cargo.toml:

[dependencies]
synbounds = "0.1"

Example: Tracking Used Generics

use syn::{parse_quote, DeriveInput};
use syn::visit::Visit;
use synbounds::BoundGenerics;

// Parse a struct definition
let input: DeriveInput = parse_quote! {
    struct MyStruct<'a, T, U> {
        field: &'a T,
    }
};

// Track which generics are used in the fields
let mut bounds = BoundGenerics::new(&input.generics);
if let syn::Data::Struct(data) = &input.data {
    bounds.visit_fields(&data.fields);
}

// Only T and 'a are used, U is not
let bound_generics = bounds.to_bound_generics();
// bound_generics is now: <'a, T>

Example: Filtering Where Clauses

use syn::{parse_quote, DeriveInput};
use syn::visit::Visit;
use synbounds::BoundGenerics;

let input: DeriveInput = parse_quote! {
    struct MyStruct<T, U>
    where
        T: Clone,
        U: Default,
    {
        field: T,
    }
};

let mut bounds = BoundGenerics::new(&input.generics);
if let syn::Data::Struct(data) = &input.data {
    bounds.visit_fields(&data.fields);
}

// Only get where predicates for used generics (T)
let where_predicates: Vec<_> = bounds.bound_where_predicates().collect();
// where_predicates contains only: T: Clone

Example: Analyzing Impl Blocks

use syn::{parse_quote, ItemImpl};
use syn::visit::Visit;
use synbounds::BoundGenerics;

let item: ItemImpl = parse_quote! {
    impl<T, U, V> MyStruct<T> {
        fn process(&self, value: U) -> String {
            unimplemented!()
        }
    }
};

// Track which generics are used
let mut bounds = BoundGenerics::new(&item.generics);
bounds.visit_type(&item.self_ty);
for impl_item in &item.items {
    if let syn::ImplItem::Fn(method) = impl_item {
        bounds.visit_signature(&method.sig);
    }
}

// T and U are used, V is not
let bound_generics = bounds.to_bound_generics();

Example: Lifetime Substitution

use syn::{parse_quote, Type};
use syn::visit_mut::VisitMut;
use synbounds::substitute_with_static_lifetime;

let mut ty: Type = parse_quote! { &'a String };
let mut visitor = substitute_with_static_lifetime::<()>();
visitor.visit_type_mut(&mut ty);
// ty is now: &'static String

Example: Substituting Self Types

use syn::{parse_quote, Type};
use syn::visit_mut::VisitMut;
use synbounds::SubstituteSelfType;

let concrete_type: Type = parse_quote! { MyStruct<T, U> };
let mut return_type: Type = parse_quote! { Result<Self, Error> };

let mut visitor = SubstituteSelfType::new(&concrete_type);
visitor.visit_type_mut(&mut return_type);
// return_type is now: Result<MyStruct<T, U>, Error>

This is especially useful when defining private wrapper objects for functions that use Self.

Features

  • proc-macro (default): Enable proc-macro support
  • full: Enable full syn feature for additional syntax support
  • substitute (default): Enable lifetime substitution utilities

Use Cases

This crate is particularly useful when:

  • Writing derive macros that need to determine which generic parameters are actually used
  • Generating trait implementations with minimal bounds
  • Analyzing AST nodes to extract relevant generic constraints
  • Creating procedural macros that manipulate lifetime parameters

Background

When writing a proc-macro for a specific library, I came across the problem, that I needed to know which generic bounds where actually used on a specific part of the AST (Functions in impl blocks to be precise).

While syn provides the necessary structures to parse and represent these bounds, it does not provide utilities to easily extract or manipulate them. So I first wrote some helper functions for my specific use case.

Later I came across the synstructure crate, which provided some similar functionality, but was more focused on deriving implementations for enums and structs. But this inspired me to generalize and refactor my helper functions into a standalone crate that could be reused in other proc-macro projects.

License

This project is licensed under either of

at your option

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Commit count: 0

cargo fmt