Skip to content

Commit

Permalink
Handle pub use Trait as _ cases correctly: the item is unnameable. (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
obi1kenobi authored Sep 26, 2023
1 parent 9a1c6ea commit 3724951
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 2 deletions.
77 changes: 76 additions & 1 deletion src/indexed_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,10 @@ mod tests {
"duplicates found: {actual_items:?}"
);

assert_eq!(expected_importable_paths, &deduplicated_actual_items);
assert_eq!(
expected_importable_paths, &deduplicated_actual_items,
"mismatch for item name {expected_item_name}",
);
}
}

Expand Down Expand Up @@ -1789,5 +1792,77 @@ expected exactly one importable path for `Foo` items in this crate but got: {act

assert_exported_items_match(test_crate, &expected_items);
}

#[test]
fn reexport_as_underscore() {
let test_crate = "reexport_as_underscore";
let expected_items = btreemap! {
"Struct" => btreeset![
"reexport_as_underscore::Struct",
],
"Trait" => btreeset![],
"hidden" => btreeset![],
"UnderscoreImported" => btreeset![],
};

assert_exported_items_match(test_crate, &expected_items);
}

#[test]
fn nested_reexport_as_underscore() {
let test_crate = "nested_reexport_as_underscore";
let expected_items = btreemap! {
"Trait" => btreeset![], // no importable paths!
};

assert_exported_items_match(test_crate, &expected_items);
}

#[test]
fn overlapping_reexport_as_underscore() {
let test_crate = "overlapping_reexport_as_underscore";

let rustdoc = load_pregenerated_rustdoc(test_crate);
let indexed_crate = IndexedCrate::new(&rustdoc);

let item_id_candidates = rustdoc
.index
.iter()
.filter_map(|(id, item)| (item.name.as_deref() == Some("Example")).then_some(id))
.collect_vec();
if item_id_candidates.len() != 2 {
panic!(
"Expected to find exactly 2 items with name \
Example, but found these matching IDs: {item_id_candidates:?}"
);
}

for item_id in item_id_candidates {
let importable_paths: Vec<_> = indexed_crate
.publicly_importable_names(item_id)
.into_iter()
.map(|components| components.into_iter().join("::"))
.collect();

match &rustdoc.index[item_id].inner {
ItemEnum::Struct(..) => {
assert_eq!(
vec!["overlapping_reexport_as_underscore::Example"],
importable_paths,
);
}
ItemEnum::Trait(..) => {
assert!(
importable_paths.is_empty(),
"expected no importable item names but found {importable_paths:?}"
);
}
_ => unreachable!(
"unexpected item for ID {item_id:?}: {:?}",
rustdoc.index[item_id]
),
}
}
}
}
}
17 changes: 16 additions & 1 deletion src/visibility_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ impl<'a> VisibilityTracker<'a> {

let (push_name, popped_name) = match &item.inner {
rustdoc_types::ItemEnum::Import(import_item) => {
if import_item.glob {
if import_item.name == "_" {
// Items re-exported as `_` are not nameable. They cannot be directly imported.
// They can be used via a glob import, but we are not interested in that here.
return;
} else if import_item.glob {
// Glob imports refer to the *contents* of the named item, not the item itself.
// Rust doesn't allow glob imports to rename items, so there's no name to add.
(None, None)
Expand Down Expand Up @@ -328,6 +332,17 @@ fn resolve_crate_names(crate_: &Crate) -> NameResolution<'_> {
.or_default()
.insert(inner_id);
} else if let Some(target) = imp.id.as_ref().and_then(|id| crate_.index.get(id)) {
if imp.name == "_" {
// `_` is a special name which causes the imported item to be available
// but unnameable. `pub use Trait as _` makes sense when constructing
// modules intended to be used as a prelude, since glob imports will
// include the (unnameable) trait and make its methods available for use.
//
// For importable path purposes, items re-exported as `_` do not exist
// since they cannot be directly imported due to lack of a usable name.
continue;
}

for name in get_names_for_item(crate_, target) {
// Handle renaming imports like `use some::foo as bar;`
let name = name.rename(&imp.name);
Expand Down
9 changes: 9 additions & 0 deletions test_crates/nested_reexport_as_underscore/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
publish = false
name = "nested_reexport_as_underscore"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
26 changes: 26 additions & 0 deletions test_crates/nested_reexport_as_underscore/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//! This crate does not re-export any *nameable* items.
//!
//! However, glob imports of this file (in the style of a prelude)
//! make `Trait::method()` visible, making `().method()` valid Rust.
//!
//! Docs: <https://doc.rust-lang.org/reference/items/use-declarations.html#underscore-imports>
mod inner {
pub trait Trait {
fn method(&self) {}
}

impl Trait for () {}
}

mod second {
pub use super::inner::Trait as _;
}

pub use second::*;

/// Verify that the trait is indeed visible.
#[allow(dead_code)]
fn proof() {
().method();
}
9 changes: 9 additions & 0 deletions test_crates/overlapping_reexport_as_underscore/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
publish = false
name = "overlapping_reexport_as_underscore"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
23 changes: 23 additions & 0 deletions test_crates/overlapping_reexport_as_underscore/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! This crate re-exports only the struct `Example`.
//!
//! Even though `second` imports two items named `Example`, one of them is renamed to `_`
//! making it unnameable.
//!
//! Docs: <https://doc.rust-lang.org/reference/items/use-declarations.html#underscore-imports>
mod inner {
pub trait Example {
fn method(&self) {}
}
}

mod inner2 {
pub struct Example {}
}

mod second {
pub use super::inner::Example as _;
pub use super::inner2::Example;
}

pub use second::*;
9 changes: 9 additions & 0 deletions test_crates/reexport_as_underscore/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
publish = false
name = "reexport_as_underscore"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
32 changes: 32 additions & 0 deletions test_crates/reexport_as_underscore/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! This crate re-exports only the name `Struct`.
//!
//! However, glob imports of this file (in the style of a prelude)
//! also make `Trait::method()` visible as well, making `().method()` valid Rust.
//!
//! The `_` re-export of the module `hidden` is not nameable at all, and here has no effect.
//! `hidden::UnderscoreImported` is not nameable outside this crate.
//!
//! Docs: <https://doc.rust-lang.org/reference/items/use-declarations.html#underscore-imports>
mod inner {
pub trait Trait {
fn method(&self) {}
}

impl Trait for () {}

pub struct Struct {}
}

mod nested {
pub mod hidden {
pub struct UnderscoreImported;
}
}

pub use inner::{
Struct,
Trait as _,
};

pub use nested::hidden as _;

0 comments on commit 3724951

Please sign in to comment.