Repository: Enselic/cargo-public-api (rust)

Issue:
Generic type information missing for items in paths
Hi, I was trying to use cargo public-api diffs on skia-safe, which uses generic handle types for a lot of the exported types. Here is an minimum working example that explains the problem:

lib.rs:
```rust
pub struct Handle<T>(T);

pub type HU32 = Handle<u32>;

impl HU32 {
    pub fn get_u32() -> u32 {
        0
    }
}

```

results in

```rust
...
pub mod public_api_test
pub struct public_api_test::Handle<T>(_)
impl public_api_test::Handle<u32>
pub fn public_api_test::Handle::get_u32() -> u32
...
pub type public_api_test::HU32 = public_api_test::Handle<u32>
```

but I I think it the functions should be represented with their complete type signature:

```rust
...
pub fn public_api_test::Handle<u32>::get_u32() -> u32
```

Without that, a diff compares all functions of the generic type instead of only the concrete type.



Pull request description:
Change rendering of `impl` items to e.g. include generic args
Closes #429

I have skimmed through how this change affects the output in other cases, and it all look like improvements to me. I have a feeling the implementation can be simplified. But spare time is scarce. The important things are

* the problem is fixed
* the public API of any of our libraries is unaffected
* we have regression tests

The above makes it easy to freely iterate on the implementation over time.

I'll let this PR sit for a day or two to not rush it. But I think this is a full and complete fix.

Patch:
diff --git a/public-api/src/intermediate_public_item.rs b/public-api/src/intermediate_public_item.rs
index 69d640b..f48a286 100644
--- a/public-api/src/intermediate_public_item.rs
+++ b/public-api/src/intermediate_public_item.rs
@@ -1,6 +1,7 @@
 use rustdoc_types::Item;
 
 use crate::nameable_item::NameableItem;
+use crate::path_component::PathComponent;
 use crate::public_item::PublicItemPath;
 use crate::render::RenderingContext;
 use crate::tokens::Token;
@@ -11,21 +12,25 @@ use crate::tokens::Token;
 /// one [`crate::PublicItem`].
 #[derive(Clone, Debug)]
 pub struct IntermediatePublicItem<'c> {
-    path: Vec<NameableItem<'c>>,
+    path: Vec<PathComponent<'c>>,
 }
 
 impl<'c> IntermediatePublicItem<'c> {
-    pub fn new(path: Vec<NameableItem<'c>>) -> Self {
+    pub fn new(path: Vec<PathComponent<'c>>) -> Self {
         Self { path }
     }
 
     #[must_use]
     pub fn item(&self) -> &'c Item {
-        self.path().last().expect("path must not be empty").item
+        self.path()
+            .last()
+            .expect("path must not be empty")
+            .item
+            .item
     }
 
     #[must_use]
-    pub fn path(&self) -> &[NameableItem<'c>] {
+    pub fn path(&self) -> &[PathComponent<'c>] {
         &self.path
     }
 
@@ -34,13 +39,13 @@ impl<'c> IntermediatePublicItem<'c> {
     pub fn sortable_path(&self, context: &RenderingContext) -> PublicItemPath {
         self.path()
             .iter()
-            .map(|p| NameableItem::sortable_name(p, context))
+            .map(|p| NameableItem::sortable_name(&p.item, context))
             .collect()
     }
 
     #[must_use]
     pub fn path_contains_renamed_item(&self) -> bool {
-        self.path().iter().any(|m| m.overridden_name.is_some())
+        self.path().iter().any(|m| m.item.overridden_name.is_some())
     }
 
     pub fn render_token_stream(&self, context: &RenderingContext) -> Vec<Token> {
diff --git a/public-api/src/item_processor.rs b/public-api/src/item_processor.rs
index a6099ac..2b57766 100644
--- a/public-api/src/item_processor.rs
+++ b/public-api/src/item_processor.rs
@@ -1,10 +1,11 @@
 use super::nameable_item::NameableItem;
 use crate::{
     crate_wrapper::CrateWrapper, intermediate_public_item::IntermediatePublicItem,
-    public_item::PublicItem, render::RenderingContext, BuilderOptions as Options, PublicApi,
+    path_component::PathComponent, public_item::PublicItem, render::RenderingContext,
+    BuilderOptions as Options, PublicApi,
 };
 use rustdoc_types::{
-    Crate, Id, Impl, Import, Item, ItemEnum, Module, Struct, StructKind, VariantKind,
+    Crate, Id, Impl, Import, Item, ItemEnum, Module, Struct, StructKind, Type, VariantKind,
 };
 use std::{
     collections::{HashMap, VecDeque},
@@ -21,7 +22,7 @@ use std::{
 #[derive(Debug)]
 struct UnprocessedItem<'c> {
     /// The path to the item to process.
-    parent_path: Vec<NameableItem<'c>>,
+    parent_path: Vec<PathComponent<'c>>,
 
     /// The Id of the item to process.
     id: &'c Id,
@@ -69,7 +70,7 @@ impl<'c> ItemProcessor<'c> {
     /// want to insert the struct fields BEFORE everything else, so that these
     /// items remain grouped together. And the same applies for many kinds of
     /// groupings (enums, impls, etc).
-    fn add_to_work_queue(&mut self, parent_path: Vec<NameableItem<'c>>, id: &'c Id) {
+    fn add_to_work_queue(&mut self, parent_path: Vec<PathComponent<'c>>, id: &'c Id) {
         self.work_queue
             .push_front(UnprocessedItem { parent_path, id });
     }
@@ -176,7 +177,7 @@ impl<'c> ItemProcessor<'c> {
             return;
         }
 
-        self.process_item(unprocessed_item, item, None);
+        self.process_item_for_type(unprocessed_item, item, None, Some(&impl_.for_));
     }
 
     /// Make sure the item we are about to process is not already part of the
@@ -191,11 +192,12 @@ impl<'c> ItemProcessor<'c> {
         if unprocessed_item
             .parent_path
             .iter()
-            .any(|m| m.item.id == item.id)
+            .any(|m| m.item.item.id == item.id)
         {
             let recursion_breaker = unprocessed_item.finish(
                 item,
                 Some(format!("<<{}>>", item.name.as_deref().unwrap_or(""))),
+                None,
             );
             self.output.push(recursion_breaker);
         } else {
@@ -211,14 +213,40 @@ impl<'c> ItemProcessor<'c> {
         item: &'c Item,
         overridden_name: Option<String>,
     ) {
-        let finished_item = unprocessed_item.finish(item, overridden_name);
+        self.process_item_for_type(unprocessed_item, item, overridden_name, None);
+    }
+
+    /// Process an item. Setup jobs for its children and impls and and then put
+    /// it in the output.
+    fn process_item_for_type(
+        &mut self,
+        unprocessed_item: UnprocessedItem<'c>,
+        item: &'c Item,
+        overridden_name: Option<String>,
+        type_: Option<&'c Type>,
+    ) {
+        let finished_item = unprocessed_item.finish(item, overridden_name, type_);
+
         let children = children_for_item(item).into_iter().flatten();
         let impls = impls_for_item(item).into_iter().flatten();
 
-        for id in children.chain(impls) {
+        for id in children {
             self.add_to_work_queue(finished_item.path().into(), id);
         }
 
+        // As usual, impls are special. We want impl items to appear grouped
+        // with the trait or type it involves. But when _rendering_ we want to
+        // use the type that we implement for, so that e.g. generic arguments
+        // can be shown. So hide the "sorting path" of the impl. We'll instead
+        // render the path to the type the impl is for.
+        for id in impls {
+            let mut path = finished_item.path().to_vec();
+            for a in &mut path {
+                a.hide = true;
+            }
+            self.add_to_work_queue(path, id);
+        }
+
         self.output.push(finished_item);
     }
 
@@ -227,10 +255,10 @@ impl<'c> ItemProcessor<'c> {
     /// which case we need to break the recursion.
     fn get_item_if_not_in_path(
         &mut self,
-        parent_path: &[NameableItem<'c>],
+        parent_path: &[PathComponent<'c>],
         id: &'c Id,
     ) -> Option<&'c Item> {
-        if parent_path.iter().any(|m| m.item.id == *id) {
+        if parent_path.iter().any(|m| m.item.item.id == *id) {
             // The item is already in the path! Break import recursion...
             return None;
         }
@@ -260,15 +288,24 @@ impl<'c> ItemProcessor<'c> {
 
 impl<'c> UnprocessedItem<'c> {
     /// Turns an [`UnprocessedItem`] into a finished [`IntermediatePublicItem`].
-    fn finish(self, item: &'c Item, overridden_name: Option<String>) -> IntermediatePublicItem<'c> {
+    fn finish(
+        self,
+        item: &'c Item,
+        overridden_name: Option<String>,
+        type_: Option<&'c Type>,
+    ) -> IntermediatePublicItem<'c> {
         // Transfer path ownership to output item
         let mut path = self.parent_path;
 
         // Complete the path with the last item
-        path.push(NameableItem {
-            item,
-            overridden_name,
-            sorting_prefix: sorting_prefix(item),
+        path.push(PathComponent {
+            item: NameableItem {
+                item,
+                overridden_name,
+                sorting_prefix: sorting_prefix(item),
+            },
+            type_,
+            hide: false,
         });
 
         // Done
diff --git a/public-api/src/lib.rs b/public-api/src/lib.rs
index 7d35546..55b37df 100644
--- a/public-api/src/lib.rs
+++ b/public-api/src/lib.rs
@@ -35,6 +35,7 @@ mod error;
 mod intermediate_public_item;
 mod item_processor;
 mod nameable_item;
+mod path_component;
 mod public_item;
 mod render;
 pub mod tokens;
diff --git a/public-api/src/path_component.rs b/public-api/src/path_component.rs
new file mode 100644
index 0000000..42d7bbe
--- /dev/null
+++ b/public-api/src/path_component.rs
@@ -0,0 +1,23 @@
+use rustdoc_types::Type;
+
+use crate::nameable_item::NameableItem;
+
+/// A public item in a public API can only be referenced via a path. For example
+/// `mod_a::mod_b::StructC`. A `PathComponent` represents one component of such
+/// a path. A path component can either be a Rust item, or a Rust type. Normally
+/// it is an item. The typical case when it is a type is when there are generic
+/// arguments involved. For example, `Option<usize>` is a type. The
+/// corresponding item is `Option<T>` (no specific generic args has been
+/// specified for the generic parameter T).
+#[derive(Clone, Debug)]
+pub struct PathComponent<'c> {
+    /// The item for this path component.
+    pub item: NameableItem<'c>,
+
+    /// The type, if applicable. If we have a type, we'll want to use that
+    /// instead of `item`, since the type might have specific generic args.
+    pub type_: Option<&'c Type>,
+
+    /// If `true`, do not render this path component to users.
+    pub hide: bool,
+}
diff --git a/public-api/src/render.rs b/public-api/src/render.rs
index 15e5cea..49f6492 100644
--- a/public-api/src/render.rs
+++ b/public-api/src/render.rs
@@ -1,6 +1,7 @@
 #![allow(clippy::unused_self)]
 use crate::intermediate_public_item::IntermediatePublicItem;
 use crate::nameable_item::NameableItem;
+use crate::path_component::PathComponent;
 use crate::tokens::Token;
 use crate::BuilderOptions as Options;
 use std::ops::Deref;
@@ -215,7 +216,7 @@ impl<'c> RenderingContext<'c> {
         resolved_fields
     }
 
-    fn render_simple(&self, tags: &[&str], path: &[NameableItem]) -> Vec<Token> {
+    fn render_simple(&self, tags: &[&str], path: &[PathComponent]) -> Vec<Token> {
         let mut output = pub_();
         output.extend(
             tags.iter()
@@ -226,10 +227,23 @@ impl<'c> RenderingContext<'c> {
         output
     }
 
-    fn render_path(&self, path: &[NameableItem]) -> Vec<Token> {
+    fn render_path(&self, path: &[PathComponent]) -> Vec<Token> {
         let mut output = vec![];
-        for item in path {
-            output.extend(self.render_nameable_item(item));
+        for component in path {
+            if component.hide {
+                continue;
+            }
+
+            let (tokens, push_a_separator) = component.type_.map_or_else(
+                || self.render_nameable_item(&component.item),
+                |ty| self.render_type_and_separator(ty),
+            );
+
+            output.extend(tokens);
+
+            if push_a_separator {
+                output.push(Token::symbol("::"));
+            }
         }
         if !path.is_empty() {
             output.pop(); // Remove last "::" so "a::b::c::" becomes "a::b::c"
@@ -237,7 +251,8 @@ impl<'c> RenderingContext<'c> {
         output
     }
 
-    fn render_nameable_item(&self, item: &NameableItem) -> Vec<Token> {
+    fn render_nameable_item(&self, item: &NameableItem) -> (Vec<Token>, bool) {
+        let mut push_a_separator = false;
         let mut output = vec![];
         let token_fn = if matches!(item.item.inner, ItemEnum::Function(_)) {
             Token::function
@@ -258,14 +273,14 @@ impl<'c> RenderingContext<'c> {
             // There is always a sortable name, so we can push the name
             // unconditionally
             output.push(token_fn(item.sortable_name(self)));
-            output.push(Token::symbol("::"));
+            push_a_separator = true;
         } else if let Some(name) = item.name() {
             // If we are not debugging, some items (read: impls) do not have
             // a name, so only push a name if it exists
             output.push(token_fn(name.to_string()));
-            output.push(Token::symbol("::"));
+            push_a_separator = true;
         }
-        output
+        (output, push_a_separator)
     }
 
     #[allow(clippy::needless_pass_by_value)]
@@ -320,6 +335,10 @@ impl<'c> RenderingContext<'c> {
         self.render_option_type(&Some(ty))
     }
 
+    fn render_type_and_separator(&self, ty: &Type) -> (Vec<Token>, bool) {
+        (self.render_type(ty), true)
+    }
+
     #[allow(clippy::ref_option_ref, clippy::trivially_copy_pass_by_ref)] // Because of `render_sequence()` arg types
     fn render_option_type(&self, ty: &Option<&Type>) -> Vec<Token> {
         let Some(ty) = ty else { return vec![Token::symbol("_")] }; // The `_` in `EnumWithStrippedTupleVariants::DoubleFirstHidden(_, bool)`
@@ -349,7 +368,7 @@ impl<'c> RenderingContext<'c> {
         }
     }
 
-    fn render_trait(&self, trait_: &Trait, path: &[NameableItem]) -> Vec<Token> {
+    fn render_trait(&self, trait_: &Trait, path: &[PathComponent]) -> Vec<Token> {
         let mut output = pub_();
         if trait_.is_unsafe {
             output.extend(vec![Token::qualifier("unsafe"), ws!()]);
@@ -597,7 +616,7 @@ impl<'c> RenderingContext<'c> {
     pub(crate) fn render_impl(
         &self,
         impl_: &Impl,
-        path: &[NameableItem],
+        path: &[PathComponent],
         disregard_negativity: bool,
     ) -> Vec<Token> {
         let mut output = vec![];


Test patch:
diff --git a/public-api/tests/expected-output/comprehensive_api.txt b/public-api/tests/expected-output/comprehensive_api.txt
index fb47806..ffeaa84 100644
--- a/public-api/tests/expected-output/comprehensive_api.txt
+++ b/public-api/tests/expected-output/comprehensive_api.txt
@@ -114,7 +114,7 @@ pub struct comprehensive_api::higher_ranked_trait_bounds::Foo<'a>
 pub comprehensive_api::higher_ranked_trait_bounds::Foo::some_func: for<'c> fn(val: &'c i32) -> i32
 pub comprehensive_api::higher_ranked_trait_bounds::Foo::some_trait: &'a dyn for<'b> comprehensive_api::higher_ranked_trait_bounds::Trait<'b>
 impl<'a> comprehensive_api::higher_ranked_trait_bounds::Foo<'a>
-pub fn comprehensive_api::higher_ranked_trait_bounds::Foo::bar<T>() where T: comprehensive_api::higher_ranked_trait_bounds::Trait<'a>
+pub fn comprehensive_api::higher_ranked_trait_bounds::Foo<'a>::bar<T>() where T: comprehensive_api::higher_ranked_trait_bounds::Trait<'a>
 pub trait comprehensive_api::higher_ranked_trait_bounds::B<'x>
 impl<'a> comprehensive_api::higher_ranked_trait_bounds::B<'a> for dyn for<'b> comprehensive_api::higher_ranked_trait_bounds::Trait<'b>
 pub trait comprehensive_api::higher_ranked_trait_bounds::Trait<'x>
@@ -122,9 +122,14 @@ pub fn comprehensive_api::higher_ranked_trait_bounds::test1<T>() where for<'a> &
 pub fn comprehensive_api::higher_ranked_trait_bounds::test2<T>() where for<'a, 'b> &'a T: comprehensive_api::higher_ranked_trait_bounds::Trait<'b>
 pub fn comprehensive_api::higher_ranked_trait_bounds::test3<F>() where F: for<'a, 'b> core::ops::function::Fn(&'a u8, &'b u8)
 pub mod comprehensive_api::impls
+pub mod comprehensive_api::impls::issue_429
+pub struct comprehensive_api::impls::issue_429::Handle<T>(_)
+impl comprehensive_api::impls::issue_429::Handle<u32>
+pub fn comprehensive_api::impls::issue_429::Handle<u32>::get_u32() -> u32
+pub type comprehensive_api::impls::issue_429::HU32 = comprehensive_api::impls::issue_429::Handle<u32>
 pub struct comprehensive_api::impls::GatTestStruct1<'a, T>(_)
 impl<'a, T> comprehensive_api::traits::Simple for comprehensive_api::impls::GatTestStruct1<'a, T>
-pub fn comprehensive_api::impls::GatTestStruct1::act()
+pub fn comprehensive_api::impls::GatTestStruct1<'a, T>::act()
 pub struct comprehensive_api::impls::GatTestStruct2<T>(_)
 pub struct comprehensive_api::impls::TestItemGrouping
 impl comprehensive_api::traits::TraitReferencingOwnAssociatedType for comprehensive_api::impls::TestItemGrouping
@@ -137,7 +142,7 @@ pub fn comprehensive_api::impls::TestItemGrouping::bar() -> <Self as comprehensi
 pub trait comprehensive_api::impls::ForUnit
 pub fn comprehensive_api::impls::ForUnit::for_unit()
 impl comprehensive_api::impls::ForUnit for ()
-pub fn comprehensive_api::impls::ForUnit::for_unit()
+pub fn ()::for_unit()
 pub mod comprehensive_api::macros
 pub mod comprehensive_api::statics
 pub static comprehensive_api::statics::ANSWER: i8
@@ -175,11 +180,11 @@ pub struct comprehensive_api::structs::WithLifetimeAndGenericParam<'a, T>
 pub comprehensive_api::structs::WithLifetimeAndGenericParam::t: T
 pub comprehensive_api::structs::WithLifetimeAndGenericParam::unit_ref: &'a comprehensive_api::structs::Unit
 impl<'b, T> comprehensive_api::structs::WithLifetimeAndGenericParam<'b, T>
-pub fn comprehensive_api::structs::WithLifetimeAndGenericParam::new_any(unit_ref: &'b comprehensive_api::structs::Unit, t: T) -> Self
+pub fn comprehensive_api::structs::WithLifetimeAndGenericParam<'b, T>::new_any(unit_ref: &'b comprehensive_api::structs::Unit, t: T) -> Self
 impl<'b, T> comprehensive_api::structs::WithLifetimeAndGenericParam<'b, T>
-pub fn comprehensive_api::structs::WithLifetimeAndGenericParam::new_any_duplicate(unit_ref: &'b comprehensive_api::structs::Unit, t: T) -> Self
+pub fn comprehensive_api::structs::WithLifetimeAndGenericParam<'b, T>::new_any_duplicate(unit_ref: &'b comprehensive_api::structs::Unit, t: T) -> Self
 impl<'b> comprehensive_api::structs::WithLifetimeAndGenericParam<'b, alloc::string::String>
-pub fn comprehensive_api::structs::WithLifetimeAndGenericParam::new(unit_ref: &'b comprehensive_api::structs::Unit, t: alloc::string::String) -> Self
+pub fn comprehensive_api::structs::WithLifetimeAndGenericParam<'b, alloc::string::String>::new(unit_ref: &'b comprehensive_api::structs::Unit, t: alloc::string::String) -> Self
 pub struct comprehensive_api::structs::WithTraitBounds<T: core::fmt::Display + core::fmt::Debug>
 pub mod comprehensive_api::traits
 pub trait comprehensive_api::traits::AssociatedConst
@@ -195,32 +200,32 @@ pub type comprehensive_api::traits::GenericAssociatedTypes::WhereSelfSized where
 pub type comprehensive_api::traits::GenericAssociatedTypes::WhereSimple<T> where T: comprehensive_api::traits::Simple
 pub type comprehensive_api::traits::GenericAssociatedTypes::WithLifetime<'a>
 impl comprehensive_api::traits::GenericAssociatedTypes for comprehensive_api::structs::Unit
-pub type comprehensive_api::traits::GenericAssociatedTypes::SimpleBound = comprehensive_api::impls::GatTestStruct1<'static, usize>
-pub type comprehensive_api::traits::GenericAssociatedTypes::WhereSelfSized = comprehensive_api::structs::Unit
-pub type comprehensive_api::traits::GenericAssociatedTypes::WhereSimple<T: comprehensive_api::traits::Simple> = comprehensive_api::impls::GatTestStruct2<T>
-pub type comprehensive_api::traits::GenericAssociatedTypes::WithLifetime<'a> = comprehensive_api::impls::GatTestStruct1<'a, bool>
+pub type comprehensive_api::structs::Unit::SimpleBound = comprehensive_api::impls::GatTestStruct1<'static, usize>
+pub type comprehensive_api::structs::Unit::WhereSelfSized = comprehensive_api::structs::Unit
+pub type comprehensive_api::structs::Unit::WhereSimple<T: comprehensive_api::traits::Simple> = comprehensive_api::impls::GatTestStruct2<T>
+pub type comprehensive_api::structs::Unit::WithLifetime<'a> = comprehensive_api::impls::GatTestStruct1<'a, bool>
 pub trait comprehensive_api::traits::Simple
 pub fn comprehensive_api::traits::Simple::act()
 impl comprehensive_api::traits::Simple for comprehensive_api::structs::Unit
-pub fn comprehensive_api::traits::Simple::act()
+pub fn comprehensive_api::structs::Unit::act()
 impl<'a, T> comprehensive_api::traits::Simple for comprehensive_api::impls::GatTestStruct1<'a, T>
-pub fn comprehensive_api::traits::Simple::act()
+pub fn comprehensive_api::impls::GatTestStruct1<'a, T>::act()
 pub trait comprehensive_api::traits::TraitReferencingOwnAssociatedType
 pub type comprehensive_api::traits::TraitReferencingOwnAssociatedType::OwnAssociatedType
 pub fn comprehensive_api::traits::TraitReferencingOwnAssociatedType::own_associated_type_output(&self) -> Self::OwnAssociatedType
 pub fn comprehensive_api::traits::TraitReferencingOwnAssociatedType::own_associated_type_output_explicit_as(&self) -> <Self as comprehensive_api::traits::TraitReferencingOwnAssociatedType>::OwnAssociatedType
 impl comprehensive_api::traits::TraitReferencingOwnAssociatedType for comprehensive_api::impls::TestItemGrouping
-pub type comprehensive_api::traits::TraitReferencingOwnAssociatedType::OwnAssociatedType = bool
-pub fn comprehensive_api::traits::TraitReferencingOwnAssociatedType::own_associated_type_output(&self) -> Self::OwnAssociatedType
-pub fn comprehensive_api::traits::TraitReferencingOwnAssociatedType::own_associated_type_output_explicit_as(&self) -> <Self as comprehensive_api::traits::TraitReferencingOwnAssociatedType>::OwnAssociatedType
+pub type comprehensive_api::impls::TestItemGrouping::OwnAssociatedType = bool
+pub fn comprehensive_api::impls::TestItemGrouping::own_associated_type_output(&self) -> Self::OwnAssociatedType
+pub fn comprehensive_api::impls::TestItemGrouping::own_associated_type_output_explicit_as(&self) -> <Self as comprehensive_api::traits::TraitReferencingOwnAssociatedType>::OwnAssociatedType
 pub trait comprehensive_api::traits::TraitWithBounds: private_mod::PubTraitInPrivateMod + comprehensive_api::traits::Simple + core::marker::Send
 pub trait comprehensive_api::traits::TraitWithBoundsAndGenerics<U>: comprehensive_api::traits::Simple
 pub trait comprehensive_api::traits::TraitWithGenerics<T, U>
 pub type comprehensive_api::traits::TraitWithGenerics::Foo
 pub fn comprehensive_api::traits::TraitWithGenerics::bar() -> <Self as comprehensive_api::traits::TraitWithGenerics<T, U>>::Foo
 impl<T, U> comprehensive_api::traits::TraitWithGenerics<T, U> for comprehensive_api::impls::TestItemGrouping
-pub type comprehensive_api::traits::TraitWithGenerics::Foo = u8
-pub fn comprehensive_api::traits::TraitWithGenerics::bar() -> <Self as comprehensive_api::traits::TraitWithGenerics<T, U>>::Foo
+pub type comprehensive_api::impls::TestItemGrouping::Foo = u8
+pub fn comprehensive_api::impls::TestItemGrouping::bar() -> <Self as comprehensive_api::traits::TraitWithGenerics<T, U>>::Foo
 pub unsafe trait comprehensive_api::traits::UnsafeTrait
 pub mod comprehensive_api::typedefs
 pub type comprehensive_api::typedefs::RedefinedResult<T, E> = core::result::Result<T, E>
@@ -233,25 +238,25 @@ pub macro comprehensive_api::simple_macro!
 pub struct comprehensive_api::Plain
 pub comprehensive_api::Plain::x: usize
 impl comprehensive_api::structs::Plain
-pub fn comprehensive_api::Plain::f()
-pub fn comprehensive_api::Plain::new() -> comprehensive_api::structs::Plain
-pub fn comprehensive_api::Plain::s1(self)
-pub fn comprehensive_api::Plain::s2(&self)
-pub fn comprehensive_api::Plain::s3(&mut self)
+pub fn comprehensive_api::structs::Plain::f()
+pub fn comprehensive_api::structs::Plain::new() -> comprehensive_api::structs::Plain
+pub fn comprehensive_api::structs::Plain::s1(self)
+pub fn comprehensive_api::structs::Plain::s2(&self)
+pub fn comprehensive_api::structs::Plain::s3(&mut self)
 impl<'a> comprehensive_api::structs::Plain
-pub fn comprehensive_api::Plain::s4(&'a self)
+pub fn comprehensive_api::structs::Plain::s4(&'a self)
 impl<'a> comprehensive_api::structs::Plain
-pub fn comprehensive_api::Plain::s5(&'a self)
+pub fn comprehensive_api::structs::Plain::s5(&'a self)
 pub struct comprehensive_api::RenamedPlain
 pub comprehensive_api::RenamedPlain::x: usize
 impl comprehensive_api::structs::Plain
-pub fn comprehensive_api::RenamedPlain::f()
-pub fn comprehensive_api::RenamedPlain::new() -> comprehensive_api::structs::Plain
-pub fn comprehensive_api::RenamedPlain::s1(self)
-pub fn comprehensive_api::RenamedPlain::s2(&self)
-pub fn comprehensive_api::RenamedPlain::s3(&mut self)
+pub fn comprehensive_api::structs::Plain::f()
+pub fn comprehensive_api::structs::Plain::new() -> comprehensive_api::structs::Plain
+pub fn comprehensive_api::structs::Plain::s1(self)
+pub fn comprehensive_api::structs::Plain::s2(&self)
+pub fn comprehensive_api::structs::Plain::s3(&mut self)
 impl<'a> comprehensive_api::structs::Plain
-pub fn comprehensive_api::RenamedPlain::s4(&'a self)
+pub fn comprehensive_api::structs::Plain::s4(&'a self)
 impl<'a> comprehensive_api::structs::Plain
-pub fn comprehensive_api::RenamedPlain::s5(&'a self)
+pub fn comprehensive_api::structs::Plain::s5(&'a self)
 pub struct comprehensive_api::StructInPrivateMod
diff --git a/test-apis/comprehensive_api/src/impls.rs b/test-apis/comprehensive_api/src/impls.rs
index 327273b..06d58e0 100644
--- a/test-apis/comprehensive_api/src/impls.rs
+++ b/test-apis/comprehensive_api/src/impls.rs
@@ -100,3 +100,16 @@ impl GenericAssociatedTypes for Unit {
 
     type WithLifetime<'a> = GatTestStruct1<'a, bool>;
 }
+
+/// Regression test for <https://github.com/Enselic/cargo-public-api/issues/429>
+pub mod issue_429 {
+    pub struct Handle<T>(T);
+
+    pub type HU32 = Handle<u32>;
+
+    impl HU32 {
+        pub fn get_u32() -> u32 {
+            0
+        }
+    }
+}